friendship ended with social-app. php is my new best friend
1<?php
2
3/*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <fabien@symfony.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12namespace Symfony\Component\DomCrawler\Field;
13
14/**
15 * ChoiceFormField represents a choice form field.
16 *
17 * It is constructed from an HTML select tag, or an HTML checkbox, or radio inputs.
18 *
19 * @author Fabien Potencier <fabien@symfony.com>
20 */
21class ChoiceFormField extends FormField
22{
23 private string $type;
24 private bool $multiple;
25 private array $options;
26 private bool $validationDisabled = false;
27
28 /**
29 * Returns true if the field should be included in the submitted values.
30 *
31 * @return bool true if the field should be included in the submitted values, false otherwise
32 */
33 public function hasValue(): bool
34 {
35 // don't send a value for unchecked checkboxes
36 if (\in_array($this->type, ['checkbox', 'radio']) && null === $this->value) {
37 return false;
38 }
39
40 return true;
41 }
42
43 /**
44 * Check if the current selected option is disabled.
45 */
46 public function isDisabled(): bool
47 {
48 if ('checkbox' === $this->type) {
49 return parent::isDisabled();
50 }
51
52 if (parent::isDisabled() && 'select' === $this->type) {
53 return true;
54 }
55
56 foreach ($this->options as $option) {
57 if ($option['value'] == $this->value && $option['disabled']) {
58 return true;
59 }
60 }
61
62 return false;
63 }
64
65 /**
66 * Sets the value of the field.
67 */
68 public function select(string|array|bool $value): void
69 {
70 $this->setValue($value);
71 }
72
73 /**
74 * Ticks a checkbox.
75 *
76 * @throws \LogicException When the type provided is not correct
77 */
78 public function tick(): void
79 {
80 if ('checkbox' !== $this->type) {
81 throw new \LogicException(\sprintf('You cannot tick "%s" as it is not a checkbox (%s).', $this->name, $this->type));
82 }
83
84 $this->setValue(true);
85 }
86
87 /**
88 * Unticks a checkbox.
89 *
90 * @throws \LogicException When the type provided is not correct
91 */
92 public function untick(): void
93 {
94 if ('checkbox' !== $this->type) {
95 throw new \LogicException(\sprintf('You cannot untick "%s" as it is not a checkbox (%s).', $this->name, $this->type));
96 }
97
98 $this->setValue(false);
99 }
100
101 /**
102 * Sets the value of the field.
103 *
104 * @throws \InvalidArgumentException When value type provided is not correct
105 */
106 public function setValue(string|array|bool|null $value): void
107 {
108 if ('checkbox' === $this->type && false === $value) {
109 // uncheck
110 $this->value = null;
111 } elseif ('checkbox' === $this->type && true === $value) {
112 // check
113 $this->value = $this->options[0]['value'];
114 } else {
115 if (\is_array($value)) {
116 if (!$this->multiple) {
117 throw new \InvalidArgumentException(\sprintf('The value for "%s" cannot be an array.', $this->name));
118 }
119
120 foreach ($value as $v) {
121 if (!$this->containsOption($v, $this->options)) {
122 throw new \InvalidArgumentException(\sprintf('Input "%s" cannot take "%s" as a value (possible values: "%s").', $this->name, $v, implode('", "', $this->availableOptionValues())));
123 }
124 }
125 } elseif (!$this->containsOption($value, $this->options)) {
126 throw new \InvalidArgumentException(\sprintf('Input "%s" cannot take "%s" as a value (possible values: "%s").', $this->name, $value, implode('", "', $this->availableOptionValues())));
127 }
128
129 if ($this->multiple) {
130 $value = (array) $value;
131 }
132
133 if (\is_array($value)) {
134 $this->value = $value;
135 } else {
136 parent::setValue($value);
137 }
138 }
139 }
140
141 /**
142 * Adds a choice to the current ones.
143 *
144 * @throws \LogicException When choice provided is not multiple nor radio
145 *
146 * @internal
147 */
148 public function addChoice(\DOMElement $node): void
149 {
150 if (!$this->multiple && 'radio' !== $this->type) {
151 throw new \LogicException(\sprintf('Unable to add a choice for "%s" as it is not multiple or is not a radio button.', $this->name));
152 }
153
154 $option = $this->buildOptionValue($node);
155 $this->options[] = $option;
156
157 if ($node->hasAttribute('checked')) {
158 $this->value = $option['value'];
159 }
160 }
161
162 /**
163 * Returns the type of the choice field (radio, select, or checkbox).
164 */
165 public function getType(): string
166 {
167 return $this->type;
168 }
169
170 /**
171 * Returns true if the field accepts multiple values.
172 */
173 public function isMultiple(): bool
174 {
175 return $this->multiple;
176 }
177
178 /**
179 * Initializes the form field.
180 *
181 * @throws \LogicException When node type is incorrect
182 */
183 protected function initialize(): void
184 {
185 if ('input' !== $this->node->nodeName && 'select' !== $this->node->nodeName) {
186 throw new \LogicException(\sprintf('A ChoiceFormField can only be created from an input or select tag (%s given).', $this->node->nodeName));
187 }
188
189 if ('input' === $this->node->nodeName && 'checkbox' !== strtolower($this->node->getAttribute('type')) && 'radio' !== strtolower($this->node->getAttribute('type'))) {
190 throw new \LogicException(\sprintf('A ChoiceFormField can only be created from an input tag with a type of checkbox or radio (given type is "%s").', $this->node->getAttribute('type')));
191 }
192
193 $this->value = null;
194 $this->options = [];
195 $this->multiple = false;
196
197 if ('input' == $this->node->nodeName) {
198 $this->type = strtolower($this->node->getAttribute('type'));
199 $optionValue = $this->buildOptionValue($this->node);
200 $this->options[] = $optionValue;
201
202 if ($this->node->hasAttribute('checked')) {
203 $this->value = $optionValue['value'];
204 }
205 } else {
206 $this->type = 'select';
207 if ($this->node->hasAttribute('multiple')) {
208 $this->multiple = true;
209 $this->value = [];
210 $this->name = str_replace('[]', '', $this->name);
211 }
212
213 $found = false;
214 foreach ($this->xpath->query('descendant::option', $this->node) as $option) {
215 $optionValue = $this->buildOptionValue($option);
216 $this->options[] = $optionValue;
217
218 if ($option->hasAttribute('selected')) {
219 $found = true;
220 if ($this->multiple) {
221 $this->value[] = $optionValue['value'];
222 } else {
223 $this->value = $optionValue['value'];
224 }
225 }
226 }
227
228 // if no option is selected and if it is a simple select box, take the first option as the value
229 if (!$found && !$this->multiple && $this->options) {
230 $this->value = $this->options[0]['value'];
231 }
232 }
233 }
234
235 /**
236 * Returns option value with associated disabled flag.
237 */
238 private function buildOptionValue(\DOMElement $node): array
239 {
240 $option = [];
241
242 $defaultDefaultValue = 'select' === $this->node->nodeName ? '' : 'on';
243 $defaultValue = (isset($node->nodeValue) && $node->nodeValue) ? $node->nodeValue : $defaultDefaultValue;
244 $option['value'] = $node->hasAttribute('value') ? $node->getAttribute('value') : $defaultValue;
245 $option['disabled'] = $node->hasAttribute('disabled');
246
247 return $option;
248 }
249
250 /**
251 * Checks whether given value is in the existing options.
252 *
253 * @internal
254 */
255 public function containsOption(string $optionValue, array $options): bool
256 {
257 if ($this->validationDisabled) {
258 return true;
259 }
260
261 foreach ($options as $option) {
262 if ($option['value'] == $optionValue) {
263 return true;
264 }
265 }
266
267 return false;
268 }
269
270 /**
271 * Returns list of available field options.
272 *
273 * @internal
274 */
275 public function availableOptionValues(): array
276 {
277 $values = [];
278
279 foreach ($this->options as $option) {
280 $values[] = $option['value'];
281 }
282
283 return $values;
284 }
285
286 /**
287 * Disables the internal validation of the field.
288 *
289 * @internal
290 *
291 * @return $this
292 */
293 public function disableValidation(): static
294 {
295 $this->validationDisabled = true;
296
297 return $this;
298 }
299}