Passed
Pull Request — master (#160)
by Wilmer
10:36
created

FieldAttributes::setInputAttributes()   A

Complexity

Conditions 6
Paths 16

Size

Total Lines 31
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 6

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 6
eloc 13
c 1
b 0
f 0
nc 16
nop 2
dl 0
loc 31
ccs 14
cts 14
cp 1
crap 6
rs 9.2222
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Form\Widget\Attribute;
6
7
use Yiisoft\Form\FormModelInterface;
8
use Yiisoft\Form\Helper\HtmlForm;
9
use Yiisoft\Form\Helper\HtmlFormErrors;
10
use Yiisoft\Html\Html;
11
use Yiisoft\Validator\Rule\HasLength;
12
use Yiisoft\Validator\Rule\MatchRegularExpression;
13
use Yiisoft\Validator\Rule\Number;
14
use Yiisoft\Validator\Rule\Required;
15
use Yiisoft\Validator\Rule\Url;
16
17
trait FieldAttributes
18
{
19
    private bool $ariaDescribedBy = false;
20
    private string $containerClass = '';
21
    /** @psalm-var array<string, string> */
22
    private array $containersClass = [];
23
    private string $errorClass = '';
24
    /** @psalm-var array<string, string> */
25
    private array $errorsClass = [];
26
    private string $errorMessage = '';
27
    private string $hintClass = '';
28
    /** @psalm-var array<string, string> */
29
    private array $hintsClass = [];
30
    private string $id = '';
31
    private string $inputClass = '';
32
    private array $inputsClass = [];
33
    private string $labelClass = '';
34
    /** @psalm-var array<string, string> */
35
    private array $labelsClass = [];
36
    private string $invalidClass = '';
37
    /** @psalm-var array<string, string> */
38
    private array $invalidsClass = [];
39
    private string $validClass = '';
40
    /** @psalm-var array<string, string> */
41
    private array $validsClass = [];
42
    private array $parts = [];
43
    private string $template = "{label}\n{input}\n{hint}\n{error}";
44
    /** @psalm-var array<string, string> */
45
    private array $templates = [];
46
    private string $type = '';
47
    private string $validationStateOn = 'input';
48
    private ?FormModelInterface $formModel = null;
49
50
    /**
51
     * Set container class each for field type.
52
     *
53
     * @param array $containerClass the container class to be used to layout the field.
54
     *
55
     * ```php
56
     * [Field::TYPE_TEXT => 'test-class-1', Field::TYPE_SUBMIT_BUTTON => 'test-class-2']
57
     *
58
     * @return static
59
     *
60
     * @psalm-param array<string, string> $containerClass
61
     */
62 3
    public function addContainerClass(array $containerClass): self
63
    {
64 3
        $new = clone $this;
65 3
        $new->containersClass = $containerClass;
66 3
        return $new;
67
    }
68
69
    /**
70
     * Set error class each for field type.
71
     *
72
     * @param array $errorClass the error class to be used to layout the field.
73
     *
74
     * ```php
75
     * [Field::TYPE_TEXT => 'test-class-1', Field::TYPE_SUBMIT_BUTTON => 'test-class-2']
76
     *
77
     * @return static
78
     *
79
     * @psalm-param array<string, string> $errorClass
80
     */
81 3
    public function addErrorClass(array $errorClass): self
82
    {
83 3
        $new = clone $this;
84 3
        $new->errorsClass = $errorClass;
85 3
        return $new;
86
    }
87
88
    /**
89
     * Set hint class each for field type.
90
     *
91
     * @param array $hintClass the hint class to be used to layout the field.
92
     *
93
     * ```php
94
     * [Field::TYPE_TEXT => 'test-class-1', Field::TYPE_SUBMIT_BUTTON => 'test-class-2']
95
     *
96
     * @return static
97
     *
98
     * @psalm-param array<string, string> $hintClass
99
     */
100 3
    public function addHintClass(array $hintClass): self
101
    {
102 3
        $new = clone $this;
103 3
        $new->hintsClass = $hintClass;
104 3
        return $new;
105
    }
106
107
    /**
108
     * Set input class each for field type.
109
     *
110
     * @param array $inputClass the input class to be used to layout the field.
111
     *
112
     * ```php
113
     * [Field::TYPE_TEXT => 'test-class-1', Field::TYPE_SUBMIT_BUTTON => 'test-class-2']
114
     *
115
     * @return static
116
     *
117
     * @psalm-param array<string, string> $inputClass
118
     */
119 3
    public function addInputClass(array $inputClass): self
120
    {
121 3
        $new = clone $this;
122 3
        $new->inputsClass = $inputClass;
123 3
        return $new;
124
    }
125
126
    /**
127
     * Set invalid class each for field type.
128
     *
129
     * @param array $invalidClass the input class to be used to layout the field.
130
     *
131
     * ```php
132
     * [Field::TYPE_TEXT => 'test-class-1', Field::TYPE_SUBMIT_BUTTON => 'test-class-2']
133
     *
134
     * @return static
135
     *
136
     * @psalm-param array<string, string> $invalidClass
137
     */
138 3
    public function addInvalidClass(array $invalidClass): self
139
    {
140 3
        $new = clone $this;
141 3
        $new->invalidsClass = $invalidClass;
142 3
        return $new;
143
    }
144
145
    /**
146
     * Set label class each for field type.
147
     *
148
     * @param array $labelClass the input class to be used to layout the field.
149
     *
150
     * ```php
151
     * [Field::TYPE_TEXT => 'test-class-1', Field::TYPE_SUBMIT_BUTTON => 'test-class-2']
152
     *
153
     * @return static
154
     *
155
     * @psalm-param array<string, string> $labelClass
156
     */
157 3
    public function addLabelClass(array $labelClass): self
158
    {
159 3
        $new = clone $this;
160 3
        $new->labelsClass = $labelClass;
161 3
        return $new;
162
    }
163
164
    /**
165
     * Set layout template for render a field with label, input, hint and error.
166
     *
167
     * @param array $template the template to be used to layout the field.
168
     *
169
     * ```php
170
     * [Field::TYPE_TEXT => '{input}', Field::TYPE_SUBMIT_BUTTON => '<div>{input}</div>']
171
     *
172
     * @return static
173
     *
174
     * @psalm-param array<string, string> $template
175
     */
176 1
    public function addTemplate(array $template): self
177
    {
178 1
        $new = clone $this;
179 1
        $new->templates = $template;
180 1
        return $new;
181
    }
182
183
    /**
184
     * Set invalid class each for field type.
185
     *
186
     * @param array $validsClass the input class to be used to layout the field.
187
     *
188
     * ```php
189
     * [Field::TYPE_TEXT => 'test-class-1', Field::TYPE_SUBMIT_BUTTON => 'test-class-2']
190
     *
191
     * @return static
192
     *
193
     * @psalm-param array<string, string> $validsClass
194
     */
195 3
    public function addValidClass(array $validsClass): self
196
    {
197 3
        $new = clone $this;
198 3
        $new->validsClass = $validsClass;
199 3
        return $new;
200
    }
201
202
    /**
203
     * Set aria-describedby attribute.
204
     *
205
     * @return static
206
     *
207
     * @link https://www.w3.org/TR/WCAG20-TECHS/ARIA1.html
208
     */
209 2
    public function ariaDescribedBy(): self
210
    {
211 2
        $new = clone $this;
212 2
        $new->ariaDescribedBy = true;
213 2
        return $new;
214
    }
215
216
    /**
217
     * Set container css class.
218
     *
219
     * @return static
220
     */
221 2
    public function containerClass(string $value): self
222
    {
223 2
        $new = clone $this;
224 2
        $new->containerClass = $value;
225 2
        return $new;
226
    }
227
228
    /**
229
     * Set error css class.
230
     *
231
     * @return static
232
     */
233 9
    public function errorClass(string $value): self
234
    {
235 9
        $new = clone $this;
236 9
        $new->errorClass = $value;
237 9
        return $new;
238
    }
239
240
    /**
241
     * Set hint css class.
242
     *
243
     * @return static
244
     */
245 9
    public function hintClass(string $value): self
246
    {
247 9
        $new = clone $this;
248 9
        $new->hintClass = $value;
249 9
        return $new;
250
    }
251
252
    /**
253
     * Set input css class.
254
     *
255
     * @return static
256
     */
257 2
    public function inputClass(string $value): self
258
    {
259 2
        $new = clone $this;
260 2
        $new->inputClass = $value;
261 2
        return $new;
262
    }
263
264
    /**
265
     * Set invalid css class.
266
     *
267
     * @return static
268
     */
269 9
    public function invalidClass(string $value): self
270
    {
271 9
        $new = clone $this;
272 9
        $new->invalidClass = $value;
273 9
        return $new;
274
    }
275
276
    /**
277
     * Set the label css class.
278
     *
279
     * @return static
280
     */
281 2
    public function labelClass(string $value): self
282
    {
283 2
        $new = clone $this;
284 2
        $new->labelClass = $value;
285 2
        return $new;
286
    }
287
288
    /**
289
     * Set layout template for render a field.
290
     *
291
     * @param string $value
292
     *
293
     * @return static
294
     */
295 2
    public function template(string $value): self
296
    {
297 2
        $new = clone $this;
298 2
        $new->template = $value;
299 2
        return $new;
300
    }
301
302
    /**
303
     * Set the value valid css class.
304
     *
305
     * @param string $value is the valid css class.
306
     *
307
     * @return static
308
     */
309 9
    public function validClass(string $value): self
310
    {
311 9
        $new = clone $this;
312 9
        $new->validClass = $value;
313 9
        return $new;
314
    }
315
316 1
    private function getSchemePattern(string $scheme): string
317
    {
318 1
        $result = '';
319 1
        for ($i = 0, $length = mb_strlen($scheme); $i < $length; $i++) {
320 1
            $result .= '[' . mb_strtolower($scheme[$i]) . mb_strtoupper($scheme[$i]) . ']';
321
        }
322 1
        return $result;
323
    }
324
325 189
    private function setInputAttributes(string $type, array $attributes): array
326
    {
327 189
        $formModel = $this->getFormModel();
328 189
        $placeHolder = '';
329
330
        // set aria-describedby attribute.
331 189
        if ($this->ariaDescribedBy === true) {
332 1
            $attributes['aria-describedby'] = $this->getId();
333
        }
334
335
        // set input class attribute.
336
        /** @var string */
337 189
        $inputClass = $this->inputsClass[$type] ?? $this->inputClass;
338
339 189
        if ($inputClass !== '') {
340 4
            Html::addCssClass($attributes, $inputClass);
341
        }
342
343
        // set placeholder attribute.
344 189
        if (!in_array($type, self::NO_PLACEHOLDER_TYPES, true)) {
345 155
            $placeHolder = $this->getFormModel()->getAttributePlaceholder($this->getAttribute());
346
        }
347
348 189
        if (!isset($attributes['placeholder']) && $placeHolder !== '') {
349 58
            $attributes['placeholder'] = $placeHolder;
350
        }
351
352
        // set input class valid and invalid.
353 189
        $attributes = $this->setValidAndInvalidClass($formModel, $type, $attributes);
354
355 189
        return $this->setValidatorAttributeHtml($formModel, $type, $attributes);
356
    }
357
358 189
    private function setValidAndInvalidClass(FormModelInterface $formModel, string $type, array $attributes): array
359
    {
360 189
        $attributeName = HtmlForm::getAttributeName($formModel, $this->getAttribute());
361
362 189
        $invalidClass = $this->invalidsClass[$type] ?? $this->invalidClass;
363 189
        $validClass = $this->validsClass[$type] ?? $this->validClass;
364
365 189
        if ($invalidClass !== '' && HtmlFormErrors::hasErrors($formModel, $attributeName)) {
366 11
            Html::addCssClass($attributes, $invalidClass);
367 189
        } elseif ($validClass !== '' && $formModel->isValidated()) {
368 11
            Html::addCssClass($attributes, $validClass);
369
        }
370
371 189
        return $attributes;
372
    }
373
374 189
    private function setValidatorAttributeHtml(FormModelInterface $formModel, string $type, array $attributes): array
375
    {
376
        /** @var array */
377 189
        $rules = $formModel->getRules()[$this->getAttribute()] ?? [];
378
379
        /** @var object $rule */
380 189
        foreach ($rules as $rule) {
381 24
            if ($rule instanceof Required) {
382 17
                $attributes['required'] = true;
383
            }
384
385 24
            if ($rule instanceof HasLength && in_array($type, self::HAS_LENGTH_TYPES, true)) {
386
                /** @var string */
387 14
                $attributes['maxlength'] = $rule->getOptions()['max'];
388
                /** @var string */
389 14
                $attributes['minlength'] = $rule->getOptions()['min'];
390
            }
391
392 24
            if ($rule instanceof MatchRegularExpression && in_array($type, self::MATCH_REGULAR_EXPRESSION_TYPES, true)) {
393
                /** @var string */
394 8
                $pattern = $rule->getOptions()['pattern'];
395 8
                $attributes['pattern'] = Html::normalizeRegexpPattern($pattern);
396
            }
397
398 24
            if ($rule instanceof Number && in_array($type, self::NUMBER_TYPES, true)) {
399
                /** @var string */
400 2
                $attributes['max'] = $rule->getOptions()['max'];
401
                /** @var string */
402 2
                $attributes['min'] = $rule->getOptions()['min'];
403
            }
404
405 24
            if ($rule instanceof Url && $type === self::TYPE_URL) {
406
                /** @var array<array-key, string> */
407 1
                $validSchemes = $rule->getOptions()['validSchemes'];
408
409 1
                $schemes = [];
410 1
                foreach ($validSchemes as $scheme) {
411 1
                    $schemes[] = $this->getSchemePattern($scheme);
412
                }
413
414
                /** @var array<array-key, float|int|string>|string */
415 1
                $pattern = $rule->getOptions()['pattern'];
416 1
                $normalizePattern = str_replace('{schemes}', '(' . implode('|', $schemes) . ')', $pattern);
417 1
                $attributes['pattern'] = Html::normalizeRegexpPattern($normalizePattern);
418
            }
419
        }
420
421 189
        return $attributes;
422
    }
423
}
424