Wordpress 3.5
[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         $charset = function_exists('get_option') ? get_option('blog_charset') : '';
470         if ($charset)
471             $xml = '<?xml version="1.0" encoding="'.$charset.'"?>'."\n".$xml;
472         else
473             $xml = '<?xml version="1.0"?>'."\n".$xml;
474         $length = strlen($xml);
475         header('Connection: close');
476         header('Content-Length: '.$length);
477         if ($charset)
478             header('Content-Type: text/xml; charset='.$charset);
479         else
480             header('Content-Type: text/xml');
481         header('Date: '.date('r'));
482         echo $xml;
483         exit;
484     }
485
486     function hasMethod($method)
487     {
488         return in_array($method, array_keys($this->callbacks));
489     }
490
491     function setCapabilities()
492     {
493         // Initialises capabilities array
494         $this->capabilities = array(
495             'xmlrpc' => array(
496                 'specUrl' => 'http://www.xmlrpc.com/spec',
497                 'specVersion' => 1
498         ),
499             'faults_interop' => array(
500                 'specUrl' => 'http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php',
501                 'specVersion' => 20010516
502         ),
503             'system.multicall' => array(
504                 'specUrl' => 'http://www.xmlrpc.com/discuss/msgReader$1208',
505                 'specVersion' => 1
506         ),
507         );
508     }
509
510     function getCapabilities($args)
511     {
512         return $this->capabilities;
513     }
514
515     function setCallbacks()
516     {
517         $this->callbacks['system.getCapabilities'] = 'this:getCapabilities';
518         $this->callbacks['system.listMethods'] = 'this:listMethods';
519         $this->callbacks['system.multicall'] = 'this:multiCall';
520     }
521
522     function listMethods($args)
523     {
524         // Returns a list of methods - uses array_reverse to ensure user defined
525         // methods are listed before server defined methods
526         return array_reverse(array_keys($this->callbacks));
527     }
528
529     function multiCall($methodcalls)
530     {
531         // See http://www.xmlrpc.com/discuss/msgReader$1208
532         $return = array();
533         foreach ($methodcalls as $call) {
534             $method = $call['methodName'];
535             $params = $call['params'];
536             if ($method == 'system.multicall') {
537                 $result = new IXR_Error(-32600, 'Recursive calls to system.multicall are forbidden');
538             } else {
539                 $result = $this->call($method, $params);
540             }
541             if (is_a($result, 'IXR_Error')) {
542                 $return[] = array(
543                     'faultCode' => $result->code,
544                     'faultString' => $result->message
545                 );
546             } else {
547                 $return[] = array($result);
548             }
549         }
550         return $return;
551     }
552 }
553
554 /**
555  * IXR_Request
556  *
557  * @package IXR
558  * @since 1.5
559  */
560 class IXR_Request
561 {
562     var $method;
563     var $args;
564     var $xml;
565
566     function IXR_Request($method, $args)
567     {
568         $this->method = $method;
569         $this->args = $args;
570         $this->xml = <<<EOD
571 <?xml version="1.0"?>
572 <methodCall>
573 <methodName>{$this->method}</methodName>
574 <params>
575
576 EOD;
577         foreach ($this->args as $arg) {
578             $this->xml .= '<param><value>';
579             $v = new IXR_Value($arg);
580             $this->xml .= $v->getXml();
581             $this->xml .= "</value></param>\n";
582         }
583         $this->xml .= '</params></methodCall>';
584     }
585
586     function getLength()
587     {
588         return strlen($this->xml);
589     }
590
591     function getXml()
592     {
593         return $this->xml;
594     }
595 }
596
597 /**
598  * IXR_Client
599  *
600  * @package IXR
601  * @since 1.5
602  *
603  */
604 class IXR_Client
605 {
606     var $server;
607     var $port;
608     var $path;
609     var $useragent;
610     var $response;
611     var $message = false;
612     var $debug = false;
613     var $timeout;
614     var $headers = array();
615
616     // Storage place for an error message
617     var $error = false;
618
619     function IXR_Client($server, $path = false, $port = 80, $timeout = 15)
620     {
621         if (!$path) {
622             // Assume we have been given a URL instead
623             $bits = parse_url($server);
624             $this->server = $bits['host'];
625             $this->port = isset($bits['port']) ? $bits['port'] : 80;
626             $this->path = isset($bits['path']) ? $bits['path'] : '/';
627
628             // Make absolutely sure we have a path
629             if (!$this->path) {
630                 $this->path = '/';
631             }
632         } else {
633             $this->server = $server;
634             $this->path = $path;
635             $this->port = $port;
636         }
637         $this->useragent = 'The Incutio XML-RPC PHP Library';
638         $this->timeout = $timeout;
639     }
640
641     function query()
642     {
643         $args = func_get_args();
644         $method = array_shift($args);
645         $request = new IXR_Request($method, $args);
646         $length = $request->getLength();
647         $xml = $request->getXml();
648         $r = "\r\n";
649         $request  = "POST {$this->path} HTTP/1.0$r";
650
651         // Merged from WP #8145 - allow custom headers
652         $this->headers['Host']          = $this->server;
653         $this->headers['Content-Type']  = 'text/xml';
654         $this->headers['User-Agent']    = $this->useragent;
655         $this->headers['Content-Length']= $length;
656
657         foreach( $this->headers as $header => $value ) {
658             $request .= "{$header}: {$value}{$r}";
659         }
660         $request .= $r;
661
662         $request .= $xml;
663
664         // Now send the request
665         if ($this->debug) {
666             echo '<pre class="ixr_request">'.htmlspecialchars($request)."\n</pre>\n\n";
667         }
668
669         if ($this->timeout) {
670             $fp = @fsockopen($this->server, $this->port, $errno, $errstr, $this->timeout);
671         } else {
672             $fp = @fsockopen($this->server, $this->port, $errno, $errstr);
673         }
674         if (!$fp) {
675             $this->error = new IXR_Error(-32300, 'transport error - could not open socket');
676             return false;
677         }
678         fputs($fp, $request);
679         $contents = '';
680         $debugContents = '';
681         $gotFirstLine = false;
682         $gettingHeaders = true;
683         while (!feof($fp)) {
684             $line = fgets($fp, 4096);
685             if (!$gotFirstLine) {
686                 // Check line for '200'
687                 if (strstr($line, '200') === false) {
688                     $this->error = new IXR_Error(-32300, 'transport error - HTTP status code was not 200');
689                     return false;
690                 }
691                 $gotFirstLine = true;
692             }
693             if (trim($line) == '') {
694                 $gettingHeaders = false;
695             }
696             if (!$gettingHeaders) {
697                 // merged from WP #12559 - remove trim
698                 $contents .= $line;
699             }
700             if ($this->debug) {
701                 $debugContents .= $line;
702             }
703         }
704         if ($this->debug) {
705             echo '<pre class="ixr_response">'.htmlspecialchars($debugContents)."\n</pre>\n\n";
706         }
707
708         // Now parse what we've got back
709         $this->message = new IXR_Message($contents);
710         if (!$this->message->parse()) {
711             // XML error
712             $this->error = new IXR_Error(-32700, 'parse error. not well formed');
713             return false;
714         }
715
716         // Is the message a fault?
717         if ($this->message->messageType == 'fault') {
718             $this->error = new IXR_Error($this->message->faultCode, $this->message->faultString);
719             return false;
720         }
721
722         // Message must be OK
723         return true;
724     }
725
726     function getResponse()
727     {
728         // methodResponses can only have one param - return that
729         return $this->message->params[0];
730     }
731
732     function isError()
733     {
734         return (is_object($this->error));
735     }
736
737     function getErrorCode()
738     {
739         return $this->error->code;
740     }
741
742     function getErrorMessage()
743     {
744         return $this->error->message;
745     }
746 }
747
748
749 /**
750  * IXR_Error
751  *
752  * @package IXR
753  * @since 1.5
754  */
755 class IXR_Error
756 {
757     var $code;
758     var $message;
759
760     function IXR_Error($code, $message)
761     {
762         $this->code = $code;
763         $this->message = htmlspecialchars($message);
764     }
765
766     function getXml()
767     {
768         $xml = <<<EOD
769 <methodResponse>
770   <fault>
771     <value>
772       <struct>
773         <member>
774           <name>faultCode</name>
775           <value><int>{$this->code}</int></value>
776         </member>
777         <member>
778           <name>faultString</name>
779           <value><string>{$this->message}</string></value>
780         </member>
781       </struct>
782     </value>
783   </fault>
784 </methodResponse>
785
786 EOD;
787         return $xml;
788     }
789 }
790
791 /**
792  * IXR_Date
793  *
794  * @package IXR
795  * @since 1.5
796  */
797 class IXR_Date {
798     var $year;
799     var $month;
800     var $day;
801     var $hour;
802     var $minute;
803     var $second;
804     var $timezone;
805
806     function IXR_Date($time)
807     {
808         // $time can be a PHP timestamp or an ISO one
809         if (is_numeric($time)) {
810             $this->parseTimestamp($time);
811         } else {
812             $this->parseIso($time);
813         }
814     }
815
816     function parseTimestamp($timestamp)
817     {
818         $this->year = date('Y', $timestamp);
819         $this->month = date('m', $timestamp);
820         $this->day = date('d', $timestamp);
821         $this->hour = date('H', $timestamp);
822         $this->minute = date('i', $timestamp);
823         $this->second = date('s', $timestamp);
824         $this->timezone = '';
825     }
826
827     function parseIso($iso)
828     {
829         $this->year = substr($iso, 0, 4);
830         $this->month = substr($iso, 4, 2);
831         $this->day = substr($iso, 6, 2);
832         $this->hour = substr($iso, 9, 2);
833         $this->minute = substr($iso, 12, 2);
834         $this->second = substr($iso, 15, 2);
835         $this->timezone = substr($iso, 17);
836     }
837
838     function getIso()
839     {
840         return $this->year.$this->month.$this->day.'T'.$this->hour.':'.$this->minute.':'.$this->second.$this->timezone;
841     }
842
843     function getXml()
844     {
845         return '<dateTime.iso8601>'.$this->getIso().'</dateTime.iso8601>';
846     }
847
848     function getTimestamp()
849     {
850         return mktime($this->hour, $this->minute, $this->second, $this->month, $this->day, $this->year);
851     }
852 }
853
854 /**
855  * IXR_Base64
856  *
857  * @package IXR
858  * @since 1.5
859  */
860 class IXR_Base64
861 {
862     var $data;
863
864     function IXR_Base64($data)
865     {
866         $this->data = $data;
867     }
868
869     function getXml()
870     {
871         return '<base64>'.base64_encode($this->data).'</base64>';
872     }
873 }
874
875 /**
876  * IXR_IntrospectionServer
877  *
878  * @package IXR
879  * @since 1.5
880  */
881 class IXR_IntrospectionServer extends IXR_Server
882 {
883     var $signatures;
884     var $help;
885
886     function IXR_IntrospectionServer()
887     {
888         $this->setCallbacks();
889         $this->setCapabilities();
890         $this->capabilities['introspection'] = array(
891             'specUrl' => 'http://xmlrpc.usefulinc.com/doc/reserved.html',
892             'specVersion' => 1
893         );
894         $this->addCallback(
895             'system.methodSignature',
896             'this:methodSignature',
897             array('array', 'string'),
898             'Returns an array describing the return type and required parameters of a method'
899         );
900         $this->addCallback(
901             'system.getCapabilities',
902             'this:getCapabilities',
903             array('struct'),
904             'Returns a struct describing the XML-RPC specifications supported by this server'
905         );
906         $this->addCallback(
907             'system.listMethods',
908             'this:listMethods',
909             array('array'),
910             'Returns an array of available methods on this server'
911         );
912         $this->addCallback(
913             'system.methodHelp',
914             'this:methodHelp',
915             array('string', 'string'),
916             'Returns a documentation string for the specified method'
917         );
918     }
919
920     function addCallback($method, $callback, $args, $help)
921     {
922         $this->callbacks[$method] = $callback;
923         $this->signatures[$method] = $args;
924         $this->help[$method] = $help;
925     }
926
927     function call($methodname, $args)
928     {
929         // Make sure it's in an array
930         if ($args && !is_array($args)) {
931             $args = array($args);
932         }
933
934         // Over-rides default call method, adds signature check
935         if (!$this->hasMethod($methodname)) {
936             return new IXR_Error(-32601, 'server error. requested method "'.$this->message->methodName.'" not specified.');
937         }
938         $method = $this->callbacks[$methodname];
939         $signature = $this->signatures[$methodname];
940         $returnType = array_shift($signature);
941
942         // Check the number of arguments
943         if (count($args) != count($signature)) {
944             return new IXR_Error(-32602, 'server error. wrong number of method parameters');
945         }
946
947         // Check the argument types
948         $ok = true;
949         $argsbackup = $args;
950         for ($i = 0, $j = count($args); $i < $j; $i++) {
951             $arg = array_shift($args);
952             $type = array_shift($signature);
953             switch ($type) {
954                 case 'int':
955                 case 'i4':
956                     if (is_array($arg) || !is_int($arg)) {
957                         $ok = false;
958                     }
959                     break;
960                 case 'base64':
961                 case 'string':
962                     if (!is_string($arg)) {
963                         $ok = false;
964                     }
965                     break;
966                 case 'boolean':
967                     if ($arg !== false && $arg !== true) {
968                         $ok = false;
969                     }
970                     break;
971                 case 'float':
972                 case 'double':
973                     if (!is_float($arg)) {
974                         $ok = false;
975                     }
976                     break;
977                 case 'date':
978                 case 'dateTime.iso8601':
979                     if (!is_a($arg, 'IXR_Date')) {
980                         $ok = false;
981                     }
982                     break;
983             }
984             if (!$ok) {
985                 return new IXR_Error(-32602, 'server error. invalid method parameters');
986             }
987         }
988         // It passed the test - run the "real" method call
989         return parent::call($methodname, $argsbackup);
990     }
991
992     function methodSignature($method)
993     {
994         if (!$this->hasMethod($method)) {
995             return new IXR_Error(-32601, 'server error. requested method "'.$method.'" not specified.');
996         }
997         // We should be returning an array of types
998         $types = $this->signatures[$method];
999         $return = array();
1000         foreach ($types as $type) {
1001             switch ($type) {
1002                 case 'string':
1003                     $return[] = 'string';
1004                     break;
1005                 case 'int':
1006                 case 'i4':
1007                     $return[] = 42;
1008                     break;
1009                 case 'double':
1010                     $return[] = 3.1415;
1011                     break;
1012                 case 'dateTime.iso8601':
1013                     $return[] = new IXR_Date(time());
1014                     break;
1015                 case 'boolean':
1016                     $return[] = true;
1017                     break;
1018                 case 'base64':
1019                     $return[] = new IXR_Base64('base64');
1020                     break;
1021                 case 'array':
1022                     $return[] = array('array');
1023                     break;
1024                 case 'struct':
1025                     $return[] = array('struct' => 'struct');
1026                     break;
1027             }
1028         }
1029         return $return;
1030     }
1031
1032     function methodHelp($method)
1033     {
1034         return $this->help[$method];
1035     }
1036 }
1037
1038 /**
1039  * IXR_ClientMulticall
1040  *
1041  * @package IXR
1042  * @since 1.5
1043  */
1044 class IXR_ClientMulticall extends IXR_Client
1045 {
1046     var $calls = array();
1047
1048     function IXR_ClientMulticall($server, $path = false, $port = 80)
1049     {
1050         parent::IXR_Client($server, $path, $port);
1051         $this->useragent = 'The Incutio XML-RPC PHP Library (multicall client)';
1052     }
1053
1054     function addCall()
1055     {
1056         $args = func_get_args();
1057         $methodName = array_shift($args);
1058         $struct = array(
1059             'methodName' => $methodName,
1060             'params' => $args
1061         );
1062         $this->calls[] = $struct;
1063     }
1064
1065     function query()
1066     {
1067         // Prepare multicall, then call the parent::query() method
1068         return parent::query('system.multicall', $this->calls);
1069     }
1070 }