Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
100.00% |
169 / 169 |
|
100.00% |
2 / 2 |
CRAP | |
100.00% |
1 / 1 |
| Fields | |
100.00% |
169 / 169 |
|
100.00% |
2 / 2 |
75 | |
100.00% |
1 / 1 |
| create | |
100.00% |
123 / 123 |
|
100.00% |
1 / 1 |
49 | |||
| getConfigFromTable | |
100.00% |
46 / 46 |
|
100.00% |
1 / 1 |
26 | |||
| 1 | <?php |
| 2 | declare(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 | */ |
| 15 | namespace Pop\Form; |
| 16 | |
| 17 | use 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 | */ |
| 29 | class 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 | } |