Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
79.39% covered (success)
79.39%
208 / 262
89.19% covered (success)
89.19%
33 / 37
CRAP
0.00% covered (danger)
0.00%
0 / 1
Fieldset
79.39% covered (success)
79.39%
208 / 262
89.19% covered (success)
89.19%
33 / 37
284.55
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 createFromConfig
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 setContainer
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setCurrent
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 createGroup
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 addField
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 addFields
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 insertFieldBefore
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
4
 insertFieldAfter
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
4
 count
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 toArray
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 getLegend
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getContainer
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getCurrent
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasField
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 getField
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 getFields
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getFieldGroups
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getAllFields
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 getFieldValue
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 setLegend
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setFieldValue
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 setFieldValues
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 getIterator
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 prepare
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
5
 prepareForView
73.91% covered (success)
73.91%
17 / 23
0.00% covered (danger)
0.00%
0 / 1
13.15
 prepareTable
65.96% covered (warning)
65.96%
31 / 47
0.00% covered (danger)
0.00%
0 / 1
33.24
 prepareElement
58.97% covered (warning)
58.97%
23 / 39
0.00% covered (danger)
0.00%
0 / 1
40.37
 prepareDl
62.79% covered (warning)
62.79%
27 / 43
0.00% covered (danger)
0.00%
0 / 1
34.69
 __set
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 __get
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 __isset
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 __unset
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
4
 offsetExists
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 offsetGet
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 offsetSet
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 offsetUnset
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2/**
3 * Pop PHP Framework (http://www.popphp.org/)
4 *
5 * @link       https://github.com/popphp/popphp-framework
6 * @author     Nick Sagona, III <dev@nolainteractive.com>
7 * @copyright  Copyright (c) 2009-2024 NOLA Interactive, LLC. (http://www.nolainteractive.com)
8 * @license    http://www.popphp.org/license     New BSD License
9 */
10
11/**
12 * @namespace
13 */
14namespace Pop\Form;
15
16use Pop\Dom\Child;
17use Pop\Form\Element\AbstractElement;
18use ArrayIterator;
19
20/**
21 * Form fieldset class
22 *
23 * @category   Pop
24 * @package    Pop\Form
25 * @author     Nick Sagona, III <dev@nolainteractive.com>
26 * @copyright  Copyright (c) 2009-2024 NOLA Interactive, LLC. (http://www.nolainteractive.com)
27 * @license    http://www.popphp.org/license     New BSD License
28 * @version    4.0.0
29 */
30
31class Fieldset extends Child implements \ArrayAccess, \Countable, \IteratorAggregate
32{
33
34    /**
35     * Form field elements
36     * @var array
37     */
38    protected array $fields = [];
39
40    /**
41     * Current field group
42     * @var int
43     */
44    protected int $current = 0;
45
46    /**
47     * Fieldset legend
48     * @var ?string
49     */
50    protected ?string $legend = null;
51
52    /**
53     * Fieldset container (dl, table, div or p)
54     * @var string
55     */
56    protected string $container = 'dl';
57
58    /**
59     * Constructor
60     *
61     * Instantiate the form fieldset object
62     *
63     * @param  ?array $fields
64     * @param  ?string $container
65     */
66    public function __construct(?array $fields = null, ?string $container = 'dl')
67    {
68        parent::__construct('fieldset');
69        if ($container !== null) {
70            $this->setContainer($container);
71        }
72        if ($fields !== null) {
73            $this->addFields($fields);
74        }
75    }
76
77    /**
78     * Method to create form fieldset object and fields from config
79     *
80     * @param  array  $config
81     * @return Fieldset
82     */
83    public static function createFromConfig(array $config): Fieldset
84    {
85        $fields = [];
86
87        foreach ($config as $name => $field) {
88            $fields[$name] = Fields::create($name, $field);
89        }
90
91        return new static($fields);
92    }
93
94    /**
95     * Method to set container
96     *
97     * @param  string $container
98     * @return Fieldset
99     */
100    public function setContainer(string $container): Fieldset
101    {
102        $this->container = strtolower($container);
103        return $this;
104    }
105
106    /**
107     * Method to get current group index
108     *
109     * @param  int $i
110     * @return Fieldset
111     */
112    public function setCurrent(int $i): Fieldset
113    {
114        $this->current = (int)$i;
115        if (!isset($this->fields[$this->current])) {
116            $this->fields[$this->current] = [];
117        }
118        return $this;
119    }
120
121    /**
122     * Method to create new group
123     *
124     * @return Fieldset
125     */
126    public function createGroup(): Fieldset
127    {
128        $this->current++;
129        $this->fields[$this->current] = [];
130        return $this;
131    }
132
133    /**
134     * Method to add a form field
135     *
136     * @param  AbstractElement $field
137     * @return Fieldset
138     */
139    public function addField(AbstractElement $field): Fieldset
140    {
141        if (!isset($this->fields[$this->current])) {
142            $this->fields[$this->current] = [];
143        }
144        $this->fields[$this->current][$field->getName()] = $field;
145        return $this;
146    }
147
148    /**
149     * Method to add form fields
150     *
151     * @param  array $fields
152     * @return Fieldset
153     */
154    public function addFields(array $fields): Fieldset
155    {
156        foreach ($fields as $field) {
157            $this->addField($field);
158        }
159        return $this;
160    }
161
162    /**
163     * Method to insert a field before another one
164     *
165     * @param  string          $name
166     * @param  AbstractElement $field
167     * @return Fieldset
168     */
169    public function insertFieldBefore(string $name, AbstractElement $field): Fieldset
170    {
171        foreach ($this->fields as $i => $group) {
172            $fields = [];
173            foreach ($group as $key => $value) {
174                if ($key == $name) {
175                    $fields[$field->getName()] = $field;
176                    $fields[$key] = $value;
177                } else {
178                    $fields[$key] = $value;
179                }
180            }
181            $this->fields[$i] = $fields;
182        }
183
184        return $this;
185    }
186
187    /**
188     * Method to insert a field after another one
189     *
190     * @param  string          $name
191     * @param  AbstractElement $field
192     * @return Fieldset
193     */
194    public function insertFieldAfter(string $name, AbstractElement $field): Fieldset
195    {
196        foreach ($this->fields as $i => $group) {
197            $fields = [];
198            foreach ($group as $key => $value) {
199                if ($key == $name) {
200                    $fields[$key] = $value;
201                    $fields[$field->getName()] = $field;
202                } else {
203                    $fields[$key] = $value;
204                }
205            }
206            $this->fields[$i] = $fields;
207        }
208
209        return $this;
210    }
211
212    /**
213     * Method to get the count of elements in the form fieldset
214     *
215     * @return int
216     */
217    public function count(): int
218    {
219        $count = 0;
220        foreach ($this->fields as $group) {
221            $count += count($group);
222        }
223        return $count;
224    }
225
226    /**
227     * Method to get the field values as an array
228     *
229     * @return array
230     */
231    public function toArray(): array
232    {
233        $fieldValues = [];
234
235        foreach ($this->fields as $group) {
236            foreach ($group as $name => $field) {
237                $fieldValues[$name] = $field->getValue();
238            }
239        }
240
241        return $fieldValues;
242    }
243
244    /**
245     * Method to get fieldset legend
246     *
247     * @return string|null
248     */
249    public function getLegend(): string|null
250    {
251        return $this->legend;
252    }
253
254    /**
255     * Method to get container
256     *
257     * @return string
258     */
259    public function getContainer(): string
260    {
261        return $this->container;
262    }
263
264    /**
265     * Method to get current group index
266     *
267     * @return int
268     */
269    public function getCurrent(): int
270    {
271        return $this->current;
272    }
273
274    /**
275     * Method to determine if the fieldset has a field
276     *
277     * @param  string $name
278     * @return bool
279     */
280    public function hasField(string $name): bool
281    {
282        $result = false;
283        foreach ($this->fields as $key => $fields) {
284            if (isset($fields[$name])) {
285                $result = true;
286                break;
287            }
288        }
289        return $result;
290    }
291
292    /**
293     * Method to get a field element object
294     *
295     * @param  string $name
296     * @return AbstractElement
297     */
298    public function getField(string $name): AbstractElement
299    {
300        $result = null;
301        foreach ($this->fields as $key => $fields) {
302            if (isset($fields[$name])) {
303                $result = $fields[$name];
304            }
305        }
306        return $result;
307    }
308
309    /**
310     * Method to get field element objects in a group
311     *
312     * @param  int  $i
313     * @return array|null
314     */
315    public function getFields(int $i): array|null
316    {
317        return $this->fields[$i] ?? null;
318    }
319
320    /**
321     * Method to get all field element groups
322     *
323     * @return array
324     */
325    public function getFieldGroups(): array
326    {
327        return $this->fields;
328    }
329
330    /**
331     * Method to get all field elements
332     *
333     * @return array
334     */
335    public function getAllFields(): array
336    {
337        $fields = [];
338        foreach ($this->fields as $group) {
339            foreach ($group as $field) {
340                $fields[$field->getName()] = $field;
341            }
342        }
343        return $fields;
344    }
345
346    /**
347     * Method to get a field element value
348     *
349     * @param  string $name
350     * @return mixed
351     */
352    public function getFieldValue(string $name): mixed
353    {
354        $result = null;
355        foreach ($this->fields as $key => $fields) {
356            if (isset($fields[$name])) {
357                $result = $this->fields[$key][$name]->getValue();
358            }
359        }
360        return $result;
361    }
362
363    /**
364     * Method to set fieldset legend
365     *
366     * @param  string $legend
367     * @return Fieldset
368     */
369    public function setLegend(string $legend): Fieldset
370    {
371        $this->legend = $legend;
372        return $this;
373    }
374
375    /**
376     * Method to set a field element value
377     *
378     * @param  string $name
379     * @param  mixed  $value
380     * @return Fieldset
381     */
382    public function setFieldValue(string $name, mixed $value): Fieldset
383    {
384        foreach ($this->fields as $key => $fields) {
385            if (isset($fields[$name])) {
386                $this->fields[$key][$name]->setValue($value);
387            }
388        }
389        return $this;
390    }
391
392    /**
393     * Method to set field element values
394     *
395     * @param  array $values
396     * @return Fieldset
397     */
398    public function setFieldValues(array $values): Fieldset
399    {
400        foreach ($values as $name => $value) {
401            $this->setFieldValue($name, $value);
402        }
403        return $this;
404    }
405
406    /**
407     * Method to iterate over the form elements
408     *
409     * @return ArrayIterator
410     */
411    public function getIterator(): ArrayIterator
412    {
413        return new ArrayIterator($this->toArray());
414    }
415
416    /**
417     * Prepare fieldset object for rendering
418     *
419     * @return Fieldset
420     */
421    public function prepare(): Fieldset
422    {
423        if (!empty($this->legend)) {
424            $this->addChild(new Child('legend', $this->legend));
425        }
426
427        switch ($this->container) {
428            case 'table':
429                $this->prepareTable();
430                break;
431            case 'dl':
432                $this->prepareDl();
433                break;
434            default:
435                $this->prepareElement($this->container);
436        }
437
438        return $this;
439    }
440
441    /**
442     * Prepare fieldset elements for rendering with a view
443     *
444     * @return array
445     */
446    public function prepareForView(): array
447    {
448        $fields = [];
449
450        foreach ($this->fields as $groups) {
451            foreach ($groups as $field) {
452                if ($field->hasLabel()) {
453                    $labelFor = $field->getName() . (($field->getNodeName() == 'fieldset') ? '1' : '');
454                    $label    = new Child('label', $field->getLabel());
455                    $label->setAttribute('for', $labelFor);
456                    if ($field->getLabelAttributes() !== null) {
457                        $label->setAttributes($field->getLabelAttributes());
458                    }
459                    if ($field->isRequired()) {
460                        if ($label->hasAttribute('class')) {
461                            $label->setAttribute('class', $label->getAttribute('class') . ' required');
462                        } else {
463                            $label->setAttribute('class', 'required');
464                        }
465                    }
466                    $fields[$field->getName() . '_label'] = $label->render();
467                }
468
469                if ($field->hasHint()) {
470                    $hint = new Child('span', $field->getHint());
471                    if ($field->getHintAttributes() !== null) {
472                        $hint->setAttributes($field->getHintAttributes());
473                    }
474                    $fields[$field->getName() . '_hint'] = $hint->render();
475                }
476
477                if ($field->hasErrors()) {
478                    $fields[$field->getName() . '_errors'] = $field->getErrors();
479                }
480
481                $fields[$field->getName()] = $field->render();
482            }
483        }
484
485        return $fields;
486    }
487
488    /**
489     * Prepare table
490     *
491     * @return void
492     */
493    protected function prepareTable(): void
494    {
495        foreach ($this->fields as $fields) {
496            $table = new Child('table');
497
498            foreach ($fields as $field) {
499                $errors = null;
500                if ($field->hasErrors()) {
501                    $errors = new Child('div');
502                    $errors->setAttribute('class', 'error');
503                    foreach ($field->getErrors() as $error) {
504                        $errors->addChild(new Child('span', $error));
505                    }
506                }
507
508                $tr = new Child('tr');
509                if ($field->hasLabel()) {
510                    $td = new Child('td');
511                    $labelFor = $field->getName() . (($field->getNodeName() == 'fieldset') ? '1' : '');
512
513                    $label = new Child('label', $field->getLabel());
514                    $label->setAttribute('for', $labelFor);
515                    if ($field->hasLabelAttributes()) {
516                        $label->setAttributes($field->getLabelAttributes());
517                    }
518                    if ($field->isRequired()) {
519                        if ($label->hasAttribute('class')) {
520                            $label->setAttribute('class', $label->getAttribute('class') . ' required');
521                        } else {
522                            $label->setAttribute('class', 'required');
523                        }
524                    }
525                    $td->addChild($label);
526                    $tr->addChild($td);
527                }
528
529                $nodeValue = null;
530                $options   = [];
531                if ($field->hasAppend()) {
532                    $nodeValue = $field->getAppend();
533                    $options   = ['childrenFirst' => true];
534                } else if ($field->hasPrepend()) {
535                    $nodeValue = $field->getPrepend();
536                    $options   = ['childrenFirst' => false];
537                }
538
539                $td = new Child('td', $nodeValue, $options);
540
541                if (!empty($errors) && ($field->isErrorPre())) {
542                    $td->addChild($errors);
543                }
544                $td->addChild($field);
545
546                if ($field->hasHint()) {
547                    $hint = new Child('span', $field->getHint());
548                    if ($field->hasHintAttributes()) {
549                        $hint->setAttributes($field->getHintAttributes());
550                    }
551                    $td->addChild($hint);
552                }
553
554                if ($field->hasLabel()) {
555                    $td->setAttribute('colspan', 2);
556                }
557                if (!empty($errors) && (!$field->isErrorPre())) {
558                    $td->addChild($errors);
559                }
560                $tr->addChild($td);
561                $table->addChild($tr);
562            }
563
564            $this->addChild($table);
565        }
566    }
567
568    /**
569     * Prepare DIV or P
570     *
571     * @param  string $element
572     * @return void
573     */
574    protected function prepareElement(string $element): void
575    {
576        foreach ($this->fields as $fields) {
577            foreach ($fields as $field) {
578                $errors = null;
579                if ($field->hasErrors()) {
580                    $errors = new Child('div');
581                    $errors->setAttribute('class', 'error');
582                    foreach ($field->getErrors() as $error) {
583                        $errors->addChild(new Child('span', $error));
584                    }
585                }
586
587                $nodeValue = null;
588                $options   = [];
589                if ($field->hasAppend()) {
590                    $nodeValue = $field->getAppend();
591                    $options   = ['childrenFirst' => true];
592                } else if ($field->hasPrepend()) {
593                    $nodeValue = $field->getPrepend();
594                    $options   = ['childrenFirst' => false];
595                }
596
597                $container = new Child($element, $nodeValue, $options);
598
599                if ($field->hasLabel()) {
600                    $labelFor = $field->getName() . (($field->getNodeName() == 'fieldset') ? '1' : '');
601                    $label    = new Child('label', $field->getLabel());
602                    $label->setAttribute('for', $labelFor);
603                    if ($field->hasLabelAttributes()) {
604                        $label->setAttributes($field->getLabelAttributes());
605                    }
606                    if ($field->isRequired()) {
607                        if ($label->hasAttribute('class')) {
608                            $label->setAttribute('class', $label->getAttribute('class') . ' required');
609                        } else {
610                            $label->setAttribute('class', 'required');
611                        }
612                    }
613                    $container->addChild($label);
614                }
615
616                if (!empty($errors) && ($field->isErrorPre())) {
617                    $container->addChild($errors);
618                }
619                $container->addChild($field);
620
621                if ($field->hasHint()) {
622                    $hint = new Child('span', $field->getHint());
623                    if ($field->hasHintAttributes()) {
624                        $hint->setAttributes($field->getHintAttributes());
625                    }
626                    $container->addChild($hint);
627                }
628                if (!empty($errors) && (!$field->isErrorPre())) {
629                    $container->addChild($errors);
630                }
631                $this->addChild($container);
632            }
633        }
634    }
635
636    /**
637     * Prepare DL
638     *
639     * @return void
640     */
641    protected function prepareDl(): void
642    {
643        foreach ($this->fields as $fields) {
644            $dl = new Child('dl');
645
646            foreach ($fields as $field) {
647                $errors = null;
648                if ($field->hasErrors()) {
649                    $errors = new Child('div');
650                    $errors->setAttribute('class', 'error');
651                    foreach ($field->getErrors() as $error) {
652                        $errors->addChild(new Child('span', $error));
653                    }
654                }
655
656                if ($field->hasLabel()) {
657                    $dt = new Child('dt');
658                    $labelFor = $field->getName() . (($field->getNodeName() == 'fieldset') ? '1' : '');
659
660                    $label = new Child('label', $field->getLabel());
661                    $label->setAttribute('for', $labelFor);
662                    if ($field->hasLabelAttributes()) {
663                        $label->setAttributes($field->getLabelAttributes());
664                    }
665                    if ($field->isRequired()) {
666                        if ($label->hasAttribute('class')) {
667                            $label->setAttribute('class', $label->getAttribute('class') . ' required');
668                        } else {
669                            $label->setAttribute('class', 'required');
670                        }
671                    }
672                    $dt->addChild($label);
673                    $dl->addChild($dt);
674                }
675
676                $nodeValue = null;
677                $options   = [];
678                if ($field->hasAppend()) {
679                    $nodeValue = $field->getAppend();
680                    $options   = ['childrenFirst' => true];
681                } else if ($field->hasPrepend()) {
682                    $nodeValue = $field->getPrepend();
683                    $options   = ['childrenFirst' => false];
684                }
685
686                $dd = new Child('dd', $nodeValue, $options);
687
688                if (!empty($errors) && ($field->isErrorPre())) {
689                    $dd->addChild($errors);
690                }
691                $dd->addChild($field);
692
693                if ($field->hasHint()) {
694                    $hint = new Child('span', $field->getHint());
695                    if ($field->hasHintAttributes()) {
696                        $hint->setAttributes($field->getHintAttributes());
697                    }
698                    $dd->addChild($hint);
699                }
700                if (!empty($errors) && (!$field->isErrorPre())) {
701                    $dd->addChild($errors);
702                }
703                $dl->addChild($dd);
704            }
705
706            $this->addChild($dl);
707        }
708    }
709
710    /**
711     * Set method to set the property to the value of fields[$name]
712     *
713     * @param  string $name
714     * @param  mixed $value
715     * @return void
716     */
717    public function __set(string $name, mixed $value): void
718    {
719        $this->setFieldValue($name, $value);
720    }
721
722    /**
723     * Get method to return the value of fields[$name]
724     *
725     * @param  string $name
726     * @return mixed
727     */
728    public function __get(string $name): mixed
729    {
730        return $this->getFieldValue($name);
731    }
732
733    /**
734     * Return the isset value of fields[$name]
735     *
736     * @param  string $name
737     * @return bool
738     */
739    public function __isset(string $name): bool
740    {
741        return $this->hasField($name);
742    }
743
744    /**
745     * Unset fields[$name]
746     *
747     * @param  string $name
748     * @return void
749     */
750    public function __unset(string $name): void
751    {
752        foreach ($this->fields as $i => $group) {
753            foreach ($group as $key => $value) {
754                if ($key == $name) {
755                    unset($this->fields[$i][$key]);
756                }
757            }
758        }
759    }
760
761    /**
762     * ArrayAccess offsetExists
763     *
764     * @param  mixed $offset
765     * @return bool
766     */
767    public function offsetExists(mixed $offset): bool
768    {
769        return $this->__isset($offset);
770    }
771
772    /**
773     * ArrayAccess offsetGet
774     *
775     * @param  mixed $offset
776     * @return mixed
777     */
778    public function offsetGet(mixed $offset): mixed
779    {
780        return $this->__get($offset);
781    }
782
783    /**
784     * ArrayAccess offsetSet
785     *
786     * @param  mixed $offset
787     * @param  mixed $value
788     * @return void
789     */
790    public function offsetSet(mixed $offset, mixed $value): void
791    {
792        $this->__set($offset, $value);
793    }
794
795    /**
796     * ArrayAccess offsetUnset
797     *
798     * @param  mixed $offset
799     * @return void
800     */
801    public function offsetUnset(mixed $offset): void
802    {
803        $this->__unset($offset);
804    }
805
806}