source: branches/feature-module-update/data/module/SOAP/WSDL.php @ 16957

Revision 16957, 89.4 KB checked in by naka, 15 years ago (diff)

PEAR::SOAPモジュールアップ

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