PageRenderTime 243ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/vendor/symfony/symfony/src/Symfony/Component/DomCrawler/Form.php

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