MDL-32081 csslib: Improved handling of parentheses when processing styles
[moodle.git] / lib / simpletestlib / browser.php
1 <?php
2 /**
3  *  Base include file for SimpleTest
4  *  @package    SimpleTest
5  *  @subpackage WebTester
6  *  @version    $Id$
7  */
9 /**#@+
10  *  include other SimpleTest class files
11  */
12 require_once(dirname(__FILE__) . '/simpletest.php');
13 require_once(dirname(__FILE__) . '/http.php');
14 require_once(dirname(__FILE__) . '/encoding.php');
15 require_once(dirname(__FILE__) . '/page.php');
16 require_once(dirname(__FILE__) . '/selector.php');
17 require_once(dirname(__FILE__) . '/frames.php');
18 require_once(dirname(__FILE__) . '/user_agent.php');
19 /**#@-*/
21 if (!defined('DEFAULT_MAX_NESTED_FRAMES')) {
22     define('DEFAULT_MAX_NESTED_FRAMES', 3);
23 }
25 /**
26  *    Browser history list.
27  *    @package SimpleTest
28  *    @subpackage WebTester
29  */
30 class SimpleBrowserHistory {
31     var $_sequence;
32     var $_position;
34     /**
35      *    Starts empty.
36      *    @access public
37      */
38     function SimpleBrowserHistory() {
39         $this->_sequence = array();
40         $this->_position = -1;
41     }
43     /**
44      *    Test for no entries yet.
45      *    @return boolean        True if empty.
46      *    @access private
47      */
48     function _isEmpty() {
49         return ($this->_position == -1);
50     }
52     /**
53      *    Test for being at the beginning.
54      *    @return boolean        True if first.
55      *    @access private
56      */
57     function _atBeginning() {
58         return ($this->_position == 0) && ! $this->_isEmpty();
59     }
61     /**
62      *    Test for being at the last entry.
63      *    @return boolean        True if last.
64      *    @access private
65      */
66     function _atEnd() {
67         return ($this->_position + 1 >= count($this->_sequence)) && ! $this->_isEmpty();
68     }
70     /**
71      *    Adds a successfully fetched page to the history.
72      *    @param SimpleUrl $url                 URL of fetch.
73      *    @param SimpleEncoding $parameters     Any post data with the fetch.
74      *    @access public
75      */
76     function recordEntry($url, $parameters) {
77         $this->_dropFuture();
78         array_push(
79                 $this->_sequence,
80                 array('url' => $url, 'parameters' => $parameters));
81         $this->_position++;
82     }
84     /**
85      *    Last fully qualified URL for current history
86      *    position.
87      *    @return SimpleUrl        URL for this position.
88      *    @access public
89      */
90     function getUrl() {
91         if ($this->_isEmpty()) {
92             return false;
93         }
94         return $this->_sequence[$this->_position]['url'];
95     }
97     /**
98      *    Parameters of last fetch from current history
99      *    position.
100      *    @return SimpleFormEncoding    Post parameters.
101      *    @access public
102      */
103     function getParameters() {
104         if ($this->_isEmpty()) {
105             return false;
106         }
107         return $this->_sequence[$this->_position]['parameters'];
108     }
110     /**
111      *    Step back one place in the history. Stops at
112      *    the first page.
113      *    @return boolean     True if any previous entries.
114      *    @access public
115      */
116     function back() {
117         if ($this->_isEmpty() || $this->_atBeginning()) {
118             return false;
119         }
120         $this->_position--;
121         return true;
122     }
124     /**
125      *    Step forward one place. If already at the
126      *    latest entry then nothing will happen.
127      *    @return boolean     True if any future entries.
128      *    @access public
129      */
130     function forward() {
131         if ($this->_isEmpty() || $this->_atEnd()) {
132             return false;
133         }
134         $this->_position++;
135         return true;
136     }
138     /**
139      *    Ditches all future entries beyond the current
140      *    point.
141      *    @access private
142      */
143     function _dropFuture() {
144         if ($this->_isEmpty()) {
145             return;
146         }
147         while (! $this->_atEnd()) {
148             array_pop($this->_sequence);
149         }
150     }
153 /**
154  *    Simulated web browser. This is an aggregate of
155  *    the user agent, the HTML parsing, request history
156  *    and the last header set.
157  *    @package SimpleTest
158  *    @subpackage WebTester
159  */
160 class SimpleBrowser {
161     var $_user_agent;
162     var $_page;
163     var $_history;
164     var $_ignore_frames;
165     var $_maximum_nested_frames;
167     /**
168      *    Starts with a fresh browser with no
169      *    cookie or any other state information. The
170      *    exception is that a default proxy will be
171      *    set up if specified in the options.
172      *    @access public
173      */
174     function SimpleBrowser() {
175         $this->_user_agent = &$this->_createUserAgent();
176         $this->_user_agent->useProxy(
177                 SimpleTest::getDefaultProxy(),
178                 SimpleTest::getDefaultProxyUsername(),
179                 SimpleTest::getDefaultProxyPassword());
180         $this->_page = new SimplePage();
181         $this->_history = &$this->_createHistory();
182         $this->_ignore_frames = false;
183         $this->_maximum_nested_frames = DEFAULT_MAX_NESTED_FRAMES;
184     }
186     /**
187      *    Creates the underlying user agent.
188      *    @return SimpleFetcher    Content fetcher.
189      *    @access protected
190      */
191     function &_createUserAgent() {
192         $user_agent = new SimpleUserAgent();
193         return $user_agent;
194     }
196     /**
197      *    Creates a new empty history list.
198      *    @return SimpleBrowserHistory    New list.
199      *    @access protected
200      */
201     function &_createHistory() {
202         $history = new SimpleBrowserHistory();
203         return $history;
204     }
206     /**
207      *    Disables frames support. Frames will not be fetched
208      *    and the frameset page will be used instead.
209      *    @access public
210      */
211     function ignoreFrames() {
212         $this->_ignore_frames = true;
213     }
215     /**
216      *    Enables frames support. Frames will be fetched from
217      *    now on.
218      *    @access public
219      */
220     function useFrames() {
221         $this->_ignore_frames = false;
222     }
223     
224     /**
225      *    Switches off cookie sending and recieving.
226      *    @access public
227      */
228     function ignoreCookies() {
229         $this->_user_agent->ignoreCookies();
230     }
231     
232     /**
233      *    Switches back on the cookie sending and recieving.
234      *    @access public
235      */
236     function useCookies() {
237         $this->_user_agent->useCookies();
238     }
240     /**
241      *    Parses the raw content into a page. Will load further
242      *    frame pages unless frames are disabled.
243      *    @param SimpleHttpResponse $response    Response from fetch.
244      *    @param integer $depth                  Nested frameset depth.
245      *    @return SimplePage                     Parsed HTML.
246      *    @access private
247      */
248     function &_parse($response, $depth = 0) {
249         $page = &$this->_buildPage($response);
250         if ($this->_ignore_frames || ! $page->hasFrames() || ($depth > $this->_maximum_nested_frames)) {
251             return $page;
252         }
253         $frameset = new SimpleFrameset($page);
254         foreach ($page->getFrameset() as $key => $url) {
255             $frame = &$this->_fetch($url, new SimpleGetEncoding(), $depth + 1);
256             $frameset->addFrame($frame, $key);
257         }
258         return $frameset;
259     }
260     
261     /**
262      *    Assembles the parsing machinery and actually parses
263      *    a single page. Frees all of the builder memory and so
264      *    unjams the PHP memory management.
265      *    @param SimpleHttpResponse $response    Response from fetch.
266      *    @return SimplePage                     Parsed top level page.
267      *    @access protected
268      */
269     function &_buildPage($response) {
270         $builder = new SimplePageBuilder();
271         $page = &$builder->parse($response);
272         $builder->free();
273         unset($builder);
274         return $page;
275     }
277     /**
278      *    Fetches a page. Jointly recursive with the _parse()
279      *    method as it descends a frameset.
280      *    @param string/SimpleUrl $url          Target to fetch.
281      *    @param SimpleEncoding $encoding       GET/POST parameters.
282      *    @param integer $depth                 Nested frameset depth protection.
283      *    @return SimplePage                    Parsed page.
284      *    @access private
285      */
286     function &_fetch($url, $encoding, $depth = 0) {
287         $response = &$this->_user_agent->fetchResponse($url, $encoding);
288         if ($response->isError()) {
289             $page = new SimplePage($response);
290         } else {
291             $page = &$this->_parse($response, $depth);
292         }
293         return $page;
294     }
296     /**
297      *    Fetches a page or a single frame if that is the current
298      *    focus.
299      *    @param SimpleUrl $url                   Target to fetch.
300      *    @param SimpleEncoding $parameters       GET/POST parameters.
301      *    @return string                          Raw content of page.
302      *    @access private
303      */
304     function _load($url, $parameters) {
305         $frame = $url->getTarget();
306         if (! $frame || ! $this->_page->hasFrames() || (strtolower($frame) == '_top')) {
307             return $this->_loadPage($url, $parameters);
308         }
309         return $this->_loadFrame(array($frame), $url, $parameters);
310     }
312     /**
313      *    Fetches a page and makes it the current page/frame.
314      *    @param string/SimpleUrl $url            Target to fetch as string.
315      *    @param SimplePostEncoding $parameters   POST parameters.
316      *    @return string                          Raw content of page.
317      *    @access private
318      */
319     function _loadPage($url, $parameters) {
320         $this->_page = &$this->_fetch($url, $parameters);
321         $this->_history->recordEntry(
322                 $this->_page->getUrl(),
323                 $this->_page->getRequestData());
324         return $this->_page->getRaw();
325     }
327     /**
328      *    Fetches a frame into the existing frameset replacing the
329      *    original.
330      *    @param array $frames                    List of names to drill down.
331      *    @param string/SimpleUrl $url            Target to fetch as string.
332      *    @param SimpleFormEncoding $parameters   POST parameters.
333      *    @return string                          Raw content of page.
334      *    @access private
335      */
336     function _loadFrame($frames, $url, $parameters) {
337         $page = &$this->_fetch($url, $parameters);
338         $this->_page->setFrame($frames, $page);
339         return $page->getRaw();
340     }
342     /**
343      *    Removes expired and temporary cookies as if
344      *    the browser was closed and re-opened.
345      *    @param string/integer $date   Time when session restarted.
346      *                                  If omitted then all persistent
347      *                                  cookies are kept.
348      *    @access public
349      */
350     function restart($date = false) {
351         $this->_user_agent->restart($date);
352     }
354     /**
355      *    Adds a header to every fetch.
356      *    @param string $header       Header line to add to every
357      *                                request until cleared.
358      *    @access public
359      */
360     function addHeader($header) {
361         $this->_user_agent->addHeader($header);
362     }
364     /**
365      *    Ages the cookies by the specified time.
366      *    @param integer $interval    Amount in seconds.
367      *    @access public
368      */
369     function ageCookies($interval) {
370         $this->_user_agent->ageCookies($interval);
371     }
373     /**
374      *    Sets an additional cookie. If a cookie has
375      *    the same name and path it is replaced.
376      *    @param string $name       Cookie key.
377      *    @param string $value      Value of cookie.
378      *    @param string $host       Host upon which the cookie is valid.
379      *    @param string $path       Cookie path if not host wide.
380      *    @param string $expiry     Expiry date.
381      *    @access public
382      */
383     function setCookie($name, $value, $host = false, $path = '/', $expiry = false) {
384         $this->_user_agent->setCookie($name, $value, $host, $path, $expiry);
385     }
387     /**
388      *    Reads the most specific cookie value from the
389      *    browser cookies.
390      *    @param string $host        Host to search.
391      *    @param string $path        Applicable path.
392      *    @param string $name        Name of cookie to read.
393      *    @return string             False if not present, else the
394      *                               value as a string.
395      *    @access public
396      */
397     function getCookieValue($host, $path, $name) {
398         return $this->_user_agent->getCookieValue($host, $path, $name);
399     }
401     /**
402      *    Reads the current cookies for the current URL.
403      *    @param string $name   Key of cookie to find.
404      *    @return string        Null if there is no current URL, false
405      *                          if the cookie is not set.
406      *    @access public
407      */
408     function getCurrentCookieValue($name) {
409         return $this->_user_agent->getBaseCookieValue($name, $this->_page->getUrl());
410     }
412     /**
413      *    Sets the maximum number of redirects before
414      *    a page will be loaded anyway.
415      *    @param integer $max        Most hops allowed.
416      *    @access public
417      */
418     function setMaximumRedirects($max) {
419         $this->_user_agent->setMaximumRedirects($max);
420     }
422     /**
423      *    Sets the maximum number of nesting of framed pages
424      *    within a framed page to prevent loops.
425      *    @param integer $max        Highest depth allowed.
426      *    @access public
427      */
428     function setMaximumNestedFrames($max) {
429         $this->_maximum_nested_frames = $max;
430     }
432     /**
433      *    Sets the socket timeout for opening a connection.
434      *    @param integer $timeout      Maximum time in seconds.
435      *    @access public
436      */
437     function setConnectionTimeout($timeout) {
438         $this->_user_agent->setConnectionTimeout($timeout);
439     }
441     /**
442      *    Sets proxy to use on all requests for when
443      *    testing from behind a firewall. Set URL
444      *    to false to disable.
445      *    @param string $proxy        Proxy URL.
446      *    @param string $username     Proxy username for authentication.
447      *    @param string $password     Proxy password for authentication.
448      *    @access public
449      */
450     function useProxy($proxy, $username = false, $password = false) {
451         $this->_user_agent->useProxy($proxy, $username, $password);
452     }
454     /**
455      *    Fetches the page content with a HEAD request.
456      *    Will affect cookies, but will not change the base URL.
457      *    @param string/SimpleUrl $url                Target to fetch as string.
458      *    @param hash/SimpleHeadEncoding $parameters  Additional parameters for
459      *                                                HEAD request.
460      *    @return boolean                             True if successful.
461      *    @access public
462      */
463     function head($url, $parameters = false) {
464         if (! is_object($url)) {
465             $url = new SimpleUrl($url);
466         }
467         if ($this->getUrl()) {
468             $url = $url->makeAbsolute($this->getUrl());
469         }
470         $response = &$this->_user_agent->fetchResponse($url, new SimpleHeadEncoding($parameters));
471         return ! $response->isError();
472     }
474     /**
475      *    Fetches the page content with a simple GET request.
476      *    @param string/SimpleUrl $url                Target to fetch.
477      *    @param hash/SimpleFormEncoding $parameters  Additional parameters for
478      *                                                GET request.
479      *    @return string                              Content of page or false.
480      *    @access public
481      */
482     function get($url, $parameters = false) {
483         if (! is_object($url)) {
484             $url = new SimpleUrl($url);
485         }
486         if ($this->getUrl()) {
487             $url = $url->makeAbsolute($this->getUrl());
488         }
489         return $this->_load($url, new SimpleGetEncoding($parameters));
490     }
492     /**
493      *    Fetches the page content with a POST request.
494      *    @param string/SimpleUrl $url                Target to fetch as string.
495      *    @param hash/SimpleFormEncoding $parameters  POST parameters.
496      *    @return string                              Content of page.
497      *    @access public
498      */
499     function post($url, $parameters = false) {
500         if (! is_object($url)) {
501             $url = new SimpleUrl($url);
502         }
503         if ($this->getUrl()) {
504             $url = $url->makeAbsolute($this->getUrl());
505         }
506         return $this->_load($url, new SimplePostEncoding($parameters));
507     }
509     /**
510      *    Equivalent to hitting the retry button on the
511      *    browser. Will attempt to repeat the page fetch. If
512      *    there is no history to repeat it will give false.
513      *    @return string/boolean   Content if fetch succeeded
514      *                             else false.
515      *    @access public
516      */
517     function retry() {
518         $frames = $this->_page->getFrameFocus();
519         if (count($frames) > 0) {
520             $this->_loadFrame(
521                     $frames,
522                     $this->_page->getUrl(),
523                     $this->_page->getRequestData());
524             return $this->_page->getRaw();
525         }
526         if ($url = $this->_history->getUrl()) {
527             $this->_page = &$this->_fetch($url, $this->_history->getParameters());
528             return $this->_page->getRaw();
529         }
530         return false;
531     }
533     /**
534      *    Equivalent to hitting the back button on the
535      *    browser. The browser history is unchanged on
536      *    failure. The page content is refetched as there
537      *    is no concept of content caching in SimpleTest.
538      *    @return boolean     True if history entry and
539      *                        fetch succeeded
540      *    @access public
541      */
542     function back() {
543         if (! $this->_history->back()) {
544             return false;
545         }
546         $content = $this->retry();
547         if (! $content) {
548             $this->_history->forward();
549         }
550         return $content;
551     }
553     /**
554      *    Equivalent to hitting the forward button on the
555      *    browser. The browser history is unchanged on
556      *    failure. The page content is refetched as there
557      *    is no concept of content caching in SimpleTest.
558      *    @return boolean     True if history entry and
559      *                        fetch succeeded
560      *    @access public
561      */
562     function forward() {
563         if (! $this->_history->forward()) {
564             return false;
565         }
566         $content = $this->retry();
567         if (! $content) {
568             $this->_history->back();
569         }
570         return $content;
571     }
573     /**
574      *    Retries a request after setting the authentication
575      *    for the current realm.
576      *    @param string $username    Username for realm.
577      *    @param string $password    Password for realm.
578      *    @return boolean            True if successful fetch. Note
579      *                               that authentication may still have
580      *                               failed.
581      *    @access public
582      */
583     function authenticate($username, $password) {
584         if (! $this->_page->getRealm()) {
585             return false;
586         }
587         $url = $this->_page->getUrl();
588         if (! $url) {
589             return false;
590         }
591         $this->_user_agent->setIdentity(
592                 $url->getHost(),
593                 $this->_page->getRealm(),
594                 $username,
595                 $password);
596         return $this->retry();
597     }
599     /**
600      *    Accessor for a breakdown of the frameset.
601      *    @return array   Hash tree of frames by name
602      *                    or index if no name.
603      *    @access public
604      */
605     function getFrames() {
606         return $this->_page->getFrames();
607     }
609     /**
610      *    Accessor for current frame focus. Will be
611      *    false if no frame has focus.
612      *    @return integer/string/boolean    Label if any, otherwise
613      *                                      the position in the frameset
614      *                                      or false if none.
615      *    @access public
616      */
617     function getFrameFocus() {
618         return $this->_page->getFrameFocus();
619     }
621     /**
622      *    Sets the focus by index. The integer index starts from 1.
623      *    @param integer $choice    Chosen frame.
624      *    @return boolean           True if frame exists.
625      *    @access public
626      */
627     function setFrameFocusByIndex($choice) {
628         return $this->_page->setFrameFocusByIndex($choice);
629     }
631     /**
632      *    Sets the focus by name.
633      *    @param string $name    Chosen frame.
634      *    @return boolean        True if frame exists.
635      *    @access public
636      */
637     function setFrameFocus($name) {
638         return $this->_page->setFrameFocus($name);
639     }
641     /**
642      *    Clears the frame focus. All frames will be searched
643      *    for content.
644      *    @access public
645      */
646     function clearFrameFocus() {
647         return $this->_page->clearFrameFocus();
648     }
650     /**
651      *    Accessor for last error.
652      *    @return string        Error from last response.
653      *    @access public
654      */
655     function getTransportError() {
656         return $this->_page->getTransportError();
657     }
659     /**
660      *    Accessor for current MIME type.
661      *    @return string    MIME type as string; e.g. 'text/html'
662      *    @access public
663      */
664     function getMimeType() {
665         return $this->_page->getMimeType();
666     }
668     /**
669      *    Accessor for last response code.
670      *    @return integer    Last HTTP response code received.
671      *    @access public
672      */
673     function getResponseCode() {
674         return $this->_page->getResponseCode();
675     }
677     /**
678      *    Accessor for last Authentication type. Only valid
679      *    straight after a challenge (401).
680      *    @return string    Description of challenge type.
681      *    @access public
682      */
683     function getAuthentication() {
684         return $this->_page->getAuthentication();
685     }
687     /**
688      *    Accessor for last Authentication realm. Only valid
689      *    straight after a challenge (401).
690      *    @return string    Name of security realm.
691      *    @access public
692      */
693     function getRealm() {
694         return $this->_page->getRealm();
695     }
697     /**
698      *    Accessor for current URL of page or frame if
699      *    focused.
700      *    @return string    Location of current page or frame as
701      *                      a string.
702      */
703     function getUrl() {
704         $url = $this->_page->getUrl();
705         return $url ? $url->asString() : false;
706     }
708     /**
709      *    Accessor for base URL of page if set via BASE tag
710      *    @return string    base URL
711      */
712     function getBaseUrl() {
713         $url = $this->_page->getBaseUrl();
714         return $url ? $url->asString() : false;
715     }
717     /**
718      *    Accessor for raw bytes sent down the wire.
719      *    @return string      Original text sent.
720      *    @access public
721      */
722     function getRequest() {
723         return $this->_page->getRequest();
724     }
726     /**
727      *    Accessor for raw header information.
728      *    @return string      Header block.
729      *    @access public
730      */
731     function getHeaders() {
732         return $this->_page->getHeaders();
733     }
735     /**
736      *    Accessor for raw page information.
737      *    @return string      Original text content of web page.
738      *    @access public
739      */
740     function getContent() {
741         return $this->_page->getRaw();
742     }
744     /**
745      *    Accessor for plain text version of the page.
746      *    @return string      Normalised text representation.
747      *    @access public
748      */
749     function getContentAsText() {
750         return $this->_page->getText();
751     }
753     /**
754      *    Accessor for parsed title.
755      *    @return string     Title or false if no title is present.
756      *    @access public
757      */
758     function getTitle() {
759         return $this->_page->getTitle();
760     }
762     /**
763      *    Accessor for a list of all links in current page.
764      *    @return array   List of urls with scheme of
765      *                    http or https and hostname.
766      *    @access public
767      */
768     function getUrls() {
769         return $this->_page->getUrls();
770     }
772     /**
773      *    Sets all form fields with that name.
774      *    @param string $label   Name or label of field in forms.
775      *    @param string $value   New value of field.
776      *    @return boolean        True if field exists, otherwise false.
777      *    @access public
778      */
779     function setField($label, $value, $position=false) {
780         return $this->_page->setField(new SimpleByLabelOrName($label), $value, $position);
781     }
783     /**
784      *    Sets all form fields with that name. Will use label if
785      *    one is available (not yet implemented).
786      *    @param string $name    Name of field in forms.
787      *    @param string $value   New value of field.
788      *    @return boolean        True if field exists, otherwise false.
789      *    @access public
790      */
791     function setFieldByName($name, $value, $position=false) {
792         return $this->_page->setField(new SimpleByName($name), $value, $position);
793     }
795     /**
796      *    Sets all form fields with that id attribute.
797      *    @param string/integer $id   Id of field in forms.
798      *    @param string $value        New value of field.
799      *    @return boolean             True if field exists, otherwise false.
800      *    @access public
801      */
802     function setFieldById($id, $value) {
803         return $this->_page->setField(new SimpleById($id), $value);
804     }
806     /**
807      *    Accessor for a form element value within the page.
808      *    Finds the first match.
809      *    @param string $label       Field label.
810      *    @return string/boolean     A value if the field is
811      *                               present, false if unchecked
812      *                               and null if missing.
813      *    @access public
814      */
815     function getField($label) {
816         return $this->_page->getField(new SimpleByLabelOrName($label));
817     }
819     /**
820      *    Accessor for a form element value within the page.
821      *    Finds the first match.
822      *    @param string $name        Field name.
823      *    @return string/boolean     A string if the field is
824      *                               present, false if unchecked
825      *                               and null if missing.
826      *    @access public
827      */
828     function getFieldByName($name) {
829         return $this->_page->getField(new SimpleByName($name));
830     }
832     /**
833      *    Accessor for a form element value within the page.
834      *    @param string/integer $id  Id of field in forms.
835      *    @return string/boolean     A string if the field is
836      *                               present, false if unchecked
837      *                               and null if missing.
838      *    @access public
839      */
840     function getFieldById($id) {
841         return $this->_page->getField(new SimpleById($id));
842     }
844     /**
845      *    Clicks the submit button by label. The owning
846      *    form will be submitted by this.
847      *    @param string $label    Button label. An unlabeled
848      *                            button can be triggered by 'Submit'.
849      *    @param hash $additional Additional form data.
850      *    @return string/boolean  Page on success.
851      *    @access public
852      */
853     function clickSubmit($label = 'Submit', $additional = false) {
854         if (! ($form = &$this->_page->getFormBySubmit(new SimpleByLabel($label)))) {
855             return false;
856         }
857         $success = $this->_load(
858                 $form->getAction(),
859                 $form->submitButton(new SimpleByLabel($label), $additional));
860         return ($success ? $this->getContent() : $success);
861     }
863     /**
864      *    Clicks the submit button by name attribute. The owning
865      *    form will be submitted by this.
866      *    @param string $name     Button name.
867      *    @param hash $additional Additional form data.
868      *    @return string/boolean  Page on success.
869      *    @access public
870      */
871     function clickSubmitByName($name, $additional = false) {
872         if (! ($form = &$this->_page->getFormBySubmit(new SimpleByName($name)))) {
873             return false;
874         }
875         $success = $this->_load(
876                 $form->getAction(),
877                 $form->submitButton(new SimpleByName($name), $additional));
878         return ($success ? $this->getContent() : $success);
879     }
881     /**
882      *    Clicks the submit button by ID attribute of the button
883      *    itself. The owning form will be submitted by this.
884      *    @param string $id       Button ID.
885      *    @param hash $additional Additional form data.
886      *    @return string/boolean  Page on success.
887      *    @access public
888      */
889     function clickSubmitById($id, $additional = false) {
890         if (! ($form = &$this->_page->getFormBySubmit(new SimpleById($id)))) {
891             return false;
892         }
893         $success = $this->_load(
894                 $form->getAction(),
895                 $form->submitButton(new SimpleById($id), $additional));
896         return ($success ? $this->getContent() : $success);
897     }
898     
899     /**
900      *    Tests to see if a submit button exists with this
901      *    label.
902      *    @param string $label    Button label.
903      *    @return boolean         True if present.
904      *    @access public
905      */
906     function isSubmit($label) {
907         return (boolean)$this->_page->getFormBySubmit(new SimpleByLabel($label));
908     }
910     /**
911      *    Clicks the submit image by some kind of label. Usually
912      *    the alt tag or the nearest equivalent. The owning
913      *    form will be submitted by this. Clicking outside of
914      *    the boundary of the coordinates will result in
915      *    a failure.
916      *    @param string $label    ID attribute of button.
917      *    @param integer $x       X-coordinate of imaginary click.
918      *    @param integer $y       Y-coordinate of imaginary click.
919      *    @param hash $additional Additional form data.
920      *    @return string/boolean  Page on success.
921      *    @access public
922      */
923     function clickImage($label, $x = 1, $y = 1, $additional = false) {
924         if (! ($form = &$this->_page->getFormByImage(new SimpleByLabel($label)))) {
925             return false;
926         }
927         $success = $this->_load(
928                 $form->getAction(),
929                 $form->submitImage(new SimpleByLabel($label), $x, $y, $additional));
930         return ($success ? $this->getContent() : $success);
931     }
933     /**
934      *    Clicks the submit image by the name. Usually
935      *    the alt tag or the nearest equivalent. The owning
936      *    form will be submitted by this. Clicking outside of
937      *    the boundary of the coordinates will result in
938      *    a failure.
939      *    @param string $name     Name attribute of button.
940      *    @param integer $x       X-coordinate of imaginary click.
941      *    @param integer $y       Y-coordinate of imaginary click.
942      *    @param hash $additional Additional form data.
943      *    @return string/boolean  Page on success.
944      *    @access public
945      */
946     function clickImageByName($name, $x = 1, $y = 1, $additional = false) {
947         if (! ($form = &$this->_page->getFormByImage(new SimpleByName($name)))) {
948             return false;
949         }
950         $success = $this->_load(
951                 $form->getAction(),
952                 $form->submitImage(new SimpleByName($name), $x, $y, $additional));
953         return ($success ? $this->getContent() : $success);
954     }
956     /**
957      *    Clicks the submit image by ID attribute. The owning
958      *    form will be submitted by this. Clicking outside of
959      *    the boundary of the coordinates will result in
960      *    a failure.
961      *    @param integer/string $id    ID attribute of button.
962      *    @param integer $x            X-coordinate of imaginary click.
963      *    @param integer $y            Y-coordinate of imaginary click.
964      *    @param hash $additional      Additional form data.
965      *    @return string/boolean       Page on success.
966      *    @access public
967      */
968     function clickImageById($id, $x = 1, $y = 1, $additional = false) {
969         if (! ($form = &$this->_page->getFormByImage(new SimpleById($id)))) {
970             return false;
971         }
972         $success = $this->_load(
973                 $form->getAction(),
974                 $form->submitImage(new SimpleById($id), $x, $y, $additional));
975         return ($success ? $this->getContent() : $success);
976     }
977     
978     /**
979      *    Tests to see if an image exists with this
980      *    title or alt text.
981      *    @param string $label    Image text.
982      *    @return boolean         True if present.
983      *    @access public
984      */
985     function isImage($label) {
986         return (boolean)$this->_page->getFormByImage(new SimpleByLabel($label));
987     }
989     /**
990      *    Submits a form by the ID.
991      *    @param string $id       The form ID. No submit button value
992      *                            will be sent.
993      *    @return string/boolean  Page on success.
994      *    @access public
995      */
996     function submitFormById($id) {
997         if (! ($form = &$this->_page->getFormById($id))) {
998             return false;
999         }
1000         $success = $this->_load(
1001                 $form->getAction(),
1002                 $form->submit());
1003         return ($success ? $this->getContent() : $success);
1004     }
1006     /**
1007      *    Finds a URL by label. Will find the first link
1008      *    found with this link text by default, or a later
1009      *    one if an index is given. The match ignores case and
1010      *    white space issues.
1011      *    @param string $label     Text between the anchor tags.
1012      *    @param integer $index    Link position counting from zero.
1013      *    @return string/boolean   URL on success.
1014      *    @access public
1015      */
1016     function getLink($label, $index = 0) {
1017         $urls = $this->_page->getUrlsByLabel($label);
1018         if (count($urls) == 0) {
1019             return false;
1020         }
1021         if (count($urls) < $index + 1) {
1022             return false;
1023         }
1024         return $urls[$index];
1025     }
1027     /**
1028      *    Follows a link by label. Will click the first link
1029      *    found with this link text by default, or a later
1030      *    one if an index is given. The match ignores case and
1031      *    white space issues.
1032      *    @param string $label     Text between the anchor tags.
1033      *    @param integer $index    Link position counting from zero.
1034      *    @return string/boolean   Page on success.
1035      *    @access public
1036      */
1037     function clickLink($label, $index = 0) {
1038         $url = $this->getLink($label, $index);
1039         if ($url === false) {
1040             return false;
1041         }
1042         $this->_load($url, new SimpleGetEncoding());
1043         return $this->getContent();
1044     }
1045     
1046     /**
1047      *    Finds a link by id attribute.
1048      *    @param string $id        ID attribute value.
1049      *    @return string/boolean   URL on success.
1050      *    @access public
1051      */
1052     function getLinkById($id) {
1053         return $this->_page->getUrlById($id);
1054     }
1056     /**
1057      *    Follows a link by id attribute.
1058      *    @param string $id        ID attribute value.
1059      *    @return string/boolean   Page on success.
1060      *    @access public
1061      */
1062     function clickLinkById($id) {
1063         if (! ($url = $this->getLinkById($id))) {
1064             return false;
1065         }
1066         $this->_load($url, new SimpleGetEncoding());
1067         return $this->getContent();
1068     }
1070     /**
1071      *    Clicks a visible text item. Will first try buttons,
1072      *    then links and then images.
1073      *    @param string $label        Visible text or alt text.
1074      *    @return string/boolean      Raw page or false.
1075      *    @access public
1076      */
1077     function click($label) {
1078         $raw = $this->clickSubmit($label);
1079         if (! $raw) {
1080             $raw = $this->clickLink($label);
1081         }
1082         if (! $raw) {
1083             $raw = $this->clickImage($label);
1084         }
1085         return $raw;
1086     }
1088     /**
1089      *    Tests to see if a click target exists.
1090      *    @param string $label    Visible text or alt text.
1091      *    @return boolean         True if target present.
1092      *    @access public
1093      */
1094     function isClickable($label) {
1095         return $this->isSubmit($label) || ($this->getLink($label) !== false) || $this->isImage($label);
1096     }
1098 ?>