xmlrpc.inc 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624
  1. <?php
  2. /**
  3. * @file
  4. * Drupal XML-RPC library.
  5. *
  6. * Based on the IXR - The Incutio XML-RPC Library - (c) Incutio Ltd 2002-2005
  7. * Version 1.7 (beta) - Simon Willison, 23rd May 2005
  8. * Site: http://scripts.incutio.com/xmlrpc/
  9. * Manual: http://scripts.incutio.com/xmlrpc/manual.php
  10. * This version is made available under the GNU GPL License
  11. */
  12. /**
  13. * Turns a data structure into objects with 'data' and 'type' attributes.
  14. *
  15. * @param $data
  16. * The data structure.
  17. * @param $type
  18. * Optional type to assign to $data.
  19. *
  20. * @return object
  21. * An XML-RPC data object containing the input $data.
  22. */
  23. function xmlrpc_value($data, $type = FALSE) {
  24. $xmlrpc_value = new stdClass();
  25. $xmlrpc_value->data = $data;
  26. if (!$type) {
  27. $type = xmlrpc_value_calculate_type($xmlrpc_value);
  28. }
  29. $xmlrpc_value->type = $type;
  30. if ($type == 'struct') {
  31. // Turn all the values in the array into new xmlrpc_values
  32. foreach ($xmlrpc_value->data as $key => $value) {
  33. $xmlrpc_value->data[$key] = xmlrpc_value($value);
  34. }
  35. }
  36. if ($type == 'array') {
  37. for ($i = 0, $j = count($xmlrpc_value->data); $i < $j; $i++) {
  38. $xmlrpc_value->data[$i] = xmlrpc_value($xmlrpc_value->data[$i]);
  39. }
  40. }
  41. return $xmlrpc_value;
  42. }
  43. /**
  44. * Maps a PHP type to an XML-RPC type.
  45. *
  46. * @param $xmlrpc_value
  47. * Variable whose type should be mapped.
  48. *
  49. * @return string
  50. * The corresponding XML-RPC type.
  51. *
  52. * @see http://www.xmlrpc.com/spec#scalars
  53. */
  54. function xmlrpc_value_calculate_type($xmlrpc_value) {
  55. // http://www.php.net/gettype: Never use gettype() to test for a certain type
  56. // [...] Instead, use the is_* functions.
  57. if (is_bool($xmlrpc_value->data)) {
  58. return 'boolean';
  59. }
  60. if (is_double($xmlrpc_value->data)) {
  61. return 'double';
  62. }
  63. if (is_int($xmlrpc_value->data)) {
  64. return 'int';
  65. }
  66. if (is_array($xmlrpc_value->data)) {
  67. // empty or integer-indexed arrays are 'array', string-indexed arrays 'struct'
  68. return empty($xmlrpc_value->data) || range(0, count($xmlrpc_value->data) - 1) === array_keys($xmlrpc_value->data) ? 'array' : 'struct';
  69. }
  70. if (is_object($xmlrpc_value->data)) {
  71. if (isset($xmlrpc_value->data->is_date)) {
  72. return 'date';
  73. }
  74. if (isset($xmlrpc_value->data->is_base64)) {
  75. return 'base64';
  76. }
  77. $xmlrpc_value->data = get_object_vars($xmlrpc_value->data);
  78. return 'struct';
  79. }
  80. // default
  81. return 'string';
  82. }
  83. /**
  84. * Generates XML representing the given value.
  85. *
  86. * @param $xmlrpc_value
  87. * A value to be represented in XML.
  88. *
  89. * @return
  90. * XML representation of $xmlrpc_value.
  91. */
  92. function xmlrpc_value_get_xml($xmlrpc_value) {
  93. switch ($xmlrpc_value->type) {
  94. case 'boolean':
  95. return '<boolean>' . (($xmlrpc_value->data) ? '1' : '0') . '</boolean>';
  96. case 'int':
  97. return '<int>' . $xmlrpc_value->data . '</int>';
  98. case 'double':
  99. return '<double>' . $xmlrpc_value->data . '</double>';
  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. case 'array':
  105. $return = '<array><data>' . "\n";
  106. foreach ($xmlrpc_value->data as $item) {
  107. $return .= ' <value>' . xmlrpc_value_get_xml($item) . "</value>\n";
  108. }
  109. $return .= '</data></array>';
  110. return $return;
  111. case 'struct':
  112. $return = '<struct>' . "\n";
  113. foreach ($xmlrpc_value->data as $name => $value) {
  114. $return .= " <member><name>" . check_plain($name) . "</name><value>";
  115. $return .= xmlrpc_value_get_xml($value) . "</value></member>\n";
  116. }
  117. $return .= '</struct>';
  118. return $return;
  119. case 'date':
  120. return xmlrpc_date_get_xml($xmlrpc_value->data);
  121. case 'base64':
  122. return xmlrpc_base64_get_xml($xmlrpc_value->data);
  123. }
  124. return FALSE;
  125. }
  126. /**
  127. * Constructs an object representing an XML-RPC message.
  128. *
  129. * @param $message
  130. * A string containing an XML message.
  131. *
  132. * @return object
  133. * An XML-RPC object containing the message.
  134. *
  135. * @see http://www.xmlrpc.com/spec
  136. */
  137. function xmlrpc_message($message) {
  138. $xmlrpc_message = new stdClass();
  139. // The stack used to keep track of the current array/struct
  140. $xmlrpc_message->array_structs = array();
  141. // The stack used to keep track of if things are structs or array
  142. $xmlrpc_message->array_structs_types = array();
  143. // A stack as well
  144. $xmlrpc_message->current_struct_name = array();
  145. $xmlrpc_message->message = $message;
  146. return $xmlrpc_message;
  147. }
  148. /**
  149. * Parses an XML-RPC message.
  150. *
  151. * If parsing fails, the faultCode and faultString will be added to the message
  152. * object.
  153. *
  154. * @param $xmlrpc_message
  155. * An object generated by xmlrpc_message().
  156. *
  157. * @return
  158. * TRUE if parsing succeeded; FALSE otherwise.
  159. */
  160. function xmlrpc_message_parse($xmlrpc_message) {
  161. $xmlrpc_message->_parser = xml_parser_create();
  162. // Set XML parser to take the case of tags into account.
  163. xml_parser_set_option($xmlrpc_message->_parser, XML_OPTION_CASE_FOLDING, FALSE);
  164. // Set XML parser callback functions
  165. xml_set_element_handler($xmlrpc_message->_parser, 'xmlrpc_message_tag_open', 'xmlrpc_message_tag_close');
  166. xml_set_character_data_handler($xmlrpc_message->_parser, 'xmlrpc_message_cdata');
  167. xmlrpc_message_set($xmlrpc_message);
  168. if (!xml_parse($xmlrpc_message->_parser, $xmlrpc_message->message)) {
  169. return FALSE;
  170. }
  171. xml_parser_free($xmlrpc_message->_parser);
  172. // Grab the error messages, if any.
  173. $xmlrpc_message = xmlrpc_message_get();
  174. if (!isset($xmlrpc_message->messagetype)) {
  175. return FALSE;
  176. }
  177. elseif ($xmlrpc_message->messagetype == 'fault') {
  178. $xmlrpc_message->fault_code = $xmlrpc_message->params[0]['faultCode'];
  179. $xmlrpc_message->fault_string = $xmlrpc_message->params[0]['faultString'];
  180. }
  181. return TRUE;
  182. }
  183. /**
  184. * Stores a copy of the most recent XML-RPC message object temporarily.
  185. *
  186. * @param $value
  187. * An XML-RPC message to store, or NULL to keep the last message.
  188. *
  189. * @return object
  190. * The most recently stored message.
  191. *
  192. * @see xmlrpc_message_get()
  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. * Returns the most recently stored XML-RPC message object.
  203. *
  204. * @return object
  205. * The most recently stored message.
  206. *
  207. * @see xmlrpc_message_set()
  208. */
  209. function xmlrpc_message_get() {
  210. return xmlrpc_message_set();
  211. }
  212. /**
  213. * Handles opening tags for XML parsing in xmlrpc_message_parse().
  214. */
  215. function xmlrpc_message_tag_open($parser, $tag, $attr) {
  216. $xmlrpc_message = xmlrpc_message_get();
  217. $xmlrpc_message->current_tag_contents = '';
  218. $xmlrpc_message->last_open = $tag;
  219. switch ($tag) {
  220. case 'methodCall':
  221. case 'methodResponse':
  222. case 'fault':
  223. $xmlrpc_message->messagetype = $tag;
  224. break;
  225. // Deal with stacks of arrays and structs
  226. case 'data':
  227. $xmlrpc_message->array_structs_types[] = 'array';
  228. $xmlrpc_message->array_structs[] = array();
  229. break;
  230. case 'struct':
  231. $xmlrpc_message->array_structs_types[] = 'struct';
  232. $xmlrpc_message->array_structs[] = array();
  233. break;
  234. }
  235. xmlrpc_message_set($xmlrpc_message);
  236. }
  237. /**
  238. * Handles character data for XML parsing in xmlrpc_message_parse().
  239. */
  240. function xmlrpc_message_cdata($parser, $cdata) {
  241. $xmlrpc_message = xmlrpc_message_get();
  242. $xmlrpc_message->current_tag_contents .= $cdata;
  243. xmlrpc_message_set($xmlrpc_message);
  244. }
  245. /**
  246. * Handles closing tags for XML parsing in xmlrpc_message_parse().
  247. */
  248. function xmlrpc_message_tag_close($parser, $tag) {
  249. $xmlrpc_message = xmlrpc_message_get();
  250. $value_flag = FALSE;
  251. switch ($tag) {
  252. case 'int':
  253. case 'i4':
  254. $value = (int)trim($xmlrpc_message->current_tag_contents);
  255. $value_flag = TRUE;
  256. break;
  257. case 'double':
  258. $value = (double)trim($xmlrpc_message->current_tag_contents);
  259. $value_flag = TRUE;
  260. break;
  261. case 'string':
  262. $value = $xmlrpc_message->current_tag_contents;
  263. $value_flag = TRUE;
  264. break;
  265. case 'dateTime.iso8601':
  266. $value = xmlrpc_date(trim($xmlrpc_message->current_tag_contents));
  267. // $value = $iso->getTimestamp();
  268. $value_flag = TRUE;
  269. break;
  270. case 'value':
  271. // If no type is indicated, the type is string
  272. // We take special care for empty values
  273. if (trim($xmlrpc_message->current_tag_contents) != '' || (isset($xmlrpc_message->last_open) && ($xmlrpc_message->last_open == 'value'))) {
  274. $value = (string) $xmlrpc_message->current_tag_contents;
  275. $value_flag = TRUE;
  276. }
  277. unset($xmlrpc_message->last_open);
  278. break;
  279. case 'boolean':
  280. $value = (boolean)trim($xmlrpc_message->current_tag_contents);
  281. $value_flag = TRUE;
  282. break;
  283. case 'base64':
  284. $value = base64_decode(trim($xmlrpc_message->current_tag_contents));
  285. $value_flag = TRUE;
  286. break;
  287. // Deal with stacks of arrays and structs
  288. case 'data':
  289. case 'struct':
  290. $value = array_pop($xmlrpc_message->array_structs);
  291. array_pop($xmlrpc_message->array_structs_types);
  292. $value_flag = TRUE;
  293. break;
  294. case 'member':
  295. array_pop($xmlrpc_message->current_struct_name);
  296. break;
  297. case 'name':
  298. $xmlrpc_message->current_struct_name[] = trim($xmlrpc_message->current_tag_contents);
  299. break;
  300. case 'methodName':
  301. $xmlrpc_message->methodname = trim($xmlrpc_message->current_tag_contents);
  302. break;
  303. }
  304. if ($value_flag) {
  305. if (count($xmlrpc_message->array_structs) > 0) {
  306. // Add value to struct or array
  307. if ($xmlrpc_message->array_structs_types[count($xmlrpc_message->array_structs_types) - 1] == 'struct') {
  308. // Add to struct
  309. $xmlrpc_message->array_structs[count($xmlrpc_message->array_structs) - 1][$xmlrpc_message->current_struct_name[count($xmlrpc_message->current_struct_name) - 1]] = $value;
  310. }
  311. else {
  312. // Add to array
  313. $xmlrpc_message->array_structs[count($xmlrpc_message->array_structs) - 1][] = $value;
  314. }
  315. }
  316. else {
  317. // Just add as a parameter
  318. $xmlrpc_message->params[] = $value;
  319. }
  320. }
  321. if (!in_array($tag, array("data", "struct", "member"))) {
  322. $xmlrpc_message->current_tag_contents = '';
  323. }
  324. xmlrpc_message_set($xmlrpc_message);
  325. }
  326. /**
  327. * Constructs an object representing an XML-RPC request.
  328. *
  329. * @param $method
  330. * The name of the method to be called.
  331. * @param $args
  332. * An array of parameters to send with the method.
  333. *
  334. * @return object
  335. * An XML-RPC object representing the request.
  336. */
  337. function xmlrpc_request($method, $args) {
  338. $xmlrpc_request = new stdClass();
  339. $xmlrpc_request->method = $method;
  340. $xmlrpc_request->args = $args;
  341. $xmlrpc_request->xml = <<<EOD
  342. <?xml version="1.0"?>
  343. <methodCall>
  344. <methodName>{$xmlrpc_request->method}</methodName>
  345. <params>
  346. EOD;
  347. foreach ($xmlrpc_request->args as $arg) {
  348. $xmlrpc_request->xml .= '<param><value>';
  349. $v = xmlrpc_value($arg);
  350. $xmlrpc_request->xml .= xmlrpc_value_get_xml($v);
  351. $xmlrpc_request->xml .= "</value></param>\n";
  352. }
  353. $xmlrpc_request->xml .= '</params></methodCall>';
  354. return $xmlrpc_request;
  355. }
  356. /**
  357. * Generates, temporarily saves, and returns an XML-RPC error object.
  358. *
  359. * @param $code
  360. * The error code.
  361. * @param $message
  362. * The error message.
  363. * @param $reset
  364. * TRUE to empty the temporary error storage. Ignored if $code is supplied.
  365. *
  366. * @return object
  367. * An XML-RPC error object representing $code and $message, or the most
  368. * recently stored error object if omitted.
  369. */
  370. function xmlrpc_error($code = NULL, $message = NULL, $reset = FALSE) {
  371. static $xmlrpc_error;
  372. if (isset($code)) {
  373. $xmlrpc_error = new stdClass();
  374. $xmlrpc_error->is_error = TRUE;
  375. $xmlrpc_error->code = $code;
  376. $xmlrpc_error->message = $message;
  377. }
  378. elseif ($reset) {
  379. $xmlrpc_error = NULL;
  380. }
  381. return $xmlrpc_error;
  382. }
  383. /**
  384. * Converts an XML-RPC error object into XML.
  385. *
  386. * @param $xmlrpc_error
  387. * The XML-RPC error object.
  388. *
  389. * @return string
  390. * An XML representation of the error as an XML methodResponse.
  391. */
  392. function xmlrpc_error_get_xml($xmlrpc_error) {
  393. return <<<EOD
  394. <methodResponse>
  395. <fault>
  396. <value>
  397. <struct>
  398. <member>
  399. <name>faultCode</name>
  400. <value><int>{$xmlrpc_error->code}</int></value>
  401. </member>
  402. <member>
  403. <name>faultString</name>
  404. <value><string>{$xmlrpc_error->message}</string></value>
  405. </member>
  406. </struct>
  407. </value>
  408. </fault>
  409. </methodResponse>
  410. EOD;
  411. }
  412. /**
  413. * Converts a PHP or ISO date/time to an XML-RPC object.
  414. *
  415. * @param $time
  416. * A PHP timestamp or an ISO date-time string.
  417. *
  418. * @return object
  419. * An XML-RPC time/date object.
  420. */
  421. function xmlrpc_date($time) {
  422. $xmlrpc_date = new stdClass();
  423. $xmlrpc_date->is_date = TRUE;
  424. // $time can be a PHP timestamp or an ISO one
  425. if (is_numeric($time)) {
  426. $xmlrpc_date->year = gmdate('Y', $time);
  427. $xmlrpc_date->month = gmdate('m', $time);
  428. $xmlrpc_date->day = gmdate('d', $time);
  429. $xmlrpc_date->hour = gmdate('H', $time);
  430. $xmlrpc_date->minute = gmdate('i', $time);
  431. $xmlrpc_date->second = gmdate('s', $time);
  432. $xmlrpc_date->iso8601 = gmdate('Ymd\TH:i:s', $time);
  433. }
  434. else {
  435. $xmlrpc_date->iso8601 = $time;
  436. $time = str_replace(array('-', ':'), '', $time);
  437. $xmlrpc_date->year = substr($time, 0, 4);
  438. $xmlrpc_date->month = substr($time, 4, 2);
  439. $xmlrpc_date->day = substr($time, 6, 2);
  440. $xmlrpc_date->hour = substr($time, 9, 2);
  441. $xmlrpc_date->minute = substr($time, 11, 2);
  442. $xmlrpc_date->second = substr($time, 13, 2);
  443. }
  444. return $xmlrpc_date;
  445. }
  446. /**
  447. * Converts an XML-RPC date-time object into XML.
  448. *
  449. * @param $xmlrpc_date
  450. * The XML-RPC date-time object.
  451. *
  452. * @return string
  453. * An XML representation of the date/time as XML.
  454. */
  455. function xmlrpc_date_get_xml($xmlrpc_date) {
  456. return '<dateTime.iso8601>' . $xmlrpc_date->year . $xmlrpc_date->month . $xmlrpc_date->day . 'T' . $xmlrpc_date->hour . ':' . $xmlrpc_date->minute . ':' . $xmlrpc_date->second . '</dateTime.iso8601>';
  457. }
  458. /**
  459. * Returns an XML-RPC base 64 object.
  460. *
  461. * @param $data
  462. * Base 64 data to store in returned object.
  463. *
  464. * @return object
  465. * An XML-RPC base 64 object.
  466. */
  467. function xmlrpc_base64($data) {
  468. $xmlrpc_base64 = new stdClass();
  469. $xmlrpc_base64->is_base64 = TRUE;
  470. $xmlrpc_base64->data = $data;
  471. return $xmlrpc_base64;
  472. }
  473. /**
  474. * Converts an XML-RPC base 64 object into XML.
  475. *
  476. * @param $xmlrpc_base64
  477. * The XML-RPC base 64 object.
  478. *
  479. * @return string
  480. * An XML representation of the base 64 data as XML.
  481. */
  482. function xmlrpc_base64_get_xml($xmlrpc_base64) {
  483. return '<base64>' . base64_encode($xmlrpc_base64->data) . '</base64>';
  484. }
  485. /**
  486. * Performs one or more XML-RPC requests.
  487. *
  488. * @param $url
  489. * An absolute URL of the XML-RPC endpoint, e.g.,
  490. * http://example.com/xmlrpc.php
  491. * @param $args
  492. * An associative array whose keys are the methods to call and whose values
  493. * are the arguments to pass to the respective method. If multiple methods
  494. * are specified, a system.multicall is performed.
  495. * @param $options
  496. * (optional) An array of options to pass along to drupal_http_request().
  497. *
  498. * @return
  499. * A single response (single request) or an array of responses (multicall
  500. * request). Each response is the return value of the method, just as if it
  501. * has been a local function call, on success, or FALSE on failure. If FALSE
  502. * is returned, see xmlrpc_errno() and xmlrpc_error_msg() to get more
  503. * information.
  504. */
  505. function _xmlrpc($url, $args, $options = array()) {
  506. xmlrpc_clear_error();
  507. if (count($args) > 1) {
  508. $multicall_args = array();
  509. foreach ($args as $method => $call) {
  510. $multicall_args[] = array('methodName' => $method, 'params' => $call);
  511. }
  512. $method = 'system.multicall';
  513. $args = array($multicall_args);
  514. }
  515. else {
  516. $method = key($args);
  517. $args = $args[$method];
  518. }
  519. $xmlrpc_request = xmlrpc_request($method, $args);
  520. // Required options which will replace any that are passed in.
  521. $options['method'] = 'POST';
  522. $options['headers']['Content-Type'] = 'text/xml';
  523. $options['data'] = $xmlrpc_request->xml;
  524. $result = drupal_http_request($url, $options);
  525. if ($result->code != 200) {
  526. xmlrpc_error($result->code, $result->error);
  527. return FALSE;
  528. }
  529. $message = xmlrpc_message($result->data);
  530. // Now parse what we've got back
  531. if (!xmlrpc_message_parse($message)) {
  532. // XML error
  533. xmlrpc_error(-32700, t('Parse error. Not well formed'));
  534. return FALSE;
  535. }
  536. // Is the message a fault?
  537. if ($message->messagetype == 'fault') {
  538. xmlrpc_error($message->fault_code, $message->fault_string);
  539. return FALSE;
  540. }
  541. // We now know that the message is well-formed and a non-fault result.
  542. if ($method == 'system.multicall') {
  543. // Return per-method results or error objects.
  544. $return = array();
  545. foreach ($message->params[0] as $result) {
  546. if (array_keys($result) == array(0)) {
  547. $return[] = $result[0];
  548. }
  549. else {
  550. $return[] = xmlrpc_error($result['faultCode'], $result['faultString']);
  551. }
  552. }
  553. }
  554. else {
  555. $return = $message->params[0];
  556. }
  557. return $return;
  558. }
  559. /**
  560. * Returns the last XML-RPC client error number.
  561. */
  562. function xmlrpc_errno() {
  563. $error = xmlrpc_error();
  564. return ($error != NULL ? $error->code : NULL);
  565. }
  566. /**
  567. * Returns the last XML-RPC client error message.
  568. */
  569. function xmlrpc_error_msg() {
  570. $error = xmlrpc_error();
  571. return ($error != NULL ? $error->message : NULL);
  572. }
  573. /**
  574. * Clears any previously-saved errors.
  575. *
  576. * @see xmlrpc_error()
  577. */
  578. function xmlrpc_clear_error() {
  579. xmlrpc_error(NULL, NULL, TRUE);
  580. }