Completed
Push — master ( 837d31...ecc98e )
by Song
03:15
created

Form::setTab()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 0
dl 0
loc 8
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), true);
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
            $callback($this);
271
        }
272
    }
273
274
    /**
275
     * @param Field $field
276
     *
277
     * @return $this
278
     */
279
    public function pushField(Field $field): self
280
    {
281
        $field->setForm($this);
282
283
        $width = $this->builder->getWidth();
284
        $field->setWidth($width['field'], $width['label']);
285
286
        $this->builder->fields()->push($field);
287
        $this->layout->addField($field);
288
289
        return $this;
290
    }
291
292
    /**
293
     * @return Model
294
     */
295
    public function model(): Model
296
    {
297
        return $this->model;
298
    }
299
300
    /**
301
     * @return Builder
302
     */
303
    public function builder(): Builder
304
    {
305
        return $this->builder;
306
    }
307
308
    /**
309
     * Generate a edit form.
310
     *
311
     * @param $id
312
     *
313
     * @return $this
314
     */
315
    public function edit($id): self
316
    {
317
        $this->builder->setMode(Builder::MODE_EDIT);
318
        $this->builder->setResourceId($id);
319
320
        $this->setFieldValue($id);
321
322
        return $this;
323
    }
324
325
    /**
326
     * Use tab to split form.
327
     *
328
     * @param string  $title
329
     * @param Closure $content
330
     * @param bool    $active
331
     *
332
     * @return $this
333
     */
334
    public function tab($title, Closure $content, bool $active = false): self
335
    {
336
        $this->setTab()->append($title, $content, $active);
337
338
        return $this;
339
    }
340
341
    /**
342
     * Get Tab instance.
343
     *
344
     * @return Tab
345
     */
346
    public function getTab()
347
    {
348
        return $this->tab;
349
    }
350
351
    /**
352
     * Set Tab instance.
353
     *
354
     * @return Tab
355
     */
356
    public function setTab(): Tab
357
    {
358
        if ($this->tab === null) {
359
            $this->tab = new Tab($this);
360
        }
361
362
        return $this->tab;
363
    }
364
365
    /**
366
     * Destroy data entity and remove files.
367
     *
368
     * @param $id
369
     *
370
     * @return mixed
371
     */
372
    public function destroy($id)
373
    {
374
        try {
375
            if (($ret = $this->callDeleting($id)) instanceof Response) {
376
                return $ret;
377
            }
378
379
            collect(explode(',', $id))->filter()->each(function ($id) {
380
                $builder = $this->model()->newQuery();
381
382
                if ($this->isSoftDeletes) {
383
                    $builder = $builder->withTrashed();
384
                }
385
386
                $model = $builder->with($this->getRelations())->findOrFail($id);
387
388
                if ($this->isSoftDeletes && $model->trashed()) {
389
                    $this->deleteFiles($model, true);
390
                    $model->forceDelete();
391
392
                    return;
393
                }
394
395
                $this->deleteFiles($model);
396
                $model->delete();
397
            });
398
399
            if (($ret = $this->callDeleted()) instanceof Response) {
400
                return $ret;
401
            }
402
403
            $response = [
404
                'status'  => true,
405
                'message' => trans('admin.delete_succeeded'),
406
            ];
407
        } catch (\Exception $exception) {
408
            $response = [
409
                'status'  => false,
410
                'message' => $exception->getMessage() ?: trans('admin.delete_failed'),
411
            ];
412
        }
413
414
        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...
415
    }
416
417
    /**
418
     * Remove files in record.
419
     *
420
     * @param Model $model
421
     * @param bool  $forceDelete
422
     */
423
    protected function deleteFiles(Model $model, $forceDelete = false)
424
    {
425
        // If it's a soft delete, the files in the data will not be deleted.
426
        if (!$forceDelete && $this->isSoftDeletes) {
427
            return;
428
        }
429
430
        $data = $model->toArray();
431
432
        $this->builder->fields()->filter(function ($field) {
433
            return $field instanceof Field\File;
434
        })->each(function (Field\File $file) use ($data) {
435
            $file->setOriginal($data);
436
437
            $file->destroy();
438
        });
439
    }
440
441
    /**
442
     * Store a new record.
443
     *
444
     * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|\Illuminate\Http\JsonResponse
445
     */
446
    public function store()
447
    {
448
        $data = \request()->all();
449
450
        // Handle validation errors.
451
        if ($validationMessages = $this->validationMessages($data)) {
452
            return $this->responseValidationError($validationMessages);
453
        }
454
455
        if (($response = $this->prepare($data)) instanceof Response) {
456
            return $response;
457
        }
458
459 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...
460
            $inserts = $this->prepareInsert($this->updates);
461
462
            foreach ($inserts as $column => $value) {
463
                $this->model->setAttribute($column, $value);
464
            }
465
466
            $this->model->save();
467
468
            $this->updateRelation($this->relations);
469
        });
470
471
        if (($response = $this->callSaved()) instanceof Response) {
472
            return $response;
473
        }
474
475
        if ($response = $this->ajaxResponse(trans('admin.save_succeeded'))) {
476
            return $response;
477
        }
478
479
        return $this->redirectAfterStore();
480
    }
481
482
    /**
483
     * @param MessageBag $message
484
     *
485
     * @return $this|\Illuminate\Http\JsonResponse
486
     */
487
    protected function responseValidationError(MessageBag $message)
488
    {
489
        if (\request()->ajax() && !\request()->pjax()) {
490
            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...
491
                'status'     => false,
492
                'validation' => $message,
493
                'message'    => $message->first(),
494
            ]);
495
        }
496
497
        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...
498
    }
499
500
    /**
501
     * Get ajax response.
502
     *
503
     * @param string $message
504
     *
505
     * @return bool|\Illuminate\Http\JsonResponse
506
     */
507
    protected function ajaxResponse($message)
508
    {
509
        $request = Request::capture();
510
511
        // ajax but not pjax
512
        if ($request->ajax() && !$request->pjax()) {
513
            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...
514
                'status'  => true,
515
                'message' => $message,
516
            ]);
517
        }
518
519
        return false;
520
    }
521
522
    /**
523
     * Prepare input data for insert or update.
524
     *
525
     * @param array $data
526
     *
527
     * @return mixed
528
     */
529
    protected function prepare($data = [])
530
    {
531
        if (($response = $this->callSubmitted()) instanceof Response) {
532
            return $response;
533
        }
534
535
        $this->inputs = array_merge($this->removeIgnoredFields($data), $this->inputs);
536
537
        if (($response = $this->callSaving()) instanceof Response) {
538
            return $response;
539
        }
540
541
        $this->relations = $this->getRelationInputs($this->inputs);
542
543
        $this->updates = Arr::except($this->inputs, array_keys($this->relations));
544
    }
545
546
    /**
547
     * Remove ignored fields from input.
548
     *
549
     * @param array $input
550
     *
551
     * @return array
552
     */
553
    protected function removeIgnoredFields($input): array
554
    {
555
        Arr::forget($input, $this->ignored);
556
557
        return $input;
558
    }
559
560
    /**
561
     * Get inputs for relations.
562
     *
563
     * @param array $inputs
564
     *
565
     * @return array
566
     */
567
    protected function getRelationInputs($inputs = []): array
568
    {
569
        $relations = [];
570
571
        foreach ($inputs as $column => $value) {
572
            $column = \Illuminate\Support\Str::camel($column);
573
            if (!method_exists($this->model, $column)) {
574
                continue;
575
            }
576
577
            $relation = call_user_func([$this->model, $column]);
578
579
            if ($relation instanceof Relations\Relation) {
580
                $relations[$column] = $value;
581
            }
582
        }
583
584
        return $relations;
585
    }
586
587
    /**
588
     * Handle update.
589
     *
590
     * @param int  $id
591
     * @param null $data
592
     *
593
     * @return bool|\Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\JsonResponse|\Illuminate\Http\RedirectResponse|\Illuminate\Http\Response|mixed|null|Response
594
     */
595
    public function update($id, $data = null)
596
    {
597
        $data = ($data) ?: request()->all();
598
599
        $isEditable = $this->isEditable($data);
600
601
        if (($data = $this->handleColumnUpdates($id, $data)) instanceof Response) {
602
            return $data;
603
        }
604
605
        /* @var Model $this->model */
606
        $builder = $this->model();
607
608
        if ($this->isSoftDeletes) {
609
            $builder = $builder->withTrashed();
610
        }
611
612
        $this->model = $builder->with($this->getRelations())->findOrFail($id);
613
614
        $this->setFieldOriginalValue();
615
616
        // Handle validation errors.
617
        if ($validationMessages = $this->validationMessages($data)) {
0 ignored issues
show
Bug introduced by
It seems like $data defined by $this->handleColumnUpdates($id, $data) on line 601 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...
618
            if (!$isEditable) {
619
                return back()->withInput()->withErrors($validationMessages);
620
            }
621
622
            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...
623
        }
624
625
        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 601 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...
626
            return $response;
627
        }
628
629 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...
630
            $updates = $this->prepareUpdate($this->updates);
631
632
            foreach ($updates as $column => $value) {
633
                /* @var Model $this->model */
634
                $this->model->setAttribute($column, $value);
635
            }
636
637
            $this->model->save();
638
639
            $this->updateRelation($this->relations);
640
        });
641
642
        if (($result = $this->callSaved()) instanceof Response) {
643
            return $result;
644
        }
645
646
        if ($response = $this->ajaxResponse(trans('admin.update_succeeded'))) {
647
            return $response;
648
        }
649
650
        return $this->redirectAfterUpdate($id);
651
    }
652
653
    /**
654
     * Get RedirectResponse after store.
655
     *
656
     * @return \Illuminate\Http\RedirectResponse
657
     */
658
    protected function redirectAfterStore()
659
    {
660
        $resourcesPath = $this->resource(0);
661
662
        $key = $this->model->getKey();
663
664
        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 664 which is incompatible with the return type documented by Encore\Admin\Form::redirectAfterStore of type Illuminate\Http\RedirectResponse.
Loading history...
665
    }
666
667
    /**
668
     * Get RedirectResponse after update.
669
     *
670
     * @param mixed $key
671
     *
672
     * @return \Illuminate\Http\RedirectResponse
673
     */
674
    protected function redirectAfterUpdate($key)
675
    {
676
        $resourcesPath = $this->resource(-1);
677
678
        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 678 which is incompatible with the return type documented by Encore\Admin\Form::redirectAfterUpdate of type Illuminate\Http\RedirectResponse.
Loading history...
679
    }
680
681
    /**
682
     * Get RedirectResponse after data saving.
683
     *
684
     * @param string $resourcesPath
685
     * @param string $key
686
     *
687
     * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
688
     */
689
    protected function redirectAfterSaving($resourcesPath, $key)
690
    {
691
        if (request('after-save') == 1) {
692
            // continue editing
693
            $url = rtrim($resourcesPath, '/')."/{$key}/edit";
694
        } elseif (request('after-save') == 2) {
695
            // continue creating
696
            $url = rtrim($resourcesPath, '/').'/create';
697
        } elseif (request('after-save') == 3) {
698
            // view resource
699
            $url = rtrim($resourcesPath, '/')."/{$key}";
700
        } else {
701
            $url = request(Builder::PREVIOUS_URL_KEY) ?: $resourcesPath;
702
        }
703
704
        admin_toastr(trans('admin.save_succeeded'));
705
706
        return redirect($url);
707
    }
708
709
    /**
710
     * Check if request is from editable.
711
     *
712
     * @param array $input
713
     *
714
     * @return bool
715
     */
716
    protected function isEditable(array $input = []): bool
717
    {
718
        return array_key_exists('_editable', $input);
719
    }
720
721
    /**
722
     * Handle updates for single column.
723
     *
724
     * @param int   $id
725
     * @param array $data
726
     *
727
     * @return array|\Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\Response|Response
728
     */
729
    protected function handleColumnUpdates($id, $data)
730
    {
731
        $data = $this->handleEditable($data);
732
733
        $data = $this->handleFileDelete($data);
734
735
        $data = $this->handleFileSort($data);
736
737
        if ($this->handleOrderable($id, $data)) {
738
            return response([
739
                'status'  => true,
740
                'message' => trans('admin.update_succeeded'),
741
            ]);
742
        }
743
744
        return $data;
745
    }
746
747
    /**
748
     * Handle editable update.
749
     *
750
     * @param array $input
751
     *
752
     * @return array
753
     */
754
    protected function handleEditable(array $input = []): array
755
    {
756
        if (array_key_exists('_editable', $input)) {
757
            $name = $input['name'];
758
            $value = $input['value'];
759
760
            Arr::forget($input, ['pk', 'value', 'name']);
761
            Arr::set($input, $name, $value);
762
        }
763
764
        return $input;
765
    }
766
767
    /**
768
     * @param array $input
769
     *
770
     * @return array
771
     */
772
    protected function handleFileDelete(array $input = []): array
773
    {
774
        if (array_key_exists(Field::FILE_DELETE_FLAG, $input)) {
775
            $input[Field::FILE_DELETE_FLAG] = $input['key'];
776
            unset($input['key']);
777
        }
778
779
        request()->replace($input);
780
781
        return $input;
782
    }
783
784
    /**
785
     * @param array $input
786
     *
787
     * @return array
788
     */
789
    protected function handleFileSort(array $input = []): array
790
    {
791
        if (!array_key_exists(Field::FILE_SORT_FLAG, $input)) {
792
            return $input;
793
        }
794
795
        $sorts = array_filter($input[Field::FILE_SORT_FLAG]);
796
797
        if (empty($sorts)) {
798
            return $input;
799
        }
800
801
        foreach ($sorts as $column => $order) {
802
            $input[$column] = $order;
803
        }
804
805
        request()->replace($input);
806
807
        return $input;
808
    }
809
810
    /**
811
     * Handle orderable update.
812
     *
813
     * @param int   $id
814
     * @param array $input
815
     *
816
     * @return bool
817
     */
818
    protected function handleOrderable($id, array $input = [])
819
    {
820
        if (array_key_exists('_orderable', $input)) {
821
            $model = $this->model->find($id);
822
823
            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...
824
                $input['_orderable'] == 1 ? $model->moveOrderUp() : $model->moveOrderDown();
825
826
                return true;
827
            }
828
        }
829
830
        return false;
831
    }
832
833
    /**
834
     * Update relation data.
835
     *
836
     * @param array $relationsData
837
     *
838
     * @return void
839
     */
840
    protected function updateRelation($relationsData)
841
    {
842
        foreach ($relationsData as $name => $values) {
843
            if (!method_exists($this->model, $name)) {
844
                continue;
845
            }
846
847
            $relation = $this->model->$name();
848
849
            $oneToOneRelation = $relation instanceof Relations\HasOne
850
                || $relation instanceof Relations\MorphOne
851
                || $relation instanceof Relations\BelongsTo;
852
853
            $prepared = $this->prepareUpdate([$name => $values], $oneToOneRelation);
854
855
            if (empty($prepared)) {
856
                continue;
857
            }
858
859
            switch (true) {
860
                case $relation instanceof Relations\BelongsToMany:
861
                case $relation instanceof Relations\MorphToMany:
862
                    if (isset($prepared[$name])) {
863
                        $relation->sync($prepared[$name]);
864
                    }
865
                    break;
866
                case $relation instanceof Relations\HasOne:
867
868
                    $related = $this->model->$name;
869
870
                    // if related is empty
871
                    if (is_null($related)) {
872
                        $related = $relation->getRelated();
873
                        $qualifiedParentKeyName = $relation->getQualifiedParentKeyName();
874
                        $localKey = Arr::last(explode('.', $qualifiedParentKeyName));
875
                        $related->{$relation->getForeignKeyName()} = $this->model->{$localKey};
876
                    }
877
878
                    foreach ($prepared[$name] as $column => $value) {
879
                        $related->setAttribute($column, $value);
880
                    }
881
882
                    $related->save();
883
                    break;
884
                case $relation instanceof Relations\BelongsTo:
885
                case $relation instanceof Relations\MorphTo:
886
887
                    $parent = $this->model->$name;
888
889
                    // if related is empty
890
                    if (is_null($parent)) {
891
                        $parent = $relation->getRelated();
892
                    }
893
894
                    foreach ($prepared[$name] as $column => $value) {
895
                        $parent->setAttribute($column, $value);
896
                    }
897
898
                    $parent->save();
899
900
                    // When in creating, associate two models
901
                    $foreignKeyMethod = version_compare(app()->version(), '5.8.0', '<') ? 'getForeignKey' : 'getForeignKeyName';
902
                    if (!$this->model->{$relation->{$foreignKeyMethod}()}) {
903
                        $this->model->{$relation->{$foreignKeyMethod}()} = $parent->getKey();
904
905
                        $this->model->save();
906
                    }
907
908
                    break;
909
                case $relation instanceof Relations\MorphOne:
910
                    $related = $this->model->$name;
911
                    if ($related === null) {
912
                        $related = $relation->make();
913
                    }
914
                    foreach ($prepared[$name] as $column => $value) {
915
                        $related->setAttribute($column, $value);
916
                    }
917
                    $related->save();
918
                    break;
919
                case $relation instanceof Relations\HasMany:
920
                case $relation instanceof Relations\MorphMany:
921
922
                    foreach ($prepared[$name] as $related) {
923
                        /** @var Relations\Relation $relation */
924
                        $relation = $this->model()->$name();
925
926
                        $keyName = $relation->getRelated()->getKeyName();
927
928
                        $instance = $relation->findOrNew(Arr::get($related, $keyName));
929
930
                        if ($related[static::REMOVE_FLAG_NAME] == 1) {
931
                            $instance->delete();
932
933
                            continue;
934
                        }
935
936
                        Arr::forget($related, static::REMOVE_FLAG_NAME);
937
938
                        $instance->fill($related);
939
940
                        $instance->save();
941
                    }
942
943
                    break;
944
            }
945
        }
946
    }
947
948
    /**
949
     * Prepare input data for update.
950
     *
951
     * @param array $updates
952
     * @param bool  $oneToOneRelation If column is one-to-one relation.
953
     *
954
     * @return array
955
     */
956
    protected function prepareUpdate(array $updates, $oneToOneRelation = false): array
957
    {
958
        $prepared = [];
959
960
        /** @var Field $field */
961
        foreach ($this->builder->fields() as $field) {
962
            $columns = $field->column();
963
964
            // If column not in input array data, then continue.
965
            if (!Arr::has($updates, $columns)) {
966
                continue;
967
            }
968
969
            if ($this->isInvalidColumn($columns, $oneToOneRelation || $field->isJsonType)) {
970
                continue;
971
            }
972
973
            $value = $this->getDataByColumn($updates, $columns);
974
975
            $value = $field->prepare($value);
976
977 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...
978
                foreach ($columns as $name => $column) {
979
                    Arr::set($prepared, $column, $value[$name]);
980
                }
981
            } elseif (is_string($columns)) {
982
                Arr::set($prepared, $columns, $value);
983
            }
984
        }
985
986
        return $prepared;
987
    }
988
989
    /**
990
     * @param string|array $columns
991
     * @param bool         $containsDot
992
     *
993
     * @return bool
994
     */
995
    protected function isInvalidColumn($columns, $containsDot = false): bool
996
    {
997
        foreach ((array) $columns as $column) {
998
            if ((!$containsDot && Str::contains($column, '.')) ||
999
                ($containsDot && !Str::contains($column, '.'))) {
1000
                return true;
1001
            }
1002
        }
1003
1004
        return false;
1005
    }
1006
1007
    /**
1008
     * Prepare input data for insert.
1009
     *
1010
     * @param $inserts
1011
     *
1012
     * @return array
1013
     */
1014
    protected function prepareInsert($inserts): array
1015
    {
1016
        if ($this->isHasOneRelation($inserts)) {
1017
            $inserts = Arr::dot($inserts);
1018
        }
1019
1020
        foreach ($inserts as $column => $value) {
1021
            if (($field = $this->getFieldByColumn($column)) === null) {
1022
                unset($inserts[$column]);
1023
                continue;
1024
            }
1025
1026
            $inserts[$column] = $field->prepare($value);
1027
        }
1028
1029
        $prepared = [];
1030
1031
        foreach ($inserts as $key => $value) {
1032
            Arr::set($prepared, $key, $value);
1033
        }
1034
1035
        return $prepared;
1036
    }
1037
1038
    /**
1039
     * Is input data is has-one relation.
1040
     *
1041
     * @param array $inserts
1042
     *
1043
     * @return bool
1044
     */
1045
    protected function isHasOneRelation($inserts): bool
1046
    {
1047
        $first = current($inserts);
1048
1049
        if (!is_array($first)) {
1050
            return false;
1051
        }
1052
1053
        if (is_array(current($first))) {
1054
            return false;
1055
        }
1056
1057
        return Arr::isAssoc($first);
1058
    }
1059
1060
    /**
1061
     * Ignore fields to save.
1062
     *
1063
     * @param string|array $fields
1064
     *
1065
     * @return $this
1066
     */
1067
    public function ignore($fields): self
1068
    {
1069
        $this->ignored = array_merge($this->ignored, (array) $fields);
1070
1071
        return $this;
1072
    }
1073
1074
    /**
1075
     * @param array        $data
1076
     * @param string|array $columns
1077
     *
1078
     * @return array|mixed
1079
     */
1080 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...
1081
    {
1082
        if (is_string($columns)) {
1083
            return Arr::get($data, $columns);
1084
        }
1085
1086
        if (is_array($columns)) {
1087
            $value = [];
1088
            foreach ($columns as $name => $column) {
1089
                if (!Arr::has($data, $column)) {
1090
                    continue;
1091
                }
1092
                $value[$name] = Arr::get($data, $column);
1093
            }
1094
1095
            return $value;
1096
        }
1097
    }
1098
1099
    /**
1100
     * Find field object by column.
1101
     *
1102
     * @param $column
1103
     *
1104
     * @return mixed
1105
     */
1106
    protected function getFieldByColumn($column)
1107
    {
1108
        return $this->builder->fields()->first(
1109
            function (Field $field) use ($column) {
1110
                if (is_array($field->column())) {
1111
                    return in_array($column, $field->column());
1112
                }
1113
1114
                return $field->column() == $column;
1115
            }
1116
        );
1117
    }
1118
1119
    /**
1120
     * Set original data for each field.
1121
     *
1122
     * @return void
1123
     */
1124
    protected function setFieldOriginalValue()
1125
    {
1126
//        static::doNotSnakeAttributes($this->model);
1127
1128
        $values = $this->model->toArray();
1129
1130
        $this->builder->fields()->each(function (Field $field) use ($values) {
1131
            $field->setOriginal($values);
1132
        });
1133
    }
1134
1135
    /**
1136
     * Set all fields value in form.
1137
     *
1138
     * @param $id
1139
     *
1140
     * @return void
1141
     */
1142
    protected function setFieldValue($id)
1143
    {
1144
        $relations = $this->getRelations();
1145
1146
        $builder = $this->model();
1147
1148
        if ($this->isSoftDeletes) {
1149
            $builder = $builder->withTrashed();
1150
        }
1151
1152
        $this->model = $builder->with($relations)->findOrFail($id);
1153
1154
        $this->callEditing();
1155
1156
//        static::doNotSnakeAttributes($this->model);
1157
1158
        $data = $this->model->toArray();
1159
1160
        $this->builder->fields()->each(function (Field $field) use ($data) {
1161
            if (!in_array($field->column(), $this->ignored, true)) {
1162
                $field->fill($data);
1163
            }
1164
        });
1165
    }
1166
1167
    /**
1168
     * Add a fieldset to form.
1169
     *
1170
     * @param string  $title
1171
     * @param Closure $setCallback
1172
     *
1173
     * @return Field\Fieldset
1174
     */
1175 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...
1176
    {
1177
        $fieldset = new Field\Fieldset();
1178
1179
        $this->html($fieldset->start($title))->plain();
1180
1181
        $setCallback($this);
1182
1183
        $this->html($fieldset->end())->plain();
1184
1185
        return $fieldset;
1186
    }
1187
1188
    /**
1189
     * Don't snake case attributes.
1190
     *
1191
     * @param Model $model
1192
     *
1193
     * @return void
1194
     */
1195
    protected static function doNotSnakeAttributes(Model $model)
1196
    {
1197
        $class = get_class($model);
1198
1199
        $class::$snakeAttributes = false;
1200
    }
1201
1202
    /**
1203
     * Get validation messages.
1204
     *
1205
     * @param array $input
1206
     *
1207
     * @return MessageBag|bool
1208
     */
1209
    public function validationMessages($input)
1210
    {
1211
        $failedValidators = [];
1212
1213
        /** @var Field $field */
1214 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...
1215
            if (!$validator = $field->getValidator($input)) {
1216
                continue;
1217
            }
1218
1219
            if (($validator instanceof Validator) && !$validator->passes()) {
1220
                $failedValidators[] = $validator;
1221
            }
1222
        }
1223
1224
        $message = $this->mergeValidationMessages($failedValidators);
1225
1226
        return $message->any() ? $message : false;
1227
    }
1228
1229
    /**
1230
     * Merge validation messages from input validators.
1231
     *
1232
     * @param \Illuminate\Validation\Validator[] $validators
1233
     *
1234
     * @return MessageBag
1235
     */
1236
    protected function mergeValidationMessages($validators): MessageBag
1237
    {
1238
        $messageBag = new MessageBag();
1239
1240
        foreach ($validators as $validator) {
1241
            $messageBag = $messageBag->merge($validator->messages());
1242
        }
1243
1244
        return $messageBag;
1245
    }
1246
1247
    /**
1248
     * Get all relations of model from callable.
1249
     *
1250
     * @return array
1251
     */
1252
    public function getRelations(): array
1253
    {
1254
        $relations = $columns = [];
1255
1256
        /** @var Field $field */
1257
        foreach ($this->builder->fields() as $field) {
1258
            $columns[] = $field->column();
1259
        }
1260
1261
        foreach (Arr::flatten($columns) as $column) {
1262
            if (Str::contains($column, '.')) {
1263
                list($relation) = explode('.', $column);
1264
1265
                if (method_exists($this->model, $relation) &&
1266
                    $this->model->$relation() instanceof Relations\Relation
1267
                ) {
1268
                    $relations[] = $relation;
1269
                }
1270
            } elseif (method_exists($this->model, $column) &&
1271
                !method_exists(Model::class, $column)
1272
            ) {
1273
                $relations[] = $column;
1274
            }
1275
        }
1276
1277
        return array_unique($relations);
1278
    }
1279
1280
    /**
1281
     * Set action for form.
1282
     *
1283
     * @param string $action
1284
     *
1285
     * @return $this
1286
     */
1287
    public function setAction($action): self
1288
    {
1289
        $this->builder()->setAction($action);
1290
1291
        return $this;
1292
    }
1293
1294
    /**
1295
     * Set field and label width in current form.
1296
     *
1297
     * @param int $fieldWidth
1298
     * @param int $labelWidth
1299
     *
1300
     * @return $this
1301
     */
1302
    public function setWidth($fieldWidth = 8, $labelWidth = 2): self
1303
    {
1304
        $this->builder()->fields()->each(function ($field) use ($fieldWidth, $labelWidth) {
1305
            /* @var Field $field  */
1306
            $field->setWidth($fieldWidth, $labelWidth);
1307
        });
1308
1309
        $this->builder()->setWidth($fieldWidth, $labelWidth);
1310
1311
        return $this;
1312
    }
1313
1314
    /**
1315
     * Set view for form.
1316
     *
1317
     * @param string $view
1318
     *
1319
     * @return $this
1320
     */
1321
    public function setView($view): self
1322
    {
1323
        $this->builder()->setView($view);
1324
1325
        return $this;
1326
    }
1327
1328
    /**
1329
     * Set title for form.
1330
     *
1331
     * @param string $title
1332
     *
1333
     * @return $this
1334
     */
1335
    public function setTitle($title = ''): self
1336
    {
1337
        $this->builder()->setTitle($title);
1338
1339
        return $this;
1340
    }
1341
1342
    /**
1343
     * Add a row in form.
1344
     *
1345
     * @param Closure $callback
1346
     *
1347
     * @return $this
1348
     */
1349
    public function row(Closure $callback): self
1350
    {
1351
        $this->rows[] = new Row($callback, $this);
1352
1353
        return $this;
1354
    }
1355
1356
    /**
1357
     * Tools setting for form.
1358
     *
1359
     * @param Closure $callback
1360
     */
1361
    public function tools(Closure $callback)
1362
    {
1363
        $callback->call($this, $this->builder->getTools());
1364
    }
1365
1366
    /**
1367
     * @param Closure|null $callback
1368
     *
1369
     * @return Form\Tools
1370
     */
1371 View Code Duplication
    public function header(Closure $callback = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1372
    {
1373
        if (func_num_args() === 0) {
1374
            return $this->builder->getTools();
1375
        }
1376
1377
        $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...
1378
    }
1379
1380
    /**
1381
     * Indicates if current form page is creating.
1382
     *
1383
     * @return bool
1384
     */
1385
    public function isCreating(): bool
1386
    {
1387
        return Str::endsWith(\request()->route()->getName(), '.create');
1388
    }
1389
1390
    /**
1391
     * Indicates if current form page is editing.
1392
     *
1393
     * @return bool
1394
     */
1395
    public function isEditing(): bool
1396
    {
1397
        return Str::endsWith(\request()->route()->getName(), '.edit');
1398
    }
1399
1400
    /**
1401
     * Disable form submit.
1402
     *
1403
     * @param bool $disable
1404
     *
1405
     * @return $this
1406
     *
1407
     * @deprecated
1408
     */
1409
    public function disableSubmit(bool $disable = true): self
1410
    {
1411
        $this->builder()->getFooter()->disableSubmit($disable);
1412
1413
        return $this;
1414
    }
1415
1416
    /**
1417
     * Disable form reset.
1418
     *
1419
     * @param bool $disable
1420
     *
1421
     * @return $this
1422
     *
1423
     * @deprecated
1424
     */
1425
    public function disableReset(bool $disable = true): self
1426
    {
1427
        $this->builder()->getFooter()->disableReset($disable);
1428
1429
        return $this;
1430
    }
1431
1432
    /**
1433
     * Disable View Checkbox on footer.
1434
     *
1435
     * @param bool $disable
1436
     *
1437
     * @return $this
1438
     */
1439
    public function disableViewCheck(bool $disable = true): self
1440
    {
1441
        $this->builder()->getFooter()->disableViewCheck($disable);
1442
1443
        return $this;
1444
    }
1445
1446
    /**
1447
     * Disable Editing Checkbox on footer.
1448
     *
1449
     * @param bool $disable
1450
     *
1451
     * @return $this
1452
     */
1453
    public function disableEditingCheck(bool $disable = true): self
1454
    {
1455
        $this->builder()->getFooter()->disableEditingCheck($disable);
1456
1457
        return $this;
1458
    }
1459
1460
    /**
1461
     * Disable Creating Checkbox on footer.
1462
     *
1463
     * @param bool $disable
1464
     *
1465
     * @return $this
1466
     */
1467
    public function disableCreatingCheck(bool $disable = true): self
1468
    {
1469
        $this->builder()->getFooter()->disableCreatingCheck($disable);
1470
1471
        return $this;
1472
    }
1473
1474
    /**
1475
     * Footer setting for form.
1476
     *
1477
     * @param Closure $callback
1478
     *
1479
     * @return \Encore\Admin\Form\Footer
1480
     */
1481 View Code Duplication
    public function footer(Closure $callback = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1482
    {
1483
        if (func_num_args() === 0) {
1484
            return $this->builder()->getFooter();
1485
        }
1486
1487
        $callback($this->builder()->getFooter());
1488
    }
1489
1490
    /**
1491
     * Get current resource route url.
1492
     *
1493
     * @param int $slice
1494
     *
1495
     * @return string
1496
     */
1497
    public function resource($slice = -2): string
1498
    {
1499
        $segments = explode('/', trim(\request()->getUri(), '/'));
1500
1501
        if ($slice !== 0) {
1502
            $segments = array_slice($segments, 0, $slice);
1503
        }
1504
1505
        return implode('/', $segments);
1506
    }
1507
1508
    /**
1509
     * Render the form contents.
1510
     *
1511
     * @return string
1512
     */
1513
    public function render()
1514
    {
1515
        try {
1516
            return $this->builder->render();
1517
        } catch (\Exception $e) {
1518
            return Handler::renderException($e);
1519
        }
1520
    }
1521
1522
    /**
1523
     * Get or set input data.
1524
     *
1525
     * @param string $key
1526
     * @param null   $value
1527
     *
1528
     * @return array|mixed
1529
     */
1530
    public function input($key, $value = null)
1531
    {
1532
        if ($value === null) {
1533
            return Arr::get($this->inputs, $key);
1534
        }
1535
1536
        return Arr::set($this->inputs, $key, $value);
1537
    }
1538
1539
    /**
1540
     * Register custom field.
1541
     *
1542
     * @param string $abstract
1543
     * @param string $class
1544
     *
1545
     * @return void
1546
     */
1547
    public static function extend($abstract, $class)
1548
    {
1549
        static::$availableFields[$abstract] = $class;
1550
    }
1551
1552
    /**
1553
     * Set form field alias.
1554
     *
1555
     * @param string $field
1556
     * @param string $alias
1557
     *
1558
     * @return void
1559
     */
1560
    public static function alias($field, $alias)
1561
    {
1562
        static::$fieldAlias[$alias] = $field;
1563
    }
1564
1565
    /**
1566
     * Remove registered field.
1567
     *
1568
     * @param array|string $abstract
1569
     */
1570
    public static function forget($abstract)
1571
    {
1572
        Arr::forget(static::$availableFields, $abstract);
1573
    }
1574
1575
    /**
1576
     * Find field class.
1577
     *
1578
     * @param string $method
1579
     *
1580
     * @return bool|mixed
1581
     */
1582
    public static function findFieldClass($method)
1583
    {
1584
        // If alias exists.
1585
        if (isset(static::$fieldAlias[$method])) {
1586
            $method = static::$fieldAlias[$method];
1587
        }
1588
1589
        $class = Arr::get(static::$availableFields, $method);
1590
1591
        if (class_exists($class)) {
1592
            return $class;
1593
        }
1594
1595
        return false;
1596
    }
1597
1598
    /**
1599
     * Collect assets required by registered field.
1600
     *
1601
     * @return array
1602
     */
1603
    public static function collectFieldAssets(): array
1604
    {
1605
        if (!empty(static::$collectedAssets)) {
1606
            return static::$collectedAssets;
1607
        }
1608
1609
        $css = collect();
1610
        $js = collect();
1611
1612
        foreach (static::$availableFields as $field) {
1613
            if (!method_exists($field, 'getAssets')) {
1614
                continue;
1615
            }
1616
1617
            $assets = call_user_func([$field, 'getAssets']);
1618
1619
            $css->push(Arr::get($assets, 'css'));
1620
            $js->push(Arr::get($assets, 'js'));
1621
        }
1622
1623
        return static::$collectedAssets = [
1624
            'css' => $css->flatten()->unique()->filter()->toArray(),
1625
            'js'  => $js->flatten()->unique()->filter()->toArray(),
1626
        ];
1627
    }
1628
1629
    /**
1630
     * Add a new layout column.
1631
     *
1632
     * @param int      $width
1633
     * @param \Closure $closure
1634
     *
1635
     * @return $this
1636
     */
1637 View Code Duplication
    public function column($width, \Closure $closure): self
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1638
    {
1639
        $width = $width < 1 ? round(12 * $width) : $width;
1640
1641
        $this->layout->column($width, $closure);
1642
1643
        return $this;
1644
    }
1645
1646
    /**
1647
     * Initialize filter layout.
1648
     */
1649
    protected function initLayout()
1650
    {
1651
        $this->layout = new Layout($this);
1652
    }
1653
1654
    /**
1655
     * Getter.
1656
     *
1657
     * @param string $name
1658
     *
1659
     * @return array|mixed
1660
     */
1661
    public function __get($name)
1662
    {
1663
        return $this->input($name);
1664
    }
1665
1666
    /**
1667
     * Setter.
1668
     *
1669
     * @param string $name
1670
     * @param mixed  $value
1671
     *
1672
     * @return array
1673
     */
1674
    public function __set($name, $value)
1675
    {
1676
        return Arr::set($this->inputs, $name, $value);
1677
    }
1678
1679
    /**
1680
     * Generate a Field object and add to form builder if Field exists.
1681
     *
1682
     * @param string $method
1683
     * @param array  $arguments
1684
     *
1685
     * @return Field
1686
     */
1687
    public function __call($method, $arguments)
1688
    {
1689
        if ($className = static::findFieldClass($method)) {
1690
            $column = Arr::get($arguments, 0, ''); //[0];
1691
1692
            $element = new $className($column, array_slice($arguments, 1));
1693
1694
            $this->pushField($element);
1695
1696
            return $element;
1697
        }
1698
1699
        admin_error('Error', "Field type [$method] does not exist.");
1700
1701
        return new Field\Nullable();
1702
    }
1703
1704
    /**
1705
     * @return Layout
1706
     */
1707
    public function getLayout(): Layout
1708
    {
1709
        return $this->layout;
1710
    }
1711
}
1712