* @version v 0.6.3 * @access public */ class nusoap_base { var $title = 'NuSOAP'; var $version = '0.6.3'; var $error_str = false; var $debug_str = ''; // toggles automatic encoding of special characters var $charencoding = true; /** * set schema version * * @var XMLSchemaVersion * @access public */ var $XMLSchemaVersion = 'http://www.w3.org/2001/XMLSchema'; /** * set default encoding * * @var soap_defencoding * @access public */ //var $soap_defencoding = 'UTF-8'; var $soap_defencoding = 'ISO-8859-1'; /** * load namespace uris into an array of uri => prefix * * @var namespaces * @access public */ var $namespaces = array( 'SOAP-ENV' => 'http://schemas.xmlsoap.org/soap/envelope/', 'xsd' => 'http://www.w3.org/2001/XMLSchema', 'xsi' => 'http://www.w3.org/2001/XMLSchema-instance', 'SOAP-ENC' => 'http://schemas.xmlsoap.org/soap/encoding/', 'si' => 'http://soapinterop.org/xsd'); /** * load types into typemap array * is this legacy yet? * no, this is used by the xmlschema class to verify type => namespace mappings. * @var typemap * @access public */ var $typemap = array( 'http://www.w3.org/2001/XMLSchema' => array( 'string' => 'string', 'boolean' => 'boolean', 'float' => 'double', 'double' => 'double', 'decimal' => 'double', 'duration' => '', 'dateTime' => 'string', 'time' => 'string', 'date' => 'string', 'gYearMonth' => '', 'gYear' => '', 'gMonthDay' => '', 'gDay' => '', 'gMonth' => '', 'hexBinary' => 'string', 'base64Binary' => 'string', // derived datatypes 'normalizedString' => 'string', 'token' => 'string', 'language' => '', 'NMTOKEN' => '', 'NMTOKENS' => '', 'Name' => '', 'NCName' => '', 'ID' => '', 'IDREF' => '', 'IDREFS' => '', 'ENTITY' => '', 'ENTITIES' => '', 'integer' => 'integer', 'nonPositiveInteger' => 'integer', 'negativeInteger' => 'integer', 'long' => 'integer', 'int' => 'integer', 'short' => 'integer', 'byte' => 'integer', 'nonNegativeInteger' => 'integer', 'unsignedLong' => '', 'unsignedInt' => '', 'unsignedShort' => '', 'unsignedByte' => '', 'positiveInteger' => ''), 'http://www.w3.org/1999/XMLSchema' => array( 'i4' => '', 'int' => 'integer', 'boolean' => 'boolean', 'string' => 'string', 'double' => 'double', 'float' => 'double', 'dateTime' => 'string', 'timeInstant' => 'string', 'base64Binary' => 'string', 'base64' => 'string', 'ur-type' => 'array'), 'http://soapinterop.org/xsd' => array('SOAPStruct' => 'struct'), 'http://schemas.xmlsoap.org/soap/encoding/' => array('base64' => 'string', 'array' => 'array', 'Array' => 'array'), 'http://xml.apache.org/xml-soap' => array('Map') ); /** * entities to convert * * @var xmlEntities * @access public */ var $xmlEntities = array('quot' => '"', 'amp' => '&', 'lt' => '<', 'gt' => '>', 'apos' => "'"); /** * adds debug data to the class level debug string * * @param string $string debug data * @access private */ function debug($string) { $this->debug_str .= get_class($this) . ": $string\n"; } /** * returns error string if present * * @return boolean $string error string * @access public */ function getError() { if ($this->error_str != '') { return $this->error_str; } return false; } /** * sets error string * * @return boolean $string error string * @access private */ function setError($str) { $this->error_str = $str; } /** * serializes PHP values in accordance w/ section 5. Type information is * not serialized if $use == 'literal'. * * @return string * @access public */ function serialize_val($val, $name = false, $type = false, $name_ns = false, $type_ns = false, $attributes = false, $use = 'encoded') { if (is_object($val) && strtolower(get_class($val)) == 'soapval') { return $val->serialize($use); } $this->debug("in serialize_val: $val, $name, $type, $name_ns, $type_ns, $attributes, $use"); // if no name, use item $name = (!$name || is_numeric($name)) ? 'soapVal' : $name; // if name has ns, add ns prefix to name $xmlns = ''; if ($name_ns) { $prefix = 'nu' . rand(1000, 9999); $name = $prefix . ':' . $name; $xmlns .= " xmlns:$prefix=\"$name_ns\""; } // if type is prefixed, create type prefix if ($type_ns != '' && $type_ns == $this->namespaces['xsd']) { // need to fix this. shouldn't default to xsd if no ns specified // w/o checking against typemap $type_prefix = 'xsd'; } elseif ($type_ns) { $type_prefix = 'ns' . rand(1000, 9999); $xmlns .= " xmlns:$type_prefix=\"$type_ns\""; } // serialize attributes if present if ($attributes) { foreach ($attributes as $k => $v) { $atts .= " $k=\"$v\""; } } // serialize if an xsd built-in primitive type if ($type != '' && isset($this->typemap[$this->XMLSchemaVersion][$type])) { if ($use == 'literal') { return "<$name$xmlns>$val"; } else { return "<$name$xmlns xsi:type=\"xsd:$type\">$val"; } } // detect type and serialize $xml = ''; $atts = ''; switch (true) { case ($type == '' && is_null($val)): if ($use == 'literal') { // TODO: depends on nillable $xml .= "<$name$xmlns/>"; } else { $xml .= "<$name$xmlns xsi:type=\"xsd:nil\"/>"; } break; case (is_bool($val) || $type == 'boolean'): if (!$val) { $val = 0; } if ($use == 'literal') { $xml .= "<$name$xmlns $atts>$val"; } else { $xml .= "<$name$xmlns xsi:type=\"xsd:boolean\"$atts>$val"; } break; case (is_int($val) || is_long($val) || $type == 'int'): if ($use == 'literal') { $xml .= "<$name$xmlns $atts>$val"; } else { $xml .= "<$name$xmlns xsi:type=\"xsd:int\"$atts>$val"; } break; case (is_float($val) || is_double($val) || $type == 'float'): if ($use == 'literal') { $xml .= "<$name$xmlns $atts>$val"; } else { $xml .= "<$name$xmlns xsi:type=\"xsd:float\"$atts>$val"; } break; case (is_string($val) || $type == 'string'): if ($this->charencoding) { $val = htmlspecialchars($val, ENT_QUOTES); } if ($use == 'literal') { $xml .= "<$name$xmlns $atts>$val"; } else { $xml .= "<$name$xmlns xsi:type=\"xsd:string\"$atts>$val"; } break; case is_object($val): $name = strtolower(get_class($val)); foreach (get_object_vars($val) as $k => $v) { $pXml = isset($pXml) ? $pXml . $this->serialize_val($v, $k, false, false, false, false, $use) : $this->serialize_val($v, $k, false, false, false, false, $use); } $xml .= '<' . $name . '>' . $pXml . ''; break; break; case (is_array($val) || $type): // detect if struct or array $keyList = array_keys($val); $valueType = 'arraySimple'; foreach ($keyList as $keyListValue) { if (!is_int($keyListValue)) { $valueType = 'arrayStruct'; break; } } if ($valueType == 'arraySimple' || ereg('^ArrayOf', $type)) { $i = 0; if (is_array($val) && count($val) > 0) { foreach ($val as $v) { if (is_object($v) && strtolower(get_class($v)) == 'soapval') { $tt = $v->type; } else { $tt = gettype($v); } $array_types[$tt] = 1; $xml .= $this->serialize_val($v, 'item', false, false, false, false, $use); if (is_array($v) && is_numeric(key($v))) { $i += sizeof($v); } else { ++$i; } } if (count($array_types) > 1) { $array_typename = 'xsd:ur-type'; } elseif (isset($tt) && isset($this->typemap[$this->XMLSchemaVersion][$tt])) { $array_typename = 'xsd:' . $tt; } elseif ($tt == 'array' || $tt == 'Array') { $array_typename = 'SOAP-ENC:Array'; } else { $array_typename = $tt; } if (isset($array_types['array'])) { $array_type = $i . "," . $i; } else { $array_type = $i; } if ($use == 'literal') { $xml = "<$name $atts>" . $xml . ""; } else { $xml = "<$name xsi:type=\"SOAP-ENC:Array\" SOAP-ENC:arrayType=\"" . $array_typename . "[$array_type]\"$atts>" . $xml . ""; } // empty array } else { if ($use == 'literal') { $xml = "<$name $atts>" . $xml . ""; ; } else { $xml = "<$name xsi:type=\"SOAP-ENC:Array\" $atts>" . $xml . ""; ; } } } else { // got a struct if (isset($type) && isset($type_prefix)) { $type_str = " xsi:type=\"$type_prefix:$type\""; } else { $type_str = ''; } if ($use == 'literal') { $xml .= "<$name$xmlns $atts>"; } else { $xml .= "<$name$xmlns$type_str$atts>"; } foreach ($val as $k => $v) { $xml .= $this->serialize_val($v, $k, false, false, false, false, $use); } $xml .= ""; } break; default: $xml .= 'not detected, got ' . gettype($val) . ' for ' . $val; break; } return $xml; } /** * serialize message * * @param string body * @param string headers * @param array namespaces * @param string style * @return string message * @access public */ function serializeEnvelope($body, $headers = false, $namespaces = array(), $style = 'rpc') { // serialize namespaces $ns_string = ''; foreach (array_merge($this->namespaces, $namespaces) as $k => $v) { $ns_string .= " xmlns:$k=\"$v\""; } if ($style == 'rpc') { $ns_string = ' SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"' . $ns_string; } // serialize headers if ($headers) { $headers = "" . $headers . ""; } // serialize envelope return 'soap_defencoding . '"?' . ">" . '" . $headers . "" . $body . "" . ""; } function formatDump($str) { $str = htmlspecialchars($str); return nl2br($str); } /** * returns the local part of a prefixed string * returns the original string, if not prefixed * * @param string * @return string * @access public */ function getLocalPart($str) { if ($sstr = strrchr($str, ':')) { // get unqualified name return substr($sstr, 1); } else { return $str; } } /** * returns the prefix part of a prefixed string * returns false, if not prefixed * * @param string * @return mixed * @access public */ function getPrefix($str) { if ($pos = strrpos($str, ':')) { // get prefix return substr($str, 0, $pos); } return false; } function varDump($data) { ob_start(); var_dump($data); $ret_val = ob_get_contents(); ob_end_clean(); return $ret_val; } } // XML Schema Datatype Helper Functions //xsd:dateTime helpers /** * convert unix timestamp to ISO 8601 compliant date string * * @param string $timestamp Unix time stamp * @access public */ function timestamp_to_iso8601($timestamp, $utc = true) { $datestr = date('Y-m-d\TH:i:sO', $timestamp); if ($utc) { $eregStr = '([0-9]{4})-' . // centuries & years CCYY- '([0-9]{2})-' . // months MM- '([0-9]{2})' . // days DD 'T' . // separator T '([0-9]{2}):' . // hours hh: '([0-9]{2}):' . // minutes mm: '([0-9]{2})(\.[0-9]*)?' . // seconds ss.ss... '(Z|[+\-][0-9]{2}:?[0-9]{2})?'; // Z to indicate UTC, -/+HH:MM:SS.SS... for local tz's if (ereg($eregStr, $datestr, $regs)) { return sprintf('%04d-%02d-%02dT%02d:%02d:%02dZ', $regs[1], $regs[2], $regs[3], $regs[4], $regs[5], $regs[6]); } return false; } else { return $datestr; } } /** * convert ISO 8601 compliant date string to unix timestamp * * @param string $datestr ISO 8601 compliant date string * @access public */ function iso8601_to_timestamp($datestr) { $eregStr = '([0-9]{4})-' . // centuries & years CCYY- '([0-9]{2})-' . // months MM- '([0-9]{2})' . // days DD 'T' . // separator T '([0-9]{2}):' . // hours hh: '([0-9]{2}):' . // minutes mm: '([0-9]{2})(\.[0-9]+)?' . // seconds ss.ss... '(Z|[+\-][0-9]{2}:?[0-9]{2})?'; // Z to indicate UTC, -/+HH:MM:SS.SS... for local tz's if (ereg($eregStr, $datestr, $regs)) { // not utc if ($regs[8] != 'Z') { $op = substr($regs[8], 0, 1); $h = substr($regs[8], 1, 2); $m = substr($regs[8], strlen($regs[8]) - 2, 2); if ($op == '-') { $regs[4] = $regs[4] + $h; $regs[5] = $regs[5] + $m; } elseif ($op == '+') { $regs[4] = $regs[4] - $h; $regs[5] = $regs[5] - $m; } } return strtotime("$regs[1]-$regs[2]-$regs[3] $regs[4]:$regs[5]:$regs[6]Z"); } else { return false; } } /** * soap_fault class, allows for creation of faults * mainly used for returning faults from deployed functions * in a server instance. * @author Dietrich Ayala * @version v 0.6.3 * @access public */ class soap_fault extends nusoap_base { var $faultcode; var $faultactor; var $faultstring; var $faultdetail; /** * constructor * * @param string $faultcode (client | server) * @param string $faultactor only used when msg routed between multiple actors * @param string $faultstring human readable error message * @param string $faultdetail */ function soap_fault($faultcode, $faultactor = '', $faultstring = '', $faultdetail = '') { $this->faultcode = $faultcode; $this->faultactor = $faultactor; $this->faultstring = $faultstring; $this->faultdetail = $faultdetail; } /** * serialize a fault * * @access public */ function serialize() { $ns_string = ''; foreach ($this->namespaces as $k => $v) { $ns_string .= "\n xmlns:$k=\"$v\""; } $return_msg = '\n" . '\n" . '' . '' . '' . $this->faultcode . '' . '' . $this->faultactor . '' . '' . $this->faultstring . '' . '' . $this->serialize_val($this->faultdetail) . '' . '' . '' . ''; return $return_msg; } } /** * parses an XML Schema, allows access to it's data, other utility methods * no validation... yet. * very experimental and limited. As is discussed on XML-DEV, I'm one of the people * that just doesn't have time to read the spec(s) thoroughly, and just have a couple of trusty * tutorials I refer to :) * * @author Dietrich Ayala * @version v 0.6.3 * @access public */ class XMLSchema extends nusoap_base { // files var $schema = ''; var $xml = ''; // define internal arrays of bindings, ports, operations, messages, etc. var $complexTypes = array(); // target namespace var $schemaTargetNamespace = ''; // parser vars var $parser; var $position; var $depth = 0; var $depth_array = array(); /** * constructor * * @param string $schema schema document URI * @param string $xml xml document URI * @access public */ function XMLSchema($schema = '', $xml = '') { $this->debug('xmlschema class instantiated, inside constructor'); // files $this->schema = $schema; $this->xml = $xml; // parse schema file if ($schema != '') { $this->debug('initial schema file: ' . $schema); $this->parseFile($schema); } // parse xml file if ($xml != '') { $this->debug('initial xml file: ' . $xml); $this->parseFile($xml); } } /** * parse an XML file * * @param string $xml, path/URL to XML file * @param string $type, (schema | xml) * @return boolean * @access public */ function parseFile($xml, $type) { // parse xml file if ($xml != "") { $this->debug('parsing $xml'); $xmlStr = @join("", @file($xml)); if ($xmlStr == "") { $this->setError('No file at the specified URL: ' . $xml); return false; } else { $this->parseString($xmlStr, $type); return true; } } return false; } /** * parse an XML string * * @param string $xml path or URL * @param string $type, (schema|xml) * @access private */ function parseString($xml, $type) { // parse xml string if ($xml != "") { // Create an XML parser. $this->parser = xml_parser_create(); // Set the options for parsing the XML data. xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING, 0); // Set the object for the parser. xml_set_object($this->parser, $this); // Set the element handlers for the parser. if ($type == "schema") { xml_set_element_handler($this->parser, 'schemaStartElement', 'schemaEndElement'); xml_set_character_data_handler($this->parser, 'schemaCharacterData'); } elseif ($type == "xml") { xml_set_element_handler($this->parser, 'xmlStartElement', 'xmlEndElement'); xml_set_character_data_handler($this->parser, 'xmlCharacterData'); } // Parse the XML file. if (!xml_parse($this->parser, $xml, true)) { // Display an error message. $errstr = sprintf('XML error on line %d: %s', xml_get_current_line_number($this->parser), xml_error_string(xml_get_error_code($this->parser)) ); $this->debug('XML parse error: ' . $errstr); $this->setError('Parser error: ' . $errstr); } xml_parser_free($this->parser); } else { $this->debug('no xml passed to parseString()!!'); $this->setError('no xml passed to parseString()!!'); } } /** * start-element handler * * @param string $parser XML parser object * @param string $name element name * @param string $attrs associative array of attributes * @access private */ function schemaStartElement($parser, $name, $attrs) { // position in the total number of elements, starting from 0 $pos = $this->position++; $depth = $this->depth++; // set self as current value for this depth $this->depth_array[$depth] = $pos; // get element prefix if ($prefix = $this->getPrefix($name)) { // get unqualified name $name = $this->getLocalPart($name); } else { $prefix = ''; } // loop thru attributes, expanding, and registering namespace declarations if (count($attrs) > 0) { foreach ($attrs as $k => $v) { // if ns declarations, add to class level array of valid namespaces if (ereg("^xmlns", $k)) { //$this->xdebug("$k: $v"); //$this->xdebug('ns_prefix: '.$this->getPrefix($k)); if ($ns_prefix = substr(strrchr($k, ':'), 1)) { $this->namespaces[$ns_prefix] = $v; } else { $this->namespaces['ns' . (count($this->namespaces) + 1)] = $v; } if ($v == 'http://www.w3.org/2001/XMLSchema' || $v == 'http://www.w3.org/1999/XMLSchema') { $this->XMLSchemaVersion = $v; $this->namespaces['xsi'] = $v . '-instance'; } } } foreach ($attrs as $k => $v) { // expand each attribute $k = strpos($k, ':') ? $this->expandQname($k) : $k; $v = strpos($v, ':') ? $this->expandQname($v) : $v; $eAttrs[$k] = $v; } $attrs = $eAttrs; } else { $attrs = array(); } // find status, register data switch ($name) { case ('all' | 'choice' | 'sequence'): //$this->complexTypes[$this->currentComplexType]['compositor'] = 'all'; $this->complexTypes[$this->currentComplexType]['compositor'] = $name; if ($name == 'all') { $this->complexTypes[$this->currentComplexType]['phpType'] = 'struct'; } break; case 'attribute': //$this->xdebug("parsing attribute $attrs[name] $attrs[ref] of value: ".$attrs['http://schemas.xmlsoap.org/wsdl/:arrayType']); if (isset($attrs['name'])) { $this->attributes[$attrs['name']] = $attrs; $aname = $attrs['name']; } elseif (isset($attrs['ref']) && $attrs['ref'] == 'http://schemas.xmlsoap.org/soap/encoding/:arrayType') { $aname = $attrs['http://schemas.xmlsoap.org/wsdl/:arrayType']; } elseif (isset($attrs['ref'])) { $aname = $attrs['ref']; $this->attributes[$attrs['ref']] = $attrs; } if (isset($this->currentComplexType)) { $this->complexTypes[$this->currentComplexType]['attrs'][$aname] = $attrs; } elseif (isset($this->currentElement)) { $this->elements[$this->currentElement]['attrs'][$aname] = $attrs; } // arrayType attribute if (isset($attrs['http://schemas.xmlsoap.org/wsdl/:arrayType']) || $this->getLocalPart($aname) == 'arrayType') { $this->complexTypes[$this->currentComplexType]['phpType'] = 'array'; $prefix = $this->getPrefix($aname); if (isset($attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'])) { $v = $attrs['http://schemas.xmlsoap.org/wsdl/:arrayType']; } else { $v = ''; } if (strpos($v, '[,]')) { $this->complexTypes[$this->currentComplexType]['multidimensional'] = true; } $v = substr($v, 0, strpos($v, '[')); // clip the [] if (!strpos($v, ':') && isset($this->typemap[$this->XMLSchemaVersion][$v])) { $v = $this->XMLSchemaVersion . ':' . $v; } $this->complexTypes[$this->currentComplexType]['arrayType'] = $v; } break; case 'complexType': if (isset($attrs['name'])) { $this->currentElement = false; $this->currentComplexType = $attrs['name']; $this->complexTypes[$this->currentComplexType] = $attrs; $this->complexTypes[$this->currentComplexType]['typeClass'] = 'complexType'; if (isset($attrs['base']) && ereg(':Array$', $attrs['base'])) { $this->complexTypes[$this->currentComplexType]['phpType'] = 'array'; } else { $this->complexTypes[$this->currentComplexType]['phpType'] = 'struct'; } $this->xdebug('processing complexType ' . $attrs['name']); } break; case 'element': if (isset($attrs['type'])) { $this->xdebug("processing element " . $attrs['name']); $this->currentElement = $attrs['name']; $this->elements[$attrs['name']] = $attrs; $this->elements[$attrs['name']]['typeClass'] = 'element'; $ename = $attrs['name']; } elseif (isset($attrs['ref'])) { $ename = $attrs['ref']; } else { $this->xdebug('adding complexType ' . $attrs['name']); $this->currentComplexType = $attrs['name']; $this->complexTypes[$attrs['name']] = $attrs; $this->complexTypes[$attrs['name']]['element'] = 1; $this->complexTypes[$this->currentComplexType]['phpType'] = 'struct'; } if (isset($ename) && $this->currentComplexType) { $this->complexTypes[$this->currentComplexType]['elements'][$ename] = $attrs; } break; case 'restriction': $this->xdebug("in restriction for ct: $this->currentComplexType and ce: $this->currentElement"); if ($this->currentElement) { $this->elements[$this->currentElement]['type'] = $attrs['base']; } elseif ($this->currentComplexType) { $this->complexTypes[$this->currentComplexType]['restrictionBase'] = $attrs['base']; if (strstr($attrs['base'], ':') == ':Array') { $this->complexTypes[$this->currentComplexType]['phpType'] = 'array'; } } break; case 'schema': $this->schema = $attrs; $this->schema['schemaVersion'] = $this->getNamespaceFromPrefix($prefix); break; case 'simpleType': $this->currentElement = $attrs['name']; $this->elements[$attrs['name']] = $attrs; $this->elements[$attrs['name']]['typeClass'] = 'element'; break; } } /** * end-element handler * * @param string $parser XML parser object * @param string $name element name * @access private */ function schemaEndElement($parser, $name) { // position of current element is equal to the last value left in depth_array for my depth if (isset($this->depth_array[$this->depth])) { $pos = $this->depth_array[$this->depth]; } // bring depth down a notch $this->depth--; // move on... if ($name == 'complexType') { $this->currentComplexType = false; $this->currentElement = false; } if ($name == 'element') { $this->currentElement = false; } } /** * element content handler * * @param string $parser XML parser object * @param string $data element content * @access private */ function schemaCharacterData($parser, $data) { $pos = $this->depth_array[$this->depth]; $this->message[$pos]['cdata'] .= $data; } /** * serialize the schema * * @access public */ function serializeSchema() { $schemaPrefix = $this->getPrefixFromNamespace($this->XMLSchemaVersion); $xml = ''; // complex types foreach ($this->complexTypes as $typeName => $attrs) { $contentStr = ''; // serialize child elements if (count($attrs['elements']) > 0) { foreach ($attrs['elements'] as $element => $eParts) { if (isset($eParts['ref'])) { $contentStr .= ""; } else { $contentStr .= ""; } } } // attributes if (count($attrs['attrs']) >= 1) { foreach ($attrs['attrs'] as $attr => $aParts) { $contentStr .= '" . $contentStr . ""; } // "all" compositor obviates complex/simple content if (isset($attrs['compositor']) && $attrs['compositor'] == 'all') { $contentStr = "<$schemaPrefix:$attrs[compositor]>" . $contentStr . ""; } // complex or simple content elseif (count($attrs['elements']) > 0 || count($attrs['attrs']) > 0) { $contentStr = "<$schemaPrefix:complexContent>" . $contentStr . ""; } // compositors if (isset($attrs['compositor']) && $attrs['compositor'] != '' && $attrs['compositor'] != 'all') { $contentStr = "<$schemaPrefix:$attrs[compositor]>" . $contentStr . ""; } // finalize complex type if ($contentStr != '') { $contentStr = "<$schemaPrefix:complexType name=\"$typeName\">" . $contentStr . ""; } else { $contentStr = "<$schemaPrefix:complexType name=\"$typeName\"/>"; } $xml .= $contentStr; } // elements if (isset($this->elements) && count($this->elements) > 0) { foreach ($this->elements as $element => $eParts) { $xml .= "<$schemaPrefix:element name=\"$element\" type=\"" . $eParts['type'] . "\"/>"; } } // attributes if (isset($this->attributes) && count($this->attributes) > 0) { foreach ($this->attributes as $attr => $aParts) { $xml .= "<$schemaPrefix:attribute name=\"$attr\" type=\"" . $aParts['type'] . "\"/>"; } } // finish 'er up $xml = "<$schemaPrefix:schema xmlns=\"$this->XMLSchemaVersion\" targetNamespace=\"$this->schemaTargetNamespace\">" . $xml . ""; return $xml; } /** * expands a qualified name * * @param string $string qname * @return string expanded qname * @access private */ function expandQname($qname) { // get element prefix if (strpos($qname, ':') && !ereg('^http://', $qname)) { // get unqualified name $name = substr(strstr($qname, ':'), 1); // get ns prefix $prefix = substr($qname, 0, strpos($qname, ':')); if (isset($this->namespaces[$prefix])) { return $this->namespaces[$prefix] . ':' . $name; } else { return $qname; } } else { return $qname; } } /** * adds debug data to the clas level debug string * * @param string $string debug data * @access private */ function xdebug($string) { $this->debug(' xmlschema: ' . $string); } /** * get the PHP type of a user defined type in the schema * PHP type is kind of a misnomer since it actually returns 'struct' for assoc. arrays * returns false if no type exists, or not w/ the given namespace * else returns a string that is either a native php type, or 'struct' * * @param string $type, name of defined type * @param string $ns, namespace of type * @return mixed * @access public */ function getPHPType($type, $ns) { global $typemap; if (isset($typemap[$ns][$type])) { //print "found type '$type' and ns $ns in typemap
"; return $typemap[$ns][$type]; } elseif (isset($this->complexTypes[$type])) { //print "getting type '$type' and ns $ns from complexTypes array
"; return $this->complexTypes[$type]['phpType']; } return false; } /** * returns the local part of a prefixed string * returns the original string, if not prefixed * * @param string * @return string * @access public */ function getLocalPart($str) { if ($sstr = strrchr($str, ':')) { // get unqualified name return substr($sstr, 1); } else { return $str; } } /** * returns the prefix part of a prefixed string * returns false, if not prefixed * * @param string * @return mixed * @access public */ function getPrefix($str) { if ($pos = strrpos($str, ':')) { // get prefix return substr($str, 0, $pos); } return false; } /** * pass it a prefix, it returns a namespace * returns false if no namespace registered with the given prefix * * @param string * @return mixed * @access public */ function getNamespaceFromPrefix($prefix) { if (isset($this->namespaces[$prefix])) { return $this->namespaces[$prefix]; } //$this->setError("No namespace registered for prefix '$prefix'"); return false; } /** * returns the prefix for a given namespace (or prefix) * or false if no prefixes registered for the given namespace * * @param string * @return mixed * @access public */ function getPrefixFromNamespace($ns) { foreach ($this->namespaces as $p => $n) { if ($ns == $n || $ns == $p) { $this->usedNamespaces[$p] = $n; return $p; } } return false; } /** * returns an array of information about a given type * returns false if no type exists by the given name * * typeDef = array( * 'elements' => array(), // refs to elements array * 'restrictionBase' => '', * 'phpType' => '', * 'order' => '(sequence|all)', * 'attrs' => array() // refs to attributes array * ) * * @param string * @return mixed * @access public */ function getTypeDef($type) { if (isset($this->complexTypes[$type])) { return $this->complexTypes[$type]; } elseif (isset($this->elements[$type])) { return $this->elements[$type]; } elseif (isset($this->attributes[$type])) { return $this->attributes[$type]; } return false; } /** * returns a sample serialization of a given type, or false if no type by the given name * * @param string $type, name of type * @return mixed * @access public */ function serializeTypeDef($type) { //print "in sTD() for type $type
"; if ($typeDef = $this->getTypeDef($type)) { $str .= '<' . $type; if (is_array($typeDef['attrs'])) { foreach ($attrs as $attName => $data) { $str .= " $attName=\"{type = " . $data['type'] . "}\""; } } $str .= " xmlns=\"" . $this->schema['targetNamespace'] . "\""; if (count($typeDef['elements']) > 0) { $str .= ">"; foreach ($typeDef['elements'] as $element => $eData) { $str .= $this->serializeTypeDef($element); } $str .= ""; } elseif ($typeDef['typeClass'] == 'element') { $str .= ">"; } else { $str .= "/>"; } return $str; } return false; } /** * returns HTML form elements that allow a user * to enter values for creating an instance of the given type. * * @param string $name, name for type instance * @param string $type, name of type * @return string * @access public */ function typeToForm($name, $type) { // get typedef if ($typeDef = $this->getTypeDef($type)) { // if struct if ($typeDef['phpType'] == 'struct') { $buffer .= ''; foreach ($typeDef['elements'] as $child => $childDef) { $buffer .= "" . ""; } $buffer .= '
$childDef[name] (type: " . $this->getLocalPart($childDef['type']) . "):
'; // if array } elseif ($typeDef['phpType'] == 'array') { $buffer .= ''; for ($i = 0; $i < 3; $i++) { $buffer .= "" . ""; } $buffer .= '
array item (type: $typeDef[arrayType]):
'; // if scalar } else { $buffer .= ""; } } else { $buffer .= ""; } return $buffer; } /** * adds an XML Schema complex type to the WSDL types * * example: array * * addType( * 'ArrayOfstring', * 'complexType', * 'array', * '', * 'SOAP-ENC:Array', * array('ref'=>'SOAP-ENC:arrayType','wsdl:arrayType'=>'string[]'), * 'xsd:string' * ); * * example: PHP associative array ( SOAP Struct ) * * addType( * 'SOAPStruct', * 'complexType', * 'struct', * 'all', * array('myVar'=> array('name'=>'myVar','type'=>'string') * ); * * @param name * @param typeClass (complexType|simpleType|attribute) * @param phpType: currently supported are array and struct (php assoc array) * @param compositor (all|sequence|choice) * @param restrictionBase namespace:name (http://schemas.xmlsoap.org/soap/encoding/:Array) * @param elements = array ( name = array(name=>'',type=>'') ) * @param attrs = array( * array( * 'ref' => "http://schemas.xmlsoap.org/soap/encoding/:arrayType", * "http://schemas.xmlsoap.org/wsdl/:arrayType" => "string[]" * ) * ) * @param arrayType: namespace:name (http://www.w3.org/2001/XMLSchema:string) * */ function addComplexType($name, $typeClass = 'complexType', $phpType = 'array', $compositor = '', $restrictionBase = '', $elements = array(), $attrs = array(), $arrayType = '') { $this->complexTypes[$name] = array( 'name' => $name, 'typeClass' => $typeClass, 'phpType' => $phpType, 'compositor' => $compositor, 'restrictionBase' => $restrictionBase, 'elements' => $elements, 'attrs' => $attrs, 'arrayType' => $arrayType ); } } /** * for creating serializable abstractions of native PHP types * NOTE: this is only really used when WSDL is not available. * * @author Dietrich Ayala * @version v 0.6.3 * @access public */ class soapval extends nusoap_base { /** * constructor * * @param string $name optional name * @param string $type optional type name * @param mixed $value optional value * @param string $namespace optional namespace of value * @param string $type_namespace optional namespace of type * @param array $attributes associative array of attributes to add to element serialization * @access public */ function soapval($name = 'soapval', $type = false, $value = -1, $element_ns = false, $type_ns = false, $attributes = false) { $this->name = $name; $this->value = $value; $this->type = $type; $this->element_ns = $element_ns; $this->type_ns = $type_ns; $this->attributes = $attributes; } /** * return serialized value * * @return string XML data * @access private */ function serialize($use = 'encoded') { return $this->serialize_val($this->value, $this->name, $this->type, $this->element_ns, $this->type_ns, $this->attributes, $use); } /** * decodes a soapval object into a PHP native type * * @param object $soapval optional SOAPx4 soapval object, else uses self * @return mixed * @access public */ function decode() { return $this->value; } } /** * transport class for sending/receiving data via HTTP and HTTPS * NOTE: PHP must be compiled with the CURL extension for HTTPS support * * @author Dietrich Ayala * @version v 0.6.3 * @access public */ class soap_transport_http extends nusoap_base { var $username = ''; var $password = ''; var $url = ''; var $proxyhost = ''; var $proxyport = ''; var $scheme = ''; var $request_method = 'POST'; var $protocol_version = '1.0'; var $encoding = ''; var $outgoing_headers = array(); var $incoming_headers = array(); var $outgoing_payload = ''; var $incoming_payload = ''; var $useSOAPAction = true; /** * constructor */ function soap_transport_http($url) { $this->url = $url; $u = parse_url($url); foreach ($u as $k => $v) { $this->debug("$k = $v"); $this->$k = $v; } if (isset($u['query']) && $u['query'] != '') { $this->path .= '?' . $u['query']; } if (!isset($u['port']) && $u['scheme'] == 'http') { $this->port = 80; } } function connect($timeout) { // proxy if ($this->proxyhost != '' && $this->proxyport != '') { $host = $this->proxyhost; $port = $this->proxyport; $this->debug("using http proxy: $host, $port"); } else { $host = $this->host; $port = $this->port; } // ssl if ($this->scheme == 'https') { $host = 'ssl://' . $host; $port = 443; } $this->debug("connection params: $host, $port"); // timeout if ($timeout > 0) { $fp = fsockopen($host, $port, $this->errno, $this->error_str, $timeout); } else { $fp = fsockopen($host, $port, $this->errno, $this->error_str); } // test pointer if (!$fp) { $this->debug('Couldn\'t open socket connection to server ' . $this->url . ', Error: ' . $this->error_str); $this->setError('Couldn\'t open socket connection to server: ' . $this->url . ', Error: ' . $this->error_str); return false; } return $fp; } /** * send the SOAP message via HTTP * * @param string $data message data * @param integer $timeout set timeout in seconds * @return string data * @access public */ function send($data, $timeout = 0) { $this->debug('entered send() with data of length: ' . strlen($data)); // get connnection if (!$fp = $this->connect($timeout)) { return false; } $this->debug('socket connected'); // start building outgoing payload: // swap url for path if going through a proxy if ($this->proxyhost != '' && $this->proxyport != '') { $this->outgoing_payload = "$this->request_method $this->url " . strtoupper($this->scheme) . "/$this->protocol_version\r\n"; } else { $this->outgoing_payload = "$this->request_method $this->path " . strtoupper($this->scheme) . "/$this->protocol_version\r\n"; } // make payload $this->outgoing_payload .= "User-Agent: $this->title/$this->version\r\n" . "Host: " . $this->host . "\r\n"; // http auth $credentials = ''; if ($this->username != '') { $this->debug('setting http auth credentials'); $this->outgoing_payload .= 'Authorization: Basic ' . base64_encode("$this->username:$this->password") . "\r\n"; } // set content type $this->outgoing_payload .= 'Content-Type: text/xml; charset=' . $this->soap_defencoding . "\r\nContent-Length: " . strlen($data) . "\r\n"; // http encoding if ($this->encoding != '' && function_exists('gzdeflate')) { $this->outgoing_payload .= "Accept-Encoding: $this->encoding\r\n" . "Connection: close\r\n"; if (!check_php_version(5, 3)) { set_magic_quotes_runtime(0); } } // set soapaction if ($this->useSOAPAction) { $this->outgoing_payload .= "SOAPAction: \"$this->soapaction\"" . "\r\n"; } $this->outgoing_payload .= "\r\n"; // add data $this->outgoing_payload .= $data; // send payload if (!fputs($fp, $this->outgoing_payload, strlen($this->outgoing_payload))) { $this->setError('couldn\'t write message data to socket'); $this->debug('Write error'); } $this->debug('wrote data to socket'); // get response $this->incoming_payload = ''; //$strlen = 0; while ($data = fread($fp, 32768)) { $this->incoming_payload .= $data; //$strlen += strlen($data); } $this->debug('received ' . strlen($this->incoming_payload) . ' bytes of data from server'); // close filepointer fclose($fp); $this->debug('closed socket'); // connection was closed unexpectedly if ($this->incoming_payload == '') { $this->setError('no response from server'); return false; } $this->debug('received incoming payload: ' . strlen($this->incoming_payload)); $data = $this->incoming_payload . "\r\n\r\n\r\n\r\n"; // remove 100 header if (ereg('^HTTP/1.1 100', $data)) { if ($pos = strpos($data, "\r\n\r\n")) { $data = ltrim(substr($data, $pos)); } elseif ($pos = strpos($data, "\n\n")) { $data = ltrim(substr($data, $pos)); } } // // separate content from HTTP headers if ($pos = strpos($data, "\r\n\r\n")) { $lb = "\r\n"; } elseif ($pos = strpos($data, "\n\n")) { $lb = "\n"; } else { $this->setError('no proper separation of headers and document'); return false; } $header_data = trim(substr($data, 0, $pos)); $header_array = explode($lb, $header_data); $data = ltrim(substr($data, $pos)); $this->debug('found proper separation of headers and document'); $this->debug('cleaned data, stringlen: ' . strlen($data)); // clean headers foreach ($header_array as $header_line) { $arr = explode(':', $header_line); if (count($arr) >= 2) { $headers[trim($arr[0])] = trim($arr[1]); } } //print "headers:
$header_data

"; //print "data:
$data

"; // decode transfer-encoding if (isset($headers['Transfer-Encoding']) && $headers['Transfer-Encoding'] == 'chunked') { //$timer->setMarker('starting to decode chunked content'); if (!$data = $this->decodeChunked($data)) { $this->setError('Decoding of chunked data failed'); return false; } //$timer->setMarker('finished decoding of chunked content'); //print "
\nde-chunked:\n---------------\n$data\n\n---------------\n
"; } // decode content-encoding if (isset($headers['Content-Encoding']) && $headers['Content-Encoding'] != '') { if ($headers['Content-Encoding'] == 'deflate' || $headers['Content-Encoding'] == 'gzip') { // if decoding works, use it. else assume data wasn't gzencoded if (function_exists('gzinflate')) { //$timer->setMarker('starting decoding of gzip/deflated content'); if ($headers['Content-Encoding'] == 'deflate' && $degzdata = @gzinflate($data)) { $data = $degzdata; } elseif ($headers['Content-Encoding'] == 'gzip' && $degzdata = gzinflate(substr($data, 10))) { $data = $degzdata; } else { $this->setError('Errors occurred when trying to decode the data'); } //$timer->setMarker('finished decoding of gzip/deflated content'); //print "\nde-inflated:\n---------------\n$data\n-------------\n"; } else { $this->setError('The server sent deflated data. Your php install must have the Zlib extension compiled in to support this.'); } } } if (strlen($data) == 0) { $this->debug('no data after headers!'); $this->setError('no data present after HTTP headers'); return false; } $this->debug('end of send()'); return $data; } /** * send the SOAP message via HTTPS 1.0 using CURL * * @param string $msg message data * @param integer $timeout set timeout in seconds * @return string data * @access public */ function sendHTTPS($data, $timeout = 0) { //global $t; //$t->setMarker('inside sendHTTPS()'); $this->debug('entered sendHTTPS() with data of length: ' . strlen($data)); // init CURL $ch = curl_init(); //$t->setMarker('got curl handle'); // set proxy if ($this->proxyhost && $this->proxyport) { $host = $this->proxyhost; $port = $this->proxyport; } else { $host = $this->host; $port = $this->port; } // set url $hostURL = ($port != '') ? "https://$host:$port" : "https://$host"; // add path $hostURL .= $this->path; curl_setopt($ch, CURLOPT_URL, $hostURL); // set other options curl_setopt($ch, CURLOPT_HEADER, 1); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); // encode if (function_exists('gzinflate')) { curl_setopt($ch, CURLOPT_ENCODING, 'deflate'); } // persistent connection //curl_setopt($ch, CURL_HTTP_VERSION_1_1, true); // set timeout if ($timeout != 0) { curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); } $credentials = ''; if ($this->username != '') { $credentials = 'Authorization: Basic ' . base64_encode("$this->username:$this->password") . '\r\n'; } if ($this->encoding != '') { if (function_exists('gzdeflate')) { $encoding_headers = "Accept-Encoding: $this->encoding\r\n" . "Connection: close\r\n"; if (!check_php_version(5, 3)) { set_magic_quotes_runtime(0); } } } if ($this->proxyhost && $this->proxyport) { $this->outgoing_payload = "POST $this->url HTTP/$this->protocol_version\r\n"; } else { $this->outgoing_payload = "POST $this->path HTTP/$this->protocol_version\r\n"; } $this->outgoing_payload .= "User-Agent: $this->title v$this->version\r\n" . "Host: " . $this->host . "\r\n" . $encoding_headers . $credentials . "Content-Type: text/xml; charset=\"$this->soap_defencoding\"\r\n" . "Content-Length: " . strlen($data) . "\r\n" . "SOAPAction: \"$this->soapaction\"" . "\r\n\r\n" . $data; // set payload curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $this->outgoing_payload); //$t->setMarker('set curl options, executing...'); // send and receive $this->incoming_payload = curl_exec($ch); //$t->setMarker('executed transfer'); $data = $this->incoming_payload; $cErr = curl_error($ch); if ($cErr != '') { $err = 'cURL ERROR: ' . curl_errno($ch) . ': ' . $cErr . '
'; foreach (curl_getinfo($ch) as $k => $v) { $err .= "$k: $v
"; } $this->setError($err); curl_close($ch); return false; } else { //echo '
';
            //var_dump(curl_getinfo($ch));
            //echo '
'; } // close curl curl_close($ch); //$t->setMarker('closed curl'); // remove 100 header if (ereg('^HTTP/1.1 100', $data)) { if ($pos = strpos($data, "\r\n\r\n")) { $data = ltrim(substr($data, $pos)); } elseif ($pos = strpos($data, "\n\n")) { $data = ltrim(substr($data, $pos)); } } // // separate content from HTTP headers if ($pos = strpos($data, "\r\n\r\n")) { $lb = "\r\n"; } elseif ($pos = strpos($data, "\n\n")) { $lb = "\n"; } else { $this->setError('no proper separation of headers and document'); return false; } $header_data = trim(substr($data, 0, $pos)); $header_array = explode($lb, $header_data); $data = ltrim(substr($data, $pos)); $this->debug('found proper separation of headers and document'); $this->debug('cleaned data, stringlen: ' . strlen($data)); // clean headers foreach ($header_array as $header_line) { $arr = explode(':', $header_line); $headers[trim($arr[0])] = trim($arr[1]); } if (strlen($data) == 0) { $this->debug('no data after headers!'); $this->setError('no data present after HTTP headers.'); return false; } // decode transfer-encoding if ($headers['Transfer-Encoding'] == 'chunked') { if (!$data = $this->decodeChunked($data)) { $this->setError('Decoding of chunked data failed'); return false; } } // decode content-encoding if ($headers['Content-Encoding'] != '') { if ($headers['Content-Encoding'] == 'deflate' || $headers['Content-Encoding'] == 'gzip') { // if decoding works, use it. else assume data wasn't gzencoded if (function_exists('gzinflate')) { if ($headers['Content-Encoding'] == 'deflate' && $degzdata = @gzinflate($data)) { $data = $degzdata; } elseif ($headers['Content-Encoding'] == 'gzip' && $degzdata = gzinflate(substr($data, 10))) { $data = $degzdata; } else { $this->setError('Errors occurred when trying to decode the data'); } } else { $this->setError('The server sent deflated data. Your php install must have the Zlib extension compiled in to support this.'); } } } // set decoded payload $this->incoming_payload = $header_data . "\r\n\r\n" . $data; return $data; } /** * if authenticating, set user credentials here * * @param string $user * @param string $pass * @access public */ function setCredentials($username, $password) { $this->username = $username; $this->password = $password; } /** * set the soapaction value * * @param string $soapaction * @access public */ function setSOAPAction($soapaction) { $this->soapaction = $soapaction; } /** * use http encoding * * @param string $enc encoding style. supported values: gzip, deflate, or both * @access public */ function setEncoding($enc = 'gzip, deflate') { $this->encoding = $enc; $this->protocol_version = '1.1'; } /** * set proxy info here * * @param string $proxyhost * @param string $proxyport * @access public */ function setProxy($proxyhost, $proxyport) { $this->proxyhost = $proxyhost; $this->proxyport = $proxyport; } /** * decode a string that is encoded w/ "chunked' transfer encoding * as defined in RFC2068 19.4.6 * * @param string $buffer * @returns string * @access public */ function decodeChunked($buffer) { $length = 0; $new = ''; // read chunk-size, chunk-extension (if any) and CRLF // get the position of the linebreak $chunkend = strpos($buffer, "\r\n") + 2; $temp = substr($buffer, 0, $chunkend); $chunk_size = hexdec(trim($temp)); $chunkstart = $chunkend; // while (chunk-size > 0) { while ($chunk_size > 0) { $chunkend = strpos($buffer, "\r\n", $chunkstart + $chunk_size); // Just in case we got a broken connection if ($chunkend == FALSE) { $chunk = substr($buffer, $chunkstart); // append chunk-data to entity-body $new .= $chunk; $length += strlen($chunk); break; } // read chunk-data and CRLF $chunk = substr($buffer, $chunkstart, $chunkend - $chunkstart); // append chunk-data to entity-body $new .= $chunk; // length := length + chunk-size $length += strlen($chunk); // read chunk-size and CRLF $chunkstart = $chunkend + 2; $chunkend = strpos($buffer, "\r\n", $chunkstart) + 2; if ($chunkend == FALSE) { break; //Just in case we got a broken connection } $temp = substr($buffer, $chunkstart, $chunkend - $chunkstart); $chunk_size = hexdec(trim($temp)); $chunkstart = $chunkend; } // Update headers //$this->Header['content-length'] = $length; //unset($this->Header['transfer-encoding']); return $new; } } /** * * soap_server allows the user to create a SOAP server * that is capable of receiving messages and returning responses * * NOTE: WSDL functionality is experimental * * @author Dietrich Ayala * @version v 0.6.3 * @access public */ class soap_server extends nusoap_base { var $service = ''; // service name var $operations = array(); // assoc array of operations => opData var $responseHeaders = false; var $headers = ''; var $request = ''; var $charset_encoding = 'UTF-8'; var $fault = false; var $result = 'successful'; var $wsdl = false; var $externalWSDLURL = false; var $debug_flag = true; /** * constructor * the optional parameter is a path to a WSDL file that you'd like to bind the server instance to. * * @param string $wsdl path or URL to a WSDL file * @access public */ function soap_server($wsdl = false) { // turn on debugging? global $debug; if (isset($debug)) { $this->debug_flag = 1; } // wsdl if ($wsdl) { $this->wsdl = new wsdl($wsdl); $this->externalWSDLURL = $wsdl; if ($err = $this->wsdl->getError()) { die('WSDL ERROR: ' . $err); } } } /** * processes request and returns response * * @param string $data usually is the value of $HTTP_RAW_POST_DATA * @access public */ function service($data) { // print wsdl global $QUERY_STRING, $HTTP_SERVER_VARS; if (isset($HTTP_SERVER_VARS['QUERY_STRING'])) { $qs = $HTTP_SERVER_VARS['QUERY_STRING']; } elseif (isset($GLOBALS['QUERY_STRING'])) { $qs = $GLOBALS['QUERY_STRING']; } elseif (isset($QUERY_STRING) && $QUERY_STRING != '') { $qs = $QUERY_STRING; } // gen wsdl if (isset($qs) && ereg('wsdl', $qs)) { if ($this->externalWSDLURL) { header('Location: ' . $this->externalWSDLURL); exit(); } else { header("Content-Type: text/xml\r\n"); print $this->wsdl->serialize(); exit(); } } // print web interface if ($data == '' && $this->wsdl) { print $this->webDescription(); } else { // $response is the serialized response message $response = $this->parse_request($data); $this->debug('server sending...'); $payload = $response; // add debug data if in debug mode if (isset($this->debug_flag) && $this->debug_flag == 1) { $payload .= ""; } // print headers if ($this->fault) { $header[] = "HTTP/1.0 500 Internal Server Error\r\n"; $header[] = "Status: 500 Internal Server Error\r\n"; } else { $header[] = "Status: 200 OK\r\n"; } $header[] = "Server: $this->title Server v$this->version\r\n"; $header[] = "Connection: Close\r\n"; $header[] = "Content-Type: text/xml; charset=$this->charset_encoding\r\n"; $header[] = "Content-Length: " . strlen($payload) . "\r\n\r\n"; reset($header); foreach ($header as $hdr) { header($hdr); } $this->response = join("\r\n", $header) . $payload; print $payload; } } /** * parses request and posts response * * @param string $data XML string * @return string XML response msg * @access private */ function parse_request($data = '') { if (!isset($_SERVER)) $_SERVER =& $GLOBALS['HTTP_SERVER_VARS']; $this->debug('entering parseRequest() on ' . date('H:i Y-m-d')); $dump = ''; // get headers if (function_exists('getallheaders')) { $this->headers = getallheaders(); foreach ($this->headers as $k => $v) { $dump .= "$k: $v\r\n"; $this->debug("$k: $v"); } // get SOAPAction header if (isset($this->headers['SOAPAction'])) { $this->SOAPAction = str_replace('"', '', $this->headers['SOAPAction']); } // get the character encoding of the incoming request if (strpos($this->headers['Content-Type'], '=')) { $enc = str_replace('"', '', substr(strstr($this->headers["Content-Type"], '='), 1)); if (eregi('^(ISO-8859-1|US-ASCII|UTF-8)$', $enc)) { $this->xml_encoding = $enc; } else { $this->xml_encoding = 'us-ascii'; } } $this->debug('got encoding: ' . $this->charset_encoding); } elseif (is_array($_SERVER)) { $this->headers['User-Agent'] = $_SERVER['HTTP_USER_AGENT']; $this->SOAPAction = isset($_SERVER['SOAPAction']) ? $_SERVER['SOAPAction'] : ''; } $this->request = $dump . "\r\n\r\n" . $data; // parse response, get soap parser obj $parser = new soap_parser($data, $this->charset_encoding); // if fault occurred during message parsing if ($err = $parser->getError()) { // parser debug $this->debug("parser debug: \n" . $parser->debug_str); $this->result = 'fault: error in msg parsing: ' . $err; $this->fault('Server', "error in msg parsing:\n" . $err); // return soapresp return $this->fault->serialize(); // else successfully parsed request into soapval object } else { // get/set methodname $this->methodname = $parser->root_struct_name; $this->debug('method name: ' . $this->methodname); // does method exist? if (!function_exists($this->methodname)) { // "method not found" fault here $this->debug("method '$this->methodname' not found!"); $this->debug("parser debug: \n" . $parser->debug_str); $this->result = 'fault: method not found'; $this->fault('Server', "method '$this->methodname' not defined in service '$this->service'"); return $this->fault->serialize(); } if ($this->wsdl) { if (!$this->opData = $this->wsdl->getOperationData($this->methodname)) { //if( $this->fault('Server', "Operation '$this->methodname' is not defined in the WSDL for this service"); return $this->fault->serialize(); } } $this->debug("method '$this->methodname' exists"); // evaluate message, getting back parameters $this->debug('calling parser->get_response()'); $request_data = $parser->get_response(); // parser debug $this->debug("parser debug: \n" . $parser->debug_str); // verify that request parameters match the method's signature if ($this->verify_method($this->methodname, $request_data)) { // if there are parameters to pass $this->debug('params var dump ' . $this->varDump($request_data)); if ($request_data) { $this->debug("calling '$this->methodname' with params"); if (!function_exists('call_user_func_array')) { $this->debug('calling method using eval()'); $funcCall = $this->methodname . '('; foreach ($request_data as $param) { $funcCall .= "\"$param\","; } $funcCall = substr($funcCall, 0, -1) . ')'; $this->debug('function call:
' . $funcCall); @eval("\$method_response = $funcCall;"); } else { $this->debug('calling method using call_user_func_array()'); $method_response = call_user_func_array("$this->methodname", $request_data); } $this->debug('response var dump' . $this->varDump($method_response)); } else { // call method w/ no parameters $this->debug("calling $this->methodname w/ no params"); $m = $this->methodname; $method_response = @$m(); } $this->debug("done calling method: $this->methodname, received $method_response of type" . gettype($method_response)); // if we got nothing back. this might be ok (echoVoid) if (isset($method_response) && $method_response != '' || is_bool($method_response)) { // if fault if (strtolower(get_class($method_response)) == 'soap_fault') { $this->debug('got a fault object from method'); $this->fault = $method_response; return $method_response->serialize(); // if return val is soapval object } elseif (strtolower(get_class($method_response)) == 'soapval') { $this->debug('got a soapval object from method'); $return_val = $method_response->serialize(); // returned other } else { $this->debug('got a(n) ' . gettype($method_response) . ' from method'); $this->debug('serializing return value'); if ($this->wsdl) { // weak attempt at supporting multiple output params if (sizeof($this->opData['output']['parts']) > 1) { $opParams = $method_response; } else { $opParams = array($method_response); } $return_val = $this->wsdl->serializeRPCParameters($this->methodname, 'output', $opParams); } else { $return_val = $this->serialize_val($method_response); } } $this->debug('return val:' . $this->varDump($return_val)); } else { $return_val = ''; $this->debug('got no response from method'); } $this->debug('serializing response'); $payload = '<' . $this->methodname . "Response>" . $return_val . 'methodname . "Response>"; $this->result = 'successful'; if ($this->wsdl) { //if($this->debug_flag){ $this->debug("WSDL debug data:\n" . $this->wsdl->debug_str); // } // Added: In case we use a WSDL, return a serialized env. WITH the usedNamespaces. return $this->serializeEnvelope($payload, $this->responseHeaders, $this->wsdl->usedNamespaces, $this->opData['style']); } else { return $this->serializeEnvelope($payload, $this->responseHeaders); } } else { // debug $this->debug('ERROR: request not verified against method signature'); $this->result = 'fault: request failed validation against method signature'; // return fault $this->fault('Server', "Operation '$this->methodname' not defined in service."); return $this->fault->serialize(); } } } /** * takes the value that was created by parsing the request * and compares to the method's signature, if available. * * @param mixed * @return boolean * @access private */ function verify_method($operation, $request) { if (isset($this->wsdl) && is_object($this->wsdl)) { if ($this->wsdl->getOperationData($operation)) { return true; } } elseif (isset($this->operations[$operation])) { return true; } return false; } /** * add a method to the dispatch map * * @param string $methodname * @param string $in array of input values * @param string $out array of output values * @access public */ function add_to_map($methodname, $in, $out) { $this->operations[$methodname] = array('name' => $methodname, 'in' => $in, 'out' => $out); } /** * register a service with the server * * @param string $methodname * @param string $in assoc array of input values: key = param name, value = param type * @param string $out assoc array of output values: key = param name, value = param type * @param string $namespace * @param string $soapaction * @param string $style (rpc|literal) * @access public */ function register($name, $in = false, $out = false, $namespace = false, $soapaction = false, $style = false, $use = false) { if ($this->externalWSDLURL) { die('You cannot bind to an external WSDL file, and register methods outside of it! Please choose either WSDL or no WSDL.'); } if (false == $in) { } if (false == $out) { } if (false == $namespace) { } if (false == $soapaction) { global $SERVER_NAME, $SCRIPT_NAME; $soapaction = "http://$SERVER_NAME$SCRIPT_NAME"; } if (false == $style) { $style = "rpc"; } if (false == $use) { $use = "encoded"; } $this->operations[] = array($name => array()); $this->operations[$name] = array( 'name' => $name, 'in' => $in, 'out' => $out, 'namespace' => $namespace, 'soapaction' => $soapaction, 'style' => $style); if ($this->wsdl) { $this->wsdl->addOperation($name, $in, $out, $namespace, $soapaction, $style, $use); } return true; } /** * create a fault. this also acts as a flag to the server that a fault has occured. * * @param string faultcode * @param string faultactor * @param string faultstring * @param string faultdetail * @access public */ function fault($faultcode, $faultactor, $faultstring = '', $faultdetail = '') { $this->fault = new soap_fault($faultcode, $faultactor, $faultstring, $faultdetail); } /** * prints html description of services * * @access private */ function webDescription() { $b = ' NuSOAP: ' . $this->wsdl->serviceName . '


' . $this->wsdl->serviceName . '
'; return $b; } /** * sets up wsdl object * this acts as a flag to enable internal WSDL generation * NOTE: NOT FUNCTIONAL * * @param string $serviceName, name of the service * @param string $namespace, tns namespace */ function configureWSDL($serviceName, $namespace = false, $endpoint = false, $style = 'rpc', $transport = 'http://schemas.xmlsoap.org/soap/http') { if (!isset($_SERVER)) $_SERVER =& $GLOBALS['HTTP_SERVER_VARS']; $SERVER_NAME = isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : $GLOBALS['SERVER_NAME']; $SCRIPT_NAME = isset($_SERVER['SCRIPT_NAME']) ? $_SERVER['SCRIPT_NAME'] : $GLOBALS['SCRIPT_NAME']; if (false == $namespace) { $namespace = "http://$SERVER_NAME/soap/$serviceName"; } if (false == $endpoint) { $endpoint = "http://$SERVER_NAME$SCRIPT_NAME"; } $this->wsdl = new wsdl; $this->wsdl->serviceName = $serviceName; $this->wsdl->endpoint = $endpoint; $this->wsdl->namespaces['tns'] = $namespace; $this->wsdl->namespaces['soap'] = 'http://schemas.xmlsoap.org/wsdl/soap/'; $this->wsdl->namespaces['wsdl'] = 'http://schemas.xmlsoap.org/wsdl/'; $this->wsdl->bindings[$serviceName . 'Binding'] = array( 'name' => $serviceName . 'Binding', 'style' => $style, 'transport' => $transport, 'portType' => $serviceName . 'PortType'); $this->wsdl->ports[$serviceName . 'Port'] = array( 'binding' => $serviceName . 'Binding', 'location' => $endpoint, 'bindingType' => 'http://schemas.xmlsoap.org/wsdl/soap/'); } } /** * parses a WSDL file, allows access to it's data, other utility methods * * @author Dietrich Ayala * @version v 0.6.3 * @access public */ class wsdl extends XMLSchema { var $wsdl; // define internal arrays of bindings, ports, operations, messages, etc. var $message = array(); var $complexTypes = array(); var $messages = array(); var $currentMessage; var $currentOperation; var $portTypes = array(); var $currentPortType; var $bindings = array(); var $currentBinding; var $ports = array(); var $currentPort; var $opData = array(); var $status = ''; var $documentation = false; var $endpoint = ''; // array of wsdl docs to import var $import = array(); // parser vars var $parser; var $position = 0; var $depth = 0; var $depth_array = array(); var $usedNamespaces = array(); // for getting wsdl var $proxyhost = ''; var $proxyport = ''; /** * constructor * * @param string $wsdl WSDL document URL * @access public */ function wsdl($wsdl = '', $proxyhost = false, $proxyport = false) { $this->wsdl = $wsdl; $this->proxyhost = $proxyhost; $this->proxyport = $proxyport; // parse wsdl file if ($wsdl != "") { $this->debug('initial wsdl file: ' . $wsdl); $this->parseWSDL($wsdl); } // imports if (sizeof($this->import) > 0) { foreach ($this->import as $ns => $url) { $this->debug('importing wsdl from ' . $url); $this->parseWSDL($url); } } } /** * parses the wsdl document * * @param string $wsdl path or URL * @access private */ function parseWSDL($wsdl = '') { if ($wsdl == '') { $this->debug('no wsdl passed to parseWSDL()!!'); $this->setError('no wsdl passed to parseWSDL()!!'); return false; } $this->debug('getting ' . $wsdl); // parse $wsdl for url format $wsdl_props = parse_url($wsdl); if (isset($wsdl_props['host'])) { // get wsdl $tr = new soap_transport_http($wsdl); $tr->request_method = 'GET'; $tr->useSOAPAction = false; if ($this->proxyhost && $this->proxyport) { $tr->setProxy($this->proxyhost, $this->proxyport); } if (isset($wsdl_props['user'])) { $tr->setCredentials($wsdl_props['user'], $wsdl_props['pass']); } $wsdl_string = $tr->send(''); // catch errors if ($err = $tr->getError()) { $this->debug('HTTP ERROR: ' . $err); $this->setError('HTTP ERROR: ' . $err); return false; } unset($tr); /* $wsdl seems to be a valid url, not a file path, do an fsockopen/HTTP GET $fsockopen_timeout = 30; // check if a port value is supplied in url if (isset($wsdl_props['port'])) { // yes $wsdl_url_port = $wsdl_props['port']; } else { // no, assign port number, based on url protocol (scheme) switch ($wsdl_props['scheme']) { case ('https') : case ('ssl') : case ('tls') : $wsdl_url_port = 443; break; case ('http') : default : $wsdl_url_port = 80; } } // FIXME: should implement SSL/TLS support here if CURL is available if ($fp = fsockopen($wsdl_props['host'], $wsdl_url_port, $fsockopen_errnum, $fsockopen_errstr, $fsockopen_timeout)) { // perform HTTP GET for WSDL file // 10.9.02 - added poulter fix for doing this properly $sHeader = "GET " . $wsdl_props['path']; if (isset($wsdl_props['query'])) { $sHeader .= "?" . $wsdl_props['query']; } $sHeader .= " HTTP/1.0\r\n"; if (isset($wsdl_props['user'])) { $base64auth = base64_encode($wsdl_props['user'] . ":" . $wsdl_props['pass']); $sHeader .= "Authorization: Basic $base64auth\r\n"; } $sHeader .= "Host: " . $wsdl_props['host'] . ( isset($wsdl_props['port']) ? ":".$wsdl_props['port'] : "" ) . "\r\n\r\n"; fputs($fp, $sHeader); while (fgets($fp, 1024) != "\r\n") { // do nothing, just read/skip past HTTP headers // FIXME: should actually detect HTTP response code, and act accordingly if error // HTTP headers end with extra CRLF before content body } // read in WSDL just like regular fopen() $wsdl_string = ''; while ($data = fread($fp, 32768)) { $wsdl_string .= $data; } fclose($fp); } else { $this->setError('bad path to WSDL file.'); return false; } */ } else { // $wsdl seems to be a non-url file path, do the regular fopen if ($fp = @fopen($wsdl, 'r')) { $wsdl_string = ''; while ($data = fread($fp, 32768)) { $wsdl_string .= $data; } fclose($fp); } else { $this->setError('bad path to WSDL file.'); return false; } } // end new code added // Create an XML parser. $this->parser = xml_parser_create(); // Set the options for parsing the XML data. // xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 1); xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING, 0); // Set the object for the parser. xml_set_object($this->parser, $this); // Set the element handlers for the parser. xml_set_element_handler($this->parser, 'start_element', 'end_element'); xml_set_character_data_handler($this->parser, 'character_data'); // Parse the XML file. if (!xml_parse($this->parser, $wsdl_string, true)) { // Display an error message. $errstr = sprintf( 'XML error on line %d: %s', xml_get_current_line_number($this->parser), xml_error_string(xml_get_error_code($this->parser)) ); $this->debug('XML parse error: ' . $errstr); $this->setError('Parser error: ' . $errstr); return false; } // free the parser xml_parser_free($this->parser); // catch wsdl parse errors if ($this->getError()) { return false; } // add new data to operation data foreach ($this->bindings as $binding => $bindingData) { if (isset($bindingData['operations']) && is_array($bindingData['operations'])) { foreach ($bindingData['operations'] as $operation => $data) { $this->debug('post-parse data gathering for ' . $operation); $this->bindings[$binding]['operations'][$operation]['input'] = isset($this->bindings[$binding]['operations'][$operation]['input']) ? array_merge($this->bindings[$binding]['operations'][$operation]['input'], $this->portTypes[$bindingData['portType']][$operation]['input']) : $this->portTypes[$bindingData['portType']][$operation]['input']; $this->bindings[$binding]['operations'][$operation]['output'] = isset($this->bindings[$binding]['operations'][$operation]['output']) ? array_merge($this->bindings[$binding]['operations'][$operation]['output'], $this->portTypes[$bindingData['portType']][$operation]['output']) : $this->portTypes[$bindingData['portType']][$operation]['output']; if (isset($this->messages[$this->bindings[$binding]['operations'][$operation]['input']['message']])) { $this->bindings[$binding]['operations'][$operation]['input']['parts'] = $this->messages[$this->bindings[$binding]['operations'][$operation]['input']['message']]; } if (isset($this->messages[$this->bindings[$binding]['operations'][$operation]['output']['message']])) { $this->bindings[$binding]['operations'][$operation]['output']['parts'] = $this->messages[$this->bindings[$binding]['operations'][$operation]['output']['message']]; } if (isset($bindingData['style'])) { $this->bindings[$binding]['operations'][$operation]['style'] = $bindingData['style']; } $this->bindings[$binding]['operations'][$operation]['transport'] = isset($bindingData['transport']) ? $bindingData['transport'] : ''; $this->bindings[$binding]['operations'][$operation]['documentation'] = isset($this->portTypes[$bindingData['portType']][$operation]['documentation']) ? $this->portTypes[$bindingData['portType']][$operation]['documentation'] : ''; $this->bindings[$binding]['operations'][$operation]['endpoint'] = isset($bindingData['endpoint']) ? $bindingData['endpoint'] : ''; } } } return true; } /** * start-element handler * * @param string $parser XML parser object * @param string $name element name * @param string $attrs associative array of attributes * @access private */ function start_element($parser, $name, $attrs) { if ($this->status == 'schema' || ereg('schema$', $name)) { // $this->debug("startElement for $name ($attrs[name]). status = $this->status (".$this->getLocalPart($name).")"); $this->status = 'schema'; $this->schemaStartElement($parser, $name, $attrs); } else { // position in the total number of elements, starting from 0 $pos = $this->position++; $depth = $this->depth++; // set self as current value for this depth $this->depth_array[$depth] = $pos; $this->message[$pos] = array('cdata' => ''); // get element prefix if (ereg(':', $name)) { // get ns prefix $prefix = substr($name, 0, strpos($name, ':')); // get ns $namespace = isset($this->namespaces[$prefix]) ? $this->namespaces[$prefix] : ''; // get unqualified name $name = substr(strstr($name, ':'), 1); } if (count($attrs) > 0) { foreach ($attrs as $k => $v) { // if ns declarations, add to class level array of valid namespaces if (ereg("^xmlns", $k)) { if ($ns_prefix = substr(strrchr($k, ':'), 1)) { $this->namespaces[$ns_prefix] = $v; } else { $this->namespaces['ns' . (count($this->namespaces) + 1)] = $v; } if ($v == 'http://www.w3.org/2001/XMLSchema' || $v == 'http://www.w3.org/1999/XMLSchema') { $this->XMLSchemaVersion = $v; $this->namespaces['xsi'] = $v . '-instance'; } } // // expand each attribute $k = strpos($k, ':') ? $this->expandQname($k) : $k; if ($k != 'location' && $k != 'soapAction' && $k != 'namespace') { $v = strpos($v, ':') ? $this->expandQname($v) : $v; } $eAttrs[$k] = $v; } $attrs = $eAttrs; } else { $attrs = array(); } // find status, register data switch ($this->status) { case 'message': if ($name == 'part') { if (isset($attrs['type'])) { $this->debug("msg " . $this->currentMessage . ": found part $attrs[name]: " . implode(',', $attrs)); $this->messages[$this->currentMessage][$attrs['name']] = $attrs['type']; } if (isset($attrs['element'])) { $this->messages[$this->currentMessage][$attrs['name']] = $attrs['element']; } } break; case 'portType': switch ($name) { case 'operation': $this->currentPortOperation = $attrs['name']; $this->debug("portType $this->currentPortType operation: $this->currentPortOperation"); if (isset($attrs['parameterOrder'])) { $this->portTypes[$this->currentPortType][$attrs['name']]['parameterOrder'] = $attrs['parameterOrder']; } break; case 'documentation': $this->documentation = true; break; // merge input/output data default: $m = isset($attrs['message']) ? $this->getLocalPart($attrs['message']) : ''; $this->portTypes[$this->currentPortType][$this->currentPortOperation][$name]['message'] = $m; break; } break; case 'binding': switch ($name) { case 'binding': // get ns prefix if (isset($attrs['style'])) { $this->bindings[$this->currentBinding]['prefix'] = $prefix; } $this->bindings[$this->currentBinding] = array_merge($this->bindings[$this->currentBinding], $attrs); break; case 'header': $this->bindings[$this->currentBinding]['operations'][$this->currentOperation][$this->opStatus]['headers'][] = $attrs; break; case 'operation': if (isset($attrs['soapAction'])) { $this->bindings[$this->currentBinding]['operations'][$this->currentOperation]['soapAction'] = $attrs['soapAction']; } if (isset($attrs['style'])) { $this->bindings[$this->currentBinding]['operations'][$this->currentOperation]['style'] = $attrs['style']; } if (isset($attrs['name'])) { $this->currentOperation = $attrs['name']; $this->debug("current binding operation: $this->currentOperation"); $this->bindings[$this->currentBinding]['operations'][$this->currentOperation]['name'] = $attrs['name']; $this->bindings[$this->currentBinding]['operations'][$this->currentOperation]['binding'] = $this->currentBinding; $this->bindings[$this->currentBinding]['operations'][$this->currentOperation]['endpoint'] = isset($this->bindings[$this->currentBinding]['endpoint']) ? $this->bindings[$this->currentBinding]['endpoint'] : ''; } break; case 'input': $this->opStatus = 'input'; break; case 'output': $this->opStatus = 'output'; break; case 'body': if (isset($this->bindings[$this->currentBinding]['operations'][$this->currentOperation][$this->opStatus])) { $this->bindings[$this->currentBinding]['operations'][$this->currentOperation][$this->opStatus] = array_merge($this->bindings[$this->currentBinding]['operations'][$this->currentOperation][$this->opStatus], $attrs); } else { $this->bindings[$this->currentBinding]['operations'][$this->currentOperation][$this->opStatus] = $attrs; } break; } break; case 'service': switch ($name) { case 'port': $this->currentPort = $attrs['name']; $this->debug('current port: ' . $this->currentPort); $this->ports[$this->currentPort]['binding'] = $this->getLocalPart($attrs['binding']); break; case 'address': $this->ports[$this->currentPort]['location'] = $attrs['location']; $this->ports[$this->currentPort]['bindingType'] = $namespace; $this->bindings[$this->ports[$this->currentPort]['binding']]['bindingType'] = $namespace; $this->bindings[$this->ports[$this->currentPort]['binding']]['endpoint'] = $attrs['location']; break; } break; } // set status switch ($name) { case "import": if (isset($attrs['location'])) { $this->import[$attrs['namespace']] = $attrs['location']; } break; case 'types': $this->status = 'schema'; break; case 'message': $this->status = 'message'; $this->messages[$attrs['name']] = array(); $this->currentMessage = $attrs['name']; break; case 'portType': $this->status = 'portType'; $this->portTypes[$attrs['name']] = array(); $this->currentPortType = $attrs['name']; break; case "binding": if (isset($attrs['name'])) { // get binding name if (strpos($attrs['name'], ':')) { $this->currentBinding = $this->getLocalPart($attrs['name']); } else { $this->currentBinding = $attrs['name']; } $this->status = 'binding'; $this->bindings[$this->currentBinding]['portType'] = $this->getLocalPart($attrs['type']); $this->debug("current binding: $this->currentBinding of portType: " . $attrs['type']); } break; case 'service': $this->serviceName = $attrs['name']; $this->status = 'service'; $this->debug('current service: ' . $this->serviceName); break; case 'definitions': foreach ($attrs as $name => $value) { $this->wsdl_info[$name] = $value; } break; } } } /** * end-element handler * * @param string $parser XML parser object * @param string $name element name * @access private */ function end_element($parser, $name) { // unset schema status if (ereg('types$', $name) || ereg('schema$', $name)) { $this->status = ""; } if ($this->status == 'schema') { $this->schemaEndElement($parser, $name); } else { // bring depth down a notch $this->depth--; } // end documentation if ($this->documentation) { $this->portTypes[$this->currentPortType][$this->currentPortOperation]['documentation'] = $this->documentation; $this->documentation = false; } } /** * element content handler * * @param string $parser XML parser object * @param string $data element content * @access private */ function character_data($parser, $data) { $pos = isset($this->depth_array[$this->depth]) ? $this->depth_array[$this->depth] : 0; if (isset($this->message[$pos]['cdata'])) { $this->message[$pos]['cdata'] .= $data; } if ($this->documentation) { $this->documentation .= $data; } } function getBindingData($binding) { if (is_array($this->bindings[$binding])) { return $this->bindings[$binding]; } } /** * returns an assoc array of operation names => operation data * NOTE: currently only supports multiple services of differing binding types * This method needs some work * * @param string $bindingType eg: soap, smtp, dime (only soap is currently supported) * @return array * @access public */ function getOperations($bindingType = 'soap') { if ($bindingType == 'soap') { $bindingType = 'http://schemas.xmlsoap.org/wsdl/soap/'; } // loop thru ports foreach ($this->ports as $port => $portData) { // binding type of port matches parameter if ($portData['bindingType'] == $bindingType) { // get binding return $this->bindings[$portData['binding']]['operations']; } } return array(); } /** * returns an associative array of data necessary for calling an operation * * @param string $operation , name of operation * @param string $bindingType , type of binding eg: soap * @return array * @access public */ function getOperationData($operation, $bindingType = 'soap') { if ($bindingType == 'soap') { $bindingType = 'http://schemas.xmlsoap.org/wsdl/soap/'; } // loop thru ports foreach ($this->ports as $port => $portData) { // binding type of port matches parameter if ($portData['bindingType'] == $bindingType) { // get binding //foreach($this->bindings[ $portData['binding'] ]['operations'] as $bOperation => $opData) { foreach (array_keys($this->bindings[$portData['binding']]['operations']) as $bOperation) { if ($operation == $bOperation) { $opData = $this->bindings[$portData['binding']]['operations'][$operation]; return $opData; } } } } } /** * serialize the parsed wsdl * * @return string , serialization of WSDL * @access public */ function serialize() { $xml = 'namespaces as $k => $v) { $xml .= " xmlns:$k=\"$v\""; } // 10.9.02 - add poulter fix for wsdl and tns declarations if (isset($this->namespaces['wsdl'])) { $xml .= " xmlns=\"" . $this->namespaces['wsdl'] . "\""; } if (isset($this->namespaces['tns'])) { $xml .= " targetNamespace=\"" . $this->namespaces['tns'] . "\""; } $xml .= '>'; // imports if (sizeof($this->import) > 0) { foreach ($this->import as $ns => $url) { $xml .= ''; } } // types if (count($this->complexTypes) >= 1) { $xml .= ''; $xml .= $this->serializeSchema(); $xml .= ''; } // messages if (count($this->messages) >= 1) { foreach ($this->messages as $msgName => $msgParts) { $xml .= ''; foreach ($msgParts as $partName => $partType) { // print 'serializing '.$partType.', sv: '.$this->XMLSchemaVersion.'
'; if (strpos($partType, ':')) { $typePrefix = $this->getPrefixFromNamespace($this->getPrefix($partType)); } elseif (isset($this->typemap[$this->namespaces['xsd']][$partType])) { // print 'checking typemap: '.$this->XMLSchemaVersion.'
'; $typePrefix = 'xsd'; } else { foreach ($this->typemap as $ns => $types) { if (isset($types[$partType])) { $typePrefix = $this->getPrefixFromNamespace($ns); } } if (!isset($typePrefix)) { die("$partType has no namespace!"); } } $xml .= ''; } $xml .= '
'; } } // bindings & porttypes if (count($this->bindings) >= 1) { $binding_xml = ''; $portType_xml = ''; foreach ($this->bindings as $bindingName => $attrs) { $binding_xml .= ''; $binding_xml .= ''; $portType_xml .= ''; foreach ($attrs['operations'] as $opName => $opParts) { $binding_xml .= ''; $binding_xml .= ''; $binding_xml .= ''; $binding_xml .= ''; $binding_xml .= ''; $portType_xml .= ''; $portType_xml .= ''; $portType_xml .= ''; } $portType_xml .= ''; $binding_xml .= ''; } $xml .= $portType_xml . $binding_xml; } // services $xml .= ''; if (count($this->ports) >= 1) { foreach ($this->ports as $pName => $attrs) { $xml .= ''; $xml .= ''; $xml .= ''; } } $xml .= ''; return $xml . ''; } /** * serialize a PHP value according to a WSDL message definition * * TODO * - multi-ref serialization * - validate PHP values against type definitions, return errors if invalid * * @param string $ type name * @param mixed $ param value * @return mixed new param or false if initial value didn't validate */ function serializeRPCParameters($operation, $direction, $parameters) { $this->debug('in serializeRPCParameters with operation ' . $operation . ', direction ' . $direction . ' and ' . count($parameters) . ' param(s), and xml schema version ' . $this->XMLSchemaVersion); if ($direction != 'input' && $direction != 'output') { $this->debug('The value of the \$direction argument needs to be either "input" or "output"'); $this->setError('The value of the \$direction argument needs to be either "input" or "output"'); return false; } if (!$opData = $this->getOperationData($operation)) { $this->debug('Unable to retrieve WSDL data for operation: ' . $operation); $this->setError('Unable to retrieve WSDL data for operation: ' . $operation); return false; } $this->debug($this->varDump($opData)); // set input params $xml = ''; if (isset($opData[$direction]['parts']) && sizeof($opData[$direction]['parts']) > 0) { $use = $opData[$direction]['use']; $this->debug("use=$use"); $this->debug('got ' . count($opData[$direction]['parts']) . ' part(s)'); foreach ($opData[$direction]['parts'] as $name => $type) { $this->debug('serializing part "' . $name . '" of type "' . $type . '"'); // NOTE: add error handling here // if serializeType returns false, then catch global error and fault if (isset($parameters[$name])) { $this->debug('calling serializeType w/ named param'); $xml .= $this->serializeType($name, $type, $parameters[$name], $use); } elseif (is_array($parameters)) { $this->debug('calling serializeType w/ unnamed param'); $xml .= $this->serializeType($name, $type, array_shift($parameters), $use); } else { $this->debug('no parameters passed.'); } } } return $xml; } /** * serializes a PHP value according a given type definition * * @param string $name , name of type (part) * @param string $type , type of type, heh (type or element) * @param mixed $value , a native PHP value (parameter value) * @param string $use , use for part (encoded|literal) * @return string serialization * @access public */ function serializeType($name, $type, $value, $use = 'encoded') { $this->debug("in serializeType: $name, $type, $value, $use"); $xml = ''; if (strpos($type, ':')) { $uqType = substr($type, strrpos($type, ':') + 1); $ns = substr($type, 0, strrpos($type, ':')); $this->debug("got a prefixed type: $uqType, $ns"); if ($ns == $this->XMLSchemaVersion || ($this->getNamespaceFromPrefix($ns)) == $this->XMLSchemaVersion ) { if ($uqType == 'boolean' && !$value) { $value = 0; } elseif ($uqType == 'boolean') { $value = 1; } if ($this->charencoding && $uqType == 'string' && gettype($value) == 'string') { $value = htmlspecialchars($value); } // it's a scalar if ($use == 'literal') { return "<$name>$value"; } else { return "<$name xsi:type=\"" . $this->getPrefixFromNamespace($this->XMLSchemaVersion) . ":$uqType\">$value"; } } } else { $uqType = $type; } if (!$typeDef = $this->getTypeDef($uqType)) { $this->setError("$uqType is not a supported type."); return false; } else { //foreach($typeDef as $k => $v) { //$this->debug("typedef, $k: $v"); //} } $phpType = $typeDef['phpType']; $this->debug("serializeType: uqType: $uqType, ns: $ns, phptype: $phpType, arrayType: " . (isset($typeDef['arrayType']) ? $typeDef['arrayType'] : '')); // if php type == struct, map value to the element names if ($phpType == 'struct') { if (isset($typeDef['element']) && $typeDef['element']) { $elementName = $uqType; // TODO: use elementFormDefault="qualified|unqualified" to determine // how to scope the namespace $elementNS = " xmlns=\"$ns\""; } else { $elementName = $name; $elementNS = ''; } if ($use == 'literal') { $xml = "<$elementName$elementNS>"; } else { $xml = "<$elementName$elementNS xsi:type=\"" . $this->getPrefixFromNamespace($ns) . ":$uqType\">"; } if (isset($this->complexTypes[$uqType]['elements']) && is_array($this->complexTypes[$uqType]['elements'])) { //if (is_array($this->complexTypes[$uqType]['elements'])) { // toggle whether all elements are present - ideally should validate against schema if (count($this->complexTypes[$uqType]['elements']) != count($value)) { $optionals = true; } foreach ($this->complexTypes[$uqType]['elements'] as $eName => $attrs) { // if user took advantage of a minOccurs=0, then only serialize named parameters if (isset($optionals) && !isset($value[$eName])) { // do nothing } else { // get value if (isset($value[$eName])) { $v = $value[$eName]; } elseif (is_array($value)) { $v = array_shift($value); } // serialize schema-defined type if (!isset($attrs['type'])) { $xml .= $this->serializeType($eName, $attrs['name'], $v, $use); // serialize generic type } else { $this->debug("calling serialize_val() for $eName, $v, " . $this->getLocalPart($attrs['type']), false, $use); $xml .= $this->serialize_val($v, $eName, $this->getLocalPart($attrs['type']), null, $this->getNamespaceFromPrefix($this->getPrefix($attrs['type'])), false, $use); } } } } $xml .= ""; } elseif ($phpType == 'array') { $rows = sizeof($value); if (isset($typeDef['multidimensional'])) { $nv = array(); foreach ($value as $v) { $cols = ',' . sizeof($v); $nv = array_merge($nv, $v); } $value = $nv; } else { $cols = ''; } if (is_array($value) && sizeof($value) >= 1) { $contents = ''; foreach ($value as $k => $v) { $this->debug("serializing array element: $k, $v of type: $typeDef[arrayType]"); //if (strpos($typeDef['arrayType'], ':') ) { if (!in_array($typeDef['arrayType'], $this->typemap['http://www.w3.org/2001/XMLSchema'])) { $contents .= $this->serializeType('item', $typeDef['arrayType'], $v, $use); } else { $contents .= $this->serialize_val($v, 'item', $typeDef['arrayType'], null, $this->XMLSchemaVersion, false, $use); } } $this->debug('contents: ' . $this->varDump($contents)); } else { $contents = null; } if ($use == 'literal') { $xml = "<$name>" . $contents . ""; } else { $xml = "<$name xsi:type=\"" . $this->getPrefixFromNamespace('http://schemas.xmlsoap.org/soap/encoding/') . ':Array" ' . $this->getPrefixFromNamespace('http://schemas.xmlsoap.org/soap/encoding/') . ':arrayType="' . $this->getPrefixFromNamespace($this->getPrefix($typeDef['arrayType'])) . ":" . $this->getLocalPart($typeDef['arrayType']) . "[$rows$cols]\">" . $contents . ""; } } $this->debug('returning: ' . $this->varDump($xml)); return $xml; } /** * register a service with the server * * @param string $methodname * @param string $in assoc array of input values: key = param name, value = param type * @param string $out assoc array of output values: key = param name, value = param type * @param string $namespace * @param string $soapaction * @param string $style (rpc|literal) * @access public */ function addOperation($name, $in = false, $out = false, $namespace = false, $soapaction = false, $style = 'rpc', $use = 'encoded', $documentation = '') { if ($style == 'rpc' && $use == 'encoded') { $encodingStyle = 'http://schemas.xmlsoap.org/soap/encoding/'; } else { $encodingStyle = ''; } // get binding $this->bindings[$this->serviceName . 'Binding']['operations'][$name] = array( 'name' => $name, 'binding' => $this->serviceName . 'Binding', 'endpoint' => $this->endpoint, 'soapAction' => $soapaction, 'style' => $style, 'input' => array( 'use' => $use, 'namespace' => $namespace, 'encodingStyle' => $encodingStyle, 'message' => $name . 'Request', 'parts' => $in), 'output' => array( 'use' => $use, 'namespace' => $namespace, 'encodingStyle' => $encodingStyle, 'message' => $name . 'Response', 'parts' => $out), 'namespace' => $namespace, 'transport' => 'http://schemas.xmlsoap.org/soap/http', 'documentation' => $documentation); // add portTypes // add messages if ($in) { foreach ($in as $pName => $pType) { if (strpos($pType, ':')) { $pType = $this->getNamespaceFromPrefix($this->getPrefix($pType)) . ":" . $this->getLocalPart($pType); } $this->messages[$name . 'Request'][$pName] = $pType; } } if ($out) { foreach ($out as $pName => $pType) { if (strpos($pType, ':')) { $pType = $this->getNamespaceFromPrefix($this->getPrefix($pType)) . ":" . $this->getLocalPart($pType); } $this->messages[$name . 'Response'][$pName] = $pType; } } return true; } } /** * * soap_parser class parses SOAP XML messages into native PHP values * * @author Dietrich Ayala * @version v 0.6.3 * @access public */ class soap_parser extends nusoap_base { var $xml = ''; var $xml_encoding = ''; var $method = ''; var $root_struct = ''; var $root_struct_name = ''; var $root_header = ''; var $document = ''; // determines where in the message we are (envelope,header,body,method) var $status = ''; var $position = 0; var $depth = 0; var $default_namespace = ''; var $namespaces = array(); var $message = array(); var $parent = ''; var $fault = false; var $fault_code = ''; var $fault_str = ''; var $fault_detail = ''; var $depth_array = array(); var $debug_flag = true; var $soapresponse = NULL; var $responseHeaders = ''; var $body_position = 0; // for multiref parsing: // array of id => pos var $ids = array(); // array of id => hrefs => pos var $multirefs = array(); /** * constructor * * @param string $xml SOAP message * @param string $encoding character encoding scheme of message * @access public */ function soap_parser($xml, $encoding = 'UTF-8', $method = '') { $this->xml = $xml; $this->xml_encoding = $encoding; $this->method = $method; // Check whether content has been read. if (!empty($xml)) { $this->debug('Entering soap_parser()'); // Create an XML parser. $this->parser = xml_parser_create($this->xml_encoding); // Set the options for parsing the XML data. //xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 1); xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING, 0); // Set the object for the parser. xml_set_object($this->parser, $this); // Set the element handlers for the parser. xml_set_element_handler($this->parser, 'start_element', 'end_element'); xml_set_character_data_handler($this->parser, 'character_data'); // Parse the XML file. if (!xml_parse($this->parser, $xml, true)) { // Display an error message. $err = sprintf('XML error on line %d: %s', xml_get_current_line_number($this->parser), xml_error_string(xml_get_error_code($this->parser))); $this->debug('parse error: ' . $err); $this->errstr = $err; } else { $this->debug('parsed successfully, found root struct: ' . $this->root_struct . ' of name ' . $this->root_struct_name); // get final value $this->soapresponse = $this->message[$this->root_struct]['result']; // get header value if ($this->root_header != '' && isset($this->message[$this->root_header]['result'])) { $this->responseHeaders = $this->message[$this->root_header]['result']; } // resolve hrefs/ids if (sizeof($this->multirefs) > 0) { foreach ($this->multirefs as $id => $hrefs) { $this->debug('resolving multirefs for id: ' . $id); $idVal = $this->buildVal($this->ids[$id]); foreach ($hrefs as $refPos => $ref) { $this->debug('resolving href at pos ' . $refPos); $this->multirefs[$id][$refPos] = $idVal; } } } } xml_parser_free($this->parser); } else { $this->debug('xml was empty, didn\'t parse!'); $this->errstr = 'xml was empty, didn\'t parse!'; } } /** * start-element handler * * @param string $parser XML parser object * @param string $name element name * @param string $attrs associative array of attributes * @access private */ function start_element($parser, $name, $attrs) { // position in a total number of elements, starting from 0 // update class level pos $pos = $this->position++; // and set mine $this->message[$pos] = array('pos' => $pos, 'children' => '', 'cdata' => ''); // depth = how many levels removed from root? // set mine as current global depth and increment global depth value $this->message[$pos]['depth'] = $this->depth++; // else add self as child to whoever the current parent is if ($pos != 0) { $this->message[$this->parent]['children'] .= '|' . $pos; } // set my parent $this->message[$pos]['parent'] = $this->parent; // set self as current parent $this->parent = $pos; // set self as current value for this depth $this->depth_array[$this->depth] = $pos; // get element prefix if (strpos($name, ':')) { // get ns prefix $prefix = substr($name, 0, strpos($name, ':')); // get unqualified name $name = substr(strstr($name, ':'), 1); } // set status if ($name == 'Envelope') { $this->status = 'envelope'; } elseif ($name == 'Header') { $this->root_header = $pos; $this->status = 'header'; } elseif ($name == 'Body') { $this->status = 'body'; $this->body_position = $pos; // set method } elseif ($this->status == 'body' && $pos == ($this->body_position + 1)) { $this->status = 'method'; $this->root_struct_name = $name; $this->root_struct = $pos; $this->message[$pos]['type'] = 'struct'; $this->debug("found root struct $this->root_struct_name, pos $this->root_struct"); } // set my status $this->message[$pos]['status'] = $this->status; // set name $this->message[$pos]['name'] = htmlspecialchars($name); // set attrs $this->message[$pos]['attrs'] = $attrs; // loop through atts, logging ns and type declarations $attstr = ''; foreach ($attrs as $key => $value) { $key_prefix = $this->getPrefix($key); $key_localpart = $this->getLocalPart($key); // if ns declarations, add to class level array of valid namespaces if ($key_prefix == 'xmlns') { if (ereg('^http://www.w3.org/[0-9]{4}/XMLSchema$', $value)) { $this->XMLSchemaVersion = $value; $this->namespaces['xsd'] = $this->XMLSchemaVersion; $this->namespaces['xsi'] = $this->XMLSchemaVersion . '-instance'; } $this->namespaces[$key_localpart] = $value; // set method namespace if ($name == $this->root_struct_name) { $this->methodNamespace = $value; } // if it's a type declaration, set type } elseif ($key_localpart == 'type') { $value_prefix = $this->getPrefix($value); $value_localpart = $this->getLocalPart($value); $this->message[$pos]['type'] = $value_localpart; $this->message[$pos]['typePrefix'] = $value_prefix; if (isset($this->namespaces[$value_prefix])) { $this->message[$pos]['type_namespace'] = $this->namespaces[$value_prefix]; } elseif (isset($attrs['xmlns:' . $value_prefix])) { $this->message[$pos]['type_namespace'] = $attrs['xmlns:' . $value_prefix]; } // should do something here with the namespace of specified type? } elseif ($key_localpart == 'arrayType') { $this->message[$pos]['type'] = 'array'; /* do arrayType ereg here [1] arrayTypeValue ::= atype asize [2] atype ::= QName rank* [3] rank ::= '[' (',')* ']' [4] asize ::= '[' length~ ']' [5] length ::= nextDimension* Digit+ [6] nextDimension ::= Digit+ ',' */ $expr = '([A-Za-z0-9_]+):([A-Za-z]+[A-Za-z0-9_]+)\[([0-9]+),?([0-9]*)\]'; if (ereg($expr, $value, $regs)) { $this->message[$pos]['typePrefix'] = $regs[1]; $this->message[$pos]['arraySize'] = $regs[3]; $this->message[$pos]['arrayCols'] = $regs[4]; } } // log id if ($key == 'id') { $this->ids[$value] = $pos; } // root if ($key_localpart == 'root' && $value == 1) { $this->status = 'method'; $this->root_struct_name = $name; $this->root_struct = $pos; $this->debug("found root struct $this->root_struct_name, pos $pos"); } // for doclit $attstr .= " $key=\"$value\""; } // get namespace - must be done after namespace atts are processed if (isset($prefix)) { $this->message[$pos]['namespace'] = $this->namespaces[$prefix]; $this->default_namespace = $this->namespaces[$prefix]; } else { $this->message[$pos]['namespace'] = $this->default_namespace; } if ($this->status == 'header') { $this->responseHeaders .= "<$name$attstr>"; } elseif ($this->root_struct_name != '') { $this->document .= "<$name$attstr>"; } } /** * end-element handler * * @param string $parser XML parser object * @param string $name element name * @access private */ function end_element($parser, $name) { // position of current element is equal to the last value left in depth_array for my depth $pos = $this->depth_array[$this->depth--]; // get element prefix if (strpos($name, ':')) { // get ns prefix $prefix = substr($name, 0, strpos($name, ':')); // get unqualified name $name = substr(strstr($name, ':'), 1); } // build to native type if (isset($this->body_position) && $pos > $this->body_position) { // deal w/ multirefs if (isset($this->message[$pos]['attrs']['href'])) { // get id $id = substr($this->message[$pos]['attrs']['href'], 1); // add placeholder to href array $this->multirefs[$id][$pos] = "placeholder"; // add set a reference to it as the result value $this->message[$pos]['result'] =& $this->multirefs[$id][$pos]; // build complex values } elseif ($this->message[$pos]['children'] != "") { $this->message[$pos]['result'] = $this->buildVal($pos); } else { $this->debug('adding data for scalar value ' . $this->message[$pos]['name'] . ' of value ' . $this->message[$pos]['cdata']); if (is_numeric($this->message[$pos]['cdata'])) { if (strpos($this->message[$pos]['cdata'], '.')) { $this->message[$pos]['result'] = doubleval($this->message[$pos]['cdata']); } else { $this->message[$pos]['result'] = intval($this->message[$pos]['cdata']); } } else { $this->message[$pos]['result'] = $this->message[$pos]['cdata']; } } } // switch status if ($pos == $this->root_struct) { $this->status = 'body'; } elseif ($name == 'Body') { $this->status = 'header'; } elseif ($name == 'Header') { $this->status = 'envelope'; } elseif ($name == 'Envelope') { // } // set parent back to my parent $this->parent = $this->message[$pos]['parent']; // for doclit if ($this->status == 'header') { $this->responseHeaders .= ""; } elseif ($pos >= $this->root_struct) { $this->document .= ""; } } /** * element content handler * * @param string $parser XML parser object * @param string $data element content * @access private */ function character_data($parser, $data) { $pos = $this->depth_array[$this->depth]; if ($this->xml_encoding == 'UTF-8') { $data = utf8_decode($data); } $this->message[$pos]['cdata'] .= $data; // for doclit if ($this->status == 'header') { $this->responseHeaders .= $data; } else { $this->document .= $data; } } /** * get the parsed message * * @return mixed * @access public */ function get_response() { return $this->soapresponse; } /** * get the parsed headers * * @return string XML or empty if no headers * @access public */ function getHeaders() { return $this->responseHeaders; } /** * decodes entities * * @param string $text string to translate * @access private */ function decode_entities($text) { foreach ($this->entities as $entity => $encoded) { $text = str_replace($encoded, $entity, $text); } return $text; } /** * builds response structures for compound values (arrays/structs) * * @param string $pos position in node tree * @access private */ function buildVal($pos) { if (!isset($this->message[$pos]['type'])) { $this->message[$pos]['type'] = ''; } $this->debug('inside buildVal() for ' . $this->message[$pos]['name'] . "(pos $pos) of type " . $this->message[$pos]['type']); // if there are children... if ($this->message[$pos]['children'] != '') { $children = explode('|', $this->message[$pos]['children']); array_shift($children); // knock off empty // md array if (isset($this->message[$pos]['arrayCols']) && $this->message[$pos]['arrayCols'] != '') { $r = 0; // rowcount $c = 0; // colcount foreach ($children as $child_pos) { $this->debug("got an MD array element: $r, $c"); $params[$r][] = $this->message[$child_pos]['result']; $c++; if ($c == $this->message[$pos]['arrayCols']) { $c = 0; $r++; } } // array } elseif ($this->message[$pos]['type'] == 'array' || $this->message[$pos]['type'] == 'Array') { $this->debug('adding array ' . $this->message[$pos]['name']); foreach ($children as $child_pos) { $params[] = &$this->message[$child_pos]['result']; } // apache Map type: java hashtable } elseif ($this->message[$pos]['type'] == 'Map' && $this->message[$pos]['type_namespace'] == 'http://xml.apache.org/xml-soap') { foreach ($children as $child_pos) { $kv = explode("|", $this->message[$child_pos]['children']); $params[$this->message[$kv[1]]['result']] = &$this->message[$kv[2]]['result']; } // generic compound type //} elseif($this->message[$pos]['type'] == 'SOAPStruct' || $this->message[$pos]['type'] == 'struct') { } else { // is array or struct? better way to do this probably foreach ($children as $child_pos) { if (isset($keys) && isset($keys[$this->message[$child_pos]['name']])) { $struct = 1; break; } $keys[$this->message[$child_pos]['name']] = 1; } // foreach ($children as $child_pos) { if (isset($struct)) { $params[] = &$this->message[$child_pos]['result']; } else { $params[$this->message[$child_pos]['name']] = &$this->message[$child_pos]['result']; } } } return is_array($params) ? $params : array(); } else { $this->debug('no children'); if (strpos($this->message[$pos]['cdata'], '&')) { return strtr($this->message[$pos]['cdata'], array_flip($this->entities)); } else { return $this->message[$pos]['cdata']; } } } } /** * * soapclient higher level class for easy usage. * * usage: * * // instantiate client with server info * $soapclient = new soapclient( string path [ ,boolean wsdl] ); * * // call method, get results * echo $soapclient->call( string methodname [ ,array parameters] ); * * // bye bye client * unset($soapclient); * * @author Dietrich Ayala * @version v 0.6.3 * @access public */ class pwsoapclient extends nusoap_base { var $username = ''; var $password = ''; var $requestHeaders = false; var $responseHeaders; var $endpoint; var $error_str = false; var $proxyhost = ''; var $proxyport = ''; var $xml_encoding = ''; var $http_encoding = false; var $timeout = 0; var $endpointType = ''; var $persistentConnection = false; var $defaultRpcParams = false; /** * fault related variables * * @var fault * @var faultcode * @var faultstring * @var faultdetail * @access public */ var $fault, $faultcode, $faultstring, $faultdetail; /** * constructor * * @param string $endpoint SOAP server or WSDL URL * @param bool $wsdl optional, set to true if using WSDL * @param int $portName optional portName in WSDL document * @access public */ function soapclient($endpoint, $wsdl = false) { $this->endpoint = $endpoint; // make values if ($wsdl) { $this->endpointType = 'wsdl'; $this->wsdlFile = $this->endpoint; // instantiate wsdl object and parse wsdl file $this->debug('instantiating wsdl class with doc: ' . $endpoint); $this->wsdl =& new wsdl($this->wsdlFile, $this->proxyhost, $this->proxyport); $this->debug("wsdl debug: \n" . $this->wsdl->debug_str); $this->wsdl->debug_str = ''; // catch errors if ($errstr = $this->wsdl->getError()) { $this->debug('got wsdl error: ' . $errstr); $this->setError('wsdl error: ' . $errstr); } elseif ($this->operations = $this->wsdl->getOperations()) { $this->debug('got ' . count($this->operations) . ' operations from wsdl ' . $this->wsdlFile); } else { $this->debug('getOperations returned false'); $this->setError('no operations defined in the WSDL document!'); } } } /** * calls method, returns PHP native type * * @param string $method SOAP server URL or path * @param array $params array of parameters, can be associative or not * @param string $namespace optional method namespace * @param string $soapAction optional SOAPAction value * @param boolean $headers optional array of soapval objects for headers * @param boolean $rpcParams optional treat params as RPC for use="literal" * This can be used on a per-call basis to overrider defaultRpcParams. * @return mixed * @access public */ function call($operation, $params = array(), $namespace = '', $soapAction = '', $headers = false, $rpcParams = null) { $this->operation = $operation; $this->fault = false; $this->error_str = ''; $this->request = ''; $this->response = ''; $this->faultstring = ''; $this->faultcode = ''; $this->opData = array(); $this->debug("call: $operation, $params, $namespace, $soapAction, $headers, $rpcParams"); $this->debug("endpointType: $this->endpointType"); // if wsdl, get operation data and process parameters if ($this->endpointType == 'wsdl' && $opData = $this->getOperationData($operation)) { $this->opData = $opData; foreach ($opData as $key => $value) { $this->debug("$key -> $value"); } $soapAction = $opData['soapAction']; $this->endpoint = $opData['endpoint']; $namespace = isset($opData['input']['namespace']) ? $opData['input']['namespace'] : 'http://testuri.org'; $style = $opData['style']; // add ns to ns array if ($namespace != '' && !isset($this->wsdl->namespaces[$namespace])) { $this->wsdl->namespaces['nu'] = $namespace; } // serialize payload if ($opData['input']['use'] == 'literal') { if (is_null($rpcParams)) { $rpcParams = $this->defaultRpcParams; } if ($rpcParams) { $this->debug("serializing literal params for operation $operation"); $payload = $this->wsdl->serializeRPCParameters($operation, 'input', $params); $defaultNamespace = $this->wsdl->wsdl_info['targetNamespace']; } else { $this->debug("serializing literal document for operation $operation"); $payload = is_array($params) ? array_shift($params) : $params; } } else { $this->debug("serializing encoded params for operation $operation"); $payload = "<" . $this->wsdl->getPrefixFromNamespace($namespace) . ":$operation>" . $this->wsdl->serializeRPCParameters($operation, 'input', $params) . 'wsdl->getPrefixFromNamespace($namespace) . ":$operation>"; } $this->debug('payload size: ' . strlen($payload)); // serialize envelope $soapmsg = $this->serializeEnvelope($payload, $this->requestHeaders, $this->wsdl->usedNamespaces, $style); $this->debug("wsdl debug: \n" . $this->wsdl->debug_str); $this->wsdl->debug_str = ''; } elseif ($this->endpointType == 'wsdl') { $this->setError('operation ' . $operation . ' not present.'); $this->debug("operation '$operation' not present."); $this->debug("wsdl debug: \n" . $this->wsdl->debug_str); return false; // no wsdl } else { // make message if (!isset($style)) { $style = 'rpc'; } if ($namespace == '') { $namespace = 'http://testuri.org'; $this->wsdl->namespaces['ns1'] = $namespace; } // serialize envelope $payload = ''; foreach ($params as $k => $v) { $payload .= $this->serialize_val($v, $k); } $payload = "\n" . $payload . "\n"; $soapmsg = $this->serializeEnvelope($payload, $this->requestHeaders); } $this->debug("endpoint: $this->endpoint, soapAction: $soapAction, namespace: $namespace"); // send $this->debug('sending msg (len: ' . strlen($soapmsg) . ") w/ soapaction '$soapAction'..."); $return = $this->send($soapmsg, $soapAction, $this->timeout); if ($errstr = $this->getError()) { $this->debug('Error: ' . $errstr); return false; } else { $this->return = $return; $this->debug('sent message successfully and got a(n) ' . gettype($return) . ' back'); // fault? if (is_array($return) && isset($return['faultcode'])) { $this->debug('got fault'); $this->setError($return['faultcode'] . ': ' . $return['faultstring']); $this->fault = true; foreach ($return as $k => $v) { $this->$k = $v; $this->debug("$k = $v
"); } return $return; } else { // array of return values if (is_array($return)) { // multiple 'out' parameters if (sizeof($return) > 1) { return $return; } // single 'out' parameter return array_shift($return); // nothing returned (ie, echoVoid) } else { return ""; } } } } /** * get available data pertaining to an operation * * @param string $operation operation name * @return array array of data pertaining to the operation * @access public */ function getOperationData($operation) { if (isset($this->operations[$operation])) { return $this->operations[$operation]; } $this->debug("No data for operation: $operation"); } /** * send the SOAP message * * Note: if the operation has multiple return values * the return value of this method will be an array * of those values. * * @param string $msg a SOAPx4 soapmsg object * @param string $soapaction SOAPAction value * @param integer $timeout set timeout in seconds * @return mixed native PHP types. * @access private */ function send($msg, $soapaction = '', $timeout = 0) { // detect transport switch (true) { // http(s) case ereg('^http', $this->endpoint): $this->debug('transporting via HTTP'); if ($this->persistentConnection && is_object($this->persistentConnection)) { $http =& $this->persistentConnection; } else { $http = new soap_transport_http($this->endpoint); // pass encoding into transport layer, so appropriate http headers are sent $http->soap_defencoding = $this->soap_defencoding; } $http->setSOAPAction($soapaction); if ($this->proxyhost && $this->proxyport) { $http->setProxy($this->proxyhost, $this->proxyport); } if ($this->username != '' && $this->password != '') { $http->setCredentials($this->username, $this->password); } if ($this->http_encoding != '') { $http->setEncoding($this->http_encoding); } $this->debug('sending message, length: ' . strlen($msg)); if (ereg('^http:', $this->endpoint)) { //if(strpos($this->endpoint,'http:')){ $response = $http->send($msg, $timeout); } elseif (ereg('^https', $this->endpoint)) { //} elseif(strpos($this->endpoint,'https:')){ //if(phpversion() == '4.3.0-dev'){ //$response = $http->send($msg,$timeout); //$this->request = $http->outgoing_payload; //$this->response = $http->incoming_payload; //} else if (extension_loaded('curl')) { $response = $http->sendHTTPS($msg, $timeout); } else { $this->setError('CURL Extension, or OpenSSL extension w/ PHP version >= 4.3 is required for HTTPS'); } } else { $this->setError('no http/s in endpoint url'); } $this->request = $http->outgoing_payload; $this->response = $http->incoming_payload; $this->debug("transport debug data...\n" . $http->debug_str); // save transport object if using persistent connections if ($this->persistentConnection && !is_object($this->persistentConnection)) { $this->persistentConnection = $http; } if ($err = $http->getError()) { $this->setError('HTTP Error: ' . $err); return false; } elseif ($this->getError()) { return false; } else { $this->debug('got response, length: ' . strlen($response)); return $this->parseResponse($response); } break; default: $this->setError('no transport found, or selected transport is not yet supported!'); return false; break; } } /** * processes SOAP message returned from server * * @param string unprocessed response data from server * @return mixed value of the message, decoded into a PHP type * @access private */ function parseResponse($data) { $this->debug('Entering parseResponse(), about to create soap_parser instance'); $parser = new soap_parser($data, $this->xml_encoding, $this->operation); // if parse errors if ($errstr = $parser->getError()) { $this->setError($errstr); // destroy the parser object unset($parser); return false; } else { // get SOAP headers $this->responseHeaders = $parser->getHeaders(); // get decoded message $return = $parser->get_response(); // add parser debug data to our debug $this->debug($parser->debug_str); // add document for doclit support $this->document = $parser->document; // destroy the parser object unset($parser); // return decode message return $return; } } /** * set the SOAP headers * * @param $headers string XML * @access public */ function setHeaders($headers) { $this->requestHeaders = $headers; } /** * get the response headers * * @return mixed object SOAPx4 soapval object or empty if no headers * @access public */ function getHeaders() { if ($this->responseHeaders != '') { return $this->responseHeaders; } } /** * set proxy info here * * @param string $proxyhost * @param string $proxyport * @access public */ function setHTTPProxy($proxyhost, $proxyport) { $this->proxyhost = $proxyhost; $this->proxyport = $proxyport; } /** * if authenticating, set user credentials here * * @param string $username * @param string $password * @access public */ function setCredentials($username, $password) { $this->username = $username; $this->password = $password; } /** * use HTTP encoding * * @param string $enc * @access public */ function setHTTPEncoding($enc = 'gzip, deflate') { $this->http_encoding = $enc; } /** * use HTTP persistent connections if possible * * @access public */ function useHTTPPersistentConnection() { $this->persistentConnection = true; } /** * gets the default RPC parameter setting. * If true, default is that call params are like RPC even for document style. * Each call() can override this value. * * @access public */ function getDefaultRpcParams() { return $this->defaultRpcParams; } /** * sets the default RPC parameter setting. * If true, default is that call params are like RPC even for document style * Each call() can override this value. * * @param boolean $rpcParams * @access public */ function setDefaultRpcParams($rpcParams) { $this->defaultRpcParams = $rpcParams; } /** * dynamically creates proxy class, allowing user to directly call methods from wsdl * * @return object soap_proxy object * @access public */ function getProxy() { $evalStr = ''; foreach ($this->operations as $operation => $opData) { if ($operation != '') { // create param string $paramStr = ''; if (sizeof($opData['input']['parts']) > 0) { foreach ($opData['input']['parts'] as $name => $type) { $paramStr .= "\$$name,"; } $paramStr = substr($paramStr, 0, strlen($paramStr) - 1); } $opData['namespace'] = !isset($opData['namespace']) ? 'http://testuri.com' : $opData['namespace']; $evalStr .= "function $operation ($paramStr){ // load params into array \$params = array($paramStr); return \$this->call('$operation',\$params,'" . $opData['namespace'] . "','" . $opData['soapAction'] . "'); }"; unset($paramStr); } } $r = rand(); $evalStr = 'class soap_proxy_' . $r . ' extends soapclient { ' . $evalStr . ' }'; //print "proxy class:
$evalStr
"; // eval the class eval($evalStr); // instantiate proxy object eval("\$proxy = new soap_proxy_$r('');"); // transfer current wsdl data to the proxy thereby avoiding parsing the wsdl twice $proxy->endpointType = 'wsdl'; $proxy->wsdlFile = $this->wsdlFile; $proxy->wsdl = $this->wsdl; $proxy->operations = $this->operations; $proxy->defaultRpcParams = $this->defaultRpcParams; return $proxy; } } // Local Variables: // mode: php // tab-width: 8 // c-basic-offset: 4 // c-hanging-comment-ender-p: nil // indent-tabs-mode: nil // End: