sfBrowserBase.class.php 25 KB

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