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