diff --git a/lib/pear/HTML/AJAX.php b/lib/pear/HTML/AJAX.php
new file mode 100755
index 00000000000..c17520f6ecd
--- /dev/null
+++ b/lib/pear/HTML/AJAX.php
@@ -0,0 +1,1076 @@
+
+ * @author Arpad Ray
+ * @author David Coallier
+ * @author Elizabeth Smith
+ * @copyright 2005-2008 Joshua Eichorn, Arpad Ray, David Coallier, Elizabeth Smith
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @version Release: 0.5.6
+ * @link http://pear.php.net/package/HTML_AJAX
+ */
+
+/**
+ * This is a quick hack, loading serializers as needed doesn't work in php5
+ */
+require_once "HTML/AJAX/Serializer/JSON.php";
+require_once "HTML/AJAX/Serializer/Null.php";
+require_once "HTML/AJAX/Serializer/Error.php";
+require_once "HTML/AJAX/Serializer/XML.php";
+require_once "HTML/AJAX/Serializer/PHP.php";
+require_once 'HTML/AJAX/Debug.php';
+
+/**
+ * OO AJAX Implementation for PHP
+ *
+ * @category HTML
+ * @package AJAX
+ * @author Joshua Eichorn
+ * @author Arpad Ray
+ * @author David Coallier
+ * @author Elizabeth Smith
+ * @copyright 2005-2008 Joshua Eichorn, Arpad Ray, David Coallier, Elizabeth Smith
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @version Release: 0.5.6
+ * @link http://pear.php.net/package/HTML_AJAX
+ */
+class HTML_AJAX
+{
+ /**
+ * An array holding the instances were exporting
+ *
+ * key is the exported name
+ *
+ * row format is
+ *
+ * array('className'=>'','exportedName'=>'','instance'=>'','exportedMethods=>'')
+ *
+ *
+ * @var object
+ * @access private
+ */
+ var $_exportedInstances = array();
+
+ /**
+ * Set the server url in the generated stubs to this value
+ * If set to false, serverUrl will not be set
+ * @var false|string
+ */
+ var $serverUrl = false;
+
+ /**
+ * What encoding your going to use for serializing data
+ * from php being sent to javascript.
+ *
+ * @var string JSON|PHP|Null
+ */
+ var $serializer = 'JSON';
+
+ /**
+ * What encoding your going to use for unserializing data sent from javascript
+ * @var string JSON|PHP|Null
+ */
+ var $unserializer = 'JSON';
+
+ /**
+ * Option to use loose typing for JSON encoding
+ * @var bool
+ * @access public
+ */
+ var $jsonLooseType = true;
+
+ /**
+ * Content-type map
+ *
+ * Used in to automatically choose serializers as needed
+ */
+ var $contentTypeMap = array(
+ 'JSON' => 'application/json',
+ 'XML' => 'application/xml',
+ 'Null' => 'text/plain',
+ 'Error' => 'application/error',
+ 'PHP' => 'application/php-serialized',
+ 'Urlencoded' => 'application/x-www-form-urlencoded'
+ );
+
+ /**
+ * This is the debug variable that we will be passing the
+ * HTML_AJAX_Debug instance to.
+ *
+ * @param object HTML_AJAX_Debug
+ */
+ var $debug;
+
+ /**
+ * This is to tell if debug is enabled or not. If so, then
+ * debug is called, instantiated then saves the file and such.
+ */
+ var $debugEnabled = false;
+
+ /**
+ * This puts the error into a session variable is set to true.
+ * set to false by default.
+ *
+ * @access public
+ */
+ var $debugSession = false;
+
+ /**
+ * Boolean telling if the Content-Length header should be sent.
+ *
+ * If your using a gzip handler on an output buffer, or run into
+ * any compatability problems, try disabling this.
+ *
+ * @access public
+ * @var boolean
+ */
+ var $sendContentLength = true;
+
+ /**
+ * Make Generated code compatible with php4 by lowercasing all
+ * class/method names before exporting to JavaScript.
+ *
+ * If you have code that works on php4 but not on php5 then setting
+ * this flag can fix the problem. The recommended solution is too
+ * specify the class and method names when registering the class
+ * letting you have function case in php4 as well
+ *
+ * @access public
+ * @var boolean
+ */
+ var $php4CompatCase = false;
+
+ /**
+ * Automatically pack all generated JavaScript making it smaller
+ *
+ * If your using output compression this might not make sense
+ */
+ var $packJavaScript = false;
+
+ /**
+ * Holds current payload info
+ *
+ * @access private
+ * @var string
+ */
+ var $_payload;
+
+ /**
+ * Holds iframe id IF this is an iframe xmlhttprequest
+ *
+ * @access private
+ * @var string
+ */
+ var $_iframe;
+
+ /**
+ * Holds the list of classes permitted to be unserialized
+ *
+ * @access private
+ * @var array
+ */
+ var $_allowedClasses = array();
+
+ /**
+ * Holds serializer instances
+ */
+ var $_serializers = array();
+
+ /**
+ * PHP callbacks we're exporting
+ */
+ var $_validCallbacks = array();
+
+ /**
+ * Interceptor instance
+ */
+ var $_interceptor = false;
+
+ /**
+ * Set a class to handle requests
+ *
+ * @param object &$instance An instance to export
+ * @param mixed $exportedName Name used for the javascript class,
+ * if false the name of the php class is used
+ * @param mixed $exportedMethods If false all functions without a _ prefix
+ * are exported, if an array only the methods
+ * listed in the array are exported
+ *
+ * @return void
+ */
+ function registerClass(&$instance, $exportedName = false,
+ $exportedMethods = false)
+ {
+ $className = strtolower(get_class($instance));
+
+ if ($exportedName === false) {
+ $exportedName = get_class($instance);
+ if ($this->php4CompatCase) {
+ $exportedName = strtolower($exportedName);
+ }
+ }
+
+ if ($exportedMethods === false) {
+ $exportedMethods = $this->_getMethodsToExport($className);
+ }
+
+
+ $index = strtolower($exportedName);
+ $this->_exportedInstances[$index] = array();
+ $this->_exportedInstances[$index]['className'] = $className;
+ $this->_exportedInstances[$index]['exportedName'] = $exportedName;
+ $this->_exportedInstances[$index]['instance'] =& $instance;
+ $this->_exportedInstances[$index]['exportedMethods'] = $exportedMethods;
+ }
+
+ /**
+ * Get a list of methods in a class to export
+ *
+ * This function uses get_class_methods to get a list of callable methods,
+ * so if you're on PHP5 extending this class with a class you want to export
+ * should export its protected methods, while normally only its public methods
+ * would be exported. All methods starting with _ are removed from the export list.
+ * This covers PHP4 style private by naming as well as magic methods in either PHP4 or PHP5
+ *
+ * @param string $className Name of the class
+ *
+ * @return array all methods of the class that are public
+ * @access private
+ */
+ function _getMethodsToExport($className)
+ {
+ $funcs = get_class_methods($className);
+
+ foreach ($funcs as $key => $func) {
+ if (strtolower($func) === $className || substr($func, 0, 1) === '_') {
+ unset($funcs[$key]);
+ } else if ($this->php4CompatCase) {
+ $funcs[$key] = strtolower($func);
+ }
+ }
+ return $funcs;
+ }
+
+ /**
+ * Generate the client Javascript code
+ *
+ * @return string generated javascript client code
+ */
+ function generateJavaScriptClient()
+ {
+ $client = '';
+
+ $names = array_keys($this->_exportedInstances);
+ foreach ($names as $name) {
+ $client .= $this->generateClassStub($name);
+ }
+ return $client;
+ }
+
+ /**
+ * Return the stub for a class
+ *
+ * @param string $name name of the class to generated the stub for,
+ * note that this is the exported name not the php class name
+ *
+ * @return string javascript proxy stub code for a single class
+ */
+ function generateClassStub($name)
+ {
+ if (!isset($this->_exportedInstances[$name])) {
+ return '';
+ }
+
+ $client = "// Client stub for the {$this->_exportedInstances[$name]['exportedName']} PHP Class\n";
+ $client .= "function {$this->_exportedInstances[$name]['exportedName']}(callback) {\n";
+ $client .= "\tmode = 'sync';\n";
+ $client .= "\tif (callback) { mode = 'async'; }\n";
+ $client .= "\tthis.className = '{$this->_exportedInstances[$name]['exportedName']}';\n";
+ if ($this->serverUrl) {
+ $client .= "\tthis.dispatcher = new HTML_AJAX_Dispatcher(this.className,mode,callback,'{$this->serverUrl}','{$this->unserializer}');\n}\n";
+ } else {
+ $client .= "\tthis.dispatcher = new HTML_AJAX_Dispatcher(this.className,mode,callback,false,'{$this->unserializer}');\n}\n";
+ }
+ $client .= "{$this->_exportedInstances[$name]['exportedName']}.prototype = {\n";
+ $client .= "\tSync: function() { this.dispatcher.Sync(); }, \n";
+ $client .= "\tAsync: function(callback) { this.dispatcher.Async(callback); },\n";
+ foreach ($this->_exportedInstances[$name]['exportedMethods'] as $method) {
+ $client .= $this->_generateMethodStub($method);
+ }
+ $client = substr($client, 0, (strlen($client)-2))."\n";
+ $client .= "}\n\n";
+
+ if ($this->packJavaScript) {
+ $client = $this->packJavaScript($client);
+ }
+ return $client;
+ }
+
+ /**
+ * Returns a methods stub
+ *
+ * @param string $method the method name
+ *
+ * @return string the js code
+ * @access private
+ */
+ function _generateMethodStub($method)
+ {
+ $stub = "\t{$method}: function() { return ".
+ "this.dispatcher.doCall('{$method}',arguments); },\n";
+ return $stub;
+ }
+
+ /**
+ * Populates the current payload
+ *
+ * @return string the js code
+ * @access private
+ */
+ function populatePayload()
+ {
+ if (isset($_REQUEST['Iframe_XHR'])) {
+ $this->_iframe = $_REQUEST['Iframe_XHR_id'];
+ if (isset($_REQUEST['Iframe_XHR_headers']) &&
+ is_array($_REQUEST['Iframe_XHR_headers'])) {
+ foreach ($_REQUEST['Iframe_XHR_headers'] as $header) {
+
+ $array = explode(':', $header);
+ $array[0] = strip_tags(strtoupper(str_replace('-', '_', $array[0])));
+ //only content-length and content-type can go in without an
+ //http_ prefix - security
+ if (strpos($array[0], 'HTTP_') !== 0
+ && strcmp('CONTENT_TYPE', $array[0])
+ && strcmp('CONTENT_LENGTH', $array[0])) {
+ $array[0] = 'HTTP_' . $array[0];
+ }
+ $_SERVER[$array[0]] = strip_tags($array[1]);
+ }
+ }
+ $this->_payload = (isset($_REQUEST['Iframe_XHR_data'])
+ ? $_REQUEST['Iframe_XHR_data'] : '');
+
+ if (isset($_REQUEST['Iframe_XHR_method'])) {
+ $_GET['m'] = $_REQUEST['Iframe_XHR_method'];
+ }
+ if (isset($_REQUEST['Iframe_XHR_class'])) {
+ $_GET['c'] = $_REQUEST['Iframe_XHR_class'];
+ }
+ }
+ }
+
+ /**
+ * Handle a ajax request if needed
+ *
+ * The current check is if GET variables c (class) and m (method) are set,
+ * more options may be available in the future
+ *
+ * @return boolean true if an ajax call was handled, false otherwise
+ */
+ function handleRequest()
+ {
+ set_error_handler(array(&$this,'_errorHandler'));
+ if (function_exists('set_exception_handler')) {
+ set_exception_handler(array(&$this,'_exceptionHandler'));
+ }
+ if (isset($_GET['px'])) {
+ if ($this->_iframeGrabProxy()) {
+ restore_error_handler();
+ if (function_exists('restore_exception_handler')) {
+ restore_exception_handler();
+ }
+ return true;
+ }
+ }
+
+ $class = strtolower($this->_getVar('c'));
+ $method = $this->_getVar('m');
+ $phpCallback = $this->_getVar('cb');
+
+
+ if (!empty($class) && !empty($method)) {
+ if (!isset($this->_exportedInstances[$class])) {
+ // handle error
+ trigger_error('Unknown class: '. $class);
+ }
+ if (!in_array(($this->php4CompatCase ? strtolower($method) : $method),
+ $this->_exportedInstances[$class]['exportedMethods'])) {
+ // handle error
+ trigger_error('Unknown method: ' . $method);
+ }
+ } else if (!empty($phpCallback)) {
+ if (strpos($phpCallback, '.') !== false) {
+ $phpCallback = explode('.', $phpCallback);
+ }
+ if (!$this->_validatePhpCallback($phpCallback)) {
+ restore_error_handler();
+ if (function_exists('restore_exception_handler')) {
+ restore_exception_handler();
+ }
+ return false;
+ }
+ } else {
+ restore_error_handler();
+ if (function_exists('restore_exception_handler')) {
+ restore_exception_handler();
+ }
+ return false;
+ }
+
+ // auto-detect serializer to use from content-type
+ $type = $this->unserializer;
+ $key = array_search($this->_getClientPayloadContentType(),
+ $this->contentTypeMap);
+ if ($key) {
+ $type = $key;
+ }
+ $unserializer = $this->_getSerializer($type);
+
+ $args = $unserializer->unserialize($this->_getClientPayload(), $this->_allowedClasses);
+ if (!is_array($args)) {
+ $args = array($args);
+ }
+
+ if ($this->_interceptor !== false) {
+ $args = $this->_processInterceptor($class, $method, $phpCallback, $args);
+ }
+
+ if (empty($phpCallback)) {
+ $ret = call_user_func_array(array(&$this->_exportedInstances[$class]['instance'], $method), $args);
+ } else {
+ $ret = call_user_func_array($phpCallback, $args);
+ }
+
+ restore_error_handler();
+ $this->_sendResponse($ret);
+ return true;
+ }
+
+ /**
+ * Determines the content type of the client payload
+ *
+ * @return string
+ * a MIME content type
+ */
+ function _getClientPayloadContentType()
+ {
+ //OPERA IS STUPID FIX
+ if (isset($_SERVER['HTTP_X_CONTENT_TYPE'])) {
+ $type = $this->_getServer('HTTP_X_CONTENT_TYPE');
+ $pos = strpos($type, ';');
+
+ return strtolower($pos ? substr($type, 0, $pos) : $type);
+ } else if (isset($_SERVER['CONTENT_TYPE'])) {
+ $type = $this->_getServer('CONTENT_TYPE');
+ $pos = strpos($type, ';');
+
+ return strtolower($pos ? substr($type, 0, $pos) : $type);
+ }
+ return 'text/plain';
+ }
+
+ /**
+ * Send a reponse adding needed headers and serializing content
+ *
+ * Note: this method echo's output as well as setting headers to prevent caching
+ * Iframe Detection: if this has been detected as an iframe response, it has to
+ * be wrapped in different code and headers changed (quite a mess)
+ *
+ * @param mixed $response content to serialize and send
+ *
+ * @access private
+ * @return void
+ */
+ function _sendResponse($response)
+ {
+ if (is_object($response) && is_a($response, 'HTML_AJAX_Response')) {
+ $output = $response->getPayload();
+ $content = $response->getContentType();
+
+ } elseif (is_a($response, 'PEAR_Error')) {
+ $serializer = $this->_getSerializer('Error');
+ $output = $serializer->serialize(array(
+ 'message' => $response->getMessage(),
+ 'userinfo' => $response->getUserInfo(),
+ 'code' => $response->getCode(),
+ 'mode' => $response->getMode()
+ ));
+ $content = $this->contentTypeMap['Error'];
+
+ } else {
+ $serializer = $this->_getSerializer($this->serializer);
+ $output = $serializer->serialize($response);
+
+ $serializerType = $this->serializer;
+ // let a serializer change its output type
+ if (isset($serializer->serializerNewType)) {
+ $serializerType = $serializer->serializerNewType;
+ }
+
+ if (isset($this->contentTypeMap[$serializerType])) {
+ $content = $this->contentTypeMap[$serializerType];
+ }
+ }
+ // headers to force things not to be cached:
+ $headers = array();
+ //OPERA IS STUPID FIX
+ if (isset($_SERVER['HTTP_X_CONTENT_TYPE'])) {
+ $headers['X-Content-Type'] = $content;
+ $content = 'text/plain';
+ }
+
+ if ($this->_sendContentLength()) {
+ $headers['Content-Length'] = strlen($output);
+ }
+
+ $headers['Expires'] = 'Mon, 26 Jul 1997 05:00:00 GMT';
+ $headers['Last-Modified'] = gmdate("D, d M Y H:i:s").'GMT';
+ $headers['Cache-Control'] = 'no-cache, must-revalidate';
+ $headers['Pragma'] = 'no-cache';
+ $headers['Content-Type'] = $content.'; charset=utf-8';
+
+ //intercept to wrap iframe return data
+ if ($this->_iframe) {
+ $output = $this->_iframeWrapper($this->_iframe,
+ $output, $headers);
+ $headers['Content-Type'] = 'text/html; charset=utf-8';
+ }
+
+ $this->_sendHeaders($headers);
+ echo $output;
+ }
+
+ /**
+ * Decide if we should send a Content-length header
+ *
+ * @return bool true if it's ok to send the header, false otherwise
+ * @access private
+ */
+ function _sendContentLength()
+ {
+ if (!$this->sendContentLength) {
+ return false;
+ }
+ $ini_tests = array( "output_handler",
+ "zlib.output_compression",
+ "zlib.output_handler");
+ foreach ($ini_tests as $test) {
+ if (ini_get($test)) {
+ return false;
+ }
+ }
+ return (ob_get_level() <= 0);
+ }
+
+ /**
+ * Actually send a list of headers
+ *
+ * @param array $array list of headers to send
+ *
+ * @access private
+ * @return void
+ */
+ function _sendHeaders($array)
+ {
+ foreach ($array as $header => $value) {
+ header($header . ': ' . $value);
+ }
+ }
+
+ /**
+ * Get an instance of a serializer class
+ *
+ * @param string $type Last part of the class name
+ *
+ * @access private
+ * @return HTML_AJAX_Serializer
+ */
+ function _getSerializer($type)
+ {
+ if (isset($this->_serializers[$type])) {
+ return $this->_serializers[$type];
+ }
+
+ $class = 'HTML_AJAX_Serializer_'.$type;
+
+ if ( (version_compare(phpversion(), 5, '>') && !class_exists($class, false))
+ || (version_compare(phpversion(), 5, '<') && !class_exists($class)) ) {
+ // include the class only if it isn't defined
+ include_once "HTML/AJAX/Serializer/{$type}.php";
+ }
+
+ //handle JSON loose typing option for associative arrays
+ if ($type == 'JSON') {
+ $this->_serializers[$type] = new $class($this->jsonLooseType);
+ } else {
+ $this->_serializers[$type] = new $class();
+ }
+ return $this->_serializers[$type];
+ }
+
+ /**
+ * Get payload in its submitted form, currently only supports raw post
+ *
+ * @access private
+ * @return string raw post data
+ */
+ function _getClientPayload()
+ {
+ if (empty($this->_payload)) {
+ if (isset($GLOBALS['HTTP_RAW_POST_DATA'])) {
+ $this->_payload = $GLOBALS['HTTP_RAW_POST_DATA'];
+ } else if (function_exists('file_get_contents')) {
+ // both file_get_contents() and php://input require PHP >= 4.3.0
+ $this->_payload = file_get_contents('php://input');
+ } else {
+ $this->_payload = '';
+ }
+ }
+ return $this->_payload;
+ }
+
+ /**
+ * stub for getting get vars - applies strip_tags
+ *
+ * @param string $var variable to get
+ *
+ * @access private
+ * @return string filtered _GET value
+ */
+ function _getVar($var)
+ {
+ if (!isset($_GET[$var])) {
+ return null;
+ } else {
+ return strip_tags($_GET[$var]);
+ }
+ }
+
+ /**
+ * stub for getting server vars - applies strip_tags
+ *
+ * @param string $var variable to get
+ *
+ * @access private
+ * @return string filtered _GET value
+ */
+ function _getServer($var)
+ {
+ if (!isset($_SERVER[$var])) {
+ return null;
+ } else {
+ return strip_tags($_SERVER[$var]);
+ }
+ }
+
+ /**
+ * Exception handler, passes them to _errorHandler to do the actual work
+ *
+ * @param Exception $ex Exception to be handled
+ *
+ * @access private
+ * @return void
+ */
+ function _exceptionHandler($ex)
+ {
+ $this->_errorHandler($ex->getCode(), $ex->getMessage(), $ex->getFile(), $ex->getLine());
+ }
+
+
+ /**
+ * Error handler that sends it errors to the client side
+ *
+ * @param int $errno Error number
+ * @param string $errstr Error string
+ * @param string $errfile Error file
+ * @param string $errline Error line
+ *
+ * @access private
+ * @return void
+ */
+ function _errorHandler($errno, $errstr, $errfile, $errline)
+ {
+ if ($errno & error_reporting()) {
+ $e = new stdClass();
+ $e->errNo = $errno;
+ $e->errStr = $errstr;
+ $e->errFile = $errfile;
+ $e->errLine = $errline;
+
+
+ $this->serializer = 'Error';
+ $this->_sendResponse($e);
+ if ($this->debugEnabled) {
+ $this->debug = new HTML_AJAX_Debug($errstr, $errline, $errno, $errfile);
+ if ($this->debugSession) {
+ $this->debug->sessionError();
+ }
+ $this->debug->_saveError();
+ }
+ die();
+ }
+ }
+
+ /**
+ * Creates html to wrap serialized info for iframe xmlhttprequest fakeout
+ *
+ * @param string $id iframe instance id
+ * @param string $data data to pass
+ * @param string $headers headers to pass
+ *
+ * @access private
+ * @return string html page with iframe passing code
+ */
+ function _iframeWrapper($id, $data, $headers = array())
+ {
+ $string = ''
+ . '';
+ return $string;
+ }
+
+ /**
+ * Handles a proxied grab request
+ *
+ * @return bool true to end the response, false to continue trying to handle it
+ * @access private
+ */
+ function _iframeGrabProxy()
+ {
+ if (!isset($_REQUEST['Iframe_XHR_id'])) {
+ trigger_error('Invalid iframe ID');
+ return false;
+ }
+ $this->_iframe = $_REQUEST['Iframe_XHR_id'];
+ $this->_payload = (isset($_REQUEST['Iframe_XHR_data']) ? $_REQUEST['Iframe_XHR_data'] : '');
+ $url = urldecode($_GET['px']);
+ $url_parts = parse_url($url);
+ $urlregex = '#^https?://#i';
+
+ if (!preg_match($urlregex, $url) || $url_parts['host'] != $_SERVER['HTTP_HOST']) {
+ trigger_error('Invalid URL for grab proxy');
+ return true;
+ }
+ $method = (isset($_REQUEST['Iframe_XHR_HTTP_method'])
+ ? strtoupper($_REQUEST['Iframe_XHR_HTTP_method'])
+ : 'GET');
+ // validate method
+ if ($method != 'GET' && $method != 'POST') {
+ trigger_error('Invalid grab URL');
+ return true;
+ }
+ // validate headers
+ $headers = '';
+ if (isset($_REQUEST['Iframe_XHR_headers'])) {
+ foreach ($_REQUEST['Iframe_XHR_headers'] as $header) {
+ if (strpos($header, "\r") !== false
+ || strpos($header, "\n") !== false) {
+ trigger_error('Invalid grab header');
+ return true;
+ }
+ $headers .= $header . "\r\n";
+ }
+ }
+ // tries to make request with file_get_contents()
+ if (ini_get('allow_url_fopen') && version_compare(phpversion(), '5.0.0'. '>=')) {
+ $opts = array(
+ $url_parts['scheme'] => array(
+ 'method' => $method,
+ 'headers' => $headers,
+ 'content' => $this->_payload
+ )
+ );
+ $ret = @file_get_contents($url, false, stream_context_create($opts));
+ if (!empty($ret)) {
+ $this->_sendResponse($ret);
+ return true;
+ }
+ }
+ // tries to make request using the curl extension
+ if (function_exists('curl_setopt')) {
+ $ch = curl_init();
+ curl_setopt($ch, CURLOPT_URL, $url);
+ curl_setopt($ch, CURLOPT_HEADER, $headers);
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+ $ret = curl_exec($ch);
+ if ($ret !== false) {
+ curl_close($ch);
+ $this->_sendResponse($ret);
+ return true;
+ }
+ }
+ if (isset($url_parts['port'])) {
+ $port = $url_parts['port'];
+ } else {
+ $port = getservbyname(strtolower($url_parts['scheme']), 'tcp');
+ if ($port === false) {
+ trigger_error('Grab proxy: Unknown port or service, defaulting to 80', E_USER_WARNING);
+ $port = 80;
+ }
+ }
+ if (!isset($url_parts['path'])) {
+ $url_parts['path'] = '/';
+ }
+ if (!empty($url_parts['query'])) {
+ $url_parts['path'] .= '?' . $url_parts['query'];
+ }
+ $request = "$method {$url_parts['path']} HTTP/1.0\r\n"
+ . "Host: {$url['host']}\r\n"
+ . "Connection: close\r\n"
+ . "$headers\r\n";
+ // tries to make request using the socket functions
+ $fp = fsockopen($_SERVER['HTTP_HOST'], $port, $errno, $errstr, 4);
+ if ($fp) {
+ fputs($fp, $request);
+
+ $ret = '';
+ $done_headers = false;
+
+ while (!feof($fp)) {
+ $ret .= fgets($fp, 2048);
+ if ($done_headers || ($contentpos = strpos($ret, "\r\n\r\n")) === false) {
+ continue;
+ }
+ $done_headers = true;
+ $ret = substr($ret, $contentpos + 4);
+ }
+ fclose($fp);
+ $this->_sendResponse($ret);
+ return true;
+ }
+ // tries to make the request using the socket extension
+ $host = gethostbyname($url['host']);
+ if (($socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)) < 0
+ || ($connected = socket_connect($socket, $host, $port)) < 0
+ || ($written = socket_write($socket, $request)) < strlen($request)) {
+ trigger_error('Grab proxy failed: ' . socket_strerror($socket));
+ return true;
+ }
+
+ $ret = '';
+ $done_headers = false;
+
+ while ($out = socket_read($socket, 2048)) {
+ $ret .= $out;
+ if ($done_headers || ($contentpos = strpos($ret, "\r\n\r\n")) === false) {
+ continue;
+ }
+ $done_headers = true;
+ $ret = substr($ret, $contentpos + 4);
+ }
+ socket_close($socket);
+ $this->_sendResponse($ret);
+ return true;
+ }
+
+ /**
+ * Add a class or classes to those allowed to be unserialized
+ *
+ * @param mixed $classes the class or array of classes to add
+ *
+ * @access public
+ * @return void
+ */
+ function addAllowedClasses($classes)
+ {
+ if (!is_array($classes)) {
+ $this->_allowedClasses[] = $classes;
+ } else {
+ $this->_allowedClasses = array_merge($this->_allowedClasses, $classes);
+ }
+ $this->_allowedClasses = array_unique($this->_allowedClasses);
+ }
+
+ /**
+ * Checks that the given callback is callable and allowed to be called
+ *
+ * @param callback $callback the callback to check
+ *
+ * @return bool true if the callback is valid, false otherwise
+ * @access private
+ */
+ function _validatePhpCallback($callback)
+ {
+ if (!is_callable($callback)) {
+ return false;
+ }
+ $sig = md5(serialize($callback));
+ return isset($this->_validCallbacks[$sig]);
+ }
+
+ /**
+ * Register a callback so it may be called from JS
+ *
+ * @param callback $callback the callback to register
+ *
+ * @access public
+ * @return void
+ */
+ function registerPhpCallback($callback)
+ {
+ $this->_validCallbacks[md5(serialize($callback))] = 1;
+ }
+
+ /**
+ * Make JavaScript code smaller
+ *
+ * Currently just strips whitespace and comments, needs to remain fast
+ * Strips comments only if they are not preceeded by code
+ * Strips /*-style comments only if they span over more than one line
+ * Since strings cannot span over multiple lines, it cannot be defeated by a
+ * string containing /*
+ *
+ * @param string $input Javascript to pack
+ *
+ * @access public
+ * @return string packed javascript
+ */
+ function packJavaScript($input)
+ {
+ $stripPregs = array(
+ '/^\s*$/',
+ '/^\s*\/\/.*$/'
+ );
+ $blockStart = '/^\s*\/\/\*/';
+ $blockEnd = '/\*\/\s*(.*)$/';
+ $inlineComment = '/\/\*.*\*\//';
+ $out = '';
+
+ $lines = explode("\n", $input);
+ $inblock = false;
+ foreach ($lines as $line) {
+ $keep = true;
+ if ($inblock) {
+ if (preg_match($blockEnd, $line)) {
+ $inblock = false;
+ $line = preg_match($blockEnd, '$1', $line);
+ $keep = strlen($line) > 0;
+ }
+ } elseif (preg_match($inlineComment, $line)) {
+ $keep = true;
+ } elseif (preg_match($blockStart, $line)) {
+ $inblock = true;
+ $keep = false;
+ }
+
+ if (!$inblock) {
+ foreach ($stripPregs as $preg) {
+ if (preg_match($preg, $line)) {
+ $keep = false;
+ break;
+ }
+ }
+ }
+
+ if ($keep && !$inblock) {
+ $out .= trim($line)."\n";
+ }
+ /* Enable to see what your striping out
+ else {
+ echo $line."
";
+ }//*/
+ }
+ $out .= "\n";
+ return $out;
+ }
+
+ /**
+ * Set an interceptor class
+ *
+ * An interceptor class runs during the process of handling a request,
+ * it allows you to run security checks globally. It also allows you to
+ * rewrite parameters
+ *
+ * You can throw errors and exceptions in your intercptor methods and
+ * they will be passed to javascript
+ *
+ * You can add interceptors are 3 levels
+ * For a particular class/method, this is done by add a method to you class
+ * named ClassName_MethodName($params)
+ * For a particular class, method ClassName($methodName,$params)
+ * Globally, method intercept($className,$methodName,$params)
+ *
+ * Only one match is done, using the most specific interceptor
+ *
+ * All methods have to return $params, if you want to empty all of the
+ * parameters return an empty array
+ *
+ * @param Object $instance an instance of you interceptor class
+ *
+ * @todo handle php callbacks
+ * @access public
+ * @return void
+ */
+ function setInterceptor($instance)
+ {
+ $this->_interceptor = $instance;
+ }
+
+ /**
+ * Attempt to intercept a call
+ *
+ * @param string $className Class Name
+ * @param string $methodName Method Name
+ * @param string $callback Not implemented
+ * @param array $params Array of parameters to pass to the interceptor
+ *
+ * @todo handle php callbacks
+ * @access private
+ * @return array Updated params
+ */
+ function _processInterceptor($className,$methodName,$callback,$params)
+ {
+
+ $m = $className.'_'.$methodName;
+ if (method_exists($this->_interceptor, $m)) {
+ return $this->_interceptor->$m($params);
+ }
+
+ $m = $className;
+ if (method_exists($this->_interceptor, $m)) {
+ return $this->_interceptor->$m($methodName, $params);
+ }
+
+ $m = 'intercept';
+ if (method_exists($this->_interceptor, $m)) {
+ return $this->_interceptor->$m($className, $methodName, $params);
+ }
+
+ return $params;
+ }
+}
+
+/**
+ * PHP 4 compat function for interface/class exists
+ *
+ * @param string $class Class name
+ * @param bool $autoload Should the autoloader be called
+ *
+ * @access public
+ * @return bool
+ */
+function HTML_AJAX_Class_exists($class, $autoload)
+{
+ if (function_exists('interface_exists')) {
+ return class_exists($class, $autoload);
+ } else {
+ return class_exists($class);
+ }
+}
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+?>
diff --git a/lib/pear/HTML/AJAX/Action.php b/lib/pear/HTML/AJAX/Action.php
new file mode 100755
index 00000000000..77ef9f57ea8
--- /dev/null
+++ b/lib/pear/HTML/AJAX/Action.php
@@ -0,0 +1,357 @@
+
+ * @copyright 2005-2008 Elizabeth Smith
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @version Release: 0.5.6
+ * @link http://htmlajax.org/HTML_AJAX/Using%20haSerializer
+ */
+
+/**
+ * Require the response class and json serializer
+ */
+require_once 'HTML/AJAX/Response.php';
+require_once 'HTML/AJAX/Serializer/JSON.php';
+
+/**
+ * Helper class to eliminate the need to write javascript functions to deal with data
+ *
+ * This class creates information that can be properly serialized and used by
+ * the haaction serializer which eliminates the need for php users to write
+ * javascript for dealing with the information returned by an ajax method -
+ * instead the javascript is basically created for them
+ *
+ * @category HTML
+ * @package AJAX
+ * @author Elizabeth Smith
+ * @copyright 2005-2008 Elizabeth Smith
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @version Release: 0.5.6
+ * @link http://htmlajax.org/HTML_AJAX/Using%20haSerializer
+ */
+class HTML_AJAX_Action extends HTML_AJAX_Response
+{
+
+ /**
+ * Content type for the HAA response
+ *
+ * goofy but unique content type to tell the javascript which deserializer to use
+ * overrides HTML_AJAX_Response
+ *
+ * @var string
+ * @access public
+ */
+ var $contentType = 'application/html_ajax_action';
+
+ /**
+ * An array holding all the actions for the class
+ *
+ * these have numeric keys and each new action is added on the end, remember
+ * these are executed in the order added
+ *
+ * @var array
+ * @access private
+ */
+ var $_actions = array();
+
+ /**
+ * Prepends data to the attribute identified by id
+ *
+ * The data will be added to the beginning of the attribute identified by the id
+ * sent, id must be unique
+ *
+ * $response->prependAttr('myid', 'class', 'red');
+ * $response->prependAttr('myid', array('class' => 'red', 'innerHTML' => 'this is an error'));
+ *
+ * @param string $id id for a specific item on the page
+ * @param string|array $attribute either an array of attribute/data pairs or a string attribute name
+ * @param mixed $data should be NULL if attribute is an array, otherwise data you wish to set the attribute to
+ *
+ * @return void
+ * @access public
+ */
+ function prependAttr($id, $attribute, $data = null)
+ {
+ if (!is_null($data)) {
+ $attribute = array($attribute => $data);
+ }
+ $this->_actions[] = array(
+ 'action' => 'prepend',
+ 'id' => $id,
+ 'attributes' => $attribute,
+ 'data' => $data,
+ );
+ return;
+ }
+
+ /**
+ * Appends data to the attribute identified by id
+ *
+ * The data will be added to the end of the attribute identified by the id
+ * sent, id must be unique
+ *
+ * $response->appendAttr('myid', 'class', 'red');
+ * $response->appendAttr('myid', array('class' => 'red', 'innerHTML' => 'this is an error'));
+ *
+ * @param string $id id for a specific item on the page
+ * @param string|array $attribute either an array of attribute/data pairs or a string attribute name
+ * @param mixed $data should be NULL if attribute is an array, otherwise data you wish to set the attribute to
+ *
+ * @return void
+ * @access public
+ */
+ function appendAttr($id, $attribute, $data = null)
+ {
+ if (!is_null($data)) {
+ $attribute = array($attribute => $data);
+ }
+ $this->_actions[] = array(
+ 'action' => 'append',
+ 'id' => $id,
+ 'attributes' => $attribute,
+ );
+ return;
+ }
+
+ /**
+ * Assigns data to the attribute identified by id overwriting any previous values
+ *
+ * The data will be assigned to the attribute identified by the id
+ * sent, id must be unique
+ *
+ * $response->assignAttr('myid', 'class', 'red');
+ * $response->assignAttr('myid', array('class' => 'red', 'innerHTML' => 'this is an error'));
+ *
+ * @param string $id id for a specific item on the page
+ * @param string|array $attribute either an array of attribute/data pairs or a string attribute name
+ * @param mixed $data should be NULL if attribute is an array, otherwise data you wish to set the attribute to
+ *
+ * @return void
+ * @access public
+ */
+ function assignAttr($id, $attribute, $data = null)
+ {
+ if (!is_null($data)) {
+ $attribute = array($attribute => $data);
+ }
+ $this->_actions[] = array(
+ 'action' => 'assign',
+ 'id' => $id,
+ 'attributes' => $attribute,
+ );
+ return;
+ }
+
+ /**
+ * Deletes or assigns a value of an empty string to an attribute
+ *
+ * You may send either a single attribute or an array of attributes to clear
+ *
+ * $response->clearAttr('myid', 'class');
+ * $response->clearAttr('myid', array('class', 'innerHTML'));
+ *
+ * @param string $id id for a specific item on the page
+ * @param string|array $attribute either an array of attribute/data pairs or a string attribute name
+ *
+ * @return void
+ * @access public
+ */
+ function clearAttr($id, $attribute)
+ {
+ if (!is_array($attribute)) {
+ $attribute = array($attribute);
+ }
+ $this->_actions[] = array(
+ 'action' => 'clear',
+ 'id' => $id,
+ 'attributes' => $attribute,
+ );
+ return;
+ }
+
+ /**
+ * create a dom node via javascript
+ *
+ * higher level dom manipulation - creates a new node to insert into the dom
+ * You can control where the new node is inserted with two things, the insertion
+ * type and the id/ The type should be append, prepend, insertBefore, or insertAfter
+ *
+ * The id is a sibling node - like a div in the same div you want to add more to
+ * If you choose to append or prepend a node it will be placed at the beginning
+ * or end of the node with the id you send. If you choose insertBefore or
+ * InsertAfter it will be put right before or right after the node you specified.
+ * You can send an array of attributes to apply to the new node as well,
+ * so you don't have to create it and then assign Attributes.
+ *
+ *
+ * $response->createNode('myid', 'div');
+ * $response->createNode('submit', 'input',
+ * array('id' => 'key',
+ * 'name' => 'key',
+ * 'type' => 'hidden',
+ * 'value' => $id),
+ * 'insertBefore');
+ *
+ *
+ * @param string $id id for a specific item on the page
+ * @param string $tag html node to create
+ * @param array $attributes array of attribute -> data to fill the node with
+ * @param string $type append|prepend|insertBefore|insertAfter default is append
+ *
+ * @return void
+ * @access public
+ */
+ function createNode($id, $tag, $attributes, $type = 'append')
+ {
+ $types = array('append', 'prepend', 'insertBefore', 'insertAfter');
+ if (!in_array($type, $types)) {
+ $type = 'append';
+ }
+ settype($attributes, 'array');
+ $this->_actions[] = array(
+ 'action' => 'create',
+ 'id' => $id,
+ 'tag' => $tag,
+ 'attributes' => $attributes,
+ 'type' => $type,
+ );
+ return;
+ }
+
+ /**
+ * Replace a dom node via javascript
+ *
+ * higher level dom manipulation - replaces one node with another
+ * This can be used to replace a div with a form for inline editing
+ * use innerHtml attribute to change inside text
+ *
+ * $response->replaceNode('myid', 'div', array('innerHTML' => 'loading complete'));
+ * $response->replaceNode('mydiv', 'form', array('innerHTML' => $form));
+ *
+ * @param string $id id for a specific item on the page
+ * @param string $tag html node to create
+ * @param array $attributes array of attribute -> data to fill the node with
+ *
+ * @return void
+ * @access public
+ */
+ function replaceNode($id, $tag, $attributes)
+ {
+ settype($attributes, 'array');
+ $this->_actions[] = array(
+ 'action' => 'replace',
+ 'id' => $id,
+ 'tag' => $tag,
+ 'attributes' => $attributes,
+ );
+ return;
+ }
+
+ /**
+ * Delete a dom node via javascript
+ *
+ * $response->removeNode('myid');
+ * $response->removeNode(array('mydiv', 'myform'));
+ *
+ * @param string $id id for a specific item on the page
+ *
+ * @return void
+ * @access public
+ */
+ function removeNode($id)
+ {
+ $this->_actions[] = array(
+ 'action' => 'remove',
+ 'id' => $id,
+ );
+ return;
+ }
+
+ /**
+ * Send a string to a javascript eval
+ *
+ * This will send the data right to the eval javascript function, it will NOT
+ * allow you to dynamically add a javascript function for use later on because
+ * it is constrined by the eval function
+ *
+ * @param string $data string to pass to the alert javascript function
+ *
+ * @return void
+ * @access public
+ */
+ function insertScript($data)
+ {
+ $this->_actions[] = array(
+ 'action' => 'script',
+ 'data' => $data,
+ );
+ return;
+ }
+
+ /**
+ * Send a string to a javascript alert
+ *
+ * This will send the data right to the alert javascript function
+ *
+ * @param string $data string to pass to the alert javascript function
+ *
+ * @return void
+ * @access public
+ */
+ function insertAlert($data)
+ {
+ $this->_actions[] = array(
+ 'action' => 'alert',
+ 'data' => $data,
+ );
+ return;
+ }
+
+ /**
+ * Returns the serialized content of the response class
+ *
+ * we actually use the json serializer underneath, so we send the actions array
+ * to the json serializer and return the data
+ *
+ * @return string serialized response content
+ * @access public
+ */
+ function getPayload()
+ {
+ $serializer = new HTML_AJAX_Serializer_JSON();
+ return $serializer->serialize($this->_actions);
+ }
+
+ /**
+ * Adds all the actions from one response object to another, feature request
+ * #6635 at pear.php.net
+ *
+ * @param object &$instance referenced HTML_AJAX_Action object
+ *
+ * @return array
+ * @access public
+ */
+ function combineActions(&$instance)
+ {
+ $this->_actions = array_merge($this->_actions, $instance->retrieveActions());
+ }
+
+ /**
+ * to follow proper property access we need a way to retrieve the private
+ * actions array
+ *
+ * @return array
+ * @access public
+ */
+ function retrieveActions()
+ {
+ return $this->_actions;
+ }
+}
+?>
diff --git a/lib/pear/HTML/AJAX/Debug.php b/lib/pear/HTML/AJAX/Debug.php
new file mode 100755
index 00000000000..7e11f7ddf94
--- /dev/null
+++ b/lib/pear/HTML/AJAX/Debug.php
@@ -0,0 +1,144 @@
+
+ * @copyright 2005 David Coallier
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @version Release: 0.5.6
+ */
+class HTML_AJAX_Debug {
+ // {{{ properties
+ /**
+ * This is the error message.
+ *
+ * @access private
+ */
+ var $errorMsg;
+
+ /**
+ * The line where the error occured.
+ *
+ * @access private
+ */
+ var $errorLine;
+
+ /**
+ * The error code.
+ *
+ * @access private
+ */
+ var $errorCode;
+
+ /**
+ * The file where the error occured.
+ *
+ * @access private
+ */
+ var $errorFile;
+
+ /**
+ * Time the error occured
+ *
+ * @access private
+ */
+ var $_timeOccured;
+
+ /**
+ * The whole error itself
+ *
+ * @access private
+ * @see errorMsg
+ * @see errorLine
+ * @see errorFile
+ * @see errorCode
+ */
+ var $error;
+
+ /**
+ * The file to save the error to.
+ *
+ * @access private
+ * @default ajaxErrLog.xml
+ */
+ var $file = 'ajaxErrLog.xml';
+ // }}}
+ // {{{ constructor
+ /**
+ * The constructor.
+ *
+ * @param string $errorMsg The error message.
+ * @param string $errLine The line where error occured.
+ * @param string $errCode The error Code.
+ * @param string $errFile The file where error occured.
+ */
+ function HTML_AJAX_Debug($errMsg, $errLine, $errCode, $errFile)
+ {
+ $this->errorMsg = $errMsg;
+ $this->errorLine = $errLine;
+ $this->errorCode = $errCode;
+ $this->errorFile = $errFile;
+ $this->_timeOccured = date("Y-m-d H:i:s", time());
+ $this->xmlError();
+ }
+ // }}}
+ // {{{ xmlError
+ /**
+ * This functions formats the error to xml format then we can save it.
+ *
+ * @access protected
+ * @return $this->error the main error.
+ */
+ function xmlError()
+ {
+ $error = " {$this->_timeOccured}" . HTML_AJAX_NEWLINE;
+ $error .= " {$this->errorMsg}" . HTML_AJAX_NEWLINE;
+ $error .= " {$this->errorCode}" . HTML_AJAX_NEWLINE;
+ $error .= " {$this->errorLine}" . HTML_AJAX_NEWLINE;
+ $error .= " {$this->errorFile}" . HTML_AJAX_NEWLINE . HTML_AJAX_NEWLINE;
+ return $this->error = $error;
+ }
+ // }}}
+ // {{{ sessionError
+ /**
+ * This function pushes the array $_SESSION['html_ajax_debug']['time'][]
+ * with the values inside of $this->error
+ *
+ * @access public
+ */
+ function sessionError()
+ {
+ $_SESSION['html_ajax_debug']['time'][] = $this->error;
+ }
+ // }}}
+ // {{{ _saveError
+ /**
+ * This function saves the error to a file
+ * appending to this file.
+ *
+ * @access private.
+ */
+ function _saveError()
+ {
+ if ($handle = fopen($this->file, 'a')) {
+ fwrite($handle, $this->error);
+ }
+ }
+ // }}}
+}
+// }}}
+?>
diff --git a/lib/pear/HTML/AJAX/Helper.php b/lib/pear/HTML/AJAX/Helper.php
new file mode 100755
index 00000000000..ecf54974a43
--- /dev/null
+++ b/lib/pear/HTML/AJAX/Helper.php
@@ -0,0 +1,186 @@
+
+ * @copyright 2005 Joshua Eichorn
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @version Release: 0.5.6
+ */
+
+/**
+ * HTML/JavaScript Generation Helper
+ *
+ * @category HTML
+ * @package AJAX
+ * @author Joshua Eichorn
+ * @copyright 2005 Joshua Eichorn
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @version Release: 0.5.6
+ * @link http://pear.php.net/package/HTML_AJAX
+ */
+class HTML_AJAX_Helper
+{
+ /**
+ * URL where an HTML_AJAX_Server instance is serving up clients and taking ajax requests
+ */
+ var $serverUrl = 'server.php';
+
+ /**
+ * JS libraries to include
+ *
+ * @var array
+ */
+ var $jsLibraries = array('Util','Main','Request','HttpClient','Dispatcher','Behavior','Loading','JSON','iframe');
+
+ /**
+ * Remote class stubs to include
+ */
+ var $stubs = array();
+
+ /**
+ * Combine jsLibraries into a single require and remove duplicates
+ */
+ var $combineJsIncludes = false;
+
+ /**
+ * Include all needed libraries, stubs, and set defaultServer
+ *
+ * @return string
+ */
+ function setupAJAX()
+ {
+ $libs = array(0=>array());
+ $combinedLibs = array();
+
+ $this->jsLibraries = array_unique($this->jsLibraries);
+ foreach($this->jsLibraries as $library) {
+ if (is_array($library)) {
+ $library = array_unique($library);
+ $combinedLibs = array_merge($combinedLibs,$library);
+ $libs[] = implode(',',$library);
+ }
+ else {
+ $libs[0][] = $library;
+ $combinedLibs[] = $library;
+ }
+ }
+ $libs[0] = implode(',',$libs[0]);
+
+ $sep = '?';
+ if (strstr($this->serverUrl,'?')) {
+ $sep = '&';
+ }
+
+ $ret = '';
+ if ($this->combineJsIncludes == true) {
+ $list = implode(',',$combinedLibs);
+ $ret .= "\n";
+ }
+ else {
+ foreach($libs as $list) {
+ $ret .= "\n";
+ }
+ }
+
+ if (count($this->stubs) > 0) {
+ $stubs = implode(',',$this->stubs);
+ $ret .= "\n";
+ }
+ $ret .= $this->encloseInScript('HTML_AJAX.defaultServerUrl = '.$this->escape($this->serverUrl));
+ return $ret;
+ }
+
+ /**
+ * Create a custom Loading message
+ *
+ * @param string $body HTML body of the loading div
+ * @param string $class CSS class of the div
+ * @param string $style style tag of the loading div
+ */
+ function loadingMessage($body, $class = 'HTML_AJAX_Loading',
+ $style = 'position: absolute; top: 0; right: 0; background-color: red; width: 80px; padding: 4px; display: none')
+ {
+ return "{$body}
\n";
+ }
+
+ /**
+ * Update the contents of an element using ajax
+ *
+ * @param string $id id of the element to update
+ * @param string|array $update Either a url to update with or a array like array('class','method')
+ * @param string $type replace or append
+ * @param boolean $enclose
+ */
+ function updateElement($id, $update, $type, $enclose = false) {
+ if (is_array($update)) {
+ $updateStr = "";
+ $comma = '';
+ foreach($update as $item) {
+ $updateStr .= $comma.$this->escape($item);
+ $comma = ',';
+ }
+ }
+ else {
+ $updateStr = $this->escape($update);
+ }
+
+ $ret = "HTML_AJAX.{$type}(".$this->escape($id).",{$updateStr});\n";
+ if ($enclose) {
+ $ret = $this->encloseInScript($ret);
+ }
+ return $ret;
+ }
+
+ /**
+ * Escape a string and add quotes allowing it to be a javascript paramater
+ *
+ * @param string $input
+ * @return string
+ * @todo do something here besides a quick hack
+ */
+ function escape($input) {
+ return "'".addslashes($input)."'";
+ }
+
+ /**
+ * Enclose a string in a script block
+ *
+ * @param string $input
+ * @return string
+ */
+ function encloseInScript($input) {
+ return '\n";
+ }
+
+ /**
+ * Generate a JSON String
+ *
+ * @param string $input
+ * @return string
+ */
+ function jsonEncode($input) {
+ require_once 'HTML/AJAX/Serializer/JSON.php';
+
+ $s = new HTML_AJAX_Serializer_JSON();
+ return $s->serialize($input);
+ }
+
+ /**
+ * Check the request headers to see if this is an AJAX request
+ *
+ * @return boolean
+ */
+ function isAJAX() {
+ if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest') {
+ return true;
+ }
+ return false;
+ }
+}
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+?>
diff --git a/lib/pear/HTML/AJAX/Response.php b/lib/pear/HTML/AJAX/Response.php
new file mode 100755
index 00000000000..d3deb089159
--- /dev/null
+++ b/lib/pear/HTML/AJAX/Response.php
@@ -0,0 +1,78 @@
+
+ * @copyright 2005-2006 Elizabeth Smith
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @version Release: 0.5.6
+ */
+
+/**
+ * Require the main AJAX library
+ */
+require_once 'HTML/AJAX.php';
+
+/**
+ * Simple base class for a response object to use as an ajax callback
+ *
+ * This is the base response class, more interesting response classes can be
+ * built off of this, simply give it a unique content type and override the
+ * getPayload method or fill the payload property with your extended classes's
+ * serialized content
+ *
+ * @version $Id$
+ */
+class HTML_AJAX_Response
+{
+
+ /**
+ * The base response class uses plain text so use that content type
+ *
+ * @var string
+ * @access public
+ */
+ var $contentType = 'text/plain';
+
+ /**
+ * Assign a string to this variable to use the bare response class
+ *
+ * @var string
+ * @access public
+ */
+ var $payload = '';
+
+ /**
+ * Returns the appropriate content type
+ *
+ * This normally simply returns the contentType property but can be overridden
+ * by an extending class if the content-type is variable
+ *
+ * @return string appropriate content type
+ * @access public
+ */
+ function getContentType()
+ {
+ return $this->contentType;
+ }
+
+ /**
+ * Returns the serialized content of the response class
+ *
+ * You can either fill the payload elsewhere in an extending class and leave
+ * this method alone, or you can override it if you have a different type
+ * of payload that needs special treatment
+ *
+ * @return string serialized response content
+ * @access public
+ */
+ function getPayload()
+ {
+ return $this->payload;
+ }
+}
+?>
diff --git a/lib/pear/HTML/AJAX/Serializer/Error.php b/lib/pear/HTML/AJAX/Serializer/Error.php
new file mode 100755
index 00000000000..736a3e4b82b
--- /dev/null
+++ b/lib/pear/HTML/AJAX/Serializer/Error.php
@@ -0,0 +1,20 @@
+
+ * @copyright 2005 Joshua Eichorn
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version Release: 0.5.6
+ * @link http://pear.php.net/package/HTML_AJAX
+ */
+class HTML_AJAX_Serializer_Error extends HTML_AJAX_Serializer_JSON
+{
+
+}
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+?>
diff --git a/lib/pear/HTML/AJAX/Serializer/JSON.php b/lib/pear/HTML/AJAX/Serializer/JSON.php
new file mode 100755
index 00000000000..62a70040452
--- /dev/null
+++ b/lib/pear/HTML/AJAX/Serializer/JSON.php
@@ -0,0 +1,96 @@
+
+ * @copyright 2005 Joshua Eichorn
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @version Release: 0.5.6
+ * @link http://pear.php.net/package/PackageName
+ */
+// {{{ class HTMLA_AJAX_Serialize_JSON
+class HTML_AJAX_Serializer_JSON
+{
+ // {{{ variables-properties
+ /**
+ * JSON instance
+ * @var HTML_AJAX_JSON
+ * @access private
+ */
+ var $_json;
+
+ /**
+ * use json php extension http://www.aurore.net/projects/php-json/
+ * @access private
+ */
+ var $_jsonext;
+
+ /**
+ * use loose typing to decode js objects into php associative arrays
+ * @access public
+ */
+ var $loose_type;
+
+ // }}}
+ // {{{ constructor
+ function HTML_AJAX_Serializer_JSON($use_loose_type = true)
+ {
+ $this->loose_type = (bool) $use_loose_type;
+ $this->_jsonext = $this->_detect();
+ if(!$this->_jsonext) {
+ $use_loose_type = ($this->loose_type) ? SERVICES_JSON_LOOSE_TYPE : 0;
+ $this->_json = new HTML_AJAX_JSON($use_loose_type);
+ }
+ }
+ // }}}
+ // {{{ serialize
+ /**
+ * This function serializes and input passed to it.
+ *
+ * @access public
+ * @param string $input The input to serialize.
+ * @return string $input The serialized input.
+ */
+ function serialize($input)
+ {
+ if($this->_jsonext) {
+ return json_encode($input);
+ } else {
+ return $this->_json->encode($input);
+ }
+ }
+ // }}}
+ // {{{ unserialize
+ /**
+ * this function unserializes the input passed to it.
+ *
+ * @access public
+ * @param string $input The input to unserialize
+ * @return string $input The unserialized input.
+ */
+ function unserialize($input)
+ {
+ if($this->_jsonext) {
+ return json_decode($input, $this->loose_type);
+ } else {
+ return $this->_json->decode($input);
+ }
+ }
+ // }}}
+ // {{{ _detect
+ /**
+ * detects the loaded extension
+ */
+ function _detect()
+ {
+ return extension_loaded('json');
+ }
+ // }}}
+}
+// }}}
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+?>
diff --git a/lib/pear/HTML/AJAX/Serializer/Null.php b/lib/pear/HTML/AJAX/Serializer/Null.php
new file mode 100755
index 00000000000..026bd5846cd
--- /dev/null
+++ b/lib/pear/HTML/AJAX/Serializer/Null.php
@@ -0,0 +1,28 @@
+
+ * @copyright 2005 Joshua Eichorn
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @version Release: 0.5.6
+ * @link http://pear.php.net/package/PackageName
+ */
+class HTML_AJAX_Serializer_Null
+{
+
+ function serialize($input)
+ {
+ return $input;
+ }
+
+ function unserialize($input)
+ {
+ return $input;
+ }
+}
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+?>
diff --git a/lib/pear/HTML/AJAX/Serializer/PHP.php b/lib/pear/HTML/AJAX/Serializer/PHP.php
new file mode 100755
index 00000000000..a3000ad0328
--- /dev/null
+++ b/lib/pear/HTML/AJAX/Serializer/PHP.php
@@ -0,0 +1,88 @@
+
+ * @copyright 2005 Arpad Ray
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @version Release: 0.5.6
+ * @link http://pear.php.net/package/HTML_AJAX
+ */
+class HTML_AJAX_Serializer_PHP
+{
+ function serialize($input)
+ {
+ return serialize($input);
+ }
+
+ /**
+ * Unserializes the given string
+ *
+ * Triggers an error if a class is found which is not
+ * in the provided array of allowed class names.
+ *
+ * @param string $input
+ * the serialized string to process
+ * @param array $allowedClasses
+ * an array of class names to check objects against
+ * before instantion
+ * @return mixed
+ * the unserialized variable on success, or false on
+ * failure. If this method fails it will also trigger
+ * a warning.
+ */
+ function unserialize($input, $allowedClasses)
+ {
+ if (version_compare(PHP_VERSION, '4.3.10', '<')
+ || (substr(PHP_VERSION, 0, 1) == '5' && version_compare(PHP_VERSION, '5.0.3', '<'))) {
+ trigger_error('Unsafe version of PHP for native unserialization');
+ return false;
+ }
+ $classes = $this->_getSerializedClassNames($input);
+ if ($classes === false) {
+ trigger_error('Invalidly serialized string');
+ return false;
+ }
+ $diff = array_diff($classes, $allowedClasses);
+ if (!empty($diff)) {
+ trigger_error('Class(es) not allowed to be serialized');
+ return false;
+ }
+ return unserialize($input);
+ }
+
+ /**
+ * Extract class names from serialized string
+ *
+ * Adapted from code by Harry Fuecks
+ *
+ * @param string $string
+ * the serialized string to process
+ * @return mixed
+ * an array of class names found, or false if the input
+ * is invalidly formed
+ */
+ function _getSerializedClassNames($string) {
+ // Strip any string representations (which might contain object syntax)
+ while (($pos = strpos($string, 's:')) !== false) {
+ $pos2 = strpos($string, ':', $pos + 2);
+ if ($pos2 === false) {
+ // invalidly serialized string
+ return false;
+ }
+ $end = $pos + 2 + substr($string, $pos + 2, $pos2) + 1;
+ $string = substr($string, 0, $pos) . substr($string, $end);
+ }
+
+ // Pull out the class names
+ preg_match_all('/O:[0-9]+:"(.*)"/U', $string, $matches);
+
+ // Make sure names are unique (same object serialized twice)
+ return array_unique($matches[1]);
+ }
+}
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+?>
diff --git a/lib/pear/HTML/AJAX/Serializer/Urlencoded.php b/lib/pear/HTML/AJAX/Serializer/Urlencoded.php
new file mode 100755
index 00000000000..a26ff1e8839
--- /dev/null
+++ b/lib/pear/HTML/AJAX/Serializer/Urlencoded.php
@@ -0,0 +1,67 @@
+
+ */
+if (!function_exists('http_build_query')) {
+ function http_build_query($formdata, $numeric_prefix = null, $key = null)
+ {
+ $res = array();
+ foreach ((array)$formdata as $k => $v) {
+ if (is_resource($v)) {
+ return null;
+ }
+ $tmp_key = urlencode(is_int($k) ? $numeric_prefix . $k : $k);
+ if (!is_null($key)) {
+ $tmp_key = $key . '[' . $tmp_key . ']';
+ }
+ $res[] = (is_scalar($v))
+ ? $tmp_key . '=' . urlencode($v)
+ : http_build_query($v, null , $tmp_key);
+ }
+ $separator = ini_get('arg_separator.output');
+ if (strlen($separator) == 0) {
+ $separator = '&';
+ }
+ return implode($separator, $res);
+ }
+}
+// }}}
+// {{{ class HTML_AJAX_Serialize_Urlencoded
+/**
+ * URL Encoding Serializer
+ *
+ * @category HTML
+ * @package AJAX
+ * @author Arpad Ray
+ * @author David Coallier
+ * @copyright 2005 Arpad Ray
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @version Release: 0.5.6
+ * @link http://pear.php.net/package/HTML_AJAX
+ */
+class HTML_AJAX_Serializer_Urlencoded
+{
+ // {{{ serialize
+ function serialize($input)
+ {
+ return http_build_query(array('_HTML_AJAX' => $input));
+ }
+ // }}}
+ // {{{ unserialize
+ function unserialize($input)
+ {
+ parse_str($input, $ret);
+ return (isset($ret['_HTML_AJAX']) ? $ret['_HTML_AJAX'] : $ret);
+ }
+ // }}}
+}
+// }}}
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+?>
diff --git a/lib/pear/HTML/AJAX/Serializer/XML.php b/lib/pear/HTML/AJAX/Serializer/XML.php
new file mode 100755
index 00000000000..6856f72b167
--- /dev/null
+++ b/lib/pear/HTML/AJAX/Serializer/XML.php
@@ -0,0 +1,88 @@
+
+ * @copyright 2005-2006 Elizabeth Smith
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @version Release: 0.5.6
+ * @link http://pear.php.net/package/PackageName
+ */
+class HTML_AJAX_Serializer_XML
+{
+
+ /**
+ * Serializes a domdocument into an xml string
+ *
+ * Uses dom or domxml to dump a string from a DomDocument instance
+ * remember dom is always the default and this will die horribly without
+ * a domdocument instance
+ *
+ * @access public
+ * @param object $input instanceof DomDocument
+ * @return string xml string of DomDocument
+ */
+ function serialize($input)
+ {
+ if(empty($input))
+ {
+ return $input;
+ }
+ // we check for the dom extension
+ elseif (extension_loaded('Dom'))
+ {
+ return $input->saveXml();
+ }
+ // then will check for domxml
+ elseif (extension_loaded('Domxml'))
+ {
+ return $input->dump_mem();
+ }
+ // will throw an error
+ else {
+ $error = new HTML_AJAX_Serializer_Error();
+ $this->serializerNewType = 'Error';
+ return $error->serialize(array('errStr'=>"Missing PHP Dom extension direct XML won't work"));
+ }
+ }
+
+ /**
+ * Unserializes the xml string sent from the document
+ *
+ * Uses dom or domxml to pump a string into a DomDocument instance
+ * remember dom is always the default and this will die horribly without
+ * one or the other, and will throw warnings if you have bad xml
+ *
+ * @access public
+ * @param string $input The input to serialize.
+ * @return object instanceofDomDocument
+ */
+ function unserialize($input)
+ {
+ if(empty($input))
+ {
+ return $input;
+ }
+ // we check for the dom extension
+ elseif (extension_loaded('Dom'))
+ {
+ $doc = new DOMDocument();
+ $doc->loadXML($input);
+ return $doc;
+ }
+ // then we check for the domxml extensions
+ elseif (extension_loaded('Domxml'))
+ {
+ return domxml_open_mem($input);
+ }
+ // we give up and just return the xml directly
+ else
+ {
+ return $input;
+ }
+ }
+}
+?>
diff --git a/lib/pear/HTML/AJAX/Server.php b/lib/pear/HTML/AJAX/Server.php
new file mode 100755
index 00000000000..b5fc98272de
--- /dev/null
+++ b/lib/pear/HTML/AJAX/Server.php
@@ -0,0 +1,725 @@
+
+ * @copyright 2005 Joshua Eichorn
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @version Release: @package_version@
+ */
+
+/**
+ * Require the main AJAX library
+ */
+require_once 'HTML/AJAX.php';
+
+/**
+ * Class for creating an external AJAX server
+ *
+ * Can be used in 2 different modes, registerClass mode where you create an instance of the server and add the classes that will be registered
+ * and then run handle request
+ *
+ * Or you can extend it and add init{className} methods for each class you want to export
+ *
+ * Client js generation is exposed through 2 _GET params client and stub
+ * Setting the _GET param client to `all` will give you all the js classes needed
+ * Setting the _GET param stub to `all` will give you stubs of all registered classes, you can also set it too just 1 class
+ *
+ * @category HTML
+ * @package AJAX
+ * @author Joshua Eichorn
+ * @copyright 2005 Joshua Eichorn
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/PackageName
+ */
+class HTML_AJAX_Server
+{
+
+ /**
+ * Client options array if set to true the code looks at _GET
+ * @var bool|array
+ */
+ var $options = true;
+
+ /**
+ * HTML_AJAX instance
+ * @var HTML_AJAX
+ */
+ var $ajax;
+
+ /**
+ * Set to true if your extending the server to add init{className methods}
+ * @var boolean
+ * @access public
+ */
+ var $initMethods = false;
+
+ /**
+ * Location on filesystem of client javascript library
+ * @var false|string if false the default pear data dir location is used
+ */
+ var $clientJsLocation = false;
+
+ /**
+ * An array of options that tell the server howto Cache output
+ *
+ * The rules are functions that make etag hash used to see if the client needs to download updated content
+ * If you extend this class you can make your own rule function the naming convention is _cacheRule{RuleName}
+ *
+ *
+ * array(
+ * 'httpCacheClient' => true, // send 304 headers for responses to ?client=* requests
+ * 'ClientCacheRule' => 'File', // create a hash from file names and modified times, options: file|content
+ * 'ClientCacheExpects'=> 'files', // what type of content to send to the hash function, options: files|classes|content
+ * 'httpCacheStub' => true, // send 304 headers for responses to ?stub=* requests
+ * 'StubCacheRule' => 'Api', // create a hash from the exposed api, options: api|content
+ * 'StubCacheExpects'=> 'classes', // what type of content to send to the hash function, options: files|classes|content
+ * )
+ *
+ *
+ * @var array
+ * @access public
+ */
+ var $cacheOptions = array(
+ 'httpCacheClient' => true,
+ 'ClientCacheRule' => 'file',
+ 'ClientCacheExpects' => 'files',
+ 'httpCacheStub' => true,
+ 'StubCacheRule' => 'api',
+ 'StubCacheExpects' => 'classes',
+ );
+
+ /**
+ * Compression Options
+ *
+ *
+ * array(
+ * 'enabled' => false, // enable compression
+ * 'type' => 'gzip' // the type of compression to do, options: gzip
+ * )
+ *
+ *
+ * @var array
+ * @access public
+ */
+ var $compression = array(
+ 'enabled' => false,
+ 'type' => 'gzip'
+ );
+
+ /**
+ * Javascript library names and there path
+ *
+ * the return of $this->clientJsLocation(), is prepended before running readfile on them
+ *
+ * @access public
+ * @var array
+ */
+ var $javascriptLibraries = array(
+ 'all' => 'HTML_AJAX.js',
+ 'html_ajax' => 'HTML_AJAX.js',
+ 'html_ajax_lite'=> 'HTML_AJAX_lite.js',
+ 'json' => 'serializer/JSON.js',
+ 'request' => 'Request.js',
+ 'main' => array('Compat.js','Main.js','clientPool.js'),
+ 'httpclient' => 'HttpClient.js',
+ 'dispatcher' => 'Dispatcher.js',
+ 'util' => 'util.js',
+ 'loading' => 'Loading.js',
+ 'phpserializer' => 'serializer/phpSerializer.js',
+ 'urlserializer' => 'serializer/UrlSerializer.js',
+ 'haserializer' => 'serializer/haSerializer.js',
+ 'clientpool' => 'clientPool.js',
+ 'iframe' => 'IframeXHR.js',
+ 'alias' => 'Alias.js',
+ 'queues' => 'Queue.js',
+ 'behavior' => array('behavior/behavior.js','behavior/cssQuery-p.js'),
+
+ // rules to help you use a minimal library set
+ 'standard' => array('Compat.js','clientPool.js','util.js','Main.js','HttpClient.js','Request.js','serializer/JSON.js',
+ 'Loading.js','serializer/UrlSerializer.js','Alias.js','behavior/behavior.js','behavior/cssQuery-p.js'),
+ 'jsonrpc' => array('Compat.js','util.js','Main.js','clientPool.js','HttpClient.js','Request.js','serializer/JSON.js'),
+ 'proxyobjects' => array('Compat.js','util.js','Main.js','clientPool.js','Request.js','serializer/JSON.js','Dispatcher.js'),
+
+ // BC rules
+ 'priorityqueue' => 'Queue.js',
+ 'orderedqueue' => 'Queue.js',
+ );
+
+ /**
+ * Custom paths to use for javascript libraries, if not set {@link clientJsLocation} is used to find the system path
+ *
+ * @access public
+ * @var array
+ * @see registerJsLibrary
+ */
+ var $javascriptLibraryPaths = array();
+
+ /**
+ * Array of className => init methods to call, generated from constructor from initClassName methods
+ *
+ * @access protected
+ */
+ var $_initLookup = array();
+
+
+ /**
+ * Constructor creates the HTML_AJAX instance
+ *
+ * @param string $serverUrl (Optional) the url the client should be making a request too
+ */
+ function HTML_AJAX_Server($serverUrl = false)
+ {
+ $this->ajax = new HTML_AJAX();
+
+ // parameters for HTML::AJAX
+ $parameters = array('stub', 'client');
+
+ // keep in the query string all the parameters that don't belong to AJAX
+ // we remove all string like "parameter=something&". Final '&' can also
+ // be '&' (to be sure) and is optional. '=something' is optional too.
+ $querystring = '';
+ if (isset($_SERVER['QUERY_STRING'])) {
+ $querystring = preg_replace('/(' . join('|', $parameters) . ')(?:=[^&]*(?:&(?:amp;)?|$))?/', '', $this->ajax->_getServer('QUERY_STRING'));
+ }
+
+ // call the server with this query string
+ if ($serverUrl === false) {
+ $serverUrl = htmlentities($this->ajax->_getServer('PHP_SELF'));
+ }
+
+ if (substr($serverUrl,-1) != '?') {
+ $serverUrl .= '?';
+ }
+ $this->ajax->serverUrl = $serverUrl . $querystring;
+
+ $methods = get_class_methods($this);
+ foreach($methods as $method) {
+ if (preg_match('/^init([a-zA-Z0-9_]+)$/',$method,$match)) {
+ $this->_initLookup[strtolower($match[1])] = $method;
+ }
+ }
+ }
+
+ /**
+ * Handle a client request, either generating a client or having HTML_AJAX handle the request
+ *
+ * @return boolean true if request was handled, false otherwise
+ */
+ function handleRequest()
+ {
+ if ($this->options == true) {
+ $this->_loadOptions();
+ }
+ //basically a hook for iframe but allows processing of data earlier
+ $this->ajax->populatePayload();
+ if (!isset($_GET['c']) && (count($this->options['client']) > 0 || count($this->options['stub']) > 0) ) {
+ $this->generateClient();
+ return true;
+ } else {
+ if (!empty($_GET['c'])) {
+ $this->_init($this->_cleanIdentifier($this->ajax->_getVar('c')));
+ }
+ return $this->ajax->handleRequest();
+ }
+ }
+
+ /**
+ * Register method passthrough to HTML_AJAX
+ *
+ * @see HTML_AJAX::registerClass for docs
+ */
+ function registerClass(&$instance, $exportedName = false, $exportedMethods = false)
+ {
+ $this->ajax->registerClass($instance,$exportedName,$exportedMethods);
+ }
+
+ /**
+ * Change default serialization - important for exporting classes
+ *
+ * I wanted this for the xml serializer :)
+ */
+ function setSerializer($type)
+ {
+ $this->ajax->serializer = $type;
+ $this->ajax->unserializer = $type;
+ }
+
+ /**
+ * Register a new js client library
+ *
+ * @param string $libraryName name you'll reference the library as
+ * @param string|array $fileName actual filename with no path, for example customLib.js
+ * @param string|false $path Optional, if not set the result from jsClientLocation is used
+ */
+ function registerJSLibrary($libraryName,$fileName,$path = false) {
+ $libraryName = strtolower($libraryName);
+ $this->javascriptLibraries[$libraryName] = $fileName;
+
+ if ($path !== false) {
+ $this->javascriptLibraryPaths[$libraryName] = $path;
+ }
+ }
+
+ /**
+ * Register init methods from an external class
+ *
+ * @param object $instance an external class with initClassName methods
+ */
+ function registerInitObject(&$instance) {
+ $instance->server =& $this;
+ $methods = get_class_methods($instance);
+ foreach($methods as $method) {
+ if (preg_match('/^init([a-zA-Z0-9_]+)$/',$method,$match)) {
+ $this->_initLookup[strtolower($match[1])] = array(&$instance,$method);
+ }
+ }
+ }
+
+ /**
+ * Register a callback to be exported to the client
+ *
+ * This function uses the PHP callback pseudo-type
+ *
+ */
+ function registerPhpCallback($callback)
+ {
+ if (!is_callable($callback)) {
+ // invalid callback
+ return false;
+ }
+
+ if (is_array($callback) && is_object($callback[0])) {
+ // object method
+ $this->registerClass($callback[0], strtolower(get_class($callback[0])), array($callback[1]));
+ return true;
+ }
+
+ // static callback
+ $this->ajax->registerPhpCallback($callback);
+ }
+
+ /**
+ * Generate client js
+ *
+ * @todo this is going to need tests to cover all the options
+ */
+ function generateClient()
+ {
+ $headers = array();
+
+ ob_start();
+
+ // create a list list of js files were going to need to output
+ // index is the full file and so is the value, this keeps duplicates out of $fileList
+ $fileList = array();
+
+ if(!is_array($this->options['client'])) {
+ $this->options['client'] = array();
+ }
+ foreach($this->options['client'] as $library) {
+ if (isset($this->javascriptLibraries[$library])) {
+ $lib = (array)$this->javascriptLibraries[$library];
+ foreach($lib as $file) {
+ if (isset($this->javascriptLibraryPaths[$library])) {
+ $fileList[$this->javascriptLibraryPaths[$library].$file] = $this->javascriptLibraryPaths[$library].$file;
+ }
+ else {
+ $fileList[$this->clientJsLocation().$file] = $this->clientJsLocation().$file;
+ }
+ }
+ }
+ }
+
+ // do needed class init if were running an init server
+ if(!is_array($this->options['stub'])) {
+ $this->options['stub'] = array();
+ }
+ $classList = $this->options['stub'];
+ if ($this->initMethods) {
+ if (isset($this->options['stub'][0]) && $this->options['stub'][0] === 'all') {
+ $this->_initAll();
+ } else {
+ foreach($this->options['stub'] as $stub) {
+ $this->_init($stub);
+ }
+ }
+ }
+ if (isset($this->options['stub'][0]) && $this->options['stub'][0] === 'all') {
+ $classList = array_keys($this->ajax->_exportedInstances);
+ }
+
+ // if were doing stub and client we have to wait for both ETags before we can compare with the client
+ $combinedOutput = false;
+ if ($classList != false && count($classList) > 0 && count($fileList) > 0) {
+ $combinedOutput = true;
+ }
+
+
+ if ($classList != false && count($classList) > 0) {
+
+ // were setup enough to make a stubETag if the input it wants is a class list
+ if ($this->cacheOptions['httpCacheStub'] &&
+ $this->cacheOptions['StubCacheExpects'] == 'classes')
+ {
+ $stubETag = $this->_callCacheRule('Stub',$classList);
+ }
+
+ // if were not in combined output compare etags, if method returns true were done
+ if (!$combinedOutput && isset($stubETag)) {
+ if ($this->_compareEtags($stubETag)) {
+ ob_end_clean();
+ return;
+ }
+ }
+
+ // output the stubs for all the classes in our list
+ foreach($classList as $class) {
+ echo $this->ajax->generateClassStub($class);
+ }
+
+ // if were cacheing and the rule expects content make a tag and check it, if the check is true were done
+ if ($this->cacheOptions['httpCacheStub'] &&
+ $this->cacheOptions['StubCacheExpects'] == 'content')
+ {
+ $stubETag = $this->_callCacheRule('Stub',ob_get_contents());
+ }
+
+ // if were not in combined output compare etags, if method returns true were done
+ if (!$combinedOutput && isset($stubETag)) {
+ if ($this->_compareEtags($stubETag)) {
+ ob_end_clean();
+ return;
+ }
+ }
+ }
+
+ if (count($fileList) > 0) {
+ // if were caching and need a file list build our jsETag
+ if ($this->cacheOptions['httpCacheClient'] &&
+ $this->cacheOptions['ClientCacheExpects'] === 'files')
+ {
+ $jsETag = $this->_callCacheRule('Client',$fileList);
+
+ }
+
+ // if were not in combined output compare etags, if method returns true were done
+ if (!$combinedOutput && isset($jsETag)) {
+ if ($this->_compareEtags($jsETag)) {
+ ob_end_clean();
+ return;
+ }
+ }
+
+ // output the needed client js files
+ foreach($fileList as $file) {
+ $this->_readFile($file);
+ }
+
+ // if were caching and need content build the etag
+ if ($this->cacheOptions['httpCacheClient'] &&
+ $this->cacheOptions['ClientCacheExpects'] === 'content')
+ {
+ $jsETag = $this->_callCacheRule('Client',ob_get_contents());
+ }
+
+ // if were not in combined output compare etags, if method returns true were done
+ if (!$combinedOutput && isset($jsETag)) {
+ if ($this->_compareEtags($jsETag)) {
+ ob_end_clean();
+ return;
+ }
+ }
+ // were in combined output, merge the 2 ETags and compare
+ else if (isset($jsETag) && isset($stubETag)) {
+ if ($this->_compareEtags(md5($stubETag.$jsETag))) {
+ ob_end_clean();
+ return;
+ }
+ }
+ }
+
+
+ // were outputting content, add our length header and send the output
+ $length = ob_get_length();
+ $output = ob_get_contents();
+ ob_end_clean();
+
+ if ($this->ajax->packJavaScript) {
+ $output = $this->ajax->packJavaScript($output);
+ $length = strlen($output);
+ }
+
+ if ($this->compression['enabled'] && $this->compression['type'] == 'gzip' && strpos($_SERVER["HTTP_ACCEPT_ENCODING"], "gzip") !== false) {
+ $output = gzencode($output,9);
+ $length = strlen($output);
+ $headers['Content-Encoding'] = 'gzip';
+ }
+
+ if ($length > 0 && $this->ajax->_sendContentLength()) {
+ $headers['Content-Length'] = $length;
+ }
+ $headers['Content-Type'] = 'text/javascript; charset=utf-8';
+ $this->ajax->_sendHeaders($headers);
+ echo($output);
+ }
+
+ /**
+ * Run readfile on input with basic error checking
+ *
+ * @param string $file file to read
+ * @access private
+ * @todo is addslashes enough encoding for js?
+ */
+ function _readFile($file)
+ {
+ if (file_exists($file)) {
+ readfile($file);
+ } else {
+ $file = addslashes($file);
+ echo "alert('Unable to find javascript file: $file');";
+ }
+ }
+
+ /**
+ * Get the location of the client js
+ * To override the default pear datadir location set $this->clientJsLocation
+ *
+ * @return string
+ */
+ function clientJsLocation()
+ {
+ if (!$this->clientJsLocation) {
+ $path = '@data-dir@'.DIRECTORY_SEPARATOR.'HTML_AJAX'.DIRECTORY_SEPARATOR.'js'.DIRECTORY_SEPARATOR;
+ if(strpos($path, '@'.'data-dir@') === 0)
+ {
+ $path = realpath(dirname(__FILE__).DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR.'js').DIRECTORY_SEPARATOR;
+ }
+ return $path;
+ } else {
+ return $this->clientJsLocation;
+ }
+ }
+
+ /**
+ * Set the location of the client js
+ *
+ * @access public
+ * @param string $location Location
+ * @return void
+ */
+ function setClientJsLocation($location)
+ {
+ $this->clientJsLocation = $location;
+ }
+
+ /**
+ * Set the path to a Javascript libraries
+ *
+ * @access public
+ * @param string $library Library name
+ * @param string $path Path
+ * @return void
+ */
+ function setJavascriptLibraryPath($library, $path)
+ {
+ $this->javascriptLibraryPaths[$library] = $path;
+ }
+
+ /**
+ * Set the path to more than one Javascript libraries at once
+ *
+ * @access public
+ * @param array $paths Paths
+ * @return void
+ */
+ function setJavascriptLibraryPaths($paths)
+ {
+ if (is_array($paths)) {
+ $this->javascriptLibraryPaths = array_merge($this->javascriptLibraryPaths, $paths);
+ }
+ }
+
+ /**
+ * Load options from _GET
+ *
+ * @access private
+ */
+ function _loadOptions()
+ {
+ $this->options = array('client'=>array(),'stub'=>array());
+ if (isset($_GET['client'])) {
+ $clients = explode(',',$this->ajax->_getVar('client'));
+ $client = array();
+ foreach($clients as $val) {
+ $cleanVal = $this->_cleanIdentifier($val);
+ if (!empty($cleanVal)) {
+ $client[] = strtolower($cleanVal);
+ }
+ }
+
+ if (count($client) > 0) {
+ $this->options['client'] = $client;
+ }
+ }
+ if (isset($_GET['stub'])) {
+ $stubs = explode(',',$this->ajax->_getVar('stub'));
+ $stub = array();
+ foreach($stubs as $val) {
+ $cleanVal = $this->_cleanIdentifier($val);
+ if (!empty($cleanVal)) {
+ $stub[] = strtolower($cleanVal);
+ }
+ }
+
+ if (count($stub) > 0) {
+ $this->options['stub'] = $stub;
+ }
+ }
+ }
+
+ /**
+ * Clean an identifier like a class name making it safe to use
+ *
+ * @param string $input
+ * @return string
+ * @access private
+ */
+ function _cleanIdentifier($input) {
+ return trim(preg_replace('/[^A-Za-z_0-9]/','',$input));
+ }
+
+ /**
+ * Run every init method on the class
+ *
+ * @access private
+ */
+ function _initAll()
+ {
+ if ($this->initMethods) {
+ foreach($this->_initLookup as $class => $method) {
+ $this->_init($class);
+ }
+ }
+ }
+
+ /**
+ * Init one class
+ *
+ * @param string $className
+ * @access private
+ */
+ function _init($className)
+ {
+ $className = strtolower($className);
+ if ($this->initMethods) {
+ if (isset($this->_initLookup[$className])) {
+ $method =& $this->_initLookup[$className];
+ if (is_array($method)) {
+ call_user_func($method);
+ }
+ else {
+ $this->$method();
+ }
+ } else {
+ trigger_error("Could find an init method for class: " . $className);
+ }
+ }
+ }
+
+ /**
+ * Generate a hash from a list of files
+ *
+ * @param array $files file list
+ * @return string a hash that can be used as an etag
+ * @access private
+ */
+ function _cacheRuleFile($files) {
+ $signature = "";
+ foreach($files as $file) {
+ if (file_exists($file)) {
+ $signature .= $file.filemtime($file);
+ }
+ }
+ return md5($signature);
+ }
+
+ /**
+ * Generate a hash from the api of registered classes
+ *
+ * @param array $classes class list
+ * @return string a hash that can be used as an etag
+ * @access private
+ */
+ function _cacheRuleApi($classes) {
+ $signature = "";
+ foreach($classes as $class) {
+ if (isset($this->ajax->_exportedInstances[$class])) {
+ $signature .= $class.implode(',',$this->ajax->_exportedInstances[$class]['exportedMethods']);
+ }
+ }
+ return md5($signature);
+ }
+
+ /**
+ * Generate a hash from the raw content
+ *
+ * @param array $content
+ * @return string a hash that can be used as an etag
+ * @access private
+ */
+ function _cacheRuleContent($content) {
+ return md5($content);
+ }
+
+ /**
+ * Send cache control headers
+ * @access private
+ */
+ function _sendCacheHeaders($etag,$notModified) {
+ header('Cache-Control: must-revalidate');
+ header('ETag: '.$etag);
+ if ($notModified) {
+ header('HTTP/1.0 304 Not Modified',false,304);
+ }
+ }
+
+ /**
+ * Compare eTags
+ *
+ * @param string $serverETag server eTag
+ * @return boolean
+ * @access private
+ */
+ function _compareEtags($serverETag) {
+ if (isset($_SERVER['HTTP_IF_NONE_MATCH'])) {
+ if (strcmp($this->ajax->_getServer('HTTP_IF_NONE_MATCH'),$serverETag) == 0) {
+ $this->_sendCacheHeaders($serverETag,true);
+ return true;
+ }
+ }
+ $this->_sendCacheHeaders($serverETag,false);
+ return false;
+ }
+
+ /**
+ * Call a cache rule and return its retusn
+ *
+ * @param string $rule Stub|Client
+ * @param mixed $payload
+ * @return boolean
+ * @access private
+ * @todo decide if error checking is needed
+ */
+ function _callCacheRule($rule,$payload) {
+ $method = '_cacheRule'.$this->cacheOptions[$rule.'CacheRule'];
+ return call_user_func(array(&$this,$method),$payload);
+ }
+}
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+?>