specfile
[plewww.git] / includes / xmlrpc.inc
1 <?php
2 // $Id: xmlrpc.inc 144 2007-03-28 07:52:20Z thierry $
3
4 /*
5   Drupal XML-RPC library. Based on the IXR - The Incutio XML-RPC Library - (c) Incutio Ltd 2002-2005
6    Version 1.7 (beta) - Simon Willison, 23rd May 2005
7    Site:   http://scripts.incutio.com/xmlrpc/
8    Manual: http://scripts.incutio.com/xmlrpc/manual.php
9    This version is made available under the GNU GPL License
10 */
11
12 /**
13  * Recursively turn a data structure into objects with 'data' and 'type' attributes.
14  *
15  * @param $data
16  *   The data structure.
17  * @param  $type
18  *   Optional type assign to $data.
19  * @return
20  *   Object.
21  */
22 function xmlrpc_value($data, $type = FALSE) {
23   $xmlrpc_value = new stdClass();
24   $xmlrpc_value->data = $data;
25   if (!$type) {
26     $type = xmlrpc_value_calculate_type($xmlrpc_value);
27   }
28   $xmlrpc_value->type = $type;
29   if ($type == 'struct') {
30     // Turn all the values in the array into new xmlrpc_values
31     foreach ($xmlrpc_value->data as $key => $value) {
32       $xmlrpc_value->data[$key] = xmlrpc_value($value);
33     }
34   }
35   if ($type == 'array') {
36     for ($i = 0, $j = count($xmlrpc_value->data); $i < $j; $i++) {
37       $xmlrpc_value->data[$i] = xmlrpc_value($xmlrpc_value->data[$i]);
38     }
39   }
40   return $xmlrpc_value;
41 }
42
43 /**
44  * Map PHP type to XML-RPC type.
45  *
46  * @param $xmlrpc_value
47  *   Variable whose type should be mapped.
48  * @return
49  *   XML-RPC type as string.
50  * @see
51  *   http://www.xmlrpc.com/spec#scalars
52  */
53 function xmlrpc_value_calculate_type(&$xmlrpc_value) {
54   // http://www.php.net/gettype: Never use gettype() to test for a certain type [...] Instead, use the is_* functions.
55   if (is_bool($xmlrpc_value->data)) {
56     return 'boolean';
57   }
58   if (is_double($xmlrpc_value->data)) {
59     return 'double';
60   }
61   if (is_int($xmlrpc_value->data)) {
62       return 'int';
63   }
64   if (is_array($xmlrpc_value->data)) {
65     // empty or integer-indexed arrays are 'array', string-indexed arrays 'struct'
66     return empty($xmlrpc_value->data) || range(0, count($xmlrpc_value->data) - 1) === array_keys($xmlrpc_value->data) ? 'array' : 'struct';
67   }
68   if (is_object($xmlrpc_value->data)) {
69     if ($xmlrpc_value->data->is_date) {
70       return 'date';
71     }
72     if ($xmlrpc_value->data->is_base64) {
73       return 'base64';
74     }
75     $xmlrpc_value->data = get_object_vars($xmlrpc_value->data);
76     return 'struct';
77   }
78   // default
79   return 'string';
80 }
81
82 /**
83  * Generate XML representing the given value.
84  *
85  * @param $xmlrpc_value
86  * @return
87  *   XML representation of value.
88  */
89 function xmlrpc_value_get_xml($xmlrpc_value) {
90   switch ($xmlrpc_value->type) {
91     case 'boolean':
92       return '<boolean>'. (($xmlrpc_value->data) ? '1' : '0') .'</boolean>';
93       break;
94     case 'int':
95       return '<int>'. $xmlrpc_value->data .'</int>';
96       break;
97     case 'double':
98       return '<double>'. $xmlrpc_value->data .'</double>';
99       break;
100     case 'string':
101       // Note: we don't escape apostrophes because of the many blogging clients
102       // that don't support numerical entities (and XML in general) properly.
103       return '<string>'. htmlspecialchars($xmlrpc_value->data) .'</string>';
104       break;
105     case 'array':
106       $return = '<array><data>'."\n";
107       foreach ($xmlrpc_value->data as $item) {
108         $return .= '  <value>'. xmlrpc_value_get_xml($item) ."</value>\n";
109       }
110       $return .= '</data></array>';
111       return $return;
112       break;
113     case 'struct':
114       $return = '<struct>'."\n";
115       foreach ($xmlrpc_value->data as $name => $value) {
116         $return .= "  <member><name>". check_plain($name) ."</name><value>";
117         $return .= xmlrpc_value_get_xml($value)."</value></member>\n";
118       }
119       $return .= '</struct>';
120       return $return;
121       break;
122     case 'date':
123       return xmlrpc_date_get_xml($xmlrpc_value->data);
124       break;
125     case 'base64':
126       return xmlrpc_base64_get_xml($xmlrpc_value->data);
127       break;
128   }
129   return FALSE;
130 }
131
132 /**
133  * Construct an object representing an XML-RPC message.
134  *
135  * @param $message
136  *   String containing XML as defined at http://www.xmlrpc.com/spec
137  * @return
138  *   Object
139  */
140 function xmlrpc_message($message) {
141   $xmlrpc_message = new stdClass();
142   $xmlrpc_message->array_structs = array();   // The stack used to keep track of the current array/struct
143   $xmlrpc_message->array_structs_types = array(); // The stack used to keep track of if things are structs or array
144   $xmlrpc_message->current_struct_name = array();  // A stack as well
145   $xmlrpc_message->message = $message;
146   return $xmlrpc_message;
147 }
148
149 /**
150  * Parse an XML-RPC message. If parsing fails, the faultCode and faultString
151  * will be added to the message object.
152  *
153  * @param $xmlrpc_message
154  *   Object generated by xmlrpc_message()
155  * @return
156  *   TRUE if parsing succeeded; FALSE otherwise
157  */
158 function xmlrpc_message_parse(&$xmlrpc_message) {
159   // First remove the XML declaration
160   $xmlrpc_message->message = preg_replace('/<\?xml(.*)?\?'.'>/', '', $xmlrpc_message->message);
161   if (trim($xmlrpc_message->message) == '') {
162     return FALSE;
163   }
164   $xmlrpc_message->_parser = xml_parser_create();
165   // Set XML parser to take the case of tags into account
166   xml_parser_set_option($xmlrpc_message->_parser, XML_OPTION_CASE_FOLDING, FALSE);
167   // Set XML parser callback functions
168   /* Do not set object. $xmlrpc_message does not have member functions any more
169   xml_set_object($xmlrpc_message->_parser, $xmlrpc_message); */
170   xml_set_element_handler($xmlrpc_message->_parser, 'xmlrpc_message_tag_open', 'xmlrpc_message_tag_close');
171   xml_set_character_data_handler($xmlrpc_message->_parser, 'xmlrpc_message_cdata');
172   xmlrpc_message_set($xmlrpc_message);
173   if (!xml_parse($xmlrpc_message->_parser, $xmlrpc_message->message)) {
174     return FALSE;
175   }
176   xml_parser_free($xmlrpc_message->_parser);
177   // Grab the error messages, if any
178   $xmlrpc_message = xmlrpc_message_get();
179   if ($xmlrpc_message->messagetype == 'fault') {
180     $xmlrpc_message->fault_code = $xmlrpc_message->params[0]['faultCode'];
181     $xmlrpc_message->fault_string = $xmlrpc_message->params[0]['faultString'];
182   }
183   return TRUE;
184 }
185
186 /**
187  * Store a copy of the $xmlrpc_message object temporarily.
188  *
189  * @param $value
190  *   Object
191  * @return
192  *   The most recently stored $xmlrpc_message
193  */
194 function xmlrpc_message_set($value = NULL) {
195   static $xmlrpc_message;
196   if ($value) {
197     $xmlrpc_message = $value;
198   }
199   return $xmlrpc_message;
200 }
201
202 function xmlrpc_message_get() {
203   return xmlrpc_message_set();
204 }
205
206 function xmlrpc_message_tag_open($parser, $tag, $attr) {
207   $xmlrpc_message = xmlrpc_message_get();
208   $xmlrpc_message->current_tag_contents = '';
209   $xmlrpc_message->last_open = $tag;
210   switch($tag) {
211     case 'methodCall':
212     case 'methodResponse':
213     case 'fault':
214       $xmlrpc_message->messagetype = $tag;
215       break;
216     // Deal with stacks of arrays and structs
217     case 'data':
218       $xmlrpc_message->array_structs_types[] = 'array';
219       $xmlrpc_message->array_structs[] = array();
220       break;
221     case 'struct':
222       $xmlrpc_message->array_structs_types[] = 'struct';
223       $xmlrpc_message->array_structs[] = array();
224       break;
225   }
226   xmlrpc_message_set($xmlrpc_message);
227 }
228
229 function xmlrpc_message_cdata($parser, $cdata) {
230   $xmlrpc_message = xmlrpc_message_get();
231   $xmlrpc_message->current_tag_contents .= $cdata;
232   xmlrpc_message_set($xmlrpc_message);
233 }
234
235 function xmlrpc_message_tag_close($parser, $tag) {
236   $xmlrpc_message = xmlrpc_message_get();
237   $value_flag = FALSE;
238   switch($tag) {
239     case 'int':
240     case 'i4':
241       $value = (int)trim($xmlrpc_message->current_tag_contents);
242       $value_flag = TRUE;
243       break;
244     case 'double':
245       $value = (double)trim($xmlrpc_message->current_tag_contents);
246       $value_flag = TRUE;
247       break;
248     case 'string':
249       $value = $xmlrpc_message->current_tag_contents;
250       $value_flag = TRUE;
251       break;
252     case 'dateTime.iso8601':
253       $value = xmlrpc_date(trim($xmlrpc_message->current_tag_contents));
254       // $value = $iso->getTimestamp();
255       $value_flag = TRUE;
256       break;
257     case 'value':
258       // If no type is indicated, the type is string
259       // We take special care for empty values
260       if (trim($xmlrpc_message->current_tag_contents) != '' || $xmlrpc_message->last_open == 'value') {
261         $value = (string)$xmlrpc_message->current_tag_contents;
262         $value_flag = TRUE;
263       }
264       unset($xmlrpc_message->last_open);
265       break;
266     case 'boolean':
267       $value = (boolean)trim($xmlrpc_message->current_tag_contents);
268       $value_flag = TRUE;
269       break;
270     case 'base64':
271       $value = base64_decode(trim($xmlrpc_message->current_tag_contents));
272       $value_flag = TRUE;
273       break;
274     // Deal with stacks of arrays and structs
275     case 'data':
276     case 'struct':
277       $value = array_pop($xmlrpc_message->array_structs );
278       array_pop($xmlrpc_message->array_structs_types);
279       $value_flag = TRUE;
280       break;
281     case 'member':
282       array_pop($xmlrpc_message->current_struct_name);
283       break;
284     case 'name':
285       $xmlrpc_message->current_struct_name[] = trim($xmlrpc_message->current_tag_contents);
286       break;
287     case 'methodName':
288       $xmlrpc_message->methodname = trim($xmlrpc_message->current_tag_contents);
289       break;
290   }
291   if ($value_flag) {
292     if (count($xmlrpc_message->array_structs ) > 0) {
293       // Add value to struct or array
294       if ($xmlrpc_message->array_structs_types[count($xmlrpc_message->array_structs_types)-1] == 'struct') {
295         // Add to struct
296         $xmlrpc_message->array_structs [count($xmlrpc_message->array_structs )-1][$xmlrpc_message->current_struct_name[count($xmlrpc_message->current_struct_name)-1]] = $value;
297       }
298       else {
299         // Add to array
300         $xmlrpc_message->array_structs [count($xmlrpc_message->array_structs )-1][] = $value;
301       }
302     }
303     else {
304       // Just add as a parameter
305       $xmlrpc_message->params[] = $value;
306     }
307   }
308   if (!in_array($tag, array("data", "struct", "member"))) {
309     $xmlrpc_message->current_tag_contents = '';
310   }
311   xmlrpc_message_set($xmlrpc_message);
312 }
313
314 /**
315  * Construct an object representing an XML-RPC request
316  *
317  * @param $method
318  *   The name of the method to be called
319  * @param $args
320  *   An array of parameters to send with the method.
321  * @return
322  *   Object
323  */
324 function xmlrpc_request($method, $args) {
325   $xmlrpc_request = new stdClass();
326   $xmlrpc_request->method = $method;
327   $xmlrpc_request->args = $args;
328   $xmlrpc_request->xml = <<<EOD
329 <?xml version="1.0"?>
330 <methodCall>
331 <methodName>{$xmlrpc_request->method}</methodName>
332 <params>
333
334 EOD;
335   foreach ($xmlrpc_request->args as $arg) {
336     $xmlrpc_request->xml .= '<param><value>';
337     $v = xmlrpc_value($arg);
338     $xmlrpc_request->xml .= xmlrpc_value_get_xml($v);
339     $xmlrpc_request->xml .= "</value></param>\n";
340   }
341   $xmlrpc_request->xml .= '</params></methodCall>';
342   return $xmlrpc_request;
343 }
344
345
346 function xmlrpc_error($code = NULL, $message = NULL) {
347   static $xmlrpc_error;
348   if (isset($code)) {
349     $xmlrpc_error = new stdClass();
350     $xmlrpc_error->is_error = TRUE;
351     $xmlrpc_error->code = $code;
352     $xmlrpc_error->message = $message;
353   }
354   return $xmlrpc_error;
355 }
356
357 function xmlrpc_error_get_xml($xmlrpc_error) {
358     return <<<EOD
359 <methodResponse>
360   <fault>
361   <value>
362     <struct>
363     <member>
364       <name>faultCode</name>
365       <value><int>{$xmlrpc_error->code}</int></value>
366     </member>
367     <member>
368       <name>faultString</name>
369       <value><string>{$xmlrpc_error->message}</string></value>
370     </member>
371     </struct>
372   </value>
373   </fault>
374 </methodResponse>
375
376 EOD;
377 }
378
379 function xmlrpc_date($time) {
380   $xmlrpc_date = new stdClass();
381   $xmlrpc_date->is_date = TRUE;
382   // $time can be a PHP timestamp or an ISO one
383   if (is_numeric($time)) {
384     $xmlrpc_date->year = date('Y', $time);
385     $xmlrpc_date->month = date('m', $time);
386     $xmlrpc_date->day = date('d', $time);
387     $xmlrpc_date->hour = date('H', $time);
388     $xmlrpc_date->minute = date('i', $time);
389     $xmlrpc_date->second = date('s', $time);
390     $xmlrpc_date->iso8601 = date('Ymd\TH:i:s');
391   }
392   else {
393     $xmlrpc_date->year = substr($time, 0, 4);
394     $xmlrpc_date->month = substr($time, 4, 2);
395     $xmlrpc_date->day = substr($time, 6, 2);
396     $xmlrpc_date->hour = substr($time, 9, 2);
397     $xmlrpc_date->minute = substr($time, 12, 2);
398     $xmlrpc_date->second = substr($time, 15, 2);
399     $xmlrpc_date->iso8601 = $time;
400   }
401   return $xmlrpc_date;
402 }
403
404 function xmlrpc_date_get_xml($xmlrpc_date) {
405   return '<dateTime.iso8601>'. $xmlrpc_date->year . $xmlrpc_date->month . $xmlrpc_date->day .'T'. $xmlrpc_date->hour .':'. $xmlrpc_date->minute .':'. $xmlrpc_date->second .'</dateTime.iso8601>';
406 }
407
408 function xmlrpc_base64($data) {
409   $xmlrpc_base64 = new stdClass();
410   $xmlrpc_base64->is_base64 = TRUE;
411   $xmlrpc_base64->data = $data;
412   return $xmlrpc_base64;
413 }
414
415 function xmlrpc_base64_get_xml($xmlrpc_base64) {
416   return '<base64>'. base64_encode($xmlrpc_base64->data) .'</base64>';
417 }
418
419 /**
420  * Execute an XML remote procedural call. This is private function; call xmlrpc()
421  * in common.inc instead of this functino.
422  *
423  * @return
424  *   A $xmlrpc_message object if the call succeeded; FALSE if the call failed
425  */
426 function _xmlrpc() {
427   $args = func_get_args();
428   $url = array_shift($args);
429   if (is_array($args[0])) {
430     $method = 'system.multicall';
431     $multicall_args = array();
432     foreach ($args[0] as $call) {
433       $multicall_args[] = array('methodName' => array_shift($call),'params' => $call);
434     }
435     $args = array($multicall_args);
436   }
437   else {
438     $method = array_shift($args);
439   }
440   $xmlrpc_request = xmlrpc_request($method, $args);
441   $result = drupal_http_request($url, array("Content-Type" => "text/xml"), 'POST', $xmlrpc_request->xml);
442   if ($result->code != 200) {
443     xmlrpc_error(-$result->code, $result->error);
444     return FALSE;
445   }
446   $message = xmlrpc_message($result->data);
447   // Now parse what we've got back
448   if (!xmlrpc_message_parse($message)) {
449     // XML error
450     xmlrpc_error(-32700, t('Parse error. Not well formed'));
451     return FALSE;
452   }
453   // Is the message a fault?
454   if ($message->messagetype == 'fault') {
455     xmlrpc_error($message->fault_code, $message->fault_string);
456     return FALSE;
457   }
458   // Message must be OK
459   return $message->params[0];
460 }
461
462 /**
463  * Returns the last XML-RPC client error number
464  */
465 function xmlrpc_errno() {
466   $error = xmlrpc_error();
467   return $error->code;
468 }
469
470 /**
471  * Returns the last XML-RPC client error message
472  */
473 function xmlrpc_error_msg() {
474   $error = xmlrpc_error();
475   return $error->message;
476 }