3
* This file contains the code for dealing with WSDL access and services.
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.
14
* @category Web Services
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
25
require_once 'SOAP/Base.php';
26
require_once 'SOAP/Fault.php';
27
require_once 'HTTP/Request.php';
29
define('WSDL_CACHE_MAX_AGE', 43200);
32
* This class parses WSDL files, and can be used by SOAP::Client to properly
33
* register soap values for services.
35
* Originally based on SOAPx4 by Dietrich Ayala
36
* http://dietrich.ganx4.com/soapx4
39
* - refactor namespace handling ($namespace/$ns)
40
* - implement IDL type syntax declaration so we can generate WSDL
44
* @author Shane Caraveo <shane@php.net> Conversion to PEAR and updates
45
* @author Dietrich Ayala <dietrich@ganx4.com> Original Author
47
class SOAP_WSDL extends SOAP_Base
50
var $definition = array();
51
var $namespaces = array();
53
var $xsd = SOAP_XML_SCHEMA_VERSION;
54
var $complexTypes = array();
55
var $elements = array();
56
var $messages = array();
57
var $portTypes = array();
58
var $bindings = array();
59
var $imports = array();
60
var $services = array();
71
* Parse documentation in the WSDL?
85
* Enable tracing in the generated proxy class?
99
* WSDL cache directory.
106
* Cache maximum lifetime (in seconds).
113
* Class to use for WSDL parsing. Can be overridden for special cases,
118
var $wsdlParserClass = 'SOAP_WSDL_Parser';
121
* Reserved PHP keywords.
123
* @link http://www.php.net/manual/en/reserved.php
127
var $_reserved = array('abstract', 'and', 'array', 'as', 'break', 'case',
128
'catch', 'cfunction', 'class', 'clone', 'const',
129
'continue', 'declare', 'default', 'die', 'do',
130
'echo', 'else', 'elseif', 'empty', 'enddeclare',
131
'endfor', 'endforeach', 'endif', 'endswitch',
132
'endwhile', 'eval', 'exception', 'exit', 'extends',
133
'final', 'for', 'foreach', 'function', 'global',
134
'if', 'implements', 'include', 'include_once',
135
'interface', 'isset', 'list', 'new', 'old_function',
136
'or', 'php_user_filter', 'print', 'private',
137
'protected', 'public', 'require', 'require_once',
138
'return', 'static', 'switch', 'this', 'throw',
139
'try', 'unset', 'use', 'var', 'while', 'xor');
142
* Regular expressions for invalid PHP labels.
144
* @link http://www.php.net/manual/en/language.variables.php.
148
var $_invalid = array('/^[^a-zA-Z_\x7f-\xff]/', '/[^a-zA-Z0-9_\x7f-\xff]/');
151
* SOAP_WSDL constructor.
153
* @param string $wsdl_uri URL to WSDL file.
154
* @param array $proxy Options for HTTP_Request class
156
* @param boolean|string $cacheUse Use WSDL caching? The cache directory
158
* @param integer $cacheMaxAge Cache maximum lifetime (in seconds).
159
* @param boolean $docs Parse documentation in the WSDL?
163
function SOAP_WSDL($wsdl_uri = false,
166
$cacheMaxAge = WSDL_CACHE_MAX_AGE,
169
parent::SOAP_Base('WSDL');
170
$this->uri = $wsdl_uri;
171
$this->proxy = $proxy;
172
$this->cacheUse = !empty($cacheUse);
173
$this->cacheMaxAge = $cacheMaxAge;
175
if (is_string($cacheUse)) {
176
$this->cacheDir = $cacheUse;
180
if (!PEAR::isError($this->parseURL($wsdl_uri))) {
181
reset($this->services);
182
$this->service = key($this->services);
188
* @deprecated Use setService().
190
function set_service($service)
192
$this->setService($service);
196
* Sets the service currently to be used.
198
* @param string $service An (existing) service name.
200
function setService($service)
202
if (array_key_exists($service, $this->services)) {
203
$this->service = $service;
208
* Fills the WSDL array tree with data from a WSDL file.
210
* @param string $wsdl_uri URL to WSDL file.
212
function parseURL($wsdl_uri)
214
$parser = new $this->wsdlParserClass($wsdl_uri, $this, $this->docs);
216
if ($parser->fault) {
217
$this->_raiseSoapFault($parser->fault);
222
* Fills the WSDL array tree with data from one or more PHP class objects.
224
* @param mixed $wsdl_obj An object or array of objects to add to
225
* the internal WSDL tree.
226
* @param string $targetNamespace The target namespace of schema types
228
* @param string $service_name Name of the WSDL service.
229
* @param string $service_desc Optional description of the WSDL
232
function parseObject($wsdl_obj, $targetNamespace, $service_name,
235
$parser = new SOAP_WSDL_ObjectParser($wsdl_obj, $this,
236
$targetNamespace, $service_name,
239
if ($parser->fault) {
240
$this->_raiseSoapFault($parser->fault);
244
function getEndpoint($portName)
246
if ($this->_isfault()) {
247
return $this->_getfault();
250
return (isset($this->services[$this->service]['ports'][$portName]['address']['location']))
251
? $this->services[$this->service]['ports'][$portName]['address']['location']
252
: $this->_raiseSoapFault("No endpoint for port for $portName", $this->uri);
255
function _getPortName($operation, $service)
257
if (isset($this->services[$service]['ports'])) {
258
$ports = $this->services[$service]['ports'];
259
foreach ($ports as $port => $portAttrs) {
260
$type = $ports[$port]['type'];
261
if ($type == 'soap' &&
262
isset($this->bindings[$portAttrs['binding']]['operations'][$operation])) {
271
* Finds the name of the first port that contains an operation of name
272
* $operation. Always returns a SOAP portName.
274
function getPortName($operation, $service = null)
276
if ($this->_isfault()) {
277
return $this->_getfault();
281
$service = $this->service;
283
if (isset($this->services[$service]['ports'])) {
284
if ($portName = $this->_getPortName($operation, $service)) {
288
// Try any service in the WSDL.
289
foreach ($this->services as $serviceName => $service) {
290
if (isset($this->services[$serviceName]['ports'])) {
291
if ($portName = $this->_getPortName($operation, $serviceName)) {
292
$this->service = $serviceName;
297
return $this->_raiseSoapFault("No operation $operation in WSDL.", $this->uri);
300
function getOperationData($portName, $operation)
302
if ($this->_isfault()) {
303
return $this->_getfault();
306
if (!isset($this->services[$this->service]['ports'][$portName]['binding']) ||
307
!($binding = $this->services[$this->service]['ports'][$portName]['binding'])) {
308
return $this->_raiseSoapFault("No binding for port $portName in WSDL.", $this->uri);
311
// Get operation data from binding.
312
if (is_array($this->bindings[$binding]['operations'][$operation])) {
313
$opData = $this->bindings[$binding]['operations'][$operation];
315
// Get operation data from porttype.
316
$portType = $this->bindings[$binding]['type'];
318
return $this->_raiseSoapFault("No port type for binding $binding in WSDL.", $this->uri);
320
if (is_array($type = $this->portTypes[$portType][$operation])) {
321
if (isset($type['parameterOrder'])) {
322
$opData['parameterOrder'] = $type['parameterOrder'];
324
$opData['input'] = array_merge($opData['input'], $type['input']);
325
$opData['output'] = array_merge($opData['output'], $type['output']);
328
return $this->_raiseSoapFault("No operation $operation for port $portName in WSDL.", $this->uri);
329
$opData['parameters'] = false;
330
if (isset($this->bindings[$binding]['operations'][$operation]['input']['namespace']))
331
$opData['namespace'] = $this->bindings[$binding]['operations'][$operation]['input']['namespace'];
332
// Message data from messages.
333
$inputMsg = $opData['input']['message'];
334
if (isset($opData['input']['parts']) &&
335
!is_array($opData['input']['parts'])) {
336
$opData['input']['parts'] = array($opData['input']['parts'] => '');
339
if (is_array($this->messages[$inputMsg])) {
340
foreach ($this->messages[$inputMsg] as $pname => $pattrs) {
341
if ($opData['style'] == 'document' &&
342
$opData['input']['use'] == 'literal' &&
343
$pname == 'parameters') {
344
$opData['parameters'] = true;
345
$opData['namespace'] = $this->namespaces[$pattrs['namespace']];
346
$el = $this->elements[$pattrs['namespace']][$pattrs['type']];
347
if (isset($el['elements'])) {
348
foreach ($el['elements'] as $elname => $elattrs) {
349
$opData['input']['parts'][$elname] = $elattrs;
353
$opData['input']['parts'][$pname] = $pattrs;
357
$outputMsg = $opData['output']['message'];
358
if (isset($opData['output']['parts']) &&
359
!is_array($opData['output']['parts'])) {
360
$opData['output']['parts'] = array($opData['output']['parts'] => '');
362
if (is_array($this->messages[$outputMsg])) {
363
foreach ($this->messages[$outputMsg] as $pname => $pattrs) {
364
if ($opData['style'] == 'document' &&
365
$opData['output']['use'] == 'literal' &&
366
$pname == 'parameters') {
368
$el = $this->elements[$pattrs['namespace']][$pattrs['type']];
369
if (isset($el['elements'])) {
370
foreach ($el['elements'] as $elname => $elattrs) {
371
$opData['output']['parts'][$elname] = $elattrs;
375
$opData['output']['parts'][$pname] = $pattrs;
382
function matchMethod(&$operation)
384
if ($this->_isfault()) {
385
return $this->_getfault();
388
// Overloading lowercases function names :(
389
foreach ($this->services[$this->service]['ports'] as $portAttrs) {
390
foreach (array_keys($this->bindings[$portAttrs['binding']]['operations']) as $op) {
391
if (strcasecmp($op, $operation) == 0) {
399
* Given a datatype, what function handles the processing?
401
* This is used for doc/literal requests where we receive a datatype, and
402
* we need to pass it to a method in out server class.
404
* @param string $datatype
405
* @param string $namespace
409
function getDataHandler($datatype, $namespace)
411
// See if we have an element by this name.
412
if (isset($this->namespaces[$namespace])) {
413
$namespace = $this->namespaces[$namespace];
416
if (!isset($this->ns[$namespace])) {
420
$nsp = $this->ns[$namespace];
421
//if (!isset($this->elements[$nsp]))
422
// $nsp = $this->namespaces[$nsp];
423
if (!isset($this->elements[$nsp][$datatype])) {
427
$checkmessages = array();
428
// Find what messages use this datatype.
429
foreach ($this->messages as $messagename => $message) {
430
foreach ($message as $part) {
431
if ($part['type'] == $datatype) {
432
$checkmessages[] = $messagename;
437
// Find the operation that uses this message.
438
foreach($this->portTypes as $porttype) {
439
foreach ($porttype as $opname => $opinfo) {
440
foreach ($checkmessages as $messagename) {
441
if ($opinfo['input']['message'] == $messagename) {
451
function getSoapAction($portName, $operation)
453
if ($this->_isfault()) {
454
return $this->_getfault();
457
if (!empty($this->bindings[$this->services[$this->service]['ports'][$portName]['binding']]['operations'][$operation]['soapAction'])) {
458
return $this->bindings[$this->services[$this->service]['ports'][$portName]['binding']]['operations'][$operation]['soapAction'];
464
function getNamespace($portName, $operation)
466
if ($this->_isfault()) {
467
return $this->_getfault();
470
if (!empty($this->bindings[$this->services[$this->service]['ports'][$portName]['binding']]['operations'][$operation]['input']['namespace'])) {
471
return $this->bindings[$this->services[$this->service]['ports'][$portName]['binding']]['operations'][$operation]['input']['namespace'];
477
function getNamespaceAttributeName($namespace)
479
/* If it doesn't exist at first, flip the array and check again. */
480
if (empty($this->ns[$namespace])) {
481
$this->ns = array_flip($this->namespaces);
484
/* If it doesn't exist now, add it. */
485
if (empty($this->ns[$namespace])) {
486
return $this->addNamespace($namespace);
489
return $this->ns[$namespace];
492
function addNamespace($namespace)
494
if (!empty($this->ns[$namespace])) {
495
return $this->ns[$namespace];
498
$n = count($this->ns);
500
$this->namespaces['ns' . $n] = $namespace;
501
$this->ns[$namespace] = $attr;
506
function _validateString($string)
508
return preg_match('/^[\w_:#\/]+$/', $string);
511
function _addArg(&$args, &$argarray, $argname)
516
$args .= '$' . $argname;
517
if (!$this->_validateString($argname)) {
523
$argarray .= "'$argname' => $" . $argname;
526
function _elementArg(&$args, &$argarray, &$_argtype, $_argname)
529
$el = $this->elements[$_argtype['namespace']][$_argtype['type']];
530
$tns = isset($this->ns[$el['namespace']])
531
? $this->ns[$el['namespace']]
532
: $_argtype['namespace'];
534
if (!empty($el['complex']) ||
535
(isset($el['type']) &&
536
isset($this->complexTypes[$tns][$el['type']]))) {
537
// The element is a complex type.
538
$comments .= " // {$_argtype['type']} is a ComplexType, refer to the WSDL for more info.\n";
539
$attrname = "{$_argtype['type']}_attr";
540
if (isset($el['type']) &&
541
isset($this->complexTypes[$tns][$el['type']]['attribute'])) {
542
$comments .= " // {$_argtype['type']} may require attributes, refer to the WSDL for more info.\n";
544
$comments .= " \${$attrname}['xmlns'] = '{$this->namespaces[$_argtype['namespace']]}';\n";
545
$comments .= " \${$_argtype['type']} = new SOAP_Value('{$_argtype['type']}', false, \${$_argtype['type']}, \$$attrname);\n";
546
$this->_addArg($args, $argarray, $_argtype['type']);
547
if (isset($el['type']) &&
548
isset($this->complexTypes[$tns][$el['type']]['attribute'])) {
552
$args .= '$' . $attrname;
554
} elseif (isset($el['elements'])) {
555
foreach ($el['elements'] as $ename => $element) {
556
$comments .= " \$$ename = new SOAP_Value('{{$this->namespaces[$element['namespace']]}}$ename', '" .
557
(isset($element['type']) ? $element['type'] : false) .
559
$this->_addArg($args, $argarray, $ename);
562
$comments .= " \$$_argname = new SOAP_Value('{{$this->namespaces[$tns]}}$_argname', '{$el['type']}', \$$_argname);\n";
563
$this->_addArg($args, $argarray, $_argname);
569
function _complexTypeArg(&$args, &$argarray, &$_argtype, $_argname)
572
if (isset($this->complexTypes[$_argtype['namespace']][$_argtype['type']])) {
573
$comments = " // $_argname is a ComplexType {$_argtype['type']},\n" .
574
" // refer to wsdl for more info\n";
575
if (isset($this->complexTypes[$_argtype['namespace']][$_argtype['type']]['attribute'])) {
576
$comments .= " // $_argname may require attributes, refer to wsdl for more info\n";
578
$wrapname = '{' . $this->namespaces[$_argtype['namespace']].'}' . $_argtype['type'];
579
$comments .= " \$$_argname = new SOAP_Value('$_argname', '$wrapname', \$$_argname);\n";
582
$this->_addArg($args, $argarray, $_argname);
588
* Generates stub code from the WSDL that can be saved to a file or eval'd
591
function generateProxyCode($port = '', $classname = '')
593
if ($this->_isfault()) {
594
return $this->_getfault();
597
$multiport = count($this->services[$this->service]['ports']) > 1;
599
reset($this->services[$this->service]['ports']);
600
$port = current($this->services[$this->service]['ports']);
602
// XXX currently do not support HTTP ports
603
if ($port['type'] != 'soap') {
607
// XXX currentPort is BAD
608
$clienturl = $port['address']['location'];
610
if ($multiport || $port) {
611
$classname = 'WebService_' . $this->service . '_' . $port['name'];
613
$classname = 'WebService_' . $this->service;
615
$classname = $this->_sanitize($classname);
618
if (!$this->_validateString($classname)) {
622
if (is_array($this->proxy) && count($this->proxy)) {
623
$class = "class $classname extends SOAP_Client\n{\n" .
624
" function $classname(\$path = '$clienturl')\n {\n" .
625
" \$this->SOAP_Client(\$path, 0, 0,\n" .
628
foreach ($this->proxy as $key => $val) {
629
if (is_array($val)) {
630
$class .= "'$key' => array(";
631
foreach ($val as $key2 => $val2) {
632
$class .= "'$key2' => '$val2', ";
636
$class .= "'$key' => '$val', ";
639
$class .= "));\n }\n";
640
$class = str_replace(', ))', '))', $class);
642
$class = "class $classname extends SOAP_Client\n{\n" .
643
" function $classname(\$path = '$clienturl')\n {\n" .
644
" \$this->SOAP_Client(\$path, 0);\n" .
648
// Get the binding, from that get the port type.
649
$primaryBinding = $port['binding'];
650
$primaryBinding = preg_replace("/^(.*:)/", '', $primaryBinding);
651
$portType = $this->bindings[$primaryBinding]['type'];
652
$portType = preg_replace("/^(.*:)/", '', $portType);
653
$style = $this->bindings[$primaryBinding]['style'];
655
// XXX currentPortType is BAD
656
foreach ($this->portTypes[$portType] as $opname => $operation) {
657
$binding = $this->bindings[$primaryBinding]['operations'][$opname];
658
if (isset($binding['soapAction'])) {
659
$soapaction = $binding['soapAction'];
663
if (isset($binding['style'])) {
664
$opstyle = $binding['style'];
668
$use = $binding['input']['use'];
669
if ($use == 'encoded') {
670
$namespace = $binding['input']['namespace'];
672
$bindingType = $this->bindings[$primaryBinding]['type'];
673
$ns = $this->portTypes[$bindingType][$opname]['input']['namespace'];
674
$namespace = $this->namespaces[$ns];
681
foreach ($operation['input'] as $argname => $argtype) {
682
if ($argname == 'message') {
683
foreach ($this->messages[$argtype] as $_argname => $_argtype) {
684
$_argname = $this->_sanitize($_argname);
685
if ($opstyle == 'document' && $use == 'literal' &&
686
$_argtype['name'] == 'parameters') {
687
// The type or element refered to is used for
690
$el = $this->elements[$_argtype['namespace']][$_argtype['type']];
692
if ($el['complex']) {
693
$namespace = $this->namespaces[$_argtype['namespace']];
694
// XXX need to wrap the parameters in a
697
if (isset($el['elements'])) {
698
foreach ($el['elements'] as $elname => $elattrs) {
699
$elname = $this->_sanitize($elname);
700
// Is the element a complex type?
701
if (isset($this->complexTypes[$elattrs['namespace']][$elname])) {
702
$comments .= $this->_complexTypeArg($args, $argarray, $_argtype, $_argname);
704
$this->_addArg($args, $argarray, $elname);
708
if ($el['complex'] && $argarray) {
709
$wrapname = '{' . $this->namespaces[$_argtype['namespace']].'}' . $el['name'];
710
$comments .= " \${$el['name']} = new SOAP_Value('$wrapname', false, \$v = array($argarray));\n";
711
$argarray = "'{$el['name']}' => \${$el['name']}";
714
if (isset($_argtype['element'])) {
716
$comments .= $this->_elementArg($args, $argarray, $_argtype, $_argtype['type']);
718
// Complex type argument.
719
$comments .= $this->_complexTypeArg($args, $argarray, $_argtype, $_argname);
728
// Operation names are function names, so try to make sure it's
729
// legal. This could potentially cause collisions, but let's try
730
// to make everything callable and see how many problems that
732
$opname_php = $this->_sanitize($opname);
733
if (!$this->_validateString($opname_php)) {
738
$argarray = "array($argarray)";
743
$class .= " function &$opname_php($args)\n {\n$comments$wrappers" .
744
" \$result = \$this->call('$opname',\n" .
745
" \$v = $argarray,\n" .
746
" array('namespace' => '$namespace',\n" .
747
" 'soapaction' => '$soapaction',\n" .
748
" 'style' => '$opstyle',\n" .
750
($this->trace ? ",\n 'trace' => true" : '') . "));\n" .
751
" return \$result;\n" .
760
function generateAllProxies()
763
foreach (array_keys($this->services[$this->service]['ports']) as $key) {
764
$port =& $this->services[$this->service]['ports'][$key];
765
$proxycode .= $this->generateProxyCode($port);
770
function &getProxy($port = '', $name = '')
772
if ($this->_isfault()) {
773
$fault =& $this->_getfault();
777
$multiport = count($this->services[$this->service]['ports']) > 1;
780
reset($this->services[$this->service]['ports']);
781
$port = current($this->services[$this->service]['ports']);
784
if ($multiport || $port) {
785
$classname = 'WebService_' . $this->service . '_' . $port['name'];
787
$classname = 'WebService_' . $this->service;
791
$classname = $name . '_' . $classname;
794
$classname = $this->_sanitize($classname);
795
if (!class_exists($classname)) {
796
$proxy = $this->generateProxyCode($port, $classname);
797
require_once 'SOAP/Client.php';
800
$proxy = new $classname;
806
* Sanitizes a SOAP value, method or class name so that it can be used as
807
* a valid PHP identifier. Invalid characters are converted into
808
* underscores and reserved words are prefixed with an underscore.
810
* @param string $name The identifier to sanitize.
812
* @return string The sanitized identifier.
814
function _sanitize($name)
816
$name = preg_replace($this->_invalid, '_', $name);
817
if (in_array($name, $this->_reserved)) {
823
function &_getComplexTypeForElement($name, $namespace)
826
if (isset($this->ns[$namespace]) &&
827
isset($this->elements[$this->ns[$namespace]][$name]['type'])) {
829
$type = $this->elements[$this->ns[$namespace]][$name]['type'];
830
$ns = $this->elements[$this->ns[$namespace]][$name]['namespace'];
832
if (isset($this->complexTypes[$ns][$type])) {
833
$t = $this->complexTypes[$ns][$type];
839
function getComplexTypeNameForElement($name, $namespace)
841
$t = $this->_getComplexTypeForElement($name, $namespace);
848
function getComplexTypeChildType($ns, $name, $child_ns, $child_name)
850
// Is the type an element?
851
$t = $this->_getComplexTypeForElement($name, $ns);
853
// No, get it from complex types directly.
854
if (isset($t['elements'][$child_name]['type']))
855
return $t['elements'][$child_name]['type'];
856
} elseif (isset($this->ns[$ns]) &&
857
isset($this->elements[$this->ns[$ns]][$name]['complex']) &&
858
$this->elements[$this->ns[$ns]][$name]['complex']) {
859
// Type is not an element but complex.
860
return $this->elements[$this->ns[$ns]][$name]['elements'][$child_name]['type'];
866
* @param QName $name A parameter name.
867
* @param QName $type A parameter type.
869
* @return array A list of [type, array element type, array element
870
* namespace, array length].
872
function getSchemaType($type, $name)
874
// see if it's a complex type so we can deal properly with
875
// SOAPENC:arrayType.
876
if ($name && $type) {
878
// look up the name in the wsdl and validate the type.
879
foreach ($this->complexTypes as $types) {
880
if (isset($types[$type->name])) {
881
if (isset($types[$type->name]['type'])) {
882
list($arraytype_ns, $arraytype, $array_depth) = isset($types[$type->name]['arrayType'])
883
? $this->_getDeepestArrayType($types[$type->name]['namespace'], $types[$type->name]['arrayType'])
884
: array($this->namespaces[$types[$type->name]['namespace']], null, 0);
885
return array($types[$type->name]['type'], $arraytype, $arraytype_ns, $array_depth);
887
if (isset($types[$type->name]['arrayType'])) {
888
list($arraytype_ns, $arraytype, $array_depth) =
889
$this->_getDeepestArrayType($types[$type->name]['namespace'], $types[$type->name]['arrayType']);
890
return array('Array', $arraytype, $arraytype_ns, $array_depth);
892
if (!empty($types[$type->name]['elements'][$name->name])) {
893
$type->name = $types[$type->name]['elements']['type'];
894
return array($type->name, null, $this->namespaces[$types[$type->name]['namespace']], null);
900
if ($type && $type->namespace) {
903
// this code currently handles only one way of encoding array
904
// types in wsdl need to do a generalized function to figure out
906
$p = $this->ns[$type->namespace];
907
if ($p && !empty($this->complexTypes[$p][$type->name])) {
908
if ($arrayType = $this->complexTypes[$p][$type->name]['arrayType']) {
909
$type->name = 'Array';
910
} elseif ($this->complexTypes[$p][$type->name]['order'] == 'sequence' &&
911
array_key_exists('elements', $this->complexTypes[$p][$type->name])) {
912
reset($this->complexTypes[$p][$type->name]['elements']);
914
if (count($this->complexTypes[$p][$type->name]['elements']) == 1) {
915
$arg = current($this->complexTypes[$p][$type->name]['elements']);
916
$arrayType = $arg['type'];
917
$type->name = 'Array';
919
foreach ($this->complexTypes[$p][$type->name]['elements'] as $element) {
920
if ($element['name'] == $type->name) {
921
$arrayType = $element['type'];
922
$type->name = $element['type'];
927
$type->name = 'Struct';
929
return array($type->name, $arrayType, $type->namespace, null);
932
return array(null, null, null, null);
936
* Recurse through the WSDL structure looking for the innermost array type
937
* of multi-dimensional arrays.
939
* Takes a namespace prefix and a type, which can be in the form 'type' or
940
* 'type[]', and returns the full namespace URI, the type of the most
941
* deeply nested array type found, and the number of levels of nesting.
944
* @return mixed array or nothing
946
function _getDeepestArrayType($nsPrefix, $arrayType)
948
static $trail = array();
950
$arrayType = preg_replace('/\[\]$/', '', $arrayType);
952
// Protect against circular references XXX We really need to remove
953
// trail from this altogether (it's very inefficient and in the wrong
954
// place!) and put circular reference checking in when the WSDL info
955
// is generated in the first place
956
if (array_search($nsPrefix . ':' . $arrayType, $trail)) {
957
return array(null, null, -count($trail));
960
if (array_key_exists($nsPrefix, $this->complexTypes) &&
961
array_key_exists($arrayType, $this->complexTypes[$nsPrefix]) &&
962
array_key_exists('arrayType', $this->complexTypes[$nsPrefix][$arrayType])) {
963
$trail[] = $nsPrefix . ':' . $arrayType;
964
$result = $this->_getDeepestArrayType($this->complexTypes[$nsPrefix][$arrayType]['namespace'],
965
$this->complexTypes[$nsPrefix][$arrayType]['arrayType']);
966
return array($result[0], $result[1], $result[2] + 1);
968
return array($this->namespaces[$nsPrefix], $arrayType, 0);
973
class SOAP_WSDL_Cache extends SOAP_Base
983
* WSDL cache directory.
990
* Cache maximum lifetime (in seconds)
999
* @param boolean $cashUse Use caching?
1000
* @param integer $cacheMaxAge Cache maximum lifetime (in seconds)
1002
function SOAP_WSDL_Cache($cacheUse = false,
1003
$cacheMaxAge = WSDL_CACHE_MAX_AGE,
1006
parent::SOAP_Base('WSDLCACHE');
1007
$this->_cacheUse = $cacheUse;
1008
$this->_cacheDir = $cacheDir;
1009
$this->_cacheMaxAge = $cacheMaxAge;
1013
* Returns the path to the cache and creates it, if it doesn't exist.
1017
* @return string The directory to use for the cache.
1019
function _cacheDir()
1021
if (!empty($this->_cacheDir)) {
1022
$dir = $this->_cacheDir;
1024
$dir = getenv('WSDLCACHE');
1026
$dir = './wsdlcache';
1034
* Retrieves a file from cache if it exists, otherwise retreive from net,
1035
* add to cache, and return from cache.
1037
* @param string URL to WSDL
1038
* @param array proxy parameters
1039
* @param int expected MD5 of WSDL URL
1041
* @return string data
1043
function get($wsdl_fname, $proxy_params = array(), $cache = 0)
1045
$cachename = $md5_wsdl = $file_data = '';
1046
if ($this->_cacheUse) {
1047
// Try to retrieve WSDL from cache
1048
$cachename = $this->_cacheDir() . '/' . md5($wsdl_fname). ' .wsdl';
1049
if (file_exists($cachename) &&
1050
$file_data = file_get_contents($cachename)) {
1051
$md5_wsdl = md5($file_data);
1053
if ($cache != $md5_wsdl) {
1054
return $this->_raiseSoapFault('WSDL Checksum error!', $wsdl_fname);
1057
$fi = stat($cachename);
1058
$cache_mtime = $fi[8];
1059
if ($cache_mtime + $this->_cacheMaxAge < time()) {
1060
// Expired, refetch.
1067
// Not cached or not using cache. Retrieve WSDL from URL
1069
// Is it a local file?
1070
if (strpos($wsdl_fname, 'file://') === 0) {
1071
$wsdl_fname = substr($wsdl_fname, 7);
1072
if (!file_exists($wsdl_fname)) {
1073
return $this->_raiseSoapFault('Unable to read local WSDL file', $wsdl_fname);
1075
$file_data = file_get_contents($wsdl_fname);
1076
} elseif (!preg_match('|^https?://|', $wsdl_fname)) {
1077
return $this->_raiseSoapFault('Unknown schema of WSDL URL', $wsdl_fname);
1079
$uri = explode('?', $wsdl_fname);
1080
$rq = new HTTP_Request($uri[0], $proxy_params);
1081
// the user agent HTTP_Request uses fouls things up
1082
if (isset($uri[1])) {
1083
$rq->addRawQueryString($uri[1]);
1086
if (isset($proxy_params['proxy_host']) &&
1087
isset($proxy_params['proxy_port']) &&
1088
isset($proxy_params['proxy_user']) &&
1089
isset($proxy_params['proxy_pass'])) {
1090
$rq->setProxy($proxy_params['proxy_host'],
1091
$proxy_params['proxy_port'],
1092
$proxy_params['proxy_user'],
1093
$proxy_params['proxy_pass']);
1094
} elseif (isset($proxy_params['proxy_host']) &&
1095
isset($proxy_params['proxy_port'])) {
1096
$rq->setProxy($proxy_params['proxy_host'],
1097
$proxy_params['proxy_port']);
1100
$result = $rq->sendRequest();
1101
if (PEAR::isError($result)) {
1102
return $this->_raiseSoapFault("Unable to retrieve WSDL $wsdl_fname," . $rq->getResponseCode(), $wsdl_fname);
1104
$file_data = $rq->getResponseBody();
1106
return $this->_raiseSoapFault("Unable to retrieve WSDL $wsdl_fname, no http body", $wsdl_fname);
1110
$md5_wsdl = md5($file_data);
1112
if ($this->_cacheUse) {
1113
$fp = fopen($cachename, "wb");
1114
fwrite($fp, $file_data);
1119
if ($this->_cacheUse && $cache && $cache != $md5_wsdl) {
1120
return $this->_raiseSoapFault('WSDL Checksum error!', $wsdl_fname);
1128
class SOAP_WSDL_Parser extends SOAP_Base
1132
* Define internal arrays of bindings, ports, operations,
1135
var $currentMessage;
1136
var $currentOperation;
1137
var $currentPortType;
1138
var $currentBinding;
1147
var $soapns = array('soap');
1152
var $element_stack = array();
1153
var $parentElement = '';
1156
var $schemaStatus = '';
1157
var $schema_stack = array();
1158
var $currentComplexType;
1159
var $schema_element_stack = array();
1160
var $currentElement;
1165
function SOAP_WSDL_Parser($uri, &$wsdl, $docs = false)
1167
parent::SOAP_Base('WSDLPARSER');
1168
$this->cache = new SOAP_WSDL_Cache($wsdl->cacheUse,
1172
$this->wsdl = &$wsdl;
1173
$this->docs = $docs;
1177
function parse($uri)
1179
// Check whether content has been read.
1180
$fd = $this->cache->get($uri, $this->wsdl->proxy);
1181
if (PEAR::isError($fd)) {
1182
return $this->_raiseSoapFault($fd);
1185
// Create an XML parser.
1186
$parser = xml_parser_create();
1187
xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
1188
xml_set_object($parser, $this);
1189
xml_set_element_handler($parser, 'startElement', 'endElement');
1191
xml_set_character_data_handler($parser, 'characterData');
1194
if (!xml_parse($parser, $fd, true)) {
1195
$detail = sprintf('XML error on line %d: %s',
1196
xml_get_current_line_number($parser),
1197
xml_error_string(xml_get_error_code($parser)));
1198
return $this->_raiseSoapFault("Unable to parse WSDL file $uri\n$detail");
1200
xml_parser_free($parser);
1205
* start-element handler
1207
function startElement($parser, $name, $attrs)
1209
// Get element prefix.
1210
$qname = new QName($name);
1211
if ($qname->prefix) {
1212
$ns = $qname->prefix;
1213
if ($ns && ((!$this->tns && strcasecmp($qname->name, 'definitions') == 0) || $ns == $this->tns)) {
1214
$name = $qname->name;
1217
$this->currentTag = $qname->name;
1218
$this->parentElement = '';
1219
$stack_size = count($this->element_stack);
1221
$this->parentElement = $this->element_stack[$stack_size - 1];
1223
$this->element_stack[] = $this->currentTag;
1225
// Find status, register data.
1226
switch ($this->status) {
1228
// sect 2.2 wsdl:types
1229
// children: xsd:schema
1231
$stack_size = count($this->schema_stack);
1233
$parent_tag = $this->schema_stack[$stack_size - 1];
1236
switch ($qname->name) {
1238
// No parent should be in the stack.
1239
if (!$parent_tag || $parent_tag == 'types') {
1240
if (array_key_exists('targetNamespace', $attrs)) {
1241
$this->schema = $this->wsdl->getNamespaceAttributeName($attrs['targetNamespace']);
1243
$this->schema = $this->wsdl->getNamespaceAttributeName($this->wsdl->tns);
1245
$this->wsdl->complexTypes[$this->schema] = array();
1246
$this->wsdl->elements[$this->schema] = array();
1251
if ($parent_tag == 'schema') {
1252
$this->currentComplexType = $attrs['name'];
1253
if (!isset($attrs['namespace'])) {
1254
$attrs['namespace'] = $this->schema;
1256
$this->wsdl->complexTypes[$this->schema][$this->currentComplexType] = $attrs;
1257
if (array_key_exists('base', $attrs)) {
1258
$qn = new QName($attrs['base']);
1259
$this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['type'] = $qn->name;
1260
$this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['namespace'] = $qn->prefix;
1262
$this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['type'] = 'Struct';
1264
$this->schemaStatus = 'complexType';
1266
$this->wsdl->elements[$this->schema][$this->currentElement]['complex'] = true;
1271
if (isset($attrs['type'])) {
1272
$qn = new QName($attrs['type']);
1273
$attrs['type'] = $qn->name;
1274
if ($qn->prefix && array_key_exists($qn->prefix, $this->wsdl->namespaces)) {
1275
$attrs['namespace'] = $qn->prefix;
1279
$parentElement = '';
1280
$stack_size = count($this->schema_element_stack);
1281
if ($stack_size > 0) {
1282
$parentElement = $this->schema_element_stack[$stack_size - 1];
1285
if (isset($attrs['ref'])) {
1286
$qn = new QName($attrs['ref']);
1287
$this->currentElement = $qn->name;
1289
$this->currentElement = $attrs['name'];
1291
$this->schema_element_stack[] = $this->currentElement;
1292
if (!isset($attrs['namespace'])) {
1293
$attrs['namespace'] = $this->schema;
1296
if ($parent_tag == 'schema') {
1297
$this->wsdl->elements[$this->schema][$this->currentElement] = $attrs;
1298
$this->wsdl->elements[$this->schema][$this->currentElement]['complex'] = false;
1299
$this->schemaStatus = 'element';
1300
} elseif ($this->currentComplexType) {
1301
// we're inside a complexType
1302
if ((isset($this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['order']) &&
1303
$this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['order'] == 'sequence')
1304
&& $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['type'] == 'Array') {
1305
$this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['arrayType'] = isset($attrs['type']) ? $attrs['type'] : null;
1307
$this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['elements'][$this->currentElement] = $attrs;
1309
$this->wsdl->elements[$this->schema][$parentElement]['elements'][$this->currentElement] = $attrs;
1313
case 'complexContent':
1314
case 'simpleContent':
1319
if ($this->schemaStatus == 'complexType') {
1320
if (!empty($attrs['base'])) {
1321
$qn = new QName($attrs['base']);
1322
$this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['type'] = $qn->name;
1324
// Types that extend from other types aren't
1325
// *of* those types. Reflect this by denoting
1326
// which type they extend. I'm leaving the
1327
// 'type' setting here since I'm not sure what
1328
// removing it might break at the moment.
1329
if ($qname->name == 'extension') {
1330
$this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['extends'] = $qn->name;
1333
$this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['type'] = 'Struct';
1339
if ($this->schemaStatus == 'complexType') {
1340
$this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['order'] = $qname->name;
1341
if (!isset($this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['type'])) {
1342
$this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['type'] = 'Array';
1348
$this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['order'] = $qname->name;
1349
if (!isset($this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['type'])) {
1350
$this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['type'] = 'Struct';
1355
$this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['order'] = $qname->name;
1356
if (!isset($this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['type'])) {
1357
$this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['type'] = 'Array';
1361
if ($this->schemaStatus == 'complexType') {
1362
if (isset($attrs['name'])) {
1363
$this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['attribute'][$attrs['name']] = $attrs;
1365
if (isset($attrs['ref'])) {
1366
$q = new QName($attrs['ref']);
1367
foreach ($attrs as $k => $v) {
1368
if ($k != 'ref' && strstr($k, $q->name)) {
1369
$vq = new QName($v);
1370
if ($q->name == 'arrayType') {
1371
$this->wsdl->complexTypes[$this->schema][$this->currentComplexType][$q->name] = $vq->name. $vq->arrayInfo;
1372
$this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['type'] = 'Array';
1373
$this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['namespace'] = $vq->prefix;
1375
$this->wsdl->complexTypes[$this->schema][$this->currentComplexType][$q->name] = $vq->name;
1385
$this->schema_stack[] = $qname->name;
1389
// sect 2.3 wsdl:message child wsdl:part
1390
switch ($qname->name) {
1393
if (isset($attrs['type'])) {
1394
$qn = new QName($attrs['type']);
1395
} elseif (isset($attrs['element'])) {
1396
$qn = new QName($attrs['element']);
1399
$attrs['type'] = $qn->name;
1400
$attrs['namespace'] = $qn->prefix;
1402
$this->wsdl->messages[$this->currentMessage][$attrs['name']] = $attrs;
1405
case 'documentation':
1415
switch ($qname->name) {
1418
// children: wsdl:input wsdl:output wsdl:fault
1419
$this->currentOperation = $attrs['name'];
1420
$this->wsdl->portTypes[$this->currentPortType][$this->currentOperation] = $attrs;
1426
// wsdl:input wsdl:output wsdl:fault
1427
// attributes: name message parameterOrder(optional)
1428
if ($this->currentOperation) {
1429
if (isset($this->wsdl->portTypes[$this->currentPortType][$this->currentOperation][$name])) {
1430
$this->wsdl->portTypes[$this->currentPortType][$this->currentOperation][$name] = array_merge($this->wsdl->portTypes[$this->currentPortType][$this->currentOperation][$name], $attrs);
1432
$this->wsdl->portTypes[$this->currentPortType][$this->currentOperation][$name] = $attrs;
1434
if (array_key_exists('message', $attrs)) {
1435
$qn = new QName($attrs['message']);
1436
$this->wsdl->portTypes[$this->currentPortType][$this->currentOperation][$name]['message'] = $qn->name;
1437
$this->wsdl->portTypes[$this->currentPortType][$this->currentOperation][$name]['namespace'] = $qn->prefix;
1442
case 'documentation':
1451
$ns = $qname->prefix ? $this->wsdl->namespaces[$qname->prefix] : SCHEMA_WSDL;
1455
// this deals with wsdl section 3 soap binding
1456
switch ($qname->name) {
1459
// soap:binding, attributes: transport(required), style(optional, default = document)
1460
// if style is missing, it is assumed to be 'document'
1461
if (!isset($attrs['style'])) {
1462
$attrs['style'] = 'document';
1464
$this->wsdl->bindings[$this->currentBinding] = array_merge($this->wsdl->bindings[$this->currentBinding], $attrs);
1469
// soap:operation, attributes: soapAction(required), style(optional, default = soap:binding:style)
1470
if (!isset($attrs['style'])) {
1471
$attrs['style'] = $this->wsdl->bindings[$this->currentBinding]['style'];
1473
if (isset($this->wsdl->bindings[$this->currentBinding]['operations'][$this->currentOperation])) {
1474
$this->wsdl->bindings[$this->currentBinding]['operations'][$this->currentOperation] = array_merge($this->wsdl->bindings[$this->currentBinding]['operations'][$this->currentOperation], $attrs);
1476
$this->wsdl->bindings[$this->currentBinding]['operations'][$this->currentOperation] = $attrs;
1482
// soap:body attributes:
1483
// part - optional. listed parts must appear in body, missing means all parts appear in body
1484
// use - required. encoded|literal
1485
// encodingStyle - optional. space seperated list of encodings (uri's)
1486
$this->wsdl->bindings[$this->currentBinding]
1487
['operations'][$this->currentOperation][$this->opStatus] = $attrs;
1492
// soap:fault attributes: name use encodingStyle namespace
1493
$this->wsdl->bindings[$this->currentBinding]
1494
['operations'][$this->currentOperation][$this->opStatus] = $attrs;
1499
// soap:header attributes: message part use encodingStyle namespace
1500
$this->wsdl->bindings[$this->currentBinding]
1501
['operations'][$this->currentOperation][$this->opStatus]['headers'][] = $attrs;
1506
// soap:header attributes: message part use encodingStyle namespace
1507
$header = count($this->wsdl->bindings[$this->currentBinding]
1508
['operations'][$this->currentOperation][$this->opStatus]['headers'])-1;
1509
$this->wsdl->bindings[$this->currentBinding]
1510
['operations'][$this->currentOperation][$this->opStatus]['headers'][$header]['fault'] = $attrs;
1513
case 'documentation':
1517
// error! not a valid element inside binding
1523
// XXX verify correct namespace
1524
// for now, default is the 'wsdl' namespace
1525
// other possible namespaces include smtp, http, etc. for alternate bindings
1526
switch ($qname->name) {
1529
// wsdl:operation attributes: name
1530
$this->currentOperation = $attrs['name'];
1537
// wsdl:input attributes: name
1538
$this->opStatus = $qname->name;
1541
case 'documentation':
1549
case SCHEMA_WSDL_HTTP:
1550
switch ($qname->name) {
1553
// http:binding attributes: verb
1554
// parent: wsdl:binding
1555
$this->wsdl->bindings[$this->currentBinding] = array_merge($this->wsdl->bindings[$this->currentBinding], $attrs);
1560
// http:operation attributes: location
1561
// parent: wsdl:operation
1562
$this->wsdl->bindings[$this->currentBinding]['operations']
1563
[$this->currentOperation] = $attrs;
1568
// http:urlEncoded attributes: location
1569
// parent: wsdl:input wsdl:output etc.
1570
$this->wsdl->bindings[$this->currentBinding]['operations'][$this->opStatus]
1571
[$this->currentOperation]['uri'] = 'urlEncoded';
1574
case 'urlReplacement':
1576
// http:urlReplacement attributes: location
1577
// parent: wsdl:input wsdl:output etc.
1578
$this->wsdl->bindings[$this->currentBinding]['operations'][$this->opStatus]
1579
[$this->currentOperation]['uri'] = 'urlReplacement';
1582
case 'documentation':
1592
// all mime parts are children of wsdl:input, wsdl:output, etc.
1593
// unsuported as of yet
1594
switch ($qname->name) {
1596
// sect 5.3 mime:content
1597
// <mime:content part="nmtoken"? type="string"?/>
1598
// part attribute only required if content is child of multipart related,
1599
// it contains the name of the part
1600
// type attribute contains the mime type
1601
case 'multipartRelated':
1602
// sect 5.4 mime:multipartRelated
1605
// sect 5.6 mime:mimeXml
1606
// <mime:mimeXml part="nmtoken"?/>
1608
case 'documentation':
1617
// DIME is defined in:
1618
// http://gotdotnet.com/team/xml_wsspecs/dime/WSDL-Extension-for-DIME.htm
1619
// all DIME parts are children of wsdl:input, wsdl:output, etc.
1620
// unsuported as of yet
1621
switch ($qname->name) {
1623
// sect 4.1 dime:message
1624
// appears in binding section
1625
$this->wsdl->bindings[$this->currentBinding]['dime'] = $attrs;
1638
$ns = $qname->prefix ? $this->wsdl->namespaces[$qname->prefix] : SCHEMA_WSDL;
1640
switch ($qname->name) {
1642
// sect 2.6 wsdl:port attributes: name binding
1643
$this->currentPort = $attrs['name'];
1644
$this->wsdl->services[$this->currentService]['ports'][$this->currentPort] = $attrs;
1645
// XXX hack to deal with binding namespaces
1646
$qn = new QName($attrs['binding']);
1647
$this->wsdl->services[$this->currentService]['ports'][$this->currentPort]['binding'] = $qn->name;
1648
$this->wsdl->services[$this->currentService]['ports'][$this->currentPort]['namespace'] = $qn->prefix;
1652
$this->wsdl->services[$this->currentService]['ports'][$this->currentPort]['address'] = $attrs;
1653
// what TYPE of port is it? SOAP or HTTP?
1654
$ns = $qname->prefix ? $this->wsdl->namespaces[$qname->prefix] : SCHEMA_WSDL;
1656
case SCHEMA_WSDL_HTTP:
1657
$this->wsdl->services[$this->currentService]['ports'][$this->currentPort]['type']='http';
1661
$this->wsdl->services[$this->currentService]['ports'][$this->currentPort]['type']='soap';
1665
// Shouldn't happen, we'll assume SOAP.
1666
$this->wsdl->services[$this->currentService]['ports'][$this->currentPort]['type']='soap';
1671
case 'documentation':
1679
// Top level elements found under wsdl:definitions.
1680
switch ($qname->name) {
1683
// WSDL 2.1.1 wsdl:import, XML Schema 4.2.3 xsd:import, XML Schema
1684
// 4.2.1 xsd:include attributes
1685
$this->status = 'types';
1686
if (isset($attrs['location']) || isset($attrs['schemaLocation'])) {
1687
$uri = isset($attrs['location']) ? $attrs['location'] : $attrs['schemaLocation'];
1688
$location = @parse_url($uri);
1689
if (!isset($location['scheme'])) {
1690
$base = @parse_url($this->uri);
1691
$uri = $this->mergeUrl($base, $uri);
1693
if (isset($this->wsdl->imports[$uri])) {
1696
$this->wsdl->imports[$uri] = $attrs;
1698
$import_parser_class = get_class($this);
1699
$import_parser = new $import_parser_class($uri, $this->wsdl, $this->docs);
1700
if ($import_parser->fault) {
1701
unset($this->wsdl->imports[$uri]);
1705
$this->status = 'types';
1709
// sect 2.2 wsdl:types
1710
$this->status = 'types';
1714
// We can hit this at the top level if we've been asked to
1715
// import an XSD file.
1716
if (!empty($attrs['targetNamespace'])) {
1717
$this->schema = $this->wsdl->getNamespaceAttributeName($attrs['targetNamespace']);
1719
$this->schema = $this->wsdl->getNamespaceAttributeName($this->wsdl->tns);
1721
$this->wsdl->complexTypes[$this->schema] = array();
1722
$this->wsdl->elements[$this->schema] = array();
1723
$this->schema_stack[] = $qname->name;
1724
$this->status = 'types';
1728
// sect 2.3 wsdl:message attributes: name children:wsdl:part
1729
$this->status = 'message';
1730
if (isset($attrs['name'])) {
1731
$this->currentMessage = $attrs['name'];
1732
$this->wsdl->messages[$this->currentMessage] = array();
1737
// sect 2.4 wsdl:portType
1739
// children: wsdl:operation
1740
$this->status = 'portType';
1741
$this->currentPortType = $attrs['name'];
1742
$this->wsdl->portTypes[$this->currentPortType] = array();
1746
// sect 2.5 wsdl:binding attributes: name type
1747
// children: wsdl:operation soap:binding http:binding
1748
if ($qname->prefix && $qname->prefix != $this->tns) {
1751
$this->status = 'binding';
1752
$this->currentBinding = $attrs['name'];
1753
$qn = new QName($attrs['type']);
1754
$this->wsdl->bindings[$this->currentBinding]['type'] = $qn->name;
1755
$this->wsdl->bindings[$this->currentBinding]['namespace'] = $qn->prefix;
1759
// sect 2.7 wsdl:service attributes: name children: ports
1760
$this->currentService = $attrs['name'];
1761
$this->wsdl->services[$this->currentService]['ports'] = array();
1762
$this->status = 'service';
1766
// sec 2.1 wsdl:definitions
1767
// attributes: name targetNamespace xmlns:*
1768
// children: wsdl:import wsdl:types wsdl:message wsdl:portType wsdl:binding wsdl:service
1769
$this->wsdl->definition = $attrs;
1770
foreach ($attrs as $key => $value) {
1771
if (strstr($key, 'xmlns:') !== false) {
1772
$qn = new QName($key);
1773
// XXX need to refactor ns handling.
1774
$this->wsdl->namespaces[$qn->name] = $value;
1775
$this->wsdl->ns[$value] = $qn->name;
1776
if ($key == 'targetNamespace' ||
1777
strcasecmp($value,SOAP_SCHEMA) == 0) {
1778
$this->soapns[] = $qn->name;
1780
if (in_array($value, $this->_XMLSchema)) {
1781
$this->wsdl->xsd = $value;
1786
if (isset($ns) && $ns) {
1787
$namespace = 'xmlns:' . $ns;
1788
if (!$this->wsdl->definition[$namespace]) {
1789
return $this->_raiseSoapFault("parse error, no namespace for $namespace", $this->uri);
1798
* end-element handler.
1800
function endElement($parser, $name)
1802
$stacksize = count($this->element_stack);
1804
if ($this->element_stack[$stacksize - 1] == 'definitions') {
1807
array_pop($this->element_stack);
1810
if (stristr($name, 'schema')) {
1811
array_pop($this->schema_stack);
1815
if ($this->schema) {
1816
array_pop($this->schema_stack);
1817
if (count($this->schema_stack) <= 1) {
1818
/* Correct the type for sequences with multiple
1820
if (isset($this->currentComplexType) && isset($this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['type'])
1821
&& $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['type'] == 'Array'
1822
&& array_key_exists('elements', $this->wsdl->complexTypes[$this->schema][$this->currentComplexType])
1823
&& count($this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['elements']) > 1) {
1824
$this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['type'] = 'Struct';
1827
if (stristr($name, 'complexType')) {
1828
$this->currentComplexType = '';
1829
if (count($this->schema_element_stack)) {
1830
$this->currentElement = array_pop($this->schema_element_stack);
1832
$this->currentElement = '';
1834
} elseif (stristr($name, 'element')) {
1835
if (count($this->schema_element_stack)) {
1836
$this->currentElement = array_pop($this->schema_element_stack);
1838
$this->currentElement = '';
1845
* Element content handler.
1847
function characterData($parser, $data)
1849
// Store the documentation in the WSDL file.
1850
if ($this->currentTag == 'documentation') {
1851
$data = trim(preg_replace('/\s+/', ' ', $data));
1852
if (!strlen($data)) {
1856
switch ($this->status) {
1858
$ptr =& $this->wsdl->services[$this->currentService];
1862
$ptr =& $this->wsdl->portTypes[$this->currentPortType][$this->currentOperation];
1866
$ptr =& $this->wsdl->bindings[$this->currentBinding];
1870
$ptr =& $this->wsdl->messages[$this->currentMessage];
1877
if (isset($this->currentComplexType) &&
1878
isset($this->wsdl->complexTypes[$this->schema][$this->currentComplexType])) {
1879
if ($this->currentElement) {
1880
$ptr =& $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['elements'][$this->currentElement];
1882
$ptr =& $this->wsdl->complexTypes[$this->schema][$this->currentComplexType];
1889
if (!isset($ptr['documentation'])) {
1890
$ptr['documentation'] = '';
1892
$ptr['documentation'] .= ' ';
1894
$ptr['documentation'] .= $data;
1900
* $parsed is an array returned by parse_url().
1904
function mergeUrl($parsed, $path)
1906
if (!is_array($parsed)) {
1911
if (!empty($parsed['scheme'])) {
1912
$sep = (strtolower($parsed['scheme']) == 'mailto' ? ':' : '://');
1913
$uri = $parsed['scheme'] . $sep;
1916
if (isset($parsed['pass'])) {
1917
$uri .= "$parsed[user]:$parsed[pass]@";
1918
} elseif (isset($parsed['user'])) {
1919
$uri .= "$parsed[user]@";
1922
if (isset($parsed['host'])) {
1923
$uri .= $parsed['host'];
1925
if (isset($parsed['port'])) {
1926
$uri .= ":$parsed[port]";
1928
if ($path[0] != '/' && isset($parsed['path'])) {
1929
if ($parsed['path'][strlen($parsed['path']) - 1] != '/') {
1930
$path = dirname($parsed['path']) . '/' . $path;
1932
$path = $parsed['path'] . $path;
1934
$path = $this->_normalize($path);
1936
$sep = $path[0] == '/' ? '' : '/';
1937
$uri .= $sep . $path;
1942
function _normalize($path_str)
1945
$strArr = preg_split('/(\/)/', $path_str, -1, PREG_SPLIT_NO_EMPTY);
1948
for ($i = 0; $i < count($strArr); $i++) {
1949
if ($strArr[$i] != ' ..') {
1950
if ($strArr[$i] != ' .') {
1951
$pwdArr[$j] = $strArr[$i];
1959
$pStr = implode('/', $pwdArr);
1960
$pwd = (strlen($pStr) > 0) ? ('/' . $pStr) : '/';
1967
* Parses the types and methods used in web service objects into the internal
1968
* data structures used by SOAP_WSDL.
1970
* Assumes the SOAP_WSDL class is unpopulated to start with.
1972
* @author Chris Coe <info@intelligentstreaming.com>
1974
class SOAP_WSDL_ObjectParser extends SOAP_Base
1977
* Target namespace for the WSDL document will have the following
1980
var $tnsPrefix = 'tns';
1983
* Reference to the SOAP_WSDL object to populate.
1990
* @param object|array $objects Reference to the object or array of
1992
* @param SOAP_WSDL $wsdl Reference to the SOAP_WSDL object to
1994
* @param string $targetNamespace The target namespace of schema types
1996
* @param string $service_name Name of the WSDL <service>.
1997
* @param string $service_desc Optional description of the WSDL
2000
function SOAP_WSDL_ObjectParser($objects, &$wsdl, $targetNamespace,
2001
$service_name, $service_desc = '')
2003
parent::SOAP_Base('WSDLOBJECTPARSER');
2005
$this->wsdl = &$wsdl;
2007
// Set up the SOAP_WSDL object
2008
$this->_initialise($service_name);
2010
// Parse each web service object
2011
$wsdl_ref = is_array($objects) ? $objects : array($objects);
2013
foreach ($wsdl_ref as $ref_item) {
2014
if (!is_object($ref_item)) {
2015
$this->_raiseSoapFault('Invalid web service object passed to object parser');
2019
if (!$this->_parse($ref_item, $targetNamespace, $service_name)) {
2024
// Build bindings from abstract data.
2025
if ($this->fault == null) {
2026
$this->_generateBindingsAndServices($targetNamespace, $service_name, $service_desc);
2031
* Initialise the SOAP_WSDL tree (destructive).
2033
* If the object has already been initialised, the only effect
2034
* will be to change the tns namespace to the new service name.
2036
* @param $service_name Name of the WSDL <service>
2039
function _initialise($service_name)
2041
// Set up the basic namespaces that all WSDL definitions use.
2042
$this->wsdl->namespaces['wsdl'] = SCHEMA_WSDL; // WSDL language
2043
$this->wsdl->namespaces['soap'] = SCHEMA_SOAP; // WSDL SOAP bindings
2044
$this->wsdl->namespaces[$this->tnsPrefix] = 'urn:' . $service_name; // Target namespace
2045
$this->wsdl->namespaces['xsd'] = array_search('xsd', $this->_namespaces); // XML Schema
2046
$this->wsdl->namespaces[SOAP_BASE::SOAPENCPrefix()] = array_search(SOAP_BASE::SOAPENCPrefix(), $this->_namespaces); // SOAP types
2048
// XXX Refactor $namespace/$ns for Shane :-)
2049
unset($this->wsdl->ns['urn:' . $service_name]);
2050
$this->wsdl->ns += array_flip($this->wsdl->namespaces);
2052
// Imports are not implemented in WSDL generation from classes.
2053
// *** <wsdl:import> ***
2057
* Parser - takes a single object to add to tree (non-destructive).
2061
* @param object $object Reference to the object to parse.
2062
* @param string $schemaNamespace
2063
* @param string $service_name Name of the WSDL <service>.
2065
function _parse($object, $schemaNamespace, $service_name)
2067
// Create namespace prefix for the schema
2068
list($schPrefix,) = $this->_getTypeNs('{' . $schemaNamespace . '}');
2070
// Parse all the types defined by the object in whatever
2071
// schema language we are using (currently __typedef arrays)
2072
// *** <wsdl:types> ***
2073
foreach ($object->__typedef as $typeName => $typeValue) {
2074
// Get/create namespace definition
2075
list($nsPrefix, $typeName) = $this->_getTypeNs($typeName);
2077
// Create type definition
2078
$this->wsdl->complexTypes[$schPrefix][$typeName] = array('name' => $typeName);
2079
$thisType =& $this->wsdl->complexTypes[$schPrefix][$typeName];
2081
// According to Dmitri's documentation, __typedef comes in two
2083
// Array = array(array("item" => "value"))
2084
// Struct = array("item1" => "value1", "item2" => "value2", ...)
2085
if (is_array($typeValue)) {
2086
if (is_array(current($typeValue)) && count($typeValue) == 1
2087
&& count(current($typeValue)) == 1) {
2089
$thisType['type'] = 'Array';
2090
$nsType = current(current($typeValue));
2091
list($nsPrefix, $typeName) = $this->_getTypeNs($nsType);
2092
$thisType['namespace'] = $nsPrefix;
2093
$thisType['arrayType'] = $typeName . '[]';
2094
} elseif (!is_array(current($typeValue))) {
2096
$thisType['type'] = 'Struct';
2097
$thisType['order'] = 'all';
2098
$thisType['namespace'] = $nsPrefix;
2099
$thisType['elements'] = array();
2101
foreach ($typeValue as $elementName => $elementType) {
2102
list($nsPrefix, $typeName) = $this->_getTypeNs($elementType);
2103
$thisType['elements'][$elementName]['name'] = $elementName;
2104
$thisType['elements'][$elementName]['type'] = $typeName;
2105
$thisType['elements'][$elementName]['namespace'] = $nsPrefix;
2109
return $this->_raiseSoapFault("The type definition for $nsPrefix:$typeName is invalid.", 'urn:' . get_class($object));
2113
return $this->_raiseSoapFault("The type definition for $nsPrefix:$typeName is invalid.", 'urn:' . get_class($object));
2117
// Create an empty element array with the target namespace
2118
// prefix, to match the results of WSDL parsing.
2119
$this->wsdl->elements[$schPrefix] = array();
2121
// Populate tree with message information
2122
// *** <wsdl:message> ***
2123
foreach ($object->__dispatch_map as $operationName => $messages) {
2124
// We need at least 'in' and 'out' parameters.
2125
if (!isset($messages['in']) || !isset($messages['out'])) {
2126
return $this->_raiseSoapFault('The dispatch map for the method "' . $operationName . '" is missing an "in" or "out" definition.', 'urn:' . get_class($object));
2128
foreach ($messages as $messageType => $messageParts) {
2129
unset($thisMessage);
2131
switch ($messageType) {
2133
$this->wsdl->messages[$operationName . 'Request'] = array();
2134
$thisMessage =& $this->wsdl->messages[$operationName . 'Request'];
2138
$this->wsdl->messages[$operationName . 'Response'] = array();
2139
$thisMessage =& $this->wsdl->messages[$operationName . 'Response'];
2151
if (isset($thisMessage)) {
2152
foreach ($messageParts as $partName => $partType) {
2153
list ($nsPrefix, $typeName) = $this->_getTypeNs($partType);
2155
$thisMessage[$partName] = array(
2156
'name' => $partName,
2157
'type' => $typeName,
2158
'namespace' => $nsPrefix
2165
// Populate tree with portType information
2166
// XXX Current implementation only supports one portType that
2167
// encompasses all of the operations available.
2168
// *** <wsdl:portType> ***
2169
if (!isset($this->wsdl->portTypes[$service_name . 'Port'])) {
2170
$this->wsdl->portTypes[$service_name . 'Port'] = array();
2172
$thisPortType =& $this->wsdl->portTypes[$service_name . 'Port'];
2174
foreach ($object->__dispatch_map as $operationName => $messages) {
2175
$thisPortType[$operationName] = array('name' => $operationName);
2177
foreach ($messages as $messageType => $messageParts) {
2178
switch ($messageType) {
2180
$thisPortType[$operationName]['input'] = array(
2181
'message' => $operationName . 'Request',
2182
'namespace' => $this->tnsPrefix);
2186
$thisPortType[$operationName]['output'] = array(
2187
'message' => $operationName . 'Response',
2188
'namespace' => $this->tnsPrefix);
2198
* Takes all the abstract WSDL data and builds concrete bindings and
2199
* services (destructive).
2202
* @todo Current implementation discards $service_desc.
2204
* @param string $schemaNamespace Namespace for types etc.
2205
* @param string $service_name Name of the WSDL <service>.
2206
* @param string $service_desc Optional description of the WSDL
2209
function _generateBindingsAndServices($schemaNamespace, $service_name,
2212
// Populate tree with bindings information
2213
// XXX Current implementation only supports one binding that
2214
// matches the single portType and all of its operations.
2215
// XXX Is this the correct use of $schemaNamespace here?
2216
// *** <wsdl:binding> ***
2217
$this->wsdl->bindings[$service_name . 'Binding'] = array(
2218
'type' => $service_name . 'Port',
2219
'namespace' => $this->tnsPrefix,
2221
'transport' => SCHEMA_SOAP_HTTP,
2222
'operations' => array());
2223
$thisBinding =& $this->wsdl->bindings[$service_name . 'Binding'];
2225
foreach ($this->wsdl->portTypes[$service_name . 'Port'] as $operationName => $operationData) {
2226
$thisBinding['operations'][$operationName] = array(
2227
'soapAction' => $schemaNamespace . '#' . $operationName,
2228
'style' => $thisBinding['style']);
2230
foreach (array('input', 'output') as $messageType)
2231
if (isset($operationData[$messageType])) {
2232
$thisBinding['operations'][$operationName][$messageType] = array(
2234
'namespace' => $schemaNamespace,
2235
'encodingStyle' => SOAP_SCHEMA_ENCODING);
2239
// Populate tree with service information
2240
// XXX Current implementation supports one service which groups
2241
// all of the ports together, one port per binding
2242
// *** <wsdl:service> ***
2244
$this->wsdl->services[$service_name . 'Service'] = array('ports' => array());
2245
$thisService =& $this->wsdl->services[$service_name . 'Service']['ports'];
2246
$https = (isset($_SERVER['HTTPS']) && ($_SERVER['HTTPS'] == 'on')) ||
2247
getenv('SSL_PROTOCOL_VERSION');
2249
foreach ($this->wsdl->bindings as $bindingName => $bindingData) {
2250
$thisService[$bindingData['type']] = array(
2251
'name' => $bindingData['type'],
2252
'binding' => $bindingName,
2253
'namespace' => $this->tnsPrefix,
2254
'address' => array('location' =>
2255
($https ? 'https://' : 'http://') .
2256
$_SERVER['SERVER_NAME'] . $_SERVER['PHP_SELF'] .
2257
(isset($_SERVER['QUERY_STRING']) ? '?' . $_SERVER['QUERY_STRING'] : '')),
2262
$this->wsdl->set_service($service_name . 'Service');
2263
$this->wsdl->uri = $this->wsdl->namespaces[$this->tnsPrefix];
2265
// Create WSDL definition
2266
// *** <wsdl:definitions> ***
2268
$this->wsdl->definition = array(
2269
'name' => $service_name,
2270
'targetNamespace' => $this->wsdl->namespaces[$this->tnsPrefix],
2271
'xmlns' => SCHEMA_WSDL);
2273
foreach ($this->wsdl->namespaces as $nsPrefix => $namespace) {
2274
$this->wsdl->definition['xmlns:' . $nsPrefix] = $namespace;
2279
* This function is adapted from Dmitri V's implementation of
2280
* DISCO/WSDL generation. It separates namespace from type name in
2281
* a __typedef key and creates a new namespace entry in the WSDL
2282
* structure if the namespace has not been used before. The
2283
* namespace prefix and type name are returned. If no namespace is
2284
* specified, xsd is assumed.
2286
* We will not need this function anymore once __typedef is
2289
function _getTypeNs($type)
2291
preg_match_all('/\{(.*)\}/sm', $type, $m);
2292
if (!empty($m[1][0])) {
2293
if (!isset($this->wsdl->ns[$m[1][0]])) {
2294
$ns_pref = 'ns' . count($this->wsdl->namespaces);
2295
$this->wsdl->ns[$m[1][0]] = $ns_pref;
2296
$this->wsdl->namespaces[$ns_pref] = $m[1][0];
2298
$typens = $this->wsdl->ns[$m[1][0]];
2299
$type = str_replace($m[0][0], '', $type);
2304
return array($typens, $type);