Make WordPress Core

source: trunk/wp-includes/class-IXR.php @ 1400

Last change on this file since 1400 was 1346, checked in by michelvaldrighi, 21 years ago

we shall use IXR instead of phpxmlrpc in the future -- so long, and thanks for all the xmlrpcval headaches

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