Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
169 / 169
100.00% covered (success)
100.00%
2 / 2
CRAP
100.00% covered (success)
100.00%
1 / 1
Fields
100.00% covered (success)
100.00%
169 / 169
100.00% covered (success)
100.00%
2 / 2
75
100.00% covered (success)
100.00%
1 / 1
 create
100.00% covered (success)
100.00%
123 / 123
100.00% covered (success)
100.00%
1 / 1
49
 getConfigFromTable
100.00% covered (success)
100.00%
46 / 46
100.00% covered (success)
100.00%
1 / 1
26
1<?php
2declare(strict_types=1);
3/**
4 * Pop PHP Framework (https://www.popphp.org/)
5 *
6 * @link       https://github.com/popphp/popphp-framework
7 * @author     Nick Sagona, III <nick@popphp.org>
8 * @copyright  Copyright (c) 2009-2026 Nick Sagona, III
9 * @license    https://www.popphp.org/license     New BSD License
10 */
11
12/**
13 * @namespace
14 */
15namespace Pop\Form;
16
17use Pop\Form\Element\AbstractElement;
18
19/**
20 * Form fields config class
21 *
22 * @category   Pop
23 * @package    Pop\Form
24 * @author     Nick Sagona, III <nick@popphp.org>
25 * @copyright  Copyright (c) 2009-2026 Nick Sagona, III
26 * @license    https://www.popphp.org/license     New BSD License
27 * @version    5.0.0
28 */
29class Fields
30{
31
32    /**
33     * Map of field 'type' string => concrete input element class, for the input types
34     * that only need the standard (name, value, indent) constructor signature.
35     *
36     * This is an explicit allowlist rather than resolving
37     * `Pop\Form\Element\Input\{Ucfirst($type)}` dynamically via `class_exists()` — that
38     * pattern would instantiate *any* class sitting in that namespace from a
39     * config-supplied string, including non-element classes (e.g. `Element\Input\Exception`),
40     * with no check afterward that the result is actually an `AbstractElement`.
41     *
42     * @var array<string, class-string>
43     */
44    private const array INPUT_ELEMENT_TYPES = [
45        'color'    => Element\Input\Color::class,
46        'date'     => Element\Input\Date::class,
47        'email'    => Element\Input\Email::class,
48        'file'     => Element\Input\File::class,
49        'hidden'   => Element\Input\Hidden::class,
50        'month'    => Element\Input\Month::class,
51        'password' => Element\Input\Password::class,
52        'reset'    => Element\Input\Reset::class,
53        'search'   => Element\Input\Search::class,
54        'submit'   => Element\Input\Submit::class,
55        'tel'      => Element\Input\Tel::class,
56        'text'     => Element\Input\Text::class,
57        'time'     => Element\Input\Time::class,
58        'url'      => Element\Input\Url::class,
59        'week'     => Element\Input\Week::class,
60    ];
61
62    /**
63     * Static factory method to create a field element object from a field config array
64     *
65     * @param  string $name
66     * @param  array  $field
67     * @throws Exception|Element\Exception
68     * @return AbstractElement
69     */
70    public static function create(string $name, array $field): AbstractElement
71    {
72        if (!isset($field['type'])) {
73            throw new Exception('Error: The field type was not set.');
74        }
75
76        $type         = $field['type'];
77        $value        = isset($field['value']) ? (string)$field['value'] : null;
78        $values       = $field['values'] ?? [];
79        $label        = $field['label'] ?? null;
80        $indent       = $field['indent'] ?? null;
81        $checked      = $field['checked'] ?? null;
82        $selected     = $field['selected'] ?? null;
83        $required     = $field['required'] ?? null;
84        $disabled     = $field['disabled'] ?? null;
85        $readonly     = $field['readonly'] ?? null;
86        $attributes   = $field['attributes'] ?? null;
87        $validators   = $field['validators'] ?? null;
88        $render       = $field['render'] ?? false;
89        $expire       = $field['expire'] ?? 300;
90        $min          = $field['min'] ?? false;
91        $max          = $field['max'] ?? false;
92        $xmlFile      = $field['xml'] ?? null;
93        $hint         = $field['hint'] ?? null;
94        $hintAttribs  = $field['hint-attributes'] ?? null;
95        $labelAttribs = $field['label-attributes'] ?? null;
96        $prepend      = $field['prepend'] ?? null;
97        $append       = $field['append'] ?? null;
98        $legend       = $field['legend'] ?? null;
99        $container    = $field['container'] ?? null;
100        $errorPre     = (isset($field['error']) && ($field['error'] == 'pre'));
101        $allowedTypes = $field['allowed-types'] ?? null;
102        $maxSize      = $field['max-size'] ?? null;
103
104        // Initialize the form element.
105        switch (strtolower($type)) {
106            case 'button':
107                $element = new Element\Button($name, $value, $indent);
108                break;
109            case 'select':
110                $element = new Element\Select($name, $values, $selected, $xmlFile, $indent);
111                break;
112            case 'select-multiple':
113                $element = new Element\SelectMultiple($name, $values, $selected, $xmlFile, $indent);
114                break;
115            case 'textarea':
116                $element = new Element\Textarea($name, $value, $indent);
117                break;
118            case 'checkbox':
119                $element = new Element\Input\Checkbox($name, $value, $indent);
120                if (($checked === true) || ($value == $checked)) {
121                    $element->check();
122                }
123                break;
124            case 'checkbox-set':
125                $element = new Element\CheckboxSet($name, $values, $checked, $indent, $container);
126                break;
127            case 'radio':
128                $element = new Element\Input\Radio($name, $value, $indent);
129                if (($checked === true) || ($value == $checked)) {
130                    $element->check();
131                }
132                break;
133            case 'radio-set':
134                $element = new Element\RadioSet($name, $values, $checked, $indent, $container);
135                break;
136            case 'csrf':
137                $element = new Element\Input\Csrf($name, $value, $expire, $indent);
138                break;
139            case 'input-button':
140                $element = new Element\Input\Button($name, $value);
141                break;
142            case 'datalist':
143                $element = new Element\Input\Datalist($name, $values, $value);
144                break;
145            case 'datetime':
146                $element = new Element\Input\DateTime($name, $value);
147                break;
148            case 'datetime-local':
149                $element = new Element\Input\DateTimeLocal($name, $value);
150                break;
151            case 'number':
152                $element = new Element\Input\Number($name, $min, $max, $value);
153                break;
154            case 'range':
155                $element = new Element\Input\Range($name, $min, $max, $value);
156                break;
157            default:
158                $typeKey = strtolower($type);
159                if (!isset(self::INPUT_ELEMENT_TYPES[$typeKey])) {
160                    throw new Exception('Error: That class for that form element (' . $type . ') does not exist.');
161                }
162                $class   = self::INPUT_ELEMENT_TYPES[$typeKey];
163                $element = new $class($name, $value);
164                if ($class === Element\Input\Password::class) {
165                    $element->setRenderValue($render);
166                }
167        }
168
169        // Set the label.
170        if ($label !== null) {
171            $element->setLabel($label);
172        }
173        // Set the label attributes.
174        if (($labelAttribs !== null) && is_array($labelAttribs)) {
175            $element->setLabelAttributes($labelAttribs);
176        }
177        // Set prepend content.
178        if ($prepend !== null) {
179            $element->setPrepend($prepend);
180        }
181        // Set append content.
182        if ($append !== null) {
183            $element->setAppend($append);
184        }
185        // Set legend content.
186        if ($legend !== null) {
187            $element->setLegend($legend);
188        }
189        // Set the hint.
190        if ($hint !== null) {
191            $element->setHint($hint);
192        }
193        // Set the hint attributes.
194        if (($hintAttribs !== null) && is_array($hintAttribs)) {
195            $element->setHintAttributes($hintAttribs);
196        }
197        // Set if required.
198        if (($required !== null) && ($required)) {
199            $element->setRequired($required, ($field['required_message'] ?? 'This field is required.'));
200        }
201        // Set if disabled.
202        if (($disabled !== null) && ($disabled)) {
203            $element->setDisabled($disabled);
204        }
205        // Set if readonly.
206        if (($readonly !== null) && ($readonly)) {
207            $element->setReadonly($readonly);
208        }
209
210        $element->setErrorPre($errorPre);
211
212        // Set file-specific options.
213        if ($element instanceof Element\Input\File) {
214            if (is_array($allowedTypes)) {
215                $element->setAllowedTypes($allowedTypes);
216            }
217            if ($maxSize !== null) {
218                $element->setMaxSize((int)$maxSize);
219            }
220        }
221
222        // Set any attributes.
223        if ($attributes !== null) {
224            if ($element instanceof Element\CheckboxSet) {
225                $element->setCheckboxAttributes($attributes);
226            } else if ($element instanceof Element\RadioSet) {
227                $element->setRadioAttributes($attributes);
228            } else {
229                $element->setAttributes($attributes);
230            }
231        }
232        // Set any validators.
233        if ($validators !== null) {
234            if (is_array($validators)) {
235                $element->addValidators($validators);
236            } else {
237                $element->addValidator($validators);
238            }
239        }
240
241        return $element;
242    }
243
244    /**
245     * Static factory method to get field configs from a database table
246     *
247     * @param  array  $tableInfo
248     * @param  ?array $attribs
249     * @param  ?array $config
250     * @param  mixed  $omit
251     * @throws Exception
252     * @return array
253     */
254    public static function getConfigFromTable(
255        array $tableInfo, ?array $attribs = null, ?array $config = null, mixed $omit = null
256    ): array
257    {
258        $fields = [];
259
260        if (!isset($tableInfo['tableName']) || !isset($tableInfo['columns'])) {
261            throw new Exception('Error: The table info parameter is not in the correct format');
262        }
263
264        if ($omit !== null) {
265            if (!is_array($omit)) {
266                $omit = [$omit];
267            }
268        } else {
269            $omit = [];
270        }
271
272        if ($config === null) {
273            $config = [];
274        }
275
276        foreach ($tableInfo['columns'] as $name => $value) {
277            if (!in_array($name, $omit)) {
278                $fieldValue = null;
279                $fieldLabel = null;
280                $attributes = null;
281
282                if (isset($config[$name]) && isset($config[$name]['validators'])) {
283                    $validators = (!is_array($config[$name]['validators'])) ?
284                        [$config[$name]['validators']] : $config[$name]['validators'];
285                } else {
286                    $validators = null;
287                }
288
289                if (isset($config[$name]) && isset($config[$name]['type'])) {
290                    $fieldType = $config[$name]['type'];
291                } else if (stripos($name, 'password') !== false) {
292                    $fieldType = 'password';
293                } else if ((stripos($name, 'email') !== false) || (stripos($name, 'e-mail') !== false) ||
294                    (stripos($name, 'e_mail') !== false)) {
295                    $fieldType = 'email';
296                    if ($validators !== null) {
297                        $validators[] = new \Pop\Validator\Email();
298                    } else {
299                        $validators = [new \Pop\Validator\Email()];
300                    }
301                } else {
302                    $fieldType = (stripos($value['type'], 'text') !== false) ? 'textarea' : 'text';
303                }
304
305                $fieldValue = (isset($config[$name]) && isset($config[$name]['value'])) ? $config[$name]['value'] : null;
306
307                if ($fieldType != 'hidden') {
308                    $fieldLabel = ucwords(str_replace('_', ' ', $name)) . ':';
309                }
310
311                if ($attribs !== null) {
312                    if (isset($attribs[$fieldType])) {
313                        $attributes =  $attribs[$fieldType];
314                    }
315                }
316
317                $required = (isset($config[$name]) && isset($config[$name]['required'])) ?
318                    (bool)$config[$name]['required'] : !($value['null']);
319
320                $fields[$name] = [
321                    'type'       => $fieldType,
322                    'label'      => $fieldLabel,
323                    'value'      => $fieldValue,
324                    'required'   => $required,
325                    'attributes' => $attributes,
326                    'validators' => $validators
327                ];
328            }
329        }
330
331        return $fields;
332    }
333
334}