<?php
/**
 * dom_xmlrpc_builder is a utility for building string representations of XML-RPC documents
 * @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__) . "/"));
}

require_once(DOM_XMLRPC_INCLUDE_PATH . 'dom_xmlrpc_constants.php');

/**
 * A utility for building string representations of XML-RPC documents
 *
 * @package dom-xmlrpc
 * @author  John Heinstein <johnkarl@nbnet.nb.ca>
 */
class dom_xmlrpc_builder
{
	/** @var string The method type of the XML-RPC data, e.g., DOM_XMLRPC_TYPE_METHODRESPONSE */
	var $methodType;
	/** @var string The XML string representing the method params */
	var $params = "";
	/** @var string The type of object marshalling to be used, if this option is enabled */
	var $objectMarshalling = DOM_XMLRPC_OBJECT_MARSHALLING_ANONYMOUS;

	/**
	 * Invokes the ability to pass PHP objects in addition to basic native types such as int and string
	 *
	 * @param string The type of object marshalling to be invoked: either anonymous, named, or serialized
	 */
	function setObjectMarshalling($type)
	{
		$type = strtolower($type);

		switch ($type)
		{
			case DOM_XMLRPC_OBJECT_MARSHALLING_ANONYMOUS: //default
			case DOM_XMLRPC_OBJECT_MARSHALLING_NAMED:
			case DOM_XMLRPC_OBJECT_MARSHALLING_SERIALIZED:
				$this->objectMarshalling = $type;
				break;
			default:
				XMLRPC_Client_Exception::raiseException(XMLRPC_CLIENT_RESPONSE_TYPE_ERR,
					('Invalid object marshalling type: ' . $type));
		}
	} //setObjectMarshalling

	/**
	 * Creates an XML-RPC representation of a scalar value (e.g. string, double, boolean)
	 *
	 * @param mixed  The value to be converted to XML-RPC notation
	 * @param string The type of the value (will be autodetected if omitted)
	 *
	 * @return string An XML-RPC representation of the value
	 */
	function createScalar($value, $type = '')
	{
		if ($type == '')
		{
			$type = dom_xmlrpc_utilities::getScalarTypeFromValue($value);
		}

		switch ($type)
		{
			case DOM_XMLRPC_TYPE_STRING:
				if ($this->methodType == DOM_XMLRPC_TYPE_METHODRESPONSE)
				{
					//htmlencode the string, because
					//DOM XML-RPC server has decoded it
					$value = htmlentities($value, ENT_QUOTES);
				}
				break;
			case DOM_XMLRPC_TYPE_DOUBLE:
				//ensure trailing .0 if it looks like an int
				$value = '' . $value;
				if (strpos($value, '.') === false)
				{
					$value .= '.0';
				}
				break;
			case DOM_XMLRPC_TYPE_BOOLEAN:
				if (is_bool($value))
				{
					$value = ($value ? '1' : '0');
				}
				break;
			case DOM_XMLRPC_TYPE_BASE64:
				if (is_object($value))
				{
					$value = $value->getEncoded();
				}
				break;
			case DOM_XMLRPC_TYPE_DATETIME:
				if (is_object($value))
				{
					$value = $value->getDateTime_iso();
				}
				break;
		}

		return ("<value><$type>$value</$type></value>");
	} //createScalar

	/**
	 * Generates an XML-RPC param from the scalar and adds it to the method params
	 *
	 * @param mixed  The value to be converted to XML-RPC notation
	 * @param string The type of the value (will be autodetected if omitted)
	 */
	function addScalar($value, $type = '')
	{
		$this->params .= "\n\t\t<param>" . $this->createScalar($value, $type) . '</param>';
	} //addScalar

	/**
	 * Creates an XML-RPC representation of a PHP array
	 *
	 * @param array The array to be converted to XML-RPC notation
	 *
	 * @return string An XML-RPC representation of the array
	 */
	function createArray(&$myArray)
	{
		$data = '<value><array><data>';

		foreach ($myArray as $key => $value)
		{
			$currDataItem =& $myArray[$key];
			$currType = dom_xmlrpc_utilities::getTypeFromValue($currDataItem);

			$data .= $this->create($currDataItem, $currType);
		}

		$data .= '</data></array></value>';

		return $data;
	} //createArray

	/**
	 * Generates an XML-RPC param from the array and adds it to the method params
	 *
	 * @param array The array to be converted to XML-RPC notation
	 */
	function addArray($myArray)
	{
		$this->addArrayByRef($myArray);
	} //addArray

	/**
	 * Generates an XML-RPC param from the array reference and adds it to the method params
	 *
	 * @param array The array reference to be converted to XML-RPC notation
	 */
	function addArrayByRef(&$myArray)
	{ //can call with reference if deep copy is required
		$this->params .= "\n\t\t<param>" . $this->createArray($myArray) . '</param>';
	} //addArrayByRef

	/**
	 * Creates an XML-RPC representation of a PHP object
	 *
	 * @param object The object to be converted to XML-RPC notation
	 *
	 * @return string An XML-RPC representation of the object
	 */
	function createObject(&$myObject)
	{
		require_once('dom_xmlrpc_object.php');

		if (get_class($myObject) == 'dom_xmlrpc_object')
		{ //wrapper for xmlrpc object
			$myObject =& $myObject->getObject(); //grab embedded object
		}

		switch ($this->objectMarshalling)
		{
			case DOM_XMLRPC_OBJECT_MARSHALLING_ANONYMOUS:
				//generic struct, one member for each
				//object property, object type is discarded
				$data = '<value><struct>';

				foreach ($myObject as $key => $value)
				{
					$currValue =& $myObject->$key;
					$currType = dom_xmlrpc_utilities::getTypeFromValue($currValue);

					$data .= $this->createMember($key, $currValue, $currType);
				}

				$data .= '</struct></value>';
				break;

			case DOM_XMLRPC_OBJECT_MARSHALLING_NAMED:
				//struct with one member for each
				//object property, object type is defined
				//by an additional member (first in list) named
				//"__phpobject__" whose value is a string
				//containing the class type of the object
				$data = '<value><struct>';
				$data .= $this->createMember(DOM_XMLRPC_PHPOBJECT, get_class($myObject), DOM_XMLRPC_TYPE_STRING);

				foreach ($myObject as $key => $value)
				{
					$currValue =& $myObject->$key;
					$currType = dom_xmlrpc_utilities::getTypeFromValue($currValue);

					$data .= $this->createMember($key, $currValue, $currType);
				}

				$data .= '</struct></value>';
				break;

			case DOM_XMLRPC_OBJECT_MARSHALLING_SERIALIZED:
				//serialized object, one member of type base64
				//which is the serialized object, base64 encoded
				require_once(DOM_XMLRPC_INCLUDE_PATH . 'dom_xmlrpc_base64.php');
				$data = '<value><struct>';
				$data .= $this->createMember(DOM_XMLRPC_PHPOBJECT, get_class($myObject), DOM_XMLRPC_TYPE_STRING);

				$serialized =& serialize($myObject);
				$currValue =& $this->createBase64($serialized);
				$data .= $this->createMember(DOM_XMLRPC_SERIALIZED, $currValue, DOM_XMLRPC_TYPE_BASE64);

				$data .= '</struct></value>';
				break;
		} // switch

		return $data;
	} //createObject

	/**
	 * Generates an XML-RPC param from the object and adds it to the method params
	 *
	 * @param object The object to be converted to XML-RPC notation
	 */
	function addObject($myObject)
	{
		$this->addObjectByRef($myObject);
	}

	//addObject

	/**
	 * Generates an XML-RPC param from the object reference and adds it to the method params
	 *
	 * @param object The object reference to be converted to XML-RPC notation
	 */
	function addObjectByRef(&$myObject)
	{ //can call with reference if deep copy is required
		$this->params .= "\n\t\t<param>" . $this->createObject($myObject) . '</param>';
	}

	//addObjectByRef

	/**
	 * Creates an XML-RPC representation of a struct
	 *
	 * @param mixed The struct to be converted to XML-RPC notation
	 *
	 * @return string An XML-RPC representation of the struct
	 */
	function createStruct(&$myStruct)
	{
		require_once('dom_xmlrpc_object.php');
		$className = get_class($myStruct);

		//if struct is explicitly cast as an xmlrpc object
		if ($className == 'dom_xmlrpc_object')
		{
			$myObject =& $myStruct->getObject(); //grab embedded object
			return $this->createObject($myObject);
		}
		else
		{
			//wrapper for numeric indexed structs
			//that are meant to be string indexed
			if ($className == 'dom_xmlrpc_struct')
			{
				$myStruct =& $myStruct->getStruct(); //grab embedded array
			}

			$isArrayNotObj = is_array($myStruct);

			//if struct is an object and isn't anonymous
			if ($isArrayNotObj && ($this->objectMarshalling != DOM_XMLRPC_OBJECT_MARSHALLING_ANONYMOUS))
			{
				return $this->createObject($myStruct);
			}
			else
			{
				$data = '<value><struct>';

				foreach ($myStruct as $key => $value)
				{
					$isArrayNotObj ? ($currValue =& $myStruct[$key]) : ($currValue =& $value);
					$currType = dom_xmlrpc_utilities::getTypeFromValue($currValue);

					$data .= $this->createMember($key, $currValue, $currType);
				}

				$data .= '</struct></value>';
				return $data;
			}
		}
	} //createStruct

	/**
	 * Generates an XML-RPC param from the struct and adds it to the method params
	 *
	 * @param mixed The struct to be converted to XML-RPC notation
	 */
	function addStruct($myStruct)
	{
		$this->addStructByRef($myStruct);
	} //addStruct

	/**
	 * Generates an XML-RPC param from the struct reference and adds it to the method params
	 *
	 * @param mixed The struct reference to be converted to XML-RPC notation
	 */
	function addStructByRef(&$myStruct)
	{ //can call with reference if deep copy is required
		$this->params .= "\n\t\t<param>" . $this->createStruct($myStruct) . '</param>';
	} //addStructRef

	/**
	 * Creates an XML-RPC representation of a member
	 *
	 * @param mixed The member to be converted to XML-RPC notation
	 *
	 * @return string An XML-RPC representation of the member
	 */
	function createMember($name, &$value, $type)
	{
		$data = '<member><name>' . $name . '</name>';

		if ($type == '')
		{
			$type = dom_xmlrpc_utilities::getScalarTypeFromValue($value);
		}

		$data .= $this->create($value, $type) . '</member>';

		return $data;
	} //createMember

	/**
	 * Generates an XML-RPC param from the value and adds it to the method params
	 *
	 * @param mixed The value to be converted to XML-RPC notation
	 */
	function create(&$value, $type = '')
	{
		$data = '';

		if ($type == '')
		{
			require_once(DOM_XMLRPC_INCLUDE_PATH . 'dom_xmlrpc_utilities.php');
			$type = dom_xmlrpc_utilities::getTypeFromValue($value);
		}

		switch ($type)
		{
			case DOM_XMLRPC_TYPE_STRING:
			case DOM_XMLRPC_TYPE_INT:
			case DOM_XMLRPC_TYPE_I4:
			case DOM_XMLRPC_TYPE_DOUBLE:
			case DOM_XMLRPC_TYPE_BOOLEAN:
			case DOM_XMLRPC_TYPE_BASE64:
			case DOM_XMLRPC_TYPE_DATETIME:
				$data .= $this->createScalar($value, $type);
				break;
			case DOM_XMLRPC_TYPE_STRUCT:
				$data .= $this->createStruct($value, $type);
				break;
			case DOM_XMLRPC_TYPE_ARRAY:
				$data .= $this->createArray($value, $type);
				break;
		}

		return $data;
	} //create

	/**
	 * Generates an XML-RPC param from the unspecified value
	 *
	 * @param mixed The value to be converted to XML-RPC notation
	 */
	function add($value, $type = '')
	{
		$this->addByRef($value, $type);
	} //add

	/**
	 * Generates an XML-RPC param from the unspecified reference
	 *
	 * @param mixed The reference to be converted to XML-RPC notation
	 */
	function addByRef(&$value, $type = '')
	{ //can call with reference if deep copy is required
		$this->params .= "\n\t\t<param>" . $this->create($value, $type) . '</param>';
	} //addByRef

	/**
	 * Generates an XML-RPC param from list of values
	 */
	function addList()
	{
		$total = func_num_args();

		for ($i = 0; $i < $total; $i++)
		{
			$this->add(func_get_arg($i));
		}
	} //addList

	/**
	 * Creates a dom_xmlrpc_base64 object from the binary data
	 *
	 * @param mixed The binary data
	 *
	 * @return object The base64 encoded data
	 */
	function &createBase64($binaryData)
	{
		require_once(DOM_XMLRPC_INCLUDE_PATH . 'dom_xmlrpc_base64.php');

		$base64 = new dom_xmlrpc_base64();
		$base64->fromBinary($binaryData);
		return $base64;
	} //createBase64

	/**
	 * Creates a dom_xmlrpc_base64 object from the file
	 *
	 * @param string The file path
	 *
	 * @return object The base64 encoded data
	 */
	function &createBase64FromFile($fileName)
	{
		require_once(DOM_XMLRPC_INCLUDE_PATH . 'dom_xmlrpc_base64.php');

		$base64 = new dom_xmlrpc_base64();
		$base64->fromFile($fileName);
		return $base64;
	} //createBase64FromFile

	/**
	 * Creates a dom_xmlrpc_datetime_iso8601 object from the PHP time
	 *
	 * @param string The time
	 *
	 * @return object The dom_xmlrpc_datetime_iso8601 object
	 */
	function &createDateTimeISO($time)
	{
		require_once(DOM_XMLRPC_INCLUDE_PATH . 'dom_xmlrpc_datetime_iso8601.php');

		$isoDateTime = new dom_xmlrpc_datetime_iso8601($time);

		return $isoDateTime;
	} //createDateTimeISO

} //dom_xmlrpc_builder
