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