Completed
Push — master ( f2fcb0...d4b67c )
by Song
03:48 queued 51s
created

Form::description()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Encore\Admin\Widgets;
4
5
use Closure;
6
use Encore\Admin\Facades\Admin;
7
use Encore\Admin\Form as BaseForm;
8
use Encore\Admin\Form\Field;
9
use Encore\Admin\Layout\Content;
10
use Illuminate\Contracts\Support\Arrayable;
11
use Illuminate\Contracts\Support\Renderable;
12
use Illuminate\Http\Request;
13
use Illuminate\Support\Arr;
14
use Illuminate\Support\MessageBag;
15
use Illuminate\Validation\Validator;
16
17
/**
18
 * Class Form.
19
 *
20
 * @method Field\Text           text($name, $label = '')
21
 * @method Field\Password       password($name, $label = '')
22
 * @method Field\Checkbox       checkbox($name, $label = '')
23
 * @method Field\CheckboxButton checkboxButton($name, $label = '')
24
 * @method Field\CheckboxCard   checkboxCard($name, $label = '')
25
 * @method Field\Radio          radio($name, $label = '')
26
 * @method Field\RadioButton    radioButton($name, $label = '')
27
 * @method Field\RadioCard      radioCard($name, $label = '')
28
 * @method Field\Select         select($name, $label = '')
29
 * @method Field\MultipleSelect multipleSelect($name, $label = '')
30
 * @method Field\Textarea       textarea($name, $label = '')
31
 * @method Field\Hidden         hidden($name, $label = '')
32
 * @method Field\Id             id($name, $label = '')
33
 * @method Field\Ip             ip($name, $label = '')
34
 * @method Field\Url            url($name, $label = '')
35
 * @method Field\Color          color($name, $label = '')
36
 * @method Field\Email          email($name, $label = '')
37
 * @method Field\Mobile         mobile($name, $label = '')
38
 * @method Field\Slider         slider($name, $label = '')
39
 * @method Field\File           file($name, $label = '')
40
 * @method Field\Image          image($name, $label = '')
41
 * @method Field\Date           date($name, $label = '')
42
 * @method Field\Datetime       datetime($name, $label = '')
43
 * @method Field\Time           time($name, $label = '')
44
 * @method Field\Year           year($column, $label = '')
45
 * @method Field\Month          month($column, $label = '')
46
 * @method Field\DateRange      dateRange($start, $end, $label = '')
47
 * @method Field\DateTimeRange  dateTimeRange($start, $end, $label = '')
48
 * @method Field\TimeRange      timeRange($start, $end, $label = '')
49
 * @method Field\Number         number($name, $label = '')
50
 * @method Field\Currency       currency($name, $label = '')
51
 * @method Field\SwitchField    switch($name, $label = '')
52
 * @method Field\Display        display($name, $label = '')
53
 * @method Field\Rate           rate($name, $label = '')
54
 * @method Field\Divider        divider($title = '')
55
 * @method Field\Decimal        decimal($column, $label = '')
56
 * @method Field\Html           html($html)
57
 * @method Field\Tags           tags($column, $label = '')
58
 * @method Field\Icon           icon($column, $label = '')
59
 * @method Field\Captcha        captcha($column, $label = '')
60
 * @method Field\Listbox        listbox($column, $label = '')
61
 * @method Field\Table          table($column, $label, $builder)
62
 * @method Field\Timezone       timezone($column, $label = '')
63
 * @method Field\KeyValue       keyValue($column, $label = '')
64
 * @method Field\ListField      list($column, $label = '')
65
 * @method mixed                handle(Request $request)
66
 */
67
class Form implements Renderable
68
{
69
    use BaseForm\Concerns\HandleCascadeFields;
70
71
    /**
72
     * The title of form.
73
     *
74
     * @var string
75
     */
76
    public $title;
77
78
    /**
79
     * The description of form.
80
     *
81
     * @var string
82
     */
83
    public $description;
84
85
    /**
86
     * @var Field[]
87
     */
88
    protected $fields = [];
89
90
    /**
91
     * @var array
92
     */
93
    protected $data = [];
94
95
    /**
96
     * @var array
97
     */
98
    protected $attributes = [];
99
100
    /**
101
     * Available buttons.
102
     *
103
     * @var array
104
     */
105
    protected $buttons = ['reset', 'submit'];
106
107
    /**
108
     * Width for label and submit field.
109
     *
110
     * @var array
111
     */
112
    protected $width = [
113
        'label' => 2,
114
        'field' => 8,
115
    ];
116
117
    /**
118
     * @var bool
119
     */
120
    public $inbox = true;
121
122
    /**
123
     * @var string
124
     */
125
    public $confirm = '';
126
127
    /**
128
     * @var Form
129
     */
130
    protected $form;
131
132
    /**
133
     * Form constructor.
134
     *
135
     * @param array $data
136
     */
137
    public function __construct($data = [])
138
    {
139
        $this->fill($data);
140
141
        $this->initFormAttributes();
142
    }
143
144
    /**
145
     * Get form title.
146
     *
147
     * @return mixed
148
     */
149
    public function title()
150
    {
151
        return $this->title;
152
    }
153
154
    /**
155
     * Get form description.
156
     *
157
     * @return mixed
158
     */
159
    public function description()
160
    {
161
        return $this->description ?: ' ';
162
    }
163
164
    /**
165
     * @return array
166
     */
167
    public function data()
168
    {
169
        return $this->data;
170
    }
171
172
    /**
173
     * @return array
174
     */
175
    public function confirm($message)
176
    {
177
        $this->confirm = $message;
178
179
        return $this;
180
    }
181
182
    /**
183
     * Fill data to form fields.
184
     *
185
     * @param array $data
186
     *
187
     * @return $this
188
     */
189
    public function fill($data = [])
190
    {
191
        if ($data instanceof Arrayable) {
192
            $data = $data->toArray();
193
        }
194
195
        if (!empty($data)) {
196
            $this->data = $data;
197
        }
198
199
        return $this;
200
    }
201
202
    /**
203
     * @return $this
204
     */
205
    public function sanitize()
206
    {
207
        foreach (['_form_', '_token'] as $key) {
208
            request()->request->remove($key);
209
        }
210
211
        return $this;
212
    }
213
214
    /**
215
     * Initialize the form attributes.
216
     */
217
    protected function initFormAttributes()
218
    {
219
        $this->attributes = [
220
            'id'             => 'widget-form-'.uniqid(),
221
            'method'         => 'POST',
222
            'action'         => '',
223
            'class'          => 'form-horizontal',
224
            'accept-charset' => 'UTF-8',
225
            'pjax-container' => true,
226
        ];
227
    }
228
229
    /**
230
     * Add form attributes.
231
     *
232
     * @param string|array $attr
233
     * @param string       $value
234
     *
235
     * @return $this
236
     */
237
    public function attribute($attr, $value = '')
238
    {
239
        if (is_array($attr)) {
240
            foreach ($attr as $key => $value) {
241
                $this->attribute($key, $value);
242
            }
243
        } else {
244
            $this->attributes[$attr] = $value;
245
        }
246
247
        return $this;
248
    }
249
250
    /**
251
     * Format form attributes form array to html.
252
     *
253
     * @param array $attributes
254
     *
255
     * @return string
256
     */
257
    public function formatAttribute($attributes = [])
258
    {
259
        $attributes = $attributes ?: $this->attributes;
260
261
        if ($this->hasFile()) {
262
            $attributes['enctype'] = 'multipart/form-data';
263
        }
264
265
        $html = [];
266
        foreach ($attributes as $key => $val) {
267
            $html[] = "$key=\"$val\"";
268
        }
269
270
        return implode(' ', $html) ?: '';
271
    }
272
273
    /**
274
     * Action uri of the form.
275
     *
276
     * @param string $action
277
     *
278
     * @return $this
279
     */
280
    public function action($action)
281
    {
282
        return $this->attribute('action', $action);
283
    }
284
285
    /**
286
     * Method of the form.
287
     *
288
     * @param string $method
289
     *
290
     * @return $this
291
     */
292
    public function method($method = 'POST')
293
    {
294
        if (strtolower($method) == 'put') {
295
            $this->hidden('_method')->default($method);
296
297
            return $this;
298
        }
299
300
        return $this->attribute('method', strtoupper($method));
301
    }
302
303
    /**
304
     * Disable Pjax.
305
     *
306
     * @return $this
307
     */
308
    public function disablePjax()
309
    {
310
        Arr::forget($this->attributes, 'pjax-container');
311
312
        return $this;
313
    }
314
315
    /**
316
     * Disable reset button.
317
     *
318
     * @return $this
319
     */
320
    public function disableReset()
321
    {
322
        array_delete($this->buttons, 'reset');
323
324
        return $this;
325
    }
326
327
    /**
328
     * Disable submit button.
329
     *
330
     * @return $this
331
     */
332
    public function disableSubmit()
333
    {
334
        array_delete($this->buttons, 'submit');
335
336
        return $this;
337
    }
338
339
    /**
340
     * Set field and label width in current form.
341
     *
342
     * @param int $fieldWidth
343
     * @param int $labelWidth
344
     *
345
     * @return $this
346
     */
347
    public function setWidth($fieldWidth = 8, $labelWidth = 2)
348
    {
349
        collect($this->fields)->each(function ($field) use ($fieldWidth, $labelWidth) {
350
            /* @var Field $field  */
351
            $field->setWidth($fieldWidth, $labelWidth);
352
        });
353
354
        // set this width
355
        $this->width = [
356
            'label' => $labelWidth,
357
            'field' => $fieldWidth,
358
        ];
359
360
        return $this;
361
    }
362
363
    /**
364
     * Determine if the form has field type.
365
     *
366
     * @param string $name
367
     *
368
     * @return bool
369
     */
370
    public function hasField($name)
371
    {
372
        return isset(BaseForm::$availableFields[$name]);
373
    }
374
375
    /**
376
     * Add a form field to form.
377
     *
378
     * @param Field $field
379
     *
380
     * @return $this
381
     */
382
    public function pushField(Field $field)
383
    {
384
        $field->setWidgetForm($this);
385
386
        array_push($this->fields, $field);
387
388
        return $this;
389
    }
390
391
    /**
392
     * Get all fields of form.
393
     *
394
     * @return Field[]
395
     */
396
    public function fields()
397
    {
398
        return collect($this->fields);
399
    }
400
401
    /**
402
     * Get variables for render form.
403
     *
404
     * @return array
405
     */
406
    protected function getVariables()
407
    {
408
        $this->fields()->each->fill($this->data());
409
410
        return [
411
            'fields'     => $this->fields,
412
            'attributes' => $this->formatAttribute(),
413
            'method'     => $this->attributes['method'],
414
            'buttons'    => $this->buttons,
415
            'width'      => $this->width,
416
        ];
417
    }
418
419
    /**
420
     * Determine if form fields has files.
421
     *
422
     * @return bool
423
     */
424
    public function hasFile()
425
    {
426
        foreach ($this->fields as $field) {
427
            if ($field instanceof Field\File) {
428
                return true;
429
            }
430
        }
431
432
        return false;
433
    }
434
435
    /**
436
     * Validate this form fields.
437
     *
438
     * @param Request $request
439
     *
440
     * @return bool|MessageBag
441
     */
442
    public function validate(Request $request)
443
    {
444
        if (method_exists($this, 'form')) {
445
            $this->form();
0 ignored issues
show
Documentation Bug introduced by
The method form does not exist on object<Encore\Admin\Widgets\Form>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
446
        }
447
448
        $failedValidators = [];
449
450
        /** @var Field $field */
451 View Code Duplication
        foreach ($this->fields() as $field) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
452
            if (!$validator = $field->getValidator($request->all())) {
453
                continue;
454
            }
455
456
            if (($validator instanceof Validator) && !$validator->passes()) {
457
                $failedValidators[] = $validator;
458
            }
459
        }
460
461
        $message = $this->mergeValidationMessages($failedValidators);
462
463
        return $message->any() ? $message : false;
464
    }
465
466
    /**
467
     * Merge validation messages from input validators.
468
     *
469
     * @param \Illuminate\Validation\Validator[] $validators
470
     *
471
     * @return MessageBag
472
     */
473
    protected function mergeValidationMessages($validators)
474
    {
475
        $messageBag = new MessageBag();
476
477
        foreach ($validators as $validator) {
478
            $messageBag = $messageBag->merge($validator->messages());
479
        }
480
481
        return $messageBag;
482
    }
483
484
    /**
485
     * Add a fieldset to form.
486
     *
487
     * @param string  $title
488
     * @param Closure $setCallback
489
     *
490
     * @return Field\Fieldset
491
     */
492 View Code Duplication
    public function fieldset(string $title, Closure $setCallback)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
493
    {
494
        $fieldset = new Field\Fieldset();
495
496
        $this->html($fieldset->start($title))->plain();
497
498
        $setCallback($this);
499
500
        $this->html($fieldset->end())->plain();
501
502
        return $fieldset;
503
    }
504
505
    /**
506
     * @return $this
507
     */
508
    public function unbox()
509
    {
510
        $this->inbox = false;
511
512
        return $this;
513
    }
514
515
    protected function addConfirmScript()
516
    {
517
        $id = $this->attributes['id'];
518
519
        $trans = [
520
            'cancel' => trans('admin.cancel'),
521
            'submit' => trans('admin.submit'),
522
        ];
523
524
        $settings = [
525
            'type'                => 'question',
526
            'showCancelButton'    => true,
527
            'confirmButtonText'   => $trans['submit'],
528
            'cancelButtonText'    => $trans['cancel'],
529
            'title'               => $this->confirm,
530
            'text'                => '',
531
        ];
532
533
        $settings = trim(json_encode($settings, JSON_PRETTY_PRINT));
534
535
        $script = <<<SCRIPT
536
537
$('form#{$id}').off('submit').on('submit', function (e) {
538
    e.preventDefault();
539
    var form = this;
540
    $.admin.swal($settings).then(function (result) {
541
        if (result.value == true) {
542
            form.submit();
543
        }
544
    });
545
    return false;
546
});
547
SCRIPT;
548
549
        Admin::script($script);
550
    }
551
552
    protected function addCascadeScript()
553
    {
554
        $id = $this->attributes['id'];
555
556
        $script = <<<SCRIPT
557
(function () {
558
    $('form#{$id}').submit(function (e) {
559
        e.preventDefault();
560
        $(this).find('div.cascade-group.hide :input').attr('disabled', true);
561
    });
562
})();
563
SCRIPT;
564
565
        Admin::script($script);
566
    }
567
568
    protected function prepareForm()
569
    {
570
        if (method_exists($this, 'form')) {
571
            $this->form();
0 ignored issues
show
Documentation Bug introduced by
The method form does not exist on object<Encore\Admin\Widgets\Form>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
572
        }
573
574
        if (!empty($this->confirm)) {
575
            $this->addConfirmScript();
576
        }
577
578
        $this->addCascadeScript();
579
    }
580
581
    protected function prepareHandle()
582
    {
583
        if (method_exists($this, 'handle')) {
584
            $this->method('POST');
585
            $this->action(admin_url('_handle_form_'));
0 ignored issues
show
Bug introduced by
It seems like admin_url('_handle_form_') targeting admin_url() can also be of type object<Illuminate\Contracts\Routing\UrlGenerator>; however, Encore\Admin\Widgets\Form::action() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
586
            $this->hidden('_form_')->default(get_called_class());
587
        }
588
    }
589
590
    /**
591
     * Render the form.
592
     *
593
     * @return string
594
     */
595
    public function render()
596
    {
597
        $this->prepareForm();
598
599
        $this->prepareHandle();
600
601
        $form = view('admin::widgets.form', $this->getVariables())->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...
602
603
        if (!($title = $this->title()) || !$this->inbox) {
604
            return $form;
605
        }
606
607
        return (new Box($title, $form))->render();
608
    }
609
610
    /**
611
     * Generate a Field object and add to form builder if Field exists.
612
     *
613
     * @param string $method
614
     * @param array  $arguments
615
     *
616
     * @return Field|$this
617
     */
618
    public function __call($method, $arguments)
619
    {
620
        if (!$this->hasField($method)) {
621
            return $this;
622
        }
623
624
        $class = BaseForm::$availableFields[$method];
625
626
        $field = new $class(Arr::get($arguments, 0), array_slice($arguments, 1));
627
628
        return tap($field, function ($field) {
629
            $this->pushField($field);
630
        });
631
    }
632
633
    /**
634
     * @param Content $content
635
     * @return Content
636
     */
637
    public function __invoke(Content $content)
638
    {
639
        return $content->title($this->title())
640
            ->description($this->description())
641
            ->body($this);
642
    }
643
}
644