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

Field::addRequiredAttribute()   B

Complexity

Conditions 6
Paths 5

Size

Total Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
nc 5
nop 1
dl 0
loc 22
rs 8.9457
c 0
b 0
f 0
1
<?php
2
3
namespace Encore\Admin\Form;
4
5
use Closure;
6
use Encore\Admin\Admin;
7
use Encore\Admin\Form;
8
use Illuminate\Contracts\Support\Arrayable;
9
use Illuminate\Contracts\Support\Renderable;
10
use Illuminate\Support\Arr;
11
use Illuminate\Support\Str;
12
use Illuminate\Support\Traits\Macroable;
13
14
/**
15
 * Class Field.
16
 */
17
class Field implements Renderable
18
{
19
    use Macroable;
20
21
    const FILE_DELETE_FLAG = '_file_del_';
22
    const FILE_SORT_FLAG = '_file_sort_';
23
24
    /**
25
     * Element id.
26
     *
27
     * @var array|string
28
     */
29
    protected $id;
30
31
    /**
32
     * Element value.
33
     *
34
     * @var mixed
35
     */
36
    protected $value;
37
38
    /**
39
     * Data of all original columns of value.
40
     *
41
     * @var mixed
42
     */
43
    protected $data;
44
45
    /**
46
     * Field original value.
47
     *
48
     * @var mixed
49
     */
50
    protected $original;
51
52
    /**
53
     * Field default value.
54
     *
55
     * @var mixed
56
     */
57
    protected $default;
58
59
    /**
60
     * Element label.
61
     *
62
     * @var string
63
     */
64
    protected $label = '';
65
66
    /**
67
     * Column name.
68
     *
69
     * @var string|array
70
     */
71
    protected $column = '';
72
73
    /**
74
     * Form element name.
75
     *
76
     * @var string
77
     */
78
    protected $elementName = [];
79
80
    /**
81
     * Form element classes.
82
     *
83
     * @var array
84
     */
85
    protected $elementClass = [];
86
87
    /**
88
     * Variables of elements.
89
     *
90
     * @var array
91
     */
92
    protected $variables = [];
93
94
    /**
95
     * Options for specify elements.
96
     *
97
     * @var array
98
     */
99
    protected $options = [];
100
101
    /**
102
     * Checked for specify elements.
103
     *
104
     * @var array
105
     */
106
    protected $checked = [];
107
108
    /**
109
     * Validation rules.
110
     *
111
     * @var array|\Closure
112
     */
113
    protected $rules = [];
114
115
    /**
116
     * The validation rules for creation.
117
     *
118
     * @var array|\Closure
119
     */
120
    public $creationRules = [];
121
122
    /**
123
     * The validation rules for updates.
124
     *
125
     * @var array|\Closure
126
     */
127
    public $updateRules = [];
128
129
    /**
130
     * @var \Closure
131
     */
132
    protected $validator;
133
134
    /**
135
     * Validation messages.
136
     *
137
     * @var array
138
     */
139
    protected $validationMessages = [];
140
141
    /**
142
     * Css required by this field.
143
     *
144
     * @var array
145
     */
146
    protected static $css = [];
147
148
    /**
149
     * Js required by this field.
150
     *
151
     * @var array
152
     */
153
    protected static $js = [];
154
155
    /**
156
     * Script for field.
157
     *
158
     * @var string
159
     */
160
    protected $script = '';
161
162
    /**
163
     * Element attributes.
164
     *
165
     * @var array
166
     */
167
    protected $attributes = [];
168
169
    /**
170
     * Parent form.
171
     *
172
     * @var Form
173
     */
174
    protected $form = null;
175
176
    /**
177
     * View for field to render.
178
     *
179
     * @var string
180
     */
181
    protected $view = '';
182
183
    /**
184
     * Help block.
185
     *
186
     * @var array
187
     */
188
    protected $help = [];
189
190
    /**
191
     * Key for errors.
192
     *
193
     * @var mixed
194
     */
195
    protected $errorKey;
196
197
    /**
198
     * Placeholder for this field.
199
     *
200
     * @var string|array
201
     */
202
    protected $placeholder;
203
204
    /**
205
     * Width for label and field.
206
     *
207
     * @var array
208
     */
209
    protected $width = [
210
        'label' => 2,
211
        'field' => 8,
212
    ];
213
214
    /**
215
     * If the form horizontal layout.
216
     *
217
     * @var bool
218
     */
219
    protected $horizontal = true;
220
221
    /**
222
     * column data format.
223
     *
224
     * @var \Closure
225
     */
226
    protected $customFormat = null;
227
228
    /**
229
     * @var bool
230
     */
231
    protected $display = true;
232
233
    /**
234
     * @var array
235
     */
236
    protected $labelClass = [];
237
238
    /**
239
     * @var array
240
     */
241
    protected $groupClass = [];
242
243
    /**
244
     * @var \Closure
245
     */
246
    protected $callback;
247
248
    /**
249
     * @var bool
250
     */
251
    public $isJsonType = false;
252
253
    /**
254
     * Field constructor.
255
     *
256
     * @param       $column
257
     * @param array $arguments
258
     */
259
    public function __construct($column = '', $arguments = [])
260
    {
261
        $this->column = $this->formatColumn($column);
262
        $this->label = $this->formatLabel($arguments);
263
        $this->id = $this->formatId($column);
264
    }
265
266
    /**
267
     * Get assets required by this field.
268
     *
269
     * @return array
270
     */
271
    public static function getAssets()
272
    {
273
        return [
274
            'css' => static::$css,
275
            'js'  => static::$js,
276
        ];
277
    }
278
279
    /**
280
     * Format the field column name.
281
     *
282
     * @param string $column
283
     *
284
     * @return mixed|string
285
     */
286
    protected function formatColumn($column = '')
287
    {
288
        if (Str::contains($column, '->')) {
289
            $this->isJsonType = true;
290
291
            $column = str_replace('->', '.', $column);
292
        }
293
294
        return $column;
295
    }
296
297
    /**
298
     * Format the field element id.
299
     *
300
     * @param string|array $column
301
     *
302
     * @return string|array
303
     */
304
    protected function formatId($column)
305
    {
306
        return str_replace('.', '_', $column);
307
    }
308
309
    /**
310
     * Format the label value.
311
     *
312
     * @param array $arguments
313
     *
314
     * @return string
315
     */
316
    protected function formatLabel($arguments = []): string
317
    {
318
        $column = is_array($this->column) ? current($this->column) : $this->column;
319
320
        $label = $arguments[0] ?? ucfirst($column);
321
322
        return str_replace(['.', '_', '->'], ' ', $label);
323
    }
324
325
    /**
326
     * Format the name of the field.
327
     *
328
     * @param string $column
329
     *
330
     * @return array|mixed|string
331
     */
332
    protected function formatName($column)
333
    {
334
        if (is_string($column)) {
335
            if (Str::contains($column, '->')) {
336
                $name = explode('->', $column);
337
            } else {
338
                $name = explode('.', $column);
339
            }
340
341
            if (count($name) === 1) {
342
                return $name[0];
343
            }
344
345
            $html = array_shift($name);
346
            foreach ($name as $piece) {
347
                $html .= "[$piece]";
348
            }
349
350
            return $html;
351
        }
352
353
        if (is_array($this->column)) {
354
            $names = [];
355
            foreach ($this->column as $key => $name) {
356
                $names[$key] = $this->formatName($name);
357
            }
358
359
            return $names;
360
        }
361
362
        return '';
363
    }
364
365
    /**
366
     * Set form element name.
367
     *
368
     * @param string $name
369
     *
370
     * @return $this
371
     *
372
     * @author Edwin Hui
373
     */
374
    public function setElementName($name): self
375
    {
376
        $this->elementName = $name;
377
378
        return $this;
379
    }
380
381
    /**
382
     * Fill data to the field.
383
     *
384
     * @param array $data
385
     *
386
     * @return void
387
     */
388 View Code Duplication
    public function fill($data)
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...
389
    {
390
        $this->data = $data;
391
392
        if (is_array($this->column)) {
393
            foreach ($this->column as $key => $column) {
394
                $this->value[$key] = Arr::get($data, $column);
395
            }
396
397
            return;
398
        }
399
400
        $this->value = Arr::get($data, $this->column);
401
402
        $this->formatValue();
403
    }
404
405
    /**
406
     * Format value by passing custom formater.
407
     */
408
    protected function formatValue()
409
    {
410
        if (isset($this->customFormat) && $this->customFormat instanceof \Closure) {
411
            $this->value = call_user_func($this->customFormat, $this->value);
412
        }
413
    }
414
415
    /**
416
     * custom format form column data when edit.
417
     *
418
     * @param \Closure $call
419
     *
420
     * @return $this
421
     */
422
    public function customFormat(\Closure $call): self
423
    {
424
        $this->customFormat = $call;
425
426
        return $this;
427
    }
428
429
    /**
430
     * Set original value to the field.
431
     *
432
     * @param array $data
433
     *
434
     * @return void
435
     */
436 View Code Duplication
    public function setOriginal($data)
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...
437
    {
438
        if (is_array($this->column)) {
439
            foreach ($this->column as $key => $column) {
440
                $this->original[$key] = Arr::get($data, $column);
441
            }
442
443
            return;
444
        }
445
446
        $this->original = Arr::get($data, $this->column);
447
    }
448
449
    /**
450
     * @param Form $form
451
     *
452
     * @return $this
453
     */
454
    public function setForm(Form $form = null)
455
    {
456
        $this->form = $form;
457
458
        return $this;
459
    }
460
461
    /**
462
     * Set width for field and label.
463
     *
464
     * @param int $field
465
     * @param int $label
466
     *
467
     * @return $this
468
     */
469
    public function setWidth($field = 8, $label = 2): self
470
    {
471
        $this->width = [
472
            'label' => $label,
473
            'field' => $field,
474
        ];
475
476
        return $this;
477
    }
478
479
    /**
480
     * Set the field options.
481
     *
482
     * @param array $options
483
     *
484
     * @return $this
485
     */
486 View Code Duplication
    public function options($options = [])
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...
487
    {
488
        if ($options instanceof Arrayable) {
489
            $options = $options->toArray();
490
        }
491
492
        $this->options = array_merge($this->options, $options);
493
494
        return $this;
495
    }
496
497
    /**
498
     * Set the field option checked.
499
     *
500
     * @param array $checked
501
     *
502
     * @return $this
503
     */
504 View Code Duplication
    public function checked($checked = [])
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...
505
    {
506
        if ($checked instanceof Arrayable) {
507
            $checked = $checked->toArray();
508
        }
509
510
        $this->checked = array_merge($this->checked, $checked);
511
512
        return $this;
513
    }
514
515
    /**
516
     * Add `required` attribute to current field if has required rule,
517
     * except file and image fields.
518
     *
519
     * @param array $rules
520
     */
521
    protected function addRequiredAttribute($rules)
522
    {
523
        if (!is_array($rules)) {
524
            return;
525
        }
526
527
        if (!in_array('required', $rules, true)) {
528
            return;
529
        }
530
531
        // Only text field has `required` attribute.
532
        if (!$this instanceof Form\Field\Text) {
533
            return;
534
        }
535
536
        //do not use required attribute with tabs
537
        if ($this->form && $this->form->getTab()) {
538
            return;
539
        }
540
541
        $this->required();
542
    }
543
544
    /**
545
     * If has `required` rule, add required attribute to this field.
546
     */
547
    protected function addRequiredAttributeFromRules()
548
    {
549
        if ($this->data === null) {
550
            // Create page
551
            $rules = $this->creationRules ?: $this->rules;
552
        } else {
553
            // Update page
554
            $rules = $this->updateRules ?: $this->rules;
555
        }
556
557
        $this->addRequiredAttribute($rules);
0 ignored issues
show
Bug introduced by
It seems like $rules can also be of type object<Closure>; however, Encore\Admin\Form\Field::addRequiredAttribute() 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...
558
    }
559
560
    /**
561
     * Format validation rules.
562
     *
563
     * @param array|string $rules
564
     *
565
     * @return array
566
     */
567
    protected function formatRules($rules): array
568
    {
569
        if (is_string($rules)) {
570
            $rules = array_filter(explode('|', $rules));
571
        }
572
573
        return array_filter((array) $rules);
574
    }
575
576
    /**
577
     * @param string|array|Closure $input
578
     * @param string|array         $original
579
     *
580
     * @return array|Closure
581
     */
582
    protected function mergeRules($input, $original)
583
    {
584
        if ($input instanceof Closure) {
585
            $rules = $input;
586
        } else {
587
            if (!empty($original)) {
588
                $original = $this->formatRules($original);
589
            }
590
591
            $rules = array_merge($original, $this->formatRules($input));
592
        }
593
594
        return $rules;
595
    }
596
597
    /**
598
     * Set the validation rules for the field.
599
     *
600
     * @param array|callable|string $rules
601
     * @param array                 $messages
602
     *
603
     * @return $this
604
     */
605 View Code Duplication
    public function rules($rules = null, $messages = []): 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...
606
    {
607
        $this->rules = $this->mergeRules($rules, $this->rules);
0 ignored issues
show
Documentation introduced by
$rules is of type callable|null, but the function expects a string|array|object<Closure>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Bug introduced by
It seems like $this->rules can also be of type object<Closure>; however, Encore\Admin\Form\Field::mergeRules() does only seem to accept string|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...
608
609
        $this->setValidationMessages('default', $messages);
610
611
        return $this;
612
    }
613
614
    /**
615
     * Set the update validation rules for the field.
616
     *
617
     * @param array|callable|string $rules
618
     * @param array                 $messages
619
     *
620
     * @return $this
621
     */
622 View Code Duplication
    public function updateRules($rules = null, $messages = []): 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...
623
    {
624
        $this->updateRules = $this->mergeRules($rules, $this->updateRules);
0 ignored issues
show
Documentation introduced by
$rules is of type callable|null, but the function expects a string|array|object<Closure>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Bug introduced by
It seems like $this->updateRules can also be of type object<Closure>; however, Encore\Admin\Form\Field::mergeRules() does only seem to accept string|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...
625
626
        $this->setValidationMessages('update', $messages);
627
628
        return $this;
629
    }
630
631
    /**
632
     * Set the creation validation rules for the field.
633
     *
634
     * @param array|callable|string $rules
635
     * @param array                 $messages
636
     *
637
     * @return $this
638
     */
639
    public function creationRules($rules = null, $messages = []): self
640
    {
641
        $this->creationRules = $this->mergeRules($rules, $this->creationRules);
0 ignored issues
show
Documentation introduced by
$rules is of type callable|null, but the function expects a string|array|object<Closure>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Bug introduced by
It seems like $this->creationRules can also be of type object<Closure>; however, Encore\Admin\Form\Field::mergeRules() does only seem to accept string|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...
642
643
        $this->setValidationMessages('creation', $messages);
644
645
        return $this;
646
    }
647
648
    /**
649
     * Set validation messages for column.
650
     *
651
     * @param string $key
652
     * @param array  $messages
653
     *
654
     * @return $this
655
     */
656
    public function setValidationMessages($key, array $messages): self
657
    {
658
        $this->validationMessages[$key] = $messages;
659
660
        return $this;
661
    }
662
663
    /**
664
     * Get validation messages for the field.
665
     *
666
     * @return array|mixed
667
     */
668
    public function getValidationMessages()
669
    {
670
        // Default validation message.
671
        $messages = $this->validationMessages['default'] ?? [];
672
673
        if (request()->isMethod('POST')) {
674
            $messages = $this->validationMessages['creation'] ?? $messages;
675
        } elseif (request()->isMethod('PUT')) {
676
            $messages = $this->validationMessages['update'] ?? $messages;
677
        }
678
679
        return $messages;
680
    }
681
682
    /**
683
     * Get field validation rules.
684
     *
685
     * @return string
686
     */
687
    protected function getRules()
688
    {
689
        if (request()->isMethod('POST')) {
690
            $rules = $this->creationRules ?: $this->rules;
691
        } elseif (request()->isMethod('PUT')) {
692
            $rules = $this->updateRules ?: $this->rules;
693
        } else {
694
            $rules = $this->rules;
695
        }
696
697
        if ($rules instanceof \Closure) {
698
            $rules = $rules->call($this, $this->form);
699
        }
700
701
        if (is_string($rules)) {
702
            $rules = array_filter(explode('|', $rules));
703
        }
704
705
        if (!$this->form) {
706
            return $rules;
707
        }
708
709
        if (!$id = $this->form->model()->getKey()) {
710
            return $rules;
711
        }
712
713
        if (is_array($rules)) {
714
            foreach ($rules as &$rule) {
715
                if (is_string($rule)) {
716
                    $rule = str_replace('{{id}}', $id, $rule);
717
                }
718
            }
719
        }
720
721
        return $rules;
722
    }
723
724
    /**
725
     * Remove a specific rule by keyword.
726
     *
727
     * @param string $rule
728
     *
729
     * @return void
730
     */
731
    protected function removeRule($rule)
732
    {
733
        if (is_array($this->rules)) {
734
            array_delete($this->rules, $rule);
735
736
            return;
737
        }
738
739
        if (!is_string($this->rules)) {
740
            return;
741
        }
742
743
        $pattern = "/{$rule}[^\|]?(\||$)/";
744
        $this->rules = preg_replace($pattern, '', $this->rules, -1);
0 ignored issues
show
Documentation Bug introduced by
It seems like preg_replace($pattern, '', $this->rules, -1) of type string is incompatible with the declared type array|object<Closure> of property $rules.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
745
    }
746
747
    /**
748
     * Set field validator.
749
     *
750
     * @param callable $validator
751
     *
752
     * @return $this
753
     */
754
    public function validator(callable $validator): self
755
    {
756
        $this->validator = $validator;
0 ignored issues
show
Documentation Bug introduced by
It seems like $validator of type callable is incompatible with the declared type object<Closure> of property $validator.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
757
758
        return $this;
759
    }
760
761
    /**
762
     * Get key for error message.
763
     *
764
     * @return string
765
     */
766
    public function getErrorKey(): string
767
    {
768
        return $this->errorKey ?: $this->column;
769
    }
770
771
    /**
772
     * Set key for error message.
773
     *
774
     * @param string $key
775
     *
776
     * @return $this
777
     */
778
    public function setErrorKey($key): self
779
    {
780
        $this->errorKey = $key;
781
782
        return $this;
783
    }
784
785
    /**
786
     * Set or get value of the field.
787
     *
788
     * @param null $value
789
     *
790
     * @return mixed
791
     */
792
    public function value($value = null)
793
    {
794
        if ($value === null) {
795
            return $this->value ?? $this->getDefault();
796
        }
797
798
        $this->value = $value;
799
800
        return $this;
801
    }
802
803
    /**
804
     * Set or get data.
805
     *
806
     * @param array $data
807
     *
808
     * @return $this
809
     */
810
    public function data(array $data = null): self
811
    {
812
        if ($data === null) {
813
            return $this->data;
814
        }
815
816
        $this->data = $data;
817
818
        return $this;
819
    }
820
821
    /**
822
     * Set default value for field.
823
     *
824
     * @param $default
825
     *
826
     * @return $this
827
     */
828
    public function default($default): self
829
    {
830
        $this->default = $default;
831
832
        return $this;
833
    }
834
835
    /**
836
     * Get default value.
837
     *
838
     * @return mixed
839
     */
840
    public function getDefault()
841
    {
842
        if ($this->default instanceof \Closure) {
843
            return call_user_func($this->default, $this->form);
844
        }
845
846
        return $this->default;
847
    }
848
849
    /**
850
     * Set help block for current field.
851
     *
852
     * @param string $text
853
     * @param string $icon
854
     *
855
     * @return $this
856
     */
857
    public function help($text = '', $icon = 'fa-info-circle'): self
858
    {
859
        $this->help = compact('text', 'icon');
860
861
        return $this;
862
    }
863
864
    /**
865
     * Get column of the field.
866
     *
867
     * @return string|array
868
     */
869
    public function column()
870
    {
871
        return $this->column;
872
    }
873
874
    /**
875
     * Get label of the field.
876
     *
877
     * @return string
878
     */
879
    public function label(): string
880
    {
881
        return $this->label;
882
    }
883
884
    /**
885
     * Get original value of the field.
886
     *
887
     * @return mixed
888
     */
889
    public function original()
890
    {
891
        return $this->original;
892
    }
893
894
    /**
895
     * Get validator for this field.
896
     *
897
     * @param array $input
898
     *
899
     * @return bool|\Illuminate\Contracts\Validation\Validator|mixed
900
     */
901
    public function getValidator(array $input)
902
    {
903
        if ($this->validator) {
904
            return $this->validator->call($this, $input);
905
        }
906
907
        $rules = $attributes = [];
908
909
        if (!$fieldRules = $this->getRules()) {
910
            return false;
911
        }
912
913
        if (is_string($this->column)) {
914
            if (!Arr::has($input, $this->column)) {
915
                return false;
916
            }
917
918
            $input = $this->sanitizeInput($input, $this->column);
919
920
            $rules[$this->column] = $fieldRules;
921
            $attributes[$this->column] = $this->label;
922
        }
923
924
        if (is_array($this->column)) {
925
            foreach ($this->column as $key => $column) {
926
                if (!array_key_exists($column, $input)) {
927
                    continue;
928
                }
929
                $input[$column.$key] = Arr::get($input, $column);
930
                $rules[$column.$key] = $fieldRules;
931
                $attributes[$column.$key] = $this->label."[$column]";
932
            }
933
        }
934
935
        return \validator($input, $rules, $this->getValidationMessages(), $attributes);
936
    }
937
938
    /**
939
     * Sanitize input data.
940
     *
941
     * @param array  $input
942
     * @param string $column
943
     *
944
     * @return array
945
     */
946
    protected function sanitizeInput($input, $column)
947
    {
948
        if ($this instanceof Field\MultipleSelect) {
949
            $value = Arr::get($input, $column);
950
            Arr::set($input, $column, array_filter($value));
951
        }
952
953
        return $input;
954
    }
955
956
    /**
957
     * Add html attributes to elements.
958
     *
959
     * @param array|string $attribute
960
     * @param mixed        $value
961
     *
962
     * @return $this
963
     */
964
    public function attribute($attribute, $value = null): self
965
    {
966
        if (is_array($attribute)) {
967
            $this->attributes = array_merge($this->attributes, $attribute);
968
        } else {
969
            $this->attributes[$attribute] = (string) $value;
970
        }
971
972
        return $this;
973
    }
974
975
    /**
976
     * Remove html attributes from elements.
977
     *
978
     * @param array|string $attribute
979
     *
980
     * @return $this
981
     */
982
    public function removeAttribute($attribute): self
983
    {
984
        Arr::forget($this->attributes, $attribute);
985
986
        return $this;
987
    }
988
989
    /**
990
     * Set Field style.
991
     *
992
     * @param string $attr
993
     * @param string $value
994
     *
995
     * @return $this
996
     */
997
    public function style($attr, $value): self
998
    {
999
        return $this->attribute('style', "{$attr}: {$value}");
1000
    }
1001
1002
    /**
1003
     * Set Field width.
1004
     *
1005
     * @param string $width
1006
     *
1007
     * @return $this
1008
     */
1009
    public function width($width): self
1010
    {
1011
        return $this->style('width', $width);
1012
    }
1013
1014
    /**
1015
     * Specifies a regular expression against which to validate the value of the input.
1016
     *
1017
     * @param string $regexp
1018
     *
1019
     * @return $this
1020
     */
1021
    public function pattern($regexp): self
1022
    {
1023
        return $this->attribute('pattern', $regexp);
1024
    }
1025
1026
    /**
1027
     * set the input filed required.
1028
     *
1029
     * @param bool $isLabelAsterisked
1030
     *
1031
     * @return $this
1032
     */
1033
    public function required($isLabelAsterisked = true): self
1034
    {
1035
        if ($isLabelAsterisked) {
1036
            $this->setLabelClass(['asterisk']);
1037
        }
1038
1039
        return $this->attribute('required', true);
1040
    }
1041
1042
    /**
1043
     * Set the field automatically get focus.
1044
     *
1045
     * @return $this
1046
     */
1047
    public function autofocus(): self
1048
    {
1049
        return $this->attribute('autofocus', true);
1050
    }
1051
1052
    /**
1053
     * Set the field as readonly mode.
1054
     *
1055
     * @return $this
1056
     */
1057
    public function readonly()
1058
    {
1059
        return $this->attribute('readonly', true);
1060
    }
1061
1062
    /**
1063
     * Set field as disabled.
1064
     *
1065
     * @return $this
1066
     */
1067
    public function disable(): self
1068
    {
1069
        return $this->attribute('disabled', true);
1070
    }
1071
1072
    /**
1073
     * Set field placeholder.
1074
     *
1075
     * @param string $placeholder
1076
     *
1077
     * @return $this
1078
     */
1079
    public function placeholder($placeholder = ''): self
1080
    {
1081
        $this->placeholder = $placeholder;
1082
1083
        return $this;
1084
    }
1085
1086
    /**
1087
     * Get placeholder.
1088
     *
1089
     * @return string
1090
     */
1091
    public function getPlaceholder(): string
1092
    {
1093
        return $this->placeholder ?: trans('admin.input').' '.$this->label;
1094
    }
1095
1096
    /**
1097
     * Prepare for a field value before update or insert.
1098
     *
1099
     * @param $value
1100
     *
1101
     * @return mixed
1102
     */
1103
    public function prepare($value)
1104
    {
1105
        return $value;
1106
    }
1107
1108
    /**
1109
     * Format the field attributes.
1110
     *
1111
     * @return string
1112
     */
1113 View Code Duplication
    protected function formatAttributes(): string
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...
1114
    {
1115
        $html = [];
1116
1117
        foreach ($this->attributes as $name => $value) {
1118
            $html[] = $name.'="'.e($value).'"';
1119
        }
1120
1121
        return implode(' ', $html);
1122
    }
1123
1124
    /**
1125
     * @return $this
1126
     */
1127
    public function disableHorizontal(): self
1128
    {
1129
        $this->horizontal = false;
1130
1131
        return $this;
1132
    }
1133
1134
    /**
1135
     * @return array
1136
     */
1137
    public function getViewElementClasses(): array
1138
    {
1139
        if ($this->horizontal) {
1140
            return [
1141
                'label'      => "col-sm-{$this->width['label']} {$this->getLabelClass()}",
1142
                'field'      => "col-sm-{$this->width['field']}",
1143
                'form-group' => $this->getGroupClass(true),
1144
            ];
1145
        }
1146
1147
        return ['label' => $this->getLabelClass(), 'field' => '', 'form-group' => ''];
1148
    }
1149
1150
    /**
1151
     * Set form element class.
1152
     *
1153
     * @param string|array $class
1154
     *
1155
     * @return $this
1156
     */
1157
    public function setElementClass($class): self
1158
    {
1159
        $this->elementClass = array_merge($this->elementClass, (array) $class);
1160
1161
        return $this;
1162
    }
1163
1164
    /**
1165
     * Get element class.
1166
     *
1167
     * @return array
1168
     */
1169
    public function getElementClass(): array
1170
    {
1171
        if (!$this->elementClass) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->elementClass of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1172
            $name = $this->elementName ?: $this->formatName($this->column);
0 ignored issues
show
Bug introduced by
It seems like $this->column can also be of type array; however, Encore\Admin\Form\Field::formatName() does only seem to accept string, 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...
1173
1174
            $this->elementClass = (array) str_replace(['[', ']'], '_', $name);
1175
        }
1176
1177
        return $this->elementClass;
1178
    }
1179
1180
    /**
1181
     * Get element class string.
1182
     *
1183
     * @return mixed
1184
     */
1185 View Code Duplication
    protected function getElementClassString()
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...
1186
    {
1187
        $elementClass = $this->getElementClass();
1188
1189
        if (Arr::isAssoc($elementClass)) {
1190
            $classes = [];
1191
1192
            foreach ($elementClass as $index => $class) {
1193
                $classes[$index] = is_array($class) ? implode(' ', $class) : $class;
1194
            }
1195
1196
            return $classes;
1197
        }
1198
1199
        return implode(' ', $elementClass);
1200
    }
1201
1202
    /**
1203
     * Get element class selector.
1204
     *
1205
     * @return string|array
1206
     */
1207 View Code Duplication
    protected function getElementClassSelector()
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...
1208
    {
1209
        $elementClass = $this->getElementClass();
1210
1211
        if (Arr::isAssoc($elementClass)) {
1212
            $classes = [];
1213
1214
            foreach ($elementClass as $index => $class) {
1215
                $classes[$index] = '.'.(is_array($class) ? implode('.', $class) : $class);
1216
            }
1217
1218
            return $classes;
1219
        }
1220
1221
        return '.'.implode('.', $elementClass);
1222
    }
1223
1224
    /**
1225
     * Add the element class.
1226
     *
1227
     * @param $class
1228
     *
1229
     * @return $this
1230
     */
1231
    public function addElementClass($class): self
1232
    {
1233
        if (is_array($class) || is_string($class)) {
1234
            $this->elementClass = array_unique(array_merge($this->elementClass, (array) $class));
1235
        }
1236
1237
        return $this;
1238
    }
1239
1240
    /**
1241
     * Remove element class.
1242
     *
1243
     * @param $class
1244
     *
1245
     * @return $this
1246
     */
1247
    public function removeElementClass($class): self
1248
    {
1249
        $delClass = [];
1250
1251
        if (is_string($class) || is_array($class)) {
1252
            $delClass = (array) $class;
1253
        }
1254
1255
        foreach ($delClass as $del) {
1256
            if (($key = array_search($del, $this->elementClass, true)) !== false) {
1257
                unset($this->elementClass[$key]);
1258
            }
1259
        }
1260
1261
        return $this;
1262
    }
1263
1264
    /**
1265
     * Set form group class.
1266
     *
1267
     * @param string|array $class
1268
     *
1269
     * @return $this
1270
     */
1271
    public function setGroupClass($class)
1272
    : self
1273
    {
1274
        if (is_array($class)) {
1275
            $this->groupClass = array_merge($this->groupClass, $class);
1276
        } else {
1277
            $this->groupClass[] = $class;
1278
        }
1279
1280
        return $this;
1281
    }
1282
1283
    /**
1284
     * Get element class.
1285
     *
1286
     * @param bool $default
1287
     *
1288
     * @return string
1289
     */
1290
    protected function getGroupClass($default = false)
1291
    : string
1292
    {
1293
        return ($default ? 'form-group ' : '').implode(' ', array_filter($this->groupClass));
1294
    }
1295
1296
    /**
1297
     * reset field className.
1298
     *
1299
     * @param string $className
1300
     * @param string $resetClassName
1301
     *
1302
     * @return $this
1303
     */
1304
    public function resetElementClassName(string $className, string $resetClassName): self
1305
    {
1306
        if (($key = array_search($className, $this->getElementClass())) !== false) {
1307
            $this->elementClass[$key] = $resetClassName;
1308
        }
1309
1310
        return $this;
1311
    }
1312
1313
    /**
1314
     * Add variables to field view.
1315
     *
1316
     * @param array $variables
1317
     *
1318
     * @return $this
1319
     */
1320
    protected function addVariables(array $variables = []): self
1321
    {
1322
        $this->variables = array_merge($this->variables, $variables);
1323
1324
        return $this;
1325
    }
1326
1327
    /**
1328
     * @return string
1329
     */
1330
    public function getLabelClass()
1331
    : string
1332
    {
1333
        return implode(' ', $this->labelClass);
1334
    }
1335
1336
    /**
1337
     * @param array $labelClass
1338
     *
1339
     * @return self
1340
     */
1341
    public function setLabelClass(array $labelClass)
1342
    : self
1343
    {
1344
        $this->labelClass = $labelClass;
1345
1346
        return $this;
1347
    }
1348
1349
    /**
1350
     * Get the view variables of this field.
1351
     *
1352
     * @return array
1353
     */
1354
    public function variables(): array
1355
    {
1356
        return array_merge($this->variables, [
1357
            'id'          => $this->id,
1358
            'name'        => $this->elementName ?: $this->formatName($this->column),
0 ignored issues
show
Bug introduced by
It seems like $this->column can also be of type array; however, Encore\Admin\Form\Field::formatName() does only seem to accept string, 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...
1359
            'help'        => $this->help,
1360
            'class'       => $this->getElementClassString(),
1361
            'value'       => $this->value(),
1362
            'label'       => $this->label,
1363
            'viewClass'   => $this->getViewElementClasses(),
1364
            'column'      => $this->column,
1365
            'errorKey'    => $this->getErrorKey(),
1366
            'attributes'  => $this->formatAttributes(),
1367
            'placeholder' => $this->getPlaceholder(),
1368
        ]);
1369
    }
1370
1371
    /**
1372
     * Get view of this field.
1373
     *
1374
     * @return string
1375
     */
1376
    public function getView(): string
1377
    {
1378
        if (!empty($this->view)) {
1379
            return $this->view;
1380
        }
1381
1382
        $class = explode('\\', static::class);
1383
1384
        return 'admin::form.'.strtolower(end($class));
1385
    }
1386
1387
    /**
1388
     * Set view of current field.
1389
     *
1390
     * @param string $view
1391
     *
1392
     * @return string
1393
     */
1394
    public function setView($view): string
1395
    {
1396
        $this->view = $view;
1397
1398
        return $this;
1399
    }
1400
1401
    /**
1402
     * Get script of current field.
1403
     *
1404
     * @return string
1405
     */
1406
    public function getScript(): string
1407
    {
1408
        return $this->script;
1409
    }
1410
1411
    /**
1412
     * Set script of current field.
1413
     *
1414
     * @param string $script
1415
     *
1416
     * @return $this
1417
     */
1418
    public function setScript($script): self
1419
    {
1420
        $this->script = $script;
1421
1422
        return $this;
1423
    }
1424
1425
    /**
1426
     * To set this field should render or not.
1427
     *
1428
     * @param bool $display
1429
     *
1430
     * @return $this
1431
     */
1432
    public function setDisplay(bool $display): self
1433
    {
1434
        $this->display = $display;
1435
1436
        return $this;
1437
    }
1438
1439
    /**
1440
     * If this field should render.
1441
     *
1442
     * @return bool
1443
     */
1444
    protected function shouldRender(): bool
1445
    {
1446
        if (!$this->display) {
1447
            return false;
1448
        }
1449
1450
        return true;
1451
    }
1452
1453
    /**
1454
     * @param \Closure $callback
1455
     *
1456
     * @return \Encore\Admin\Form\Field
1457
     */
1458
    public function with(Closure $callback): self
1459
    {
1460
        $this->callback = $callback;
1461
1462
        return $this;
1463
    }
1464
1465
    /**
1466
     * Render this filed.
1467
     *
1468
     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View|string
1469
     */
1470
    public function render()
1471
    {
1472
        if (!$this->shouldRender()) {
1473
            return '';
1474
        }
1475
1476
        $this->addRequiredAttributeFromRules();
1477
1478
        if ($this->callback instanceof Closure) {
1479
            $this->value = $this->callback->call($this->form->model(), $this->value, $this);
1480
        }
1481
1482
        Admin::script($this->script);
1483
1484
        return view($this->getView(), $this->variables());
0 ignored issues
show
Bug Compatibility introduced by
The expression view($this->getView(), $this->variables()); of type Illuminate\View\View|Ill...\Contracts\View\Factory adds the type Illuminate\Contracts\View\Factory to the return on line 1484 which is incompatible with the return type declared by the interface Illuminate\Contracts\Support\Renderable::render of type string.
Loading history...
1485
    }
1486
1487
    /**
1488
     * @return string
1489
     */
1490
    public function __toString()
1491
    {
1492
        return $this->render()->render();
0 ignored issues
show
Bug introduced by
The method render does only exist in Illuminate\View\View, but not in Illuminate\Contracts\View\Factory.

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...
1493
    }
1494
}
1495