web/wp-includes/class-IXR.php
changeset 136 bde1974c263b
child 194 32102edaa81b
equal deleted inserted replaced
135:53cff4b4a802 136:bde1974c263b
       
     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  */
       
    22 class 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  */
       
   139 class 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  */
       
   294 class 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 
       
   343 EOD;
       
   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  */
       
   465 class 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 
       
   478 EOD;
       
   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  */
       
   501 class 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  */
       
   629 class 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 
       
   656 EOD;
       
   657         return $xml;
       
   658     }
       
   659 }
       
   660 
       
   661 /**
       
   662  * IXR_Date
       
   663  *
       
   664  * @package IXR
       
   665  * @since 1.5
       
   666  */
       
   667 class 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, 4, 2);
       
   696         $this->day = substr($iso, 6, 2);
       
   697         $this->hour = substr($iso, 9, 2);
       
   698         $this->minute = substr($iso, 12, 2);
       
   699         $this->second = substr($iso, 15, 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  */
       
   721 class 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  */
       
   737 class 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  */
       
   886 class 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 ?>