WordPress 3.4
[autoinstalls/wordpress.git] / wp-includes / class-IXR.php
1 <?php
2 /**
3  * IXR - The Incutio XML-RPC Library
4  *
5  * Copyright (c) 2010, Incutio Ltd.
6  * All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions are met:
10  *
11  *  - Redistributions of source code must retain the above copyright notice,
12  *    this list of conditions and the following disclaimer.
13  *  - Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  *  - Neither the name of Incutio Ltd. nor the names of its contributors
17  *    may be used to endorse or promote products derived from this software
18  *    without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
21  * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
22  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
24  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
28  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
30  * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31  *
32  * @package IXR
33  * @since 1.5
34  *
35  * @copyright  Incutio Ltd 2010 (http://www.incutio.com)
36  * @version    1.7.4 7th September 2010
37  * @author     Simon Willison
38  * @link       http://scripts.incutio.com/xmlrpc/ Site/manual
39  * @license    http://www.opensource.org/licenses/bsd-license.php BSD
40  */
41
42 /**
43  * IXR_Value
44  *
45  * @package IXR
46  * @since 1.5
47  */
48 class IXR_Value {
49     var $data;
50     var $type;
51
52     function IXR_Value($data, $type = false)
53     {
54         $this->data = $data;
55         if (!$type) {
56             $type = $this->calculateType();
57         }
58         $this->type = $type;
59         if ($type == 'struct') {
60             // Turn all the values in the array in to new IXR_Value objects
61             foreach ($this->data as $key => $value) {
62                 $this->data[$key] = new IXR_Value($value);
63             }
64         }
65         if ($type == 'array') {
66             for ($i = 0, $j = count($this->data); $i < $j; $i++) {
67                 $this->data[$i] = new IXR_Value($this->data[$i]);
68             }
69         }
70     }
71
72     function calculateType()
73     {
74         if ($this->data === true || $this->data === false) {
75             return 'boolean';
76         }
77         if (is_integer($this->data)) {
78             return 'int';
79         }
80         if (is_double($this->data)) {
81             return 'double';
82         }
83
84         // Deal with IXR object types base64 and date
85         if (is_object($this->data) && is_a($this->data, 'IXR_Date')) {
86             return 'date';
87         }
88         if (is_object($this->data) && is_a($this->data, 'IXR_Base64')) {
89             return 'base64';
90         }
91
92         // If it is a normal PHP object convert it in to a struct
93         if (is_object($this->data)) {
94             $this->data = get_object_vars($this->data);
95             return 'struct';
96         }
97         if (!is_array($this->data)) {
98             return 'string';
99         }
100
101         // We have an array - is it an array or a struct?
102         if ($this->isStruct($this->data)) {
103             return 'struct';
104         } else {
105             return 'array';
106         }
107     }
108
109     function getXml()
110     {
111         // Return XML for this value
112         switch ($this->type) {
113             case 'boolean':
114                 return '<boolean>'.(($this->data) ? '1' : '0').'</boolean>';
115                 break;
116             case 'int':
117                 return '<int>'.$this->data.'</int>';
118                 break;
119             case 'double':
120                 return '<double>'.$this->data.'</double>';
121                 break;
122             case 'string':
123                 return '<string>'.htmlspecialchars($this->data).'</string>';
124                 break;
125             case 'array':
126                 $return = '<array><data>'."\n";
127                 foreach ($this->data as $item) {
128                     $return .= '  <value>'.$item->getXml()."</value>\n";
129                 }
130                 $return .= '</data></array>';
131                 return $return;
132                 break;
133             case 'struct':
134                 $return = '<struct>'."\n";
135                 foreach ($this->data as $name => $value) {
136                                         $name = htmlspecialchars($name);
137                     $return .= "  <member><name>$name</name><value>";
138                     $return .= $value->getXml()."</value></member>\n";
139                 }
140                 $return .= '</struct>';
141                 return $return;
142                 break;
143             case 'date':
144             case 'base64':
145                 return $this->data->getXml();
146                 break;
147         }
148         return false;
149     }
150
151     /**
152      * Checks whether or not the supplied array is a struct or not
153      *
154      * @param unknown_type $array
155      * @return boolean
156      */
157     function isStruct($array)
158     {
159         $expected = 0;
160         foreach ($array as $key => $value) {
161             if ((string)$key != (string)$expected) {
162                 return true;
163             }
164             $expected++;
165         }
166         return false;
167     }
168 }
169
170 /**
171  * IXR_MESSAGE
172  *
173  * @package IXR
174  * @since 1.5
175  *
176  */
177 class IXR_Message
178 {
179     var $message;
180     var $messageType;  // methodCall / methodResponse / fault
181     var $faultCode;
182     var $faultString;
183     var $methodName;
184     var $params;
185
186     // Current variable stacks
187     var $_arraystructs = array();   // The stack used to keep track of the current array/struct
188     var $_arraystructstypes = array(); // Stack keeping track of if things are structs or array
189     var $_currentStructName = array();  // A stack as well
190     var $_param;
191     var $_value;
192     var $_currentTag;
193     var $_currentTagContents;
194     // The XML parser
195     var $_parser;
196
197     function IXR_Message($message)
198     {
199         $this->message =& $message;
200     }
201
202     function parse()
203     {
204         // first remove the XML declaration
205         // merged from WP #10698 - this method avoids the RAM usage of preg_replace on very large messages
206         $header = preg_replace( '/<\?xml.*?\?'.'>/', '', substr($this->message, 0, 100), 1);
207         $this->message = substr_replace($this->message, $header, 0, 100);
208         if (trim($this->message) == '') {
209             return false;
210         }
211         $this->_parser = xml_parser_create();
212         // Set XML parser to take the case of tags in to account
213         xml_parser_set_option($this->_parser, XML_OPTION_CASE_FOLDING, false);
214         // Set XML parser callback functions
215         xml_set_object($this->_parser, $this);
216         xml_set_element_handler($this->_parser, 'tag_open', 'tag_close');
217         xml_set_character_data_handler($this->_parser, 'cdata');
218         $chunk_size = 262144; // 256Kb, parse in chunks to avoid the RAM usage on very large messages
219         $final = false;
220         do {
221             if (strlen($this->message) <= $chunk_size) {
222                 $final = true;
223             }
224             $part = substr($this->message, 0, $chunk_size);
225             $this->message = substr($this->message, $chunk_size);
226             if (!xml_parse($this->_parser, $part, $final)) {
227                 return false;
228             }
229             if ($final) {
230                 break;
231             }
232         } while (true);
233         xml_parser_free($this->_parser);
234
235         // Grab the error messages, if any
236         if ($this->messageType == 'fault') {
237             $this->faultCode = $this->params[0]['faultCode'];
238             $this->faultString = $this->params[0]['faultString'];
239         }
240         return true;
241     }
242
243     function tag_open($parser, $tag, $attr)
244     {
245         $this->_currentTagContents = '';
246         $this->currentTag = $tag;
247         switch($tag) {
248             case 'methodCall':
249             case 'methodResponse':
250             case 'fault':
251                 $this->messageType = $tag;
252                 break;
253                 /* Deal with stacks of arrays and structs */
254             case 'data':    // data is to all intents and puposes more interesting than array
255                 $this->_arraystructstypes[] = 'array';
256                 $this->_arraystructs[] = array();
257                 break;
258             case 'struct':
259                 $this->_arraystructstypes[] = 'struct';
260                 $this->_arraystructs[] = array();
261                 break;
262         }
263     }
264
265     function cdata($parser, $cdata)
266     {
267         $this->_currentTagContents .= $cdata;
268     }
269
270     function tag_close($parser, $tag)
271     {
272         $valueFlag = false;
273         switch($tag) {
274             case 'int':
275             case 'i4':
276                 $value = (int)trim($this->_currentTagContents);
277                 $valueFlag = true;
278                 break;
279             case 'double':
280                 $value = (double)trim($this->_currentTagContents);
281                 $valueFlag = true;
282                 break;
283             case 'string':
284                 $value = (string)trim($this->_currentTagContents);
285                 $valueFlag = true;
286                 break;
287             case 'dateTime.iso8601':
288                 $value = new IXR_Date(trim($this->_currentTagContents));
289                 $valueFlag = true;
290                 break;
291             case 'value':
292                 // "If no type is indicated, the type is string."
293                 if (trim($this->_currentTagContents) != '') {
294                     $value = (string)$this->_currentTagContents;
295                     $valueFlag = true;
296                 }
297                 break;
298             case 'boolean':
299                 $value = (boolean)trim($this->_currentTagContents);
300                 $valueFlag = true;
301                 break;
302             case 'base64':
303                 $value = base64_decode($this->_currentTagContents);
304                 $valueFlag = true;
305                 break;
306                 /* Deal with stacks of arrays and structs */
307             case 'data':
308             case 'struct':
309                 $value = array_pop($this->_arraystructs);
310                 array_pop($this->_arraystructstypes);
311                 $valueFlag = true;
312                 break;
313             case 'member':
314                 array_pop($this->_currentStructName);
315                 break;
316             case 'name':
317                 $this->_currentStructName[] = trim($this->_currentTagContents);
318                 break;
319             case 'methodName':
320                 $this->methodName = trim($this->_currentTagContents);
321                 break;
322         }
323
324         if ($valueFlag) {
325             if (count($this->_arraystructs) > 0) {
326                 // Add value to struct or array
327                 if ($this->_arraystructstypes[count($this->_arraystructstypes)-1] == 'struct') {
328                     // Add to struct
329                     $this->_arraystructs[count($this->_arraystructs)-1][$this->_currentStructName[count($this->_currentStructName)-1]] = $value;
330                 } else {
331                     // Add to array
332                     $this->_arraystructs[count($this->_arraystructs)-1][] = $value;
333                 }
334             } else {
335                 // Just add as a paramater
336                 $this->params[] = $value;
337             }
338         }
339         $this->_currentTagContents = '';
340     }
341 }
342
343 /**
344  * IXR_Server
345  *
346  * @package IXR
347  * @since 1.5
348  */
349 class IXR_Server
350 {
351     var $data;
352     var $callbacks = array();
353     var $message;
354     var $capabilities;
355
356     function IXR_Server($callbacks = false, $data = false, $wait = false)
357     {
358         $this->setCapabilities();
359         if ($callbacks) {
360             $this->callbacks = $callbacks;
361         }
362         $this->setCallbacks();
363         if (!$wait) {
364             $this->serve($data);
365         }
366     }
367
368     function serve($data = false)
369     {
370         if (!$data) {
371             if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] !== 'POST') {
372                 header('Content-Type: text/plain'); // merged from WP #9093
373                 die('XML-RPC server accepts POST requests only.');
374             }
375
376             global $HTTP_RAW_POST_DATA;
377             if (empty($HTTP_RAW_POST_DATA)) {
378                 // workaround for a bug in PHP 5.2.2 - http://bugs.php.net/bug.php?id=41293
379                 $data = file_get_contents('php://input');
380             } else {
381                 $data =& $HTTP_RAW_POST_DATA;
382             }
383         }
384         $this->message = new IXR_Message($data);
385         if (!$this->message->parse()) {
386             $this->error(-32700, 'parse error. not well formed');
387         }
388         if ($this->message->messageType != 'methodCall') {
389             $this->error(-32600, 'server error. invalid xml-rpc. not conforming to spec. Request must be a methodCall');
390         }
391         $result = $this->call($this->message->methodName, $this->message->params);
392
393         // Is the result an error?
394         if (is_a($result, 'IXR_Error')) {
395             $this->error($result);
396         }
397
398         // Encode the result
399         $r = new IXR_Value($result);
400         $resultxml = $r->getXml();
401
402         // Create the XML
403         $xml = <<<EOD
404 <methodResponse>
405   <params>
406     <param>
407       <value>
408       $resultxml
409       </value>
410     </param>
411   </params>
412 </methodResponse>
413
414 EOD;
415       // Send it
416       $this->output($xml);
417     }
418
419     function call($methodname, $args)
420     {
421         if (!$this->hasMethod($methodname)) {
422             return new IXR_Error(-32601, 'server error. requested method '.$methodname.' does not exist.');
423         }
424         $method = $this->callbacks[$methodname];
425
426         // Perform the callback and send the response
427         if (count($args) == 1) {
428             // If only one paramater just send that instead of the whole array
429             $args = $args[0];
430         }
431
432         // Are we dealing with a function or a method?
433         if (is_string($method) && substr($method, 0, 5) == 'this:') {
434             // It's a class method - check it exists
435             $method = substr($method, 5);
436             if (!method_exists($this, $method)) {
437                 return new IXR_Error(-32601, 'server error. requested class method "'.$method.'" does not exist.');
438             }
439
440             //Call the method
441             $result = $this->$method($args);
442         } else {
443             // It's a function - does it exist?
444             if (is_array($method)) {
445                 if (!is_callable(array($method[0], $method[1]))) {
446                     return new IXR_Error(-32601, 'server error. requested object method "'.$method[1].'" does not exist.');
447                 }
448             } else if (!function_exists($method)) {
449                 return new IXR_Error(-32601, 'server error. requested function "'.$method.'" does not exist.');
450             }
451
452             // Call the function
453             $result = call_user_func($method, $args);
454         }
455         return $result;
456     }
457
458     function error($error, $message = false)
459     {
460         // Accepts either an error object or an error code and message
461         if ($message && !is_object($error)) {
462             $error = new IXR_Error($error, $message);
463         }
464         $this->output($error->getXml());
465     }
466
467     function output($xml)
468     {
469         $xml = '<?xml version="1.0"?>'."\n".$xml;
470         $length = strlen($xml);
471         header('Connection: close');
472         header('Content-Length: '.$length);
473         header('Content-Type: text/xml');
474         header('Date: '.date('r'));
475         echo $xml;
476         exit;
477     }
478
479     function hasMethod($method)
480     {
481         return in_array($method, array_keys($this->callbacks));
482     }
483
484     function setCapabilities()
485     {
486         // Initialises capabilities array
487         $this->capabilities = array(
488             'xmlrpc' => array(
489                 'specUrl' => 'http://www.xmlrpc.com/spec',
490                 'specVersion' => 1
491         ),
492             'faults_interop' => array(
493                 'specUrl' => 'http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php',
494                 'specVersion' => 20010516
495         ),
496             'system.multicall' => array(
497                 'specUrl' => 'http://www.xmlrpc.com/discuss/msgReader$1208',
498                 'specVersion' => 1
499         ),
500         );
501     }
502
503     function getCapabilities($args)
504     {
505         return $this->capabilities;
506     }
507
508     function setCallbacks()
509     {
510         $this->callbacks['system.getCapabilities'] = 'this:getCapabilities';
511         $this->callbacks['system.listMethods'] = 'this:listMethods';
512         $this->callbacks['system.multicall'] = 'this:multiCall';
513     }
514
515     function listMethods($args)
516     {
517         // Returns a list of methods - uses array_reverse to ensure user defined
518         // methods are listed before server defined methods
519         return array_reverse(array_keys($this->callbacks));
520     }
521
522     function multiCall($methodcalls)
523     {
524         // See http://www.xmlrpc.com/discuss/msgReader$1208
525         $return = array();
526         foreach ($methodcalls as $call) {
527             $method = $call['methodName'];
528             $params = $call['params'];
529             if ($method == 'system.multicall') {
530                 $result = new IXR_Error(-32600, 'Recursive calls to system.multicall are forbidden');
531             } else {
532                 $result = $this->call($method, $params);
533             }
534             if (is_a($result, 'IXR_Error')) {
535                 $return[] = array(
536                     'faultCode' => $result->code,
537                     'faultString' => $result->message
538                 );
539             } else {
540                 $return[] = array($result);
541             }
542         }
543         return $return;
544     }
545 }
546
547 /**
548  * IXR_Request
549  *
550  * @package IXR
551  * @since 1.5
552  */
553 class IXR_Request
554 {
555     var $method;
556     var $args;
557     var $xml;
558
559     function IXR_Request($method, $args)
560     {
561         $this->method = $method;
562         $this->args = $args;
563         $this->xml = <<<EOD
564 <?xml version="1.0"?>
565 <methodCall>
566 <methodName>{$this->method}</methodName>
567 <params>
568
569 EOD;
570         foreach ($this->args as $arg) {
571             $this->xml .= '<param><value>';
572             $v = new IXR_Value($arg);
573             $this->xml .= $v->getXml();
574             $this->xml .= "</value></param>\n";
575         }
576         $this->xml .= '</params></methodCall>';
577     }
578
579     function getLength()
580     {
581         return strlen($this->xml);
582     }
583
584     function getXml()
585     {
586         return $this->xml;
587     }
588 }
589
590 /**
591  * IXR_Client
592  *
593  * @package IXR
594  * @since 1.5
595  *
596  */
597 class IXR_Client
598 {
599     var $server;
600     var $port;
601     var $path;
602     var $useragent;
603     var $response;
604     var $message = false;
605     var $debug = false;
606     var $timeout;
607     var $headers = array();
608
609     // Storage place for an error message
610     var $error = false;
611
612     function IXR_Client($server, $path = false, $port = 80, $timeout = 15)
613     {
614         if (!$path) {
615             // Assume we have been given a URL instead
616             $bits = parse_url($server);
617             $this->server = $bits['host'];
618             $this->port = isset($bits['port']) ? $bits['port'] : 80;
619             $this->path = isset($bits['path']) ? $bits['path'] : '/';
620
621             // Make absolutely sure we have a path
622             if (!$this->path) {
623                 $this->path = '/';
624             }
625         } else {
626             $this->server = $server;
627             $this->path = $path;
628             $this->port = $port;
629         }
630         $this->useragent = 'The Incutio XML-RPC PHP Library';
631         $this->timeout = $timeout;
632     }
633
634     function query()
635     {
636         $args = func_get_args();
637         $method = array_shift($args);
638         $request = new IXR_Request($method, $args);
639         $length = $request->getLength();
640         $xml = $request->getXml();
641         $r = "\r\n";
642         $request  = "POST {$this->path} HTTP/1.0$r";
643
644         // Merged from WP #8145 - allow custom headers
645         $this->headers['Host']          = $this->server;
646         $this->headers['Content-Type']  = 'text/xml';
647         $this->headers['User-Agent']    = $this->useragent;
648         $this->headers['Content-Length']= $length;
649
650         foreach( $this->headers as $header => $value ) {
651             $request .= "{$header}: {$value}{$r}";
652         }
653         $request .= $r;
654
655         $request .= $xml;
656
657         // Now send the request
658         if ($this->debug) {
659             echo '<pre class="ixr_request">'.htmlspecialchars($request)."\n</pre>\n\n";
660         }
661
662         if ($this->timeout) {
663             $fp = @fsockopen($this->server, $this->port, $errno, $errstr, $this->timeout);
664         } else {
665             $fp = @fsockopen($this->server, $this->port, $errno, $errstr);
666         }
667         if (!$fp) {
668             $this->error = new IXR_Error(-32300, 'transport error - could not open socket');
669             return false;
670         }
671         fputs($fp, $request);
672         $contents = '';
673         $debugContents = '';
674         $gotFirstLine = false;
675         $gettingHeaders = true;
676         while (!feof($fp)) {
677             $line = fgets($fp, 4096);
678             if (!$gotFirstLine) {
679                 // Check line for '200'
680                 if (strstr($line, '200') === false) {
681                     $this->error = new IXR_Error(-32300, 'transport error - HTTP status code was not 200');
682                     return false;
683                 }
684                 $gotFirstLine = true;
685             }
686             if (trim($line) == '') {
687                 $gettingHeaders = false;
688             }
689             if (!$gettingHeaders) {
690                 // merged from WP #12559 - remove trim
691                 $contents .= $line;
692             }
693             if ($this->debug) {
694                 $debugContents .= $line;
695             }
696         }
697         if ($this->debug) {
698             echo '<pre class="ixr_response">'.htmlspecialchars($debugContents)."\n</pre>\n\n";
699         }
700
701         // Now parse what we've got back
702         $this->message = new IXR_Message($contents);
703         if (!$this->message->parse()) {
704             // XML error
705             $this->error = new IXR_Error(-32700, 'parse error. not well formed');
706             return false;
707         }
708
709         // Is the message a fault?
710         if ($this->message->messageType == 'fault') {
711             $this->error = new IXR_Error($this->message->faultCode, $this->message->faultString);
712             return false;
713         }
714
715         // Message must be OK
716         return true;
717     }
718
719     function getResponse()
720     {
721         // methodResponses can only have one param - return that
722         return $this->message->params[0];
723     }
724
725     function isError()
726     {
727         return (is_object($this->error));
728     }
729
730     function getErrorCode()
731     {
732         return $this->error->code;
733     }
734
735     function getErrorMessage()
736     {
737         return $this->error->message;
738     }
739 }
740
741
742 /**
743  * IXR_Error
744  *
745  * @package IXR
746  * @since 1.5
747  */
748 class IXR_Error
749 {
750     var $code;
751     var $message;
752
753     function IXR_Error($code, $message)
754     {
755         $this->code = $code;
756         $this->message = htmlspecialchars($message);
757     }
758
759     function getXml()
760     {
761         $xml = <<<EOD
762 <methodResponse>
763   <fault>
764     <value>
765       <struct>
766         <member>
767           <name>faultCode</name>
768           <value><int>{$this->code}</int></value>
769         </member>
770         <member>
771           <name>faultString</name>
772           <value><string>{$this->message}</string></value>
773         </member>
774       </struct>
775     </value>
776   </fault>
777 </methodResponse>
778
779 EOD;
780         return $xml;
781     }
782 }
783
784 /**
785  * IXR_Date
786  *
787  * @package IXR
788  * @since 1.5
789  */
790 class IXR_Date {
791     var $year;
792     var $month;
793     var $day;
794     var $hour;
795     var $minute;
796     var $second;
797     var $timezone;
798
799     function IXR_Date($time)
800     {
801         // $time can be a PHP timestamp or an ISO one
802         if (is_numeric($time)) {
803             $this->parseTimestamp($time);
804         } else {
805             $this->parseIso($time);
806         }
807     }
808
809     function parseTimestamp($timestamp)
810     {
811         $this->year = date('Y', $timestamp);
812         $this->month = date('m', $timestamp);
813         $this->day = date('d', $timestamp);
814         $this->hour = date('H', $timestamp);
815         $this->minute = date('i', $timestamp);
816         $this->second = date('s', $timestamp);
817         $this->timezone = '';
818     }
819
820     function parseIso($iso)
821     {
822         $this->year = substr($iso, 0, 4);
823         $this->month = substr($iso, 4, 2);
824         $this->day = substr($iso, 6, 2);
825         $this->hour = substr($iso, 9, 2);
826         $this->minute = substr($iso, 12, 2);
827         $this->second = substr($iso, 15, 2);
828         $this->timezone = substr($iso, 17);
829     }
830
831     function getIso()
832     {
833         return $this->year.$this->month.$this->day.'T'.$this->hour.':'.$this->minute.':'.$this->second.$this->timezone;
834     }
835
836     function getXml()
837     {
838         return '<dateTime.iso8601>'.$this->getIso().'</dateTime.iso8601>';
839     }
840
841     function getTimestamp()
842     {
843         return mktime($this->hour, $this->minute, $this->second, $this->month, $this->day, $this->year);
844     }
845 }
846
847 /**
848  * IXR_Base64
849  *
850  * @package IXR
851  * @since 1.5
852  */
853 class IXR_Base64
854 {
855     var $data;
856
857     function IXR_Base64($data)
858     {
859         $this->data = $data;
860     }
861
862     function getXml()
863     {
864         return '<base64>'.base64_encode($this->data).'</base64>';
865     }
866 }
867
868 /**
869  * IXR_IntrospectionServer
870  *
871  * @package IXR
872  * @since 1.5
873  */
874 class IXR_IntrospectionServer extends IXR_Server
875 {
876     var $signatures;
877     var $help;
878
879     function IXR_IntrospectionServer()
880     {
881         $this->setCallbacks();
882         $this->setCapabilities();
883         $this->capabilities['introspection'] = array(
884             'specUrl' => 'http://xmlrpc.usefulinc.com/doc/reserved.html',
885             'specVersion' => 1
886         );
887         $this->addCallback(
888             'system.methodSignature',
889             'this:methodSignature',
890             array('array', 'string'),
891             'Returns an array describing the return type and required parameters of a method'
892         );
893         $this->addCallback(
894             'system.getCapabilities',
895             'this:getCapabilities',
896             array('struct'),
897             'Returns a struct describing the XML-RPC specifications supported by this server'
898         );
899         $this->addCallback(
900             'system.listMethods',
901             'this:listMethods',
902             array('array'),
903             'Returns an array of available methods on this server'
904         );
905         $this->addCallback(
906             'system.methodHelp',
907             'this:methodHelp',
908             array('string', 'string'),
909             'Returns a documentation string for the specified method'
910         );
911     }
912
913     function addCallback($method, $callback, $args, $help)
914     {
915         $this->callbacks[$method] = $callback;
916         $this->signatures[$method] = $args;
917         $this->help[$method] = $help;
918     }
919
920     function call($methodname, $args)
921     {
922         // Make sure it's in an array
923         if ($args && !is_array($args)) {
924             $args = array($args);
925         }
926
927         // Over-rides default call method, adds signature check
928         if (!$this->hasMethod($methodname)) {
929             return new IXR_Error(-32601, 'server error. requested method "'.$this->message->methodName.'" not specified.');
930         }
931         $method = $this->callbacks[$methodname];
932         $signature = $this->signatures[$methodname];
933         $returnType = array_shift($signature);
934
935         // Check the number of arguments
936         if (count($args) != count($signature)) {
937             return new IXR_Error(-32602, 'server error. wrong number of method parameters');
938         }
939
940         // Check the argument types
941         $ok = true;
942         $argsbackup = $args;
943         for ($i = 0, $j = count($args); $i < $j; $i++) {
944             $arg = array_shift($args);
945             $type = array_shift($signature);
946             switch ($type) {
947                 case 'int':
948                 case 'i4':
949                     if (is_array($arg) || !is_int($arg)) {
950                         $ok = false;
951                     }
952                     break;
953                 case 'base64':
954                 case 'string':
955                     if (!is_string($arg)) {
956                         $ok = false;
957                     }
958                     break;
959                 case 'boolean':
960                     if ($arg !== false && $arg !== true) {
961                         $ok = false;
962                     }
963                     break;
964                 case 'float':
965                 case 'double':
966                     if (!is_float($arg)) {
967                         $ok = false;
968                     }
969                     break;
970                 case 'date':
971                 case 'dateTime.iso8601':
972                     if (!is_a($arg, 'IXR_Date')) {
973                         $ok = false;
974                     }
975                     break;
976             }
977             if (!$ok) {
978                 return new IXR_Error(-32602, 'server error. invalid method parameters');
979             }
980         }
981         // It passed the test - run the "real" method call
982         return parent::call($methodname, $argsbackup);
983     }
984
985     function methodSignature($method)
986     {
987         if (!$this->hasMethod($method)) {
988             return new IXR_Error(-32601, 'server error. requested method "'.$method.'" not specified.');
989         }
990         // We should be returning an array of types
991         $types = $this->signatures[$method];
992         $return = array();
993         foreach ($types as $type) {
994             switch ($type) {
995                 case 'string':
996                     $return[] = 'string';
997                     break;
998                 case 'int':
999                 case 'i4':
1000                     $return[] = 42;
1001                     break;
1002                 case 'double':
1003                     $return[] = 3.1415;
1004                     break;
1005                 case 'dateTime.iso8601':
1006                     $return[] = new IXR_Date(time());
1007                     break;
1008                 case 'boolean':
1009                     $return[] = true;
1010                     break;
1011                 case 'base64':
1012                     $return[] = new IXR_Base64('base64');
1013                     break;
1014                 case 'array':
1015                     $return[] = array('array');
1016                     break;
1017                 case 'struct':
1018                     $return[] = array('struct' => 'struct');
1019                     break;
1020             }
1021         }
1022         return $return;
1023     }
1024
1025     function methodHelp($method)
1026     {
1027         return $this->help[$method];
1028     }
1029 }
1030
1031 /**
1032  * IXR_ClientMulticall
1033  *
1034  * @package IXR
1035  * @since 1.5
1036  */
1037 class IXR_ClientMulticall extends IXR_Client
1038 {
1039     var $calls = array();
1040
1041     function IXR_ClientMulticall($server, $path = false, $port = 80)
1042     {
1043         parent::IXR_Client($server, $path, $port);
1044         $this->useragent = 'The Incutio XML-RPC PHP Library (multicall client)';
1045     }
1046
1047     function addCall()
1048     {
1049         $args = func_get_args();
1050         $methodName = array_shift($args);
1051         $struct = array(
1052             'methodName' => $methodName,
1053             'params' => $args
1054         );
1055         $this->calls[] = $struct;
1056     }
1057
1058     function query()
1059     {
1060         // Prepare multicall, then call the parent::query() method
1061         return parent::query('system.multicall', $this->calls);
1062     }
1063 }