WordPress.org

Make WordPress Core

Ticket #13300: class-IXR.php

File class-IXR.php, 28.4 KB (added by nasenmann, 4 years ago)

fix

Line 
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 */
22class 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 */
139class 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 */
294class 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
343EOD;
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 */
465class 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
478EOD;
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 */
501class 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 */
629class 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
656EOD;
657        return $xml;
658    }
659}
660
661/**
662 * IXR_Date
663 *
664 * @package IXR
665 * @since 1.5
666 */
667class 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, 5, 2);
696        $this->day = substr($iso, 8, 2);
697        $this->hour = substr($iso, 11, 2);
698        $this->minute = substr($iso, 14, 2);
699        $this->second = substr($iso, 17, 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 */
721class 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 */
737class 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 */
886class 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?>