3 * IXR - The Inutio XML-RPC Library
8 * @copyright Incutio Ltd 2002-2005
9 * @version 1.7 (beta) 23rd May 2005
10 * @author Simon Willison
11 * @link http://scripts.incutio.com/xmlrpc/ Site
12 * @link http://scripts.incutio.com/xmlrpc/manual.php Manual
13 * @license BSD License http://www.opensource.org/licenses/bsd-license.php
25 function IXR_Value ($data, $type = false) {
28 $type = $this->calculateType();
31 if ($type == 'struct') {
32 /* Turn all the values in the array in to new IXR_Value objects */
33 foreach ($this->data as $key => $value) {
34 $this->data[$key] = new IXR_Value($value);
37 if ($type == 'array') {
38 for ($i = 0, $j = count($this->data); $i < $j; $i++) {
39 $this->data[$i] = new IXR_Value($this->data[$i]);
43 function calculateType() {
44 if ($this->data === true || $this->data === false) {
47 if (is_integer($this->data)) {
50 if (is_double($this->data)) {
53 // Deal with IXR object types base64 and date
54 if (is_object($this->data) && is_a($this->data, 'IXR_Date')) {
57 if (is_object($this->data) && is_a($this->data, 'IXR_Base64')) {
60 // If it is a normal PHP object convert it in to a struct
61 if (is_object($this->data)) {
63 $this->data = get_object_vars($this->data);
66 if (!is_array($this->data)) {
69 /* We have an array - is it an array or a struct ? */
70 if ($this->isStruct($this->data)) {
77 /* Return XML for this value */
78 switch ($this->type) {
80 return '<boolean>'.(($this->data) ? '1' : '0').'</boolean>';
83 return '<int>'.$this->data.'</int>';
86 return '<double>'.$this->data.'</double>';
89 return '<string>'.htmlspecialchars($this->data).'</string>';
92 $return = '<array><data>'."\n";
93 foreach ($this->data as $item) {
94 $return .= ' <value>'.$item->getXml()."</value>\n";
96 $return .= '</data></array>';
100 $return = '<struct>'."\n";
101 foreach ($this->data as $name => $value) {
102 $name = htmlspecialchars($name);
103 $return .= " <member><name>$name</name><value>";
104 $return .= $value->getXml()."</value></member>\n";
106 $return .= '</struct>';
111 return $this->data->getXml();
116 function isStruct($array) {
117 /* Nasty function to check if an array is a struct or not */
119 foreach ($array as $key => $value) {
120 if ((string)$key != (string)$expected) {
137 var $messageType; // methodCall / methodResponse / fault
142 // Current variable stacks
143 var $_arraystructs = array(); // The stack used to keep track of the current array/struct
144 var $_arraystructstypes = array(); // Stack keeping track of if things are structs or array
145 var $_currentStructName = array(); // A stack as well
149 var $_currentTagContents;
152 function IXR_Message ($message) {
153 $this->message = $message;
156 // first remove the XML declaration
157 $this->message = preg_replace('/<\?xml(.*)?\?'.'>/', '', $this->message);
158 if (trim($this->message) == '') {
161 $this->_parser = xml_parser_create();
162 // Set XML parser to take the case of tags in to account
163 xml_parser_set_option($this->_parser, XML_OPTION_CASE_FOLDING, false);
164 // Set XML parser callback functions
165 xml_set_object($this->_parser, $this);
166 xml_set_element_handler($this->_parser, 'tag_open', 'tag_close');
167 xml_set_character_data_handler($this->_parser, 'cdata');
168 if (!xml_parse($this->_parser, $this->message)) {
169 /* die(sprintf('XML error: %s at line %d',
170 xml_error_string(xml_get_error_code($this->_parser)),
171 xml_get_current_line_number($this->_parser))); */
174 xml_parser_free($this->_parser);
175 // Grab the error messages, if any
176 if ($this->messageType == 'fault') {
177 $this->faultCode = $this->params[0]['faultCode'];
178 $this->faultString = $this->params[0]['faultString'];
182 function tag_open($parser, $tag, $attr) {
183 $this->_currentTagContents = '';
184 $this->currentTag = $tag;
187 case 'methodResponse':
189 $this->messageType = $tag;
191 /* Deal with stacks of arrays and structs */
192 case 'data': // data is to all intents and puposes more interesting than array
193 $this->_arraystructstypes[] = 'array';
194 $this->_arraystructs[] = array();
197 $this->_arraystructstypes[] = 'struct';
198 $this->_arraystructs[] = array();
202 function cdata($parser, $cdata) {
203 $this->_currentTagContents .= $cdata;
205 function tag_close($parser, $tag) {
210 $value = (int) trim($this->_currentTagContents);
214 $value = (double) trim($this->_currentTagContents);
218 $value = $this->_currentTagContents;
221 case 'dateTime.iso8601':
222 $value = new IXR_Date(trim($this->_currentTagContents));
223 // $value = $iso->getTimestamp();
227 // "If no type is indicated, the type is string."
228 if (trim($this->_currentTagContents) != '') {
229 $value = (string)$this->_currentTagContents;
234 $value = (boolean) trim($this->_currentTagContents);
238 $value = base64_decode( trim( $this->_currentTagContents ) );
241 /* Deal with stacks of arrays and structs */
244 $value = array_pop($this->_arraystructs);
245 array_pop($this->_arraystructstypes);
249 array_pop($this->_currentStructName);
252 $this->_currentStructName[] = trim($this->_currentTagContents);
255 $this->methodName = trim($this->_currentTagContents);
259 if (count($this->_arraystructs) > 0) {
260 // Add value to struct or array
261 if ($this->_arraystructstypes[count($this->_arraystructstypes)-1] == 'struct') {
263 $this->_arraystructs[count($this->_arraystructs)-1][$this->_currentStructName[count($this->_currentStructName)-1]] = $value;
266 $this->_arraystructs[count($this->_arraystructs)-1][] = $value;
269 // Just add as a paramater
270 $this->params[] = $value;
273 $this->_currentTagContents = '';
285 var $callbacks = array();
288 function IXR_Server($callbacks = false, $data = false) {
289 $this->setCapabilities();
291 $this->callbacks = $callbacks;
293 $this->setCallbacks();
296 function serve($data = false) {
298 global $HTTP_RAW_POST_DATA;
299 if (!$HTTP_RAW_POST_DATA) {
300 die('XML-RPC server accepts POST requests only.');
302 $data = $HTTP_RAW_POST_DATA;
304 $this->message = new IXR_Message($data);
305 if (!$this->message->parse()) {
306 $this->error(-32700, 'parse error. not well formed');
308 if ($this->message->messageType != 'methodCall') {
309 $this->error(-32600, 'server error. invalid xml-rpc. not conforming to spec. Request must be a methodCall');
311 $result = $this->call($this->message->methodName, $this->message->params);
312 // Is the result an error?
313 if (is_a($result, 'IXR_Error')) {
314 $this->error($result);
317 $r = new IXR_Value($result);
318 $resultxml = $r->getXml();
335 function call($methodname, $args) {
336 if (!$this->hasMethod($methodname)) {
337 return new IXR_Error(-32601, 'server error. requested method '.$methodname.' does not exist.');
339 $method = $this->callbacks[$methodname];
340 // Perform the callback and send the response
341 if (count($args) == 1) {
342 // If only one paramater just send that instead of the whole array
345 // Are we dealing with a function or a method?
346 if (substr($method, 0, 5) == 'this:') {
347 // It's a class method - check it exists
348 $method = substr($method, 5);
349 if (!method_exists($this, $method)) {
350 return new IXR_Error(-32601, 'server error. requested class method "'.$method.'" does not exist.');
353 $result = $this->$method($args);
355 // It's a function - does it exist?
356 if (is_array($method)) {
357 if (!method_exists($method[0], $method[1])) {
358 return new IXR_Error(-32601, 'server error. requested object method "'.$method[1].'" does not exist.');
360 } else if (!function_exists($method)) {
361 return new IXR_Error(-32601, 'server error. requested function "'.$method.'" does not exist.');
364 $result = call_user_func($method, $args);
369 function error($error, $message = false) {
370 // Accepts either an error object or an error code and message
371 if ($message && !is_object($error)) {
372 $error = new IXR_Error($error, $message);
374 $this->output($error->getXml());
376 function output($xml) {
377 $xml = '<?xml version="1.0"?>'."\n".$xml;
378 $length = strlen($xml);
379 header('Connection: close');
380 header('Content-Length: '.$length);
381 header('Content-Type: text/xml');
382 header('Date: '.date('r'));
386 function hasMethod($method) {
387 return in_array($method, array_keys($this->callbacks));
389 function setCapabilities() {
390 // Initialises capabilities array
391 $this->capabilities = array(
393 'specUrl' => 'http://www.xmlrpc.com/spec',
396 'faults_interop' => array(
397 'specUrl' => 'http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php',
398 'specVersion' => 20010516
400 'system.multicall' => array(
401 'specUrl' => 'http://www.xmlrpc.com/discuss/msgReader$1208',
406 function getCapabilities($args) {
407 return $this->capabilities;
409 function setCallbacks() {
410 $this->callbacks['system.getCapabilities'] = 'this:getCapabilities';
411 $this->callbacks['system.listMethods'] = 'this:listMethods';
412 $this->callbacks['system.multicall'] = 'this:multiCall';
414 function listMethods($args) {
415 // Returns a list of methods - uses array_reverse to ensure user defined
416 // methods are listed before server defined methods
417 return array_reverse(array_keys($this->callbacks));
419 function multiCall($methodcalls) {
420 // See http://www.xmlrpc.com/discuss/msgReader$1208
422 foreach ($methodcalls as $call) {
423 $method = $call['methodName'];
424 $params = $call['params'];
425 if ($method == 'system.multicall') {
426 $result = new IXR_Error(-32600, 'Recursive calls to system.multicall are forbidden');
428 $result = $this->call($method, $params);
430 if (is_a($result, 'IXR_Error')) {
432 'faultCode' => $result->code,
433 'faultString' => $result->message
436 $return[] = array($result);
453 function IXR_Request($method, $args) {
454 $this->method = $method;
457 <?xml version="1.0"?>
459 <methodName>{$this->method}</methodName>
463 foreach ($this->args as $arg) {
464 $this->xml .= '<param><value>';
465 $v = new IXR_Value($arg);
466 $this->xml .= $v->getXml();
467 $this->xml .= "</value></param>\n";
469 $this->xml .= '</params></methodCall>';
471 function getLength() {
472 return strlen($this->xml);
491 var $message = false;
494 // Storage place for an error message
496 function IXR_Client($server, $path = false, $port = 80, $timeout = false) {
498 // Assume we have been given a URL instead
499 $bits = parse_url($server);
500 $this->server = $bits['host'];
501 $this->port = isset($bits['port']) ? $bits['port'] : 80;
502 $this->path = isset($bits['path']) ? $bits['path'] : '/';
503 // Make absolutely sure we have a path
508 $this->server = $server;
512 $this->useragent = 'Incutio XML-RPC';
513 $this->timeout = $timeout;
516 $args = func_get_args();
517 $method = array_shift($args);
518 $request = new IXR_Request($method, $args);
519 $length = $request->getLength();
520 $xml = $request->getXml();
522 $request = "POST {$this->path} HTTP/1.0$r";
523 $request .= "Host: {$this->server}$r";
524 $request .= "Content-Type: text/xml$r";
525 $request .= "User-Agent: {$this->useragent}$r";
526 $request .= "Content-length: {$length}$r$r";
528 // Now send the request
530 echo '<pre>'.htmlspecialchars($request)."\n</pre>\n\n";
532 if ($this->timeout) {
533 $fp = @fsockopen($this->server, $this->port, $errno, $errstr, $this->timeout);
535 $fp = @fsockopen($this->server, $this->port, $errno, $errstr);
538 $this->error = new IXR_Error(-32300, "transport error - could not open socket: $errno $errstr");
541 fputs($fp, $request);
543 $gotFirstLine = false;
544 $gettingHeaders = true;
546 $line = fgets($fp, 4096);
547 if (!$gotFirstLine) {
548 // Check line for '200'
549 if (strstr($line, '200') === false) {
550 $this->error = new IXR_Error(-32300, 'transport error - HTTP status code was not 200');
553 $gotFirstLine = true;
555 if (trim($line) == '') {
556 $gettingHeaders = false;
558 if (!$gettingHeaders) {
559 $contents .= trim($line)."\n";
563 echo '<pre>'.htmlspecialchars($contents)."\n</pre>\n\n";
565 // Now parse what we've got back
566 $this->message = new IXR_Message($contents);
567 if (!$this->message->parse()) {
569 $this->error = new IXR_Error(-32700, 'parse error. not well formed');
572 // Is the message a fault?
573 if ($this->message->messageType == 'fault') {
574 $this->error = new IXR_Error($this->message->faultCode, $this->message->faultString);
577 // Message must be OK
580 function getResponse() {
581 // methodResponses can only have one param - return that
582 return $this->message->params[0];
585 return (is_object($this->error));
587 function getErrorCode() {
588 return $this->error->code;
590 function getErrorMessage() {
591 return $this->error->message;
604 function IXR_Error($code, $message) {
606 $this->message = htmlspecialchars($message);
615 <name>faultCode</name>
616 <value><int>{$this->code}</int></value>
619 <name>faultString</name>
620 <value><string>{$this->message}</string></value>
645 function IXR_Date($time) {
646 // $time can be a PHP timestamp or an ISO one
647 if (is_numeric($time)) {
648 $this->parseTimestamp($time);
650 $this->parseIso($time);
653 function parseTimestamp($timestamp) {
654 $this->year = date('Y', $timestamp);
655 $this->month = date('m', $timestamp);
656 $this->day = date('d', $timestamp);
657 $this->hour = date('H', $timestamp);
658 $this->minute = date('i', $timestamp);
659 $this->second = date('s', $timestamp);
661 function parseIso($iso) {
662 $this->year = substr($iso, 0, 4);
663 $this->month = substr($iso, 4, 2);
664 $this->day = substr($iso, 6, 2);
665 $this->hour = substr($iso, 9, 2);
666 $this->minute = substr($iso, 12, 2);
667 $this->second = substr($iso, 15, 2);
668 $this->timezone = substr($iso, 17);
671 return $this->year.$this->month.$this->day.'T'.$this->hour.':'.$this->minute.':'.$this->second.$this->timezone;
674 return '<dateTime.iso8601>'.$this->getIso().'</dateTime.iso8601>';
676 function getTimestamp() {
677 return mktime($this->hour, $this->minute, $this->second, $this->month, $this->day, $this->year);
689 function IXR_Base64($data) {
693 return '<base64>'.base64_encode($this->data).'</base64>';
698 * IXR_IntrospectionServer
703 class IXR_IntrospectionServer extends IXR_Server {
706 function IXR_IntrospectionServer() {
707 $this->setCallbacks();
708 $this->setCapabilities();
709 $this->capabilities['introspection'] = array(
710 'specUrl' => 'http://xmlrpc.usefulinc.com/doc/reserved.html',
714 'system.methodSignature',
715 'this:methodSignature',
716 array('array', 'string'),
717 'Returns an array describing the return type and required parameters of a method'
720 'system.getCapabilities',
721 'this:getCapabilities',
723 'Returns a struct describing the XML-RPC specifications supported by this server'
726 'system.listMethods',
729 'Returns an array of available methods on this server'
734 array('string', 'string'),
735 'Returns a documentation string for the specified method'
738 function addCallback($method, $callback, $args, $help) {
739 $this->callbacks[$method] = $callback;
740 $this->signatures[$method] = $args;
741 $this->help[$method] = $help;
743 function call($methodname, $args) {
744 // Make sure it's in an array
745 if ($args && !is_array($args)) {
746 $args = array($args);
748 // Over-rides default call method, adds signature check
749 if (!$this->hasMethod($methodname)) {
750 return new IXR_Error(-32601, 'server error. requested method "'.$this->message->methodName.'" not specified.');
752 $method = $this->callbacks[$methodname];
753 $signature = $this->signatures[$methodname];
754 $returnType = array_shift($signature);
755 // Check the number of arguments
756 if (count($args) != count($signature)) {
757 return new IXR_Error(-32602, 'server error. wrong number of method parameters');
759 // Check the argument types
762 for ($i = 0, $j = count($args); $i < $j; $i++) {
763 $arg = array_shift($args);
764 $type = array_shift($signature);
768 if (is_array($arg) || !is_int($arg)) {
774 if (!is_string($arg)) {
779 if ($arg !== false && $arg !== true) {
785 if (!is_float($arg)) {
790 case 'dateTime.iso8601':
791 if (!is_a($arg, 'IXR_Date')) {
797 return new IXR_Error(-32602, 'server error. invalid method parameters');
800 // It passed the test - run the "real" method call
801 return parent::call($methodname, $argsbackup);
803 function methodSignature($method) {
804 if (!$this->hasMethod($method)) {
805 return new IXR_Error(-32601, 'server error. requested method "'.$method.'" not specified.');
807 // We should be returning an array of types
808 $types = $this->signatures[$method];
810 foreach ($types as $type) {
813 $return[] = 'string';
822 case 'dateTime.iso8601':
823 $return[] = new IXR_Date(time());
829 $return[] = new IXR_Base64('base64');
832 $return[] = array('array');
835 $return[] = array('struct' => 'struct');
841 function methodHelp($method) {
842 return $this->help[$method];
847 * IXR_ClientMulticall
852 class IXR_ClientMulticall extends IXR_Client {
853 var $calls = array();
854 function IXR_ClientMulticall($server, $path = false, $port = 80) {
855 parent::IXR_Client($server, $path, $port);
856 $this->useragent = 'The Incutio XML-RPC PHP Library (multicall client)';
859 $args = func_get_args();
860 $methodName = array_shift($args);
862 'methodName' => $methodName,
865 $this->calls[] = $struct;
868 // Prepare multicall, then call the parent::query() method
869 return parent::query('system.multicall', $this->calls);