PluginProbe
BeyondWords – AI audio for publishers / 7.2.0
BeyondWords – AI audio for publishers v7.2.0
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 7.2.0, at vendor/symfony/dom-crawler/Form.php

501 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 $currentParameters = [];
207 if ($query = parse_url($uri, \PHP_URL_QUERY)) {
208 parse_str($query, $currentParameters);
209 }
210
211 $queryString = http_build_query(array_merge($currentParameters, $this->getValues()), '', '&');
212
213 $pos = strpos($uri, '?');
214 $base = false === $pos ? $uri : substr($uri, 0, $pos);
215 $uri = rtrim($base.'?'.$queryString, '?');
216 }
217
218 return $uri;
219 }
220
221 protected function getRawUri()
222 {
223 // If the form was created from a button rather than the form node, check for HTML5 action overrides
224 if ($this->button !== $this->node && $this->button->getAttribute('formaction')) {
225 return $this->button->getAttribute('formaction');
226 }
227
228 return $this->node->getAttribute('action');
229 }
230
231 /**
232 * Gets the form method.
233 *
234 * If no method is defined in the form, GET is returned.
235 *
236 * @return string
237 */
238 public function getMethod()
239 {
240 if (null !== $this->method) {
241 return $this->method;
242 }
243
244 // If the form was created from a button rather than the form node, check for HTML5 method override
245 if ($this->button !== $this->node && $this->button->getAttribute('formmethod')) {
246 return strtoupper($this->button->getAttribute('formmethod'));
247 }
248
249 return $this->node->getAttribute('method') ? strtoupper($this->node->getAttribute('method')) : 'GET';
250 }
251
252 /**
253 * Gets the form name.
254 *
255 * If no name is defined on the form, an empty string is returned.
256 */
257 public function getName(): string
258 {
259 return $this->node->getAttribute('name');
260 }
261
262 /**
263 * Returns true if the named field exists.
264 *
265 * @return bool
266 */
267 public function has(string $name)
268 {
269 return $this->fields->has($name);
270 }
271
272 /**
273 * Removes a field from the form.
274 */
275 public function remove(string $name)
276 {
277 $this->fields->remove($name);
278 }
279
280 /**
281 * Gets a named field.
282 *
283 * @return FormField|FormField[]|FormField[][]
284 *
285 * @throws \InvalidArgumentException When field is not present in this form
286 */
287 public function get(string $name)
288 {
289 return $this->fields->get($name);
290 }
291
292 /**
293 * Sets a named field.
294 */
295 public function set(FormField $field)
296 {
297 $this->fields->add($field);
298 }
299
300 /**
301 * Gets all fields.
302 *
303 * @return FormField[]
304 */
305 public function all()
306 {
307 return $this->fields->all();
308 }
309
310 /**
311 * Returns true if the named field exists.
312 *
313 * @param string $name The field name
314 *
315 * @return bool
316 */
317 #[\ReturnTypeWillChange]
318 public function offsetExists($name)
319 {
320 return $this->has($name);
321 }
322
323 /**
324 * Gets the value of a field.
325 *
326 * @param string $name The field name
327 *
328 * @return FormField|FormField[]|FormField[][]
329 *
330 * @throws \InvalidArgumentException if the field does not exist
331 */
332 #[\ReturnTypeWillChange]
333 public function offsetGet($name)
334 {
335 return $this->fields->get($name);
336 }
337
338 /**
339 * Sets the value of a field.
340 *
341 * @param string $name The field name
342 * @param string|array $value The value of the field
343 *
344 * @return void
345 *
346 * @throws \InvalidArgumentException if the field does not exist
347 */
348 #[\ReturnTypeWillChange]
349 public function offsetSet($name, $value)
350 {
351 $this->fields->set($name, $value);
352 }
353
354 /**
355 * Removes a field from the form.
356 *
357 * @param string $name The field name
358 *
359 * @return void
360 */
361 #[\ReturnTypeWillChange]
362 public function offsetUnset($name)
363 {
364 $this->fields->remove($name);
365 }
366
367 /**
368 * Disables validation.
369 *
370 * @return $this
371 */
372 public function disableValidation()
373 {
374 foreach ($this->fields->all() as $field) {
375 if ($field instanceof Field\ChoiceFormField) {
376 $field->disableValidation();
377 }
378 }
379
380 return $this;
381 }
382
383 /**
384 * Sets the node for the form.
385 *
386 * Expects a 'submit' button \DOMElement and finds the corresponding form element, or the form element itself.
387 *
388 * @throws \LogicException If given node is not a button or input or does not have a form ancestor
389 */
390 protected function setNode(\DOMElement $node)
391 {
392 $this->button = $node;
393 if ('button' === $node->nodeName || ('input' === $node->nodeName && \in_array(strtolower($node->getAttribute('type')), ['submit', 'button', 'image']))) {
394 if ($node->hasAttribute('form')) {
395 // if the node has the HTML5-compliant 'form' attribute, use it
396 $formId = $node->getAttribute('form');
397 $form = $node->ownerDocument->getElementById($formId);
398 if (null === $form) {
399 throw new \LogicException(sprintf('The selected node has an invalid form attribute (%s).', $formId));
400 }
401 $this->node = $form;
402
403 return;
404 }
405 // we loop until we find a form ancestor
406 do {
407 if (null === $node = $node->parentNode) {
408 throw new \LogicException('The selected node does not have a form ancestor.');
409 }
410 } while ('form' !== $node->nodeName);
411 } elseif ('form' !== $node->nodeName) {
412 throw new \LogicException(sprintf('Unable to submit on a "%s" tag.', $node->nodeName));
413 }
414
415 $this->node = $node;
416 }
417
418 /**
419 * Adds form elements related to this form.
420 *
421 * Creates an internal copy of the submitted 'button' element and
422 * the form node or the entire document depending on whether we need
423 * to find non-descendant elements through HTML5 'form' attribute.
424 */
425 private function initialize()
426 {
427 $this->fields = new FormFieldRegistry();
428
429 $xpath = new \DOMXPath($this->node->ownerDocument);
430
431 // add submitted button if it has a valid name
432 if ('form' !== $this->button->nodeName && $this->button->hasAttribute('name') && $this->button->getAttribute('name')) {
433 if ('input' == $this->button->nodeName && 'image' == strtolower($this->button->getAttribute('type'))) {
434 $name = $this->button->getAttribute('name');
435 $this->button->setAttribute('value', '0');
436
437 // temporarily change the name of the input node for the x coordinate
438 $this->button->setAttribute('name', $name.'.x');
439 $this->set(new Field\InputFormField($this->button));
440
441 // temporarily change the name of the input node for the y coordinate
442 $this->button->setAttribute('name', $name.'.y');
443 $this->set(new Field\InputFormField($this->button));
444
445 // restore the original name of the input node
446 $this->button->setAttribute('name', $name);
447 } else {
448 $this->set(new Field\InputFormField($this->button));
449 }
450 }
451
452 // find form elements corresponding to the current form
453 if ($this->node->hasAttribute('id')) {
454 // corresponding elements are either descendants or have a matching HTML5 form attribute
455 $formId = Crawler::xpathLiteral($this->node->getAttribute('id'));
456
457 $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));
458 foreach ($fieldNodes as $node) {
459 $this->addField($node);
460 }
461 } else {
462 // do the xpath query with $this->node as the context node, to only find descendant elements
463 // however, descendant elements with form attribute are not part of this form
464 $fieldNodes = $xpath->query('( descendant::input[not(@form)] | descendant::button[not(@form)] | descendant::textarea[not(@form)] | descendant::select[not(@form)] )[not(ancestor::template)]', $this->node);
465 foreach ($fieldNodes as $node) {
466 $this->addField($node);
467 }
468 }
469
470 if ($this->baseHref && '' !== $this->node->getAttribute('action')) {
471 $this->currentUri = $this->baseHref;
472 }
473 }
474
475 private function addField(\DOMElement $node)
476 {
477 if (!$node->hasAttribute('name') || !$node->getAttribute('name')) {
478 return;
479 }
480
481 $nodeName = $node->nodeName;
482 if ('select' == $nodeName || 'input' == $nodeName && 'checkbox' == strtolower($node->getAttribute('type'))) {
483 $this->set(new Field\ChoiceFormField($node));
484 } elseif ('input' == $nodeName && 'radio' == strtolower($node->getAttribute('type'))) {
485 // there may be other fields with the same name that are no choice
486 // fields already registered (see https://github.com/symfony/symfony/issues/11689)
487 if ($this->has($node->getAttribute('name')) && $this->get($node->getAttribute('name')) instanceof ChoiceFormField) {
488 $this->get($node->getAttribute('name'))->addChoice($node);
489 } else {
490 $this->set(new Field\ChoiceFormField($node));
491 }
492 } elseif ('input' == $nodeName && 'file' == strtolower($node->getAttribute('type'))) {
493 $this->set(new Field\FileFormField($node));
494 } elseif ('input' == $nodeName && !\in_array(strtolower($node->getAttribute('type')), ['submit', 'button', 'image'])) {
495 $this->set(new Field\InputFormField($node));
496 } elseif ('textarea' == $nodeName) {
497 $this->set(new Field\TextareaFormField($node));
498 }
499 }
500 }
501