Passed
Pull Request — master (#160)
by Wilmer
04:38 queued 02:07
created

FieldAttributes::withErrorClass()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 1
dl 0
loc 5
ccs 4
cts 4
cp 1
crap 1
rs 10
c 0
b 0
f 0
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 invalid class each for field type.
52
     *
53
     * @param array $invalidClass the input 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> $invalidClass
61
     */
62 3
    public function addInvalidClass(array $invalidClass): self
63
    {
64 3
        $new = clone $this;
65 3
        $new->invalidsClass = $invalidClass;
66 3
        return $new;
67
    }
68
69
    /**
70
     * Set label class each for field type.
71
     *
72
     * @param array $labelClass the input 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> $labelClass
80
     */
81 3
    public function addLabelClass(array $labelClass): self
82
    {
83 3
        $new = clone $this;
84 3
        $new->labelsClass = $labelClass;
85 3
        return $new;
86
    }
87
88
    /**
89
     * Set layout template for render a field with label, input, hint and error.
90
     *
91
     * @param array $template the template to be used to layout the field.
92
     *
93
     * ```php
94
     * [Field::TYPE_TEXT => '{input}', Field::TYPE_SUBMIT_BUTTON => '<div>{input}</div>']
95
     *
96
     * @return static
97
     *
98
     * @psalm-param array<string, string> $template
99
     */
100 1
    public function addTemplate(array $template): self
101
    {
102 1
        $new = clone $this;
103 1
        $new->templates = $template;
104 1
        return $new;
105
    }
106
107
    /**
108
     * Set invalid class each for field type.
109
     *
110
     * @param array $validsClass 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> $validsClass
118
     */
119 3
    public function addValidClass(array $validsClass): self
120
    {
121 3
        $new = clone $this;
122 3
        $new->validsClass = $validsClass;
123 3
        return $new;
124
    }
125
126
    /**
127
     * Set aria-describedby attribute.
128
     *
129
     * @return static
130
     *
131
     * @link https://www.w3.org/TR/WCAG20-TECHS/ARIA1.html
132
     */
133 2
    public function ariaDescribedBy(): self
134
    {
135 2
        $new = clone $this;
136 2
        $new->ariaDescribedBy = true;
137 2
        return $new;
138
    }
139
140
    /**
141
     * Set container css class.
142
     *
143
     * @return static
144
     */
145 2
    public function containerClass(string $value): self
146
    {
147 2
        $new = clone $this;
148 2
        $new->containerClass = $value;
149 2
        return $new;
150
    }
151
152
    /**
153
     * Set error css class.
154
     *
155
     * @return static
156
     */
157 9
    public function errorClass(string $value): self
158
    {
159 9
        $new = clone $this;
160 9
        $new->errorClass = $value;
161 9
        return $new;
162
    }
163
164
    /**
165
     * Set hint css class.
166
     *
167
     * @return static
168
     */
169 9
    public function hintClass(string $value): self
170
    {
171 9
        $new = clone $this;
172 9
        $new->hintClass = $value;
173 9
        return $new;
174
    }
175
176
    /**
177
     * Set input css class.
178
     *
179
     * @return static
180
     */
181 2
    public function inputClass(string $value): self
182
    {
183 2
        $new = clone $this;
184 2
        $new->inputClass = $value;
185 2
        return $new;
186
    }
187
188
    /**
189
     * Set invalid css class.
190
     *
191
     * @return static
192
     */
193 9
    public function invalidClass(string $value): self
194
    {
195 9
        $new = clone $this;
196 9
        $new->invalidClass = $value;
197 9
        return $new;
198
    }
199
200
    /**
201
     * Set the label css class.
202
     *
203
     * @return static
204
     */
205 2
    public function labelClass(string $value): self
206
    {
207 2
        $new = clone $this;
208 2
        $new->labelClass = $value;
209 2
        return $new;
210
    }
211
212
    /**
213
     * Set layout template for render a field.
214
     *
215
     * @param string $value
216
     *
217
     * @return static
218
     */
219 2
    public function template(string $value): self
220
    {
221 2
        $new = clone $this;
222 2
        $new->template = $value;
223 2
        return $new;
224
    }
225
226
    /**
227
     * Set the value valid css class.
228
     *
229
     * @param string $value is the valid css class.
230
     *
231
     * @return static
232
     */
233 9
    public function validClass(string $value): self
234
    {
235 9
        $new = clone $this;
236 9
        $new->validClass = $value;
237 9
        return $new;
238
    }
239
240
    /**
241
     * Set container class each for field type.
242
     *
243
     * @param array $containerClass The container class to be applied to fields's container tag.
244
     *
245
     * ```php
246
     * [Field::TYPE_TEXT => 'test-class-1', Field::TYPE_SUBMIT_BUTTON => 'test-class-2']
247
     *
248
     * @return static
249
     *
250
     * @psalm-param array<string, string> $containerClass
251
     */
252 3
    public function withContainerClass(array $containerClass): self
253
    {
254 3
        $new = clone $this;
255 3
        $new->containersClass = $containerClass;
256 3
        return $new;
257
    }
258
259
    /**
260
     * Set error class used for an invalid field.
261
     *
262
     * @param array $errorClass The error class to apply to an invalid field.
263
     *
264
     * ```php
265
     * [Field::TYPE_TEXT => 'test-class-1', Field::TYPE_SUBMIT_BUTTON => 'test-class-2']
266
     *
267
     * @return static
268
     *
269
     * @psalm-param array<string, string> $errorClass
270
     */
271 3
    public function withErrorClass(array $errorClass): self
272
    {
273 3
        $new = clone $this;
274 3
        $new->errorsClass = $errorClass;
275 3
        return $new;
276
    }
277
278
    /**
279
     * Set hint class for a field.
280
     *
281
     * @param array $hintClass The hint class to be applied to a field.
282
     *
283
     * ```php
284
     * [Field::TYPE_TEXT => 'test-class-1', Field::TYPE_SUBMIT_BUTTON => 'test-class-2']
285
     *
286
     * @return static
287
     *
288
     * @psalm-param array<string, string> $hintClass
289
     */
290 3
    public function withHintClass(array $hintClass): self
291
    {
292 3
        $new = clone $this;
293 3
        $new->hintsClass = $hintClass;
294 3
        return $new;
295
    }
296
297
    /**
298
     * Set input class for a field.
299
     *
300
     * @param array $inputClass The input class to be applied field container.
301
     *
302
     * ```php
303
     * [Field::TYPE_TEXT => 'test-class-1', Field::TYPE_SUBMIT_BUTTON => 'test-class-2']
304
     *
305
     * @return static
306
     *
307
     * @psalm-param array<string, string> $inputClass
308
     */
309 3
    public function withInputClass(array $inputClass): self
310
    {
311 3
        $new = clone $this;
312 3
        $new->inputsClass = $inputClass;
313 3
        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 181
    private function setInputAttributes(string $type, array $attributes): array
326
    {
327 181
        $formModel = $this->getFormModel();
328 181
        $placeHolder = '';
329
330
        // set aria-describedby attribute.
331 181
        if ($this->ariaDescribedBy === true) {
332 1
            $attributes['aria-describedby'] = $this->getId();
333
        }
334
335
        // set input class attribute.
336
        /** @var string */
337 181
        $inputClass = $this->inputsClass[$type] ?? $this->inputClass;
338
339 181
        if ($inputClass !== '') {
340 4
            Html::addCssClass($attributes, $inputClass);
341
        }
342
343
        // set placeholder attribute.
344 181
        if (!in_array($type, self::NO_PLACEHOLDER_TYPES, true)) {
345 151
            $placeHolder = $this->getFormModel()->getAttributePlaceholder($this->getAttribute());
346
        }
347
348 181
        if (!isset($attributes['placeholder']) && $placeHolder !== '') {
349 57
            $attributes['placeholder'] = $placeHolder;
350
        }
351
352
        // set input class valid and invalid.
353 181
        $attributes = $this->setValidAndInvalidClass($formModel, $type, $attributes);
354
355 181
        return $this->setValidatorAttributeHtml($formModel, $type, $attributes);
356
    }
357
358 181
    private function setValidAndInvalidClass(FormModelInterface $formModel, string $type, array $attributes): array
359
    {
360 181
        $attributeName = HtmlForm::getAttributeName($formModel, $this->getAttribute());
361
362 181
        $invalidClass = $this->invalidsClass[$type] ?? $this->invalidClass;
363 181
        $validClass = $this->validsClass[$type] ?? $this->validClass;
364
365 181
        if ($invalidClass !== '' && HtmlFormErrors::hasErrors($formModel, $attributeName)) {
366 11
            Html::addCssClass($attributes, $invalidClass);
367 181
        } elseif ($validClass !== '' && $formModel->isValidated()) {
368 11
            Html::addCssClass($attributes, $validClass);
369
        }
370
371 181
        return $attributes;
372
    }
373
374 181
    private function setValidatorAttributeHtml(FormModelInterface $formModel, string $type, array $attributes): array
375
    {
376
        /** @var array */
377 181
        $rules = $formModel->getRules()[$this->getAttribute()] ?? [];
378
379
        /** @var object $rule */
380 181
        foreach ($rules as $rule) {
381 22
            if ($rule instanceof Required) {
382 16
                $attributes['required'] = true;
383
            }
384
385 22
            if ($rule instanceof HasLength && in_array($type, self::HAS_LENGTH_TYPES, true)) {
386
                /** @var string */
387 13
                $attributes['maxlength'] = $rule->getOptions()['max'];
388
                /** @var string */
389 13
                $attributes['minlength'] = $rule->getOptions()['min'];
390
            }
391
392 22
            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 22
            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 22
            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 181
        return $attributes;
422
    }
423
}
424