<?php
/**
* dom_xmlrpc_client provides basic XML-RPC client functionality
* @package dom-xmlrpc
* @copyright (C) 2004 John Heinstein. All rights reserved
* @license http://www.gnu.org/copyleft/lesser.html LGPL License
* @author John Heinstein <johnkarl@nbnet.nb.ca>
* @link http://www.engageinteractive.com/dom_xmlrpc/ DOM XML-RPC Home Page
* DOM XML-RPC is Free Software
**/

if (!defined('DOM_XMLRPC_INCLUDE_PATH')) {
	define('DOM_XMLRPC_INCLUDE_PATH', (dirname(__FILE__) . "/"));
}

//must change these to match the XML-RPC error spec
/** invalid method response type error */
define('XMLRPC_CLIENT_RESPONSE_TYPE_ERR', 1);
/** malformed XML response error */
define('XMLRPC_CLIENT_MALFORMED_XML_ERR', 2);

require_once(DOM_XMLRPC_INCLUDE_PATH . 'dom_xmlrpc_methodcall.php');
require_once(DOM_XMLRPC_INCLUDE_PATH . 'dom_xmlrpc_constants.php');
require_once(DOM_XMLRPC_INCLUDE_PATH . 'php_http_client_generic.php');

/**
* Provides basic XML-RPC client functionality
*
* @package dom-xmlrpc
* @author John Heinstein <johnkarl@nbnet.nb.ca>
*/
class dom_xmlrpc_client extends php_http_client_generic {
	/** @var string The method response type requested by the client */
	var $responseType = DOM_XMLRPC_RESPONSE_TYPE_XML_DOMIT;
	/** @var boolean True if multiple method calls are to be made in a single request */
	var $isMultiCall = false;
	
	/**
	* XML-RPC Client constructor
	* @param string The client connection host name, with or without its protocol prefix
	* @param string The client connection path, not including the host name
	* @param int The port to establish the client connection on
	* @param string A proxy connection host name, with or without its protocol prefix
	* @param int The timeout value for the client connection
	*/
	function dom_xmlrpc_client ($host = '', $path = '/', $port = 80, $proxy = '', $timeout = 0) {
		if ($proxy != '') {
			$host = $proxy;	    
		}
		
		$this->php_http_client_generic($host, $path, $port, $timeout);	
		$this->setHeaders();
	} //dom_xmlrpc_client	
	
	/**
	* Sets the headers for the client connection
	*/
	function setHeaders() {
		$this->setHeader('Content-Type', 'text/xml');
		$this->setHeader('Host', $this->connection->host);
		$this->setHeader('User-Agent', 'DOM XML-RPC Client/0.1');
		$this->setHeader('Connection', 'close');
	} //setHeaders

	/**
	* Determines whether message is multicall
	* @param mixed The message
	* @return mixed The evaluated message
	*/
	function evaluateMessage(&$message) {
		if (!is_string($message)) {
			if ($message->methodName == 'system.multicall') $this->isMultiCall = true;
			return $message->toXML();
		}
		else {
			if (strpos($message, 'system.multicall') !== false) $this->isMultiCall = true;
		}
		
		return $message;
	} //evaluateMessage
	
	/**
	* Sends data through the client connection
	* @param string The message to be sent
	* @return string The http response
	*/
	function &send(&$message) {
		if (!$this->isConnected()) {
			$this->connect();
		}
	
		$message = $this->evaluateMessage($message);
		$this->setHeader('Content-Length', strlen($message));
		
		$response =& parent::send($message);
		
		return $this->formatResponse($response->getResponse());
	} //send
	
	/**
	* Sends data through the client connection and disconnects
	* @param string The message to be sent
	* @return string The http response
	*/
	function &sendAndDisconnect($message) {
		$response =& $this->send($message);
		$this->disconnect();
		return $response;
	} //send	
	
	/**
	* Returns the message response, formatted according to the specified response type
	* @param string The unformatted response
	* @return string The formatted response
	*/
	function &formatResponse($response) {
		switch ($this->responseType) {
			case DOM_XMLRPC_RESPONSE_TYPE_XML_DOMIT:
			case DOM_XMLRPC_RESPONSE_TYPE_XML_DOMIT_LITE:
			case DOM_XMLRPC_RESPONSE_TYPE_XML_DOMXML:
				return $this->returnAsXML($response);
				break;
			
			case DOM_XMLRPC_RESPONSE_TYPE_ARRAY:
				return $this->returnAsArray($response);
				break;
				
			case DOM_XMLRPC_RESPONSE_TYPE_STRING:
				return $response;
				break;
		}
	} //formatResponse
	
	/**
	* Returns the message response as an XML document of the specified type
	* @param string The unformatted response
	* @return string An XML document representing the method response
	*/	
	function &returnAsXML($response) {
		switch ($this->responseType) {
			case DOM_XMLRPC_RESPONSE_TYPE_XML_DOMIT:
				require_once(DOM_XMLRPC_INCLUDE_PATH . 'dom_xmlrpc_domit_parser.php' );
				$xmlrpcDoc =& new dom_xmlrpc_domit_document();
				$success = $xmlrpcDoc->parseXML($response, false);
				break;
				
			case DOM_XMLRPC_RESPONSE_TYPE_XML_DOMIT_LITE:
				require_once(DOM_XMLRPC_INCLUDE_PATH . 'dom_xmlrpc_domit_lite_parser.php' );
				$xmlrpcDoc =& new dom_xmlrpc_domit_lite_document();
				$success = $xmlrpcDoc->parseXML($response, false);
				break;
				
			case DOM_XMLRPC_RESPONSE_TYPE_XML_XML_DOMXML:
				require_once(DOM_XMLRPC_INCLUDE_PATH . 'dom_xmlrpc_domxml_parser.php' );
				$xmlrpcDoc =& new dom_xmlrpc_domxml_document();
				$success = $xmlrpcDoc->parseXML($response);
				break;
		}
		
		if ($success) {
			return $xmlrpcDoc;
		}
		
		XMLRPC_Client_Exception::raiseException(XMLRPC_CLIENT_MALFORMED_XML_ERR, 
										("Malformed xml returned: \n $response"));
	} //returnAsXML
	
	/**
	* Returns the message response as a PHP array
	* @param string The unformatted response
	* @return string A PHP array representation of the method response
	*/
	function &returnAsArray($response) {
		require_once(DOM_XMLRPC_INCLUDE_PATH . 'dom_xmlrpc_array_parser.php');		

		$arrayParser =& new dom_xmlrpc_array_parser();
		
		if ($arrayParser->parseXML($response, false)) {		
			return $arrayParser->getArrayDocument();
		}
		else {
			XMLRPC_Client_Exception::raiseException(XMLRPC_CLIENT_MALFORMED_XML_ERR, 
										("Malformed xml returned:  \n $response"));
		}
	} //returnAsArray
	
	/**
	* Converts PHP arrays to stdclass objects
	* @param array The PHP array
	* @return object A stdclass object
	*/
	function &arraysToObjects(&$myArray) {
		foreach ($myArray as $key => $value) {
			$currItem =& $myArray[$key];
			
			if (is_array($currItem)) {
				$currItem =& $this->arraysToObjects($currItem);	
			}
		}
		
		if (dom_xmlrpc_utilities::isAssociativeArray($myArray)) {
			$obj =& new stdclass();
			
			foreach ($myArray as $key => $value) {
				$obj->$key =& $myArray[$value];
			}
			
			return $obj;
		}
		else {
			return $myArray;
		}
	} //arraysToObjects
	
	/**
	* Sets the method response type to the specified type
	* @param array The requested method response type
	*/
	function setResponseType($type) {
		$type = strtolower($type);
		
		switch ($type) {
			case DOM_XMLRPC_RESPONSE_TYPE_ARRAY:
			case DOM_XMLRPC_RESPONSE_TYPE_XML_DOMIT:
			case DOM_XMLRPC_RESPONSE_TYPE_XML_DOMIT_LITE:
			case DOM_XMLRPC_RESPONSE_TYPE_XML_DOMXML:
			case DOM_XMLRPC_RESPONSE_TYPE_STRING:
				$this->responseType = $type;
				break;
			default:
				XMLRPC_Client_Exception::raiseException(XMLRPC_CLIENT_RESPONSE_TYPE_ERR, 
								('Invalid response type: ' . $type));
		}
	} //setResponseType
	
	/**
	* Returns the current method response type
	* @return string The current method response type
	*/		
	function getResponseType() {
		return $this->responseType;
	} //getResponseType	
} //dom_xmlrpc_client


/**
* An XML-RPC Client excpetion class
*
* @package dom-xmlrpc
* @author John Heinstein <johnkarl@nbnet.nb.ca>
*/
class XMLRPC_Client_Exception {
	
	/**
	* Raises the specified exception
	* @param int The error number
	* @param int The error string
	*/
	function raiseException($errorNum, $errorString) {
		$errorMessage = $errorNum  .  "\n " . $errorString;
		
		if ((!isset($GLOBALS['DOMIT_XMLRPC_ERROR_FORMATTING_HTML'])) ||
			($GLOBALS['DOMIT_XMLRPC_ERROR_FORMATTING_HTML'] == true)) {
		        $errorMessage = "<p><pre>" . $errorMessage . "</pre></p>";
		}

		die($errorMessage);
	} //raiseException
} //XMLRPC_Client_Exception


?>