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