Completed
Pull Request — master (#2327)
by
unknown
02:57
created

Form::disableViewCheck()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 6
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Encore\Admin;
4
5
use Closure;
6
use Encore\Admin\Exception\Handler;
7
use Encore\Admin\Form\Builder;
8
use Encore\Admin\Form\Field;
9
use Encore\Admin\Form\Row;
10
use Encore\Admin\Form\Tab;
11
use Illuminate\Contracts\Support\Renderable;
12
use Illuminate\Database\Eloquent\Model;
13
use Illuminate\Database\Eloquent\Relations;
14
use Illuminate\Http\Request;
15
use Illuminate\Support\Arr;
16
use Illuminate\Support\Facades\DB;
17
use Illuminate\Support\Facades\Input;
18
use Illuminate\Support\MessageBag;
19
use Illuminate\Support\Str;
20
use Illuminate\Validation\Validator;
21
use Spatie\EloquentSortable\Sortable;
22
use Symfony\Component\HttpFoundation\Response;
23
24
/**
25
 * Class Form.
26
 *
27
 * @method Field\Text           text($column, $label = '')
28
 * @method Field\Checkbox       checkbox($column, $label = '')
29
 * @method Field\Radio          radio($column, $label = '')
30
 * @method Field\Select         select($column, $label = '')
31
 * @method Field\MultipleSelect multipleSelect($column, $label = '')
32
 * @method Field\Textarea       textarea($column, $label = '')
33
 * @method Field\Hidden         hidden($column, $label = '')
34
 * @method Field\Id             id($column, $label = '')
35
 * @method Field\Ip             ip($column, $label = '')
36
 * @method Field\Url            url($column, $label = '')
37
 * @method Field\Color          color($column, $label = '')
38
 * @method Field\Email          email($column, $label = '')
39
 * @method Field\Mobile         mobile($column, $label = '')
40
 * @method Field\Slider         slider($column, $label = '')
41
 * @method Field\Map            map($latitude, $longitude, $label = '')
42
 * @method Field\Editor         editor($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, $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\Divide         divider()
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 = '')
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
 */
71
class Form implements Renderable
72
{
73
    /**
74
     * Eloquent model of the form.
75
     *
76
     * @var Model
77
     */
78
    protected $model;
79
80
    /**
81
     * @var \Illuminate\Validation\Validator
82
     */
83
    protected $validator;
84
85
    /**
86
     * @var Builder
87
     */
88
    protected $builder;
89
90
    /**
91
     * Submitted callback.
92
     *
93
     * @var Closure
94
     */
95
    protected $submitted;
96
97
    /**
98
     * Saving callback.
99
     *
100
     * @var Closure
101
     */
102
    protected $saving;
103
104
    /**
105
     * Saved callback.
106
     *
107
     * @var Closure
108
     */
109
    protected $saved;
110
111
    /**
112
     * Data for save to current model from input.
113
     *
114
     * @var array
115
     */
116
    protected $updates = [];
117
118
    /**
119
     * Data for save to model's relations from input.
120
     *
121
     * @var array
122
     */
123
    protected $relations = [];
124
125
    /**
126
     * Input data.
127
     *
128
     * @var array
129
     */
130
    protected $inputs = [];
131
132
    /**
133
     * Available fields.
134
     *
135
     * @var array
136
     */
137
    public static $availableFields = [];
138
139
    /**
140
     * Ignored saving fields.
141
     *
142
     * @var array
143
     */
144
    protected $ignored = [];
145
146
    /**
147
     * Collected field assets.
148
     *
149
     * @var array
150
     */
151
    protected static $collectedAssets = [];
152
153
    /**
154
     * @var Form\Tab
155
     */
156
    protected $tab = null;
157
158
    /**
159
     * Remove flag in `has many` form.
160
     */
161
    const REMOVE_FLAG_NAME = '_remove_';
162
163
    /**
164
     * Field rows in form.
165
     *
166
     * @var array
167
     */
168
    public $rows = [];
169
170
    /**
171
     * Create a new form instance.
172
     *
173
     * @param $model
174
     * @param \Closure $callback
175
     */
176
    public function __construct($model, Closure $callback = null)
177
    {
178
        $this->model = $model;
179
180
        $this->builder = new Builder($this);
181
182
        if ($callback instanceof Closure) {
183
            $callback($this);
184
        }
185
    }
186
187
    /**
188
     * @param Field $field
189
     *
190
     * @return $this
191
     */
192
    public function pushField(Field $field)
193
    {
194
        $field->setForm($this);
195
196
        $this->builder->fields()->push($field);
197
198
        return $this;
199
    }
200
201
    /**
202
     * @return Model
203
     */
204
    public function model()
205
    {
206
        return $this->model;
207
    }
208
209
    /**
210
     * @return Builder
211
     */
212
    public function builder()
213
    {
214
        return $this->builder;
215
    }
216
217
    /**
218
     * Generate a edit form.
219
     *
220
     * @param $id
221
     *
222
     * @return $this
223
     */
224
    public function edit($id)
225
    {
226
        $this->builder->setMode(Builder::MODE_EDIT);
227
        $this->builder->setResourceId($id);
228
229
        $this->setFieldValue($id);
230
231
        return $this;
232
    }
233
234
    /**
235
     * Use tab to split form.
236
     *
237
     * @param string  $title
238
     * @param Closure $content
239
     *
240
     * @return $this
241
     */
242
    public function tab($title, Closure $content, $active = false)
243
    {
244
        $this->getTab()->append($title, $content, $active);
245
246
        return $this;
247
    }
248
249
    /**
250
     * Get Tab instance.
251
     *
252
     * @return Tab
253
     */
254
    public function getTab()
255
    {
256
        if (is_null($this->tab)) {
257
            $this->tab = new Tab($this);
258
        }
259
260
        return $this->tab;
261
    }
262
263
    /**
264
     * Destroy data entity and remove files.
265
     *
266
     * @param $id
267
     *
268
     * @return mixed
269
     */
270
    public function destroy($id)
271
    {
272
        $ids = explode(',', $id);
273
274
        foreach ($ids as $id) {
275
            if (empty($id)) {
276
                continue;
277
            }
278
            $this->deleteFilesAndImages($id);
279
            $this->model->find($id)->delete();
280
        }
281
282
        return true;
283
    }
284
285
    /**
286
     * Remove files or images in record.
287
     *
288
     * @param $id
289
     */
290
    protected function deleteFilesAndImages($id)
291
    {
292
        $data = $this->model->with($this->getRelations())
0 ignored issues
show
Bug introduced by
The method findOrFail does only exist in Illuminate\Database\Eloquent\Builder, but not in Illuminate\Database\Eloquent\Model.

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...
293
            ->findOrFail($id)->toArray();
294
295
        $this->builder->fields()->filter(function ($field) {
296
            return $field instanceof Field\File;
297
        })->each(function (Field\File $file) use ($data) {
298
            $file->setOriginal($data);
299
300
            $file->destroy();
301
        });
302
    }
303
304
    /**
305
     * Store a new record.
306
     *
307
     * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|\Illuminate\Http\JsonResponse
308
     */
309
    public function store()
310
    {
311
        $data = Input::all();
312
313
        // Handle validation errors.
314
        if ($validationMessages = $this->validationMessages($data)) {
315
            return back()->withInput()->withErrors($validationMessages);
316
        }
317
318
        if (($response = $this->prepare($data)) instanceof Response) {
319
            return $response;
320
        }
321
322 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...
323
            $inserts = $this->prepareInsert($this->updates);
324
325
            foreach ($inserts as $column => $value) {
326
                $this->model->setAttribute($column, $value);
327
            }
328
329
            $this->model->save();
330
331
            $this->updateRelation($this->relations);
332
        });
333
334
        if (($response = $this->complete($this->saved)) instanceof Response) {
335
            return $response;
336
        }
337
338
        if ($response = $this->ajaxResponse(trans('admin.save_succeeded'))) {
339
            return $response;
340
        }
341
342
        return $this->redirectAfterStore();
343
    }
344
345
    /**
346
     * Get ajax response.
347
     *
348
     * @param string $message
349
     *
350
     * @return bool|\Illuminate\Http\JsonResponse
351
     */
352
    protected function ajaxResponse($message)
353
    {
354
        $request = Request::capture();
355
356
        // ajax but not pjax
357
        if ($request->ajax() && !$request->pjax()) {
358
            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...
359
                'status'  => true,
360
                'message' => $message,
361
            ]);
362
        }
363
364
        return false;
365
    }
366
367
    /**
368
     * Prepare input data for insert or update.
369
     *
370
     * @param array $data
371
     *
372
     * @return mixed
373
     */
374
    protected function prepare($data = [])
375
    {
376
        if (($response = $this->callSubmitted()) instanceof Response) {
377
            return $response;
378
        }
379
380
        $this->inputs = array_merge($this->removeIgnoredFields($data), $this->inputs);
381
382
        if (($response = $this->callSaving()) instanceof Response) {
383
            return $response;
384
        }
385
386
        $this->relations = $this->getRelationInputs($this->inputs);
387
388
        $this->updates = array_except($this->inputs, array_keys($this->relations));
389
    }
390
391
    /**
392
     * Remove ignored fields from input.
393
     *
394
     * @param array $input
395
     *
396
     * @return array
397
     */
398
    protected function removeIgnoredFields($input)
399
    {
400
        array_forget($input, $this->ignored);
401
402
        return $input;
403
    }
404
405
    /**
406
     * Get inputs for relations.
407
     *
408
     * @param array $inputs
409
     *
410
     * @return array
411
     */
412
    protected function getRelationInputs($inputs = [])
413
    {
414
        $relations = [];
415
416
        foreach ($inputs as $column => $value) {
417
            if (method_exists($this->model, $column)) {
418
                $relation = call_user_func([$this->model, $column]);
419
420
                if ($relation instanceof Relations\Relation) {
421
                    $relations[$column] = $value;
422
                }
423
            }
424
        }
425
426
        return $relations;
427
    }
428
429
    /**
430
     * Call submitted callback.
431
     *
432
     * @return mixed
433
     */
434
    protected function callSubmitted()
435
    {
436
        if ($this->submitted instanceof Closure) {
437
            return call_user_func($this->submitted, $this);
438
        }
439
    }
440
441
    /**
442
     * Call saving callback.
443
     *
444
     * @return mixed
445
     */
446
    protected function callSaving()
447
    {
448
        if ($this->saving instanceof Closure) {
449
            return call_user_func($this->saving, $this);
450
        }
451
    }
452
453
    /**
454
     * Callback after saving a Model.
455
     *
456
     * @param Closure|null $callback
457
     *
458
     * @return mixed|null
459
     */
460
    protected function complete(Closure $callback = null)
461
    {
462
        if ($callback instanceof Closure) {
463
            return $callback($this);
464
        }
465
    }
466
467
    /**
468
     * Handle update.
469
     *
470
     * @param int $id
471
     *
472
     * @return \Symfony\Component\HttpFoundation\Response
473
     */
474
    public function update($id, $data = null)
475
    {
476
        $data = ($data) ?: Input::all();
477
478
        $isEditable = $this->isEditable($data);
479
480
        $data = $this->handleEditable($data);
481
482
        $data = $this->handleFileDelete($data);
483
484
        if ($this->handleOrderable($id, $data)) {
485
            return response([
0 ignored issues
show
Bug Compatibility introduced by
The expression response(array('status' ...n.update_succeeded'))); of type Illuminate\Http\Response...Routing\ResponseFactory adds the type Illuminate\Contracts\Routing\ResponseFactory to the return on line 485 which is incompatible with the return type documented by Encore\Admin\Form::update of type Symfony\Component\HttpFoundation\Response.
Loading history...
486
                'status'  => true,
487
                'message' => trans('admin.update_succeeded'),
488
            ]);
489
        }
490
491
        /* @var Model $this->model */
492
        $this->model = $this->model->with($this->getRelations())->findOrFail($id);
0 ignored issues
show
Bug introduced by
The method findOrFail does only exist in Illuminate\Database\Eloquent\Builder, but not in Illuminate\Database\Eloquent\Model.

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...
493
494
        $this->setFieldOriginalValue();
495
496
        // Handle validation errors.
497
        if ($validationMessages = $this->validationMessages($data)) {
498
            if (!$isEditable) {
499
                return back()->withInput()->withErrors($validationMessages);
500
            } else {
501
                return response()->json(['errors' => array_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...
502
            }
503
        }
504
505
        if (($response = $this->prepare($data)) instanceof Response) {
506
            return $response;
507
        }
508
509 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...
510
            $updates = $this->prepareUpdate($this->updates);
511
512
            foreach ($updates as $column => $value) {
513
                /* @var Model $this->model */
514
                $this->model->setAttribute($column, $value);
515
            }
516
517
            $this->model->save();
518
519
            $this->updateRelation($this->relations);
520
        });
521
522
        if (($result = $this->complete($this->saved)) instanceof Response) {
523
            return $result;
524
        }
525
526
        if ($response = $this->ajaxResponse(trans('admin.update_succeeded'))) {
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->ajaxResponse(tran...in.update_succeeded')); of type boolean|Illuminate\Http\JsonResponse adds the type boolean to the return on line 527 which is incompatible with the return type documented by Encore\Admin\Form::update of type Symfony\Component\HttpFoundation\Response.
Loading history...
527
            return $response;
528
        }
529
530
        return $this->redirectAfterUpdate($id);
531
    }
532
533
    /**
534
     * Get RedirectResponse after store.
535
     *
536
     * @return \Illuminate\Http\RedirectResponse
537
     */
538
    protected function redirectAfterStore()
539
    {
540
        $resourcesPath = $this->resource(0);
541
542
        $key = $this->model->getKey();
543
544
        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 544 which is incompatible with the return type documented by Encore\Admin\Form::redirectAfterStore of type Illuminate\Http\RedirectResponse.
Loading history...
545
    }
546
547
    /**
548
     * Get RedirectResponse after update.
549
     *
550
     * @param mixed $key
551
     *
552
     * @return \Illuminate\Http\RedirectResponse
553
     */
554
    protected function redirectAfterUpdate($key)
555
    {
556
        $resourcesPath = $this->resource(-1);
557
558
        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 558 which is incompatible with the return type documented by Encore\Admin\Form::redirectAfterUpdate of type Illuminate\Http\RedirectResponse.
Loading history...
559
    }
560
561
    /**
562
     * Get RedirectResponse after data saving.
563
     *
564
     * @param string $resourcesPath
565
     * @param string $key
566
     *
567
     * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
568
     */
569
    protected function redirectAfterSaving($resourcesPath, $key)
570
    {
571
        if (request('after-save') == 1) {
572
            // continue editing
573
            $url = rtrim($resourcesPath, '/')."/{$key}/edit";
574
        } elseif (request('after-save') == 2) {
575
            // view resource
576
            $url = rtrim($resourcesPath, '/')."/{$key}";
577
        } else {
578
            $url = request(Builder::PREVIOUS_URL_KEY) ?: $resourcesPath;
579
        }
580
581
        admin_toastr(trans('admin.save_succeeded'));
582
583
        return redirect($url);
584
    }
585
586
    /**
587
     * Check if request is from editable.
588
     *
589
     * @param array $input
590
     *
591
     * @return bool
592
     */
593
    protected function isEditable(array $input = [])
594
    {
595
        return array_key_exists('_editable', $input);
596
    }
597
598
    /**
599
     * Handle editable update.
600
     *
601
     * @param array $input
602
     *
603
     * @return array
604
     */
605
    protected function handleEditable(array $input = [])
606
    {
607
        if (array_key_exists('_editable', $input)) {
608
            $name = $input['name'];
609
            $value = $input['value'];
610
611
            array_forget($input, ['pk', 'value', 'name']);
612
            array_set($input, $name, $value);
613
        }
614
615
        return $input;
616
    }
617
618
    /**
619
     * @param array $input
620
     *
621
     * @return array
622
     */
623
    protected function handleFileDelete(array $input = [])
624
    {
625
        if (array_key_exists(Field::FILE_DELETE_FLAG, $input)) {
626
            $input[Field::FILE_DELETE_FLAG] = $input['key'];
627
            unset($input['key']);
628
        }
629
630
        Input::replace($input);
631
632
        return $input;
633
    }
634
635
    /**
636
     * Handle orderable update.
637
     *
638
     * @param int   $id
639
     * @param array $input
640
     *
641
     * @return bool
642
     */
643
    protected function handleOrderable($id, array $input = [])
644
    {
645
        if (array_key_exists('_orderable', $input)) {
646
            $model = $this->model->find($id);
647
648
            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...
649
                $input['_orderable'] == 1 ? $model->moveOrderUp() : $model->moveOrderDown();
650
651
                return true;
652
            }
653
        }
654
655
        return false;
656
    }
657
658
    /**
659
     * Update relation data.
660
     *
661
     * @param array $relationsData
662
     *
663
     * @return void
664
     */
665
    protected function updateRelation($relationsData)
666
    {
667
        foreach ($relationsData as $name => $values) {
668
            if (!method_exists($this->model, $name)) {
669
                continue;
670
            }
671
672
            $relation = $this->model->$name();
673
674
            $oneToOneRelation = $relation instanceof Relations\HasOne
675
                || $relation instanceof Relations\MorphOne
676
                || $relation instanceof Relations\BelongsTo;
677
678
            $prepared = $this->prepareUpdate([$name => $values], $oneToOneRelation);
679
680
            if (empty($prepared)) {
681
                continue;
682
            }
683
684
            switch (get_class($relation)) {
685
                case Relations\BelongsToMany::class:
686
                case Relations\MorphToMany::class:
687
                    if (isset($prepared[$name])) {
688
                        $relation->sync($prepared[$name]);
689
                    }
690
                    break;
691
                case Relations\HasOne::class:
692
                case Relations\BelongsTo::class:
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
693
694
                    $related = $this->model->$name;
695
696
                    // if related is empty
697
                    if (is_null($related)) {
698
                        $related = $relation->getRelated();
699
                        $related->{$relation->getForeignKeyName()} = $this->model->{$this->model->getKeyName()};
700
                    }
701
702
                    foreach ($prepared[$name] as $column => $value) {
703
                        $related->setAttribute($column, $value);
704
                    }
705
706
                    $related->save();
707
                    break;
708
                case Relations\MorphOne::class:
709
                    $related = $this->model->$name;
710
                    if (is_null($related)) {
711
                        $related = $relation->make();
712
                    }
713
                    foreach ($prepared[$name] as $column => $value) {
714
                        $related->setAttribute($column, $value);
715
                    }
716
                    $related->save();
717
                    break;
718
                case Relations\HasMany::class:
719
                case Relations\MorphMany::class:
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
720
721
                    foreach ($prepared[$name] as $related) {
722
                        /** @var Relations\Relation $relation */
723
                        $relation = $this->model()->$name();
724
725
                        $keyName = $relation->getRelated()->getKeyName();
726
727
                        $instance = $relation->findOrNew(array_get($related, $keyName));
728
729
                        if ($related[static::REMOVE_FLAG_NAME] == 1) {
730
                            $instance->delete();
731
732
                            continue;
733
                        }
734
735
                        array_forget($related, static::REMOVE_FLAG_NAME);
736
737
                        $instance->fill($related);
738
739
                        $instance->save();
740
                    }
741
742
                    break;
743
            }
744
        }
745
    }
746
747
    /**
748
     * Prepare input data for update.
749
     *
750
     * @param array $updates
751
     * @param bool  $oneToOneRelation If column is one-to-one relation.
752
     *
753
     * @return array
754
     */
755
    protected function prepareUpdate(array $updates, $oneToOneRelation = false)
756
    {
757
        $prepared = [];
758
759
        /** @var Field $field */
760
        foreach ($this->builder->fields() as $field) {
761
            $columns = $field->column();
762
763
            // If column not in input array data, then continue.
764
            if (!array_has($updates, $columns)) {
765
                continue;
766
            }
767
768
            if ($this->invalidColumn($columns, $oneToOneRelation)) {
769
                continue;
770
            }
771
772
            $value = $this->getDataByColumn($updates, $columns);
773
774
            $value = $field->prepare($value);
775
776 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...
777
                foreach ($columns as $name => $column) {
778
                    array_set($prepared, $column, $value[$name]);
779
                }
780
            } elseif (is_string($columns)) {
781
                array_set($prepared, $columns, $value);
782
            }
783
        }
784
785
        return $prepared;
786
    }
787
788
    /**
789
     * @param string|array $columns
790
     * @param bool         $oneToOneRelation
791
     *
792
     * @return bool
793
     */
794
    protected function invalidColumn($columns, $oneToOneRelation = false)
795
    {
796
        foreach ((array) $columns as $column) {
797
            if ((!$oneToOneRelation && Str::contains($column, '.')) ||
798
                ($oneToOneRelation && !Str::contains($column, '.'))) {
799
                return true;
800
            }
801
        }
802
803
        return false;
804
    }
805
806
    /**
807
     * Prepare input data for insert.
808
     *
809
     * @param $inserts
810
     *
811
     * @return array
812
     */
813
    protected function prepareInsert($inserts)
814
    {
815
        if ($this->isHasOneRelation($inserts)) {
816
            $inserts = array_dot($inserts);
817
        }
818
819
        foreach ($inserts as $column => $value) {
820
            if (is_null($field = $this->getFieldByColumn($column))) {
821
                unset($inserts[$column]);
822
                continue;
823
            }
824
825
            $inserts[$column] = $field->prepare($value);
826
        }
827
828
        $prepared = [];
829
830
        foreach ($inserts as $key => $value) {
831
            array_set($prepared, $key, $value);
832
        }
833
834
        return $prepared;
835
    }
836
837
    /**
838
     * Is input data is has-one relation.
839
     *
840
     * @param array $inserts
841
     *
842
     * @return bool
843
     */
844
    protected function isHasOneRelation($inserts)
845
    {
846
        $first = current($inserts);
847
848
        if (!is_array($first)) {
849
            return false;
850
        }
851
852
        if (is_array(current($first))) {
853
            return false;
854
        }
855
856
        return Arr::isAssoc($first);
857
    }
858
859
    /**
860
     * Set submitted callback.
861
     *
862
     * @param Closure $callback
863
     *
864
     * @return void
865
     */
866
    public function submitted(Closure $callback)
867
    {
868
        $this->submitted = $callback;
869
    }
870
871
    /**
872
     * Set saving callback.
873
     *
874
     * @param Closure $callback
875
     *
876
     * @return void
877
     */
878
    public function saving(Closure $callback)
879
    {
880
        $this->saving = $callback;
881
    }
882
883
    /**
884
     * Set saved callback.
885
     *
886
     * @param Closure $callback
887
     *
888
     * @return void
889
     */
890
    public function saved(Closure $callback)
891
    {
892
        $this->saved = $callback;
893
    }
894
895
    /**
896
     * Ignore fields to save.
897
     *
898
     * @param string|array $fields
899
     *
900
     * @return $this
901
     */
902
    public function ignore($fields)
903
    {
904
        $this->ignored = array_merge($this->ignored, (array) $fields);
905
906
        return $this;
907
    }
908
909
    /**
910
     * @param array        $data
911
     * @param string|array $columns
912
     *
913
     * @return array|mixed
914
     */
915 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...
916
    {
917
        if (is_string($columns)) {
918
            return array_get($data, $columns);
919
        }
920
921
        if (is_array($columns)) {
922
            $value = [];
923
            foreach ($columns as $name => $column) {
924
                if (!array_has($data, $column)) {
925
                    continue;
926
                }
927
                $value[$name] = array_get($data, $column);
928
            }
929
930
            return $value;
931
        }
932
    }
933
934
    /**
935
     * Find field object by column.
936
     *
937
     * @param $column
938
     *
939
     * @return mixed
940
     */
941
    protected function getFieldByColumn($column)
942
    {
943
        return $this->builder->fields()->first(
944
            function (Field $field) use ($column) {
945
                if (is_array($field->column())) {
946
                    return in_array($column, $field->column());
947
                }
948
949
                return $field->column() == $column;
950
            }
951
        );
952
    }
953
954
    /**
955
     * Set original data for each field.
956
     *
957
     * @return void
958
     */
959
    protected function setFieldOriginalValue()
960
    {
961
//        static::doNotSnakeAttributes($this->model);
0 ignored issues
show
Unused Code Comprehensibility introduced by
70% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
962
963
        $values = $this->model->toArray();
964
965
        $this->builder->fields()->each(function (Field $field) use ($values) {
966
            $field->setOriginal($values);
967
        });
968
    }
969
970
    /**
971
     * Set all fields value in form.
972
     *
973
     * @param $id
974
     *
975
     * @return void
976
     */
977
    protected function setFieldValue($id)
978
    {
979
        $relations = $this->getRelations();
980
981
        $this->model = $this->model->with($relations)->findOrFail($id);
0 ignored issues
show
Bug introduced by
The method findOrFail does only exist in Illuminate\Database\Eloquent\Builder, but not in Illuminate\Database\Eloquent\Model.

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...
982
983
//        static::doNotSnakeAttributes($this->model);
0 ignored issues
show
Unused Code Comprehensibility introduced by
70% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
984
985
        $data = $this->model->toArray();
986
987
        $this->builder->fields()->each(function (Field $field) use ($data) {
988
            if (!in_array($field->column(), $this->ignored)) {
989
                $field->fill($data);
990
            }
991
        });
992
    }
993
994
    /**
995
     * Don't snake case attributes.
996
     *
997
     * @param Model $model
998
     *
999
     * @return void
1000
     */
1001
    protected static function doNotSnakeAttributes(Model $model)
1002
    {
1003
        $class = get_class($model);
1004
1005
        $class::$snakeAttributes = false;
1006
    }
1007
1008
    /**
1009
     * Get validation messages.
1010
     *
1011
     * @param array $input
1012
     *
1013
     * @return MessageBag|bool
1014
     */
1015
    protected function validationMessages($input)
1016
    {
1017
        $failedValidators = [];
1018
1019
        /** @var Field $field */
1020
        foreach ($this->builder->fields() as $field) {
1021
            if (!$validator = $field->getValidator($input)) {
1022
                continue;
1023
            }
1024
1025
            if (($validator instanceof Validator) && !$validator->passes()) {
1026
                $failedValidators[] = $validator;
1027
            }
1028
        }
1029
1030
        $message = $this->mergeValidationMessages($failedValidators);
1031
1032
        return $message->any() ? $message : false;
1033
    }
1034
1035
    /**
1036
     * Merge validation messages from input validators.
1037
     *
1038
     * @param \Illuminate\Validation\Validator[] $validators
1039
     *
1040
     * @return MessageBag
1041
     */
1042
    protected function mergeValidationMessages($validators)
1043
    {
1044
        $messageBag = new MessageBag();
1045
1046
        foreach ($validators as $validator) {
1047
            $messageBag = $messageBag->merge($validator->messages());
1048
        }
1049
1050
        return $messageBag;
1051
    }
1052
1053
    /**
1054
     * Get all relations of model from callable.
1055
     *
1056
     * @return array
1057
     */
1058
    public function getRelations()
1059
    {
1060
        $relations = $columns = [];
1061
1062
        /** @var Field $field */
1063
        foreach ($this->builder->fields() as $field) {
1064
            $columns[] = $field->column();
1065
        }
1066
1067
        foreach (array_flatten($columns) as $column) {
1068
            if (str_contains($column, '.')) {
1069
                list($relation) = explode('.', $column);
1070
1071
                if (method_exists($this->model, $relation) &&
1072
                    $this->model->$relation() instanceof Relations\Relation
1073
                ) {
1074
                    $relations[] = $relation;
1075
                }
1076
            } elseif (method_exists($this->model, $column) &&
1077
                !method_exists(Model::class, $column)
1078
            ) {
1079
                $relations[] = $column;
1080
            }
1081
        }
1082
1083
        return array_unique($relations);
1084
    }
1085
1086
    /**
1087
     * Set action for form.
1088
     *
1089
     * @param string $action
1090
     *
1091
     * @return $this
1092
     */
1093
    public function setAction($action)
1094
    {
1095
        $this->builder()->setAction($action);
1096
1097
        return $this;
1098
    }
1099
1100
    /**
1101
     * Set field and label width in current form.
1102
     *
1103
     * @param int $fieldWidth
1104
     * @param int $labelWidth
1105
     *
1106
     * @return $this
1107
     */
1108
    public function setWidth($fieldWidth = 8, $labelWidth = 2)
1109
    {
1110
        $this->builder()->fields()->each(function ($field) use ($fieldWidth, $labelWidth) {
1111
            /* @var Field $field  */
1112
            $field->setWidth($fieldWidth, $labelWidth);
1113
        });
1114
1115
        $this->builder()->setWidth($fieldWidth, $labelWidth);
1116
1117
        return $this;
1118
    }
1119
1120
    /**
1121
     * Set view for form.
1122
     *
1123
     * @param string $view
1124
     *
1125
     * @return $this
1126
     */
1127
    public function setView($view)
1128
    {
1129
        $this->builder()->setView($view);
1130
1131
        return $this;
1132
    }
1133
1134
    /**
1135
     * Set title for form.
1136
     *
1137
     * @param string $title
1138
     *
1139
     * @return $this
1140
     */
1141
    public function setTitle($title = '')
1142
    {
1143
        $this->builder()->setTitle($title);
1144
1145
        return $this;
1146
    }
1147
1148
    /**
1149
     * Add a row in form.
1150
     *
1151
     * @param Closure $callback
1152
     *
1153
     * @return $this
1154
     */
1155
    public function row(Closure $callback)
1156
    {
1157
        $this->rows[] = new Row($callback, $this);
1158
1159
        return $this;
1160
    }
1161
1162
    /**
1163
     * Tools setting for form.
1164
     *
1165
     * @param Closure $callback
1166
     */
1167
    public function tools(Closure $callback)
1168
    {
1169
        $callback->call($this, $this->builder->getTools());
1170
    }
1171
1172
    /**
1173
     * Disable form submit.
1174
     *
1175
     * @return $this
1176
     *
1177
     * @deprecated
1178
     */
1179
    public function disableSubmit()
1180
    {
1181
        $this->builder()->getFooter()->disableSubmit();
1182
1183
        return $this;
1184
    }
1185
1186
    /**
1187
     * Disable form reset.
1188
     *
1189
     * @return $this
1190
     *
1191
     * @deprecated
1192
     */
1193
    public function disableReset()
1194
    {
1195
        $this->builder()->getFooter()->disableReset();
1196
1197
        return $this;
1198
    }
1199
1200
    /**
1201
     * Disable View Checkbox on footer.
1202
     *
1203
     * @return $this
1204
     */
1205
    public function disableViewCheck()
1206
    {
1207
        $this->builder()->getFooter()->disableViewCheck();
1208
1209
        return $this;
1210
    }
1211
1212
    /**
1213
     * Disable Editing Checkbox on footer.
1214
     *
1215
     * @return $this
1216
     */
1217
    public function disableEditingCheck()
1218
    {
1219
        $this->builder()->getFooter()->disableEditingCheck();
1220
1221
        return $this;
1222
    }
1223
1224
    /**
1225
     * Footer setting for form.
1226
     *
1227
     * @param Closure $callback
1228
     */
1229
    public function footer(Closure $callback)
1230
    {
1231
        call_user_func($callback, $this->builder()->getFooter());
1232
    }
1233
1234
    /**
1235
     * Get current resource route url.
1236
     *
1237
     * @param int $slice
1238
     *
1239
     * @return string
1240
     */
1241
    public function resource($slice = -2)
1242
    {
1243
        $segments = explode('/', trim(app('request')->getUri(), '/'));
1244
1245
        if ($slice != 0) {
1246
            $segments = array_slice($segments, 0, $slice);
1247
        }
1248
        // # fix #1768
1249
        if ($segments[0] == 'http:' && config('admin.secure') == true) {
1250
            $segments[0] = 'https:';
1251
        }
1252
1253
        return implode('/', $segments);
1254
    }
1255
1256
    /**
1257
     * Render the form contents.
1258
     *
1259
     * @return string
1260
     */
1261
    public function render()
1262
    {
1263
        try {
1264
            return $this->builder->render();
1265
        } catch (\Exception $e) {
1266
            return Handler::renderException($e);
1267
        }
1268
    }
1269
1270
    /**
1271
     * Get or set input data.
1272
     *
1273
     * @param string $key
1274
     * @param null   $value
1275
     *
1276
     * @return array|mixed
1277
     */
1278
    public function input($key, $value = null)
1279
    {
1280
        if (is_null($value)) {
1281
            return array_get($this->inputs, $key);
1282
        }
1283
1284
        return array_set($this->inputs, $key, $value);
1285
    }
1286
1287
    /**
1288
     * Register builtin fields.
1289
     *
1290
     * @return void
1291
     */
1292
    public static function registerBuiltinFields()
1293
    {
1294
        $map = [
1295
            'button'         => Field\Button::class,
1296
            'checkbox'       => Field\Checkbox::class,
1297
            'color'          => Field\Color::class,
1298
            'currency'       => Field\Currency::class,
1299
            'date'           => Field\Date::class,
1300
            'dateRange'      => Field\DateRange::class,
1301
            'datetime'       => Field\Datetime::class,
1302
            'dateTimeRange'  => Field\DatetimeRange::class,
1303
            'datetimeRange'  => Field\DatetimeRange::class,
1304
            'decimal'        => Field\Decimal::class,
1305
            'display'        => Field\Display::class,
1306
            'divider'        => Field\Divide::class,
1307
            'divide'         => Field\Divide::class,
1308
            'embeds'         => Field\Embeds::class,
1309
            'editor'         => Field\Editor::class,
1310
            'email'          => Field\Email::class,
1311
            'file'           => Field\File::class,
1312
            'hasMany'        => Field\HasMany::class,
1313
            'hidden'         => Field\Hidden::class,
1314
            'id'             => Field\Id::class,
1315
            'image'          => Field\Image::class,
1316
            'ip'             => Field\Ip::class,
1317
            'map'            => Field\Map::class,
1318
            'mobile'         => Field\Mobile::class,
1319
            'month'          => Field\Month::class,
1320
            'multipleSelect' => Field\MultipleSelect::class,
1321
            'number'         => Field\Number::class,
1322
            'password'       => Field\Password::class,
1323
            'radio'          => Field\Radio::class,
1324
            'rate'           => Field\Rate::class,
1325
            'select'         => Field\Select::class,
1326
            'slider'         => Field\Slider::class,
1327
            'switch'         => Field\SwitchField::class,
1328
            'text'           => Field\Text::class,
1329
            'textarea'       => Field\Textarea::class,
1330
            'time'           => Field\Time::class,
1331
            'timeRange'      => Field\TimeRange::class,
1332
            'url'            => Field\Url::class,
1333
            'year'           => Field\Year::class,
1334
            'html'           => Field\Html::class,
1335
            'tags'           => Field\Tags::class,
1336
            'icon'           => Field\Icon::class,
1337
            'multipleFile'   => Field\MultipleFile::class,
1338
            'multipleImage'  => Field\MultipleImage::class,
1339
            'captcha'        => Field\Captcha::class,
1340
            'listbox'        => Field\Listbox::class,
1341
        ];
1342
1343
        foreach ($map as $abstract => $class) {
1344
            static::extend($abstract, $class);
1345
        }
1346
    }
1347
1348
    /**
1349
     * Register custom field.
1350
     *
1351
     * @param string $abstract
1352
     * @param string $class
1353
     *
1354
     * @return void
1355
     */
1356
    public static function extend($abstract, $class)
1357
    {
1358
        static::$availableFields[$abstract] = $class;
1359
    }
1360
1361
    /**
1362
     * Remove registered field.
1363
     *
1364
     * @param array|string $abstract
1365
     */
1366
    public static function forget($abstract)
1367
    {
1368
        array_forget(static::$availableFields, $abstract);
1369
    }
1370
1371
    /**
1372
     * Find field class.
1373
     *
1374
     * @param string $method
1375
     *
1376
     * @return bool|mixed
1377
     */
1378 View Code Duplication
    public static function findFieldClass($method)
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...
1379
    {
1380
        $class = array_get(static::$availableFields, $method);
1381
1382
        if (class_exists($class)) {
1383
            return $class;
1384
        }
1385
1386
        return false;
1387
    }
1388
1389
    /**
1390
     * Collect assets required by registered field.
1391
     *
1392
     * @return array
1393
     */
1394
    public static function collectFieldAssets()
1395
    {
1396
        if (!empty(static::$collectedAssets)) {
1397
            return static::$collectedAssets;
1398
        }
1399
1400
        $css = collect();
1401
        $js = collect();
1402
1403
        foreach (static::$availableFields as $field) {
1404
            if (!method_exists($field, 'getAssets')) {
1405
                continue;
1406
            }
1407
1408
            $assets = call_user_func([$field, 'getAssets']);
1409
1410
            $css->push(array_get($assets, 'css'));
1411
            $js->push(array_get($assets, 'js'));
1412
        }
1413
1414
        return static::$collectedAssets = [
1415
            'css' => $css->flatten()->unique()->filter()->toArray(),
1416
            'js'  => $js->flatten()->unique()->filter()->toArray(),
1417
        ];
1418
    }
1419
1420
    /**
1421
     * Getter.
1422
     *
1423
     * @param string $name
1424
     *
1425
     * @return array|mixed
1426
     */
1427
    public function __get($name)
1428
    {
1429
        return $this->input($name);
1430
    }
1431
1432
    /**
1433
     * Setter.
1434
     *
1435
     * @param string $name
1436
     * @param $value
1437
     */
1438
    public function __set($name, $value)
1439
    {
1440
        $this->input($name, $value);
1441
    }
1442
1443
    /**
1444
     * Generate a Field object and add to form builder if Field exists.
1445
     *
1446
     * @param string $method
1447
     * @param array  $arguments
1448
     *
1449
     * @return Field|void
1450
     */
1451
    public function __call($method, $arguments)
1452
    {
1453
        if ($className = static::findFieldClass($method)) {
1454
            $column = array_get($arguments, 0, ''); //[0];
1455
1456
            $element = new $className($column, array_slice($arguments, 1));
1457
1458
            $this->pushField($element);
1459
1460
            return $element;
1461
        }
1462
    }
1463
}
1464