Development

/branches/1.4/lib/util/sfBrowserBase.class.php

You must first sign up to be able to contribute.

root/branches/1.4/lib/util/sfBrowserBase.class.php

Revision 33373, 27.3 kB (checked in by fabien, 3 years ago)

[1.4] fixed test browser click function does not handle css selector without [ or ] (closes #9982, patch from mouette)

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