Test Failed
Pull Request — main (#21)
by
unknown
07:08 queued 03:25
created

FormBuilder::attributeElement()   B

Complexity

Conditions 8
Paths 6

Size

Total Lines 25
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 5
Bugs 0 Features 0
Metric Value
eloc 9
c 5
b 0
f 0
dl 0
loc 25
rs 8.4444
cc 8
nc 6
nop 2
1
<?php
2
3
namespace Hafijul233\Form\Builders;
4
5
use BadMethodCallException;
6
use DateTime;
7
use Hafijul233\Form\Traits\ComponentTrait;
8
use Illuminate\Contracts\Routing\UrlGenerator;
9
use Illuminate\Contracts\Session\Session;
10
use Illuminate\Contracts\View\Factory;
11
use Illuminate\Http\Request;
12
use Illuminate\Support\Arr;
13
use Illuminate\Support\Collection;
14
use Illuminate\Support\Facades\Config;
15
use Illuminate\Support\HtmlString;
16
use Illuminate\Support\Traits\Macroable;
17
use Illuminate\Support\ViewErrorBag;
18
19
/**
20
 * Class FormBuilder
21
 */
22
class FormBuilder
23
{
24
    use Macroable, ComponentTrait {
0 ignored issues
show
Bug introduced by
The trait Illuminate\Support\Traits\Macroable requires the property $name which is not provided by Hafijul233\Form\Builders\FormBuilder.
Loading history...
25
        Macroable::__call as macroCall;
26
        ComponentTrait::__call as componentCall;
27
    }
28
29
    protected $payload;
30
31
    /**
32
     * The URL generator instance.
33
     *
34
     * @var UrlGenerator
35
     */
36
    protected $url;
37
38
    /**
39
     * The View factory instance.
40
     *
41
     * @var Factory
42
     */
43
    protected $view;
44
45
    /**
46
     * The CSRF token used by the form builder.
47
     *
48
     * @var string
49
     */
50
    protected $csrfToken;
51
52
    /**
53
     * Consider Request variables while auto fill.
54
     *
55
     * @var bool
56
     */
57
    protected $considerRequest = false;
58
59
    /**
60
     * The session store implementation.
61
     *
62
     * @var Session
63
     */
64
    protected $session;
65
66
    /**
67
     * The current model instance for the form.
68
     *
69
     * @var mixed
70
     */
71
    protected $model;
72
73
    /**
74
     * An array of label names we've created.
75
     *
76
     * @var array
77
     */
78
    protected $labels = [];
79
80
    protected $request;
81
82
    /**
83
     * The reserved form open attributes.
84
     *
85
     * @var array
86
     */
87
    protected $reserved = ['method', 'url', 'route', 'action', 'files'];
88
89
    /**
90
     * The form methods that should be spoofed, in uppercase.
91
     *
92
     * @var array
93
     */
94
    protected $spoofedMethods = ['DELETE', 'PATCH', 'PUT'];
95
96
    /**
97
     * The types of inputs to not fill values on by default.
98
     *
99
     * @var array
100
     */
101
    protected $skipValueTypes = ['file', 'password', 'checkbox', 'radio'];
102
103
    /**
104
     * Input Type.
105
     *
106
     * @var string|null
107
     */
108
    protected $type = 'text';
109
110
    /**
111
     * Create a new form builder instance.
112
     *
113
     * @param  Factory  $view
114
     * @param  string|null  $csrfToken
115
     * @param  UrlGenerator|null  $url
116
     * @param  Request|null  $request
117
     */
118
    public function __construct(Factory $view, string $csrfToken = null, UrlGenerator $url = null, Request $request = null)
119
    {
120
        $this->view = $view;
121
        $this->csrfToken = $csrfToken;
122
        $this->url = $url;
123
        $this->request = $request;
124
    }
125
126
    /**
127
     * Create a new model based form builder.
128
     *
129
     * @param  mixed  $model
130
     * @param  array  $options
131
     * @return HtmlString
132
     */
133
    public function model($model, array $options = []): HtmlString
134
    {
135
        $this->model = $model;
136
137
        return $this->open($options);
138
    }
139
140
    /**
141
     * Open up a new HTML form.
142
     *
143
     * @param  array  $options
144
     * @return HtmlString
145
     */
146
    public function open(array $options = []): HtmlString
147
    {
148
        $method = Arr::get($options, 'method', 'post');
149
150
        // We need to extract the proper method from the attributes. If the method is
151
        // something other than GET or POST we'll use POST since we will spoof the
152
        // actual method since forms don't support the reserved methods in HTML.
153
        $attributes['method'] = $this->getMethod($method);
0 ignored issues
show
Comprehensibility Best Practice introduced by
$attributes was never initialized. Although not strictly required by PHP, it is generally a good practice to add $attributes = array(); before regardless.
Loading history...
154
155
        $attributes['action'] = $this->getAction($options);
156
157
        $attributes['accept-charset'] = 'UTF-8';
158
159
        // If the method is PUT, PATCH or DELETE we will need to add a spoofer hidden
160
        // field that will instruct the Symfony request to pretend the method is a
161
        // different method than it actually is, for convenience from the forms.
162
        $append = $this->getAppendage($method);
163
164
        if (isset($options['files']) && $options['files']) {
165
            $options['enctype'] = 'multipart/form-data';
166
        }
167
168
        // Finally we're ready to create the final form HTML field. We will attribute
169
        // format the array of attributes. We will also add on the appendage which
170
        // is used to spoof requests for this PUT, PATCH, etc. methods on forms.
171
        $attributes = array_merge(
172
173
            $attributes, Arr::except($options, $this->reserved)
174
175
        );
176
177
        // Finally, we will concatenate all of the attributes into a single string so
178
        // we can build out the final form open statement. We'll also append on an
179
        // extra value for the hidden _method field if it's needed for the form.
180
        $attributes = $this->attributes($attributes);
181
182
        return $this->toHtmlString('<form'.$attributes.'>'.$append);
183
    }
184
185
    /**
186
     * Parse the form action method.
187
     *
188
     * @param  string  $method
189
     * @return string
190
     */
191
    protected function getMethod(string $method): string
192
    {
193
        $method = strtoupper($method);
194
195
        return $method !== 'GET' ? 'POST' : $method;
196
    }
197
198
    /**
199
     * Get the form action from the options.
200
     *
201
     * @param  array  $options
202
     * @return string
203
     */
204
    protected function getAction(array $options): string
205
    {
206
        // We will also check for a "route" or "action" parameter on the array so that
207
        // developers can easily specify a route or controller action when creating
208
        // a form providing a convenient interface for creating the form actions.
209
        if (isset($options['url'])) {
210
            return $this->getUrlAction($options['url']);
211
        }
212
213
        if (isset($options['route'])) {
214
            return $this->getRouteAction($options['route']);
215
        }
216
217
        // If an action is available, we are attempting to open a form to a controller
218
        // action route. So, we will use the URL generator to get the path to these
219
        // actions and return them from the method. Otherwise, we'll use current.
220
        elseif (isset($options['action'])) {
221
            return $this->getControllerAction($options['action']);
222
        }
223
224
        return $this->url->current();
225
    }
226
227
    /**
228
     * Get the action for a "url" option.
229
     *
230
     * @param  array|string  $options
231
     * @return string
232
     */
233
    protected function getUrlAction($options): string
234
    {
235
        if (is_array($options)) {
236
            return $this->url->to($options[0], array_slice($options, 1));
237
        }
238
239
        return $this->url->to($options);
240
    }
241
242
    /**
243
     * Get the action for a "route" option.
244
     *
245
     * @param  array|string  $options
246
     * @return string
247
     */
248
    protected function getRouteAction($options): string
249
    {
250
        if (is_array($options)) {
251
            $parameters = array_slice($options, 1);
252
253
            if (array_keys($options) === [0, 1]) {
254
                $parameters = head($parameters);
255
            }
256
257
            return $this->url->route($options[0], $parameters);
258
        }
259
260
        return $this->url->route($options);
261
    }
262
263
    /**
264
     * Get the action for an "action" option.
265
     *
266
     * @param  array|string  $options
267
     * @return string
268
     */
269
    protected function getControllerAction($options): string
270
    {
271
        if (is_array($options)) {
272
            return $this->url->action($options[0], array_slice($options, 1));
273
        }
274
275
        return $this->url->action($options);
276
    }
277
278
    /**
279
     * Get the form appendage for the given method.
280
     *
281
     * @param  string  $method
282
     * @return string
283
     */
284
    protected function getAppendage(string $method): string
285
    {
286
        [$method, $appendage] = [strtoupper($method), ''];
287
288
        // If the HTTP method is in this list of spoofed methods, we will attach the
289
        // method spoofer hidden input to the form. This allows us to use regular
290
        // form to initiate PUT and DELETE requests in addition to the typical.
291
        if (in_array($method, $this->spoofedMethods)) {
292
            $appendage .= $this->hidden('_method', $method);
293
        }
294
295
        // If the method is something other than GET we will go ahead and attach the
296
        // CSRF token to the form, as this can't hurt and is convenient to simply
297
        // always have available on every form the developers creates for them.
298
        if ($method !== 'GET') {
299
            $appendage .= $this->token();
300
        }
301
302
        return $appendage;
303
    }
304
305
    /**
306
     * Create a hidden input field.
307
     *
308
     * @param  string  $name
309
     * @param  null  $value
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $value is correct as it would always require null to be passed?
Loading history...
310
     * @param  array  $options
311
     * @return HtmlString
312
     */
313
    public function hidden(string $name, $value = null, array $options = []): HtmlString
314
    {
315
        return $this->input('hidden', $name, $value, $options);
316
    }
317
318
    /**
319
     * Create a form input field.
320
     *
321
     * @param  string  $type
322
     * @param  string  $name
323
     * @param  null  $value
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $value is correct as it would always require null to be passed?
Loading history...
324
     * @param  array  $options
325
     * @return HtmlString
326
     */
327
    public function input(string $type, string $name, $value = null, array $options = []): HtmlString
328
    {
329
        $this->type = $type;
330
331
        if (! isset($options['name'])) {
332
            $options['name'] = $name;
333
        }
334
335
        // We will get the appropriate value for the given field. We will look for the
336
        // value in the session for the value in the old input data then we'll look
337
        // in the model instance if one is set. Otherwise we will just use empty.
338
        $id = $this->getIdAttribute($name, $options);
339
340
        if (! in_array($type, $this->skipValueTypes)) {
341
            $value = $this->getValueAttribute($name, $value);
342
        }
343
344
        // Once we have the type, value, and ID we can merge them into the rest of the
345
        // attributes array so we can convert them into their HTML attribute format
346
        // when creating the HTML element. Then, we will return the entire input.
347
        $merge = compact('type', 'value', 'id');
348
349
        $options = array_merge($options, $merge);
350
351
        return $this->toHtmlString('<input'.$this->attributes($options).'>');
352
    }
353
354
    /**
355
     * Get the ID attribute for a field name.
356
     *
357
     * @param  string  $name
358
     * @param  array  $attributes
359
     * @return string|null
360
     */
361
    public function getIdAttribute(string $name, array $attributes): ?string
362
    {
363
        if (array_key_exists('id', $attributes)) {
364
            return $attributes['id'];
365
        }
366
367
        if (in_array($name, $this->labels)) {
368
            return $name;
369
        }
370
371
        return null;
372
    }
373
374
    /**
375
     * Get the value that should be assigned to the field.
376
     *
377
     * @param  string|null  $name
378
     * @param  null  $value
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $value is correct as it would always require null to be passed?
Loading history...
379
     * @return mixed
380
     */
381
    public function getValueAttribute(string $name = null, $value = null)
382
    {
383
        if (is_null($name)) {
384
            return $value;
385
        }
386
387
        $old = $this->old($name);
388
389
        if (! is_null($old) && $name !== '_method') {
390
            return $old;
391
        }
392
393
        if (function_exists('app')) {
394
            $hasNullMiddleware = app("Illuminate\Contracts\Http\Kernel")
395
                ->hasMiddleware('\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull');
396
397
            if ($hasNullMiddleware
398
                && is_null($old)
399
                && is_null($value)
400
                && ! is_null($this->view->shared('errors'))
401
                && count(is_countable($this->view->shared('errors')) ? $this->view->shared('errors') : []) > 0
0 ignored issues
show
Bug introduced by
It seems like is_countable($this->view...red('errors') : array() can also be of type null; however, parameter $value of count() does only seem to accept Countable|array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

401
                && count(/** @scrutinizer ignore-type */ is_countable($this->view->shared('errors')) ? $this->view->shared('errors') : []) > 0
Loading history...
402
            ) {
403
                return null;
404
            }
405
        }
406
407
        $request = $this->request($name);
408
        if (! is_null($request) && $name != '_method') {
409
            return $request;
410
        }
411
412
        if (! is_null($value)) {
413
            return $value;
414
        }
415
416
        if (isset($this->model)) {
417
            return $this->getModelValueAttribute($name);
418
        }
419
420
        return null;
421
    }
422
423
    /**
424
     * Get a value from the session's old input.
425
     *
426
     * @param  string  $name
427
     * @return mixed
428
     */
429
    public function old(string $name)
430
    {
431
        /*if (isset($this->session)) {*/
432
        $key = $this->transformKey($name);
433
        $payload = $this->session->getOldInput($key);
434
435
        if (! is_array($payload)) {
436
            return $payload;
437
        }
438
439
        if (! in_array($this->type, ['select', 'checkbox'])) {
440
            if (! isset($this->payload[$key])) {
441
                $this->payload[$key] = collect($payload);
442
            }
443
444
            if (! empty($this->payload[$key])) {
445
                return $this->payload[$key]->shift();
446
            }
447
        }
448
449
        return $payload;
450
        /*    }
451
452
            return [];*/
453
    }
454
455
    /**
456
     * Transform key from array to dot syntax.
457
     *
458
     * @param  string  $key
459
     * @return mixed
460
     */
461
    protected function transformKey(string $key)
462
    {
463
        return str_replace(['.', '[]', '[', ']'], ['_', '', '.', ''], $key);
464
    }
465
466
    /**
467
     * Get value from current Request
468
     *
469
     * @param $name
470
     * @return array|null|string
471
     */
472
    protected function request($name)
473
    {
474
        if (! $this->considerRequest) {
475
            return null;
476
        }
477
478
        if (! isset($this->request)) {
479
            return null;
480
        }
481
482
        return $this->request->input($this->transformKey($name));
0 ignored issues
show
Bug introduced by
The method input() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

482
        return $this->request->/** @scrutinizer ignore-call */ input($this->transformKey($name));

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
483
    }
484
485
    /**
486
     * Get the model value that should be assigned to the field.
487
     *
488
     * @param  string  $name
489
     * @return mixed
490
     */
491
    protected function getModelValueAttribute(string $name)
492
    {
493
        $key = $this->transformKey($name);
494
495
        if ((is_string($this->model) || is_object($this->model)) && method_exists($this->model, 'getFormValue')) {
496
            return $this->model->getFormValue($key);
497
        }
498
499
        return data_get($this->model, $key);
500
    }
501
502
    /**
503
     * Transform the string to an Html serializable object
504
     *
505
     * @param $html
506
     * @return HtmlString
507
     */
508
    protected function toHtmlString($html): HtmlString
509
    {
510
        return new HtmlString($html);
511
    }
512
513
    /**
514
     * Build an HTML attribute string from an array.
515
     *
516
     * @param  array  $attributes
517
     * @return string
518
     */
519
    public function attributes(array $attributes): string
520
    {
521
        $html = [];
522
523
        foreach ((array) $attributes as $key => $value) {
524
            $element = $this->attributeElement($key, $value);
525
526
            if ($element != null) {
527
                $html[] = $element;
528
            }
529
        }
530
531
        return count($html) > 0 ? ' '.implode(' ', $html) : '';
532
    }
533
534
    /**
535
     * Build a single attribute element.
536
     *
537
     * @param  string  $key
538
     * @param  mixed  $value
539
     * @return string
540
     */
541
    protected function attributeElement(string $key, $value): string
542
    {
543
        // For numeric keys we will assume that the value is a boolean attribute
544
        // where the presence of the attribute represents a true value and the
545
        // absence represents a false value.
546
        // This will convert HTML attributes such as "required" to a correct
547
        // form instead of using incorrect numerics.
548
        if (is_numeric($key)) {
549
            return $value;
550
        }
551
552
        // Treat boolean attributes as HTML properties
553
        if (is_bool($value) && $key !== 'value') {
554
            return $value ? $key : '';
555
        }
556
557
        if (is_array($value) && $key === 'class') {
558
            return 'class="'.implode(' ', $value).'"';
559
        }
560
561
        if (! is_null($value)) {
562
            return $key.'="'.e($value, false).'"';
563
        }
564
565
        return '';
566
    }
567
568
    /**
569
     * Generate a hidden field with the current CSRF token.
570
     *
571
     * @return string
572
     */
573
    public function token()
574
    {
575
        $token = ! empty($this->csrfToken) ? $this->csrfToken : $this->session->token();
576
577
        return $this->hidden('_token', $token);
578
    }
579
580
    /**
581
     * Get the current model instance on the form builder.
582
     *
583
     * @return mixed $model
584
     */
585
    public function getModel()
586
    {
587
        return $this->model;
588
    }
589
590
    /**
591
     * Set the model instance on the form builder.
592
     *
593
     * @param  mixed  $model
594
     * @return void
595
     */
596
    public function setModel($model)
597
    {
598
        $this->model = $model;
599
    }
600
601
    /**
602
     * Close the current form.
603
     *
604
     * @return string
605
     */
606
    public function close()
607
    {
608
        $this->labels = [];
609
610
        $this->model = null;
611
612
        return $this->toHtmlString('</form>');
613
    }
614
615
    /**
616
     * Create a form label element.
617
     *
618
     * @param  string  $name
619
     * @param  null  $title
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $title is correct as it would always require null to be passed?
Loading history...
620
     * @param  bool  $required
621
     * @param  array  $options
622
     * @param  bool  $escape_html
623
     * @return HtmlString
624
     */
625
    public function label(string $name, $title = null, bool $required = false, array $options = [], $escape_html = true): HtmlString
626
    {
627
        $this->labels[] = $name;
628
629
        $options = $this->attributes($options);
630
631
        $title = $this->formatLabel($name, $title);
632
633
        if ($escape_html) {
634
            $title = $this->entities($title);
635
        }
636
637
        if ($required) {
638
            $title = $title.'<span style="color: #dc3545; font-weight:700">*</span>';
639
        }
640
641
        return $this->toHtmlString("<label for=\"{$name}\" {$options}>{$title}</label>");
642
    }
643
644
    /**
645
     * Format the label value.
646
     *
647
     * @param  string  $name
648
     * @param  string|null  $value
649
     * @return string
650
     */
651
    protected function formatLabel(string $name, string $value = null): string
652
    {
653
        return $value ?: ucwords(str_replace('_', ' ', $name));
654
    }
655
656
    /**
657
     * Convert an HTML string to entities.
658
     *
659
     * @param  string  $value
660
     * @return string
661
     */
662
    public function entities(string $value): string
663
    {
664
        return htmlentities($value, ENT_QUOTES, 'UTF-8', false);
665
    }
666
667
    /**
668
     * Create a text input field.
669
     *
670
     * @param  string  $name
671
     * @param  string  $value
672
     * @param  array  $options
673
     * @return HtmlString
674
     */
675
    public function text(string $name, $value = null, array $options = []): HtmlString
676
    {
677
        return $this->input('text', $name, $value, $options);
0 ignored issues
show
Bug introduced by
It seems like $value can also be of type string; however, parameter $value of Hafijul233\Form\Builders\FormBuilder::input() does only seem to accept null, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

677
        return $this->input('text', $name, /** @scrutinizer ignore-type */ $value, $options);
Loading history...
678
    }
679
680
    /**
681
     * Create a password input field.
682
     *
683
     * @param  string  $name
684
     * @param  array  $options
685
     * @return HtmlString
686
     */
687
    public function password(string $name, array $options = []): HtmlString
688
    {
689
        return $this->input('password', $name, '', $options);
690
    }
691
692
    /**
693
     * Create a range input field.
694
     *
695
     * @param  string  $name
696
     * @param  string  $value
697
     * @param  array  $options
698
     * @return HtmlString
699
     */
700
    public function range(string $name, $value = null, array $options = []): HtmlString
701
    {
702
        return $this->input('range', $name, $value, $options);
0 ignored issues
show
Bug introduced by
It seems like $value can also be of type string; however, parameter $value of Hafijul233\Form\Builders\FormBuilder::input() does only seem to accept null, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

702
        return $this->input('range', $name, /** @scrutinizer ignore-type */ $value, $options);
Loading history...
703
    }
704
705
    /**
706
     * Create a search input field.
707
     *
708
     * @param  string  $name
709
     * @param  string  $value
710
     * @param  array  $options
711
     * @return HtmlString
712
     */
713
    public function search(string $name, $value = null, array $options = []): HtmlString
714
    {
715
        return $this->input('search', $name, $value, $options);
0 ignored issues
show
Bug introduced by
It seems like $value can also be of type string; however, parameter $value of Hafijul233\Form\Builders\FormBuilder::input() does only seem to accept null, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

715
        return $this->input('search', $name, /** @scrutinizer ignore-type */ $value, $options);
Loading history...
716
    }
717
718
    /**
719
     * Create an e-mail input field.
720
     *
721
     * @param  string  $name
722
     * @param  string  $value
723
     * @param  array  $options
724
     * @return HtmlString
725
     */
726
    public function email(string $name, $value = null, array $options = []): HtmlString
727
    {
728
        return $this->input('email', $name, $value, $options);
0 ignored issues
show
Bug introduced by
It seems like $value can also be of type string; however, parameter $value of Hafijul233\Form\Builders\FormBuilder::input() does only seem to accept null, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

728
        return $this->input('email', $name, /** @scrutinizer ignore-type */ $value, $options);
Loading history...
729
    }
730
731
    /**
732
     * Create a tel input field.
733
     *
734
     * @param  string  $name
735
     * @param  string  $value
736
     * @param  array  $options
737
     * @return HtmlString
738
     */
739
    public function tel(string $name, $value = null, array $options = []): HtmlString
740
    {
741
        return $this->input('tel', $name, $value, $options);
0 ignored issues
show
Bug introduced by
It seems like $value can also be of type string; however, parameter $value of Hafijul233\Form\Builders\FormBuilder::input() does only seem to accept null, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

741
        return $this->input('tel', $name, /** @scrutinizer ignore-type */ $value, $options);
Loading history...
742
    }
743
744
    /**
745
     * Create a number input field.
746
     *
747
     * @param  string  $name
748
     * @param  string  $value
749
     * @param  array  $options
750
     * @return HtmlString
751
     */
752
    public function number(string $name, $value = null, array $options = []): HtmlString
753
    {
754
        return $this->input('number', $name, $value, $options);
0 ignored issues
show
Bug introduced by
It seems like $value can also be of type string; however, parameter $value of Hafijul233\Form\Builders\FormBuilder::input() does only seem to accept null, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

754
        return $this->input('number', $name, /** @scrutinizer ignore-type */ $value, $options);
Loading history...
755
    }
756
757
    /**
758
     * Create a date input field.
759
     *
760
     * @param  string  $name
761
     * @param  mixed  $value
762
     * @param  array  $options
763
     * @return HtmlString
764
     */
765
    public function date(string $name, $value = null, array $options = []): HtmlString
766
    {
767
        if ($value instanceof DateTime) {
768
            $value = $value->format('Y-m-d');
769
        }
770
771
        return $this->input('date', $name, $value, $options);
0 ignored issues
show
Bug introduced by
It seems like $value can also be of type string; however, parameter $value of Hafijul233\Form\Builders\FormBuilder::input() does only seem to accept null, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

771
        return $this->input('date', $name, /** @scrutinizer ignore-type */ $value, $options);
Loading history...
772
    }
773
774
    /**
775
     * Create a datetime input field.
776
     *
777
     * @param  string  $name
778
     * @param  mixed  $value
779
     * @param  array  $options
780
     * @return HtmlString
781
     */
782
    public function datetime(string $name, $value = null, array $options = []): HtmlString
783
    {
784
        if ($value instanceof DateTime) {
785
            $value = $value->format(DateTime::RFC3339);
786
        }
787
788
        return $this->input('datetime', $name, $value, $options);
0 ignored issues
show
Bug introduced by
It seems like $value can also be of type string; however, parameter $value of Hafijul233\Form\Builders\FormBuilder::input() does only seem to accept null, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

788
        return $this->input('datetime', $name, /** @scrutinizer ignore-type */ $value, $options);
Loading history...
789
    }
790
791
    /**
792
     * Create a datetime-local input field.
793
     *
794
     * @param  string  $name
795
     * @param  mixed  $value
796
     * @param  array  $options
797
     * @return HtmlString
798
     */
799
    public function datetimeLocal(string $name, $value = null, array $options = []): HtmlString
800
    {
801
        if ($value instanceof DateTime) {
802
            $value = $value->format('Y-m-d\TH:i');
803
        }
804
805
        return $this->input('datetime-local', $name, $value, $options);
0 ignored issues
show
Bug introduced by
It seems like $value can also be of type string; however, parameter $value of Hafijul233\Form\Builders\FormBuilder::input() does only seem to accept null, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

805
        return $this->input('datetime-local', $name, /** @scrutinizer ignore-type */ $value, $options);
Loading history...
806
    }
807
808
    /**
809
     * Create a time input field.
810
     *
811
     * @param  string  $name
812
     * @param  mixed  $value
813
     * @param  array  $options
814
     * @return HtmlString
815
     */
816
    public function time(string $name, $value = null, array $options = []): HtmlString
817
    {
818
        if ($value instanceof DateTime) {
819
            $value = $value->format('H:i');
820
        }
821
822
        return $this->input('time', $name, $value, $options);
0 ignored issues
show
Bug introduced by
It seems like $value can also be of type string; however, parameter $value of Hafijul233\Form\Builders\FormBuilder::input() does only seem to accept null, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

822
        return $this->input('time', $name, /** @scrutinizer ignore-type */ $value, $options);
Loading history...
823
    }
824
825
    /**
826
     * Create a url input field.
827
     *
828
     * @param  string  $name
829
     * @param  string  $value
830
     * @param  array  $options
831
     * @return HtmlString
832
     */
833
    public function url(string $name, $value = null, array $options = []): HtmlString
834
    {
835
        return $this->input('url', $name, $value, $options);
0 ignored issues
show
Bug introduced by
It seems like $value can also be of type string; however, parameter $value of Hafijul233\Form\Builders\FormBuilder::input() does only seem to accept null, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

835
        return $this->input('url', $name, /** @scrutinizer ignore-type */ $value, $options);
Loading history...
836
    }
837
838
    /**
839
     * Create a week input field.
840
     *
841
     * @param  string  $name
842
     * @param  mixed  $value
843
     * @param  array  $options
844
     * @return HtmlString
845
     */
846
    public function week(string $name, $value = null, array $options = []): HtmlString
847
    {
848
        if ($value instanceof DateTime) {
849
            $value = $value->format('Y-\WW');
850
        }
851
852
        return $this->input('week', $name, $value, $options);
0 ignored issues
show
Bug introduced by
It seems like $value can also be of type string; however, parameter $value of Hafijul233\Form\Builders\FormBuilder::input() does only seem to accept null, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

852
        return $this->input('week', $name, /** @scrutinizer ignore-type */ $value, $options);
Loading history...
853
    }
854
855
    /**
856
     * Create a file input field.
857
     *
858
     * @param  string  $name
859
     * @param  array  $options
860
     * @return HtmlString
861
     */
862
    public function file(string $name, array $options = []): HtmlString
863
    {
864
        return $this->input('file', $name, null, $options);
865
    }
866
867
    /**
868
     * Create a textarea input field.
869
     *
870
     * @param  string  $name
871
     * @param  string  $value
872
     * @param  array  $options
873
     * @return HtmlString
874
     */
875
    public function textarea(string $name, $value = null, array $options = []): HtmlString
876
    {
877
        $this->type = 'textarea';
878
879
        if (! isset($options['name'])) {
880
            $options['name'] = $name;
881
        }
882
883
        // Next we will look for the rows and cols attributes, as each of these are put
884
        // on the textarea element definition. If they are not present, we will just
885
        // assume some sane default values for these attributes for the developer.
886
        $options = $this->setTextAreaSize($options);
887
888
        $options['id'] = $this->getIdAttribute($name, $options);
889
890
        $value = (string) $this->getValueAttribute($name, $value);
0 ignored issues
show
Bug introduced by
It seems like $value can also be of type string; however, parameter $value of Hafijul233\Form\Builders...er::getValueAttribute() does only seem to accept null, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

890
        $value = (string) $this->getValueAttribute($name, /** @scrutinizer ignore-type */ $value);
Loading history...
891
892
        unset($options['size']);
893
894
        // Next we will convert the attributes into a string form. Also we have removed
895
        // the size attribute, as it was merely a short-cut for the rows and cols on
896
        // the element. Then we'll create the final textarea elements HTML for us.
897
        $options = $this->attributes($options);
898
899
        return $this->toHtmlString('<textarea'.$options.'>'.e($value, false).'</textarea>');
900
    }
901
902
    /**
903
     * Set the text area size on the attributes.
904
     *
905
     * @param  array  $options
906
     * @return array
907
     */
908
    protected function setTextAreaSize(array $options): array
909
    {
910
        if (isset($options['size'])) {
911
            return $this->setQuickTextAreaSize($options);
912
        }
913
914
        // If the "size" attribute was not specified, we will just look for the regular
915
        // columns and rows attributes, using sane defaults if these do not exist on
916
        // the attributes array. We'll then return this entire options array back.
917
        $cols = Arr::get($options, 'cols', 50);
918
919
        $rows = Arr::get($options, 'rows', 10);
920
921
        return array_merge($options, compact('cols', 'rows'));
922
    }
923
924
    /**
925
     * Set the text area size using the quick "size" attribute.
926
     *
927
     * @param  array  $options
928
     * @return array
929
     */
930
    protected function setQuickTextAreaSize(array $options): array
931
    {
932
        $segments = explode('x', $options['size']);
933
934
        return array_merge($options, ['cols' => $segments[0], 'rows' => $segments[1]]);
935
    }
936
937
    /**
938
     * Create a select year field.
939
     *
940
     * @param  string  $name
941
     * @param  string  $begin
942
     * @param  string  $end
943
     * @param  string|null  $selected
944
     * @param  array  $options
945
     * @return HtmlString
946
     */
947
    public function selectYear(string $name, string $begin, string $end, string $selected = null, array $options = []): HtmlString
948
    {
949
        return $this->selectRange($name, $begin, $end, $selected, $options);
950
    }
951
952
    /**
953
     * Create a select range field.
954
     *
955
     * @param  string  $name
956
     * @param  string  $begin
957
     * @param  string  $end
958
     * @param  string  $selected
959
     * @param  array  $options
960
     * @return HtmlString
961
     */
962
    public function selectRange(string $name, $begin, $end, $selected = null, array $options = []): HtmlString
963
    {
964
        $range = array_combine($range = range($begin, $end), $range);
965
966
        return $this->select($name, $range, $selected, $options);
0 ignored issues
show
Bug introduced by
It seems like $selected can also be of type string; however, parameter $selected of Hafijul233\Form\Builders\FormBuilder::select() does only seem to accept null, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

966
        return $this->select($name, $range, /** @scrutinizer ignore-type */ $selected, $options);
Loading history...
967
    }
968
969
    /**
970
     * Create a select box field.
971
     *
972
     * @param  string  $name
973
     * @param  array  $list
974
     * @param  null  $selected
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $selected is correct as it would always require null to be passed?
Loading history...
975
     * @param  array  $selectAttributes
976
     * @param  array  $optionsAttributes
977
     * @param  array  $optgroupsAttributes
978
     * @return HtmlString
979
     */
980
    public function select(
981
        string $name,
982
        array $list = [],
983
        $selected = null,
984
        array $selectAttributes = [],
985
        array $optionsAttributes = [],
986
        array $optgroupsAttributes = []
987
    ): HtmlString {
988
        $this->type = 'select';
989
990
        // When building a select box the "value" attribute is really the selected one
991
        // so we will use that when checking the model or session for a value which
992
        // should provide a convenient method of re-populating the forms on post.
993
        $selected = $this->getValueAttribute($name, $selected);
994
995
        $selectAttributes['id'] = $this->getIdAttribute($name, $selectAttributes);
996
997
        if (! isset($selectAttributes['name'])) {
998
            $selectAttributes['name'] = $name;
999
        }
1000
1001
        // We will simply loop through the options and build an HTML value for each of
1002
        // them until we have an array of HTML declarations. Then we will join them
1003
        // all together into one single HTML element that can be put on the form.
1004
        $html = [];
1005
1006
        if (isset($selectAttributes['placeholder'])) {
1007
            $html[] = $this->placeholderOption($selectAttributes['placeholder'], $selected);
1008
            unset($selectAttributes['placeholder']);
1009
        }
1010
1011
        foreach ($list as $value => $display) {
1012
            $optionAttributes = $optionsAttributes[$value] ?? [];
1013
            $optgroupAttributes = $optgroupsAttributes[$value] ?? [];
1014
            $html[] = $this->getSelectOption($display, $value, $selected, $optionAttributes, $optgroupAttributes);
1015
        }
1016
1017
        // Once we have all of this HTML, we can join this into a single element after
1018
        // formatting the attributes into an HTML "attributes" string, then we will
1019
        // build out a final select statement, which will contain all the values.
1020
        $selectAttributes = $this->attributes($selectAttributes);
1021
1022
        $list = implode('', $html);
1023
1024
        return $this->toHtmlString("<select{$selectAttributes}>{$list}</select>");
1025
    }
1026
1027
    /**
1028
     * Create a placeholder select element option.
1029
     *
1030
     * @param $display
1031
     * @param $selected
1032
     * @return HtmlString
1033
     */
1034
    protected function placeholderOption($display, $selected): HtmlString
1035
    {
1036
        $selected = $this->getSelectedValue(null, $selected);
1037
1038
        $options = [
1039
            'selected' => $selected,
1040
            'value' => '',
1041
        ];
1042
1043
        return $this->toHtmlString('<option'.$this->attributes($options).'>'.e($display, false).'</option>');
1044
    }
1045
1046
    /**
1047
     * Determine if the value is selected.
1048
     *
1049
     * @param  string|null  $value
1050
     * @param  mixed  $selected
1051
     * @return mixed
1052
     */
1053
    protected function getSelectedValue($value, $selected)
1054
    {
1055
        if (is_array($selected)) {
1056
            return in_array($value, $selected, true) || in_array((string) $value, $selected, true) ? 'selected' : null;
1057
        } elseif ($selected instanceof Collection) {
1058
            return $selected->contains($value) ? 'selected' : null;
1059
        }
1060
        /** @phpstan-ignore-next-line */
1061
        if (is_int($value) && is_bool($selected)) {
0 ignored issues
show
introduced by
The condition is_int($value) is always false.
Loading history...
1062
            return (bool) $value === $selected;
1063
        }
1064
1065
        return ((string) $value === (string) $selected) ? 'selected' : null;
1066
    }
1067
1068
    /**
1069
     * Get the select option for the given value.
1070
     *
1071
     * @param  mixed  $display
1072
     * @param  string  $value
1073
     * @param  string|null  $selected
1074
     * @param  array  $attributes
1075
     * @param  array  $optgroupAttributes
1076
     * @return HtmlString
1077
     */
1078
    public function getSelectOption($display, string $value, $selected, array $attributes = [], array $optgroupAttributes = []): HtmlString
1079
    {
1080
        if (is_iterable($display)) {
1081
            return $this->optionGroup($display, $value, $selected, $optgroupAttributes, $attributes);
0 ignored issues
show
Bug introduced by
It seems like $selected can also be of type null; however, parameter $selected of Hafijul233\Form\Builders...mBuilder::optionGroup() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1081
            return $this->optionGroup($display, $value, /** @scrutinizer ignore-type */ $selected, $optgroupAttributes, $attributes);
Loading history...
1082
        }
1083
1084
        return $this->option($display, $value, $selected, $attributes);
1085
    }
1086
1087
    /**
1088
     * Create an option group form element.
1089
     *
1090
     * @param  array  $list
1091
     * @param  string  $label
1092
     * @param  string  $selected
1093
     * @param  array  $attributes
1094
     * @param  array  $optionsAttributes
1095
     * @param  int  $level
1096
     * @return HtmlString
1097
     */
1098
    protected function optionGroup(array $list, string $label, string $selected, array $attributes = [], array $optionsAttributes = [], $level = 0): HtmlString
1099
    {
1100
        $html = [];
1101
        $space = str_repeat('&nbsp;', $level);
1102
        foreach ($list as $value => $display) {
1103
            $optionAttributes = $optionsAttributes[$value] ?? [];
1104
            if (is_iterable($display)) {
1105
                $html[] = $this->optionGroup($display, $value, $selected, $attributes, $optionAttributes, $level + 5);
1106
            } else {
1107
                $html[] = $this->option($space.$display, $value, $selected, $optionAttributes);
1108
            }
1109
        }
1110
1111
        return $this->toHtmlString('<optgroup label="'.e($space.$label, false).'"'.$this->attributes($attributes).'>'.implode('', $html).'</optgroup>');
1112
    }
1113
1114
    /**
1115
     * Create a select element option.
1116
     *
1117
     * @param  string  $display
1118
     * @param  string  $value
1119
     * @param  string|null  $selected
1120
     * @param  array  $attributes
1121
     * @return HtmlString
1122
     */
1123
    protected function option(string $display, string $value, $selected, array $attributes = []): HtmlString
1124
    {
1125
        $selected = $this->getSelectedValue($value, $selected);
1126
1127
        $options = array_merge(['value' => $value, 'selected' => $selected], $attributes);
1128
1129
        $string = '<option'.$this->attributes($options).'>';
1130
        if ($display !== null) {
0 ignored issues
show
introduced by
The condition $display !== null is always true.
Loading history...
1131
            $string .= e($display, false).'</option>';
1132
        }
1133
1134
        return $this->toHtmlString($string);
1135
    }
1136
1137
    /**
1138
     * Create a select month field.
1139
     *
1140
     * @param  string  $name
1141
     * @param  null  $selected
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $selected is correct as it would always require null to be passed?
Loading history...
1142
     * @param  array  $options
1143
     * @return HtmlString
1144
     */
1145
    public function selectMonth(string $name, $selected = null, array $options = []): HtmlString
1146
    {
1147
        $months = Config::get('form.months', [
1148
            '1' => 'January',
1149
            '2' => 'February',
1150
            '3' => 'March',
1151
            '4' => 'April',
1152
            '5' => 'May',
1153
            '6' => 'June',
1154
            '7' => 'July',
1155
            '8' => 'August',
1156
            '9' => 'September',
1157
            '10' => 'October',
1158
            '11' => 'November',
1159
            '12' => 'December',
1160
        ]);
1161
1162
        return $this->select($name, $months, $selected, $options);
1163
    }
1164
1165
    /**
1166
     * Create a checkbox input field.
1167
     *
1168
     * @param  string  $name
1169
     * @param  mixed  $value
1170
     * @param  bool  $checked
1171
     * @param  array  $options
1172
     * @return HtmlString
1173
     */
1174
    public function checkbox(string $name, $value = 1, $checked = null, array $options = []): HtmlString
1175
    {
1176
        return $this->checkable('checkbox', $name, $value, $checked, $options);
0 ignored issues
show
Bug introduced by
It seems like $checked can also be of type null; however, parameter $checked of Hafijul233\Form\Builders\FormBuilder::checkable() does only seem to accept boolean, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1176
        return $this->checkable('checkbox', $name, $value, /** @scrutinizer ignore-type */ $checked, $options);
Loading history...
1177
    }
1178
1179
    /**
1180
     * Create a checkable input field.
1181
     *
1182
     * @param  string  $type
1183
     * @param  string  $name
1184
     * @param  mixed  $value
1185
     * @param  bool  $checked
1186
     * @param  array  $options
1187
     * @return HtmlString
1188
     */
1189
    protected function checkable(string $type, string $name, $value, bool $checked, array $options = []): HtmlString
1190
    {
1191
        $this->type = $type;
1192
1193
        $checked = $this->getCheckedState($type, $name, $value, $checked);
1194
1195
        if ($checked) {
1196
            $options['checked'] = 'checked';
1197
        }
1198
1199
        return $this->input($type, $name, $value, $options);
1200
    }
1201
1202
    /**
1203
     * Get the check state for a checkable input.
1204
     *
1205
     * @param  string  $type
1206
     * @param  string  $name
1207
     * @param  mixed  $value
1208
     * @param  bool  $checked
1209
     * @return bool
1210
     */
1211
    protected function getCheckedState(string $type, string $name, $value, bool $checked): bool
1212
    {
1213
        switch ($type) {
1214
            case 'checkbox':
1215
                return $this->getCheckboxCheckedState($name, $value, $checked);
1216
1217
            case 'radio':
1218
                return $this->getRadioCheckedState($name, $value, $checked);
1219
1220
            default:
1221
                return $this->compareValues($name, $value);
1222
        }
1223
    }
1224
1225
    /**
1226
     * Get the check state for a checkbox input.
1227
     *
1228
     * @param  string  $name
1229
     * @param  mixed  $value
1230
     * @param  bool  $checked
1231
     * @return bool
1232
     */
1233
    protected function getCheckboxCheckedState(string $name, $value, bool $checked): bool
1234
    {
1235
        $request = $this->request($name);
1236
1237
        if (! $this->oldInputIsEmpty() && is_null($this->old($name)) && ! $request) {
1238
            return false;
1239
        }
1240
1241
        if ($this->missingOldAndModel($name) && is_null($request)) {
1242
            return $checked;
1243
        }
1244
1245
        $posted = $this->getValueAttribute($name, $checked);
1246
1247
        if (is_array($posted)) {
1248
            return in_array($value, $posted);
1249
        } elseif ($posted instanceof Collection) {
1250
            return $posted->contains('id', $value);
1251
        } else {
1252
            return (bool) $posted;
1253
        }
1254
    }
1255
1256
    /**
1257
     * Determine if the old input is empty.
1258
     *
1259
     * @return bool
1260
     */
1261
    public function oldInputIsEmpty(): bool
1262
    {
1263
        return count((array) $this->session->getOldInput()) === 0;
1264
    }
1265
1266
    /**
1267
     * Determine if old input or model input exists for a key.
1268
     *
1269
     * @param  string  $name
1270
     * @return bool
1271
     */
1272
    protected function missingOldAndModel(string $name): bool
1273
    {
1274
        return is_null($this->old($name)) && is_null($this->getModelValueAttribute($name));
1275
    }
1276
1277
    /**
1278
     * Get the check state for a radio input.
1279
     *
1280
     * @param  string  $name
1281
     * @param  mixed  $value
1282
     * @param  bool  $checked
1283
     * @return bool
1284
     */
1285
    protected function getRadioCheckedState(string $name, $value, bool $checked): bool
1286
    {
1287
        $request = $this->request($name);
1288
1289
        if ($this->missingOldAndModel($name) && ! $request) {
1290
            return $checked;
1291
        }
1292
1293
        return $this->compareValues($name, $value);
1294
    }
1295
1296
    /**
1297
     * Determine if the provide value loosely compares to the value assigned to the field.
1298
     * Use loose comparison because Laravel model casting may be in affect and therefore
1299
     * 1 == true and 0 == false.
1300
     *
1301
     * @param  string  $name
1302
     * @param  string  $value
1303
     * @return bool
1304
     */
1305
    protected function compareValues(string $name, string $value): bool
1306
    {
1307
        return $this->getValueAttribute($name) == $value;
1308
    }
1309
1310
    /**
1311
     * Create a radio button input field.
1312
     *
1313
     * @param  string  $name
1314
     * @param  mixed  $value
1315
     * @param  bool  $checked
1316
     * @param  array  $options
1317
     * @return HtmlString
1318
     */
1319
    public function radio(string $name, $value = null, $checked = null, array $options = []): HtmlString
1320
    {
1321
        if (is_null($value)) {
1322
            $value = $name;
1323
        }
1324
1325
        return $this->checkable('radio', $name, $value, $checked, $options);
0 ignored issues
show
Bug introduced by
It seems like $checked can also be of type null; however, parameter $checked of Hafijul233\Form\Builders\FormBuilder::checkable() does only seem to accept boolean, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1325
        return $this->checkable('radio', $name, $value, /** @scrutinizer ignore-type */ $checked, $options);
Loading history...
1326
    }
1327
1328
    /**
1329
     * Create a HTML reset input element.
1330
     *
1331
     * @param  string  $value
1332
     * @param  array  $attributes
1333
     * @return HtmlString
1334
     */
1335
    public function reset(string $value, $attributes = []): HtmlString
1336
    {
1337
        return $this->input('reset', null, $value, $attributes);
0 ignored issues
show
Bug introduced by
null of type null is incompatible with the type string expected by parameter $name of Hafijul233\Form\Builders\FormBuilder::input(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1337
        return $this->input('reset', /** @scrutinizer ignore-type */ null, $value, $attributes);
Loading history...
1338
    }
1339
1340
    /**
1341
     * Create a HTML image input element.
1342
     *
1343
     * @param  string  $url
1344
     * @param  null  $name
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $name is correct as it would always require null to be passed?
Loading history...
1345
     * @param  array  $attributes
1346
     * @return HtmlString
1347
     */
1348
    public function image(string $url, $name = null, $attributes = []): HtmlString
1349
    {
1350
        $attributes['src'] = $this->url->asset($url);
1351
1352
        return $this->input('image', $name, null, $attributes);
0 ignored issues
show
Bug introduced by
$name of type null is incompatible with the type string expected by parameter $name of Hafijul233\Form\Builders\FormBuilder::input(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1352
        return $this->input('image', /** @scrutinizer ignore-type */ $name, null, $attributes);
Loading history...
1353
    }
1354
1355
    /**
1356
     * Create a month input field.
1357
     *
1358
     * @param  string  $name
1359
     * @param  mixed  $value
1360
     * @param  array  $options
1361
     * @return HtmlString
1362
     */
1363
    public function month(string $name, $value = null, array $options = []): HtmlString
1364
    {
1365
        if ($value instanceof DateTime) {
1366
            $value = $value->format('Y-m');
1367
        }
1368
1369
        return $this->input('month', $name, $value, $options);
0 ignored issues
show
Bug introduced by
It seems like $value can also be of type string; however, parameter $value of Hafijul233\Form\Builders\FormBuilder::input() does only seem to accept null, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1369
        return $this->input('month', $name, /** @scrutinizer ignore-type */ $value, $options);
Loading history...
1370
    }
1371
1372
    /**
1373
     * Create a color input field.
1374
     *
1375
     * @param  string  $name
1376
     * @param  string  $value
1377
     * @param  array  $options
1378
     * @return HtmlString
1379
     */
1380
    public function color(string $name, $value = null, array $options = []): HtmlString
1381
    {
1382
        return $this->input('color', $name, $value, $options);
0 ignored issues
show
Bug introduced by
It seems like $value can also be of type string; however, parameter $value of Hafijul233\Form\Builders\FormBuilder::input() does only seem to accept null, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1382
        return $this->input('color', $name, /** @scrutinizer ignore-type */ $value, $options);
Loading history...
1383
    }
1384
1385
    /**
1386
     * Create a submit button element.
1387
     *
1388
     * @param  string  $name
1389
     * @param  null  $value
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $value is correct as it would always require null to be passed?
Loading history...
1390
     * @param  bool  $button
1391
     * @param  array  $options
1392
     * @return HtmlString
1393
     */
1394
    public function submit($name = 'submit', $value = null, $button = false, array $options = []): HtmlString
1395
    {
1396
        if ($button) {
1397
            $options['type'] = 'submit';
1398
            $options['name'] = $name;
1399
1400
            if (empty($options['class'])) {
1401
                $options['class'] = Config::get('form.submit_class', 'btn btn-primary fw-bold');
1402
            }
1403
1404
            return $this->button($value, $options);
1405
        }
1406
1407
        return $this->input('submit', null, $value, $options);
0 ignored issues
show
Bug introduced by
null of type null is incompatible with the type string expected by parameter $name of Hafijul233\Form\Builders\FormBuilder::input(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1407
        return $this->input('submit', /** @scrutinizer ignore-type */ null, $value, $options);
Loading history...
1408
    }
1409
1410
    /**
1411
     * Create a button element.
1412
     *
1413
     * @param  string  $value
1414
     * @param  array  $options
1415
     * @return HtmlString
1416
     */
1417
    public function button($value = null, array $options = []): HtmlString
1418
    {
1419
        if (! array_key_exists('type', $options)) {
1420
            $options['type'] = 'button';
1421
        }
1422
1423
        return $this->toHtmlString('<button'.$this->attributes($options).'>'.$value.'</button>');
1424
    }
1425
1426
    /**
1427
     * Create a datalist box field.
1428
     *
1429
     * @param  string  $id
1430
     * @param  array  $list
1431
     * @return HtmlString
1432
     */
1433
    public function datalist(string $id, $list = []): HtmlString
1434
    {
1435
        $this->type = 'datalist';
1436
1437
        $attributes['id'] = $id;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$attributes was never initialized. Although not strictly required by PHP, it is generally a good practice to add $attributes = array(); before regardless.
Loading history...
1438
1439
        $html = [];
1440
1441
        if ($this->isAssociativeArray($list)) {
1442
            foreach ($list as $value => $display) {
1443
                $html[] = $this->option($display, $value, null, []);
1444
            }
1445
        } else {
1446
            foreach ($list as $value) {
1447
                $html[] = $this->option($value, $value, null, []);
1448
            }
1449
        }
1450
1451
        $attributes = $this->attributes($attributes);
1452
1453
        $list = implode('', $html);
1454
1455
        return $this->toHtmlString("<datalist{$attributes}>{$list}</datalist>");
1456
    }
1457
1458
    /**
1459
     * Determine if an array is associative.
1460
     *
1461
     * @param  array  $array
1462
     * @return bool
1463
     */
1464
    protected function isAssociativeArray(array $array): bool
1465
    {
1466
        return array_values($array) !== $array;
1467
    }
1468
1469
    /**
1470
     * Create a form error display element.
1471
     *
1472
     * @param  string  $name
1473
     * @param  bool  $all
1474
     * @param  array  $options
1475
     * @return HtmlString
1476
     */
1477
    public function error(string $name, bool $all = false, array $options = []): HtmlString
1478
    {
1479
        if (empty($options['class'])) {
1480
            $options['class'] = Config::get('form.error_class', 'invalid-feedback');
1481
        }
1482
1483
        return $this->getErrorMessage($name, $all, $options);
1484
    }
1485
1486
    /**
1487
     * @param  string  $name
1488
     * @param  bool  $list
1489
     * @param  array  $options
1490
     * @return HtmlString
1491
     */
1492
    protected function getErrorMessage(string $name, bool $list = false, array $options = []): HtmlString
1493
    {
1494
        $errors = $this->request->session()->get('errors') ?? new ViewErrorBag;
1495
1496
        $errorMessage = ($list)
1497
            ? (($errors->all($name) ?? []))
1498
            : ($errors->first($name) ?? null);
1499
1500
        $message = null;
1501
1502
        $options = $this->attributes($options);
1503
1504
        if (is_array($errorMessage)) {
1505
            $message = "<ul id=\"{$name}-error\">";
1506
            foreach ($errorMessage as $error) {
1507
                $message .= ("<li {$options}>{$error}</li>");
1508
            }
1509
            $message .= '</ul>';
1510
        } else {
1511
            $message = ("<span id=\"{$name}-error\" {$options}>{$errorMessage}</span>");
1512
        }
1513
1514
        return $this->toHtmlString($message);
1515
    }
1516
1517
    /**
1518
     * Take Request in fill process
1519
     *
1520
     * @param  bool  $consider
1521
     */
1522
    public function considerRequest(bool $consider = true)
1523
    {
1524
        $this->considerRequest = $consider;
1525
    }
1526
1527
    /**
1528
     * Get the session store implementation.
1529
     *
1530
     * @return  Session  $session
1531
     */
1532
    public function getSessionStore(): Session
1533
    {
1534
        return $this->session;
1535
    }
1536
1537
    /**
1538
     * Set the session store implementation.
1539
     *
1540
     * @param  Session  $session
1541
     * @return $this
1542
     */
1543
    public function setSessionStore(Session $session): self
1544
    {
1545
        $this->session = $session;
1546
1547
        return $this;
1548
    }
1549
1550
    /**
1551
     * Dynamically handle calls to the class.
1552
     *
1553
     * @param  string  $method
1554
     * @param  array  $parameters
1555
     * @return mixed
1556
     *
1557
     * @throws BadMethodCallException
1558
     */
1559
    public function __call(string $method, array $parameters)
1560
    {
1561
        if (static::hasComponent($method)) {
1562
            return $this->componentCall($method, $parameters);
0 ignored issues
show
Bug introduced by
The method componentCall() does not exist on Hafijul233\Form\Builders\FormBuilder. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1562
            return $this->/** @scrutinizer ignore-call */ componentCall($method, $parameters);
Loading history...
1563
        }
1564
1565
        if (static::hasMacro($method)) {
1566
            return $this->macroCall($method, $parameters);
0 ignored issues
show
Bug introduced by
The method macroCall() does not exist on Hafijul233\Form\Builders\FormBuilder. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1566
            return $this->/** @scrutinizer ignore-call */ macroCall($method, $parameters);
Loading history...
1567
        }
1568
1569
        throw new BadMethodCallException("Method {$method} does not exist.");
1570
    }
1571
}
1572