Completed
Push — master ( ca14f0...c2f74e )
by Song
02:26
created

Form::setFieldValue()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
nc 2
nop 1
dl 0
loc 24
rs 9.536
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 = '', $callback)
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($id)) 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 $this->responseValidationError($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
     * @param MessageBag $message
413
     *
414
     * @return $this|\Illuminate\Http\JsonResponse
415
     */
416
    protected function responseValidationError(MessageBag $message)
417
    {
418 View Code Duplication
        if (\request()->ajax()) {
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...
419
            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...
420
                'status'     => false,
421
                'validation' => $message,
422
                'message'    => $message->first(),
423
            ]);
424
        }
425
426
        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...
427
    }
428
429
    /**
430
     * Get ajax response.
431
     *
432
     * @param string $message
433
     *
434
     * @return bool|\Illuminate\Http\JsonResponse
435
     */
436
    protected function ajaxResponse($message)
437
    {
438
        $request = Request::capture();
439
440
        // ajax but not pjax
441 View Code Duplication
        if ($request->ajax() && !$request->pjax()) {
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...
442
            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...
443
                'status'  => true,
444
                'message' => $message,
445
            ]);
446
        }
447
448
        return false;
449
    }
450
451
    /**
452
     * Prepare input data for insert or update.
453
     *
454
     * @param array $data
455
     *
456
     * @return mixed
457
     */
458
    protected function prepare($data = [])
459
    {
460
        if (($response = $this->callSubmitted()) instanceof Response) {
461
            return $response;
462
        }
463
464
        $this->inputs = array_merge($this->removeIgnoredFields($data), $this->inputs);
465
466
        if (($response = $this->callSaving()) instanceof Response) {
467
            return $response;
468
        }
469
470
        $this->relations = $this->getRelationInputs($this->inputs);
471
472
        $this->updates = Arr::except($this->inputs, array_keys($this->relations));
473
    }
474
475
    /**
476
     * Remove ignored fields from input.
477
     *
478
     * @param array $input
479
     *
480
     * @return array
481
     */
482
    protected function removeIgnoredFields($input)
483
    {
484
        Arr::forget($input, $this->ignored);
485
486
        return $input;
487
    }
488
489
    /**
490
     * Get inputs for relations.
491
     *
492
     * @param array $inputs
493
     *
494
     * @return array
495
     */
496
    protected function getRelationInputs($inputs = [])
497
    {
498
        $relations = [];
499
500
        foreach ($inputs as $column => $value) {
501
            if (!method_exists($this->model, $column)) {
502
                continue;
503
            }
504
505
            $relation = call_user_func([$this->model, $column]);
506
507
            if ($relation instanceof Relations\Relation) {
508
                $relations[$column] = $value;
509
            }
510
        }
511
512
        return $relations;
513
    }
514
515
    /**
516
     * Handle update.
517
     *
518
     * @param int  $id
519
     * @param null $data
520
     *
521
     * @return bool|\Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\JsonResponse|\Illuminate\Http\RedirectResponse|\Illuminate\Http\Response|mixed|null|Response
522
     */
523
    public function update($id, $data = null)
524
    {
525
        $data = ($data) ?: request()->all();
526
527
        $isEditable = $this->isEditable($data);
528
529
        if (($data = $this->handleColumnUpdates($id, $data)) instanceof Response) {
530
            return $data;
531
        }
532
533
        /* @var Model $this->model */
534
        $builder = $this->model();
535
536
        if ($this->isSoftDeletes) {
537
            $builder = $builder->withTrashed();
538
        }
539
540
        $this->model = $builder->with($this->getRelations())->findOrFail($id);
541
542
        $this->setFieldOriginalValue();
543
544
        // Handle validation errors.
545
        if ($validationMessages = $this->validationMessages($data)) {
0 ignored issues
show
Bug introduced by
It seems like $data defined by $this->handleColumnUpdates($id, $data) on line 529 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...
546
            if (!$isEditable) {
547
                return back()->withInput()->withErrors($validationMessages);
548
            }
549
550
            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...
551
        }
552
553
        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 529 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...
554
            return $response;
555
        }
556
557 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...
558
            $updates = $this->prepareUpdate($this->updates);
559
560
            foreach ($updates as $column => $value) {
561
                /* @var Model $this->model */
562
                $this->model->setAttribute($column, $value);
563
            }
564
565
            $this->model->save();
566
567
            $this->updateRelation($this->relations);
568
        });
569
570
        if (($result = $this->callSaved()) instanceof Response) {
571
            return $result;
572
        }
573
574
        if ($response = $this->ajaxResponse(trans('admin.update_succeeded'))) {
575
            return $response;
576
        }
577
578
        return $this->redirectAfterUpdate($id);
579
    }
580
581
    /**
582
     * Get RedirectResponse after store.
583
     *
584
     * @return \Illuminate\Http\RedirectResponse
585
     */
586
    protected function redirectAfterStore()
587
    {
588
        $resourcesPath = $this->resource(0);
589
590
        $key = $this->model->getKey();
591
592
        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 592 which is incompatible with the return type documented by Encore\Admin\Form::redirectAfterStore of type Illuminate\Http\RedirectResponse.
Loading history...
593
    }
594
595
    /**
596
     * Get RedirectResponse after update.
597
     *
598
     * @param mixed $key
599
     *
600
     * @return \Illuminate\Http\RedirectResponse
601
     */
602
    protected function redirectAfterUpdate($key)
603
    {
604
        $resourcesPath = $this->resource(-1);
605
606
        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 606 which is incompatible with the return type documented by Encore\Admin\Form::redirectAfterUpdate of type Illuminate\Http\RedirectResponse.
Loading history...
607
    }
608
609
    /**
610
     * Get RedirectResponse after data saving.
611
     *
612
     * @param string $resourcesPath
613
     * @param string $key
614
     *
615
     * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
616
     */
617
    protected function redirectAfterSaving($resourcesPath, $key)
618
    {
619
        if (request('after-save') == 1) {
620
            // continue editing
621
            $url = rtrim($resourcesPath, '/')."/{$key}/edit";
622
        } elseif (request('after-save') == 2) {
623
            // continue creating
624
            $url = rtrim($resourcesPath, '/').'/create';
625
        } elseif (request('after-save') == 3) {
626
            // view resource
627
            $url = rtrim($resourcesPath, '/')."/{$key}";
628
        } else {
629
            $url = request(Builder::PREVIOUS_URL_KEY) ?: $resourcesPath;
630
        }
631
632
        admin_toastr(trans('admin.save_succeeded'));
633
634
        return redirect($url);
635
    }
636
637
    /**
638
     * Check if request is from editable.
639
     *
640
     * @param array $input
641
     *
642
     * @return bool
643
     */
644
    protected function isEditable(array $input = [])
645
    {
646
        return array_key_exists('_editable', $input);
647
    }
648
649
    /**
650
     * Handle updates for single column.
651
     *
652
     * @param int   $id
653
     * @param array $data
654
     *
655
     * @return array|\Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\Response|Response
656
     */
657
    protected function handleColumnUpdates($id, $data)
658
    {
659
        $data = $this->handleEditable($data);
660
661
        $data = $this->handleFileDelete($data);
662
663
        $data = $this->handleFileSort($data);
664
665
        if ($this->handleOrderable($id, $data)) {
666
            return response([
667
                'status'  => true,
668
                'message' => trans('admin.update_succeeded'),
669
            ]);
670
        }
671
672
        return $data;
673
    }
674
675
    /**
676
     * Handle editable update.
677
     *
678
     * @param array $input
679
     *
680
     * @return array
681
     */
682
    protected function handleEditable(array $input = [])
683
    {
684
        if (array_key_exists('_editable', $input)) {
685
            $name = $input['name'];
686
            $value = $input['value'];
687
688
            Arr::forget($input, ['pk', 'value', 'name']);
689
            Arr::set($input, $name, $value);
690
        }
691
692
        return $input;
693
    }
694
695
    /**
696
     * @param array $input
697
     *
698
     * @return array
699
     */
700
    protected function handleFileDelete(array $input = [])
701
    {
702
        if (array_key_exists(Field::FILE_DELETE_FLAG, $input)) {
703
            $input[Field::FILE_DELETE_FLAG] = $input['key'];
704
            unset($input['key']);
705
        }
706
707
        request()->replace($input);
708
709
        return $input;
710
    }
711
712
    /**
713
     * @param array $input
714
     *
715
     * @return array
716
     */
717
    protected function handleFileSort(array $input = [])
718
    {
719
        if (!array_key_exists(Field::FILE_SORT_FLAG, $input)) {
720
            return $input;
721
        }
722
723
        $sorts = array_filter($input[Field::FILE_SORT_FLAG]);
724
725
        if (empty($sorts)) {
726
            return $input;
727
        }
728
729
        foreach ($sorts as $column => $order) {
730
            $input[$column] = $order;
731
        }
732
733
        request()->replace($input);
734
735
        return $input;
736
    }
737
738
    /**
739
     * Handle orderable update.
740
     *
741
     * @param int   $id
742
     * @param array $input
743
     *
744
     * @return bool
745
     */
746
    protected function handleOrderable($id, array $input = [])
747
    {
748
        if (array_key_exists('_orderable', $input)) {
749
            $model = $this->model->find($id);
750
751
            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...
752
                $input['_orderable'] == 1 ? $model->moveOrderUp() : $model->moveOrderDown();
753
754
                return true;
755
            }
756
        }
757
758
        return false;
759
    }
760
761
    /**
762
     * Update relation data.
763
     *
764
     * @param array $relationsData
765
     *
766
     * @return void
767
     */
768
    protected function updateRelation($relationsData)
769
    {
770
        foreach ($relationsData as $name => $values) {
771
            if (!method_exists($this->model, $name)) {
772
                continue;
773
            }
774
775
            $relation = $this->model->$name();
776
777
            $oneToOneRelation = $relation instanceof Relations\HasOne
778
                || $relation instanceof Relations\MorphOne
779
                || $relation instanceof Relations\BelongsTo;
780
781
            $prepared = $this->prepareUpdate([$name => $values], $oneToOneRelation);
782
783
            if (empty($prepared)) {
784
                continue;
785
            }
786
787
            switch (true) {
788
                case $relation instanceof Relations\BelongsToMany:
789
                case $relation instanceof Relations\MorphToMany:
790
                    if (isset($prepared[$name])) {
791
                        $relation->sync($prepared[$name]);
792
                    }
793
                    break;
794
                case $relation instanceof Relations\HasOne:
795
796
                    $related = $this->model->$name;
797
798
                    // if related is empty
799
                    if (is_null($related)) {
800
                        $related = $relation->getRelated();
801
                        $qualifiedParentKeyName = $relation->getQualifiedParentKeyName();
802
                        $localKey = Arr::last(explode('.', $qualifiedParentKeyName));
803
                        $related->{$relation->getForeignKeyName()} = $this->model->{$localKey};
804
                    }
805
806
                    foreach ($prepared[$name] as $column => $value) {
807
                        $related->setAttribute($column, $value);
808
                    }
809
810
                    $related->save();
811
                    break;
812
                case $relation instanceof Relations\BelongsTo:
813
                case $relation instanceof Relations\MorphTo:
814
815
                    $parent = $this->model->$name;
816
817
                    // if related is empty
818
                    if (is_null($parent)) {
819
                        $parent = $relation->getRelated();
820
                    }
821
822
                    foreach ($prepared[$name] as $column => $value) {
823
                        $parent->setAttribute($column, $value);
824
                    }
825
826
                    $parent->save();
827
828
                    // When in creating, associate two models
829
                    $foreignKeyMethod = (app()->version() < '5.8.0') ? 'getForeignKey' : 'getForeignKeyName';
830
                    if (!$this->model->{$relation->{$foreignKeyMethod}()}) {
831
                        $this->model->{$relation->{$foreignKeyMethod}()} = $parent->getKey();
832
833
                        $this->model->save();
834
                    }
835
836
                    break;
837
                case $relation instanceof Relations\MorphOne:
838
                    $related = $this->model->$name;
839
                    if (is_null($related)) {
840
                        $related = $relation->make();
841
                    }
842
                    foreach ($prepared[$name] as $column => $value) {
843
                        $related->setAttribute($column, $value);
844
                    }
845
                    $related->save();
846
                    break;
847
                case $relation instanceof Relations\HasMany:
848
                case $relation instanceof Relations\MorphMany:
849
850
                    foreach ($prepared[$name] as $related) {
851
                        /** @var Relations\Relation $relation */
852
                        $relation = $this->model()->$name();
853
854
                        $keyName = $relation->getRelated()->getKeyName();
855
856
                        $instance = $relation->findOrNew(Arr::get($related, $keyName));
857
858
                        if ($related[static::REMOVE_FLAG_NAME] == 1) {
859
                            $instance->delete();
860
861
                            continue;
862
                        }
863
864
                        Arr::forget($related, static::REMOVE_FLAG_NAME);
865
866
                        $instance->fill($related);
867
868
                        $instance->save();
869
                    }
870
871
                    break;
872
            }
873
        }
874
    }
875
876
    /**
877
     * Prepare input data for update.
878
     *
879
     * @param array $updates
880
     * @param bool  $oneToOneRelation If column is one-to-one relation.
881
     *
882
     * @return array
883
     */
884
    protected function prepareUpdate(array $updates, $oneToOneRelation = false)
885
    {
886
        $prepared = [];
887
888
        /** @var Field $field */
889
        foreach ($this->builder->fields() as $field) {
890
            $columns = $field->column();
891
892
            // If column not in input array data, then continue.
893
            if (!Arr::has($updates, $columns)) {
894
                continue;
895
            }
896
897
            if ($this->isInvalidColumn($columns, $oneToOneRelation || $field->isJsonType)) {
898
                continue;
899
            }
900
901
            $value = $this->getDataByColumn($updates, $columns);
902
903
            $value = $field->prepare($value);
904
905 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...
906
                foreach ($columns as $name => $column) {
907
                    Arr::set($prepared, $column, $value[$name]);
908
                }
909
            } elseif (is_string($columns)) {
910
                Arr::set($prepared, $columns, $value);
911
            }
912
        }
913
914
        return $prepared;
915
    }
916
917
    /**
918
     * @param string|array $columns
919
     * @param bool         $containsDot
920
     *
921
     * @return bool
922
     */
923
    protected function isInvalidColumn($columns, $containsDot = false)
924
    {
925
        foreach ((array) $columns as $column) {
926
            if ((!$containsDot && Str::contains($column, '.')) ||
927
                ($containsDot && !Str::contains($column, '.'))) {
928
                return true;
929
            }
930
        }
931
932
        return false;
933
    }
934
935
    /**
936
     * Prepare input data for insert.
937
     *
938
     * @param $inserts
939
     *
940
     * @return array
941
     */
942
    protected function prepareInsert($inserts)
943
    {
944
        if ($this->isHasOneRelation($inserts)) {
945
            $inserts = Arr::dot($inserts);
946
        }
947
948
        foreach ($inserts as $column => $value) {
949
            if (is_null($field = $this->getFieldByColumn($column))) {
950
                unset($inserts[$column]);
951
                continue;
952
            }
953
954
            $inserts[$column] = $field->prepare($value);
955
        }
956
957
        $prepared = [];
958
959
        foreach ($inserts as $key => $value) {
960
            Arr::set($prepared, $key, $value);
961
        }
962
963
        return $prepared;
964
    }
965
966
    /**
967
     * Is input data is has-one relation.
968
     *
969
     * @param array $inserts
970
     *
971
     * @return bool
972
     */
973
    protected function isHasOneRelation($inserts)
974
    {
975
        $first = current($inserts);
976
977
        if (!is_array($first)) {
978
            return false;
979
        }
980
981
        if (is_array(current($first))) {
982
            return false;
983
        }
984
985
        return Arr::isAssoc($first);
986
    }
987
988
    /**
989
     * Ignore fields to save.
990
     *
991
     * @param string|array $fields
992
     *
993
     * @return $this
994
     */
995
    public function ignore($fields)
996
    {
997
        $this->ignored = array_merge($this->ignored, (array) $fields);
998
999
        return $this;
1000
    }
1001
1002
    /**
1003
     * @param array        $data
1004
     * @param string|array $columns
1005
     *
1006
     * @return array|mixed
1007
     */
1008 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...
1009
    {
1010
        if (is_string($columns)) {
1011
            return Arr::get($data, $columns);
1012
        }
1013
1014
        if (is_array($columns)) {
1015
            $value = [];
1016
            foreach ($columns as $name => $column) {
1017
                if (!Arr::has($data, $column)) {
1018
                    continue;
1019
                }
1020
                $value[$name] = Arr::get($data, $column);
1021
            }
1022
1023
            return $value;
1024
        }
1025
    }
1026
1027
    /**
1028
     * Find field object by column.
1029
     *
1030
     * @param $column
1031
     *
1032
     * @return mixed
1033
     */
1034
    protected function getFieldByColumn($column)
1035
    {
1036
        return $this->builder->fields()->first(
1037
            function (Field $field) use ($column) {
1038
                if (is_array($field->column())) {
1039
                    return in_array($column, $field->column());
1040
                }
1041
1042
                return $field->column() == $column;
1043
            }
1044
        );
1045
    }
1046
1047
    /**
1048
     * Set original data for each field.
1049
     *
1050
     * @return void
1051
     */
1052
    protected function setFieldOriginalValue()
1053
    {
1054
//        static::doNotSnakeAttributes($this->model);
1055
1056
        $values = $this->model->toArray();
1057
1058
        $this->builder->fields()->each(function (Field $field) use ($values) {
1059
            $field->setOriginal($values);
1060
        });
1061
    }
1062
1063
    /**
1064
     * Set all fields value in form.
1065
     *
1066
     * @param $id
1067
     *
1068
     * @return void
1069
     */
1070
    protected function setFieldValue($id)
1071
    {
1072
        $relations = $this->getRelations();
1073
1074
        $builder = $this->model();
1075
1076
        if ($this->isSoftDeletes) {
1077
            $builder = $builder->withTrashed();
1078
        }
1079
1080
        $this->model = $builder->with($relations)->findOrFail($id);
1081
1082
        $this->callEditing();
1083
1084
//        static::doNotSnakeAttributes($this->model);
1085
1086
        $data = $this->model->toArray();
1087
1088
        $this->builder->fields()->each(function (Field $field) use ($data) {
1089
            if (!in_array($field->column(), $this->ignored)) {
1090
                $field->fill($data);
1091
            }
1092
        });
1093
    }
1094
1095
    /**
1096
     * Add a fieldset to form.
1097
     *
1098
     * @param string  $title
1099
     * @param Closure $setCallback
1100
     *
1101
     * @return Field\Fieldset
1102
     */
1103 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...
1104
    {
1105
        $fieldset = new Field\Fieldset();
1106
1107
        $this->html($fieldset->start($title))->plain();
1108
1109
        $setCallback($this);
1110
1111
        $this->html($fieldset->end())->plain();
1112
1113
        return $fieldset;
1114
    }
1115
1116
    /**
1117
     * Don't snake case attributes.
1118
     *
1119
     * @param Model $model
1120
     *
1121
     * @return void
1122
     */
1123
    protected static function doNotSnakeAttributes(Model $model)
1124
    {
1125
        $class = get_class($model);
1126
1127
        $class::$snakeAttributes = false;
1128
    }
1129
1130
    /**
1131
     * Get validation messages.
1132
     *
1133
     * @param array $input
1134
     *
1135
     * @return MessageBag|bool
1136
     */
1137
    public function validationMessages($input)
1138
    {
1139
        $failedValidators = [];
1140
1141
        /** @var Field $field */
1142 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...
1143
            if (!$validator = $field->getValidator($input)) {
1144
                continue;
1145
            }
1146
1147
            if (($validator instanceof Validator) && !$validator->passes()) {
1148
                $failedValidators[] = $validator;
1149
            }
1150
        }
1151
1152
        $message = $this->mergeValidationMessages($failedValidators);
1153
1154
        return $message->any() ? $message : false;
1155
    }
1156
1157
    /**
1158
     * Merge validation messages from input validators.
1159
     *
1160
     * @param \Illuminate\Validation\Validator[] $validators
1161
     *
1162
     * @return MessageBag
1163
     */
1164
    protected function mergeValidationMessages($validators)
1165
    {
1166
        $messageBag = new MessageBag();
1167
1168
        foreach ($validators as $validator) {
1169
            $messageBag = $messageBag->merge($validator->messages());
1170
        }
1171
1172
        return $messageBag;
1173
    }
1174
1175
    /**
1176
     * Get all relations of model from callable.
1177
     *
1178
     * @return array
1179
     */
1180
    public function getRelations()
1181
    {
1182
        $relations = $columns = [];
1183
1184
        /** @var Field $field */
1185
        foreach ($this->builder->fields() as $field) {
1186
            $columns[] = $field->column();
1187
        }
1188
1189
        foreach (Arr::flatten($columns) as $column) {
1190
            if (Str::contains($column, '.')) {
1191
                list($relation) = explode('.', $column);
1192
1193
                if (method_exists($this->model, $relation) &&
1194
                    $this->model->$relation() instanceof Relations\Relation
1195
                ) {
1196
                    $relations[] = $relation;
1197
                }
1198
            } elseif (method_exists($this->model, $column) &&
1199
                !method_exists(Model::class, $column)
1200
            ) {
1201
                $relations[] = $column;
1202
            }
1203
        }
1204
1205
        return array_unique($relations);
1206
    }
1207
1208
    /**
1209
     * Set action for form.
1210
     *
1211
     * @param string $action
1212
     *
1213
     * @return $this
1214
     */
1215
    public function setAction($action)
1216
    {
1217
        $this->builder()->setAction($action);
1218
1219
        return $this;
1220
    }
1221
1222
    /**
1223
     * Set field and label width in current form.
1224
     *
1225
     * @param int $fieldWidth
1226
     * @param int $labelWidth
1227
     *
1228
     * @return $this
1229
     */
1230
    public function setWidth($fieldWidth = 8, $labelWidth = 2)
1231
    {
1232
        $this->builder()->fields()->each(function ($field) use ($fieldWidth, $labelWidth) {
1233
            /* @var Field $field  */
1234
            $field->setWidth($fieldWidth, $labelWidth);
1235
        });
1236
1237
        $this->builder()->setWidth($fieldWidth, $labelWidth);
1238
1239
        return $this;
1240
    }
1241
1242
    /**
1243
     * Set view for form.
1244
     *
1245
     * @param string $view
1246
     *
1247
     * @return $this
1248
     */
1249
    public function setView($view)
1250
    {
1251
        $this->builder()->setView($view);
1252
1253
        return $this;
1254
    }
1255
1256
    /**
1257
     * Set title for form.
1258
     *
1259
     * @param string $title
1260
     *
1261
     * @return $this
1262
     */
1263
    public function setTitle($title = '')
1264
    {
1265
        $this->builder()->setTitle($title);
1266
1267
        return $this;
1268
    }
1269
1270
    /**
1271
     * Add a row in form.
1272
     *
1273
     * @param Closure $callback
1274
     *
1275
     * @return $this
1276
     */
1277
    public function row(Closure $callback)
1278
    {
1279
        $this->rows[] = new Row($callback, $this);
1280
1281
        return $this;
1282
    }
1283
1284
    /**
1285
     * Tools setting for form.
1286
     *
1287
     * @param Closure $callback
1288
     */
1289
    public function tools(Closure $callback)
1290
    {
1291
        $callback->call($this, $this->builder->getTools());
1292
    }
1293
1294
    /**
1295
     * @param Closure|null $callback
1296
     *
1297
     * @return Form\Tools
1298
     */
1299
    public function header(Closure $callback = null)
1300
    {
1301
        if (func_num_args() == 0) {
1302
            return $this->builder->getTools();
1303
        }
1304
1305
        $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...
1306
    }
1307
1308
    /**
1309
     * Disable form submit.
1310
     *
1311
     * @param bool $disable
1312
     *
1313
     * @return $this
1314
     *
1315
     * @deprecated
1316
     */
1317
    public function disableSubmit(bool $disable = true)
1318
    {
1319
        $this->builder()->getFooter()->disableSubmit($disable);
1320
1321
        return $this;
1322
    }
1323
1324
    /**
1325
     * Disable form reset.
1326
     *
1327
     * @param bool $disable
1328
     *
1329
     * @return $this
1330
     *
1331
     * @deprecated
1332
     */
1333
    public function disableReset(bool $disable = true)
1334
    {
1335
        $this->builder()->getFooter()->disableReset($disable);
1336
1337
        return $this;
1338
    }
1339
1340
    /**
1341
     * Disable View Checkbox on footer.
1342
     *
1343
     * @param bool $disable
1344
     *
1345
     * @return $this
1346
     */
1347
    public function disableViewCheck(bool $disable = true)
1348
    {
1349
        $this->builder()->getFooter()->disableViewCheck($disable);
1350
1351
        return $this;
1352
    }
1353
1354
    /**
1355
     * Disable Editing Checkbox on footer.
1356
     *
1357
     * @param bool $disable
1358
     *
1359
     * @return $this
1360
     */
1361
    public function disableEditingCheck(bool $disable = true)
1362
    {
1363
        $this->builder()->getFooter()->disableEditingCheck($disable);
1364
1365
        return $this;
1366
    }
1367
1368
    /**
1369
     * Disable Creating Checkbox on footer.
1370
     *
1371
     * @param bool $disable
1372
     *
1373
     * @return $this
1374
     */
1375
    public function disableCreatingCheck(bool $disable = true)
1376
    {
1377
        $this->builder()->getFooter()->disableCreatingCheck($disable);
1378
1379
        return $this;
1380
    }
1381
1382
    /**
1383
     * Footer setting for form.
1384
     *
1385
     * @param Closure $callback
1386
     */
1387
    public function footer(Closure $callback = null)
1388
    {
1389
        if (func_num_args() == 0) {
1390
            return $this->builder()->getFooter();
1391
        }
1392
1393
        call_user_func($callback, $this->builder()->getFooter());
1394
    }
1395
1396
    /**
1397
     * Get current resource route url.
1398
     *
1399
     * @param int $slice
1400
     *
1401
     * @return string
1402
     */
1403
    public function resource($slice = -2)
1404
    {
1405
        $segments = explode('/', trim(app('request')->getUri(), '/'));
1406
1407
        if ($slice != 0) {
1408
            $segments = array_slice($segments, 0, $slice);
1409
        }
1410
1411
        return implode('/', $segments);
1412
    }
1413
1414
    /**
1415
     * Render the form contents.
1416
     *
1417
     * @return string
1418
     */
1419
    public function render()
1420
    {
1421
        try {
1422
            return $this->builder->render();
1423
        } catch (\Exception $e) {
1424
            return Handler::renderException($e);
1425
        }
1426
    }
1427
1428
    /**
1429
     * Get or set input data.
1430
     *
1431
     * @param string $key
1432
     * @param null   $value
1433
     *
1434
     * @return array|mixed
1435
     */
1436
    public function input($key, $value = null)
1437
    {
1438
        if (is_null($value)) {
1439
            return Arr::get($this->inputs, $key);
1440
        }
1441
1442
        return Arr::set($this->inputs, $key, $value);
1443
    }
1444
1445
    /**
1446
     * Register builtin fields.
1447
     *
1448
     * @return void
1449
     */
1450
    public static function registerBuiltinFields()
1451
    {
1452
        $map = [
1453
            'button'         => Field\Button::class,
1454
            'checkbox'       => Field\Checkbox::class,
1455
            'color'          => Field\Color::class,
1456
            'currency'       => Field\Currency::class,
1457
            'date'           => Field\Date::class,
1458
            'dateRange'      => Field\DateRange::class,
1459
            'datetime'       => Field\Datetime::class,
1460
            'dateTimeRange'  => Field\DatetimeRange::class,
1461
            'datetimeRange'  => Field\DatetimeRange::class,
1462
            'decimal'        => Field\Decimal::class,
1463
            'display'        => Field\Display::class,
1464
            'divider'        => Field\Divider::class,
1465
            'embeds'         => Field\Embeds::class,
1466
            'email'          => Field\Email::class,
1467
            'file'           => Field\File::class,
1468
            'hasMany'        => Field\HasMany::class,
1469
            'hidden'         => Field\Hidden::class,
1470
            'id'             => Field\Id::class,
1471
            'image'          => Field\Image::class,
1472
            'ip'             => Field\Ip::class,
1473
            'mobile'         => Field\Mobile::class,
1474
            'month'          => Field\Month::class,
1475
            'multipleSelect' => Field\MultipleSelect::class,
1476
            'number'         => Field\Number::class,
1477
            'password'       => Field\Password::class,
1478
            'radio'          => Field\Radio::class,
1479
            'rate'           => Field\Rate::class,
1480
            'select'         => Field\Select::class,
1481
            'slider'         => Field\Slider::class,
1482
            'switch'         => Field\SwitchField::class,
1483
            'text'           => Field\Text::class,
1484
            'textarea'       => Field\Textarea::class,
1485
            'time'           => Field\Time::class,
1486
            'timeRange'      => Field\TimeRange::class,
1487
            'url'            => Field\Url::class,
1488
            'year'           => Field\Year::class,
1489
            'html'           => Field\Html::class,
1490
            'tags'           => Field\Tags::class,
1491
            'icon'           => Field\Icon::class,
1492
            'multipleFile'   => Field\MultipleFile::class,
1493
            'multipleImage'  => Field\MultipleImage::class,
1494
            'captcha'        => Field\Captcha::class,
1495
            'listbox'        => Field\Listbox::class,
1496
            'table'          => Field\Table::class,
1497
            'timezone'       => Field\Timezone::class,
1498
            'keyValue'       => Field\KeyValue::class,
1499
            'list'           => Field\ListField::class,
1500
        ];
1501
1502
        foreach ($map as $abstract => $class) {
1503
            static::extend($abstract, $class);
1504
        }
1505
    }
1506
1507
    /**
1508
     * Register custom field.
1509
     *
1510
     * @param string $abstract
1511
     * @param string $class
1512
     *
1513
     * @return void
1514
     */
1515
    public static function extend($abstract, $class)
1516
    {
1517
        static::$availableFields[$abstract] = $class;
1518
    }
1519
1520
    /**
1521
     * Set form field alias.
1522
     *
1523
     * @param string $field
1524
     * @param string $alias
1525
     *
1526
     * @return void
1527
     */
1528
    public static function alias($field, $alias)
1529
    {
1530
        static::$fieldAlias[$alias] = $field;
1531
    }
1532
1533
    /**
1534
     * Remove registered field.
1535
     *
1536
     * @param array|string $abstract
1537
     */
1538
    public static function forget($abstract)
1539
    {
1540
        Arr::forget(static::$availableFields, $abstract);
1541
    }
1542
1543
    /**
1544
     * Find field class.
1545
     *
1546
     * @param string $method
1547
     *
1548
     * @return bool|mixed
1549
     */
1550
    public static function findFieldClass($method)
1551
    {
1552
        // If alias exists.
1553
        if (isset(static::$fieldAlias[$method])) {
1554
            $method = static::$fieldAlias[$method];
1555
        }
1556
1557
        $class = Arr::get(static::$availableFields, $method);
1558
1559
        if (class_exists($class)) {
1560
            return $class;
1561
        }
1562
1563
        return false;
1564
    }
1565
1566
    /**
1567
     * Collect assets required by registered field.
1568
     *
1569
     * @return array
1570
     */
1571
    public static function collectFieldAssets()
1572
    {
1573
        if (!empty(static::$collectedAssets)) {
1574
            return static::$collectedAssets;
1575
        }
1576
1577
        $css = collect();
1578
        $js = collect();
1579
1580
        foreach (static::$availableFields as $field) {
1581
            if (!method_exists($field, 'getAssets')) {
1582
                continue;
1583
            }
1584
1585
            $assets = call_user_func([$field, 'getAssets']);
1586
1587
            $css->push(Arr::get($assets, 'css'));
1588
            $js->push(Arr::get($assets, 'js'));
1589
        }
1590
1591
        return static::$collectedAssets = [
1592
            'css' => $css->flatten()->unique()->filter()->toArray(),
1593
            'js'  => $js->flatten()->unique()->filter()->toArray(),
1594
        ];
1595
    }
1596
1597
    /**
1598
     * Getter.
1599
     *
1600
     * @param string $name
1601
     *
1602
     * @return array|mixed
1603
     */
1604
    public function __get($name)
1605
    {
1606
        return $this->input($name);
1607
    }
1608
1609
    /**
1610
     * Setter.
1611
     *
1612
     * @param string $name
1613
     * @param mixed  $value
1614
     *
1615
     * @return array
1616
     */
1617
    public function __set($name, $value)
1618
    {
1619
        return Arr::set($this->inputs, $name, $value);
1620
    }
1621
1622
    /**
1623
     * Generate a Field object and add to form builder if Field exists.
1624
     *
1625
     * @param string $method
1626
     * @param array  $arguments
1627
     *
1628
     * @return Field
1629
     */
1630
    public function __call($method, $arguments)
1631
    {
1632
        if ($className = static::findFieldClass($method)) {
1633
            $column = Arr::get($arguments, 0, ''); //[0];
1634
1635
            $element = new $className($column, array_slice($arguments, 1));
1636
1637
            $this->pushField($element);
1638
1639
            return $element;
1640
        }
1641
1642
        admin_error('Error', "Field type [$method] does not exist.");
1643
1644
        return new Field\Nullable();
1645
    }
1646
}
1647