From da691ecb0cae9c10a8d5732a669f13b1f427d818 Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Wed, 24 Feb 2010 00:53:55 +0000 Subject: [PATCH] MINOR Readded sapphire/thirdparty modules without svn:externals git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/branches/2.3@99765 467b73ca-7a2a-4603-9d3b-597d59a354a9 --- thirdparty/jsmin/JSMin.php | 296 ++ thirdparty/json/JSON.php | 806 ++++ thirdparty/json/LICENSE | 21 + thirdparty/json/Test-JSON.php | 521 +++ thirdparty/simplepie/LICENSE.txt | 26 + thirdparty/simplepie/README.txt | 30 + thirdparty/simplepie/SimplePie.php | 5413 +++++++++++++++++++++++ thirdparty/simpletest/LICENSE | 502 +++ thirdparty/simpletest/VERSION | 1 + thirdparty/simpletest/_manifest_exclude | 0 thirdparty/simpletest/compatibility.php | 173 + thirdparty/simpletest/cookies.php | 380 ++ thirdparty/simpletest/encoding.php | 552 +++ thirdparty/simpletest/form.php | 355 ++ thirdparty/simpletest/http.php | 624 +++ thirdparty/simpletest/page.php | 983 ++++ thirdparty/simpletest/parser.php | 764 ++++ thirdparty/simpletest/selector.php | 137 + thirdparty/simpletest/socket.php | 216 + thirdparty/simpletest/tag.php | 1418 ++++++ thirdparty/simpletest/url.php | 528 +++ thirdparty/spyc/README | 162 + thirdparty/spyc/Spyc.php | 868 ++++ 23 files changed, 14776 insertions(+) create mode 100644 thirdparty/jsmin/JSMin.php create mode 100644 thirdparty/json/JSON.php create mode 100644 thirdparty/json/LICENSE create mode 100644 thirdparty/json/Test-JSON.php create mode 100644 thirdparty/simplepie/LICENSE.txt create mode 100644 thirdparty/simplepie/README.txt create mode 100755 thirdparty/simplepie/SimplePie.php create mode 100644 thirdparty/simpletest/LICENSE create mode 100644 thirdparty/simpletest/VERSION create mode 100644 thirdparty/simpletest/_manifest_exclude create mode 100644 thirdparty/simpletest/compatibility.php create mode 100644 thirdparty/simpletest/cookies.php create mode 100644 thirdparty/simpletest/encoding.php create mode 100644 thirdparty/simpletest/form.php create mode 100644 thirdparty/simpletest/http.php create mode 100644 thirdparty/simpletest/page.php create mode 100644 thirdparty/simpletest/parser.php create mode 100644 thirdparty/simpletest/selector.php create mode 100644 thirdparty/simpletest/socket.php create mode 100644 thirdparty/simpletest/tag.php create mode 100644 thirdparty/simpletest/url.php create mode 100644 thirdparty/spyc/README create mode 100644 thirdparty/spyc/Spyc.php diff --git a/thirdparty/jsmin/JSMin.php b/thirdparty/jsmin/JSMin.php new file mode 100644 index 000000000..d77d08332 --- /dev/null +++ b/thirdparty/jsmin/JSMin.php @@ -0,0 +1,296 @@ + + * @copyright 2002 Douglas Crockford (jsmin.c) + * @copyright 2007 Ryan Grove (PHP port) + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 1.1.0 (2007-06-01) + * @link http://code.google.com/p/jsmin-php/ + */ + +class JSMin { + const ORD_LF = 10; + const ORD_SPACE = 32; + + protected $a = ''; + protected $b = ''; + protected $input = ''; + protected $inputIndex = 0; + protected $inputLength = 0; + protected $lookAhead = null; + protected $output = array(); + + // -- Public Static Methods -------------------------------------------------- + + public static function minify($js) { + $jsmin = new JSMin($js); + return $jsmin->min(); + } + + // -- Public Instance Methods ------------------------------------------------ + + public function __construct($input) { + $this->input = str_replace("\r\n", "\n", $input); + $this->inputLength = strlen($this->input); + } + + // -- Protected Instance Methods --------------------------------------------- + + protected function action($d) { + switch($d) { + case 1: + $this->output[] = $this->a; + + case 2: + $this->a = $this->b; + + if ($this->a === "'" || $this->a === '"') { + for (;;) { + $this->output[] = $this->a; + $this->a = $this->get(); + + if ($this->a === $this->b) { + break; + } + + if (ord($this->a) <= self::ORD_LF) { + throw new JSMinException('Unterminated string literal.'); + } + + if ($this->a === '\\') { + $this->output[] = $this->a; + $this->a = $this->get(); + } + } + } + + case 3: + $this->b = $this->next(); + + if ($this->b === '/' && ( + $this->a === '(' || $this->a === ',' || $this->a === '=' || + $this->a === ':' || $this->a === '[' || $this->a === '!' || + $this->a === '&' || $this->a === '|' || $this->a === '?')) { + + $this->output[] = $this->a; + $this->output[] = $this->b; + + for (;;) { + $this->a = $this->get(); + + if ($this->a === '/') { + break; + } + elseif ($this->a === '\\') { + $this->output[] = $this->a; + $this->a = $this->get(); + } + elseif (ord($this->a) <= self::ORD_LF) { + throw new JSMinException('Unterminated regular expression '. + 'literal.'); + } + + $this->output[] = $this->a; + } + + $this->b = $this->next(); + } + } + } + + protected function get() { + $c = $this->lookAhead; + $this->lookAhead = null; + + if ($c === null) { + if ($this->inputIndex < $this->inputLength) { + $c = $this->input[$this->inputIndex]; + $this->inputIndex += 1; + } + else { + $c = null; + } + } + + if ($c === "\r") { + return "\n"; + } + + if ($c === null || $c === "\n" || ord($c) >= self::ORD_SPACE) { + return $c; + } + + return ' '; + } + + protected function isAlphaNum($c) { + return ord($c) > 126 || $c === '\\' || preg_match('/^[\w\$]$/', $c) === 1; + } + + protected function min() { + $this->a = "\n"; + $this->action(3); + + while ($this->a !== null) { + switch ($this->a) { + case ' ': + if ($this->isAlphaNum($this->b)) { + $this->action(1); + } + else { + $this->action(2); + } + break; + + case "\n": + switch ($this->b) { + case '{': + case '[': + case '(': + case '+': + case '-': + $this->action(1); + break; + + case ' ': + $this->action(3); + break; + + default: + if ($this->isAlphaNum($this->b)) { + $this->action(1); + } + else { + $this->action(2); + } + } + break; + + default: + switch ($this->b) { + case ' ': + if ($this->isAlphaNum($this->a)) { + $this->action(1); + break; + } + + $this->action(3); + break; + + case "\n": + switch ($this->a) { + case '}': + case ']': + case ')': + case '+': + case '-': + case '"': + case "'": + $this->action(1); + break; + + default: + if ($this->isAlphaNum($this->a)) { + $this->action(1); + } + else { + $this->action(3); + } + } + break; + + default: + $this->action(1); + break; + } + } + } + + return implode('', $this->output); + } + + protected function next() { + $c = $this->get(); + + if ($c === '/') { + switch($this->peek()) { + case '/': + for (;;) { + $c = $this->get(); + + if (ord($c) <= self::ORD_LF) { + return $c; + } + } + + case '*': + $this->get(); + + for (;;) { + switch($this->get()) { + case '*': + if ($this->peek() === '/') { + $this->get(); + return ' '; + } + break; + + case null: + throw new JSMinException('Unterminated comment.'); + } + } + + default: + return $c; + } + } + + return $c; + } + + protected function peek() { + $this->lookAhead = $this->get(); + return $this->lookAhead; + } +} + +// -- Exceptions --------------------------------------------------------------- +class JSMinException extends Exception {} +?> \ No newline at end of file diff --git a/thirdparty/json/JSON.php b/thirdparty/json/JSON.php new file mode 100644 index 000000000..0cddbddb4 --- /dev/null +++ b/thirdparty/json/JSON.php @@ -0,0 +1,806 @@ + + * @author Matt Knapp + * @author Brett Stimmerman + * @copyright 2005 Michal Migurski + * @version CVS: $Id: JSON.php,v 1.31 2006/06/28 05:54:17 migurski Exp $ + * @license http://www.opensource.org/licenses/bsd-license.php + * @link http://pear.php.net/pepr/pepr-proposal-show.php?id=198 + */ + +/** + * Marker constant for Services_JSON::decode(), used to flag stack state + */ +define('SERVICES_JSON_SLICE', 1); + +/** + * Marker constant for Services_JSON::decode(), used to flag stack state + */ +define('SERVICES_JSON_IN_STR', 2); + +/** + * Marker constant for Services_JSON::decode(), used to flag stack state + */ +define('SERVICES_JSON_IN_ARR', 3); + +/** + * Marker constant for Services_JSON::decode(), used to flag stack state + */ +define('SERVICES_JSON_IN_OBJ', 4); + +/** + * Marker constant for Services_JSON::decode(), used to flag stack state + */ +define('SERVICES_JSON_IN_CMT', 5); + +/** + * Behavior switch for Services_JSON::decode() + */ +define('SERVICES_JSON_LOOSE_TYPE', 16); + +/** + * Behavior switch for Services_JSON::decode() + */ +define('SERVICES_JSON_SUPPRESS_ERRORS', 32); + +/** + * Converts to and from JSON format. + * + * Brief example of use: + * + * + * // create a new instance of Services_JSON + * $json = new Services_JSON(); + * + * // convert a complexe value to JSON notation, and send it to the browser + * $value = array('foo', 'bar', array(1, 2, 'baz'), array(3, array(4))); + * $output = $json->encode($value); + * + * print($output); + * // prints: ["foo","bar",[1,2,"baz"],[3,[4]]] + * + * // accept incoming POST data, assumed to be in JSON notation + * $input = file_get_contents('php://input', 1000000); + * $value = $json->decode($input); + * + */ +class Services_JSON +{ + /** + * constructs a new JSON instance + * + * @param int $use object behavior flags; combine with boolean-OR + * + * possible values: + * - SERVICES_JSON_LOOSE_TYPE: loose typing. + * "{...}" syntax creates associative arrays + * instead of objects in decode(). + * - SERVICES_JSON_SUPPRESS_ERRORS: error suppression. + * Values which can't be encoded (e.g. resources) + * appear as NULL instead of throwing errors. + * By default, a deeply-nested resource will + * bubble up with an error, so all return values + * from encode() should be checked with isError() + */ + function Services_JSON($use = 0) + { + $this->use = $use; + } + + /** + * convert a string from one UTF-16 char to one UTF-8 char + * + * Normally should be handled by mb_convert_encoding, but + * provides a slower PHP-only method for installations + * that lack the multibye string extension. + * + * @param string $utf16 UTF-16 character + * @return string UTF-8 character + * @access private + */ + function utf162utf8($utf16) + { + // oh please oh please oh please oh please oh please + if(function_exists('mb_convert_encoding')) { + return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16'); + } + + $bytes = (ord($utf16{0}) << 8) | ord($utf16{1}); + + switch(true) { + case ((0x7F & $bytes) == $bytes): + // this case should never be reached, because we are in ASCII range + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr(0x7F & $bytes); + + case (0x07FF & $bytes) == $bytes: + // return a 2-byte UTF-8 character + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr(0xC0 | (($bytes >> 6) & 0x1F)) + . chr(0x80 | ($bytes & 0x3F)); + + case (0xFFFF & $bytes) == $bytes: + // return a 3-byte UTF-8 character + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr(0xE0 | (($bytes >> 12) & 0x0F)) + . chr(0x80 | (($bytes >> 6) & 0x3F)) + . chr(0x80 | ($bytes & 0x3F)); + } + + // ignoring UTF-32 for now, sorry + return ''; + } + + /** + * convert a string from one UTF-8 char to one UTF-16 char + * + * Normally should be handled by mb_convert_encoding, but + * provides a slower PHP-only method for installations + * that lack the multibye string extension. + * + * @param string $utf8 UTF-8 character + * @return string UTF-16 character + * @access private + */ + function utf82utf16($utf8) + { + // oh please oh please oh please oh please oh please + if(function_exists('mb_convert_encoding')) { + return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8'); + } + + switch(strlen($utf8)) { + case 1: + // this case should never be reached, because we are in ASCII range + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return $utf8; + + case 2: + // return a UTF-16 character from a 2-byte UTF-8 char + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr(0x07 & (ord($utf8{0}) >> 2)) + . chr((0xC0 & (ord($utf8{0}) << 6)) + | (0x3F & ord($utf8{1}))); + + case 3: + // return a UTF-16 character from a 3-byte UTF-8 char + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr((0xF0 & (ord($utf8{0}) << 4)) + | (0x0F & (ord($utf8{1}) >> 2))) + . chr((0xC0 & (ord($utf8{1}) << 6)) + | (0x7F & ord($utf8{2}))); + } + + // ignoring UTF-32 for now, sorry + return ''; + } + + /** + * encodes an arbitrary variable into JSON format + * + * @param mixed $var any number, boolean, string, array, or object to be encoded. + * see argument 1 to Services_JSON() above for array-parsing behavior. + * if var is a strng, note that encode() always expects it + * to be in ASCII or UTF-8 format! + * + * @return mixed JSON string representation of input var or an error if a problem occurs + * @access public + */ + function encode($var) + { + switch (gettype($var)) { + case 'boolean': + return $var ? 'true' : 'false'; + + case 'NULL': + return 'null'; + + case 'integer': + return (int) $var; + + case 'double': + case 'float': + return (float) $var; + + case 'string': + // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT + $ascii = ''; + $strlen_var = strlen($var); + + /* + * Iterate over every character in the string, + * escaping with a slash or encoding to UTF-8 where necessary + */ + for ($c = 0; $c < $strlen_var; ++$c) { + + $ord_var_c = ord($var{$c}); + + switch (true) { + case $ord_var_c == 0x08: + $ascii .= '\b'; + break; + case $ord_var_c == 0x09: + $ascii .= '\t'; + break; + case $ord_var_c == 0x0A: + $ascii .= '\n'; + break; + case $ord_var_c == 0x0C: + $ascii .= '\f'; + break; + case $ord_var_c == 0x0D: + $ascii .= '\r'; + break; + + case $ord_var_c == 0x22: + case $ord_var_c == 0x2F: + case $ord_var_c == 0x5C: + // double quote, slash, slosh + $ascii .= '\\'.$var{$c}; + break; + + case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)): + // characters U-00000000 - U-0000007F (same as ASCII) + $ascii .= $var{$c}; + break; + + case (($ord_var_c & 0xE0) == 0xC0): + // characters U-00000080 - U-000007FF, mask 110XXXXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, ord($var{$c + 1})); + $c += 1; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + + case (($ord_var_c & 0xF0) == 0xE0): + // characters U-00000800 - U-0000FFFF, mask 1110XXXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, + ord($var{$c + 1}), + ord($var{$c + 2})); + $c += 2; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + + case (($ord_var_c & 0xF8) == 0xF0): + // characters U-00010000 - U-001FFFFF, mask 11110XXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, + ord($var{$c + 1}), + ord($var{$c + 2}), + ord($var{$c + 3})); + $c += 3; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + + case (($ord_var_c & 0xFC) == 0xF8): + // characters U-00200000 - U-03FFFFFF, mask 111110XX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, + ord($var{$c + 1}), + ord($var{$c + 2}), + ord($var{$c + 3}), + ord($var{$c + 4})); + $c += 4; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + + case (($ord_var_c & 0xFE) == 0xFC): + // characters U-04000000 - U-7FFFFFFF, mask 1111110X + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, + ord($var{$c + 1}), + ord($var{$c + 2}), + ord($var{$c + 3}), + ord($var{$c + 4}), + ord($var{$c + 5})); + $c += 5; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + } + } + + return '"'.$ascii.'"'; + + case 'array': + /* + * As per JSON spec if any array key is not an integer + * we must treat the the whole array as an object. We + * also try to catch a sparsely populated associative + * array with numeric keys here because some JS engines + * will create an array with empty indexes up to + * max_index which can cause memory issues and because + * the keys, which may be relevant, will be remapped + * otherwise. + * + * As per the ECMA and JSON specification an object may + * have any string as a property. Unfortunately due to + * a hole in the ECMA specification if the key is a + * ECMA reserved word or starts with a digit the + * parameter is only accessible using ECMAScript's + * bracket notation. + */ + + // treat as a JSON object + if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) { + $properties = array_map(array($this, 'name_value'), + array_keys($var), + array_values($var)); + + foreach($properties as $property) { + if(Services_JSON::isError($property)) { + return $property; + } + } + + return '{' . join(',', $properties) . '}'; + } + + // treat it like a regular array + $elements = array_map(array($this, 'encode'), $var); + + foreach($elements as $element) { + if(Services_JSON::isError($element)) { + return $element; + } + } + + return '[' . join(',', $elements) . ']'; + + case 'object': + $vars = get_object_vars($var); + + $properties = array_map(array($this, 'name_value'), + array_keys($vars), + array_values($vars)); + + foreach($properties as $property) { + if(Services_JSON::isError($property)) { + return $property; + } + } + + return '{' . join(',', $properties) . '}'; + + default: + return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS) + ? 'null' + : new Services_JSON_Error(gettype($var)." can not be encoded as JSON string"); + } + } + + /** + * array-walking function for use in generating JSON-formatted name-value pairs + * + * @param string $name name of key to use + * @param mixed $value reference to an array element to be encoded + * + * @return string JSON-formatted name-value pair, like '"name":value' + * @access private + */ + function name_value($name, $value) + { + $encoded_value = $this->encode($value); + + if(Services_JSON::isError($encoded_value)) { + return $encoded_value; + } + + return $this->encode(strval($name)) . ':' . $encoded_value; + } + + /** + * reduce a string by removing leading and trailing comments and whitespace + * + * @param $str string string value to strip of comments and whitespace + * + * @return string string value stripped of comments and whitespace + * @access private + */ + function reduce_string($str) + { + $str = preg_replace(array( + + // eliminate single line comments in '// ...' form + '#^\s*//(.+)$#m', + + // eliminate multi-line comments in '/* ... */' form, at start of string + '#^\s*/\*(.+)\*/#Us', + + // eliminate multi-line comments in '/* ... */' form, at end of string + '#/\*(.+)\*/\s*$#Us' + + ), '', $str); + + // eliminate extraneous space + return trim($str); + } + + /** + * decodes a JSON string into appropriate variable + * + * @param string $str JSON-formatted string + * + * @return mixed number, boolean, string, array, or object + * corresponding to given JSON input string. + * See argument 1 to Services_JSON() above for object-output behavior. + * Note that decode() always returns strings + * in ASCII or UTF-8 format! + * @access public + */ + function decode($str) + { + $str = $this->reduce_string($str); + + switch (strtolower($str)) { + case 'true': + return true; + + case 'false': + return false; + + case 'null': + return null; + + default: + $m = array(); + + if (is_numeric($str)) { + // Lookie-loo, it's a number + + // This would work on its own, but I'm trying to be + // good about returning integers where appropriate: + // return (float)$str; + + // Return float or int, as appropriate + return ((float)$str == (integer)$str) + ? (integer)$str + : (float)$str; + + } elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) { + // STRINGS RETURNED IN UTF-8 FORMAT + $delim = substr($str, 0, 1); + $chrs = substr($str, 1, -1); + $utf8 = ''; + $strlen_chrs = strlen($chrs); + + for ($c = 0; $c < $strlen_chrs; ++$c) { + + $substr_chrs_c_2 = substr($chrs, $c, 2); + $ord_chrs_c = ord($chrs{$c}); + + switch (true) { + case $substr_chrs_c_2 == '\b': + $utf8 .= chr(0x08); + ++$c; + break; + case $substr_chrs_c_2 == '\t': + $utf8 .= chr(0x09); + ++$c; + break; + case $substr_chrs_c_2 == '\n': + $utf8 .= chr(0x0A); + ++$c; + break; + case $substr_chrs_c_2 == '\f': + $utf8 .= chr(0x0C); + ++$c; + break; + case $substr_chrs_c_2 == '\r': + $utf8 .= chr(0x0D); + ++$c; + break; + + case $substr_chrs_c_2 == '\\"': + case $substr_chrs_c_2 == '\\\'': + case $substr_chrs_c_2 == '\\\\': + case $substr_chrs_c_2 == '\\/': + if (($delim == '"' && $substr_chrs_c_2 != '\\\'') || + ($delim == "'" && $substr_chrs_c_2 != '\\"')) { + $utf8 .= $chrs{++$c}; + } + break; + + case preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6)): + // single, escaped unicode character + $utf16 = chr(hexdec(substr($chrs, ($c + 2), 2))) + . chr(hexdec(substr($chrs, ($c + 4), 2))); + $utf8 .= $this->utf162utf8($utf16); + $c += 5; + break; + + case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F): + $utf8 .= $chrs{$c}; + break; + + case ($ord_chrs_c & 0xE0) == 0xC0: + // characters U-00000080 - U-000007FF, mask 110XXXXX + //see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 2); + ++$c; + break; + + case ($ord_chrs_c & 0xF0) == 0xE0: + // characters U-00000800 - U-0000FFFF, mask 1110XXXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 3); + $c += 2; + break; + + case ($ord_chrs_c & 0xF8) == 0xF0: + // characters U-00010000 - U-001FFFFF, mask 11110XXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 4); + $c += 3; + break; + + case ($ord_chrs_c & 0xFC) == 0xF8: + // characters U-00200000 - U-03FFFFFF, mask 111110XX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 5); + $c += 4; + break; + + case ($ord_chrs_c & 0xFE) == 0xFC: + // characters U-04000000 - U-7FFFFFFF, mask 1111110X + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 6); + $c += 5; + break; + + } + + } + + return $utf8; + + } elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) { + // array, or object notation + + if ($str{0} == '[') { + $stk = array(SERVICES_JSON_IN_ARR); + $arr = array(); + } else { + if ($this->use & SERVICES_JSON_LOOSE_TYPE) { + $stk = array(SERVICES_JSON_IN_OBJ); + $obj = array(); + } else { + $stk = array(SERVICES_JSON_IN_OBJ); + $obj = new stdClass(); + } + } + + array_push($stk, array('what' => SERVICES_JSON_SLICE, + 'where' => 0, + 'delim' => false)); + + $chrs = substr($str, 1, -1); + $chrs = $this->reduce_string($chrs); + + if ($chrs == '') { + if (reset($stk) == SERVICES_JSON_IN_ARR) { + return $arr; + + } else { + return $obj; + + } + } + + //print("\nparsing {$chrs}\n"); + + $strlen_chrs = strlen($chrs); + + for ($c = 0; $c <= $strlen_chrs; ++$c) { + + $top = end($stk); + $substr_chrs_c_2 = substr($chrs, $c, 2); + + if (($c == $strlen_chrs) || (($chrs{$c} == ',') && ($top['what'] == SERVICES_JSON_SLICE))) { + // found a comma that is not inside a string, array, etc., + // OR we've reached the end of the character list + $slice = substr($chrs, $top['where'], ($c - $top['where'])); + array_push($stk, array('what' => SERVICES_JSON_SLICE, 'where' => ($c + 1), 'delim' => false)); + //print("Found split at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); + + if (reset($stk) == SERVICES_JSON_IN_ARR) { + // we are in an array, so just push an element onto the stack + array_push($arr, $this->decode($slice)); + + } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) { + // we are in an object, so figure + // out the property name and set an + // element in an associative array, + // for now + $parts = array(); + + if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:\s*(\S.*),?$/Uis', $slice, $parts)) { + // "name":value pair + $key = $this->decode($parts[1]); + $val = $this->decode($parts[2]); + + if ($this->use & SERVICES_JSON_LOOSE_TYPE) { + $obj[$key] = $val; + } else { + $obj->$key = $val; + } + } elseif (preg_match('/^\s*(\w+)\s*:\s*(\S.*),?$/Uis', $slice, $parts)) { + // name:value pair, where name is unquoted + $key = $parts[1]; + $val = $this->decode($parts[2]); + + if ($this->use & SERVICES_JSON_LOOSE_TYPE) { + $obj[$key] = $val; + } else { + $obj->$key = $val; + } + } + + } + + } elseif ((($chrs{$c} == '"') || ($chrs{$c} == "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) { + // found a quote, and we are not inside a string + array_push($stk, array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs{$c})); + //print("Found start of string at {$c}\n"); + + } elseif (($chrs{$c} == $top['delim']) && + ($top['what'] == SERVICES_JSON_IN_STR) && + ((strlen(substr($chrs, 0, $c)) - strlen(rtrim(substr($chrs, 0, $c), '\\'))) % 2 != 1)) { + // found a quote, we're in a string, and it's not escaped + // we know that it's not escaped becase there is _not_ an + // odd number of backslashes at the end of the string so far + array_pop($stk); + //print("Found end of string at {$c}: ".substr($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n"); + + } elseif (($chrs{$c} == '[') && + in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { + // found a left-bracket, and we are in an array, object, or slice + array_push($stk, array('what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false)); + //print("Found start of array at {$c}\n"); + + } elseif (($chrs{$c} == ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) { + // found a right-bracket, and we're in an array + array_pop($stk); + //print("Found end of array at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); + + } elseif (($chrs{$c} == '{') && + in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { + // found a left-brace, and we are in an array, object, or slice + array_push($stk, array('what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false)); + //print("Found start of object at {$c}\n"); + + } elseif (($chrs{$c} == '}') && ($top['what'] == SERVICES_JSON_IN_OBJ)) { + // found a right-brace, and we're in an object + array_pop($stk); + //print("Found end of object at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); + + } elseif (($substr_chrs_c_2 == '/*') && + in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { + // found a comment start, and we are in an array, object, or slice + array_push($stk, array('what' => SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false)); + $c++; + //print("Found start of comment at {$c}\n"); + + } elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == SERVICES_JSON_IN_CMT)) { + // found a comment end, and we're in one now + array_pop($stk); + $c++; + + for ($i = $top['where']; $i <= $c; ++$i) + $chrs = substr_replace($chrs, ' ', $i, 1); + + //print("Found end of comment at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); + + } + + } + + if (reset($stk) == SERVICES_JSON_IN_ARR) { + return $arr; + + } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) { + return $obj; + + } + + } + } + } + + /** + * @todo Ultimately, this should just call PEAR::isError() + */ + function isError($data, $code = null) + { + if (class_exists('pear')) { + return PEAR::isError($data, $code); + } elseif (is_object($data) && (get_class($data) == 'services_json_error' || + is_subclass_of($data, 'services_json_error'))) { + return true; + } + + return false; + } +} + +if (class_exists('PEAR_Error')) { + + class Services_JSON_Error extends PEAR_Error + { + function Services_JSON_Error($message = 'unknown error', $code = null, + $mode = null, $options = null, $userinfo = null) + { + parent::PEAR_Error($message, $code, $mode, $options, $userinfo); + } + } + +} else { + + /** + * @todo Ultimately, this class shall be descended from PEAR_Error + */ + class Services_JSON_Error + { + function Services_JSON_Error($message = 'unknown error', $code = null, + $mode = null, $options = null, $userinfo = null) + { + + } + } + +} + +?> diff --git a/thirdparty/json/LICENSE b/thirdparty/json/LICENSE new file mode 100644 index 000000000..4ae6bef55 --- /dev/null +++ b/thirdparty/json/LICENSE @@ -0,0 +1,21 @@ +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF +USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/thirdparty/json/Test-JSON.php b/thirdparty/json/Test-JSON.php new file mode 100644 index 000000000..4a437f743 --- /dev/null +++ b/thirdparty/json/Test-JSON.php @@ -0,0 +1,521 @@ + + * @author Matt Knapp + * @author Brett Stimmerman + * @copyright 2005 Michal Migurski + * @version CVS: $Id: Test-JSON.php,v 1.28 2006/06/28 05:54:17 migurski Exp $ + * @license http://www.opensource.org/licenses/bsd-license.php + * @link http://pear.php.net/pepr/pepr-proposal-show.php?id=198 + */ + + error_reporting(E_ALL); + + require_once 'PHPUnit.php'; + require_once 'JSON.php'; + + class Services_JSON_EncDec_TestCase extends PHPUnit_TestCase { + + function Services_JSON_EncDec_TestCase($name) { + $this->PHPUnit_TestCase($name); + } + + function setUp() { + $this->json = new Services_JSON(); + + $obj = new stdClass(); + $obj->a_string = '"he":llo}:{world'; + $obj->an_array = array(1, 2, 3); + $obj->obj = new stdClass(); + $obj->obj->a_number = 123; + + $this->obj = $obj; + $this->obj_j = '{"a_string":"\"he\":llo}:{world","an_array":[1,2,3],"obj":{"a_number":123}}'; + $this->obj_d = 'object with properties, nested object and arrays'; + + $this->arr = array(null, true, array(1, 2, 3), "hello\"],[world!"); + $this->arr_j = '[null,true,[1,2,3],"hello\"],[world!"]'; + $this->arr_d = 'array with elements and nested arrays'; + + $this->str1 = 'hello world'; + $this->str1_j = '"hello world"'; + $this->str1_j_ = "'hello world'"; + $this->str1_d = 'hello world'; + $this->str1_d_ = 'hello world, double quotes'; + + $this->str2 = "hello\t\"world\""; + $this->str2_j = '"hello\\t\\"world\\""'; + $this->str2_d = 'hello world, with tab, double-quotes'; + + $this->str3 = "\\\r\n\t\"/"; + $this->str3_j = '"\\\\\\r\\n\\t\\"\\/"'; + $this->str3_d = 'backslash, return, newline, tab, double-quote'; + + $this->str4 = 'héllö wørłd'; + $this->str4_j = '"h\u00e9ll\u00f6 w\u00f8r\u0142d"'; + $this->str4_j_ = '"héllö wørłd"'; + $this->str4_d = 'hello world, with unicode'; + } + + function test_to_JSON() + { + $this->assertEquals('null', $this->json->encode(null), 'type case: null'); + $this->assertEquals('true', $this->json->encode(true), 'type case: boolean true'); + $this->assertEquals('false', $this->json->encode(false), 'type case: boolean false'); + + $this->assertEquals('1', $this->json->encode(1), 'numeric case: 1'); + $this->assertEquals('-1', $this->json->encode(-1), 'numeric case: -1'); + $this->assertEquals('1.000000', $this->json->encode(1.0), 'numeric case: 1.0'); + $this->assertEquals('1.100000', $this->json->encode(1.1), 'numeric case: 1.1'); + + $this->assertEquals($this->str1_j, $this->json->encode($this->str1), "string case: {$this->str1_d}"); + $this->assertEquals($this->str2_j, $this->json->encode($this->str2), "string case: {$this->str2_d}"); + $this->assertEquals($this->str3_j, $this->json->encode($this->str3), "string case: {$this->str3_d}"); + $this->assertEquals($this->str4_j, $this->json->encode($this->str4), "string case: {$this->str4_d}"); + + $this->assertEquals($this->arr_j, $this->json->encode($this->arr), "array case: {$this->arr_d}"); + $this->assertEquals($this->obj_j, $this->json->encode($this->obj), "object case: {$this->obj_d}"); + } + + function test_from_JSON() + { + $this->assertEquals(null, $this->json->decode('null'), 'type case: null'); + $this->assertEquals(true, $this->json->decode('true'), 'type case: boolean true'); + $this->assertEquals(false, $this->json->decode('false'), 'type case: boolean false'); + + $this->assertEquals(1, $this->json->decode('1'), 'numeric case: 1'); + $this->assertEquals(-1, $this->json->decode('-1'), 'numeric case: -1'); + $this->assertEquals(1.0, $this->json->decode('1.0'), 'numeric case: 1.0'); + $this->assertEquals(1.1, $this->json->decode('1.1'), 'numeric case: 1.1'); + + $this->assertEquals(11.0, $this->json->decode('1.1e1'), 'numeric case: 1.1e1'); + $this->assertEquals(11.0, $this->json->decode('1.10e+1'), 'numeric case: 1.10e+1'); + $this->assertEquals(0.11, $this->json->decode('1.1e-1'), 'numeric case: 1.1e-1'); + $this->assertEquals(-0.11, $this->json->decode('-1.1e-1'), 'numeric case: -1.1e-1'); + + $this->assertEquals($this->str1, $this->json->decode($this->str1_j), "string case: {$this->str1_d}"); + $this->assertEquals($this->str1, $this->json->decode($this->str1_j_), "string case: {$this->str1_d_}"); + $this->assertEquals($this->str2, $this->json->decode($this->str2_j), "string case: {$this->str2_d}"); + $this->assertEquals($this->str3, $this->json->decode($this->str3_j), "string case: {$this->str3_d}"); + $this->assertEquals($this->str4, $this->json->decode($this->str4_j), "string case: {$this->str4_d}"); + $this->assertEquals($this->str4, $this->json->decode($this->str4_j_), "string case: {$this->str4_d}"); + + $this->assertEquals($this->arr, $this->json->decode($this->arr_j), "array case: {$this->arr_d}"); + $this->assertEquals($this->obj, $this->json->decode($this->obj_j), "object case: {$this->obj_d}"); + } + + function test_to_then_from_JSON() + { + $this->assertEquals(null, $this->json->decode($this->json->encode(null)), 'type case: null'); + $this->assertEquals(true, $this->json->decode($this->json->encode(true)), 'type case: boolean true'); + $this->assertEquals(false, $this->json->decode($this->json->encode(false)), 'type case: boolean false'); + + $this->assertEquals(1, $this->json->decode($this->json->encode(1)), 'numeric case: 1'); + $this->assertEquals(-1, $this->json->decode($this->json->encode(-1)), 'numeric case: -1'); + $this->assertEquals(1.0, $this->json->decode($this->json->encode(1.0)), 'numeric case: 1.0'); + $this->assertEquals(1.1, $this->json->decode($this->json->encode(1.1)), 'numeric case: 1.1'); + + $this->assertEquals($this->str1, $this->json->decode($this->json->encode($this->str1)), "string case: {$this->str1_d}"); + $this->assertEquals($this->str2, $this->json->decode($this->json->encode($this->str2)), "string case: {$this->str2_d}"); + $this->assertEquals($this->str3, $this->json->decode($this->json->encode($this->str3)), "string case: {$this->str3_d}"); + $this->assertEquals($this->str4, $this->json->decode($this->json->encode($this->str4)), "string case: {$this->str4_d}"); + + $this->assertEquals($this->arr, $this->json->decode($this->json->encode($this->arr)), "array case: {$this->arr_d}"); + $this->assertEquals($this->obj, $this->json->decode($this->json->encode($this->obj)), "object case: {$this->obj_d}"); + } + + function test_from_then_to_JSON() + { + $this->assertEquals('null', $this->json->encode($this->json->decode('null')), 'type case: null'); + $this->assertEquals('true', $this->json->encode($this->json->decode('true')), 'type case: boolean true'); + $this->assertEquals('false', $this->json->encode($this->json->decode('false')), 'type case: boolean false'); + + $this->assertEquals('1', $this->json->encode($this->json->decode('1')), 'numeric case: 1'); + $this->assertEquals('-1', $this->json->encode($this->json->decode('-1')), 'numeric case: -1'); + $this->assertEquals('1.0', $this->json->encode($this->json->decode('1.0')), 'numeric case: 1.0'); + $this->assertEquals('1.1', $this->json->encode($this->json->decode('1.1')), 'numeric case: 1.1'); + + $this->assertEquals($this->str1_j, $this->json->encode($this->json->decode($this->str1_j)), "string case: {$this->str1_d}"); + $this->assertEquals($this->str2_j, $this->json->encode($this->json->decode($this->str2_j)), "string case: {$this->str2_d}"); + $this->assertEquals($this->str3_j, $this->json->encode($this->json->decode($this->str3_j)), "string case: {$this->str3_d}"); + $this->assertEquals($this->str4_j, $this->json->encode($this->json->decode($this->str4_j)), "string case: {$this->str4_d}"); + $this->assertEquals($this->str4_j, $this->json->encode($this->json->decode($this->str4_j_)), "string case: {$this->str4_d}"); + + $this->assertEquals($this->arr_j, $this->json->encode($this->json->decode($this->arr_j)), "array case: {$this->arr_d}"); + $this->assertEquals($this->obj_j, $this->json->encode($this->json->decode($this->obj_j)), "object case: {$this->obj_d}"); + } + } + + class Services_JSON_AssocArray_TestCase extends PHPUnit_TestCase { + + function Services_JSON_AssocArray_TestCase($name) { + $this->PHPUnit_TestCase($name); + } + + function setUp() { + $this->json_l = new Services_JSON(SERVICES_JSON_LOOSE_TYPE); + $this->json_s = new Services_JSON(); + + $this->arr = array('car1'=> array('color'=> 'tan', 'model' => 'sedan'), + 'car2' => array('color' => 'red', 'model' => 'sports')); + $this->arr_jo = '{"car1":{"color":"tan","model":"sedan"},"car2":{"color":"red","model":"sports"}}'; + $this->arr_d = 'associative array with nested associative arrays'; + + $this->arn = array(0=> array(0=> 'tan\\', 'model\\' => 'sedan'), 1 => array(0 => 'red', 'model' => 'sports')); + $this->arn_ja = '[{"0":"tan\\\\","model\\\\":"sedan"},{"0":"red","model":"sports"}]'; + $this->arn_d = 'associative array with nested associative arrays, and some numeric keys thrown in'; + + $this->arrs = array (1 => 'one', 2 => 'two', 5 => 'five'); + $this->arrs_jo = '{"1":"one","2":"two","5":"five"}'; + $this->arrs_d = 'associative array numeric keys which are not fully populated in a range of 0 to length-1'; + } + + function test_type() + { + $this->assertEquals('array', gettype($this->json_l->decode($this->arn_ja)), "loose type should be array"); + $this->assertEquals('array', gettype($this->json_s->decode($this->arn_ja)), "strict type should be array"); + } + + function test_to_JSON() + { + // both strict and loose JSON should result in an object + $this->assertEquals($this->arr_jo, $this->json_l->encode($this->arr), "array case - loose: {$this->arr_d}"); + $this->assertEquals($this->arr_jo, $this->json_s->encode($this->arr), "array case - strict: {$this->arr_d}"); + + // ...unless the input array has some numeric indeces, in which case the behavior is to degrade to a regular array + $this->assertEquals($this->arn_ja, $this->json_s->encode($this->arn), "array case - strict: {$this->arn_d}"); + + // Test a sparsely populated numerically indexed associative array + $this->assertEquals($this->arrs_jo, $this->json_l->encode($this->arrs), "sparse numeric assoc array: {$this->arrs_d}"); + } + + function test_to_then_from_JSON() + { + // these tests motivated by a bug in which strings that end + // with backslashes followed by quotes were incorrectly decoded. + + foreach(array('\\"', '\\\\"', '\\"\\"', '\\""\\""', '\\\\"\\\\"') as $v) { + $this->assertEquals(array($v), $this->json_l->decode($this->json_l->encode(array($v)))); + $this->assertEquals(array('a' => $v), $this->json_l->decode($this->json_l->encode(array('a' => $v)))); + } + } + } + + class Services_JSON_NestedArray_TestCase extends PHPUnit_TestCase { + + function Services_JSON_NestedArray_TestCase($name) { + $this->PHPUnit_TestCase($name); + } + + function setUp() { + $this->json = new Services_JSON(SERVICES_JSON_LOOSE_TYPE); + + $this->str1 = '[{"this":"that"}]'; + $this->arr1 = array(array('this' => 'that')); + + $this->str2 = '{"this":["that"]}'; + $this->arr2 = array('this' => array('that')); + + $this->str3 = '{"params":[{"foo":["1"],"bar":"1"}]}'; + $this->arr3 = array('params' => array(array('foo' => array('1'), 'bar' => '1'))); + + $this->str4 = '{"0": {"foo": "bar", "baz": "winkle"}}'; + $this->arr4 = array('0' => array('foo' => 'bar', 'baz' => 'winkle')); + + $this->str5 = '{"params":[{"options": {"old": [ ], "new": {"0": {"elements": {"old": [], "new": {"0": {"elementName": "aa", "isDefault": false, "elementRank": "0", "priceAdjust": "0", "partNumber": ""}}}, "optionName": "aa", "isRequired": false, "optionDesc": null}}}}]}'; + $this->arr5 = array ( + 'params' => array ( + 0 => array ( + 'options' => + array ( + 'old' => array(), + 'new' => array ( + 0 => array ( + 'elements' => array ( + 'old' => array(), + 'new' => array ( + 0 => array ( + 'elementName' => 'aa', + 'isDefault' => false, + 'elementRank' => '0', + 'priceAdjust' => '0', + 'partNumber' => '', + ), + ), + ), + 'optionName' => 'aa', + 'isRequired' => false, + 'optionDesc' => NULL, + ), + ), + ), + ), + ), + ); + } + + function test_type() + { + $this->assertEquals('array', gettype($this->json->decode($this->str1)), "loose type should be array"); + $this->assertEquals('array', gettype($this->json->decode($this->str2)), "loose type should be array"); + $this->assertEquals('array', gettype($this->json->decode($this->str3)), "loose type should be array"); + } + + function test_from_JSON() + { + $this->assertEquals($this->arr1, $this->json->decode($this->str1), "simple compactly-nested array"); + $this->assertEquals($this->arr2, $this->json->decode($this->str2), "simple compactly-nested array"); + $this->assertEquals($this->arr3, $this->json->decode($this->str3), "complex compactly nested array"); + $this->assertEquals($this->arr4, $this->json->decode($this->str4), "complex compactly nested array"); + $this->assertEquals($this->arr5, $this->json->decode($this->str5), "super complex compactly nested array"); + } + + function _test_from_JSON() + { + $super = '{"params":[{"options": {"old": {}, "new": {"0": {"elements": {"old": {}, "new": {"0": {"elementName": "aa", "isDefault": false, "elementRank": "0", "priceAdjust": "0", "partNumber": ""}}}, "optionName": "aa", "isRequired": false, "optionDesc": ""}}}}]}'; + print("trying {$super}...\n"); + print var_export($this->json->decode($super)); + } + } + + class Services_JSON_Object_TestCase extends PHPUnit_TestCase { + + function Services_JSON_Object_TestCase($name) { + $this->PHPUnit_TestCase($name); + } + + function setUp() { + $this->json_l = new Services_JSON(SERVICES_JSON_LOOSE_TYPE); + $this->json_s = new Services_JSON(); + + $this->obj_j = '{"a_string":"\"he\":llo}:{world","an_array":[1,2,3],"obj":{"a_number":123}}'; + + $this->obj1->car1->color = 'tan'; + $this->obj1->car1->model = 'sedan'; + $this->obj1->car2->color = 'red'; + $this->obj1->car2->model = 'sports'; + $this->obj1_j = '{"car1":{"color":"tan","model":"sedan"},"car2":{"color":"red","model":"sports"}}'; + $this->obj1_d = 'Object with nested objects'; + } + + function test_type() + { + $this->assertEquals('object', gettype($this->json_s->decode($this->obj_j)), "checking whether decoded type is object"); + $this->assertEquals('array', gettype($this->json_l->decode($this->obj_j)), "checking whether decoded type is array"); + } + + function test_to_JSON() + { + $this->assertEquals($this->obj1_j, $this->json_s->encode($this->obj1), "object - strict: {$this->obj1_d}"); + $this->assertEquals($this->obj1_j, $this->json_l->encode($this->obj1), "object - loose: {$this->obj1_d}"); + } + + function test_from_then_to_JSON() + { + $this->assertEquals($this->obj_j, $this->json_s->encode($this->json_s->decode($this->obj_j)), "object case"); + $this->assertEquals($this->obj_j, $this->json_l->encode($this->json_l->decode($this->obj_j)), "array case"); + } + } + + class Services_JSON_Spaces_Comments_TestCase extends PHPUnit_TestCase { + + function Services_JSON_Spaces_Comments_TestCase($name) { + $this->PHPUnit_TestCase($name); + } + + function setUp() { + $this->json = new Services_JSON(SERVICES_JSON_LOOSE_TYPE); + + $this->obj_j = '{"a_string":"\"he\":llo}:{world","an_array":[1,2,3],"obj":{"a_number":123}}'; + + $this->obj_js = '{"a_string": "\"he\":llo}:{world", + "an_array":[1, 2, 3], + "obj": {"a_number":123}}'; + + $this->obj_jc1 = '{"a_string": "\"he\":llo}:{world", + // here is a comment, hoorah + "an_array":[1, 2, 3], + "obj": {"a_number":123}}'; + + $this->obj_jc2 = '/* this here is the sneetch */ "the sneetch" + // this has been the sneetch.'; + + $this->obj_jc3 = '{"a_string": "\"he\":llo}:{world", + /* here is a comment, hoorah */ + "an_array":[1, 2, 3 /* and here is another */], + "obj": {"a_number":123}}'; + + $this->obj_jc4 = '{\'a_string\': "\"he\":llo}:{world", + /* here is a comment, hoorah */ + \'an_array\':[1, 2, 3 /* and here is another */], + "obj": {"a_number":123}}'; + } + + function test_spaces() + { + $this->assertEquals($this->json->decode($this->obj_j), $this->json->decode($this->obj_js), "checking whether notation with spaces works"); + } + + function test_comments() + { + $this->assertEquals($this->json->decode($this->obj_j), $this->json->decode($this->obj_jc1), "checking whether notation with single line comments works"); + $this->assertEquals('the sneetch', $this->json->decode($this->obj_jc2), "checking whether notation with multiline comments works"); + $this->assertEquals($this->json->decode($this->obj_j), $this->json->decode($this->obj_jc3), "checking whether notation with multiline comments works"); + $this->assertEquals($this->json->decode($this->obj_j), $this->json->decode($this->obj_jc4), "checking whether notation with single-quotes and multiline comments works"); + } + } + + class Services_JSON_Empties_TestCase extends PHPUnit_TestCase { + + function Services_JSON_Empties_TestCase($name) { + $this->PHPUnit_TestCase($name); + } + + function setUp() { + $this->json_l = new Services_JSON(SERVICES_JSON_LOOSE_TYPE); + $this->json_s = new Services_JSON(); + + $this->obj0_j = '{}'; + $this->arr0_j = '[]'; + + $this->obj1_j = '{ }'; + $this->arr1_j = '[ ]'; + + $this->obj2_j = '{ /* comment inside */ }'; + $this->arr2_j = '[ /* comment inside */ ]'; + } + + function test_type() + { + $this->assertEquals('array', gettype($this->json_l->decode($this->arr0_j)), "should be array"); + $this->assertEquals('object', gettype($this->json_s->decode($this->obj0_j)), "should be object"); + + $this->assertEquals(0, count($this->json_l->decode($this->arr0_j)), "should be empty array"); + $this->assertEquals(0, count(get_object_vars($this->json_s->decode($this->obj0_j))), "should be empty object"); + + $this->assertEquals('array', gettype($this->json_l->decode($this->arr1_j)), "should be array, even with space"); + $this->assertEquals('object', gettype($this->json_s->decode($this->obj1_j)), "should be object, even with space"); + + $this->assertEquals(0, count($this->json_l->decode($this->arr1_j)), "should be empty array, even with space"); + $this->assertEquals(0, count(get_object_vars($this->json_s->decode($this->obj1_j))), "should be empty object, even with space"); + + $this->assertEquals('array', gettype($this->json_l->decode($this->arr2_j)), "should be array, despite comment"); + $this->assertEquals('object', gettype($this->json_s->decode($this->obj2_j)), "should be object, despite comment"); + + $this->assertEquals(0, count($this->json_l->decode($this->arr2_j)), "should be empty array, despite comment"); + $this->assertEquals(0, count(get_object_vars($this->json_s->decode($this->obj2_j))), "should be empty object, despite commentt"); + } + } + + class Services_JSON_UnquotedKeys_TestCase extends PHPUnit_TestCase { + + function Services_JSON_UnquotedKeys_TestCase($name) { + $this->PHPUnit_TestCase($name); + } + + function setUp() { + $this->json = new Services_JSON(SERVICES_JSON_LOOSE_TYPE); + + $this->arn = array(0=> array(0=> 'tan', 'model' => 'sedan'), 1 => array(0 => 'red', 'model' => 'sports')); + $this->arn_ja = '[{0:"tan","model":"sedan"},{"0":"red",model:"sports"}]'; + $this->arn_d = 'associative array with unquoted keys, nested associative arrays, and some numeric keys thrown in'; + + $this->arrs = array (1 => 'one', 2 => 'two', 5 => 'fi"ve'); + $this->arrs_jo = '{"1":"one",2:"two","5":\'fi"ve\'}'; + $this->arrs_d = 'associative array with unquoted keys, single-quoted values, numeric keys which are not fully populated in a range of 0 to length-1'; + } + + function test_from_JSON() + { + // ...unless the input array has some numeric indeces, in which case the behavior is to degrade to a regular array + $this->assertEquals($this->arn, $this->json->decode($this->arn_ja), "array case - strict: {$this->arn_d}"); + + // Test a sparsely populated numerically indexed associative array + $this->assertEquals($this->arrs, $this->json->decode($this->arrs_jo), "sparse numeric assoc array: {$this->arrs_d}"); + } + } + + class Services_JSON_ErrorSuppression_TestCase extends PHPUnit_TestCase { + + function Services_JSON_ErrorSuppression_TestCase($name) { + $this->PHPUnit_TestCase($name); + } + + function setUp() { + $this->json = new Services_JSON(); + $this->json_ = new Services_JSON(SERVICES_JSON_SUPPRESS_ERRORS); + + $this->res = tmpfile(); + $this->res_j_ = 'null'; + $this->res_d = 'naked resource'; + + $this->arr = array('a', 1, tmpfile()); + $this->arr_j_ = '["a",1,null]'; + $this->arr_d = 'array with string, number and resource'; + + $obj = new stdClass(); + $obj->a_string = '"he":llo}:{world'; + $obj->an_array = array(1, 2, 3); + $obj->resource = tmpfile(); + + $this->obj = $obj; + $this->obj_j_ = '{"a_string":"\"he\":llo}:{world","an_array":[1,2,3],"resource":null}'; + $this->obj_d = 'object with properties, array, and nested resource'; + } + + function test_to_JSON() + { + $this->assertTrue(Services_JSON::isError($this->json->encode($this->res)), "resource case: {$this->res_d}"); + $this->assertTrue(Services_JSON::isError($this->json->encode($this->arr)), "array case: {$this->arr_d}"); + $this->assertTrue(Services_JSON::isError($this->json->encode($this->obj)), "object case: {$this->obj_d}"); + } + + function test_to_JSON_suppressed() + { + $this->assertEquals($this->res_j_, $this->json_->encode($this->res), "resource case: {$this->res_d}"); + $this->assertEquals($this->arr_j_, $this->json_->encode($this->arr), "array case: {$this->arr_d}"); + $this->assertEquals($this->obj_j_, $this->json_->encode($this->obj), "object case: {$this->obj_d}"); + } + } + + $suite = new PHPUnit_TestSuite('Services_JSON_EncDec_TestCase'); + $result = PHPUnit::run($suite); + echo $result->toString(); + + $suite = new PHPUnit_TestSuite('Services_JSON_AssocArray_TestCase'); + $result = PHPUnit::run($suite); + echo $result->toString(); + + $suite = new PHPUnit_TestSuite('Services_JSON_NestedArray_TestCase'); + $result = PHPUnit::run($suite); + echo $result->toString(); + + $suite = new PHPUnit_TestSuite('Services_JSON_Object_TestCase'); + $result = PHPUnit::run($suite); + echo $result->toString(); + + $suite = new PHPUnit_TestSuite('Services_JSON_Spaces_Comments_TestCase'); + $result = PHPUnit::run($suite); + echo $result->toString(); + + $suite = new PHPUnit_TestSuite('Services_JSON_Empties_TestCase'); + $result = PHPUnit::run($suite); + echo $result->toString(); + + $suite = new PHPUnit_TestSuite('Services_JSON_UnquotedKeys_TestCase'); + $result = PHPUnit::run($suite); + echo $result->toString(); + + $suite = new PHPUnit_TestSuite('Services_JSON_ErrorSuppression_TestCase'); + $result = PHPUnit::run($suite); + echo $result->toString(); + +?> diff --git a/thirdparty/simplepie/LICENSE.txt b/thirdparty/simplepie/LICENSE.txt new file mode 100644 index 000000000..a822a4bd9 --- /dev/null +++ b/thirdparty/simplepie/LICENSE.txt @@ -0,0 +1,26 @@ +Copyright (c) 2004-2007, Ryan Parman and Geoffrey Sneddon. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are +permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of + conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, this list + of conditions and the following disclaimer in the documentation and/or other materials + provided with the distribution. + + * Neither the name of the SimplePie Team nor the names of its contributors may be used + to endorse or promote products derived from this software without specific prior + written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS +OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS +AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/thirdparty/simplepie/README.txt b/thirdparty/simplepie/README.txt new file mode 100644 index 000000000..fd1713e6e --- /dev/null +++ b/thirdparty/simplepie/README.txt @@ -0,0 +1,30 @@ +SIMPLEPIE +http://simplepie.org +By Ryan Parman and Geoffrey Sneddon + +BSD-LICENSED +http://www.opensource.org/licenses/bsd-license.php + +WHAT COMES IN THE PACKAGE? +1) simplepie.inc - The SimplePie library. This is all that's required for your pages. +2) README.txt - This document. +3) LICENSE.txt - A copy of the BSD license. +4) compatibility_test - The SimplePie compatibility test that checks your server for required settings. +5) demo - A basic feed reader demo that shows off some of SimplePie's more noticable features. +6) idn - A third-party library that SimplePie can optionally use to understand Internationalized Domain Names (IDNs). +7) test - SimplePie's unit test suite. This is only available in SVN builds. + +TO START THE DEMO: +1) Upload this package to your webserver. +2) Make sure that the cache folder inside of the demo folder is server-writable. +3) Navigate your browser to the demo folder. + +SUPPORT: +For further setup and install documentation, function references, etc., visit: +http://simplepie.org/wiki/ + +For bug reports, feature requests and other support, visit: +http://simplepie.org/support/ + +For more insight on SimplePie development, visit: +http://simplepie.org/development/ \ No newline at end of file diff --git a/thirdparty/simplepie/SimplePie.php b/thirdparty/simplepie/SimplePie.php new file mode 100755 index 000000000..7b5a3bc60 --- /dev/null +++ b/thirdparty/simplepie/SimplePie.php @@ -0,0 +1,5413 @@ +useragent = $this->name . '/' . $this->version . ' (Feed Parser; ' . $this->url . '; Allow like Gecko) Build/' . $this->build; + $this->linkback = '' . $this->name . ''; + + // Other objects, instances created here so we can set options on them + $this->sanitize = new SimplePie_Sanitize; + + // Set options if they're passed to the constructor + if (!is_null($feed_url)) + { + $this->feed_url($feed_url); + } + + if (!is_null($cache_location)) + { + $this->cache_location($cache_location); + } + + if (!is_null($cache_max_minutes)) + { + $this->cache_max_minutes($cache_max_minutes); + } + + // If we've passed an xmldump variable in the URL, snap into XMLdump mode + if (isset($_GET['xmldump'])) + { + $this->enable_xmldump(true); + } + + // Only init the script if we're passed a feed URL + if (!is_null($feed_url)) + { + return $this->init(); + } + } + + function feed_url($url) + { + $this->rss_url = SimplePie_Misc::fix_protocol($url, 1); + } + + function set_file(&$file) + { + if (is_a($file, 'SimplePie_File')) + { + $this->rss_url = $file->url; + $this->file =& $file; + } + } + + function set_timeout($timeout = 10) + { + $this->timeout = (int) $timeout; + } + + function set_raw_data($data) + { + $this->raw_data = trim((string) $data); + } + + function enable_xmldump($enable = false) + { + $this->xml_dump = (bool) $enable; + } + + function enable_caching($enable = true) + { + $this->enable_cache = (bool) $enable; + } + + function cache_max_minutes($minutes = 60) + { + $this->max_minutes = (float) $minutes; + } + + function cache_location($location = './cache') + { + $this->cache_location = (string) $location; + } + + function order_by_date($enable = true) + { + $this->order_by_date = (bool) $enable; + } + + function input_encoding($encoding = false) + { + if ($encoding) + { + $this->input_encoding = (string) $encoding; + } + else + { + $this->input_encoding = false; + } + } + + function set_cache_class($class = 'SimplePie_Cache') + { + if (SimplePie_Misc::is_a_class($class, 'SimplePie_Cache')) + { + $this->cache_class = $class; + return true; + } + return false; + } + + function set_locator_class($class = 'SimplePie_Locator') + { + if (SimplePie_Misc::is_a_class($class, 'SimplePie_Locator')) + { + $this->locator_class = $class; + return true; + } + return false; + } + + function set_parser_class($class = 'SimplePie_Parser') + { + if (SimplePie_Misc::is_a_class($class, 'SimplePie_Parser')) + { + $this->parser_class = $class; + return true; + } + return false; + } + + function set_file_class($class = 'SimplePie_File') + { + if (SimplePie_Misc::is_a_class($class, 'SimplePie_File')) + { + $this->file_class = $class; + return true; + } + return false; + } + + function set_sanitize_class($object = 'SimplePie_Sanitize') + { + if (class_exists($object)) + { + $this->sanitize = new $object; + return true; + } + return false; + } + + function set_useragent($ua) + { + $this->useragent = (string) $ua; + } + + function force_fsockopen($enable = false) + { + $this->force_fsockopen = (bool) $enable; + } + + function set_cache_name_type($type = 'sha1') + { + $type = strtolower(trim($type)); + switch ($type) + { + case 'crc32': + $this->cache_name_type = 'crc32'; + break; + + case 'md5': + $this->cache_name_type = 'md5'; + break; + + case 'rawurlencode': + $this->cache_name_type = 'rawurlencode'; + break; + + case 'urlencode': + $this->cache_name_type = 'urlencode'; + break; + + default: + $this->cache_name_type = 'sha1'; + break; + } + } + + function bypass_image_hotlink($get = false) + { + $this->sanitize->bypass_image_hotlink($get); + } + + function bypass_image_hotlink_page($page = false) + { + $this->sanitize->bypass_image_hotlink_page($page); + } + + function replace_headers($enable = false) + { + $this->sanitize->replace_headers($enable); + } + + function remove_div($enable = true) + { + $this->sanitize->remove_div($enable); + } + + function strip_ads($enable = false) + { + $this->sanitize->strip_ads($enable); + } + + function strip_htmltags($tags = array('base', 'blink', 'body', 'doctype', 'embed', 'font', 'form', 'frame', 'frameset', 'html', 'iframe', 'input', 'marquee', 'meta', 'noscript', 'object', 'param', 'script', 'style'), $encode = null) + { + $this->sanitize->strip_htmltags($tags); + if (!is_null($encode)) + { + $this->sanitize->encode_instead_of_strip($tags); + } + } + + function encode_instead_of_strip($enable = true) + { + $this->sanitize->encode_instead_of_strip($enable); + } + + function strip_attributes($attribs = array('bgsound', 'class', 'expr', 'id', 'style', 'onclick', 'onerror', 'onfinish', 'onmouseover', 'onmouseout', 'onfocus', 'onblur')) + { + $this->sanitize->strip_attributes($attribs); + } + + function output_encoding($encoding = 'UTF-8') + { + $this->sanitize->output_encoding($encoding); + } + + function set_item_class($class = 'SimplePie_Item') + { + return $this->sanitize->set_item_class($class); + } + + function set_author_class($class = 'SimplePie_Author') + { + return $this->sanitize->set_author_class($class); + } + + function set_enclosure_class($class = 'SimplePie_Enclosure') + { + return $this->sanitize->set_enclosure_class($class); + } + + function init() + { + if (!(function_exists('version_compare') && ((version_compare(phpversion(), '4.3.2', '>=') && version_compare(phpversion(), '5', '<')) || version_compare(phpversion(), '5.0.3', '>='))) || !extension_loaded('xml') || !extension_loaded('pcre')) + { + return false; + } + if ($this->sanitize->bypass_image_hotlink && !empty($_GET[$this->sanitize->bypass_image_hotlink])) + { + if (get_magic_quotes_gpc()) + { + $_GET[$this->sanitize->bypass_image_hotlink] = stripslashes($_GET[$this->sanitize->bypass_image_hotlink]); + } + SimplePie_Misc::display_file($_GET[$this->sanitize->bypass_image_hotlink], 10, $this->useragent); + } + + if (isset($_GET['js'])) + { + $embed = <<'); +} + +function embed_quicktime(type, bgcolor, width, height, link, placeholder, loop) { + if (placeholder != '') { + document.writeln(''); + } + else { + document.writeln(''); + } +} + +function embed_flash(bgcolor, width, height, link, loop, type) { + document.writeln(''); +} + +function embed_wmedia(width, height, link) { + document.writeln(''); +} +EOT; + + if (function_exists('ob_gzhandler')) + { + ob_start('ob_gzhandler'); + } + header('Content-type: text/javascript; charset: UTF-8'); + header('Cache-Control: must-revalidate'); + header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 86400) . ' GMT'); + echo $embed; + exit; + } + + if (!empty($this->rss_url) || !empty($this->raw_data)) + { + $this->data = array(); + $cache = false; + + if (!empty($this->rss_url)) + { + // Decide whether to enable caching + if ($this->enable_cache && preg_match('/^http(s)?:\/\//i', $this->rss_url)) + { + $cache = new $this->cache_class($this->cache_location, call_user_func($this->cache_name_type, $this->rss_url), 'spc'); + } + // If it's enabled and we don't want an XML dump, use the cache + if ($cache && !$this->xml_dump) + { + // Load the Cache + $this->data = $cache->load(); + if (!empty($this->data)) + { + // If we've hit a collision just rerun it with caching disabled + if (isset($this->data['url']) && $this->data['url'] != $this->rss_url) + { + $cache = false; + } + // If we've got a feed_url stored (if the page isn't actually a feed, or is a redirect) use that URL + else if (!empty($this->data['feed_url'])) + { + if ($this->data['feed_url'] == $this->data['url']) + { + $cache->unlink(); + } + else + { + $this->feed_url($this->data['feed_url']); + return $this->init(); + } + } + // If the cache is new enough + else if ($cache->mtime() + $this->max_minutes * 60 < time()) + { + // If we have last-modified and/or etag set + if (!empty($this->data['last-modified']) || !empty($this->data['etag'])) + { + $headers = array(); + if (!empty($this->data['last-modified'])) + { + $headers['if-modified-since'] = $this->data['last-modified']; + } + if (!empty($this->data['etag'])) + { + $headers['if-none-match'] = $this->data['etag']; + } + $file = new $this->file_class($this->rss_url, $this->timeout/10, 5, $headers, $this->useragent, $this->force_fsockopen); + if ($file->success) + { + $headers = $file->headers(); + if ($headers['status']['code'] == 304) + { + $cache->touch(); + return true; + } + } + else + { + unset($file); + } + } + // If we don't have last-modified or etag set, just clear the cache + else + { + $cache->unlink(); + } + } + // If the cache is still valid, just return true + else + { + return true; + } + } + // If the cache is empty, delete it + else + { + $cache->unlink(); + } + } + $this->data = array(); + // If we don't already have the file (it'll only exist if we've opened it to check if the cache has been modified), open it. + if (!isset($file)) + { + if (is_a($this->file, 'SimplePie_File') && $this->file->url == $this->rss_url) + { + $file =& $this->file; + } + else + { + $file = new $this->file_class($this->rss_url, $this->timeout, 5, null, $this->useragent, $this->force_fsockopen); + } + } + // If the file connection has an error, set SimplePie::error to that and quit + if (!$file->success) + { + $this->error = $file->error; + return false; + } + + // Check if the supplied URL is a feed, if it isn't, look for it. + $locate = new $this->locator_class($file, $this->timeout, $this->useragent); + if (!$locate->is_feed($file)) + { + $feed = $locate->find(); + if ($feed) + { + if ($cache && !$cache->save(array('url' => $this->rss_url, 'feed_url' => $feed))) + { + $this->error = "$cache->name is not writeable"; + SimplePie_Misc::error($this->error, E_USER_WARNING, __FILE__, __LINE__); + } + $this->rss_url = $feed; + return $this->init(); + } + else + { + $this->error = "A feed could not be found at $this->rss_url"; + SimplePie_Misc::error($this->error, E_USER_WARNING, __FILE__, __LINE__); + return false; + } + } + + $headers = $file->headers(); + $data = trim($file->body()); + $file->close(); + unset($file); + } + else + { + $data = $this->raw_data; + } + + // First check to see if input has been overridden. + if (!empty($this->input_encoding)) + { + $encoding = $this->input_encoding; + } + // Second try HTTP headers + else if (!empty($headers['content-type']) && preg_match('/charset\s*=\s*([^;]*)/i', $headers['content-type'], $charset)) + { + $encoding = $charset[1]; + } + // Then prolog, if at the very start of the document + else if (preg_match('/^<\?xml(.*)?>/msiU', $data, $prolog) && preg_match('/encoding\s*=\s*("([^"]*)"|\'([^\']*)\')/Ui', $prolog[1], $encoding)) + { + $encoding = substr($encoding[1], 1, -1); + } + // UTF-32 Big Endian BOM + else if (strpos($data, sprintf('%c%c%c%c', 0x00, 0x00, 0xFE, 0xFF)) === 0) + { + $encoding = 'UTF-32be'; + } + // UTF-32 Little Endian BOM + else if (strpos($data, sprintf('%c%c%c%c', 0xFF, 0xFE, 0x00, 0x00)) === 0) + { + $encoding = 'UTF-32'; + } + // UTF-16 Big Endian BOM + else if (strpos($data, sprintf('%c%c', 0xFE, 0xFF)) === 0) + { + $encoding = 'UTF-16be'; + } + // UTF-16 Little Endian BOM + else if (strpos($data, sprintf('%c%c', 0xFF, 0xFE)) === 0) + { + $encoding = 'UTF-16le'; + } + // UTF-8 BOM + else if (strpos($data, sprintf('%c%c%c', 0xEF, 0xBB, 0xBF)) === 0) + { + $encoding = 'UTF-8'; + } + // Fallback to the default + else + { + $encoding = null; + } + + // Change the encoding to UTF-8 (as we always use UTF-8 internally) + $data = SimplePie_Misc::change_encoding($data, $encoding, 'UTF-8'); + + // Start parsing + $data = new $this->parser_class($data, 'UTF-8', $this->xml_dump); + // If we want the XML, just output that and quit + if ($this->xml_dump) + { + header('Content-type: text/xml; charset=UTF-8'); + echo $data->data; + exit; + } + // If it's parsed fine + else if (!$data->error_code) + { + // Parse the data, and make it sane + $this->sanitize->parse_data_array($data->data, $this->rss_url); + unset($data); + // Get the sane data + $this->data['feedinfo'] = $this->sanitize->feedinfo; + unset($this->sanitize->feedinfo); + $this->data['info'] = $this->sanitize->info; + unset($this->sanitize->info); + $this->data['items'] = $this->sanitize->items; + unset($this->sanitize->items); + $this->data['feedinfo']['encoding'] = $this->sanitize->output_encoding; + $this->data['url'] = $this->rss_url; + + // Store the headers that we need + if (!empty($headers['last-modified'])) + { + $this->data['last-modified'] = $headers['last-modified']; + } + if (!empty($headers['etag'])) + { + $this->data['etag'] = $headers['etag']; + } + + // If we want to order it by date, check if all items have a date, and then sort it + if ($this->order_by_date && !empty($this->data['items'])) + { + $do_sort = true; + foreach ($this->data['items'] as $item) + { + if (!$item->get_date('U')) + { + $do_sort = false; + break; + } + } + if ($do_sort) + { + usort($this->data['items'], create_function('$a, $b', 'if ($a->get_date(\'U\') == $b->get_date(\'U\')) return 1; return ($a->get_date(\'U\') < $b->get_date(\'U\')) ? 1 : -1;')); + } + } + + // Cache the file if caching is enabled + if ($cache && !$cache->save($this->data)) + { + $this->error = "$cache->name is not writeable"; + SimplePie_Misc::error($this->error, E_USER_WARNING, __FILE__, __LINE__); + } + return true; + } + // If we have an error, just set SimplePie::error to it and quit + else + { + $this->error = "XML error: $data->error_string at line $data->current_line, column $data->current_column"; + SimplePie_Misc::error($this->error, E_USER_WARNING, __FILE__, __LINE__); + return false; + } + } + } + + function get_encoding() + { + if (!empty($this->data['feedinfo']['encoding'])) + { + return $this->data['feedinfo']['encoding']; + } + else + { + return false; + } + } + + function handle_content_type($mime = 'text/html') + { + if (!headers_sent()) + { + $header = "Content-type: $mime;"; + if ($this->get_encoding()) + { + $header .= ' charset=' . $this->get_encoding(); + } + else + { + $header .= ' charset=UTF-8'; + } + header($header); + } + } + + function get_type() + { + if (!empty($this->data['feedinfo']['type'])) + { + return $this->data['feedinfo']['type']; + } + else + { + return false; + } + } + + function get_version() + { + if (!empty($this->data['feedinfo']['version'])) + { + return $this->data['feedinfo']['version']; + } + else + { + return false; + } + } + + function get_favicon($check = false, $alternate = null) + { + if (!empty($this->data['info']['link']['alternate'][0])) + { + $favicon = SimplePie_Misc::absolutize_url('/favicon.ico', $this->get_feed_link()); + + if ($check) + { + $file = new $this->file_class($favicon, $this->timeout/10, 5, null, $this->useragent, $this->force_fsockopen); + $headers = $file->headers(); + $file->close(); + + if ($headers['status']['code'] == 200) + { + return $favicon; + } + } + else + { + return $favicon; + } + } + if (!is_null($alternate)) + { + return $alternate; + } + else + { + return false; + } + } + + function subscribe_url() + { + if (!empty($this->rss_url)) + { + return $this->rss_url; + } + else + { + return false; + } + } + + function subscribe_feed() + { + if (!empty($this->rss_url)) + { + return SimplePie_Misc::fix_protocol($this->rss_url, 2); + } + else + { + return false; + } + } + + function subscribe_outlook() + { + if (!empty($this->rss_url)) + { + return 'outlook' . SimplePie_Misc::fix_protocol($this->rss_url, 2); + } + else + { + return false; + } + } + + function subscribe_podcast() + { + if (!empty($this->rss_url)) + { + return SimplePie_Misc::fix_protocol($this->rss_url, 3); + } + else + { + return false; + } + } + + function subscribe_aol() + { + if ($this->subscribe_url()) + { + return 'http://feeds.my.aol.com/add.jsp?url=' . rawurlencode($this->subscribe_url()); + } + else + { + return false; + } + } + + function subscribe_bloglines() + { + if ($this->subscribe_url()) + { + return 'http://www.bloglines.com/sub/' . rawurlencode($this->subscribe_url()); + } + else + { + return false; + } + } + + function subscribe_eskobo() + { + if ($this->subscribe_url()) + { + return 'http://www.eskobo.com/?AddToMyPage=' . rawurlencode($this->subscribe_url()); + } + else + { + return false; + } + } + + function subscribe_feedfeeds() + { + if ($this->subscribe_url()) + { + return 'http://www.feedfeeds.com/add?feed=' . rawurlencode($this->subscribe_url()); + } + else + { + return false; + } + } + + function subscribe_feedlounge() + { + if ($this->subscribe_url()) + { + return 'http://my.feedlounge.com/external/subscribe?url=' . rawurlencode($this->subscribe_url()); + } + else + { + return false; + } + } + + function subscribe_feedster() + { + if ($this->subscribe_url()) + { + return 'http://www.feedster.com/myfeedster.php?action=addrss&confirm=no&rssurl=' . rawurlencode($this->subscribe_url()); + } + else + { + return false; + } + } + + function subscribe_google() + { + if ($this->subscribe_url()) + { + return 'http://fusion.google.com/add?feedurl=' . rawurlencode($this->subscribe_url()); + } + else + { + return false; + } + } + + function subscribe_gritwire() + { + if ($this->subscribe_url()) + { + return 'http://my.gritwire.com/feeds/addExternalFeed.aspx?FeedUrl=' . rawurlencode($this->subscribe_url()); + } + else + { + return false; + } + } + + function subscribe_msn() + { + if ($this->subscribe_url()) + { + $url = 'http://my.msn.com/addtomymsn.armx?id=rss&ut=' . rawurlencode($this->subscribe_url()); + if ($this->get_feed_link()) + { + $url .= '&ru=' . rawurlencode($this->get_feed_link()); + } + return $url; + } + else + { + return false; + } + } + + function subscribe_netvibes() + { + if ($this->subscribe_url()) + { + return 'http://www.netvibes.com/subscribe.php?url=' . rawurlencode($this->subscribe_url()); + } + else + { + return false; + } + } + + function subscribe_newsburst() + { + if ($this->subscribe_url()) + { + return 'http://www.newsburst.com/Source/?add=' . rawurlencode($this->subscribe_url()); + } + else + { + return false; + } + } + + function subscribe_newsgator() + { + if ($this->subscribe_url()) + { + return 'http://www.newsgator.com/ngs/subscriber/subext.aspx?url=' . rawurlencode($this->subscribe_url()); + } + else + { + return false; + } + } + + function subscribe_odeo() + { + if ($this->subscribe_url()) + { + return 'http://www.odeo.com/listen/subscribe?feed=' . rawurlencode($this->subscribe_url()); + } + else + { + return false; + } + } + + function subscribe_pluck() + { + if ($this->subscribe_url()) + { + return 'http://client.pluck.com/pluckit/prompt.aspx?GCID=C12286x053&a=' . rawurlencode($this->subscribe_url()); + } + else + { + return false; + } + } + + function subscribe_podnova() + { + if ($this->subscribe_url()) + { + return 'http://www.podnova.com/index_your_podcasts.srf?action=add&url=' . rawurlencode($this->subscribe_url()); + } + else + { + return false; + } + } + + function subscribe_rojo() + { + if ($this->subscribe_url()) + { + return 'http://www.rojo.com/add-subscription?resource=' . rawurlencode($this->subscribe_url()); + } + else + { + return false; + } + } + + function subscribe_yahoo() + { + if ($this->subscribe_url()) + { + return 'http://add.my.yahoo.com/rss?url=' . rawurlencode($this->subscribe_url()); + } + else + { + return false; + } + } + + function get_feed_title() + { + if (!empty($this->data['info']['title'])) + { + return $this->data['info']['title']; + } + else + { + return false; + } + } + + function get_feed_link() + { + if (!empty($this->data['info']['link']['alternate'][0])) + { + return $this->data['info']['link']['alternate'][0]; + } + else + { + return false; + } + } + + function get_feed_links() + { + if (!empty($this->data['info']['link'])) + { + return $this->data['info']['link']; + } + else + { + return false; + } + } + + function get_feed_description() + { + if (!empty($this->data['info']['description'])) + { + return $this->data['info']['description']; + } + else if (!empty($this->data['info']['dc:description'])) + { + return $this->data['info']['dc:description']; + } + else if (!empty($this->data['info']['tagline'])) + { + return $this->data['info']['tagline']; + } + else if (!empty($this->data['info']['subtitle'])) + { + return $this->data['info']['subtitle']; + } + else + { + return false; + } + } + + function get_feed_copyright() + { + if (!empty($this->data['info']['copyright'])) + { + return $this->data['info']['copyright']; + } + else + { + return false; + } + } + + function get_feed_language() + { + if (!empty($this->data['info']['language'])) + { + return $this->data['info']['language']; + } + else if (!empty($this->data['info']['xml:lang'])) + { + return $this->data['info']['xml:lang']; + } + else + { + return false; + } + } + + function get_image_exist() + { + if (!empty($this->data['info']['image']['url']) || !empty($this->data['info']['image']['logo'])) + { + return true; + } + else + { + return false; + } + } + + function get_image_title() + { + if (!empty($this->data['info']['image']['title'])) + { + return $this->data['info']['image']['title']; + } + else + { + return false; + } + } + + function get_image_url() + { + if (!empty($this->data['info']['image']['url'])) + { + return $this->data['info']['image']['url']; + } + else if (!empty($this->data['info']['image']['logo'])) + { + return $this->data['info']['image']['logo']; + } + else + { + return false; + } + } + + function get_image_link() + { + if (!empty($this->data['info']['image']['link'])) + { + return $this->data['info']['image']['link']; + } + else + { + return false; + } + } + + function get_image_width() + { + if (!empty($this->data['info']['image']['width'])) + { + return $this->data['info']['image']['width']; + } + else + { + return false; + } + } + + function get_image_height() + { + if (!empty($this->data['info']['image']['height'])) + { + return $this->data['info']['image']['height']; + } + else + { + return false; + } + } + + function get_item_quantity($max = 0) + { + if (!empty($this->data['items'])) + { + $qty = sizeof($this->data['items']); + } + else + { + $qty = 0; + } + if ($max == 0) + { + return $qty; + } + else + { + return ($qty > $max) ? $max : $qty; + } + } + + function get_item($key = 0) + { + if (!empty($this->data['items'][$key])) + { + return $this->data['items'][$key]; + } + else + { + return false; + } + } + + function get_items($start = 0, $end = 0) + { + if ($this->get_item_quantity() > 0) + { + if ($end == 0) + { + return array_slice($this->data['items'], $start); + } + else + { + return array_slice($this->data['items'], $start, $end); + } + } + else + { + return false; + } + } +} + +/** + * @package sapphire + * @subpackage integration + */ +class SimplePie_Item +{ + var $data; + + function SimplePie_Item($data) + { + $this->data =& $data; + } + + function get_id() + { + if (!empty($this->data['guid']['data'])) + { + return $this->data['guid']['data']; + } + else if (!empty($this->data['id'])) + { + return $this->data['id']; + } + else + { + return false; + } + } + + function get_title() + { + if (!empty($this->data['title'])) + { + return $this->data['title']; + } + else if (!empty($this->data['dc:title'])) + { + return $this->data['dc:title']; + } + else + { + return false; + } + } + + function get_description() + { + if (!empty($this->data['content'])) + { + return $this->data['content']; + } + else if (!empty($this->data['encoded'])) + { + return $this->data['encoded']; + } + else if (!empty($this->data['summary'])) + { + return $this->data['summary']; + } + else if (!empty($this->data['description'])) + { + return $this->data['description']; + } + else if (!empty($this->data['dc:description'])) + { + return $this->data['dc:description']; + } + else if (!empty($this->data['longdesc'])) + { + return $this->data['longdesc']; + } + else + { + return false; + } + } + + function get_category($key = 0) + { + $categories = $this->get_categories(); + if (!empty($categories[$key])) + { + return $categories[$key]; + } + else + { + return false; + } + } + + function get_categories() + { + $categories = array(); + if (!empty($this->data['category'])) + { + $categories = array_merge($categories, $this->data['category']); + } + if (!empty($this->data['subject'])) + { + $categories = array_merge($categories, $this->data['subject']); + } + if (!empty($this->data['term'])) + { + $categories = array_merge($categories, $this->data['term']); + } + if (!empty($categories)) + { + return array_unique($categories); + } + else + { + return false; + } + } + + function get_author($key = 0) + { + $authors = $this->get_authors(); + if (!empty($authors[$key])) + { + return $authors[$key]; + } + else + { + return false; + } + } + + function get_authors() + { + $authors = array(); + if (!empty($this->data['author'])) + { + $authors = array_merge($authors, $this->data['author']); + } + if (!empty($this->data['creator'])) + { + $authors = array_merge($authors, $this->data['creator']); + } + if (!empty($authors)) + { + return array_unique($authors); + } + else + { + return false; + } + } + + function get_date($date_format = 'j F Y, g:i a') + { + if (!empty($this->data['pubdate'])) + { + return date($date_format, $this->data['pubdate']); + } + else if (!empty($this->data['dc:date'])) + { + return date($date_format, $this->data['dc:date']); + } + else if (!empty($this->data['issued'])) + { + return date($date_format, $this->data['issued']); + } + else if (!empty($this->data['published'])) + { + return date($date_format, $this->data['published']); + } + else if (!empty($this->data['modified'])) + { + return date($date_format, $this->data['modified']); + } + else if (!empty($this->data['updated'])) + { + return date($date_format, $this->data['updated']); + } + else + { + return false; + } + } + + function get_permalink() + { + $link = $this->get_link(0); + $enclosure = $this->get_enclosure(0); + if (!empty($link)) + { + return $link; + } + else if (!empty($enclosure)) + { + return $enclosure->get_link(); + } + else + { + return false; + } + } + + function get_link($key = 0, $rel = 'alternate') + { + $links = $this->get_links($rel); + if (!empty($links[$key])) + { + return $links[$key]; + } + else + { + return false; + } + } + + function get_links($rel = 'alternate') + { + if ($rel == 'alternate') + { + $links = array(); + if (!empty($this->data['link'][$rel])) + { + $links = $this->data['link'][$rel]; + } + if (!empty($this->data['guid']['data']) && $this->data['guid']['permalink'] == true) + { + $links[] = $this->data['guid']['data']; + } + return $links; + } + else if (!empty($this->data['link'][$rel])) + { + return $this->data['link'][$rel]; + } + else + { + return false; + } + } + + function get_enclosure($key = 0) + { + $enclosures = $this->get_enclosures(); + if (!empty($enclosures[$key])) + { + return $enclosures[$key]; + } + else + { + return false; + } + } + + function get_enclosures() + { + $enclosures = array(); + $links = $this->get_links('enclosure'); + if (!empty($this->data['enclosures'])) + { + $enclosures = array_merge($enclosures, $this->data['enclosures']); + } + if (!empty($links)) + { + $enclosures = array_merge($enclosures, $links); + } + if (!empty($enclosures)) + { + return array_unique($enclosures); + } + else + { + return false; + } + } + + function add_to_blinklist() + { + if ($this->get_permalink()) + { + $url = 'http://www.blinklist.com/index.php?Action=Blink/addblink.php&Description=&Url=' . rawurlencode($this->get_permalink()); + if ($this->get_title()) + { + $url .= '&Title=' . rawurlencode($this->get_title()); + } + return $url; + } + else + { + return false; + } + } + + function add_to_blogmarks() + { + if ($this->get_permalink()) + { + $url = 'http://blogmarks.net/my/new.php?mini=1&simple=1&url=' . rawurlencode($this->get_permalink()); + if ($this->get_title()) + { + $url .= '&title=' . rawurlencode($this->get_title()); + } + return $url; + } + else + { + return false; + } + } + + function add_to_delicious() + { + if ($this->get_permalink()) + { + $url = 'http://del.icio.us/post/?v=3&url=' . rawurlencode($this->get_permalink()); + if ($this->get_title()) + { + $url .= '&title=' . rawurlencode($this->get_title()); + } + return $url; + } + else + { + return false; + } + } + + function add_to_digg() + { + if ($this->get_permalink()) + { + return 'http://digg.com/submit?phase=2&URL=' . rawurlencode($this->get_permalink()); + } + else + { + return false; + } + } + + function add_to_furl() + { + if ($this->get_permalink()) + { + $url = 'http://www.furl.net/storeIt.jsp?u=' . rawurlencode($this->get_permalink()); + if ($this->get_title()) + { + $url .= '&t=' . rawurlencode($this->get_title()); + } + return $url; + } + else + { + return false; + } + } + + function add_to_magnolia() + { + if ($this->get_permalink()) + { + $url = 'http://ma.gnolia.com/bookmarklet/add?url=' . rawurlencode($this->get_permalink()); + if ($this->get_title()) + { + $url .= '&title=' . rawurlencode($this->get_title()); + } + return $url; + } + else + { + return false; + } + } + + function add_to_myweb20() + { + if ($this->get_permalink()) + { + $url = 'http://myweb2.search.yahoo.com/myresults/bookmarklet?u=' . rawurlencode($this->get_permalink()); + if ($this->get_title()) + { + $url .= '&t=' . rawurlencode($this->get_title()); + } + return $url; + } + else + { + return false; + } + } + + function add_to_newsvine() + { + if ($this->get_permalink()) + { + $url = 'http://www.newsvine.com/_wine/save?u=' . rawurlencode($this->get_permalink()); + if ($this->get_title()) + { + $url .= '&h=' . rawurlencode($this->get_title()); + } + return $url; + } + else + { + return false; + } + } + + function add_to_reddit() + { + if ($this->get_permalink()) + { + $url = 'http://reddit.com/submit?url=' . rawurlencode($this->get_permalink()); + if ($this->get_title()) + { + $url .= '&title=' . rawurlencode($this->get_title()); + } + return $url; + } + else + { + return false; + } + } + + function add_to_segnalo() + { + if ($this->get_permalink()) + { + $url = 'http://segnalo.com/post.html.php?url=' . rawurlencode($this->get_permalink()); + if ($this->get_title()) + { + $url .= '&title=' . rawurlencode($this->get_title()); + } + return $url; + } + else + { + return false; + } + } + + function add_to_simpy() + { + if ($this->get_permalink()) + { + $url = 'http://www.simpy.com/simpy/LinkAdd.do?href=' . rawurlencode($this->get_permalink()); + if ($this->get_title()) + { + $url .= '&title=' . rawurlencode($this->get_title()); + } + return $url; + } + else + { + return false; + } + } + + function add_to_smarking() + { + if ($this->get_permalink()) + { + return 'http://smarking.com/editbookmark/?url=' . rawurlencode($this->get_permalink()); + } + else + { + return false; + } + } + + function add_to_spurl() + { + if ($this->get_permalink()) + { + $url = 'http://www.spurl.net/spurl.php?v=3&url=' . rawurlencode($this->get_permalink()); + if ($this->get_title()) + { + $url .= '&title=' . rawurlencode($this->get_title()); + } + return $url; + } + else + { + return false; + } + } + + function add_to_wists() + { + if ($this->get_permalink()) + { + $url = 'http://wists.com/r.php?c=&r=' . rawurlencode($this->get_permalink()); + if ($this->get_title()) + { + $url .= '&title=' . rawurlencode($this->get_title()); + } + return $url; + } + else + { + return false; + } + } + + function search_technorati() + { + if ($this->get_permalink()) + { + return 'http://www.technorati.com/search/' . rawurlencode($this->get_permalink()); + } + else + { + return false; + } + } +} + +/** + * @package sapphire + * @subpackage integration + */ +class SimplePie_Author +{ + var $name; + var $link; + var $email; + + // Constructor, used to input the data + function SimplePie_Author($name, $link, $email) + { + $this->name = $name; + $this->link = $link; + $this->email = $email; + } + + function get_name() + { + if (!empty($this->name)) + { + return $this->name; + } + else + { + return false; + } + } + + function get_link() + { + if (!empty($this->link)) + { + return $this->link; + } + else + { + return false; + } + } + + function get_email() + { + if (!empty($this->email)) + { + return $this->email; + } + else + { + return false; + } + } +} + +/** + * @package sapphire + * @subpackage integration + */ +class SimplePie_Enclosure +{ + var $link; + var $type; + var $length; + + // Constructor, used to input the data + function SimplePie_Enclosure($link, $type, $length) + { + $this->link = $link; + $this->type = $type; + $this->length = $length; + } + + function get_link() + { + if (!empty($this->link)) + { + if (class_exists('idna_convert')) + { + $idn = new idna_convert; + $this->link = $idn->encode($this->link); + } + return $this->link; + } + else + { + return false; + } + } + + function get_extension() + { + if (!empty($this->link)) + { + return pathinfo($this->link, PATHINFO_EXTENSION); + } + else + { + return false; + } + } + + function get_type() + { + if (!empty($this->type)) + { + return $this->type; + } + else + { + return false; + } + } + + function get_length() + { + if (!empty($this->length)) + { + return $this->length; + } + else + { + return false; + } + } + + function get_size() + { + $length = $this->get_length(); + if (!empty($length)) + { + return round($length/1048576, 2); + } + else + { + return false; + } + } + + function native_embed($options='') + { + return $this->embed($options, true); + } + + function embed($options = '', $native = false) + { + // Set up defaults + $audio = ''; + $video = ''; + $alt = ''; + $altclass = ''; + $loop = 'false'; + $width = 'auto'; + $height = 'auto'; + $bgcolor = '#ffffff'; + + // Process options and reassign values as necessary + if (is_array($options)) + { + extract($options); + } + else + { + $options = explode(',', $options); + foreach($options as $option) + { + $opt = explode(':', $option, 2); + if (isset($opt[0], $opt[1])) + { + $opt[0] = trim($opt[0]); + $opt[1] = trim($opt[1]); + switch ($opt[0]) + { + case 'audio': + $audio = $opt[1]; + break; + + case 'video': + $video = $opt[1]; + break; + + case 'alt': + $alt = $opt[1]; + break; + + case 'altclass': + $altclass = $opt[1]; + break; + + case 'loop': + $loop = $opt[1]; + break; + + case 'width': + $width = $opt[1]; + break; + + case 'height': + $height = $opt[1]; + break; + + case 'bgcolor': + $bgcolor = $opt[1]; + break; + } + } + } + } + + $type = strtolower($this->get_type()); + + // If we encounter an unsupported mime-type, check the file extension and guess intelligently. + if (!in_array($type, array('audio/3gpp', 'audio/3gpp2', 'audio/aac', 'audio/x-aac', 'audio/aiff', 'audio/x-aiff', 'audio/mid', 'audio/midi', 'audio/x-midi', 'audio/mpeg', 'audio/x-mpeg', 'audio/mp3', 'x-audio/mp3', 'audio/mp4', 'audio/m4a', 'audio/x-m4a', 'audio/wav', 'audio/x-wav', 'video/3gpp', 'video/3gpp2', 'video/m4v', 'video/x-m4v', 'video/mp4', 'video/mpeg', 'video/x-mpeg', 'video/quicktime', 'video/sd-video', 'application/x-shockwave-flash', 'application/futuresplash', 'application/asx', 'application/x-mplayer2', 'audio/x-ms-wma', 'audio/x-ms-wax', 'video/x-ms-asf-plugin', 'video/x-ms-asf', 'video/x-ms-wm', 'video/x-ms-wmv', 'video/x-ms-wvx'))) + { + switch (strtolower($this->get_extension())) + { + // Audio mime-types + case 'aac': + case 'adts': + $type = 'audio/acc'; + break; + + case 'aif': + case 'aifc': + case 'aiff': + case 'cdda': + $type = 'audio/aiff'; + break; + + case 'bwf': + $type = 'audio/wav'; + break; + + case 'kar': + case 'mid': + case 'midi': + case 'smf': + $type = 'audio/midi'; + break; + + case 'm4a': + $type = 'audio/x-m4a'; + break; + + case 'mp3': + case 'swa': + $type = 'audio/mp3'; + break; + + case 'wav': + $type = 'audio/wav'; + break; + + case 'wax': + $type = 'audio/x-ms-wax'; + break; + + case 'wma': + $type = 'audio/x-ms-wma'; + break; + + // Video mime-types + case '3gp': + case '3gpp': + $type = 'video/3gpp'; + break; + + case '3g2': + case '3gp2': + $type = 'video/3gpp2'; + break; + + case 'asf': + $type = 'video/x-ms-asf'; + break; + + case 'm1a': + case 'm1s': + case 'm1v': + case 'm15': + case 'm75': + case 'mp2': + case 'mpa': + case 'mpeg': + case 'mpg': + case 'mpm': + case 'mpv': + $type = 'video/mpeg'; + break; + + case 'm4v': + $type = 'video/x-m4v'; + break; + + case 'mov': + case 'qt': + $type = 'video/quicktime'; + break; + + case 'mp4': + case 'mpg4': + $type = 'video/mp4'; + break; + + case 'sdv': + $type = 'video/sd-video'; + break; + + case 'wm': + $type = 'video/x-ms-wm'; + break; + + case 'wmv': + $type = 'video/x-ms-wmv'; + break; + + case 'wvx': + $type = 'video/x-ms-wvx'; + break; + + // Flash mime-types + case 'spl': + $type = 'application/futuresplash'; + break; + + case 'swf': + $type = 'application/x-shockwave-flash'; + break; + } + } + + $mime = explode('/', $type, 2); + $mime = $mime[0]; + + // Process values for 'auto' + if ($width == 'auto') + { + if ($mime == 'video') + { + $width = '320'; + } + else + { + $width = '100%'; + } + } + if ($height == 'auto') + { + if ($mime == 'audio') + { + $height = 0; + } + else if ($mime == 'video') + { + $height = 240; + } + else + { + $height = 256; + } + } + + // Set proper placeholder value + if ($mime == 'audio') + { + $placeholder = $audio; + } + else if ($mime == 'video') + { + $placeholder = $video; + } + + $embed = ''; + + // Make sure the JS library is included + // (I know it'll be included multiple times, but I can't think of a better way to do this automatically) + if (!$native) + { + $embed .= ''; + } + + // Odeo Feed MP3's + if (substr(strtolower($this->get_link()), 0, 15) == 'http://odeo.com') { + if ($native) + { + $embed .= ''; + } + else + { + $embed .= ''; + } + } + + // QuickTime 7 file types. Need to test with QuickTime 6. + else if (in_array($type, array('audio/3gpp', 'audio/3gpp2', 'audio/aac', 'audio/x-aac', 'audio/aiff', 'audio/x-aiff', 'audio/mid', 'audio/midi', 'audio/x-midi', 'audio/mpeg', 'audio/x-mpeg', 'audio/mp3', 'x-audio/mp3', 'audio/mp4', 'audio/m4a', 'audio/x-m4a', 'audio/wav', 'audio/x-wav', 'video/3gpp', 'video/3gpp2', 'video/m4v', 'video/x-m4v', 'video/mp4', 'video/mpeg', 'video/x-mpeg', 'video/quicktime', 'video/sd-video'))) + { + $height += 16; + if ($native) + { + if ($placeholder != "") { + $embed .= "get_link() . "\" src=\"$placeholder\" width=\"$width\" height=\"$height\" autoplay=\"false\" target=\"myself\" controller=\"false\" loop=\"$loop\" scale=\"aspect\" bgcolor=\"$bgcolor\" pluginspage=\"http://www.apple.com/quicktime/download/\">"; + } + else { + $embed .= "get_link() . "\" width=\"$width+\" height=\"$height\" autoplay=\"false\" target=\"myself\" controller=\"true\" loop=\"$loop\" scale=\"aspect\" bgcolor=\"$bgcolor\" pluginspage=\"http://www.apple.com/quicktime/download/\">"; + } + } + else + { + $embed .= ""; + } + } + + // Flash + else if (in_array($type, array('application/x-shockwave-flash', 'application/futuresplash'))) + { + if ($native) + { + $embed .= "get_link() . "\" pluginspage=\"http://www.macromedia.com/shockwave/download/index.cgi?P1_Prod_Version=ShockwaveFlash\" type=\"$type\" quality=\"high\" width=\"$width\" height=\"$height\" bgcolor=\"$bgcolor\" loop=\"$loop\">"; + } + else + { + $embed .= ""; + } + } + + // Windows Media + else if (in_array($type, array('application/asx', 'application/x-mplayer2', 'audio/x-ms-wma', 'audio/x-ms-wax', 'video/x-ms-asf-plugin', 'video/x-ms-asf', 'video/x-ms-wm', 'video/x-ms-wmv', 'video/x-ms-wvx'))) + { + $height += 45; + if ($native) + { + $embed .= "get_link() . "\" autosize=\"1\" width=\"$width\" height=\"$height\" showcontrols=\"1\" showstatusbar=\"0\" showdisplay=\"0\" autostart=\"0\">"; + } + else + { + $embed .= ""; + } + } + + // Everything else + else $embed .= '' . $alt . ''; + + return $embed; + } +} + +/** + * @package sapphire + * @subpackage integration + */ +class SimplePie_File +{ + var $url; + var $useragent; + var $success = true; + var $headers = array(); + var $body; + var $fp; + var $redirects = 0; + var $error; + var $method; + + function SimplePie_File($url, $timeout = 10, $redirects = 5, $headers = null, $useragent = null, $force_fsockopen = false) + { + if (class_exists('idna_convert')) + { + $idn = new idna_convert; + $url = $idn->encode($url); + } + $this->url = $url; + $this->useragent = $useragent; + if (preg_match('/^http(s)?:\/\//i', $url)) + { + if (empty($useragent)) + { + $useragent = ini_get('user_agent'); + $this->useragent = $useragent; + } + if (!is_array($headers)) + { + $headers = array(); + } + if (extension_loaded('curl') && version_compare(SimplePie_Misc::get_curl_version(), '7.10.5', '>=') && !$force_fsockopen) + { + $this->method = 'curl'; + $fp = curl_init(); + $headers2 = array(); + foreach ($headers as $key => $value) + { + $headers2[] = "$key: $value"; + } + curl_setopt($fp, CURLOPT_ENCODING, ''); + curl_setopt($fp, CURLOPT_URL, $url); + curl_setopt($fp, CURLOPT_HEADER, 1); + curl_setopt($fp, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($fp, CURLOPT_TIMEOUT, $timeout); + curl_setopt($fp, CURLOPT_REFERER, $url); + curl_setopt($fp, CURLOPT_USERAGENT, $useragent); + curl_setopt($fp, CURLOPT_HTTPHEADER, $headers2); + if (!ini_get('open_basedir') && !ini_get('safe_mode')) + { + curl_setopt($fp, CURLOPT_FOLLOWLOCATION, 1); + curl_setopt($fp, CURLOPT_MAXREDIRS, $redirects); + } + + $this->headers = trim(curl_exec($fp)); + if (curl_errno($fp) == 23 || curl_errno($fp) == 61) + { + curl_setopt($fp, CURLOPT_ENCODING, 'none'); + $this->headers = trim(curl_exec($fp)); + } + if (curl_errno($fp)) + { + $this->error = 'cURL error ' . curl_errno($fp) . ': ' . curl_error($fp); + $this->success = false; + return false; + } + $info = curl_getinfo($fp); + $this->headers = explode("\r\n\r\n", $this->headers, $info['redirect_count'] + 2); + if (count($this->headers) == $info['redirect_count'] + 1) + { + $this->headers = array_pop($this->headers); + $this->body = ''; + } + else + { + $this->body = array_pop($this->headers); + $this->headers = array_pop($this->headers); + } + $this->headers = $this->parse_headers($this->headers); + if (($this->headers['status']['code'] == 301 || $this->headers['status']['code'] == 302 || $this->headers['status']['code'] == 303 || $this->headers['status']['code'] == 307) && !empty($this->headers['location']) && $this->redirects < $redirects) + { + $this->redirects++; + return $this->SimplePie_File($this->headers['location'], $timeout, $redirects, $headers, $useragent, $force_fsockopen); + } + } + else + { + $this->method = 'fsockopen'; + $url_parts = parse_url($url); + if (isset($url_parts['scheme']) && strtolower($url_parts['scheme']) == 'https') + { + $url_parts['host'] = "ssl://$url_parts[host]"; + $url_parts['port'] = 443; + } + if (!isset($url_parts['port'])) + { + $url_parts['port'] = 80; + } + $this->fp = fsockopen($url_parts['host'], $url_parts['port'], $errno, $errstr, $timeout); + if (!$this->fp) + { + $this->error = 'fsockopen error: ' . $errstr; + $this->success = false; + return false; + } + else + { + stream_set_timeout($this->fp, $timeout); + $get = (isset($url_parts['query'])) ? "$url_parts[path]?$url_parts[query]" : $url_parts['path']; + $out = "GET $get HTTP/1.0\r\n"; + $out .= "Host: $url_parts[host]\r\n"; + $out .= "User-Agent: $useragent\r\n"; + if (function_exists('gzinflate')) + { + $out .= "Accept-Encoding: gzip,deflate\r\n"; + } + + if (!empty($url_parts['user']) && !empty($url_parts['pass'])) + { + $out .= "Authorization: Basic " . base64_encode("$url_parts[user]:$url_parts[pass]") . "\r\n"; + } + foreach ($headers as $key => $value) + { + $out .= "$key: $value\r\n"; + } + $out .= "Connection: Close\r\n\r\n"; + fwrite($this->fp, $out); + + $info = stream_get_meta_data($this->fp); + $data = ''; + while (strpos($data, "\r\n\r\n") === false && !$info['timed_out']) + { + $data .= fgets($this->fp, 128); + $info = stream_get_meta_data($this->fp); + } + if (!$info['timed_out']) + { + $this->headers = $this->parse_headers($data); + if (($this->headers['status']['code'] == 301 || $this->headers['status']['code'] == 302 || $this->headers['status']['code'] == 303 || $this->headers['status']['code'] == 307) && !empty($this->headers['location']) && $this->redirects < $redirects) + { + $this->redirects++; + return $this->SimplePie_File($this->headers['location'], $timeout, $redirects, $headers, $useragent, $force_fsockopen); + } + } + else + { + $this->close(); + $this->error = 'fsocket timed out'; + $this->success = false; + return false; + } + } + } + return $this->headers['status']['code']; + } + else + { + $this->method = 'fopen'; + if ($this->fp = fopen($url, 'r')) + { + return true; + } + else + { + $this->error = 'fopen could not open the file'; + $this->success = false; + return false; + } + } + } + + function headers() + { + return $this->headers; + } + + function body() + { + if (is_null($this->body)) + { + if ($this->fp) + { + $info = stream_get_meta_data($this->fp); + $this->body = ''; + while (!$info['eof'] && !$info['timed_out']) + { + $this->body .= fread($this->fp, 1024); + $info = stream_get_meta_data($this->fp); + } + if (!$info['timed_out']) + { + $this->body = trim($this->body); + if ($this->method == 'fsockopen' && !empty($this->headers['content-encoding']) && ($this->headers['content-encoding'] == 'gzip' || $this->headers['content-encoding'] == 'deflate')) + { + if (substr($this->body, 0, 8) == "\x1f\x8b\x08\x00\x00\x00\x00\x00") + { + $this->body = substr($this->body, 10); + } + $this->body = gzinflate($this->body); + } + $this->close(); + } + else + { + return false; + } + } + else + { + return false; + } + } + return $this->body; + } + + function close() + { + if (!is_null($this->fp)) + { + if (fclose($this->fp)) + { + $this->fp = null; + return true; + } + else + { + return false; + } + } + else + { + return false; + } + } + + function parse_headers($headers) + { + $headers = explode("\r\n", trim($headers)); + $status = array_shift($headers); + foreach ($headers as $header) + { + $data = explode(':', $header, 2); + $head[strtolower(trim($data[0]))] = trim($data[1]); + } + if (preg_match('/HTTP\/[0-9\.]+ ([0-9]+)(.*)$/i', $status, $matches)) + { + if (isset($head['status'])) + { + unset($head['status']); + } + $head['status']['code'] = $matches[1]; + $head['status']['name'] = trim($matches[2]); + } + return $head; + } +} + +/** + * @package sapphire + * @subpackage integration + */ +class SimplePie_Cache +{ + var $location; + var $filename; + var $extension; + var $name; + + function SimplePie_Cache($location, $filename, $extension) + { + $this->location = $location; + $this->filename = rawurlencode($filename); + $this->extension = rawurlencode($extension); + $this->name = "$location/$this->filename.$this->extension"; + } + + function save($data) + { + if (file_exists($this->name) && is_writeable($this->name) || file_exists($this->location) && is_writeable($this->location)) + { + $fp = fopen($this->name, 'w'); + if ($fp) + { + fwrite($fp, serialize($data)); + fclose($fp); + return true; + } + } + return false; + } + + function load() + { + if (file_exists($this->name) && is_readable($this->name)) + { + return unserialize(file_get_contents($this->name)); + } + return false; + } + + function mtime() + { + if (file_exists($this->name)) + { + return filemtime($this->name); + } + return false; + } + + function touch() + { + if (file_exists($this->name)) + { + return touch($this->name); + } + return false; + } + + function unlink() + { + if (file_exists($this->name)) + { + return unlink($this->name); + } + return false; + } +} + +/** + * @package sapphire + * @subpackage integration + */ +class SimplePie_Misc +{ + function absolutize_url($relative, $base) + { + $relative = trim($relative); + $base = trim($base); + if (!empty($relative)) + { + $relative = SimplePie_Misc::parse_url($relative, false); + $relative = array('scheme' => $relative[2], 'authority' => $relative[3], 'path' => $relative[5], 'query' => $relative[7], 'fragment' => $relative[9]); + if (!empty($relative['scheme'])) + { + $target = $relative; + } + else if (!empty($base)) + { + $base = SimplePie_Misc::parse_url($base, false); + $base = array('scheme' => $base[2], 'authority' => $base[3], 'path' => $base[5], 'query' => $base[7], 'fragment' => $base[9]); + $target['scheme'] = $base['scheme']; + if (!empty($relative['authority'])) + { + $target = array_merge($relative, $target); + } + else + { + $target['authority'] = $base['authority']; + if (!empty($relative['path'])) + { + if (strpos($relative['path'], '/') === 0) + { + $target['path'] = $relative['path']; + } + else + { + if (!empty($base['path'])) + { + $target['path'] = dirname("$base[path].") . '/' . $relative['path']; + } + else + { + $target['path'] = '/' . $relative['path']; + } + } + if (!empty($relative['query'])) + { + $target['query'] = $relative['query']; + } + $input = $target['path']; + $target['path'] = ''; + while (!empty($input)) + { + if (strpos($input, '../') === 0) + { + $input = substr($input, 3); + } + else if (strpos($input, './') === 0) + { + $input = substr($input, 2); + } + else if (strpos($input, '/./') === 0) + { + $input = substr_replace($input, '/', 0, 3); + } + else if (strpos($input, '/.') === 0 && SimplePie_Misc::strendpos($input, '/.') === 0) + { + $input = substr_replace($input, '/', -2); + } + else if (strpos($input, '/../') === 0) + { + $input = substr_replace($input, '/', 0, 4); + $target['path'] = preg_replace('/(\/)?([^\/]+)$/msiU', '', $target['path']); + } + else if (strpos($input, '/..') === 0 && SimplePie_Misc::strendpos($input, '/..') === 0) + { + $input = substr_replace($input, '/', 0, 3); + $target['path'] = preg_replace('/(\/)?([^\/]+)$/msiU', '', $target['path']); + } + else if ($input == '.' || $input == '..') + { + $input = ''; + } + else + { + if (preg_match('/^(.+)(\/|$)/msiU', $input, $match)) + { + $target['path'] .= $match[1]; + $input = substr_replace($input, '', 0, strlen($match[1])); + } + } + } + } + else + { + if (!empty($base['path'])) + { + $target['path'] = $base['path']; + } + else + { + $target['path'] = '/'; + } + if (!empty($relative['query'])) + { + $target['query'] = $relative['query']; + } + else if (!empty($base['query'])) + { + $target['query'] = $base['query']; + } + } + } + if (!empty($relative['fragment'])) + { + $target['fragment'] = $relative['fragment']; + } + } + else + { + return false; + } + $return = ''; + if (!empty($target['scheme'])) + { + $return .= "$target[scheme]:"; + } + if (!empty($target['authority'])) + { + $return .= $target['authority']; + } + if (!empty($target['path'])) + { + $return .= $target['path']; + } + if (!empty($target['query'])) + { + $return .= "?$target[query]"; + } + if (!empty($target['fragment'])) + { + $return .= "#$target[fragment]"; + } + } + else + { + $return = $base; + } + return $return; + } + + function strendpos($haystack, $needle) + { + return strlen($haystack) - strpos($haystack, $needle) - strlen($needle); + } + + function get_element($realname, $string) + { + $return = array(); + $name = preg_quote($realname, '/'); + preg_match_all("/<($name)((\s*((\w+:)?\w+)\s*=\s*(\"([^\"]*)\"|'([^']*)'|(.*)))*)\s*((\/)?>|>(.*)<\/$name>)/msiU", $string, $matches, PREG_SET_ORDER); + for ($i = 0; $i < count($matches); $i++) + { + $return[$i]['tag'] = $realname; + $return[$i]['full'] = $matches[$i][0]; + if (strlen($matches[$i][10]) <= 2) + { + $return[$i]['self_closing'] = true; + } + else + { + $return[$i]['self_closing'] = false; + $return[$i]['content'] = $matches[$i][12]; + } + $return[$i]['attribs'] = array(); + if (!empty($matches[$i][2])) + { + preg_match_all('/((\w+:)?\w+)\s*=\s*("([^"]*)"|\'([^\']*)\'|(\S+))\s/msiU', ' ' . $matches[$i][2] . ' ', $attribs, PREG_SET_ORDER); + for ($j = 0; $j < count($attribs); $j++) + { + $return[$i]['attribs'][strtoupper($attribs[$j][1])]['data'] = $attribs[$j][count($attribs[$j])-1]; + $first = substr($attribs[$j][2], 0, 1); + $return[$i]['attribs'][strtoupper($attribs[$j][1])]['split'] = ($first == '"' || $first == "'") ? $first : '"'; + } + } + } + return $return; + } + + function element_implode($element) + { + $full = "<$element[tag]"; + foreach ($element['attribs'] as $key => $value) + { + $key = strtolower($key); + $full .= " $key=$value[split]$value[data]$value[split]"; + } + if ($element['self_closing']) + { + $full .= ' />'; + } + else + { + $full .= ">$element[content]"; + } + return $full; + } + + function error($message, $level, $file, $line) + { + switch ($level) + { + case E_USER_ERROR: + $note = 'PHP Error'; + break; + case E_USER_WARNING: + $note = 'PHP Warning'; + break; + case E_USER_NOTICE: + $note = 'PHP Notice'; + break; + default: + $note = 'Unknown Error'; + break; + } + error_log("$note: $message in $file on line $line", 0); + return $message; + } + + function display_file($url, $timeout = 10, $useragent = null) + { + $file = new SimplePie_File($url, $timeout, 5, array('X-FORWARDED-FOR' => $_SERVER['REMOTE_ADDR']), $useragent); + $headers = $file->headers(); + if ($file->body() !== false) + { + header('Content-type: ' . $headers['content-type']); + echo $file->body(); + exit; + } + } + + function fix_protocol($url, $http = 1) + { + $parsed = SimplePie_Misc::parse_url($url); + if (!empty($parsed['scheme']) && strtolower($parsed['scheme']) != 'http' && strtolower($parsed['scheme']) != 'https') + { + return SimplePie_Misc::fix_protocol("$parsed[authority]$parsed[path]$parsed[query]$parsed[fragment]", $http); + } + if (!file_exists($url) && empty($parsed['scheme'])) + { + return SimplePie_Misc::fix_protocol("http://$url", $http); + } + + if ($http == 2 && !empty($parsed['scheme'])) + { + return "feed:$url"; + } + else if ($http == 3 && strtolower($parsed['scheme']) == 'http') + { + return substr_replace($url, 'podcast', 0, 4); + } + else + { + return $url; + } + } + + function parse_url($url, $parse_match = true) + { + preg_match('/^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/i', $url, $match); + if (empty($match[0])) + { + return false; + } + else + { + for ($i = 6; $i < 10; $i++) + { + if (!isset($match[$i])) + { + $match[$i] = ''; + } + } + if ($parse_match) + { + $match = array('scheme' => $match[2], 'authority' => $match[4], 'path' => $match[5], 'query' => $match[6], 'fragment' => $match[8]); + } + return $match; + } + } + + function change_encoding($data, $input, $output) + { + $input = SimplePie_Misc::encoding($input); + $output = SimplePie_Misc::encoding($output); + + if ($input != $output) + { + if (function_exists('iconv') && $input['use_iconv'] && $output['use_iconv'] && iconv($input['encoding'], "$output[encoding]//TRANSLIT", $data)) + { + return iconv($input['encoding'], "$output[encoding]//TRANSLIT", $data); + } + else if (function_exists('iconv') && $input['use_iconv'] && $output['use_iconv'] && iconv($input['encoding'], $output['encoding'], $data)) + { + return iconv($input['encoding'], $output['encoding'], $data); + } + else if (function_exists('mb_convert_encoding') && $input['use_mbstring'] && $output['use_mbstring']) + { + return mb_convert_encoding($data, $output['encoding'], $input['encoding']); + } + else if ($input['encoding'] == 'ISO-8859-1' && $output['encoding'] == 'UTF-8') + { + return utf8_encode($data); + } + else if ($input['encoding'] == 'UTF-8' && $output['encoding'] == 'ISO-8859-1') + { + return utf8_decode($data); + } + } + return $data; + } + + function encoding($encoding) + { + $return['use_mbstring'] = false; + $return['use_iconv'] = false; + switch (strtolower($encoding)) + { + + // 7bit + case '7bit': + case '7-bit': + $return['encoding'] = '7bit'; + $return['use_mbstring'] = true; + break; + + // 8bit + case '8bit': + case '8-bit': + $return['encoding'] = '8bit'; + $return['use_mbstring'] = true; + break; + + // ARMSCII-8 + case 'armscii-8': + case 'armscii': + $return['encoding'] = 'ARMSCII-8'; + $return['use_iconv'] = true; + break; + + // ASCII + case 'us-ascii': + case 'ascii': + $return['encoding'] = 'US-ASCII'; + $return['use_iconv'] = true; + $return['use_mbstring'] = true; + break; + + // BASE64 + case 'base64': + case 'base-64': + $return['encoding'] = 'BASE64'; + $return['use_mbstring'] = true; + break; + + // Big5 - Traditional Chinese, mainly used in Taiwan + case 'big5': + case '950': + $return['encoding'] = 'BIG5'; + $return['use_iconv'] = true; + $return['use_mbstring'] = true; + break; + + // Big5 with Hong Kong extensions, Traditional Chinese + case 'big5-hkscs': + $return['encoding'] = 'BIG5-HKSCS'; + $return['use_iconv'] = true; + $return['use_mbstring'] = true; + break; + + // byte2be + case 'byte2be': + $return['encoding'] = 'byte2be'; + $return['use_mbstring'] = true; + break; + + // byte2le + case 'byte2le': + $return['encoding'] = 'byte2le'; + $return['use_mbstring'] = true; + break; + + // byte4be + case 'byte4be': + $return['encoding'] = 'byte4be'; + $return['use_mbstring'] = true; + break; + + // byte4le + case 'byte4le': + $return['encoding'] = 'byte4le'; + $return['use_mbstring'] = true; + break; + + // EUC-CN + case 'euc-cn': + case 'euccn': + $return['encoding'] = 'EUC-CN'; + $return['use_iconv'] = true; + $return['use_mbstring'] = true; + break; + + // EUC-JISX0213 + case 'euc-jisx0213': + case 'eucjisx0213': + $return['encoding'] = 'EUC-JISX0213'; + $return['use_iconv'] = true; + break; + + // EUC-JP + case 'euc-jp': + case 'eucjp': + $return['encoding'] = 'EUC-JP'; + $return['use_iconv'] = true; + $return['use_mbstring'] = true; + break; + + // EUCJP-win + case 'euc-jp-win': + case 'eucjp-win': + case 'eucjpwin': + $return['encoding'] = 'EUCJP-win'; + $return['use_iconv'] = true; + $return['use_mbstring'] = true; + break; + + // EUC-KR + case 'euc-kr': + case 'euckr': + $return['encoding'] = 'EUC-KR'; + $return['use_iconv'] = true; + $return['use_mbstring'] = true; + break; + + // EUC-TW + case 'euc-tw': + case 'euctw': + $return['encoding'] = 'EUC-TW'; + $return['use_iconv'] = true; + $return['use_mbstring'] = true; + break; + + // GB18030 - Simplified Chinese, national standard character set + case 'gb18030-2000': + case 'gb18030': + $return['encoding'] = 'GB18030'; + $return['use_iconv'] = true; + break; + + // GB2312 - Simplified Chinese, national standard character set + case 'gb2312': + case '936': + $return['encoding'] = 'GB2312'; + $return['use_mbstring'] = true; + break; + + // GBK + case 'gbk': + $return['encoding'] = 'GBK'; + $return['use_iconv'] = true; + break; + + // Georgian-Academy + case 'georgian-academy': + $return['encoding'] = 'Georgian-Academy'; + $return['use_iconv'] = true; + break; + + // Georgian-PS + case 'georgian-ps': + $return['encoding'] = 'Georgian-PS'; + $return['use_iconv'] = true; + break; + + // HTML-ENTITIES + case 'html-entities': + case 'htmlentities': + $return['encoding'] = 'HTML-ENTITIES'; + $return['use_mbstring'] = true; + break; + + // HZ + case 'hz': + $return['encoding'] = 'HZ'; + $return['use_iconv'] = true; + $return['use_mbstring'] = true; + break; + + // ISO-2022-CN + case 'iso-2022-cn': + case 'iso2022-cn': + case 'iso2022cn': + $return['encoding'] = 'ISO-2022-CN'; + $return['use_iconv'] = true; + break; + + // ISO-2022-CN-EXT + case 'iso-2022-cn-ext': + case 'iso2022-cn-ext': + case 'iso2022cn-ext': + case 'iso2022cnext': + $return['encoding'] = 'ISO-2022-CN'; + $return['use_iconv'] = true; + break; + + // ISO-2022-JP + case 'iso-2022-jp': + case 'iso2022-jp': + case 'iso2022jp': + $return['encoding'] = 'ISO-2022-JP'; + $return['use_iconv'] = true; + $return['use_mbstring'] = true; + break; + + // ISO-2022-JP-1 + case 'iso-2022-jp-1': + case 'iso2022-jp-1': + case 'iso2022jp-1': + case 'iso2022jp1': + $return['encoding'] = 'ISO-2022-JP-1'; + $return['use_iconv'] = true; + break; + + // ISO-2022-JP-2 + case 'iso-2022-jp-2': + case 'iso2022-jp-2': + case 'iso2022jp-2': + case 'iso2022jp2': + $return['encoding'] = 'ISO-2022-JP-2'; + $return['use_iconv'] = true; + break; + + // ISO-2022-JP-3 + case 'iso-2022-jp-3': + case 'iso2022-jp-3': + case 'iso2022jp-3': + case 'iso2022jp3': + $return['encoding'] = 'ISO-2022-JP-3'; + $return['use_iconv'] = true; + break; + + // ISO-2022-KR + case 'iso-2022-kr': + case 'iso2022-kr': + case 'iso2022kr': + $return['encoding'] = 'ISO-2022-KR'; + $return['use_iconv'] = true; + $return['use_mbstring'] = true; + break; + + // ISO-8859-1 + case 'iso-8859-1': + case 'iso8859-1': + $return['encoding'] = 'ISO-8859-1'; + $return['use_iconv'] = true; + $return['use_mbstring'] = true; + break; + + // ISO-8859-2 + case 'iso-8859-2': + case 'iso8859-2': + $return['encoding'] = 'ISO-8859-2'; + $return['use_iconv'] = true; + $return['use_mbstring'] = true; + break; + + // ISO-8859-3 + case 'iso-8859-3': + case 'iso8859-3': + $return['encoding'] = 'ISO-8859-3'; + $return['use_iconv'] = true; + $return['use_mbstring'] = true; + break; + + // ISO-8859-4 + case 'iso-8859-4': + case 'iso8859-4': + $return['encoding'] = 'ISO-8859-4'; + $return['use_iconv'] = true; + $return['use_mbstring'] = true; + break; + + // ISO-8859-5 + case 'iso-8859-5': + case 'iso8859-5': + $return['encoding'] = 'ISO-8859-5'; + $return['use_iconv'] = true; + $return['use_mbstring'] = true; + break; + + // ISO-8859-6 + case 'iso-8859-6': + case 'iso8859-6': + $return['encoding'] = 'ISO-8859-6'; + $return['use_iconv'] = true; + $return['use_mbstring'] = true; + break; + + // ISO-8859-7 + case 'iso-8859-7': + case 'iso8859-7': + $return['encoding'] = 'ISO-8859-7'; + $return['use_iconv'] = true; + $return['use_mbstring'] = true; + break; + + // ISO-8859-8 + case 'iso-8859-8': + case 'iso8859-8': + $return['encoding'] = 'ISO-8859-8'; + $return['use_iconv'] = true; + $return['use_mbstring'] = true; + break; + + // ISO-8859-9 + case 'iso-8859-9': + case 'iso8859-9': + $return['encoding'] = 'ISO-8859-9'; + $return['use_iconv'] = true; + $return['use_mbstring'] = true; + break; + + // ISO-8859-10 + case 'iso-8859-10': + case 'iso8859-10': + $return['encoding'] = 'ISO-8859-10'; + $return['use_iconv'] = true; + $return['use_mbstring'] = true; + break; + + // mbstring/iconv functions don't appear to support 11 & 12 + + // ISO-8859-13 + case 'iso-8859-13': + case 'iso8859-13': + $return['encoding'] = 'ISO-8859-13'; + $return['use_iconv'] = true; + $return['use_mbstring'] = true; + break; + + // ISO-8859-14 + case 'iso-8859-14': + case 'iso8859-14': + $return['encoding'] = 'ISO-8859-14'; + $return['use_iconv'] = true; + $return['use_mbstring'] = true; + break; + + // ISO-8859-15 + case 'iso-8859-15': + case 'iso8859-15': + $return['encoding'] = 'ISO-8859-15'; + $return['use_iconv'] = true; + $return['use_mbstring'] = true; + break; + + // ISO-8859-16 + case 'iso-8859-16': + case 'iso8859-16': + $return['encoding'] = 'ISO-8859-16'; + $return['use_iconv'] = true; + break; + + // JIS + case 'jis': + $return['encoding'] = 'JIS'; + $return['use_mbstring'] = true; + break; + + // JOHAB - Korean + case 'johab': + $return['encoding'] = 'JOHAB'; + $return['use_iconv'] = true; + break; + + // Russian + case 'koi8-r': + case 'koi8r': + $return['encoding'] = 'KOI8-R'; + $return['use_iconv'] = true; + $return['use_mbstring'] = true; + break; + + // Turkish + case 'koi8-t': + case 'koi8t': + $return['encoding'] = 'KOI8-T'; + $return['use_iconv'] = true; + break; + + // Ukrainian + case 'koi8-u': + case 'koi8u': + $return['encoding'] = 'KOI8-U'; + $return['use_iconv'] = true; + break; + + // Russian+Ukrainian + case 'koi8-ru': + case 'koi8ru': + $return['encoding'] = 'KOI8-RU'; + $return['use_iconv'] = true; + break; + + // Macintosh (Mac OS Classic) + case 'macintosh': + $return['encoding'] = 'Macintosh'; + $return['use_iconv'] = true; + break; + + // MacArabic (Mac OS Classic) + case 'macarabic': + $return['encoding'] = 'MacArabic'; + $return['use_iconv'] = true; + break; + + // MacCentralEurope (Mac OS Classic) + case 'maccentraleurope': + $return['encoding'] = 'MacCentralEurope'; + $return['use_iconv'] = true; + break; + + // MacCroatian (Mac OS Classic) + case 'maccroatian': + $return['encoding'] = 'MacCroatian'; + $return['use_iconv'] = true; + break; + + // MacCyrillic (Mac OS Classic) + case 'maccyrillic': + $return['encoding'] = 'MacCyrillic'; + $return['use_iconv'] = true; + break; + + // MacGreek (Mac OS Classic) + case 'macgreek': + $return['encoding'] = 'MacGreek'; + $return['use_iconv'] = true; + break; + + // MacHebrew (Mac OS Classic) + case 'machebrew': + $return['encoding'] = 'MacHebrew'; + $return['use_iconv'] = true; + break; + + // MacIceland (Mac OS Classic) + case 'maciceland': + $return['encoding'] = 'MacIceland'; + $return['use_iconv'] = true; + break; + + // MacRoman (Mac OS Classic) + case 'macroman': + $return['encoding'] = 'MacRoman'; + $return['use_iconv'] = true; + break; + + // MacRomania (Mac OS Classic) + case 'macromania': + $return['encoding'] = 'MacRomania'; + $return['use_iconv'] = true; + break; + + // MacThai (Mac OS Classic) + case 'macthai': + $return['encoding'] = 'MacThai'; + $return['use_iconv'] = true; + break; + + // MacTurkish (Mac OS Classic) + case 'macturkish': + $return['encoding'] = 'MacTurkish'; + $return['use_iconv'] = true; + break; + + // MacUkraine (Mac OS Classic) + case 'macukraine': + $return['encoding'] = 'MacUkraine'; + $return['use_iconv'] = true; + break; + + // MuleLao-1 + case 'mulelao-1': + case 'mulelao1': + $return['encoding'] = 'MuleLao-1'; + $return['use_iconv'] = true; + break; + + // Shift_JIS + case 'shift_jis': + case 'sjis': + case '932': + $return['encoding'] = 'Shift_JIS'; + $return['use_iconv'] = true; + $return['use_mbstring'] = true; + break; + + // Shift_JISX0213 + case 'shift-jisx0213': + case 'shiftjisx0213': + $return['encoding'] = 'Shift_JISX0213'; + $return['use_iconv'] = true; + break; + + // SJIS-win + case 'sjis-win': + case 'sjiswin': + case 'shift_jis-win': + $return['encoding'] = 'SJIS-win'; + $return['use_iconv'] = true; + $return['use_mbstring'] = true; + break; + + // TCVN - Vietnamese + case 'tcvn': + $return['encoding'] = 'TCVN'; + $return['use_iconv'] = true; + break; + + // TDS565 - Turkish + case 'tds565': + $return['encoding'] = 'TDS565'; + $return['use_iconv'] = true; + break; + + // TIS-620 Thai + case 'tis-620': + case 'tis620': + $return['encoding'] = 'TIS-620'; + $return['use_iconv'] = true; + $return['use_mbstring'] = true; + break; + + // UCS-2 + case 'ucs-2': + case 'ucs2': + case 'utf-16': + case 'utf16': + $return['encoding'] = 'UCS-2'; + $return['use_iconv'] = true; + $return['use_mbstring'] = true; + break; + + // UCS-2BE + case 'ucs-2be': + case 'ucs2be': + case 'utf-16be': + case 'utf16be': + $return['encoding'] = 'UCS-2BE'; + $return['use_iconv'] = true; + $return['use_mbstring'] = true; + break; + + // UCS-2LE + case 'ucs-2le': + case 'ucs2le': + case 'utf-16le': + case 'utf16le': + $return['encoding'] = 'UCS-2LE'; + $return['use_iconv'] = true; + $return['use_mbstring'] = true; + break; + + // UCS-2-INTERNAL + case 'ucs-2-internal': + case 'ucs2internal': + $return['encoding'] = 'UCS-2-INTERNAL'; + $return['use_iconv'] = true; + break; + + // UCS-4 + case 'ucs-4': + case 'ucs4': + case 'utf-32': + case 'utf32': + $return['encoding'] = 'UCS-4'; + $return['use_iconv'] = true; + $return['use_mbstring'] = true; + break; + + // UCS-4BE + case 'ucs-4be': + case 'ucs4be': + case 'utf-32be': + case 'utf32be': + $return['encoding'] = 'UCS-4BE'; + $return['use_iconv'] = true; + $return['use_mbstring'] = true; + break; + + // UCS-4LE + case 'ucs-4le': + case 'ucs4le': + case 'utf-32le': + case 'utf32le': + $return['encoding'] = 'UCS-4LE'; + $return['use_iconv'] = true; + $return['use_mbstring'] = true; + break; + + // UCS-4-INTERNAL + case 'ucs-4-internal': + case 'ucs4internal': + $return['encoding'] = 'UCS-4-INTERNAL'; + $return['use_iconv'] = true; + break; + + // UCS-16 + case 'ucs-16': + case 'ucs16': + $return['encoding'] = 'UCS-16'; + $return['use_iconv'] = true; + $return['use_mbstring'] = true; + break; + + // UCS-16BE + case 'ucs-16be': + case 'ucs16be': + $return['encoding'] = 'UCS-16BE'; + $return['use_iconv'] = true; + $return['use_mbstring'] = true; + break; + + // UCS-16LE + case 'ucs-16le': + case 'ucs16le': + $return['encoding'] = 'UCS-16LE'; + $return['use_iconv'] = true; + $return['use_mbstring'] = true; + break; + + // UCS-32 + case 'ucs-32': + case 'ucs32': + $return['encoding'] = 'UCS-32'; + $return['use_iconv'] = true; + $return['use_mbstring'] = true; + break; + + // UCS-32BE + case 'ucs-32be': + case 'ucs32be': + $return['encoding'] = 'UCS-32BE'; + $return['use_iconv'] = true; + $return['use_mbstring'] = true; + break; + + // UCS-32LE + case 'ucs-32le': + case 'ucs32le': + $return['encoding'] = 'UCS-32LE'; + $return['use_iconv'] = true; + $return['use_mbstring'] = true; + break; + + // UTF-7 + case 'utf-7': + case 'utf7': + $return['encoding'] = 'UTF-7'; + $return['use_iconv'] = true; + $return['use_mbstring'] = true; + break; + + // UTF7-IMAP + case 'utf-7-imap': + case 'utf7-imap': + case 'utf7imap': + $return['encoding'] = 'UTF7-IMAP'; + $return['use_mbstring'] = true; + break; + + // VISCII - Vietnamese ASCII + case 'viscii': + $return['encoding'] = 'VISCII'; + $return['use_iconv'] = true; + break; + + // Windows-specific Central & Eastern Europe + case 'cp1250': + case 'windows-1250': + case 'win-1250': + case '1250': + $return['encoding'] = 'Windows-1250'; + $return['use_iconv'] = true; + break; + + // Windows-specific Cyrillic + case 'cp1251': + case 'windows-1251': + case 'win-1251': + case '1251': + $return['encoding'] = 'Windows-1251'; + $return['use_iconv'] = true; + $return['use_mbstring'] = true; + break; + + // Windows-specific Western Europe + case 'cp1252': + case 'windows-1252': + case '1252': + $return['encoding'] = 'Windows-1252'; + $return['use_iconv'] = true; + $return['use_mbstring'] = true; + break; + + // Windows-specific Greek + case 'cp1253': + case 'windows-1253': + case '1253': + $return['encoding'] = 'Windows-1253'; + $return['use_iconv'] = true; + break; + + // Windows-specific Turkish + case 'cp1254': + case 'windows-1254': + case '1254': + $return['encoding'] = 'Windows-1254'; + $return['use_iconv'] = true; + break; + + // Windows-specific Hebrew + case 'cp1255': + case 'windows-1255': + case '1255': + $return['encoding'] = 'Windows-1255'; + $return['use_iconv'] = true; + break; + + // Windows-specific Arabic + case 'cp1256': + case 'windows-1256': + case '1256': + $return['encoding'] = 'Windows-1256'; + $return['use_iconv'] = true; + break; + + // Windows-specific Baltic + case 'cp1257': + case 'windows-1257': + case '1257': + $return['encoding'] = 'Windows-1257'; + $return['use_iconv'] = true; + break; + + // Windows-specific Vietnamese + case 'cp1258': + case 'windows-1258': + case '1258': + $return['encoding'] = 'Windows-1258'; + $return['use_iconv'] = true; + break; + + // Default to UTF-8 + default: + $return['encoding'] = 'UTF-8'; + $return['use_iconv'] = true; + $return['use_mbstring'] = true; + break; + } + + // Then, return it. + return $return; + } + + function get_curl_version() + { + $curl = 0; + if (is_array(curl_version())) + { + $curl = curl_version(); + $curl = $curl['version']; + } + else + { + $curl = curl_version(); + $curl = explode(' ', $curl); + $curl = explode('/', $curl[0]); + $curl = $curl[1]; + } + return $curl; + } + + function is_a_class($class1, $class2) + { + if (class_exists($class1)) + { + $classes = array(strtolower($class1)); + while ($class1 = get_parent_class($class1)) + { + $classes[] = strtolower($class1); + } + return in_array(strtolower($class2), $classes); + } + else + { + return false; + } + } +} + +/** + * @package sapphire + * @subpackage integration + */ +class SimplePie_Locator +{ + var $useragent; + var $timeout; + var $file; + var $local; + var $elsewhere; + var $file_class = 'SimplePie_File'; + + function SimplePie_Locator(&$file, $timeout = 10, $useragent = null, $file_class = 'SimplePie_File') + { + if (!is_a($file, 'SimplePie_File')) + { + $this->file = new $this->file_class($file, $timeout, $useragent); + } + else + { + $this->file =& $file; + } + $this->file_class = $file_class; + $this->useragent = $useragent; + $this->timeout = $timeout; + } + + + function find() + { + if ($this->is_feed($this->file)) + { + return $this->file->url; + } + + $autodiscovery = $this->autodiscovery($this->file); + if ($autodiscovery) + { + return $autodiscovery; + } + + if ($this->get_links($this->file)) + { + if (!empty($this->local)) + { + $extension_local = $this->extension($this->local); + if ($extension_local) + { + return $extension_local; + } + + $body_local = $this->body($this->local); + if ($body_local) + { + return $body_local; + } + } + + if (!empty($this->elsewhere)) + { + $extension_elsewhere = $this->extension($this->elsewhere); + if ($extension_elsewhere) + { + return $extension_elsewhere; + } + + $body_elsewhere = $this->body($this->elsewhere); + if ($body_elsewhere) + { + return $body_elsewhere; + } + } + } + return false; + } + + function is_feed(&$file) + { + if (!is_a($file, 'SimplePie_File')) + { + if (isset($this)) + { + $file2 = new $this->file_class($file, $this->timeout, 5, null, $this->useragent); + } + else + { + $file2 = new $this->file_class($file); + } + $file2->body(); + $file2->close(); + } + else + { + $file2 =& $file; + } + $body = preg_replace('/<\!-(.*)-\>/msiU', '', $file2->body()); + if (preg_match('/<(\w+\:)?rss/msiU', $body) || preg_match('/<(\w+\:)?RDF/mi', $body) || preg_match('/<(\w+\:)?feed/mi', $body)) + { + return true; + } + return false; + } + + function autodiscovery(&$file) + { + $links = SimplePie_Misc::get_element('link', $file->body()); + $done = array(); + foreach ($links as $link) + { + if (!empty($link['attribs']['TYPE']['data']) && !empty($link['attribs']['HREF']['data']) && !empty($link['attribs']['REL']['data'])) + { + $rel = preg_split('/\s+/', strtolower(trim($link['attribs']['REL']['data']))); + $type = preg_match('/^(application\/rss\+xml|application\/atom\+xml|application\/rdf\+xml|application\/xml\+rss|application\/xml\+atom|application\/xml\+rdf|application\/xml|application\/x\.atom\+xml|text\/xml)(;|$)/msiU', trim($link['attribs']['TYPE']['data'])); + $href = SimplePie_Misc::absolutize_url(trim($link['attribs']['HREF']['data']), $this->file->url); + if (!in_array($href, $done) && in_array('alternate', $rel) && $type) + { + $feed = $this->is_feed($href); + if ($feed) + { + return $href; + } + } + $done[] = $href; + } + } + return false; + } + + function get_links(&$file) + { + $links = SimplePie_Misc::get_element('a', $file->body()); + foreach ($links as $link) + { + if (!empty($link['attribs']['HREF']['data'])) + { + $href = trim($link['attribs']['HREF']['data']); + $parsed = SimplePie_Misc::parse_url($href); + if (empty($parsed['scheme']) || $parsed['scheme'] != 'javascript') + { + $current = SimplePie_Misc::parse_url($this->file->url); + if (empty($parsed['authority']) || $parsed['authority'] == $current['authority']) + { + $this->local[] = SimplePie_Misc::absolutize_url($href, $this->file->url); + } + else + { + $this->elsewhere[] = SimplePie_Misc::absolutize_url($href, $this->file->url); + } + } + } + } + if (!empty($this->local)) + { + $this->local = array_unique($this->local); + } + if (!empty($this->elsewhere)) + { + $this->elsewhere = array_unique($this->elsewhere); + } + if (!empty($this->local) || !empty($this->elsewhere)) + { + return true; + } + return false; + } + + function extension(&$array) + { + foreach ($array as $key => $value) + { + $value = SimplePie_Misc::absolutize_url($value, $this->file->url); + if (in_array(strrchr($value, '.'), array('.rss', '.rdf', '.atom', '.xml'))) + { + if ($this->is_feed($value)) + { + return $value; + } + else + { + unset($array[$key]); + } + } + } + return false; + } + + function body(&$array) + { + foreach ($array as $key => $value) + { + $value = SimplePie_Misc::absolutize_url($value, $this->file->url); + if (preg_match('/(rss|rdf|atom|xml)/i', $value)) + { + if ($this->is_feed($value)) + { + return $value; + } + else + { + unset($array[$key]); + } + } + } + return false; + } +} + +/** + * @package sapphire + * @subpackage integration + */ +class SimplePie_Parser +{ + var $encoding; + var $data; + var $namespaces = array('xml' => 'HTTP://WWW.W3.ORG/XML/1998/NAMESPACE', 'atom' => 'ATOM', 'rss2' => 'RSS', 'rdf' => 'RDF', 'rss1' => 'RSS', 'dc' => 'DC', 'xhtml' => 'XHTML', 'content' => 'CONTENT'); + var $xml; + var $error_code; + var $error_string; + var $current_line; + var $current_column; + var $current_byte; + var $tag_name; + var $inside_item; + var $item_number = 0; + var $inside_channel; + var $author_number= 0; + var $category_number = 0; + var $enclosure_number = 0; + var $link_number = 0; + var $item_link_number = 0; + var $inside_image; + var $attribs; + var $is_first; + var $inside_author; + + + function SimplePie_Parser($data, $encoding, $return_xml = false) + { + $this->encoding = $encoding; + + // Strip BOM: + // UTF-32 Big Endian BOM + if (strpos($data, sprintf('%c%c%c%c', 0x00, 0x00, 0xFE, 0xFF)) === 0) + { + $data = substr($data, 4); + } + // UTF-32 Little Endian BOM + else if (strpos($data, sprintf('%c%c%c%c', 0xFF, 0xFE, 0x00, 0x00)) === 0) + { + $data = substr($data, 4); + } + // UTF-16 Big Endian BOM + else if (strpos($data, sprintf('%c%c', 0xFE, 0xFF)) === 0) + { + $data = substr($data, 2); + } + // UTF-16 Little Endian BOM + else if (strpos($data, sprintf('%c%c', 0xFF, 0xFE)) === 0) + { + $data = substr($data, 2); + } + // UTF-8 BOM + else if (strpos($data, sprintf('%c%c%c', 0xEF, 0xBB, 0xBF)) === 0) + { + $data = substr($data, 3); + } + + // Make sure the XML prolog is sane and has the correct encoding + if (preg_match('/^<\?xml(.*)?>/msiU', $data, $prolog)) + { + $data = substr_replace($data, '', 0, strlen($prolog[0])); + } + $data = "\n" . $data; + + // Put some data into CDATA blocks + // If we're RSS + if ((stristr($data, '|>(.*)<\/$full>)/msiU", array(&$this, 'add_cdata'), $data); + } + foreach ($sp_elements as $full) + { + // Deal with CDATA within CDATA (this can be caused by us inserting CDATA above) + $data = preg_replace_callback("/<($full)((\s*((\w+:)?\w+)\s*=\s*(\"([^\"]*)\"|'([^']*)'))*)\s*(\/>|><\/$full>)/msiU", array(&$this, 'cdata_in_cdata'), $data); + } + + // Return the XML, if so desired + if ($return_xml) + { + $this->data =& $data; + return; + } + + // Create the parser + $this->xml = xml_parser_create_ns($encoding); + xml_parser_set_option($this->xml, XML_OPTION_SKIP_WHITE, 1); + xml_set_object($this->xml, $this); + xml_set_character_data_handler($this->xml, 'data_handler'); + xml_set_element_handler($this->xml, 'start_handler', 'end_handler'); + xml_set_start_namespace_decl_handler($this->xml, 'start_name_space'); + xml_set_end_namespace_decl_handler($this->xml, 'end_name_space'); + + // Parse! + if (!xml_parse($this->xml, $data)) + { + $this->data = null; + $this->error_code = xml_get_error_code($this->xml); + $this->error_string = xml_error_string($this->error_code); + } + $this->current_line = xml_get_current_line_number($this->xml); + $this->current_column = xml_get_current_column_number($this->xml); + $this->current_byte = xml_get_current_byte_index($this->xml); + xml_parser_free($this->xml); + return; + } + + function add_cdata($match) + { + if (isset($match[10])) + { + return "<$match[1]$match[2]>"; + } + return $match[0]; + } + + function cdata_in_cdata($match) + { + if (isset($match[10])) + { + $match[10] = preg_replace_callback('//msiU', array(&$this, 'real_cdata_in_cdata'), $match[10]); + return "<$match[1]$match[2]>"; + } + return $match[0]; + } + + function real_cdata_in_cdata($match) + { + return htmlspecialchars($match[1], ENT_NOQUOTES); + } + + function do_add_content(&$array, $data) + { + if ($this->is_first) + { + $array['data'] = $data; + $array['attribs'] = $this->attribs; + } + else + { + $array['data'] .= $data; + } + } + + function start_handler($parser, $name, $attribs) + { + $this->tag_name = $name; + $this->attribs = $attribs; + $this->is_first = true; + switch ($this->tag_name) + { + case 'ITEM': + case $this->namespaces['rss2'] . ':ITEM': + case $this->namespaces['rss1'] . ':ITEM': + case 'ENTRY': + case $this->namespaces['atom'] . ':ENTRY': + $this->inside_item = true; + $this->do_add_content($this->data['items'][$this->item_number], ''); + break; + + case 'CHANNEL': + case $this->namespaces['rss2'] . ':CHANNEL': + case $this->namespaces['rss1'] . ':CHANNEL': + $this->inside_channel = true; + break; + + case 'RSS': + case $this->namespaces['rss2'] . ':RSS': + $this->data['feedinfo']['type'] = 'RSS'; + $this->do_add_content($this->data['feeddata'], ''); + if (!empty($attribs['VERSION'])) + { + $this->data['feedinfo']['version'] = trim($attribs['VERSION']); + } + break; + + case $this->namespaces['rdf'] . ':RDF': + $this->data['feedinfo']['type'] = 'RSS'; + $this->do_add_content($this->data['feeddata'], ''); + $this->data['feedinfo']['version'] = 1; + break; + + case 'FEED': + case $this->namespaces['atom'] . ':FEED': + $this->data['feedinfo']['type'] = 'Atom'; + $this->do_add_content($this->data['feeddata'], ''); + if (!empty($attribs['VERSION'])) + { + $this->data['feedinfo']['version'] = trim($attribs['VERSION']); + } + break; + + case 'IMAGE': + case $this->namespaces['rss2'] . ':IMAGE': + case $this->namespaces['rss1'] . ':IMAGE': + if ($this->inside_channel) + { + $this->inside_image = true; + } + break; + } + + if (!empty($this->data['feedinfo']['type']) && $this->data['feedinfo']['type'] == 'Atom' && ($this->tag_name == 'AUTHOR' || $this->tag_name == $this->namespaces['atom'] . ':AUTHOR')) + { + $this->inside_author = true; + } + $this->data_handler($this->xml, ''); + } + + function data_handler($parser, $data) + { + if ($this->inside_item) + { + switch ($this->tag_name) + { + case 'TITLE': + case $this->namespaces['rss1'] . ':TITLE': + case $this->namespaces['rss2'] . ':TITLE': + case $this->namespaces['atom'] . ':TITLE': + $this->do_add_content($this->data['items'][$this->item_number]['title'], $data); + break; + + case $this->namespaces['dc'] . ':TITLE': + $this->do_add_content($this->data['items'][$this->item_number]['dc:title'], $data); + break; + + case 'CONTENT': + case $this->namespaces['atom'] . ':CONTENT': + $this->do_add_content($this->data['items'][$this->item_number]['content'], $data); + break; + + case $this->namespaces['content'] . ':ENCODED': + $this->do_add_content($this->data['items'][$this->item_number]['encoded'], $data); + break; + + case 'SUMMARY': + case $this->namespaces['atom'] . ':SUMMARY': + $this->do_add_content($this->data['items'][$this->item_number]['summary'], $data); + break; + + case 'LONGDESC': + $this->do_add_content($this->data['items'][$this->item_number]['longdesc'], $data); + break; + + case 'DESCRIPTION': + case $this->namespaces['rss1'] . ':DESCRIPTION': + case $this->namespaces['rss2'] . ':DESCRIPTION': + $this->do_add_content($this->data['items'][$this->item_number]['description'], $data); + break; + + case $this->namespaces['dc'] . ':DESCRIPTION': + $this->do_add_content($this->data['items'][$this->item_number]['dc:description'], $data); + break; + + case 'LINK': + case $this->namespaces['rss1'] . ':LINK': + case $this->namespaces['rss2'] . ':LINK': + case $this->namespaces['atom'] . ':LINK': + $this->do_add_content($this->data['items'][$this->item_number]['link'][$this->item_link_number], $data); + break; + + case 'ENCLOSURE': + case $this->namespaces['rss1'] . ':ENCLOSURE': + case $this->namespaces['rss2'] . ':ENCLOSURE': + case $this->namespaces['atom'] . ':ENCLOSURE': + $this->do_add_content($this->data['items'][$this->item_number]['enclosure'][$this->enclosure_number], $data); + break; + + case 'GUID': + case $this->namespaces['rss1'] . ':GUID': + case $this->namespaces['rss2'] . ':GUID': + $this->do_add_content($this->data['items'][$this->item_number]['guid'], $data); + break; + + case 'ID': + case $this->namespaces['atom'] . ':ID': + $this->do_add_content($this->data['items'][$this->item_number]['id'], $data); + break; + + case 'PUBDATE': + case $this->namespaces['rss1'] . ':PUBDATE': + case $this->namespaces['rss2'] . ':PUBDATE': + $this->do_add_content($this->data['items'][$this->item_number]['pubdate'], $data); + break; + + case $this->namespaces['dc'] . ':DATE': + $this->do_add_content($this->data['items'][$this->item_number]['dc:date'], $data); + break; + + case 'ISSUED': + case $this->namespaces['atom'] . ':ISSUED': + $this->do_add_content($this->data['items'][$this->item_number]['issued'], $data); + break; + + case 'PUBLISHED': + case $this->namespaces['atom'] . ':PUBLISHED': + $this->do_add_content($this->data['items'][$this->item_number]['published'], $data); + break; + + case 'MODIFIED': + case $this->namespaces['atom'] . ':MODIFIED': + $this->do_add_content($this->data['items'][$this->item_number]['modified'], $data); + break; + + case 'UPDATED': + case $this->namespaces['atom'] . ':UPDATED': + $this->do_add_content($this->data['items'][$this->item_number]['updated'], $data); + break; + + case 'CATEGORY': + case $this->namespaces['rss1'] . ':CATEGORY': + case $this->namespaces['rss2'] . ':CATEGORY': + case $this->namespaces['atom'] . ':CATEGORY': + $this->do_add_content($this->data['items'][$this->item_number]['category'][$this->category_number], $data); + break; + + case $this->namespaces['dc'] . ':SUBJECT': + $this->do_add_content($this->data['items'][$this->item_number]['subject'][$this->category_number], $data); + break; + + case $this->namespaces['dc'] . ':CREATOR': + $this->do_add_content($this->data['items'][$this->item_number]['creator'][$this->author_number], $data); + break; + + case 'AUTHOR': + case $this->namespaces['rss1'] . ':AUTHOR': + case $this->namespaces['rss2'] . ':AUTHOR': + $this->do_add_content($this->data['items'][$this->item_number]['author'][$this->author_number]['rss'], $data); + break; + } + + if ($this->inside_author) + { + switch ($this->tag_name) + { + case 'NAME': + case $this->namespaces['atom'] . ':NAME': + $this->do_add_content($this->data['items'][$this->item_number]['author'][$this->author_number]['name'], $data); + break; + + case 'URL': + case $this->namespaces['atom'] . ':URL': + $this->do_add_content($this->data['items'][$this->item_number]['author'][$this->author_number]['url'], $data); + break; + + case 'URI': + case $this->namespaces['atom'] . ':URI': + $this->do_add_content($this->data['items'][$this->item_number]['author'][$this->author_number]['uri'], $data); + break; + + case 'HOMEPAGE': + case $this->namespaces['atom'] . ':HOMEPAGE': + $this->do_add_content($this->data['items'][$this->item_number]['author'][$this->author_number]['homepage'], $data); + break; + + case 'EMAIL': + case $this->namespaces['atom'] . ':EMAIL': + $this->do_add_content($this->data['items'][$this->item_number]['author'][$this->author_number]['email'], $data); + break; + } + } + } + + else if (($this->inside_channel && !$this->inside_image) || (isset($this->data['feedinfo']['type']) && $this->data['feedinfo']['type'] == 'Atom')) + { + switch ($this->tag_name) + { + case 'TITLE': + case $this->namespaces['rss1'] . ':TITLE': + case $this->namespaces['rss2'] . ':TITLE': + case $this->namespaces['atom'] . ':TITLE': + $this->do_add_content($this->data['info']['title'], $data); + break; + + case 'LINK': + case $this->namespaces['rss1'] . ':LINK': + case $this->namespaces['rss2'] . ':LINK': + case $this->namespaces['atom'] . ':LINK': + $this->do_add_content($this->data['info']['link'][$this->link_number], $data); + break; + + case 'DESCRIPTION': + case $this->namespaces['rss1'] . ':DESCRIPTION': + case $this->namespaces['rss2'] . ':DESCRIPTION': + $this->do_add_content($this->data['info']['description'], $data); + break; + + case $this->namespaces['dc'] . ':DESCRIPTION': + $this->do_add_content($this->data['info']['dc:description'], $data); + break; + + case 'TAGLINE': + case $this->namespaces['atom'] . ':TAGLINE': + $this->do_add_content($this->data['info']['tagline'], $data); + break; + + case 'SUBTITLE': + case $this->namespaces['atom'] . ':SUBTITLE': + $this->do_add_content($this->data['info']['subtitle'], $data); + break; + + case 'COPYRIGHT': + case $this->namespaces['rss1'] . ':COPYRIGHT': + case $this->namespaces['rss2'] . ':COPYRIGHT': + case $this->namespaces['atom'] . ':COPYRIGHT': + $this->do_add_content($this->data['info']['copyright'], $data); + break; + + case 'LANGUAGE': + case $this->namespaces['rss1'] . ':LANGUAGE': + case $this->namespaces['rss2'] . ':LANGUAGE': + $this->do_add_content($this->data['info']['language'], $data); + break; + + case 'LOGO': + case $this->namespaces['atom'] . ':LOGO': + $this->do_add_content($this->data['info']['logo'], $data); + break; + + } + } + + else if ($this->inside_channel && $this->inside_image) + { + switch ($this->tag_name) + { + case 'TITLE': + case $this->namespaces['rss1'] . ':TITLE': + case $this->namespaces['rss2'] . ':TITLE': + $this->do_add_content($this->data['info']['image']['title'], $data); + break; + + case 'URL': + case $this->namespaces['rss1'] . ':URL': + case $this->namespaces['rss2'] . ':URL': + $this->do_add_content($this->data['info']['image']['url'], $data); + break; + + case 'LINK': + case $this->namespaces['rss1'] . ':LINK': + case $this->namespaces['rss2'] . ':LINK': + $this->do_add_content($this->data['info']['image']['link'], $data); + break; + + case 'WIDTH': + case $this->namespaces['rss1'] . ':WIDTH': + case $this->namespaces['rss2'] . ':WIDTH': + $this->do_add_content($this->data['info']['image']['width'], $data); + break; + + case 'HEIGHT': + case $this->namespaces['rss1'] . ':HEIGHT': + case $this->namespaces['rss2'] . ':HEIGHT': + $this->do_add_content($this->data['info']['image']['height'], $data); + break; + } + } + $this->is_first = false; + } + + function end_handler($parser, $name) + { + $this->tag_name = ''; + switch ($name) + { + case 'ITEM': + case $this->namespaces['rss1'] . ':ITEM': + case $this->namespaces['rss2'] . ':ITEM': + case 'ENTRY': + case $this->namespaces['atom'] . ':ENTRY': + $this->inside_item = false; + $this->item_number++; + $this->author_number = 0; + $this->category_number = 0; + $this->enclosure_number = 0; + $this->item_link_number = 0; + break; + + case 'CHANNEL': + case $this->namespaces['rss1'] . ':CHANNEL': + case $this->namespaces['rss2'] . ':CHANNEL': + $this->inside_channel = false; + break; + + case 'IMAGE': + case $this->namespaces['rss1'] . ':IMAGE': + case $this->namespaces['rss2'] . ':IMAGE': + $this->inside_image = false; + break; + + case 'AUTHOR': + case $this->namespaces['rss1'] . ':AUTHOR': + case $this->namespaces['rss2'] . ':AUTHOR': + case $this->namespaces['atom'] . ':AUTHOR': + $this->author_number++; + $this->inside_author = false; + break; + + case 'CATEGORY': + case $this->namespaces['rss1'] . ':CATEGORY': + case $this->namespaces['rss2'] . ':CATEGORY': + case $this->namespaces['atom'] . ':CATEGORY': + case $this->namespaces['dc'] . ':SUBJECT': + $this->category_number++; + break; + + case 'ENCLOSURE': + case $this->namespaces['rss1'] . ':ENCLOSURE': + case $this->namespaces['rss2'] . ':ENCLOSURE': + $this->enclosure_number++; + break; + + case 'LINK': + case $this->namespaces['rss1'] . ':LINK': + case $this->namespaces['rss2'] . ':LINK': + case $this->namespaces['atom'] . ':LINK': + if ($this->inside_item) + { + $this->item_link_number++; + } + else + { + $this->link_number++; + } + break; + } + } + + function start_name_space($parser, $prefix, $uri = null) + { + $prefix = strtoupper($prefix); + $uri = strtoupper($uri); + if ($prefix == 'ATOM' || $uri == 'HTTP://WWW.W3.ORG/2005/ATOM' || $uri == 'HTTP://PURL.ORG/ATOM/NS#') + { + $this->namespaces['atom'] = $uri; + } + else if ($prefix == 'RSS2' || $uri == 'HTTP://BACKEND.USERLAND.COM/RSS2') + { + $this->namespaces['rss2'] = $uri; + } + else if ($prefix == 'RDF' || $uri == 'HTTP://WWW.W3.ORG/1999/02/22-RDF-SYNTAX-NS#') + { + $this->namespaces['rdf'] = $uri; + } + else if ($prefix == 'RSS' || $uri == 'HTTP://PURL.ORG/RSS/1.0/' || $uri == 'HTTP://MY.NETSCAPE.COM/RDF/SIMPLE/0.9/') + { + $this->namespaces['rss1'] = $uri; + } + else if ($prefix == 'DC' || $uri == 'HTTP://PURL.ORG/DC/ELEMENTS/1.1/') + { + $this->namespaces['dc'] = $uri; + } + else if ($prefix == 'XHTML' || $uri == 'HTTP://WWW.W3.ORG/1999/XHTML') + { + $this->namespaces['xhtml'] = $uri; + $this->xhtml_prefix = $prefix; + } + else if ($prefix == 'CONTENT' || $uri == 'HTTP://PURL.ORG/RSS/1.0/MODULES/CONTENT/') + { + $this->namespaces['content'] = $uri; + } + } + + function end_name_space($parser, $prefix) + { + if ($key = array_search(strtoupper($prefix), $this->namespaces)) + { + if ($key == 'atom') + { + $this->namespaces['atom'] = 'ATOM'; + } + else if ($key == 'rss2') + { + $this->namespaces['rss2'] = 'RSS'; + } + else if ($key == 'rdf') + { + $this->namespaces['rdf'] = 'RDF'; + } + else if ($key == 'rss1') + { + $this->namespaces['rss1'] = 'RSS'; + } + else if ($key == 'dc') + { + $this->namespaces['dc'] = 'DC'; + } + else if ($key == 'xhtml') + { + $this->namespaces['xhtml'] = 'XHTML'; + $this->xhtml_prefix = 'XHTML'; + } + else if ($key == 'content') + { + $this->namespaces['content'] = 'CONTENT'; + } + } + } +} + +/** + * @package sapphire + * @subpackage integration + */ +class SimplePie_Sanitize +{ + // Private vars + var $feedinfo; + var $info; + var $items; + var $feed_xmlbase; + var $item_xmlbase; + var $attribs; + var $cached_entities; + var $cache_convert_entities; + + // Options + var $remove_div = true; + var $strip_ads = false; + var $replace_headers = false; + var $bypass_image_hotlink = false; + var $bypass_image_hotlink_page = false; + var $strip_htmltags = array('base', 'blink', 'body', 'doctype', 'embed', 'font', 'form', 'frame', 'frameset', 'html', 'iframe', 'input', 'marquee', 'meta', 'noscript', 'object', 'param', 'script', 'style'); + var $encode_instead_of_strip = false; + var $strip_attributes = array('bgsound', 'class', 'expr', 'id', 'style', 'onclick', 'onerror', 'onfinish', 'onmouseover', 'onmouseout', 'onfocus', 'onblur'); + var $input_encoding = 'UTF-8'; + var $output_encoding = 'UTF-8'; + var $item_class = 'SimplePie_Item'; + var $author_class = 'SimplePie_Author'; + var $enclosure_class = 'SimplePie_Enclosure'; + + function remove_div($enable = true) + { + $this->remove_div = (bool) $enable; + } + + function strip_ads($enable = false) + { + $this->strip_ads = (bool) $enable; + } + + function replace_headers($enable = false) + { + $this->enable_headers = (bool) $enable; + } + + function bypass_image_hotlink($get = false) + { + if ($get) + { + $this->bypass_image_hotlink = (string) $get; + } + else + { + $this->bypass_image_hotlink = false; + } + } + + function bypass_image_hotlink_page($page = false) + { + if ($page) + { + $this->bypass_image_hotlink_page = (string) $page; + } + else + { + $this->bypass_image_hotlink_page = false; + } + } + + function strip_htmltags($tags = array('base', 'blink', 'body', 'doctype', 'embed', 'font', 'form', 'frame', 'frameset', 'html', 'iframe', 'input', 'marquee', 'meta', 'noscript', 'object', 'param', 'script', 'style')) + { + if ($tags) + { + if (is_array($tags)) + { + $this->strip_htmltags = $tags; + } + else + { + $this->strip_htmltags = explode(',', $tags); + } + } + else + { + $this->strip_htmltags = false; + } + } + + function encode_instead_of_strip($enable = false) + { + $this->encode_instead_of_strip = (bool) $enable; + } + + function strip_attributes($attribs = array('bgsound', 'class', 'expr', 'id', 'style', 'onclick', 'onerror', 'onfinish', 'onmouseover', 'onmouseout', 'onfocus', 'onblur')) + { + if ($attribs) + { + if (is_array($attribs)) + { + $this->strip_attributes = $attribs; + } + else + { + $this->strip_attributes = explode(',', $attribs); + } + } + else + { + $this->strip_attributes = false; + } + } + + function input_encoding($encoding = 'UTF-8') + { + $this->input_encoding = (string) $encoding; + } + + function output_encoding($encoding = 'UTF-8') + { + $this->output_encoding = (string) $encoding; + } + + function set_item_class($class = 'SimplePie_Item') + { + if (SimplePie_Misc::is_a_class($class, 'SimplePie_Item')) + { + $this->item_class = $class; + return true; + } + return false; + } + + function set_author_class($class = 'SimplePie_Author') + { + if (SimplePie_Misc::is_a_class($class, 'SimplePie_Author')) + { + $this->author_class = $class; + return true; + } + return false; + } + + function set_enclosure_class($class = 'SimplePie_Enclosure') + { + if (SimplePie_Misc::is_a_class($class, 'SimplePie_Enclosure')) + { + $this->enclosure_class = $class; + return true; + } + return false; + } + + function parse_data_array(&$data, $url) + { + // Feed Info (Type and Version) + if (!empty($data['feedinfo']['type'])) + { + $this->feedinfo = $data['feedinfo']; + } + + // Feed level xml:base + if (!empty($data['feeddata']['attribs']['XML:BASE'])) + { + $this->feed_xmlbase = $data['feeddata']['attribs']['XML:BASE']; + } + else if (!empty($data['feeddata']['attribs']['HTTP://WWW.W3.ORG/XML/1998/NAMESPACE:BASE'])) + { + $this->feed_xmlbase = $data['feeddata']['attribs']['HTTP://WWW.W3.ORG/XML/1998/NAMESPACE:BASE']; + } + // FeedBurner feeds use alternate link + else if (strpos($url, 'http://feeds.feedburner.com/') !== 0) + { + $this->feed_xmlbase = SimplePie_Misc::parse_url($url); + if (empty($this->feed_xmlbase['authority'])) + { + $this->feed_xmlbase = preg_replace('/^' . preg_quote(realpath($_SERVER['DOCUMENT_ROOT']), '/') . '/', '', realpath($url)); + } + else + { + $this->feed_xmlbase = $url; + } + } + + + // Feed link(s) + if (!empty($data['info']['link'])) + { + foreach ($data['info']['link'] as $link) + { + if (empty($link['attribs']['REL'])) + { + $rel = 'alternate'; + } + else + { + $rel = strtolower($link['attribs']['REL']); + } + if ($rel == 'enclosure') + { + $href = null; + $type = null; + $length = null; + if (!empty($link['data'])) + { + $href = $this->sanitize($link['data'], $link['attribs'], true); + } + else if (!empty($link['attribs']['HREF'])) + { + $href = $this->sanitize($link['attribs']['HREF'], $link['attribs'], true); + } + if (!empty($link['attribs']['TYPE'])) { + $type = $this->sanitize($link['attribs']['TYPE'], $link['attribs']); + } + if (!empty($link['attribs']['LENGTH'])) { + $length = $this->sanitize($link['attribs']['LENGTH'], $link['attribs']); + } + $this->info['link']['enclosure'][] = new $this->enclosure_class($href, $type, $length); + } + else + { + if (!empty($link['data'])) + { + $this->info['link'][$rel][] = $this->sanitize($link['data'], $link['attribs'], true); + } + else if (!empty($link['attribs']['HREF'])) + { + $this->info['link'][$rel][] = $this->sanitize($link['attribs']['HREF'], $link['attribs'], true); + } + } + } + } + + // Use the first alternate link if we don't have any feed xml:base + if (empty($this->feed_xmlbase) && !empty($this->info['link']['alternate'][0])) + { + $this->feed_xmlbase = $this->info['link']['alternate'][0]; + } + + // Feed Title + if (!empty($data['info']['title']['data'])) + { + $this->info['title'] = $this->sanitize($data['info']['title']['data'], $data['info']['title']['attribs']); + } + + // Feed Descriptions + if (!empty($data['info']['description']['data'])) + { + $this->info['description'] = $this->sanitize($data['info']['description']['data'], $data['info']['description']['attribs'], false, true); + } + if (!empty($data['info']['dc:description']['data'])) + { + $this->info['dc:description'] = $this->sanitize($data['info']['dc:description']['data'], $data['info']['dc:description']['attribs']); + } + if (!empty($data['info']['tagline']['data'])) + { + $this->info['tagline'] = $this->sanitize($data['info']['tagline']['data'], $data['info']['tagline']['attribs']); + } + if (!empty($data['info']['subtitle']['data'])) + { + $this->info['subtitle'] = $this->sanitize($data['info']['subtitle']['data'], $data['info']['subtitle']['attribs']); + } + + // Feed Language + if (!empty($data['info']['language']['data'])) + { + $this->info['language'] = $this->sanitize($data['info']['language']['data'], $data['info']['language']['attribs']); + } + if (!empty($data['feeddata']['attribs']['XML:LANG'])) + { + $this->info['xml:lang'] = $this->sanitize($data['feeddata']['attribs']['XML:LANG'], null); + } + else if (!empty($data['feeddata']['attribs']['HTTP://WWW.W3.ORG/XML/1998/NAMESPACE:LANG'])) + { + $this->info['xml:lang'] = $this->sanitize($data['feeddata']['attribs']['HTTP://WWW.W3.ORG/XML/1998/NAMESPACE:LANG'], null); + } + + // Feed Copyright + if (!empty($data['info']['copyright']['data'])) + { + $this->info['copyright'] = $this->sanitize($data['info']['copyright']['data'], $data['info']['copyright']['attribs']); + } + + // Feed Image + if (!empty($data['info']['image']['title']['data'])) + { + $this->info['image']['title'] = $this->sanitize($data['info']['image']['title']['data'], $data['info']['image']['title']['attribs']); + } + if (!empty($data['info']['image']['url']['data'])) + { + $this->info['image']['url'] = $this->sanitize($data['info']['image']['url']['data'], $data['info']['image']['url']['attribs'], true); + } + if (!empty($data['info']['logo']['data'])) + { + $this->info['image']['logo'] = $this->sanitize($data['info']['logo']['data'], $data['info']['logo']['attribs'], true); + } + if (!empty($data['info']['image']['link']['data'])) + { + $this->info['image']['link'] = $this->sanitize($data['info']['image']['link']['data'], $data['info']['image']['link']['attribs'], true); + } + if (!empty($data['info']['image']['width']['data'])) + { + $this->info['image']['width'] = $this->sanitize($data['info']['image']['width']['data'], $data['info']['image']['width']['attribs']); + } + if (!empty($data['info']['image']['height']['data'])) + { + $this->info['image']['height'] = $this->sanitize($data['info']['image']['height']['data'], $data['info']['image']['height']['attribs']); + } + + // Items + if (!empty($data['items'])) + { + foreach ($data['items'] as $key => $item) + { + $newitem = null; + + // Item level xml:base + if (!empty($item['attribs']['XML:BASE'])) + { + $this->item_xmlbase = SimplePie_Misc::absolutize_url($item['attribs']['XML:BASE'], $this->feed_xmlbase); + } + else if (!empty($item['attribs']['HTTP://WWW.W3.ORG/XML/1998/NAMESPACE:BASE'])) + { + $this->item_xmlbase = SimplePie_Misc::absolutize_url($item['attribs']['HTTP://WWW.W3.ORG/XML/1998/NAMESPACE:BASE'], $this->feed_xmlbase); + } + else + { + $this->item_xmlbase = null; + } + + // Title + if (!empty($item['title']['data'])) { + $newitem['title'] = $this->sanitize($item['title']['data'], $item['title']['attribs']); + } + if (!empty($item['dc:title']['data'])) + { + $newitem['dc:title'] = $this->sanitize($item['dc:title']['data'], $item['dc:title']['attribs']); + } + + // Description + if (!empty($item['content']['data'])) + { + $newitem['content'] = $this->sanitize($item['content']['data'], $item['content']['attribs']); + } + if (!empty($item['encoded']['data'])) + { + $newitem['encoded'] = $this->sanitize($item['encoded']['data'], $item['encoded']['attribs']); + } + if (!empty($item['summary']['data'])) + { + $newitem['summary'] = $this->sanitize($item['summary']['data'], $item['summary']['attribs']); + } + if (!empty($item['description']['data'])) + { + $newitem['description'] = $this->sanitize($item['description']['data'], $item['description']['attribs'], false, true); + } + if (!empty($item['dc:description']['data'])) + { + $newitem['dc:description'] = $this->sanitize($item['dc:description']['data'], $item['dc:description']['attribs']); + } + if (!empty($item['longdesc']['data'])) + { + $newitem['longdesc'] = $this->sanitize($item['longdesc']['data'], $item['longdesc']['attribs']); + } + + // Link(s) + if (!empty($item['link'])) + { + foreach ($item['link'] as $link) + { + if (empty($link['attribs']['REL'])) + { + $rel = 'alternate'; + } + else + { + $rel = strtolower($link['attribs']['REL']); + } + if ($rel == 'enclosure') + { + $href = null; + $type = null; + $length = null; + if (!empty($link['data'])) + { + $href = $this->sanitize($link['data'], $link['attribs'], true); + } + else if (!empty($link['attribs']['HREF'])) + { + $href = $this->sanitize($link['attribs']['HREF'], $link['attribs'], true); + } + if (!empty($link['attribs']['TYPE'])) { + $type = $this->sanitize($link['attribs']['TYPE'], $link['attribs']); + } + if (!empty($link['attribs']['LENGTH'])) { + $length = $this->sanitize($link['attribs']['LENGTH'], $link['attribs']); + } + if (!empty($href)) + { + $newitem['link'][$rel][] = new $this->enclosure_class($href, $type, $length); + } + } + else + { + if (!empty($link['data'])) + { + $newitem['link'][$rel][] = $this->sanitize($link['data'], $link['attribs'], true); + } + else if (!empty($link['attribs']['HREF'])) + { + $newitem['link'][$rel][] = $this->sanitize($link['attribs']['HREF'], $link['attribs'], true); + } + } + } + } + + // Enclosure(s) + if (!empty($item['enclosure'])) + { + foreach ($item['enclosure'] as $enclosure) + { + if (!empty($enclosure['attribs']['URL'])) + { + $type = null; + $length = null; + $href = $this->sanitize($enclosure['attribs']['URL'], $enclosure['attribs'], true); + if (!empty($enclosure['attribs']['TYPE'])) + { + $type = $this->sanitize($enclosure['attribs']['TYPE'], $enclosure['attribs']); + } + if (!empty($enclosure['attribs']['LENGTH'])) + { + $length = $this->sanitize($enclosure['attribs']['LENGTH'], $enclosure['attribs']); + } + $newitem['enclosures'][] = new $this->enclosure_class($href, $type, $length); + } + } + } + + // ID + if (!empty($item['guid']['data'])) + { + if (!empty($item['guid']['attribs']['ISPERMALINK']) && strtolower($item['guid']['attribs']['ISPERMALINK']) == 'false') + { + $newitem['guid']['permalink'] = false; + } + else + { + $newitem['guid']['permalink'] = true; + } + $newitem['guid']['data'] = $this->sanitize($item['guid']['data'], $item['guid']['attribs']); + } + if (!empty($item['id']['data'])) + { + $newitem['id'] = $this->sanitize($item['id']['data'], $item['id']['attribs']); + } + + // Date + if (!empty($item['pubdate']['data'])) + { + $newitem['pubdate'] = $this->parse_date($this->sanitize($item['pubdate']['data'], $item['pubdate']['attribs'])); + } + if (!empty($item['dc:date']['data'])) + { + $newitem['dc:date'] = $this->parse_date($this->sanitize($item['dc:date']['data'], $item['dc:date']['attribs'])); + } + if (!empty($item['issued']['data'])) + { + $newitem['issued'] = $this->parse_date($this->sanitize($item['issued']['data'], $item['issued']['attribs'])); + } + if (!empty($item['published']['data'])) + { + $newitem['published'] = $this->parse_date($this->sanitize($item['published']['data'], $item['published']['attribs'])); + } + if (!empty($item['modified']['data'])) + { + $newitem['modified'] = $this->parse_date($this->sanitize($item['modified']['data'], $item['modified']['attribs'])); + } + if (!empty($item['updated']['data'])) + { + $newitem['updated'] = $this->parse_date($this->sanitize($item['updated']['data'], $item['updated']['attribs'])); + } + + // Categories + if (!empty($item['category'])) + { + foreach ($item['category'] as $category) + { + if (!empty($category['data'])) + { + $newitem['category'][] = $this->sanitize($category['data'], $category['attribs']); + } + else if (!empty($category['attribs']['TERM'])) + { + $newitem['term'][] = $this->sanitize($category['attribs']['TERM'], $category['attribs']); + } + } + } + if (!empty($item['subject'])) + { + foreach ($item['subject'] as $category) + { + if (!empty($category['data'])) + { + $newitem['subject'][] = $this->sanitize($category['data'], $category['attribs']); + } + } + } + + // Author + if (!empty($item['creator'])) + { + foreach ($item['creator'] as $creator) + { + if (!empty($creator['data'])) + { + $newitem['creator'][] = new $this->author_class($this->sanitize($creator['data'], $creator['attribs']), null, null); + } + } + } + if (!empty($item['author'])) + { + foreach ($item['author'] as $author) + { + $name = null; + $link = null; + $email = null; + if (!empty($author['rss'])) + { + $sane = $this->sanitize($author['rss']['data'], $author['rss']['attribs']); + if (preg_match('/(.*)@(.*) \((.*)\)/msiU', $sane, $matches)) { + $name = trim($matches[3]); + $email = trim("$matches[1]@$matches[2]"); + } else { + $email = $sane; + } + } + else + { + if (!empty($author['name'])) + { + $name = $this->sanitize($author['name']['data'], $author['name']['attribs']); + } + if (!empty($author['url'])) + { + $link = $this->sanitize($author['url']['data'], $author['url']['attribs'], true); + } + else if (!empty($author['uri'])) + { + $link = $this->sanitize($author['uri']['data'], $author['uri']['attribs'], true); + } + else if (!empty($author['homepage'])) + { + $link = $this->sanitize($author['homepage']['data'], $author['homepage']['attribs'], true); + } + if (!empty($author['email'])) { + $email = $this->sanitize($author['email']['data'], $author['email']['attribs']); + } + } + $newitem['author'][] = new $this->author_class($name, $link, $email); + } + } + unset($data['items'][$key]); + $this->items[] = new $this->item_class($newitem); + } + } + } + + function sanitize($data, $attribs, $is_url = false, $force_decode = false) + { + $this->attribs = $attribs; + if (isset($this->feedinfo['type']) && $this->feedinfo['type'] == 'Atom') + { + if ((!empty($attribs['MODE']) && $attribs['MODE'] == 'base64') || (!empty($attribs['TYPE']) && $attribs['TYPE'] == 'application/octet-stream')) + { + $data = trim($data); + $data = base64_decode($data); + } + else if ((!empty($attribs['MODE']) && $attribs['MODE'] == 'escaped' || !empty($attribs['TYPE']) && ($attribs['TYPE'] == 'html' || $attribs['TYPE'] == 'text/html'))) + { + $data = $this->entities_decode($data); + } + if (!empty($attribs['TYPE']) && ($attribs['TYPE'] == 'xhtml' || $attribs['TYPE'] == 'application/xhtml+xml')) + { + if ($this->remove_div) + { + $data = preg_replace('//msiU', '', strrev(preg_replace('/>vid\//msiU', '
', $data, 1); + } + $data = $this->convert_entities($data); + } + } + else + { + $data = $this->convert_entities($data); + } + if ($force_decode) + { + $data = $this->entities_decode($data); + } + $data = trim($data); + $data = preg_replace('/<\!--([^-]|-[^-])*-->/msiU', '', $data); + + // If Strip Ads is enabled, strip them. + if ($this->strip_ads) + { + $data = preg_replace('//msiU', '', $data); // Pheedo links (tested with Dooce.com) + $data = preg_replace('/(.*)/msiU', '', $data); + $data = preg_replace('/<\/h[1-3]>/i', '', $data); + } + + if ($is_url) + { + $data = $this->replace_urls($data, true); + } + else + { + $data = preg_replace_callback('/<(\S+)((\s*((\w+:)?\w+)\s*=\s*("([^"]*)"|\'([^\']*)\'|(.*)))*)\s*(\/>|>(.*)<\/\S+>)/msiU', array(&$this, 'replace_urls'), $data); + } + + // If Bypass Image Hotlink is enabled, rewrite all the image tags. + if ($this->bypass_image_hotlink) + { + $images = SimplePie_Misc::get_element('img', $data); + foreach ($images as $img) + { + if (!empty($img['attribs']['SRC']['data'])) + { + $pre = ''; + if ($this->bypass_image_hotlink_page) + { + $pre = $this->bypass_image_hotlink_page; + } + $pre .= "?$this->bypass_image_hotlink="; + $img['attribs']['SRC']['data'] = $pre . rawurlencode(strtr($img['attribs']['SRC']['data'], array_flip(get_html_translation_table(HTML_SPECIALCHARS, ENT_QUOTES)))); + $data = str_replace($img['full'], SimplePie_Misc::element_implode($img), $data); + } + } + } + + // Strip out HTML tags and attributes that might cause various security problems. + // Based on recommendations by Mark Pilgrim at: + // http://diveintomark.org/archives/2003/06/12/how_to_consume_rss_safely + if ($this->strip_htmltags) + { + foreach ($this->strip_htmltags as $tag) + { + $data = preg_replace_callback("/<($tag)((\s*((\w+:)?\w+)(\s*=\s*(\"([^\"]*)\"|'([^']*)'|(.*)))?)*)\s*(\/>|>(.*)<\/($tag)((\s*((\w+:)?\w+)(\s*=\s*(\"([^\"]*)\"|'([^']*)'|(.*)))?)*)\s*>)/msiU", array(&$this, 'do_strip_htmltags'), $data); + } + } + + if ($this->strip_attributes) + { + foreach ($this->strip_attributes as $attrib) + { + $data = preg_replace('/ '. trim($attrib) .'=("|")(\w|\s|=|-|:|;|\/|\.|\?|&|,|#|!|\(|\)|\'|'|<|>|\+|{|})*("|")/i', '', $data); + $data = preg_replace('/ '. trim($attrib) .'=(\'|')(\w|\s|=|-|:|;|\/|\.|\?|&|,|#|!|\(|\)|"|"|<|>|\+|{|})*(\'|')/i', '', $data); + $data = preg_replace('/ '. trim($attrib) .'=(\w|\s|=|-|:|;|\/|\.|\?|&|,|#|!|\(|\)|\+|{|})*/i', '', $data); + } + } + + // Convert encoding + $data = SimplePie_Misc::change_encoding($data, $this->input_encoding, $this->output_encoding); + + return $data; + } + + function do_strip_htmltags($match) + { + if ($this->encode_instead_of_strip) + { + if (isset($match[12]) && !in_array(strtolower($match[1]), array('script', 'style'))) + { + return "<$match[1]$match[2]>$match[12]</$match[1]>"; + } + else if (isset($match[12])) + { + return "<$match[1]$match[2]></$match[1]>"; + } + else + { + return "<$match[1]$match[2]/>"; + } + } + else + { + if (isset($match[12]) && !in_array(strtolower($match[1]), array('script', 'style'))) + { + return $match[12]; + } + else + { + return ''; + } + } + } + + function replace_urls($data, $raw_url = false) + { + if (!empty($this->attribs['XML:BASE'])) + { + $xmlbase = $attribs['XML:BASE']; + } + else if (!empty($this->attribs['HTTP://WWW.W3.ORG/XML/1998/NAMESPACE:BASE'])) + { + $xmlbase = $this->attribs['HTTP://WWW.W3.ORG/XML/1998/NAMESPACE:BASE']; + } + if (!empty($xmlbase)) + { + if (!empty($this->item_xmlbase)) + { + $xmlbase = SimplePie_Misc::absolutize_url($xmlbase, $this->item_xmlbase); + } + else + { + $xmlbase = SimplePie_Misc::absolutize_url($xmlbase, $this->feed_xmlbase); + } + } + else if (!empty($this->item_xmlbase)) + { + $xmlbase = $this->item_xmlbase; + } + else + { + $xmlbase = $this->feed_xmlbase; + } + + if ($raw_url) + { + return SimplePie_Misc::absolutize_url($data, $xmlbase); + } + else + { + $attributes = array( + 'background', + 'href', + 'src', + 'longdesc', + 'usemap', + 'codebase', + 'data', + 'classid', + 'cite', + 'action', + 'profile', + 'for' + ); + foreach ($attributes as $attribute) + { + if (preg_match("/$attribute='(.*)'/siU", $data[0], $attrib) || preg_match("/$attribute=\"(.*)\"/siU", $data[0], $attrib) || preg_match("/$attribute=(.*)[ |\/|>]/siU", $data[0], $attrib)) + { + $new_tag = str_replace($attrib[1], SimplePie_Misc::absolutize_url($attrib[1], $xmlbase), $attrib[0]); + $data[0] = str_replace($attrib[0], $new_tag, $data[0]); + } + } + return $data[0]; + } + } + + function entities_decode($data) + { + return preg_replace_callback('/&(#)?(x)?([0-9a-z]+);/mi', array(&$this, 'do_entites_decode'), $data); + } + + function do_entites_decode($data) + { + if (isset($this->cached_entities[$data[0]])) + { + return $this->cached_entities[$data[0]]; + } + else + { + $return = SimplePie_Misc::change_encoding(html_entity_decode($data[0], ENT_QUOTES), 'ISO-8859-1', $this->input_encoding); + if ($return == $data[0]) + { + $return = SimplePie_Misc::change_encoding(preg_replace_callback('/&#([x]?[0-9a-f]+);/mi', array(&$this, 'replace_num_entity'), $data[0]), 'UTF-8', $this->input_encoding); + } + $this->cached_entities[$data[0]] = $return; + return $return; + } + } + + function convert_entities($data) + { + return preg_replace_callback('/&#(x)?([0-9a-z]+);/mi', array(&$this, 'do_convert_entities'), $data); + } + + function do_convert_entities($data) + { + if (isset($this->cache_convert_entities[$data[0]])) + { + return $this->cache_convert_entities[$data[0]]; + } + else if (isset($this->cached_entities[$data[0]])) + { + $return = htmlentities($this->cached_entities[$data[0]], ENT_QUOTES, 'UTF-8'); + } + else + { + $return = htmlentities(preg_replace_callback('/&#([x]?[0-9a-f]+);/mi', array(&$this, 'replace_num_entity'), $data[0]), ENT_QUOTES, 'UTF-8'); + } + $this->cache_convert_entities[$data[0]] = $return; + return $return; + } + + /* + * Escape numeric entities + * From a PHP Manual note (on html_entity_decode()) + * Copyright (c) 2005 by "php dot net at c dash ovidiu dot tk", + * "emilianomartinezluque at yahoo dot com" and "hurricane at cyberworldz dot org". + * + * This material may be distributed only subject to the terms and conditions set forth in + * the Open Publication License, v1.0 or later (the latest version is presently available at + * http://www.opencontent.org/openpub/). + */ + function replace_num_entity($ord) + { + $ord = $ord[1]; + if (preg_match('/^x([0-9a-f]+)$/i', $ord, $match)) + { + $ord = hexdec($match[1]); + } + else + { + $ord = intval($ord); + } + + $no_bytes = 0; + $byte = array(); + if ($ord < 128) + { + return chr($ord); + } + if ($ord < 2048) + { + $no_bytes = 2; + } + else if ($ord < 65536) + { + $no_bytes = 3; + } + else if ($ord < 1114112) + { + $no_bytes = 4; + } + else + { + return; + } + switch ($no_bytes) + { + case 2: + $prefix = array(31, 192); + break; + + case 3: + $prefix = array(15, 224); + break; + + case 4: + $prefix = array(7, 240); + break; + } + + for ($i = 0; $i < $no_bytes; $i++) + { + $byte[$no_bytes-$i-1] = (($ord & (63 * pow(2,6*$i))) / pow(2,6*$i)) & 63 | 128; + } + $byte[0] = ($byte[0] & $prefix[0]) | $prefix[1]; + + $ret = ''; + for ($i = 0; $i < $no_bytes; $i++) + { + $ret .= chr($byte[$i]); + } + return $ret; + } + + function parse_date($date) + { + $military_timezone = array('A' => '-0100', 'B' => '-0200', 'C' => '-0300', 'D' => '-0400', 'E' => '-0500', 'F' => '-0600', 'G' => '-0700', 'H' => '-0800', 'I' => '-0900', 'K' => '-1000', 'L' => '-1100', 'M' => '-1200', 'N' => '+0100', 'O' => '+0200', 'P' => '+0300', 'Q' => '+0400', 'R' => '+0500', 'S' => '+0600', 'T' => '+0700', 'U' => '+0800', 'V' => '+0900', 'W' => '+1000', 'X' => '+1100', 'Y' => '+1200', 'Z' => '-0000'); + $north_american_timezone = array('GMT' => '-0000', 'EST' => '-0500', 'EDT' => '-0400', 'CST' => '-0600', 'CDT' => '-0500', 'MST' => '-0700', 'MDT' => '-0600', 'PST' => '-0800', 'PDT' => '-0700'); + if (preg_match('/([0-9]{2,4})-?([0-9]{2})-?([0-9]{2})T([0-9]{2}):?([0-9]{2})(:?([0-9]{2}(\.[0-9]*)?))?(UT|GMT|EST|EDT|CST|CDT|MST|MDT|PST|PDT|[a-z]|(\\+|-)[0-9]{4}|(\\+|-)[0-9]{2}:[0-9]{2})?/i', $date, $matches)) + { + if (!isset($matches[7])) + { + $matches[7] = ''; + } + if (!isset($matches[9])) + { + $matches[9] = ''; + } + $matches[7] = str_pad(round($matches[7]), 2, '0', STR_PAD_LEFT); + switch (strlen($matches[9])) + { + case 0: + $timezone = ''; + break; + + case 1: + $timezone = $military_timezone[strtoupper($matches[9])]; + break; + + case 2: + $timezone = '-0000'; + break; + + case 3: + $timezone = $north_american_timezone[strtoupper($matches[9])]; + break; + + case 5: + $timezone = $matches[9]; + break; + + case 6: + $timezone = substr_replace($matches[9], '', 3, 1); + break; + } + $date = strtotime("$matches[1]-$matches[2]-$matches[3] $matches[4]:$matches[5]:$matches[7] $timezone"); + } + else if (preg_match('/([0-9]{1,2})\s*(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s*([0-9]{2}|[0-9]{4})\s*([0-9]{2}):([0-9]{2})(:([0-9]{2}(\.[0-9]*)?))?\s*(UT|GMT|EST|EDT|CST|CDT|MST|MDT|PST|PDT|[a-z]|(\\+|-)[0-9]{4}|(\\+|-)[0-9]{2}:[0-9]{2})?/i', $date, $matches)) + { + $three_month = array('Jan' => 1, 'Feb' => 2, 'Mar' => 3, 'Apr' => 4, 'May' => 5, 'Jun' => 6, 'Jul' => 7, 'Aug' => 8, 'Sep' => 9, 'Oct' => 10, 'Nov' => 11, 'Dec' => 12); + $month = $three_month[$matches[2]]; + if (strlen($matches[3]) == 2) + { + $year = ($matches[3] < 70) ? "20$matches[3]" : "19$matches[3]"; + } + else + { + $year = $matches[3]; + } + if (!isset($matches[7])) + { + $matches[7] = ''; + } + if (!isset($matches[9])) + { + $matches[9] = ''; + } + $second = str_pad(round($matches[7]), 2, '0', STR_PAD_LEFT); + switch (strlen($matches[9])) + { + case 0: + $timezone = ''; + break; + + case 1: + $timezone = $military_timezone[strtoupper($matches[9])]; + break; + + case 2: + $timezone = '-0000'; + break; + + case 3: + $timezone = $north_american_timezone[strtoupper($matches[9])]; + break; + + case 5: + $timezone = $matches[9]; + break; + + case 6: + $timezone = substr_replace($matches[9], '', 3, 1); + break; + } + $date = strtotime("$year-$month-$matches[1] $matches[4]:$matches[5]:$second $timezone"); + } + else + { + $date = strtotime($date); + } + if ($date !== false && $date !== -1) + { + return $date; + } + else + { + return false; + } + } +} + +?> \ No newline at end of file diff --git a/thirdparty/simpletest/LICENSE b/thirdparty/simpletest/LICENSE new file mode 100644 index 000000000..09f465ab7 --- /dev/null +++ b/thirdparty/simpletest/LICENSE @@ -0,0 +1,502 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/thirdparty/simpletest/VERSION b/thirdparty/simpletest/VERSION new file mode 100644 index 000000000..7f207341d --- /dev/null +++ b/thirdparty/simpletest/VERSION @@ -0,0 +1 @@ +1.0.1 \ No newline at end of file diff --git a/thirdparty/simpletest/_manifest_exclude b/thirdparty/simpletest/_manifest_exclude new file mode 100644 index 000000000..e69de29bb diff --git a/thirdparty/simpletest/compatibility.php b/thirdparty/simpletest/compatibility.php new file mode 100644 index 000000000..4e0f78a4b --- /dev/null +++ b/thirdparty/simpletest/compatibility.php @@ -0,0 +1,173 @@ += 0) { + eval('$copy = clone $object;'); + return $copy; + } + return $object; + } + + /** + * Identity test. Drops back to equality + types for PHP5 + * objects as the === operator counts as the + * stronger reference constraint. + * @param mixed $first Test subject. + * @param mixed $second Comparison object. + * @return boolean True if identical. + * @access public + * @static + */ + function isIdentical($first, $second) { + if (version_compare(phpversion(), '5') >= 0) { + return SimpleTestCompatibility::_isIdenticalType($first, $second); + } + if ($first != $second) { + return false; + } + return ($first === $second); + } + + /** + * Recursive type test. + * @param mixed $first Test subject. + * @param mixed $second Comparison object. + * @return boolean True if same type. + * @access private + * @static + */ + function _isIdenticalType($first, $second) { + if (gettype($first) != gettype($second)) { + return false; + } + if (is_object($first) && is_object($second)) { + if (get_class($first) != get_class($second)) { + return false; + } + return SimpleTestCompatibility::_isArrayOfIdenticalTypes( + get_object_vars($first), + get_object_vars($second)); + } + if (is_array($first) && is_array($second)) { + return SimpleTestCompatibility::_isArrayOfIdenticalTypes($first, $second); + } + if ($first !== $second) { + return false; + } + return true; + } + + /** + * Recursive type test for each element of an array. + * @param mixed $first Test subject. + * @param mixed $second Comparison object. + * @return boolean True if identical. + * @access private + * @static + */ + function _isArrayOfIdenticalTypes($first, $second) { + if (array_keys($first) != array_keys($second)) { + return false; + } + foreach (array_keys($first) as $key) { + $is_identical = SimpleTestCompatibility::_isIdenticalType( + $first[$key], + $second[$key]); + if (! $is_identical) { + return false; + } + } + return true; + } + + /** + * Test for two variables being aliases. + * @param mixed $first Test subject. + * @param mixed $second Comparison object. + * @return boolean True if same. + * @access public + * @static + */ + function isReference(&$first, &$second) { + if (version_compare(phpversion(), '5', '>=') && is_object($first)) { + return ($first === $second); + } + if (is_object($first) && is_object($second)) { + $id = uniqid("test"); + $first->$id = true; + $is_ref = isset($second->$id); + unset($first->$id); + return $is_ref; + } + $temp = $first; + $first = uniqid("test"); + $is_ref = ($first === $second); + $first = $temp; + return $is_ref; + } + + /** + * Test to see if an object is a member of a + * class hiearchy. + * @param object $object Object to test. + * @param string $class Root name of hiearchy. + * @return boolean True if class in hiearchy. + * @access public + * @static + */ + function isA($object, $class) { + if (version_compare(phpversion(), '5') >= 0) { + if (! class_exists($class, false)) { + if (function_exists('interface_exists')) { + if (! interface_exists($class, false)) { + return false; + } + } + } + eval("\$is_a = \$object instanceof $class;"); + return $is_a; + } + if (function_exists('is_a')) { + return is_a($object, $class); + } + return ((strtolower($class) == get_class($object)) + or (is_subclass_of($object, $class))); + } + + /** + * Sets a socket timeout for each chunk. + * @param resource $handle Socket handle. + * @param integer $timeout Limit in seconds. + * @access public + * @static + */ + function setTimeout($handle, $timeout) { + if (function_exists('stream_set_timeout')) { + stream_set_timeout($handle, $timeout, 0); + } elseif (function_exists('socket_set_timeout')) { + socket_set_timeout($handle, $timeout, 0); + } elseif (function_exists('set_socket_timeout')) { + set_socket_timeout($handle, $timeout, 0); + } + } +} +?> \ No newline at end of file diff --git a/thirdparty/simpletest/cookies.php b/thirdparty/simpletest/cookies.php new file mode 100644 index 000000000..ed1c025d2 --- /dev/null +++ b/thirdparty/simpletest/cookies.php @@ -0,0 +1,380 @@ +_host = false; + $this->_name = $name; + $this->_value = $value; + $this->_path = ($path ? $this->_fixPath($path) : "/"); + $this->_expiry = false; + if (is_string($expiry)) { + $this->_expiry = strtotime($expiry); + } elseif (is_integer($expiry)) { + $this->_expiry = $expiry; + } + $this->_is_secure = $is_secure; + } + + /** + * Sets the host. The cookie rules determine + * that the first two parts are taken for + * certain TLDs and three for others. If the + * new host does not match these rules then the + * call will fail. + * @param string $host New hostname. + * @return boolean True if hostname is valid. + * @access public + */ + function setHost($host) { + if ($host = $this->_truncateHost($host)) { + $this->_host = $host; + return true; + } + return false; + } + + /** + * Accessor for the truncated host to which this + * cookie applies. + * @return string Truncated hostname. + * @access public + */ + function getHost() { + return $this->_host; + } + + /** + * Test for a cookie being valid for a host name. + * @param string $host Host to test against. + * @return boolean True if the cookie would be valid + * here. + */ + function isValidHost($host) { + return ($this->_truncateHost($host) === $this->getHost()); + } + + /** + * Extracts just the domain part that determines a + * cookie's host validity. + * @param string $host Host name to truncate. + * @return string Domain or false on a bad host. + * @access private + */ + function _truncateHost($host) { + $tlds = SimpleUrl::getAllTopLevelDomains(); + if (preg_match('/[a-z\-]+\.(' . $tlds . ')$/i', $host, $matches)) { + return $matches[0]; + } elseif (preg_match('/[a-z\-]+\.[a-z\-]+\.[a-z\-]+$/i', $host, $matches)) { + return $matches[0]; + } + return false; + } + + /** + * Accessor for name. + * @return string Cookie key. + * @access public + */ + function getName() { + return $this->_name; + } + + /** + * Accessor for value. A deleted cookie will + * have an empty string for this. + * @return string Cookie value. + * @access public + */ + function getValue() { + return $this->_value; + } + + /** + * Accessor for path. + * @return string Valid cookie path. + * @access public + */ + function getPath() { + return $this->_path; + } + + /** + * Tests a path to see if the cookie applies + * there. The test path must be longer or + * equal to the cookie path. + * @param string $path Path to test against. + * @return boolean True if cookie valid here. + * @access public + */ + function isValidPath($path) { + return (strncmp( + $this->_fixPath($path), + $this->getPath(), + strlen($this->getPath())) == 0); + } + + /** + * Accessor for expiry. + * @return string Expiry string. + * @access public + */ + function getExpiry() { + if (! $this->_expiry) { + return false; + } + return gmdate("D, d M Y H:i:s", $this->_expiry) . " GMT"; + } + + /** + * Test to see if cookie is expired against + * the cookie format time or timestamp. + * Will give true for a session cookie. + * @param integer/string $now Time to test against. Result + * will be false if this time + * is later than the cookie expiry. + * Can be either a timestamp integer + * or a cookie format date. + * @access public + */ + function isExpired($now) { + if (! $this->_expiry) { + return true; + } + if (is_string($now)) { + $now = strtotime($now); + } + return ($this->_expiry < $now); + } + + /** + * Ages the cookie by the specified number of + * seconds. + * @param integer $interval In seconds. + * @public + */ + function agePrematurely($interval) { + if ($this->_expiry) { + $this->_expiry -= $interval; + } + } + + /** + * Accessor for the secure flag. + * @return boolean True if cookie needs SSL. + * @access public + */ + function isSecure() { + return $this->_is_secure; + } + + /** + * Adds a trailing and leading slash to the path + * if missing. + * @param string $path Path to fix. + * @access private + */ + function _fixPath($path) { + if (substr($path, 0, 1) != '/') { + $path = '/' . $path; + } + if (substr($path, -1, 1) != '/') { + $path .= '/'; + } + return $path; + } +} + +/** + * Repository for cookies. This stuff is a + * tiny bit browser dependent. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleCookieJar { + var $_cookies; + + /** + * Constructor. Jar starts empty. + * @access public + */ + function SimpleCookieJar() { + $this->_cookies = array(); + } + + /** + * Removes expired and temporary cookies as if + * the browser was closed and re-opened. + * @param string/integer $now Time to test expiry against. + * @access public + */ + function restartSession($date = false) { + $surviving_cookies = array(); + for ($i = 0; $i < count($this->_cookies); $i++) { + if (! $this->_cookies[$i]->getValue()) { + continue; + } + if (! $this->_cookies[$i]->getExpiry()) { + continue; + } + if ($date && $this->_cookies[$i]->isExpired($date)) { + continue; + } + $surviving_cookies[] = $this->_cookies[$i]; + } + $this->_cookies = $surviving_cookies; + } + + /** + * Ages all cookies in the cookie jar. + * @param integer $interval The old session is moved + * into the past by this number + * of seconds. Cookies now over + * age will be removed. + * @access public + */ + function agePrematurely($interval) { + for ($i = 0; $i < count($this->_cookies); $i++) { + $this->_cookies[$i]->agePrematurely($interval); + } + } + + /** + * Sets an additional cookie. If a cookie has + * the same name and path it is replaced. + * @param string $name Cookie key. + * @param string $value Value of cookie. + * @param string $host Host upon which the cookie is valid. + * @param string $path Cookie path if not host wide. + * @param string $expiry Expiry date. + * @access public + */ + function setCookie($name, $value, $host = false, $path = '/', $expiry = false) { + $cookie = new SimpleCookie($name, $value, $path, $expiry); + if ($host) { + $cookie->setHost($host); + } + $this->_cookies[$this->_findFirstMatch($cookie)] = $cookie; + } + + /** + * Finds a matching cookie to write over or the + * first empty slot if none. + * @param SimpleCookie $cookie Cookie to write into jar. + * @return integer Available slot. + * @access private + */ + function _findFirstMatch($cookie) { + for ($i = 0; $i < count($this->_cookies); $i++) { + $is_match = $this->_isMatch( + $cookie, + $this->_cookies[$i]->getHost(), + $this->_cookies[$i]->getPath(), + $this->_cookies[$i]->getName()); + if ($is_match) { + return $i; + } + } + return count($this->_cookies); + } + + /** + * Reads the most specific cookie value from the + * browser cookies. Looks for the longest path that + * matches. + * @param string $host Host to search. + * @param string $path Applicable path. + * @param string $name Name of cookie to read. + * @return string False if not present, else the + * value as a string. + * @access public + */ + function getCookieValue($host, $path, $name) { + $longest_path = ''; + foreach ($this->_cookies as $cookie) { + if ($this->_isMatch($cookie, $host, $path, $name)) { + if (strlen($cookie->getPath()) > strlen($longest_path)) { + $value = $cookie->getValue(); + $longest_path = $cookie->getPath(); + } + } + } + return (isset($value) ? $value : false); + } + + /** + * Tests cookie for matching against search + * criteria. + * @param SimpleTest $cookie Cookie to test. + * @param string $host Host must match. + * @param string $path Cookie path must be shorter than + * this path. + * @param string $name Name must match. + * @return boolean True if matched. + * @access private + */ + function _isMatch($cookie, $host, $path, $name) { + if ($cookie->getName() != $name) { + return false; + } + if ($host && $cookie->getHost() && ! $cookie->isValidHost($host)) { + return false; + } + if (! $cookie->isValidPath($path)) { + return false; + } + return true; + } + + /** + * Uses a URL to sift relevant cookies by host and + * path. Results are list of strings of form "name=value". + * @param SimpleUrl $url Url to select by. + * @return array Valid name and value pairs. + * @access public + */ + function selectAsPairs($url) { + $pairs = array(); + foreach ($this->_cookies as $cookie) { + if ($this->_isMatch($cookie, $url->getHost(), $url->getPath(), $cookie->getName())) { + $pairs[] = $cookie->getName() . '=' . $cookie->getValue(); + } + } + return $pairs; + } +} +?> \ No newline at end of file diff --git a/thirdparty/simpletest/encoding.php b/thirdparty/simpletest/encoding.php new file mode 100644 index 000000000..112fe3304 --- /dev/null +++ b/thirdparty/simpletest/encoding.php @@ -0,0 +1,552 @@ +_key = $key; + $this->_value = $value; + } + + /** + * The pair as a single string. + * @return string Encoded pair. + * @access public + */ + function asRequest() { + return urlencode($this->_key) . '=' . urlencode($this->_value); + } + + /** + * The MIME part as a string. + * @return string MIME part encoding. + * @access public + */ + function asMime() { + $part = 'Content-Disposition: form-data; '; + $part .= "name=\"" . $this->_key . "\"\r\n"; + $part .= "\r\n" . $this->_value; + return $part; + } + + /** + * Is this the value we are looking for? + * @param string $key Identifier. + * @return boolean True if matched. + * @access public + */ + function isKey($key) { + return $key == $this->_key; + } + + /** + * Is this the value we are looking for? + * @return string Identifier. + * @access public + */ + function getKey() { + return $this->_key; + } + + /** + * Is this the value we are looking for? + * @return string Content. + * @access public + */ + function getValue() { + return $this->_value; + } +} + +/** + * Single post parameter. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleAttachment { + var $_key; + var $_content; + var $_filename; + + /** + * Stashes the data for rendering later. + * @param string $key Key to add value to. + * @param string $content Raw data. + * @param hash $filename Original filename. + */ + function SimpleAttachment($key, $content, $filename) { + $this->_key = $key; + $this->_content = $content; + $this->_filename = $filename; + } + + /** + * The pair as a single string. + * @return string Encoded pair. + * @access public + */ + function asRequest() { + return ''; + } + + /** + * The MIME part as a string. + * @return string MIME part encoding. + * @access public + */ + function asMime() { + $part = 'Content-Disposition: form-data; '; + $part .= 'name="' . $this->_key . '"; '; + $part .= 'filename="' . $this->_filename . '"'; + $part .= "\r\nContent-Type: " . $this->_deduceMimeType(); + $part .= "\r\n\r\n" . $this->_content; + return $part; + } + + /** + * Attempts to figure out the MIME type from the + * file extension and the content. + * @return string MIME type. + * @access private + */ + function _deduceMimeType() { + if ($this->_isOnlyAscii($this->_content)) { + return 'text/plain'; + } + return 'application/octet-stream'; + } + + /** + * Tests each character is in the range 0-127. + * @param string $ascii String to test. + * @access private + */ + function _isOnlyAscii($ascii) { + for ($i = 0, $length = strlen($ascii); $i < $length; $i++) { + if (ord($ascii[$i]) > 127) { + return false; + } + } + return true; + } + + /** + * Is this the value we are looking for? + * @param string $key Identifier. + * @return boolean True if matched. + * @access public + */ + function isKey($key) { + return $key == $this->_key; + } + + /** + * Is this the value we are looking for? + * @return string Identifier. + * @access public + */ + function getKey() { + return $this->_key; + } + + /** + * Is this the value we are looking for? + * @return string Content. + * @access public + */ + function getValue() { + return $this->_filename; + } +} + +/** + * Bundle of GET/POST parameters. Can include + * repeated parameters. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleEncoding { + var $_request; + + /** + * Starts empty. + * @param array $query Hash of parameters. + * Multiple values are + * as lists on a single key. + * @access public + */ + function SimpleEncoding($query = false) { + if (! $query) { + $query = array(); + } + $this->clear(); + $this->merge($query); + } + + /** + * Empties the request of parameters. + * @access public + */ + function clear() { + $this->_request = array(); + } + + /** + * Adds a parameter to the query. + * @param string $key Key to add value to. + * @param string/array $value New data. + * @access public + */ + function add($key, $value) { + if ($value === false) { + return; + } + if (is_array($value)) { + foreach ($value as $item) { + $this->_addPair($key, $item); + } + } else { + $this->_addPair($key, $value); + } + } + + /** + * Adds a new value into the request. + * @param string $key Key to add value to. + * @param string/array $value New data. + * @access private + */ + function _addPair($key, $value) { + $this->_request[] = new SimpleEncodedPair($key, $value); + } + + /** + * Adds a MIME part to the query. Does nothing for a + * form encoded packet. + * @param string $key Key to add value to. + * @param string $content Raw data. + * @param hash $filename Original filename. + * @access public + */ + function attach($key, $content, $filename) { + $this->_request[] = new SimpleAttachment($key, $content, $filename); + } + + /** + * Adds a set of parameters to this query. + * @param array/SimpleQueryString $query Multiple values are + * as lists on a single key. + * @access public + */ + function merge($query) { + if (is_object($query)) { + $this->_request = array_merge($this->_request, $query->getAll()); + } elseif (is_array($query)) { + foreach ($query as $key => $value) { + $this->add($key, $value); + } + } + } + + /** + * Accessor for single value. + * @return string/array False if missing, string + * if present and array if + * multiple entries. + * @access public + */ + function getValue($key) { + $values = array(); + foreach ($this->_request as $pair) { + if ($pair->isKey($key)) { + $values[] = $pair->getValue(); + } + } + if (count($values) == 0) { + return false; + } elseif (count($values) == 1) { + return $values[0]; + } else { + return $values; + } + } + + /** + * Accessor for listing of pairs. + * @return array All pair objects. + * @access public + */ + function getAll() { + return $this->_request; + } + + /** + * Renders the query string as a URL encoded + * request part. + * @return string Part of URL. + * @access protected + */ + function _encode() { + $statements = array(); + foreach ($this->_request as $pair) { + if ($statement = $pair->asRequest()) { + $statements[] = $statement; + } + } + return implode('&', $statements); + } +} + +/** + * Bundle of GET parameters. Can include + * repeated parameters. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleGetEncoding extends SimpleEncoding { + + /** + * Starts empty. + * @param array $query Hash of parameters. + * Multiple values are + * as lists on a single key. + * @access public + */ + function SimpleGetEncoding($query = false) { + $this->SimpleEncoding($query); + } + + /** + * HTTP request method. + * @return string Always GET. + * @access public + */ + function getMethod() { + return 'GET'; + } + + /** + * Writes no extra headers. + * @param SimpleSocket $socket Socket to write to. + * @access public + */ + function writeHeadersTo(&$socket) { + } + + /** + * No data is sent to the socket as the data is encoded into + * the URL. + * @param SimpleSocket $socket Socket to write to. + * @access public + */ + function writeTo(&$socket) { + } + + /** + * Renders the query string as a URL encoded + * request part for attaching to a URL. + * @return string Part of URL. + * @access public + */ + function asUrlRequest() { + return $this->_encode(); + } +} + +/** + * Bundle of URL parameters for a HEAD request. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleHeadEncoding extends SimpleGetEncoding { + + /** + * Starts empty. + * @param array $query Hash of parameters. + * Multiple values are + * as lists on a single key. + * @access public + */ + function SimpleHeadEncoding($query = false) { + $this->SimpleGetEncoding($query); + } + + /** + * HTTP request method. + * @return string Always HEAD. + * @access public + */ + function getMethod() { + return 'HEAD'; + } +} + +/** + * Bundle of POST parameters. Can include + * repeated parameters. + * @package SimpleTest + * @subpackage WebTester + */ +class SimplePostEncoding extends SimpleEncoding { + + /** + * Starts empty. + * @param array $query Hash of parameters. + * Multiple values are + * as lists on a single key. + * @access public + */ + function SimplePostEncoding($query = false) { + if (is_array($query) and $this->hasMoreThanOneLevel($query)) { + $query = $this->rewriteArrayWithMultipleLevels($query); + } + $this->SimpleEncoding($query); + } + + function hasMoreThanOneLevel($query) { + foreach ($query as $key => $value) { + if (is_array($value)) { + return true; + } + } + return false; + } + + function rewriteArrayWithMultipleLevels($query) { + $query_ = array(); + foreach ($query as $key => $value) { + if (is_array($value)) { + foreach ($value as $sub_key => $sub_value) { + $query_[$key."[".$sub_key."]"] = $sub_value; + } + } else { + $query_[$key] = $value; + } + } + if ($this->hasMoreThanOneLevel($query_)) { + $query_ = $this->rewriteArrayWithMultipleLevels($query_); + } + + return $query_; + } + + + /** + * HTTP request method. + * @return string Always POST. + * @access public + */ + function getMethod() { + return 'POST'; + } + + /** + * Dispatches the form headers down the socket. + * @param SimpleSocket $socket Socket to write to. + * @access public + */ + function writeHeadersTo(&$socket) { + $socket->write("Content-Length: " . (integer)strlen($this->_encode()) . "\r\n"); + $socket->write("Content-Type: application/x-www-form-urlencoded\r\n"); + } + + /** + * Dispatches the form data down the socket. + * @param SimpleSocket $socket Socket to write to. + * @access public + */ + function writeTo(&$socket) { + $socket->write($this->_encode()); + } + + /** + * Renders the query string as a URL encoded + * request part for attaching to a URL. + * @return string Part of URL. + * @access public + */ + function asUrlRequest() { + return ''; + } +} + +/** + * Bundle of POST parameters in the multipart + * format. Can include file uploads. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleMultipartEncoding extends SimplePostEncoding { + var $_boundary; + + /** + * Starts empty. + * @param array $query Hash of parameters. + * Multiple values are + * as lists on a single key. + * @access public + */ + function SimpleMultipartEncoding($query = false, $boundary = false) { + $this->SimplePostEncoding($query); + $this->_boundary = ($boundary === false ? uniqid('st') : $boundary); + } + + /** + * Dispatches the form headers down the socket. + * @param SimpleSocket $socket Socket to write to. + * @access public + */ + function writeHeadersTo(&$socket) { + $socket->write("Content-Length: " . (integer)strlen($this->_encode()) . "\r\n"); + $socket->write("Content-Type: multipart/form-data, boundary=" . $this->_boundary . "\r\n"); + } + + /** + * Dispatches the form data down the socket. + * @param SimpleSocket $socket Socket to write to. + * @access public + */ + function writeTo(&$socket) { + $socket->write($this->_encode()); + } + + /** + * Renders the query string as a URL encoded + * request part. + * @return string Part of URL. + * @access public + */ + function _encode() { + $stream = ''; + foreach ($this->_request as $pair) { + $stream .= "--" . $this->_boundary . "\r\n"; + $stream .= $pair->asMime() . "\r\n"; + } + $stream .= "--" . $this->_boundary . "--\r\n"; + return $stream; + } +} +?> \ No newline at end of file diff --git a/thirdparty/simpletest/form.php b/thirdparty/simpletest/form.php new file mode 100644 index 000000000..cbef6636d --- /dev/null +++ b/thirdparty/simpletest/form.php @@ -0,0 +1,355 @@ +_method = $tag->getAttribute('method'); + $this->_action = $this->_createAction($tag->getAttribute('action'), $page); + $this->_encoding = $this->_setEncodingClass($tag); + $this->_default_target = false; + $this->_id = $tag->getAttribute('id'); + $this->_buttons = array(); + $this->_images = array(); + $this->_widgets = array(); + $this->_radios = array(); + $this->_checkboxes = array(); + } + + /** + * Creates the request packet to be sent by the form. + * @param SimpleTag $tag Form tag to read. + * @return string Packet class. + * @access private + */ + function _setEncodingClass($tag) { + if (strtolower($tag->getAttribute('method')) == 'post') { + if (strtolower($tag->getAttribute('enctype')) == 'multipart/form-data') { + return 'SimpleMultipartEncoding'; + } + return 'SimplePostEncoding'; + } + return 'SimpleGetEncoding'; + } + + /** + * Sets the frame target within a frameset. + * @param string $frame Name of frame. + * @access public + */ + function setDefaultTarget($frame) { + $this->_default_target = $frame; + } + + /** + * Accessor for method of form submission. + * @return string Either get or post. + * @access public + */ + function getMethod() { + return ($this->_method ? strtolower($this->_method) : 'get'); + } + + /** + * Combined action attribute with current location + * to get an absolute form target. + * @param string $action Action attribute from form tag. + * @param SimpleUrl $base Page location. + * @return SimpleUrl Absolute form target. + */ + function _createAction($action, &$page) { + if (($action === '') || ($action === false)) { + return $page->expandUrl($page->getUrl()); + } + return $page->expandUrl(new SimpleUrl($action));; + } + + /** + * Absolute URL of the target. + * @return SimpleUrl URL target. + * @access public + */ + function getAction() { + $url = $this->_action; + if ($this->_default_target && ! $url->getTarget()) { + $url->setTarget($this->_default_target); + } + return $url; + } + + /** + * Creates the encoding for the current values in the + * form. + * @return SimpleFormEncoding Request to submit. + * @access private + */ + function _encode() { + $class = $this->_encoding; + $encoding = new $class(); + for ($i = 0, $count = count($this->_widgets); $i < $count; $i++) { + $this->_widgets[$i]->write($encoding); + } + return $encoding; + } + + /** + * ID field of form for unique identification. + * @return string Unique tag ID. + * @access public + */ + function getId() { + return $this->_id; + } + + /** + * Adds a tag contents to the form. + * @param SimpleWidget $tag Input tag to add. + * @access public + */ + function addWidget(&$tag) { + if (strtolower($tag->getAttribute('type')) == 'submit') { + $this->_buttons[] = &$tag; + } elseif (strtolower($tag->getAttribute('type')) == 'image') { + $this->_images[] = &$tag; + } elseif ($tag->getName()) { + $this->_setWidget($tag); + } + } + + /** + * Sets the widget into the form, grouping radio + * buttons if any. + * @param SimpleWidget $tag Incoming form control. + * @access private + */ + function _setWidget(&$tag) { + if (strtolower($tag->getAttribute('type')) == 'radio') { + $this->_addRadioButton($tag); + } elseif (strtolower($tag->getAttribute('type')) == 'checkbox') { + $this->_addCheckbox($tag); + } else { + $this->_widgets[] = &$tag; + } + } + + /** + * Adds a radio button, building a group if necessary. + * @param SimpleRadioButtonTag $tag Incoming form control. + * @access private + */ + function _addRadioButton(&$tag) { + if (! isset($this->_radios[$tag->getName()])) { + $this->_widgets[] = &new SimpleRadioGroup(); + $this->_radios[$tag->getName()] = count($this->_widgets) - 1; + } + $this->_widgets[$this->_radios[$tag->getName()]]->addWidget($tag); + } + + /** + * Adds a checkbox, making it a group on a repeated name. + * @param SimpleCheckboxTag $tag Incoming form control. + * @access private + */ + function _addCheckbox(&$tag) { + if (! isset($this->_checkboxes[$tag->getName()])) { + $this->_widgets[] = &$tag; + $this->_checkboxes[$tag->getName()] = count($this->_widgets) - 1; + } else { + $index = $this->_checkboxes[$tag->getName()]; + if (! SimpleTestCompatibility::isA($this->_widgets[$index], 'SimpleCheckboxGroup')) { + $previous = &$this->_widgets[$index]; + $this->_widgets[$index] = &new SimpleCheckboxGroup(); + $this->_widgets[$index]->addWidget($previous); + } + $this->_widgets[$index]->addWidget($tag); + } + } + + /** + * Extracts current value from form. + * @param SimpleSelector $selector Criteria to apply. + * @return string/array Value(s) as string or null + * if not set. + * @access public + */ + function getValue($selector) { + for ($i = 0, $count = count($this->_widgets); $i < $count; $i++) { + if ($selector->isMatch($this->_widgets[$i])) { + return $this->_widgets[$i]->getValue(); + } + } + foreach ($this->_buttons as $button) { + if ($selector->isMatch($button)) { + return $button->getValue(); + } + } + return null; + } + + /** + * Sets a widget value within the form. + * @param SimpleSelector $selector Criteria to apply. + * @param string $value Value to input into the widget. + * @return boolean True if value is legal, false + * otherwise. If the field is not + * present, nothing will be set. + * @access public + */ + function setField($selector, $value, $position=false) { + $success = false; + $_position = 0; + for ($i = 0, $count = count($this->_widgets); $i < $count; $i++) { + if ($selector->isMatch($this->_widgets[$i])) { + $_position++; + if ($position === false or $_position === (int)$position) { + if ($this->_widgets[$i]->setValue($value)) { + $success = true; + } + } + } + } + return $success; + } + + /** + * Used by the page object to set widgets labels to + * external label tags. + * @param SimpleSelector $selector Criteria to apply. + * @access public + */ + function attachLabelBySelector($selector, $label) { + for ($i = 0, $count = count($this->_widgets); $i < $count; $i++) { + if ($selector->isMatch($this->_widgets[$i])) { + if (method_exists($this->_widgets[$i], 'setLabel')) { + $this->_widgets[$i]->setLabel($label); + return; + } + } + } + } + + /** + * Test to see if a form has a submit button. + * @param SimpleSelector $selector Criteria to apply. + * @return boolean True if present. + * @access public + */ + function hasSubmit($selector) { + foreach ($this->_buttons as $button) { + if ($selector->isMatch($button)) { + return true; + } + } + return false; + } + + /** + * Test to see if a form has an image control. + * @param SimpleSelector $selector Criteria to apply. + * @return boolean True if present. + * @access public + */ + function hasImage($selector) { + foreach ($this->_images as $image) { + if ($selector->isMatch($image)) { + return true; + } + } + return false; + } + + /** + * Gets the submit values for a selected button. + * @param SimpleSelector $selector Criteria to apply. + * @param hash $additional Additional data for the form. + * @return SimpleEncoding Submitted values or false + * if there is no such button + * in the form. + * @access public + */ + function submitButton($selector, $additional = false) { + $additional = $additional ? $additional : array(); + foreach ($this->_buttons as $button) { + if ($selector->isMatch($button)) { + $encoding = $this->_encode(); + $button->write($encoding); + if ($additional) { + $encoding->merge($additional); + } + return $encoding; + } + } + return false; + } + + /** + * Gets the submit values for an image. + * @param SimpleSelector $selector Criteria to apply. + * @param integer $x X-coordinate of click. + * @param integer $y Y-coordinate of click. + * @param hash $additional Additional data for the form. + * @return SimpleEncoding Submitted values or false + * if there is no such button in the + * form. + * @access public + */ + function submitImage($selector, $x, $y, $additional = false) { + $additional = $additional ? $additional : array(); + foreach ($this->_images as $image) { + if ($selector->isMatch($image)) { + $encoding = $this->_encode(); + $image->write($encoding, $x, $y); + if ($additional) { + $encoding->merge($additional); + } + return $encoding; + } + } + return false; + } + + /** + * Simply submits the form without the submit button + * value. Used when there is only one button or it + * is unimportant. + * @return hash Submitted values. + * @access public + */ + function submit() { + return $this->_encode(); + } +} +?> \ No newline at end of file diff --git a/thirdparty/simpletest/http.php b/thirdparty/simpletest/http.php new file mode 100644 index 000000000..e6c6e89da --- /dev/null +++ b/thirdparty/simpletest/http.php @@ -0,0 +1,624 @@ +_url = $url; + } + + /** + * Resource name. + * @return SimpleUrl Current url. + * @access protected + */ + function getUrl() { + return $this->_url; + } + + /** + * Creates the first line which is the actual request. + * @param string $method HTTP request method, usually GET. + * @return string Request line content. + * @access protected + */ + function _getRequestLine($method) { + return $method . ' ' . $this->_url->getPath() . + $this->_url->getEncodedRequest() . ' HTTP/1.0'; + } + + /** + * Creates the host part of the request. + * @return string Host line content. + * @access protected + */ + function _getHostLine() { + $line = 'Host: ' . $this->_url->getHost(); + if ($this->_url->getPort()) { + $line .= ':' . $this->_url->getPort(); + } + return $line; + } + + /** + * Opens a socket to the route. + * @param string $method HTTP request method, usually GET. + * @param integer $timeout Connection timeout. + * @return SimpleSocket New socket. + * @access public + */ + function &createConnection($method, $timeout) { + $default_port = ('https' == $this->_url->getScheme()) ? 443 : 80; + $socket = &$this->_createSocket( + $this->_url->getScheme() ? $this->_url->getScheme() : 'http', + $this->_url->getHost(), + $this->_url->getPort() ? $this->_url->getPort() : $default_port, + $timeout); + if (! $socket->isError()) { + $socket->write($this->_getRequestLine($method) . "\r\n"); + $socket->write($this->_getHostLine() . "\r\n"); + $socket->write("Connection: close\r\n"); + } + return $socket; + } + + /** + * Factory for socket. + * @param string $scheme Protocol to use. + * @param string $host Hostname to connect to. + * @param integer $port Remote port. + * @param integer $timeout Connection timeout. + * @return SimpleSocket/SimpleSecureSocket New socket. + * @access protected + */ + function &_createSocket($scheme, $host, $port, $timeout) { + if (in_array($scheme, array('https'))) { + $socket = &new SimpleSecureSocket($host, $port, $timeout); + } else { + $socket = &new SimpleSocket($host, $port, $timeout); + } + return $socket; + } +} + +/** + * Creates HTTP headers for the end point of + * a HTTP request via a proxy server. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleProxyRoute extends SimpleRoute { + var $_proxy; + var $_username; + var $_password; + + /** + * Stashes the proxy address. + * @param SimpleUrl $url URL as object. + * @param string $proxy Proxy URL. + * @param string $username Username for autentication. + * @param string $password Password for autentication. + * @access public + */ + function SimpleProxyRoute($url, $proxy, $username = false, $password = false) { + $this->SimpleRoute($url); + $this->_proxy = $proxy; + $this->_username = $username; + $this->_password = $password; + } + + /** + * Creates the first line which is the actual request. + * @param string $method HTTP request method, usually GET. + * @param SimpleUrl $url URL as object. + * @return string Request line content. + * @access protected + */ + function _getRequestLine($method) { + $url = $this->getUrl(); + $scheme = $url->getScheme() ? $url->getScheme() : 'http'; + $port = $url->getPort() ? ':' . $url->getPort() : ''; + return $method . ' ' . $scheme . '://' . $url->getHost() . $port . + $url->getPath() . $url->getEncodedRequest() . ' HTTP/1.0'; + } + + /** + * Creates the host part of the request. + * @param SimpleUrl $url URL as object. + * @return string Host line content. + * @access protected + */ + function _getHostLine() { + $host = 'Host: ' . $this->_proxy->getHost(); + $port = $this->_proxy->getPort() ? $this->_proxy->getPort() : 8080; + return "$host:$port"; + } + + /** + * Opens a socket to the route. + * @param string $method HTTP request method, usually GET. + * @param integer $timeout Connection timeout. + * @return SimpleSocket New socket. + * @access public + */ + function &createConnection($method, $timeout) { + $socket = &$this->_createSocket( + $this->_proxy->getScheme() ? $this->_proxy->getScheme() : 'http', + $this->_proxy->getHost(), + $this->_proxy->getPort() ? $this->_proxy->getPort() : 8080, + $timeout); + if ($socket->isError()) { + return $socket; + } + $socket->write($this->_getRequestLine($method) . "\r\n"); + $socket->write($this->_getHostLine() . "\r\n"); + if ($this->_username && $this->_password) { + $socket->write('Proxy-Authorization: Basic ' . + base64_encode($this->_username . ':' . $this->_password) . + "\r\n"); + } + $socket->write("Connection: close\r\n"); + return $socket; + } +} + +/** + * HTTP request for a web page. Factory for + * HttpResponse object. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleHttpRequest { + var $_route; + var $_encoding; + var $_headers; + var $_cookies; + + /** + * Builds the socket request from the different pieces. + * These include proxy information, URL, cookies, headers, + * request method and choice of encoding. + * @param SimpleRoute $route Request route. + * @param SimpleFormEncoding $encoding Content to send with + * request. + * @access public + */ + function SimpleHttpRequest(&$route, $encoding) { + $this->_route = &$route; + $this->_encoding = $encoding; + $this->_headers = array(); + $this->_cookies = array(); + } + + /** + * Dispatches the content to the route's socket. + * @param integer $timeout Connection timeout. + * @return SimpleHttpResponse A response which may only have + * an error, but hopefully has a + * complete web page. + * @access public + */ + function &fetch($timeout) { + $socket = &$this->_route->createConnection($this->_encoding->getMethod(), $timeout); + if (! $socket->isError()) { + $this->_dispatchRequest($socket, $this->_encoding); + } + $response = &$this->_createResponse($socket); + return $response; + } + + /** + * Sends the headers. + * @param SimpleSocket $socket Open socket. + * @param string $method HTTP request method, + * usually GET. + * @param SimpleFormEncoding $encoding Content to send with request. + * @access private + */ + function _dispatchRequest(&$socket, $encoding) { + foreach ($this->_headers as $header_line) { + $socket->write($header_line . "\r\n"); + } + if (count($this->_cookies) > 0) { + $socket->write("Cookie: " . implode(";", $this->_cookies) . "\r\n"); + } + $encoding->writeHeadersTo($socket); + $socket->write("\r\n"); + $encoding->writeTo($socket); + } + + /** + * Adds a header line to the request. + * @param string $header_line Text of full header line. + * @access public + */ + function addHeaderLine($header_line) { + $this->_headers[] = $header_line; + } + + /** + * Reads all the relevant cookies from the + * cookie jar. + * @param SimpleCookieJar $jar Jar to read + * @param SimpleUrl $url Url to use for scope. + * @access public + */ + function readCookiesFromJar($jar, $url) { + $this->_cookies = $jar->selectAsPairs($url); + } + + /** + * Wraps the socket in a response parser. + * @param SimpleSocket $socket Responding socket. + * @return SimpleHttpResponse Parsed response object. + * @access protected + */ + function &_createResponse(&$socket) { + $response = &new SimpleHttpResponse( + $socket, + $this->_route->getUrl(), + $this->_encoding); + return $response; + } +} + +/** + * Collection of header lines in the response. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleHttpHeaders { + var $_raw_headers; + var $_response_code; + var $_http_version; + var $_mime_type; + var $_location; + var $_cookies; + var $_authentication; + var $_realm; + + /** + * Parses the incoming header block. + * @param string $headers Header block. + * @access public + */ + function SimpleHttpHeaders($headers) { + $this->_raw_headers = $headers; + $this->_response_code = false; + $this->_http_version = false; + $this->_mime_type = ''; + $this->_location = false; + $this->_cookies = array(); + $this->_authentication = false; + $this->_realm = false; + foreach (split("\r\n", $headers) as $header_line) { + $this->_parseHeaderLine($header_line); + } + } + + /** + * Accessor for parsed HTTP protocol version. + * @return integer HTTP error code. + * @access public + */ + function getHttpVersion() { + return $this->_http_version; + } + + /** + * Accessor for raw header block. + * @return string All headers as raw string. + * @access public + */ + function getRaw() { + return $this->_raw_headers; + } + + /** + * Accessor for parsed HTTP error code. + * @return integer HTTP error code. + * @access public + */ + function getResponseCode() { + return (integer)$this->_response_code; + } + + /** + * Returns the redirected URL or false if + * no redirection. + * @return string URL or false for none. + * @access public + */ + function getLocation() { + return $this->_location; + } + + /** + * Test to see if the response is a valid redirect. + * @return boolean True if valid redirect. + * @access public + */ + function isRedirect() { + return in_array($this->_response_code, array(301, 302, 303, 307)) && + (boolean)$this->getLocation(); + } + + /** + * Test to see if the response is an authentication + * challenge. + * @return boolean True if challenge. + * @access public + */ + function isChallenge() { + return ($this->_response_code == 401) && + (boolean)$this->_authentication && + (boolean)$this->_realm; + } + + /** + * Accessor for MIME type header information. + * @return string MIME type. + * @access public + */ + function getMimeType() { + return $this->_mime_type; + } + + /** + * Accessor for authentication type. + * @return string Type. + * @access public + */ + function getAuthentication() { + return $this->_authentication; + } + + /** + * Accessor for security realm. + * @return string Realm. + * @access public + */ + function getRealm() { + return $this->_realm; + } + + /** + * Writes new cookies to the cookie jar. + * @param SimpleCookieJar $jar Jar to write to. + * @param SimpleUrl $url Host and path to write under. + * @access public + */ + function writeCookiesToJar(&$jar, $url) { + foreach ($this->_cookies as $cookie) { + $jar->setCookie( + $cookie->getName(), + $cookie->getValue(), + $url->getHost(), + $cookie->getPath(), + $cookie->getExpiry()); + } + } + + /** + * Called on each header line to accumulate the held + * data within the class. + * @param string $header_line One line of header. + * @access protected + */ + function _parseHeaderLine($header_line) { + if (preg_match('/HTTP\/(\d+\.\d+)\s+(\d+)/i', $header_line, $matches)) { + $this->_http_version = $matches[1]; + $this->_response_code = $matches[2]; + } + if (preg_match('/Content-type:\s*(.*)/i', $header_line, $matches)) { + $this->_mime_type = trim($matches[1]); + } + if (preg_match('/Location:\s*(.*)/i', $header_line, $matches)) { + $this->_location = trim($matches[1]); + } + if (preg_match('/Set-cookie:(.*)/i', $header_line, $matches)) { + $this->_cookies[] = $this->_parseCookie($matches[1]); + } + if (preg_match('/WWW-Authenticate:\s+(\S+)\s+realm=\"(.*?)\"/i', $header_line, $matches)) { + $this->_authentication = $matches[1]; + $this->_realm = trim($matches[2]); + } + } + + /** + * Parse the Set-cookie content. + * @param string $cookie_line Text after "Set-cookie:" + * @return SimpleCookie New cookie object. + * @access private + */ + function _parseCookie($cookie_line) { + $parts = split(";", $cookie_line); + $cookie = array(); + preg_match('/\s*(.*?)\s*=(.*)/', array_shift($parts), $cookie); + foreach ($parts as $part) { + if (preg_match('/\s*(.*?)\s*=(.*)/', $part, $matches)) { + $cookie[$matches[1]] = trim($matches[2]); + } + } + return new SimpleCookie( + $cookie[1], + trim($cookie[2]), + isset($cookie["path"]) ? $cookie["path"] : "", + isset($cookie["expires"]) ? $cookie["expires"] : false); + } +} + +/** + * Basic HTTP response. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleHttpResponse extends SimpleStickyError { + var $_url; + var $_encoding; + var $_sent; + var $_content; + var $_headers; + + /** + * Constructor. Reads and parses the incoming + * content and headers. + * @param SimpleSocket $socket Network connection to fetch + * response text from. + * @param SimpleUrl $url Resource name. + * @param mixed $encoding Record of content sent. + * @access public + */ + function SimpleHttpResponse(&$socket, $url, $encoding) { + $this->SimpleStickyError(); + $this->_url = $url; + $this->_encoding = $encoding; + $this->_sent = $socket->getSent(); + $this->_content = false; + $raw = $this->_readAll($socket); + if ($socket->isError()) { + $this->_setError('Error reading socket [' . $socket->getError() . ']'); + return; + } + $this->_parse($raw); + } + + /** + * Splits up the headers and the rest of the content. + * @param string $raw Content to parse. + * @access private + */ + function _parse($raw) { + if (! $raw) { + $this->_setError('Nothing fetched'); + $this->_headers = &new SimpleHttpHeaders(''); + } elseif (! strstr($raw, "\r\n\r\n")) { + $this->_setError('Could not split headers from content'); + $this->_headers = &new SimpleHttpHeaders($raw); + } else { + list($headers, $this->_content) = split("\r\n\r\n", $raw, 2); + $this->_headers = &new SimpleHttpHeaders($headers); + } + } + + /** + * Original request method. + * @return string GET, POST or HEAD. + * @access public + */ + function getMethod() { + return $this->_encoding->getMethod(); + } + + /** + * Resource name. + * @return SimpleUrl Current url. + * @access public + */ + function getUrl() { + return $this->_url; + } + + /** + * Original request data. + * @return mixed Sent content. + * @access public + */ + function getRequestData() { + return $this->_encoding; + } + + /** + * Raw request that was sent down the wire. + * @return string Bytes actually sent. + * @access public + */ + function getSent() { + return $this->_sent; + } + + /** + * Accessor for the content after the last + * header line. + * @return string All content. + * @access public + */ + function getContent() { + return $this->_content; + } + + /** + * Accessor for header block. The response is the + * combination of this and the content. + * @return SimpleHeaders Wrapped header block. + * @access public + */ + function getHeaders() { + return $this->_headers; + } + + /** + * Accessor for any new cookies. + * @return array List of new cookies. + * @access public + */ + function getNewCookies() { + return $this->_headers->getNewCookies(); + } + + /** + * Reads the whole of the socket output into a + * single string. + * @param SimpleSocket $socket Unread socket. + * @return string Raw output if successful + * else false. + * @access private + */ + function _readAll(&$socket) { + $all = ''; + while (! $this->_isLastPacket($next = $socket->read())) { + $all .= $next; + } + return $all; + } + + /** + * Test to see if the packet from the socket is the + * last one. + * @param string $packet Chunk to interpret. + * @return boolean True if empty or EOF. + * @access private + */ + function _isLastPacket($packet) { + if (is_string($packet)) { + return $packet === ''; + } + return ! $packet; + } +} +?> \ No newline at end of file diff --git a/thirdparty/simpletest/page.php b/thirdparty/simpletest/page.php new file mode 100644 index 000000000..08e5649dc --- /dev/null +++ b/thirdparty/simpletest/page.php @@ -0,0 +1,983 @@ + 'SimpleAnchorTag', + 'title' => 'SimpleTitleTag', + 'base' => 'SimpleBaseTag', + 'button' => 'SimpleButtonTag', + 'textarea' => 'SimpleTextAreaTag', + 'option' => 'SimpleOptionTag', + 'label' => 'SimpleLabelTag', + 'form' => 'SimpleFormTag', + 'frame' => 'SimpleFrameTag'); + $attributes = $this->_keysToLowerCase($attributes); + if (array_key_exists($name, $map)) { + $tag_class = $map[$name]; + return new $tag_class($attributes); + } elseif ($name == 'select') { + return $this->_createSelectionTag($attributes); + } elseif ($name == 'input') { + return $this->_createInputTag($attributes); + } + return new SimpleTag($name, $attributes); + } + + /** + * Factory for selection fields. + * @param hash $attributes Element attributes. + * @return SimpleTag Tag object. + * @access protected + */ + function _createSelectionTag($attributes) { + if (isset($attributes['multiple'])) { + return new MultipleSelectionTag($attributes); + } + return new SimpleSelectionTag($attributes); + } + + /** + * Factory for input tags. + * @param hash $attributes Element attributes. + * @return SimpleTag Tag object. + * @access protected + */ + function _createInputTag($attributes) { + if (! isset($attributes['type'])) { + return new SimpleTextTag($attributes); + } + $type = strtolower(trim($attributes['type'])); + $map = array( + 'submit' => 'SimpleSubmitTag', + 'image' => 'SimpleImageSubmitTag', + 'checkbox' => 'SimpleCheckboxTag', + 'radio' => 'SimpleRadioButtonTag', + 'text' => 'SimpleTextTag', + 'hidden' => 'SimpleTextTag', + 'password' => 'SimpleTextTag', + 'file' => 'SimpleUploadTag'); + if (array_key_exists($type, $map)) { + $tag_class = $map[$type]; + return new $tag_class($attributes); + } + return false; + } + + /** + * Make the keys lower case for case insensitive look-ups. + * @param hash $map Hash to convert. + * @return hash Unchanged values, but keys lower case. + * @access private + */ + function _keysToLowerCase($map) { + $lower = array(); + foreach ($map as $key => $value) { + $lower[strtolower($key)] = $value; + } + return $lower; + } +} + +/** + * SAX event handler. Maintains a list of + * open tags and dispatches them as they close. + * @package SimpleTest + * @subpackage WebTester + */ +class SimplePageBuilder extends SimpleSaxListener { + var $_tags; + var $_page; + var $_private_content_tag; + + /** + * Sets the builder up empty. + * @access public + */ + function SimplePageBuilder() { + $this->SimpleSaxListener(); + } + + /** + * Frees up any references so as to allow the PHP garbage + * collection from unset() to work. + * @access public + */ + function free() { + unset($this->_tags); + unset($this->_page); + unset($this->_private_content_tags); + } + + /** + * Reads the raw content and send events + * into the page to be built. + * @param $response SimpleHttpResponse Fetched response. + * @return SimplePage Newly parsed page. + * @access public + */ + function &parse($response) { + $this->_tags = array(); + $this->_page = &$this->_createPage($response); + $parser = &$this->_createParser($this); + $parser->parse($response->getContent()); + $this->_page->acceptPageEnd(); + return $this->_page; + } + + /** + * Creates an empty page. + * @return SimplePage New unparsed page. + * @access protected + */ + function &_createPage($response) { + $page = &new SimplePage($response); + return $page; + } + + /** + * Creates the parser used with the builder. + * @param $listener SimpleSaxListener Target of parser. + * @return SimpleSaxParser Parser to generate + * events for the builder. + * @access protected + */ + function &_createParser(&$listener) { + $parser = &new SimpleHtmlSaxParser($listener); + return $parser; + } + + /** + * Start of element event. Opens a new tag. + * @param string $name Element name. + * @param hash $attributes Attributes without content + * are marked as true. + * @return boolean False on parse error. + * @access public + */ + function startElement($name, $attributes) { + $factory = &new SimpleTagBuilder(); + $tag = $factory->createTag($name, $attributes); + if (! $tag) { + return true; + } + if ($tag->getTagName() == 'label') { + $this->_page->acceptLabelStart($tag); + $this->_openTag($tag); + return true; + } + if ($tag->getTagName() == 'form') { + $this->_page->acceptFormStart($tag); + return true; + } + if ($tag->getTagName() == 'frameset') { + $this->_page->acceptFramesetStart($tag); + return true; + } + if ($tag->getTagName() == 'frame') { + $this->_page->acceptFrame($tag); + return true; + } + if ($tag->isPrivateContent() && ! isset($this->_private_content_tag)) { + $this->_private_content_tag = &$tag; + } + if ($tag->expectEndTag()) { + $this->_openTag($tag); + return true; + } + $this->_page->acceptTag($tag); + return true; + } + + /** + * End of element event. + * @param string $name Element name. + * @return boolean False on parse error. + * @access public + */ + function endElement($name) { + if ($name == 'label') { + $this->_page->acceptLabelEnd(); + return true; + } + if ($name == 'form') { + $this->_page->acceptFormEnd(); + return true; + } + if ($name == 'frameset') { + $this->_page->acceptFramesetEnd(); + return true; + } + if ($this->_hasNamedTagOnOpenTagStack($name)) { + $tag = array_pop($this->_tags[$name]); + if ($tag->isPrivateContent() && $this->_private_content_tag->getTagName() == $name) { + unset($this->_private_content_tag); + } + $this->_addContentTagToOpenTags($tag); + $this->_page->acceptTag($tag); + return true; + } + return true; + } + + /** + * Test to see if there are any open tags awaiting + * closure that match the tag name. + * @param string $name Element name. + * @return boolean True if any are still open. + * @access private + */ + function _hasNamedTagOnOpenTagStack($name) { + return isset($this->_tags[$name]) && (count($this->_tags[$name]) > 0); + } + + /** + * Unparsed, but relevant data. The data is added + * to every open tag. + * @param string $text May include unparsed tags. + * @return boolean False on parse error. + * @access public + */ + function addContent($text) { + if (isset($this->_private_content_tag)) { + $this->_private_content_tag->addContent($text); + } else { + $this->_addContentToAllOpenTags($text); + } + return true; + } + + /** + * Any content fills all currently open tags unless it + * is part of an option tag. + * @param string $text May include unparsed tags. + * @access private + */ + function _addContentToAllOpenTags($text) { + foreach (array_keys($this->_tags) as $name) { + for ($i = 0, $count = count($this->_tags[$name]); $i < $count; $i++) { + $this->_tags[$name][$i]->addContent($text); + } + } + } + + /** + * Parsed data in tag form. The parsed tag is added + * to every open tag. Used for adding options to select + * fields only. + * @param SimpleTag $tag Option tags only. + * @access private + */ + function _addContentTagToOpenTags(&$tag) { + if ($tag->getTagName() != 'option') { + return; + } + foreach (array_keys($this->_tags) as $name) { + for ($i = 0, $count = count($this->_tags[$name]); $i < $count; $i++) { + $this->_tags[$name][$i]->addTag($tag); + } + } + } + + /** + * Opens a tag for receiving content. Multiple tags + * will be receiving input at the same time. + * @param SimpleTag $tag New content tag. + * @access private + */ + function _openTag(&$tag) { + $name = $tag->getTagName(); + if (! in_array($name, array_keys($this->_tags))) { + $this->_tags[$name] = array(); + } + $this->_tags[$name][] = &$tag; + } +} + +/** + * A wrapper for a web page. + * @package SimpleTest + * @subpackage WebTester + */ +class SimplePage { + var $_links; + var $_title; + var $_last_widget; + var $_label; + var $_left_over_labels; + var $_open_forms; + var $_complete_forms; + var $_frameset; + var $_frames; + var $_frameset_nesting_level; + var $_transport_error; + var $_raw; + var $_text; + var $_sent; + var $_headers; + var $_method; + var $_url; + var $_base = false; + var $_request_data; + + /** + * Parses a page ready to access it's contents. + * @param SimpleHttpResponse $response Result of HTTP fetch. + * @access public + */ + function SimplePage($response = false) { + $this->_links = array(); + $this->_title = false; + $this->_left_over_labels = array(); + $this->_open_forms = array(); + $this->_complete_forms = array(); + $this->_frameset = false; + $this->_frames = array(); + $this->_frameset_nesting_level = 0; + $this->_text = false; + if ($response) { + $this->_extractResponse($response); + } else { + $this->_noResponse(); + } + } + + /** + * Extracts all of the response information. + * @param SimpleHttpResponse $response Response being parsed. + * @access private + */ + function _extractResponse($response) { + $this->_transport_error = $response->getError(); + $this->_raw = $response->getContent(); + $this->_sent = $response->getSent(); + $this->_headers = $response->getHeaders(); + $this->_method = $response->getMethod(); + $this->_url = $response->getUrl(); + $this->_request_data = $response->getRequestData(); + } + + /** + * Sets up a missing response. + * @access private + */ + function _noResponse() { + $this->_transport_error = 'No page fetched yet'; + $this->_raw = false; + $this->_sent = false; + $this->_headers = false; + $this->_method = 'GET'; + $this->_url = false; + $this->_request_data = false; + } + + /** + * Original request as bytes sent down the wire. + * @return mixed Sent content. + * @access public + */ + function getRequest() { + return $this->_sent; + } + + /** + * Accessor for raw text of page. + * @return string Raw unparsed content. + * @access public + */ + function getRaw() { + return $this->_raw; + } + + /** + * Accessor for plain text of page as a text browser + * would see it. + * @return string Plain text of page. + * @access public + */ + function getText() { + if (! $this->_text) { + $this->_text = SimpleHtmlSaxParser::normalise($this->_raw); + } + return $this->_text; + } + + /** + * Accessor for raw headers of page. + * @return string Header block as text. + * @access public + */ + function getHeaders() { + if ($this->_headers) { + return $this->_headers->getRaw(); + } + return false; + } + + /** + * Original request method. + * @return string GET, POST or HEAD. + * @access public + */ + function getMethod() { + return $this->_method; + } + + /** + * Original resource name. + * @return SimpleUrl Current url. + * @access public + */ + function getUrl() { + return $this->_url; + } + + /** + * Base URL if set via BASE tag page url otherwise + * @return SimpleUrl Base url. + * @access public + */ + function getBaseUrl() { + return $this->_base; + } + + /** + * Original request data. + * @return mixed Sent content. + * @access public + */ + function getRequestData() { + return $this->_request_data; + } + + /** + * Accessor for last error. + * @return string Error from last response. + * @access public + */ + function getTransportError() { + return $this->_transport_error; + } + + /** + * Accessor for current MIME type. + * @return string MIME type as string; e.g. 'text/html' + * @access public + */ + function getMimeType() { + if ($this->_headers) { + return $this->_headers->getMimeType(); + } + return false; + } + + /** + * Accessor for HTTP response code. + * @return integer HTTP response code received. + * @access public + */ + function getResponseCode() { + if ($this->_headers) { + return $this->_headers->getResponseCode(); + } + return false; + } + + /** + * Accessor for last Authentication type. Only valid + * straight after a challenge (401). + * @return string Description of challenge type. + * @access public + */ + function getAuthentication() { + if ($this->_headers) { + return $this->_headers->getAuthentication(); + } + return false; + } + + /** + * Accessor for last Authentication realm. Only valid + * straight after a challenge (401). + * @return string Name of security realm. + * @access public + */ + function getRealm() { + if ($this->_headers) { + return $this->_headers->getRealm(); + } + return false; + } + + /** + * Accessor for current frame focus. Will be + * false as no frames. + * @return array Always empty. + * @access public + */ + function getFrameFocus() { + return array(); + } + + /** + * Sets the focus by index. The integer index starts from 1. + * @param integer $choice Chosen frame. + * @return boolean Always false. + * @access public + */ + function setFrameFocusByIndex($choice) { + return false; + } + + /** + * Sets the focus by name. Always fails for a leaf page. + * @param string $name Chosen frame. + * @return boolean False as no frames. + * @access public + */ + function setFrameFocus($name) { + return false; + } + + /** + * Clears the frame focus. Does nothing for a leaf page. + * @access public + */ + function clearFrameFocus() { + } + + /** + * Adds a tag to the page. + * @param SimpleTag $tag Tag to accept. + * @access public + */ + function acceptTag(&$tag) { + if ($tag->getTagName() == "a") { + $this->_addLink($tag); + } elseif ($tag->getTagName() == "base") { + $this->_setBase($tag); + } elseif ($tag->getTagName() == "title") { + $this->_setTitle($tag); + } elseif ($this->_isFormElement($tag->getTagName())) { + for ($i = 0; $i < count($this->_open_forms); $i++) { + $this->_open_forms[$i]->addWidget($tag); + } + $this->_last_widget = &$tag; + } + } + + /** + * Opens a label for a described widget. + * @param SimpleFormTag $tag Tag to accept. + * @access public + */ + function acceptLabelStart(&$tag) { + $this->_label = &$tag; + unset($this->_last_widget); + } + + /** + * Closes the most recently opened label. + * @access public + */ + function acceptLabelEnd() { + if (isset($this->_label)) { + if (isset($this->_last_widget)) { + $this->_last_widget->setLabel($this->_label->getText()); + unset($this->_last_widget); + } else { + $this->_left_over_labels[] = SimpleTestCompatibility::copy($this->_label); + } + unset($this->_label); + } + } + + /** + * Tests to see if a tag is a possible form + * element. + * @param string $name HTML element name. + * @return boolean True if form element. + * @access private + */ + function _isFormElement($name) { + return in_array($name, array('input', 'button', 'textarea', 'select')); + } + + /** + * Opens a form. New widgets go here. + * @param SimpleFormTag $tag Tag to accept. + * @access public + */ + function acceptFormStart(&$tag) { + $this->_open_forms[] = &new SimpleForm($tag, $this); + } + + /** + * Closes the most recently opened form. + * @access public + */ + function acceptFormEnd() { + if (count($this->_open_forms)) { + $this->_complete_forms[] = array_pop($this->_open_forms); + } + } + + /** + * Opens a frameset. A frameset may contain nested + * frameset tags. + * @param SimpleFramesetTag $tag Tag to accept. + * @access public + */ + function acceptFramesetStart(&$tag) { + if (! $this->_isLoadingFrames()) { + $this->_frameset = &$tag; + } + $this->_frameset_nesting_level++; + } + + /** + * Closes the most recently opened frameset. + * @access public + */ + function acceptFramesetEnd() { + if ($this->_isLoadingFrames()) { + $this->_frameset_nesting_level--; + } + } + + /** + * Takes a single frame tag and stashes it in + * the current frame set. + * @param SimpleFrameTag $tag Tag to accept. + * @access public + */ + function acceptFrame(&$tag) { + if ($this->_isLoadingFrames()) { + if ($tag->getAttribute('src')) { + $this->_frames[] = &$tag; + } + } + } + + /** + * Test to see if in the middle of reading + * a frameset. + * @return boolean True if inframeset. + * @access private + */ + function _isLoadingFrames() { + if (! $this->_frameset) { + return false; + } + return ($this->_frameset_nesting_level > 0); + } + + /** + * Test to see if link is an absolute one. + * @param string $url Url to test. + * @return boolean True if absolute. + * @access protected + */ + function _linkIsAbsolute($url) { + $parsed = new SimpleUrl($url); + return (boolean)($parsed->getScheme() && $parsed->getHost()); + } + + /** + * Adds a link to the page. + * @param SimpleAnchorTag $tag Link to accept. + * @access protected + */ + function _addLink($tag) { + $this->_links[] = $tag; + } + + /** + * Marker for end of complete page. Any work in + * progress can now be closed. + * @access public + */ + function acceptPageEnd() { + while (count($this->_open_forms)) { + $this->_complete_forms[] = array_pop($this->_open_forms); + } + foreach ($this->_left_over_labels as $label) { + for ($i = 0, $count = count($this->_complete_forms); $i < $count; $i++) { + $this->_complete_forms[$i]->attachLabelBySelector( + new SimpleById($label->getFor()), + $label->getText()); + } + } + } + + /** + * Test for the presence of a frameset. + * @return boolean True if frameset. + * @access public + */ + function hasFrames() { + return (boolean)$this->_frameset; + } + + /** + * Accessor for frame name and source URL for every frame that + * will need to be loaded. Immediate children only. + * @return boolean/array False if no frameset or + * otherwise a hash of frame URLs. + * The key is either a numerical + * base one index or the name attribute. + * @access public + */ + function getFrameset() { + if (! $this->_frameset) { + return false; + } + $urls = array(); + for ($i = 0; $i < count($this->_frames); $i++) { + $name = $this->_frames[$i]->getAttribute('name'); + $url = new SimpleUrl($this->_frames[$i]->getAttribute('src')); + $urls[$name ? $name : $i + 1] = $this->expandUrl($url); + } + return $urls; + } + + /** + * Fetches a list of loaded frames. + * @return array/string Just the URL for a single page. + * @access public + */ + function getFrames() { + $url = $this->expandUrl($this->getUrl()); + return $url->asString(); + } + + /** + * Accessor for a list of all links. + * @return array List of urls with scheme of + * http or https and hostname. + * @access public + */ + function getUrls() { + $all = array(); + foreach ($this->_links as $link) { + $url = $this->_getUrlFromLink($link); + $all[] = $url->asString(); + } + return $all; + } + + /** + * Accessor for URLs by the link label. Label will match + * regardess of whitespace issues and case. + * @param string $label Text of link. + * @return array List of links with that label. + * @access public + */ + function getUrlsByLabel($label) { + $matches = array(); + foreach ($this->_links as $link) { + if ($link->getText() == $label) { + $matches[] = $this->_getUrlFromLink($link); + } + } + return $matches; + } + + /** + * Accessor for a URL by the id attribute. + * @param string $id Id attribute of link. + * @return SimpleUrl URL with that id of false if none. + * @access public + */ + function getUrlById($id) { + foreach ($this->_links as $link) { + if ($link->getAttribute('id') === (string)$id) { + return $this->_getUrlFromLink($link); + } + } + return false; + } + + /** + * Converts a link tag into a target URL. + * @param SimpleAnchor $link Parsed link. + * @return SimpleUrl URL with frame target if any. + * @access private + */ + function _getUrlFromLink($link) { + $url = $this->expandUrl($link->getHref()); + if ($link->getAttribute('target')) { + $url->setTarget($link->getAttribute('target')); + } + return $url; + } + + /** + * Expands expandomatic URLs into fully qualified + * URLs. + * @param SimpleUrl $url Relative URL. + * @return SimpleUrl Absolute URL. + * @access public + */ + function expandUrl($url) { + if (! is_object($url)) { + $url = new SimpleUrl($url); + } + $location = $this->getBaseUrl() ? $this->getBaseUrl() : new SimpleUrl(); + return $url->makeAbsolute($location->makeAbsolute($this->getUrl())); + } + + /** + * Sets the base url for the page. + * @param SimpleTag $tag Base URL for page. + * @access protected + */ + function _setBase(&$tag) { + $url = $tag->getAttribute('href'); + $this->_base = new SimpleUrl($url); + } + + /** + * Sets the title tag contents. + * @param SimpleTitleTag $tag Title of page. + * @access protected + */ + function _setTitle(&$tag) { + $this->_title = &$tag; + } + + /** + * Accessor for parsed title. + * @return string Title or false if no title is present. + * @access public + */ + function getTitle() { + if ($this->_title) { + return $this->_title->getText(); + } + return false; + } + + /** + * Finds a held form by button label. Will only + * search correctly built forms. + * @param SimpleSelector $selector Button finder. + * @return SimpleForm Form object containing + * the button. + * @access public + */ + function &getFormBySubmit($selector) { + for ($i = 0; $i < count($this->_complete_forms); $i++) { + if ($this->_complete_forms[$i]->hasSubmit($selector)) { + return $this->_complete_forms[$i]; + } + } + $null = null; + return $null; + } + + /** + * Finds a held form by image using a selector. + * Will only search correctly built forms. + * @param SimpleSelector $selector Image finder. + * @return SimpleForm Form object containing + * the image. + * @access public + */ + function &getFormByImage($selector) { + for ($i = 0; $i < count($this->_complete_forms); $i++) { + if ($this->_complete_forms[$i]->hasImage($selector)) { + return $this->_complete_forms[$i]; + } + } + $null = null; + return $null; + } + + /** + * Finds a held form by the form ID. A way of + * identifying a specific form when we have control + * of the HTML code. + * @param string $id Form label. + * @return SimpleForm Form object containing the matching ID. + * @access public + */ + function &getFormById($id) { + for ($i = 0; $i < count($this->_complete_forms); $i++) { + if ($this->_complete_forms[$i]->getId() == $id) { + return $this->_complete_forms[$i]; + } + } + $null = null; + return $null; + } + + /** + * Sets a field on each form in which the field is + * available. + * @param SimpleSelector $selector Field finder. + * @param string $value Value to set field to. + * @return boolean True if value is valid. + * @access public + */ + function setField($selector, $value, $position=false) { + $is_set = false; + for ($i = 0; $i < count($this->_complete_forms); $i++) { + if ($this->_complete_forms[$i]->setField($selector, $value, $position)) { + $is_set = true; + } + } + return $is_set; + } + + /** + * Accessor for a form element value within a page. + * @param SimpleSelector $selector Field finder. + * @return string/boolean A string if the field is + * present, false if unchecked + * and null if missing. + * @access public + */ + function getField($selector) { + for ($i = 0; $i < count($this->_complete_forms); $i++) { + $value = $this->_complete_forms[$i]->getValue($selector); + if (isset($value)) { + return $value; + } + } + return null; + } +} +?> \ No newline at end of file diff --git a/thirdparty/simpletest/parser.php b/thirdparty/simpletest/parser.php new file mode 100644 index 000000000..3f3b37b83 --- /dev/null +++ b/thirdparty/simpletest/parser.php @@ -0,0 +1,764 @@ + $constant) { + if (! defined($constant)) { + define($constant, $i + 1); + } +} +/**#@-*/ + +/** + * Compounded regular expression. Any of + * the contained patterns could match and + * when one does, it's label is returned. + * @package SimpleTest + * @subpackage WebTester + */ +class ParallelRegex { + var $_patterns; + var $_labels; + var $_regex; + var $_case; + + /** + * Constructor. Starts with no patterns. + * @param boolean $case True for case sensitive, false + * for insensitive. + * @access public + */ + function ParallelRegex($case) { + $this->_case = $case; + $this->_patterns = array(); + $this->_labels = array(); + $this->_regex = null; + } + + /** + * Adds a pattern with an optional label. + * @param string $pattern Perl style regex, but ( and ) + * lose the usual meaning. + * @param string $label Label of regex to be returned + * on a match. + * @access public + */ + function addPattern($pattern, $label = true) { + $count = count($this->_patterns); + $this->_patterns[$count] = $pattern; + $this->_labels[$count] = $label; + $this->_regex = null; + } + + /** + * Attempts to match all patterns at once against + * a string. + * @param string $subject String to match against. + * @param string $match First matched portion of + * subject. + * @return boolean True on success. + * @access public + */ + function match($subject, &$match) { + if (count($this->_patterns) == 0) { + return false; + } + if (! preg_match($this->_getCompoundedRegex(), $subject, $matches)) { + $match = ''; + return false; + } + $match = $matches[0]; + for ($i = 1; $i < count($matches); $i++) { + if ($matches[$i]) { + return $this->_labels[$i - 1]; + } + } + return true; + } + + /** + * Compounds the patterns into a single + * regular expression separated with the + * "or" operator. Caches the regex. + * Will automatically escape (, ) and / tokens. + * @param array $patterns List of patterns in order. + * @access private + */ + function _getCompoundedRegex() { + if ($this->_regex == null) { + for ($i = 0, $count = count($this->_patterns); $i < $count; $i++) { + $this->_patterns[$i] = '(' . str_replace( + array('/', '(', ')'), + array('\/', '\(', '\)'), + $this->_patterns[$i]) . ')'; + } + $this->_regex = "/" . implode("|", $this->_patterns) . "/" . $this->_getPerlMatchingFlags(); + } + return $this->_regex; + } + + /** + * Accessor for perl regex mode flags to use. + * @return string Perl regex flags. + * @access private + */ + function _getPerlMatchingFlags() { + return ($this->_case ? "msS" : "msSi"); + } +} + +/** + * States for a stack machine. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleStateStack { + var $_stack; + + /** + * Constructor. Starts in named state. + * @param string $start Starting state name. + * @access public + */ + function SimpleStateStack($start) { + $this->_stack = array($start); + } + + /** + * Accessor for current state. + * @return string State. + * @access public + */ + function getCurrent() { + return $this->_stack[count($this->_stack) - 1]; + } + + /** + * Adds a state to the stack and sets it + * to be the current state. + * @param string $state New state. + * @access public + */ + function enter($state) { + array_push($this->_stack, $state); + } + + /** + * Leaves the current state and reverts + * to the previous one. + * @return boolean False if we drop off + * the bottom of the list. + * @access public + */ + function leave() { + if (count($this->_stack) == 1) { + return false; + } + array_pop($this->_stack); + return true; + } +} + +/** + * Accepts text and breaks it into tokens. + * Some optimisation to make the sure the + * content is only scanned by the PHP regex + * parser once. Lexer modes must not start + * with leading underscores. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleLexer { + var $_regexes; + var $_parser; + var $_mode; + var $_mode_handlers; + var $_case; + + /** + * Sets up the lexer in case insensitive matching + * by default. + * @param SimpleSaxParser $parser Handling strategy by + * reference. + * @param string $start Starting handler. + * @param boolean $case True for case sensitive. + * @access public + */ + function SimpleLexer(&$parser, $start = "accept", $case = false) { + $this->_case = $case; + $this->_regexes = array(); + $this->_parser = &$parser; + $this->_mode = &new SimpleStateStack($start); + $this->_mode_handlers = array($start => $start); + } + + /** + * Adds a token search pattern for a particular + * parsing mode. The pattern does not change the + * current mode. + * @param string $pattern Perl style regex, but ( and ) + * lose the usual meaning. + * @param string $mode Should only apply this + * pattern when dealing with + * this type of input. + * @access public + */ + function addPattern($pattern, $mode = "accept") { + if (! isset($this->_regexes[$mode])) { + $this->_regexes[$mode] = new ParallelRegex($this->_case); + } + $this->_regexes[$mode]->addPattern($pattern); + if (! isset($this->_mode_handlers[$mode])) { + $this->_mode_handlers[$mode] = $mode; + } + } + + /** + * Adds a pattern that will enter a new parsing + * mode. Useful for entering parenthesis, strings, + * tags, etc. + * @param string $pattern Perl style regex, but ( and ) + * lose the usual meaning. + * @param string $mode Should only apply this + * pattern when dealing with + * this type of input. + * @param string $new_mode Change parsing to this new + * nested mode. + * @access public + */ + function addEntryPattern($pattern, $mode, $new_mode) { + if (! isset($this->_regexes[$mode])) { + $this->_regexes[$mode] = new ParallelRegex($this->_case); + } + $this->_regexes[$mode]->addPattern($pattern, $new_mode); + if (! isset($this->_mode_handlers[$new_mode])) { + $this->_mode_handlers[$new_mode] = $new_mode; + } + } + + /** + * Adds a pattern that will exit the current mode + * and re-enter the previous one. + * @param string $pattern Perl style regex, but ( and ) + * lose the usual meaning. + * @param string $mode Mode to leave. + * @access public + */ + function addExitPattern($pattern, $mode) { + if (! isset($this->_regexes[$mode])) { + $this->_regexes[$mode] = new ParallelRegex($this->_case); + } + $this->_regexes[$mode]->addPattern($pattern, "__exit"); + if (! isset($this->_mode_handlers[$mode])) { + $this->_mode_handlers[$mode] = $mode; + } + } + + /** + * Adds a pattern that has a special mode. Acts as an entry + * and exit pattern in one go, effectively calling a special + * parser handler for this token only. + * @param string $pattern Perl style regex, but ( and ) + * lose the usual meaning. + * @param string $mode Should only apply this + * pattern when dealing with + * this type of input. + * @param string $special Use this mode for this one token. + * @access public + */ + function addSpecialPattern($pattern, $mode, $special) { + if (! isset($this->_regexes[$mode])) { + $this->_regexes[$mode] = new ParallelRegex($this->_case); + } + $this->_regexes[$mode]->addPattern($pattern, "_$special"); + if (! isset($this->_mode_handlers[$special])) { + $this->_mode_handlers[$special] = $special; + } + } + + /** + * Adds a mapping from a mode to another handler. + * @param string $mode Mode to be remapped. + * @param string $handler New target handler. + * @access public + */ + function mapHandler($mode, $handler) { + $this->_mode_handlers[$mode] = $handler; + } + + /** + * Splits the page text into tokens. Will fail + * if the handlers report an error or if no + * content is consumed. If successful then each + * unparsed and parsed token invokes a call to the + * held listener. + * @param string $raw Raw HTML text. + * @return boolean True on success, else false. + * @access public + */ + function parse($raw) { + if (! isset($this->_parser)) { + return false; + } + $length = strlen($raw); + while (is_array($parsed = $this->_reduce($raw))) { + list($raw, $unmatched, $matched, $mode) = $parsed; + if (! $this->_dispatchTokens($unmatched, $matched, $mode)) { + return false; + } + if ($raw === '') { + return true; + } + if (strlen($raw) == $length) { + return false; + } + $length = strlen($raw); + } + if (! $parsed) { + return false; + } + return $this->_invokeParser($raw, LEXER_UNMATCHED); + } + + /** + * Sends the matched token and any leading unmatched + * text to the parser changing the lexer to a new + * mode if one is listed. + * @param string $unmatched Unmatched leading portion. + * @param string $matched Actual token match. + * @param string $mode Mode after match. A boolean + * false mode causes no change. + * @return boolean False if there was any error + * from the parser. + * @access private + */ + function _dispatchTokens($unmatched, $matched, $mode = false) { + if (! $this->_invokeParser($unmatched, LEXER_UNMATCHED)) { + return false; + } + if (is_bool($mode)) { + return $this->_invokeParser($matched, LEXER_MATCHED); + } + if ($this->_isModeEnd($mode)) { + if (! $this->_invokeParser($matched, LEXER_EXIT)) { + return false; + } + return $this->_mode->leave(); + } + if ($this->_isSpecialMode($mode)) { + $this->_mode->enter($this->_decodeSpecial($mode)); + if (! $this->_invokeParser($matched, LEXER_SPECIAL)) { + return false; + } + return $this->_mode->leave(); + } + $this->_mode->enter($mode); + return $this->_invokeParser($matched, LEXER_ENTER); + } + + /** + * Tests to see if the new mode is actually to leave + * the current mode and pop an item from the matching + * mode stack. + * @param string $mode Mode to test. + * @return boolean True if this is the exit mode. + * @access private + */ + function _isModeEnd($mode) { + return ($mode === "__exit"); + } + + /** + * Test to see if the mode is one where this mode + * is entered for this token only and automatically + * leaves immediately afterwoods. + * @param string $mode Mode to test. + * @return boolean True if this is the exit mode. + * @access private + */ + function _isSpecialMode($mode) { + return (strncmp($mode, "_", 1) == 0); + } + + /** + * Strips the magic underscore marking single token + * modes. + * @param string $mode Mode to decode. + * @return string Underlying mode name. + * @access private + */ + function _decodeSpecial($mode) { + return substr($mode, 1); + } + + /** + * Calls the parser method named after the current + * mode. Empty content will be ignored. The lexer + * has a parser handler for each mode in the lexer. + * @param string $content Text parsed. + * @param boolean $is_match Token is recognised rather + * than unparsed data. + * @access private + */ + function _invokeParser($content, $is_match) { + if (($content === '') || ($content === false)) { + return true; + } + $handler = $this->_mode_handlers[$this->_mode->getCurrent()]; + return $this->_parser->$handler($content, $is_match); + } + + /** + * Tries to match a chunk of text and if successful + * removes the recognised chunk and any leading + * unparsed data. Empty strings will not be matched. + * @param string $raw The subject to parse. This is the + * content that will be eaten. + * @return array/boolean Three item list of unparsed + * content followed by the + * recognised token and finally the + * action the parser is to take. + * True if no match, false if there + * is a parsing error. + * @access private + */ + function _reduce($raw) { + if ($action = $this->_regexes[$this->_mode->getCurrent()]->match($raw, $match)) { + $unparsed_character_count = strpos($raw, $match); + $unparsed = substr($raw, 0, $unparsed_character_count); + $raw = substr($raw, $unparsed_character_count + strlen($match)); + return array($raw, $unparsed, $match, $action); + } + return true; + } +} + +/** + * Breaks HTML into SAX events. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleHtmlLexer extends SimpleLexer { + + /** + * Sets up the lexer with case insensitive matching + * and adds the HTML handlers. + * @param SimpleSaxParser $parser Handling strategy by + * reference. + * @access public + */ + function SimpleHtmlLexer(&$parser) { + $this->SimpleLexer($parser, 'text'); + $this->mapHandler('text', 'acceptTextToken'); + $this->_addSkipping(); + foreach ($this->_getParsedTags() as $tag) { + $this->_addTag($tag); + } + $this->_addInTagTokens(); + } + + /** + * List of parsed tags. Others are ignored. + * @return array List of searched for tags. + * @access private + */ + function _getParsedTags() { + return array('a', 'base', 'title', 'form', 'input', 'button', 'textarea', 'select', + 'option', 'frameset', 'frame', 'label'); + } + + /** + * The lexer has to skip certain sections such + * as server code, client code and styles. + * @access private + */ + function _addSkipping() { + $this->mapHandler('css', 'ignore'); + $this->addEntryPattern('addExitPattern('', 'css'); + $this->mapHandler('js', 'ignore'); + $this->addEntryPattern('addExitPattern('', 'js'); + $this->mapHandler('comment', 'ignore'); + $this->addEntryPattern('', 'comment'); + } + + /** + * Pattern matches to start and end a tag. + * @param string $tag Name of tag to scan for. + * @access private + */ + function _addTag($tag) { + $this->addSpecialPattern("", 'text', 'acceptEndToken'); + $this->addEntryPattern("<$tag", 'text', 'tag'); + } + + /** + * Pattern matches to parse the inside of a tag + * including the attributes and their quoting. + * @access private + */ + function _addInTagTokens() { + $this->mapHandler('tag', 'acceptStartToken'); + $this->addSpecialPattern('\s+', 'tag', 'ignore'); + $this->_addAttributeTokens(); + $this->addExitPattern('/>', 'tag'); + $this->addExitPattern('>', 'tag'); + } + + /** + * Matches attributes that are either single quoted, + * double quoted or unquoted. + * @access private + */ + function _addAttributeTokens() { + $this->mapHandler('dq_attribute', 'acceptAttributeToken'); + $this->addEntryPattern('=\s*"', 'tag', 'dq_attribute'); + $this->addPattern("\\\\\"", 'dq_attribute'); + $this->addExitPattern('"', 'dq_attribute'); + $this->mapHandler('sq_attribute', 'acceptAttributeToken'); + $this->addEntryPattern("=\s*'", 'tag', 'sq_attribute'); + $this->addPattern("\\\\'", 'sq_attribute'); + $this->addExitPattern("'", 'sq_attribute'); + $this->mapHandler('uq_attribute', 'acceptAttributeToken'); + $this->addSpecialPattern('=\s*[^>\s]*', 'tag', 'uq_attribute'); + } +} + +/** + * Converts HTML tokens into selected SAX events. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleHtmlSaxParser { + var $_lexer; + var $_listener; + var $_tag; + var $_attributes; + var $_current_attribute; + + /** + * Sets the listener. + * @param SimpleSaxListener $listener SAX event handler. + * @access public + */ + function SimpleHtmlSaxParser(&$listener) { + $this->_listener = &$listener; + $this->_lexer = &$this->createLexer($this); + $this->_tag = ''; + $this->_attributes = array(); + $this->_current_attribute = ''; + } + + /** + * Runs the content through the lexer which + * should call back to the acceptors. + * @param string $raw Page text to parse. + * @return boolean False if parse error. + * @access public + */ + function parse($raw) { + return $this->_lexer->parse($raw); + } + + /** + * Sets up the matching lexer. Starts in 'text' mode. + * @param SimpleSaxParser $parser Event generator, usually $self. + * @return SimpleLexer Lexer suitable for this parser. + * @access public + * @static + */ + function &createLexer(&$parser) { + $lexer = &new SimpleHtmlLexer($parser); + return $lexer; + } + + /** + * Accepts a token from the tag mode. If the + * starting element completes then the element + * is dispatched and the current attributes + * set back to empty. The element or attribute + * name is converted to lower case. + * @param string $token Incoming characters. + * @param integer $event Lexer event type. + * @return boolean False if parse error. + * @access public + */ + function acceptStartToken($token, $event) { + if ($event == LEXER_ENTER) { + $this->_tag = strtolower(substr($token, 1)); + return true; + } + if ($event == LEXER_EXIT) { + $success = $this->_listener->startElement( + $this->_tag, + $this->_attributes); + $this->_tag = ''; + $this->_attributes = array(); + return $success; + } + if ($token != '=') { + $this->_current_attribute = strtolower(SimpleHtmlSaxParser::decodeHtml($token)); + $this->_attributes[$this->_current_attribute] = ''; + } + return true; + } + + /** + * Accepts a token from the end tag mode. + * The element name is converted to lower case. + * @param string $token Incoming characters. + * @param integer $event Lexer event type. + * @return boolean False if parse error. + * @access public + */ + function acceptEndToken($token, $event) { + if (! preg_match('/<\/(.*)>/', $token, $matches)) { + return false; + } + return $this->_listener->endElement(strtolower($matches[1])); + } + + /** + * Part of the tag data. + * @param string $token Incoming characters. + * @param integer $event Lexer event type. + * @return boolean False if parse error. + * @access public + */ + function acceptAttributeToken($token, $event) { + if ($this->_current_attribute) { + if ($event == LEXER_UNMATCHED) { + $this->_attributes[$this->_current_attribute] .= + SimpleHtmlSaxParser::decodeHtml($token); + } + if ($event == LEXER_SPECIAL) { + $this->_attributes[$this->_current_attribute] .= + preg_replace('/^=\s*/' , '', SimpleHtmlSaxParser::decodeHtml($token)); + } + } + return true; + } + + /** + * A character entity. + * @param string $token Incoming characters. + * @param integer $event Lexer event type. + * @return boolean False if parse error. + * @access public + */ + function acceptEntityToken($token, $event) { + } + + /** + * Character data between tags regarded as + * important. + * @param string $token Incoming characters. + * @param integer $event Lexer event type. + * @return boolean False if parse error. + * @access public + */ + function acceptTextToken($token, $event) { + return $this->_listener->addContent($token); + } + + /** + * Incoming data to be ignored. + * @param string $token Incoming characters. + * @param integer $event Lexer event type. + * @return boolean False if parse error. + * @access public + */ + function ignore($token, $event) { + return true; + } + + /** + * Decodes any HTML entities. + * @param string $html Incoming HTML. + * @return string Outgoing plain text. + * @access public + * @static + */ + function decodeHtml($html) { + return html_entity_decode($html, ENT_QUOTES); + } + + /** + * Turns HTML into text browser visible text. Images + * are converted to their alt text and tags are supressed. + * Entities are converted to their visible representation. + * @param string $html HTML to convert. + * @return string Plain text. + * @access public + * @static + */ + function normalise($html) { + $text = preg_replace('||', '', $html); + $text = preg_replace('|]*>.*?|', '', $text); + $text = preg_replace('|]*alt\s*=\s*"([^"]*)"[^>]*>|', ' \1 ', $text); + $text = preg_replace('|]*alt\s*=\s*\'([^\']*)\'[^>]*>|', ' \1 ', $text); + $text = preg_replace('|]*alt\s*=\s*([a-zA-Z_]+)[^>]*>|', ' \1 ', $text); + $text = preg_replace('|<[^>]*>|', '', $text); + $text = SimpleHtmlSaxParser::decodeHtml($text); + $text = preg_replace('|\s+|', ' ', $text); + return trim(trim($text), "\xA0"); // TODO: The \xAO is a  . Add a test for this. + } +} + +/** + * SAX event handler. + * @package SimpleTest + * @subpackage WebTester + * @abstract + */ +class SimpleSaxListener { + + /** + * Sets the document to write to. + * @access public + */ + function SimpleSaxListener() { + } + + /** + * Start of element event. + * @param string $name Element name. + * @param hash $attributes Name value pairs. + * Attributes without content + * are marked as true. + * @return boolean False on parse error. + * @access public + */ + function startElement($name, $attributes) { + } + + /** + * End of element event. + * @param string $name Element name. + * @return boolean False on parse error. + * @access public + */ + function endElement($name) { + } + + /** + * Unparsed, but relevant data. + * @param string $text May include unparsed tags. + * @return boolean False on parse error. + * @access public + */ + function addContent($text) { + } +} +?> \ No newline at end of file diff --git a/thirdparty/simpletest/selector.php b/thirdparty/simpletest/selector.php new file mode 100644 index 000000000..de044b85a --- /dev/null +++ b/thirdparty/simpletest/selector.php @@ -0,0 +1,137 @@ +_name = $name; + } + + function getName() { + return $this->_name; + } + + /** + * Compares with name attribute of widget. + * @param SimpleWidget $widget Control to compare. + * @access public + */ + function isMatch($widget) { + return ($widget->getName() == $this->_name); + } +} + +/** + * Used to extract form elements for testing against. + * Searches by visible label or alt text. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleByLabel { + var $_label; + + /** + * Stashes the name for later comparison. + * @param string $label Visible text to match. + */ + function SimpleByLabel($label) { + $this->_label = $label; + } + + /** + * Comparison. Compares visible text of widget or + * related label. + * @param SimpleWidget $widget Control to compare. + * @access public + */ + function isMatch($widget) { + if (! method_exists($widget, 'isLabel')) { + return false; + } + return $widget->isLabel($this->_label); + } +} + +/** + * Used to extract form elements for testing against. + * Searches dy id attribute. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleById { + var $_id; + + /** + * Stashes the name for later comparison. + * @param string $id ID atribute to match. + */ + function SimpleById($id) { + $this->_id = $id; + } + + /** + * Comparison. Compares id attribute of widget. + * @param SimpleWidget $widget Control to compare. + * @access public + */ + function isMatch($widget) { + return $widget->isId($this->_id); + } +} + +/** + * Used to extract form elements for testing against. + * Searches by visible label, name or alt text. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleByLabelOrName { + var $_label; + + /** + * Stashes the name/label for later comparison. + * @param string $label Visible text to match. + */ + function SimpleByLabelOrName($label) { + $this->_label = $label; + } + + /** + * Comparison. Compares visible text of widget or + * related label or name. + * @param SimpleWidget $widget Control to compare. + * @access public + */ + function isMatch($widget) { + if (method_exists($widget, 'isLabel')) { + if ($widget->isLabel($this->_label)) { + return true; + } + } + return ($widget->getName() == $this->_label); + } +} +?> \ No newline at end of file diff --git a/thirdparty/simpletest/socket.php b/thirdparty/simpletest/socket.php new file mode 100644 index 000000000..3ad5a9ff4 --- /dev/null +++ b/thirdparty/simpletest/socket.php @@ -0,0 +1,216 @@ +_clearError(); + } + + /** + * Test for an outstanding error. + * @return boolean True if there is an error. + * @access public + */ + function isError() { + return ($this->_error != ''); + } + + /** + * Accessor for an outstanding error. + * @return string Empty string if no error otherwise + * the error message. + * @access public + */ + function getError() { + return $this->_error; + } + + /** + * Sets the internal error. + * @param string Error message to stash. + * @access protected + */ + function _setError($error) { + $this->_error = $error; + } + + /** + * Resets the error state to no error. + * @access protected + */ + function _clearError() { + $this->_setError(''); + } +} + +/** + * Wrapper for TCP/IP socket. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleSocket extends SimpleStickyError { + var $_handle; + var $_is_open = false; + var $_sent = ''; + var $lock_size; + + /** + * Opens a socket for reading and writing. + * @param string $host Hostname to send request to. + * @param integer $port Port on remote machine to open. + * @param integer $timeout Connection timeout in seconds. + * @param integer $block_size Size of chunk to read. + * @access public + */ + function SimpleSocket($host, $port, $timeout, $block_size = 255) { + $this->SimpleStickyError(); + if (! ($this->_handle = $this->_openSocket($host, $port, $error_number, $error, $timeout))) { + $this->_setError("Cannot open [$host:$port] with [$error] within [$timeout] seconds"); + return; + } + $this->_is_open = true; + $this->_block_size = $block_size; + SimpleTestCompatibility::setTimeout($this->_handle, $timeout); + } + + /** + * Writes some data to the socket and saves alocal copy. + * @param string $message String to send to socket. + * @return boolean True if successful. + * @access public + */ + function write($message) { + if ($this->isError() || ! $this->isOpen()) { + return false; + } + $count = fwrite($this->_handle, $message); + if (! $count) { + if ($count === false) { + $this->_setError('Cannot write to socket'); + $this->close(); + } + return false; + } + fflush($this->_handle); + $this->_sent .= $message; + return true; + } + + /** + * Reads data from the socket. The error suppresion + * is a workaround for PHP4 always throwing a warning + * with a secure socket. + * @return integer/boolean Incoming bytes. False + * on error. + * @access public + */ + function read() { + if ($this->isError() || ! $this->isOpen()) { + return false; + } + $raw = @fread($this->_handle, $this->_block_size); + if ($raw === false) { + $this->_setError('Cannot read from socket'); + $this->close(); + } + return $raw; + } + + /** + * Accessor for socket open state. + * @return boolean True if open. + * @access public + */ + function isOpen() { + return $this->_is_open; + } + + /** + * Closes the socket preventing further reads. + * Cannot be reopened once closed. + * @return boolean True if successful. + * @access public + */ + function close() { + $this->_is_open = false; + return fclose($this->_handle); + } + + /** + * Accessor for content so far. + * @return string Bytes sent only. + * @access public + */ + function getSent() { + return $this->_sent; + } + + /** + * Actually opens the low level socket. + * @param string $host Host to connect to. + * @param integer $port Port on host. + * @param integer $error_number Recipient of error code. + * @param string $error Recipoent of error message. + * @param integer $timeout Maximum time to wait for connection. + * @access protected + */ + function _openSocket($host, $port, &$error_number, &$error, $timeout) { + return @fsockopen($host, $port, $error_number, $error, $timeout); + } +} + +/** + * Wrapper for TCP/IP socket over TLS. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleSecureSocket extends SimpleSocket { + + /** + * Opens a secure socket for reading and writing. + * @param string $host Hostname to send request to. + * @param integer $port Port on remote machine to open. + * @param integer $timeout Connection timeout in seconds. + * @access public + */ + function SimpleSecureSocket($host, $port, $timeout) { + $this->SimpleSocket($host, $port, $timeout); + } + + /** + * Actually opens the low level socket. + * @param string $host Host to connect to. + * @param integer $port Port on host. + * @param integer $error_number Recipient of error code. + * @param string $error Recipient of error message. + * @param integer $timeout Maximum time to wait for connection. + * @access protected + */ + function _openSocket($host, $port, &$error_number, &$error, $timeout) { + return parent::_openSocket("tls://$host", $port, $error_number, $error, $timeout); + } +} +?> \ No newline at end of file diff --git a/thirdparty/simpletest/tag.php b/thirdparty/simpletest/tag.php new file mode 100644 index 000000000..7bccae205 --- /dev/null +++ b/thirdparty/simpletest/tag.php @@ -0,0 +1,1418 @@ +_name = strtolower(trim($name)); + $this->_attributes = $attributes; + $this->_content = ''; + } + + /** + * Check to see if the tag can have both start and + * end tags with content in between. + * @return boolean True if content allowed. + * @access public + */ + function expectEndTag() { + return true; + } + + /** + * The current tag should not swallow all content for + * itself as it's searchable page content. Private + * content tags are usually widgets that contain default + * values. + * @return boolean False as content is available + * to other tags by default. + * @access public + */ + function isPrivateContent() { + return false; + } + + /** + * Appends string content to the current content. + * @param string $content Additional text. + * @access public + */ + function addContent($content) { + $this->_content .= (string)$content; + } + + /** + * Adds an enclosed tag to the content. + * @param SimpleTag $tag New tag. + * @access public + */ + function addTag(&$tag) { + } + + /** + * Accessor for tag name. + * @return string Name of tag. + * @access public + */ + function getTagName() { + return $this->_name; + } + + /** + * List of legal child elements. + * @return array List of element names. + * @access public + */ + function getChildElements() { + return array(); + } + + /** + * Accessor for an attribute. + * @param string $label Attribute name. + * @return string Attribute value. + * @access public + */ + function getAttribute($label) { + $label = strtolower($label); + if (! isset($this->_attributes[$label])) { + return false; + } + return (string)$this->_attributes[$label]; + } + + /** + * Sets an attribute. + * @param string $label Attribute name. + * @return string $value New attribute value. + * @access protected + */ + function _setAttribute($label, $value) { + $this->_attributes[strtolower($label)] = $value; + } + + /** + * Accessor for the whole content so far. + * @return string Content as big raw string. + * @access public + */ + function getContent() { + return $this->_content; + } + + /** + * Accessor for content reduced to visible text. Acts + * like a text mode browser, normalising space and + * reducing images to their alt text. + * @return string Content as plain text. + * @access public + */ + function getText() { + return SimpleHtmlSaxParser::normalise($this->_content); + } + + /** + * Test to see if id attribute matches. + * @param string $id ID to test against. + * @return boolean True on match. + * @access public + */ + function isId($id) { + return ($this->getAttribute('id') == $id); + } +} + +/** + * Base url. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleBaseTag extends SimpleTag { + + /** + * Starts with a named tag with attributes only. + * @param hash $attributes Attribute names and + * string values. + */ + function SimpleBaseTag($attributes) { + $this->SimpleTag('base', $attributes); + } + + /** + * Base tag is not a block tag. + * @return boolean false + * @access public + */ + function expectEndTag() { + return false; + } +} + +/** + * Page title. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleTitleTag extends SimpleTag { + + /** + * Starts with a named tag with attributes only. + * @param hash $attributes Attribute names and + * string values. + */ + function SimpleTitleTag($attributes) { + $this->SimpleTag('title', $attributes); + } +} + +/** + * Link. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleAnchorTag extends SimpleTag { + + /** + * Starts with a named tag with attributes only. + * @param hash $attributes Attribute names and + * string values. + */ + function SimpleAnchorTag($attributes) { + $this->SimpleTag('a', $attributes); + } + + /** + * Accessor for URL as string. + * @return string Coerced as string. + * @access public + */ + function getHref() { + $url = $this->getAttribute('href'); + if (is_bool($url)) { + $url = ''; + } + return $url; + } +} + +/** + * Form element. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleWidget extends SimpleTag { + var $_value; + var $_label; + var $_is_set; + + /** + * Starts with a named tag with attributes only. + * @param string $name Tag name. + * @param hash $attributes Attribute names and + * string values. + */ + function SimpleWidget($name, $attributes) { + $this->SimpleTag($name, $attributes); + $this->_value = false; + $this->_label = false; + $this->_is_set = false; + } + + /** + * Accessor for name submitted as the key in + * GET/POST variables hash. + * @return string Parsed value. + * @access public + */ + function getName() { + return $this->getAttribute('name'); + } + + /** + * Accessor for default value parsed with the tag. + * @return string Parsed value. + * @access public + */ + function getDefault() { + return $this->getAttribute('value'); + } + + /** + * Accessor for currently set value or default if + * none. + * @return string Value set by form or default + * if none. + * @access public + */ + function getValue() { + if (! $this->_is_set) { + return $this->getDefault(); + } + return $this->_value; + } + + /** + * Sets the current form element value. + * @param string $value New value. + * @return boolean True if allowed. + * @access public + */ + function setValue($value) { + $this->_value = $value; + $this->_is_set = true; + return true; + } + + /** + * Resets the form element value back to the + * default. + * @access public + */ + function resetValue() { + $this->_is_set = false; + } + + /** + * Allows setting of a label externally, say by a + * label tag. + * @param string $label Label to attach. + * @access public + */ + function setLabel($label) { + $this->_label = trim($label); + } + + /** + * Reads external or internal label. + * @param string $label Label to test. + * @return boolean True is match. + * @access public + */ + function isLabel($label) { + return $this->_label == trim($label); + } + + /** + * Dispatches the value into the form encoded packet. + * @param SimpleEncoding $encoding Form packet. + * @access public + */ + function write(&$encoding) { + if ($this->getName()) { + $encoding->add($this->getName(), $this->getValue()); + } + } +} + +/** + * Text, password and hidden field. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleTextTag extends SimpleWidget { + + /** + * Starts with a named tag with attributes only. + * @param hash $attributes Attribute names and + * string values. + */ + function SimpleTextTag($attributes) { + $this->SimpleWidget('input', $attributes); + if ($this->getAttribute('value') === false) { + $this->_setAttribute('value', ''); + } + } + + /** + * Tag contains no content. + * @return boolean False. + * @access public + */ + function expectEndTag() { + return false; + } + + /** + * Sets the current form element value. Cannot + * change the value of a hidden field. + * @param string $value New value. + * @return boolean True if allowed. + * @access public + */ + function setValue($value) { + if ($this->getAttribute('type') == 'hidden') { + return false; + } + return parent::setValue($value); + } +} + +/** + * Submit button as input tag. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleSubmitTag extends SimpleWidget { + + /** + * Starts with a named tag with attributes only. + * @param hash $attributes Attribute names and + * string values. + */ + function SimpleSubmitTag($attributes) { + $this->SimpleWidget('input', $attributes); + if ($this->getAttribute('value') === false) { + $this->_setAttribute('value', 'Submit'); + } + } + + /** + * Tag contains no end element. + * @return boolean False. + * @access public + */ + function expectEndTag() { + return false; + } + + /** + * Disables the setting of the button value. + * @param string $value Ignored. + * @return boolean True if allowed. + * @access public + */ + function setValue($value) { + return false; + } + + /** + * Value of browser visible text. + * @return string Visible label. + * @access public + */ + function getLabel() { + return $this->getValue(); + } + + /** + * Test for a label match when searching. + * @param string $label Label to test. + * @return boolean True on match. + * @access public + */ + function isLabel($label) { + return trim($label) == trim($this->getLabel()); + } +} + +/** + * Image button as input tag. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleImageSubmitTag extends SimpleWidget { + + /** + * Starts with a named tag with attributes only. + * @param hash $attributes Attribute names and + * string values. + */ + function SimpleImageSubmitTag($attributes) { + $this->SimpleWidget('input', $attributes); + } + + /** + * Tag contains no end element. + * @return boolean False. + * @access public + */ + function expectEndTag() { + return false; + } + + /** + * Disables the setting of the button value. + * @param string $value Ignored. + * @return boolean True if allowed. + * @access public + */ + function setValue($value) { + return false; + } + + /** + * Value of browser visible text. + * @return string Visible label. + * @access public + */ + function getLabel() { + if ($this->getAttribute('title')) { + return $this->getAttribute('title'); + } + return $this->getAttribute('alt'); + } + + /** + * Test for a label match when searching. + * @param string $label Label to test. + * @return boolean True on match. + * @access public + */ + function isLabel($label) { + return trim($label) == trim($this->getLabel()); + } + + /** + * Dispatches the value into the form encoded packet. + * @param SimpleEncoding $encoding Form packet. + * @param integer $x X coordinate of click. + * @param integer $y Y coordinate of click. + * @access public + */ + function write(&$encoding, $x, $y) { + if ($this->getName()) { + $encoding->add($this->getName() . '.x', $x); + $encoding->add($this->getName() . '.y', $y); + } else { + $encoding->add('x', $x); + $encoding->add('y', $y); + } + } +} + +/** + * Submit button as button tag. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleButtonTag extends SimpleWidget { + + /** + * Starts with a named tag with attributes only. + * Defaults are very browser dependent. + * @param hash $attributes Attribute names and + * string values. + */ + function SimpleButtonTag($attributes) { + $this->SimpleWidget('button', $attributes); + } + + /** + * Check to see if the tag can have both start and + * end tags with content in between. + * @return boolean True if content allowed. + * @access public + */ + function expectEndTag() { + return true; + } + + /** + * Disables the setting of the button value. + * @param string $value Ignored. + * @return boolean True if allowed. + * @access public + */ + function setValue($value) { + return false; + } + + /** + * Value of browser visible text. + * @return string Visible label. + * @access public + */ + function getLabel() { + return $this->getContent(); + } + + /** + * Test for a label match when searching. + * @param string $label Label to test. + * @return boolean True on match. + * @access public + */ + function isLabel($label) { + return trim($label) == trim($this->getLabel()); + } +} + +/** + * Content tag for text area. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleTextAreaTag extends SimpleWidget { + + /** + * Starts with a named tag with attributes only. + * @param hash $attributes Attribute names and + * string values. + */ + function SimpleTextAreaTag($attributes) { + $this->SimpleWidget('textarea', $attributes); + } + + /** + * Accessor for starting value. + * @return string Parsed value. + * @access public + */ + function getDefault() { + return $this->_wrap(SimpleHtmlSaxParser::decodeHtml($this->getContent())); + } + + /** + * Applies word wrapping if needed. + * @param string $value New value. + * @return boolean True if allowed. + * @access public + */ + function setValue($value) { + return parent::setValue($this->_wrap($value)); + } + + /** + * Test to see if text should be wrapped. + * @return boolean True if wrapping on. + * @access private + */ + function _wrapIsEnabled() { + if ($this->getAttribute('cols')) { + $wrap = $this->getAttribute('wrap'); + if (($wrap == 'physical') || ($wrap == 'hard')) { + return true; + } + } + return false; + } + + /** + * Performs the formatting that is peculiar to + * this tag. There is strange behaviour in this + * one, including stripping a leading new line. + * Go figure. I am using Firefox as a guide. + * @param string $text Text to wrap. + * @return string Text wrapped with carriage + * returns and line feeds + * @access private + */ + function _wrap($text) { + $text = str_replace("\r\r\n", "\r\n", str_replace("\n", "\r\n", $text)); + $text = str_replace("\r\n\n", "\r\n", str_replace("\r", "\r\n", $text)); + if (strncmp($text, "\r\n", strlen("\r\n")) == 0) { + $text = substr($text, strlen("\r\n")); + } + if ($this->_wrapIsEnabled()) { + return wordwrap( + $text, + (integer)$this->getAttribute('cols'), + "\r\n"); + } + return $text; + } + + /** + * The content of textarea is not part of the page. + * @return boolean True. + * @access public + */ + function isPrivateContent() { + return true; + } +} + +/** + * File upload widget. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleUploadTag extends SimpleWidget { + + /** + * Starts with attributes only. + * @param hash $attributes Attribute names and + * string values. + */ + function SimpleUploadTag($attributes) { + $this->SimpleWidget('input', $attributes); + } + + /** + * Tag contains no content. + * @return boolean False. + * @access public + */ + function expectEndTag() { + return false; + } + + /** + * Dispatches the value into the form encoded packet. + * @param SimpleEncoding $encoding Form packet. + * @access public + */ + function write(&$encoding) { + if (! file_exists($this->getValue())) { + return; + } + $encoding->attach( + $this->getName(), + implode('', file($this->getValue())), + basename($this->getValue())); + } +} + +/** + * Drop down widget. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleSelectionTag extends SimpleWidget { + var $_options; + var $_choice; + + /** + * Starts with attributes only. + * @param hash $attributes Attribute names and + * string values. + */ + function SimpleSelectionTag($attributes) { + $this->SimpleWidget('select', $attributes); + $this->_options = array(); + $this->_choice = false; + } + + /** + * Adds an option tag to a selection field. + * @param SimpleOptionTag $tag New option. + * @access public + */ + function addTag(&$tag) { + if ($tag->getTagName() == 'option') { + $this->_options[] = &$tag; + } + } + + /** + * Text within the selection element is ignored. + * @param string $content Ignored. + * @access public + */ + function addContent($content) { + } + + /** + * Scans options for defaults. If none, then + * the first option is selected. + * @return string Selected field. + * @access public + */ + function getDefault() { + for ($i = 0, $count = count($this->_options); $i < $count; $i++) { + if ($this->_options[$i]->getAttribute('selected') !== false) { + return $this->_options[$i]->getDefault(); + } + } + if ($count > 0) { + return $this->_options[0]->getDefault(); + } + return ''; + } + + /** + * Can only set allowed values. + * @param string $value New choice. + * @return boolean True if allowed. + * @access public + */ + function setValue($value) { + for ($i = 0, $count = count($this->_options); $i < $count; $i++) { + if ($this->_options[$i]->isValue($value)) { + $this->_choice = $i; + return true; + } + } + return false; + } + + /** + * Accessor for current selection value. + * @return string Value attribute or + * content of opton. + * @access public + */ + function getValue() { + if ($this->_choice === false) { + return $this->getDefault(); + } + return $this->_options[$this->_choice]->getValue(); + } +} + +/** + * Drop down widget. + * @package SimpleTest + * @subpackage WebTester + */ +class MultipleSelectionTag extends SimpleWidget { + var $_options; + var $_values; + + /** + * Starts with attributes only. + * @param hash $attributes Attribute names and + * string values. + */ + function MultipleSelectionTag($attributes) { + $this->SimpleWidget('select', $attributes); + $this->_options = array(); + $this->_values = false; + } + + /** + * Adds an option tag to a selection field. + * @param SimpleOptionTag $tag New option. + * @access public + */ + function addTag(&$tag) { + if ($tag->getTagName() == 'option') { + $this->_options[] = &$tag; + } + } + + /** + * Text within the selection element is ignored. + * @param string $content Ignored. + * @access public + */ + function addContent($content) { + } + + /** + * Scans options for defaults to populate the + * value array(). + * @return array Selected fields. + * @access public + */ + function getDefault() { + $default = array(); + for ($i = 0, $count = count($this->_options); $i < $count; $i++) { + if ($this->_options[$i]->getAttribute('selected') !== false) { + $default[] = $this->_options[$i]->getDefault(); + } + } + return $default; + } + + /** + * Can only set allowed values. Any illegal value + * will result in a failure, but all correct values + * will be set. + * @param array $desired New choices. + * @return boolean True if all allowed. + * @access public + */ + function setValue($desired) { + $achieved = array(); + foreach ($desired as $value) { + $success = false; + for ($i = 0, $count = count($this->_options); $i < $count; $i++) { + if ($this->_options[$i]->isValue($value)) { + $achieved[] = $this->_options[$i]->getValue(); + $success = true; + break; + } + } + if (! $success) { + return false; + } + } + $this->_values = $achieved; + return true; + } + + /** + * Accessor for current selection value. + * @return array List of currently set options. + * @access public + */ + function getValue() { + if ($this->_values === false) { + return $this->getDefault(); + } + return $this->_values; + } +} + +/** + * Option for selection field. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleOptionTag extends SimpleWidget { + + /** + * Stashes the attributes. + */ + function SimpleOptionTag($attributes) { + $this->SimpleWidget('option', $attributes); + } + + /** + * Does nothing. + * @param string $value Ignored. + * @return boolean Not allowed. + * @access public + */ + function setValue($value) { + return false; + } + + /** + * Test to see if a value matches the option. + * @param string $compare Value to compare with. + * @return boolean True if possible match. + * @access public + */ + function isValue($compare) { + $compare = trim($compare); + if (trim($this->getValue()) == $compare) { + return true; + } + return trim($this->getContent()) == $compare; + } + + /** + * Accessor for starting value. Will be set to + * the option label if no value exists. + * @return string Parsed value. + * @access public + */ + function getDefault() { + if ($this->getAttribute('value') === false) { + return $this->getContent(); + } + return $this->getAttribute('value'); + } + + /** + * The content of options is not part of the page. + * @return boolean True. + * @access public + */ + function isPrivateContent() { + return true; + } +} + +/** + * Radio button. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleRadioButtonTag extends SimpleWidget { + + /** + * Stashes the attributes. + * @param array $attributes Hash of attributes. + */ + function SimpleRadioButtonTag($attributes) { + $this->SimpleWidget('input', $attributes); + if ($this->getAttribute('value') === false) { + $this->_setAttribute('value', 'on'); + } + } + + /** + * Tag contains no content. + * @return boolean False. + * @access public + */ + function expectEndTag() { + return false; + } + + /** + * The only allowed value sn the one in the + * "value" attribute. + * @param string $value New value. + * @return boolean True if allowed. + * @access public + */ + function setValue($value) { + if ($value === false) { + return parent::setValue($value); + } + if ($value != $this->getAttribute('value')) { + return false; + } + return parent::setValue($value); + } + + /** + * Accessor for starting value. + * @return string Parsed value. + * @access public + */ + function getDefault() { + if ($this->getAttribute('checked') !== false) { + return $this->getAttribute('value'); + } + return false; + } +} + +/** + * Checkbox widget. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleCheckboxTag extends SimpleWidget { + + /** + * Starts with attributes only. + * @param hash $attributes Attribute names and + * string values. + */ + function SimpleCheckboxTag($attributes) { + $this->SimpleWidget('input', $attributes); + if ($this->getAttribute('value') === false) { + $this->_setAttribute('value', 'on'); + } + } + + /** + * Tag contains no content. + * @return boolean False. + * @access public + */ + function expectEndTag() { + return false; + } + + /** + * The only allowed value in the one in the + * "value" attribute. The default for this + * attribute is "on". If this widget is set to + * true, then the usual value will be taken. + * @param string $value New value. + * @return boolean True if allowed. + * @access public + */ + function setValue($value) { + if ($value === false) { + return parent::setValue($value); + } + if ($value === true) { + return parent::setValue($this->getAttribute('value')); + } + if ($value != $this->getAttribute('value')) { + return false; + } + return parent::setValue($value); + } + + /** + * Accessor for starting value. The default + * value is "on". + * @return string Parsed value. + * @access public + */ + function getDefault() { + if ($this->getAttribute('checked') !== false) { + return $this->getAttribute('value'); + } + return false; + } +} + +/** + * A group of multiple widgets with some shared behaviour. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleTagGroup { + var $_widgets = array(); + + /** + * Adds a tag to the group. + * @param SimpleWidget $widget + * @access public + */ + function addWidget(&$widget) { + $this->_widgets[] = &$widget; + } + + /** + * Accessor to widget set. + * @return array All widgets. + * @access protected + */ + function &_getWidgets() { + return $this->_widgets; + } + + /** + * Accessor for an attribute. + * @param string $label Attribute name. + * @return boolean Always false. + * @access public + */ + function getAttribute($label) { + return false; + } + + /** + * Fetches the name for the widget from the first + * member. + * @return string Name of widget. + * @access public + */ + function getName() { + if (count($this->_widgets) > 0) { + return $this->_widgets[0]->getName(); + } + } + + /** + * Scans the widgets for one with the appropriate + * ID field. + * @param string $id ID value to try. + * @return boolean True if matched. + * @access public + */ + function isId($id) { + for ($i = 0, $count = count($this->_widgets); $i < $count; $i++) { + if ($this->_widgets[$i]->isId($id)) { + return true; + } + } + return false; + } + + /** + * Scans the widgets for one with the appropriate + * attached label. + * @param string $label Attached label to try. + * @return boolean True if matched. + * @access public + */ + function isLabel($label) { + for ($i = 0, $count = count($this->_widgets); $i < $count; $i++) { + if ($this->_widgets[$i]->isLabel($label)) { + return true; + } + } + return false; + } + + /** + * Dispatches the value into the form encoded packet. + * @param SimpleEncoding $encoding Form packet. + * @access public + */ + function write(&$encoding) { + $encoding->add($this->getName(), $this->getValue()); + } +} + +/** + * A group of tags with the same name within a form. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleCheckboxGroup extends SimpleTagGroup { + + /** + * Accessor for current selected widget or false + * if none. + * @return string/array Widget values or false if none. + * @access public + */ + function getValue() { + $values = array(); + $widgets = &$this->_getWidgets(); + for ($i = 0, $count = count($widgets); $i < $count; $i++) { + if ($widgets[$i]->getValue() !== false) { + $values[] = $widgets[$i]->getValue(); + } + } + return $this->_coerceValues($values); + } + + /** + * Accessor for starting value that is active. + * @return string/array Widget values or false if none. + * @access public + */ + function getDefault() { + $values = array(); + $widgets = &$this->_getWidgets(); + for ($i = 0, $count = count($widgets); $i < $count; $i++) { + if ($widgets[$i]->getDefault() !== false) { + $values[] = $widgets[$i]->getDefault(); + } + } + return $this->_coerceValues($values); + } + + /** + * Accessor for current set values. + * @param string/array/boolean $values Either a single string, a + * hash or false for nothing set. + * @return boolean True if all values can be set. + * @access public + */ + function setValue($values) { + $values = $this->_makeArray($values); + if (! $this->_valuesArePossible($values)) { + return false; + } + $widgets = &$this->_getWidgets(); + for ($i = 0, $count = count($widgets); $i < $count; $i++) { + $possible = $widgets[$i]->getAttribute('value'); + if (in_array($widgets[$i]->getAttribute('value'), $values)) { + $widgets[$i]->setValue($possible); + } else { + $widgets[$i]->setValue(false); + } + } + return true; + } + + /** + * Tests to see if a possible value set is legal. + * @param string/array/boolean $values Either a single string, a + * hash or false for nothing set. + * @return boolean False if trying to set a + * missing value. + * @access private + */ + function _valuesArePossible($values) { + $matches = array(); + $widgets = &$this->_getWidgets(); + for ($i = 0, $count = count($widgets); $i < $count; $i++) { + $possible = $widgets[$i]->getAttribute('value'); + if (in_array($possible, $values)) { + $matches[] = $possible; + } + } + return ($values == $matches); + } + + /** + * Converts the output to an appropriate format. This means + * that no values is false, a single value is just that + * value and only two or more are contained in an array. + * @param array $values List of values of widgets. + * @return string/array/boolean Expected format for a tag. + * @access private + */ + function _coerceValues($values) { + if (count($values) == 0) { + return false; + } elseif (count($values) == 1) { + return $values[0]; + } else { + return $values; + } + } + + /** + * Converts false or string into array. The opposite of + * the coercian method. + * @param string/array/boolean $value A single item is converted + * to a one item list. False + * gives an empty list. + * @return array List of values, possibly empty. + * @access private + */ + function _makeArray($value) { + if ($value === false) { + return array(); + } + if (is_string($value)) { + return array($value); + } + return $value; + } +} + +/** + * A group of tags with the same name within a form. + * Used for radio buttons. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleRadioGroup extends SimpleTagGroup { + + /** + * Each tag is tried in turn until one is + * successfully set. The others will be + * unchecked if successful. + * @param string $value New value. + * @return boolean True if any allowed. + * @access public + */ + function setValue($value) { + if (! $this->_valueIsPossible($value)) { + return false; + } + $index = false; + $widgets = &$this->_getWidgets(); + for ($i = 0, $count = count($widgets); $i < $count; $i++) { + if (! $widgets[$i]->setValue($value)) { + $widgets[$i]->setValue(false); + } + } + return true; + } + + /** + * Tests to see if a value is allowed. + * @param string Attempted value. + * @return boolean True if a valid value. + * @access private + */ + function _valueIsPossible($value) { + $widgets = &$this->_getWidgets(); + for ($i = 0, $count = count($widgets); $i < $count; $i++) { + if ($widgets[$i]->getAttribute('value') == $value) { + return true; + } + } + return false; + } + + /** + * Accessor for current selected widget or false + * if none. + * @return string/boolean Value attribute or + * content of opton. + * @access public + */ + function getValue() { + $widgets = &$this->_getWidgets(); + for ($i = 0, $count = count($widgets); $i < $count; $i++) { + if ($widgets[$i]->getValue() !== false) { + return $widgets[$i]->getValue(); + } + } + return false; + } + + /** + * Accessor for starting value that is active. + * @return string/boolean Value of first checked + * widget or false if none. + * @access public + */ + function getDefault() { + $widgets = &$this->_getWidgets(); + for ($i = 0, $count = count($widgets); $i < $count; $i++) { + if ($widgets[$i]->getDefault() !== false) { + return $widgets[$i]->getDefault(); + } + } + return false; + } +} + +/** + * Tag to keep track of labels. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleLabelTag extends SimpleTag { + + /** + * Starts with a named tag with attributes only. + * @param hash $attributes Attribute names and + * string values. + */ + function SimpleLabelTag($attributes) { + $this->SimpleTag('label', $attributes); + } + + /** + * Access for the ID to attach the label to. + * @return string For attribute. + * @access public + */ + function getFor() { + return $this->getAttribute('for'); + } +} + +/** + * Tag to aid parsing the form. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleFormTag extends SimpleTag { + + /** + * Starts with a named tag with attributes only. + * @param hash $attributes Attribute names and + * string values. + */ + function SimpleFormTag($attributes) { + $this->SimpleTag('form', $attributes); + } +} + +/** + * Tag to aid parsing the frames in a page. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleFrameTag extends SimpleTag { + + /** + * Starts with a named tag with attributes only. + * @param hash $attributes Attribute names and + * string values. + */ + function SimpleFrameTag($attributes) { + $this->SimpleTag('frame', $attributes); + } + + /** + * Tag contains no content. + * @return boolean False. + * @access public + */ + function expectEndTag() { + return false; + } +} +?> \ No newline at end of file diff --git a/thirdparty/simpletest/url.php b/thirdparty/simpletest/url.php new file mode 100644 index 000000000..0ea220409 --- /dev/null +++ b/thirdparty/simpletest/url.php @@ -0,0 +1,528 @@ +_chompCoordinates($url); + $this->setCoordinates($x, $y); + $this->_scheme = $this->_chompScheme($url); + list($this->_username, $this->_password) = $this->_chompLogin($url); + $this->_host = $this->_chompHost($url); + $this->_port = false; + if (preg_match('/(.*?):(.*)/', $this->_host, $host_parts)) { + $this->_host = $host_parts[1]; + $this->_port = (integer)$host_parts[2]; + } + $this->_path = $this->_chompPath($url); + $this->_request = $this->_parseRequest($this->_chompRequest($url)); + $this->_fragment = (strncmp($url, "#", 1) == 0 ? substr($url, 1) : false); + $this->_target = false; + } + + /** + * Extracts the X, Y coordinate pair from an image map. + * @param string $url URL so far. The coordinates will be + * removed. + * @return array X, Y as a pair of integers. + * @access private + */ + function _chompCoordinates(&$url) { + if (preg_match('/(.*)\?(\d+),(\d+)$/', $url, $matches)) { + $url = $matches[1]; + return array((integer)$matches[2], (integer)$matches[3]); + } + return array(false, false); + } + + /** + * Extracts the scheme part of an incoming URL. + * @param string $url URL so far. The scheme will be + * removed. + * @return string Scheme part or false. + * @access private + */ + function _chompScheme(&$url) { + if (preg_match('/^([^\/:]*):(\/\/)(.*)/', $url, $matches)) { + $url = $matches[2] . $matches[3]; + return $matches[1]; + } + return false; + } + + /** + * Extracts the username and password from the + * incoming URL. The // prefix will be reattached + * to the URL after the doublet is extracted. + * @param string $url URL so far. The username and + * password are removed. + * @return array Two item list of username and + * password. Will urldecode() them. + * @access private + */ + function _chompLogin(&$url) { + $prefix = ''; + if (preg_match('/^(\/\/)(.*)/', $url, $matches)) { + $prefix = $matches[1]; + $url = $matches[2]; + } + if (preg_match('/^([^\/]*)@(.*)/', $url, $matches)) { + $url = $prefix . $matches[2]; + $parts = split(":", $matches[1]); + return array( + urldecode($parts[0]), + isset($parts[1]) ? urldecode($parts[1]) : false); + } + $url = $prefix . $url; + return array(false, false); + } + + /** + * Extracts the host part of an incoming URL. + * Includes the port number part. Will extract + * the host if it starts with // or it has + * a top level domain or it has at least two + * dots. + * @param string $url URL so far. The host will be + * removed. + * @return string Host part guess or false. + * @access private + */ + function _chompHost(&$url) { + if (preg_match('/^(\/\/)(.*?)(\/.*|\?.*|#.*|$)/', $url, $matches)) { + $url = $matches[3]; + return $matches[2]; + } + if (preg_match('/(.*?)(\.\.\/|\.\/|\/|\?|#|$)(.*)/', $url, $matches)) { + $tlds = SimpleUrl::getAllTopLevelDomains(); + if (preg_match('/[a-z0-9\-]+\.(' . $tlds . ')/i', $matches[1])) { + $url = $matches[2] . $matches[3]; + return $matches[1]; + } elseif (preg_match('/[a-z0-9\-]+\.[a-z0-9\-]+\.[a-z0-9\-]+/i', $matches[1])) { + $url = $matches[2] . $matches[3]; + return $matches[1]; + } + } + return false; + } + + /** + * Extracts the path information from the incoming + * URL. Strips this path from the URL. + * @param string $url URL so far. The host will be + * removed. + * @return string Path part or '/'. + * @access private + */ + function _chompPath(&$url) { + if (preg_match('/(.*?)(\?|#|$)(.*)/', $url, $matches)) { + $url = $matches[2] . $matches[3]; + return ($matches[1] ? $matches[1] : ''); + } + return ''; + } + + /** + * Strips off the request data. + * @param string $url URL so far. The request will be + * removed. + * @return string Raw request part. + * @access private + */ + function _chompRequest(&$url) { + if (preg_match('/\?(.*?)(#|$)(.*)/', $url, $matches)) { + $url = $matches[2] . $matches[3]; + return $matches[1]; + } + return ''; + } + + /** + * Breaks the request down into an object. + * @param string $raw Raw request. + * @return SimpleFormEncoding Parsed data. + * @access private + */ + function _parseRequest($raw) { + $this->_raw = $raw; + $request = new SimpleGetEncoding(); + foreach (split("&", $raw) as $pair) { + if (preg_match('/(.*?)=(.*)/', $pair, $matches)) { + $request->add($matches[1], urldecode($matches[2])); + } elseif ($pair) { + $request->add($pair, ''); + } + } + return $request; + } + + /** + * Accessor for protocol part. + * @param string $default Value to use if not present. + * @return string Scheme name, e.g "http". + * @access public + */ + function getScheme($default = false) { + return $this->_scheme ? $this->_scheme : $default; + } + + /** + * Accessor for user name. + * @return string Username preceding host. + * @access public + */ + function getUsername() { + return $this->_username; + } + + /** + * Accessor for password. + * @return string Password preceding host. + * @access public + */ + function getPassword() { + return $this->_password; + } + + /** + * Accessor for hostname and port. + * @param string $default Value to use if not present. + * @return string Hostname only. + * @access public + */ + function getHost($default = false) { + return $this->_host ? $this->_host : $default; + } + + /** + * Accessor for top level domain. + * @return string Last part of host. + * @access public + */ + function getTld() { + $path_parts = pathinfo($this->getHost()); + return (isset($path_parts['extension']) ? $path_parts['extension'] : false); + } + + /** + * Accessor for port number. + * @return integer TCP/IP port number. + * @access public + */ + function getPort() { + return $this->_port; + } + + /** + * Accessor for path. + * @return string Full path including leading slash if implied. + * @access public + */ + function getPath() { + if (! $this->_path && $this->_host) { + return '/'; + } + return $this->_path; + } + + /** + * Accessor for page if any. This may be a + * directory name if ambiguious. + * @return Page name. + * @access public + */ + function getPage() { + if (! preg_match('/([^\/]*?)$/', $this->getPath(), $matches)) { + return false; + } + return $matches[1]; + } + + /** + * Gets the path to the page. + * @return string Path less the page. + * @access public + */ + function getBasePath() { + if (! preg_match('/(.*\/)[^\/]*?$/', $this->getPath(), $matches)) { + return false; + } + return $matches[1]; + } + + /** + * Accessor for fragment at end of URL after the "#". + * @return string Part after "#". + * @access public + */ + function getFragment() { + return $this->_fragment; + } + + /** + * Sets image coordinates. Set to false to clear + * them. + * @param integer $x Horizontal position. + * @param integer $y Vertical position. + * @access public + */ + function setCoordinates($x = false, $y = false) { + if (($x === false) || ($y === false)) { + $this->_x = $this->_y = false; + return; + } + $this->_x = (integer)$x; + $this->_y = (integer)$y; + } + + /** + * Accessor for horizontal image coordinate. + * @return integer X value. + * @access public + */ + function getX() { + return $this->_x; + } + + /** + * Accessor for vertical image coordinate. + * @return integer Y value. + * @access public + */ + function getY() { + return $this->_y; + } + + /** + * Accessor for current request parameters + * in URL string form. Will return teh original request + * if at all possible even if it doesn't make much + * sense. + * @return string Form is string "?a=1&b=2", etc. + * @access public + */ + function getEncodedRequest() { + if ($this->_raw) { + $encoded = $this->_raw; + } else { + $encoded = $this->_request->asUrlRequest(); + } + if ($encoded) { + return '?' . preg_replace('/^\?/', '', $encoded); + } + return ''; + } + + /** + * Adds an additional parameter to the request. + * @param string $key Name of parameter. + * @param string $value Value as string. + * @access public + */ + function addRequestParameter($key, $value) { + $this->_raw = false; + $this->_request->add($key, $value); + } + + /** + * Adds additional parameters to the request. + * @param hash/SimpleFormEncoding $parameters Additional + * parameters. + * @access public + */ + function addRequestParameters($parameters) { + $this->_raw = false; + $this->_request->merge($parameters); + } + + /** + * Clears down all parameters. + * @access public + */ + function clearRequest() { + $this->_raw = false; + $this->_request = &new SimpleGetEncoding(); + } + + /** + * Gets the frame target if present. Although + * not strictly part of the URL specification it + * acts as similarily to the browser. + * @return boolean/string Frame name or false if none. + * @access public + */ + function getTarget() { + return $this->_target; + } + + /** + * Attaches a frame target. + * @param string $frame Name of frame. + * @access public + */ + function setTarget($frame) { + $this->_raw = false; + $this->_target = $frame; + } + + /** + * Renders the URL back into a string. + * @return string URL in canonical form. + * @access public + */ + function asString() { + $path = $this->_path; + $scheme = $identity = $host = $encoded = $fragment = ''; + if ($this->_username && $this->_password) { + $identity = $this->_username . ':' . $this->_password . '@'; + } + if ($this->getHost()) { + $scheme = $this->getScheme() ? $this->getScheme() : 'http'; + $scheme .= "://"; + $host = $this->getHost(); + } + if (substr($this->_path, 0, 1) == '/') { + $path = $this->normalisePath($this->_path); + } + $encoded = $this->getEncodedRequest(); + $fragment = $this->getFragment() ? '#'. $this->getFragment() : ''; + $coords = $this->getX() === false ? '' : '?' . $this->getX() . ',' . $this->getY(); + return "$scheme$identity$host$path$encoded$fragment$coords"; + } + + /** + * Replaces unknown sections to turn a relative + * URL into an absolute one. The base URL can + * be either a string or a SimpleUrl object. + * @param string/SimpleUrl $base Base URL. + * @access public + */ + function makeAbsolute($base) { + if (! is_object($base)) { + $base = new SimpleUrl($base); + } + if ($this->getHost()) { + $scheme = $this->getScheme(); + $host = $this->getHost(); + $port = $this->getPort() ? ':' . $this->getPort() : ''; + $identity = $this->getIdentity() ? $this->getIdentity() . '@' : ''; + if (! $identity) { + $identity = $base->getIdentity() ? $base->getIdentity() . '@' : ''; + } + } else { + $scheme = $base->getScheme(); + $host = $base->getHost(); + $port = $base->getPort() ? ':' . $base->getPort() : ''; + $identity = $base->getIdentity() ? $base->getIdentity() . '@' : ''; + } + $path = $this->normalisePath($this->_extractAbsolutePath($base)); + $encoded = $this->getEncodedRequest(); + $fragment = $this->getFragment() ? '#'. $this->getFragment() : ''; + $coords = $this->getX() === false ? '' : '?' . $this->getX() . ',' . $this->getY(); + return new SimpleUrl("$scheme://$identity$host$port$path$encoded$fragment$coords"); + } + + /** + * Replaces unknown sections of the path with base parts + * to return a complete absolute one. + * @param string/SimpleUrl $base Base URL. + * @param string Absolute path. + * @access private + */ + function _extractAbsolutePath($base) { + if ($this->getHost()) { + return $this->_path; + } + if (! $this->_isRelativePath($this->_path)) { + return $this->_path; + } + if ($this->_path) { + return $base->getBasePath() . $this->_path; + } + return $base->getPath(); + } + + /** + * Simple test to see if a path part is relative. + * @param string $path Path to test. + * @return boolean True if starts with a "/". + * @access private + */ + function _isRelativePath($path) { + return (substr($path, 0, 1) != '/'); + } + + /** + * Extracts the username and password for use in rendering + * a URL. + * @return string/boolean Form of username:password or false. + * @access public + */ + function getIdentity() { + if ($this->_username && $this->_password) { + return $this->_username . ':' . $this->_password; + } + return false; + } + + /** + * Replaces . and .. sections of the path. + * @param string $path Unoptimised path. + * @return string Path with dots removed if possible. + * @access public + */ + function normalisePath($path) { + $path = preg_replace('|/\./|', '/', $path); + return preg_replace('|/[^/]+/\.\./|', '/', $path); + } + + /** + * A pipe seperated list of all TLDs that result in two part + * domain names. + * @return string Pipe separated list. + * @access public + * @static + */ + function getAllTopLevelDomains() { + return 'com|edu|net|org|gov|mil|int|biz|info|name|pro|aero|coop|museum'; + } +} +?> \ No newline at end of file diff --git a/thirdparty/spyc/README b/thirdparty/spyc/README new file mode 100644 index 000000000..0fbbea7c8 --- /dev/null +++ b/thirdparty/spyc/README @@ -0,0 +1,162 @@ +# +# S P Y C +# a simple php yaml class +# v0.2(.5) +# +# Load this README! +# >> $readme = Spyc::YAMLLoad('README'); +# +--- %YAML:1.1 +title: Spyc -- a Simple PHP YAML Class +version: 0.2.5 +author: [chris wanstrath, chris@ozmm.org] +websites: [http://www.yaml.org, http://spyc.sourceforge.net] +license: [MIT License, http://www.opensource.org/licenses/mit-license.php] +copyright: (c) 2005-2006 Chris Wanstrath +tested on: [php 4.3.11, php 5.0.4] + +installation: > + Copy spyc.php to a directory you can + access with your YAML-ready PHP script. + + That's it! + +about: > + From www.yaml.org: + + "YAML(tm) (rhymes with 'camel') is a human-friendly, cross language, + Unicode based data serialization language designed around the common + native data structures of agile programming languages. It is broadly + useful for programming needs ranging from configuration files to + Internet messaging to object persistence to data auditing. Together + with the Unicode standard for characters, the YAML specification provides + all the information necessary to understand YAML Version 1.1 and to + creating programs that process YAML information. + + YAML(tm) is a balance of the following design goals: + - YAML documents are very readable by humans. + - YAML interacts well with scripting languages. + - YAML uses host languages' native data structures. + - YAML has a consistent information model. + - YAML enables stream-based processing. + - YAML is expressive and extensible. + - YAML is easy to implement." + + YAML makes a lot of sense. It's easy to use, easy to learn, and cool. + As the lucky stiff named why once said, "YAML is a beacon of light." + + If you're new to YAML, may we suggest YAML In Five Minutes: + - http://yaml.kwiki.org/?YamlInFiveMinutes + + If you don't have five minutes, realize that this README is a completely + valid YAML document. Dig in, load this or any YAML file into an array + with Spyc and see how easy it is to translate friendly text into usable + data. + + The purpose of Spyc is to provide a pure PHP alternative to Syck, a + simple API for loading and dumping YAML documents, a YAML loader which + understands a usable subset of the YAML spec, and to further spread + the glory of YAML to the PHP masses. + + If you're at all hesitant ("usable subset of YAML?!"), navigate + http://yaml.org/start.html. Spyc completely understands the YAML + document shown there, a document which has features way beyond the + scope of what normal config files might require. Try it for yourself, + and then start enjoying the peace of mind YAML brings to your life. + +meat and a few potatoes: + - concept: Loading a YAML document into PHP + brief: > + $yaml will become an array of all the data in wicked.yaml + code: | + + include('spyc.php'); + + $yaml = Spyc::YAMLLoad('wicked.yaml'); + + - concept: Loading a YAML string into PHP + brief: > + $array will look like this: + array('A YAML','document in a','string') + code: | + + include('spyc.php'); + + $yaml = '- A YAML\n- document in a\n- string.'; + $array = Spyc::YAMLLoad($yaml); + + - concept: Dumping a PHP array to YAML + brief: > + $yaml will become a string of a YAML document created from + $array. + code: | + + include('spyc.php'); + + $array['name'] = 'chris'; + $array['sport'] = 'curbing'; + + $yaml = Spyc::YAMLDump($array); + +prior art: + - who: [Brian Ingerson, Clark Evans, Oren Ben-Kiki] + why?: > + The YAML spec is really a piece of work, and these guys + did a great job on it. A simple and elegant language like + YAML was a long time coming and it's refreshing to know + such able minded individuals took the task to heart and + executed it with cunning and strength. In addition to + their various noteworthy contributions to YAML parsers + and related projects, YAML.pm's README is a treasure trove + of information for knowledge seekers. Thanks, guys. + + - who: why the lucky stiff + why?: > + As the author of Syck, the code used in Ruby for the language's + YAML class and methods, why is indirectly (directly?) responsible + for my first exposure to YAML (as a config file in a Ruby web-app) + and the countless hours I spent playing with this sheik new data + format afterwards. Syck's README is a YAML file and thus the + inspiration for this file and, even, this very piece of software. + + - who: Steve Howell + why?: > + Python's YAML implementation. PyYAML's README file is also YAML, + so it too inspired the YAML format of this README file. + + - who: [Rasmus Lerdorf, Zeev Suraski, Andi Gutmans, et al] + why?: > + PHP is great at what it does best. It's also paid a lot of my bills. + Thanks. + +bugs: + report: > + Please see Spyc's Sourceforge project page for information on reporting bugs. + speed: > + This implementation was not designed for speed. Rather, it + was designed for those who need a pure PHP implementation of + a YAML parser and who are not overly concerned with performance. + If you want speed, check out Syck. + depth: > + This parser is by no means a comprehensive YAML parser. For supported + features and future plans, check the website. + unicode: > + YAML is supposed to be unicode, but for now we're just using ASCII. + PHP has crappy unicode support but who knows what the future holds. + +resources: + - http://www.yaml.org + - http://www.yaml.org/spec/ + - http://yaml.kwiki.org/?YamlInFiveMinutes + - http://www.whytheluckystiff.net/syck/ + - http://yaml4r.sourceforge.net/cookbook/ + - http://www.sourceforge.net/projects/spyc/ + - http://spyc.sourceforge.net/ + +thanks: + - Adam Wood + - Daniel Ferreira + - Aaron Jensen + - Mike Thornton + - Fabien Potencier + - Mustafa Kumas \ No newline at end of file diff --git a/thirdparty/spyc/Spyc.php b/thirdparty/spyc/Spyc.php new file mode 100644 index 000000000..04d399076 --- /dev/null +++ b/thirdparty/spyc/Spyc.php @@ -0,0 +1,868 @@ + + * @author Vlad Andersen + * @link http://spyc.sourceforge.net/ + * @copyright Copyright 2005-2006 Chris Wanstrath + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +/** + * A node, used by Spyc for parsing YAML. + * @package sapphire + * @subpackage misc + */ +class YAMLNode { + /**#@+ + * @access public + * @var string + */ + public $parent; + public $id; + /**#@-*/ + /** + * @access public + * @var mixed + */ + public $data; + /** + * @access public + * @var int + */ + public $indent; + /** + * @access public + * @var bool + */ + public $children = false; + + /** + * The constructor assigns the node a unique ID. + * @access public + * @return void + */ + public function YAMLNode($nodeId) { + $this->id = $nodeId; + } +} + +/** + * The Simple PHP YAML Class. + * + * This class can be used to read a YAML file and convert its contents + * into a PHP array. It currently supports a very limited subsection of + * the YAML spec. + * + * Usage: + * + * $parser = new Spyc; + * $array = $parser->load($file); + * + * @package sapphire + * @subpackage misc + */ +class Spyc { + + /** + * Load YAML into a PHP array statically + * + * The load method, when supplied with a YAML stream (string or file), + * will do its best to convert YAML in a file into a PHP array. Pretty + * simple. + * Usage: + * + * $array = Spyc::YAMLLoad('lucky.yaml'); + * print_r($array); + * + * @access public + * @return array + * @param string $input Path of YAML file or string containing YAML + */ + public static function YAMLLoad($input) { + $spyc = new Spyc; + return $spyc->load($input); + } + + /** + * Dump YAML from PHP array statically + * + * The dump method, when supplied with an array, will do its best + * to convert the array into friendly YAML. Pretty simple. Feel free to + * save the returned string as nothing.yaml and pass it around. + * + * Oh, and you can decide how big the indent is and what the wordwrap + * for folding is. Pretty cool -- just pass in 'false' for either if + * you want to use the default. + * + * Indent's default is 2 spaces, wordwrap's default is 40 characters. And + * you can turn off wordwrap by passing in 0. + * + * @access public + * @return string + * @param array $array PHP array + * @param int $indent Pass in false to use the default, which is 2 + * @param int $wordwrap Pass in 0 for no wordwrap, false for default (40) + */ + public static function YAMLDump($array,$indent = false,$wordwrap = false) { + $spyc = new Spyc; + return $spyc->dump($array,$indent,$wordwrap); + } + + /** + * Load YAML into a PHP array from an instantiated object + * + * The load method, when supplied with a YAML stream (string or file path), + * will do its best to convert the YAML into a PHP array. Pretty simple. + * Usage: + * + * $parser = new Spyc; + * $array = $parser->load('lucky.yaml'); + * print_r($array); + * + * @access public + * @return array + * @param string $input Path of YAML file or string containing YAML + */ + public function load($input) { + // See what type of input we're talking about + // If it's not a file, assume it's a string + if (!empty($input) && (strpos($input, "\n") === false) + && file_exists($input)) { + $yaml = file($input); + } else { + $yaml = explode("\n",$input); + } + // Initiate some objects and values + $base = new YAMLNode (1); + $base->indent = 0; + $this->_lastIndent = 0; + $this->_lastNode = $base->id; + $this->_inBlock = false; + $this->_isInline = false; + $this->_nodeId = 2; + + foreach ($yaml as $linenum => $line) { + $ifchk = trim($line); + + // If the line starts with a tab (instead of a space), throw a fit. + if (preg_match('/^(\t)+(\w+)/', $line)) { + $err = 'ERROR: Line '. ($linenum + 1) .' in your input YAML begins'. + ' with a tab. YAML only recognizes spaces. Please reformat.'; + die($err); + } + + if ($this->_inBlock === false && empty($ifchk)) { + continue; + } elseif ($this->_inBlock == true && empty($ifchk)) { + $last =& $this->_allNodes[$this->_lastNode]; + $last->data[key($last->data)] .= "\n"; + } elseif ($ifchk{0} != '#' && substr($ifchk,0,3) != '---') { + // Create a new node and get its indent + $node = new YAMLNode ($this->_nodeId); + $this->_nodeId++; + $node->indent = $this->_getIndent($line); + + // Check where the node lies in the hierarchy + if ($this->_lastIndent == $node->indent) { + // If we're in a block, add the text to the parent's data + if ($this->_inBlock === true) { + $parent =& $this->_allNodes[$this->_lastNode]; + $parent->data[key($parent->data)] .= trim($line).$this->_blockEnd; + } else { + // The current node's parent is the same as the previous node's + if (isset($this->_allNodes[$this->_lastNode])) { + $node->parent = $this->_allNodes[$this->_lastNode]->parent; + } + } + } elseif ($this->_lastIndent < $node->indent) { + if ($this->_inBlock === true) { + $parent =& $this->_allNodes[$this->_lastNode]; + $parent->data[key($parent->data)] .= trim($line).$this->_blockEnd; + } elseif ($this->_inBlock === false) { + // The current node's parent is the previous node + $node->parent = $this->_lastNode; + + // If the value of the last node's data was > or | we need to + // start blocking i.e. taking in all lines as a text value until + // we drop our indent. + $parent =& $this->_allNodes[$node->parent]; + $this->_allNodes[$node->parent]->children = true; + if (is_array($parent->data)) { + // if (isset ($parent->data[key($parent->data)])) + $chk = $parent->data[key($parent->data)]; + if ($chk === '>') { + $this->_inBlock = true; + $this->_blockEnd = ' '; + $parent->data[key($parent->data)] = + str_replace('>','',$parent->data[key($parent->data)]); + $parent->data[key($parent->data)] .= trim($line).' '; + $this->_allNodes[$node->parent]->children = false; + $this->_lastIndent = $node->indent; + } elseif ($chk === '|') { + $this->_inBlock = true; + $this->_blockEnd = "\n"; + $parent->data[key($parent->data)] = + str_replace('|','',$parent->data[key($parent->data)]); + $parent->data[key($parent->data)] .= trim($line)."\n"; + $this->_allNodes[$node->parent]->children = false; + $this->_lastIndent = $node->indent; + } + } + } + } elseif ($this->_lastIndent > $node->indent) { + // Any block we had going is dead now + if ($this->_inBlock === true) { + $this->_inBlock = false; + if ($this->_blockEnd = "\n") { + $last =& $this->_allNodes[$this->_lastNode]; + $last->data[key($last->data)] = + trim($last->data[key($last->data)]); + } + } + + // We don't know the parent of the node so we have to find it + // foreach ($this->_allNodes as $n) { + foreach ($this->_indentSort[$node->indent] as $n) { + if ($n->indent == $node->indent) { + $node->parent = $n->parent; + } + } + } + + if ($this->_inBlock === false) { + // Set these properties with information from our current node + $this->_lastIndent = $node->indent; + // Set the last node + $this->_lastNode = $node->id; + // Parse the YAML line and return its data + $node->data = $this->_parseLine($line); + // Add the node to the master list + $this->_allNodes[$node->id] = $node; + // Add a reference to the parent list + $this->_allParent[intval($node->parent)][] = $node->id; + // Add a reference to the node in an indent array + $this->_indentSort[$node->indent][] =& $this->_allNodes[$node->id]; + // Add a reference to the node in a References array if this node + // has a YAML reference in it. + if ( + ( (is_array($node->data)) && + isset($node->data[key($node->data)]) && + (!is_array($node->data[key($node->data)])) ) + && + ( (preg_match('/^&([^ ]+)/',$node->data[key($node->data)])) + || + (preg_match('/^\*([^ ]+)/',$node->data[key($node->data)])) ) + ) { + $this->_haveRefs[] =& $this->_allNodes[$node->id]; + } elseif ( + ( (is_array($node->data)) && + isset($node->data[key($node->data)]) && + (is_array($node->data[key($node->data)])) ) + ) { + // Incomplete reference making code. Ugly, needs cleaned up. + foreach ($node->data[key($node->data)] as $d) { + if ( !is_array($d) && + ( (preg_match('/^&([^ ]+)/',$d)) + || + (preg_match('/^\*([^ ]+)/',$d)) ) + ) { + $this->_haveRefs[] =& $this->_allNodes[$node->id]; + } + } + } + } + } + } + unset($node); + + // Here we travel through node-space and pick out references (& and *) + $this->_linkReferences(); + + // Build the PHP array out of node-space + $trunk = $this->_buildArray(); + return $trunk; + } + + /** + * Dump PHP array to YAML + * + * The dump method, when supplied with an array, will do its best + * to convert the array into friendly YAML. Pretty simple. Feel free to + * save the returned string as tasteful.yaml and pass it around. + * + * Oh, and you can decide how big the indent is and what the wordwrap + * for folding is. Pretty cool -- just pass in 'false' for either if + * you want to use the default. + * + * Indent's default is 2 spaces, wordwrap's default is 40 characters. And + * you can turn off wordwrap by passing in 0. + * + * @access public + * @return string + * @param array $array PHP array + * @param int $indent Pass in false to use the default, which is 2 + * @param int $wordwrap Pass in 0 for no wordwrap, false for default (40) + */ + public function dump($array,$indent = false,$wordwrap = false) { + // Dumps to some very clean YAML. We'll have to add some more features + // and options soon. And better support for folding. + + // New features and options. + if ($indent === false or !is_numeric($indent)) { + $this->_dumpIndent = 2; + } else { + $this->_dumpIndent = $indent; + } + + if ($wordwrap === false or !is_numeric($wordwrap)) { + $this->_dumpWordWrap = 40; + } else { + $this->_dumpWordWrap = $wordwrap; + } + + // New YAML document + $string = "---\n"; + + // Start at the base of the array and move through it. + foreach ($array as $key => $value) { + $string .= $this->_yamlize($key,$value,0); + } + return $string; + } + + /**** Private Properties ****/ + + /**#@+ + * @access private + * @var mixed + */ + private $_haveRefs; + private $_allNodes; + private $_allParent; + private $_lastIndent; + private $_lastNode; + private $_inBlock; + private $_isInline; + private $_dumpIndent; + private $_dumpWordWrap; + /**#@-*/ + + /**** Public Properties ****/ + + /**#@+ + * @access public + * @var mixed + */ + public $_nodeId; + /**#@-*/ + + /**** Private Methods ****/ + + /** + * Attempts to convert a key / value array item to YAML + * @access private + * @return string + * @param $key The name of the key + * @param $value The value of the item + * @param $indent The indent of the current node + */ + private function _yamlize($key,$value,$indent) { + if (is_array($value)) { + // It has children. What to do? + // Make it the right kind of item + $string = $this->_dumpNode($key,NULL,$indent); + // Add the indent + $indent += $this->_dumpIndent; + // Yamlize the array + $string .= $this->_yamlizeArray($value,$indent); + } elseif (!is_array($value)) { + // It doesn't have children. Yip. + $string = $this->_dumpNode($key,$value,$indent); + } + return $string; + } + + /** + * Attempts to convert an array to YAML + * @access private + * @return string + * @param $array The array you want to convert + * @param $indent The indent of the current level + */ + private function _yamlizeArray($array,$indent) { + if (is_array($array)) { + $string = ''; + foreach ($array as $key => $value) { + $string .= $this->_yamlize($key,$value,$indent); + } + return $string; + } else { + return false; + } + } + + /** + * Returns YAML from a key and a value + * @access private + * @return string + * @param $key The name of the key + * @param $value The value of the item + * @param $indent The indent of the current node + */ + private function _dumpNode($key,$value,$indent) { + // do some folding here, for blocks + if (strpos($value,"\n") !== false || strpos($value,": ") !== false || strpos($value,"- ") !== false) { + $value = $this->_doLiteralBlock($value,$indent); + } else { + $value = $this->_doFolding($value,$indent); + } + + if (is_bool($value)) { + $value = ($value) ? "true" : "false"; + } + + $spaces = str_repeat(' ',$indent); + + if (is_int($key)) { + // It's a sequence + $string = $spaces.'- '.$value."\n"; + } else { + // It's mapped + $string = $spaces.$key.': '.$value."\n"; + } + return $string; + } + + /** + * Creates a literal block for dumping + * @access private + * @return string + * @param $value + * @param $indent int The value of the indent + */ + private function _doLiteralBlock($value,$indent) { + $exploded = explode("\n",$value); + $newValue = '|'; + $indent += $this->_dumpIndent; + $spaces = str_repeat(' ',$indent); + foreach ($exploded as $line) { + $newValue .= "\n" . $spaces . trim($line); + } + return $newValue; + } + + /** + * Folds a string of text, if necessary + * @access private + * @return string + * @param $value The string you wish to fold + */ + private function _doFolding($value,$indent) { + // Don't do anything if wordwrap is set to 0 + if ($this->_dumpWordWrap === 0) { + return $value; + } + + if (strlen($value) > $this->_dumpWordWrap) { + $indent += $this->_dumpIndent; + $indent = str_repeat(' ',$indent); + $wrapped = wordwrap($value,$this->_dumpWordWrap,"\n$indent"); + $value = ">\n".$indent.$wrapped; + } + return $value; + } + + /* Methods used in loading */ + + /** + * Finds and returns the indentation of a YAML line + * @access private + * @return int + * @param string $line A line from the YAML file + */ + private function _getIndent($line) { + preg_match('/^\s{1,}/',$line,$match); + if (!empty($match[0])) { + $indent = substr_count($match[0],' '); + } else { + $indent = 0; + } + return $indent; + } + + /** + * Parses YAML code and returns an array for a node + * @access private + * @return array + * @param string $line A line from the YAML file + */ + private function _parseLine($line) { + $line = trim($line); + + $array = array(); + + if (preg_match('/^-(.*):$/',$line)) { + // It's a mapped sequence + $key = trim(substr(substr($line,1),0,-1)); + $array[$key] = ''; + } elseif ($line[0] == '-' && substr($line,0,3) != '---') { + // It's a list item but not a new stream + if (strlen($line) > 1) { + $value = trim(substr($line,1)); + // Set the type of the value. Int, string, etc + $value = $this->_toType($value); + $array[] = $value; + } else { + $array[] = array(); + } + } elseif (preg_match('/^(.+):/',$line,$key)) { + // It's a key/value pair most likely + // If the key is in double quotes pull it out + if (preg_match('/^(["\'](.*)["\'](\s)*:)/',$line,$matches)) { + $value = trim(str_replace($matches[1],'',$line)); + $key = $matches[2]; + } else { + // Do some guesswork as to the key and the value + $explode = explode(':',$line); + $key = trim($explode[0]); + array_shift($explode); + $value = trim(implode(':',$explode)); + } + + // Set the type of the value. Int, string, etc + $value = $this->_toType($value); + if (empty($key)) { + $array[] = $value; + } else { + $array[$key] = $value; + } + } + return $array; + } + + /** + * Finds the type of the passed value, returns the value as the new type. + * @access private + * @param string $value + * @return mixed + */ + private function _toType($value) { + if (preg_match('/^("(.*)"|\'(.*)\')/',$value,$matches)) { + $value = (string)preg_replace('/(\'\'|\\\\\')/',"'",end($matches)); + $value = preg_replace('/\\\\"/','"',$value); + } elseif (preg_match('/^\\[(.+)\\]$/',$value,$matches)) { + // Inline Sequence + + // Take out strings sequences and mappings + $explode = $this->_inlineEscape($matches[1]); + + // Propogate value array + $value = array(); + foreach ($explode as $v) { + $value[] = $this->_toType($v); + } + } elseif (strpos($value,': ')!==false && !preg_match('/^{(.+)/',$value)) { + // It's a map + $array = explode(': ',$value); + $key = trim($array[0]); + array_shift($array); + $value = trim(implode(': ',$array)); + $value = $this->_toType($value); + $value = array($key => $value); + } elseif (preg_match("/{(.+)}$/",$value,$matches)) { + // Inline Mapping + + // Take out strings sequences and mappings + $explode = $this->_inlineEscape($matches[1]); + + // Propogate value array + $array = array(); + foreach ($explode as $v) { + $array = $array + $this->_toType($v); + } + $value = $array; + } elseif (strtolower($value) == 'null' or $value == '' or $value == '~') { + $value = NULL; + } elseif (preg_match ('/^[0-9]+$/', $value)) { + $value = (int)$value; + } elseif (in_array(strtolower($value), + array('true', 'on', '+', 'yes', 'y'))) { + $value = TRUE; + } elseif (in_array(strtolower($value), + array('false', 'off', '-', 'no', 'n'))) { + $value = FALSE; + } elseif (is_numeric($value)) { + $value = (float)$value; + } else { + // Just a normal string, right? + $value = trim(preg_replace('/#(.+)$/','',$value)); + } + + return $value; + } + + /** + * Used in inlines to check for more inlines or quoted strings + * @access private + * @return array + */ + private function _inlineEscape($inline) { + // There's gotta be a cleaner way to do this... + // While pure sequences seem to be nesting just fine, + // pure mappings and mappings with sequences inside can't go very + // deep. This needs to be fixed. + + $saved_strings = array(); + + // Check for strings + $regex = '/(?:(")|(?:\'))((?(1)[^"]+|[^\']+))(?(1)"|\')/'; + if (preg_match_all($regex,$inline,$strings)) { + $saved_strings = $strings[0]; + $inline = preg_replace($regex,'YAMLString',$inline); + } + unset($regex); + + // Check for sequences + if (preg_match_all('/\[(.+)\]/U',$inline,$seqs)) { + $inline = preg_replace('/\[(.+)\]/U','YAMLSeq',$inline); + $seqs = $seqs[0]; + } + + // Check for mappings + if (preg_match_all('/{(.+)}/U',$inline,$maps)) { + $inline = preg_replace('/{(.+)}/U','YAMLMap',$inline); + $maps = $maps[0]; + } + + $explode = explode(', ',$inline); + + + // Re-add the sequences + if (!empty($seqs)) { + $i = 0; + foreach ($explode as $key => $value) { + if (strpos($value,'YAMLSeq') !== false) { + $explode[$key] = str_replace('YAMLSeq',$seqs[$i],$value); + ++$i; + } + } + } + + // Re-add the mappings + if (!empty($maps)) { + $i = 0; + foreach ($explode as $key => $value) { + if (strpos($value,'YAMLMap') !== false) { + $explode[$key] = str_replace('YAMLMap',$maps[$i],$value); + ++$i; + } + } + } + + + // Re-add the strings + if (!empty($saved_strings)) { + $i = 0; + foreach ($explode as $key => $value) { + while (strpos($value,'YAMLString') !== false) { + $explode[$key] = preg_replace('/YAMLString/',$saved_strings[$i],$value, 1); + ++$i; + $value = $explode[$key]; + } + } + } + + return $explode; + } + + /** + * Builds the PHP array from all the YAML nodes we've gathered + * @access private + * @return array + */ + private function _buildArray() { + $trunk = array(); + + if (!isset($this->_indentSort[0])) { + return $trunk; + } + + foreach ($this->_indentSort[0] as $n) { + if (empty($n->parent)) { + $this->_nodeArrayizeData($n); + // Check for references and copy the needed data to complete them. + $this->_makeReferences($n); + // Merge our data with the big array we're building + $trunk = $this->_array_kmerge($trunk,$n->data); + } + } + + return $trunk; + } + + /** + * Traverses node-space and sets references (& and *) accordingly + * @access private + * @return bool + */ + private function _linkReferences() { + if (is_array($this->_haveRefs)) { + foreach ($this->_haveRefs as $node) { + if (!empty($node->data)) { + $key = key($node->data); + // If it's an array, don't check. + if (is_array($node->data[$key])) { + foreach ($node->data[$key] as $k => $v) { + $this->_linkRef($node,$key,$k,$v); + } + } else { + $this->_linkRef($node,$key); + } + } + } + } + return true; + } + + function _linkRef(&$n,$key,$k = NULL,$v = NULL) { + if (empty($k) && empty($v)) { + // Look for &refs + if (preg_match('/^&([^ ]+)/',$n->data[$key],$matches)) { + // Flag the node so we know it's a reference + $this->_allNodes[$n->id]->ref = substr($matches[0],1); + $this->_allNodes[$n->id]->data[$key] = + substr($n->data[$key],strlen($matches[0])+1); + // Look for *refs + } elseif (preg_match('/^\*([^ ]+)/',$n->data[$key],$matches)) { + $ref = substr($matches[0],1); + // Flag the node as having a reference + $this->_allNodes[$n->id]->refKey = $ref; + } + } elseif (!empty($k) && !empty($v)) { + if (preg_match('/^&([^ ]+)/',$v,$matches)) { + // Flag the node so we know it's a reference + $this->_allNodes[$n->id]->ref = substr($matches[0],1); + $this->_allNodes[$n->id]->data[$key][$k] = + substr($v,strlen($matches[0])+1); + // Look for *refs + } elseif (preg_match('/^\*([^ ]+)/',$v,$matches)) { + $ref = substr($matches[0],1); + // Flag the node as having a reference + $this->_allNodes[$n->id]->refKey = $ref; + } + } + } + + /** + * Finds the children of a node and aids in the building of the PHP array + * @access private + * @param int $nid The id of the node whose children we're gathering + * @return array + */ + private function _gatherChildren($nid) { + $return = array(); + $node =& $this->_allNodes[$nid]; + if (is_array ($this->_allParent[$node->id])) { + foreach ($this->_allParent[$node->id] as $nodeZ) { + $z =& $this->_allNodes[$nodeZ]; + // We found a child + $this->_nodeArrayizeData($z); + // Check for references + $this->_makeReferences($z); + // Merge with the big array we're returning + // The big array being all the data of the children of our parent node + $return = $this->_array_kmerge($return,$z->data); + } + } + return $return; + } + + /** + * Turns a node's data and its children's data into a PHP array + * + * @access private + * @param array $node The node which you want to arrayize + * @return boolean + */ + private function _nodeArrayizeData(&$node) { + if (is_array($node->data) && $node->children == true) { + // This node has children, so we need to find them + $childs = $this->_gatherChildren($node->id); + // We've gathered all our children's data and are ready to use it + $key = key($node->data); + $key = empty($key) ? 0 : $key; + // If it's an array, add to it of course + if (isset ($node->data[$key])) { + if (is_array($node->data[$key])) { + $node->data[$key] = $this->_array_kmerge($node->data[$key],$childs); + } else { + $node->data[$key] = $childs; + } + } else { + $node->data[$key] = $childs; + } + } elseif (!is_array($node->data) && $node->children == true) { + // Same as above, find the children of this node + $childs = $this->_gatherChildren($node->id); + $node->data = array(); + $node->data[] = $childs; + } + + // We edited $node by reference, so just return true + return true; + } + + /** + * Traverses node-space and copies references to / from this object. + * @access private + * @param object $z A node whose references we wish to make real + * @return bool + */ + private function _makeReferences(&$z) { + // It is a reference + if (isset($z->ref)) { + $key = key($z->data); + // Copy the data to this object for easy retrieval later + $this->ref[$z->ref] =& $z->data[$key]; + // It has a reference + } elseif (isset($z->refKey)) { + if (isset($this->ref[$z->refKey])) { + $key = key($z->data); + // Copy the data from this object to make the node a real reference + $z->data[$key] =& $this->ref[$z->refKey]; + } + } + return true; + } + + + /** + * Merges arrays and maintains numeric keys. + * + * An ever-so-slightly modified version of the array_kmerge() function posted + * to php.net by mail at nospam dot iaindooley dot com on 2004-04-08. + * + * http://us3.php.net/manual/en/function.array-merge.php + * + * @access private + * @param array $arr1 + * @param array $arr2 + * @return array + */ + private function _array_kmerge($arr1,$arr2) { + if(!is_array($arr1)) $arr1 = array(); + if(!is_array($arr2)) $arr2 = array(); + + $keys = array_merge(array_keys($arr1),array_keys($arr2)); + $vals = array_merge(array_values($arr1),array_values($arr2)); + $ret = array(); + foreach($keys as $key) { + list($unused,$val) = each($vals); + if (isset($ret[$key]) and is_int($key)) $ret[] = $val; else $ret[$key] = $val; + } + return $ret; + } +} +?>