Subversion-Projekte lars-tiefland.php_share

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

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