PluginProbe
BeyondWords – AI audio for publishers / 4.0.4
BeyondWords – AI audio for publishers v4.0.4
7.2.0 7.1.0 trunk 4.0.0 4.0.1 4.0.2 4.0.3 4.0.4 4.0.5 4.0.6 4.1.0 4.1.1 4.1.2 4.2.0 4.2.1 4.2.2 4.2.3 4.2.4 4.3.0 4.4.0 4.5.0 4.5.1 4.6.0 4.6.1 4.6.2 All 44 releases
speechkit / vendor / symfony / dom-crawler / Form.php

Form.php in BeyondWords – AI audio for publishers 4.0.4, at vendor/symfony/dom-crawler/Form.php

502 lines 15.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <[email protected]>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12 namespace Symfony\Component\DomCrawler;
13
14 use Symfony\Component\DomCrawler\Field\ChoiceFormField;
15 use Symfony\Component\DomCrawler\Field\FormField;
16
17 /**
18 * Form represents an HTML form.
19 *
20 * @author Fabien Potencier <[email protected]>
21 */
22 class Form extends Link implements \ArrayAccess
23 {
24 /**
25 * @var \DOMElement
26 */
27 private $button;
28
29 /**
30 * @var FormFieldRegistry
31 */
32 private $fields;
33
34 /**
35 * @var string
36 */
37 private $baseHref;
38
39 /**
40 * @param \DOMElement $node A \DOMElement instance
41 * @param string|null $currentUri The URI of the page where the form is embedded
42 * @param string|null $method The method to use for the link (if null, it defaults to the method defined by the form)
43 * @param string|null $baseHref The URI of the <base> used for relative links, but not for empty action
44 *
45 * @throws \LogicException if the node is not a button inside a form tag
46 */
47 public function __construct(\DOMElement $node, string $currentUri = null, string $method = null, string $baseHref = null)
48 {
49 parent::__construct($node, $currentUri, $method);
50 $this->baseHref = $baseHref;
51
52 $this->initialize();
53 }
54
55 /**
56 * Gets the form node associated with this form.
57 *
58 * @return \DOMElement
59 */
60 public function getFormNode()
61 {
62 return $this->node;
63 }
64
65 /**
66 * Sets the value of the fields.
67 *
68 * @param array $values An array of field values
69 *
70 * @return $this
71 */
72 public function setValues(array $values)
73 {
74 foreach ($values as $name => $value) {
75 $this->fields->set($name, $value);
76 }
77
78 return $this;
79 }
80
81 /**
82 * Gets the field values.
83 *
84 * The returned array does not include file fields (@see getFiles).
85 *
86 * @return array
87 */
88 public function getValues()
89 {
90 $values = [];
91 foreach ($this->fields->all() as $name => $field) {
92 if ($field->isDisabled()) {
93 continue;
94 }
95
96 if (!$field instanceof Field\FileFormField && $field->hasValue()) {
97 $values[$name] = $field->getValue();
98 }
99 }
100
101 return $values;
102 }
103
104 /**
105 * Gets the file field values.
106 *
107 * @return array
108 */
109 public function getFiles()
110 {
111 if (!\in_array($this->getMethod(), ['POST', 'PUT', 'DELETE', 'PATCH'])) {
112 return [];
113 }
114
115 $files = [];
116
117 foreach ($this->fields->all() as $name => $field) {
118 if ($field->isDisabled()) {
119 continue;
120 }
121
122 if ($field instanceof Field\FileFormField) {
123 $files[$name] = $field->getValue();
124 }
125 }
126
127 return $files;
128 }
129
130 /**
131 * Gets the field values as PHP.
132 *
133 * This method converts fields with the array notation
134 * (like foo[bar] to arrays) like PHP does.
135 *
136 * @return array
137 */
138 public function getPhpValues()
139 {
140 $values = [];
141 foreach ($this->getValues() as $name => $value) {
142 $qs = http_build_query([$name => $value], '', '&');
143 if (!empty($qs)) {
144 parse_str($qs, $expandedValue);
145 $varName = substr($name, 0, \strlen(key($expandedValue)));
146 $values[] = [$varName => current($expandedValue)];
147 }
148 }
149
150 return array_replace_recursive([], ...$values);
151 }
152
153 /**
154 * Gets the file field values as PHP.
155 *
156 * This method converts fields with the array notation
157 * (like foo[bar] to arrays) like PHP does.
158 * The returned array is consistent with the array for field values
159 * (@see getPhpValues), rather than uploaded files found in $_FILES.
160 * For a compound file field foo[bar] it will create foo[bar][name],
161 * instead of foo[name][bar] which would be found in $_FILES.
162 *
163 * @return array
164 */
165 public function getPhpFiles()
166 {
167 $values = [];
168 foreach ($this->getFiles() as $name => $value) {
169 $qs = http_build_query([$name => $value], '', '&');
170 if (!empty($qs)) {
171 parse_str($qs, $expandedValue);
172 $varName = substr($name, 0, \strlen(key($expandedValue)));
173
174 array_walk_recursive(
175 $expandedValue,
176 function (&$value, $key) {
177 if (ctype_digit($value) && ('size' === $key || 'error' === $key)) {
178 $value = (int) $value;
179 }
180 }
181 );
182
183 reset($expandedValue);
184
185 $values[] = [$varName => current($expandedValue)];
186 }
187 }
188
189 return array_replace_recursive([], ...$values);
190 }
191
192 /**
193 * Gets the URI of the form.
194 *
195 * The returned URI is not the same as the form "action" attribute.
196 * This method merges the value if the method is GET to mimics
197 * browser behavior.
198 *
199 * @return string
200 */
201 public function getUri()
202 {
203 $uri = parent::getUri();
204
205 if (!\in_array($this->getMethod(), ['POST', 'PUT', 'DELETE', 'PATCH'])) {
206 $query = parse_url($uri, \PHP_URL_QUERY);
207 $currentParameters = [];
208 if ($query) {
209 parse_str($query, $currentParameters);
210 }
211
212 $queryString = http_build_query(array_merge($currentParameters, $this->getValues()), '', '&');
213
214 $pos = strpos($uri, '?');
215 $base = false === $pos ? $uri : substr($uri, 0, $pos);
216 $uri = rtrim($base.'?'.$queryString, '?');
217 }
218
219 return $uri;
220 }
221
222 protected function getRawUri()
223 {
224 // If the form was created from a button rather than the form node, check for HTML5 action overrides
225 if ($this->button !== $this->node && $this->button->getAttribute('formaction')) {
226 return $this->button->getAttribute('formaction');
227 }
228
229 return $this->node->getAttribute('action');
230 }
231
232 /**
233 * Gets the form method.
234 *
235 * If no method is defined in the form, GET is returned.
236 *
237 * @return string
238 */
239 public function getMethod()
240 {
241 if (null !== $this->method) {
242 return $this->method;
243 }
244
245 // If the form was created from a button rather than the form node, check for HTML5 method override
246 if ($this->button !== $this->node && $this->button->getAttribute('formmethod')) {
247 return strtoupper($this->button->getAttribute('formmethod'));
248 }
249
250 return $this->node->getAttribute('method') ? strtoupper($this->node->getAttribute('method')) : 'GET';
251 }
252
253 /**
254 * Gets the form name.
255 *
256 * If no name is defined on the form, an empty string is returned.
257 */
258 public function getName(): string
259 {
260 return $this->node->getAttribute('name');
261 }
262
263 /**
264 * Returns true if the named field exists.
265 *
266 * @return bool
267 */
268 public function has(string $name)
269 {
270 return $this->fields->has($name);
271 }
272
273 /**
274 * Removes a field from the form.
275 */
276 public function remove(string $name)
277 {
278 $this->fields->remove($name);
279 }
280
281 /**
282 * Gets a named field.
283 *
284 * @return FormField|FormField[]|FormField[][]
285 *
286 * @throws \InvalidArgumentException When field is not present in this form
287 */
288 public function get(string $name)
289 {
290 return $this->fields->get($name);
291 }
292
293 /**
294 * Sets a named field.
295 */
296 public function set(FormField $field)
297 {
298 $this->fields->add($field);
299 }
300
301 /**
302 * Gets all fields.
303 *
304 * @return FormField[]
305 */
306 public function all()
307 {
308 return $this->fields->all();
309 }
310
311 /**
312 * Returns true if the named field exists.
313 *
314 * @param string $name The field name
315 *
316 * @return bool
317 */
318 #[\ReturnTypeWillChange]
319 public function offsetExists($name)
320 {
321 return $this->has($name);
322 }
323
324 /**
325 * Gets the value of a field.
326 *
327 * @param string $name The field name
328 *
329 * @return FormField|FormField[]|FormField[][]
330 *
331 * @throws \InvalidArgumentException if the field does not exist
332 */
333 #[\ReturnTypeWillChange]
334 public function offsetGet($name)
335 {
336 return $this->fields->get($name);
337 }
338
339 /**
340 * Sets the value of a field.
341 *
342 * @param string $name The field name
343 * @param string|array $value The value of the field
344 *
345 * @return void
346 *
347 * @throws \InvalidArgumentException if the field does not exist
348 */
349 #[\ReturnTypeWillChange]
350 public function offsetSet($name, $value)
351 {
352 $this->fields->set($name, $value);
353 }
354
355 /**
356 * Removes a field from the form.
357 *
358 * @param string $name The field name
359 *
360 * @return void
361 */
362 #[\ReturnTypeWillChange]
363 public function offsetUnset($name)
364 {
365 $this->fields->remove($name);
366 }
367
368 /**
369 * Disables validation.
370 *
371 * @return $this
372 */
373 public function disableValidation()
374 {
375 foreach ($this->fields->all() as $field) {
376 if ($field instanceof Field\ChoiceFormField) {
377 $field->disableValidation();
378 }
379 }
380
381 return $this;
382 }
383
384 /**
385 * Sets the node for the form.
386 *
387 * Expects a 'submit' button \DOMElement and finds the corresponding form element, or the form element itself.
388 *
389 * @throws \LogicException If given node is not a button or input or does not have a form ancestor
390 */
391 protected function setNode(\DOMElement $node)
392 {
393 $this->button = $node;
394 if ('button' === $node->nodeName || ('input' === $node->nodeName && \in_array(strtolower($node->getAttribute('type')), ['submit', 'button', 'image']))) {
395 if ($node->hasAttribute('form')) {
396 // if the node has the HTML5-compliant 'form' attribute, use it
397 $formId = $node->getAttribute('form');
398 $form = $node->ownerDocument->getElementById($formId);
399 if (null === $form) {
400 throw new \LogicException(sprintf('The selected node has an invalid form attribute (%s).', $formId));
401 }
402 $this->node = $form;
403
404 return;
405 }
406 // we loop until we find a form ancestor
407 do {
408 if (null === $node = $node->parentNode) {
409 throw new \LogicException('The selected node does not have a form ancestor.');
410 }
411 } while ('form' !== $node->nodeName);
412 } elseif ('form' !== $node->nodeName) {
413 throw new \LogicException(sprintf('Unable to submit on a "%s" tag.', $node->nodeName));
414 }
415
416 $this->node = $node;
417 }
418
419 /**
420 * Adds form elements related to this form.
421 *
422 * Creates an internal copy of the submitted 'button' element and
423 * the form node or the entire document depending on whether we need
424 * to find non-descendant elements through HTML5 'form' attribute.
425 */
426 private function initialize()
427 {
428 $this->fields = new FormFieldRegistry();
429
430 $xpath = new \DOMXPath($this->node->ownerDocument);
431
432 // add submitted button if it has a valid name
433 if ('form' !== $this->button->nodeName && $this->button->hasAttribute('name') && $this->button->getAttribute('name')) {
434 if ('input' == $this->button->nodeName && 'image' == strtolower($this->button->getAttribute('type'))) {
435 $name = $this->button->getAttribute('name');
436 $this->button->setAttribute('value', '0');
437
438 // temporarily change the name of the input node for the x coordinate
439 $this->button->setAttribute('name', $name.'.x');
440 $this->set(new Field\InputFormField($this->button));
441
442 // temporarily change the name of the input node for the y coordinate
443 $this->button->setAttribute('name', $name.'.y');
444 $this->set(new Field\InputFormField($this->button));
445
446 // restore the original name of the input node
447 $this->button->setAttribute('name', $name);
448 } else {
449 $this->set(new Field\InputFormField($this->button));
450 }
451 }
452
453 // find form elements corresponding to the current form
454 if ($this->node->hasAttribute('id')) {
455 // corresponding elements are either descendants or have a matching HTML5 form attribute
456 $formId = Crawler::xpathLiteral($this->node->getAttribute('id'));
457
458 $fieldNodes = $xpath->query(sprintf('( descendant::input[@form=%s] | descendant::button[@form=%1$s] | descendant::textarea[@form=%1$s] | descendant::select[@form=%1$s] | //form[@id=%1$s]//input[not(@form)] | //form[@id=%1$s]//button[not(@form)] | //form[@id=%1$s]//textarea[not(@form)] | //form[@id=%1$s]//select[not(@form)] )[not(ancestor::template)]', $formId));
459 foreach ($fieldNodes as $node) {
460 $this->addField($node);
461 }
462 } else {
463 // do the xpath query with $this->node as the context node, to only find descendant elements
464 // however, descendant elements with form attribute are not part of this form
465 $fieldNodes = $xpath->query('( descendant::input[not(@form)] | descendant::button[not(@form)] | descendant::textarea[not(@form)] | descendant::select[not(@form)] )[not(ancestor::template)]', $this->node);
466 foreach ($fieldNodes as $node) {
467 $this->addField($node);
468 }
469 }
470
471 if ($this->baseHref && '' !== $this->node->getAttribute('action')) {
472 $this->currentUri = $this->baseHref;
473 }
474 }
475
476 private function addField(\DOMElement $node)
477 {
478 if (!$node->hasAttribute('name') || !$node->getAttribute('name')) {
479 return;
480 }
481
482 $nodeName = $node->nodeName;
483 if ('select' == $nodeName || 'input' == $nodeName && 'checkbox' == strtolower($node->getAttribute('type'))) {
484 $this->set(new Field\ChoiceFormField($node));
485 } elseif ('input' == $nodeName && 'radio' == strtolower($node->getAttribute('type'))) {
486 // there may be other fields with the same name that are no choice
487 // fields already registered (see https://github.com/symfony/symfony/issues/11689)
488 if ($this->has($node->getAttribute('name')) && $this->get($node->getAttribute('name')) instanceof ChoiceFormField) {
489 $this->get($node->getAttribute('name'))->addChoice($node);
490 } else {
491 $this->set(new Field\ChoiceFormField($node));
492 }
493 } elseif ('input' == $nodeName && 'file' == strtolower($node->getAttribute('type'))) {
494 $this->set(new Field\FileFormField($node));
495 } elseif ('input' == $nodeName && !\in_array(strtolower($node->getAttribute('type')), ['submit', 'button', 'image'])) {
496 $this->set(new Field\InputFormField($node));
497 } elseif ('textarea' == $nodeName) {
498 $this->set(new Field\TextareaFormField($node));
499 }
500 }
501 }
502