Passed
Push — php51 ( 29fddb...e2e917 )
by Gaetano
09:58
created

wrap_xmlrpc_server()   F

Complexity

Conditions 24
Paths > 20000

Size

Total Lines 105
Code Lines 61

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 24
eloc 61
c 0
b 0
f 0
nc 62976
nop 2
dl 0
loc 105
rs 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * PHP-XMLRPC "wrapper" functions
4
 * Generate stubs to transparently access xmlrpc methods as php functions and viceversa
5
 *
6
 * @author Gaetano Giunta
7
 * @copyright (C) 2006-2014 G. Giunta
8
 * @license code licensed under the BSD License: http://phpxmlrpc.sourceforge.net/license.txt
9
 *
10
 * @todo separate introspection from code generation for func-2-method wrapping
11
 * @todo use some better templating system for code generation?
12
 * @todo implement method wrapping with preservation of php objs in calls
13
 * @todo when wrapping methods without obj rebuilding, use return_type = 'phpvals' (faster)
14
 * @todo implement self-parsing of php code for PHP <= 4
15
 */
16
17
	// requires: xmlrpc.inc
18
19
	/**
20
	* Given a string defining a php type or phpxmlrpc type (loosely defined: strings
21
	* accepted come from javadoc blocks), return corresponding phpxmlrpc type.
22
	* NB: for php 'resource' types returns empty string, since resources cannot be serialized;
23
	* for php class names returns 'struct', since php objects can be serialized as xmlrpc structs
24
	* for php arrays always return array, even though arrays sometiles serialize as json structs
25
	* @param string $phptype
26
	* @return string
27
	*/
28
	function php_2_xmlrpc_type($phptype)
29
	{
30
		switch(strtolower($phptype))
31
		{
32
			case 'string':
33
				return $GLOBALS['xmlrpcString'];
34
			case 'integer':
35
			case $GLOBALS['xmlrpcInt']: // 'int'
36
			case $GLOBALS['xmlrpcI4']:
37
				return $GLOBALS['xmlrpcInt'];
38
			case 'double':
39
				return $GLOBALS['xmlrpcDouble'];
40
			case 'boolean':
41
				return $GLOBALS['xmlrpcBoolean'];
42
			case 'array':
43
				return $GLOBALS['xmlrpcArray'];
44
			case 'object':
45
				return $GLOBALS['xmlrpcStruct'];
46
			case $GLOBALS['xmlrpcBase64']:
47
			case $GLOBALS['xmlrpcStruct']:
48
				return strtolower($phptype);
49
			case 'resource':
50
				return '';
51
			default:
52
				if(class_exists($phptype))
53
				{
54
					return $GLOBALS['xmlrpcStruct'];
55
				}
56
				else
57
				{
58
					// unknown: might be any 'extended' xmlrpc type
59
					return $GLOBALS['xmlrpcValue'];
60
				}
61
		}
62
	}
63
64
	/**
65
	* Given a string defining a phpxmlrpc type return corresponding php type.
66
	* @param string $xmlrpctype
67
	* @return string
68
	*/
69
	function xmlrpc_2_php_type($xmlrpctype)
70
	{
71
		switch(strtolower($xmlrpctype))
72
		{
73
			case 'base64':
74
			case 'datetime.iso8601':
75
			case 'string':
76
				return $GLOBALS['xmlrpcString'];
77
			case 'int':
78
			case 'i4':
79
				return 'integer';
80
			case 'struct':
81
			case 'array':
82
				return 'array';
83
			case 'double':
84
				return 'float';
85
			case 'undefined':
86
				return 'mixed';
87
			case 'boolean':
88
			case 'null':
89
			default:
90
				// unknown: might be any xmlrpc type
91
				return strtolower($xmlrpctype);
92
		}
93
	}
94
95
	/**
96
	* Given a user-defined PHP function, create a PHP 'wrapper' function that can
97
	* be exposed as xmlrpc method from an xmlrpc_server object and called from remote
98
	* clients (as well as its corresponding signature info).
99
	*
100
	* Since php is a typeless language, to infer types of input and output parameters,
101
	* it relies on parsing the javadoc-style comment block associated with the given
102
	* function. Usage of xmlrpc native types (such as datetime.dateTime.iso8601 and base64)
103
	* in the @param tag is also allowed, if you need the php function to receive/send
0 ignored issues
show
Bug introduced by
The type is was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
104
	* data in that particular format (note that base64 encoding/decoding is transparently
105
	* carried out by the lib, while datetime vals are passed around as strings)
106
	*
107
	* Known limitations:
108
	* - only works for user-defined functions, not for PHP internal functions
109
	*   (reflection does not support retrieving number/type of params for those)
110
	* - functions returning php objects will generate special xmlrpc responses:
111
	*   when the xmlrpc decoding of those responses is carried out by this same lib, using
112
	*   the appropriate param in php_xmlrpc_decode, the php objects will be rebuilt.
113
	*   In short: php objects can be serialized, too (except for their resource members),
114
	*   using this function.
115
	*   Other libs might choke on the very same xml that will be generated in this case
116
	*   (i.e. it has a nonstandard attribute on struct element tags)
117
	* - usage of javadoc @param tags using param names in a different order from the
0 ignored issues
show
Bug introduced by
The type using was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
118
	*   function prototype is not considered valid (to be fixed?)
119
	*
120
	* Note that since rel. 2.0RC3 the preferred method to have the server call 'standard'
121
	* php functions (ie. functions not expecting a single xmlrpcmsg obj as parameter)
122
	* is by making use of the functions_parameters_type class member.
123
	*
124
	* @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
125
	* @param string $newfuncname (optional) name for function to be created
126
	* @param array $extra_options (optional) array of options for conversion. valid values include:
127
	*        bool  return_source when true, php code w. function definition will be returned, not evaluated
128
	*        bool  encode_php_objs let php objects be sent to server using the 'improved' xmlrpc notation, so server can deserialize them as php objects
129
	*        bool  decode_php_objs --- WARNING !!! possible security hazard. only use it with trusted servers ---
130
	*        bool  suppress_warnings  remove from produced xml any runtime warnings due to the php function being invoked
131
	* @return false on error, or an array containing the name of the new php function,
132
	*         its signature and docs, to be used in the server dispatch map
133
	*
134
	* @todo decide how to deal with params passed by ref: bomb out or allow?
135
	* @todo finish using javadoc info to build method sig if all params are named but out of order
136
	* @todo add a check for params of 'resource' type
137
	* @todo add some trigger_errors / error_log when returning false?
138
	* @todo what to do when the PHP function returns NULL? we are currently returning an empty string value...
139
	* @todo add an option to suppress php warnings in invocation of user function, similar to server debug level 3?
140
	* @todo if $newfuncname is empty, we could use create_user_func instead of eval, as it is possibly faster
141
	* @todo add a verbatim_object_copy parameter to allow avoiding the same obj instance?
142
	*/
143
	function wrap_php_function($funcname, $newfuncname='', $extra_options=array())
144
	{
145
		$buildit = isset($extra_options['return_source']) ? !($extra_options['return_source']) : true;
146
		$prefix = isset($extra_options['prefix']) ? $extra_options['prefix'] : 'xmlrpc';
147
		$encode_php_objects = isset($extra_options['encode_php_objs']) ? (bool)$extra_options['encode_php_objs'] : false;
148
		$decode_php_objects = isset($extra_options['decode_php_objs']) ? (bool)$extra_options['decode_php_objs'] : false;
149
		$catch_warnings = isset($extra_options['suppress_warnings']) && $extra_options['suppress_warnings'] ? '@' : '';
150
151
		$exists = false;
152
		if (is_string($funcname) && strpos($funcname, '::') !== false)
153
		{
154
			$funcname = explode('::', $funcname);
155
		}
156
		if(is_array($funcname))
157
		{
158
			if(count($funcname) < 2 || (!is_string($funcname[0]) && !is_object($funcname[0])))
159
			{
160
				error_log('XML-RPC: syntax for function to be wrapped is wrong');
161
				return false;
162
			}
163
			if(is_string($funcname[0]))
164
			{
165
				$plainfuncname = implode('::', $funcname);
166
			}
167
			elseif(is_object($funcname[0]))
168
			{
169
				$plainfuncname = get_class($funcname[0]) . '->' . $funcname[1];
170
			}
171
			$exists = method_exists($funcname[0], $funcname[1]);
172
		}
173
		else
174
		{
175
			$plainfuncname = $funcname;
176
			$exists = function_exists($funcname);
177
		}
178
179
		if(!$exists)
180
		{
181
			error_log('XML-RPC: function to be wrapped is not defined: '.$plainfuncname);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $plainfuncname does not seem to be defined for all execution paths leading up to this point.
Loading history...
182
			return false;
183
		}
184
		else
185
		{
186
			// determine name of new php function
187
			if($newfuncname == '')
188
			{
189
				if(is_array($funcname))
190
				{
191
					if(is_string($funcname[0]))
192
						$xmlrpcfuncname = "{$prefix}_".implode('_', $funcname);
193
					else
194
						$xmlrpcfuncname = "{$prefix}_".get_class($funcname[0]) . '_' . $funcname[1];
195
				}
196
				else
197
				{
198
					$xmlrpcfuncname = "{$prefix}_$funcname";
199
				}
200
			}
201
			else
202
			{
203
				$xmlrpcfuncname = $newfuncname;
204
			}
205
			while($buildit && function_exists($xmlrpcfuncname))
206
			{
207
				$xmlrpcfuncname .= 'x';
208
			}
209
210
			// start to introspect PHP code
211
			if(is_array($funcname))
212
			{
213
				$func = new ReflectionMethod($funcname[0], $funcname[1]);
214
				if($func->isPrivate())
215
				{
216
					error_log('XML-RPC: method to be wrapped is private: '.$plainfuncname);
217
					return false;
218
				}
219
				if($func->isProtected())
220
				{
221
					error_log('XML-RPC: method to be wrapped is protected: '.$plainfuncname);
222
					return false;
223
				}
224
	 			if($func->isConstructor())
225
				{
226
					error_log('XML-RPC: method to be wrapped is the constructor: '.$plainfuncname);
227
					return false;
228
				}
229
				if($func->isDestructor())
230
				{
231
					error_log('XML-RPC: method to be wrapped is the destructor: '.$plainfuncname);
232
					return false;
233
				}
234
				if($func->isAbstract())
235
				{
236
					error_log('XML-RPC: method to be wrapped is abstract: '.$plainfuncname);
237
					return false;
238
				}
239
				/// @todo add more checks for static vs. nonstatic?
240
			}
241
			else
242
			{
243
				$func = new ReflectionFunction($funcname);
244
			}
245
			if($func->isInternal())
246
			{
247
				// Note: from PHP 5.1.0 onward, we will possibly be able to use invokeargs
248
				// instead of getparameters to fully reflect internal php functions ?
249
				error_log('XML-RPC: function to be wrapped is internal: '.$plainfuncname);
250
				return false;
251
			}
252
253
			// retrieve parameter names, types and description from javadoc comments
254
255
			// function description
256
			$desc = '';
257
			// type of return val: by default 'any'
258
			$returns = $GLOBALS['xmlrpcValue'];
259
			// desc of return val
260
			$returnsDocs = '';
261
			// type + name of function parameters
262
			$paramDocs = array();
263
264
			$docs = $func->getDocComment();
265
			if($docs != '')
266
			{
267
				$docs = explode("\n", $docs);
268
				$i = 0;
269
				foreach($docs as $doc)
270
				{
271
					$doc = trim($doc, " \r\t/*");
272
					if(strlen($doc) && strpos($doc, '@') !== 0 && !$i)
273
					{
274
						if($desc)
275
						{
276
							$desc .= "\n";
277
						}
278
						$desc .= $doc;
279
					}
280
					elseif(strpos($doc, '@param') === 0)
281
					{
282
						// syntax: @param type [$name] desc
283
						if(preg_match('/@param\s+(\S+)(\s+\$\S+)?\s+(.+)/', $doc, $matches))
284
						{
285
							if(strpos($matches[1], '|'))
286
							{
287
								//$paramDocs[$i]['type'] = explode('|', $matches[1]);
288
								$paramDocs[$i]['type'] = 'mixed';
289
							}
290
							else
291
							{
292
								$paramDocs[$i]['type'] = $matches[1];
293
							}
294
							$paramDocs[$i]['name'] = trim($matches[2]);
295
							$paramDocs[$i]['doc'] = $matches[3];
296
						}
297
						$i++;
298
					}
299
					elseif(strpos($doc, '@return') === 0)
300
					{
301
						// syntax: @return type desc
302
						//$returns = preg_split('/\s+/', $doc);
303
						if(preg_match('/@return\s+(\S+)\s+(.+)/', $doc, $matches))
304
						{
305
							$returns = php_2_xmlrpc_type($matches[1]);
306
							if(isset($matches[2]))
307
							{
308
								$returnsDocs = $matches[2];
309
							}
310
						}
311
					}
312
				}
313
			}
314
315
			// execute introspection of actual function prototype
316
			$params = array();
317
			$i = 0;
318
			foreach($func->getParameters() as $paramobj)
319
			{
320
				$params[$i] = array();
321
				$params[$i]['name'] = '$'.$paramobj->getName();
322
				$params[$i]['isoptional'] = $paramobj->isOptional();
323
				$i++;
324
			}
325
326
327
			// start  building of PHP code to be eval'd
328
			$innercode = '';
329
			$i = 0;
330
			$parsvariations = array();
331
			$pars = array();
332
			$pnum = count($params);
333
			foreach($params as $param)
334
			{
335
				if (isset($paramDocs[$i]['name']) && $paramDocs[$i]['name'] && strtolower($paramDocs[$i]['name']) != strtolower($param['name']))
336
				{
337
					// param name from phpdoc info does not match param definition!
338
					$paramDocs[$i]['type'] = 'mixed';
339
				}
340
341
				if($param['isoptional'])
342
				{
343
					// this particular parameter is optional. save as valid previous list of parameters
344
					$innercode .= "if (\$paramcount > $i) {\n";
345
					$parsvariations[] = $pars;
346
				}
347
				$innercode .= "\$p$i = \$msg->getParam($i);\n";
348
				if ($decode_php_objects)
349
				{
350
					$innercode .= "if (\$p{$i}->kindOf() == 'scalar') \$p$i = \$p{$i}->scalarval(); else \$p$i = php_{$prefix}_decode(\$p$i, array('decode_php_objs'));\n";
351
				}
352
				else
353
				{
354
					$innercode .= "if (\$p{$i}->kindOf() == 'scalar') \$p$i = \$p{$i}->scalarval(); else \$p$i = php_{$prefix}_decode(\$p$i);\n";
355
				}
356
357
				$pars[] = "\$p$i";
358
				$i++;
359
				if($param['isoptional'])
360
				{
361
					$innercode .= "}\n";
362
				}
363
				if($i == $pnum)
364
				{
365
					// last allowed parameters combination
366
					$parsvariations[] = $pars;
367
				}
368
			}
369
370
			$sigs = array();
371
			$psigs = array();
372
			if(count($parsvariations) == 0)
373
			{
374
				// only known good synopsis = no parameters
375
				$parsvariations[] = array();
376
				$minpars = 0;
377
			}
378
			else
379
			{
380
				$minpars = count($parsvariations[0]);
381
			}
382
383
			if($minpars)
384
			{
385
				// add to code the check for min params number
386
				// NB: this check needs to be done BEFORE decoding param values
387
				$innercode = "\$paramcount = \$msg->getNumParams();\n" .
388
				"if (\$paramcount < $minpars) return new {$prefix}resp(0, {$GLOBALS['xmlrpcerr']['incorrect_params']}, '{$GLOBALS['xmlrpcstr']['incorrect_params']}');\n" . $innercode;
389
			}
390
			else
391
			{
392
				$innercode = "\$paramcount = \$msg->getNumParams();\n" . $innercode;
393
			}
394
395
			$innercode .= "\$np = false;\n";
396
			// since there are no closures in php, if we are given an object instance,
397
			// we store a pointer to it in a global var...
398
			if ( is_array($funcname) && is_object($funcname[0]) )
399
			{
400
				$GLOBALS['xmlrpcWPFObjHolder'][$xmlrpcfuncname] =& $funcname[0];
401
				$innercode .= "\$obj =& \$GLOBALS['xmlrpcWPFObjHolder']['$xmlrpcfuncname'];\n";
402
				$realfuncname = '$obj->'.$funcname[1];
403
			}
404
			else
405
			{
406
				$realfuncname = $plainfuncname;
407
			}
408
			foreach($parsvariations as $pars)
409
			{
410
				$innercode .= "if (\$paramcount == " . count($pars) . ") \$retval = {$catch_warnings}$realfuncname(" . implode(',', $pars) . "); else\n";
411
				// build a 'generic' signature (only use an appropriate return type)
412
				$sig = array($returns);
413
				$psig = array($returnsDocs);
414
				for($i=0; $i < count($pars); $i++)
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
415
				{
416
					if (isset($paramDocs[$i]['type']))
417
					{
418
						$sig[] = php_2_xmlrpc_type($paramDocs[$i]['type']);
419
					}
420
					else
421
					{
422
						$sig[] = $GLOBALS['xmlrpcValue'];
423
					}
424
					$psig[] = isset($paramDocs[$i]['doc']) ? $paramDocs[$i]['doc'] : '';
425
				}
426
				$sigs[] = $sig;
427
				$psigs[] = $psig;
428
			}
429
			$innercode .= "\$np = true;\n";
430
			$innercode .= "if (\$np) return new {$prefix}resp(0, {$GLOBALS['xmlrpcerr']['incorrect_params']}, '{$GLOBALS['xmlrpcstr']['incorrect_params']}'); else {\n";
431
			//$innercode .= "if (\$_xmlrpcs_error_occurred) return new xmlrpcresp(0, $GLOBALS['xmlrpcerr']user, \$_xmlrpcs_error_occurred); else\n";
432
			$innercode .= "if (is_a(\$retval, '{$prefix}resp')) return \$retval; else\n";
433
			if($returns == $GLOBALS['xmlrpcDateTime'] || $returns == $GLOBALS['xmlrpcBase64'])
434
			{
435
				$innercode .= "return new {$prefix}resp(new {$prefix}val(\$retval, '$returns'));";
436
			}
437
			else
438
			{
439
				if ($encode_php_objects)
440
					$innercode .= "return new {$prefix}resp(php_{$prefix}_encode(\$retval, array('encode_php_objs')));\n";
441
				else
442
					$innercode .= "return new {$prefix}resp(php_{$prefix}_encode(\$retval));\n";
443
			}
444
			// shall we exclude functions returning by ref?
445
			// if($func->returnsReference())
446
			// 	return false;
447
			$code = "function $xmlrpcfuncname(\$msg) {\n" . $innercode . "}\n}";
448
			//print_r($code);
449
			if ($buildit)
450
			{
451
				$allOK = 0;
452
				eval($code.'$allOK=1;');
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
453
				// alternative
454
				//$xmlrpcfuncname = create_function('$m', $innercode);
455
456
				if(!$allOK)
457
				{
458
					error_log('XML-RPC: could not create function '.$xmlrpcfuncname.' to wrap php function '.$plainfuncname);
459
					return false;
460
				}
461
			}
462
463
			/// @todo examine if $paramDocs matches $parsvariations and build array for
464
			/// usage as method signature, plus put together a nice string for docs
465
466
			$ret = array('function' => $xmlrpcfuncname, 'signature' => $sigs, 'docstring' => $desc, 'signature_docs' => $psigs, 'source' => $code);
467
			return $ret;
468
		}
469
	}
470
471
	/**
472
	* Given a user-defined PHP class or php object, map its methods onto a list of
473
	* PHP 'wrapper' functions that can be exposed as xmlrpc methods from an xmlrpc_server
474
	* object and called from remote clients (as well as their corresponding signature info).
475
	*
476
	* @param mixed $classname the name of the class whose methods are to be exposed as xmlrpc methods, or an object instance of that class
477
	* @param array $extra_options see the docs for wrap_php_method for more options
478
	*        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
479
	* @return array or false on failure
480
	*
481
	* @todo get_class_methods will return both static and non-static methods.
482
	*       we have to differentiate the action, depending on whether we received a class name or object
483
	*/
484
	function wrap_php_class($classname, $extra_options=array())
485
	{
486
		$methodfilter = isset($extra_options['method_filter']) ? $extra_options['method_filter'] : '';
487
		$methodtype = isset($extra_options['method_type']) ? $extra_options['method_type'] : 'auto';
488
489
		$result = array();
490
		$mlist = get_class_methods($classname);
491
		foreach($mlist as $mname)
492
		{
493
			if ($methodfilter == '' || preg_match($methodfilter, $mname))
494
			{
495
				// echo $mlist."\n";
496
				$func = new ReflectionMethod($classname, $mname);
497
				if(!$func->isPrivate() && !$func->isProtected() && !$func->isConstructor() && !$func->isDestructor() && !$func->isAbstract())
498
				{
499
					if(($func->isStatic && ($methodtype == 'all' || $methodtype == 'static' || ($methodtype == 'auto' && is_string($classname)))) ||
0 ignored issues
show
Bug introduced by
The property isStatic does not seem to exist on ReflectionMethod.
Loading history...
introduced by
Consider adding parentheses for clarity. Current Interpretation: ($func->isStatic && $met...& is_object($classname), Probably Intended Meaning: $func->isStatic && ($met... is_object($classname))
Loading history...
500
						(!$func->isStatic && ($methodtype == 'all' || $methodtype == 'nonstatic' || ($methodtype == 'auto' && is_object($classname)))))
501
					{
502
						$methodwrap = wrap_php_function(array($classname, $mname), '', $extra_options);
0 ignored issues
show
Bug introduced by
array($classname, $mname) of type array<integer,mixed> is incompatible with the type string expected by parameter $funcname of wrap_php_function(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

502
						$methodwrap = wrap_php_function(/** @scrutinizer ignore-type */ array($classname, $mname), '', $extra_options);
Loading history...
503
						if ( $methodwrap )
504
						{
505
							$result[$methodwrap['function']] = $methodwrap['function'];
506
						}
507
					}
508
				}
509
			}
510
		}
511
		return $result;
512
	}
513
514
	/**
515
	* Given an xmlrpc client and a method name, register a php wrapper function
516
	* that will call it and return results using native php types for both
517
	* params and results. The generated php function will return an xmlrpcresp
518
	* object for failed xmlrpc calls
519
	*
520
	* Known limitations:
521
	* - server must support system.methodsignature for the wanted xmlrpc method
522
	* - for methods that expose many signatures, only one can be picked (we
523
	*   could in principle check if signatures differ only by number of params
524
	*   and not by type, but it would be more complication than we can spare time)
525
	* - nested xmlrpc params: the caller of the generated php function has to
526
	*   encode on its own the params passed to the php function if these are structs
527
	*   or arrays whose (sub)members include values of type datetime or base64
528
	*
529
	* Notes: the connection properties of the given client will be copied
530
	* and reused for the connection used during the call to the generated
531
	* php function.
532
	* Calling the generated php function 'might' be slow: a new xmlrpc client
533
	* is created on every invocation and an xmlrpc-connection opened+closed.
534
	* An extra 'debug' param is appended to param list of xmlrpc method, useful
535
	* for debugging purposes.
536
	*
537
	* @param xmlrpc_client $client     an xmlrpc client set up correctly to communicate with target server
538
	* @param string        $methodname the xmlrpc method to be mapped to a php function
539
	* @param array         $extra_options array of options that specify conversion details. valid options include
540
	*        integer       signum      the index of the method signature to use in mapping (if method exposes many sigs)
541
	*        integer       timeout     timeout (in secs) to be used when executing function/calling remote method
542
	*        string        protocol    'http' (default), 'http11' or 'https'
543
	*        string        new_function_name the name of php function to create. If unspecified, lib will pick an appropriate name
544
	*        string        return_source if true return php code w. function definition instead fo function name
545
	*        bool          encode_php_objs let php objects be sent to server using the 'improved' xmlrpc notation, so server can deserialize them as php objects
546
	*        bool          decode_php_objs --- WARNING !!! possible security hazard. only use it with trusted servers ---
547
	*        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
548
	*        bool          debug        set it to 1 or 2 to see debug results of querying server for method synopsis
549
	* @return string                   the name of the generated php function (or false) - OR AN ARRAY...
550
	*/
551
	function wrap_xmlrpc_method($client, $methodname, $extra_options=0, $timeout=0, $protocol='', $newfuncname='')
552
	{
553
		// mind numbing: let caller use sane calling convention (as per javadoc, 3 params),
554
		// OR the 2.0 calling convention (no options) - we really love backward compat, don't we?
555
		if (!is_array($extra_options))
556
		{
557
			$signum = $extra_options;
558
			$extra_options = array();
559
		}
560
		else
561
		{
562
			$signum = isset($extra_options['signum']) ? (int)$extra_options['signum'] : 0;
563
			$timeout = isset($extra_options['timeout']) ? (int)$extra_options['timeout'] : 0;
564
			$protocol = isset($extra_options['protocol']) ? $extra_options['protocol'] : '';
565
			$newfuncname = isset($extra_options['new_function_name']) ? $extra_options['new_function_name'] : '';
566
		}
567
		//$encode_php_objects = in_array('encode_php_objects', $extra_options);
568
		//$verbatim_client_copy = in_array('simple_client_copy', $extra_options) ? 1 :
569
		//	in_array('build_class_code', $extra_options) ? 2 : 0;
570
571
		$encode_php_objects = isset($extra_options['encode_php_objs']) ? (bool)$extra_options['encode_php_objs'] : false;
572
		$decode_php_objects = isset($extra_options['decode_php_objs']) ? (bool)$extra_options['decode_php_objs'] : false;
573
		// it seems like the meaning of 'simple_client_copy' here is swapped wrt client_copy_mode later on...
574
		$simple_client_copy = isset($extra_options['simple_client_copy']) ? (int)($extra_options['simple_client_copy']) : 0;
575
		$buildit = isset($extra_options['return_source']) ? !($extra_options['return_source']) : true;
576
		$prefix = isset($extra_options['prefix']) ? $extra_options['prefix'] : 'xmlrpc';
577
		if (isset($extra_options['return_on_fault']))
578
		{
579
			$decode_fault = true;
580
			$fault_response = $extra_options['return_on_fault'];
581
		}
582
		else
583
		{
584
			$decode_fault = false;
585
			$fault_response = '';
586
		}
587
		$debug = isset($extra_options['debug']) ? ($extra_options['debug']) : 0;
588
589
		$msgclass = $prefix.'msg';
590
		$valclass = $prefix.'val';
591
		$decodefunc = 'php_'.$prefix.'_decode';
592
593
		$msg = new $msgclass('system.methodSignature');
594
		$msg->addparam(new $valclass($methodname));
595
		$client->setDebug($debug);
596
		$response =& $client->send($msg, $timeout, $protocol);
597
		if($response->faultCode())
598
		{
599
			error_log('XML-RPC: could not retrieve method signature from remote server for method '.$methodname);
600
			return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type string.
Loading history...
601
		}
602
		else
603
		{
604
			$msig = $response->value();
605
			if ($client->return_type != 'phpvals')
606
			{
607
				$msig = $decodefunc($msig);
608
			}
609
			if(!is_array($msig) || count($msig) <= $signum)
610
			{
611
				error_log('XML-RPC: could not retrieve method signature nr.'.$signum.' from remote server for method '.$methodname);
612
				return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type string.
Loading history...
613
			}
614
			else
615
			{
616
				// pick a suitable name for the new function, avoiding collisions
617
				if($newfuncname != '')
618
				{
619
					$xmlrpcfuncname = $newfuncname;
620
				}
621
				else
622
				{
623
					// take care to insure that methodname is translated to valid
624
					// php function name
625
					$xmlrpcfuncname = $prefix.'_'.preg_replace(array('/\./', '/[^a-zA-Z0-9_\x7f-\xff]/'),
626
						array('_', ''), $methodname);
627
				}
628
				while($buildit && function_exists($xmlrpcfuncname))
629
				{
630
					$xmlrpcfuncname .= 'x';
631
				}
632
633
				$msig = $msig[$signum];
634
				$mdesc = '';
635
				// if in 'offline' mode, get method description too.
636
				// in online mode, favour speed of operation
637
				if(!$buildit)
638
				{
639
					$msg = new $msgclass('system.methodHelp');
640
					$msg->addparam(new $valclass($methodname));
641
					$response =& $client->send($msg, $timeout, $protocol);
642
					if (!$response->faultCode())
643
					{
644
						$mdesc = $response->value();
645
						if ($client->return_type != 'phpvals')
646
						{
647
							$mdesc = $mdesc->scalarval();
0 ignored issues
show
Bug introduced by
The method scalarval() does not exist on integer. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

647
							/** @scrutinizer ignore-call */ $mdesc = $mdesc->scalarval();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
648
						}
649
					}
650
				}
651
652
				$results = build_remote_method_wrapper_code($client, $methodname,
653
					$xmlrpcfuncname, $msig, $mdesc, $timeout, $protocol, $simple_client_copy,
654
					$prefix, $decode_php_objects, $encode_php_objects, $decode_fault,
655
					$fault_response);
656
657
				//print_r($code);
658
				if ($buildit)
659
				{
660
					$allOK = 0;
661
					eval($results['source'].'$allOK=1;');
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
662
					// alternative
663
					//$xmlrpcfuncname = create_function('$m', $innercode);
664
					if($allOK)
665
					{
666
						return $xmlrpcfuncname;
667
					}
668
					else
669
					{
670
						error_log('XML-RPC: could not create function '.$xmlrpcfuncname.' to wrap remote method '.$methodname);
671
						return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type string.
Loading history...
672
					}
673
				}
674
				else
675
				{
676
					$results['function'] = $xmlrpcfuncname;
677
					return $results;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $results returns the type array<string,string> which is incompatible with the documented return type string.
Loading history...
678
				}
679
			}
680
		}
681
	}
682
683
	/**
684
	* Similar to wrap_xmlrpc_method, but will generate a php class that wraps
685
	* all xmlrpc methods exposed by the remote server as own methods.
686
	* For more details see wrap_xmlrpc_method.
687
	* @param xmlrpc_client $client the client obj all set to query the desired server
688
	* @param array $extra_options list of options for wrapped code
689
	* @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)
690
	*/
691
	function wrap_xmlrpc_server($client, $extra_options=array())
692
	{
693
		$methodfilter = isset($extra_options['method_filter']) ? $extra_options['method_filter'] : '';
694
		//$signum = isset($extra_options['signum']) ? (int)$extra_options['signum'] : 0;
695
		$timeout = isset($extra_options['timeout']) ? (int)$extra_options['timeout'] : 0;
696
		$protocol = isset($extra_options['protocol']) ? $extra_options['protocol'] : '';
697
		$newclassname = isset($extra_options['new_class_name']) ? $extra_options['new_class_name'] : '';
698
		$encode_php_objects = isset($extra_options['encode_php_objs']) ? (bool)$extra_options['encode_php_objs'] : false;
699
		$decode_php_objects = isset($extra_options['decode_php_objs']) ? (bool)$extra_options['decode_php_objs'] : false;
700
		$verbatim_client_copy = isset($extra_options['simple_client_copy']) ? !($extra_options['simple_client_copy']) : true;
701
		$buildit = isset($extra_options['return_source']) ? !($extra_options['return_source']) : true;
702
		$prefix = isset($extra_options['prefix']) ? $extra_options['prefix'] : 'xmlrpc';
703
704
		$msgclass = $prefix.'msg';
705
		//$valclass = $prefix.'val';
706
		$decodefunc = 'php_'.$prefix.'_decode';
707
708
		$msg = new $msgclass('system.listMethods');
709
		$response =& $client->send($msg, $timeout, $protocol);
710
		if($response->faultCode())
711
		{
712
			error_log('XML-RPC: could not retrieve method list from remote server');
713
			return false;
714
		}
715
		else
716
		{
717
			$mlist = $response->value();
718
			if ($client->return_type != 'phpvals')
719
			{
720
				$mlist = $decodefunc($mlist);
721
			}
722
			if(!is_array($mlist) || !count($mlist))
723
			{
724
				error_log('XML-RPC: could not retrieve meaningful method list from remote server');
725
				return false;
726
			}
727
			else
728
			{
729
				// pick a suitable name for the new function, avoiding collisions
730
				if($newclassname != '')
731
				{
732
					$xmlrpcclassname = $newclassname;
733
				}
734
				else
735
				{
736
					$xmlrpcclassname = $prefix.'_'.preg_replace(array('/\./', '/[^a-zA-Z0-9_\x7f-\xff]/'),
737
						array('_', ''), $client->server).'_client';
738
				}
739
				while($buildit && class_exists($xmlrpcclassname))
740
				{
741
					$xmlrpcclassname .= 'x';
742
				}
743
744
				/// @todo add function setdebug() to new class, to enable/disable debugging
745
				$source = "class $xmlrpcclassname\n{\nvar \$client;\n\n";
746
				$source .= "function $xmlrpcclassname()\n{\n";
747
				$source .= build_client_wrapper_code($client, $verbatim_client_copy, $prefix);
748
				$source .= "\$this->client =& \$client;\n}\n\n";
749
				$opts = array('simple_client_copy' => 2, 'return_source' => true,
750
					'timeout' => $timeout, 'protocol' => $protocol,
751
					'encode_php_objs' => $encode_php_objects, 'prefix' => $prefix,
752
					'decode_php_objs' => $decode_php_objects
753
					);
754
				/// @todo build javadoc for class definition, too
755
				foreach($mlist as $mname)
756
				{
757
					if ($methodfilter == '' || preg_match($methodfilter, $mname))
758
					{
759
						$opts['new_function_name'] = preg_replace(array('/\./', '/[^a-zA-Z0-9_\x7f-\xff]/'),
760
							array('_', ''), $mname);
761
						$methodwrap = wrap_xmlrpc_method($client, $mname, $opts);
762
						if ($methodwrap)
763
						{
764
							if (!$buildit)
765
							{
766
								$source .= $methodwrap['docstring'];
767
							}
768
							$source .= $methodwrap['source']."\n";
769
						}
770
						else
771
						{
772
							error_log('XML-RPC: will not create class method to wrap remote method '.$mname);
773
						}
774
					}
775
				}
776
				$source .= "}\n";
777
				if ($buildit)
778
				{
779
					$allOK = 0;
780
					eval($source.'$allOK=1;');
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
781
					// alternative
782
					//$xmlrpcfuncname = create_function('$m', $innercode);
783
					if($allOK)
784
					{
785
						return $xmlrpcclassname;
786
					}
787
					else
788
					{
789
						error_log('XML-RPC: could not create class '.$xmlrpcclassname.' to wrap remote server '.$client->server);
790
						return false;
791
					}
792
				}
793
				else
794
				{
795
					return array('class' => $xmlrpcclassname, 'code' => $source, 'docstring' => '');
796
				}
797
			}
798
		}
799
	}
800
801
	/**
802
	* Given the necessary info, build php code that creates a new function to
803
	* invoke a remote xmlrpc method.
804
	* Take care that no full checking of input parameters is done to ensure that
805
	* valid php code is emitted.
806
	* Note: real spaghetti code follows...
807
	* @access private
808
	*/
809
	function build_remote_method_wrapper_code($client, $methodname, $xmlrpcfuncname,
810
		$msig, $mdesc='', $timeout=0, $protocol='', $client_copy_mode=0, $prefix='xmlrpc',
811
		$decode_php_objects=false, $encode_php_objects=false, $decode_fault=false,
812
		$fault_response='')
813
	{
814
		$code = "function $xmlrpcfuncname (";
815
		if ($client_copy_mode < 2)
816
		{
817
			// client copy mode 0 or 1 == partial / full client copy in emitted code
818
			$innercode = build_client_wrapper_code($client, $client_copy_mode, $prefix);
819
			$innercode .= "\$client->setDebug(\$debug);\n";
820
			$this_ = '';
821
		}
822
		else
823
		{
824
			// client copy mode 2 == no client copy in emitted code
825
			$innercode = '';
826
			$this_ = 'this->';
827
		}
828
		$innercode .= "\$msg = new {$prefix}msg('$methodname');\n";
829
830
		if ($mdesc != '')
831
		{
832
			// take care that PHP comment is not terminated unwillingly by method description
833
			$mdesc = "/**\n* ".str_replace('*/', '* /', $mdesc)."\n";
834
		}
835
		else
836
		{
837
			$mdesc = "/**\nFunction $xmlrpcfuncname\n";
838
		}
839
840
		// param parsing
841
		$plist = array();
842
		$pcount = count($msig);
843
		for($i = 1; $i < $pcount; $i++)
844
		{
845
			$plist[] = "\$p$i";
846
			$ptype = $msig[$i];
847
			if($ptype == 'i4' || $ptype == 'int' || $ptype == 'boolean' || $ptype == 'double' ||
848
				$ptype == 'string' || $ptype == 'dateTime.iso8601' || $ptype == 'base64' || $ptype == 'null')
849
			{
850
				// only build directly xmlrpcvals when type is known and scalar
851
				$innercode .= "\$p$i = new {$prefix}val(\$p$i, '$ptype');\n";
852
			}
853
			else
854
			{
855
				if ($encode_php_objects)
856
				{
857
					$innercode .= "\$p$i =& php_{$prefix}_encode(\$p$i, array('encode_php_objs'));\n";
858
				}
859
				else
860
				{
861
					$innercode .= "\$p$i =& php_{$prefix}_encode(\$p$i);\n";
862
				}
863
			}
864
			$innercode .= "\$msg->addparam(\$p$i);\n";
865
			$mdesc .= '* @param '.xmlrpc_2_php_type($ptype)." \$p$i\n";
866
		}
867
		if ($client_copy_mode < 2)
868
		{
869
			$plist[] = '$debug=0';
870
			$mdesc .= "* @param int \$debug when 1 (or 2) will enable debugging of the underlying {$prefix} call (defaults to 0)\n";
871
		}
872
		$plist = implode(', ', $plist);
873
		$mdesc .= '* @return '.xmlrpc_2_php_type($msig[0])." (or an {$prefix}resp obj instance if call fails)\n*/\n";
874
875
		$innercode .= "\$res =& \${$this_}client->send(\$msg, $timeout, '$protocol');\n";
876
		if ($decode_fault)
877
		{
878
			if (is_string($fault_response) && ((strpos($fault_response, '%faultCode%') !== false) || (strpos($fault_response, '%faultString%') !== false)))
879
			{
880
				$respcode = "str_replace(array('%faultCode%', '%faultString%'), array(\$res->faultCode(), \$res->faultString()), '".str_replace("'", "''", $fault_response)."')";
881
			}
882
			else
883
			{
884
				$respcode = var_export($fault_response, true);
885
			}
886
		}
887
		else
888
		{
889
			$respcode = '$res';
890
		}
891
		if ($decode_php_objects)
892
		{
893
			$innercode .= "if (\$res->faultcode()) return $respcode; else return php_{$prefix}_decode(\$res->value(), array('decode_php_objs'));";
894
		}
895
		else
896
		{
897
			$innercode .= "if (\$res->faultcode()) return $respcode; else return php_{$prefix}_decode(\$res->value());";
898
		}
899
900
		$code = $code . $plist. ") {\n" . $innercode . "\n}\n";
901
902
		return array('source' => $code, 'docstring' => $mdesc);
903
	}
904
905
	/**
906
	* Given necessary info, generate php code that will rebuild a client object
907
	* Take care that no full checking of input parameters is done to ensure that
908
	* valid php code is emitted.
909
	* @access private
910
	*/
911
	function build_client_wrapper_code($client, $verbatim_client_copy, $prefix='xmlrpc')
912
	{
913
		$code = "\$client = new {$prefix}_client('".str_replace("'", "\'", $client->path).
914
			"', '" . str_replace("'", "\'", $client->server) . "', $client->port);\n";
915
916
		// copy all client fields to the client that will be generated runtime
917
		// (this provides for future expansion or subclassing of client obj)
918
		if ($verbatim_client_copy)
919
		{
920
			foreach($client as $fld => $val)
921
			{
922
				if($fld != 'debug' && $fld != 'return_type')
923
				{
924
					$val = var_export($val, true);
925
					$code .= "\$client->$fld = $val;\n";
926
				}
927
			}
928
		}
929
		// only make sure that client always returns the correct data type
930
		$code .= "\$client->return_type = '{$prefix}vals';\n";
931
		//$code .= "\$client->setDebug(\$debug);\n";
932
		return $code;
933
	}
934
?>
0 ignored issues
show
Best Practice introduced by
It is not recommended to use PHP's closing tag ?> in files other than templates.

Using a closing tag in PHP files that only contain PHP code is not recommended as you might accidentally add whitespace after the closing tag which would then be output by PHP. This can cause severe problems, for example headers cannot be sent anymore.

A simple precaution is to leave off the closing tag as it is not required, and it also has no negative effects whatsoever.

Loading history...