Development

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

You must first sign up to be able to contribute.

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

Revision 28702, 27.2 kB (checked in by fabien, 5 years ago)

[1.3, 1.4] fixed browser to match more closely the behavior of a real browser (closes #7816)

  • 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     $list = $this->getResponseDomXpath()->query($query);
704
705     $position = isset($options['position']) ? $options['position'] - 1 : 0;
706
707     if (!$item = $list->item($position))
708     {
709       throw new InvalidArgumentException(sprintf('Cannot find the "%s" link or button (position %d).', $name, $position + 1));
710     }
711
712     return $this->doClickElement($item, $arguments, $options);
713   }
714
715   /**
716    * Simulates a click on an element indicated by CSS selector.
717    *
718    * This method is called internally by the {@link click()} method.
719    *
720    * @param  string $selector  The CSS selector
721    * @param  array  $arguments The arguments to pass to the link
722    * @param  array  $options   An array of options
723    *
724    * @return array An array composed of the URI, the method and the arguments to pass to the {@link call()} call
725    *
726    * @uses   getResponseDomCssSelector() doClickElement()
727    * @throws InvalidArgumentException If a matching element cannot be found
728    */
729   public function doClickCssSelector($selector, $arguments = array(), $options = array())
730   {
731     $elements = $this->getResponseDomCssSelector()->matchAll($selector)->getNodes();
732     $position = isset($options['position']) ? $options['position'] - 1 : 0;
733
734     if (isset($elements[$position]))
735     {
736       return $this->doClickElement($elements[$position], $arguments, $options);
737     }
738     else
739     {
740       throw new InvalidArgumentException(sprintf('Could not find the element "%s" (position %d) in the current DOM.', $selector, $position + 1));
741     }
742   }
743
744   /**
745    * Simulates a click on the supplied DOM element.
746    *
747    * This method is called internally by the {@link click()} method.
748    *
749    * @param  DOMElement $item      The element being clicked
750    * @param  array      $arguments The arguments to pass to the link
751    * @param  array      $options   An array of options
752    *
753    * @return array An array composed of the URI, the method and the arguments to pass to the call() call
754    *
755    * @uses getResponseDomXpath()
756    */
757   public function doClickElement(DOMElement $item, $arguments = array(), $options = array())
758   {
759     $method = strtolower(isset($options['method']) ? $options['method'] : 'get');
760
761     if ('a' == $item->nodeName)
762     {
763       if (in_array($method, array('post', 'put', 'delete')))
764       {
765         if (isset($options['_with_csrf']) && $options['_with_csrf'])
766         {
767           $arguments['_with_csrf'] = true;
768         }
769
770         return array($item->getAttribute('href'), $method, $arguments);
771       }
772       else
773       {
774         return array($item->getAttribute('href'), 'get', $arguments);
775       }
776     }
777     else if ('button' == $item->nodeName || ('input' == $item->nodeName && in_array($item->getAttribute('type'), array('submit', 'button', 'image'))))
778     {
779       // add the item's value to the arguments
780       $this->parseArgumentAsArray($item->getAttribute('name'), $item->getAttribute('value'), $arguments);
781
782       // use the ancestor form element
783       do
784       {
785         if (null === $item = $item->parentNode)
786         {
787           throw new Exception('The clicked form element does not have a form ancestor.');
788         }
789       }
790       while ('form' != $item->nodeName);
791     }
792
793     // form attributes
794     $url = $item->getAttribute('action');
795     if (!$url || '#' == $url)
796     {
797       $url = $this->stack[$this->stackPosition]['uri'];
798     }
799     $method = strtolower(isset($options['method']) ? $options['method'] : ($item->getAttribute('method') ? $item->getAttribute('method') : 'get'));
800
801     // merge form default values and arguments
802     $defaults = array();
803     $arguments = sfToolkit::arrayDeepMerge($this->fields, $arguments);
804
805     $xpath = $this->getResponseDomXpath();
806     foreach ($xpath->query('descendant::input | descendant::textarea | descendant::select', $item) as $element)
807     {
808       if ($element->hasAttribute('disabled'))
809       {
810         continue;
811       }
812
813       $elementName = $element->getAttribute('name');
814       $nodeName    = $element->nodeName;
815       $value       = null;
816
817       if ($nodeName == 'input' && ($element->getAttribute('type') == 'checkbox' || $element->getAttribute('type') == 'radio'))
818       {
819         if ($element->getAttribute('checked'))
820         {
821           $value = $element->hasAttribute('value') ? $element->getAttribute('value') : '1';
822         }
823       }
824       else if ($nodeName == 'input' && $element->getAttribute('type') == 'file')
825       {
826         $filename = array_key_exists($elementName, $arguments) ? $arguments[$elementName] : sfToolkit::getArrayValueForPath($arguments, $elementName, '');
827
828         if (is_readable($filename))
829         {
830           $fileError = UPLOAD_ERR_OK;
831           $fileSize = filesize($filename);
832         }
833         else
834         {
835           $fileError = UPLOAD_ERR_NO_FILE;
836           $fileSize = 0;
837         }
838
839         unset($arguments[$elementName]);
840
841         $this->parseArgumentAsArray($elementName, array('name' => basename($filename), 'type' => '', 'tmp_name' => $filename, 'error' => $fileError, 'size' => $fileSize), $this->files);
842       }
843       else if ('input' == $nodeName && !in_array($element->getAttribute('type'), array('submit', 'button', 'image')))
844       {
845         $value = $element->getAttribute('value');
846       }
847       else if ($nodeName == 'textarea')
848       {
849         $value = '';
850         foreach ($element->childNodes as $el)
851         {
852           $value .= $this->getResponseDom()->saveXML($el);
853         }
854       }
855       else if ($nodeName == 'select')
856       {
857         if ($multiple = $element->hasAttribute('multiple'))
858         {
859           $elementName = str_replace('[]', '', $elementName);
860           $value = array();
861         }
862         else
863         {
864           $value = null;
865         }
866
867         $found = false;
868         foreach ($xpath->query('descendant::option', $element) as $option)
869         {
870           if ($option->getAttribute('selected'))
871           {
872             $found = true;
873             if ($multiple)
874             {
875               $value[] = $option->getAttribute('value');
876             }
877             else
878             {
879               $value = $option->getAttribute('value');
880             }
881           }
882         }
883
884         // if no option is selected and if it is a simple select box, take the first option as the value
885         $option = $xpath->query('descendant::option', $element)->item(0);
886         if (!$found && !$multiple && $option instanceof DOMElement)
887         {
888           $value = $option->getAttribute('value');
889         }
890       }
891
892       if (null !== $value)
893       {
894         $this->parseArgumentAsArray($elementName, $value, $defaults);
895       }
896     }
897
898     // create request parameters
899     $arguments = sfToolkit::arrayDeepMerge($defaults, $arguments);
900     if (in_array($method, array('post', 'put', 'delete')))
901     {
902       return array($url, $method, $arguments);
903     }
904     else
905     {
906       $queryString = http_build_query($arguments, null, '&');
907       $sep = false === strpos($url, '?') ? '?' : '&';
908
909       return array($url.($queryString ? $sep.$queryString : ''), 'get', array());
910     }
911   }
912
913   /**
914    * Parses arguments as array
915    *
916    * @param string $name   The argument name
917    * @param string $value  The argument value
918    * @param array  $vars
919    */
920   protected function parseArgumentAsArray($name, $value, &$vars)
921   {
922     if (false !== $pos = strpos($name, '['))
923     {
924       $var = &$vars;
925       $tmps = array_filter(preg_split('/(\[ | \[\] | \])/x', $name), create_function('$s', 'return $s !== "";'));
926       foreach ($tmps as $tmp)
927       {
928         $var = &$var[$tmp];
929       }
930       if ($var && '[]' === substr($name, -2))
931       {
932         if (!is_array($var))
933         {
934           $var = array($var);
935         }
936         $var[] = $value;
937       }
938       else
939       {
940         $var = $value;
941       }
942     }
943     else
944     {
945       $vars[$name] = $value;
946     }
947   }
948
949   /**
950    * Reset browser to original state
951    *
952    * @return sfBrowserBase
953    */
954   public function restart()
955   {
956     $this->newSession();
957     $this->cookieJar     = array();
958     $this->stack         = array();
959     $this->fields        = array();
960     $this->vars          = array();
961     $this->dom           = null;
962     $this->stackPosition = -1;
963
964     return $this;
965   }
966
967   /**
968    * Shutdown function to clean up and remove sessions
969    *
970    * @return void
971    */
972   public function shutdown()
973   {
974     $this->checkCurrentExceptionIsEmpty();
975   }
976
977   /**
978    * Fixes uri removing # declarations and front controller.
979    *
980    * @param  string $uri  The URI to fix
981    * @return string The fixed uri
982    */
983   public function fixUri($uri)
984   {
985     // remove absolute information if needed (to be able to do follow redirects, click on links, ...)
986     if (0 === strpos($uri, 'http'))
987     {
988       // detect secure request
989       if (0 === strpos($uri, 'https'))
990       {
991         $this->defaultServerArray['HTTPS'] = 'on';
992       }
993       else
994       {
995         unset($this->defaultServerArray['HTTPS']);
996       }
997
998       $uri = preg_replace('#^https?\://[^/]+/#', '/', $uri);
999     }
1000     $uri = str_replace('/index.php', '', $uri);
1001
1002     // # as a uri
1003     if ($uri && '#' == $uri[0])
1004     {
1005       $uri = $this->stack[$this->stackPosition]['uri'].$uri;
1006     }
1007
1008     return $uri;
1009   }
1010
1011   /**
1012    * Creates a new session in the browser.
1013    *
1014    * @return void
1015    */
1016   protected function newSession()
1017   {
1018     $this->defaultServerArray['session_id'] = $_SERVER['session_id'] = md5(uniqid(rand(), true));
1019   }
1020 }
1021
Note: See TracBrowser for help on using the browser.