Open Monograph Press  3.3.0
Wrapper.php
1 <?php
8 namespace PhpXmlRpc;
9 
19 class Wrapper
20 {
22  public static $objHolder = array();
23 
37  public function php2XmlrpcType($phpType)
38  {
39  switch (strtolower($phpType)) {
40  case 'string':
41  return Value::$xmlrpcString;
42  case 'integer':
43  case Value::$xmlrpcInt: // 'int'
44  case Value::$xmlrpcI4:
45  return Value::$xmlrpcInt;
46  case Value::$xmlrpcDouble: // 'double'
47  return Value::$xmlrpcDouble;
48  case 'bool':
49  case Value::$xmlrpcBoolean: // 'boolean'
50  case 'false':
51  case 'true':
52  return Value::$xmlrpcBoolean;
53  case Value::$xmlrpcArray: // 'array':
54  return Value::$xmlrpcArray;
55  case 'object':
56  case Value::$xmlrpcStruct: // 'struct'
57  return Value::$xmlrpcStruct;
58  case Value::$xmlrpcBase64:
59  return Value::$xmlrpcBase64;
60  case 'resource':
61  return '';
62  default:
63  if (class_exists($phpType)) {
64  return Value::$xmlrpcStruct;
65  } else {
66  // unknown: might be any 'extended' xmlrpc type
67  return Value::$xmlrpcValue;
68  }
69  }
70  }
71 
79  public function xmlrpc2PhpType($xmlrpcType)
80  {
81  switch (strtolower($xmlrpcType)) {
82  case 'base64':
83  case 'datetime.iso8601':
84  case 'string':
85  return Value::$xmlrpcString;
86  case 'int':
87  case 'i4':
88  return 'integer';
89  case 'struct':
90  case 'array':
91  return 'array';
92  case 'double':
93  return 'float';
94  case 'undefined':
95  return 'mixed';
96  case 'boolean':
97  case 'null':
98  default:
99  // unknown: might be any xmlrpc type
100  return strtolower($xmlrpcType);
101  }
102  }
103 
151  public function wrapPhpFunction($callable, $newFuncName = '', $extraOptions = array())
152  {
153  $buildIt = isset($extraOptions['return_source']) ? !($extraOptions['return_source']) : true;
154 
155  if (is_string($callable) && strpos($callable, '::') !== false) {
156  $callable = explode('::', $callable);
157  }
158  if (is_array($callable)) {
159  if (count($callable) < 2 || (!is_string($callable[0]) && !is_object($callable[0]))) {
160  error_log('XML-RPC: ' . __METHOD__ . ': syntax for function to be wrapped is wrong');
161  return false;
162  }
163  if (is_string($callable[0])) {
164  $plainFuncName = implode('::', $callable);
165  } elseif (is_object($callable[0])) {
166  $plainFuncName = get_class($callable[0]) . '->' . $callable[1];
167  }
168  $exists = method_exists($callable[0], $callable[1]);
169  } else if ($callable instanceof \Closure) {
170  // we do not support creating code which wraps closures, as php does not allow to serialize them
171  if (!$buildIt) {
172  error_log('XML-RPC: ' . __METHOD__ . ': a closure can not be wrapped in generated source code');
173  return false;
174  }
175 
176  $plainFuncName = 'Closure';
177  $exists = true;
178  } else {
179  $plainFuncName = $callable;
180  $exists = function_exists($callable);
181  }
182 
183  if (!$exists) {
184  error_log('XML-RPC: ' . __METHOD__ . ': function to be wrapped is not defined: ' . $plainFuncName);
185  return false;
186  }
187 
188  $funcDesc = $this->introspectFunction($callable, $plainFuncName);
189  if (!$funcDesc) {
190  return false;
191  }
192 
193  $funcSigs = $this->buildMethodSignatures($funcDesc);
194 
195  if ($buildIt) {
196  $callable = $this->buildWrapFunctionClosure($callable, $extraOptions, $plainFuncName, $funcDesc);
197  } else {
198  $newFuncName = $this->newFunctionName($callable, $newFuncName, $extraOptions);
199  $code = $this->buildWrapFunctionSource($callable, $newFuncName, $extraOptions, $plainFuncName, $funcDesc);
200  }
201 
202  $ret = array(
203  'function' => $callable,
204  'signature' => $funcSigs['sigs'],
205  'docstring' => $funcDesc['desc'],
206  'signature_docs' => $funcSigs['sigsDocs'],
207  );
208  if (!$buildIt) {
209  $ret['function'] = $newFuncName;
210  $ret['source'] = $code;
211  }
212  return $ret;
213  }
214 
222  protected function introspectFunction($callable, $plainFuncName)
223  {
224  // start to introspect PHP code
225  if (is_array($callable)) {
226  $func = new \ReflectionMethod($callable[0], $callable[1]);
227  if ($func->isPrivate()) {
228  error_log('XML-RPC: ' . __METHOD__ . ': method to be wrapped is private: ' . $plainFuncName);
229  return false;
230  }
231  if ($func->isProtected()) {
232  error_log('XML-RPC: ' . __METHOD__ . ': method to be wrapped is protected: ' . $plainFuncName);
233  return false;
234  }
235  if ($func->isConstructor()) {
236  error_log('XML-RPC: ' . __METHOD__ . ': method to be wrapped is the constructor: ' . $plainFuncName);
237  return false;
238  }
239  if ($func->isDestructor()) {
240  error_log('XML-RPC: ' . __METHOD__ . ': method to be wrapped is the destructor: ' . $plainFuncName);
241  return false;
242  }
243  if ($func->isAbstract()) {
244  error_log('XML-RPC: ' . __METHOD__ . ': method to be wrapped is abstract: ' . $plainFuncName);
245  return false;
246  }
248  } else {
249  $func = new \ReflectionFunction($callable);
250  }
251  if ($func->isInternal()) {
252  // Note: from PHP 5.1.0 onward, we will possibly be able to use invokeargs
253  // instead of getparameters to fully reflect internal php functions ?
254  error_log('XML-RPC: ' . __METHOD__ . ': function to be wrapped is internal: ' . $plainFuncName);
255  return false;
256  }
257 
258  // retrieve parameter names, types and description from javadoc comments
259 
260  // function description
261  $desc = '';
262  // type of return val: by default 'any'
263  $returns = Value::$xmlrpcValue;
264  // desc of return val
265  $returnsDocs = '';
266  // type + name of function parameters
267  $paramDocs = array();
268 
269  $docs = $func->getDocComment();
270  if ($docs != '') {
271  $docs = explode("\n", $docs);
272  $i = 0;
273  foreach ($docs as $doc) {
274  $doc = trim($doc, " \r\t/*");
275  if (strlen($doc) && strpos($doc, '@') !== 0 && !$i) {
276  if ($desc) {
277  $desc .= "\n";
278  }
279  $desc .= $doc;
280  } elseif (strpos($doc, '@param') === 0) {
281  // syntax: @param type $name [desc]
282  if (preg_match('/@param\s+(\S+)\s+(\$\S+)\s*(.+)?/', $doc, $matches)) {
283  $name = strtolower(trim($matches[2]));
284  //$paramDocs[$name]['name'] = trim($matches[2]);
285  $paramDocs[$name]['doc'] = isset($matches[3]) ? $matches[3] : '';
286  $paramDocs[$name]['type'] = $matches[1];
287  }
288  $i++;
289  } elseif (strpos($doc, '@return') === 0) {
290  // syntax: @return type [desc]
291  if (preg_match('/@return\s+(\S+)(\s+.+)?/', $doc, $matches)) {
292  $returns = $matches[1];
293  if (isset($matches[2])) {
294  $returnsDocs = trim($matches[2]);
295  }
296  }
297  }
298  }
299  }
300 
301  // execute introspection of actual function prototype
302  $params = array();
303  $i = 0;
304  foreach ($func->getParameters() as $paramObj) {
305  $params[$i] = array();
306  $params[$i]['name'] = '$' . $paramObj->getName();
307  $params[$i]['isoptional'] = $paramObj->isOptional();
308  $i++;
309  }
310 
311  return array(
312  'desc' => $desc,
313  'docs' => $docs,
314  'params' => $params, // array, positionally indexed
315  'paramDocs' => $paramDocs, // array, indexed by name
316  'returns' => $returns,
317  'returnsDocs' =>$returnsDocs,
318  );
319  }
320 
331  protected function buildMethodSignatures($funcDesc)
332  {
333  $i = 0;
334  $parsVariations = array();
335  $pars = array();
336  $pNum = count($funcDesc['params']);
337  foreach ($funcDesc['params'] as $param) {
338  /* // match by name real param and documented params
339  $name = strtolower($param['name']);
340  if (!isset($funcDesc['paramDocs'][$name])) {
341  $funcDesc['paramDocs'][$name] = array();
342  }
343  if (!isset($funcDesc['paramDocs'][$name]['type'])) {
344  $funcDesc['paramDocs'][$name]['type'] = 'mixed';
345  }*/
346 
347  if ($param['isoptional']) {
348  // this particular parameter is optional. save as valid previous list of parameters
349  $parsVariations[] = $pars;
350  }
351 
352  $pars[] = "\$p$i";
353  $i++;
354  if ($i == $pNum) {
355  // last allowed parameters combination
356  $parsVariations[] = $pars;
357  }
358  }
359 
360  if (count($parsVariations) == 0) {
361  // only known good synopsis = no parameters
362  $parsVariations[] = array();
363  }
364 
365  $sigs = array();
366  $sigsDocs = array();
367  foreach ($parsVariations as $pars) {
368  // build a signature
369  $sig = array($this->php2XmlrpcType($funcDesc['returns']));
370  $pSig = array($funcDesc['returnsDocs']);
371  for ($i = 0; $i < count($pars); $i++) {
372  $name = strtolower($funcDesc['params'][$i]['name']);
373  if (isset($funcDesc['paramDocs'][$name]['type'])) {
374  $sig[] = $this->php2XmlrpcType($funcDesc['paramDocs'][$name]['type']);
375  } else {
376  $sig[] = Value::$xmlrpcValue;
377  }
378  $pSig[] = isset($funcDesc['paramDocs'][$name]['doc']) ? $funcDesc['paramDocs'][$name]['doc'] : '';
379  }
380  $sigs[] = $sig;
381  $sigsDocs[] = $pSig;
382  }
383 
384  return array(
385  'sigs' => $sigs,
386  'sigsDocs' => $sigsDocs
387  );
388  }
389 
401  protected function buildWrapFunctionClosure($callable, $extraOptions, $plainFuncName, $funcDesc)
402  {
403  $function = function($req) use($callable, $extraOptions, $funcDesc)
404  {
405  $nameSpace = '\\PhpXmlRpc\\';
406  $encoderClass = $nameSpace.'Encoder';
407  $responseClass = $nameSpace.'Response';
408  $valueClass = $nameSpace.'Value';
409 
410  // validate number of parameters received
411  // this should be optional really, as we assume the server does the validation
412  $minPars = count($funcDesc['params']);
413  $maxPars = $minPars;
414  foreach ($funcDesc['params'] as $i => $param) {
415  if ($param['isoptional']) {
416  // this particular parameter is optional. We assume later ones are as well
417  $minPars = $i;
418  break;
419  }
420  }
421  $numPars = $req->getNumParams();
422  if ($numPars < $minPars || $numPars > $maxPars) {
423  return new $responseClass(0, 3, 'Incorrect parameters passed to method');
424  }
425 
426  $encoder = new $encoderClass();
427  $options = array();
428  if (isset($extraOptions['decode_php_objs']) && $extraOptions['decode_php_objs']) {
429  $options[] = 'decode_php_objs';
430  }
431  $params = $encoder->decode($req, $options);
432 
433  $result = call_user_func_array($callable, $params);
434 
435  if (! is_a($result, $responseClass)) {
436  if ($funcDesc['returns'] == Value::$xmlrpcDateTime || $funcDesc['returns'] == Value::$xmlrpcBase64) {
437  $result = new $valueClass($result, $funcDesc['returns']);
438  } else {
439  $options = array();
440  if (isset($extraOptions['encode_php_objs']) && $extraOptions['encode_php_objs']) {
441  $options[] = 'encode_php_objs';
442  }
443 
444  $result = $encoder->encode($result, $options);
445  }
446  $result = new $responseClass($result);
447  }
448 
449  return $result;
450  };
451 
452  return $function;
453  }
454 
461  protected function newFunctionName($callable, $newFuncName, $extraOptions)
462  {
463  // determine name of new php function
464 
465  $prefix = isset($extraOptions['prefix']) ? $extraOptions['prefix'] : 'xmlrpc';
466 
467  if ($newFuncName == '') {
468  if (is_array($callable)) {
469  if (is_string($callable[0])) {
470  $xmlrpcFuncName = "{$prefix}_" . implode('_', $callable);
471  } else {
472  $xmlrpcFuncName = "{$prefix}_" . get_class($callable[0]) . '_' . $callable[1];
473  }
474  } else {
475  if ($callable instanceof \Closure) {
476  $xmlrpcFuncName = "{$prefix}_closure";
477  } else {
478  $callable = preg_replace(array('/\./', '/[^a-zA-Z0-9_\x7f-\xff]/'),
479  array('_', ''), $callable);
480  $xmlrpcFuncName = "{$prefix}_$callable";
481  }
482  }
483  } else {
484  $xmlrpcFuncName = $newFuncName;
485  }
486 
487  while (function_exists($xmlrpcFuncName)) {
488  $xmlrpcFuncName .= 'x';
489  }
490 
491  return $xmlrpcFuncName;
492  }
493 
504  protected function buildWrapFunctionSource($callable, $newFuncName, $extraOptions, $plainFuncName, $funcDesc)
505  {
506  $namespace = '\\PhpXmlRpc\\';
507 
508  $encodePhpObjects = isset($extraOptions['encode_php_objs']) ? (bool)$extraOptions['encode_php_objs'] : false;
509  $decodePhpObjects = isset($extraOptions['decode_php_objs']) ? (bool)$extraOptions['decode_php_objs'] : false;
510  $catchWarnings = isset($extraOptions['suppress_warnings']) && $extraOptions['suppress_warnings'] ? '@' : '';
511 
512  $i = 0;
513  $parsVariations = array();
514  $pars = array();
515  $pNum = count($funcDesc['params']);
516  foreach ($funcDesc['params'] as $param) {
517 
518  if ($param['isoptional']) {
519  // this particular parameter is optional. save as valid previous list of parameters
520  $parsVariations[] = $pars;
521  }
522 
523  $pars[] = "\$p[$i]";
524  $i++;
525  if ($i == $pNum) {
526  // last allowed parameters combination
527  $parsVariations[] = $pars;
528  }
529  }
530 
531  if (count($parsVariations) == 0) {
532  // only known good synopsis = no parameters
533  $parsVariations[] = array();
534  $minPars = 0;
535  $maxPars = 0;
536  } else {
537  $minPars = count($parsVariations[0]);
538  $maxPars = count($parsVariations[count($parsVariations)-1]);
539  }
540 
541  // build body of new function
542 
543  $innerCode = "\$paramCount = \$req->getNumParams();\n";
544  $innerCode .= "if (\$paramCount < $minPars || \$paramCount > $maxPars) return new {$namespace}Response(0, " . PhpXmlRpc::$xmlrpcerr['incorrect_params'] . ", '" . PhpXmlRpc::$xmlrpcstr['incorrect_params'] . "');\n";
545 
546  $innerCode .= "\$encoder = new {$namespace}Encoder();\n";
547  if ($decodePhpObjects) {
548  $innerCode .= "\$p = \$encoder->decode(\$req, array('decode_php_objs'));\n";
549  } else {
550  $innerCode .= "\$p = \$encoder->decode(\$req);\n";
551  }
552 
553  // since we are building source code for later use, if we are given an object instance,
554  // we go out of our way and store a pointer to it in a static class var var...
555  if (is_array($callable) && is_object($callable[0])) {
556  self::$objHolder[$newFuncName] = $callable[0];
557  $innerCode .= "\$obj = PhpXmlRpc\\Wrapper::\$objHolder['$newFuncName'];\n";
558  $realFuncName = '$obj->' . $callable[1];
559  } else {
560  $realFuncName = $plainFuncName;
561  }
562  foreach ($parsVariations as $i => $pars) {
563  $innerCode .= "if (\$paramCount == " . count($pars) . ") \$retval = {$catchWarnings}$realFuncName(" . implode(',', $pars) . ");\n";
564  if ($i < (count($parsVariations) - 1))
565  $innerCode .= "else\n";
566  }
567  $innerCode .= "if (is_a(\$retval, '{$namespace}Response')) return \$retval; else\n";
568  if ($funcDesc['returns'] == Value::$xmlrpcDateTime || $funcDesc['returns'] == Value::$xmlrpcBase64) {
569  $innerCode .= "return new {$namespace}Response(new {$namespace}Value(\$retval, '{$funcDesc['returns']}'));";
570  } else {
571  if ($encodePhpObjects) {
572  $innerCode .= "return new {$namespace}Response(\$encoder->encode(\$retval, array('encode_php_objs')));\n";
573  } else {
574  $innerCode .= "return new {$namespace}Response(\$encoder->encode(\$retval));\n";
575  }
576  }
577  // shall we exclude functions returning by ref?
578  // if($func->returnsReference())
579  // return false;
580 
581  $code = "function $newFuncName(\$req) {\n" . $innerCode . "\n}";
582 
583  return $code;
584  }
585 
599  public function wrapPhpClass($className, $extraOptions = array())
600  {
601  $methodFilter = isset($extraOptions['method_filter']) ? $extraOptions['method_filter'] : '';
602  $methodType = isset($extraOptions['method_type']) ? $extraOptions['method_type'] : 'auto';
603  $prefix = isset($extraOptions['prefix']) ? $extraOptions['prefix'] : '';
604 
605  $results = array();
606  $mList = get_class_methods($className);
607  foreach ($mList as $mName) {
608  if ($methodFilter == '' || preg_match($methodFilter, $mName)) {
609  $func = new \ReflectionMethod($className, $mName);
610  if (!$func->isPrivate() && !$func->isProtected() && !$func->isConstructor() && !$func->isDestructor() && !$func->isAbstract()) {
611  if (($func->isStatic() && ($methodType == 'all' || $methodType == 'static' || ($methodType == 'auto' && is_string($className)))) ||
612  (!$func->isStatic() && ($methodType == 'all' || $methodType == 'nonstatic' || ($methodType == 'auto' && is_object($className))))
613  ) {
614  $methodWrap = $this->wrapPhpFunction(array($className, $mName), '', $extraOptions);
615  if ($methodWrap) {
616  if (is_object($className)) {
617  $realClassName = get_class($className);
618  }else {
619  $realClassName = $className;
620  }
621  $results[$prefix."$realClassName.$mName"] = $methodWrap;
622  }
623  }
624  }
625  }
626  }
627 
628  return $results;
629  }
630 
676  public function wrapXmlrpcMethod($client, $methodName, $extraOptions = array())
677  {
678  $newFuncName = isset($extraOptions['new_function_name']) ? $extraOptions['new_function_name'] : '';
679 
680  $buildIt = isset($extraOptions['return_source']) ? !($extraOptions['return_source']) : true;
681 
682  $mSig = $this->retrieveMethodSignature($client, $methodName, $extraOptions);
683  if (!$mSig) {
684  return false;
685  }
686 
687  if ($buildIt) {
688  return $this->buildWrapMethodClosure($client, $methodName, $extraOptions, $mSig);
689  } else {
690  // if in 'offline' mode, retrieve method description too.
691  // in online mode, favour speed of operation
692  $mDesc = $this->retrieveMethodHelp($client, $methodName, $extraOptions);
693 
694  $newFuncName = $this->newFunctionName($methodName, $newFuncName, $extraOptions);
695 
696  $results = $this->buildWrapMethodSource($client, $methodName, $extraOptions, $newFuncName, $mSig, $mDesc);
697  /* was: $results = $this->build_remote_method_wrapper_code($client, $methodName,
698  $newFuncName, $mSig, $mDesc, $timeout, $protocol, $simpleClientCopy,
699  $prefix, $decodePhpObjects, $encodePhpObjects, $decodeFault,
700  $faultResponse, $namespace);*/
701 
702  $results['function'] = $newFuncName;
703 
704  return $results;
705  }
706 
707  }
708 
716  protected function retrieveMethodSignature($client, $methodName, array $extraOptions = array())
717  {
718  $namespace = '\\PhpXmlRpc\\';
719  $reqClass = $namespace . 'Request';
720  $valClass = $namespace . 'Value';
721  $decoderClass = $namespace . 'Encoder';
722 
723  $debug = isset($extraOptions['debug']) ? ($extraOptions['debug']) : 0;
724  $timeout = isset($extraOptions['timeout']) ? (int)$extraOptions['timeout'] : 0;
725  $protocol = isset($extraOptions['protocol']) ? $extraOptions['protocol'] : '';
726  $sigNum = isset($extraOptions['signum']) ? (int)$extraOptions['signum'] : 0;
727 
728  $req = new $reqClass('system.methodSignature');
729  $req->addparam(new $valClass($methodName));
730  $client->setDebug($debug);
731  $response = $client->send($req, $timeout, $protocol);
732  if ($response->faultCode()) {
733  error_log('XML-RPC: ' . __METHOD__ . ': could not retrieve method signature from remote server for method ' . $methodName);
734  return false;
735  }
736 
737  $mSig = $response->value();
738  if ($client->return_type != 'phpvals') {
739  $decoder = new $decoderClass();
740  $mSig = $decoder->decode($mSig);
741  }
742 
743  if (!is_array($mSig) || count($mSig) <= $sigNum) {
744  error_log('XML-RPC: ' . __METHOD__ . ': could not retrieve method signature nr.' . $sigNum . ' from remote server for method ' . $methodName);
745  return false;
746  }
747 
748  return $mSig[$sigNum];
749  }
750 
757  protected function retrieveMethodHelp($client, $methodName, array $extraOptions = array())
758  {
759  $namespace = '\\PhpXmlRpc\\';
760  $reqClass = $namespace . 'Request';
761  $valClass = $namespace . 'Value';
762 
763  $debug = isset($extraOptions['debug']) ? ($extraOptions['debug']) : 0;
764  $timeout = isset($extraOptions['timeout']) ? (int)$extraOptions['timeout'] : 0;
765  $protocol = isset($extraOptions['protocol']) ? $extraOptions['protocol'] : '';
766 
767  $mDesc = '';
768 
769  $req = new $reqClass('system.methodHelp');
770  $req->addparam(new $valClass($methodName));
771  $client->setDebug($debug);
772  $response = $client->send($req, $timeout, $protocol);
773  if (!$response->faultCode()) {
774  $mDesc = $response->value();
775  if ($client->return_type != 'phpvals') {
776  $mDesc = $mDesc->scalarval();
777  }
778  }
779 
780  return $mDesc;
781  }
782 
792  protected function buildWrapMethodClosure($client, $methodName, array $extraOptions, $mSig)
793  {
794  // we clone the client, so that we can modify it a bit independently of the original
795  $clientClone = clone $client;
796  $function = function() use($clientClone, $methodName, $extraOptions, $mSig)
797  {
798  $timeout = isset($extraOptions['timeout']) ? (int)$extraOptions['timeout'] : 0;
799  $protocol = isset($extraOptions['protocol']) ? $extraOptions['protocol'] : '';
800  $encodePhpObjects = isset($extraOptions['encode_php_objs']) ? (bool)$extraOptions['encode_php_objs'] : false;
801  $decodePhpObjects = isset($extraOptions['decode_php_objs']) ? (bool)$extraOptions['decode_php_objs'] : false;
802  if (isset($extraOptions['return_on_fault'])) {
803  $decodeFault = true;
804  $faultResponse = $extraOptions['return_on_fault'];
805  } else {
806  $decodeFault = false;
807  }
808 
809  $namespace = '\\PhpXmlRpc\\';
810  $reqClass = $namespace . 'Request';
811  $encoderClass = $namespace . 'Encoder';
812  $valueClass = $namespace . 'Value';
813 
814  $encoder = new $encoderClass();
815  $encodeOptions = array();
816  if ($encodePhpObjects) {
817  $encodeOptions[] = 'encode_php_objs';
818  }
819  $decodeOptions = array();
820  if ($decodePhpObjects) {
821  $decodeOptions[] = 'decode_php_objs';
822  }
823 
825 
826  // support one extra parameter: debug
827  $maxArgs = count($mSig)-1; // 1st element is the return type
828  $currentArgs = func_get_args();
829  if (func_num_args() == ($maxArgs+1)) {
830  $debug = array_pop($currentArgs);
831  $clientClone->setDebug($debug);
832  }
833 
834  $xmlrpcArgs = array();
835  foreach($currentArgs as $i => $arg) {
836  if ($i == $maxArgs) {
837  break;
838  }
839  $pType = $mSig[$i+1];
840  if ($pType == 'i4' || $pType == 'int' || $pType == 'boolean' || $pType == 'double' ||
841  $pType == 'string' || $pType == 'dateTime.iso8601' || $pType == 'base64' || $pType == 'null'
842  ) {
843  // by building directly xmlrpc values when type is known and scalar (instead of encode() calls),
844  // we make sure to honour the xmlrpc signature
845  $xmlrpcArgs[] = new $valueClass($arg, $pType);
846  } else {
847  $xmlrpcArgs[] = $encoder->encode($arg, $encodeOptions);
848  }
849  }
850 
851  $req = new $reqClass($methodName, $xmlrpcArgs);
852  // use this to get the maximum decoding flexibility
853  $clientClone->return_type = 'xmlrpcvals';
854  $resp = $clientClone->send($req, $timeout, $protocol);
855  if ($resp->faultcode()) {
856  if ($decodeFault) {
857  if (is_string($faultResponse) && ((strpos($faultResponse, '%faultCode%') !== false) ||
858  (strpos($faultResponse, '%faultString%') !== false))) {
859  $faultResponse = str_replace(array('%faultCode%', '%faultString%'),
860  array($resp->faultCode(), $resp->faultString()), $faultResponse);
861  }
862  return $faultResponse;
863  } else {
864  return $resp;
865  }
866  } else {
867  return $encoder->decode($resp->value(), $decodeOptions);
868  }
869  };
870 
871  return $function;
872  }
873 
883  public function buildWrapMethodSource($client, $methodName, array $extraOptions, $newFuncName, $mSig, $mDesc='')
884  {
885  $timeout = isset($extraOptions['timeout']) ? (int)$extraOptions['timeout'] : 0;
886  $protocol = isset($extraOptions['protocol']) ? $extraOptions['protocol'] : '';
887  $encodePhpObjects = isset($extraOptions['encode_php_objs']) ? (bool)$extraOptions['encode_php_objs'] : false;
888  $decodePhpObjects = isset($extraOptions['decode_php_objs']) ? (bool)$extraOptions['decode_php_objs'] : false;
889  $clientCopyMode = isset($extraOptions['simple_client_copy']) ? (int)($extraOptions['simple_client_copy']) : 0;
890  $prefix = isset($extraOptions['prefix']) ? $extraOptions['prefix'] : 'xmlrpc';
891  if (isset($extraOptions['return_on_fault'])) {
892  $decodeFault = true;
893  $faultResponse = $extraOptions['return_on_fault'];
894  } else {
895  $decodeFault = false;
896  $faultResponse = '';
897  }
898 
899  $namespace = '\\PhpXmlRpc\\';
900 
901  $code = "function $newFuncName (";
902  if ($clientCopyMode < 2) {
903  // client copy mode 0 or 1 == full / partial client copy in emitted code
904  $verbatimClientCopy = !$clientCopyMode;
905  $innerCode = $this->buildClientWrapperCode($client, $verbatimClientCopy, $prefix, $namespace);
906  $innerCode .= "\$client->setDebug(\$debug);\n";
907  $this_ = '';
908  } else {
909  // client copy mode 2 == no client copy in emitted code
910  $innerCode = '';
911  $this_ = 'this->';
912  }
913  $innerCode .= "\$req = new {$namespace}Request('$methodName');\n";
914 
915  if ($mDesc != '') {
916  // take care that PHP comment is not terminated unwillingly by method description
917  $mDesc = "/**\n* " . str_replace('*/', '* /', $mDesc) . "\n";
918  } else {
919  $mDesc = "/**\nFunction $newFuncName\n";
920  }
921 
922  // param parsing
923  $innerCode .= "\$encoder = new {$namespace}Encoder();\n";
924  $plist = array();
925  $pCount = count($mSig);
926  for ($i = 1; $i < $pCount; $i++) {
927  $plist[] = "\$p$i";
928  $pType = $mSig[$i];
929  if ($pType == 'i4' || $pType == 'int' || $pType == 'boolean' || $pType == 'double' ||
930  $pType == 'string' || $pType == 'dateTime.iso8601' || $pType == 'base64' || $pType == 'null'
931  ) {
932  // only build directly xmlrpc values when type is known and scalar
933  $innerCode .= "\$p$i = new {$namespace}Value(\$p$i, '$pType');\n";
934  } else {
935  if ($encodePhpObjects) {
936  $innerCode .= "\$p$i = \$encoder->encode(\$p$i, array('encode_php_objs'));\n";
937  } else {
938  $innerCode .= "\$p$i = \$encoder->encode(\$p$i);\n";
939  }
940  }
941  $innerCode .= "\$req->addparam(\$p$i);\n";
942  $mDesc .= '* @param ' . $this->xmlrpc2PhpType($pType) . " \$p$i\n";
943  }
944  if ($clientCopyMode < 2) {
945  $plist[] = '$debug=0';
946  $mDesc .= "* @param int \$debug when 1 (or 2) will enable debugging of the underlying {$prefix} call (defaults to 0)\n";
947  }
948  $plist = implode(', ', $plist);
949  $mDesc .= '* @return ' . $this->xmlrpc2PhpType($mSig[0]) . " (or an {$namespace}Response obj instance if call fails)\n*/\n";
950 
951  $innerCode .= "\$res = \${$this_}client->send(\$req, $timeout, '$protocol');\n";
952  if ($decodeFault) {
953  if (is_string($faultResponse) && ((strpos($faultResponse, '%faultCode%') !== false) || (strpos($faultResponse, '%faultString%') !== false))) {
954  $respCode = "str_replace(array('%faultCode%', '%faultString%'), array(\$res->faultCode(), \$res->faultString()), '" . str_replace("'", "''", $faultResponse) . "')";
955  } else {
956  $respCode = var_export($faultResponse, true);
957  }
958  } else {
959  $respCode = '$res';
960  }
961  if ($decodePhpObjects) {
962  $innerCode .= "if (\$res->faultcode()) return $respCode; else return \$encoder->decode(\$res->value(), array('decode_php_objs'));";
963  } else {
964  $innerCode .= "if (\$res->faultcode()) return $respCode; else return \$encoder->decode(\$res->value());";
965  }
966 
967  $code = $code . $plist . ") {\n" . $innerCode . "\n}\n";
968 
969  return array('source' => $code, 'docstring' => $mDesc);
970  }
971 
990  public function wrapXmlrpcServer($client, $extraOptions = array())
991  {
992  $methodFilter = isset($extraOptions['method_filter']) ? $extraOptions['method_filter'] : '';
993  $timeout = isset($extraOptions['timeout']) ? (int)$extraOptions['timeout'] : 0;
994  $protocol = isset($extraOptions['protocol']) ? $extraOptions['protocol'] : '';
995  $newClassName = isset($extraOptions['new_class_name']) ? $extraOptions['new_class_name'] : '';
996  $encodePhpObjects = isset($extraOptions['encode_php_objs']) ? (bool)$extraOptions['encode_php_objs'] : false;
997  $decodePhpObjects = isset($extraOptions['decode_php_objs']) ? (bool)$extraOptions['decode_php_objs'] : false;
998  $verbatimClientCopy = isset($extraOptions['simple_client_copy']) ? !($extraOptions['simple_client_copy']) : true;
999  $buildIt = isset($extraOptions['return_source']) ? !($extraOptions['return_source']) : true;
1000  $prefix = isset($extraOptions['prefix']) ? $extraOptions['prefix'] : 'xmlrpc';
1001  $namespace = '\\PhpXmlRpc\\';
1002 
1003  $reqClass = $namespace . 'Request';
1004  $decoderClass = $namespace . 'Encoder';
1005 
1006  $req = new $reqClass('system.listMethods');
1007  $response = $client->send($req, $timeout, $protocol);
1008  if ($response->faultCode()) {
1009  error_log('XML-RPC: ' . __METHOD__ . ': could not retrieve method list from remote server');
1010 
1011  return false;
1012  } else {
1013  $mList = $response->value();
1014  if ($client->return_type != 'phpvals') {
1015  $decoder = new $decoderClass();
1016  $mList = $decoder->decode($mList);
1017  }
1018  if (!is_array($mList) || !count($mList)) {
1019  error_log('XML-RPC: ' . __METHOD__ . ': could not retrieve meaningful method list from remote server');
1020 
1021  return false;
1022  } else {
1023  // pick a suitable name for the new function, avoiding collisions
1024  if ($newClassName != '') {
1025  $xmlrpcClassName = $newClassName;
1026  } else {
1027  $xmlrpcClassName = $prefix . '_' . preg_replace(array('/\./', '/[^a-zA-Z0-9_\x7f-\xff]/'),
1028  array('_', ''), $client->server) . '_client';
1029  }
1030  while ($buildIt && class_exists($xmlrpcClassName)) {
1031  $xmlrpcClassName .= 'x';
1032  }
1033 
1035  $source = "class $xmlrpcClassName\n{\npublic \$client;\n\n";
1036  $source .= "function __construct()\n{\n";
1037  $source .= $this->buildClientWrapperCode($client, $verbatimClientCopy, $prefix, $namespace);
1038  $source .= "\$this->client = \$client;\n}\n\n";
1039  $opts = array(
1040  'return_source' => true,
1041  'simple_client_copy' => 2, // do not produce code to copy the client object
1042  'timeout' => $timeout,
1043  'protocol' => $protocol,
1044  'encode_php_objs' => $encodePhpObjects,
1045  'decode_php_objs' => $decodePhpObjects,
1046  'prefix' => $prefix,
1047  );
1049  foreach ($mList as $mName) {
1050  if ($methodFilter == '' || preg_match($methodFilter, $mName)) {
1051  // note: this will fail if server exposes 2 methods called f.e. do.something and do_something
1052  $opts['new_function_name'] = preg_replace(array('/\./', '/[^a-zA-Z0-9_\x7f-\xff]/'),
1053  array('_', ''), $mName);
1054  $methodWrap = $this->wrapXmlrpcMethod($client, $mName, $opts);
1055  if ($methodWrap) {
1056  if (!$buildIt) {
1057  $source .= $methodWrap['docstring'];
1058  }
1059  $source .= $methodWrap['source'] . "\n";
1060  } else {
1061  error_log('XML-RPC: ' . __METHOD__ . ': will not create class method to wrap remote method ' . $mName);
1062  }
1063  }
1064  }
1065  $source .= "}\n";
1066  if ($buildIt) {
1067  $allOK = 0;
1068  eval($source . '$allOK=1;');
1069  if ($allOK) {
1070  return $xmlrpcClassName;
1071  } else {
1072  error_log('XML-RPC: ' . __METHOD__ . ': could not create class ' . $xmlrpcClassName . ' to wrap remote server ' . $client->server);
1073  return false;
1074  }
1075  } else {
1076  return array('class' => $xmlrpcClassName, 'code' => $source, 'docstring' => '');
1077  }
1078  }
1079  }
1080  }
1081 
1093  protected function buildClientWrapperCode($client, $verbatimClientCopy, $prefix = 'xmlrpc', $namespace = '\\PhpXmlRpc\\' )
1094  {
1095  $code = "\$client = new {$namespace}Client('" . str_replace("'", "\'", $client->path) .
1096  "', '" . str_replace("'", "\'", $client->server) . "', $client->port);\n";
1097 
1098  // copy all client fields to the client that will be generated runtime
1099  // (this provides for future expansion or subclassing of client obj)
1100  if ($verbatimClientCopy) {
1101  foreach ($client as $fld => $val) {
1102  if ($fld != 'debug' && $fld != 'return_type') {
1103  $val = var_export($val, true);
1104  $code .= "\$client->$fld = $val;\n";
1105  }
1106  }
1107  }
1108  // only make sure that client always returns the correct data type
1109  $code .= "\$client->return_type = '{$prefix}vals';\n";
1110  //$code .= "\$client->setDebug(\$debug);\n";
1111  return $code;
1112  }
1113 }
PhpXmlRpc\Wrapper\wrapXmlrpcMethod
wrapXmlrpcMethod($client, $methodName, $extraOptions=array())
Definition: Wrapper.php:676
PhpXmlRpc\Wrapper
Definition: Wrapper.php:19
PhpXmlRpc\Wrapper\retrieveMethodSignature
retrieveMethodSignature($client, $methodName, array $extraOptions=array())
Definition: Wrapper.php:716
PhpXmlRpc\Wrapper\buildWrapMethodSource
buildWrapMethodSource($client, $methodName, array $extraOptions, $newFuncName, $mSig, $mDesc='')
Definition: Wrapper.php:883
PhpXmlRpc\Wrapper\wrapPhpFunction
wrapPhpFunction($callable, $newFuncName='', $extraOptions=array())
Definition: Wrapper.php:151
PhpXmlRpc\Wrapper\buildMethodSignatures
buildMethodSignatures($funcDesc)
Definition: Wrapper.php:331
PhpXmlRpc\Wrapper\buildWrapFunctionSource
buildWrapFunctionSource($callable, $newFuncName, $extraOptions, $plainFuncName, $funcDesc)
Definition: Wrapper.php:504
PhpXmlRpc\Wrapper\newFunctionName
newFunctionName($callable, $newFuncName, $extraOptions)
Definition: Wrapper.php:461
PhpXmlRpc\Wrapper\wrapXmlrpcServer
wrapXmlrpcServer($client, $extraOptions=array())
Definition: Wrapper.php:990
PhpXmlRpc\Wrapper\wrapPhpClass
wrapPhpClass($className, $extraOptions=array())
Definition: Wrapper.php:599
PhpXmlRpc\Wrapper\retrieveMethodHelp
retrieveMethodHelp($client, $methodName, array $extraOptions=array())
Definition: Wrapper.php:757
PhpXmlRpc\Wrapper\introspectFunction
introspectFunction($callable, $plainFuncName)
Definition: Wrapper.php:222
PhpXmlRpc
Definition: Autoloader.php:3
PhpXmlRpc\Wrapper\buildWrapMethodClosure
buildWrapMethodClosure($client, $methodName, array $extraOptions, $mSig)
Definition: Wrapper.php:792
PhpXmlRpc\Wrapper\php2XmlrpcType
php2XmlrpcType($phpType)
Definition: Wrapper.php:37
PhpXmlRpc\Wrapper\buildClientWrapperCode
buildClientWrapperCode($client, $verbatimClientCopy, $prefix='xmlrpc', $namespace='\\PhpXmlRpc\\')
Definition: Wrapper.php:1093
PhpXmlRpc\Wrapper\xmlrpc2PhpType
xmlrpc2PhpType($xmlrpcType)
Definition: Wrapper.php:79
PhpXmlRpc\Wrapper\buildWrapFunctionClosure
buildWrapFunctionClosure($callable, $extraOptions, $plainFuncName, $funcDesc)
Definition: Wrapper.php:401