Subversion-Projekte lars-tiefland.php_share

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
1 lars 1
<?php
2
//
3
// +----------------------------------------------------------------------+
4
// | PHP Version 4                                                        |
5
// +----------------------------------------------------------------------+
6
// | Copyright (c) 1997-2003 The PHP Group                                |
7
// +----------------------------------------------------------------------+
8
// | This source file is subject to version 2.02 of the PHP license,      |
9
// | that is bundled with this package in the file LICENSE, and is        |
10
// | available at through the world-wide-web at                           |
11
// | http://www.php.net/license/2_02.txt.                                 |
12
// | If you did not receive a copy of the PHP license and are unable to   |
13
// | obtain it through the world-wide-web, please send a note to          |
14
// | license@php.net so we can mail you a copy immediately.               |
15
// +----------------------------------------------------------------------+
16
// | Authors: Shane Caraveo <Shane@Caraveo.com>   Port to PEAR and more   |
17
// | Authors: Dietrich Ayala <dietrich@ganx4.com> Original Author         |
18
// +----------------------------------------------------------------------+
19
//
20
// $Id: WSDL.php 696 2011-09-08 09:08:23Z tiefland $
21
//
22
require_once 'PayPal/SOAP/Base.php';
23
require_once 'PayPal/SOAP/Fault.php';
24
require_once 'HTTP/Request.php';
25
 
26
define('WSDL_CACHE_MAX_AGE', 43200);
27
define('WSDL_CACHE_USE',     0); // set to zero to turn off caching
28
 
29
/**
30
 * This class parses WSDL files, and can be used by SOAP::Client to
31
 * properly register soap values for services.
32
 *
33
 * Originally based on SOAPx4 by Dietrich Ayala
34
 * http://dietrich.ganx4.com/soapx4
35
 *
36
 * TODO:
37
 *   add wsdl caching
38
 *   refactor namespace handling ($namespace/$ns)
39
 *   implement IDL type syntax declaration so we can generate WSDL
40
 *
41
 * @access public
42
 * @package SOAP::Client
43
 * @author Shane Caraveo <shane@php.net> Conversion to PEAR and updates
44
 * @author Dietrich Ayala <dietrich@ganx4.com> Original Author
45
 */
46
class SOAP_WSDL extends SOAP_Base
47
{
48
    var $tns = null;
49
    var $definition = array();
50
    var $namespaces = array();
51
    var $ns = array();
52
    var $xsd = SOAP_XML_SCHEMA_VERSION;
53
    var $complexTypes = array();
54
    var $elements = array();
55
    var $messages = array();
56
    var $portTypes = array();
57
    var $bindings = array();
58
    var $imports = array();
59
    var $services = array();
60
    var $service = '';
61
    var $uri = '';
62
    var $docs = false;
63
 
64
    /**
65
     * Proxy parameters
66
     *
67
     * @var array
68
     */
69
    var $proxy = null;
70
 
71
    var $trace = 0;
72
 
73
    /**
74
     * Use WSDL cache
75
     *
76
     * @var boolean
77
     */
78
    var $cacheUse = null;
79
 
80
    /**
81
     * Cache max lifetime (in seconds)
82
     *
83
     * @var int
84
     */
85
    var $cacheMaxAge = null;
86
 
87
    /**
88
     * Class to use for WSDL parsing. Can be overridden for special
89
     * cases, subclasses, etc.
90
     * @var string $wsdlParserClass
91
     */
92
    var $wsdlParserClass = 'SOAP_WSDL_Parser';
93
 
94
    /**
95
     * SOAP_WSDL constructor.
96
     *
97
     * @param string  $wsdl_uri     URL to WSDL file.
98
     * @param array   $proxy        Contains options for HTTP_Request class (see HTTP/Request.php).
99
     * @param boolean $cacheUse     Use WSDL caching. Defaults to false.
100
     * @param integer $cacheMaxAge  Cache max lifetime (in seconds).
101
     * @param boolean $docs         Parse documentation in the WSDL? Defaults to false.
102
     *
103
     * @access public
104
     */
105
    function SOAP_WSDL($wsdl_uri    = false,
106
                       $proxy       = array(),
107
                       $cacheUse    = WSDL_CACHE_USE,
108
                       $cacheMaxAge = WSDL_CACHE_MAX_AGE,
109
                       $docs        = false)
110
    {
111
        parent::SOAP_Base('WSDL');
112
        $this->uri         = $wsdl_uri;
113
        $this->proxy       = $proxy;
114
        $this->cacheUse    = $cacheUse;
115
        $this->cacheMaxAge = $cacheMaxAge;
116
        $this->docs        = $docs;
117
 
118
        if ($wsdl_uri) {
119
            $this->parseURL($wsdl_uri);
120
            reset($this->services);
121
            $this->service = key($this->services);
122
        }
123
    }
124
 
125
    function set_service($service)
126
    {
127
        if (array_key_exists($service, $this->services)) {
128
            $this->service = $service;
129
        }
130
    }
131
 
132
    /**
133
     * @deprecated use parseURL instead
134
     */
135
    function parse($wsdl_uri, $proxy = array())
136
    {
137
        $this->parseURL($wsdl_uri, $proxy);
138
    }
139
 
140
    /**
141
     * Fill the WSDL array tree with data from a WSDL file
142
     *
143
     * @param  string
144
     * @param  array  proxy related parameters
145
     * @return void
146
     */
147
    function parseURL($wsdl_uri, $proxy = array())
148
    {
149
        $parser =& new $this->wsdlParserClass($wsdl_uri, $this, $this->docs);
150
 
151
        if ($parser->fault) {
152
            $this->_raiseSoapFault($parser->fault);
153
        }
154
    }
155
 
156
    /**
157
     * Fill the WSDL array tree with data from one or more PHP class objects
158
     *
159
     * @param  mixed  $wsdl_obj An object or array of objects to add to the internal WSDL tree
160
     * @param  string  $service_name Name of the WSDL <service>
161
     * @param  string  $service_desc Optional description of the WSDL <service>
162
     * @return void
163
     */
164
    function parseObject(&$wsdl_obj, $targetNamespace, $service_name, $service_desc = '')
165
    {
166
        $parser =& new SOAP_WSDL_ObjectParser($wsdl_obj, $this, $targetNamespace, $service_name, $service_desc);
167
 
168
         if ($parser->fault) {
169
             $this->_raiseSoapFault($parser->fault);
170
         }
171
    }
172
 
173
    function getEndpoint($portName)
174
    {
175
        return (isset($this->services[$this->service]['ports'][$portName]['address']['location']))
176
                ? $this->services[$this->service]['ports'][$portName]['address']['location']
177
                : $this->_raiseSoapFault("no endpoint for port for $portName", $this->uri);
178
    }
179
 
180
    function _getPortName($operation, $service)
181
    {
182
        if (isset($this->services[$service]['ports'])) {
183
            foreach ($this->services[$service]['ports'] as $port => $portAttrs) {
184
                $type = $this->services[$service]['ports'][$port]['type'];
185
                if ($type == 'soap' &&
186
                    isset($this->bindings[$portAttrs['binding']]['operations'][$operation])) {
187
                    return $port;
188
                }
189
            }
190
        }
191
        return null;
192
    }
193
 
194
    /**
195
     * find the name of the first port that contains an operation of
196
     * name $operation.  always returns a the soap portName.
197
     */
198
    function getPortName($operation, $service = null)
199
    {
200
        if (!$service) $service = $this->service;
201
        if (isset($this->services[$service]['ports'])) {
202
            $portName = $this->_getPortName($operation, $service);
203
            if ($portName) return $portName;
204
        }
205
        // Try any service in the WSDL.
206
        foreach ($this->services as $serviceName => $service) {
207
            if (isset($this->services[$serviceName]['ports'])) {
208
                $portName = $this->_getPortName($operation, $serviceName);
209
                if ($portName) {
210
                    $this->service = $serviceName;
211
                    return $portName;
212
                }
213
            }
214
        }
215
        return $this->_raiseSoapFault("no operation $operation in wsdl", $this->uri);
216
    }
217
 
218
    function getOperationData($portName, $operation)
219
    {
220
        if (isset($this->services[$this->service]['ports'][$portName]['binding'])
221
            && $binding = $this->services[$this->service]['ports'][$portName]['binding']) {
222
            // get operation data from binding
223
            if (is_array($this->bindings[$binding]['operations'][$operation])) {
224
                $opData = $this->bindings[$binding]['operations'][$operation];
225
            }
226
            // get operation data from porttype
227
            $portType = $this->bindings[$binding]['type'];
228
            if (!$portType) {
229
                return $this->_raiseSoapFault("no port type for binding $binding in wsdl " . $this->uri);
230
            }
231
            if (is_array($this->portTypes[$portType][$operation])) {
232
                if (isset($this->portTypes[$portType][$operation]['parameterOrder']))
233
                    $opData['parameterOrder'] = $this->portTypes[$portType][$operation]['parameterOrder'];
234
                $opData['input'] = array_merge($opData['input'], $this->portTypes[$portType][$operation]['input']);
235
                $opData['output'] = array_merge($opData['output'], $this->portTypes[$portType][$operation]['output']);
236
            }
237
            if (!$opData)
238
                return $this->_raiseSoapFault("no operation $operation for port $portName, in wsdl", $this->uri);
239
            $opData['parameters'] = false;
240
            if (isset($this->bindings[$this->services[$this->service]['ports'][$portName]['binding']]['operations'][$operation]['input']['namespace']))
241
                $opData['namespace'] = $this->bindings[$this->services[$this->service]['ports'][$portName]['binding']]['operations'][$operation]['input']['namespace'];
242
            // message data from messages
243
            $inputMsg = $opData['input']['message'];
244
            if (is_array($this->messages[$inputMsg])) {
245
                foreach ($this->messages[$inputMsg] as $pname => $pattrs) {
246
                    if ($opData['style'] == 'document' && $opData['input']['use'] == 'literal'
247
                        && $pname == 'parameters') {
248
                        $opData['parameters'] = true;
249
                        $opData['namespace'] = $this->namespaces[$pattrs['namespace']];
250
                        $el = $this->elements[$pattrs['namespace']][$pattrs['type']];
251
                        if (isset($el['elements'])) {
252
                            foreach ($el['elements'] as $elname => $elattrs) {
253
                                $opData['input']['parts'][$elname] = $elattrs;
254
                            }
255
                        }
256
                    } else {
257
                        $opData['input']['parts'][$pname] = $pattrs;
258
                    }
259
                }
260
            }
261
            $outputMsg = $opData['output']['message'];
262
            if (is_array($this->messages[$outputMsg])) {
263
                foreach ($this->messages[$outputMsg] as $pname => $pattrs) {
264
                    if ($opData['style'] == 'document' && $opData['output']['use'] == 'literal'
265
                        && $pname == 'parameters') {
266
 
267
                        $el = $this->elements[$pattrs['namespace']][$pattrs['type']];
268
                        if (isset($el['elements'])) {
269
                            foreach ($el['elements'] as $elname => $elattrs) {
270
                                $opData['output']['parts'][$elname] = $elattrs;
271
                            }
272
                        }
273
                    } else {
274
                        $opData['output']['parts'][$pname] = $pattrs;
275
                    }
276
                }
277
            }
278
            return $opData;
279
        }
280
        return $this->_raiseSoapFault("no binding for port $portName in wsdl", $this->uri);
281
    }
282
 
283
    function matchMethod(&$operation)
284
    {
285
        // Overloading lowercases function names :(
286
        foreach ($this->services[$this->service]['ports'] as $port => $portAttrs) {
287
            foreach (array_keys($this->bindings[$portAttrs['binding']]['operations']) as $op) {
288
                if (strcasecmp($op, $operation) == 0) {
289
                    $operation = $op;
290
                }
291
            }
292
        }
293
    }
294
 
295
    /**
296
     * getDataHandler
297
     *
298
     * Given a datatype, what function handles the processing?
299
     * this is used for doc/literal requests where we receive
300
     * a datatype, and we need to pass it to a method in out
301
     * server class
302
     *
303
     * @param string datatype
304
     * @param string namespace
305
     * @returns string methodname
306
     * @access public
307
     */
308
    function getDataHandler($datatype, $namespace)
309
    {
310
        // see if we have an element by this name
311
        if (isset($this->namespaces[$namespace]))
312
            $namespace = $this->namespaces[$namespace];
313
        if (isset($this->ns[$namespace])) {
314
            $nsp = $this->ns[$namespace];
315
            //if (!isset($this->elements[$nsp]))
316
            //    $nsp = $this->namespaces[$nsp];
317
            if (isset($this->elements[$nsp][$datatype])) {
318
                $checkmessages = array();
319
                // find what messages use this datatype
320
                foreach ($this->messages as $messagename => $message) {
321
                    foreach ($message as $partname => $part) {
322
                        if ($part['type'] == $datatype) {
323
                            $checkmessages[] = $messagename;
324
                            break;
325
                        }
326
                    }
327
                }
328
                // find the operation that uses this message
329
                $dataHandler = null;
330
                foreach($this->portTypes as $portname => $porttype) {
331
                    foreach ($porttype as $opname => $opinfo) {
332
                        foreach ($checkmessages as $messagename) {
333
                            if ($opinfo['input']['message'] == $messagename) {
334
                                return $opname;
335
                            }
336
                        }
337
                    }
338
                }
339
            }
340
        }
341
        return null;
342
    }
343
 
344
    function getSoapAction($portName, $operation)
345
    {
346
        if (isset($this->bindings[$this->services[$this->service]['ports'][$portName]['binding']]['operations'][$operation]['soapAction']) &&
347
            $soapAction = $this->bindings[$this->services[$this->service]['ports'][$portName]['binding']]['operations'][$operation]['soapAction']) {
348
            return $soapAction;
349
        }
350
        return false;
351
    }
352
 
353
    function getNamespace($portName, $operation)
354
    {
355
        if (isset($this->bindings[$this->services[$this->service]['ports'][$portName]['binding']]) &&
356
            isset($this->bindings[$this->services[$this->service]['ports'][$portName]['binding']]['operations'][$operation]['input']['namespace']) &&
357
            $namespace = $this->bindings[$this->services[$this->service]['ports'][$portName]['binding']]['operations'][$operation]['input']['namespace']) {
358
            return $namespace;
359
        }
360
        return false;
361
    }
362
 
363
    function getNamespaceAttributeName($namespace)
364
    {
365
        /* If it doesn't exist at first, flip the array and check
366
         * again. */
367
        if (empty($this->ns[$namespace])) {
368
            $this->ns = array_flip($this->namespaces);
369
        }
370
        /* If it doesn't exist now, add it. */
371
        if (empty($this->ns[$namespace])) {
372
            return $this->addNamespace($namespace);
373
        }
374
 
375
        return $this->ns[$namespace];
376
    }
377
 
378
    function addNamespace($namespace)
379
    {
380
        if (!empty($this->ns[$namespace])) {
381
            return $this->ns[$namespace];
382
        }
383
 
384
        $n = count($this->ns);
385
        $attr = 'ns' . $n;
386
        $this->namespaces['ns' . $n] = $namespace;
387
        $this->ns[$namespace] = $attr;
388
        return $attr;
389
    }
390
 
391
    function _validateString($string)
392
    {
393
        // XXX this should be done sooner or later
394
        // return true;
395
        return preg_match("/^[\w_:#\/]+$/", $string);
396
    }
397
 
398
    function _addArg(&$args, &$argarray, $argname)
399
    {
400
        if ($args) {
401
            $args .= ', ';
402
        }
403
        $args .= "\$" . $argname;
404
        if (!$this->_validateString($argname)) {
405
            return null;
406
        }
407
        if ($argarray) {
408
            $argarray .= ', ';
409
        }
410
        $argarray .= "\"$argname\" => \$" . $argname;
411
    }
412
 
413
    function _elementArg(&$args, &$argarray, &$_argtype, $_argname)
414
    {
415
        $comments = '';
416
        $el = $this->elements[$_argtype['namespace']][$_argtype['type']];
417
        $tns = isset($this->ns[$el['namespace']]) ? $this->ns[$el['namespace']] : $_argtype['namespace'];
418
 
419
        if (!empty($el['complex']) || (isset($el['type']) && isset($this->complexTypes[$tns][$el['type']]))) {
420
            // The element is a complex type.
421
            $comments .= "        // {$_argtype['type']} is a ComplexType, refer to the WSDL for more info.\n";
422
            $attrname = "{$_argtype['type']}_attr";
423
            if (isset($el['type']) && isset($this->complexTypes[$tns][$el['type']]['attribute'])) {
424
                $comments .= "        // {$_argtype['type']} may require attributes, refer to the WSDL for more info.\n";
425
            }
426
            $comments .= "        \${$attrname}['xmlns'] = '{$this->namespaces[$_argtype['namespace']]}';\n";
427
            $comments .= "        \${$_argtype['type']} =& new SOAP_Value('{$_argtype['type']}', false, \${$_argtype['type']}, \$$attrname);\n";
428
            $this->_addArg($args, $argarray, $_argtype['type']);
429
            if (isset($el['type']) && isset($this->complexTypes[$tns][$el['type']]['attribute'])) {
430
                if ($args) {
431
                    $args .= ', ';
432
                }
433
                $args .= '$' . $attrname;
434
            }
435
        } elseif (isset($el['elements'])) {
436
            foreach ($el['elements'] as $ename => $element) {
437
                $comments .= "        \$$ename =& new SOAP_Value('{{$this->namespaces[$element['namespace']]}}$ename', '" .
438
                    (isset($element['type']) ? $element['type'] : false) . "', \$$ename);\n";
439
                $this->_addArg($args, $argarray, $ename);
440
            }
441
        } else {
442
            $comments .= "        \$$_argname =& new SOAP_Value('{{$this->namespaces[$tns]}}$_argname', '{$el['type']}', \$$_argname);\n";
443
            $this->_addArg($args, $argarray, $_argname);
444
        }
445
 
446
        return $comments;
447
    }
448
 
449
    function _complexTypeArg(&$args, &$argarray, &$_argtype, $_argname)
450
    {
451
        $comments = '';
452
        if (isset($this->complexTypes[$_argtype['namespace']][$_argtype['type']])) {
453
            $comments  = "        // $_argname is a ComplexType {$_argtype['type']},\n";
454
            $comments .= "        // refer to wsdl for more info\n";
455
            if (isset($this->complexTypes[$_argtype['namespace']][$_argtype['type']]['attribute'])) {
456
                $comments .= "        // $_argname may require attributes, refer to wsdl for more info\n";
457
            }
458
            $wrapname = '{' . $this->namespaces[$_argtype['namespace']].'}' . $_argtype['type'];
459
            $comments .= "        \$$_argname =& new SOAP_Value('$_argname', '$wrapname', \$$_argname);\n";
460
        }
461
 
462
        $this->_addArg($args, $argarray, $_argname);
463
        return $comments;
464
    }
465
 
466
    /**
467
     * Generates stub code from the WSDL that can be saved to a file
468
     * or eval'd into existence.
469
     */
470
    function generateProxyCode($port = '', $classname='')
471
    {
472
        $multiport = count($this->services[$this->service]['ports']) > 1;
473
        if (!$port) {
474
            reset($this->services[$this->service]['ports']);
475
            $port = current($this->services[$this->service]['ports']);
476
        }
477
        // XXX currently do not support HTTP ports
478
        if ($port['type'] != 'soap') {
479
            return null;
480
        }
481
 
482
        // XXX currentPort is BAD
483
        $clienturl = $port['address']['location'];
484
        if (!$classname) {
485
            if ($multiport || $port) {
486
                $classname = 'WebService_' . $this->service . '_' . $port['name'];
487
            } else {
488
                $classname = 'WebService_' . $this->service;
489
            }
490
            $classname = preg_replace('/[ .\-\(\)]+/', '_', $classname);
491
        }
492
 
493
        if (!$this->_validateString($classname)) {
494
            return null;
495
        }
496
 
497
        if (is_array($this->proxy) && count($this->proxy)) {
498
            $class = "class $classname extends SOAP_Client\n{\n" .
499
            "    function $classname(\$path = \"$clienturl\")\n{\n" .
500
            "        \$this->SOAP_Client(\$path, 0, 0,
501
                    array(";
502
 
503
            foreach ($this->proxy as $key => $val) {
504
                if (is_array($val)) {
505
                    $class .= "\"$key\" => array(";
506
                    foreach ($val as $key2 => $val2) {
507
                        $class .= "\"$key2\" => \"$val2\",";
508
                    }
509
                    $class .= ')';
510
                } else {
511
                    $class .= "\"$key\" => \"$val\",";
512
                }
513
            }
514
            $class .= "));\n }\n";
515
            $class = str_replace(',))', '))', $class);
516
        } else {
517
            $class = "class $classname extends SOAP_Client\n{\n" .
518
            "    function $classname(\$path = \"$clienturl\")\n    {\n" .
519
            "        \$this->SOAP_Client(\$path, 0);\n" .
520
            "    }\n";
521
        }
522
 
523
        // get the binding, from that get the port type
524
        $primaryBinding = $port['binding']; //$this->services[$this->service]['ports'][$port['name']]["binding"];
525
        $primaryBinding = preg_replace("/^(.*:)/", '', $primaryBinding);
526
        $portType = $this->bindings[$primaryBinding]['type'];
527
        $portType = preg_replace("/^(.*:)/", '', $portType);
528
        $style = $this->bindings[$primaryBinding]['style'];
529
 
530
        // XXX currentPortType is BAD
531
        foreach ($this->portTypes[$portType] as $opname => $operation) {
532
            $soapaction = isset($this->bindings[$primaryBinding]['operations'][$opname]['soapAction']) ?
533
                $this->bindings[$primaryBinding]['operations'][$opname]['soapAction'] :
534
                null;
535
            if (isset($this->bindings[$primaryBinding]['operations'][$opname]['style'])) {
536
                $opstyle = $this->bindings[$primaryBinding]['operations'][$opname]['style'];
537
            } else {
538
                $opstyle = $style;
539
            }
540
            $use = $this->bindings[$primaryBinding]['operations'][$opname]['input']['use'];
541
            if ($use == 'encoded') {
542
                $namespace = $this->bindings[$primaryBinding]['operations'][$opname]['input']['namespace'];
543
            } else {
544
                $bindingType = $this->bindings[$primaryBinding]['type'];
545
                $ns = $this->portTypes[$bindingType][$opname]['input']['namespace'];
546
                $namespace = $this->namespaces[$ns];
547
            }
548
 
549
            $args = '';
550
            $argarray = '';
551
            $comments = '';
552
            $wrappers = '';
553
            foreach ($operation['input'] as $argname => $argtype) {
554
                if ($argname == 'message') {
555
                    foreach ($this->messages[$argtype] as $_argname => $_argtype) {
556
                        if ($opstyle == 'document' && $use == 'literal' &&
557
                            $_argtype['name'] == 'parameters') {
558
                            // The type or element refered to is used
559
                            // for parameters.
560
                            $elattrs = null;
561
                            $element = $_argtype['element'];
562
                            $el = $this->elements[$_argtype['namespace']][$_argtype['type']];
563
 
564
                            if ($el['complex']) {
565
                                $namespace = $this->namespaces[$_argtype['namespace']];
566
                                // XXX need to wrap the parameters in
567
                                // a soap_value.
568
                            }
569
                            if (isset($el['elements'])) {
570
                                foreach ($el['elements'] as $elname => $elattrs) {
571
                                    // Is the element a complex type?
572
                                    if (isset($this->complexTypes[$elattrs['namespace']][$elname])) {
573
                                        $comments .= $this->_complexTypeArg($args, $argarray, $_argtype, $_argname);
574
                                    } else {
575
                                        $this->_addArg($args, $argarray, $elname);
576
                                    }
577
                                }
578
                            }
579
                            if ($el['complex'] && $argarray) {
580
                                $wrapname = '{' . $this->namespaces[$_argtype['namespace']].'}' . $el['name'];
581
                                $comments .= "        \${$el['name']} =& new SOAP_Value('$wrapname', false, \$v = array($argarray));\n";
582
                                $argarray = "'{$el['name']}' => \${$el['name']}";
583
                            }
584
                        } else {
585
                            if (isset($_argtype['element'])) {
586
                                // Element argument.
587
                                $comments .= $this->_elementArg($args, $argarray, $_argtype, $_argtype['type']);
588
                            } else {
589
                                // Complex type argument.
590
                                $comments .= $this->_complexTypeArg($args, $argarray, $_argtype, $_argname);
591
                            }
592
                        }
593
                    }
594
                }
595
            }
596
 
597
            // Validate entries.
598
 
599
            // Operation names are function names, so try to make sure
600
            // it's legal. This could potentially cause collisions,
601
            // but let's try to make everything callable and see how
602
            // many problems that causes.
603
            $opname_php = preg_replace('/[ .\-\(\)]+/', '_', $opname);
604
            if (!$this->_validateString($opname_php)) {
605
                return null;
606
            }
607
 
608
            if ($argarray) {
609
                $argarray = "array($argarray)";
610
            } else {
611
                $argarray = 'null';
612
            }
613
 
614
            $class .= "    function &$opname_php($args)\n    {\n$comments$wrappers" .
615
                "        return \$this->call('$opname',\n" .
616
                "                           \$v = $argarray,\n" .
617
                "                           array('namespace' => '$namespace',\n" .
618
                "                                 'soapaction' => '$soapaction',\n" .
619
                "                                 'style' => '$opstyle',\n" .
620
                "                                 'use' => '$use'" .
621
                ($this->trace?",\n                                 'trace' => 1" : '') . "));\n" .
622
                "    }\n";
623
        }
624
 
625
        $class .= "}\n";
626
        return $class;
627
    }
628
 
629
    function generateAllProxies()
630
    {
631
        $proxycode = '';
632
        foreach (array_keys($this->services[$this->service]['ports']) as $key) {
633
            $port =& $this->services[$this->service]['ports'][$key];
634
            $proxycode .= $this->generateProxyCode($port);
635
        }
636
        return $proxycode;
637
    }
638
 
639
    function &getProxy($port = '', $name = '')
640
    {
641
        $multiport = count($this->services[$this->service]['ports']) > 1;
642
 
643
        if (!$port) {
644
            reset($this->services[$this->service]['ports']);
645
            $port = current($this->services[$this->service]['ports']);
646
        }
647
 
648
        if ($multiport || $port) {
649
            $classname = 'WebService_' . $this->service . '_' . $port['name'];
650
        } else {
651
            $classname = 'WebService_' . $this->service;
652
        }
653
 
654
        if ($name) {
655
            $classname = $name . '_' . $classname;
656
        }
657
 
658
        $classname = preg_replace('/[ .\-\(\)]+/', '_', $classname);
659
        if (!class_exists($classname)) {
660
            $proxy = $this->generateProxyCode($port, $classname);
661
            require_once 'PayPal/SOAP/Client.php';
662
            eval($proxy);
663
        }
664
 
665
        return new $classname;
666
    }
667
 
668
    function &_getComplexTypeForElement($name, $namespace)
669
    {
670
        $t = null;
671
        if (isset($this->ns[$namespace]) &&
672
            isset($this->elements[$this->ns[$namespace]][$name]['type'])) {
673
 
674
            $type = $this->elements[$this->ns[$namespace]][$name]['type'];
675
            $ns = $this->elements[$this->ns[$namespace]][$name]['namespace'];
676
 
677
            if (isset($this->complexTypes[$ns][$type])) {
678
                $t = $this->complexTypes[$ns][$type];
679
            }
680
        }
681
        return $t;
682
    }
683
 
684
    function getComplexTypeNameForElement($name, $namespace)
685
    {
686
        $t = $this->_getComplexTypeForElement($name, $namespace);
687
        if ($t) {
688
            return $t['name'];
689
        }
690
        return null;
691
    }
692
 
693
    function getComplexTypeChildType($ns, $name, $child_ns, $child_name)
694
    {
695
        // is the type an element?
696
        $t = $this->_getComplexTypeForElement($name, $ns);
697
        if ($t) {
698
            // no, get it from complex types directly
699
            if (isset($t['elements'][$child_name]['type']))
700
                return $t['elements'][$child_name]['type'];
701
        }
702
        return null;
703
    }
704
 
705
    function getSchemaType($type, $name, $type_namespace)
706
    {
707
        // see if it's a complex type so we can deal properly with
708
        // SOAPENC:arrayType.
709
        if ($name && $type) {
710
            // XXX TODO:
711
            // look up the name in the wsdl and validate the type.
712
            foreach ($this->complexTypes as $ns => $types) {
713
                if (array_key_exists($type, $types)) {
714
                    if (array_key_exists('type', $types[$type])) {
715
                        list($arraytype_ns, $arraytype, $array_depth) = isset($types[$type]['arrayType'])?
716
                            $this->_getDeepestArrayType($types[$type]['namespace'], $types[$type]['arrayType'])
717
                            : array($this->namespaces[$types[$type]['namespace']], null, 0);
718
                        return array($types[$type]['type'], $arraytype, $arraytype_ns, $array_depth);
719
                    }
720
                    if (array_key_exists('arrayType', $types[$type])) {
721
                        list($arraytype_ns, $arraytype, $array_depth) =
722
                            $this->_getDeepestArrayType($types[$type]['namespace'], $types[$type]['arrayType']);
723
                        return array('Array', $arraytype, $arraytype_ns, $array_depth);
724
                    }
725
                    if (array_key_exists('elements', $types[$type]) &&
726
                        array_key_exists($name, $types[$type]['elements'])) {
727
                        $type = $types[$type]['elements']['type'];
728
                        return array($type, null, $this->namespaces[$types[$type]['namespace']], null);
729
                    }
730
                }
731
            }
732
        }
733
        if ($type && $type_namespace) {
734
            $arrayType = null;
735
            // XXX TODO:
736
            // this code currently handles only one way of encoding array types in wsdl
737
            // need to do a generalized function to figure out complex types
738
            $p = $this->ns[$type_namespace];
739
            if ($p &&
740
                array_key_exists($p, $this->complexTypes) &&
741
                array_key_exists($type, $this->complexTypes[$p])) {
742
                if ($arrayType = $this->complexTypes[$p][$type]['arrayType']) {
743
                    $type = 'Array';
744
                } elseif ($this->complexTypes[$p][$type]['order']=='sequence' &&
745
                          array_key_exists('elements', $this->complexTypes[$p][$type])) {
746
                    reset($this->complexTypes[$p][$type]['elements']);
747
                    // assume an array
748
                    if (count($this->complexTypes[$p][$type]['elements']) == 1) {
749
                        $arg = current($this->complexTypes[$p][$type]['elements']);
750
                        $arrayType = $arg['type'];
751
                        $type = 'Array';
752
                    } else {
753
                        foreach ($this->complexTypes[$p][$type]['elements'] as $element) {
754
                            if ($element['name'] == $type) {
755
                                $arrayType = $element['type'];
756
                                $type = $element['type'];
757
                            }
758
                        }
759
                    }
760
                } else {
761
                    $type = 'Struct';
762
                }
763
                return array($type, $arrayType, $type_namespace, null);
764
            }
765
        }
766
        return array(null, null, null, null);
767
    }
768
 
769
    /**
770
     * Recurse through the WSDL structure looking for the innermost
771
     * array type of multi-dimensional arrays.
772
     *
773
     * Takes a namespace prefix and a type, which can be in the form
774
     * 'type' or 'type[]', and returns the full namespace URI, the
775
     * type of the most deeply nested array type found, and the number
776
     * of levels of nesting.
777
     *
778
     * @access private
779
     * @return mixed array or nothing
780
     */
781
    function _getDeepestArrayType($nsPrefix, $arrayType)
782
    {
783
        static $trail = array();
784
 
785
        $arrayType = ereg_replace('\[\]$', '', $arrayType);
786
 
787
        // Protect against circular references
788
        // XXX We really need to remove trail from this altogether (it's very inefficient and
789
        // in the wrong place!) and put circular reference checking in when the WSDL info
790
        // is generated in the first place
791
        if (array_search($nsPrefix . ':' . $arrayType, $trail)) {
792
            return array(null, null, -count($trail));
793
        }
794
 
795
        if (array_key_exists($nsPrefix, $this->complexTypes) &&
796
            array_key_exists($arrayType, $this->complexTypes[$nsPrefix]) &&
797
            array_key_exists('arrayType', $this->complexTypes[$nsPrefix][$arrayType])) {
798
            $trail[] = $nsPrefix . ':' . $arrayType;
799
            $result = $this->_getDeepestArrayType($this->complexTypes[$nsPrefix][$arrayType]['namespace'],
800
                                                  $this->complexTypes[$nsPrefix][$arrayType]['arrayType']);
801
            return array($result[0], $result[1], $result[2] + 1);
802
        }
803
        return array($this->namespaces[$nsPrefix], $arrayType, 0);
804
    }
805
 
806
}
807
 
808
class SOAP_WSDL_Cache extends SOAP_Base
809
{
810
    // Cache settings
811
 
812
    /**
813
     * Use WSDL cache
814
     *
815
     * @var boolean
816
     */
817
    var $_cacheUse = null;
818
 
819
    /**
820
     * Cache max lifetime (in seconds)
821
     *
822
     * @var int
823
     */
824
    var $_cacheMaxAge = null;
825
 
826
    /**
827
     * SOAP_WSDL_Cache constructor
828
     *
829
     * @param  boolean use caching
830
     * @param  int     cache max lifetime (in seconds)
831
     * @access public
832
     */
833
    function SOAP_WSDL_Cache($cacheUse = WSDL_CACHE_USE,
834
                             $cacheMaxAge = WSDL_CACHE_MAX_AGE)
835
    {
836
        parent::SOAP_Base('WSDLCACHE');
837
        $this->_cacheUse = $cacheUse;
838
        $this->_cacheMaxAge = $cacheMaxAge;
839
    }
840
 
841
    /**
842
     * _cacheDir
843
     * return the path to the cache, if it doesn't exist, make it
844
     */
845
    function _cacheDir()
846
    {
847
        $dir = getenv("WSDLCACHE");
848
        if (!$dir) $dir = " ./wsdlcache";
849
        @mkdir($dir, 0700);
850
        return $dir;
851
    }
852
 
853
    /**
854
     * Retrieves a file from cache if it exists, otherwise retreive from net,
855
     * add to cache, and return from cache.
856
     *
857
     * @param  string   URL to WSDL
858
     * @param  array    proxy parameters
859
     * @param  int      expected MD5 of WSDL URL
860
     * @access public
861
     * @return string  data
862
     */
863
    function get($wsdl_fname, $proxy_params = array(), $cache = 0)
864
    {
865
        $cachename = $md5_wsdl = $file_data = '';
866
        if ($this->_cacheUse) {
867
            // Try to retrieve WSDL from cache
868
            $cachename = SOAP_WSDL_Cache::_cacheDir() . '/' . md5($wsdl_fname). ' .wsdl';
869
            if (file_exists($cachename)) {
870
                $wf = fopen($cachename, 'rb');
871
                if ($wf) {
872
                    // Reading cached file
873
                    $file_data = fread($wf, filesize($cachename));
874
                    $md5_wsdl = md5($file_data);
875
                    fclose($wf);
876
                }
877
                if ($cache) {
878
                    if ($cache != $md5_wsdl) {
879
                        return $this->_raiseSoapFault('WSDL Checksum error!', $wsdl_fname);
880
                    }
881
                } else {
882
                    $fi = stat($cachename);
883
                    $cache_mtime = $fi[8];
884
                    //print cache_mtime, time()
885
                    if ($cache_mtime + $this->_cacheMaxAge < time()) {
886
                        // expired
887
                        $md5_wsdl = ''; // refetch
888
                    }
889
                }
890
            }
891
        }
892
 
893
        if (!$md5_wsdl) {
894
            // Not cached or not using cache. Retrieve WSDL from URL
895
 
896
            // is it a local file?
897
            // this section should be replace by curl at some point
898
            if (!preg_match('/^(https?|file):\/\//', $wsdl_fname)) {
899
                if (!file_exists($wsdl_fname)) {
900
                    return $this->_raiseSoapFault("Unable to read local WSDL $wsdl_fname", $wsdl_fname);
901
                }
902
                if (function_exists('file_get_contents')) {
903
                    $file_data = file_get_contents($wsdl_fname);
904
                } else {
905
                    $file_data = implode('',file($wsdl_fname));
906
                }
907
            } else {
908
                $uri = explode('?', $wsdl_fname);
909
                $rq =& new HTTP_Request($uri[0], $proxy_params);
910
                // the user agent HTTP_Request uses fouls things up
911
                if (isset($uri[1])) {
912
                    $rq->addRawQueryString($uri[1]);
913
                }
914
 
915
                if (isset($proxy_params['proxy_host']) &&
916
                    isset($proxy_params['proxy_port']) &&
917
                    isset($proxy_params['proxy_user']) &&
918
                    isset($proxy_params['proxy_pass'])) {
919
                    $rq->setProxy($proxy_params['proxy_host'], $proxy_params['proxy_port'],
920
                                  $proxy_params['proxy_user'], $proxy_params['proxy_pass']);
921
                } elseif (isset($proxy_params['proxy_host']) &&
922
                          isset($proxy_params['proxy_port'])) {
923
                    $rq->setProxy($proxy_params['proxy_host'], $proxy_params['proxy_port']);
924
                }
925
 
926
                $result = $rq->sendRequest();
927
                if (PEAR::isError($result)) {
928
                    return $this->_raiseSoapFault("Unable to retrieve WSDL $wsdl_fname," . $rq->getResponseCode(), $wsdl_fname);
929
                }
930
                $file_data = $rq->getResponseBody();
931
                if (!$file_data) {
932
                    return $this->_raiseSoapFault("Unable to retrieve WSDL $wsdl_fname, no http body", $wsdl_fname);
933
                }
934
            }
935
 
936
            $md5_wsdl = md5($file_data);
937
 
938
            if ($this->_cacheUse) {
939
                $fp = fopen($cachename, "wb");
940
                fwrite($fp, $file_data);
941
                fclose($fp);
942
            }
943
        }
944
        if ($this->_cacheUse && $cache && $cache != $md5_wsdl) {
945
            return $this->_raiseSoapFault("WSDL Checksum error!", $wsdl_fname);
946
        }
947
        return $file_data;
948
    }
949
 
950
}
951
 
952
class SOAP_WSDL_Parser extends SOAP_Base
953
{
954
 
955
    /**
956
     * Define internal arrays of bindings, ports, operations,
957
     * messages, etc.
958
     */
959
    var $currentMessage;
960
    var $currentOperation;
961
    var $currentPortType;
962
    var $currentBinding;
963
    var $currentPort;
964
 
965
    /**
966
     * Parser vars.
967
     */
968
    var $cache;
969
 
970
    var $tns = null;
971
    var $soapns = array('soap');
972
    var $uri = '';
973
    var $wsdl = null;
974
 
975
    var $status = '';
976
    var $element_stack = array();
977
    var $parentElement = '';
978
 
979
    var $schema = '';
980
    var $schemaStatus = '';
981
    var $schema_stack = array();
982
    var $currentComplexType;
983
    var $schema_element_stack = array();
984
    var $currentElement;
985
 
986
    /**
987
     * constructor
988
     */
989
    function SOAP_WSDL_Parser($uri, &$wsdl, $docs = false)
990
    {
991
        parent::SOAP_Base('WSDLPARSER');
992
        $this->cache =& new SOAP_WSDL_Cache($wsdl->cacheUse, $wsdl->cacheMaxAge);
993
        $this->uri = $uri;
994
        $this->wsdl = &$wsdl;
995
        $this->docs = $docs;
996
        $this->parse($uri);
997
    }
998
 
999
    function parse($uri)
1000
    {
1001
        // Check whether content has been read.
1002
        $fd = $this->cache->get($uri, $this->wsdl->proxy);
1003
        if (PEAR::isError($fd)) {
1004
            return $this->_raiseSoapFault($fd);
1005
        }
1006
 
1007
        // Create an XML parser.
1008
        $parser = xml_parser_create();
1009
        xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
1010
        xml_set_object($parser, $this);
1011
        xml_set_element_handler($parser, 'startElement', 'endElement');
1012
        if ($this->docs) {
1013
            xml_set_character_data_handler($parser, 'characterData');
1014
        }
1015
 
1016
        if (!xml_parse($parser, $fd, true)) {
1017
            $detail = sprintf('XML error on line %d: %s',
1018
                              xml_get_current_line_number($parser),
1019
                              xml_error_string(xml_get_error_code($parser)));
1020
            return $this->_raiseSoapFault("Unable to parse WSDL file $uri\n$detail");
1021
        }
1022
        xml_parser_free($parser);
1023
        return true;
1024
    }
1025
 
1026
    /**
1027
     * start-element handler
1028
     */
1029
    function startElement($parser, $name, $attrs)
1030
    {
1031
        // Get element prefix.
1032
        $qname =& new QName($name);
1033
        if ($qname->ns) {
1034
            $ns = $qname->ns;
1035
            if ($ns && ((!$this->tns && strcasecmp($qname->name, 'definitions') == 0) || $ns == $this->tns)) {
1036
                $name = $qname->name;
1037
            }
1038
        }
1039
        $this->currentTag = $qname->name;
1040
        $this->parentElement = '';
1041
        $stack_size = count($this->element_stack);
1042
        if ($stack_size) {
1043
            $this->parentElement = $this->element_stack[$stack_size - 1];
1044
        }
1045
        $this->element_stack[] = $this->currentTag;
1046
 
1047
        // Find status, register data.
1048
        switch ($this->status) {
1049
        case 'types':
1050
            // sect 2.2 wsdl:types
1051
            // children: xsd:schema
1052
            $parent_tag = '';
1053
            $stack_size = count($this->schema_stack);
1054
            if ($stack_size) {
1055
                $parent_tag = $this->schema_stack[$stack_size - 1];
1056
            }
1057
 
1058
            switch ($qname->name) {
1059
            case 'schema':
1060
                // No parent should be in the stack.
1061
                if (!$parent_tag || $parent_tag == 'types') {
1062
                    if (array_key_exists('targetNamespace', $attrs)) {
1063
                        $this->schema = $this->wsdl->getNamespaceAttributeName($attrs['targetNamespace']);
1064
                    } else {
1065
                        $this->schema = $this->wsdl->getNamespaceAttributeName($this->wsdl->tns);
1066
                    }
1067
                    $this->wsdl->complexTypes[$this->schema] = array();
1068
                    $this->wsdl->elements[$this->schema] = array();
1069
                }
1070
                break;
1071
 
1072
            case 'complexType':
1073
                if ($parent_tag == 'schema') {
1074
                    $this->currentComplexType = $attrs['name'];
1075
                    if (!isset($attrs['namespace'])) {
1076
                        $attrs['namespace'] = $this->schema;
1077
                    }
1078
                    $this->wsdl->complexTypes[$this->schema][$this->currentComplexType] = $attrs;
1079
                    if (array_key_exists('base', $attrs)) {
1080
                        $qn =& new QName($attrs['base']);
1081
                        $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['type'] = $qn->name;
1082
                        $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['namespace'] = $qn->ns;
1083
                    } else {
1084
                        $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['type'] = 'Struct';
1085
                    }
1086
                    $this->schemaStatus = 'complexType';
1087
                } else {
1088
                    $this->wsdl->elements[$this->schema][$this->currentElement]['complex'] = true;
1089
                }
1090
                break;
1091
 
1092
            case 'element':
1093
                if (isset($attrs['type'])) {
1094
                    $qn =& new QName($attrs['type']);
1095
                    $attrs['type'] = $qn->name;
1096
                    if ($qn->ns && array_key_exists($qn->ns, $this->wsdl->namespaces)) {
1097
                        $attrs['namespace'] = $qn->ns;
1098
                    }
1099
                }
1100
 
1101
                $parentElement = '';
1102
                $stack_size = count($this->schema_element_stack);
1103
                if ($stack_size > 0) {
1104
                    $parentElement = $this->schema_element_stack[$stack_size - 1];
1105
                }
1106
 
1107
                if (isset($attrs['ref'])) {
1108
                    $qn =& new QName($attrs['ref']);
1109
                    $this->currentElement = $qn->name;
1110
                } else {
1111
                    $this->currentElement = $attrs['name'];
1112
                }
1113
                $this->schema_element_stack[] = $this->currentElement;
1114
                if (!isset($attrs['namespace'])) {
1115
                    $attrs['namespace'] = $this->schema;
1116
                }
1117
 
1118
                if ($parent_tag == 'schema') {
1119
                    $this->wsdl->elements[$this->schema][$this->currentElement] = $attrs;
1120
                    $this->wsdl->elements[$this->schema][$this->currentElement]['complex'] = false;
1121
                    $this->schemaStatus = 'element';
1122
                } elseif ($this->currentComplexType) {
1123
                    // we're inside a complexType
1124
                    if ((isset($this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['order']) &&
1125
                         $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['order'] == 'sequence')
1126
                        && $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['type'] == 'Array') {
1127
                        $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['arrayType'] = isset($attrs['type']) ? $attrs['type'] : null;
1128
                    }
1129
                    $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['elements'][$this->currentElement] = $attrs;
1130
                } else {
1131
                    $this->wsdl->elements[$this->schema][$parentElement]['elements'][$this->currentElement] = $attrs;
1132
                }
1133
                break;
1134
 
1135
            case 'complexContent':
1136
            case 'simpleContent':
1137
                break;
1138
 
1139
            case 'extension':
1140
            case 'restriction':
1141
                if ($this->schemaStatus == 'complexType') {
1142
                    if (!empty($attrs['base'])) {
1143
                        $qn =& new QName($attrs['base']);
1144
                        $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['type'] = $qn->name;
1145
 
1146
                        // Types that extend from other types aren't
1147
                        // *of* those types. Reflect this by denoting
1148
                        // which type they extend. I'm leaving the
1149
                        // 'type' setting here since I'm not sure what
1150
                        // removing it might break at the moment.
1151
                        if ($qname->name == 'extension') {
1152
                            $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['extends'] = $qn->name;
1153
                        }
1154
                    } else {
1155
                        $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['type'] = 'Struct';
1156
                    }
1157
                }
1158
                break;
1159
 
1160
            case 'sequence':
1161
                if ($this->schemaStatus == 'complexType') {
1162
                    $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['order'] = $qname->name;
1163
                    if (!isset($this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['type'])) {
1164
                        $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['type'] = 'Array';
1165
                    }
1166
                }
1167
                break;
1168
 
1169
            case 'all':
1170
                $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['order'] = $qname->name;
1171
                if (!isset($this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['type'])) {
1172
                    $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['type'] = 'Struct';
1173
                }
1174
                break;
1175
 
1176
            case 'choice':
1177
                $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['order'] = $qname->name;
1178
                if (!isset($this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['type'])) {
1179
                    $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['type'] = 'Array';
1180
                }
1181
 
1182
            case 'attribute':
1183
                if ($this->schemaStatus == 'complexType') {
1184
                    if (isset($attrs['name'])) {
1185
                        $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['attribute'][$attrs['name']] = $attrs;
1186
                    } else {
1187
                        if (isset($attrs['ref'])) {
1188
                            $q =& new QName($attrs['ref']);
1189
                            foreach ($attrs as $k => $v) {
1190
                                if ($k != 'ref' && strstr($k, $q->name)) {
1191
                                    $vq =& new QName($v);
1192
                                    if ($q->name == 'arrayType') {
1193
                                        $this->wsdl->complexTypes[$this->schema][$this->currentComplexType][$q->name] = $vq->name. $vq->arrayInfo;
1194
                                        $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['type'] = 'Array';
1195
                                        $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['namespace'] = $vq->ns;
1196
                                    } else {
1197
                                        $this->wsdl->complexTypes[$this->schema][$this->currentComplexType][$q->name] = $vq->name;
1198
                                    }
1199
                                }
1200
                            }
1201
                        }
1202
                    }
1203
                }
1204
                break;
1205
            }
1206
 
1207
            $this->schema_stack[] = $qname->name;
1208
            break;
1209
 
1210
        case 'message':
1211
            // sect 2.3 wsdl:message child wsdl:part
1212
            switch ($qname->name) {
1213
            case 'part':
1214
                $qn = null;
1215
                if (isset($attrs['type'])) {
1216
                    $qn =& new QName($attrs['type']);
1217
                } elseif (isset($attrs['element'])) {
1218
                    $qn =& new QName($attrs['element']);
1219
                }
1220
                if ($qn) {
1221
                    $attrs['type'] = $qn->name;
1222
                    $attrs['namespace'] = $qn->ns;
1223
                }
1224
                $this->wsdl->messages[$this->currentMessage][$attrs['name']] = $attrs;
1225
                // error in wsdl
1226
 
1227
            case 'documentation':
1228
                break;
1229
 
1230
            default:
1231
                break;
1232
            }
1233
            break;
1234
 
1235
        case 'portType':
1236
            // sect 2.4
1237
            switch ($qname->name) {
1238
            case 'operation':
1239
                // attributes: name
1240
                // children: wsdl:input wsdl:output wsdl:fault
1241
                $this->currentOperation = $attrs['name'];
1242
                $this->wsdl->portTypes[$this->currentPortType][$this->currentOperation] = $attrs;
1243
                break;
1244
 
1245
            case 'input':
1246
            case 'output':
1247
            case 'fault':
1248
                // wsdl:input wsdl:output wsdl:fault
1249
                // attributes: name message parameterOrder(optional)
1250
                if ($this->currentOperation) {
1251
                    if (isset($this->wsdl->portTypes[$this->currentPortType][$this->currentOperation][$name])) {
1252
                        $this->wsdl->portTypes[$this->currentPortType][$this->currentOperation][$name] = array_merge($this->wsdl->portTypes[$this->currentPortType][$this->currentOperation][$name], $attrs);
1253
                    } else {
1254
                        $this->wsdl->portTypes[$this->currentPortType][$this->currentOperation][$name] = $attrs;
1255
                    }
1256
                    if (array_key_exists('message', $attrs)) {
1257
                        $qn =& new QName($attrs['message']);
1258
                        $this->wsdl->portTypes[$this->currentPortType][$this->currentOperation][$name]['message'] = $qn->name;
1259
                        $this->wsdl->portTypes[$this->currentPortType][$this->currentOperation][$name]['namespace'] = $qn->ns;
1260
                    }
1261
                }
1262
                break;
1263
 
1264
            case 'documentation':
1265
                break;
1266
 
1267
            default:
1268
                break;
1269
            }
1270
            break;
1271
 
1272
        case 'binding':
1273
            $ns = $qname->ns ? $this->wsdl->namespaces[$qname->ns] : SCHEMA_WSDL;
1274
            switch ($ns) {
1275
            case SCHEMA_SOAP:
1276
                // this deals with wsdl section 3 soap binding
1277
                switch ($qname->name) {
1278
                case 'binding':
1279
                    // sect 3.3
1280
                    // soap:binding, attributes: transport(required), style(optional, default = document)
1281
                    // if style is missing, it is assumed to be 'document'
1282
                    if (!isset($attrs['style'])) {
1283
                        $attrs['style'] = 'document';
1284
                    }
1285
                    $this->wsdl->bindings[$this->currentBinding] = array_merge($this->wsdl->bindings[$this->currentBinding], $attrs);
1286
                    break;
1287
 
1288
                case 'operation':
1289
                    // sect 3.4
1290
                    // soap:operation, attributes: soapAction(required), style(optional, default = soap:binding:style)
1291
                    if (!isset($attrs['style'])) {
1292
                        $attrs['style'] = $this->wsdl->bindings[$this->currentBinding]['style'];
1293
                    }
1294
                    if (isset($this->wsdl->bindings[$this->currentBinding]['operations'][$this->currentOperation])) {
1295
                        $this->wsdl->bindings[$this->currentBinding]['operations'][$this->currentOperation] = array_merge($this->wsdl->bindings[$this->currentBinding]['operations'][$this->currentOperation], $attrs);
1296
                    } else {
1297
                        $this->wsdl->bindings[$this->currentBinding]['operations'][$this->currentOperation] = $attrs;
1298
                    }
1299
                    break;
1300
 
1301
                case 'body':
1302
                    // sect 3.5
1303
                    // soap:body attributes:
1304
                    // part - optional.  listed parts must appear in body, missing means all parts appear in body
1305
                    // use - required. encoded|literal
1306
                    // encodingStyle - optional.  space seperated list of encodings (uri's)
1307
                    $this->wsdl->bindings[$this->currentBinding]
1308
                                    ['operations'][$this->currentOperation][$this->opStatus] = $attrs;
1309
                    break;
1310
 
1311
                case 'fault':
1312
                    // sect 3.6
1313
                    // soap:fault attributes: name use  encodingStyle namespace
1314
                    $this->wsdl->bindings[$this->currentBinding]
1315
                                    ['operations'][$this->currentOperation][$this->opStatus] = $attrs;
1316
                    break;
1317
 
1318
                case 'header':
1319
                    // sect 3.7
1320
                    // soap:header attributes: message part use encodingStyle namespace
1321
                    $this->wsdl->bindings[$this->currentBinding]
1322
                                    ['operations'][$this->currentOperation][$this->opStatus]['headers'][] = $attrs;
1323
                    break;
1324
 
1325
                case 'headerfault':
1326
                    // sect 3.7
1327
                    // soap:header attributes: message part use encodingStyle namespace
1328
                    $header = count($this->wsdl->bindings[$this->currentBinding]
1329
                                    ['operations'][$this->currentOperation][$this->opStatus]['headers'])-1;
1330
                    $this->wsdl->bindings[$this->currentBinding]
1331
                                    ['operations'][$this->currentOperation][$this->opStatus]['headers'][$header]['fault'] = $attrs;
1332
                    break;
1333
 
1334
                case 'documentation':
1335
                    break;
1336
 
1337
                default:
1338
                    // error!  not a valid element inside binding
1339
                    break;
1340
                }
1341
                break;
1342
 
1343
            case SCHEMA_WSDL:
1344
                // XXX verify correct namespace
1345
                // for now, default is the 'wsdl' namespace
1346
                // other possible namespaces include smtp, http, etc. for alternate bindings
1347
                switch ($qname->name) {
1348
                case 'operation':
1349
                    // sect 2.5
1350
                    // wsdl:operation attributes: name
1351
                    $this->currentOperation = $attrs['name'];
1352
                    break;
1353
 
1354
                case 'output':
1355
                case 'input':
1356
                case 'fault':
1357
                    // sect 2.5
1358
                    // wsdl:input attributes: name
1359
                    $this->opStatus = $qname->name;
1360
                    break;
1361
 
1362
                case 'documentation':
1363
                    break;
1364
 
1365
                default:
1366
                    break;
1367
                }
1368
                break;
1369
 
1370
            case SCHEMA_WSDL_HTTP:
1371
                switch ($qname->name) {
1372
                case 'binding':
1373
                    // sect 4.4
1374
                    // http:binding attributes: verb
1375
                    // parent: wsdl:binding
1376
                    $this->wsdl->bindings[$this->currentBinding] = array_merge($this->wsdl->bindings[$this->currentBinding], $attrs);
1377
                    break;
1378
 
1379
                case 'operation':
1380
                    // sect 4.5
1381
                    // http:operation attributes: location
1382
                    // parent: wsdl:operation
1383
                    $this->wsdl->bindings[$this->currentBinding]['operations']
1384
                                                        [$this->currentOperation] = $attrs;
1385
                    break;
1386
 
1387
                case 'urlEncoded':
1388
                    // sect 4.6
1389
                    // http:urlEncoded attributes: location
1390
                    // parent: wsdl:input wsdl:output etc.
1391
                    $this->wsdl->bindings[$this->currentBinding]['operations'][$this->opStatus]
1392
                                                        [$this->currentOperation]['uri'] = 'urlEncoded';
1393
                    break;
1394
 
1395
                case 'urlReplacement':
1396
                    // sect 4.7
1397
                    // http:urlReplacement attributes: location
1398
                    // parent: wsdl:input wsdl:output etc.
1399
                    $this->wsdl->bindings[$this->currentBinding]['operations'][$this->opStatus]
1400
                                                        [$this->currentOperation]['uri'] = 'urlReplacement';
1401
                    break;
1402
 
1403
                case 'documentation':
1404
                    break;
1405
 
1406
                default:
1407
                    // error
1408
                    break;
1409
                }
1410
 
1411
            case SCHEMA_MIME:
1412
                // sect 5
1413
                // all mime parts are children of wsdl:input, wsdl:output, etc.
1414
                // unsuported as of yet
1415
                switch ($qname->name) {
1416
                case 'content':
1417
                    // sect 5.3 mime:content
1418
                    // <mime:content part="nmtoken"? type="string"?/>
1419
                    // part attribute only required if content is child of multipart related,
1420
                    //        it contains the name of the part
1421
                    // type attribute contains the mime type
1422
                case 'multipartRelated':
1423
                    // sect 5.4 mime:multipartRelated
1424
                case 'part':
1425
                case 'mimeXml':
1426
                    // sect 5.6 mime:mimeXml
1427
                    // <mime:mimeXml part="nmtoken"?/>
1428
                    //
1429
                case 'documentation':
1430
                    break;
1431
 
1432
                default:
1433
                    // error
1434
                    break;
1435
                }
1436
 
1437
            case SCHEMA_DIME:
1438
                // DIME is defined in:
1439
                // http://gotdotnet.com/team/xml_wsspecs/dime/WSDL-Extension-for-DIME.htm
1440
                // all DIME parts are children of wsdl:input, wsdl:output, etc.
1441
                // unsuported as of yet
1442
                switch ($qname->name) {
1443
                case 'message':
1444
                    // sect 4.1 dime:message
1445
                    // appears in binding section
1446
                    $this->wsdl->bindings[$this->currentBinding]['dime'] = $attrs;
1447
                    break;
1448
 
1449
                default:
1450
                    break;
1451
                }
1452
 
1453
            default:
1454
                break;
1455
            }
1456
            break;
1457
 
1458
        case 'service':
1459
            $ns = $qname->ns ? $this->wsdl->namespaces[$qname->ns] : SCHEMA_WSDL;
1460
 
1461
            switch ($qname->name) {
1462
            case 'port':
1463
                // sect 2.6 wsdl:port attributes: name binding
1464
                $this->currentPort = $attrs['name'];
1465
                $this->wsdl->services[$this->currentService]['ports'][$this->currentPort] = $attrs;
1466
                // XXX hack to deal with binding namespaces
1467
                $qn =& new QName($attrs['binding']);
1468
                $this->wsdl->services[$this->currentService]['ports'][$this->currentPort]['binding'] = $qn->name;
1469
                $this->wsdl->services[$this->currentService]['ports'][$this->currentPort]['namespace'] = $qn->ns;
1470
                break;
1471
 
1472
            case 'address':
1473
                $this->wsdl->services[$this->currentService]['ports'][$this->currentPort]['address'] = $attrs;
1474
                // what TYPE of port is it?  SOAP or HTTP?
1475
                $ns = $qname->ns ? $this->wsdl->namespaces[$qname->ns] : SCHEMA_WSDL;
1476
                switch ($ns) {
1477
                case SCHEMA_WSDL_HTTP:
1478
                    $this->wsdl->services[$this->currentService]['ports'][$this->currentPort]['type']='http';
1479
                    break;
1480
 
1481
                case SCHEMA_SOAP:
1482
                    $this->wsdl->services[$this->currentService]['ports'][$this->currentPort]['type']='soap';
1483
                    break;
1484
 
1485
                default:
1486
                    // Shouldn't happen, we'll assume SOAP.
1487
                    $this->wsdl->services[$this->currentService]['ports'][$this->currentPort]['type']='soap';
1488
                }
1489
 
1490
                break;
1491
 
1492
            case 'documentation':
1493
                break;
1494
 
1495
            default:
1496
                break;
1497
            }
1498
        }
1499
 
1500
        // Top level elements found under wsdl:definitions.
1501
        switch ($qname->name) {
1502
        case 'import':
1503
            // sect 2.1.1 wsdl:import attributes: namespace location
1504
            if ((isset($attrs['location']) || isset($attrs['schemaLocation'])) &&
1505
                !isset($this->wsdl->imports[$attrs['namespace']])) {
1506
                $uri = isset($attrs['location']) ? $attrs['location'] : $attrs['schemaLocation'];
1507
                $location = @parse_url($uri);
1508
                if (!isset($location['scheme'])) {
1509
                    $base = @parse_url($this->uri);
1510
                    $uri = $this->mergeUrl($base, $uri);
1511
                }
1512
 
1513
                $this->wsdl->imports[$attrs['namespace']] = $attrs;
1514
                $import_parser_class = get_class($this);
1515
                $import_parser =& new $import_parser_class($uri, $this->wsdl, $this->docs);
1516
                if ($import_parser->fault) {
1517
                    unset($this->wsdl->imports[$attrs['namespace']]);
1518
                    return false;
1519
                }
1520
                $this->currentImport = $attrs['namespace'];
1521
            }
1522
            // Continue on to the 'types' case - lack of break; is
1523
            // intentional.
1524
 
1525
        case 'types':
1526
            // sect 2.2 wsdl:types
1527
            $this->status = 'types';
1528
            break;
1529
 
1530
        case 'schema':
1531
            // We can hit this at the top level if we've been asked to
1532
            // import an XSD file.
1533
            if (!empty($attrs['targetNamespace'])) {
1534
                $this->schema = $this->wsdl->getNamespaceAttributeName($attrs['targetNamespace']);
1535
            } else {
1536
                $this->schema = $this->wsdl->getNamespaceAttributeName($this->wsdl->tns);
1537
            }
1538
            $this->wsdl->complexTypes[$this->schema] = array();
1539
            $this->wsdl->elements[$this->schema] = array();
1540
            $this->schema_stack[] = $qname->name;
1541
            $this->status = 'types';
1542
            break;
1543
 
1544
        case 'message':
1545
            // sect 2.3 wsdl:message attributes: name children:wsdl:part
1546
            $this->status = 'message';
1547
            if (isset($attrs['name'])) {
1548
                $this->currentMessage = $attrs['name'];
1549
                $this->wsdl->messages[$this->currentMessage] = array();
1550
            }
1551
            break;
1552
 
1553
        case 'portType':
1554
            // sect 2.4 wsdl:portType
1555
            // attributes: name
1556
            // children: wsdl:operation
1557
            $this->status = 'portType';
1558
            $this->currentPortType = $attrs['name'];
1559
            $this->wsdl->portTypes[$this->currentPortType] = array();
1560
            break;
1561
 
1562
        case 'binding':
1563
            // sect 2.5 wsdl:binding attributes: name type
1564
            // children: wsdl:operation soap:binding http:binding
1565
            if ($qname->ns && $qname->ns != $this->tns) {
1566
                break;
1567
            }
1568
            $this->status = 'binding';
1569
            $this->currentBinding = $attrs['name'];
1570
            $qn =& new QName($attrs['type']);
1571
            $this->wsdl->bindings[$this->currentBinding]['type'] = $qn->name;
1572
            $this->wsdl->bindings[$this->currentBinding]['namespace'] = $qn->ns;
1573
            break;
1574
 
1575
        case 'service':
1576
            // sect 2.7 wsdl:service attributes: name children: ports
1577
            $this->currentService = $attrs['name'];
1578
            $this->wsdl->services[$this->currentService]['ports'] = array();
1579
            $this->status = 'service';
1580
            break;
1581
 
1582
        case 'definitions':
1583
            // sec 2.1 wsdl:definitions
1584
            // attributes: name targetNamespace xmlns:*
1585
            // children: wsdl:import wsdl:types wsdl:message wsdl:portType wsdl:binding wsdl:service
1586
            $this->wsdl->definition = $attrs;
1587
            foreach ($attrs as $key => $value) {
1588
                if (strstr($key, 'xmlns:') !== false) {
1589
                    $qn =& new QName($key);
1590
                    // XXX need to refactor ns handling.
1591
                    $this->wsdl->namespaces[$qn->name] = $value;
1592
                    $this->wsdl->ns[$value] = $qn->name;
1593
                    if ($key == 'targetNamespace' ||
1594
                        strcasecmp($value,SOAP_SCHEMA) == 0) {
1595
                        $this->soapns[] = $qn->name;
1596
                    } else {
1597
                        if (in_array($value, $this->_XMLSchema)) {
1598
                            $this->wsdl->xsd = $value;
1599
                        }
1600
                    }
1601
                }
1602
            }
1603
            if (isset($ns) && $ns) {
1604
                $namespace = 'xmlns:' . $ns;
1605
                if (!$this->wsdl->definition[$namespace]) {
1606
                    return $this->_raiseSoapFault("parse error, no namespace for $namespace", $this->uri);
1607
                }
1608
                $this->tns = $ns;
1609
            }
1610
            break;
1611
        }
1612
    }
1613
 
1614
    /**
1615
     * end-element handler.
1616
     */
1617
    function endElement($parser, $name)
1618
    {
1619
        $stacksize = count($this->element_stack);
1620
        if ($stacksize) {
1621
            if ($this->element_stack[$stacksize - 1] == 'definitions') {
1622
                $this->status = '';
1623
            }
1624
            array_pop($this->element_stack);
1625
        }
1626
 
1627
        if (stristr($name, 'schema')) {
1628
            array_pop($this->schema_stack);
1629
            $this->schema = '';
1630
        }
1631
 
1632
        if ($this->schema) {
1633
            array_pop($this->schema_stack);
1634
            if (count($this->schema_stack) <= 1) {
1635
                /* Correct the type for sequences with multiple
1636
                 * elements. */
1637
                if (isset($this->currentComplexType) && isset($this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['type'])
1638
                    && $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['type'] == 'Array'
1639
                    && array_key_exists('elements', $this->wsdl->complexTypes[$this->schema][$this->currentComplexType])
1640
                    && count($this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['elements']) > 1) {
1641
                        $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['type'] = 'Struct';
1642
                }
1643
            }
1644
            if (stristr($name, 'complexType')) {
1645
                $this->currentComplexType = '';
1646
                if (count($this->schema_element_stack)) {
1647
                    $this->currentElement = array_pop($this->schema_element_stack);
1648
                } else {
1649
                    $this->currentElement = '';
1650
                }
1651
            } elseif (stristr($name, 'element')) {
1652
                if (count($this->schema_element_stack)) {
1653
                    $this->currentElement = array_pop($this->schema_element_stack);
1654
                } else {
1655
                    $this->currentElement = '';
1656
                }
1657
            }
1658
        }
1659
    }
1660
 
1661
    /**
1662
     * Element content handler.
1663
     */
1664
    function characterData($parser, $data)
1665
    {
1666
        // Store the documentation in the WSDL file.
1667
        if ($this->currentTag == 'documentation') {
1668
            $data = trim(preg_replace('/\s+/', ' ', $data));
1669
            if (!strlen($data)) {
1670
                return;
1671
            }
1672
 
1673
            switch ($this->status) {
1674
            case 'service':
1675
                $ptr =& $this->wsdl->services[$this->currentService];
1676
                break;
1677
 
1678
            case 'portType':
1679
                $ptr =& $this->wsdl->portTypes[$this->currentPortType][$this->currentOperation];
1680
                break;
1681
 
1682
            case 'binding':
1683
                $ptr =& $this->wsdl->bindings[$this->currentBinding];
1684
                break;
1685
 
1686
            case 'message':
1687
                $ptr =& $this->wsdl->messages[$this->currentMessage];
1688
                break;
1689
 
1690
            case 'operation':
1691
                break;
1692
 
1693
            case 'types':
1694
                if (isset($this->currentComplexType) &&
1695
                    isset($this->wsdl->complexTypes[$this->schema][$this->currentComplexType])) {
1696
                    if ($this->currentElement) {
1697
                        $ptr =& $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['elements'][$this->currentElement];
1698
                    } else {
1699
                        $ptr =& $this->wsdl->complexTypes[$this->schema][$this->currentComplexType];
1700
                    }
1701
                }
1702
                break;
1703
            }
1704
 
1705
            if (isset($ptr)) {
1706
                if (!isset($ptr['documentation'])) {
1707
                    $ptr['documentation'] = '';
1708
                } else {
1709
                    $ptr['documentation'] .= ' ';
1710
                }
1711
                $ptr['documentation'] .= $data;
1712
            }
1713
        }
1714
    }
1715
 
1716
    /**
1717
     * $parsed is an array returned by parse_url().
1718
     *
1719
     * @access private
1720
     */
1721
    function mergeUrl($parsed, $path)
1722
    {
1723
        if (!is_array($parsed)) {
1724
            return false;
1725
        }
1726
 
1727
        $uri = '';
1728
        if (!empty($parsed['scheme'])) {
1729
            $sep = (strtolower($parsed['scheme']) == 'mailto' ? ':' : '://');
1730
            $uri = $parsed['scheme'] . $sep;
1731
        }
1732
 
1733
        if (isset($parsed['pass'])) {
1734
            $uri .= "$parsed[user]:$parsed[pass]@";
1735
        } elseif (isset($parsed['user'])) {
1736
            $uri .= "$parsed[user]@";
1737
        }
1738
 
1739
        if (isset($parsed['host'])) {
1740
            $uri .= $parsed['host'];
1741
        }
1742
        if (isset($parsed['port'])) {
1743
            $uri .= ":$parsed[port]";
1744
        }
1745
        if ($path[0] != '/' && isset($parsed['path'])) {
1746
            if ($parsed['path'][strlen($parsed['path']) - 1] != '/') {
1747
                $path = dirname($parsed['path']) . '/' . $path;
1748
            } else {
1749
                $path = $parsed['path'] . $path;
1750
            }
1751
            $path = $this->_normalize($path);
1752
        }
1753
        $sep = $path[0] == '/' ? '' : '/';
1754
        $uri .= $sep . $path;
1755
 
1756
        return $uri;
1757
    }
1758
 
1759
    function _normalize($path_str)
1760
    {
1761
        $pwd = '';
1762
        $strArr = preg_split('/(\/)/', $path_str, -1, PREG_SPLIT_NO_EMPTY);
1763
        $pwdArr = '';
1764
        $j = 0;
1765
        for ($i = 0; $i < count($strArr); $i++) {
1766
            if ($strArr[$i] != ' ..') {
1767
                if ($strArr[$i] != ' .') {
1768
                    $pwdArr[$j] = $strArr[$i];
1769
                    $j++;
1770
                }
1771
            } else {
1772
                array_pop($pwdArr);
1773
                $j--;
1774
            }
1775
        }
1776
        $pStr = implode('/', $pwdArr);
1777
        $pwd = (strlen($pStr) > 0) ? ('/' . $pStr) : '/';
1778
        return $pwd;
1779
    }
1780
 
1781
}
1782
 
1783
/**
1784
 * Parses the types and methods used in web service objects into the internal
1785
 * data structures used by SOAP_WSDL.
1786
 *
1787
 * Assumes the SOAP_WSDL class is unpopulated to start with.
1788
 *
1789
 * @author Chris Coe <info@intelligentstreaming.com>
1790
 */
1791
class SOAP_WSDL_ObjectParser extends SOAP_Base
1792
{
1793
    /**
1794
     * Target namespace for the WSDL document will have the following
1795
     * prefix.
1796
     */
1797
    var $tnsPrefix = 'tns';
1798
 
1799
    /**
1800
     * Reference to the SOAP_WSDL object to populate.
1801
     */
1802
    var $wsdl = null;
1803
 
1804
    /** Constructor
1805
     *
1806
     * @param  $objects Reference to the object or array of objects to parse
1807
     * @param  $wsdl Reference to the SOAP_WSDL object to populate
1808
     * @param  $targetNamespace The target namespace of schema types etc.
1809
     * @param  $service_name Name of the WSDL <service>
1810
     * @param  $service_desc Optional description of the WSDL <service>
1811
     */
1812
    function SOAP_WSDL_ObjectParser(&$objects, &$wsdl, $targetNamespace, $service_name, $service_desc = '')
1813
    {
1814
        parent::SOAP_Base('WSDLOBJECTPARSER');
1815
 
1816
        $this->wsdl = &$wsdl;
1817
 
1818
        // Set up the SOAP_WSDL object
1819
        $this->_initialise($service_name);
1820
 
1821
        // Parse each web service object
1822
        $wsdl_ref = (is_array($objects)? $objects : array(&$objects));
1823
 
1824
        foreach ($wsdl_ref as $ref_item) {
1825
            if (!is_object($ref_item))
1826
                return $this->_raiseSoapFault('Invalid web service object passed to object parser', 'urn:' . get_class($object));
1827
 
1828
            if ($this->_parse($ref_item, $targetNamespace, $service_name) != true)
1829
                break;
1830
        }
1831
 
1832
        // Build bindings from abstract data.
1833
        if ($this->fault == null) {
1834
            $this->_generateBindingsAndServices($targetNamespace, $service_name, $service_desc);
1835
        }
1836
    }
1837
 
1838
    /**
1839
     * Initialise the SOAP_WSDL tree (destructive).
1840
     *
1841
     * If the object has already been initialised, the only effect
1842
     * will be to change the tns namespace to the new service name.
1843
     *
1844
     * @param  $service_name Name of the WSDL <service>
1845
     * @access private
1846
     */
1847
    function _initialise($service_name)
1848
    {
1849
        // Set up the basic namespaces that all WSDL definitions use.
1850
        $this->wsdl->namespaces['wsdl'] = SCHEMA_WSDL;                                      // WSDL language
1851
        $this->wsdl->namespaces['soap'] = SCHEMA_SOAP;                                      // WSDL SOAP bindings
1852
        $this->wsdl->namespaces[$this->tnsPrefix] = 'urn:' . $service_name;                 // Target namespace
1853
        $this->wsdl->namespaces['xsd'] = array_search('xsd', $this->_namespaces);           // XML Schema
1854
        $this->wsdl->namespaces['SOAP-ENC'] = array_search('SOAP-ENC', $this->_namespaces); // SOAP types
1855
 
1856
        // XXX Refactor $namespace/$ns for Shane :-)
1857
        unset($this->wsdl->ns['urn:' . $service_name]);
1858
        $this->wsdl->ns += array_flip($this->wsdl->namespaces);
1859
 
1860
        // Imports are not implemented in WSDL generation from classes.
1861
        // *** <wsdl:import> ***
1862
    }
1863
 
1864
    /**
1865
     * Parser - takes a single object to add to tree
1866
     * (non-destructive).
1867
     *
1868
     * @param  $object Reference to the object to parse
1869
     * @param  $service_name Name of the WSDL <service>
1870
    * @access private
1871
     */
1872
    function _parse(&$object, $schemaNamespace, $service_name)
1873
    {
1874
        // Create namespace prefix for the schema
1875
        // XXX not very elegant :-(
1876
 
1877
        list($schPrefix, $foo) = $this->_getTypeNs('{' . $schemaNamespace.'}');
1878
        unset($foo);
1879
 
1880
        // Parse all the types defined by the object in whatever
1881
        // schema language we are using (currently __typedef arrays)
1882
        // *** <wsdl:types> ***
1883
 
1884
        foreach ($object->__typedef as $typeName => $typeValue) {
1885
            // Get/create namespace definition
1886
 
1887
            list($nsPrefix, $typeName) = $this->_getTypeNs($typeName);
1888
 
1889
            // Create type definition
1890
 
1891
            $this->wsdl->complexTypes[$schPrefix][$typeName] = array('name' => $typeName);
1892
            $thisType =& $this->wsdl->complexTypes[$schPrefix][$typeName];
1893
 
1894
            // According to Dmitri's documentation, __typedef comes in two
1895
            // flavors:
1896
            // Array = array(array("item" => "value"))
1897
            // Struct = array("item1" => "value1", "item2" => "value2", ...)
1898
 
1899
            if (is_array($typeValue)) {
1900
                reset($typeValue);
1901
                if (is_array(current($typeValue)) && count($typeValue) == 1
1902
                    && count(current($typeValue)) == 1) {
1903
                    // It's an array
1904
 
1905
                    $thisType['type'] = 'Array';
1906
                    reset(current($typeValue));
1907
                    list($nsPrefix, $typeName) = $this->_getTypeNs(current(current($typeValue)));
1908
                    $thisType['namespace'] = $nsPrefix;
1909
                    $thisType['arrayType'] = $typeName . '[]';
1910
                } elseif (!is_array(current($typeValue))) {
1911
                    // It's a struct
1912
 
1913
                    $thisType['type'] = 'Struct';
1914
                    $thisType['order'] = 'all';
1915
                    $thisType['namespace'] = $nsPrefix;
1916
                    $thisType['elements'] = array();
1917
 
1918
                    foreach ($typeValue as $elementName => $elementType) {
1919
                        list($nsPrefix, $typeName) = $this->_getTypeNs($elementType);
1920
                        $thisType['elements'][$elementName]['name'] = $elementName;
1921
                        $thisType['elements'][$elementName]['type'] = $typeName;
1922
                        $thisType['elements'][$elementName]['namespace'] = $nsPrefix;
1923
                    }
1924
                } else {
1925
                    // It's erroneous
1926
                    return $this->_raiseSoapFault("The type definition for $nsPrefix:$typeName is invalid.", 'urn:' . get_class($object));
1927
                }
1928
            } else {
1929
                // It's erroneous
1930
                return $this->_raiseSoapFault("The type definition for $nsPrefix:$typeName is invalid.", 'urn:' . get_class($object));
1931
            }
1932
        }
1933
 
1934
        // Create an empty element array with the target namespace
1935
        // prefix, to match the results of WSDL parsing.
1936
 
1937
        $this->wsdl->elements[$schPrefix] = array();
1938
 
1939
        // Populate tree with message information
1940
        // *** <wsdl:message> ***
1941
 
1942
        foreach ($object->__dispatch_map as $operationName => $messages) {
1943
            foreach ($messages as $messageType => $messageParts) {
1944
                unset($thisMessage);
1945
 
1946
                switch ($messageType) {
1947
                case 'in':
1948
                    $this->wsdl->messages[$operationName . 'Request'] = array();
1949
                    $thisMessage =& $this->wsdl->messages[$operationName . 'Request'];
1950
                    break;
1951
 
1952
                case 'out':
1953
                    $this->wsdl->messages[$operationName . 'Response'] = array();
1954
                    $thisMessage =& $this->wsdl->messages[$operationName . 'Response'];
1955
                    break;
1956
 
1957
                case 'alias':
1958
                    // Do nothing
1959
                    break;
1960
 
1961
                default:
1962
                    // Error condition
1963
                    break;
1964
                }
1965
 
1966
                if (isset($thisMessage)) {
1967
                    foreach ($messageParts as $partName => $partType) {
1968
                        list ($nsPrefix, $typeName) = $this->_getTypeNs($partType);
1969
 
1970
                        $thisMessage[$partName] = array(
1971
                            'name' => $partName,
1972
                            'type' => $typeName,
1973
                            'namespace' => $nsPrefix
1974
                            );
1975
                    }
1976
                }
1977
            }
1978
        }
1979
 
1980
        // Populate tree with portType information
1981
        // XXX Current implementation only supports one portType that
1982
        // encompasses all of the operations available.
1983
        // *** <wsdl:portType> ***
1984
 
1985
        if (!isset($this->wsdl->portTypes[$service_name . 'Port'])) {
1986
            $this->wsdl->portTypes[$service_name . 'Port'] = array();
1987
        }
1988
        $thisPortType =& $this->wsdl->portTypes[$service_name . 'Port'];
1989
 
1990
        foreach ($object->__dispatch_map as $operationName => $messages) {
1991
            $thisPortType[$operationName] = array('name' => $operationName);
1992
 
1993
            foreach ($messages as $messageType => $messageParts) {
1994
                switch ($messageType) {
1995
                case 'in':
1996
                    $thisPortType[$operationName]['input'] = array(
1997
                        'message' => $operationName . 'Request',
1998
                        'namespace' => $this->tnsPrefix);
1999
                    break;
2000
 
2001
                case 'out':
2002
                    $thisPortType[$operationName]['output'] = array(
2003
                        'message' => $operationName . 'Response',
2004
                        'namespace' => $this->tnsPrefix);
2005
                    break;
2006
                }
2007
            }
2008
        }
2009
 
2010
        return true;
2011
    }
2012
 
2013
    /**
2014
     * Take all the abstract WSDL data and build concrete bindings and
2015
     * services (destructive).
2016
     *
2017
     * XXX Current implementation discards $service_desc.
2018
     *
2019
     * @param  $schemaNamespace Namespace for types etc.
2020
     * @param  $service_name Name of the WSDL <service>
2021
     * @param  $service_desc Optional description of the WSDL <service>
2022
     * @access private
2023
     */
2024
    function _generateBindingsAndServices($schemaNamespace, $service_name, $service_desc = '')
2025
    {
2026
        // Populate tree with bindings information
2027
        // XXX Current implementation only supports one binding that
2028
        // matches the single portType and all of its operations.
2029
        // XXX Is this the correct use of $schemaNamespace here?
2030
        // *** <wsdl:binding> ***
2031
 
2032
        $this->wsdl->bindings[$service_name . 'Binding'] = array(
2033
                'type' => $service_name . 'Port',
2034
                'namespace' => $this->tnsPrefix,
2035
                'style' => 'rpc',
2036
                'transport' => SCHEMA_SOAP_HTTP,
2037
                'operations' => array());
2038
        $thisBinding =& $this->wsdl->bindings[$service_name . 'Binding'];
2039
 
2040
        foreach ($this->wsdl->portTypes[$service_name . 'Port'] as $operationName => $operationData) {
2041
            $thisBinding['operations'][$operationName] = array(
2042
                'soapAction' => $schemaNamespace . '#' . $operationName,
2043
                'style' => $thisBinding['style']);
2044
 
2045
            foreach (array('input', 'output') as $messageType)
2046
                if (isset($operationData[$messageType])) {
2047
                    $thisBinding['operations'][$operationName][$messageType] = array(
2048
                            'use' => 'encoded',
2049
                            'namespace' => $schemaNamespace,
2050
                            'encodingStyle' => SOAP_SCHEMA_ENCODING);
2051
                }
2052
        }
2053
 
2054
        // Populate tree with service information
2055
        // XXX Current implementation supports one service which groups
2056
        // all of the ports together, one port per binding
2057
        // XXX What about https?
2058
        // *** <wsdl:service> ***
2059
 
2060
        $this->wsdl->services[$service_name . 'Service'] = array('ports' => array());
2061
        $thisService =& $this->wsdl->services[$service_name . 'Service']['ports'];
2062
 
2063
        foreach ($this->wsdl->bindings as $bindingName => $bindingData) {
2064
            $thisService[$bindingData['type']] = array(
2065
                    'name' => $bindingData['type'],
2066
                    'binding' => $bindingName,
2067
                    'namespace' => $this->tnsPrefix,
2068
                    'address' => array('location' =>
2069
                        'http://' . $_SERVER['SERVER_NAME'] . $_SERVER['PHP_SELF'] .
2070
                        (isset($_SERVER['QUERY_STRING']) ? '?' . $_SERVER['QUERY_STRING'] : '')),
2071
                    'type' => 'soap');
2072
        }
2073
 
2074
        // Set service
2075
        $this->wsdl->set_service($service_name . 'Service');
2076
        $this->wsdl->uri = $this->wsdl->namespaces[$this->tnsPrefix];
2077
 
2078
        // Create WSDL definition
2079
        // *** <wsdl:definitions> ***
2080
 
2081
        $this->wsdl->definition = array(
2082
                'name' => $service_name,
2083
                'targetNamespace' => $this->wsdl->namespaces[$this->tnsPrefix],
2084
                'xmlns' => SCHEMA_WSDL);
2085
 
2086
        foreach ($this->wsdl->namespaces as $nsPrefix => $namespace) {
2087
            $this->wsdl->definition['xmlns:' . $nsPrefix] = $namespace;
2088
        }
2089
    }
2090
 
2091
    /**
2092
     * This function is adapted from Dmitri V's implementation of
2093
     * DISCO/WSDL generation. It separates namespace from type name in
2094
     * a __typedef key and creates a new namespace entry in the WSDL
2095
     * structure if the namespace has not been used before. The
2096
     * namespace prefix and type name are returned. If no namespace is
2097
     * specified, xsd is assumed.
2098
     *
2099
     * We will not need this function anymore once __typedef is
2100
     * eliminated.
2101
     */
2102
    function _getTypeNs($type)
2103
    {
2104
        preg_match_all("'\{(.*)\}'sm", $type, $m);
2105
        if (isset($m[1][0]) && $m[1][0] != '') {
2106
            if (!array_key_exists($m[1][0], $this->wsdl->ns)) {
2107
                $ns_pref = 'ns' . count($this->wsdl->namespaces);
2108
                $this->wsdl->ns[$m[1][0]] = $ns_pref;
2109
                $this->wsdl->namespaces[$ns_pref] = $m[1][0];
2110
            }
2111
            $typens = $this->wsdl->ns[$m[1][0]];
2112
            $type = ereg_replace($m[0][0], '', $type);
2113
        } else {
2114
            $typens = 'xsd';
2115
        }
2116
 
2117
        return array($typens, $type);
2118
    }
2119
 
2120
}