Unit test for #149.
[akelos.git] / vendor / simpletest / page.php
blob47883b0dfe6bd87aaeb450c0598142ab3adb6b42
1 <?php
2 /**
3 * Base include file for SimpleTest
4 * @package SimpleTest
5 * @subpackage WebTester
6 * @version $Id: page.php,v 1.131 2006/02/05 01:07:49 lastcraft Exp $
7 */
9 /**#@+
10 * include other SimpleTest class files
12 require_once(dirname(__FILE__) . '/http.php');
13 require_once(dirname(__FILE__) . '/parser.php');
14 require_once(dirname(__FILE__) . '/tag.php');
15 require_once(dirname(__FILE__) . '/form.php');
16 require_once(dirname(__FILE__) . '/selector.php');
17 /**#@-*/
19 /**
20 * Creates tags and widgets given HTML tag
21 * attributes.
22 * @package SimpleTest
23 * @subpackage WebTester
25 class SimpleTagBuilder {
27 /**
28 * Factory for the tag objects. Creates the
29 * appropriate tag object for the incoming tag name
30 * and attributes.
31 * @param string $name HTML tag name.
32 * @param hash $attributes Element attributes.
33 * @return SimpleTag Tag object.
34 * @access public
36 function createTag($name, $attributes) {
37 static $map = array(
38 'a' => 'SimpleAnchorTag',
39 'title' => 'SimpleTitleTag',
40 'button' => 'SimpleButtonTag',
41 'textarea' => 'SimpleTextAreaTag',
42 'option' => 'SimpleOptionTag',
43 'label' => 'SimpleLabelTag',
44 'form' => 'SimpleFormTag',
45 'frame' => 'SimpleFrameTag');
46 $attributes = $this->_keysToLowerCase($attributes);
47 if (array_key_exists($name, $map)) {
48 $tag_class = $map[$name];
49 return new $tag_class($attributes);
50 } elseif ($name == 'select') {
51 return $this->_createSelectionTag($attributes);
52 } elseif ($name == 'input') {
53 return $this->_createInputTag($attributes);
55 return new SimpleTag($name, $attributes);
58 /**
59 * Factory for selection fields.
60 * @param hash $attributes Element attributes.
61 * @return SimpleTag Tag object.
62 * @access protected
64 function _createSelectionTag($attributes) {
65 if (isset($attributes['multiple'])) {
66 return new MultipleSelectionTag($attributes);
68 return new SimpleSelectionTag($attributes);
71 /**
72 * Factory for input tags.
73 * @param hash $attributes Element attributes.
74 * @return SimpleTag Tag object.
75 * @access protected
77 function _createInputTag($attributes) {
78 if (! isset($attributes['type'])) {
79 return new SimpleTextTag($attributes);
81 $type = strtolower(trim($attributes['type']));
82 $map = array(
83 'submit' => 'SimpleSubmitTag',
84 'image' => 'SimpleImageSubmitTag',
85 'checkbox' => 'SimpleCheckboxTag',
86 'radio' => 'SimpleRadioButtonTag',
87 'text' => 'SimpleTextTag',
88 'hidden' => 'SimpleTextTag',
89 'password' => 'SimpleTextTag',
90 'file' => 'SimpleUploadTag');
91 if (array_key_exists($type, $map)) {
92 $tag_class = $map[$type];
93 return new $tag_class($attributes);
95 return false;
98 /**
99 * Make the keys lower case for case insensitive look-ups.
100 * @param hash $map Hash to convert.
101 * @return hash Unchanged values, but keys lower case.
102 * @access private
104 function _keysToLowerCase($map) {
105 $lower = array();
106 foreach ($map as $key => $value) {
107 $lower[strtolower($key)] = $value;
109 return $lower;
114 * SAX event handler. Maintains a list of
115 * open tags and dispatches them as they close.
116 * @package SimpleTest
117 * @subpackage WebTester
119 class SimplePageBuilder extends SimpleSaxListener {
120 var $_tags;
121 var $_page;
122 var $_private_content_tag;
125 * Sets the builder up empty.
126 * @access public
128 function SimplePageBuilder() {
129 $this->SimpleSaxListener();
133 * Frees up any references so as to allow the PHP garbage
134 * collection from unset() to work.
135 * @access public
137 function free() {
138 unset($this->_tags);
139 unset($this->_page);
140 unset($this->_private_content_tags);
144 * Reads the raw content and send events
145 * into the page to be built.
146 * @param $response SimpleHttpResponse Fetched response.
147 * @return SimplePage Newly parsed page.
148 * @access public
150 function &parse($response) {
151 $this->_tags = array();
152 $this->_page = &$this->_createPage($response);
153 $parser = &$this->_createParser($this);
154 $parser->parse($response->getContent());
155 $this->_page->acceptPageEnd();
156 return $this->_page;
160 * Creates an empty page.
161 * @return SimplePage New unparsed page.
162 * @access protected
164 function &_createPage($response) {
165 $page = &new SimplePage($response);
166 return $page;
170 * Creates the parser used with the builder.
171 * @param $listener SimpleSaxListener Target of parser.
172 * @return SimpleSaxParser Parser to generate
173 * events for the builder.
174 * @access protected
176 function &_createParser(&$listener) {
177 $parser = &new SimpleHtmlSaxParser($listener);
178 return $parser;
182 * Start of element event. Opens a new tag.
183 * @param string $name Element name.
184 * @param hash $attributes Attributes without content
185 * are marked as true.
186 * @return boolean False on parse error.
187 * @access public
189 function startElement($name, $attributes) {
190 $factory = &new SimpleTagBuilder();
191 $tag = $factory->createTag($name, $attributes);
192 if (! $tag) {
193 return true;
195 if ($tag->getTagName() == 'label') {
196 $this->_page->acceptLabelStart($tag);
197 $this->_openTag($tag);
198 return true;
200 if ($tag->getTagName() == 'form') {
201 $this->_page->acceptFormStart($tag);
202 return true;
204 if ($tag->getTagName() == 'frameset') {
205 $this->_page->acceptFramesetStart($tag);
206 return true;
208 if ($tag->getTagName() == 'frame') {
209 $this->_page->acceptFrame($tag);
210 return true;
212 if ($tag->isPrivateContent() && ! isset($this->_private_content_tag)) {
213 $this->_private_content_tag = &$tag;
215 if ($tag->expectEndTag()) {
216 $this->_openTag($tag);
217 return true;
219 $this->_page->acceptTag($tag);
220 return true;
224 * End of element event.
225 * @param string $name Element name.
226 * @return boolean False on parse error.
227 * @access public
229 function endElement($name) {
230 if ($name == 'label') {
231 $this->_page->acceptLabelEnd();
232 return true;
234 if ($name == 'form') {
235 $this->_page->acceptFormEnd();
236 return true;
238 if ($name == 'frameset') {
239 $this->_page->acceptFramesetEnd();
240 return true;
242 if ($this->_hasNamedTagOnOpenTagStack($name)) {
243 $tag = array_pop($this->_tags[$name]);
244 if ($tag->isPrivateContent() && $this->_private_content_tag->getTagName() == $name) {
245 unset($this->_private_content_tag);
247 $this->_addContentTagToOpenTags($tag);
248 $this->_page->acceptTag($tag);
249 return true;
251 return true;
255 * Test to see if there are any open tags awaiting
256 * closure that match the tag name.
257 * @param string $name Element name.
258 * @return boolean True if any are still open.
259 * @access private
261 function _hasNamedTagOnOpenTagStack($name) {
262 return isset($this->_tags[$name]) && (count($this->_tags[$name]) > 0);
266 * Unparsed, but relevant data. The data is added
267 * to every open tag.
268 * @param string $text May include unparsed tags.
269 * @return boolean False on parse error.
270 * @access public
272 function addContent($text) {
273 if (isset($this->_private_content_tag)) {
274 $this->_private_content_tag->addContent($text);
275 } else {
276 $this->_addContentToAllOpenTags($text);
278 return true;
282 * Any content fills all currently open tags unless it
283 * is part of an option tag.
284 * @param string $text May include unparsed tags.
285 * @access private
287 function _addContentToAllOpenTags($text) {
288 foreach (array_keys($this->_tags) as $name) {
289 for ($i = 0, $count = count($this->_tags[$name]); $i < $count; $i++) {
290 $this->_tags[$name][$i]->addContent($text);
296 * Parsed data in tag form. The parsed tag is added
297 * to every open tag. Used for adding options to select
298 * fields only.
299 * @param SimpleTag $tag Option tags only.
300 * @access private
302 function _addContentTagToOpenTags(&$tag) {
303 if ($tag->getTagName() != 'option') {
304 return;
306 foreach (array_keys($this->_tags) as $name) {
307 for ($i = 0, $count = count($this->_tags[$name]); $i < $count; $i++) {
308 $this->_tags[$name][$i]->addTag($tag);
314 * Opens a tag for receiving content. Multiple tags
315 * will be receiving input at the same time.
316 * @param SimpleTag $tag New content tag.
317 * @access private
319 function _openTag(&$tag) {
320 $name = $tag->getTagName();
321 if (! in_array($name, array_keys($this->_tags))) {
322 $this->_tags[$name] = array();
324 $this->_tags[$name][] = &$tag;
329 * A wrapper for a web page.
330 * @package SimpleTest
331 * @subpackage WebTester
333 class SimplePage {
334 var $_links;
335 var $_title;
336 var $_last_widget;
337 var $_label;
338 var $_left_over_labels;
339 var $_open_forms;
340 var $_complete_forms;
341 var $_frameset;
342 var $_frames;
343 var $_frameset_nesting_level;
344 var $_transport_error;
345 var $_raw;
346 var $_text;
347 var $_sent;
348 var $_headers;
349 var $_method;
350 var $_url;
351 var $_request_data;
354 * Parses a page ready to access it's contents.
355 * @param SimpleHttpResponse $response Result of HTTP fetch.
356 * @access public
358 function SimplePage($response = false) {
359 $this->_links = array();
360 $this->_title = false;
361 $this->_left_over_labels = array();
362 $this->_open_forms = array();
363 $this->_complete_forms = array();
364 $this->_frameset = false;
365 $this->_frames = array();
366 $this->_frameset_nesting_level = 0;
367 $this->_text = false;
368 if ($response) {
369 $this->_extractResponse($response);
370 } else {
371 $this->_noResponse();
376 * Extracts all of the response information.
377 * @param SimpleHttpResponse $response Response being parsed.
378 * @access private
380 function _extractResponse($response) {
381 $this->_transport_error = $response->getError();
382 $this->_raw = $response->getContent();
383 $this->_sent = $response->getSent();
384 $this->_headers = $response->getHeaders();
385 $this->_method = $response->getMethod();
386 $this->_url = $response->getUrl();
387 $this->_request_data = $response->getRequestData();
391 * Sets up a missing response.
392 * @access private
394 function _noResponse() {
395 $this->_transport_error = 'No page fetched yet';
396 $this->_raw = false;
397 $this->_sent = false;
398 $this->_headers = false;
399 $this->_method = 'GET';
400 $this->_url = false;
401 $this->_request_data = false;
405 * Original request as bytes sent down the wire.
406 * @return mixed Sent content.
407 * @access public
409 function getRequest() {
410 return $this->_sent;
414 * Accessor for raw text of page.
415 * @return string Raw unparsed content.
416 * @access public
418 function getRaw() {
419 return $this->_raw;
423 * Accessor for plain text of page as a text browser
424 * would see it.
425 * @return string Plain text of page.
426 * @access public
428 function getText() {
429 if (! $this->_text) {
430 $this->_text = SimpleHtmlSaxParser::normalise($this->_raw);
432 return $this->_text;
436 * Accessor for raw headers of page.
437 * @return string Header block as text.
438 * @access public
440 function getHeaders() {
441 if ($this->_headers) {
442 return $this->_headers->getRaw();
444 return false;
448 * Original request method.
449 * @return string GET, POST or HEAD.
450 * @access public
452 function getMethod() {
453 return $this->_method;
457 * Original resource name.
458 * @return SimpleUrl Current url.
459 * @access public
461 function getUrl() {
462 return $this->_url;
466 * Original request data.
467 * @return mixed Sent content.
468 * @access public
470 function getRequestData() {
471 return $this->_request_data;
475 * Accessor for last error.
476 * @return string Error from last response.
477 * @access public
479 function getTransportError() {
480 return $this->_transport_error;
484 * Accessor for current MIME type.
485 * @return string MIME type as string; e.g. 'text/html'
486 * @access public
488 function getMimeType() {
489 if ($this->_headers) {
490 return $this->_headers->getMimeType();
492 return false;
496 * Accessor for HTTP response code.
497 * @return integer HTTP response code received.
498 * @access public
500 function getResponseCode() {
501 if ($this->_headers) {
502 return $this->_headers->getResponseCode();
504 return false;
508 * Accessor for last Authentication type. Only valid
509 * straight after a challenge (401).
510 * @return string Description of challenge type.
511 * @access public
513 function getAuthentication() {
514 if ($this->_headers) {
515 return $this->_headers->getAuthentication();
517 return false;
521 * Accessor for last Authentication realm. Only valid
522 * straight after a challenge (401).
523 * @return string Name of security realm.
524 * @access public
526 function getRealm() {
527 if ($this->_headers) {
528 return $this->_headers->getRealm();
530 return false;
534 * Accessor for current frame focus. Will be
535 * false as no frames.
536 * @return array Always empty.
537 * @access public
539 function getFrameFocus() {
540 return array();
544 * Sets the focus by index. The integer index starts from 1.
545 * @param integer $choice Chosen frame.
546 * @return boolean Always false.
547 * @access public
549 function setFrameFocusByIndex($choice) {
550 return false;
554 * Sets the focus by name. Always fails for a leaf page.
555 * @param string $name Chosen frame.
556 * @return boolean False as no frames.
557 * @access public
559 function setFrameFocus($name) {
560 return false;
564 * Clears the frame focus. Does nothing for a leaf page.
565 * @access public
567 function clearFrameFocus() {
571 * Adds a tag to the page.
572 * @param SimpleTag $tag Tag to accept.
573 * @access public
575 function acceptTag(&$tag) {
576 if ($tag->getTagName() == "a") {
577 $this->_addLink($tag);
578 } elseif ($tag->getTagName() == "title") {
579 $this->_setTitle($tag);
580 } elseif ($this->_isFormElement($tag->getTagName())) {
581 for ($i = 0; $i < count($this->_open_forms); $i++) {
582 $this->_open_forms[$i]->addWidget($tag);
584 $this->_last_widget = &$tag;
589 * Opens a label for a described widget.
590 * @param SimpleFormTag $tag Tag to accept.
591 * @access public
593 function acceptLabelStart(&$tag) {
594 $this->_label = &$tag;
595 unset($this->_last_widget);
599 * Closes the most recently opened label.
600 * @access public
602 function acceptLabelEnd() {
603 if (isset($this->_label)) {
604 if (isset($this->_last_widget)) {
605 $this->_last_widget->setLabel($this->_label->getText());
606 unset($this->_last_widget);
607 } else {
608 $this->_left_over_labels[] = SimpleTestCompatibility::copy($this->_label);
610 unset($this->_label);
615 * Tests to see if a tag is a possible form
616 * element.
617 * @param string $name HTML element name.
618 * @return boolean True if form element.
619 * @access private
621 function _isFormElement($name) {
622 return in_array($name, array('input', 'button', 'textarea', 'select'));
626 * Opens a form. New widgets go here.
627 * @param SimpleFormTag $tag Tag to accept.
628 * @access public
630 function acceptFormStart(&$tag) {
631 $this->_open_forms[] = &new SimpleForm($tag, $this->getUrl());
635 * Closes the most recently opened form.
636 * @access public
638 function acceptFormEnd() {
639 if (count($this->_open_forms)) {
640 $this->_complete_forms[] = array_pop($this->_open_forms);
645 * Opens a frameset. A frameset may contain nested
646 * frameset tags.
647 * @param SimpleFramesetTag $tag Tag to accept.
648 * @access public
650 function acceptFramesetStart(&$tag) {
651 if (! $this->_isLoadingFrames()) {
652 $this->_frameset = &$tag;
654 $this->_frameset_nesting_level++;
658 * Closes the most recently opened frameset.
659 * @access public
661 function acceptFramesetEnd() {
662 if ($this->_isLoadingFrames()) {
663 $this->_frameset_nesting_level--;
668 * Takes a single frame tag and stashes it in
669 * the current frame set.
670 * @param SimpleFrameTag $tag Tag to accept.
671 * @access public
673 function acceptFrame(&$tag) {
674 if ($this->_isLoadingFrames()) {
675 if ($tag->getAttribute('src')) {
676 $this->_frames[] = &$tag;
682 * Test to see if in the middle of reading
683 * a frameset.
684 * @return boolean True if inframeset.
685 * @access private
687 function _isLoadingFrames() {
688 if (! $this->_frameset) {
689 return false;
691 return ($this->_frameset_nesting_level > 0);
695 * Test to see if link is an absolute one.
696 * @param string $url Url to test.
697 * @return boolean True if absolute.
698 * @access protected
700 function _linkIsAbsolute($url) {
701 $parsed = new SimpleUrl($url);
702 return (boolean)($parsed->getScheme() && $parsed->getHost());
706 * Adds a link to the page.
707 * @param SimpleAnchorTag $tag Link to accept.
708 * @access protected
710 function _addLink($tag) {
711 $this->_links[] = $tag;
715 * Marker for end of complete page. Any work in
716 * progress can now be closed.
717 * @access public
719 function acceptPageEnd() {
720 while (count($this->_open_forms)) {
721 $this->_complete_forms[] = array_pop($this->_open_forms);
723 foreach ($this->_left_over_labels as $label) {
724 for ($i = 0, $count = count($this->_complete_forms); $i < $count; $i++) {
725 $this->_complete_forms[$i]->attachLabelBySelector(
726 new SimpleById($label->getFor()),
727 $label->getText());
733 * Test for the presence of a frameset.
734 * @return boolean True if frameset.
735 * @access public
737 function hasFrames() {
738 return (boolean)$this->_frameset;
742 * Accessor for frame name and source URL for every frame that
743 * will need to be loaded. Immediate children only.
744 * @return boolean/array False if no frameset or
745 * otherwise a hash of frame URLs.
746 * The key is either a numerical
747 * base one index or the name attribute.
748 * @access public
750 function getFrameset() {
751 if (! $this->_frameset) {
752 return false;
754 $urls = array();
755 for ($i = 0; $i < count($this->_frames); $i++) {
756 $name = $this->_frames[$i]->getAttribute('name');
757 $url = new SimpleUrl($this->_frames[$i]->getAttribute('src'));
758 $urls[$name ? $name : $i + 1] = $url->makeAbsolute($this->getUrl());
760 return $urls;
764 * Fetches a list of loaded frames.
765 * @return array/string Just the URL for a single page.
766 * @access public
768 function getFrames() {
769 $url = $this->getUrl();
770 return $url->asString();
774 * Accessor for a list of all fixed links.
775 * @return array List of urls with scheme of
776 * http or https and hostname.
777 * @access public
779 function getAbsoluteUrls() {
780 $all = array();
781 foreach ($this->_links as $link) {
782 if ($this->_linkIsAbsolute($link->getHref())) {
783 $all[] = $link->getHref();
786 return $all;
790 * Accessor for a list of all relative links.
791 * @return array List of urls without hostname.
792 * @access public
794 function getRelativeUrls() {
795 $all = array();
796 foreach ($this->_links as $link) {
797 if (! $this->_linkIsAbsolute($link->getHref())) {
798 $all[] = $link->getHref();
801 return $all;
805 * Accessor for URLs by the link label. Label will match
806 * regardess of whitespace issues and case.
807 * @param string $label Text of link.
808 * @return array List of links with that label.
809 * @access public
811 function getUrlsByLabel($label) {
812 $matches = array();
813 foreach ($this->_links as $link) {
814 if ($link->getText() == $label) {
815 $matches[] = $this->_getUrlFromLink($link);
818 return $matches;
822 * Accessor for a URL by the id attribute.
823 * @param string $id Id attribute of link.
824 * @return SimpleUrl URL with that id of false if none.
825 * @access public
827 function getUrlById($id) {
828 foreach ($this->_links as $link) {
829 if ($link->getAttribute('id') === (string)$id) {
830 return $this->_getUrlFromLink($link);
833 return false;
837 * Converts a link into a target URL.
838 * @param SimpleAnchor $link Parsed link.
839 * @return SimpleUrl URL with frame target if any.
840 * @access private
842 function _getUrlFromLink($link) {
843 $url = $this->_makeAbsolute($link->getHref());
844 if ($link->getAttribute('target')) {
845 $url->setTarget($link->getAttribute('target'));
847 return $url;
851 * Expands expandomatic URLs into fully qualified
852 * URLs.
853 * @param SimpleUrl $url Relative URL.
854 * @return SimpleUrl Absolute URL.
855 * @access protected
857 function _makeAbsolute($url) {
858 if (! is_object($url)) {
859 $url = new SimpleUrl($url);
861 return $url->makeAbsolute($this->getUrl());
865 * Sets the title tag contents.
866 * @param SimpleTitleTag $tag Title of page.
867 * @access protected
869 function _setTitle(&$tag) {
870 $this->_title = &$tag;
874 * Accessor for parsed title.
875 * @return string Title or false if no title is present.
876 * @access public
878 function getTitle() {
879 if ($this->_title) {
880 return $this->_title->getText();
882 return false;
886 * Finds a held form by button label. Will only
887 * search correctly built forms.
888 * @param SimpleSelector $selector Button finder.
889 * @return SimpleForm Form object containing
890 * the button.
891 * @access public
893 function &getFormBySubmit($selector) {
894 for ($i = 0; $i < count($this->_complete_forms); $i++) {
895 if ($this->_complete_forms[$i]->hasSubmit($selector)) {
896 return $this->_complete_forms[$i];
899 $null = null;
900 return $null;
904 * Finds a held form by image using a selector.
905 * Will only search correctly built forms.
906 * @param SimpleSelector $selector Image finder.
907 * @return SimpleForm Form object containing
908 * the image.
909 * @access public
911 function &getFormByImage($selector) {
912 for ($i = 0; $i < count($this->_complete_forms); $i++) {
913 if ($this->_complete_forms[$i]->hasImage($selector)) {
914 return $this->_complete_forms[$i];
917 $null = null;
918 return $null;
922 * Finds a held form by the form ID. A way of
923 * identifying a specific form when we have control
924 * of the HTML code.
925 * @param string $id Form label.
926 * @return SimpleForm Form object containing the matching ID.
927 * @access public
929 function &getFormById($id) {
930 for ($i = 0; $i < count($this->_complete_forms); $i++) {
931 if ($this->_complete_forms[$i]->getId() == $id) {
932 return $this->_complete_forms[$i];
935 $null = null;
936 return $null;
940 * Sets a field on each form in which the field is
941 * available.
942 * @param SimpleSelector $selector Field finder.
943 * @param string $value Value to set field to.
944 * @return boolean True if value is valid.
945 * @access public
947 function setField($selector, $value) {
948 $is_set = false;
949 for ($i = 0; $i < count($this->_complete_forms); $i++) {
950 if ($this->_complete_forms[$i]->setField($selector, $value)) {
951 $is_set = true;
954 return $is_set;
958 * Accessor for a form element value within a page.
959 * @param SimpleSelector $selector Field finder.
960 * @return string/boolean A string if the field is
961 * present, false if unchecked
962 * and null if missing.
963 * @access public
965 function getField($selector) {
966 for ($i = 0; $i < count($this->_complete_forms); $i++) {
967 $value = $this->_complete_forms[$i]->getValue($selector);
968 if (isset($value)) {
969 return $value;
972 return null;