Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
100.00% |
132 / 132 |
|
100.00% |
11 / 11 |
CRAP | |
100.00% |
1 / 1 |
AbstractSelect | |
100.00% |
132 / 132 |
|
100.00% |
11 / 11 |
52 | |
100.00% |
1 / 1 |
setRequired | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
setDisabled | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
setReadonly | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
5 | |||
getType | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getValue | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getSelected | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getOptions | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
6 | |||
getOptionsAsArray | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
2 | |||
validate | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
3 | |||
parseValues | |
100.00% |
80 / 80 |
|
100.00% |
1 / 1 |
24 | |||
parseXml | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
5 |
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 | */ |
14 | namespace Pop\Form\Element; |
15 | |
16 | use SimpleXMLElement; |
17 | |
18 | /** |
19 | * Abstract select element class |
20 | * |
21 | * @category Pop |
22 | * @package Pop\Form |
23 | * @author Nick Sagona, III <dev@nolainteractive.com> |
24 | * @copyright Copyright (c) 2009-2024 NOLA Interactive, LLC. (http://www.nolainteractive.com) |
25 | * @license http://www.popphp.org/license New BSD License |
26 | * @version 4.0.0 |
27 | */ |
28 | |
29 | abstract class AbstractSelect extends AbstractElement |
30 | { |
31 | |
32 | /** |
33 | * Constant for months, short |
34 | * @var string |
35 | */ |
36 | const MONTHS_SHORT = 'MONTHS_SHORT'; |
37 | |
38 | /** |
39 | * Constant for days of the month |
40 | * @var string |
41 | */ |
42 | const DAYS_OF_MONTH = 'DAYS_OF_MONTH'; |
43 | |
44 | /** |
45 | * Constant for 12 hours |
46 | * @var string |
47 | */ |
48 | const HOURS_12 = 'HOURS_12'; |
49 | |
50 | /** |
51 | * Constant for 24 hours |
52 | * @var string |
53 | */ |
54 | const HOURS_24 = 'HOURS_24'; |
55 | |
56 | /** |
57 | * Constant for 60 minutes (0-59) |
58 | * @var string |
59 | */ |
60 | const MINUTES = 'MINUTES'; |
61 | |
62 | /** |
63 | * Constant for minutes in increments of 5 |
64 | * @var string |
65 | */ |
66 | const MINUTES_5 = 'MINUTES_5'; |
67 | |
68 | /** |
69 | * Constant for minutes in increments of 10 |
70 | * @var string |
71 | */ |
72 | const MINUTES_10 = 'MINUTES_10'; |
73 | |
74 | /** |
75 | * Constant for minutes in increments of 15 |
76 | * @var string |
77 | */ |
78 | const MINUTES_15 = 'MINUTES_15'; |
79 | |
80 | /** |
81 | * Selected value(s) |
82 | * @var mixed |
83 | */ |
84 | protected mixed $selected = null; |
85 | |
86 | /** |
87 | * Set whether the form element is required |
88 | * |
89 | * @param bool $required |
90 | * @param ?string $requiredMessage |
91 | * @return AbstractSelect |
92 | */ |
93 | public function setRequired(bool $required, ?string $requiredMessage = 'This field is required.'): AbstractSelect |
94 | { |
95 | if ($required) { |
96 | $this->setAttribute('required', 'required'); |
97 | } else { |
98 | $this->removeAttribute('required'); |
99 | } |
100 | return parent::setRequired($required, $requiredMessage); |
101 | } |
102 | |
103 | /** |
104 | * Set whether the form element is disabled |
105 | * |
106 | * @param bool $disabled |
107 | * @return AbstractSelect |
108 | */ |
109 | public function setDisabled(bool $disabled): AbstractSelect |
110 | { |
111 | if ($disabled) { |
112 | $this->setAttribute('disabled', 'disabled'); |
113 | } else { |
114 | $this->removeAttribute('disabled'); |
115 | } |
116 | return parent::setDisabled($disabled); |
117 | } |
118 | |
119 | /** |
120 | * Set whether the form element is readonly |
121 | * |
122 | * @param bool $readonly |
123 | * @return AbstractSelect |
124 | */ |
125 | public function setReadonly(bool $readonly): AbstractSelect |
126 | { |
127 | if ($readonly) { |
128 | $this->setAttribute('readonly', 'readonly'); |
129 | foreach ($this->childNodes as $childNode) { |
130 | if ($childNode->getAttribute('selected') != 'selected') { |
131 | $childNode->setAttribute('disabled', 'disabled'); |
132 | } else { |
133 | $childNode->setAttribute('readonly', 'readonly'); |
134 | } |
135 | } |
136 | } else { |
137 | $this->removeAttribute('readonly'); |
138 | foreach ($this->childNodes as $childNode) { |
139 | $childNode->removeAttribute('disabled'); |
140 | $childNode->removeAttribute('readonly'); |
141 | } |
142 | } |
143 | |
144 | return parent::setReadonly($readonly); |
145 | } |
146 | |
147 | /** |
148 | * Get form element object type |
149 | * |
150 | * @return string |
151 | */ |
152 | public function getType(): string |
153 | { |
154 | return 'select'; |
155 | } |
156 | |
157 | /** |
158 | * Get select form element selected value |
159 | * |
160 | * @return mixed |
161 | */ |
162 | public function getValue(): mixed |
163 | { |
164 | return $this->selected; |
165 | } |
166 | |
167 | /** |
168 | * Get select form element selected value (alias) |
169 | * |
170 | * @return mixed |
171 | */ |
172 | public function getSelected(): mixed |
173 | { |
174 | return $this->getValue(); |
175 | } |
176 | |
177 | /** |
178 | * Get select options |
179 | * |
180 | * @return array |
181 | */ |
182 | public function getOptions(): array |
183 | { |
184 | $options = []; |
185 | |
186 | foreach ($this->childNodes as $child) { |
187 | if ($child instanceof Select\Option) { |
188 | $options[] = $child; |
189 | } else if ($child instanceof Select\Optgroup) { |
190 | foreach ($child->getChildren() as $c) { |
191 | if ($c instanceof Select\Option) { |
192 | $options[] = $c; |
193 | } |
194 | } |
195 | } |
196 | } |
197 | |
198 | return $options; |
199 | } |
200 | |
201 | /** |
202 | * Get select options as array |
203 | * |
204 | * @return array |
205 | */ |
206 | public function getOptionsAsArray(): array |
207 | { |
208 | $options = $this->getOptions(); |
209 | $optionsArray = []; |
210 | |
211 | foreach ($options as $option) { |
212 | $optionsArray[$option->getValue()] = $option->getNodeValue(); |
213 | } |
214 | |
215 | return $optionsArray; |
216 | } |
217 | |
218 | /** |
219 | * Validate the form element object |
220 | * |
221 | * @param array $formValues |
222 | * @return bool |
223 | */ |
224 | public function validate(array $formValues = []): bool |
225 | { |
226 | $value = $this->getValue(); |
227 | |
228 | // Check if the element is required |
229 | if (($this->required) && empty($value)) { |
230 | $this->errors[] = $this->getRequiredMessage(); |
231 | } |
232 | |
233 | $this->validateValue($value, $formValues); |
234 | |
235 | return (count($this->errors) == 0); |
236 | } |
237 | |
238 | /** |
239 | * Set the select element as multiple |
240 | * |
241 | * @param string|array $values |
242 | * @param ?string $xmlFile |
243 | * @return array |
244 | */ |
245 | public static function parseValues(string|array $values, ?string $xmlFile = null): array |
246 | { |
247 | $parsedValues = null; |
248 | |
249 | // If the values are an array of values already |
250 | if (is_array($values)) { |
251 | $parsedValues = $values; |
252 | // Else, if the value is a string |
253 | } else if (is_string($values)) { |
254 | // If the value flag is YEAR-based, calculate the year range for the select drop-down menu. |
255 | if (str_contains($values, 'YEAR')) { |
256 | $years = []; |
257 | $yearAry = explode('_', $values); |
258 | // YEAR_1111_2222 (from year 1111 to 2222) |
259 | if (isset($yearAry[1]) && isset($yearAry[2])) { |
260 | if ($yearAry[1] < $yearAry[2]) { |
261 | for ($i = $yearAry[1]; $i <= $yearAry[2]; $i++) { |
262 | $years[$i] = $i; |
263 | } |
264 | } else { |
265 | for ($i = $yearAry[1]; $i >= $yearAry[2]; $i--) { |
266 | $years[$i] = $i; |
267 | } |
268 | } |
269 | // YEAR_1111 |
270 | // If 1111 is less than today's year, then 1111 to present year, |
271 | // else from present year to 1111 |
272 | } else if (isset($yearAry[1])) { |
273 | $year = date('Y'); |
274 | if ($year < $yearAry[1]) { |
275 | for ($i = $year; $i <= $yearAry[1]; $i++) { |
276 | $years[$i] = $i; |
277 | } |
278 | } else { |
279 | for ($i = $year; $i >= $yearAry[1]; $i--) { |
280 | $years[$i] = $i; |
281 | } |
282 | } |
283 | // YEAR, from present year to 10+ years |
284 | } else { |
285 | $year = date('Y'); |
286 | for ($i = $year; $i <= ($year + 10); $i++) { |
287 | $years[$i] = $i; |
288 | } |
289 | } |
290 | $parsedValues = $years; |
291 | } else { |
292 | // Else, if the value flag is one of the pre-defined , set the value of the select drop-down menu to it. |
293 | switch ($values) { |
294 | // Hours, 12-hour values. |
295 | // Months, numeric short values. |
296 | case Select::HOURS_12: |
297 | case Select::MONTHS_SHORT: |
298 | $parsedValues = [ |
299 | '01' => '01', '02' => '02', '03' => '03', '04' => '04', '05' => '05', '06' => '06', |
300 | '07' => '07', '08' => '08', '09' => '09', '10' => '10', '11' => '11', '12' => '12' |
301 | ]; |
302 | break; |
303 | // Days of Month, numeric short values. |
304 | case Select::DAYS_OF_MONTH: |
305 | $parsedValues = [ |
306 | '01' => '01', '02' => '02', '03' => '03', '04' => '04', '05' => '05', |
307 | '06' => '06', '07' => '07', '08' => '08', '09' => '09', '10' => '10', '11' => '11', |
308 | '12' => '12', '13' => '13', '14' => '14', '15' => '15', '16' => '16', '17' => '17', |
309 | '18' => '18', '19' => '19', '20' => '20', '21' => '21', '22' => '22', '23' => '23', |
310 | '24' => '24', '25' => '25', '26' => '26', '27' => '27', '28' => '28', '29' => '29', |
311 | '30' => '30', '31' => '31' |
312 | ]; |
313 | break; |
314 | // Military hours, 24-hour values. |
315 | case Select::HOURS_24: |
316 | $parsedValues = [ |
317 | '00' => '00', '01' => '01', '02' => '02', '03' => '03', '04' => '04', '05' => '05', |
318 | '06' => '06', '07' => '07', '08' => '08', '09' => '09', '10' => '10', '11' => '11', '12' => '12', |
319 | '13' => '13', '14' => '14', '15' => '15', '16' => '16', '17' => '17', '18' => '18', '19' => '19', |
320 | '20' => '20', '21' => '21', '22' => '22', '23' => '23' |
321 | ]; |
322 | break; |
323 | // Minutes, incremental by 1 minute. |
324 | case Select::MINUTES: |
325 | $parsedValues = [ |
326 | '00' => '00', '01' => '01', '02' => '02', '03' => '03', '04' => '04', '05' => '05', |
327 | '06' => '06', '07' => '07', '08' => '08', '09' => '09', '10' => '10', '11' => '11', '12' => '12', |
328 | '13' => '13', '14' => '14', '15' => '15', '16' => '16', '17' => '17', '18' => '18', '19' => '19', |
329 | '20' => '20', '21' => '21', '22' => '22', '23' => '23', '24' => '24', '25' => '25', '26' => '26', |
330 | '27' => '27', '28' => '28', '29' => '29', '30' => '30', '31' => '31', '32' => '32', '33' => '33', |
331 | '34' => '34', '35' => '35', '36' => '36', '37' => '37', '38' => '38', '39' => '39', '40' => '40', |
332 | '41' => '41', '42' => '42', '43' => '43', '44' => '44', '45' => '45', '46' => '46', '47' => '47', |
333 | '48' => '48', '49' => '49', '50' => '50', '51' => '51', '52' => '52', '53' => '53', '54' => '54', |
334 | '55' => '55', '56' => '56', '57' => '57', '58' => '58', '59' => '59' |
335 | ]; |
336 | break; |
337 | // Minutes, incremental by 5 minutes. |
338 | case Select::MINUTES_5: |
339 | $parsedValues = [ |
340 | '00' => '00', '05' => '05', '10' => '10', '15' => '15', '20' => '20', '25' => '25', |
341 | '30' => '30', '35' => '35', '40' => '40', '45' => '45', '50' => '50', '55' => '55' |
342 | ]; |
343 | break; |
344 | // Minutes, incremental by 10 minutes. |
345 | case Select::MINUTES_10: |
346 | $parsedValues = [ |
347 | '00' => '00', '10' => '10', '20' => '20', '30' => '30', '40' => '40', '50' => '50' |
348 | ]; |
349 | break; |
350 | // Minutes, incremental by 15 minutes. |
351 | case Select::MINUTES_15: |
352 | $parsedValues = ['00' => '00', '15' => '15', '30' => '30', '45' => '45']; |
353 | break; |
354 | // Else, set the custom array of values passed. |
355 | default: |
356 | if ($xmlFile === null) { |
357 | $xmlFile = __DIR__ . DIRECTORY_SEPARATOR . 'Data' . DIRECTORY_SEPARATOR . 'options.xml'; |
358 | } |
359 | $parsedValues = self::parseXml($xmlFile, $values); |
360 | } |
361 | } |
362 | } |
363 | |
364 | return $parsedValues; |
365 | } |
366 | |
367 | /** |
368 | * Static method to parse an XML file of options |
369 | * |
370 | * @param string $xmlFile |
371 | * @param string $name |
372 | * @throws \Exception |
373 | * @return array |
374 | */ |
375 | protected static function parseXml(string $xmlFile, string $name): array |
376 | { |
377 | $options = []; |
378 | |
379 | if (file_exists($xmlFile)) { |
380 | $xml = new SimpleXMLElement($xmlFile, 0, true); |
381 | $xmlValues = []; |
382 | foreach ($xml->set as $node) { |
383 | $xmlValues[(string)$node->attributes()->name] = []; |
384 | foreach ($node->opt as $opt) { |
385 | $xmlValues[(string)$node->attributes()->name][(string)$opt->attributes()->value] = (string)$opt; |
386 | } |
387 | } |
388 | if (array_key_exists($name, $xmlValues)) { |
389 | $options = $xmlValues[$name]; |
390 | } |
391 | } |
392 | |
393 | return $options; |
394 | } |
395 | |
396 | } |