Completed
Push — master ( 0a3c25...4ed9d0 )
by ARCANEDEV
10s
created

FormBuilder::getSelectedValue()   B

Complexity

Conditions 9
Paths 9

Size

Total Lines 13
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 9.1582

Importance

Changes 0
Metric Value
dl 0
loc 13
ccs 7
cts 8
cp 0.875
rs 7.756
c 0
b 0
f 0
cc 9
eloc 8
nc 9
nop 2
crap 9.1582
1
<?php namespace Arcanedev\LaravelHtml;
2
3
use Arcanedev\LaravelHtml\Bases\Builder;
4
use Arcanedev\LaravelHtml\Contracts\FormBuilder as FormBuilderContract;
5
use DateTime;
6
use Illuminate\Contracts\Routing\UrlGenerator;
7
use Illuminate\Contracts\Session\Session;
8
use Illuminate\Support\Arr;
9
use Illuminate\Support\Collection;
10
11
/**
12
 * Class     FormBuilder
13
 *
14
 * @package  Arcanedev\LaravelHtml
15
 * @author   ARCANEDEV <[email protected]>
16
 */
17
class FormBuilder extends Builder implements FormBuilderContract
18
{
19
    /* -----------------------------------------------------------------
20
     |  Properties
21
     | -----------------------------------------------------------------
22
     */
23
24
    /**
25
    * The HTML builder instance.
26
    *
27
    * @var \Arcanedev\LaravelHtml\Contracts\HtmlBuilder
28
    */
29
    protected $html;
30
31
    /**
32
    * The URL generator instance.
33
    *
34
    * @var \Illuminate\Contracts\Routing\UrlGenerator
35
    */
36
    protected $url;
37
38
    /**
39
    * The CSRF token used by the form builder.
40
    *
41
    * @var string
42
    */
43
    protected $csrfToken;
44
45
    /**
46
    * The session store implementation.
47
    *
48
    * @var \Illuminate\Contracts\Session\Session|\Illuminate\Session\Store
49
    */
50
    protected $session;
51
52
    /**
53
    * The current model instance for the form.
54
    *
55
    * @var \Illuminate\Database\Eloquent\Model
56
    */
57
    protected $model;
58
59
    /**
60
    * An array of label names we've created.
61
    *
62
    * @var array
63
    */
64
    protected $labels = [];
65
66
    /**
67
    * The reserved form open attributes.
68
    *
69
    * @var array
70
    */
71
    protected $reserved = ['method', 'url', 'route', 'action', 'files'];
72
73
    /**
74
    * The form methods that should be spoofed, in uppercase.
75
    *
76
    * @var array
77
    */
78
    protected $spoofedMethods = ['DELETE', 'PATCH', 'PUT'];
79
80
    /**
81
    * The types of inputs to not fill values on by default.
82
    *
83
    * @var array
84
    */
85
    protected $skipValueTypes = ['file', 'password', 'checkbox', 'radio'];
86
87
    /* -----------------------------------------------------------------
88
     |  Constructor
89
     | -----------------------------------------------------------------
90
     */
91
92
    /**
93
    * Create a new form builder instance.
94
    *
95
    * @param  \Arcanedev\LaravelHtml\Contracts\HtmlBuilder  $html
96
    * @param  \Illuminate\Contracts\Routing\UrlGenerator    $url
97
    * @param  \Illuminate\Contracts\Session\Session         $session
98
    */
99 321
    public function __construct(
100
        Contracts\HtmlBuilder $html,
101
        UrlGenerator $url,
102
        Session $session
103
    ) {
104 321
        $this->url       = $url;
105 321
        $this->html      = $html;
106 321
        $this->csrfToken = $session->token();
107
108 321
        $this->setSessionStore($session);
109 321
    }
110
111
    /* -----------------------------------------------------------------
112
     |  Getters & Setters
113
     | -----------------------------------------------------------------
114
     */
115
116
    /**
117
     * Get the session store implementation.
118
     *
119
     * @return  \Illuminate\Contracts\Session\Session
120
     */
121 3
    public function getSessionStore()
122
    {
123 3
        return $this->session;
124
    }
125
126
    /**
127
     * Set the session store implementation.
128
     *
129
     * @param  \Illuminate\Contracts\Session\Session  $session
130
     *
131
     * @return self
132
     */
133 321
    public function setSessionStore(Session $session)
134
    {
135 321
        $this->session = $session;
136
137 321
        return $this;
138
    }
139
140
    /**
141
     * Set the model instance on the form builder.
142
     *
143
     * @param  \Illuminate\Database\Eloquent\Model  $model
144
     *
145
     * @return self
146
     */
147 36
    public function setModel($model)
148
    {
149 36
        $this->model = $model;
150
151 36
        return $this;
152
    }
153
154
    /**
155
     * Get the model instance on the form builder.
156
     *
157
     * @return \Illuminate\Database\Eloquent\Model
158
     */
159 15
    public function getModel()
160
    {
161 15
        return $this->model;
162
    }
163
164
    /**
165
     * Get the ID attribute for a field name.
166
     *
167
     * @param  string  $name
168
     * @param  array   $attributes
169
     *
170
     * @return string
171
     */
172 261
    public function getIdAttribute($name, array $attributes)
173
    {
174 261
        if (array_key_exists('id', $attributes))
175 21
            return $attributes['id'];
176
177 249
        if (in_array($name, $this->labels))
178 6
            return $name;
179
180 243
        return null;
181
    }
182
183
    /**
184
     * Get the value that should be assigned to the field.
185
     *
186
     * @param  string  $name
187
     * @param  mixed   $value
188
     *
189
     * @return mixed
190
     */
191 237
    public function getValueAttribute($name, $value = null)
192
    {
193 237
        if (is_null($name))
194 9
            return $value;
195
196 228
        if ( ! is_null($this->old($name)) && $name !== '_method')
197 15
            return $this->old($name);
198
199 222
        if ( ! is_null($value))
200 129
            return $value;
201
202 126
        return isset($this->model)
203 18
            ? $this->getModelValueAttribute($name)
204 126
            : null;
205
    }
206
207
    /**
208
     * Get the model value that should be assigned to the field.
209
     *
210
     * @param  string                               $name
211
     * @param  \Illuminate\Database\Eloquent\Model  $model
212
     *
213
     * @return mixed
214
     */
215 27
    private function getModelValueAttribute($name, $model = null)
216
    {
217 27
        if (is_null($model))
218 27
            $model = $this->model;
219
220 27
        $key = $this->transformKey($name);
221
222 27
        if (strpos($key, '.') !== false) {
223 15
            $keys = explode('.', $key, 2);
224
225 15
            return $this->getModelValueAttribute($keys[1],
226 15
                $this->getModelValueAttribute($keys[0], $model)
227
            );
228
        }
229
230 27
        return method_exists($model, 'getFormValue')
231 3
            ? $model->getFormValue($key)
232 27
            : data_get($model, $key);
233
    }
234
235
    /**
236
     * Get a value from the session's old input.
237
     *
238
     * @param  string  $name
239
     *
240
     * @return mixed
241
     */
242 237
    public function old($name)
243
    {
244 237
        return isset($this->session)
245 237
            ? $this->session->getOldInput($this->transformKey($name))
246 237
            : null;
247
    }
248
249
    /**
250
     * Transform key from array to dot syntax.
251
     *
252
     * @param  string  $key
253
     *
254
     * @return string
255
     */
256 237
    private function transformKey($key)
257
    {
258 237
        return str_replace(['.', '[]', '[', ']'], ['_', '', '.', ''], $key);
259
    }
260
261
    /**
262
     * Determine if the old input is empty.
263
     *
264
     * @return bool
265
     */
266 12
    public function oldInputIsEmpty()
267
    {
268 12
        return isset($this->session) && (count($this->session->getOldInput()) == 0);
269
    }
270
271
    /**
272
     * Parse the form action method.
273
     *
274
     * @param  string  $method
275
     *
276
     * @return string
277
     */
278 57
    private function getMethod($method)
279
    {
280 57
        $method = strtoupper($method);
281
282 57
        return $method !== 'GET' ? 'POST' : $method;
283
    }
284
285
    /* -----------------------------------------------------------------
286
     |  Main Methods
287
     | -----------------------------------------------------------------
288
     */
289
290
    /**
291
     * Open up a new HTML form.
292
     *
293
     * @param  array  $options
294
     *
295
     * @return \Illuminate\Support\HtmlString
296
     */
297 57
    public function open(array $options = [])
298
    {
299 57
        $method = Arr::get($options, 'method', 'post');
300
301
        // We need to extract the proper method from the attributes. If the method is
302
        // something other than GET or POST we'll use POST since we will spoof the
303
        // actual method since forms don't support the reserved methods in HTML.
304
        $attributes = [
305 57
            'method'         => $this->getMethod($method),
306 57
            'action'         => $this->getAction($options),
307 57
            'accept-charset' => 'UTF-8',
308
        ];
309
310 57
        if (isset($options['files']) && $options['files'])
311 6
            $options['enctype'] = 'multipart/form-data';
312
313
        // Finally we're ready to create the final form HTML field. We will attribute
314
        // format the array of attributes. We will also add on the appendage which
315
        // is used to spoof requests for this PUT, PATCH, etc. methods on forms.
316 57
        $attributes = array_merge(
317 57
            $attributes, array_except($options, $this->reserved)
318
        );
319
320
        // Finally, we will concatenate all of the attributes into a single string so
321
        // we can build out the final form open statement. We'll also append on an
322
        // extra value for the hidden _method field if it's needed for the form.
323 57
        $attributes = $this->html->attributes($attributes);
324
325
        // If the method is PUT, PATCH or DELETE we will need to add a spoofer hidden
326
        // field that will instruct the Symfony request to pretend the method is a
327
        // different method than it actually is, for convenience from the forms.
328 57
        $append = $this->getAppendage($method);
329
330 57
        return $this->toHtmlString('<form'.$attributes.'>' . $append);
331
    }
332
333
    /**
334
     * Create a new model based form builder.
335
     *
336
     * @param  mixed  $model
337
     * @param  array  $options
338
     *
339
     * @return \Illuminate\Support\HtmlString
340
     */
341 15
    public function model($model, array $options = [])
342
    {
343 15
        $this->setModel($model);
344
345 15
        return $this->open($options);
346
    }
347
348
    /**
349
     * Close the current form.
350
     *
351
     * @return \Illuminate\Support\HtmlString
352
     */
353 6
    public function close()
354
    {
355 6
        $this->labels = [];
356 6
        $this->setModel(null);
0 ignored issues
show
Documentation introduced by
null is of type null, but the function expects a object<Illuminate\Database\Eloquent\Model>.

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...
357
358 6
        return $this->toHtmlString('</form>');
359
    }
360
361
    /**
362
     * Generate a hidden field with the current CSRF token.
363
     *
364
     * @return \Illuminate\Support\HtmlString
365
     */
366 30
    public function token()
367
    {
368 30
        return $this->hidden(
369 30
            '_token',
370 30
            empty($this->csrfToken) ? $this->session->token() : $this->csrfToken
371
        );
372
    }
373
374
    /**
375
     * Create a form label element.
376
     *
377
     * @param  string  $name
378
     * @param  string  $value
379
     * @param  array   $options
380
     * @param  bool    $escaped
381
     *
382
     * @return \Illuminate\Support\HtmlString
383
     */
384 15
    public function label($name, $value = null, array $options = [], $escaped = true)
385
    {
386 15
        $this->labels[] = $name;
387
388 15
        return $this->toHtmlString(
389 15
            Helpers\Label::make($name, $value, $options, $escaped)
390
        );
391
    }
392
393
    /**
394
     * Create a form input field.
395
     *
396
     * @param  string  $type
397
     * @param  string  $name
398
     * @param  string  $value
399
     * @param  array   $options
400
     *
401
     * @return \Illuminate\Support\HtmlString
402
     */
403 198
    public function input($type, $name, $value = null, array $options = [])
404
    {
405 198
        if ( ! isset($options['name']))
406 198
            $options['name'] = $name;
407
408
        // We will get the appropriate value for the given field. We will look for the
409
        // value in the session for the value in the old input data then we'll look
410
        // in the model instance if one is set. Otherwise we will just use empty.
411 198
        $id = $this->getIdAttribute($name, $options);
412
413 198
        if ( ! in_array($type, $this->skipValueTypes))
414 162
            $value = $this->getValueAttribute($name, $value);
415
416
        // Once we have the type, value, and ID we can merge them into the rest of the
417
        // attributes array so we can convert them into their HTML attribute format
418
        // when creating the HTML element. Then, we will return the entire input.
419 198
        $options = array_merge($options, compact('type', 'value', 'id'));
420
421 198
        return $this->toHtmlString('<input'.$this->html->attributes($options).'>');
422
    }
423
424
    /**
425
     * Create a text input field.
426
     *
427
     * @param  string  $name
428
     * @param  string  $value
429
     * @param  array   $options
430
     *
431
     * @return \Illuminate\Support\HtmlString
432
     */
433 21
    public function text($name, $value = null, array $options = [])
434
    {
435 21
        return $this->input('text', $name, $value, $options);
436
    }
437
438
    /**
439
     * Create a password input field.
440
     *
441
     * @param  string  $name
442
     * @param  array   $options
443
     *
444
     * @return \Illuminate\Support\HtmlString
445
     */
446 9
    public function password($name, array $options = [])
447
    {
448 9
        return $this->input('password', $name, '', $options);
449
    }
450
451
    /**
452
     * Create a hidden input field.
453
     *
454
     * @param  string  $name
455
     * @param  string  $value
456
     * @param  array   $options
457
     *
458
     * @return \Illuminate\Support\HtmlString
459
     */
460 39
    public function hidden($name, $value = null, array $options = [])
461
    {
462 39
        return $this->input('hidden', $name, $value, $options);
463
    }
464
465
    /**
466
     * Create an e-mail input field.
467
     *
468
     * @param  string  $name
469
     * @param  string  $value
470
     * @param  array   $options
471
     *
472
     * @return \Illuminate\Support\HtmlString
473
     */
474 9
    public function email($name, $value = null, array $options = [])
475
    {
476 9
        return $this->input('email', $name, $value, $options);
477
    }
478
479
    /**
480
     * Create a tel input field.
481
     *
482
     * @param  string  $name
483
     * @param  string  $value
484
     * @param  array   $options
485
     *
486
     * @return \Illuminate\Support\HtmlString
487
     */
488 9
    public function tel($name, $value = null, array $options = [])
489
    {
490 9
        return $this->input('tel', $name, $value, $options);
491
    }
492
493
    /**
494
     * Create a number input field.
495
     *
496
     * @param  string  $name
497
     * @param  string  $value
498
     * @param  array   $options
499
     *
500
     * @return \Illuminate\Support\HtmlString
501
     */
502 9
    public function number($name, $value = null, array $options = [])
503
    {
504 9
        return $this->input('number', $name, $value, $options);
505
    }
506
507
    /**
508
     * Create a date input field.
509
     *
510
     * @param  string  $name
511
     * @param  string  $value
512
     * @param  array   $options
513
     *
514
     * @return \Illuminate\Support\HtmlString
515
     */
516 12
    public function date($name, $value = null, array $options = [])
517
    {
518 12
        if ($value instanceof DateTime)
519 3
            $value = $value->format('Y-m-d');
520
521 12
        return $this->input('date', $name, $value, $options);
522
    }
523
524
    /**
525
     * Create a datetime input field.
526
     *
527
     * @param  string  $name
528
     * @param  string  $value
529
     * @param  array   $options
530
     *
531
     * @return \Illuminate\Support\HtmlString
532
     */
533 12
    public function datetime($name, $value = null, array $options = [])
534
    {
535 12
        if ($value instanceof DateTime)
536 6
            $value = $value->format(DateTime::RFC3339);
537
538 12
        return $this->input('datetime', $name, $value, $options);
539
    }
540
541
    /**
542
     * Create a datetime-local input field.
543
     *
544
     * @param  string  $name
545
     * @param  string  $value
546
     * @param  array   $options
547
     *
548
     * @return \Illuminate\Support\HtmlString
549
     */
550 12
    public function datetimeLocal($name, $value = null, array $options = [])
551
    {
552 12
        if ($value instanceof DateTime)
553 6
            $value = $value->format('Y-m-d\TH:i');
554
555 12
        return $this->input('datetime-local', $name, $value, $options);
556
    }
557
558
    /**
559
     * Create a time input field.
560
     *
561
     * @param  string  $name
562
     * @param  string  $value
563
     * @param  array   $options
564
     *
565
     * @return \Illuminate\Support\HtmlString
566
     */
567 9
    public function time($name, $value = null, array $options = [])
568
    {
569 9
        return $this->input('time', $name, $value, $options);
570
    }
571
572
    /**
573
     * Create a url input field.
574
     *
575
     * @param  string  $name
576
     * @param  string  $value
577
     * @param  array   $options
578
     *
579
     * @return \Illuminate\Support\HtmlString
580
     */
581 6
    public function url($name, $value = null, array $options = [])
582
    {
583 6
        return $this->input('url', $name, $value, $options);
584
    }
585
586
    /**
587
     * Create a file input field.
588
     *
589
     * @param  string  $name
590
     * @param  array   $options
591
     *
592
     * @return \Illuminate\Support\HtmlString
593
     */
594 9
    public function file($name, array $options = [])
595
    {
596 9
        return $this->input('file', $name, null, $options);
597
    }
598
599
    /**
600
     * Create a textarea input field.
601
     *
602
     * @param  string  $name
603
     * @param  string  $value
604
     * @param  array   $options
605
     *
606
     * @return \Illuminate\Support\HtmlString
607
     */
608 18
    public function textarea($name, $value = null, array $options = [])
609
    {
610 18
        if ( ! isset($options['name']))
611 18
            $options['name'] = $name;
612
613
        // Next we will look for the rows and cols attributes, as each of these are put
614
        // on the textarea element definition. If they are not present, we will just
615
        // assume some sane default values for these attributes for the developer.
616 18
        $options       = $this->setTextAreaSize($options);
617 18
        $options['id'] = $this->getIdAttribute($name, $options);
618 18
        $value         = (string) $this->getValueAttribute($name, $value);
619
620 18
        unset($options['size']);
621
622
        // Next we will convert the attributes into a string form. Also we have removed
623
        // the size attribute, as it was merely a short-cut for the rows and cols on
624
        // the element. Then we'll create the final textarea elements HTML for us.
625 18
        $options = $this->html->attributes($options);
626
627 18
        return $this->toHtmlString('<textarea'.$options.'>'.$this->html->escape($value).'</textarea>');
628
    }
629
630
    /**
631
     * Set the text area size on the attributes.
632
     *
633
     * @param  array  $options
634
     *
635
     * @return array
636
     */
637 18
    private function setTextAreaSize(array $options)
638
    {
639 18
        if (isset($options['size']))
640 9
            return $this->setQuickTextAreaSize($options);
641
642
        // If the "size" attribute was not specified, we will just look for the regular
643
        // columns and rows attributes, using sane defaults if these do not exist on
644
        // the attributes array. We'll then return this entire options array back.
645 9
        $cols = Arr::get($options, 'cols', 50);
646 9
        $rows = Arr::get($options, 'rows', 10);
647
648 9
        return array_merge($options, compact('cols', 'rows'));
649
    }
650
651
    /**
652
     * Set the text area size using the quick "size" attribute.
653
     *
654
     * @param  array  $options
655
     *
656
     * @return array
657
     */
658 9
    protected function setQuickTextAreaSize(array $options)
659
    {
660 9
        list($cols, $rows) = explode('x', $options['size']);
661
662 9
        return array_merge($options, compact('cols', 'rows'));
663
    }
664
665
    /**
666
     * Create a select box field.
667
     *
668
     * @param  string                                $name
669
     * @param  array|\Illuminate\Support\Collection  $list
670
     * @param  string|bool                           $selected
671
     * @param  array                                 $attributes
672
     * @param  array                                 $optionsAttributes
673
     * @param  array                                 $optgroupsAttributes
674
     *
675
     * @return \Illuminate\Support\HtmlString
676
     */
677 45
    public function select(
678
        $name,
679
        $list = [],
680
        $selected = null,
681
        array $attributes = [],
682
        array $optionsAttributes = [],
683
        array $optgroupsAttributes = []
684
    ) {
685
        // When building a select box the "value" attribute is really the selected one
686
        // so we will use that when checking the model or session for a value which
687
        // should provide a convenient method of re-populating the forms on post.
688 45
        $selected = $this->getValueAttribute($name, $selected);
689
690
        // Transform to array if it is a collection
691 45
        if ($selected instanceof Collection)
692 3
            $selected = $selected->all();
693
694 45
        $attributes['id'] = $this->getIdAttribute($name, $attributes);
695
696 45
        if ( ! isset($attributes['name']))
697 33
            $attributes['name'] = $name;
698
699
        // We will simply loop through the options and build an HTML value for each of
700
        // them until we have an array of HTML declarations. Then we will join them
701
        // all together into one single HTML element that can be put on the form.
702 45
        $html = [];
703
704 45
        if (isset($attributes['placeholder'])) {
705 6
            $html[] = $this->placeholderOption($attributes['placeholder'], $selected);
706 6
            unset($attributes['placeholder']);
707
        }
708
709 45
        foreach($list as $value => $display) {
710 42
            $optionAttributes = $optionsAttributes[$value] ?? [];
711 42
            $optgroupAttributes = $optgroupsAttributes[$value] ?? [];
712
713 42
            $html[] = $this->getSelectOption($display, $value, $selected, $optionAttributes, $optgroupAttributes);
714
        }
715
716
        // Once we have all of this HTML, we can join this into a single element after
717
        // formatting the attributes into an HTML "attributes" string, then we will
718
        // build out a final select statement, which will contain all the values.
719 45
        $attributes = $this->html->attributes($attributes);
720
721 45
        return $this->toHtmlString("<select{$attributes}>".implode('', $html).'</select>');
722
    }
723
724
    /**
725
     * Create a select range field.
726
     *
727
     * @param  string  $name
728
     * @param  string  $begin
729
     * @param  string  $end
730
     * @param  string  $selected
731
     * @param  array   $options
732
     *
733
     * @return \Illuminate\Support\HtmlString
734
     */
735 6
    public function selectRange($name, $begin, $end, $selected = null, array $options = [])
736
    {
737 6
        $range = array_combine($range = range($begin, $end), $range);
738
739 6
        return $this->select($name, $range, $selected, $options);
740
    }
741
742
    /**
743
     * Create a select year field.
744
     *
745
     * @param  string  $name
746
     * @param  string  $begin
747
     * @param  string  $end
748
     * @param  string  $selected
749
     * @param  array   $options
750
     *
751
     * @return \Illuminate\Support\HtmlString
752
     */
753 3
    public function selectYear($name, $begin, $end, $selected = null, array $options = [])
754
    {
755 3
        return call_user_func_array(
756 3
            [$this, 'selectRange'],
757 3
            compact('name', 'begin', 'end', 'selected', 'options')
758
        );
759
    }
760
761
    /**
762
     * Create a select month field.
763
     *
764
     * @param  string  $name
765
     * @param  string  $selected
766
     * @param  array   $options
767
     * @param  string  $format
768
     *
769
     * @return \Illuminate\Support\HtmlString
770
     */
771 3
    public function selectMonth($name, $selected = null, array $options = [], $format = '%B')
772
    {
773 3
        $months = [];
774
775 3
        foreach(range(1, 12) as $month) {
776 3
            $months[$month] = strftime($format, mktime(0, 0, 0, $month, 1));
777
        }
778
779 3
        return $this->select($name, $months, $selected, $options);
780
    }
781
782
    /**
783
     * Get the select option for the given value.
784
     *
785
     * @param  string  $display
786
     * @param  string  $value
787
     * @param  string  $selected
788
     * @param  array   $attributes
789
     * @param  array   $optgroupAttributes
790
     *
791
     * @return string
792
     */
793 42
    private function getSelectOption($display, $value, $selected, array $attributes = [], array $optgroupAttributes = [])
794
    {
795 42
        return is_array($display)
796 6
            ? $this->optionGroup($display, $value, $selected, $optgroupAttributes, $attributes)
797 42
            : $this->option($display, $value, $selected, $attributes);
798
    }
799
800
    /**
801
     * Create an option group form element.
802
     *
803
     * @param  array   $list
804
     * @param  string  $label
805
     * @param  string  $selected
806
     * @param  array   $attributes
807
     * @param  array   $optionsAttributes
808
     * @param  int     $level
809
     *
810
     * @return string
811
     */
812 6
    private function optionGroup(array $list, $label, $selected, array $attributes = [], array $optionsAttributes = [], $level = 0)
813
    {
814 6
        $html  = [];
815 6
        $space = str_repeat("&nbsp;", $level);
816
817 6
        foreach($list as $value => $display) {
818 6
            $optionAttributes = $optionsAttributes[$value] ?? [];
819
820 6
            $html[] = is_array($display)
821
                ? $this->optionGroup($display, $value, $selected, $attributes, $optionAttributes, $level + 5)
822 6
                : $this->option($space.$display, $value, $selected, $optionAttributes);
823
        }
824
825 6
        return '<optgroup label="'.$this->html->escape($label).'"'.$this->html->attributes($attributes).'>'.implode('', $html).'</optgroup>';
826
    }
827
828
    /**
829
     * Create a select element option.
830
     *
831
     * @param  string  $display
832
     * @param  string  $value
833
     * @param  string  $selected
834
     * @param  array   $attributes
835
     *
836
     * @return string
837
     */
838 42
    private function option($display, $value, $selected, array $attributes = [])
839
    {
840 42
        $selected = $this->getSelectedValue($value, $selected);
841 42
        $options  = array_merge(compact('value', 'selected'), $attributes);
842
843 42
        return '<option'.$this->html->attributes($options).'>'.$this->html->escape($display).'</option>';
844
    }
845
846
    /**
847
     * Create a placeholder select element option.
848
     *
849
     * @param  string  $display
850
     * @param  string  $selected
851
     *
852
     * @return string
853
     */
854 6
    private function placeholderOption($display, $selected)
855
    {
856 6
        $selected = $this->getSelectedValue(null, $selected);
857 6
        $options  = array_merge(compact('selected'), ['value' => '']);
858
859 6
        return '<option'.$this->html->attributes($options).'>'.$this->html->escape($display).'</option>';
860
    }
861
862
    /**
863
     * Determine if the value is selected.
864
     *
865
     * @param  string  $value
866
     * @param  string  $selected
867
     *
868
     * @return string|null
869
     */
870 42
    private function getSelectedValue($value, $selected)
871
    {
872 42
        if (is_array($selected))
873 15
            return (in_array($value, $selected, true) || in_array((string) $value, $selected, true)) ? 'selected' : null;
874
875 33
        if ($selected instanceof Collection)
876
            return $selected->contains($value) ? 'selected' : null;
877
878 33
        if (is_int($value) && is_bool($selected))
879 3
            return (bool) $value === $selected;
880
881 33
        return ((string) $value === (string) $selected) ? 'selected' : null;
882
    }
883
884
    /**
885
     * Create a checkbox input field.
886
     *
887
     * @param  string     $name
888
     * @param  mixed      $value
889
     * @param  bool|null  $checked
890
     * @param  array      $options
891
     *
892
     * @return \Illuminate\Support\HtmlString
893
     */
894 12
    public function checkbox($name, $value = 1, $checked = null, array $options = [])
895
    {
896 12
        return $this->checkable('checkbox', $name, $value, $checked, $options);
897
    }
898
899
    /**
900
     * Create a radio button input field.
901
     *
902
     * @param  string  $name
903
     * @param  mixed   $value
904
     * @param  bool    $checked
905
     * @param  array   $options
906
     *
907
     * @return \Illuminate\Support\HtmlString
908
     */
909 6
    public function radio($name, $value = null, $checked = null, array $options = [])
910
    {
911 6
        return $this->checkable('radio', $name, $value ?? $name, $checked, $options);
912
    }
913
914
    /**
915
     * Create a checkable input field.
916
     *
917
     * @param  string     $type
918
     * @param  string     $name
919
     * @param  mixed      $value
920
     * @param  bool|null  $checked
921
     * @param  array      $options
922
     *
923
     * @return \Illuminate\Support\HtmlString
924
     */
925 21
    protected function checkable($type, $name, $value, $checked, array $options)
926
    {
927 21
        $checked = $this->getCheckedState($type, $name, $value, $checked);
928
929 21
        if ( ! is_null($checked) && $checked)
930 18
            $options['checked'] = 'checked';
931
932 21
        return $this->input($type, $name, $value, $options);
933
    }
934
935
    /**
936
     * Get the check state for a checkable input.
937
     *
938
     * @param  string     $type
939
     * @param  string     $name
940
     * @param  mixed      $value
941
     * @param  bool|null  $checked
942
     *
943
     * @return bool
944
     */
945 21
    private function getCheckedState($type, $name, $value, $checked)
946
    {
947 7
        switch($type) {
948 21
            case 'checkbox':
949 12
                return $this->getCheckboxCheckedState($name, $value, $checked);
950
951 9
            case 'radio':
952 6
                return $this->getRadioCheckedState($name, $value, $checked);
953
954
            default:
955 3
                return $this->getValueAttribute($name) === $value;
956
        }
957
    }
958
959
    /**
960
     * Get the check state for a checkbox input.
961
     *
962
     * @param  string     $name
963
     * @param  mixed      $value
964
     * @param  bool|null  $checked
965
     *
966
     * @return bool
967
     */
968 12
    private function getCheckboxCheckedState($name, $value, $checked)
969
    {
970
        if (
971 12
            isset($this->session) &&
972 12
            ! $this->oldInputIsEmpty() &&
973 9
            is_null($this->old($name))
974
        )
975 3
            return false;
976
977 12
        if ($this->missingOldAndModel($name))
978 6
            return $checked;
979
980 6
        $posted = $this->getValueAttribute($name, $checked);
981
982 6
        if (is_array($posted))
983 3
            return in_array($value, $posted);
984
985 6
        if ($posted instanceof Collection)
986 3
            return $posted->contains('id', $value);
987
988 6
        return (bool) $posted;
989
    }
990
991
    /**
992
     * Get the check state for a radio input.
993
     *
994
     * @param  string     $name
995
     * @param  mixed      $value
996
     * @param  bool|null  $checked
997
     *
998
     * @return bool
999
     */
1000 6
    private function getRadioCheckedState($name, $value, $checked)
1001
    {
1002 6
        return $this->missingOldAndModel($name)
1003 2
            ? $checked
1004 6
            : $this->getValueAttribute($name) === $value;
1005
    }
1006
1007
    /**
1008
     * Determine if old input or model input exists for a key.
1009
     *
1010
     * @param  string  $name
1011
     *
1012
     * @return bool
1013
     */
1014 18
    private function missingOldAndModel($name)
1015
    {
1016 18
        return (is_null($this->old($name)) && is_null($this->getModelValueAttribute($name)));
1017
    }
1018
1019
    /**
1020
     * Create a HTML reset input element.
1021
     *
1022
     * @param  string  $value
1023
     * @param  array   $attributes
1024
     *
1025
     * @return \Illuminate\Support\HtmlString
1026
     */
1027 3
    public function reset($value, array $attributes = [])
1028
    {
1029 3
        return $this->input('reset', null, $value, $attributes);
1030
    }
1031
1032
    /**
1033
    * Create a HTML image input element.
1034
    *
1035
    * @param  string  $url
1036
    * @param  string  $name
1037
    * @param  array   $attributes
1038
    *
1039
     * @return \Illuminate\Support\HtmlString
1040
    */
1041 3
    public function image($url, $name = null, array $attributes = [])
1042
    {
1043 3
        return $this->input('image', $name, null, array_merge($attributes, [
1044 3
            'src' => $this->url->asset($url),
1045
        ]));
1046
    }
1047
1048
    /**
1049
     * Create a submit button element.
1050
     *
1051
     * @param  string  $value
1052
     * @param  array   $options
1053
     *
1054
     * @return \Illuminate\Support\HtmlString
1055
     */
1056 3
    public function submit($value = null, array $options = [])
1057
    {
1058 3
        return $this->input('submit', null, $value, $options);
1059
    }
1060
1061
    /**
1062
     * Create a button element.
1063
     *
1064
     * @param  string  $value
1065
     * @param  array   $options
1066
     *
1067
     * @return \Illuminate\Support\HtmlString
1068
     */
1069 3
    public function button($value = null, array $options = [])
1070
    {
1071 3
        if ( ! array_key_exists('type', $options))
1072 3
            $options['type'] = 'button';
1073
1074 3
        return $this->toHtmlString(
1075 3
            '<button'.$this->html->attributes($options).'>'.$value.'</button>'
1076
        );
1077
    }
1078
1079
    /**
1080
     * Create a color input field.
1081
     *
1082
     * @param  string  $name
1083
     * @param  string  $value
1084
     * @param  array   $options
1085
     *
1086
     * @return \Illuminate\Support\HtmlString
1087
     */
1088 9
    public function color($name, $value = null, array $options = [])
1089
    {
1090 9
        return $this->input('color', $name, $value, $options);
1091
    }
1092
1093
    /* -----------------------------------------------------------------
1094
     |  Other Methods
1095
     | -----------------------------------------------------------------
1096
     */
1097
1098
    /**
1099
     * Get the form action from the options.
1100
     *
1101
     * @param  array  $options
1102
     *
1103
     * @return string
1104
     */
1105 57
    private function getAction(array $options)
1106
    {
1107
        // We will also check for a "route" or "action" parameter on the array so that
1108
        // developers can easily specify a route or controller action when creating
1109
        // a form providing a convenient interface for creating the form actions.
1110 57
        if (isset($options['url']))
1111 18
            return $this->getUrlAction($options['url']);
1112
1113 39
        if (isset($options['route']))
1114 6
            return $this->getRouteAction($options['route']);
1115
1116 36
        if (isset($options['action']))
1117
            // If an action is available, we are attempting to open a form to a controller
1118
            // action route. So, we will use the URL generator to get the path to these
1119
            // actions and return them from the method. Otherwise, we'll use current.
1120 6
            return $this->getControllerAction($options['action']);
1121
1122 30
        return $this->url->current();
1123
    }
1124
1125
    /**
1126
     * Get the action for a "url" option.
1127
     *
1128
     * @param  array|string  $options
1129
     *
1130
     * @return string
1131
     */
1132 18
    private function getUrlAction($options)
1133
    {
1134 18
        return is_array($options)
1135 3
            ? $this->url->to($options[0], array_slice($options, 1))
1136 18
            : $this->url->to($options);
1137
    }
1138
1139
    /**
1140
     * Get the action for a "route" option.
1141
     *
1142
     * @param  array|string  $options
1143
     *
1144
     * @return string
1145
     */
1146 6
    private function getRouteAction($options)
1147
    {
1148 6
        return is_array($options)
1149 3
            ? $this->url->route($options[0], array_slice($options, 1))
1150 6
            : $this->url->route($options);
1151
    }
1152
1153
    /**
1154
     * Get the action for an "action" option.
1155
     *
1156
     * @param  array|string  $options
1157
     *
1158
     * @return string
1159
     */
1160 6
    private function getControllerAction($options)
1161
    {
1162 6
        return is_array($options)
1163 3
            ? $this->url->action($options[0], array_slice($options, 1))
1164 6
            : $this->url->action($options);
1165
    }
1166
1167
    /**
1168
     * Get the form appendage for the given method.
1169
     *
1170
     * @param  string  $method
1171
     *
1172
     * @return string
1173
     */
1174 57
    private function getAppendage($method)
1175
    {
1176 57
        list($method, $appendage) = [strtoupper($method), ''];
1177
1178
        // If the HTTP method is in this list of spoofed methods, we will attach the
1179
        // method spoofer hidden input to the form. This allows us to use regular
1180
        // form to initiate PUT and DELETE requests in addition to the typical.
1181 57
        if (in_array($method, $this->spoofedMethods))
1182 6
            $appendage .= $this->hidden('_method', $method);
1183
1184
        // If the method is something other than GET we will go ahead and attach the
1185
        // CSRF token to the form, as this can't hurt and is convenient to simply
1186
        // always have available on every form the developers creates for them.
1187 57
        if ($method !== 'GET')
1188 30
            $appendage .= $this->token();
1189
1190 57
        return $appendage;
1191
    }
1192
}
1193