1. sfBrowserBase.class.php
  2. /** * sfBrowserBase is the base class for sfBrowser. * * It implements features that is independent from the symfony controllers. * * @package symfony * @subpackage util * @author Fabien Potencier * @version SVN: $Id: sfBrowserBase.class.php 23901 2009-11-14 13:33:03Z bschussek $ */
  3. abstract class sfBrowserBase
  4. {
  5. protected
  6. $hostname = null,
  7. $remote = null,
  8. $dom = null,
  9. $stack = array(),
  10. $stackPosition = -1,
  11. $cookieJar = array(),
  12. $fields = array(),
  13. $files = array(),
  14. $vars = array(),
  15. $defaultServerArray = array(),
  16. $headers = array(),
  17. $currentException = null,
  18. $domCssSelector = null;
  19. /**
  20. * Class constructor.
  21. *
  22. * @param string $hostname Hostname to browse
  23. * @param string $remote Remote address to spook
  24. * @param array $options Options for sfBrowser
  25. *
  26. * @return void
  27. */
  28. public function __construct($hostname = null, $remote = null, $options = array())
  29. {
  30. $this->initialize($hostname, $remote, $options);
  31. }
  32. /**
  33. * Initializes sfBrowser - sets up environment
  34. *
  35. * @param string $hostname Hostname to browse
  36. * @param string $remote Remote address to spook
  37. * @param array $options Options for sfBrowser
  38. *
  39. * @return void
  40. */
  41. public function initialize($hostname = null, $remote = null, $options = array())
  42. {
  43. unset($_SERVER['argv']);
  44. unset($_SERVER['argc']);
  45. // setup our fake environment
  46. $this->hostname = null === $hostname ? 'localhost' : $hostname;
  47. $this->remote = null === $remote ? '127.0.0.1' : $remote;
  48. // we set a session id (fake cookie / persistence)
  49. $this->newSession();
  50. // store default global $_SERVER array
  51. $this->defaultServerArray = $_SERVER;
  52. // register our shutdown function
  53. register_shutdown_function(array($this, 'shutdown'));
  54. }
  55. /**
  56. * Sets variable name
  57. *
  58. * @param string $name The variable name
  59. * @param mixed $value The value
  60. *
  61. * @return sfBrowserBase
  62. */
  63. public function setVar($name, $value)
  64. {
  65. $this->vars[$name] = $value;
  66. return $this;
  67. }
  68. /**
  69. * Sets a HTTP header for the very next request.
  70. *
  71. * @param string $header The header name
  72. * @param string $value The header value
  73. */
  74. public function setHttpHeader($header, $value)
  75. {
  76. $this->headers[$header] = $value;
  77. return $this;
  78. }
  79. /**
  80. * Sets a cookie.
  81. *
  82. * @param string $name The cookie name
  83. * @param string $value Value for the cookie
  84. * @param string $expire Cookie expiration period
  85. * @param string $path Path
  86. * @param string $domain Domain name
  87. * @param bool $secure If secure
  88. * @param bool $httpOnly If uses only HTTP
  89. *
  90. * @return sfBrowserBase This sfBrowserBase instance
  91. */
  92. public function setCookie($name, $value, $expire = null, $path = '/', $domain = '', $secure = false, $httpOnly = false)
  93. {
  94. $this->cookieJar[$name] = array(
  95. 'name' => $name,
  96. 'value' => $value,
  97. 'expire' => $expire,
  98. 'path' => $path,
  99. 'domain' => $domain,
  100. 'secure' => (Boolean) $secure,
  101. 'httpOnly' => $httpOnly,
  102. );
  103. return $this;
  104. }
  105. /**
  106. * Removes a cookie by name.
  107. *
  108. * @param string $name The cookie name
  109. *
  110. * @return sfBrowserBase This sfBrowserBase instance
  111. */
  112. public function removeCookie($name)
  113. {
  114. unset($this->cookieJar[$name]);
  115. return $this;
  116. }
  117. /**
  118. * Clears all cookies.
  119. *
  120. * @return sfBrowserBase This sfBrowserBase instance
  121. */
  122. public function clearCookies()
  123. {
  124. $this->cookieJar = array();
  125. return $this;
  126. }
  127. /**
  128. * Sets username and password for simulating http authentication.
  129. *
  130. * @param string $username The username
  131. * @param string $password The password
  132. *
  133. * @return sfBrowserBase
  134. */
  135. public function setAuth($username, $password)
  136. {
  137. $this->vars['PHP_AUTH_USER'] = $username;
  138. $this->vars['PHP_AUTH_PW'] = $password;
  139. return $this;
  140. }
  141. /**
  142. * Gets a uri.
  143. *
  144. * @param string $uri The URI to fetch
  145. * @param array $parameters The Request parameters
  146. * @param bool $changeStack Change the browser history stack?
  147. *
  148. * @return sfBrowserBase
  149. */
  150. public function get($uri, $parameters = array(), $changeStack = true)
  151. {
  152. return $this->call($uri, 'get', $parameters);
  153. }
  154. /**
  155. * Posts a uri.
  156. *
  157. * @param string $uri The URI to fetch
  158. * @param array $parameters The Request parameters
  159. * @param bool $changeStack Change the browser history stack?
  160. *
  161. * @return sfBrowserBase
  162. */
  163. public function post($uri, $parameters = array(), $changeStack = true)
  164. {
  165. return $this->call($uri, 'post', $parameters);
  166. }
  167. /**
  168. * Calls a request to a uri.
  169. *
  170. * @param string $uri The URI to fetch
  171. * @param string $method The request method
  172. * @param array $parameters The Request parameters
  173. * @param bool $changeStack Change the browser history stack?
  174. *
  175. * @return sfBrowserBase
  176. */
  177. public function call($uri, $method = 'get', $parameters = array(), $changeStack = true)
  178. {
  179. // check that the previous call() hasn't returned an uncatched exception
  180. $this->checkCurrentExceptionIsEmpty();
  181. $uri = $this->fixUri($uri);
  182. // add uri to the stack
  183. if ($changeStack)
  184. {
  185. $this->stack = array_slice($this->stack, 0, $this->stackPosition + 1);
  186. $this->stack[] = array(
  187. 'uri' => $uri,
  188. 'method' => $method,
  189. 'parameters' => $parameters,
  190. );
  191. $this->stackPosition = count($this->stack) - 1;
  192. }
  193. list($path, $queryString) = false !== ($pos = strpos($uri, '?')) ? array(substr($uri, 0, $pos), substr($uri, $pos + 1)) : array($uri, '');
  194. $queryString = html_entity_decode($queryString);
  195. // remove anchor
  196. $path = preg_replace('/#.*/', '', $path);
  197. // removes all fields from previous request
  198. $this->fields = array();
  199. // prepare the request object
  200. $_SERVER = $this->defaultServerArray;
  201. $_SERVER['HTTP_HOST'] = $this->hostname;
  202. $_SERVER['SERVER_NAME'] = $_SERVER['HTTP_HOST'];
  203. $_SERVER['SERVER_PORT'] = 80;
  204. $_SERVER['HTTP_USER_AGENT'] = 'PHP5/CLI';
  205. $_SERVER['REMOTE_ADDR'] = $this->remote;
  206. $_SERVER['REQUEST_METHOD'] = strtoupper($method);
  207. $_SERVER['PATH_INFO'] = $path;
  208. $_SERVER['REQUEST_URI'] = '/index.php'.$uri;
  209. $_SERVER['SCRIPT_NAME'] = '/index.php';
  210. $_SERVER['SCRIPT_FILENAME'] = '/index.php';
  211. $_SERVER['QUERY_STRING'] = $queryString;
  212. if ($this->stackPosition >= 1)
  213. {
  214. $_SERVER['HTTP_REFERER'] = sprintf('http%s://%s%s', isset($this->defaultServerArray['HTTPS']) ? 's' : '', $this->hostname, $this->stack[$this->stackPosition - 1]['uri']);
  215. }
  216. foreach ($this->vars as $key => $value)
  217. {
  218. $_SERVER[strtoupper($key)] = $value;
  219. }
  220. foreach ($this->headers as $header => $value)
  221. {
  222. $_SERVER['HTTP_'.strtoupper(str_replace('-', '_', $header))] = $value;
  223. }
  224. $this->headers = array();
  225. // request parameters
  226. $_GET = $_POST = array();
  227. if (in_array(strtoupper($method), array('POST', 'DELETE', 'PUT')))
  228. {
  229. if (isset($parameters['_with_csrf']) && $parameters['_with_csrf'])
  230. {
  231. unset($parameters['_with_csrf']);
  232. $form = new BaseForm();
  233. $parameters[$form->getCSRFFieldName()] = $form->getCSRFToken();
  234. }
  235. $_POST = $parameters;
  236. }
  237. if (strtoupper($method) == 'GET')
  238. {
  239. $_GET = $parameters;
  240. }
  241. // handle input type="file" fields
  242. $_FILES = array();
  243. if (count($this->files))
  244. {
  245. $_FILES = $this->files;
  246. }
  247. $this->files = array();
  248. parse_str($queryString, $qs);
  249. if (is_array($qs))
  250. {
  251. $_GET = array_merge($qs, $_GET);
  252. }
  253. // expire cookies
  254. $cookies = $this->cookieJar;
  255. foreach ($cookies as $name => $cookie)
  256. {
  257. if ($cookie['expire'] && $cookie['expire'] < time())
  258. {
  259. unset($this->cookieJar[$name]);
  260. }
  261. }
  262. // restore cookies
  263. $_COOKIE = array();
  264. foreach ($this->cookieJar as $name => $cookie)
  265. {
  266. $_COOKIE[$name] = $cookie['value'];
  267. }
  268. $this->doCall();
  269. $response = $this->getResponse();
  270. // save cookies
  271. foreach ($response->getCookies() as $name => $cookie)
  272. {
  273. // FIXME: deal with path, secure, ...
  274. $this->cookieJar[$name] = $cookie;
  275. }
  276. // support for the ETag header
  277. if ($etag = $response->getHttpHeader('Etag'))
  278. {
  279. $this->vars['HTTP_IF_NONE_MATCH'] = $etag;
  280. }
  281. else
  282. {
  283. unset($this->vars['HTTP_IF_NONE_MATCH']);
  284. }
  285. // support for the last modified header
  286. if ($lastModified = $response->getHttpHeader('Last-Modified'))
  287. {
  288. $this->vars['HTTP_IF_MODIFIED_SINCE'] = $lastModified;
  289. }
  290. else
  291. {
  292. unset($this->vars['HTTP_IF_MODIFIED_SINCE']);
  293. }
  294. // for HTML/XML content, create a DOM and sfDomCssSelector objects for the response content
  295. $this->dom = null;
  296. $this->domCssSelector = null;
  297. if (preg_match('/(x|ht)ml/i', $response->getContentType(), $matches))
  298. {
  299. $this->dom = new DomDocument('1.0', $response->getCharset());
  300. $this->dom->validateOnParse = true;
  301. if ('x' == $matches[1])
  302. {
  303. @$this->dom->loadXML($response->getContent());
  304. }
  305. else
  306. {
  307. @$this->dom->loadHTML($response->getContent());
  308. }
  309. $this->domCssSelector = new sfDomCssSelector($this->dom);
  310. }
  311. return $this;
  312. }
  313. /**
  314. * Calls a request to a uri.
  315. */
  316. abstract protected function doCall();
  317. /**
  318. * Go back in the browser history stack.
  319. *
  320. * @return sfBrowserBase
  321. */
  322. public function back()
  323. {
  324. if ($this->stackPosition < 1)
  325. {
  326. throw new LogicException('You are already on the first page.');
  327. }
  328. --$this->stackPosition;
  329. return $this->call($this->stack[$this->stackPosition]['uri'], $this->stack[$this->stackPosition]['method'], $this->stack[$this->stackPosition]['parameters'], false);
  330. }
  331. /**
  332. * Go forward in the browser history stack.
  333. *
  334. * @return sfBrowserBase
  335. */
  336. public function forward()
  337. {
  338. if ($this->stackPosition > count($this->stack) - 2)
  339. {
  340. throw new LogicException('You are already on the last page.');
  341. }
  342. ++$this->stackPosition;
  343. return $this->call($this->stack[$this->stackPosition]['uri'], $this->stack[$this->stackPosition]['method'], $this->stack[$this->stackPosition]['parameters'], false);
  344. }
  345. /**
  346. * Reload the current browser.
  347. *
  348. * @return sfBrowserBase
  349. */
  350. public function reload()
  351. {
  352. if (-1 == $this->stackPosition)
  353. {
  354. throw new LogicException('No page to reload.');
  355. }
  356. return $this->call($this->stack[$this->stackPosition]['uri'], $this->stack[$this->stackPosition]['method'], $this->stack[$this->stackPosition]['parameters'], false);
  357. }
  358. /**
  359. * Get response DOM CSS selector.
  360. *
  361. * @return sfDomCssSelector
  362. */
  363. public function getResponseDomCssSelector()
  364. {
  365. if (null === $this->domCssSelector)
  366. {
  367. throw new LogicException('The DOM is not accessible because the browser response content type is not HTML.');
  368. }
  369. return $this->domCssSelector;
  370. }
  371. /**
  372. * Get the response DOM XPath selector.
  373. *
  374. * @return DOMXPath
  375. *
  376. * @uses getResponseDom()
  377. */
  378. public function getResponseDomXpath()
  379. {
  380. return new DOMXPath($this->getResponseDom());
  381. }
  382. /**
  383. * Get response DOM.
  384. *
  385. * @return sfDomCssSelector
  386. */
  387. public function getResponseDom()
  388. {
  389. if (null === $this->dom)
  390. {
  391. throw new LogicException('The DOM is not accessible because the browser response content type is not HTML.');
  392. }
  393. return $this->dom;
  394. }
  395. /**
  396. * Gets response.
  397. *
  398. * @return sfWebResponse
  399. */
  400. abstract public function getResponse();
  401. /**
  402. * Gets request.
  403. *
  404. * @return sfWebRequest
  405. */
  406. abstract public function getRequest();
  407. /**
  408. * Gets user.
  409. *
  410. * @return sfUser
  411. */
  412. abstract public function getUser();
  413. /**
  414. * Gets the current exception.
  415. *
  416. * @return Exception
  417. */
  418. public function getCurrentException()
  419. {
  420. return $this->currentException;
  421. }
  422. /**
  423. * Sets the current exception.
  424. *
  425. * @param Exception $exception An Exception instance
  426. */
  427. public function setCurrentException(Exception $exception)
  428. {
  429. $this->currentException = $exception;
  430. }
  431. /**
  432. * Resets the current exception.
  433. */
  434. public function resetCurrentException()
  435. {
  436. $this->currentException = null;
  437. sfException::clearLastException();
  438. }
  439. /**
  440. * Test for an uncaught exception.
  441. *
  442. * @return boolean
  443. */
  444. public function checkCurrentExceptionIsEmpty()
  445. {
  446. return null === $this->getCurrentException() || $this->getCurrentException() instanceof sfError404Exception;
  447. }
  448. /**
  449. * Follow redirects?
  450. *
  451. * @throws sfException If request was not a redirect
  452. *
  453. * @return sfBrowserBase
  454. */
  455. public function followRedirect()
  456. {
  457. if (null === $this->getResponse()->getHttpHeader('Location'))
  458. {
  459. throw new LogicException('The request was not redirected.');
  460. }
  461. return $this->get($this->getResponse()->getHttpHeader('Location'));
  462. }
  463. /**
  464. * Sets a form field in the browser.
  465. *
  466. * @param string $name The field name
  467. * @param string $value The field value
  468. *
  469. * @return sfBrowserBase
  470. */
  471. public function setField($name, $value)
  472. {
  473. // as we don't know yet the form, just store name/value pairs
  474. $this->parseArgumentAsArray($name, $value, $this->fields);
  475. return $this;
  476. }
  477. /**
  478. * Simulates deselecting a checkbox or radiobutton.
  479. *
  480. * @param string $name The checkbox or radiobutton id, name or text
  481. *
  482. * @return sfBrowserBase
  483. *
  484. * @see doSelect()
  485. */
  486. public function deselect($name)
  487. {
  488. $this->doSelect($name, false);
  489. return $this;
  490. }
  491. /**
  492. * Simulates selecting a checkbox or radiobutton.
  493. *
  494. * @param string $name The checkbox or radiobutton id, name or text
  495. *
  496. * @return sfBrowserBase
  497. *
  498. * @see doSelect()
  499. */
  500. public function select($name)
  501. {
  502. $this->doSelect($name, true);
  503. return $this;
  504. }
  505. /**
  506. * Simulates selecting a checkbox or radiobutton.
  507. *
  508. * This method is called internally by the select() and deselect() methods.
  509. *
  510. * @param string $name The checkbox or radiobutton id, name or text
  511. * @param boolean $selected If true the item will be selected
  512. *
  513. */
  514. public function doSelect($name, $selected)
  515. {
  516. $xpath = $this->getResponseDomXpath();
  517. if ($element = $xpath->query(sprintf('//input[(@type="radio" or @type="checkbox") and (.="%s" or @id="%s" or @name="%s")]', $name, $name, $name))->item(0))
  518. {
  519. if ($selected)
  520. {
  521. if ($element->getAttribute('type') == 'radio')
  522. {
  523. //we need to deselect all other radio buttons with the same name
  524. foreach ($xpath->query(sprintf('//input[@type="radio" and @name="%s"]', $element->getAttribute('name'))) as $radio)
  525. {
  526. $radio->removeAttribute('checked');
  527. }
  528. }
  529. $element->setAttribute('checked', 'checked');
  530. }
  531. else
  532. {
  533. if ($element->getAttribute('type') == 'radio')
  534. {
  535. throw new InvalidArgumentException('Radiobutton cannot be deselected - Select another radiobutton to deselect the current.');
  536. }
  537. $element->removeAttribute('checked');
  538. }
  539. }
  540. else
  541. {
  542. throw new InvalidArgumentException(sprintf('Cannot find the "%s" checkbox or radiobutton.', $name));
  543. }
  544. }
  545. /**
  546. * Simulates a click on a link or button.
  547. *
  548. * Available options:
  549. *
  550. * * position: The position of the linked to link if several ones have the same name
  551. * (the first one is 1, not 0)
  552. * * method: The method to used instead of the form ones
  553. * (useful when you need to click on a link that is converted to a form with JavaScript code)
  554. *
  555. * @param string|DOMElement $name The link, button text, CSS selector or DOMElement
  556. * @param array $arguments The arguments to pass to the link
  557. * @param array $options An array of options
  558. *
  559. * @return sfBrowserBase
  560. *
  561. * @uses doClickElement() doClick() doClickCssSelector()
  562. */
  563. public function click($name, $arguments = array(), $options = array())
  564. {
  565. if ($name instanceof DOMElement)
  566. {
  567. list($uri, $method, $parameters) = $this->doClickElement($name, $arguments, $options);
  568. }
  569. else
  570. {
  571. try
  572. {
  573. list($uri, $method, $parameters) = $this->doClick($name, $arguments, $options);
  574. }
  575. catch (InvalidArgumentException $e)
  576. {
  577. list($uri, $method, $parameters) = $this->doClickCssSelector($name, $arguments, $options);
  578. }
  579. }
  580. return $this->call($uri, $method, $parameters);
  581. }
  582. /**
  583. * Simulates a click on a link or button.
  584. *
  585. * This method is called internally by the {@link click()} method.
  586. *
  587. * @param string $name The link or button text
  588. * @param array $arguments The arguments to pass to the link
  589. * @param array $options An array of options
  590. *
  591. * @return array An array composed of the URI, the method and the arguments to pass to the {@link call()} call
  592. *
  593. * @uses getResponseDomXpath() doClickElement()
  594. * @throws InvalidArgumentException If a matching element cannot be found
  595. *
  596. * @deprecated call {@link click()} using a CSS selector instead
  597. */
  598. public function doClick($name, $arguments = array(), $options = array())
  599. {
  600. if (false !== strpos($name, '[') || false !== strpos($name, ']'))
  601. {
  602. throw new InvalidArgumentException(sprintf('The name "%s" is not valid', $name));
  603. }
  604. $query = sprintf('//a[.="%s"]', $name);
  605. $query .= sprintf('|//a/img[@alt="%s"]/ancestor::a', $name);
  606. $query .= sprintf('|//input[((@type="submit" or @type="button") and @value="%s") or (@type="image" and @alt="%s")]', $name, $name);
  607. $query .= sprintf('|//button[.="%s" or @id="%s" or @name="%s"]', $name, $name, $name);
  608. $list = $this->getResponseDomXpath()->query($query);
  609. $position = isset($options['position']) ? $options['position'] - 1 : 0;
  610. if (!$item = $list->item($position))
  611. {
  612. throw new InvalidArgumentException(sprintf('Cannot find the "%s" link or button (position %d).', $name, $position + 1));
  613. }
  614. return $this->doClickElement($item, $arguments, $options);
  615. }
  616. /**
  617. * Simulates a click on an element indicated by CSS selector.
  618. *
  619. * This method is called internally by the {@link click()} method.
  620. *
  621. * @param string $selector The CSS selector
  622. * @param array $arguments The arguments to pass to the link
  623. * @param array $options An array of options
  624. *
  625. * @return array An array composed of the URI, the method and the arguments to pass to the {@link call()} call
  626. *
  627. * @uses getResponseDomCssSelector() doClickElement()
  628. * @throws InvalidArgumentException If a matching element cannot be found
  629. */
  630. public function doClickCssSelector($selector, $arguments = array(), $options = array())
  631. {
  632. $elements = $this->getResponseDomCssSelector()->matchAll($selector)->getNodes();
  633. $position = isset($options['position']) ? $options['position'] - 1 : 0;
  634. if (isset($elements[$position]))
  635. {
  636. return $this->doClickElement($elements[$position], $arguments, $options);
  637. }
  638. else
  639. {
  640. throw new InvalidArgumentException(sprintf('Could not find the element "%s" (position %d) in the current DOM.', $selector, $position + 1));
  641. }
  642. }
  643. /**
  644. * Simulates a click on the supplied DOM element.
  645. *
  646. * This method is called internally by the {@link click()} method.
  647. *
  648. * @param DOMElement $item The element being clicked
  649. * @param array $arguments The arguments to pass to the link
  650. * @param array $options An array of options
  651. *
  652. * @return array An array composed of the URI, the method and the arguments to pass to the call() call
  653. *
  654. * @uses getResponseDomXpath()
  655. */
  656. public function doClickElement(DOMElement $item, $arguments = array(), $options = array())
  657. {
  658. $method = strtolower(isset($options['method']) ? $options['method'] : 'get');
  659. if ('a' == $item->nodeName)
  660. {
  661. if (in_array($method, array('post', 'put', 'delete')))
  662. {
  663. if (isset($options['_with_csrf']) && $options['_with_csrf'])
  664. {
  665. $arguments['_with_csrf'] = true;
  666. }
  667. return array($item->getAttribute('href'), $method, $arguments);
  668. }
  669. else
  670. {
  671. return array($item->getAttribute('href'), 'get', $arguments);
  672. }
  673. }
  674. else if ('button' == $item->nodeName || ('input' == $item->nodeName && in_array($item->getAttribute('type'), array('submit', 'button', 'image'))))
  675. {
  676. // add the item's value to the arguments
  677. $this->parseArgumentAsArray($item->getAttribute('name'), $item->getAttribute('value'), $arguments);
  678. // use the ancestor form element
  679. do
  680. {
  681. if (null === $item = $item->parentNode)
  682. {
  683. throw new Exception('The clicked form element does not have a form ancestor.');
  684. }
  685. }
  686. while ('form' != $item->nodeName);
  687. }
  688. // form attributes
  689. $url = $item->getAttribute('action');
  690. if (!$url || '#' == $url)
  691. {
  692. $url = $this->stack[$this->stackPosition]['uri'];
  693. }
  694. $method = strtolower(isset($options['method']) ? $options['method'] : ($item->getAttribute('method') ? $item->getAttribute('method') : 'get'));
  695. // merge form default values and arguments
  696. $defaults = array();
  697. $arguments = sfToolkit::arrayDeepMerge($this->fields, $arguments);
  698. $xpath = $this->getResponseDomXpath();
  699. foreach ($xpath->query('descendant::input | descendant::textarea | descendant::select', $item) as $element)
  700. {
  701. $elementName = $element->getAttribute('name');
  702. $nodeName = $element->nodeName;
  703. $value = null;
  704. if ($nodeName == 'input' && ($element->getAttribute('type') == 'checkbox' || $element->getAttribute('type') == 'radio'))
  705. {
  706. if ($element->getAttribute('checked'))
  707. {
  708. $value = $element->hasAttribute('value') ? $element->getAttribute('value') : '1';
  709. }
  710. }
  711. else if ($nodeName == 'input' && $element->getAttribute('type') == 'file')
  712. {
  713. $filename = array_key_exists($elementName, $arguments) ? $arguments[$elementName] : sfToolkit::getArrayValueForPath($arguments, $elementName, '');
  714. if (is_readable($filename))
  715. {
  716. $fileError = UPLOAD_ERR_OK;
  717. $fileSize = filesize($filename);
  718. }
  719. else
  720. {
  721. $fileError = UPLOAD_ERR_NO_FILE;
  722. $fileSize = 0;
  723. }
  724. unset($arguments[$elementName]);
  725. $this->parseArgumentAsArray($elementName, array('name' => basename($filename), 'type' => '', 'tmp_name' => $filename, 'error' => $fileError, 'size' => $fileSize), $this->files);
  726. }
  727. else if ('input' == $nodeName && !in_array($element->getAttribute('type'), array('submit', 'button', 'image')))
  728. {
  729. $value = $element->getAttribute('value');
  730. }
  731. else if ($nodeName == 'textarea')
  732. {
  733. $value = '';
  734. foreach ($element->childNodes as $el)
  735. {
  736. $value .= $this->getResponseDom()->saveXML($el);
  737. }
  738. }
  739. else if ($nodeName == 'select')
  740. {
  741. if ($multiple = $element->hasAttribute('multiple'))
  742. {
  743. $elementName = str_replace('[]', '', $elementName);
  744. $value = array();
  745. }
  746. else
  747. {
  748. $value = null;
  749. }
  750. $found = false;
  751. foreach ($xpath->query('descendant::option', $element) as $option)
  752. {
  753. if ($option->getAttribute('selected'))
  754. {
  755. $found = true;
  756. if ($multiple)
  757. {
  758. $value[] = $option->getAttribute('value');
  759. }
  760. else
  761. {
  762. $value = $option->getAttribute('value');
  763. }
  764. }
  765. }
  766. // if no option is selected and if it is a simple select box, take the first option as the value
  767. $option = $xpath->query('descendant::option', $element)->item(0);
  768. if (!$found && !$multiple && $option instanceof DOMElement)
  769. {
  770. $value = $option->getAttribute('value');
  771. }
  772. }
  773. if (null !== $value)
  774. {
  775. $this->parseArgumentAsArray($elementName, $value, $defaults);
  776. }
  777. }
  778. // create request parameters
  779. $arguments = sfToolkit::arrayDeepMerge($defaults, $arguments);
  780. if (in_array($method, array('post', 'put', 'delete')))
  781. {
  782. return array($url, $method, $arguments);
  783. }
  784. else
  785. {
  786. $queryString = http_build_query($arguments, null, '&');
  787. $sep = false === strpos($url, '?') ? '?' : '&';
  788. return array($url.($queryString ? $sep.$queryString : ''), 'get', array());
  789. }
  790. }
  791. /**
  792. * Parses arguments as array
  793. *
  794. * @param string $name The argument name
  795. * @param string $value The argument value
  796. * @param array $vars
  797. */
  798. protected function parseArgumentAsArray($name, $value, &$vars)
  799. {
  800. if (false !== $pos = strpos($name, '['))
  801. {
  802. $var = &$vars;
  803. $tmps = array_filter(preg_split('/(\[ | \[\] | \])/x', $name), create_function('$s', 'return $s !== "";'));
  804. foreach ($tmps as $tmp)
  805. {
  806. $var = &$var[$tmp];
  807. }
  808. if ($var)
  809. {
  810. if (!is_array($var))
  811. {
  812. $var = array($var);
  813. }
  814. $var[] = $value;
  815. }
  816. else
  817. {
  818. $var = $value;
  819. }
  820. }
  821. else
  822. {
  823. $vars[$name] = $value;
  824. }
  825. }
  826. /**
  827. * Reset browser to original state
  828. *
  829. * @return sfBrowserBase
  830. */
  831. public function restart()
  832. {
  833. $this->newSession();
  834. $this->cookieJar = array();
  835. $this->stack = array();
  836. $this->fields = array();
  837. $this->vars = array();
  838. $this->dom = null;
  839. $this->stackPosition = -1;
  840. return $this;
  841. }
  842. /**
  843. * Shutdown function to clean up and remove sessions
  844. *
  845. * @return void
  846. */
  847. public function shutdown()
  848. {
  849. $this->checkCurrentExceptionIsEmpty();
  850. }
  851. /**
  852. * Fixes uri removing # declarations and front controller.
  853. *
  854. * @param string $uri The URI to fix
  855. * @return string The fixed uri
  856. */
  857. public function fixUri($uri)
  858. {
  859. // remove absolute information if needed (to be able to do follow redirects, click on links, ...)
  860. if (0 === strpos($uri, 'http'))
  861. {
  862. // detect secure request
  863. if (0 === strpos($uri, 'https'))
  864. {
  865. $this->defaultServerArray['HTTPS'] = 'on';
  866. }
  867. else
  868. {
  869. unset($this->defaultServerArray['HTTPS']);
  870. }
  871. $uri = preg_replace('#^https?\://[^/]+/#', '/', $uri);
  872. }
  873. $uri = str_replace('/index.php', '', $uri);
  874. // # as a uri
  875. if ($uri && '#' == $uri[0])
  876. {
  877. $uri = $this->stack[$this->stackPosition]['uri'].$uri;
  878. }
  879. return $uri;
  880. }
  881. /**
  882. * Creates a new session in the browser.
  883. *
  884. * @return void
  885. */
  886. protected function newSession()
  887. {
  888. $this->defaultServerArray['session_id'] = $_SERVER['session_id'] = md5(uniqid(rand(), true));
  889. }
  890. }

Debug toolbar