See Release Notes
Long Term Support Release
Differences Between: [Versions 401 and 402] [Versions 401 and 403]
1 <?php 2 /** 3 * @author Gaetano Giunta 4 * @copyright (C) 2006-2021 G. Giunta 5 * @license code licensed under the BSD License: see file license.txt 6 */ 7 8 namespace PhpXmlRpc; 9 10 use PhpXmlRpc\Helper\Logger; 11 12 /** 13 * PHP-XMLRPC "wrapper" class - generate stubs to transparently access xmlrpc methods as php functions and vice-versa. 14 * Note: this class implements the PROXY pattern, but it is not named so to avoid confusion with http proxies. 15 * 16 * @todo use some better templating system for code generation? 17 * @todo implement method wrapping with preservation of php objs in calls 18 * @todo when wrapping methods without obj rebuilding, use return_type = 'phpvals' (faster) 19 * @todo add support for 'epivals' mode 20 * @todo allow setting custom namespace for generated wrapping code 21 */ 22 class Wrapper 23 { 24 /// used to hold a reference to object instances whose methods get wrapped by wrapPhpFunction(), in 'create source' mode 25 public static $objHolder = array(); 26 27 protected static $logger; 28 29 public function getLogger() 30 { 31 if (self::$logger === null) { 32 self::$logger = Logger::instance(); 33 } 34 return self::$logger; 35 } 36 37 public static function setLogger($logger) 38 { 39 self::$logger = $logger; 40 } 41 42 /** 43 * Given a string defining a php type or phpxmlrpc type (loosely defined: strings 44 * accepted come from javadoc blocks), return corresponding phpxmlrpc type. 45 * Notes: 46 * - for php 'resource' types returns empty string, since resources cannot be serialized; 47 * - for php class names returns 'struct', since php objects can be serialized as xmlrpc structs 48 * - for php arrays always return array, even though arrays sometimes serialize as structs... 49 * - for 'void' and 'null' returns 'undefined' 50 * 51 * @param string $phpType 52 * 53 * @return string 54 * 55 * @todo support notation `something[]` as 'array' 56 */ 57 public function php2XmlrpcType($phpType) 58 { 59 switch (strtolower($phpType)) { 60 case 'string': 61 return Value::$xmlrpcString; 62 case 'integer': 63 case Value::$xmlrpcInt: // 'int' 64 case Value::$xmlrpcI4: 65 case Value::$xmlrpcI8: 66 return Value::$xmlrpcInt; 67 case Value::$xmlrpcDouble: // 'double' 68 return Value::$xmlrpcDouble; 69 case 'bool': 70 case Value::$xmlrpcBoolean: // 'boolean' 71 case 'false': 72 case 'true': 73 return Value::$xmlrpcBoolean; 74 case Value::$xmlrpcArray: // 'array': 75 case 'array[]'; 76 return Value::$xmlrpcArray; 77 case 'object': 78 case Value::$xmlrpcStruct: // 'struct' 79 return Value::$xmlrpcStruct; 80 case Value::$xmlrpcBase64: 81 return Value::$xmlrpcBase64; 82 case 'resource': 83 return ''; 84 default: 85 if (class_exists($phpType)) { 86 if (is_a($phpType, 'DateTimeInterface')) { 87 return Value::$xmlrpcDateTime; 88 } 89 return Value::$xmlrpcStruct; 90 } else { 91 // unknown: might be any 'extended' xmlrpc type 92 return Value::$xmlrpcValue; 93 } 94 } 95 } 96 97 /** 98 * Given a string defining a phpxmlrpc type return the corresponding php type. 99 * 100 * @param string $xmlrpcType 101 * 102 * @return string 103 */ 104 public function xmlrpc2PhpType($xmlrpcType) 105 { 106 switch (strtolower($xmlrpcType)) { 107 case 'base64': 108 case 'datetime.iso8601': 109 case 'string': 110 return Value::$xmlrpcString; 111 case 'int': 112 case 'i4': 113 case 'i8': 114 return 'integer'; 115 case 'struct': 116 case 'array': 117 return 'array'; 118 case 'double': 119 return 'float'; 120 case 'undefined': 121 return 'mixed'; 122 case 'boolean': 123 case 'null': 124 default: 125 // unknown: might be any xmlrpc type 126 return strtolower($xmlrpcType); 127 } 128 } 129 130 /** 131 * Given a user-defined PHP function, create a PHP 'wrapper' function that can 132 * be exposed as xmlrpc method from an xmlrpc server object and called from remote 133 * clients (as well as its corresponding signature info). 134 * 135 * Since php is a typeless language, to infer types of input and output parameters, 136 * it relies on parsing the javadoc-style comment block associated with the given 137 * function. Usage of xmlrpc native types (such as datetime.dateTime.iso8601 and base64) 138 * in the '@param' tag is also allowed, if you need the php function to receive/send 139 * data in that particular format (note that base64 encoding/decoding is transparently 140 * carried out by the lib, while datetime vals are passed around as strings) 141 * 142 * Known limitations: 143 * - only works for user-defined functions, not for PHP internal functions 144 * (reflection does not support retrieving number/type of params for those) 145 * - functions returning php objects will generate special structs in xmlrpc responses: 146 * when the xmlrpc decoding of those responses is carried out by this same lib, using 147 * the appropriate param in php_xmlrpc_decode, the php objects will be rebuilt. 148 * In short: php objects can be serialized, too (except for their resource members), 149 * using this function. 150 * Other libs might choke on the very same xml that will be generated in this case 151 * (i.e. it has a nonstandard attribute on struct element tags) 152 * 153 * Note that since rel. 2.0RC3 the preferred method to have the server call 'standard' 154 * php functions (ie. functions not expecting a single Request obj as parameter) 155 * is by making use of the functions_parameters_type class member. 156 * 157 * @param callable $callable the PHP user function to be exposed as xmlrpc method/ a closure, function name, array($obj, 'methodname') or array('class', 'methodname') are ok 158 * @param string $newFuncName (optional) name for function to be created. Used only when return_source in $extraOptions is true 159 * @param array $extraOptions (optional) array of options for conversion. valid values include: 160 * - bool return_source when true, php code w. function definition will be returned, instead of a closure 161 * - bool encode_php_objs let php objects be sent to server using the 'improved' xmlrpc notation, so server can deserialize them as php objects 162 * - bool decode_php_objs --- WARNING !!! possible security hazard. only use it with trusted servers --- 163 * - bool suppress_warnings remove from produced xml any warnings generated at runtime by the php function being invoked 164 * 165 * @return array|false false on error, or an array containing the name of the new php function, 166 * its signature and docs, to be used in the server dispatch map 167 * 168 * @todo decide how to deal with params passed by ref in function definition: bomb out or allow? 169 * @todo finish using phpdoc info to build method sig if all params are named but out of order 170 * @todo add a check for params of 'resource' type 171 * @todo add some trigger_errors / error_log when returning false? 172 * @todo what to do when the PHP function returns NULL? We are currently returning an empty string value... 173 * @todo add an option to suppress php warnings in invocation of user function, similar to server debug level 3? 174 * @todo add a verbatim_object_copy parameter to allow avoiding usage the same obj instance? 175 * @todo add an option to allow generated function to skip validation of number of parameters, as that is done by the server anyway 176 */ 177 public function wrapPhpFunction($callable, $newFuncName = '', $extraOptions = array()) 178 { 179 $buildIt = isset($extraOptions['return_source']) ? !($extraOptions['return_source']) : true; 180 181 if (is_string($callable) && strpos($callable, '::') !== false) { 182 $callable = explode('::', $callable); 183 } 184 if (is_array($callable)) { 185 if (count($callable) < 2 || (!is_string($callable[0]) && !is_object($callable[0]))) { 186 $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': syntax for function to be wrapped is wrong'); 187 return false; 188 } 189 if (is_string($callable[0])) { 190 $plainFuncName = implode('::', $callable); 191 } elseif (is_object($callable[0])) { 192 $plainFuncName = get_class($callable[0]) . '->' . $callable[1]; 193 } 194 $exists = method_exists($callable[0], $callable[1]); 195 } else if ($callable instanceof \Closure) { 196 // we do not support creating code which wraps closures, as php does not allow to serialize them 197 if (!$buildIt) { 198 $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': a closure can not be wrapped in generated source code'); 199 return false; 200 } 201 202 $plainFuncName = 'Closure'; 203 $exists = true; 204 } else { 205 $plainFuncName = $callable; 206 $exists = function_exists($callable); 207 } 208 209 if (!$exists) { 210 $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': function to be wrapped is not defined: ' . $plainFuncName); 211 return false; 212 } 213 214 $funcDesc = $this->introspectFunction($callable, $plainFuncName); 215 if (!$funcDesc) { 216 return false; 217 } 218 219 $funcSigs = $this->buildMethodSignatures($funcDesc); 220 221 if ($buildIt) { 222 $callable = $this->buildWrapFunctionClosure($callable, $extraOptions, $plainFuncName, $funcDesc); 223 } else { 224 $newFuncName = $this->newFunctionName($callable, $newFuncName, $extraOptions); 225 $code = $this->buildWrapFunctionSource($callable, $newFuncName, $extraOptions, $plainFuncName, $funcDesc); 226 } 227 228 $ret = array( 229 'function' => $callable, 230 'signature' => $funcSigs['sigs'], 231 'docstring' => $funcDesc['desc'], 232 'signature_docs' => $funcSigs['sigsDocs'], 233 ); 234 if (!$buildIt) { 235 $ret['function'] = $newFuncName; 236 $ret['source'] = $code; 237 } 238 return $ret; 239 } 240 241 /** 242 * Introspect a php callable and its phpdoc block and extract information about its signature 243 * 244 * @param callable $callable 245 * @param string $plainFuncName 246 * @return array|false 247 */ 248 protected function introspectFunction($callable, $plainFuncName) 249 { 250 // start to introspect PHP code 251 if (is_array($callable)) { 252 $func = new \ReflectionMethod($callable[0], $callable[1]); 253 if ($func->isPrivate()) { 254 $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': method to be wrapped is private: ' . $plainFuncName); 255 return false; 256 } 257 if ($func->isProtected()) { 258 $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': method to be wrapped is protected: ' . $plainFuncName); 259 return false; 260 } 261 if ($func->isConstructor()) { 262 $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': method to be wrapped is the constructor: ' . $plainFuncName); 263 return false; 264 } 265 if ($func->isDestructor()) { 266 $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': method to be wrapped is the destructor: ' . $plainFuncName); 267 return false; 268 } 269 if ($func->isAbstract()) { 270 $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': method to be wrapped is abstract: ' . $plainFuncName); 271 return false; 272 } 273 /// @todo add more checks for static vs. nonstatic? 274 } else { 275 $func = new \ReflectionFunction($callable); 276 } 277 if ($func->isInternal()) { 278 // Note: from PHP 5.1.0 onward, we will possibly be able to use invokeargs 279 // instead of getparameters to fully reflect internal php functions ? 280 $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': function to be wrapped is internal: ' . $plainFuncName); 281 return false; 282 } 283 284 // retrieve parameter names, types and description from javadoc comments 285 286 // function description 287 $desc = ''; 288 // type of return val: by default 'any' 289 $returns = Value::$xmlrpcValue; 290 // desc of return val 291 $returnsDocs = ''; 292 // type + name of function parameters 293 $paramDocs = array(); 294 295 $docs = $func->getDocComment(); 296 if ($docs != '') { 297 $docs = explode("\n", $docs); 298 $i = 0; 299 foreach ($docs as $doc) { 300 $doc = trim($doc, " \r\t/*"); 301 if (strlen($doc) && strpos($doc, '@') !== 0 && !$i) { 302 if ($desc) { 303 $desc .= "\n"; 304 } 305 $desc .= $doc; 306 } elseif (strpos($doc, '@param') === 0) { 307 // syntax: @param type $name [desc] 308 if (preg_match('/@param\s+(\S+)\s+(\$\S+)\s*(.+)?/', $doc, $matches)) { 309 $name = strtolower(trim($matches[2])); 310 //$paramDocs[$name]['name'] = trim($matches[2]); 311 $paramDocs[$name]['doc'] = isset($matches[3]) ? $matches[3] : ''; 312 $paramDocs[$name]['type'] = $matches[1]; 313 } 314 $i++; 315 } elseif (strpos($doc, '@return') === 0) { 316 // syntax: @return type [desc] 317 if (preg_match('/@return\s+(\S+)(\s+.+)?/', $doc, $matches)) { 318 $returns = $matches[1]; 319 if (isset($matches[2])) { 320 $returnsDocs = trim($matches[2]); 321 } 322 } 323 } 324 } 325 } 326 327 // execute introspection of actual function prototype 328 $params = array(); 329 $i = 0; 330 foreach ($func->getParameters() as $paramObj) { 331 $params[$i] = array(); 332 $params[$i]['name'] = '$' . $paramObj->getName(); 333 $params[$i]['isoptional'] = $paramObj->isOptional(); 334 $i++; 335 } 336 337 return array( 338 'desc' => $desc, 339 'docs' => $docs, 340 'params' => $params, // array, positionally indexed 341 'paramDocs' => $paramDocs, // array, indexed by name 342 'returns' => $returns, 343 'returnsDocs' =>$returnsDocs, 344 ); 345 } 346 347 /** 348 * Given the method description given by introspection, create method signature data 349 * 350 * @todo support better docs with multiple types separated by pipes by creating multiple signatures 351 * (this is questionable, as it might produce a big matrix of possible signatures with many such occurrences) 352 * 353 * @param array $funcDesc as generated by self::introspectFunction() 354 * 355 * @return array 356 */ 357 protected function buildMethodSignatures($funcDesc) 358 { 359 $i = 0; 360 $parsVariations = array(); 361 $pars = array(); 362 $pNum = count($funcDesc['params']); 363 foreach ($funcDesc['params'] as $param) { 364 /* // match by name real param and documented params 365 $name = strtolower($param['name']); 366 if (!isset($funcDesc['paramDocs'][$name])) { 367 $funcDesc['paramDocs'][$name] = array(); 368 } 369 if (!isset($funcDesc['paramDocs'][$name]['type'])) { 370 $funcDesc['paramDocs'][$name]['type'] = 'mixed'; 371 }*/ 372 373 if ($param['isoptional']) { 374 // this particular parameter is optional. save as valid previous list of parameters 375 $parsVariations[] = $pars; 376 } 377 378 $pars[] = "\$p$i"; 379 $i++; 380 if ($i == $pNum) { 381 // last allowed parameters combination 382 $parsVariations[] = $pars; 383 } 384 } 385 386 if (count($parsVariations) == 0) { 387 // only known good synopsis = no parameters 388 $parsVariations[] = array(); 389 } 390 391 $sigs = array(); 392 $sigsDocs = array(); 393 foreach ($parsVariations as $pars) { 394 // build a signature 395 $sig = array($this->php2XmlrpcType($funcDesc['returns'])); 396 $pSig = array($funcDesc['returnsDocs']); 397 for ($i = 0; $i < count($pars); $i++) { 398 $name = strtolower($funcDesc['params'][$i]['name']); 399 if (isset($funcDesc['paramDocs'][$name]['type'])) { 400 $sig[] = $this->php2XmlrpcType($funcDesc['paramDocs'][$name]['type']); 401 } else { 402 $sig[] = Value::$xmlrpcValue; 403 } 404 $pSig[] = isset($funcDesc['paramDocs'][$name]['doc']) ? $funcDesc['paramDocs'][$name]['doc'] : ''; 405 } 406 $sigs[] = $sig; 407 $sigsDocs[] = $pSig; 408 } 409 410 return array( 411 'sigs' => $sigs, 412 'sigsDocs' => $sigsDocs 413 ); 414 } 415 416 /** 417 * Creates a closure that will execute $callable 418 * @todo validate params? In theory all validation is left to the dispatch map... 419 * @todo add support for $catchWarnings 420 * 421 * @param $callable 422 * @param array $extraOptions 423 * @param string $plainFuncName 424 * @param array $funcDesc 425 * @return \Closure 426 */ 427 protected function buildWrapFunctionClosure($callable, $extraOptions, $plainFuncName, $funcDesc) 428 { 429 /** 430 * @param Request $req 431 * @return mixed 432 */ 433 $function = function($req) use($callable, $extraOptions, $funcDesc) 434 { 435 $nameSpace = '\\PhpXmlRpc\\'; 436 $encoderClass = $nameSpace.'Encoder'; 437 $responseClass = $nameSpace.'Response'; 438 $valueClass = $nameSpace.'Value'; 439 440 // validate number of parameters received 441 // this should be optional really, as we assume the server does the validation 442 $minPars = count($funcDesc['params']); 443 $maxPars = $minPars; 444 foreach ($funcDesc['params'] as $i => $param) { 445 if ($param['isoptional']) { 446 // this particular parameter is optional. We assume later ones are as well 447 $minPars = $i; 448 break; 449 } 450 } 451 $numPars = $req->getNumParams(); 452 if ($numPars < $minPars || $numPars > $maxPars) { 453 return new $responseClass(0, 3, 'Incorrect parameters passed to method'); 454 } 455 456 $encoder = new $encoderClass(); 457 $options = array(); 458 if (isset($extraOptions['decode_php_objs']) && $extraOptions['decode_php_objs']) { 459 $options[] = 'decode_php_objs'; 460 } 461 $params = $encoder->decode($req, $options); 462 463 $result = call_user_func_array($callable, $params); 464 465 if (! is_a($result, $responseClass)) { 466 if ($funcDesc['returns'] == Value::$xmlrpcDateTime || $funcDesc['returns'] == Value::$xmlrpcBase64) { 467 $result = new $valueClass($result, $funcDesc['returns']); 468 } else { 469 $options = array(); 470 if (isset($extraOptions['encode_php_objs']) && $extraOptions['encode_php_objs']) { 471 $options[] = 'encode_php_objs'; 472 } 473 474 $result = $encoder->encode($result, $options); 475 } 476 $result = new $responseClass($result); 477 } 478 479 return $result; 480 }; 481 482 return $function; 483 } 484 485 /** 486 * Return a name for a new function, based on $callable, insuring its uniqueness 487 * @param mixed $callable a php callable, or the name of an xmlrpc method 488 * @param string $newFuncName when not empty, it is used instead of the calculated version 489 * @return string 490 */ 491 protected function newFunctionName($callable, $newFuncName, $extraOptions) 492 { 493 // determine name of new php function 494 495 $prefix = isset($extraOptions['prefix']) ? $extraOptions['prefix'] : 'xmlrpc'; 496 497 if ($newFuncName == '') { 498 if (is_array($callable)) { 499 if (is_string($callable[0])) { 500 $xmlrpcFuncName = "{$prefix}_" . implode('_', $callable); 501 } else { 502 $xmlrpcFuncName = "{$prefix}_" . get_class($callable[0]) . '_' . $callable[1]; 503 } 504 } else { 505 if ($callable instanceof \Closure) { 506 $xmlrpcFuncName = "{$prefix}_closure"; 507 } else { 508 $callable = preg_replace(array('/\./', '/[^a-zA-Z0-9_\x7f-\xff]/'), 509 array('_', ''), $callable); 510 $xmlrpcFuncName = "{$prefix}_$callable"; 511 } 512 } 513 } else { 514 $xmlrpcFuncName = $newFuncName; 515 } 516 517 while (function_exists($xmlrpcFuncName)) { 518 $xmlrpcFuncName .= 'x'; 519 } 520 521 return $xmlrpcFuncName; 522 } 523 524 /** 525 * @param $callable 526 * @param string $newFuncName 527 * @param array $extraOptions 528 * @param string $plainFuncName 529 * @param array $funcDesc 530 * @return string 531 * 532 * @todo add a nice phpdoc block in the generated source 533 */ 534 protected function buildWrapFunctionSource($callable, $newFuncName, $extraOptions, $plainFuncName, $funcDesc) 535 { 536 $namespace = '\\PhpXmlRpc\\'; 537 538 $encodePhpObjects = isset($extraOptions['encode_php_objs']) ? (bool)$extraOptions['encode_php_objs'] : false; 539 $decodePhpObjects = isset($extraOptions['decode_php_objs']) ? (bool)$extraOptions['decode_php_objs'] : false; 540 $catchWarnings = isset($extraOptions['suppress_warnings']) && $extraOptions['suppress_warnings'] ? '@' : ''; 541 542 $i = 0; 543 $parsVariations = array(); 544 $pars = array(); 545 $pNum = count($funcDesc['params']); 546 foreach ($funcDesc['params'] as $param) { 547 548 if ($param['isoptional']) { 549 // this particular parameter is optional. save as valid previous list of parameters 550 $parsVariations[] = $pars; 551 } 552 553 $pars[] = "\$p[$i]"; 554 $i++; 555 if ($i == $pNum) { 556 // last allowed parameters combination 557 $parsVariations[] = $pars; 558 } 559 } 560 561 if (count($parsVariations) == 0) { 562 // only known good synopsis = no parameters 563 $parsVariations[] = array(); 564 $minPars = 0; 565 $maxPars = 0; 566 } else { 567 $minPars = count($parsVariations[0]); 568 $maxPars = count($parsVariations[count($parsVariations)-1]); 569 } 570 571 // build body of new function 572 573 $innerCode = "\$paramCount = \$req->getNumParams();\n"; 574 $innerCode .= "if (\$paramCount < $minPars || \$paramCount > $maxPars) return new {$namespace}Response(0, " . PhpXmlRpc::$xmlrpcerr['incorrect_params'] . ", '" . PhpXmlRpc::$xmlrpcstr['incorrect_params'] . "');\n"; 575 576 $innerCode .= "\$encoder = new {$namespace}Encoder();\n"; 577 if ($decodePhpObjects) { 578 $innerCode .= "\$p = \$encoder->decode(\$req, array('decode_php_objs'));\n"; 579 } else { 580 $innerCode .= "\$p = \$encoder->decode(\$req);\n"; 581 } 582 583 // since we are building source code for later use, if we are given an object instance, 584 // we go out of our way and store a pointer to it in a static class var... 585 if (is_array($callable) && is_object($callable[0])) { 586 self::$objHolder[$newFuncName] = $callable[0]; 587 $innerCode .= "\$obj = PhpXmlRpc\\Wrapper::\$objHolder['$newFuncName'];\n"; 588 $realFuncName = '$obj->' . $callable[1]; 589 } else { 590 $realFuncName = $plainFuncName; 591 } 592 foreach ($parsVariations as $i => $pars) { 593 $innerCode .= "if (\$paramCount == " . count($pars) . ") \$retval = {$catchWarnings}$realFuncName(" . implode(',', $pars) . ");\n"; 594 if ($i < (count($parsVariations) - 1)) 595 $innerCode .= "else\n"; 596 } 597 $innerCode .= "if (is_a(\$retval, '{$namespace}Response')) return \$retval; else\n"; 598 if ($funcDesc['returns'] == Value::$xmlrpcDateTime || $funcDesc['returns'] == Value::$xmlrpcBase64) { 599 $innerCode .= "return new {$namespace}Response(new {$namespace}Value(\$retval, '{$funcDesc['returns']}'));"; 600 } else { 601 if ($encodePhpObjects) { 602 $innerCode .= "return new {$namespace}Response(\$encoder->encode(\$retval, array('encode_php_objs')));\n"; 603 } else { 604 $innerCode .= "return new {$namespace}Response(\$encoder->encode(\$retval));\n"; 605 } 606 } 607 // shall we exclude functions returning by ref? 608 // if($func->returnsReference()) 609 // return false; 610 611 $code = "function $newFuncName(\$req) {\n" . $innerCode . "\n}"; 612 613 return $code; 614 } 615 616 /** 617 * Given a user-defined PHP class or php object, map its methods onto a list of 618 * PHP 'wrapper' functions that can be exposed as xmlrpc methods from an xmlrpc server 619 * object and called from remote clients (as well as their corresponding signature info). 620 * 621 * @param string|object $className the name of the class whose methods are to be exposed as xmlrpc methods, or an object instance of that class 622 * @param array $extraOptions see the docs for wrapPhpMethod for basic options, plus 623 * - string method_type 'static', 'nonstatic', 'all' and 'auto' (default); the latter will switch between static and non-static depending on whether $className is a class name or object instance 624 * - string method_filter a regexp used to filter methods to wrap based on their names 625 * - string prefix used for the names of the xmlrpc methods created. 626 * - string replace_class_name use to completely replace the class name with the prefix in the generated method names. e.g. instead of \Some\Namespace\Class.method use prefixmethod 627 * @return array|false false on failure 628 */ 629 public function wrapPhpClass($className, $extraOptions = array()) 630 { 631 $methodFilter = isset($extraOptions['method_filter']) ? $extraOptions['method_filter'] : ''; 632 $methodType = isset($extraOptions['method_type']) ? $extraOptions['method_type'] : 'auto'; 633 634 $results = array(); 635 $mList = get_class_methods($className); 636 foreach ($mList as $mName) { 637 if ($methodFilter == '' || preg_match($methodFilter, $mName)) { 638 $func = new \ReflectionMethod($className, $mName); 639 if (!$func->isPrivate() && !$func->isProtected() && !$func->isConstructor() && !$func->isDestructor() && !$func->isAbstract()) { 640 if (($func->isStatic() && ($methodType == 'all' || $methodType == 'static' || ($methodType == 'auto' && is_string($className)))) || 641 (!$func->isStatic() && ($methodType == 'all' || $methodType == 'nonstatic' || ($methodType == 'auto' && is_object($className)))) 642 ) { 643 $methodWrap = $this->wrapPhpFunction(array($className, $mName), '', $extraOptions); 644 645 if ($methodWrap) { 646 $results[$this->generateMethodNameForClassMethod($className, $mName, $extraOptions)] = $methodWrap; 647 } 648 } 649 } 650 } 651 } 652 653 return $results; 654 } 655 656 /** 657 * @param string|object $className 658 * @param string $classMethod 659 * @param array $extraOptions 660 * @return string 661 */ 662 protected function generateMethodNameForClassMethod($className, $classMethod, $extraOptions = array()) 663 { 664 if (isset($extraOptions['replace_class_name']) && $extraOptions['replace_class_name']) { 665 return (isset($extraOptions['prefix']) ? $extraOptions['prefix'] : '') . $classMethod; 666 } 667 668 if (is_object($className)) { 669 $realClassName = get_class($className); 670 } else { 671 $realClassName = $className; 672 } 673 return (isset($extraOptions['prefix']) ? $extraOptions['prefix'] : '') . "$realClassName.$classMethod"; 674 } 675 676 /** 677 * Given an xmlrpc client and a method name, register a php wrapper function 678 * that will call it and return results using native php types for both 679 * params and results. The generated php function will return a Response 680 * object for failed xmlrpc calls. 681 * 682 * Known limitations: 683 * - server must support system.methodsignature for the wanted xmlrpc method 684 * - for methods that expose many signatures, only one can be picked (we 685 * could in principle check if signatures differ only by number of params 686 * and not by type, but it would be more complication than we can spare time) 687 * - nested xmlrpc params: the caller of the generated php function has to 688 * encode on its own the params passed to the php function if these are structs 689 * or arrays whose (sub)members include values of type datetime or base64 690 * 691 * Notes: the connection properties of the given client will be copied 692 * and reused for the connection used during the call to the generated 693 * php function. 694 * Calling the generated php function 'might' be slow: a new xmlrpc client 695 * is created on every invocation and an xmlrpc-connection opened+closed. 696 * An extra 'debug' param is appended to param list of xmlrpc method, useful 697 * for debugging purposes. 698 * 699 * @todo allow caller to give us the method signature instead of querying for it, or just say 'skip it' 700 * @todo if we can not retrieve method signature, create a php function with varargs 701 * @todo allow the created function to throw exceptions on method calls failures 702 * @todo if caller did not specify a specific sig, shall we support all of them? 703 * It might be hard (hence slow) to match based on type and number of arguments... 704 * 705 * @param Client $client an xmlrpc client set up correctly to communicate with target server 706 * @param string $methodName the xmlrpc method to be mapped to a php function 707 * @param array $extraOptions array of options that specify conversion details. Valid options include 708 * - integer signum the index of the method signature to use in mapping (if method exposes many sigs) 709 * - integer timeout timeout (in secs) to be used when executing function/calling remote method 710 * - string protocol 'http' (default), 'http11' or 'https' 711 * - string new_function_name the name of php function to create, when return_source is used. If unspecified, lib will pick an appropriate name 712 * - string return_source if true return php code w. function definition instead of function itself (closure) 713 * - bool encode_php_objs let php objects be sent to server using the 'improved' xmlrpc notation, so server can deserialize them as php objects 714 * - bool decode_php_objs --- WARNING !!! possible security hazard. only use it with trusted servers --- 715 * - mixed return_on_fault a php value to be returned when the xmlrpc call fails/returns a fault response (by default the Response object is returned in this case). If a string is used, '%faultCode%' and '%faultString%' tokens will be substituted with actual error values 716 * - bool debug set it to 1 or 2 to see debug results of querying server for method synopsis 717 * - int simple_client_copy set it to 1 to have a lightweight copy of the $client object made in the generated code (only used when return_source = true) 718 * 719 * @return \closure|string[]|false false on failure, closure by default and array for return_source = true 720 */ 721 public function wrapXmlrpcMethod($client, $methodName, $extraOptions = array()) 722 { 723 $newFuncName = isset($extraOptions['new_function_name']) ? $extraOptions['new_function_name'] : ''; 724 725 $buildIt = isset($extraOptions['return_source']) ? !($extraOptions['return_source']) : true; 726 727 $mSig = $this->retrieveMethodSignature($client, $methodName, $extraOptions); 728 if (!$mSig) { 729 return false; 730 } 731 732 if ($buildIt) { 733 return $this->buildWrapMethodClosure($client, $methodName, $extraOptions, $mSig); 734 } else { 735 // if in 'offline' mode, retrieve method description too. 736 // in online mode, favour speed of operation 737 $mDesc = $this->retrieveMethodHelp($client, $methodName, $extraOptions); 738 739 $newFuncName = $this->newFunctionName($methodName, $newFuncName, $extraOptions); 740 741 $results = $this->buildWrapMethodSource($client, $methodName, $extraOptions, $newFuncName, $mSig, $mDesc); 742 /* was: $results = $this->build_remote_method_wrapper_code($client, $methodName, 743 $newFuncName, $mSig, $mDesc, $timeout, $protocol, $simpleClientCopy, 744 $prefix, $decodePhpObjects, $encodePhpObjects, $decodeFault, 745 $faultResponse, $namespace);*/ 746 747 $results['function'] = $newFuncName; 748 749 return $results; 750 } 751 } 752 753 /** 754 * Retrieves an xmlrpc method signature from a server which supports system.methodSignature 755 * @param Client $client 756 * @param string $methodName 757 * @param array $extraOptions 758 * @return false|array 759 */ 760 protected function retrieveMethodSignature($client, $methodName, array $extraOptions = array()) 761 { 762 $namespace = '\\PhpXmlRpc\\'; 763 $reqClass = $namespace . 'Request'; 764 $valClass = $namespace . 'Value'; 765 $decoderClass = $namespace . 'Encoder'; 766 767 $debug = isset($extraOptions['debug']) ? ($extraOptions['debug']) : 0; 768 $timeout = isset($extraOptions['timeout']) ? (int)$extraOptions['timeout'] : 0; 769 $protocol = isset($extraOptions['protocol']) ? $extraOptions['protocol'] : ''; 770 $sigNum = isset($extraOptions['signum']) ? (int)$extraOptions['signum'] : 0; 771 772 $req = new $reqClass('system.methodSignature'); 773 $req->addparam(new $valClass($methodName)); 774 $client->setDebug($debug); 775 $response = $client->send($req, $timeout, $protocol); 776 if ($response->faultCode()) { 777 $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': could not retrieve method signature from remote server for method ' . $methodName); 778 return false; 779 } 780 781 $mSig = $response->value(); 782 if ($client->return_type != 'phpvals') { 783 $decoder = new $decoderClass(); 784 $mSig = $decoder->decode($mSig); 785 } 786 787 if (!is_array($mSig) || count($mSig) <= $sigNum) { 788 $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': could not retrieve method signature nr.' . $sigNum . ' from remote server for method ' . $methodName); 789 return false; 790 } 791 792 return $mSig[$sigNum]; 793 } 794 795 /** 796 * @param Client $client 797 * @param string $methodName 798 * @param array $extraOptions 799 * @return string in case of any error, an empty string is returned, no warnings generated 800 */ 801 protected function retrieveMethodHelp($client, $methodName, array $extraOptions = array()) 802 { 803 $namespace = '\\PhpXmlRpc\\'; 804 $reqClass = $namespace . 'Request'; 805 $valClass = $namespace . 'Value'; 806 807 $debug = isset($extraOptions['debug']) ? ($extraOptions['debug']) : 0; 808 $timeout = isset($extraOptions['timeout']) ? (int)$extraOptions['timeout'] : 0; 809 $protocol = isset($extraOptions['protocol']) ? $extraOptions['protocol'] : ''; 810 811 $mDesc = ''; 812 813 $req = new $reqClass('system.methodHelp'); 814 $req->addparam(new $valClass($methodName)); 815 $client->setDebug($debug); 816 $response = $client->send($req, $timeout, $protocol); 817 if (!$response->faultCode()) { 818 $mDesc = $response->value(); 819 if ($client->return_type != 'phpvals') { 820 $mDesc = $mDesc->scalarval(); 821 } 822 } 823 824 return $mDesc; 825 } 826 827 /** 828 * @param Client $client 829 * @param string $methodName 830 * @param array $extraOptions 831 * @param array $mSig 832 * @return \Closure 833 * 834 * @todo should we allow usage of parameter simple_client_copy to mean 'do not clone' in this case? 835 */ 836 protected function buildWrapMethodClosure($client, $methodName, array $extraOptions, $mSig) 837 { 838 // we clone the client, so that we can modify it a bit independently of the original 839 $clientClone = clone $client; 840 $function = function() use($clientClone, $methodName, $extraOptions, $mSig) 841 { 842 $timeout = isset($extraOptions['timeout']) ? (int)$extraOptions['timeout'] : 0; 843 $protocol = isset($extraOptions['protocol']) ? $extraOptions['protocol'] : ''; 844 $encodePhpObjects = isset($extraOptions['encode_php_objs']) ? (bool)$extraOptions['encode_php_objs'] : false; 845 $decodePhpObjects = isset($extraOptions['decode_php_objs']) ? (bool)$extraOptions['decode_php_objs'] : false; 846 if (isset($extraOptions['return_on_fault'])) { 847 $decodeFault = true; 848 $faultResponse = $extraOptions['return_on_fault']; 849 } else { 850 $decodeFault = false; 851 } 852 853 $namespace = '\\PhpXmlRpc\\'; 854 $reqClass = $namespace . 'Request'; 855 $encoderClass = $namespace . 'Encoder'; 856 $valueClass = $namespace . 'Value'; 857 858 $encoder = new $encoderClass(); 859 $encodeOptions = array(); 860 if ($encodePhpObjects) { 861 $encodeOptions[] = 'encode_php_objs'; 862 } 863 $decodeOptions = array(); 864 if ($decodePhpObjects) { 865 $decodeOptions[] = 'decode_php_objs'; 866 } 867 868 /// @todo check for insufficient nr. of args besides excess ones? note that 'source' version does not... 869 870 // support one extra parameter: debug 871 $maxArgs = count($mSig)-1; // 1st element is the return type 872 $currentArgs = func_get_args(); 873 if (func_num_args() == ($maxArgs+1)) { 874 $debug = array_pop($currentArgs); 875 $clientClone->setDebug($debug); 876 } 877 878 $xmlrpcArgs = array(); 879 foreach($currentArgs as $i => $arg) { 880 if ($i == $maxArgs) { 881 break; 882 } 883 $pType = $mSig[$i+1]; 884 if ($pType == 'i4' || $pType == 'i8' || $pType == 'int' || $pType == 'boolean' || $pType == 'double' || 885 $pType == 'string' || $pType == 'dateTime.iso8601' || $pType == 'base64' || $pType == 'null' 886 ) { 887 // by building directly xmlrpc values when type is known and scalar (instead of encode() calls), 888 // we make sure to honour the xmlrpc signature 889 $xmlrpcArgs[] = new $valueClass($arg, $pType); 890 } else { 891 $xmlrpcArgs[] = $encoder->encode($arg, $encodeOptions); 892 } 893 } 894 895 $req = new $reqClass($methodName, $xmlrpcArgs); 896 // use this to get the maximum decoding flexibility 897 $clientClone->return_type = 'xmlrpcvals'; 898 $resp = $clientClone->send($req, $timeout, $protocol); 899 if ($resp->faultcode()) { 900 if ($decodeFault) { 901 if (is_string($faultResponse) && ((strpos($faultResponse, '%faultCode%') !== false) || 902 (strpos($faultResponse, '%faultString%') !== false))) { 903 $faultResponse = str_replace(array('%faultCode%', '%faultString%'), 904 array($resp->faultCode(), $resp->faultString()), $faultResponse); 905 } 906 return $faultResponse; 907 } else { 908 return $resp; 909 } 910 } else { 911 return $encoder->decode($resp->value(), $decodeOptions); 912 } 913 }; 914 915 return $function; 916 } 917 918 /** 919 * @param Client $client 920 * @param string $methodName 921 * @param array $extraOptions 922 * @param string $newFuncName 923 * @param array $mSig 924 * @param string $mDesc 925 * @return string[] keys: source, docstring 926 */ 927 public function buildWrapMethodSource($client, $methodName, array $extraOptions, $newFuncName, $mSig, $mDesc='') 928 { 929 $timeout = isset($extraOptions['timeout']) ? (int)$extraOptions['timeout'] : 0; 930 $protocol = isset($extraOptions['protocol']) ? $extraOptions['protocol'] : ''; 931 $encodePhpObjects = isset($extraOptions['encode_php_objs']) ? (bool)$extraOptions['encode_php_objs'] : false; 932 $decodePhpObjects = isset($extraOptions['decode_php_objs']) ? (bool)$extraOptions['decode_php_objs'] : false; 933 $clientCopyMode = isset($extraOptions['simple_client_copy']) ? (int)($extraOptions['simple_client_copy']) : 0; 934 $prefix = isset($extraOptions['prefix']) ? $extraOptions['prefix'] : 'xmlrpc'; 935 if (isset($extraOptions['return_on_fault'])) { 936 $decodeFault = true; 937 $faultResponse = $extraOptions['return_on_fault']; 938 } else { 939 $decodeFault = false; 940 $faultResponse = ''; 941 } 942 943 $namespace = '\\PhpXmlRpc\\'; 944 945 $code = "function $newFuncName ("; 946 if ($clientCopyMode < 2) { 947 // client copy mode 0 or 1 == full / partial client copy in emitted code 948 $verbatimClientCopy = !$clientCopyMode; 949 $innerCode = $this->buildClientWrapperCode($client, $verbatimClientCopy, $prefix, $namespace); 950 $innerCode .= "\$client->setDebug(\$debug);\n"; 951 $this_ = ''; 952 } else { 953 // client copy mode 2 == no client copy in emitted code 954 $innerCode = ''; 955 $this_ = 'this->'; 956 } 957 $innerCode .= "\$req = new {$namespace}Request('$methodName');\n"; 958 959 if ($mDesc != '') { 960 // take care that PHP comment is not terminated unwillingly by method description 961 $mDesc = "/**\n* " . str_replace('*/', '* /', $mDesc) . "\n"; 962 } else { 963 $mDesc = "/**\nFunction $newFuncName\n"; 964 } 965 966 // param parsing 967 $innerCode .= "\$encoder = new {$namespace}Encoder();\n"; 968 $plist = array(); 969 $pCount = count($mSig); 970 for ($i = 1; $i < $pCount; $i++) { 971 $plist[] = "\$p$i"; 972 $pType = $mSig[$i]; 973 if ($pType == 'i4' || $pType == 'i8' || $pType == 'int' || $pType == 'boolean' || $pType == 'double' || 974 $pType == 'string' || $pType == 'dateTime.iso8601' || $pType == 'base64' || $pType == 'null' 975 ) { 976 // only build directly xmlrpc values when type is known and scalar 977 $innerCode .= "\$p$i = new {$namespace}Value(\$p$i, '$pType');\n"; 978 } else { 979 if ($encodePhpObjects) { 980 $innerCode .= "\$p$i = \$encoder->encode(\$p$i, array('encode_php_objs'));\n"; 981 } else { 982 $innerCode .= "\$p$i = \$encoder->encode(\$p$i);\n"; 983 } 984 } 985 $innerCode .= "\$req->addparam(\$p$i);\n"; 986 $mDesc .= '* @param ' . $this->xmlrpc2PhpType($pType) . " \$p$i\n"; 987 } 988 if ($clientCopyMode < 2) { 989 $plist[] = '$debug=0'; 990 $mDesc .= "* @param int \$debug when 1 (or 2) will enable debugging of the underlying {$prefix} call (defaults to 0)\n"; 991 } 992 $plist = implode(', ', $plist); 993 $mDesc .= '* @return {$namespace}Response|' . $this->xmlrpc2PhpType($mSig[0]) . " (an {$namespace}Response obj instance if call fails)\n*/\n"; 994 995 $innerCode .= "\$res = \${$this_}client->send(\$req, $timeout, '$protocol');\n"; 996 if ($decodeFault) { 997 if (is_string($faultResponse) && ((strpos($faultResponse, '%faultCode%') !== false) || (strpos($faultResponse, '%faultString%') !== false))) { 998 $respCode = "str_replace(array('%faultCode%', '%faultString%'), array(\$res->faultCode(), \$res->faultString()), '" . str_replace("'", "''", $faultResponse) . "')"; 999 } else { 1000 $respCode = var_export($faultResponse, true); 1001 } 1002 } else { 1003 $respCode = '$res'; 1004 } 1005 if ($decodePhpObjects) { 1006 $innerCode .= "if (\$res->faultcode()) return $respCode; else return \$encoder->decode(\$res->value(), array('decode_php_objs'));"; 1007 } else { 1008 $innerCode .= "if (\$res->faultcode()) return $respCode; else return \$encoder->decode(\$res->value());"; 1009 } 1010 1011 $code = $code . $plist . ") {\n" . $innerCode . "\n}\n"; 1012 1013 return array('source' => $code, 'docstring' => $mDesc); 1014 } 1015 1016 /** 1017 * Similar to wrapXmlrpcMethod, but will generate a php class that wraps 1018 * all xmlrpc methods exposed by the remote server as own methods. 1019 * For more details see wrapXmlrpcMethod. 1020 * 1021 * For a slimmer alternative, see the code in demo/client/proxy.php 1022 * 1023 * Note that unlike wrapXmlrpcMethod, we always have to generate php code here. It seems that php 7 will have anon classes... 1024 * 1025 * @param Client $client the client obj all set to query the desired server 1026 * @param array $extraOptions list of options for wrapped code. See the ones from wrapXmlrpcMethod plus 1027 * - string method_filter regular expression 1028 * - string new_class_name 1029 * - string prefix 1030 * - bool simple_client_copy set it to true to avoid copying all properties of $client into the copy made in the new class 1031 * 1032 * @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 appropriate option is set in extra_options) 1033 */ 1034 public function wrapXmlrpcServer($client, $extraOptions = array()) 1035 { 1036 $methodFilter = isset($extraOptions['method_filter']) ? $extraOptions['method_filter'] : ''; 1037 $timeout = isset($extraOptions['timeout']) ? (int)$extraOptions['timeout'] : 0; 1038 $protocol = isset($extraOptions['protocol']) ? $extraOptions['protocol'] : ''; 1039 $newClassName = isset($extraOptions['new_class_name']) ? $extraOptions['new_class_name'] : ''; 1040 $encodePhpObjects = isset($extraOptions['encode_php_objs']) ? (bool)$extraOptions['encode_php_objs'] : false; 1041 $decodePhpObjects = isset($extraOptions['decode_php_objs']) ? (bool)$extraOptions['decode_php_objs'] : false; 1042 $verbatimClientCopy = isset($extraOptions['simple_client_copy']) ? !($extraOptions['simple_client_copy']) : true; 1043 $buildIt = isset($extraOptions['return_source']) ? !($extraOptions['return_source']) : true; 1044 $prefix = isset($extraOptions['prefix']) ? $extraOptions['prefix'] : 'xmlrpc'; 1045 $namespace = '\\PhpXmlRpc\\'; 1046 1047 $reqClass = $namespace . 'Request'; 1048 $decoderClass = $namespace . 'Encoder'; 1049 1050 $req = new $reqClass('system.listMethods'); 1051 $response = $client->send($req, $timeout, $protocol); 1052 if ($response->faultCode()) { 1053 $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': could not retrieve method list from remote server'); 1054 1055 return false; 1056 } else { 1057 $mList = $response->value(); 1058 if ($client->return_type != 'phpvals') { 1059 $decoder = new $decoderClass(); 1060 $mList = $decoder->decode($mList); 1061 } 1062 if (!is_array($mList) || !count($mList)) { 1063 $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': could not retrieve meaningful method list from remote server'); 1064 1065 return false; 1066 } else { 1067 // pick a suitable name for the new function, avoiding collisions 1068 if ($newClassName != '') { 1069 $xmlrpcClassName = $newClassName; 1070 } else { 1071 $xmlrpcClassName = $prefix . '_' . preg_replace(array('/\./', '/[^a-zA-Z0-9_\x7f-\xff]/'), 1072 array('_', ''), $client->server) . '_client'; 1073 } 1074 while ($buildIt && class_exists($xmlrpcClassName)) { 1075 $xmlrpcClassName .= 'x'; 1076 } 1077 1078 /// @todo add function setdebug() to new class, to enable/disable debugging 1079 $source = "class $xmlrpcClassName\n{\npublic \$client;\n\n"; 1080 $source .= "function __construct()\n{\n"; 1081 $source .= $this->buildClientWrapperCode($client, $verbatimClientCopy, $prefix, $namespace); 1082 $source .= "\$this->client = \$client;\n}\n\n"; 1083 $opts = array( 1084 'return_source' => true, 1085 'simple_client_copy' => 2, // do not produce code to copy the client object 1086 'timeout' => $timeout, 1087 'protocol' => $protocol, 1088 'encode_php_objs' => $encodePhpObjects, 1089 'decode_php_objs' => $decodePhpObjects, 1090 'prefix' => $prefix, 1091 ); 1092 /// @todo build phpdoc for class definition, too 1093 foreach ($mList as $mName) { 1094 if ($methodFilter == '' || preg_match($methodFilter, $mName)) { 1095 // note: this will fail if server exposes 2 methods called f.e. do.something and do_something 1096 $opts['new_function_name'] = preg_replace(array('/\./', '/[^a-zA-Z0-9_\x7f-\xff]/'), 1097 array('_', ''), $mName); 1098 $methodWrap = $this->wrapXmlrpcMethod($client, $mName, $opts); 1099 if ($methodWrap) { 1100 if (!$buildIt) { 1101 $source .= $methodWrap['docstring']; 1102 } 1103 $source .= $methodWrap['source'] . "\n"; 1104 } else { 1105 $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': will not create class method to wrap remote method ' . $mName); 1106 } 1107 } 1108 } 1109 $source .= "}\n"; 1110 if ($buildIt) { 1111 $allOK = 0; 1112 eval($source . '$allOK=1;'); 1113 if ($allOK) { 1114 return $xmlrpcClassName; 1115 } else { 1116 $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': could not create class ' . $xmlrpcClassName . ' to wrap remote server ' . $client->server); 1117 return false; 1118 } 1119 } else { 1120 return array('class' => $xmlrpcClassName, 'code' => $source, 'docstring' => ''); 1121 } 1122 } 1123 } 1124 } 1125 1126 /** 1127 * Given necessary info, generate php code that will build a client object just like the given one. 1128 * Take care that no full checking of input parameters is done to ensure that 1129 * valid php code is emitted. 1130 * @param Client $client 1131 * @param bool $verbatimClientCopy when true, copy all of the state of the client, except for 'debug' and 'return_type' 1132 * @param string $prefix used for the return_type of the created client 1133 * @param string $namespace 1134 * 1135 * @return string 1136 */ 1137 protected function buildClientWrapperCode($client, $verbatimClientCopy, $prefix = 'xmlrpc', $namespace = '\\PhpXmlRpc\\' ) 1138 { 1139 $code = "\$client = new {$namespace}Client('" . str_replace("'", "\'", $client->path) . 1140 "', '" . str_replace("'", "\'", $client->server) . "', $client->port);\n"; 1141 1142 // copy all client fields to the client that will be generated runtime 1143 // (this provides for future expansion or subclassing of client obj) 1144 if ($verbatimClientCopy) { 1145 foreach ($client as $fld => $val) { 1146 /// @todo in php 8.0, curl handles became objects, but they have no __set_state, thus var_export will 1147 /// fail for xmlrpc_curl_handle. So we disabled copying it. 1148 /// We should examine in depth if this change can have side effects - at first sight if the 1149 /// client's curl handle is not set, all curl options are (re)set on each http call, so there 1150 /// should be no loss of state... 1151 if ($fld != 'debug' && $fld != 'return_type' && $fld != 'xmlrpc_curl_handle') { 1152 $val = var_export($val, true); 1153 $code .= "\$client->$fld = $val;\n"; 1154 } 1155 } 1156 } 1157 // only make sure that client always returns the correct data type 1158 $code .= "\$client->return_type = '{$prefix}vals';\n"; 1159 //$code .= "\$client->setDebug(\$debug);\n"; 1160 return $code; 1161 } 1162 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body