/vendor/symfony/dom-crawler/Form.php |
@@ -0,0 +1,473 @@ |
<?php |
|
/* |
* This file is part of the Symfony package. |
* |
* (c) Fabien Potencier <fabien@symfony.com> |
* |
* For the full copyright and license information, please view the LICENSE |
* file that was distributed with this source code. |
*/ |
|
namespace Symfony\Component\DomCrawler; |
|
use Symfony\Component\DomCrawler\Field\ChoiceFormField; |
use Symfony\Component\DomCrawler\Field\FormField; |
|
/** |
* Form represents an HTML form. |
* |
* @author Fabien Potencier <fabien@symfony.com> |
*/ |
class Form extends Link implements \ArrayAccess |
{ |
/** |
* @var \DOMElement |
*/ |
private $button; |
|
/** |
* @var FormFieldRegistry |
*/ |
private $fields; |
|
/** |
* @var string |
*/ |
private $baseHref; |
|
/** |
* Constructor. |
* |
* @param \DOMElement $node A \DOMElement instance |
* @param string $currentUri The URI of the page where the form is embedded |
* @param string $method The method to use for the link (if null, it defaults to the method defined by the form) |
* @param string $baseHref The URI of the <base> used for relative links, but not for empty action |
* |
* @throws \LogicException if the node is not a button inside a form tag |
*/ |
public function __construct(\DOMElement $node, $currentUri, $method = null, $baseHref = null) |
{ |
parent::__construct($node, $currentUri, $method); |
$this->baseHref = $baseHref; |
|
$this->initialize(); |
} |
|
/** |
* Gets the form node associated with this form. |
* |
* @return \DOMElement A \DOMElement instance |
*/ |
public function getFormNode() |
{ |
return $this->node; |
} |
|
/** |
* Sets the value of the fields. |
* |
* @param array $values An array of field values |
* |
* @return $this |
*/ |
public function setValues(array $values) |
{ |
foreach ($values as $name => $value) { |
$this->fields->set($name, $value); |
} |
|
return $this; |
} |
|
/** |
* Gets the field values. |
* |
* The returned array does not include file fields (@see getFiles). |
* |
* @return array An array of field values |
*/ |
public function getValues() |
{ |
$values = array(); |
foreach ($this->fields->all() as $name => $field) { |
if ($field->isDisabled()) { |
continue; |
} |
|
if (!$field instanceof Field\FileFormField && $field->hasValue()) { |
$values[$name] = $field->getValue(); |
} |
} |
|
return $values; |
} |
|
/** |
* Gets the file field values. |
* |
* @return array An array of file field values |
*/ |
public function getFiles() |
{ |
if (!in_array($this->getMethod(), array('POST', 'PUT', 'DELETE', 'PATCH'))) { |
return array(); |
} |
|
$files = array(); |
|
foreach ($this->fields->all() as $name => $field) { |
if ($field->isDisabled()) { |
continue; |
} |
|
if ($field instanceof Field\FileFormField) { |
$files[$name] = $field->getValue(); |
} |
} |
|
return $files; |
} |
|
/** |
* Gets the field values as PHP. |
* |
* This method converts fields with the array notation |
* (like foo[bar] to arrays) like PHP does. |
* |
* @return array An array of field values |
*/ |
public function getPhpValues() |
{ |
$values = array(); |
foreach ($this->getValues() as $name => $value) { |
$qs = http_build_query(array($name => $value), '', '&'); |
if (!empty($qs)) { |
parse_str($qs, $expandedValue); |
$varName = substr($name, 0, strlen(key($expandedValue))); |
$values = array_replace_recursive($values, array($varName => current($expandedValue))); |
} |
} |
|
return $values; |
} |
|
/** |
* Gets the file field values as PHP. |
* |
* This method converts fields with the array notation |
* (like foo[bar] to arrays) like PHP does. |
* The returned array is consistent with the array for field values |
* (@see getPhpValues), rather than uploaded files found in $_FILES. |
* For a compound file field foo[bar] it will create foo[bar][name], |
* instead of foo[name][bar] which would be found in $_FILES. |
* |
* @return array An array of file field values |
*/ |
public function getPhpFiles() |
{ |
$values = array(); |
foreach ($this->getFiles() as $name => $value) { |
$qs = http_build_query(array($name => $value), '', '&'); |
if (!empty($qs)) { |
parse_str($qs, $expandedValue); |
$varName = substr($name, 0, strlen(key($expandedValue))); |
$values = array_replace_recursive($values, array($varName => current($expandedValue))); |
} |
} |
|
return $values; |
} |
|
/** |
* Gets the URI of the form. |
* |
* The returned URI is not the same as the form "action" attribute. |
* This method merges the value if the method is GET to mimics |
* browser behavior. |
* |
* @return string The URI |
*/ |
public function getUri() |
{ |
$uri = parent::getUri(); |
|
if (!in_array($this->getMethod(), array('POST', 'PUT', 'DELETE', 'PATCH'))) { |
$query = parse_url($uri, PHP_URL_QUERY); |
$currentParameters = array(); |
if ($query) { |
parse_str($query, $currentParameters); |
} |
|
$queryString = http_build_query(array_merge($currentParameters, $this->getValues()), null, '&'); |
|
$pos = strpos($uri, '?'); |
$base = false === $pos ? $uri : substr($uri, 0, $pos); |
$uri = rtrim($base.'?'.$queryString, '?'); |
} |
|
return $uri; |
} |
|
protected function getRawUri() |
{ |
return $this->node->getAttribute('action'); |
} |
|
/** |
* Gets the form method. |
* |
* If no method is defined in the form, GET is returned. |
* |
* @return string The method |
*/ |
public function getMethod() |
{ |
if (null !== $this->method) { |
return $this->method; |
} |
|
return $this->node->getAttribute('method') ? strtoupper($this->node->getAttribute('method')) : 'GET'; |
} |
|
/** |
* Returns true if the named field exists. |
* |
* @param string $name The field name |
* |
* @return bool true if the field exists, false otherwise |
*/ |
public function has($name) |
{ |
return $this->fields->has($name); |
} |
|
/** |
* Removes a field from the form. |
* |
* @param string $name The field name |
*/ |
public function remove($name) |
{ |
$this->fields->remove($name); |
} |
|
/** |
* Gets a named field. |
* |
* @param string $name The field name |
* |
* @return FormField The field instance |
* |
* @throws \InvalidArgumentException When field is not present in this form |
*/ |
public function get($name) |
{ |
return $this->fields->get($name); |
} |
|
/** |
* Sets a named field. |
* |
* @param FormField $field The field |
*/ |
public function set(FormField $field) |
{ |
$this->fields->add($field); |
} |
|
/** |
* Gets all fields. |
* |
* @return FormField[] |
*/ |
public function all() |
{ |
return $this->fields->all(); |
} |
|
/** |
* Returns true if the named field exists. |
* |
* @param string $name The field name |
* |
* @return bool true if the field exists, false otherwise |
*/ |
public function offsetExists($name) |
{ |
return $this->has($name); |
} |
|
/** |
* Gets the value of a field. |
* |
* @param string $name The field name |
* |
* @return FormField The associated Field instance |
* |
* @throws \InvalidArgumentException if the field does not exist |
*/ |
public function offsetGet($name) |
{ |
return $this->fields->get($name); |
} |
|
/** |
* Sets the value of a field. |
* |
* @param string $name The field name |
* @param string|array $value The value of the field |
* |
* @throws \InvalidArgumentException if the field does not exist |
*/ |
public function offsetSet($name, $value) |
{ |
$this->fields->set($name, $value); |
} |
|
/** |
* Removes a field from the form. |
* |
* @param string $name The field name |
*/ |
public function offsetUnset($name) |
{ |
$this->fields->remove($name); |
} |
|
/** |
* Disables validation. |
* |
* @return self |
*/ |
public function disableValidation() |
{ |
foreach ($this->fields->all() as $field) { |
if ($field instanceof Field\ChoiceFormField) { |
$field->disableValidation(); |
} |
} |
|
return $this; |
} |
|
/** |
* Sets the node for the form. |
* |
* Expects a 'submit' button \DOMElement and finds the corresponding form element, or the form element itself. |
* |
* @param \DOMElement $node A \DOMElement instance |
* |
* @throws \LogicException If given node is not a button or input or does not have a form ancestor |
*/ |
protected function setNode(\DOMElement $node) |
{ |
$this->button = $node; |
if ('button' === $node->nodeName || ('input' === $node->nodeName && in_array(strtolower($node->getAttribute('type')), array('submit', 'button', 'image')))) { |
if ($node->hasAttribute('form')) { |
// if the node has the HTML5-compliant 'form' attribute, use it |
$formId = $node->getAttribute('form'); |
$form = $node->ownerDocument->getElementById($formId); |
if (null === $form) { |
throw new \LogicException(sprintf('The selected node has an invalid form attribute (%s).', $formId)); |
} |
$this->node = $form; |
|
return; |
} |
// we loop until we find a form ancestor |
do { |
if (null === $node = $node->parentNode) { |
throw new \LogicException('The selected node does not have a form ancestor.'); |
} |
} while ('form' !== $node->nodeName); |
} elseif ('form' !== $node->nodeName) { |
throw new \LogicException(sprintf('Unable to submit on a "%s" tag.', $node->nodeName)); |
} |
|
$this->node = $node; |
} |
|
/** |
* Adds form elements related to this form. |
* |
* Creates an internal copy of the submitted 'button' element and |
* the form node or the entire document depending on whether we need |
* to find non-descendant elements through HTML5 'form' attribute. |
*/ |
private function initialize() |
{ |
$this->fields = new FormFieldRegistry(); |
|
$xpath = new \DOMXPath($this->node->ownerDocument); |
|
// add submitted button if it has a valid name |
if ('form' !== $this->button->nodeName && $this->button->hasAttribute('name') && $this->button->getAttribute('name')) { |
if ('input' == $this->button->nodeName && 'image' == strtolower($this->button->getAttribute('type'))) { |
$name = $this->button->getAttribute('name'); |
$this->button->setAttribute('value', '0'); |
|
// temporarily change the name of the input node for the x coordinate |
$this->button->setAttribute('name', $name.'.x'); |
$this->set(new Field\InputFormField($this->button)); |
|
// temporarily change the name of the input node for the y coordinate |
$this->button->setAttribute('name', $name.'.y'); |
$this->set(new Field\InputFormField($this->button)); |
|
// restore the original name of the input node |
$this->button->setAttribute('name', $name); |
} else { |
$this->set(new Field\InputFormField($this->button)); |
} |
} |
|
// find form elements corresponding to the current form |
if ($this->node->hasAttribute('id')) { |
// corresponding elements are either descendants or have a matching HTML5 form attribute |
$formId = Crawler::xpathLiteral($this->node->getAttribute('id')); |
|
$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)); |
foreach ($fieldNodes as $node) { |
$this->addField($node); |
} |
} else { |
// do the xpath query with $this->node as the context node, to only find descendant elements |
// however, descendant elements with form attribute are not part of this form |
$fieldNodes = $xpath->query('descendant::input[not(@form)] | descendant::button[not(@form)] | descendant::textarea[not(@form)] | descendant::select[not(@form)]', $this->node); |
foreach ($fieldNodes as $node) { |
$this->addField($node); |
} |
} |
|
if ($this->baseHref && '' !== $this->node->getAttribute('action')) { |
$this->currentUri = $this->baseHref; |
} |
} |
|
private function addField(\DOMElement $node) |
{ |
if (!$node->hasAttribute('name') || !$node->getAttribute('name')) { |
return; |
} |
|
$nodeName = $node->nodeName; |
if ('select' == $nodeName || 'input' == $nodeName && 'checkbox' == strtolower($node->getAttribute('type'))) { |
$this->set(new Field\ChoiceFormField($node)); |
} elseif ('input' == $nodeName && 'radio' == strtolower($node->getAttribute('type'))) { |
// there may be other fields with the same name that are no choice |
// fields already registered (see https://github.com/symfony/symfony/issues/11689) |
if ($this->has($node->getAttribute('name')) && $this->get($node->getAttribute('name')) instanceof ChoiceFormField) { |
$this->get($node->getAttribute('name'))->addChoice($node); |
} else { |
$this->set(new Field\ChoiceFormField($node)); |
} |
} elseif ('input' == $nodeName && 'file' == strtolower($node->getAttribute('type'))) { |
$this->set(new Field\FileFormField($node)); |
} elseif ('input' == $nodeName && !in_array(strtolower($node->getAttribute('type')), array('submit', 'button', 'image'))) { |
$this->set(new Field\InputFormField($node)); |
} elseif ('textarea' == $nodeName) { |
$this->set(new Field\TextareaFormField($node)); |
} |
} |
} |