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