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