b2evolution PHP Cross Reference Blogging Systems

Source: /inc/_ext/xmlrpc/_xmlrpc_wrappers.inc.php - 956 lines - 35561 bytes - Summary - Text - Print

Description: PHP-XMLRPC "wrapper" functions Generate stubs to transparently access xmlrpc methods as php functions and viceversa

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

title

Description

title

Description

title

Description

title

title

Body