~chroot64bit/zivios/gentoo-experimental

« back to all changes in this revision

Viewing changes to application/library/xmlrpc/xmlrpc_wrappers.inc

  • Committer: Mustafa A. Hashmi
  • Date: 2008-12-04 13:32:21 UTC
  • Revision ID: mhashmi@zivios.org-20081204133221-0nd1trunwevijj38
Inclusion of new installation framework with ties to zend layout and dojo layout

Show diffs side-by-side

added added

removed removed

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