Passed
Push — master ( 65be89...3604a1 )
by Alexander
02:15
created

Form::class()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 4
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 5
ccs 4
cts 4
cp 1
crap 1
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Form\Widget;
6
7
use InvalidArgumentException;
8
use Stringable;
9
use Yiisoft\Html\Html;
10
use Yiisoft\Http\Method;
11
use Yiisoft\Widget\Widget;
12
13
use function explode;
14
use function implode;
15
use function strpos;
16
use function substr;
17
use function urldecode;
18
19
/**
20
 *  Generates a form start tag.
21
 *
22
 *  @link https://www.w3.org/TR/html52/sec-forms.html
23
 */
24
final class Form extends Widget
25
{
26
    private string $action = '';
27
    private array $attributes = [];
28
    private string $csrfName = '';
29
    private string $csrfToken = '';
30
    private string $id = '';
0 ignored issues
show
introduced by
The private property $id is not used, and could be removed.
Loading history...
31
    private string $method = Method::POST;
32
33
    /**
34
     * @return string the generated form start tag.
35
     *
36
     * {@see end())}
37
     */
38 17
    public function begin(): string
39
    {
40 17
        parent::begin();
41
42 17
        $attributes = $this->attributes;
43 17
        $action = $this->action;
44 17
        $hiddenInputs = [];
45
46 17
        if ($this->csrfToken !== '' && $this->method === Method::POST) {
47 2
            $hiddenInputs[] = Html::hiddenInput($this->csrfName, $this->csrfToken);
48
        }
49
50 17
        if ($this->method === Method::GET && ($pos = strpos($action, '?')) !== false) {
51
            /**
52
             * Query parameters in the action are ignored for GET method we use hidden fields to add them back.
53
             */
54 1
            foreach (explode('&', substr($action, $pos + 1)) as $pair) {
55 1
                if (($pos1 = strpos($pair, '=')) !== false) {
56 1
                    $hiddenInputs[] = Html::hiddenInput(
57 1
                        urldecode(substr($pair, 0, $pos1)),
58 1
                        urldecode(substr($pair, $pos1 + 1))
59
                    );
60
                } else {
61 1
                    $hiddenInputs[] = Html::hiddenInput(urldecode($pair), '');
62
                }
63
            }
64
65 1
            $action = substr($action, 0, $pos);
66
        }
67
68 17
        if ($action !== '') {
69 8
            $attributes['action'] = $action;
70
        }
71
72 17
        $attributes['method'] = $this->method;
73
74 17
        if ($this->csrfToken !== '') {
75
            /** @var string */
76 3
            $attributes[$this->csrfName] = $this->csrfToken;
77
        }
78
79 17
        $form = Html::openTag('form', $attributes);
80
81 17
        if (!empty($hiddenInputs)) {
82 3
            $form .= PHP_EOL . implode(PHP_EOL, $hiddenInputs);
83
        }
84
85 17
        return $form;
86
    }
87
88
    /**
89
     * The accept-charset content attribute gives the character encodings that are to be used for the submission.
90
     * If specified, the value must be an ordered set of unique space-separated tokens that are ASCII case-insensitive,
91
     * and each token must be an ASCII case-insensitive match for one of the labels of an ASCII-compatible encoding.
92
     *
93
     * @param string $value the accept-charset attribute value.
94
     *
95
     * @return static
96
     *
97
     * @link https://www.w3.org/TR/html52/sec-forms.html#element-attrdef-form-accept-charset
98
     */
99 2
    public function acceptCharset(string $value): self
100
    {
101 2
        $new = clone $this;
102 2
        $new->attributes['accept-charset'] = $value;
103 2
        return $new;
104
    }
105
106
    /**
107
     * The action and formaction content attributes, if specified, must have a value that is a valid non-empty URL
108
     * potentially surrounded by spaces.
109
     *
110
     * @param string $value the action attribute value.
111
     *
112
     * @return static
113
     *
114
     * @link https://www.w3.org/TR/html52/sec-forms.html#element-attrdef-form-action
115
     */
116 11
    public function action(string $value): self
117
    {
118 11
        $new = clone $this;
119 11
        $new->action = $value;
120 11
        return $new;
121
    }
122
123
    /**
124
     * The HTML attributes. The following special options are recognized.
125
     *
126
     * @param array $value
127
     *
128
     * @return static
129
     *
130
     * See {@see \Yiisoft\Html\Html::renderTagAttributes()} for details on how attributes are being rendered.
131
     */
132 3
    public function attributes(array $value): self
133
    {
134 3
        $new = clone $this;
135 3
        $new->attributes = $value;
136 3
        return $new;
137
    }
138
139
    /**
140
     * Specifies whether the element represents an input control for which a UA is meant to store the value entered by
141
     * the user (so that the UA can prefill the form later).
142
     *
143
     * @param bool $value
144
     *
145
     * @return static
146
     *
147
     * @link https://www.w3.org/TR/html52/sec-forms.html#element-attrdef-autocompleteelements-autocomplete
148
     */
149 2
    public function autocomplete(bool $value = true): self
150
    {
151 2
        $new = clone $this;
152 2
        $new->attributes['autocomplete'] = $value ? 'on' : 'off';
153 2
        return $new;
154
    }
155
156
    /**
157
     * The CSRF-token content attribute token that are known to be safe to use for.
158
     *
159
     * @param mixed|string|Stringable $csrfToken the CSRF-token attribute value.
160
     * @param string $csrfName the CSRF-token attribute name.
161
     *
162
     * @return static
163
     */
164 8
    public function csrf($csrfToken, string $csrfName = '_csrf'): self
165
    {
166 8
        $new = clone $this;
167
168 8
        if (is_string($csrfToken) || (is_object($csrfToken) && method_exists($csrfToken, '__toString'))) {
169 6
            $new->csrfToken = (string) $csrfToken;
170
        } else {
171 2
            throw new InvalidArgumentException('$csrfToken must be a string or \Stringable object.');
172
        }
173
174 6
        $new->csrfName = $csrfName;
175 6
        return $new;
176
    }
177
178
    /**
179
     * Set CSS class of the field widget.
180
     *
181
     * @param string $class
182
     *
183
     * @return static
184
     */
185 2
    public function class(string $class): self
186
    {
187 2
        $new = clone $this;
188 2
        Html::addCssClass($new->attributes, $class);
189 2
        return $new;
190
    }
191
192
    /**
193
     * The formenctype content attribute specifies the content type of the form submission.
194
     *
195
     * @param string $value the formenctype attribute value.
196
     *
197
     * @return static
198
     *
199
     * @link https://www.w3.org/TR/html52/sec-forms.html#element-attrdef-form-enctype
200
     */
201 2
    public function enctype(string $value): self
202
    {
203 2
        $new = clone $this;
204 2
        $new->attributes['id'] = $value;
205 2
        return $new;
206
    }
207
208
    /**
209
     * Set the ID of the widget.
210
     *
211
     * @param string|null $id
212
     *
213
     * @return static
214
     *
215
     * @link https://html.spec.whatwg.org/multipage/dom.html#the-id-attribute
216
     */
217 2
    public function id(?string $id): self
218
    {
219 2
        $new = clone $this;
220 2
        $new->attributes['id'] = $id;
221 2
        return $new;
222
    }
223
224
    /**
225
     * The method content attribute specifies how the form-data should be submitted.
226
     *
227
     * @param string $value the method attribute value.
228
     *
229
     * @return static
230
     *
231
     * @link https://www.w3.org/TR/html52/sec-forms.html#element-attrdef-form-method
232
     */
233 9
    public function method(string $value): self
234
    {
235 9
        $new = clone $this;
236 9
        $new->method = strtoupper($value);
237 9
        return $new;
238
    }
239
240
    /**
241
     * The novalidate and formnovalidate content attributes are boolean attributes. If present, they indicate that the
242
     * form is not to be validated during submission.
243
     *
244
     * @return static
245
     *
246
     * @link https://www.w3.org/TR/html52/sec-forms.html#element-attrdef-form-novalidate
247
     */
248 2
    public function noHtmlValidation(): self
249
    {
250 2
        $new = clone $this;
251 2
        $new->attributes['novalidate'] = true;
252 2
        return $new;
253
    }
254
255
    /**
256
     * The target and formtarget content attributes, if specified, must have values that are valid browsing context
257
     * names or keywords.
258
     *
259
     * @param string $value the target attribute value, for default its `_blank`.
260
     *
261
     * @return static
262
     *
263
     * @link https://www.w3.org/TR/html52/sec-forms.html#element-attrdef-form-target
264
     */
265 2
    public function target(string $value): self
266
    {
267 2
        $new = clone $this;
268 2
        $new->attributes['target'] = $value;
269 2
        return $new;
270
    }
271
272
    /**
273
     * Generates a form end tag.
274
     *
275
     * @return string the generated tag.
276
     *
277
     * {@see beginForm()}
278
     */
279 2
    protected function run(): string
280
    {
281 2
        return Html::closeTag('form');
282
    }
283
}
284