source: branches/version-2_11-dev/data/module/SOAP/WSDL.php @ 21299

Revision 21299, 87.8 KB checked in by Seasoft, 12 years ago (diff)

#1521 (PEAR::SOAP 配布と異なる部分がある)

  • 新しいバージョンの配布ファイルを上書きすることで解決

#1522 (PEAR::SOAP をバージョンアップ)

  • 0.11.0 -> 0.12.0
  • Property svn:eol-style set to LF
  • Property svn:mime-type set to text/x-httpd-php; charset=UTF-8
Line 
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
25require_once 'SOAP/Base.php';
26require_once 'SOAP/Fault.php';
27require_once 'HTTP/Request.php';
28
29define('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 */
47class 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
964class 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
1119class 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 */
1961class 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}
Note: See TracBrowser for help on using the repository browser.