source: branches/version-2_12-dev/data/module/SOAP/WSDL.php @ 21461

Revision 21461, 88.2 KB checked in by Seasoft, 12 years ago (diff)

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

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