Form::setLayout()   A
last analyzed

Complexity

Conditions 4
Paths 3

Size

Total Lines 21
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 4

Importance

Changes 4
Bugs 0 Features 0
Metric Value
cc 4
eloc 17
nc 3
nop 1
dl 0
loc 21
ccs 18
cts 18
cp 1
crap 4
rs 9.7
c 4
b 0
f 0
1
<?php
2
3
namespace kalanis\kw_forms;
4
5
6
use kalanis\kw_forms\Adapters\AAdapter;
7
use kalanis\kw_forms\Adapters\FilesAdapter;
8
use kalanis\kw_input\Interfaces\IEntry;
9
use kalanis\kw_rules\Validate;
10
use kalanis\kw_storage\StorageException;
11
use kalanis\kw_templates\Interfaces\IHtmlElement;
12
use kalanis\kw_templates\HtmlElement\THtmlElement;
13
14
15
/**
16
 * Class Form
17
 * @package kalanis\kw_forms
18
 * Basic class for work with forms
19
 */
20
class Form implements IHtmlElement
21
{
22
    use Cache\TStorage;
23
    use Controls\TSubControls;
24
    use Controls\TSubErrors;
25
    use Form\TControl;
26
    use Form\TMethod;
27
    use THtmlElement;
28
    use Controls\TWrappers;
29
30
    protected Controls\Factory $controlFactory;
31
    protected Validate $validate;
32
    protected ?AAdapter $entries = null;
33
    protected ?FilesAdapter $files = null;
34
    /** @var string Form label */
35
    protected string $label = '';
36
37
    /**
38
     * Main Form template
39
     * @-var string
40
     * params: %1 attributes, %2 errors, %3 controls
41
     */
42
43
    /** @var string Template for error output */
44
    protected string $templateErrors = '<div class="errors">%s</div>';
45
    /**
46
     * Start tag template - for rendering just inside something
47
     * @var string
48
     * params: %1 attributes
49
     */
50
    protected string $templateStart = '<form %1$s>';
51
    /**
52
     * End tag template
53
     * @var string
54
     */
55
    protected string $templateEnd = '</form>';
56
57
    /**
58
     * @var string
59
     * params: %1 labelText, %2 content
60
     */
61
    protected string $templateLabel = '<fieldset><legend>%1$s</legend>%2$s</fieldset>';
62
63 8
    public function __construct(string $alias = '', ?IHtmlElement $parent = null)
64
    {
65 8
        $this->alias = $alias;
66 8
        $this->setAttribute('name', $this->alias);
67 8
        $this->setMethod(IEntry::SOURCE_POST);
68 8
        $this->setTemplate('%2$s<form %1$s>%3$s</form>');
69
70 8
        $this->templateError = '';
71 8
        $this->controlFactory = new Controls\Factory();
72 8
        $this->validate = new Validate();
73 8
        $this->setParent($parent);
74 8
    }
75
76
    /**
77
     * @param AAdapter $entries
78
     * @param FilesAdapter|null $files
79
     * @throws Exceptions\FormsException
80
     * @return $this
81
     */
82 8
    public function setInputs(AAdapter $entries, ?FilesAdapter $files = null): self
83
    {
84 8
        $entries->loadEntries($this->getMethod());
85 8
        if ($files) {
86 2
            $files->loadEntries($this->getMethod());
87
        }
88 8
        $this->entries = $entries;
89 8
        $this->files = $files;
90 8
        return $this;
91
    }
92
93 1
    public function getControlFactory(): Controls\Factory
94
    {
95 1
        return $this->controlFactory;
96
    }
97
98 8
    public function addControlDefaultKey(Controls\AControl $control): void
99
    {
100 8
        $this->addControl($control->getKey(), $control);
101 8
    }
102
103
    /**
104
     * Merge children, attr etc.
105
     * @param IHtmlElement $child
106
     * @codeCoverageIgnore merge with what?
107
     */
108
    public function merge(IHtmlElement $child): void
109
    {
110
        if (method_exists($child, 'getLabel')) {
111
            $this->setLabel($child->getLabel());
112
        }
113
        $this->setChildren($child->getChildren());
114
        $this->setAttributes($child->getAttributes());
115
    }
116
117
    /**
118
     * Set value of object or child
119
     * @param string $key
120
     * @param string|int|float|bool|null $value
121
     */
122 1
    public function setValue(string $key, $value = null): void
123
    {
124 1
        $control = $this->getControl($key);
125 1
        if ($control) {
126 1
            $control->setValue($value);
127
        }
128 1
    }
129
130
    /**
131
     * Get value of object or child
132
     * @param string $key
133
     * @return string|int|float|bool|null
134
     */
135 1
    public function getValue(string $key)
136
    {
137 1
        $control = $this->getControl($key);
138 1
        return $control ? $control->getValue() : null ;
139
    }
140
141
    /**
142
     * Get object or child label
143
     * @param string|null $key
144
     * @return string|null
145
     */
146 3
    public function getLabel(?string $key = null): ?string
147
    {
148 3
        if (is_null($key)) {
149 3
            return $this->label;
150
        } else {
151 1
            $control = $this->getControl($key);
152 1
            if ($control) {
153 1
                return $control->getLabel();
154
            }
155
        }
156 1
        return null;
157
    }
158
159
    /**
160
     * Set object or child label
161
     * @param string|null $value
162
     * @param string|null $key
163
     * @return $this
164
     */
165 1
    public function setLabel(?string $value = null, ?string $key = null): self
166
    {
167 1
        if (is_null($key)) {
168 1
            $this->label = strval($value);
169
        } else {
170 1
            $control = $this->getControl($key);
171 1
            if ($control) {
172 1
                $control->setLabel($value);
173
            }
174
        }
175 1
        return $this;
176
    }
177
178
    /**
179
     * Set sent values and process checks on form
180
     * @param string|null $checkKey
181
     * @return boolean
182
     */
183 1
    public function process(?string $checkKey = null): bool
184
    {
185 1
        $this->setSentValues();
186 1
        if ($this->isSubmitted($checkKey)) {
187 1
            return $this->isValid();
188
        } else {
189 1
            return false;
190
        }
191
    }
192
193 1
    public function isSubmitted(?string $checkKey = null): bool
194
    {
195 1
        if (empty($this->entries)) {
196 1
            return false;
197
        }
198
199
        // any predefined submitted key
200 1
        if ($checkKey && $this->entries->offsetExists($checkKey)) {
201 1
            return true;
202
        }
203
204
        // lookup for submit button
205 1
        foreach ($this->controls as &$control) {
206 1
            if ($control instanceof Controls\Submit && !empty($control->getValue())) {
207 1
                return true;
208
            }
209
        }
210 1
        return false;
211
    }
212
213
    /**
214
     * Set files first, then entries
215
     * It's necessary due setting checkboxes - files removes that setting, then normal entries set it back
216
     */
217 4
    public function setSentValues(): void
218
    {
219 4
        if ($this->files) $this->setValues($this->setValuesToFill($this->files, true));
220 4
        if ($this->entries) $this->setValues($this->setValuesToFill($this->entries));
221 4
    }
222
223
    /**
224
     * @param AAdapter $adapter
225
     * @param bool $raw
226
     * @return array<string, string|int|float|null>
227
     */
228 4
    protected function setValuesToFill(AAdapter $adapter, bool $raw = false): array
229
    {
230 4
        $result = [];
231 4
        foreach ($adapter as $key => $entry) {
232 4
            $result[strval($key)] = is_object($entry) && !$raw
233 4
                ? ( method_exists($entry, 'getValue')
234 4
                    ? $entry->getValue()
235 4
                    : strval($entry)
236
                )
237 4
                : $entry ;
238
        }
239 4
        return $result;
240
    }
241
242
    /**
243
     * Form validation
244
     * Check each control if is valid
245
     * @return boolean
246
     */
247 4
    public function isValid(): bool
248
    {
249 4
        $this->errors = [];
250 4
        $validation = true;
251 4
        foreach ($this->controls as &$child) {
252 4
            if ($child instanceof Interfaces\IContainsControls) {
253 3
                $validation &= $child->/** @scrutinizer ignore-call */validateControls($this->validate);
254 3
                $this->errors += $child->/** @scrutinizer ignore-call */getValidatedErrors();
255 1
            } elseif ($child instanceof Controls\AControl) {
256 1
                $validation &= $this->validate->validate($child);
257 1
                $this->errors += $this->validate->getErrors();
258
            }
259
        }
260
261 4
        return boolval($validation);
262
    }
263
264 8
    public function setTemplate(string $string): void
265
    {
266 8
        $this->template = $string;
267 8
    }
268
269
    /**
270
     * Save current form data in storage
271
     * @throws StorageException
272
     */
273 1
    public function store(): void
274
    {
275 1
        if ($this->storage) $this->storage->store($this->getValues(), 86400); # day
276 1
    }
277
278
    /**
279
     * Load data from storage into form
280
     * @throws StorageException
281
     */
282 1
    public function loadStored(): void
283
    {
284 1
        if ($this->storage) $this->setValues($this->storage->load());
285 1
    }
286
287
    /**
288
     * Render whole form
289
     * @param string|string[] $attributes
290
     * @throws Exceptions\RenderException
291
     * @return string
292
     */
293 2
    public function render($attributes = []): string
294
    {
295 2
        $this->addAttributes($attributes);
296 2
        $label = $this->getLabel();
297 2
        $content = empty($label) ? $this->renderChildren() : sprintf($this->templateLabel, $label, $this->renderChildren());
298 2
        return sprintf($this->template, $this->renderAttributes(), $this->renderErrors(), $content);
299
    }
300
301
    /**
302
     * Render all errors from controls
303
     * @throws Exceptions\RenderException
304
     * @return string
305
     */
306 2
    public function renderErrors(): string
307
    {
308 2
        $errors = $this->renderErrorsArray();
309 2
        if (!empty ($errors)) {
310 1
            $return = $this->wrapIt(implode('', array_keys($errors)), $this->wrappersErrors);
311
312 1
            return sprintf($this->templateErrors, $return);
313
        } else {
314 2
            return '';
315
        }
316
    }
317
318
    /**
319
     * Get all errors from controls and return them as indexed array
320
     * @throws Exceptions\RenderException
321
     * @return array<string, string>
322
     */
323 3
    public function renderErrorsArray(): array
324
    {
325 3
        return $this->getErrors($this->errors, $this->wrappersError);
326
    }
327
328
    /**
329
     * @param string $key
330
     * @throws Exceptions\RenderException
331
     * @return string
332
     */
333 1
    public function renderControlErrors(string $key): string
334
    {
335 1
        $control = $this->getControl($key);
336 1
        if ($control && isset($this->errors[$control->getKey()])) {
337 1
            return $control->renderErrors($this->errors[$control->getKey()]);
338
        }
339 1
        return '';
340
    }
341
342
    /**
343
     * Render all form controls, add missing wrappers
344
     * @throws Exceptions\RenderException
345
     * @return string
346
     */
347 2
    public function renderChildren(): string
348
    {
349 2
        $return = '';
350 2
        $hidden = '';
351 2
        foreach ($this->controls as &$child) {
352
353 2
            if ($child instanceof IHtmlElement) {
354 2
                if ($child instanceof Controls\AControl) {
355 2
                    if (!$child->wrappersLabel()) {
356 2
                        $child->addWrapperLabel($this->wrappersLabel);
357
                    }
358 2
                    if (!$child->wrappersInput()) {
359 2
                        $child->addWrapperInput($this->wrappersInput);
360
                    }
361 2
                    if (!$child->wrappers()) {
362 2
                        $child->addWrapper($this->wrappersChild);
363
                    }
364
                }
365 2
                if ($child instanceof Controls\Hidden) {
366 1
                    $hidden .= $child->render() . PHP_EOL;
367
                } else {
368 2
                    $return .= $child->render() . PHP_EOL;
369
                }
370
                // @phpstan-ignore-next-line
371
            } else {
372
                // @codeCoverageIgnoreStart
373
                // How to make this one? Only by extending. Then all the security fly outside the window
374
                $return .= strval($child);
375
                // @codeCoverageIgnoreEnd
376
            }
377
        }
378
379 2
        return $hidden . $this->wrapIt($return, $this->wrappersChildren);
380
    }
381
382
    /**
383
     * Set form layout
384
     * @param string $layoutName
385
     * @return $this
386
     */
387 1
    public function setLayout(string $layoutName = ''): self
388
    {
389 1
        if (('inlineTable' == $layoutName) || ('tableInline' == $layoutName)) {
390 1
            $this->resetWrappers();
391 1
            $this->addWrapperChildren('tr');
392 1
            $this->addWrapperChildren('table', ['class' => 'form']);
393 1
            $this->addWrapperLabel('td');
394 1
            $this->addWrapperInput('td');
395 1
            $this->addWrapperErrors('div', ['class' => 'errors']);
396 1
            $this->addWrapperError('div');
397 1
        } elseif ('table' == $layoutName) {
398 1
            $this->resetWrappers();
399 1
            $this->addWrapperChildren('table', ['class' => 'form']);
400 1
            $this->addWrapperChild('tr');
401 1
            $this->addWrapperLabel('td');
402 1
            $this->addWrapperInput('td');
403 1
            $this->addWrapperErrors('div', ['class' => 'errors']);
404 1
            $this->addWrapperError('div');
405
        }
406
407 1
        return $this;
408
    }
409
410
    /**
411
     * Render Start tag and hidden attributes
412
     * @param string|string[] $attributes
413
     * @param bool $noChildren
414
     * @throws Exceptions\RenderException
415
     * @return string
416
     */
417 1
    public function renderStart($attributes = [], bool $noChildren = false): string
418
    {
419 1
        $this->addAttributes($attributes);
420 1
        $return = sprintf($this->templateStart, $this->renderAttributes());
421 1
        if (!$noChildren) {
422 1
            foreach ($this->controls as &$control) {
423 1
                if ($control instanceof Controls\Hidden) {
424 1
                    $return .= $control->renderInput() . PHP_EOL;
425
                }
426
            }
427
        }
428
429 1
        return $return;
430
    }
431
432
    /**
433
     * Render End tag
434
     * @return string
435
     */
436 1
    public function renderEnd(): string
437
    {
438 1
        return $this->templateEnd;
439
    }
440
}
441