Mercurial > hg > isophonics-drupal-site
comparison vendor/behat/mink-browserkit-driver/src/BrowserKitDriver.php @ 0:4c8ae668cc8c
Initial import (non-working)
author | Chris Cannam |
---|---|
date | Wed, 29 Nov 2017 16:09:58 +0000 |
parents | |
children | c2387f117808 |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:4c8ae668cc8c |
---|---|
1 <?php | |
2 | |
3 /* | |
4 * This file is part of the Behat\Mink. | |
5 * (c) Konstantin Kudryashov <ever.zet@gmail.com> | |
6 * | |
7 * For the full copyright and license information, please view the LICENSE | |
8 * file that was distributed with this source code. | |
9 */ | |
10 | |
11 namespace Behat\Mink\Driver; | |
12 | |
13 use Behat\Mink\Exception\DriverException; | |
14 use Behat\Mink\Exception\UnsupportedDriverActionException; | |
15 use Symfony\Component\BrowserKit\Client; | |
16 use Symfony\Component\BrowserKit\Cookie; | |
17 use Symfony\Component\BrowserKit\Response; | |
18 use Symfony\Component\DomCrawler\Crawler; | |
19 use Symfony\Component\DomCrawler\Field\ChoiceFormField; | |
20 use Symfony\Component\DomCrawler\Field\FileFormField; | |
21 use Symfony\Component\DomCrawler\Field\FormField; | |
22 use Symfony\Component\DomCrawler\Field\InputFormField; | |
23 use Symfony\Component\DomCrawler\Field\TextareaFormField; | |
24 use Symfony\Component\DomCrawler\Form; | |
25 use Symfony\Component\HttpKernel\Client as HttpKernelClient; | |
26 | |
27 /** | |
28 * Symfony2 BrowserKit driver. | |
29 * | |
30 * @author Konstantin Kudryashov <ever.zet@gmail.com> | |
31 */ | |
32 class BrowserKitDriver extends CoreDriver | |
33 { | |
34 private $client; | |
35 | |
36 /** | |
37 * @var Form[] | |
38 */ | |
39 private $forms = array(); | |
40 private $serverParameters = array(); | |
41 private $started = false; | |
42 private $removeScriptFromUrl = false; | |
43 private $removeHostFromUrl = false; | |
44 | |
45 /** | |
46 * Initializes BrowserKit driver. | |
47 * | |
48 * @param Client $client BrowserKit client instance | |
49 * @param string|null $baseUrl Base URL for HttpKernel clients | |
50 */ | |
51 public function __construct(Client $client, $baseUrl = null) | |
52 { | |
53 $this->client = $client; | |
54 $this->client->followRedirects(true); | |
55 | |
56 if ($baseUrl !== null && $client instanceof HttpKernelClient) { | |
57 $client->setServerParameter('SCRIPT_FILENAME', parse_url($baseUrl, PHP_URL_PATH)); | |
58 } | |
59 } | |
60 | |
61 /** | |
62 * Returns BrowserKit HTTP client instance. | |
63 * | |
64 * @return Client | |
65 */ | |
66 public function getClient() | |
67 { | |
68 return $this->client; | |
69 } | |
70 | |
71 /** | |
72 * Tells driver to remove hostname from URL. | |
73 * | |
74 * @param Boolean $remove | |
75 * | |
76 * @deprecated Deprecated as of 1.2, to be removed in 2.0. Pass the base url in the constructor instead. | |
77 */ | |
78 public function setRemoveHostFromUrl($remove = true) | |
79 { | |
80 trigger_error( | |
81 'setRemoveHostFromUrl() is deprecated as of 1.2 and will be removed in 2.0. Pass the base url in the constructor instead.', | |
82 E_USER_DEPRECATED | |
83 ); | |
84 $this->removeHostFromUrl = (bool) $remove; | |
85 } | |
86 | |
87 /** | |
88 * Tells driver to remove script name from URL. | |
89 * | |
90 * @param Boolean $remove | |
91 * | |
92 * @deprecated Deprecated as of 1.2, to be removed in 2.0. Pass the base url in the constructor instead. | |
93 */ | |
94 public function setRemoveScriptFromUrl($remove = true) | |
95 { | |
96 trigger_error( | |
97 'setRemoveScriptFromUrl() is deprecated as of 1.2 and will be removed in 2.0. Pass the base url in the constructor instead.', | |
98 E_USER_DEPRECATED | |
99 ); | |
100 $this->removeScriptFromUrl = (bool) $remove; | |
101 } | |
102 | |
103 /** | |
104 * {@inheritdoc} | |
105 */ | |
106 public function start() | |
107 { | |
108 $this->started = true; | |
109 } | |
110 | |
111 /** | |
112 * {@inheritdoc} | |
113 */ | |
114 public function isStarted() | |
115 { | |
116 return $this->started; | |
117 } | |
118 | |
119 /** | |
120 * {@inheritdoc} | |
121 */ | |
122 public function stop() | |
123 { | |
124 $this->reset(); | |
125 $this->started = false; | |
126 } | |
127 | |
128 /** | |
129 * {@inheritdoc} | |
130 */ | |
131 public function reset() | |
132 { | |
133 // Restarting the client resets the cookies and the history | |
134 $this->client->restart(); | |
135 $this->forms = array(); | |
136 $this->serverParameters = array(); | |
137 } | |
138 | |
139 /** | |
140 * {@inheritdoc} | |
141 */ | |
142 public function visit($url) | |
143 { | |
144 $this->client->request('GET', $this->prepareUrl($url), array(), array(), $this->serverParameters); | |
145 $this->forms = array(); | |
146 } | |
147 | |
148 /** | |
149 * {@inheritdoc} | |
150 */ | |
151 public function getCurrentUrl() | |
152 { | |
153 $request = $this->client->getInternalRequest(); | |
154 | |
155 if ($request === null) { | |
156 throw new DriverException('Unable to access the request before visiting a page'); | |
157 } | |
158 | |
159 return $request->getUri(); | |
160 } | |
161 | |
162 /** | |
163 * {@inheritdoc} | |
164 */ | |
165 public function reload() | |
166 { | |
167 $this->client->reload(); | |
168 $this->forms = array(); | |
169 } | |
170 | |
171 /** | |
172 * {@inheritdoc} | |
173 */ | |
174 public function forward() | |
175 { | |
176 $this->client->forward(); | |
177 $this->forms = array(); | |
178 } | |
179 | |
180 /** | |
181 * {@inheritdoc} | |
182 */ | |
183 public function back() | |
184 { | |
185 $this->client->back(); | |
186 $this->forms = array(); | |
187 } | |
188 | |
189 /** | |
190 * {@inheritdoc} | |
191 */ | |
192 public function setBasicAuth($user, $password) | |
193 { | |
194 if (false === $user) { | |
195 unset($this->serverParameters['PHP_AUTH_USER'], $this->serverParameters['PHP_AUTH_PW']); | |
196 | |
197 return; | |
198 } | |
199 | |
200 $this->serverParameters['PHP_AUTH_USER'] = $user; | |
201 $this->serverParameters['PHP_AUTH_PW'] = $password; | |
202 } | |
203 | |
204 /** | |
205 * {@inheritdoc} | |
206 */ | |
207 public function setRequestHeader($name, $value) | |
208 { | |
209 $contentHeaders = array('CONTENT_LENGTH' => true, 'CONTENT_MD5' => true, 'CONTENT_TYPE' => true); | |
210 $name = str_replace('-', '_', strtoupper($name)); | |
211 | |
212 // CONTENT_* are not prefixed with HTTP_ in PHP when building $_SERVER | |
213 if (!isset($contentHeaders[$name])) { | |
214 $name = 'HTTP_' . $name; | |
215 } | |
216 | |
217 $this->serverParameters[$name] = $value; | |
218 } | |
219 | |
220 /** | |
221 * {@inheritdoc} | |
222 */ | |
223 public function getResponseHeaders() | |
224 { | |
225 return $this->getResponse()->getHeaders(); | |
226 } | |
227 | |
228 /** | |
229 * {@inheritdoc} | |
230 */ | |
231 public function setCookie($name, $value = null) | |
232 { | |
233 if (null === $value) { | |
234 $this->deleteCookie($name); | |
235 | |
236 return; | |
237 } | |
238 | |
239 $jar = $this->client->getCookieJar(); | |
240 $jar->set(new Cookie($name, $value)); | |
241 } | |
242 | |
243 /** | |
244 * Deletes a cookie by name. | |
245 * | |
246 * @param string $name Cookie name. | |
247 */ | |
248 private function deleteCookie($name) | |
249 { | |
250 $path = $this->getCookiePath(); | |
251 $jar = $this->client->getCookieJar(); | |
252 | |
253 do { | |
254 if (null !== $jar->get($name, $path)) { | |
255 $jar->expire($name, $path); | |
256 } | |
257 | |
258 $path = preg_replace('/.$/', '', $path); | |
259 } while ($path); | |
260 } | |
261 | |
262 /** | |
263 * Returns current cookie path. | |
264 * | |
265 * @return string | |
266 */ | |
267 private function getCookiePath() | |
268 { | |
269 $path = dirname(parse_url($this->getCurrentUrl(), PHP_URL_PATH)); | |
270 | |
271 if ('\\' === DIRECTORY_SEPARATOR) { | |
272 $path = str_replace('\\', '/', $path); | |
273 } | |
274 | |
275 return $path; | |
276 } | |
277 | |
278 /** | |
279 * {@inheritdoc} | |
280 */ | |
281 public function getCookie($name) | |
282 { | |
283 // Note that the following doesn't work well because | |
284 // Symfony\Component\BrowserKit\CookieJar stores cookies by name, | |
285 // path, AND domain and if you don't fill them all in correctly then | |
286 // you won't get the value that you're expecting. | |
287 // | |
288 // $jar = $this->client->getCookieJar(); | |
289 // | |
290 // if (null !== $cookie = $jar->get($name)) { | |
291 // return $cookie->getValue(); | |
292 // } | |
293 | |
294 $allValues = $this->client->getCookieJar()->allValues($this->getCurrentUrl()); | |
295 | |
296 if (isset($allValues[$name])) { | |
297 return $allValues[$name]; | |
298 } | |
299 | |
300 return null; | |
301 } | |
302 | |
303 /** | |
304 * {@inheritdoc} | |
305 */ | |
306 public function getStatusCode() | |
307 { | |
308 return $this->getResponse()->getStatus(); | |
309 } | |
310 | |
311 /** | |
312 * {@inheritdoc} | |
313 */ | |
314 public function getContent() | |
315 { | |
316 return $this->getResponse()->getContent(); | |
317 } | |
318 | |
319 /** | |
320 * {@inheritdoc} | |
321 */ | |
322 public function findElementXpaths($xpath) | |
323 { | |
324 $nodes = $this->getCrawler()->filterXPath($xpath); | |
325 | |
326 $elements = array(); | |
327 foreach ($nodes as $i => $node) { | |
328 $elements[] = sprintf('(%s)[%d]', $xpath, $i + 1); | |
329 } | |
330 | |
331 return $elements; | |
332 } | |
333 | |
334 /** | |
335 * {@inheritdoc} | |
336 */ | |
337 public function getTagName($xpath) | |
338 { | |
339 return $this->getCrawlerNode($this->getFilteredCrawler($xpath))->nodeName; | |
340 } | |
341 | |
342 /** | |
343 * {@inheritdoc} | |
344 */ | |
345 public function getText($xpath) | |
346 { | |
347 $text = $this->getFilteredCrawler($xpath)->text(); | |
348 $text = str_replace("\n", ' ', $text); | |
349 $text = preg_replace('/ {2,}/', ' ', $text); | |
350 | |
351 return trim($text); | |
352 } | |
353 | |
354 /** | |
355 * {@inheritdoc} | |
356 */ | |
357 public function getHtml($xpath) | |
358 { | |
359 // cut the tag itself (making innerHTML out of outerHTML) | |
360 return preg_replace('/^\<[^\>]+\>|\<[^\>]+\>$/', '', $this->getOuterHtml($xpath)); | |
361 } | |
362 | |
363 /** | |
364 * {@inheritdoc} | |
365 */ | |
366 public function getOuterHtml($xpath) | |
367 { | |
368 $node = $this->getCrawlerNode($this->getFilteredCrawler($xpath)); | |
369 | |
370 return $node->ownerDocument->saveHTML($node); | |
371 } | |
372 | |
373 /** | |
374 * {@inheritdoc} | |
375 */ | |
376 public function getAttribute($xpath, $name) | |
377 { | |
378 $node = $this->getFilteredCrawler($xpath); | |
379 | |
380 if ($this->getCrawlerNode($node)->hasAttribute($name)) { | |
381 return $node->attr($name); | |
382 } | |
383 | |
384 return null; | |
385 } | |
386 | |
387 /** | |
388 * {@inheritdoc} | |
389 */ | |
390 public function getValue($xpath) | |
391 { | |
392 if (in_array($this->getAttribute($xpath, 'type'), array('submit', 'image', 'button'), true)) { | |
393 return $this->getAttribute($xpath, 'value'); | |
394 } | |
395 | |
396 $node = $this->getCrawlerNode($this->getFilteredCrawler($xpath)); | |
397 | |
398 if ('option' === $node->tagName) { | |
399 return $this->getOptionValue($node); | |
400 } | |
401 | |
402 try { | |
403 $field = $this->getFormField($xpath); | |
404 } catch (\InvalidArgumentException $e) { | |
405 return $this->getAttribute($xpath, 'value'); | |
406 } | |
407 | |
408 return $field->getValue(); | |
409 } | |
410 | |
411 /** | |
412 * {@inheritdoc} | |
413 */ | |
414 public function setValue($xpath, $value) | |
415 { | |
416 $this->getFormField($xpath)->setValue($value); | |
417 } | |
418 | |
419 /** | |
420 * {@inheritdoc} | |
421 */ | |
422 public function check($xpath) | |
423 { | |
424 $this->getCheckboxField($xpath)->tick(); | |
425 } | |
426 | |
427 /** | |
428 * {@inheritdoc} | |
429 */ | |
430 public function uncheck($xpath) | |
431 { | |
432 $this->getCheckboxField($xpath)->untick(); | |
433 } | |
434 | |
435 /** | |
436 * {@inheritdoc} | |
437 */ | |
438 public function selectOption($xpath, $value, $multiple = false) | |
439 { | |
440 $field = $this->getFormField($xpath); | |
441 | |
442 if (!$field instanceof ChoiceFormField) { | |
443 throw new DriverException(sprintf('Impossible to select an option on the element with XPath "%s" as it is not a select or radio input', $xpath)); | |
444 } | |
445 | |
446 if ($multiple) { | |
447 $oldValue = (array) $field->getValue(); | |
448 $oldValue[] = $value; | |
449 $value = $oldValue; | |
450 } | |
451 | |
452 $field->select($value); | |
453 } | |
454 | |
455 /** | |
456 * {@inheritdoc} | |
457 */ | |
458 public function isSelected($xpath) | |
459 { | |
460 $optionValue = $this->getOptionValue($this->getCrawlerNode($this->getFilteredCrawler($xpath))); | |
461 $selectField = $this->getFormField('(' . $xpath . ')/ancestor-or-self::*[local-name()="select"]'); | |
462 $selectValue = $selectField->getValue(); | |
463 | |
464 return is_array($selectValue) ? in_array($optionValue, $selectValue, true) : $optionValue === $selectValue; | |
465 } | |
466 | |
467 /** | |
468 * {@inheritdoc} | |
469 */ | |
470 public function click($xpath) | |
471 { | |
472 $crawler = $this->getFilteredCrawler($xpath); | |
473 $node = $this->getCrawlerNode($crawler); | |
474 $tagName = $node->nodeName; | |
475 | |
476 if ('a' === $tagName) { | |
477 $this->client->click($crawler->link()); | |
478 $this->forms = array(); | |
479 } elseif ($this->canSubmitForm($node)) { | |
480 $this->submit($crawler->form()); | |
481 } elseif ($this->canResetForm($node)) { | |
482 $this->resetForm($node); | |
483 } else { | |
484 $message = sprintf('%%s supports clicking on links and submit or reset buttons only. But "%s" provided', $tagName); | |
485 | |
486 throw new UnsupportedDriverActionException($message, $this); | |
487 } | |
488 } | |
489 | |
490 /** | |
491 * {@inheritdoc} | |
492 */ | |
493 public function isChecked($xpath) | |
494 { | |
495 $field = $this->getFormField($xpath); | |
496 | |
497 if (!$field instanceof ChoiceFormField || 'select' === $field->getType()) { | |
498 throw new DriverException(sprintf('Impossible to get the checked state of the element with XPath "%s" as it is not a checkbox or radio input', $xpath)); | |
499 } | |
500 | |
501 if ('checkbox' === $field->getType()) { | |
502 return $field->hasValue(); | |
503 } | |
504 | |
505 $radio = $this->getCrawlerNode($this->getFilteredCrawler($xpath)); | |
506 | |
507 return $radio->getAttribute('value') === $field->getValue(); | |
508 } | |
509 | |
510 /** | |
511 * {@inheritdoc} | |
512 */ | |
513 public function attachFile($xpath, $path) | |
514 { | |
515 $field = $this->getFormField($xpath); | |
516 | |
517 if (!$field instanceof FileFormField) { | |
518 throw new DriverException(sprintf('Impossible to attach a file on the element with XPath "%s" as it is not a file input', $xpath)); | |
519 } | |
520 | |
521 $field->upload($path); | |
522 } | |
523 | |
524 /** | |
525 * {@inheritdoc} | |
526 */ | |
527 public function submitForm($xpath) | |
528 { | |
529 $crawler = $this->getFilteredCrawler($xpath); | |
530 | |
531 $this->submit($crawler->form()); | |
532 } | |
533 | |
534 /** | |
535 * @return Response | |
536 * | |
537 * @throws DriverException If there is not response yet | |
538 */ | |
539 protected function getResponse() | |
540 { | |
541 $response = $this->client->getInternalResponse(); | |
542 | |
543 if (null === $response) { | |
544 throw new DriverException('Unable to access the response before visiting a page'); | |
545 } | |
546 | |
547 return $response; | |
548 } | |
549 | |
550 /** | |
551 * Prepares URL for visiting. | |
552 * Removes "*.php/" from urls and then passes it to BrowserKitDriver::visit(). | |
553 * | |
554 * @param string $url | |
555 * | |
556 * @return string | |
557 */ | |
558 protected function prepareUrl($url) | |
559 { | |
560 $replacement = ($this->removeHostFromUrl ? '' : '$1') . ($this->removeScriptFromUrl ? '' : '$2'); | |
561 | |
562 return preg_replace('#(https?\://[^/]+)(/[^/\.]+\.php)?#', $replacement, $url); | |
563 } | |
564 | |
565 /** | |
566 * Returns form field from XPath query. | |
567 * | |
568 * @param string $xpath | |
569 * | |
570 * @return FormField | |
571 * | |
572 * @throws DriverException | |
573 */ | |
574 protected function getFormField($xpath) | |
575 { | |
576 $fieldNode = $this->getCrawlerNode($this->getFilteredCrawler($xpath)); | |
577 $fieldName = str_replace('[]', '', $fieldNode->getAttribute('name')); | |
578 | |
579 $formNode = $this->getFormNode($fieldNode); | |
580 $formId = $this->getFormNodeId($formNode); | |
581 | |
582 if (!isset($this->forms[$formId])) { | |
583 $this->forms[$formId] = new Form($formNode, $this->getCurrentUrl()); | |
584 } | |
585 | |
586 if (is_array($this->forms[$formId][$fieldName])) { | |
587 return $this->forms[$formId][$fieldName][$this->getFieldPosition($fieldNode)]; | |
588 } | |
589 | |
590 return $this->forms[$formId][$fieldName]; | |
591 } | |
592 | |
593 /** | |
594 * Returns the checkbox field from xpath query, ensuring it is valid. | |
595 * | |
596 * @param string $xpath | |
597 * | |
598 * @return ChoiceFormField | |
599 * | |
600 * @throws DriverException when the field is not a checkbox | |
601 */ | |
602 private function getCheckboxField($xpath) | |
603 { | |
604 $field = $this->getFormField($xpath); | |
605 | |
606 if (!$field instanceof ChoiceFormField) { | |
607 throw new DriverException(sprintf('Impossible to check the element with XPath "%s" as it is not a checkbox', $xpath)); | |
608 } | |
609 | |
610 return $field; | |
611 } | |
612 | |
613 /** | |
614 * @param \DOMElement $element | |
615 * | |
616 * @return \DOMElement | |
617 * | |
618 * @throws DriverException if the form node cannot be found | |
619 */ | |
620 private function getFormNode(\DOMElement $element) | |
621 { | |
622 if ($element->hasAttribute('form')) { | |
623 $formId = $element->getAttribute('form'); | |
624 $formNode = $element->ownerDocument->getElementById($formId); | |
625 | |
626 if (null === $formNode || 'form' !== $formNode->nodeName) { | |
627 throw new DriverException(sprintf('The selected node has an invalid form attribute (%s).', $formId)); | |
628 } | |
629 | |
630 return $formNode; | |
631 } | |
632 | |
633 $formNode = $element; | |
634 | |
635 do { | |
636 // use the ancestor form element | |
637 if (null === $formNode = $formNode->parentNode) { | |
638 throw new DriverException('The selected node does not have a form ancestor.'); | |
639 } | |
640 } while ('form' !== $formNode->nodeName); | |
641 | |
642 return $formNode; | |
643 } | |
644 | |
645 /** | |
646 * Gets the position of the field node among elements with the same name | |
647 * | |
648 * BrowserKit uses the field name as index to find the field in its Form object. | |
649 * When multiple fields have the same name (checkboxes for instance), it will return | |
650 * an array of elements in the order they appear in the DOM. | |
651 * | |
652 * @param \DOMElement $fieldNode | |
653 * | |
654 * @return integer | |
655 */ | |
656 private function getFieldPosition(\DOMElement $fieldNode) | |
657 { | |
658 $elements = $this->getCrawler()->filterXPath('//*[@name=\''.$fieldNode->getAttribute('name').'\']'); | |
659 | |
660 if (count($elements) > 1) { | |
661 // more than one element contains this name ! | |
662 // so we need to find the position of $fieldNode | |
663 foreach ($elements as $key => $element) { | |
664 /** @var \DOMElement $element */ | |
665 if ($element->getNodePath() === $fieldNode->getNodePath()) { | |
666 return $key; | |
667 } | |
668 } | |
669 } | |
670 | |
671 return 0; | |
672 } | |
673 | |
674 private function submit(Form $form) | |
675 { | |
676 $formId = $this->getFormNodeId($form->getFormNode()); | |
677 | |
678 if (isset($this->forms[$formId])) { | |
679 $this->mergeForms($form, $this->forms[$formId]); | |
680 } | |
681 | |
682 // remove empty file fields from request | |
683 foreach ($form->getFiles() as $name => $field) { | |
684 if (empty($field['name']) && empty($field['tmp_name'])) { | |
685 $form->remove($name); | |
686 } | |
687 } | |
688 | |
689 foreach ($form->all() as $field) { | |
690 // Add a fix for https://github.com/symfony/symfony/pull/10733 to support Symfony versions which are not fixed | |
691 if ($field instanceof TextareaFormField && null === $field->getValue()) { | |
692 $field->setValue(''); | |
693 } | |
694 } | |
695 | |
696 $this->client->submit($form); | |
697 | |
698 $this->forms = array(); | |
699 } | |
700 | |
701 private function resetForm(\DOMElement $fieldNode) | |
702 { | |
703 $formNode = $this->getFormNode($fieldNode); | |
704 $formId = $this->getFormNodeId($formNode); | |
705 unset($this->forms[$formId]); | |
706 } | |
707 | |
708 /** | |
709 * Determines if a node can submit a form. | |
710 * | |
711 * @param \DOMElement $node Node. | |
712 * | |
713 * @return boolean | |
714 */ | |
715 private function canSubmitForm(\DOMElement $node) | |
716 { | |
717 $type = $node->hasAttribute('type') ? $node->getAttribute('type') : null; | |
718 | |
719 if ('input' === $node->nodeName && in_array($type, array('submit', 'image'), true)) { | |
720 return true; | |
721 } | |
722 | |
723 return 'button' === $node->nodeName && (null === $type || 'submit' === $type); | |
724 } | |
725 | |
726 /** | |
727 * Determines if a node can reset a form. | |
728 * | |
729 * @param \DOMElement $node Node. | |
730 * | |
731 * @return boolean | |
732 */ | |
733 private function canResetForm(\DOMElement $node) | |
734 { | |
735 $type = $node->hasAttribute('type') ? $node->getAttribute('type') : null; | |
736 | |
737 return in_array($node->nodeName, array('input', 'button'), true) && 'reset' === $type; | |
738 } | |
739 | |
740 /** | |
741 * Returns form node unique identifier. | |
742 * | |
743 * @param \DOMElement $form | |
744 * | |
745 * @return string | |
746 */ | |
747 private function getFormNodeId(\DOMElement $form) | |
748 { | |
749 return md5($form->getLineNo() . $form->getNodePath() . $form->nodeValue); | |
750 } | |
751 | |
752 /** | |
753 * Gets the value of an option element | |
754 * | |
755 * @param \DOMElement $option | |
756 * | |
757 * @return string | |
758 * | |
759 * @see \Symfony\Component\DomCrawler\Field\ChoiceFormField::buildOptionValue | |
760 */ | |
761 private function getOptionValue(\DOMElement $option) | |
762 { | |
763 if ($option->hasAttribute('value')) { | |
764 return $option->getAttribute('value'); | |
765 } | |
766 | |
767 if (!empty($option->nodeValue)) { | |
768 return $option->nodeValue; | |
769 } | |
770 | |
771 return '1'; // DomCrawler uses 1 by default if there is no text in the option | |
772 } | |
773 | |
774 /** | |
775 * Merges second form values into first one. | |
776 * | |
777 * @param Form $to merging target | |
778 * @param Form $from merging source | |
779 */ | |
780 private function mergeForms(Form $to, Form $from) | |
781 { | |
782 foreach ($from->all() as $name => $field) { | |
783 $fieldReflection = new \ReflectionObject($field); | |
784 $nodeReflection = $fieldReflection->getProperty('node'); | |
785 $valueReflection = $fieldReflection->getProperty('value'); | |
786 | |
787 $nodeReflection->setAccessible(true); | |
788 $valueReflection->setAccessible(true); | |
789 | |
790 $isIgnoredField = $field instanceof InputFormField && | |
791 in_array($nodeReflection->getValue($field)->getAttribute('type'), array('submit', 'button', 'image'), true); | |
792 | |
793 if (!$isIgnoredField) { | |
794 $valueReflection->setValue($to[$name], $valueReflection->getValue($field)); | |
795 } | |
796 } | |
797 } | |
798 | |
799 /** | |
800 * Returns DOMElement from crawler instance. | |
801 * | |
802 * @param Crawler $crawler | |
803 * | |
804 * @return \DOMElement | |
805 * | |
806 * @throws DriverException when the node does not exist | |
807 */ | |
808 private function getCrawlerNode(Crawler $crawler) | |
809 { | |
810 $node = null; | |
811 | |
812 if ($crawler instanceof \Iterator) { | |
813 // for symfony 2.3 compatibility as getNode is not public before symfony 2.4 | |
814 $crawler->rewind(); | |
815 $node = $crawler->current(); | |
816 } else { | |
817 $node = $crawler->getNode(0); | |
818 } | |
819 | |
820 if (null !== $node) { | |
821 return $node; | |
822 } | |
823 | |
824 throw new DriverException('The element does not exist'); | |
825 } | |
826 | |
827 /** | |
828 * Returns a crawler filtered for the given XPath, requiring at least 1 result. | |
829 * | |
830 * @param string $xpath | |
831 * | |
832 * @return Crawler | |
833 * | |
834 * @throws DriverException when no matching elements are found | |
835 */ | |
836 private function getFilteredCrawler($xpath) | |
837 { | |
838 if (!count($crawler = $this->getCrawler()->filterXPath($xpath))) { | |
839 throw new DriverException(sprintf('There is no element matching XPath "%s"', $xpath)); | |
840 } | |
841 | |
842 return $crawler; | |
843 } | |
844 | |
845 /** | |
846 * Returns crawler instance (got from client). | |
847 * | |
848 * @return Crawler | |
849 * | |
850 * @throws DriverException | |
851 */ | |
852 private function getCrawler() | |
853 { | |
854 $crawler = $this->client->getCrawler(); | |
855 | |
856 if (null === $crawler) { | |
857 throw new DriverException('Unable to access the response content before visiting a page'); | |
858 } | |
859 | |
860 return $crawler; | |
861 } | |
862 } |