home *** CD-ROM | disk | FTP | other *** search
/ Cricao de Sites - 650 Layouts Prontos / WebMasters.iso / Servidores / xampp-win32-1.6.7-installer.exe / php / PEAR / SOAP / WSDL.php < prev   
Encoding:
PHP Script  |  2008-07-02  |  87.2 KB  |  2,281 lines

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