Completed
Push — master ( dc232d...5ccba3 )
by Song
02:47 queued 15s
created

Form::getLayout()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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