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