xmlrpc_wrappers.inc 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819
  1. <?php
  2. /**
  3. * PHP-XMLRPC "wrapper" functions
  4. * Generate stubs to transparently access xmlrpc methods as php functions and viceversa
  5. *
  6. * @version $Id: xmlrpc_wrappers.inc,v 1.12 2008/03/06 18:58:44 ggiunta Exp $
  7. * @author Gaetano Giunta
  8. * @copyright (C) 2006-2008 G. Giunta
  9. * @license code licensed under the BSD License: http://phpxmlrpc.sourceforge.net/license.txt
  10. *
  11. * @todo separate introspection from code generation for func-2-method wrapping
  12. * @todo use some better templating system from code generation?
  13. * @todo implement method wrapping with preservation of php objs in calls
  14. * @todo when wrapping methods without obj rebuilding, use return_type = 'phpvals' (faster)
  15. * @todo implement self-parsing of php code for PHP <= 4
  16. */
  17. // requires: xmlrpc.inc
  18. /**
  19. * Given a string defining a php type or phpxmlrpc type (loosely defined: strings
  20. * accepted come from javadoc blocks), return corresponding phpxmlrpc type.
  21. * NB: for php 'resource' types returns empty string, since resources cannot be serialized;
  22. * for php class names returns 'struct', since php objects can be serialized as xmlrpc structs
  23. * @param string $phptype
  24. * @return string
  25. */
  26. function php_2_xmlrpc_type($phptype)
  27. {
  28. switch(strtolower($phptype))
  29. {
  30. case 'string':
  31. return $GLOBALS['xmlrpcString'];
  32. case 'integer':
  33. case $GLOBALS['xmlrpcInt']: // 'int'
  34. case $GLOBALS['xmlrpcI4']:
  35. return $GLOBALS['xmlrpcInt'];
  36. case 'double':
  37. return $GLOBALS['xmlrpcDouble'];
  38. case 'boolean':
  39. return $GLOBALS['xmlrpcBoolean'];
  40. case 'array':
  41. return $GLOBALS['xmlrpcArray'];
  42. case 'object':
  43. return $GLOBALS['xmlrpcStruct'];
  44. case $GLOBALS['xmlrpcBase64']:
  45. case $GLOBALS['xmlrpcStruct']:
  46. return strtolower($phptype);
  47. case 'resource':
  48. return '';
  49. default:
  50. if(class_exists($phptype))
  51. {
  52. return $GLOBALS['xmlrpcStruct'];
  53. }
  54. else
  55. {
  56. // unknown: might be any 'extended' xmlrpc type
  57. return $GLOBALS['xmlrpcValue'];
  58. }
  59. }
  60. }
  61. /**
  62. * Given a string defining a phpxmlrpc type return corresponding php type.
  63. * @param string $xmlrpctype
  64. * @return string
  65. */
  66. function xmlrpc_2_php_type($xmlrpctype)
  67. {
  68. switch(strtolower($xmlrpctype))
  69. {
  70. case 'base64':
  71. case 'datetime.iso8601':
  72. case 'string':
  73. return $GLOBALS['xmlrpcString'];
  74. case 'int':
  75. case 'i4':
  76. return 'integer';
  77. case 'struct':
  78. case 'array':
  79. return 'array';
  80. case 'double':
  81. return 'float';
  82. case 'undefined':
  83. return 'mixed';
  84. case 'boolean':
  85. case 'null':
  86. default:
  87. // unknown: might be any xmlrpc type
  88. return strtolower($xmlrpctype);
  89. }
  90. }
  91. /**
  92. * Given a user-defined PHP function, create a PHP 'wrapper' function that can
  93. * be exposed as xmlrpc method from an xmlrpc_server object and called from remote
  94. * clients (as well as its corresponding signature info).
  95. *
  96. * Since php is a typeless language, to infer types of input and output parameters,
  97. * it relies on parsing the javadoc-style comment block associated with the given
  98. * function. Usage of xmlrpc native types (such as datetime.dateTime.iso8601 and base64)
  99. * in the @param tag is also allowed, if you need the php function to receive/send
  100. * data in that particular format (note that base64 encoding/decoding is transparently
  101. * carried out by the lib, while datetime vals are passed around as strings)
  102. *
  103. * Known limitations:
  104. * - requires PHP 5.0.3 +
  105. * - only works for user-defined functions, not for PHP internal functions
  106. * (reflection does not support retrieving number/type of params for those)
  107. * - functions returning php objects will generate special xmlrpc responses:
  108. * when the xmlrpc decoding of those responses is carried out by this same lib, using
  109. * the appropriate param in php_xmlrpc_decode, the php objects will be rebuilt.
  110. * In short: php objects can be serialized, too (except for their resource members),
  111. * using this function.
  112. * Other libs might choke on the very same xml that will be generated in this case
  113. * (i.e. it has a nonstandard attribute on struct element tags)
  114. * - usage of javadoc @param tags using param names in a different order from the
  115. * function prototype is not considered valid (to be fixed?)
  116. *
  117. * Note that since rel. 2.0RC3 the preferred method to have the server call 'standard'
  118. * php functions (ie. functions not expecting a single xmlrpcmsg obj as parameter)
  119. * is by making use of the functions_parameters_type class member.
  120. *
  121. * @param string $funcname the name of the PHP user function to be exposed as xmlrpc method; array($obj, 'methodname') might be ok too, in the future...
  122. * @param string $newfuncname (optional) name for function to be created
  123. * @param array $extra_options (optional) array of options for conversion. valid values include:
  124. * bool return_source when true, php code w. function definition will be returned, not evaluated
  125. * bool encode_php_objs let php objects be sent to server using the 'improved' xmlrpc notation, so server can deserialize them as php objects
  126. * bool decode_php_objs --- WARNING !!! possible security hazard. only use it with trusted servers ---
  127. * bool suppress_warnings remove from produced xml any runtime warnings due to the php function being invoked
  128. * @return false on error, or an array containing the name of the new php function,
  129. * its signature and docs, to be used in the server dispatch map
  130. *
  131. * @todo decide how to deal with params passed by ref: bomb out or allow?
  132. * @todo finish using javadoc info to build method sig if all params are named but out of order
  133. * @todo add a check for params of 'resource' type
  134. * @todo add some trigger_errors / error_log when returning false?
  135. * @todo what to do when the PHP function returns NULL? we are currently an empty string value...
  136. * @todo add an option to suppress php warnings in invocation of user function, similar to server debug level 3?
  137. */
  138. function wrap_php_function($funcname, $newfuncname='', $extra_options=array())
  139. {
  140. $buildit = isset($extra_options['return_source']) ? !($extra_options['return_source']) : true;
  141. $prefix = isset($extra_options['prefix']) ? $extra_options['prefix'] : 'xmlrpc';
  142. $encode_php_objects = isset($extra_options['encode_php_objs']) ? (bool)$extra_options['encode_php_objs'] : false;
  143. $decode_php_objects = isset($extra_options['decode_php_objs']) ? (bool)$extra_options['decode_php_objs'] : false;
  144. $catch_warnings = isset($extra_options['suppress_warnings']) && $extra_options['suppress_warnings'] ? '@' : '';
  145. if(version_compare(phpversion(), '5.0.3') == -1)
  146. {
  147. // up to php 5.0.3 some useful reflection methods were missing
  148. error_log('XML-RPC: cannot not wrap php functions unless running php version bigger than 5.0.3');
  149. return false;
  150. }
  151. if((is_array($funcname) && !method_exists($funcname[0], $funcname[1])) || !function_exists($funcname))
  152. {
  153. error_log('XML-RPC: function to be wrapped is not defined: '.$funcname);
  154. return false;
  155. }
  156. else
  157. {
  158. // determine name of new php function
  159. if($newfuncname == '')
  160. {
  161. if(is_array($funcname))
  162. {
  163. $xmlrpcfuncname = "{$prefix}_".implode('_', $funcname);
  164. }
  165. else
  166. {
  167. $xmlrpcfuncname = "{$prefix}_$funcname";
  168. }
  169. }
  170. else
  171. {
  172. $xmlrpcfuncname = $newfuncname;
  173. }
  174. while($buildit && function_exists($xmlrpcfuncname))
  175. {
  176. $xmlrpcfuncname .= 'x';
  177. }
  178. // start to introspect PHP code
  179. $func = new ReflectionFunction($funcname);
  180. if($func->isInternal())
  181. {
  182. // Note: from PHP 5.1.0 onward, we will possibly be able to use invokeargs
  183. // instead of getparameters to fully reflect internal php functions ?
  184. error_log('XML-RPC: function to be wrapped is internal: '.$funcname);
  185. return false;
  186. }
  187. // retrieve parameter names, types and description from javadoc comments
  188. // function description
  189. $desc = '';
  190. // type of return val: by default 'any'
  191. $returns = $GLOBALS['xmlrpcValue'];
  192. // desc of return val
  193. $returnsDocs = '';
  194. // type + name of function parameters
  195. $paramDocs = array();
  196. $docs = $func->getDocComment();
  197. if($docs != '')
  198. {
  199. $docs = explode("\n", $docs);
  200. $i = 0;
  201. foreach($docs as $doc)
  202. {
  203. $doc = trim($doc, " \r\t/*");
  204. if(strlen($doc) && strpos($doc, '@') !== 0 && !$i)
  205. {
  206. if($desc)
  207. {
  208. $desc .= "\n";
  209. }
  210. $desc .= $doc;
  211. }
  212. elseif(strpos($doc, '@param') === 0)
  213. {
  214. // syntax: @param type [$name] desc
  215. if(preg_match('/@param\s+(\S+)(\s+\$\S+)?\s+(.+)/', $doc, $matches))
  216. {
  217. if(strpos($matches[1], '|'))
  218. {
  219. //$paramDocs[$i]['type'] = explode('|', $matches[1]);
  220. $paramDocs[$i]['type'] = 'mixed';
  221. }
  222. else
  223. {
  224. $paramDocs[$i]['type'] = $matches[1];
  225. }
  226. $paramDocs[$i]['name'] = trim($matches[2]);
  227. $paramDocs[$i]['doc'] = $matches[3];
  228. }
  229. $i++;
  230. }
  231. elseif(strpos($doc, '@return') === 0)
  232. {
  233. // syntax: @return type desc
  234. //$returns = preg_split('/\s+/', $doc);
  235. if(preg_match('/@return\s+(\S+)\s+(.+)/', $doc, $matches))
  236. {
  237. $returns = php_2_xmlrpc_type($matches[1]);
  238. if(isset($matches[2]))
  239. {
  240. $returnsDocs = $matches[2];
  241. }
  242. }
  243. }
  244. }
  245. }
  246. // execute introspection of actual function prototype
  247. $params = array();
  248. $i = 0;
  249. foreach($func->getParameters() as $paramobj)
  250. {
  251. $params[$i] = array();
  252. $params[$i]['name'] = '$'.$paramobj->getName();
  253. $params[$i]['isoptional'] = $paramobj->isOptional();
  254. $i++;
  255. }
  256. // start building of PHP code to be eval'd
  257. $innercode = '';
  258. $i = 0;
  259. $parsvariations = array();
  260. $pars = array();
  261. $pnum = count($params);
  262. foreach($params as $param)
  263. {
  264. if (isset($paramDocs[$i]['name']) && $paramDocs[$i]['name'] && strtolower($paramDocs[$i]['name']) != strtolower($param['name']))
  265. {
  266. // param name from phpdoc info does not match param definition!
  267. $paramDocs[$i]['type'] = 'mixed';
  268. }
  269. if($param['isoptional'])
  270. {
  271. // this particular parameter is optional. save as valid previous list of parameters
  272. $innercode .= "if (\$paramcount > $i) {\n";
  273. $parsvariations[] = $pars;
  274. }
  275. $innercode .= "\$p$i = \$msg->getParam($i);\n";
  276. if ($decode_php_objects)
  277. {
  278. $innercode .= "if (\$p{$i}->kindOf() == 'scalar') \$p$i = \$p{$i}->scalarval(); else \$p$i = php_{$prefix}_decode(\$p$i, array('decode_php_objs'));\n";
  279. }
  280. else
  281. {
  282. $innercode .= "if (\$p{$i}->kindOf() == 'scalar') \$p$i = \$p{$i}->scalarval(); else \$p$i = php_{$prefix}_decode(\$p$i);\n";
  283. }
  284. $pars[] = "\$p$i";
  285. $i++;
  286. if($param['isoptional'])
  287. {
  288. $innercode .= "}\n";
  289. }
  290. if($i == $pnum)
  291. {
  292. // last allowed parameters combination
  293. $parsvariations[] = $pars;
  294. }
  295. }
  296. $sigs = array();
  297. $psigs = array();
  298. if(count($parsvariations) == 0)
  299. {
  300. // only known good synopsis = no parameters
  301. $parsvariations[] = array();
  302. $minpars = 0;
  303. }
  304. else
  305. {
  306. $minpars = count($parsvariations[0]);
  307. }
  308. if($minpars)
  309. {
  310. // add to code the check for min params number
  311. // NB: this check needs to be done BEFORE decoding param values
  312. $innercode = "\$paramcount = \$msg->getNumParams();\n" .
  313. "if (\$paramcount < $minpars) return new {$prefix}resp(0, {$GLOBALS['xmlrpcerr']['incorrect_params']}, '{$GLOBALS['xmlrpcstr']['incorrect_params']}');\n" . $innercode;
  314. }
  315. else
  316. {
  317. $innercode = "\$paramcount = \$msg->getNumParams();\n" . $innercode;
  318. }
  319. $innercode .= "\$np = false;\n";
  320. foreach($parsvariations as $pars)
  321. {
  322. $innercode .= "if (\$paramcount == " . count($pars) . ") \$retval = {$catch_warnings}$funcname(" . implode(',', $pars) . "); else\n";
  323. // build a 'generic' signature (only use an appropriate return type)
  324. $sig = array($returns);
  325. $psig = array($returnsDocs);
  326. for($i=0; $i < count($pars); $i++)
  327. {
  328. if (isset($paramDocs[$i]['type']))
  329. {
  330. $sig[] = php_2_xmlrpc_type($paramDocs[$i]['type']);
  331. }
  332. else
  333. {
  334. $sig[] = $GLOBALS['xmlrpcValue'];
  335. }
  336. $psig[] = isset($paramDocs[$i]['doc']) ? $paramDocs[$i]['doc'] : '';
  337. }
  338. $sigs[] = $sig;
  339. $psigs[] = $psig;
  340. }
  341. $innercode .= "\$np = true;\n";
  342. $innercode .= "if (\$np) return new {$prefix}resp(0, {$GLOBALS['xmlrpcerr']['incorrect_params']}, '{$GLOBALS['xmlrpcstr']['incorrect_params']}'); else {\n";
  343. //$innercode .= "if (\$_xmlrpcs_error_occurred) return new xmlrpcresp(0, $GLOBALS['xmlrpcerr']user, \$_xmlrpcs_error_occurred); else\n";
  344. $innercode .= "if (\$retval instanceof {$prefix}resp) return \$retval; else\n";
  345. if($returns == $GLOBALS['xmlrpcDateTime'] || $returns == $GLOBALS['xmlrpcBase64'])
  346. {
  347. $innercode .= "return new {$prefix}resp(new {$prefix}val(\$retval, '$returns'));";
  348. }
  349. else
  350. {
  351. if ($encode_php_objects)
  352. $innercode .= "return new {$prefix}resp(php_{$prefix}_encode(\$retval, array('encode_php_objs')));\n";
  353. else
  354. $innercode .= "return new {$prefix}resp(php_{$prefix}_encode(\$retval));\n";
  355. }
  356. // shall we exclude functions returning by ref?
  357. // if($func->returnsReference())
  358. // return false;
  359. $code = "function $xmlrpcfuncname(\$msg) {\n" . $innercode . "}\n}";
  360. //print_r($code);
  361. if ($buildit)
  362. {
  363. $allOK = 0;
  364. eval($code.'$allOK=1;');
  365. // alternative
  366. //$xmlrpcfuncname = create_function('$m', $innercode);
  367. if(!$allOK)
  368. {
  369. error_log('XML-RPC: could not create function '.$xmlrpcfuncname.' to wrap php function '.$funcname);
  370. return false;
  371. }
  372. }
  373. /// @todo examine if $paramDocs matches $parsvariations and build array for
  374. /// usage as method signature, plus put together a nice string for docs
  375. $ret = array('function' => $xmlrpcfuncname, 'signature' => $sigs, 'docstring' => $desc, 'signature_docs' => $psigs, 'source' => $code);
  376. return $ret;
  377. }
  378. }
  379. /**
  380. * Given an xmlrpc client and a method name, register a php wrapper function
  381. * that will call it and return results using native php types for both
  382. * params and results. The generated php function will return an xmlrpcresp
  383. * oject for failed xmlrpc calls
  384. *
  385. * Known limitations:
  386. * - server must support system.methodsignature for the wanted xmlrpc method
  387. * - for methods that expose many signatures, only one can be picked (we
  388. * could in priciple check if signatures differ only by number of params
  389. * and not by type, but it would be more complication than we can spare time)
  390. * - nested xmlrpc params: the caller of the generated php function has to
  391. * encode on its own the params passed to the php function if these are structs
  392. * or arrays whose (sub)members include values of type datetime or base64
  393. *
  394. * Notes: the connection properties of the given client will be copied
  395. * and reused for the connection used during the call to the generated
  396. * php function.
  397. * Calling the generated php function 'might' be slow: a new xmlrpc client
  398. * is created on every invocation and an xmlrpc-connection opened+closed.
  399. * An extra 'debug' param is appended to param list of xmlrpc method, useful
  400. * for debugging purposes.
  401. *
  402. * @param xmlrpc_client $client an xmlrpc client set up correctly to communicate with target server
  403. * @param string $methodname the xmlrpc method to be mapped to a php function
  404. * @param array $extra_options array of options that specify conversion details. valid ptions include
  405. * integer signum the index of the method signature to use in mapping (if method exposes many sigs)
  406. * integer timeout timeout (in secs) to be used when executing function/calling remote method
  407. * string protocol 'http' (default), 'http11' or 'https'
  408. * string new_function_name the name of php function to create. If unsepcified, lib will pick an appropriate name
  409. * string return_source if true return php code w. function definition instead fo function name
  410. * bool encode_php_objs let php objects be sent to server using the 'improved' xmlrpc notation, so server can deserialize them as php objects
  411. * bool decode_php_objs --- WARNING !!! possible security hazard. only use it with trusted servers ---
  412. * mixed return_on_fault a php value to be returned when the xmlrpc call fails/returns a fault response (by default the xmlrpcresp object is returned in this case). If a string is used, '%faultCode%' and '%faultString%' tokens will be substituted with actual error values
  413. * bool debug set it to 1 or 2 to see debug results of querying server for method synopsis
  414. * @return string the name of the generated php function (or false) - OR AN ARRAY...
  415. */
  416. function wrap_xmlrpc_method($client, $methodname, $extra_options=0, $timeout=0, $protocol='', $newfuncname='')
  417. {
  418. // mind numbing: let caller use sane calling convention (as per javadoc, 3 params),
  419. // OR the 2.0 calling convention (no ptions) - we really love backward compat, don't we?
  420. if (!is_array($extra_options))
  421. {
  422. $signum = $extra_options;
  423. $extra_options = array();
  424. }
  425. else
  426. {
  427. $signum = isset($extra_options['signum']) ? (int)$extra_options['signum'] : 0;
  428. $timeout = isset($extra_options['timeout']) ? (int)$extra_options['timeout'] : 0;
  429. $protocol = isset($extra_options['protocol']) ? $extra_options['protocol'] : '';
  430. $newfuncname = isset($extra_options['new_function_name']) ? $extra_options['new_function_name'] : '';
  431. }
  432. //$encode_php_objects = in_array('encode_php_objects', $extra_options);
  433. //$verbatim_client_copy = in_array('simple_client_copy', $extra_options) ? 1 :
  434. // in_array('build_class_code', $extra_options) ? 2 : 0;
  435. $encode_php_objects = isset($extra_options['encode_php_objs']) ? (bool)$extra_options['encode_php_objs'] : false;
  436. $decode_php_objects = isset($extra_options['decode_php_objs']) ? (bool)$extra_options['decode_php_objs'] : false;
  437. $simple_client_copy = isset($extra_options['simple_client_copy']) ? (int)($extra_options['simple_client_copy']) : 0;
  438. $buildit = isset($extra_options['return_source']) ? !($extra_options['return_source']) : true;
  439. $prefix = isset($extra_options['prefix']) ? $extra_options['prefix'] : 'xmlrpc';
  440. if (isset($extra_options['return_on_fault']))
  441. {
  442. $decode_fault = true;
  443. $fault_response = $extra_options['return_on_fault'];
  444. }
  445. else
  446. {
  447. $decode_fault = false;
  448. $fault_response = '';
  449. }
  450. $debug = isset($extra_options['debug']) ? ($extra_options['debug']) : 0;
  451. $msgclass = $prefix.'msg';
  452. $valclass = $prefix.'val';
  453. $decodefunc = 'php_'.$prefix.'_decode';
  454. $msg = new $msgclass('system.methodSignature');
  455. $msg->addparam(new $valclass($methodname));
  456. $client->setDebug($debug);
  457. $response =& $client->send($msg, $timeout, $protocol);
  458. if($response->faultCode())
  459. {
  460. error_log('XML-RPC: could not retrieve method signature from remote server for method '.$methodname);
  461. return false;
  462. }
  463. else
  464. {
  465. $msig = $response->value();
  466. if ($client->return_type != 'phpvals')
  467. {
  468. $msig = $decodefunc($msig);
  469. }
  470. if(!is_array($msig) || count($msig) <= $signum)
  471. {
  472. error_log('XML-RPC: could not retrieve method signature nr.'.$signum.' from remote server for method '.$methodname);
  473. return false;
  474. }
  475. else
  476. {
  477. // pick a suitable name for the new function, avoiding collisions
  478. if($newfuncname != '')
  479. {
  480. $xmlrpcfuncname = $newfuncname;
  481. }
  482. else
  483. {
  484. // take care to insure that methodname is translated to valid
  485. // php function name
  486. $xmlrpcfuncname = $prefix.'_'.preg_replace(array('/\./', '/[^a-zA-Z0-9_\x7f-\xff]/'),
  487. array('_', ''), $methodname);
  488. }
  489. while($buildit && function_exists($xmlrpcfuncname))
  490. {
  491. $xmlrpcfuncname .= 'x';
  492. }
  493. $msig = $msig[$signum];
  494. $mdesc = '';
  495. // if in 'offline' mode, get method description too.
  496. // in online mode, favour speed of operation
  497. if(!$buildit)
  498. {
  499. $msg = new $msgclass('system.methodHelp');
  500. $msg->addparam(new $valclass($methodname));
  501. $response =& $client->send($msg, $timeout, $protocol);
  502. if (!$response->faultCode())
  503. {
  504. $mdesc = $response->value();
  505. if ($client->return_type != 'phpvals')
  506. {
  507. $mdesc = $mdesc->scalarval();
  508. }
  509. }
  510. }
  511. $results = build_remote_method_wrapper_code($client, $methodname,
  512. $xmlrpcfuncname, $msig, $mdesc, $timeout, $protocol, $simple_client_copy,
  513. $prefix, $decode_php_objects, $encode_php_objects, $decode_fault,
  514. $fault_response);
  515. //print_r($code);
  516. if ($buildit)
  517. {
  518. $allOK = 0;
  519. eval($results['source'].'$allOK=1;');
  520. // alternative
  521. //$xmlrpcfuncname = create_function('$m', $innercode);
  522. if($allOK)
  523. {
  524. return $xmlrpcfuncname;
  525. }
  526. else
  527. {
  528. error_log('XML-RPC: could not create function '.$xmlrpcfuncname.' to wrap remote method '.$methodname);
  529. return false;
  530. }
  531. }
  532. else
  533. {
  534. $results['function'] = $xmlrpcfuncname;
  535. return $results;
  536. }
  537. }
  538. }
  539. }
  540. /**
  541. * Similar to wrap_xmlrpc_method, but will generate a php class that wraps
  542. * all xmlrpc methods exposed by the remote server as own methods.
  543. * For more details see wrap_xmlrpc_method.
  544. * @param xmlrpc_client $client the client obj all set to query the desired server
  545. * @param array $extra_options list of options for wrapped code
  546. * @return mixed false on error, the name of the created class if all ok or an array with code, class name and comments (if the appropriatevoption is set in extra_options)
  547. */
  548. function wrap_xmlrpc_server($client, $extra_options=array())
  549. {
  550. $methodfilter = isset($extra_options['method_filter']) ? $extra_options['method_filter'] : '';
  551. $signum = isset($extra_options['signum']) ? (int)$extra_options['signum'] : 0;
  552. $timeout = isset($extra_options['timeout']) ? (int)$extra_options['timeout'] : 0;
  553. $protocol = isset($extra_options['protocol']) ? $extra_options['protocol'] : '';
  554. $newclassname = isset($extra_options['new_class_name']) ? $extra_options['new_class_name'] : '';
  555. $encode_php_objects = isset($extra_options['encode_php_objs']) ? (bool)$extra_options['encode_php_objs'] : false;
  556. $decode_php_objects = isset($extra_options['decode_php_objs']) ? (bool)$extra_options['decode_php_objs'] : false;
  557. $verbatim_client_copy = isset($extra_options['simple_client_copy']) ? !($extra_options['simple_client_copy']) : true;
  558. $buildit = isset($extra_options['return_source']) ? !($extra_options['return_source']) : true;
  559. $prefix = isset($extra_options['prefix']) ? $extra_options['prefix'] : 'xmlrpc';
  560. $msgclass = $prefix.'msg';
  561. //$valclass = $prefix.'val';
  562. $decodefunc = 'php_'.$prefix.'_decode';
  563. $msg = new $msgclass('system.listMethods');
  564. $response =& $client->send($msg, $timeout, $protocol);
  565. if($response->faultCode())
  566. {
  567. error_log('XML-RPC: could not retrieve method list from remote server');
  568. return false;
  569. }
  570. else
  571. {
  572. $mlist = $response->value();
  573. if ($client->return_type != 'phpvals')
  574. {
  575. $mlist = $decodefunc($mlist);
  576. }
  577. if(!is_array($mlist) || !count($mlist))
  578. {
  579. error_log('XML-RPC: could not retrieve meaningful method list from remote server');
  580. return false;
  581. }
  582. else
  583. {
  584. // pick a suitable name for the new function, avoiding collisions
  585. if($newclassname != '')
  586. {
  587. $xmlrpcclassname = $newclassname;
  588. }
  589. else
  590. {
  591. $xmlrpcclassname = $prefix.'_'.preg_replace(array('/\./', '/[^a-zA-Z0-9_\x7f-\xff]/'),
  592. array('_', ''), $client->server).'_client';
  593. }
  594. while($buildit && class_exists($xmlrpcclassname))
  595. {
  596. $xmlrpcclassname .= 'x';
  597. }
  598. /// @todo add function setdebug() to new class, to enable/disable debugging
  599. $source = "class $xmlrpcclassname\n{\nvar \$client;\n\n";
  600. $source .= "function $xmlrpcclassname()\n{\n";
  601. $source .= build_client_wrapper_code($client, $verbatim_client_copy, $prefix);
  602. $source .= "\$this->client =& \$client;\n}\n\n";
  603. $opts = array('simple_client_copy' => 2, 'return_source' => true,
  604. 'timeout' => $timeout, 'protocol' => $protocol,
  605. 'encode_php_objs' => $encode_php_objects, 'prefix' => $prefix,
  606. 'decode_php_objs' => $decode_php_objects
  607. );
  608. /// @todo build javadoc for class definition, too
  609. foreach($mlist as $mname)
  610. {
  611. if ($methodfilter == '' || preg_match($methodfilter, $mname))
  612. {
  613. $opts['new_function_name'] = preg_replace(array('/\./', '/[^a-zA-Z0-9_\x7f-\xff]/'),
  614. array('_', ''), $mname);
  615. $methodwrap = wrap_xmlrpc_method($client, $mname, $opts);
  616. if ($methodwrap)
  617. {
  618. if (!$buildit)
  619. {
  620. $source .= $methodwrap['docstring'];
  621. }
  622. $source .= $methodwrap['source']."\n";
  623. }
  624. else
  625. {
  626. error_log('XML-RPC: will not create class method to wrap remote method '.$mname);
  627. }
  628. }
  629. }
  630. $source .= "}\n";
  631. if ($buildit)
  632. {
  633. $allOK = 0;
  634. eval($source.'$allOK=1;');
  635. // alternative
  636. //$xmlrpcfuncname = create_function('$m', $innercode);
  637. if($allOK)
  638. {
  639. return $xmlrpcclassname;
  640. }
  641. else
  642. {
  643. error_log('XML-RPC: could not create class '.$xmlrpcclassname.' to wrap remote server '.$client->server);
  644. return false;
  645. }
  646. }
  647. else
  648. {
  649. return array('class' => $xmlrpcclassname, 'code' => $source, 'docstring' => '');
  650. }
  651. }
  652. }
  653. }
  654. /**
  655. * Given the necessary info, build php code that creates a new function to
  656. * invoke a remote xmlrpc method.
  657. * Take care that no full checking of input parameters is done to ensure that
  658. * valid php code is emitted.
  659. * Note: real spaghetti code follows...
  660. * @access private
  661. */
  662. function build_remote_method_wrapper_code($client, $methodname, $xmlrpcfuncname,
  663. $msig, $mdesc='', $timeout=0, $protocol='', $client_copy_mode=0, $prefix='xmlrpc',
  664. $decode_php_objects=false, $encode_php_objects=false, $decode_fault=false,
  665. $fault_response='')
  666. {
  667. $code = "function $xmlrpcfuncname (";
  668. if ($client_copy_mode < 2)
  669. {
  670. // client copy mode 0 or 1 == partial / full client copy in emitted code
  671. $innercode = build_client_wrapper_code($client, $client_copy_mode, $prefix);
  672. $innercode .= "\$client->setDebug(\$debug);\n";
  673. $this_ = '';
  674. }
  675. else
  676. {
  677. // client copy mode 2 == no client copy in emitted code
  678. $innercode = '';
  679. $this_ = 'this->';
  680. }
  681. $innercode .= "\$msg = new {$prefix}msg('$methodname');\n";
  682. if ($mdesc != '')
  683. {
  684. // take care that PHP comment is not terminated unwillingly by method description
  685. $mdesc = "/**\n* ".str_replace('*/', '* /', $mdesc)."\n";
  686. }
  687. else
  688. {
  689. $mdesc = "/**\nFunction $xmlrpcfuncname\n";
  690. }
  691. // param parsing
  692. $plist = array();
  693. $pcount = count($msig);
  694. for($i = 1; $i < $pcount; $i++)
  695. {
  696. $plist[] = "\$p$i";
  697. $ptype = $msig[$i];
  698. if($ptype == 'i4' || $ptype == 'int' || $ptype == 'boolean' || $ptype == 'double' ||
  699. $ptype == 'string' || $ptype == 'dateTime.iso8601' || $ptype == 'base64' || $ptype == 'null')
  700. {
  701. // only build directly xmlrpcvals when type is known and scalar
  702. $innercode .= "\$p$i = new {$prefix}val(\$p$i, '$ptype');\n";
  703. }
  704. else
  705. {
  706. if ($encode_php_objects)
  707. {
  708. $innercode .= "\$p$i =& php_{$prefix}_encode(\$p$i, array('encode_php_objs'));\n";
  709. }
  710. else
  711. {
  712. $innercode .= "\$p$i =& php_{$prefix}_encode(\$p$i);\n";
  713. }
  714. }
  715. $innercode .= "\$msg->addparam(\$p$i);\n";
  716. $mdesc .= '* @param '.xmlrpc_2_php_type($ptype)." \$p$i\n";
  717. }
  718. if ($client_copy_mode < 2)
  719. {
  720. $plist[] = '$debug=0';
  721. $mdesc .= "* @param int \$debug when 1 (or 2) will enable debugging of the underlying {$prefix} call (defaults to 0)\n";
  722. }
  723. $plist = implode(', ', $plist);
  724. $mdesc .= '* @return '.xmlrpc_2_php_type($msig[0])." (or an {$prefix}resp obj instance if call fails)\n*/\n";
  725. $innercode .= "\$res =& \${$this_}client->send(\$msg, $timeout, '$protocol');\n";
  726. if ($decode_fault)
  727. {
  728. if (is_string($fault_response) && ((strpos($fault_response, '%faultCode%') !== false) || (strpos($fault_response, '%faultString%') !== false)))
  729. {
  730. $respcode = "str_replace(array('%faultCode%', '%faultString%'), array(\$res->faultCode(), \$res->faultString()), '".str_replace("'", "''", $fault_response)."')";
  731. }
  732. else
  733. {
  734. $respcode = var_export($fault_response, true);
  735. }
  736. }
  737. else
  738. {
  739. $respcode = '$res';
  740. }
  741. if ($decode_php_objects)
  742. {
  743. $innercode .= "if (\$res->faultcode()) return $respcode; else return php_{$prefix}_decode(\$res->value(), array('decode_php_objs'));";
  744. }
  745. else
  746. {
  747. $innercode .= "if (\$res->faultcode()) return $respcode; else return php_{$prefix}_decode(\$res->value());";
  748. }
  749. $code = $code . $plist. ") {\n" . $innercode . "\n}\n";
  750. return array('source' => $code, 'docstring' => $mdesc);
  751. }
  752. /**
  753. * Given necessary info, generate php code that will rebuild a client object
  754. * Take care that no full checking of input parameters is done to ensure that
  755. * valid php code is emitted.
  756. * @access private
  757. */
  758. function build_client_wrapper_code($client, $verbatim_client_copy, $prefix='xmlrpc')
  759. {
  760. $code = "\$client = new {$prefix}_client('".str_replace("'", "\'", $client->path).
  761. "', '" . str_replace("'", "\'", $client->server) . "', $client->port);\n";
  762. // copy all client fields to the client that will be generated runtime
  763. // (this provides for future expansion or subclassing of client obj)
  764. if ($verbatim_client_copy)
  765. {
  766. foreach($client as $fld => $val)
  767. {
  768. if($fld != 'debug' && $fld != 'return_type')
  769. {
  770. $val = var_export($val, true);
  771. $code .= "\$client->$fld = $val;\n";
  772. }
  773. }
  774. }
  775. // only make sure that client always returns the correct data type
  776. $code .= "\$client->return_type = '{$prefix}vals';\n";
  777. //$code .= "\$client->setDebug(\$debug);\n";
  778. return $code;
  779. }
  780. ?>