Wordpress 3.0.4
[autoinstalls/wordpress.git] / wp-includes / class-IXR.php
1 <?php
2 /**
3  * IXR - The Inutio XML-RPC Library
4  *
5  * @package IXR
6  * @since 1.5
7  *
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
14  */
15
16 /**
17  * IXR_Value
18  *
19  * @package IXR
20  * @since 1.5
21  */
22 class IXR_Value {
23     var $data;
24     var $type;
25
26     function IXR_Value ($data, $type = false) {
27         $this->data = $data;
28         if (!$type) {
29             $type = $this->calculateType();
30         }
31         $this->type = $type;
32         if ($type == 'struct') {
33             /* Turn all the values in the array in to new IXR_Value objects */
34             foreach ($this->data as $key => $value) {
35                 $this->data[$key] = new IXR_Value($value);
36             }
37         }
38         if ($type == 'array') {
39             for ($i = 0, $j = count($this->data); $i < $j; $i++) {
40                 $this->data[$i] = new IXR_Value($this->data[$i]);
41             }
42         }
43     }
44
45     function calculateType() {
46         if ($this->data === true || $this->data === false) {
47             return 'boolean';
48         }
49         if (is_integer($this->data)) {
50             return 'int';
51         }
52         if (is_double($this->data)) {
53             return 'double';
54         }
55         // Deal with IXR object types base64 and date
56         if (is_object($this->data) && is_a($this->data, 'IXR_Date')) {
57             return 'date';
58         }
59         if (is_object($this->data) && is_a($this->data, 'IXR_Base64')) {
60             return 'base64';
61         }
62         // If it is a normal PHP object convert it in to a struct
63         if (is_object($this->data)) {
64
65             $this->data = get_object_vars($this->data);
66             return 'struct';
67         }
68         if (!is_array($this->data)) {
69             return 'string';
70         }
71         /* We have an array - is it an array or a struct ? */
72         if ($this->isStruct($this->data)) {
73             return 'struct';
74         } else {
75             return 'array';
76         }
77     }
78
79     function getXml() {
80         /* Return XML for this value */
81         switch ($this->type) {
82             case 'boolean':
83                 return '<boolean>'.(($this->data) ? '1' : '0').'</boolean>';
84                 break;
85             case 'int':
86                 return '<int>'.$this->data.'</int>';
87                 break;
88             case 'double':
89                 return '<double>'.$this->data.'</double>';
90                 break;
91             case 'string':
92                 return '<string>'.htmlspecialchars($this->data).'</string>';
93                 break;
94             case 'array':
95                 $return = '<array><data>'."\n";
96                 foreach ($this->data as $item) {
97                     $return .= '  <value>'.$item->getXml()."</value>\n";
98                 }
99                 $return .= '</data></array>';
100                 return $return;
101                 break;
102             case 'struct':
103                 $return = '<struct>'."\n";
104                 foreach ($this->data as $name => $value) {
105                                         $name = htmlspecialchars($name);
106                     $return .= "  <member><name>$name</name><value>";
107                     $return .= $value->getXml()."</value></member>\n";
108                 }
109                 $return .= '</struct>';
110                 return $return;
111                 break;
112             case 'date':
113             case 'base64':
114                 return $this->data->getXml();
115                 break;
116         }
117         return false;
118     }
119
120     function isStruct($array) {
121         /* Nasty function to check if an array is a struct or not */
122         $expected = 0;
123         foreach ($array as $key => $value) {
124             if ((string)$key != (string)$expected) {
125                 return true;
126             }
127             $expected++;
128         }
129         return false;
130     }
131 }
132
133 /**
134  * IXR_Message
135  *
136  * @package IXR
137  * @since 1.5
138  */
139 class IXR_Message {
140     var $message;
141     var $messageType;  // methodCall / methodResponse / fault
142     var $faultCode;
143     var $faultString;
144     var $methodName;
145     var $params;
146     // Current variable stacks
147     var $_arraystructs = array();   // The stack used to keep track of the current array/struct
148     var $_arraystructstypes = array(); // Stack keeping track of if things are structs or array
149     var $_currentStructName = array();  // A stack as well
150     var $_param;
151     var $_value;
152     var $_currentTag;
153     var $_currentTagContents;
154     // The XML parser
155     var $_parser;
156     function IXR_Message (&$message) {
157         $this->message = &$message;
158     }
159     function parse() {
160                 // first remove the XML declaration
161                 // this method avoids the RAM usage of preg_replace on very large messages
162                 $header = preg_replace( '/<\?xml.*?\?'.'>/', '', substr( $this->message, 0, 100 ), 1 );
163                 $this->message = substr_replace($this->message, $header, 0, 100);
164         if (trim($this->message) == '') {
165             return false;
166                 }
167         $this->_parser = xml_parser_create();
168         // Set XML parser to take the case of tags in to account
169         xml_parser_set_option($this->_parser, XML_OPTION_CASE_FOLDING, false);
170         // Set XML parser callback functions
171         xml_set_object($this->_parser, $this);
172         xml_set_element_handler($this->_parser, 'tag_open', 'tag_close');
173                 xml_set_character_data_handler($this->_parser, 'cdata');
174                 $chunk_size = 262144; // 256Kb, parse in chunks to avoid the RAM usage on very large messages
175                 do {
176                         if ( strlen($this->message) <= $chunk_size )
177                                 $final=true;
178                         $part = substr( $this->message, 0, $chunk_size );
179                         $this->message = substr( $this->message, $chunk_size );
180                         if ( !xml_parse( $this->_parser, $part, $final ) )
181                                 return false;
182                         if ( $final )
183                                 break;
184                 } while ( true );
185                 xml_parser_free($this->_parser);
186         // Grab the error messages, if any
187         if ($this->messageType == 'fault') {
188             $this->faultCode = $this->params[0]['faultCode'];
189             $this->faultString = $this->params[0]['faultString'];
190                 }
191         return true;
192     }
193     function tag_open($parser, $tag, $attr) {
194         $this->_currentTagContents = '';
195         $this->currentTag = $tag;
196         switch($tag) {
197             case 'methodCall':
198             case 'methodResponse':
199             case 'fault':
200                 $this->messageType = $tag;
201                 break;
202             /* Deal with stacks of arrays and structs */
203             case 'data':    // data is to all intents and puposes more interesting than array
204                 $this->_arraystructstypes[] = 'array';
205                 $this->_arraystructs[] = array();
206                 break;
207             case 'struct':
208                 $this->_arraystructstypes[] = 'struct';
209                 $this->_arraystructs[] = array();
210                 break;
211         }
212     }
213     function cdata($parser, $cdata) {
214         $this->_currentTagContents .= $cdata;
215     }
216     function tag_close($parser, $tag) {
217         $valueFlag = false;
218         switch($tag) {
219             case 'int':
220             case 'i4':
221                 $value = (int) trim($this->_currentTagContents);
222                 $valueFlag = true;
223                 break;
224             case 'double':
225                 $value = (double) trim($this->_currentTagContents);
226                 $valueFlag = true;
227                 break;
228             case 'string':
229                 $value = $this->_currentTagContents;
230                 $valueFlag = true;
231                 break;
232             case 'dateTime.iso8601':
233                 $value = new IXR_Date(trim($this->_currentTagContents));
234                 // $value = $iso->getTimestamp();
235                 $valueFlag = true;
236                 break;
237             case 'value':
238                 // "If no type is indicated, the type is string."
239                 if (trim($this->_currentTagContents) != '') {
240                     $value = (string)$this->_currentTagContents;
241                     $valueFlag = true;
242                 }
243                 break;
244             case 'boolean':
245                 $value = (boolean) trim($this->_currentTagContents);
246                 $valueFlag = true;
247                 break;
248             case 'base64':
249                 $value = base64_decode( trim( $this->_currentTagContents ) );
250                 $valueFlag = true;
251                 break;
252             /* Deal with stacks of arrays and structs */
253             case 'data':
254             case 'struct':
255                 $value = array_pop($this->_arraystructs);
256                 array_pop($this->_arraystructstypes);
257                 $valueFlag = true;
258                 break;
259             case 'member':
260                 array_pop($this->_currentStructName);
261                 break;
262             case 'name':
263                 $this->_currentStructName[] = trim($this->_currentTagContents);
264                 break;
265             case 'methodName':
266                 $this->methodName = trim($this->_currentTagContents);
267                 break;
268         }
269         if ($valueFlag) {
270             if (count($this->_arraystructs) > 0) {
271                 // Add value to struct or array
272                 if ($this->_arraystructstypes[count($this->_arraystructstypes)-1] == 'struct') {
273                     // Add to struct
274                     $this->_arraystructs[count($this->_arraystructs)-1][$this->_currentStructName[count($this->_currentStructName)-1]] = $value;
275                 } else {
276                     // Add to array
277                     $this->_arraystructs[count($this->_arraystructs)-1][] = $value;
278                 }
279             } else {
280                 // Just add as a paramater
281                 $this->params[] = $value;
282             }
283         }
284         $this->_currentTagContents = '';
285     }
286 }
287
288 /**
289  * IXR_Server
290  *
291  * @package IXR
292  * @since 1.5
293  */
294 class IXR_Server {
295     var $data;
296     var $callbacks = array();
297     var $message;
298     var $capabilities;
299     function IXR_Server($callbacks = false, $data = false) {
300         $this->setCapabilities();
301         if ($callbacks) {
302             $this->callbacks = $callbacks;
303         }
304         $this->setCallbacks();
305         $this->serve($data);
306     }
307     function serve($data = false) {
308         if (!$data) {
309             global $HTTP_RAW_POST_DATA;
310             if (!$HTTP_RAW_POST_DATA) {
311                header( 'Content-Type: text/plain' );
312                die('XML-RPC server accepts POST requests only.');
313             }
314             $data = &$HTTP_RAW_POST_DATA;
315         }
316         $this->message = new IXR_Message($data);
317         if (!$this->message->parse()) {
318             $this->error(-32700, 'parse error. not well formed');
319         }
320         if ($this->message->messageType != 'methodCall') {
321             $this->error(-32600, 'server error. invalid xml-rpc. not conforming to spec. Request must be a methodCall');
322         }
323         $result = $this->call($this->message->methodName, $this->message->params);
324         // Is the result an error?
325         if (is_a($result, 'IXR_Error')) {
326             $this->error($result);
327         }
328         // Encode the result
329         $r = new IXR_Value($result);
330         $resultxml = $r->getXml();
331         // Create the XML
332         $xml = <<<EOD
333 <methodResponse>
334   <params>
335     <param>
336       <value>
337         $resultxml
338       </value>
339     </param>
340   </params>
341 </methodResponse>
342
343 EOD;
344         // Send it
345         $this->output($xml);
346     }
347     function call($methodname, $args) {
348         if (!$this->hasMethod($methodname)) {
349             return new IXR_Error(-32601, 'server error. requested method '.
350                 $methodname.' does not exist.');
351         }
352         $method = $this->callbacks[$methodname];
353         // Perform the callback and send the response
354         if (count($args) == 1) {
355             // If only one paramater just send that instead of the whole array
356             $args = $args[0];
357         }
358         // Are we dealing with a function or a method?
359         if ( is_string( $method ) && substr($method, 0, 5) == 'this:' ) {
360             // It's a class method - check it exists
361             $method = substr($method, 5);
362             if (!method_exists($this, $method)) {
363                 return new IXR_Error(-32601, 'server error. requested class method "'.
364                     $method.'" does not exist.');
365             }
366             // Call the method
367             $result = $this->$method($args);
368         } else {
369             // It's a function - does it exist?
370             if (is_array($method)) {
371                 if (!method_exists($method[0], $method[1])) {
372                     return new IXR_Error(-32601, 'server error. requested object method "'.
373                         $method[1].'" does not exist.');
374                 }
375             } else if (!function_exists($method)) {
376                 return new IXR_Error(-32601, 'server error. requested function "'.
377                     $method.'" does not exist.');
378             }
379             // Call the function
380             $result = call_user_func($method, $args);
381         }
382         return $result;
383     }
384
385     function error($error, $message = false) {
386         // Accepts either an error object or an error code and message
387         if ($message && !is_object($error)) {
388             $error = new IXR_Error($error, $message);
389         }
390         $this->output($error->getXml());
391     }
392     function output($xml) {
393         $xml = '<?xml version="1.0"?>'."\n".$xml;
394         $length = strlen($xml);
395         header('Connection: close');
396         header('Content-Length: '.$length);
397         header('Content-Type: text/xml');
398         header('Date: '.date('r'));
399         echo $xml;
400         exit;
401     }
402     function hasMethod($method) {
403         return in_array($method, array_keys($this->callbacks));
404     }
405     function setCapabilities() {
406         // Initialises capabilities array
407         $this->capabilities = array(
408             'xmlrpc' => array(
409                 'specUrl' => 'http://www.xmlrpc.com/spec',
410                 'specVersion' => 1
411             ),
412             'faults_interop' => array(
413                 'specUrl' => 'http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php',
414                 'specVersion' => 20010516
415             ),
416             'system.multicall' => array(
417                 'specUrl' => 'http://www.xmlrpc.com/discuss/msgReader$1208',
418                 'specVersion' => 1
419             ),
420         );
421     }
422     function getCapabilities($args) {
423         return $this->capabilities;
424     }
425     function setCallbacks() {
426         $this->callbacks['system.getCapabilities'] = 'this:getCapabilities';
427         $this->callbacks['system.listMethods'] = 'this:listMethods';
428         $this->callbacks['system.multicall'] = 'this:multiCall';
429     }
430     function listMethods($args) {
431         // Returns a list of methods - uses array_reverse to ensure user defined
432         // methods are listed before server defined methods
433         return array_reverse(array_keys($this->callbacks));
434     }
435     function multiCall($methodcalls) {
436         // See http://www.xmlrpc.com/discuss/msgReader$1208
437         $return = array();
438         foreach ($methodcalls as $call) {
439             $method = $call['methodName'];
440             $params = $call['params'];
441             if ($method == 'system.multicall') {
442                 $result = new IXR_Error(-32600, 'Recursive calls to system.multicall are forbidden');
443             } else {
444                 $result = $this->call($method, $params);
445             }
446             if (is_a($result, 'IXR_Error')) {
447                 $return[] = array(
448                     'faultCode' => $result->code,
449                     'faultString' => $result->message
450                 );
451             } else {
452                 $return[] = array($result);
453             }
454         }
455         return $return;
456     }
457 }
458
459 /**
460  * IXR_Request
461  *
462  * @package IXR
463  * @since 1.5
464  */
465 class IXR_Request {
466     var $method;
467     var $args;
468     var $xml;
469     function IXR_Request($method, $args) {
470         $this->method = $method;
471         $this->args = $args;
472         $this->xml = <<<EOD
473 <?xml version="1.0"?>
474 <methodCall>
475 <methodName>{$this->method}</methodName>
476 <params>
477
478 EOD;
479         foreach ($this->args as $arg) {
480             $this->xml .= '<param><value>';
481             $v = new IXR_Value($arg);
482             $this->xml .= $v->getXml();
483             $this->xml .= "</value></param>\n";
484         }
485         $this->xml .= '</params></methodCall>';
486     }
487     function getLength() {
488         return strlen($this->xml);
489     }
490     function getXml() {
491         return $this->xml;
492     }
493 }
494
495 /**
496  * IXR_Client
497  *
498  * @package IXR
499  * @since 1.5
500  */
501 class IXR_Client {
502     var $server;
503     var $port;
504     var $path;
505     var $useragent;
506         var $headers;
507     var $response;
508     var $message = false;
509     var $debug = false;
510     var $timeout;
511     // Storage place for an error message
512     var $error = false;
513     function IXR_Client($server, $path = false, $port = 80, $timeout = false) {
514         if (!$path) {
515             // Assume we have been given a URL instead
516             $bits = parse_url($server);
517             $this->server = $bits['host'];
518             $this->port = isset($bits['port']) ? $bits['port'] : 80;
519             $this->path = isset($bits['path']) ? $bits['path'] : '/';
520             // Make absolutely sure we have a path
521             if (!$this->path) {
522                 $this->path = '/';
523             }
524         } else {
525             $this->server = $server;
526             $this->path = $path;
527             $this->port = $port;
528         }
529         $this->useragent = 'The Incutio XML-RPC PHP Library';
530         $this->timeout = $timeout;
531     }
532     function query() {
533         $args = func_get_args();
534         $method = array_shift($args);
535         $request = new IXR_Request($method, $args);
536         $length = $request->getLength();
537         $xml = $request->getXml();
538         $r = "\r\n";
539         $request  = "POST {$this->path} HTTP/1.0$r";
540
541                 $this->headers['Host']                  = $this->server;
542                 $this->headers['Content-Type']  = 'text/xml';
543                 $this->headers['User-Agent']    = $this->useragent;
544                 $this->headers['Content-Length']= $length;
545
546                 foreach( $this->headers as $header => $value ) {
547                         $request .= "{$header}: {$value}{$r}";
548                 }
549                 $request .= $r;
550
551         $request .= $xml;
552         // Now send the request
553         if ($this->debug) {
554             echo '<pre class="ixr_request">'.htmlspecialchars($request)."\n</pre>\n\n";
555         }
556         if ($this->timeout) {
557             $fp = @fsockopen($this->server, $this->port, $errno, $errstr, $this->timeout);
558         } else {
559             $fp = @fsockopen($this->server, $this->port, $errno, $errstr);
560         }
561         if (!$fp) {
562             $this->error = new IXR_Error(-32300, "transport error - could not open socket: $errno $errstr");
563             return false;
564         }
565         fputs($fp, $request);
566         $contents = '';
567         $debug_contents = '';
568         $gotFirstLine = false;
569         $gettingHeaders = true;
570         while (!feof($fp)) {
571             $line = fgets($fp, 4096);
572             if (!$gotFirstLine) {
573                 // Check line for '200'
574                 if (strstr($line, '200') === false) {
575                     $this->error = new IXR_Error(-32301, 'transport error - HTTP status code was not 200');
576                     return false;
577                 }
578                 $gotFirstLine = true;
579             }
580             if (trim($line) == '') {
581                 $gettingHeaders = false;
582             }
583             if (!$gettingHeaders) {
584                 // WP#12559 remove trim so as to not strip newlines from received response.
585                 $contents .= $line;
586             }
587             if ($this->debug) {
588                 $debug_contents .= $line;
589             }
590         }
591         if ($this->debug) {
592             echo '<pre class="ixr_response">'.htmlspecialchars($debug_contents)."\n</pre>\n\n";
593         }
594         // Now parse what we've got back
595         $this->message = new IXR_Message($contents);
596         if (!$this->message->parse()) {
597             // XML error
598             $this->error = new IXR_Error(-32700, 'parse error. not well formed');
599             return false;
600         }
601         // Is the message a fault?
602         if ($this->message->messageType == 'fault') {
603             $this->error = new IXR_Error($this->message->faultCode, $this->message->faultString);
604             return false;
605         }
606         // Message must be OK
607         return true;
608     }
609     function getResponse() {
610         // methodResponses can only have one param - return that
611         return $this->message->params[0];
612     }
613     function isError() {
614         return (is_object($this->error));
615     }
616     function getErrorCode() {
617         return $this->error->code;
618     }
619     function getErrorMessage() {
620         return $this->error->message;
621     }
622 }
623
624 /**
625  * IXR_Error
626  *
627  * @package IXR
628  * @since 1.5
629  */
630 class IXR_Error {
631     var $code;
632     var $message;
633     function IXR_Error($code, $message) {
634         $this->code = $code;
635         // WP adds htmlspecialchars(). See #5666
636         $this->message = htmlspecialchars($message);
637     }
638     function getXml() {
639         $xml = <<<EOD
640 <methodResponse>
641   <fault>
642     <value>
643       <struct>
644         <member>
645           <name>faultCode</name>
646           <value><int>{$this->code}</int></value>
647         </member>
648         <member>
649           <name>faultString</name>
650           <value><string>{$this->message}</string></value>
651         </member>
652       </struct>
653     </value>
654   </fault>
655 </methodResponse>
656
657 EOD;
658         return $xml;
659     }
660 }
661
662 /**
663  * IXR_Date
664  *
665  * @package IXR
666  * @since 1.5
667  */
668 class IXR_Date {
669     var $year;
670     var $month;
671     var $day;
672     var $hour;
673     var $minute;
674     var $second;
675     var $timezone;
676     function IXR_Date($time) {
677         // $time can be a PHP timestamp or an ISO one
678         if (is_numeric($time)) {
679             $this->parseTimestamp($time);
680         } else {
681             $this->parseIso($time);
682         }
683     }
684     function parseTimestamp($timestamp) {
685         $this->year = date('Y', $timestamp);
686         $this->month = date('m', $timestamp);
687         $this->day = date('d', $timestamp);
688         $this->hour = date('H', $timestamp);
689         $this->minute = date('i', $timestamp);
690         $this->second = date('s', $timestamp);
691         // WP adds timezone. See #2036
692         $this->timezone = '';
693     }
694     function parseIso($iso) {
695         $this->year = substr($iso, 0, 4);
696         $this->month = substr($iso, 4, 2);
697         $this->day = substr($iso, 6, 2);
698         $this->hour = substr($iso, 9, 2);
699         $this->minute = substr($iso, 12, 2);
700         $this->second = substr($iso, 15, 2);
701         // WP adds timezone. See #2036
702         $this->timezone = substr($iso, 17);
703     }
704     function getIso() {
705         // WP adds timezone. See #2036
706         return $this->year.$this->month.$this->day.'T'.$this->hour.':'.$this->minute.':'.$this->second.$this->timezone;
707     }
708     function getXml() {
709         return '<dateTime.iso8601>'.$this->getIso().'</dateTime.iso8601>';
710     }
711     function getTimestamp() {
712         return mktime($this->hour, $this->minute, $this->second, $this->month, $this->day, $this->year);
713     }
714 }
715
716 /**
717  * IXR_Base64
718  *
719  * @package IXR
720  * @since 1.5
721  */
722 class IXR_Base64 {
723     var $data;
724     function IXR_Base64($data) {
725         $this->data = $data;
726     }
727     function getXml() {
728         return '<base64>'.base64_encode($this->data).'</base64>';
729     }
730 }
731
732 /**
733  * IXR_IntrospectionServer
734  *
735  * @package IXR
736  * @since 1.5
737  */
738 class IXR_IntrospectionServer extends IXR_Server {
739     var $signatures;
740     var $help;
741     function IXR_IntrospectionServer() {
742         $this->setCallbacks();
743         $this->setCapabilities();
744         $this->capabilities['introspection'] = array(
745             'specUrl' => 'http://xmlrpc.usefulinc.com/doc/reserved.html',
746             'specVersion' => 1
747         );
748         $this->addCallback(
749             'system.methodSignature',
750             'this:methodSignature',
751             array('array', 'string'),
752             'Returns an array describing the return type and required parameters of a method'
753         );
754         $this->addCallback(
755             'system.getCapabilities',
756             'this:getCapabilities',
757             array('struct'),
758             'Returns a struct describing the XML-RPC specifications supported by this server'
759         );
760         $this->addCallback(
761             'system.listMethods',
762             'this:listMethods',
763             array('array'),
764             'Returns an array of available methods on this server'
765         );
766         $this->addCallback(
767             'system.methodHelp',
768             'this:methodHelp',
769             array('string', 'string'),
770             'Returns a documentation string for the specified method'
771         );
772     }
773     function addCallback($method, $callback, $args, $help) {
774         $this->callbacks[$method] = $callback;
775         $this->signatures[$method] = $args;
776         $this->help[$method] = $help;
777     }
778     function call($methodname, $args) {
779         // Make sure it's in an array
780         if ($args && !is_array($args)) {
781             $args = array($args);
782         }
783         // Over-rides default call method, adds signature check
784         if (!$this->hasMethod($methodname)) {
785             return new IXR_Error(-32601, 'server error. requested method "'.$this->message->methodName.'" not specified.');
786         }
787         $method = $this->callbacks[$methodname];
788         $signature = $this->signatures[$methodname];
789         $returnType = array_shift($signature);
790         // Check the number of arguments
791         if (count($args) != count($signature)) {
792             return new IXR_Error(-32602, 'server error. wrong number of method parameters');
793         }
794         // Check the argument types
795         $ok = true;
796         $argsbackup = $args;
797         for ($i = 0, $j = count($args); $i < $j; $i++) {
798             $arg = array_shift($args);
799             $type = array_shift($signature);
800             switch ($type) {
801                 case 'int':
802                 case 'i4':
803                     if (is_array($arg) || !is_int($arg)) {
804                         $ok = false;
805                     }
806                     break;
807                 case 'base64':
808                 case 'string':
809                     if (!is_string($arg)) {
810                         $ok = false;
811                     }
812                     break;
813                 case 'boolean':
814                     if ($arg !== false && $arg !== true) {
815                         $ok = false;
816                     }
817                     break;
818                 case 'float':
819                 case 'double':
820                     if (!is_float($arg)) {
821                         $ok = false;
822                     }
823                     break;
824                 case 'date':
825                 case 'dateTime.iso8601':
826                     if (!is_a($arg, 'IXR_Date')) {
827                         $ok = false;
828                     }
829                     break;
830             }
831             if (!$ok) {
832                 return new IXR_Error(-32602, 'server error. invalid method parameters');
833             }
834         }
835         // It passed the test - run the "real" method call
836         return parent::call($methodname, $argsbackup);
837     }
838     function methodSignature($method) {
839         if (!$this->hasMethod($method)) {
840             return new IXR_Error(-32601, 'server error. requested method "'.$method.'" not specified.');
841         }
842         // We should be returning an array of types
843         $types = $this->signatures[$method];
844         $return = array();
845         foreach ($types as $type) {
846             switch ($type) {
847                 case 'string':
848                     $return[] = 'string';
849                     break;
850                 case 'int':
851                 case 'i4':
852                     $return[] = 42;
853                     break;
854                 case 'double':
855                     $return[] = 3.1415;
856                     break;
857                 case 'dateTime.iso8601':
858                     $return[] = new IXR_Date(time());
859                     break;
860                 case 'boolean':
861                     $return[] = true;
862                     break;
863                 case 'base64':
864                     $return[] = new IXR_Base64('base64');
865                     break;
866                 case 'array':
867                     $return[] = array('array');
868                     break;
869                 case 'struct':
870                     $return[] = array('struct' => 'struct');
871                     break;
872             }
873         }
874         return $return;
875     }
876     function methodHelp($method) {
877         return $this->help[$method];
878     }
879 }
880
881 /**
882  * IXR_ClientMulticall
883  *
884  * @package IXR
885  * @since 1.5
886  */
887 class IXR_ClientMulticall extends IXR_Client {
888     var $calls = array();
889     function IXR_ClientMulticall($server, $path = false, $port = 80) {
890         parent::IXR_Client($server, $path, $port);
891         $this->useragent = 'The Incutio XML-RPC PHP Library (multicall client)';
892     }
893     function addCall() {
894         $args = func_get_args();
895         $methodName = array_shift($args);
896         $struct = array(
897             'methodName' => $methodName,
898             'params' => $args
899         );
900         $this->calls[] = $struct;
901     }
902     function query() {
903         // Prepare multicall, then call the parent::query() method
904         return parent::query('system.multicall', $this->calls);
905     }
906 }
907
908 ?>