]> scripts.mit.edu Git - autoinstalls/wordpress.git/blob - wp-includes/class-IXR.php
Wordpress 2.9.2
[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 (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                 $contents .= trim($line);
585             }
586             if ($this->debug) {
587                 $debug_contents .= $line;
588             }
589         }
590         if ($this->debug) {
591             echo '<pre class="ixr_response">'.htmlspecialchars($debug_contents)."\n</pre>\n\n";
592         }
593         // Now parse what we've got back
594         $this->message = new IXR_Message($contents);
595         if (!$this->message->parse()) {
596             // XML error
597             $this->error = new IXR_Error(-32700, 'parse error. not well formed');
598             return false;
599         }
600         // Is the message a fault?
601         if ($this->message->messageType == 'fault') {
602             $this->error = new IXR_Error($this->message->faultCode, $this->message->faultString);
603             return false;
604         }
605         // Message must be OK
606         return true;
607     }
608     function getResponse() {
609         // methodResponses can only have one param - return that
610         return $this->message->params[0];
611     }
612     function isError() {
613         return (is_object($this->error));
614     }
615     function getErrorCode() {
616         return $this->error->code;
617     }
618     function getErrorMessage() {
619         return $this->error->message;
620     }
621 }
622
623 /**
624  * IXR_Error
625  *
626  * @package IXR
627  * @since 1.5
628  */
629 class IXR_Error {
630     var $code;
631     var $message;
632     function IXR_Error($code, $message) {
633         $this->code = $code;
634         // WP adds htmlspecialchars(). See #5666
635         $this->message = htmlspecialchars($message);
636     }
637     function getXml() {
638         $xml = <<<EOD
639 <methodResponse>
640   <fault>
641     <value>
642       <struct>
643         <member>
644           <name>faultCode</name>
645           <value><int>{$this->code}</int></value>
646         </member>
647         <member>
648           <name>faultString</name>
649           <value><string>{$this->message}</string></value>
650         </member>
651       </struct>
652     </value>
653   </fault>
654 </methodResponse>
655
656 EOD;
657         return $xml;
658     }
659 }
660
661 /**
662  * IXR_Date
663  *
664  * @package IXR
665  * @since 1.5
666  */
667 class IXR_Date {
668     var $year;
669     var $month;
670     var $day;
671     var $hour;
672     var $minute;
673     var $second;
674     var $timezone;
675     function IXR_Date($time) {
676         // $time can be a PHP timestamp or an ISO one
677         if (is_numeric($time)) {
678             $this->parseTimestamp($time);
679         } else {
680             $this->parseIso($time);
681         }
682     }
683     function parseTimestamp($timestamp) {
684         $this->year = date('Y', $timestamp);
685         $this->month = date('m', $timestamp);
686         $this->day = date('d', $timestamp);
687         $this->hour = date('H', $timestamp);
688         $this->minute = date('i', $timestamp);
689         $this->second = date('s', $timestamp);
690         // WP adds timezone. See #2036
691         $this->timezone = '';
692     }
693     function parseIso($iso) {
694         $this->year = substr($iso, 0, 4);
695         $this->month = substr($iso, 4, 2);
696         $this->day = substr($iso, 6, 2);
697         $this->hour = substr($iso, 9, 2);
698         $this->minute = substr($iso, 12, 2);
699         $this->second = substr($iso, 15, 2);
700         // WP adds timezone. See #2036
701         $this->timezone = substr($iso, 17);
702     }
703     function getIso() {
704         // WP adds timezone. See #2036
705         return $this->year.$this->month.$this->day.'T'.$this->hour.':'.$this->minute.':'.$this->second.$this->timezone;
706     }
707     function getXml() {
708         return '<dateTime.iso8601>'.$this->getIso().'</dateTime.iso8601>';
709     }
710     function getTimestamp() {
711         return mktime($this->hour, $this->minute, $this->second, $this->month, $this->day, $this->year);
712     }
713 }
714
715 /**
716  * IXR_Base64
717  *
718  * @package IXR
719  * @since 1.5
720  */
721 class IXR_Base64 {
722     var $data;
723     function IXR_Base64($data) {
724         $this->data = $data;
725     }
726     function getXml() {
727         return '<base64>'.base64_encode($this->data).'</base64>';
728     }
729 }
730
731 /**
732  * IXR_IntrospectionServer
733  *
734  * @package IXR
735  * @since 1.5
736  */
737 class IXR_IntrospectionServer extends IXR_Server {
738     var $signatures;
739     var $help;
740     function IXR_IntrospectionServer() {
741         $this->setCallbacks();
742         $this->setCapabilities();
743         $this->capabilities['introspection'] = array(
744             'specUrl' => 'http://xmlrpc.usefulinc.com/doc/reserved.html',
745             'specVersion' => 1
746         );
747         $this->addCallback(
748             'system.methodSignature',
749             'this:methodSignature',
750             array('array', 'string'),
751             'Returns an array describing the return type and required parameters of a method'
752         );
753         $this->addCallback(
754             'system.getCapabilities',
755             'this:getCapabilities',
756             array('struct'),
757             'Returns a struct describing the XML-RPC specifications supported by this server'
758         );
759         $this->addCallback(
760             'system.listMethods',
761             'this:listMethods',
762             array('array'),
763             'Returns an array of available methods on this server'
764         );
765         $this->addCallback(
766             'system.methodHelp',
767             'this:methodHelp',
768             array('string', 'string'),
769             'Returns a documentation string for the specified method'
770         );
771     }
772     function addCallback($method, $callback, $args, $help) {
773         $this->callbacks[$method] = $callback;
774         $this->signatures[$method] = $args;
775         $this->help[$method] = $help;
776     }
777     function call($methodname, $args) {
778         // Make sure it's in an array
779         if ($args && !is_array($args)) {
780             $args = array($args);
781         }
782         // Over-rides default call method, adds signature check
783         if (!$this->hasMethod($methodname)) {
784             return new IXR_Error(-32601, 'server error. requested method "'.$this->message->methodName.'" not specified.');
785         }
786         $method = $this->callbacks[$methodname];
787         $signature = $this->signatures[$methodname];
788         $returnType = array_shift($signature);
789         // Check the number of arguments
790         if (count($args) != count($signature)) {
791             return new IXR_Error(-32602, 'server error. wrong number of method parameters');
792         }
793         // Check the argument types
794         $ok = true;
795         $argsbackup = $args;
796         for ($i = 0, $j = count($args); $i < $j; $i++) {
797             $arg = array_shift($args);
798             $type = array_shift($signature);
799             switch ($type) {
800                 case 'int':
801                 case 'i4':
802                     if (is_array($arg) || !is_int($arg)) {
803                         $ok = false;
804                     }
805                     break;
806                 case 'base64':
807                 case 'string':
808                     if (!is_string($arg)) {
809                         $ok = false;
810                     }
811                     break;
812                 case 'boolean':
813                     if ($arg !== false && $arg !== true) {
814                         $ok = false;
815                     }
816                     break;
817                 case 'float':
818                 case 'double':
819                     if (!is_float($arg)) {
820                         $ok = false;
821                     }
822                     break;
823                 case 'date':
824                 case 'dateTime.iso8601':
825                     if (!is_a($arg, 'IXR_Date')) {
826                         $ok = false;
827                     }
828                     break;
829             }
830             if (!$ok) {
831                 return new IXR_Error(-32602, 'server error. invalid method parameters');
832             }
833         }
834         // It passed the test - run the "real" method call
835         return parent::call($methodname, $argsbackup);
836     }
837     function methodSignature($method) {
838         if (!$this->hasMethod($method)) {
839             return new IXR_Error(-32601, 'server error. requested method "'.$method.'" not specified.');
840         }
841         // We should be returning an array of types
842         $types = $this->signatures[$method];
843         $return = array();
844         foreach ($types as $type) {
845             switch ($type) {
846                 case 'string':
847                     $return[] = 'string';
848                     break;
849                 case 'int':
850                 case 'i4':
851                     $return[] = 42;
852                     break;
853                 case 'double':
854                     $return[] = 3.1415;
855                     break;
856                 case 'dateTime.iso8601':
857                     $return[] = new IXR_Date(time());
858                     break;
859                 case 'boolean':
860                     $return[] = true;
861                     break;
862                 case 'base64':
863                     $return[] = new IXR_Base64('base64');
864                     break;
865                 case 'array':
866                     $return[] = array('array');
867                     break;
868                 case 'struct':
869                     $return[] = array('struct' => 'struct');
870                     break;
871             }
872         }
873         return $return;
874     }
875     function methodHelp($method) {
876         return $this->help[$method];
877     }
878 }
879
880 /**
881  * IXR_ClientMulticall
882  *
883  * @package IXR
884  * @since 1.5
885  */
886 class IXR_ClientMulticall extends IXR_Client {
887     var $calls = array();
888     function IXR_ClientMulticall($server, $path = false, $port = 80) {
889         parent::IXR_Client($server, $path, $port);
890         $this->useragent = 'The Incutio XML-RPC PHP Library (multicall client)';
891     }
892     function addCall() {
893         $args = func_get_args();
894         $methodName = array_shift($args);
895         $struct = array(
896             'methodName' => $methodName,
897             'params' => $args
898         );
899         $this->calls[] = $struct;
900     }
901     function query() {
902         // Prepare multicall, then call the parent::query() method
903         return parent::query('system.multicall', $this->calls);
904     }
905 }
906
907 ?>