]> git.andy128k.dev Git - ipf-xmlrpc.git/commitdiff
xmlrpc
authoravl <alex.litovchenko@gmail.com>
Mon, 29 Sep 2008 12:14:21 +0000 (15:14 +0300)
committeravl <alex.litovchenko@gmail.com>
Mon, 29 Sep 2008 12:14:21 +0000 (15:14 +0300)
21 files changed:
ipf/xmlrpc/fault.php [new file with mode: 0644]
ipf/xmlrpc/request.php [new file with mode: 0644]
ipf/xmlrpc/request/http.php [new file with mode: 0644]
ipf/xmlrpc/request/stdin.php [new file with mode: 0644]
ipf/xmlrpc/response.php [new file with mode: 0644]
ipf/xmlrpc/response/http.php [new file with mode: 0644]
ipf/xmlrpc/server.php [new file with mode: 0644]
ipf/xmlrpc/server/cache.php [new file with mode: 0644]
ipf/xmlrpc/server/fault.php [new file with mode: 0644]
ipf/xmlrpc/value.php [new file with mode: 0644]
ipf/xmlrpc/value/array.php [new file with mode: 0644]
ipf/xmlrpc/value/base64.php [new file with mode: 0644]
ipf/xmlrpc/value/boolean.php [new file with mode: 0644]
ipf/xmlrpc/value/collection.php [new file with mode: 0644]
ipf/xmlrpc/value/datetime.php [new file with mode: 0644]
ipf/xmlrpc/value/double.php [new file with mode: 0644]
ipf/xmlrpc/value/integer.php [new file with mode: 0644]
ipf/xmlrpc/value/nil.php [new file with mode: 0644]
ipf/xmlrpc/value/scalar.php [new file with mode: 0644]
ipf/xmlrpc/value/string.php [new file with mode: 0644]
ipf/xmlrpc/value/struct.php [new file with mode: 0644]

diff --git a/ipf/xmlrpc/fault.php b/ipf/xmlrpc/fault.php
new file mode 100644 (file)
index 0000000..7b1d7b7
--- /dev/null
@@ -0,0 +1,183 @@
+<?php
+
+class IPF_XmlRpc_Fault
+{
+    protected $_code;
+    protected $_encoding = 'UTF-8';
+    protected $_message;
+
+    protected $_internal = array(
+        404 => 'Unknown Error',
+
+        // 610 - 619 reflection errors
+        610 => 'Invalid method class',
+        611 => 'Unable to attach function or callback; not callable',
+        612 => 'Unable to load array; not an array',
+        613 => 'One or more method records are corrupt or otherwise unusable',
+
+        // 620 - 629 dispatch errors
+        620 => 'Method does not exist',
+        621 => 'Error instantiating class to invoke method',
+        622 => 'Method missing implementation',
+        623 => 'Calling parameters do not match signature',
+
+        // 630 - 639 request errors
+        630 => 'Unable to read request',
+        631 => 'Failed to parse request',
+        632 => 'Invalid request, no method passed; request must contain a \'methodName\' tag',
+        633 => 'Param must contain a value',
+        634 => 'Invalid method name',
+        635 => 'Invalid XML provided to request',
+        636 => 'Error creating xmlrpc value',
+
+        // 640 - 649 system.* errors
+        640 => 'Method does not exist',
+
+        // 650 - 659 response errors
+        650 => 'Invalid XML provided for response',
+        651 => 'Failed to parse response',
+        652 => 'Invalid response',
+        653 => 'Invalid XMLRPC value in response',
+    );
+
+    public function __construct($code = 404, $message = '')
+    {
+        $this->setCode($code);
+        $code = $this->getCode();
+
+        if (empty($message) && isset($this->_internal[$code])) {
+            $message = $this->_internal[$code];
+        } elseif (empty($message)) {
+            $message = 'Unknown error';
+        }
+        $this->setMessage($message);
+    }
+
+    public function setCode($code)
+    {
+        $this->_code = (int) $code;
+        return $this;
+    }
+
+    public function getCode()
+    {
+        return $this->_code;
+    }
+
+    public function setMessage($message)
+    {
+        $this->_message = (string) $message;
+        return $this;
+    }
+
+    public function getMessage()
+    {
+        return $this->_message;
+    }
+
+    public function setEncoding($encoding)
+    {
+        $this->_encoding = $encoding;
+        return $this;
+    }
+
+    public function getEncoding()
+    {
+        return $this->_encoding;
+    }
+
+    public function loadXml($fault)
+    {
+        if (!is_string($fault)) {
+            throw new IPF_Exception('Invalid XML provided to fault');
+        }
+
+        try {
+            $xml = @new SimpleXMLElement($fault);
+        } catch (Exception $e) {
+            // Not valid XML
+            throw new IPF_Exception('Failed to parse XML fault: ' .  $e->getMessage(), 500);
+        }
+
+        // Check for fault
+        if (!$xml->fault) {
+            // Not a fault
+            return false;
+        }
+
+        if (!$xml->fault->value->struct) {
+            // not a proper fault
+            throw new IPF_Exception('Invalid fault structure', 500);
+        }
+
+        $structXml = $xml->fault->value->asXML();
+        $structXml = preg_replace('/<\?xml version=.*?\?>/i', '', $structXml);
+        $struct    = IPF_XmlRpc_Value::getXmlRpcValue(trim($structXml), IPF_XmlRpc_Value::XML_STRING);
+        $struct    = $struct->getValue();
+
+        if (isset($struct['faultCode'])) {
+            $code = $struct['faultCode'];
+        }
+        if (isset($struct['faultString'])) {
+            $message = $struct['faultString'];
+        }
+
+        if (empty($code) && empty($message)) {
+            throw new IPF_Exception('Fault code and string required');
+        }
+
+        if (empty($code)) {
+            $code = '404';
+        }
+
+        if (empty($message)) {
+            if (isset($this->_internal[$code])) {
+                $message = $this->_internal[$code];
+            } else {
+                $message = 'Unknown Error';
+            }
+        }
+
+        $this->setCode($code);
+        $this->setMessage($message);
+
+        return true;
+    }
+
+    public static function isFault($xml)
+    {
+        $fault = new self();
+        try {
+            $isFault = $fault->loadXml($xml);
+        } catch (IPF_Exception $e) {
+            $isFault = false;
+        }
+
+        return $isFault;
+    }
+
+    public function saveXML()
+    {
+        // Create fault value
+        $faultStruct = array(
+            'faultCode'   => $this->getCode(),
+            'faultString' => $this->getMessage()
+        );
+        $value = IPF_XmlRpc_Value::getXmlRpcValue($faultStruct);
+        $valueDOM = new DOMDocument('1.0', $this->getEncoding());
+        $valueDOM->loadXML($value->saveXML());
+
+        // Build response XML
+        $dom  = new DOMDocument('1.0', 'ISO-8859-1');
+        $r    = $dom->appendChild($dom->createElement('methodResponse'));
+        $f    = $r->appendChild($dom->createElement('fault'));
+        $f->appendChild($dom->importNode($valueDOM->documentElement, 1));
+
+        return $dom->saveXML();
+    }
+
+    public function __toString()
+    {
+        return $this->saveXML();
+    }
+}
diff --git a/ipf/xmlrpc/request.php b/ipf/xmlrpc/request.php
new file mode 100644 (file)
index 0000000..39c4edc
--- /dev/null
@@ -0,0 +1,257 @@
+<?php
+
+class IPF_XmlRpc_Request
+{
+    protected $_encoding = 'UTF-8';
+    protected $_method;
+    protected $_xml;
+    protected $_params = array();
+    protected $_fault = null;
+    protected $_types = array();
+    protected $_xmlRpcParams = array();
+
+    public function __construct($method = null, $params = null)
+    {
+        if ($method !== null) {
+            $this->setMethod($method);
+        }
+
+        if ($params !== null) {
+            $this->setParams($params);
+        }
+    }
+
+    public function setEncoding($encoding)
+    {
+        $this->_encoding = $encoding;
+        return $this;
+    }
+
+    public function getEncoding()
+    {
+        return $this->_encoding;
+    }
+
+    public function setMethod($method)
+    {
+        if (!is_string($method) || !preg_match('/^[a-z0-9_.:\/]+$/i', $method)) {
+            $this->_fault = new IPF_XmlRpc_Fault(634, 'Invalid method name ("' . $method . '")');
+            $this->_fault->setEncoding($this->getEncoding());
+            return false;
+        }
+
+        $this->_method = $method;
+        return true;
+    }
+
+    public function getMethod()
+    {
+        return $this->_method;
+    }
+
+    public function addParam($value, $type = null)
+    {
+        $this->_params[] = $value;
+        if (null === $type) {
+            // Detect type if not provided explicitly
+            if ($value instanceof IPF_XmlRpc_Value) {
+                $type = $value->getType();
+            } else {
+                $xmlRpcValue = IPF_XmlRpc_Value::getXmlRpcValue($value);
+                $type        = $xmlRpcValue->getType();
+            }
+        }
+        $this->_types[]  = $type;
+        $this->_xmlRpcParams[] = array('value' => $value, 'type' => $type);
+    }
+
+    public function setParams()
+    {
+        $argc = func_num_args();
+        $argv = func_get_args();
+        if (0 == $argc) {
+            return;
+        }
+
+        if ((1 == $argc) && is_array($argv[0])) {
+            $params     = array();
+            $types      = array();
+            $wellFormed = true;
+            foreach ($argv[0] as $arg) {
+                if (!is_array($arg) || !isset($arg['value'])) {
+                    $wellFormed = false;
+                    break;
+                }
+                $params[] = $arg['value'];
+
+                if (!isset($arg['type'])) {
+                    $xmlRpcValue = IPF_XmlRpc_Value::getXmlRpcValue($arg['value']);
+                    $arg['type'] = $xmlRpcValue->getType();
+                }
+                $types[] = $arg['type'];
+            }
+            if ($wellFormed) {
+                $this->_xmlRpcParams = $argv[0];
+                $this->_params = $params;
+                $this->_types  = $types;
+            } else {
+                $this->_params = $argv[0];
+                $this->_types  = array();
+                $xmlRpcParams  = array();
+                foreach ($argv[0] as $arg) {
+                    if ($arg instanceof IPF_XmlRpc_Value) {
+                        $type = $arg->getType();
+                    } else {
+                        $xmlRpcValue = IPF_XmlRpc_Value::getXmlRpcValue($arg);
+                        $type        = $xmlRpcValue->getType();
+                    }
+                    $xmlRpcParams[] = array('value' => $arg, 'type' => $type);
+                    $this->_types[] = $type;
+                }
+                $this->_xmlRpcParams = $xmlRpcParams;
+            }
+            return;
+        }
+
+        $this->_params = $argv;
+        $this->_types  = array();
+        $xmlRpcParams  = array();
+        foreach ($argv as $arg) {
+            if ($arg instanceof IPF_XmlRpc_Value) {
+                $type = $arg->getType();
+            } else {
+                $xmlRpcValue = IPF_XmlRpc_Value::getXmlRpcValue($arg);
+                $type        = $xmlRpcValue->getType();
+            }
+            $xmlRpcParams[] = array('value' => $arg, 'type' => $type);
+            $this->_types[] = $type;
+        }
+        $this->_xmlRpcParams = $xmlRpcParams;
+    }
+
+    public function getParams()
+    {
+        return $this->_params;
+    }
+
+    public function getTypes()
+    {
+        return $this->_types;
+    }
+
+    public function loadXml($request)
+    {
+        if (!is_string($request)) {
+            $this->_fault = new IPF_XmlRpc_Fault(635);
+            $this->_fault->setEncoding($this->getEncoding());
+            return false;
+        }
+
+        try {
+            $xml = @new SimpleXMLElement($request);
+        } catch (Exception $e) {
+            // Not valid XML
+            $this->_fault = new IPF_XmlRpc_Fault(631);
+            $this->_fault->setEncoding($this->getEncoding());
+            return false;
+        }
+
+        // Check for method name
+        if (empty($xml->methodName)) {
+            // Missing method name
+            $this->_fault = new IPF_XmlRpc_Fault(632);
+            $this->_fault->setEncoding($this->getEncoding());
+            return false;
+        }
+
+        $this->_method = (string) $xml->methodName;
+
+        // Check for parameters
+        if (!empty($xml->params)) {
+            $types = array();
+            $argv  = array();
+            foreach ($xml->params->children() as $param) {
+                if (! $param->value instanceof SimpleXMLElement) {
+                    $this->_fault = new IPF_XmlRpc_Fault(633);
+                    $this->_fault->setEncoding($this->getEncoding());
+                    return false;
+                }
+
+                try {
+                    $param   = IPF_XmlRpc_Value::getXmlRpcValue($param->value, IPF_XmlRpc_Value::XML_STRING);
+                    $types[] = $param->getType();
+                    $argv[]  = $param->getValue();
+                } catch (Exception $e) {
+                    $this->_fault = new IPF_XmlRpc_Fault(636);
+                    $this->_fault->setEncoding($this->getEncoding());
+                    return false;
+                }
+            }
+
+            $this->_types  = $types;
+            $this->_params = $argv;
+        }
+
+        $this->_xml = $request;
+
+        return true;
+    }
+
+    public function isFault()
+    {
+        return $this->_fault instanceof IPF_XmlRpc_Fault;
+    }
+
+    public function getFault()
+    {
+        return $this->_fault;
+    }
+
+    protected function _getXmlRpcParams()
+    {
+        $params = array();
+        if (is_array($this->_xmlRpcParams)) {
+            foreach ($this->_xmlRpcParams as $param) {
+                $value = $param['value'];
+                $type  = isset($param['type']) ? $param['type'] : IPF_XmlRpc_Value::AUTO_DETECT_TYPE;
+
+                if (!$value instanceof IPF_XmlRpc_Value) {
+                    $value = IPF_XmlRpc_Value::getXmlRpcValue($value, $type);
+                }
+                $params[] = $value;
+            }
+        }
+
+        return $params;
+    }
+
+    public function saveXML()
+    {
+        $args   = $this->_getXmlRpcParams();
+        $method = $this->getMethod();
+
+        $dom = new DOMDocument('1.0', $this->getEncoding());
+        $mCall = $dom->appendChild($dom->createElement('methodCall'));
+        $mName = $mCall->appendChild($dom->createElement('methodName', $method));
+
+        if (is_array($args) && count($args)) {
+            $params = $mCall->appendChild($dom->createElement('params'));
+
+            foreach ($args as $arg) {
+                /* @var $arg IPF_XmlRpc_Value */
+                $argDOM = new DOMDocument('1.0', $this->getEncoding());
+                $argDOM->loadXML($arg->saveXML());
+
+                $param = $params->appendChild($dom->createElement('param'));
+                $param->appendChild($dom->importNode($argDOM->documentElement, 1));
+            }
+        }
+
+        return $dom->saveXML();
+    }
+
+    public function __toString()
+    {
+        return $this->saveXML();
+    }
+}
diff --git a/ipf/xmlrpc/request/http.php b/ipf/xmlrpc/request/http.php
new file mode 100644 (file)
index 0000000..f66114e
--- /dev/null
@@ -0,0 +1,57 @@
+<?php
+
+class IPF_XmlRpc_Request_Http extends IPF_XmlRpc_Request
+{
+    protected $_headers;
+    protected $_xml;
+    public function __construct()
+    {
+        $fh = fopen('php://input', 'r');
+        if (!$fh) {
+            $this->_fault = new IPF_Exception(630);
+            return;
+        }
+
+        $xml = '';
+        while (!feof($fh)) {
+            $xml .= fgets($fh);
+        }
+        fclose($fh);
+
+        $this->_xml = $xml;
+
+        $this->loadXml($xml);
+    }
+
+    public function getRawRequest()
+    {
+        return $this->_xml;
+    }
+
+    public function getHeaders()
+    {
+        if (null === $this->_headers) {
+            $this->_headers = array();
+            foreach ($_SERVER as $key => $value) {
+                if ('HTTP_' == substr($key, 0, 5)) {
+                    $header = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($key, 5)))));
+                    $this->_headers[$header] = $value;
+                }
+            }
+        }
+
+        return $this->_headers;
+    }
+
+    public function getFullRequest()
+    {
+        $request = '';
+        foreach ($this->getHeaders() as $key => $value) {
+            $request .= $key . ': ' . $value . "\n";
+        }
+
+        $request .= $this->_xml;
+
+        return $request;
+    }
+}
diff --git a/ipf/xmlrpc/request/stdin.php b/ipf/xmlrpc/request/stdin.php
new file mode 100644 (file)
index 0000000..0538d2b
--- /dev/null
@@ -0,0 +1,30 @@
+<?php
+
+class IPF_XmlRpc_Request_Stdin extends IPF_XmlRpc_Request
+{
+    protected $_xml;
+
+    public function __construct()
+    {
+        $fh = fopen('php://stdin', 'r');
+        if (!$fh) {
+            $this->_fault = new IPF_XmlRpc_Fault(630);
+            return;
+        }
+
+        $xml = '';
+        while (!feof($fh)) {
+            $xml .= fgets($fh);
+        }
+        fclose($fh);
+
+        $this->_xml = $xml;
+
+        $this->loadXml($xml);
+    }
+
+    public function getRawRequest()
+    {
+        return $this->_xml;
+    }
+}
diff --git a/ipf/xmlrpc/response.php b/ipf/xmlrpc/response.php
new file mode 100644 (file)
index 0000000..5f2f78a
--- /dev/null
@@ -0,0 +1,121 @@
+<?php
+
+class IPF_XmlRpc_Response
+{
+    protected $_return;
+    protected $_type;
+    protected $_encoding = 'UTF-8';
+    protected $_fault = null;
+
+    public function __construct($return = null, $type = null)
+    {
+        $this->setReturnValue($return, $type);
+    }
+
+    public function setEncoding($encoding)
+    {
+        $this->_encoding = $encoding;
+        return $this;
+    }
+
+    public function getEncoding()
+    {
+        return $this->_encoding;
+    }
+
+    public function setReturnValue($value, $type = null)
+    {
+        $this->_return = $value;
+        $this->_type = (string) $type;
+    }
+
+    public function getReturnValue()
+    {
+        return $this->_return;
+    }
+
+    protected function _getXmlRpcReturn()
+    {
+        return IPF_XmlRpc_Value::getXmlRpcValue($this->_return);
+    }
+
+    public function isFault()
+    {
+        return $this->_fault instanceof IPF_XmlRpc_Fault;
+    }
+
+    public function getFault()
+    {
+        return $this->_fault;
+    }
+
+    public function loadXml($response)
+    {
+        if (!is_string($response)) {
+            $this->_fault = new IPF_XmlRpc_Fault(650);
+            $this->_fault->setEncoding($this->getEncoding());
+            return false;
+        }
+
+        try {
+            $xml = @new SimpleXMLElement($response);
+        } catch (Exception $e) {
+            // Not valid XML
+            $this->_fault = new IPF_XmlRpc_Fault(651);
+            $this->_fault->setEncoding($this->getEncoding());
+            return false;
+        }
+
+        if (!empty($xml->fault)) {
+            // fault response
+            $this->_fault = new IPF_XmlRpc_Fault();
+            $this->_fault->setEncoding($this->getEncoding());
+            $this->_fault->loadXml($response);
+            return false;
+        }
+
+        if (empty($xml->params)) {
+            // Invalid response
+            $this->_fault = new IPF_XmlRpc_Fault(652);
+            $this->_fault->setEncoding($this->getEncoding());
+            return false;
+        }
+
+        try {
+            if (!isset($xml->params) || !isset($xml->params->param) || !isset($xml->params->param->value)) {
+                throw new IPF_Exception('Missing XML-RPC value in XML');
+            }
+            $valueXml = $xml->params->param->value->asXML();
+            $valueXml = preg_replace('/<\?xml version=.*?\?>/i', '', $valueXml);
+            $value = IPF_XmlRpc_Value::getXmlRpcValue(trim($valueXml), IPF_XmlRpc_Value::XML_STRING);
+        } catch (IPF_Exception $e) {
+            $this->_fault = new IPF_XmlRpc_Fault(653);
+            $this->_fault->setEncoding($this->getEncoding());
+            return false;
+        }
+
+        $this->setReturnValue($value->getValue());
+        return true;
+    }
+
+    public function saveXML()
+    {
+        $value = $this->_getXmlRpcReturn();
+        $valueDOM = new DOMDocument('1.0', $this->getEncoding());
+        $valueDOM->loadXML($value->saveXML());
+
+        $dom      = new DOMDocument('1.0', $this->getEncoding());
+        $response = $dom->appendChild($dom->createElement('methodResponse'));
+        $params   = $response->appendChild($dom->createElement('params'));
+        $param    = $params->appendChild($dom->createElement('param'));
+
+        $param->appendChild($dom->importNode($valueDOM->documentElement, true));
+
+        return $dom->saveXML();
+    }
+
+    public function __toString()
+    {
+        return $this->saveXML();
+    }
+}
diff --git a/ipf/xmlrpc/response/http.php b/ipf/xmlrpc/response/http.php
new file mode 100644 (file)
index 0000000..fa767b5
--- /dev/null
@@ -0,0 +1,12 @@
+<?php
+
+class IPF_XmlRpc_Response_Http extends IPF_XmlRpc_Response
+{
+    public function __toString()
+    {
+        if (!headers_sent()) {
+            header('Content-Type: text/xml; charset=' . strtolower($this->getEncoding()));
+        }
+        return parent::__toString();
+    }
+}
diff --git a/ipf/xmlrpc/server.php b/ipf/xmlrpc/server.php
new file mode 100644 (file)
index 0000000..a3a5a3a
--- /dev/null
@@ -0,0 +1,428 @@
+<?php
+
+class IPF_XmlRpc_Server
+{
+    protected $_encoding = 'UTF-8';
+    protected $_methods = array();
+    protected $_request = null;
+    protected $_responseClass = 'IPF_XmlRpc_Response_Http';
+
+    protected $_table = array();
+
+    protected $_typeMap = array(
+        'i4'               => 'i4',
+        'int'              => 'int',
+        'integer'          => 'int',
+        'double'           => 'double',
+        'float'            => 'double',
+        'real'             => 'double',
+        'boolean'          => 'boolean',
+        'bool'             => 'boolean',
+        'true'             => 'boolean',
+        'false'            => 'boolean',
+        'string'           => 'string',
+        'str'              => 'string',
+        'base64'           => 'base64',
+        'dateTime.iso8601' => 'dateTime.iso8601',
+        'date'             => 'dateTime.iso8601',
+        'time'             => 'dateTime.iso8601',
+        'time'             => 'dateTime.iso8601',
+        'array'            => 'array',
+        'struct'           => 'struct',
+        'null'             => 'nil',
+        'nil'              => 'nil',
+        'void'             => 'void',
+        'mixed'            => 'struct'
+    );
+
+    public function __construct()
+    {
+        // Setup system.* methods
+        $system = array(
+            'listMethods',
+            'methodHelp',
+            'methodSignature',
+            'multicall'
+        );
+
+        $class = IPF_Server_Reflection::reflectClass($this);
+        foreach ($system as $method) {
+            $reflection = new IPF_Server_Reflection_Method($class, new ReflectionMethod($this, $method), 'system');
+            $reflection->system = true;
+            $this->_methods[] = $reflection;
+        }
+
+        $this->_buildDispatchTable();
+    }
+
+    protected function _fixTypes(IPF_Server_Reflection_Function_Abstract $method)
+    {
+        foreach ($method->getPrototypes() as $prototype) {
+            foreach ($prototype->getParameters() as $param) {
+                $pType = $param->getType();
+                if (isset($this->_typeMap[$pType])) {
+                    $param->setType($this->_typeMap[$pType]);
+                } else {
+                    $param->setType('void');
+                }
+            }
+        }
+    }
+
+    protected function _buildDispatchTable()
+    {
+        $table      = array();
+        foreach ($this->_methods as $dispatchable) {
+            if ($dispatchable instanceof IPF_Server_Reflection_Function_Abstract) {
+                // function/method call
+                $ns   = $dispatchable->getNamespace();
+                $name = $dispatchable->getName();
+                $name = empty($ns) ? $name : $ns . '.' . $name;
+
+                if (isset($table[$name])) {
+                    throw new IPF_Exception('Duplicate method registered: ' . $name);
+                }
+                $table[$name] = $dispatchable;
+                $this->_fixTypes($dispatchable);
+
+                continue;
+            }
+
+            if ($dispatchable instanceof IPF_Server_Reflection_Class) {
+                foreach ($dispatchable->getMethods() as $method) {
+                    $ns   = $method->getNamespace();
+                    $name = $method->getName();
+                    $name = empty($ns) ? $name : $ns . '.' . $name;
+
+                    if (isset($table[$name])) {
+                        throw new IPF_Exception('Duplicate method registered: ' . $name);
+                    }
+                    $table[$name] = $method;
+                    $this->_fixTypes($method);
+                    continue;
+                }
+            }
+        }
+
+        $this->_table = $table;
+    }
+
+    public function setEncoding($encoding)
+    {
+        $this->_encoding = $encoding;
+        return $this;
+    }
+
+    public function getEncoding()
+    {
+        return $this->_encoding;
+    }
+
+    public function addFunction($function, $namespace = '')
+    {
+        if (!is_string($function) && !is_array($function)) {
+            throw new IPF_Exception('Unable to attach function; invalid', 611);
+        }
+
+        $argv = null;
+        if (2 < func_num_args()) {
+            $argv = func_get_args();
+            $argv = array_slice($argv, 2);
+        }
+
+        $function = (array) $function;
+        foreach ($function as $func) {
+            if (!is_string($func) || !function_exists($func)) {
+                throw new IPF_Exception('Unable to attach function; invalid', 611);
+            }
+            $this->_methods[] = IPF_Server_Reflection::reflectFunction($func, $argv, $namespace);
+        }
+
+        $this->_buildDispatchTable();
+    }
+
+    public function loadFunctions($array)
+    {
+        if (!is_array($array)) {
+            throw new IPF_Exception('Unable to load array; not an array', 612);
+        }
+
+        foreach ($array as $key => $value) {
+            if (!$value instanceof IPF_Server_Reflection_Function_Abstract
+                && !$value instanceof IPF_Server_Reflection_Class)
+            {
+                throw new IPF_Exception('One or more method records are corrupt or otherwise unusable', 613);
+            }
+
+            if ($value->system) {
+                unset($array[$key]);
+            }
+        }
+
+        foreach ($array as $dispatchable) {
+            $this->_methods[] = $dispatchable;
+        }
+
+        $this->_buildDispatchTable();
+    }
+
+    public function setPersistence($class = null)
+    {
+    }
+
+    public function setClass($class, $namespace = '', $argv = null)
+    {
+        if (is_string($class) && !class_exists($class)) {
+            if (!class_exists($class)) {
+                throw new IPF_Exception('Invalid method class', 610);
+            }
+        }
+
+        $argv = null;
+        if (3 < func_num_args()) {
+            $argv = func_get_args();
+            $argv = array_slice($argv, 3);
+        }
+
+        $this->_methods[] = IPF_Reflection::reflectClass($class, $argv, $namespace);
+        $this->_buildDispatchTable();
+    }
+
+    public function setRequest($request)
+    {
+        if (is_string($request) && class_exists($request)) {
+            $request = new $request();
+            if (!$request instanceof IPF_XmlRpc_Request) {
+                throw new IPF_Exception('Invalid request class');
+            }
+            $request->setEncoding($this->getEncoding());
+        } elseif (!$request instanceof IPF_XmlRpc_Request) {
+            throw new IPF_Exception('Invalid request object');
+        }
+
+        $this->_request = $request;
+        return $this;
+    }
+
+    public function getRequest()
+    {
+        return $this->_request;
+    }
+
+    public function fault($fault, $code = 404)
+    {
+        if (!$fault instanceof Exception) {
+            $fault = (string) $fault;
+            $fault = new IPF_Exception($fault, $code);
+        }
+        return IPF_XmlRpc_Server_Fault::getInstance($fault);
+    }
+
+    protected function _handle(IPF_XmlRpc_Request $request)
+    {
+        $method = $request->getMethod();
+
+        // Check for valid method
+        if (!isset($this->_table[$method])) {
+            throw new IPF_Exception('Method "' . $method . '" does not exist', 620);
+        }
+
+        $info     = $this->_table[$method];
+        $params   = $request->getParams();
+        $argv     = $info->getInvokeArguments();
+        if (0 < count($argv)) {
+            $params = array_merge($params, $argv);
+        }
+
+        // Check calling parameters against signatures
+        $matched    = false;
+        $sigCalled  = $request->getTypes();
+
+        $sigLength  = count($sigCalled);
+        $paramsLen  = count($params);
+        if ($sigLength < $paramsLen) {
+            for ($i = $sigLength; $i < $paramsLen; ++$i) {
+                $xmlRpcValue = IPF_XmlRpc_Value::getXmlRpcValue($params[$i]);
+                $sigCalled[] = $xmlRpcValue->getType();
+            }
+        }
+
+        $signatures = $info->getPrototypes();
+        foreach ($signatures as $signature) {
+            $sigParams = $signature->getParameters();
+            $tmpParams = array();
+            foreach ($sigParams as $param) {
+                $tmpParams[] = $param->getType();
+            }
+            if ($sigCalled === $tmpParams) {
+                $matched = true;
+                break;
+            }
+        }
+        if (!$matched) {
+            throw new IPF_Exception('Calling parameters do not match signature', 623);
+        }
+
+        if ($info instanceof IPF_Server_Reflection_Function) {
+            $func = $info->getName();
+            $return = call_user_func_array($func, $params);
+        } elseif (($info instanceof IPF_Server_Reflection_Method) && $info->system) {
+            // System methods
+            $return = $info->invokeArgs($this, $params);
+        } elseif ($info instanceof IPF_Server_Reflection_Method) {
+            // Get class
+            $class = $info->getDeclaringClass()->getName();
+
+            if ('static' == $info->isStatic()) {
+                // for some reason, invokeArgs() does not work the same as
+                // invoke(), and expects the first argument to be an object.
+                // So, using a callback if the method is static.
+                $return = call_user_func_array(array($class, $info->getName()), $params);
+            } else {
+                // Object methods
+                try {
+                    $object = $info->getDeclaringClass()->newInstance();
+                } catch (Exception $e) {
+                    throw new IPF_Exception('Error instantiating class ' . $class . ' to invoke method ' . $info->getName(), 621);
+                }
+
+                $return = $info->invokeArgs($object, $params);
+            }
+        } else {
+            throw new IPF_Exception('Method missing implementation ' . get_class($info), 622);
+        }
+
+        $response = new ReflectionClass($this->_responseClass);
+        return $response->newInstance($return);
+    }
+
+    public function handle(IPF_XmlRpc_Request $request = null)
+    {
+        // Get request
+        if ((null === $request) && (null === ($request = $this->getRequest()))) {
+            $request = new IPF_XmlRpc_Request_Http();
+            $request->setEncoding($this->getEncoding());
+        }
+
+        $this->setRequest($request);
+
+        if ($request->isFault()) {
+            $response = $request->getFault();
+        } else {
+            try {
+                $response = $this->_handle($request);
+            } catch (Exception $e) {
+                $response = $this->fault($e);
+            }
+        }
+
+        // Set output encoding
+        $response->setEncoding($this->getEncoding());
+
+        return $response;
+    }
+
+    public function setResponseClass($class)
+    {
+        if (class_exists($class)) {
+            $reflection = new ReflectionClass($class);
+            if ($reflection->isSubclassOf(new ReflectionClass('IPF_XmlRpc_Response'))) {
+                $this->_responseClass = $class;
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    public function getFunctions()
+    {
+        $return = array();
+        foreach ($this->_methods as $method) {
+            if ($method instanceof IPF_Server_Reflection_Class
+                && ($method->system))
+            {
+                continue;
+            }
+
+            $return[] = $method;
+        }
+
+        return $return;
+    }
+
+    public function listMethods()
+    {
+        return array_keys($this->_table);
+    }
+
+    public function methodHelp($method)
+    {
+        if (!isset($this->_table[$method])) {
+            throw new IPF_Exception('Method "' . $method . '"does not exist', 640);
+        }
+
+        return $this->_table[$method]->getDescription();
+    }
+
+    public function methodSignature($method)
+    {
+        if (!isset($this->_table[$method])) {
+            throw new IPF_Exception('Method "' . $method . '"does not exist', 640);
+        }
+        $prototypes = $this->_table[$method]->getPrototypes();
+
+        $signatures = array();
+        foreach ($prototypes as $prototype) {
+            $signature = array($prototype->getReturnType());
+            foreach ($prototype->getParameters() as $parameter) {
+                $signature[] = $parameter->getType();
+            }
+            $signatures[] = $signature;
+        }
+
+        return $signatures;
+    }
+
+    public function multicall($methods)
+    {
+        $responses = array();
+        foreach ($methods as $method) {
+            $fault = false;
+            if (!is_array($method)) {
+                $fault = $this->fault('system.multicall expects each method to be a struct', 601);
+            } elseif (!isset($method['methodName'])) {
+                $fault = $this->fault('Missing methodName', 602);
+            } elseif (!isset($method['params'])) {
+                $fault = $this->fault('Missing params', 603);
+            } elseif (!is_array($method['params'])) {
+                $fault = $this->fault('Params must be an array', 604);
+            } else {
+                if ('system.multicall' == $method['methodName']) {
+                    // don't allow recursive calls to multicall
+                    $fault = $this->fault('Recursive system.multicall forbidden', 605);
+                }
+            }
+
+            if (!$fault) {
+                try {
+                    $request = new IPF_XmlRpc_Request();
+                    $request->setMethod($method['methodName']);
+                    $request->setParams($method['params']);
+                    $response = $this->_handle($request);
+                    $responses[] = $response->getReturnValue();
+                } catch (Exception $e) {
+                    $fault = $this->fault($e);
+                }
+            }
+
+            if ($fault) {
+                $responses[] = array(
+                    'faultCode'   => $fault->getCode(),
+                    'faultString' => $fault->getMessage()
+                );
+            }
+        }
+        return $responses;
+    }
+}
diff --git a/ipf/xmlrpc/server/cache.php b/ipf/xmlrpc/server/cache.php
new file mode 100644 (file)
index 0000000..aa6c7e7
--- /dev/null
@@ -0,0 +1,60 @@
+<?php
+
+class IPF_XmlRpc_Server_Cache
+{
+    public static function save($filename, IPF_XmlRpc_Server $server)
+    {
+        if (!is_string($filename)
+            || (!file_exists($filename) && !is_writable(dirname($filename))))
+        {
+            return false;
+        }
+
+        // Remove system.* methods
+        $methods = $server->getFunctions();
+        foreach ($methods as $name => $method) {
+            if ($method->system) {
+                unset($methods[$name]);
+            }
+        }
+
+        // Store
+        if (0 === @file_put_contents($filename, serialize($methods))) {
+            return false;
+        }
+
+        return true;
+    }
+
+    public static function get($filename, IPF_XmlRpc_Server $server)
+    {
+        if (!is_string($filename)
+            || !file_exists($filename)
+            || !is_readable($filename))
+        {
+            return false;
+        }
+
+        if (false === ($dispatch = @file_get_contents($filename))) {
+            return false;
+        }
+
+        if (false === ($dispatchArray = @unserialize($dispatch))) {
+            return false;
+        }
+
+        $server->loadFunctions($dispatchArray);
+
+        return true;
+    }
+
+    public static function delete($filename)
+    {
+        if (is_string($filename) && file_exists($filename)) {
+            unlink($filename);
+            return true;
+        }
+
+        return false;
+    }
+}
diff --git a/ipf/xmlrpc/server/fault.php b/ipf/xmlrpc/server/fault.php
new file mode 100644 (file)
index 0000000..50db5ed
--- /dev/null
@@ -0,0 +1,95 @@
+<?php
+
+class IPF_XmlRpc_Server_Fault extends IPF_XmlRpc_Fault
+{
+    protected $_exception;
+    protected static $_faultExceptionClasses = array('IPF_Exception' => true);
+    protected static $_observers = array();
+
+    public function __construct(Exception $e)
+    {
+        $this->_exception = $e;
+        $code             = 404;
+        $message          = 'Unknown error';
+        $exceptionClass   = get_class($e);
+
+        foreach (array_keys(self::$_faultExceptionClasses) as $class) {
+            if ($e instanceof $class) {
+                $code    = $e->getCode();
+                $message = $e->getMessage();
+                break;
+            }
+        }
+
+        parent::__construct($code, $message);
+
+        // Notify exception observers, if present
+        if (!empty(self::$_observers)) {
+            foreach (array_keys(self::$_observers) as $observer) {
+                call_user_func(array($observer, 'observe'), $this);
+            }
+        }
+    }
+
+    public static function getInstance(Exception $e)
+    {
+        return new self($e);
+    }
+
+    public static function attachFaultException($classes)
+    {
+        if (!is_array($classes)) {
+            $classes = (array) $classes;
+        }
+
+        foreach ($classes as $class) {
+            if (is_string($class) && class_exists($class)) {
+                self::$_faultExceptionClasses[$class] = true;
+            }
+        }
+    }
+
+    public static function detachFaultException($classes)
+    {
+        if (!is_array($classes)) {
+            $classes = (array) $classes;
+        }
+
+        foreach ($classes as $class) {
+            if (is_string($class) && isset(self::$_faultExceptionClasses[$class])) {
+                unset(self::$_faultExceptionClasses[$class]);
+            }
+        }
+    }
+
+    public static function attachObserver($class)
+    {
+        if (!is_string($class)
+            || !class_exists($class)
+            || !is_callable(array($class, 'observe')))
+        {
+            return false;
+        }
+
+        if (!isset(self::$_observers[$class])) {
+            self::$_observers[$class] = true;
+        }
+
+        return true;
+    }
+
+    public static function detachObserver($class)
+    {
+        if (!isset(self::$_observers[$class])) {
+            return false;
+        }
+
+        unset(self::$_observers[$class]);
+        return true;
+    }
+
+    public function getException()
+    {
+        return $this->_exception;
+    }
+}
diff --git a/ipf/xmlrpc/value.php b/ipf/xmlrpc/value.php
new file mode 100644 (file)
index 0000000..5201bed
--- /dev/null
@@ -0,0 +1,240 @@
+<?php
+
+abstract class IPF_XmlRpc_Value
+{
+    protected $_value;
+    protected $_type;
+    protected $_as_xml;
+    protected $_as_dom;
+
+    const AUTO_DETECT_TYPE = 'auto_detect';
+    const XML_STRING = 'xml';
+
+    const XMLRPC_TYPE_I4       = 'i4';
+    const XMLRPC_TYPE_INTEGER  = 'int';
+    const XMLRPC_TYPE_DOUBLE   = 'double';
+    const XMLRPC_TYPE_BOOLEAN  = 'boolean';
+    const XMLRPC_TYPE_STRING   = 'string';
+    const XMLRPC_TYPE_DATETIME = 'dateTime.iso8601';
+    const XMLRPC_TYPE_BASE64   = 'base64';
+    const XMLRPC_TYPE_ARRAY    = 'array';
+    const XMLRPC_TYPE_STRUCT   = 'struct';
+    const XMLRPC_TYPE_NIL      = 'nil';
+
+    public function getType()
+    {
+        return $this->_type;
+    }
+
+    abstract public function getValue();
+    abstract public function saveXML();
+
+    public function getAsDOM()
+    {
+        if (!$this->_as_dom) {
+            $doc = new DOMDocument('1.0');
+            $doc->loadXML($this->saveXML());
+            $this->_as_dom = $doc->documentElement;
+        }
+
+        return $this->_as_dom;
+    }
+
+    protected function _stripXmlDeclaration(DOMDocument $dom)
+    {
+        return preg_replace('/<\?xml version="1.0"( encoding="[^\"]*")?\?>\n/u', '', $dom->saveXML());
+    }
+
+    public static function getXmlRpcValue($value, $type = self::AUTO_DETECT_TYPE)
+    {
+        switch ($type) {
+            case self::AUTO_DETECT_TYPE:
+                // Auto detect the XML-RPC native type from the PHP type of $value
+                return self::_phpVarToNativeXmlRpc($value);
+
+            case self::XML_STRING:
+                // Parse the XML string given in $value and get the XML-RPC value in it
+                return self::_xmlStringToNativeXmlRpc($value);
+
+            case self::XMLRPC_TYPE_I4:
+                // fall through to the next case
+            case self::XMLRPC_TYPE_INTEGER:
+                return new IPF_XmlRpc_Value_Integer($value);
+
+            case self::XMLRPC_TYPE_DOUBLE:
+                return new IPF_XmlRpc_Value_Double($value);
+
+            case self::XMLRPC_TYPE_BOOLEAN:
+                return new IPF_XmlRpc_Value_Boolean($value);
+
+            case self::XMLRPC_TYPE_STRING:
+                return new IPF_XmlRpc_Value_String($value);
+
+            case self::XMLRPC_TYPE_BASE64:
+                return new IPF_XmlRpc_Value_Base64($value);
+
+            case self::XMLRPC_TYPE_NIL:
+                return new IPF_XmlRpc_Value_Nil();
+
+            case self::XMLRPC_TYPE_DATETIME:
+                return new IPF_XmlRpc_Value_DateTime($value);
+
+            case self::XMLRPC_TYPE_ARRAY:
+                return new IPF_XmlRpc_Value_Array($value);
+
+            case self::XMLRPC_TYPE_STRUCT:
+                return new IPF_XmlRpc_Value_Struct($value);
+
+            default:
+                throw new IPF_Exception('Given type is not a '. __CLASS__ .' constant');
+        }
+    }
+
+    private static function _phpVarToNativeXmlRpc($value)
+    {
+        switch (gettype($value)) {
+            case 'object':
+                // Check to see if it's an XmlRpc value
+                if ($value instanceof IPF_XmlRpc_Value) {
+                    return $value;
+                }
+                
+                // Otherwise, we convert the object into a struct
+                $value = get_object_vars($value);
+                // Break intentionally omitted
+            case 'array':
+                // Default native type for a PHP array (a simple numeric array) is 'array'
+                $obj = 'IPF_XmlRpc_Value_Array';
+
+                // Determine if this is an associative array
+                if (!empty($value) && is_array($value) && (array_keys($value) !== range(0, count($value) - 1))) {
+                    $obj = 'IPF_XmlRpc_Value_Struct';
+                }
+                return new $obj($value);
+
+            case 'integer':
+                return new IPF_XmlRpc_Value_Integer($value);
+
+            case 'double':
+                return new IPF_XmlRpc_Value_Double($value);
+
+            case 'boolean':
+                return new IPF_XmlRpc_Value_Boolean($value);
+
+            case 'NULL':
+            case 'null':
+                return new IPF_XmlRpc_Value_Nil();
+
+            case 'string':
+                // Fall through to the next case
+            default:
+                // If type isn't identified (or identified as string), it treated as string
+                return new IPF_XmlRpc_Value_String($value);
+        }
+    }
+
+    private static function _xmlStringToNativeXmlRpc($simple_xml)
+    {
+        if (!$simple_xml instanceof SimpleXMLElement) {
+            try {
+                $simple_xml = @new SimpleXMLElement($simple_xml);
+            } catch (Exception $e) {
+                // The given string is not a valid XML
+                throw new IPF_Exception('Failed to create XML-RPC value from XML string: '.$e->getMessage(),$e->getCode());
+            }
+        }
+
+        // Get the key (tag name) and value from the simple xml object and convert the value to an XML-RPC native value
+        list($type, $value) = each($simple_xml);
+        if (!$type) {    // If no type was specified, the default is string
+            $type = self::XMLRPC_TYPE_STRING;
+        }
+
+        switch ($type) {
+            // All valid and known XML-RPC native values
+            case self::XMLRPC_TYPE_I4:
+                // Fall through to the next case
+            case self::XMLRPC_TYPE_INTEGER:
+                $xmlrpc_val = new IPF_XmlRpc_Value_Integer($value);
+                break;
+            case self::XMLRPC_TYPE_DOUBLE:
+                $xmlrpc_val = new IPF_XmlRpc_Value_Double($value);
+                break;
+            case self::XMLRPC_TYPE_BOOLEAN:
+                $xmlrpc_val = new IPF_XmlRpc_Value_Boolean($value);
+                break;
+            case self::XMLRPC_TYPE_STRING:
+                $xmlrpc_val = new IPF_XmlRpc_Value_String($value);
+                break;
+            case self::XMLRPC_TYPE_DATETIME:  // The value should already be in a iso8601 format
+                $xmlrpc_val = new IPF_XmlRpc_Value_DateTime($value);
+                break;
+            case self::XMLRPC_TYPE_BASE64:    // The value should already be base64 encoded
+                $xmlrpc_val = new IPF_XmlRpc_Value_Base64($value ,true);
+                break;
+            case self::XMLRPC_TYPE_NIL:    // The value should always be NULL
+                $xmlrpc_val = new IPF_XmlRpc_Value_Nil();
+                break;
+            case self::XMLRPC_TYPE_ARRAY:
+                // If the XML is valid, $value must be an SimpleXML element and contain the <data> tag
+                if (!$value instanceof SimpleXMLElement) {
+                    throw new IPF_Exception('XML string is invalid for XML-RPC native '. self::XMLRPC_TYPE_ARRAY .' type');
+                } 
+
+                // PHP 5.2.4 introduced a regression in how empty($xml->value) 
+                // returns; need to look for the item specifically
+                $data = null;
+                foreach ($value->children() as $key => $value) {
+                    if ('data' == $key) {
+                        $data = $value;
+                        break;
+                    }
+                }
+                
+                if (null === $data) {
+                    throw new IPF_Exception('Invalid XML for XML-RPC native '. self::XMLRPC_TYPE_ARRAY .' type: ARRAY tag must contain DATA tag');
+                }
+                $values = array();
+                // Parse all the elements of the array from the XML string
+                // (simple xml element) to IPF_XmlRpc_Value objects
+                foreach ($data->value as $element) {
+                    $values[] = self::_xmlStringToNativeXmlRpc($element);
+                }
+                $xmlrpc_val = new IPF_XmlRpc_Value_Array($values);
+                break;
+            case self::XMLRPC_TYPE_STRUCT:
+                // If the XML is valid, $value must be an SimpleXML
+                if ((!$value instanceof SimpleXMLElement)) {
+                    throw new IPF_Exception('XML string is invalid for XML-RPC native '. self::XMLRPC_TYPE_STRUCT .' type');
+                }
+                $values = array();
+                // Parse all the memebers of the struct from the XML string
+                // (simple xml element) to IPF_XmlRpc_Value objects
+                foreach ($value->member as $member) {
+                    // @todo? If a member doesn't have a <value> tag, we don't add it to the struct
+                    // Maybe we want to throw an exception here ?
+                    if ((!$member->value instanceof SimpleXMLElement) || empty($member->value)) {
+                        continue;
+                        //throw new IPF_XmlRpc_Value_Exception('Member of the '. self::XMLRPC_TYPE_STRUCT .' XML-RPC native type must contain a VALUE tag');
+                    }
+                    $values[(string)$member->name] = self::_xmlStringToNativeXmlRpc($member->value);
+                }
+                $xmlrpc_val = new IPF_XmlRpc_Value_Struct($values);
+                break;
+            default:
+                throw new IPF_Exception('Value type \''. $type .'\' parsed from the XML string is not a known XML-RPC native type');
+                break;
+        }
+        $xmlrpc_val->_setXML($simple_xml->asXML());
+
+        return $xmlrpc_val;
+    }
+
+    private function _setXML($xml)
+    {
+        $this->_as_xml = $xml;
+    }
+
+}
+
+
diff --git a/ipf/xmlrpc/value/array.php b/ipf/xmlrpc/value/array.php
new file mode 100644 (file)
index 0000000..ead3419
--- /dev/null
@@ -0,0 +1,33 @@
+<?php
+
+class IPF_XmlRpc_Value_Array extends IPF_XmlRpc_Value_Collection
+{
+    public function __construct($value)
+    {
+        $this->_type = self::XMLRPC_TYPE_ARRAY;
+        parent::__construct($value);
+    }
+
+    public function saveXML()
+    {
+        if (!$this->_as_xml) {   // The XML code was not calculated yet
+            $dom   = new DOMDocument('1.0');
+            $value = $dom->appendChild($dom->createElement('value'));
+            $array = $value->appendChild($dom->createElement('array'));
+            $data  = $array->appendChild($dom->createElement('data'));
+
+            if (is_array($this->_value)) {
+                foreach ($this->_value as $val) {
+                    /* @var $val IPF_XmlRpc_Value */
+                    $data->appendChild($dom->importNode($val->getAsDOM(), true));
+                }
+            }
+
+            $this->_as_dom = $value;
+            $this->_as_xml = $this->_stripXmlDeclaration($dom);
+        }
+
+        return $this->_as_xml;
+    }
+}
+
diff --git a/ipf/xmlrpc/value/base64.php b/ipf/xmlrpc/value/base64.php
new file mode 100644 (file)
index 0000000..0583f9f
--- /dev/null
@@ -0,0 +1,36 @@
+<?php
+
+class IPF_XmlRpc_Value_Base64 extends IPF_XmlRpc_Value_Scalar
+{
+    public function __construct($value, $already_encoded=false)
+    {
+        $this->_type = self::XMLRPC_TYPE_BASE64;
+
+        $value = (string)$value;    // Make sure this value is string
+        if (!$already_encoded) {
+            $value = base64_encode($value);     // We encode it in base64
+        }
+        $this->_value = $value;
+    }
+
+    public function getValue()
+    {
+        return base64_decode($this->_value);
+    }
+
+    public function saveXML()
+    {
+        if (! $this->_as_xml) {   // The XML was not generated yet
+            $dom   = new DOMDocument('1.0', 'UTF-8');
+            $value = $dom->appendChild($dom->createElement('value'));
+            $type  = $value->appendChild($dom->createElement($this->_type));
+            $type->appendChild($dom->createTextNode($this->_value));
+
+            $this->_as_dom = $value;
+            $this->_as_xml = $this->_stripXmlDeclaration($dom);
+        }
+
+        return $this->_as_xml;
+    }
+}
+
diff --git a/ipf/xmlrpc/value/boolean.php b/ipf/xmlrpc/value/boolean.php
new file mode 100644 (file)
index 0000000..c0cf52c
--- /dev/null
@@ -0,0 +1,31 @@
+<?php
+
+class IPF_XmlRpc_Value_Boolean extends IPF_XmlRpc_Value_Scalar
+{
+    public function __construct($value)
+    {
+        $this->_type = self::XMLRPC_TYPE_BOOLEAN;
+        $this->_value = (int)(bool)$value;
+    }
+
+    public function getValue()
+    {
+        return (bool)$this->_value;
+    }
+
+    public function saveXML()
+    {
+        if (! $this->_as_xml) {   // The XML was not generated yet
+            $dom   = new DOMDocument('1.0', 'UTF-8');
+            $value = $dom->appendChild($dom->createElement('value'));
+            $type  = $value->appendChild($dom->createElement($this->_type));
+            $type->appendChild($dom->createTextNode($this->_value));
+
+            $this->_as_dom = $value;
+            $this->_as_xml = $this->_stripXmlDeclaration($dom);
+        }
+
+        return $this->_as_xml;
+    }
+}
+
diff --git a/ipf/xmlrpc/value/collection.php b/ipf/xmlrpc/value/collection.php
new file mode 100644 (file)
index 0000000..a720f10
--- /dev/null
@@ -0,0 +1,32 @@
+<?php
+
+abstract class IPF_XmlRpc_Value_Collection extends IPF_XmlRpc_Value
+{
+    public function __construct($value)
+    {
+        $values = (array)$value;   // Make sure that the value is an array
+        foreach ($values as $key => $value) {
+            // If the elements of the given array are not IPF_XmlRpc_Value objects,
+            // we need to convert them as such (using auto-detection from PHP value)
+            if (!$value instanceof parent) {
+                $value = self::getXmlRpcValue($value, self::AUTO_DETECT_TYPE);
+            }
+            $this->_value[$key] = $value;
+        }
+    }
+
+    public function getValue()
+    {
+        $values = (array)$this->_value;
+        foreach ($values as $key => $value) {
+            /* @var $value IPF_XmlRpc_Value */
+            if (!$value instanceof parent) {
+                throw new IPF_Exception('Values of '. get_class($this) .' type must be IPF_XmlRpc_Value objects');
+            }
+            $values[$key] = $value->getValue();
+        }
+        return $values;
+    }
+
+}
+
diff --git a/ipf/xmlrpc/value/datetime.php b/ipf/xmlrpc/value/datetime.php
new file mode 100644 (file)
index 0000000..569cd75
--- /dev/null
@@ -0,0 +1,33 @@
+<?php
+
+class IPF_XmlRpc_Value_DateTime extends IPF_XmlRpc_Value_Scalar
+{
+    public function __construct($value)
+    {
+        $this->_type = self::XMLRPC_TYPE_DATETIME;
+
+        // If the value is not numeric, we try to convert it to a timestamp (using the strtotime function)
+        if (is_numeric($value)) {   // The value is numeric, we make sure it is an integer
+            $value = (int)$value;
+        } else {
+            $value = strtotime($value);
+            if ($value === false || $value == -1) { // cannot convert the value to a timestamp
+                throw new IPF_Exception('Cannot convert given value \''. $value .'\' to a timestamp');
+            }
+        }
+        $value = date('c', $value); // Convert the timestamp to iso8601 format
+
+        // Strip out TZ information and dashes
+        $value = preg_replace('/(\+|-)\d{2}:\d{2}$/', '', $value);
+        $value = str_replace('-', '', $value);
+
+        $this->_value = $value;
+    }
+
+    public function getValue()
+    {
+        return $this->_value;
+    }
+
+}
+
diff --git a/ipf/xmlrpc/value/double.php b/ipf/xmlrpc/value/double.php
new file mode 100644 (file)
index 0000000..e0a8536
--- /dev/null
@@ -0,0 +1,17 @@
+<?php
+
+class IPF_XmlRpc_Value_Double extends IPF_XmlRpc_Value_Scalar
+{
+    public function __construct($value)
+    {
+        $this->_type = self::XMLRPC_TYPE_DOUBLE;
+        $this->_value = sprintf('%f',(float)$value);    // Make sure this value is float (double) and without the scientific notation
+    }
+
+    public function getValue()
+    {
+        return (float)$this->_value;
+    }
+
+}
+
diff --git a/ipf/xmlrpc/value/integer.php b/ipf/xmlrpc/value/integer.php
new file mode 100644 (file)
index 0000000..5de4c8d
--- /dev/null
@@ -0,0 +1,17 @@
+<?php
+
+class IPF_XmlRpc_Value_Integer extends IPF_XmlRpc_Value_Scalar
+{
+    public function __construct($value)
+    {
+        $this->_type = self::XMLRPC_TYPE_INTEGER;
+        $this->_value = (int)$value;    // Make sure this value is integer
+    }
+
+    public function getValue()
+    {
+        return $this->_value;
+    }
+
+}
+
diff --git a/ipf/xmlrpc/value/nil.php b/ipf/xmlrpc/value/nil.php
new file mode 100644 (file)
index 0000000..e04c670
--- /dev/null
@@ -0,0 +1,30 @@
+<?php
+
+class IPF_XmlRpc_Value_Nil extends IPF_XmlRpc_Value_Scalar
+{
+    public function __construct()
+    {
+        $this->_type = self::XMLRPC_TYPE_NIL;
+        $this->_value = null;
+    }
+
+    public function getValue()
+    {
+        return null;
+    }
+
+    public function saveXML()
+    {
+        if (! $this->_as_xml) {   // The XML was not generated yet
+            $dom   = new DOMDocument('1.0', 'UTF-8');
+            $value = $dom->appendChild($dom->createElement('value'));
+            $type  = $value->appendChild($dom->createElement($this->_type));
+
+            $this->_as_dom = $value;
+            $this->_as_xml = $this->_stripXmlDeclaration($dom);
+        }
+
+        return $this->_as_xml;
+    }
+}
+
diff --git a/ipf/xmlrpc/value/scalar.php b/ipf/xmlrpc/value/scalar.php
new file mode 100644 (file)
index 0000000..a950fb5
--- /dev/null
@@ -0,0 +1,20 @@
+<?php
+
+abstract class IPF_XmlRpc_Value_Scalar extends IPF_XmlRpc_Value
+{
+    public function saveXML()
+    {
+        if (!$this->_as_xml) {   // The XML code was not calculated yet
+            $dom   = new DOMDocument('1.0');
+            $value = $dom->appendChild($dom->createElement('value'));
+            $type  = $value->appendChild($dom->createElement($this->_type));
+            $type->appendChild($dom->createTextNode($this->getValue()));
+
+            $this->_as_dom = $value;
+            $this->_as_xml = $this->_stripXmlDeclaration($dom);
+        }
+
+        return $this->_as_xml;
+    }
+}
+
diff --git a/ipf/xmlrpc/value/string.php b/ipf/xmlrpc/value/string.php
new file mode 100644 (file)
index 0000000..635293a
--- /dev/null
@@ -0,0 +1,24 @@
+<?php
+
+class IPF_XmlRpc_Value_String extends IPF_XmlRpc_Value_Scalar
+{
+    public function __construct($value)
+    {
+        $this->_type = self::XMLRPC_TYPE_STRING;
+
+        // Make sure this value is string and all XML characters are encoded
+        $this->_value = $this->_xml_entities($value);
+    }
+
+    public function getValue()
+    {
+        return html_entity_decode($this->_value, ENT_QUOTES, 'UTF-8');
+    }
+
+    private function _xml_entities($str)
+    {
+        return htmlentities($str, ENT_QUOTES, 'UTF-8');
+    }
+
+}
+
diff --git a/ipf/xmlrpc/value/struct.php b/ipf/xmlrpc/value/struct.php
new file mode 100644 (file)
index 0000000..f6ed6ce
--- /dev/null
@@ -0,0 +1,34 @@
+<?php
+
+class IPF_XmlRpc_Value_Struct extends IPF_XmlRpc_Value_Collection
+{
+    public function __construct($value)
+    {
+        $this->_type = self::XMLRPC_TYPE_STRUCT;
+        parent::__construct($value);
+    }
+
+    public function saveXML()
+    {
+        if (!$this->_as_xml) {   // The XML code was not calculated yet
+            $dom    = new DOMDocument('1.0');
+            $value  = $dom->appendChild($dom->createElement('value'));
+            $struct = $value->appendChild($dom->createElement('struct'));
+
+            if (is_array($this->_value)) {
+                foreach ($this->_value as $name => $val) {
+                    /* @var $val IPF_XmlRpc_Value */
+                    $member = $struct->appendChild($dom->createElement('member'));
+                    $member->appendChild($dom->createElement('name', $name));
+                    $member->appendChild($dom->importNode($val->getAsDOM(), 1));
+                }
+            }
+
+            $this->_as_dom = $value;
+            $this->_as_xml = $this->_stripXmlDeclaration($dom);
+        }
+
+        return $this->_as_xml;
+    }
+}
+