6 NuSOAP - Web Services Toolkit for PHP
8 Copyright (c) 2002 NuSphere Corporation
10 This library is free software; you can redistribute it and/or
11 modify it under the terms of the GNU Lesser General Public
12 License as published by the Free Software Foundation; either
13 version 2.1 of the License, or (at your option) any later version.
15 This library is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 Lesser General Public License for more details.
20 You should have received a copy of the GNU Lesser General Public
21 License along with this library; if not, write to the Free Software
22 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 If you have any questions or comments, please email:
28 http://dietrich.ganx4.com/nusoap
31 http://www.nusphere.com
38 require_once('class.soapclient.php');
39 require_once('class.soap_val.php');
40 require_once('class.soap_parser.php');
41 require_once('class.soap_fault.php');
44 require_once('class.soap_transport_http.php');
46 // optional add-on classes
47 require_once('class.xmlschema.php');
48 require_once('class.wsdl.php');
51 require_once('class.soap_server.php');*/
57 * @author Dietrich Ayala <dietrich@ganx4.com>
63 var $title = 'NuSOAP';
64 var $version = '0.6.7';
65 var $revision = '$Revision: 1.1 $';
66 var $error_str = false;
68 // toggles automatic encoding of special characters as entities
69 // (should always be true, I think)
70 var $charencoding = true;
75 * @var XMLSchemaVersion
78 var $XMLSchemaVersion = 'http://www.w3.org/2001/XMLSchema';
81 * set charset encoding for outgoing messages
83 * @var soap_defencoding
86 //var $soap_defencoding = 'UTF-8';
87 var $soap_defencoding = 'ISO-8859-1';
90 * load namespace uris into an array of uri => prefix
95 var $namespaces = array(
96 'SOAP-ENV' => 'http://schemas.xmlsoap.org/soap/envelope/',
97 'xsd' => 'http://www.w3.org/2001/XMLSchema',
98 'xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
99 'SOAP-ENC' => 'http://schemas.xmlsoap.org/soap/encoding/',
100 'si' => 'http://soapinterop.org/xsd');
101 var $usedNamespaces = array();
104 * load types into typemap array
105 * is this legacy yet?
106 * no, this is used by the xmlschema class to verify type => namespace mappings.
110 var $typemap = array(
111 'http://www.w3.org/2001/XMLSchema' => array(
112 'string'=>'string','boolean'=>'boolean','float'=>'double','double'=>'double','decimal'=>'double',
113 'duration'=>'','dateTime'=>'string','time'=>'string','date'=>'string','gYearMonth'=>'',
114 'gYear'=>'','gMonthDay'=>'','gDay'=>'','gMonth'=>'','hexBinary'=>'string','base64Binary'=>'string',
116 'normalizedString'=>'string','token'=>'string','language'=>'','NMTOKEN'=>'','NMTOKENS'=>'','Name'=>'','NCName'=>'','ID'=>'',
117 'IDREF'=>'','IDREFS'=>'','ENTITY'=>'','ENTITIES'=>'','integer'=>'integer','nonPositiveInteger'=>'integer',
118 'negativeInteger'=>'integer','long'=>'integer','int'=>'integer','short'=>'integer','byte'=>'integer','nonNegativeInteger'=>'integer',
119 'unsignedLong'=>'','unsignedInt'=>'','unsignedShort'=>'','unsignedByte'=>'','positiveInteger'=>''),
120 'http://www.w3.org/1999/XMLSchema' => array(
121 'i4'=>'','int'=>'integer','boolean'=>'boolean','string'=>'string','double'=>'double',
122 'float'=>'double','dateTime'=>'string',
123 'timeInstant'=>'string','base64Binary'=>'string','base64'=>'string','ur-type'=>'array'),
124 'http://soapinterop.org/xsd' => array('SOAPStruct'=>'struct'),
125 'http://schemas.xmlsoap.org/soap/encoding/' => array('base64'=>'string','array'=>'array','Array'=>'array'),
126 'http://xml.apache.org/xml-soap' => array('Map')
130 * entities to convert
135 var $xmlEntities = array('quot' => '"','amp' => '&',
136 'lt' => '<','gt' => '>','apos' => "'");
139 * adds debug data to the class level debug string
141 * @param string $string debug data
144 function debug($string){
145 $this->debug_str .= get_class($this).": $string\n";
149 * expands entities, e.g. changes '<' to '<'.
151 * @param string $val The string in which to expand entities.
154 function expandEntities($val) {
155 if ($this->charencoding) {
156 $val = str_replace('&', '&', $val);
157 $val = str_replace("'", ''', $val);
158 $val = str_replace('"', '"', $val);
159 $val = str_replace('<', '<', $val);
160 $val = str_replace('>', '>', $val);
166 * returns error string if present
168 * @return boolean $string error string
172 if($this->error_str != ''){
173 return $this->error_str;
181 * @return boolean $string error string
184 function setError($str){
185 $this->error_str = $str;
189 * detect if array is a simple array or a struct (associative array)
191 * @param $val The PHP array
192 * @return string (arraySimple|arrayStruct)
195 function isArraySimpleOrStruct($val) {
196 $keyList = array_keys($val);
197 foreach ($keyList as $keyListValue) {
198 if (!is_int($keyListValue)) {
199 return 'arrayStruct';
202 return 'arraySimple';
206 * serializes PHP values in accordance w/ section 5. Type information is
207 * not serialized if $use == 'literal'.
212 function serialize_val($val,$name=false,$type=false,$name_ns=false,$type_ns=false,$attributes=false,$use='encoded'){
213 if(is_object($val) && get_class($val) == 'soapval'){
214 return $val->serialize($use);
216 $this->debug( "in serialize_val: $val, $name, $type, $name_ns, $type_ns, $attributes, $use");
217 // if no name, use item
218 $name = (!$name|| is_numeric($name)) ? 'soapVal' : $name;
219 // if name has ns, add ns prefix to name
222 $prefix = 'nu'.rand(1000,9999);
223 $name = $prefix.':'.$name;
224 $xmlns .= " xmlns:$prefix=\"$name_ns\"";
226 // if type is prefixed, create type prefix
227 if($type_ns != '' && $type_ns == $this->namespaces['xsd']){
228 // need to fix this. shouldn't default to xsd if no ns specified
229 // w/o checking against typemap
230 $type_prefix = 'xsd';
232 $type_prefix = 'ns'.rand(1000,9999);
233 $xmlns .= " xmlns:$type_prefix=\"$type_ns\"";
235 // serialize attributes if present
238 foreach($attributes as $k => $v){
239 $atts .= " $k=\"$v\"";
242 // serialize if an xsd built-in primitive type
243 if($type != '' && isset($this->typemap[$this->XMLSchemaVersion][$type])){
244 if(is_bool($val) && !$val){
246 } else if (is_string($val)) {
247 $val = $this->expandEntities($val);
249 if ($use == 'literal') {
250 return "<$name$xmlns>$val</$name>";
252 return "<$name$xmlns xsi:type=\"xsd:$type\">$val</$name>";
255 // detect type and serialize
258 case ($type == '' && is_null($val)):
259 if ($use == 'literal') {
260 // TODO: depends on nillable
261 $xml .= "<$name$xmlns/>";
263 $xml .= "<$name$xmlns xsi:nil=\"true\"/>";
266 case (is_bool($val) || $type == 'boolean'):
270 if ($use == 'literal') {
271 $xml .= "<$name$xmlns $atts>$val</$name>";
273 $xml .= "<$name$xmlns xsi:type=\"xsd:boolean\"$atts>$val</$name>";
276 case (is_int($val) || is_long($val) || $type == 'int'):
277 if ($use == 'literal') {
278 $xml .= "<$name$xmlns $atts>$val</$name>";
280 $xml .= "<$name$xmlns xsi:type=\"xsd:int\"$atts>$val</$name>";
283 case (is_float($val)|| is_double($val) || $type == 'float'):
284 if ($use == 'literal') {
285 $xml .= "<$name$xmlns $atts>$val</$name>";
287 $xml .= "<$name$xmlns xsi:type=\"xsd:float\"$atts>$val</$name>";
290 case (is_string($val) || $type == 'string'):
291 $val = $this->expandEntities($val);
292 if ($use == 'literal') {
293 $xml .= "<$name$xmlns $atts>$val</$name>";
295 $xml .= "<$name$xmlns xsi:type=\"xsd:string\"$atts>$val</$name>";
298 case is_object($val):
299 $name = get_class($val);
300 foreach(get_object_vars($val) as $k => $v){
301 $pXml = isset($pXml) ? $pXml.$this->serialize_val($v,$k,false,false,false,false,$use) : $this->serialize_val($v,$k,false,false,false,false,$use);
303 $xml .= '<'.$name.'>'.$pXml.'</'.$name.'>';
306 case (is_array($val) || $type):
307 // detect if struct or array
308 $valueType = $this->isArraySimpleOrStruct($val);
309 if($valueType=='arraySimple' || ereg('^ArrayOf',$type)){
311 if(is_array($val) && count($val)> 0){
313 if(is_object($v) && get_class($v) == 'soapval'){
314 $tt_ns = $v->type_ns;
319 $array_types[$tt] = 1;
320 $xml .= $this->serialize_val($v,'item',false,false,false,false,$use);
323 if(count($array_types) > 1){
324 $array_typename = 'xsd:ur-type';
325 } elseif(isset($tt) && isset($this->typemap[$this->XMLSchemaVersion][$tt])) {
326 $array_typename = 'xsd:'.$tt;
327 } elseif($tt == 'array' || $tt == 'Array'){
328 $array_typename = 'SOAP-ENC:Array';
330 // if type is prefixed, create type prefix
331 if ($tt_ns != '' && $tt_ns == $this->namespaces['xsd']){
332 $array_typename = 'xsd:' . $tt;
334 $tt_prefix = 'ns' . rand(1000, 9999);
335 $array_typename = "$tt_prefix:$tt";
336 $xmlns .= " xmlns:$tt_prefix=\"$tt_ns\"";
338 $array_typename = $tt;
342 if ($use == 'literal') {
344 } else if (isset($type) && isset($type_prefix)) {
345 $type_str = " xsi:type=\"$type_prefix:$type\"";
347 $type_str = " xsi:type=\"SOAP-ENC:Array\" SOAP-ENC:arrayType=\"".$array_typename."[$array_type]\"";
351 if ($use == 'literal') {
353 } else if (isset($type) && isset($type_prefix)) {
354 $type_str = " xsi:type=\"$type_prefix:$type\"";
356 $type_str = " xsi:type=\"SOAP-ENC:Array\"";
359 $xml = "<$name$xmlns$type_str$atts>".$xml."</$name>";
362 if(isset($type) && isset($type_prefix)){
363 $type_str = " xsi:type=\"$type_prefix:$type\"";
367 if ($use == 'literal') {
368 $xml .= "<$name$xmlns $atts>";
370 $xml .= "<$name$xmlns$type_str$atts>";
372 foreach($val as $k => $v){
374 if ($type == 'Map' && $type_ns == 'http://xml.apache.org/xml-soap') {
376 $xml .= $this->serialize_val($k,'key',false,false,false,false,$use);
377 $xml .= $this->serialize_val($v,'value',false,false,false,false,$use);
380 $xml .= $this->serialize_val($v,$k,false,false,false,false,$use);
387 $xml .= 'not detected, got '.gettype($val).' for '.$val;
397 * @param string headers optional
398 * @param array namespaces optional
399 * @param string style optional (rpc|document)
400 * @param string use optional (encoded|literal)
401 * @return string message
404 function serializeEnvelope($body,$headers=false,$namespaces=array(),$style='rpc',$use='encoded'){
405 // TODO: add an option to automatically run utf8_encode on $body and $headers
406 // if $this->soap_defencoding is UTF-8. Not doing this automatically allows
407 // one to send arbitrary UTF-8 characters, not just characters that map to ISO-8859-1
409 // serialize namespaces
411 foreach(array_merge($this->namespaces,$namespaces) as $k => $v){
412 $ns_string .= " xmlns:$k=\"$v\"";
414 if($style == 'rpc' && $use == 'encoded') {
415 $ns_string = ' SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"' . $ns_string;
420 $headers = "<SOAP-ENV:Header>".$headers."</SOAP-ENV:Header>";
422 // serialize envelope
424 '<?xml version="1.0" encoding="'.$this->soap_defencoding .'"?'.">".
425 '<SOAP-ENV:Envelope'.$ns_string.">".
430 "</SOAP-ENV:Envelope>";
433 function formatDump($str){
434 $str = htmlspecialchars($str);
439 * contracts a qualified name
441 * @param string $string qname
442 * @return string contracted qname
445 function contractQname($qname){
446 // get element namespace
447 //$this->xdebug("Contract $qname");
448 if (strrpos($qname, ':')) {
449 // get unqualified name
450 $name = substr($qname, strrpos($qname, ':') + 1);
452 $ns = substr($qname, 0, strrpos($qname, ':'));
453 $p = $this->getPrefixFromNamespace($ns);
455 return $p . ':' . $name;
464 * expands a qualified name
466 * @param string $string qname
467 * @return string expanded qname
470 function expandQname($qname){
471 // get element prefix
472 if(strpos($qname,':') && !ereg('^http://',$qname)){
473 // get unqualified name
474 $name = substr(strstr($qname,':'),1);
476 $prefix = substr($qname,0,strpos($qname,':'));
477 if(isset($this->namespaces[$prefix])){
478 return $this->namespaces[$prefix].':'.$name;
488 * returns the local part of a prefixed string
489 * returns the original string, if not prefixed
495 function getLocalPart($str){
496 if($sstr = strrchr($str,':')){
497 // get unqualified name
498 return substr( $sstr, 1 );
505 * returns the prefix part of a prefixed string
506 * returns false, if not prefixed
512 function getPrefix($str){
513 if($pos = strrpos($str,':')){
515 return substr($str,0,$pos);
521 * pass it a prefix, it returns a namespace
522 * returns false if no namespace registered with the given prefix
528 function getNamespaceFromPrefix($prefix){
529 if (isset($this->namespaces[$prefix])) {
530 return $this->namespaces[$prefix];
532 //$this->setError("No namespace registered for prefix '$prefix'");
537 * returns the prefix for a given namespace (or prefix)
538 * or false if no prefixes registered for the given namespace
544 function getPrefixFromNamespace($ns) {
545 foreach ($this->namespaces as $p => $n) {
546 if ($ns == $n || $ns == $p) {
547 $this->usedNamespaces[$p] = $n;
554 function varDump($data) {
557 $ret_val = ob_get_contents();
563 // XML Schema Datatype Helper Functions
565 //xsd:dateTime helpers
568 * convert unix timestamp to ISO 8601 compliant date string
570 * @param string $timestamp Unix time stamp
573 function timestamp_to_iso8601($timestamp,$utc=true){
574 $datestr = date('Y-m-d\TH:i:sO',$timestamp);
577 '([0-9]{4})-'. // centuries & years CCYY-
578 '([0-9]{2})-'. // months MM-
579 '([0-9]{2})'. // days DD
581 '([0-9]{2}):'. // hours hh:
582 '([0-9]{2}):'. // minutes mm:
583 '([0-9]{2})(\.[0-9]*)?'. // seconds ss.ss...
584 '(Z|[+\-][0-9]{2}:?[0-9]{2})?'; // Z to indicate UTC, -/+HH:MM:SS.SS... for local tz's
586 if(ereg($eregStr,$datestr,$regs)){
587 return sprintf('%04d-%02d-%02dT%02d:%02d:%02dZ',$regs[1],$regs[2],$regs[3],$regs[4],$regs[5],$regs[6]);
596 * convert ISO 8601 compliant date string to unix timestamp
598 * @param string $datestr ISO 8601 compliant date string
601 function iso8601_to_timestamp($datestr){
603 '([0-9]{4})-'. // centuries & years CCYY-
604 '([0-9]{2})-'. // months MM-
605 '([0-9]{2})'. // days DD
607 '([0-9]{2}):'. // hours hh:
608 '([0-9]{2}):'. // minutes mm:
609 '([0-9]{2})(\.[0-9]+)?'. // seconds ss.ss...
610 '(Z|[+\-][0-9]{2}:?[0-9]{2})?'; // Z to indicate UTC, -/+HH:MM:SS.SS... for local tz's
611 if(ereg($eregStr,$datestr,$regs)){
614 $op = substr($regs[8],0,1);
615 $h = substr($regs[8],1,2);
616 $m = substr($regs[8],strlen($regs[8])-2,2);
618 $regs[4] = $regs[4] + $h;
619 $regs[5] = $regs[5] + $m;
620 } elseif($op == '+'){
621 $regs[4] = $regs[4] - $h;
622 $regs[5] = $regs[5] - $m;
625 return strtotime("$regs[1]-$regs[2]-$regs[3] $regs[4]:$regs[5]:$regs[6]Z");
631 function usleepWindows($usec)
633 $start = gettimeofday();
637 $stop = gettimeofday();
638 $timePassed = 1000000 * ($stop['sec'] - $start['sec'])
639 + $stop['usec'] - $start['usec'];
641 while ($timePassed < $usec);
649 * soap_fault class, allows for creation of faults
650 * mainly used for returning faults from deployed functions
651 * in a server instance.
652 * @author Dietrich Ayala <dietrich@ganx4.com>
656 class soap_fault extends nusoap_base {
666 * @param string $faultcode (client | server)
667 * @param string $faultactor only used when msg routed between multiple actors
668 * @param string $faultstring human readable error message
669 * @param string $faultdetail
671 function soap_fault($faultcode,$faultactor='',$faultstring='',$faultdetail=''){
672 $this->faultcode = $faultcode;
673 $this->faultactor = $faultactor;
674 $this->faultstring = $faultstring;
675 $this->faultdetail = $faultdetail;
683 function serialize(){
685 foreach($this->namespaces as $k => $v){
686 $ns_string .= "\n xmlns:$k=\"$v\"";
689 '<?xml version="1.0" encoding="'.$this->soap_defencoding.'"?>'.
690 '<SOAP-ENV:Envelope SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"'.$ns_string.">\n".
693 '<faultcode>'.$this->expandEntities($this->faultcode).'</faultcode>'.
694 '<faultactor>'.$this->expandEntities($this->faultactor).'</faultactor>'.
695 '<faultstring>'.$this->expandEntities($this->faultstring).'</faultstring>'.
696 '<detail>'.$this->serialize_val($this->faultdetail).'</detail>'.
699 '</SOAP-ENV:Envelope>';
711 * parses an XML Schema, allows access to it's data, other utility methods
712 * no validation... yet.
713 * very experimental and limited. As is discussed on XML-DEV, I'm one of the people
714 * that just doesn't have time to read the spec(s) thoroughly, and just have a couple of trusty
715 * tutorials I refer to :)
717 * @author Dietrich Ayala <dietrich@ganx4.com>
721 class XMLSchema extends nusoap_base {
727 var $enclosingNamespaces;
729 var $schemaInfo = array();
730 var $schemaTargetNamespace = '';
731 // types, elements, attributes defined by the schema
732 var $attributes = array();
733 var $complexTypes = array();
734 var $currentComplexType = false;
735 var $elements = array();
736 var $currentElement = false;
737 var $simpleTypes = array();
738 var $currentSimpleType = false;
740 var $imports = array();
745 var $depth_array = array();
746 var $message = array();
747 var $defaultNamespace = array();
752 * @param string $schema schema document URI
753 * @param string $xml xml document URI
754 * @param string $namespaces namespaces defined in enclosing XML
757 function XMLSchema($schema='',$xml='',$namespaces=array()){
759 $this->debug('xmlschema class instantiated, inside constructor');
761 $this->schema = $schema;
765 $this->enclosingNamespaces = $namespaces;
766 $this->namespaces = array_merge($this->namespaces, $namespaces);
770 $this->debug('initial schema file: '.$schema);
771 $this->parseFile($schema, 'schema');
776 $this->debug('initial xml file: '.$xml);
777 $this->parseFile($xml, 'xml');
785 * @param string $xml, path/URL to XML file
786 * @param string $type, (schema | xml)
790 function parseFile($xml,$type){
793 $xmlStr = @join("",@file($xml));
795 $msg = 'Error reading XML from '.$xml;
796 $this->setError($msg);
800 $this->debug("parsing $xml");
801 $this->parseString($xmlStr,$type);
802 $this->debug("done parsing $xml");
810 * parse an XML string
812 * @param string $xml path or URL
813 * @param string $type, (schema|xml)
816 function parseString($xml,$type){
820 // Create an XML parser.
821 $this->parser = xml_parser_create();
822 // Set the options for parsing the XML data.
823 xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING, 0);
825 // Set the object for the parser.
826 xml_set_object($this->parser, $this);
828 // Set the element handlers for the parser.
829 if($type == "schema"){
830 xml_set_element_handler($this->parser, 'schemaStartElement','schemaEndElement');
831 xml_set_character_data_handler($this->parser,'schemaCharacterData');
832 } elseif($type == "xml"){
833 xml_set_element_handler($this->parser, 'xmlStartElement','xmlEndElement');
834 xml_set_character_data_handler($this->parser,'xmlCharacterData');
837 // Parse the XML file.
838 if(!xml_parse($this->parser,$xml,true)){
839 // Display an error message.
840 $errstr = sprintf('XML error parsing XML schema on line %d: %s',
841 xml_get_current_line_number($this->parser),
842 xml_error_string(xml_get_error_code($this->parser))
844 $this->debug($errstr);
845 $this->debug("XML payload:\n" . $xml);
846 $this->setError($errstr);
849 xml_parser_free($this->parser);
851 $this->debug('no xml passed to parseString()!!');
852 $this->setError('no xml passed to parseString()!!');
857 * start-element handler
859 * @param string $parser XML parser object
860 * @param string $name element name
861 * @param string $attrs associative array of attributes
864 function schemaStartElement($parser, $name, $attrs) {
866 // position in the total number of elements, starting from 0
867 $pos = $this->position++;
868 $depth = $this->depth++;
869 // set self as current value for this depth
870 $this->depth_array[$depth] = $pos;
871 $this->message[$pos] = array('cdata' => '');
873 $this->defaultNamespace[$pos] = $this->defaultNamespace[$this->depth_array[$depth - 1]];
875 $this->defaultNamespace[$pos] = false;
878 // get element prefix
879 if($prefix = $this->getPrefix($name)){
880 // get unqualified name
881 $name = $this->getLocalPart($name);
886 // loop thru attributes, expanding, and registering namespace declarations
887 if(count($attrs) > 0){
888 foreach($attrs as $k => $v){
889 // if ns declarations, add to class level array of valid namespaces
890 if(ereg("^xmlns",$k)){
891 //$this->xdebug("$k: $v");
892 //$this->xdebug('ns_prefix: '.$this->getPrefix($k));
893 if($ns_prefix = substr(strrchr($k,':'),1)){
894 //$this->xdebug("Add namespace[$ns_prefix] = $v");
895 $this->namespaces[$ns_prefix] = $v;
897 $this->defaultNamespace[$pos] = $v;
898 if (! $this->getPrefixFromNamespace($v)) {
899 $this->namespaces['ns'.(count($this->namespaces)+1)] = $v;
902 if($v == 'http://www.w3.org/2001/XMLSchema' || $v == 'http://www.w3.org/1999/XMLSchema'){
903 $this->XMLSchemaVersion = $v;
904 $this->namespaces['xsi'] = $v.'-instance';
908 foreach($attrs as $k => $v){
909 // expand each attribute
910 $k = strpos($k,':') ? $this->expandQname($k) : $k;
911 $v = strpos($v,':') ? $this->expandQname($v) : $v;
918 // find status, register data
923 //$this->xdebug("compositor $name for currentComplexType: $this->currentComplexType and currentElement: $this->currentElement");
924 $this->complexTypes[$this->currentComplexType]['compositor'] = $name;
925 if($name == 'all' || $name == 'sequence'){
926 $this->complexTypes[$this->currentComplexType]['phpType'] = 'struct';
930 //$this->xdebug("parsing attribute $attrs[name] $attrs[ref] of value: ".$attrs['http://schemas.xmlsoap.org/wsdl/:arrayType']);
931 $this->xdebug("parsing attribute " . $this->varDump($attrs));
932 if (isset($attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'])) {
933 $v = $attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'];
934 if (!strpos($v, ':')) {
935 // no namespace in arrayType attribute value...
936 if ($this->defaultNamespace[$pos]) {
937 // ...so use the default
938 $attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'] = $this->defaultNamespace[$pos] . ':' . $attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'];
942 if(isset($attrs['name'])){
943 $this->attributes[$attrs['name']] = $attrs;
944 $aname = $attrs['name'];
945 } elseif(isset($attrs['ref']) && $attrs['ref'] == 'http://schemas.xmlsoap.org/soap/encoding/:arrayType'){
946 if (isset($attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'])) {
947 $aname = $attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'];
951 } elseif(isset($attrs['ref'])){
952 $aname = $attrs['ref'];
953 $this->attributes[$attrs['ref']] = $attrs;
956 if(isset($this->currentComplexType)){
957 $this->complexTypes[$this->currentComplexType]['attrs'][$aname] = $attrs;
958 } elseif(isset($this->currentElement)){
959 $this->elements[$this->currentElement]['attrs'][$aname] = $attrs;
961 // arrayType attribute
962 if(isset($attrs['http://schemas.xmlsoap.org/wsdl/:arrayType']) || $this->getLocalPart($aname) == 'arrayType'){
963 $this->complexTypes[$this->currentComplexType]['phpType'] = 'array';
964 $prefix = $this->getPrefix($aname);
965 if(isset($attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'])){
966 $v = $attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'];
970 if(strpos($v,'[,]')){
971 $this->complexTypes[$this->currentComplexType]['multidimensional'] = true;
973 $v = substr($v,0,strpos($v,'[')); // clip the []
974 if(!strpos($v,':') && isset($this->typemap[$this->XMLSchemaVersion][$v])){
975 $v = $this->XMLSchemaVersion.':'.$v;
977 $this->complexTypes[$this->currentComplexType]['arrayType'] = $v;
981 if(isset($attrs['name'])){
982 $this->xdebug('processing named complexType '.$attrs['name']);
983 $this->currentElement = false;
984 $this->currentComplexType = $attrs['name'];
985 $this->complexTypes[$this->currentComplexType] = $attrs;
986 $this->complexTypes[$this->currentComplexType]['typeClass'] = 'complexType';
987 if(isset($attrs['base']) && ereg(':Array$',$attrs['base'])){
988 $this->complexTypes[$this->currentComplexType]['phpType'] = 'array';
990 $this->complexTypes[$this->currentComplexType]['phpType'] = 'struct';
993 $this->xdebug('processing unnamed complexType for element '.$this->currentElement);
994 $this->currentComplexType = $this->currentElement . '_ContainedType';
995 $this->currentElement = false;
996 $this->complexTypes[$this->currentComplexType] = $attrs;
997 $this->complexTypes[$this->currentComplexType]['typeClass'] = 'complexType';
998 if(isset($attrs['base']) && ereg(':Array$',$attrs['base'])){
999 $this->complexTypes[$this->currentComplexType]['phpType'] = 'array';
1001 $this->complexTypes[$this->currentComplexType]['phpType'] = 'struct';
1006 // elements defined as part of a complex type should
1007 // not really be added to $this->elements, but for some
1009 if(isset($attrs['type'])){
1010 $this->xdebug("processing typed element ".$attrs['name']." of type ".$attrs['type']);
1011 $this->currentElement = $attrs['name'];
1012 $this->elements[ $attrs['name'] ] = $attrs;
1013 $this->elements[ $attrs['name'] ]['typeClass'] = 'element';
1014 if (!isset($this->elements[ $attrs['name'] ]['form'])) {
1015 $this->elements[ $attrs['name'] ]['form'] = $this->schemaInfo['elementFormDefault'];
1017 $ename = $attrs['name'];
1018 } elseif(isset($attrs['ref'])){
1019 $ename = $attrs['ref'];
1021 $this->xdebug("processing untyped element ".$attrs['name']);
1022 $this->currentElement = $attrs['name'];
1023 $this->elements[ $attrs['name'] ] = $attrs;
1024 $this->elements[ $attrs['name'] ]['typeClass'] = 'element';
1025 $this->elements[ $attrs['name'] ]['type'] = $this->schemaTargetNamespace . ':' . $attrs['name'] . '_ContainedType';
1026 if (!isset($this->elements[ $attrs['name'] ]['form'])) {
1027 $this->elements[ $attrs['name'] ]['form'] = $this->schemaInfo['elementFormDefault'];
1030 if(isset($ename) && $this->currentComplexType){
1031 $this->complexTypes[$this->currentComplexType]['elements'][$ename] = $attrs;
1034 // we ignore enumeration values
1035 //case 'enumeration':
1038 if (isset($attrs['schemaLocation'])) {
1039 //$this->xdebug('import namespace ' . $attrs['namespace'] . ' from ' . $attrs['schemaLocation']);
1040 $this->imports[$attrs['namespace']][] = array('location' => $attrs['schemaLocation'], 'loaded' => false);
1042 //$this->xdebug('import namespace ' . $attrs['namespace']);
1043 $this->imports[$attrs['namespace']][] = array('location' => '', 'loaded' => true);
1044 if (! $this->getPrefixFromNamespace($attrs['namespace'])) {
1045 $this->namespaces['ns'.(count($this->namespaces)+1)] = $attrs['namespace'];
1050 //$this->xdebug("in restriction for currentComplexType: $this->currentComplexType and currentElement: $this->currentElement");
1051 if($this->currentElement){
1052 $this->elements[$this->currentElement]['type'] = $attrs['base'];
1053 } elseif($this->currentSimpleType){
1054 $this->simpleTypes[$this->currentSimpleType]['type'] = $attrs['base'];
1055 } elseif($this->currentComplexType){
1056 $this->complexTypes[$this->currentComplexType]['restrictionBase'] = $attrs['base'];
1057 if(strstr($attrs['base'],':') == ':Array'){
1058 $this->complexTypes[$this->currentComplexType]['phpType'] = 'array';
1063 $this->schemaInfo = $attrs;
1064 $this->schemaInfo['schemaVersion'] = $this->getNamespaceFromPrefix($prefix);
1065 if (isset($attrs['targetNamespace'])) {
1066 $this->schemaTargetNamespace = $attrs['targetNamespace'];
1068 if (!isset($attrs['elementFormDefault'])) {
1069 $this->schemaInfo['elementFormDefault'] = 'unqualified';
1073 if(isset($attrs['name'])){
1074 $this->xdebug("processing simpleType for name " . $attrs['name']);
1075 $this->currentSimpleType = $attrs['name'];
1076 $this->simpleTypes[ $attrs['name'] ] = $attrs;
1077 $this->simpleTypes[ $attrs['name'] ]['typeClass'] = 'simpleType';
1078 $this->simpleTypes[ $attrs['name'] ]['phpType'] = 'scalar';
1080 //echo 'not parsing: '.$name;
1085 //$this->xdebug("do not have anything to do for element $name");
1090 * end-element handler
1092 * @param string $parser XML parser object
1093 * @param string $name element name
1096 function schemaEndElement($parser, $name) {
1097 // bring depth down a notch
1099 // position of current element is equal to the last value left in depth_array for my depth
1100 if(isset($this->depth_array[$this->depth])){
1101 $pos = $this->depth_array[$this->depth];
1104 if($name == 'complexType'){
1105 $this->currentComplexType = false;
1106 $this->currentElement = false;
1108 if($name == 'element'){
1109 $this->currentElement = false;
1111 if($name == 'simpleType'){
1112 $this->currentSimpleType = false;
1117 * element content handler
1119 * @param string $parser XML parser object
1120 * @param string $data element content
1123 function schemaCharacterData($parser, $data){
1124 $pos = $this->depth_array[$this->depth - 1];
1125 $this->message[$pos]['cdata'] .= $data;
1129 * serialize the schema
1133 function serializeSchema(){
1135 $schemaPrefix = $this->getPrefixFromNamespace($this->XMLSchemaVersion);
1138 if (sizeof($this->imports) > 0) {
1139 foreach($this->imports as $ns => $list) {
1140 foreach ($list as $ii) {
1141 if ($ii['location'] != '') {
1142 $xml .= " <$schemaPrefix:import location=\"" . $ii['location'] . '" namespace="' . $ns . "\" />\n";
1144 $xml .= " <$schemaPrefix:import namespace=\"" . $ns . "\" />\n";
1150 foreach($this->complexTypes as $typeName => $attrs){
1152 // serialize child elements
1153 if(isset($attrs['elements']) && (count($attrs['elements']) > 0)){
1154 foreach($attrs['elements'] as $element => $eParts){
1155 if(isset($eParts['ref'])){
1156 $contentStr .= " <$schemaPrefix:element ref=\"$element\"/>\n";
1158 $contentStr .= " <$schemaPrefix:element name=\"$element\" type=\"" . $this->contractQName($eParts['type']) . "\"/>\n";
1163 if(isset($attrs['attrs']) && (count($attrs['attrs']) >= 1)){
1164 foreach($attrs['attrs'] as $attr => $aParts){
1165 $contentStr .= " <$schemaPrefix:attribute ref=\"".$this->contractQName($aParts['ref']).'"';
1166 if(isset($aParts['http://schemas.xmlsoap.org/wsdl/:arrayType'])){
1167 $this->usedNamespaces['wsdl'] = $this->namespaces['wsdl'];
1168 $contentStr .= ' wsdl:arrayType="'.$this->contractQName($aParts['http://schemas.xmlsoap.org/wsdl/:arrayType']).'"';
1170 $contentStr .= "/>\n";
1174 if( isset($attrs['restrictionBase']) && $attrs['restrictionBase'] != ''){
1175 $contentStr = " <$schemaPrefix:restriction base=\"".$this->contractQName($attrs['restrictionBase'])."\">\n".$contentStr." </$schemaPrefix:restriction>\n";
1177 // compositor obviates complex/simple content
1178 if(isset($attrs['compositor']) && ($attrs['compositor'] != '')){
1179 $contentStr = " <$schemaPrefix:$attrs[compositor]>\n".$contentStr." </$schemaPrefix:$attrs[compositor]>\n";
1181 // complex or simple content
1182 elseif((isset($attrs['elements']) && count($attrs['elements']) > 0) || (isset($attrs['attrs']) && count($attrs['attrs']) > 0)){
1183 $contentStr = " <$schemaPrefix:complexContent>\n".$contentStr." </$schemaPrefix:complexContent>\n";
1185 // finalize complex type
1186 if($contentStr != ''){
1187 $contentStr = " <$schemaPrefix:complexType name=\"$typeName\">\n".$contentStr." </$schemaPrefix:complexType>\n";
1189 $contentStr = " <$schemaPrefix:complexType name=\"$typeName\"/>\n";
1191 $xml .= $contentStr;
1194 if(isset($this->simpleTypes) && count($this->simpleTypes) > 0){
1195 foreach($this->simpleTypes as $typeName => $attr){
1196 $xml .= " <$schemaPrefix:simpleType name=\"$typeName\">\n <restriction base=\"".$this->contractQName($eParts['type'])."\"/>\n </$schemaPrefix:simpleType>";
1200 if(isset($this->elements) && count($this->elements) > 0){
1201 foreach($this->elements as $element => $eParts){
1202 $xml .= " <$schemaPrefix:element name=\"$element\" type=\"".$this->contractQName($eParts['type'])."\"/>\n";
1206 if(isset($this->attributes) && count($this->attributes) > 0){
1207 foreach($this->attributes as $attr => $aParts){
1208 $xml .= " <$schemaPrefix:attribute name=\"$attr\" type=\"".$this->contractQName($aParts['type'])."\"\n/>";
1212 $el = "<$schemaPrefix:schema targetNamespace=\"$this->schemaTargetNamespace\"\n";
1213 foreach (array_diff($this->usedNamespaces, $this->enclosingNamespaces) as $nsp => $ns) {
1214 $el .= " xmlns:$nsp=\"$ns\"\n";
1216 $xml = $el . ">\n".$xml."</$schemaPrefix:schema>\n";
1221 * adds debug data to the clas level debug string
1223 * @param string $string debug data
1226 function xdebug($string){
1227 $this->debug('<' . $this->schemaTargetNamespace . '> '.$string);
1231 * get the PHP type of a user defined type in the schema
1232 * PHP type is kind of a misnomer since it actually returns 'struct' for assoc. arrays
1233 * returns false if no type exists, or not w/ the given namespace
1234 * else returns a string that is either a native php type, or 'struct'
1236 * @param string $type, name of defined type
1237 * @param string $ns, namespace of type
1241 function getPHPType($type,$ns){
1242 if(isset($this->typemap[$ns][$type])){
1243 //print "found type '$type' and ns $ns in typemap<br>";
1244 return $this->typemap[$ns][$type];
1245 } elseif(isset($this->complexTypes[$type])){
1246 //print "getting type '$type' and ns $ns from complexTypes array<br>";
1247 return $this->complexTypes[$type]['phpType'];
1253 * returns an array of information about a given type
1254 * returns false if no type exists by the given name
1257 * 'elements' => array(), // refs to elements array
1258 * 'restrictionBase' => '',
1260 * 'order' => '(sequence|all)',
1261 * 'attrs' => array() // refs to attributes array
1268 function getTypeDef($type){
1269 //$this->debug("in getTypeDef for type $type");
1270 if(isset($this->complexTypes[$type])){
1271 $this->xdebug("in getTypeDef, found complexType $type");
1272 return $this->complexTypes[$type];
1273 } elseif(isset($this->simpleTypes[$type])){
1274 $this->xdebug("in getTypeDef, found simpleType $type");
1275 if (!isset($this->simpleTypes[$type]['phpType'])) {
1276 // get info for type to tack onto the simple type
1277 // TODO: can this ever really apply (i.e. what is a simpleType really?)
1278 $uqType = substr($this->simpleTypes[$type]['type'], strrpos($this->simpleTypes[$type]['type'], ':') + 1);
1279 $ns = substr($this->simpleTypes[$type]['type'], 0, strrpos($this->simpleTypes[$type]['type'], ':'));
1280 $etype = $this->getTypeDef($uqType);
1282 if (isset($etype['phpType'])) {
1283 $this->simpleTypes[$type]['phpType'] = $etype['phpType'];
1285 if (isset($etype['elements'])) {
1286 $this->simpleTypes[$type]['elements'] = $etype['elements'];
1290 return $this->simpleTypes[$type];
1291 } elseif(isset($this->elements[$type])){
1292 $this->xdebug("in getTypeDef, found element $type");
1293 if (!isset($this->elements[$type]['phpType'])) {
1294 // get info for type to tack onto the element
1295 $uqType = substr($this->elements[$type]['type'], strrpos($this->elements[$type]['type'], ':') + 1);
1296 $ns = substr($this->elements[$type]['type'], 0, strrpos($this->elements[$type]['type'], ':'));
1297 $etype = $this->getTypeDef($uqType);
1299 if (isset($etype['phpType'])) {
1300 $this->elements[$type]['phpType'] = $etype['phpType'];
1302 if (isset($etype['elements'])) {
1303 $this->elements[$type]['elements'] = $etype['elements'];
1305 } elseif ($ns == 'http://www.w3.org/2001/XMLSchema') {
1306 $this->elements[$type]['phpType'] = 'scalar';
1309 return $this->elements[$type];
1310 } elseif(isset($this->attributes[$type])){
1311 $this->xdebug("in getTypeDef, found attribute $type");
1312 return $this->attributes[$type];
1314 $this->xdebug("in getTypeDef, did not find $type");
1319 * returns a sample serialization of a given type, or false if no type by the given name
1321 * @param string $type, name of type
1325 function serializeTypeDef($type){
1326 //print "in sTD() for type $type<br>";
1327 if($typeDef = $this->getTypeDef($type)){
1329 if(is_array($typeDef['attrs'])){
1330 foreach($attrs as $attName => $data){
1331 $str .= " $attName=\"{type = ".$data['type']."}\"";
1334 $str .= " xmlns=\"".$this->schema['targetNamespace']."\"";
1335 if(count($typeDef['elements']) > 0){
1337 foreach($typeDef['elements'] as $element => $eData){
1338 $str .= $this->serializeTypeDef($element);
1341 } elseif($typeDef['typeClass'] == 'element') {
1342 $str .= "></$type>";
1352 * returns HTML form elements that allow a user
1353 * to enter values for creating an instance of the given type.
1355 * @param string $name, name for type instance
1356 * @param string $type, name of type
1360 function typeToForm($name,$type){
1362 if($typeDef = $this->getTypeDef($type)){
1364 if($typeDef['phpType'] == 'struct'){
1365 $buffer .= '<table>';
1366 foreach($typeDef['elements'] as $child => $childDef){
1368 <tr><td align='right'>$childDef[name] (type: ".$this->getLocalPart($childDef['type'])."):</td>
1369 <td><input type='text' name='parameters[".$name."][$childDef[name]]'></td></tr>";
1371 $buffer .= '</table>';
1373 } elseif($typeDef['phpType'] == 'array'){
1374 $buffer .= '<table>';
1375 for($i=0;$i < 3; $i++){
1377 <tr><td align='right'>array item (type: $typeDef[arrayType]):</td>
1378 <td><input type='text' name='parameters[".$name."][]'></td></tr>";
1380 $buffer .= '</table>';
1383 $buffer .= "<input type='text' name='parameters[$name]'>";
1386 $buffer .= "<input type='text' name='parameters[$name]'>";
1392 * adds a complex type to the schema
1402 * array('ref'=>'SOAP-ENC:arrayType','wsdl:arrayType'=>'string[]'),
1406 * example: PHP associative array ( SOAP Struct )
1413 * array('myVar'=> array('name'=>'myVar','type'=>'string')
1417 * @param typeClass (complexType|simpleType|attribute)
1418 * @param phpType: currently supported are array and struct (php assoc array)
1419 * @param compositor (all|sequence|choice)
1420 * @param restrictionBase namespace:name (http://schemas.xmlsoap.org/soap/encoding/:Array)
1421 * @param elements = array ( name = array(name=>'',type=>'') )
1422 * @param attrs = array(
1424 * 'ref' => "http://schemas.xmlsoap.org/soap/encoding/:arrayType",
1425 * "http://schemas.xmlsoap.org/wsdl/:arrayType" => "string[]"
1428 * @param arrayType: namespace:name (http://www.w3.org/2001/XMLSchema:string)
1431 function addComplexType($name,$typeClass='complexType',$phpType='array',$compositor='',$restrictionBase='',$elements=array(),$attrs=array(),$arrayType=''){
1432 $this->complexTypes[$name] = array(
1434 'typeClass' => $typeClass,
1435 'phpType' => $phpType,
1436 'compositor'=> $compositor,
1437 'restrictionBase' => $restrictionBase,
1438 'elements' => $elements,
1440 'arrayType' => $arrayType
1443 $this->xdebug("addComplexType $name: " . $this->varDump($this->complexTypes[$name]));
1447 * adds a simple type to the schema
1450 * @param restrictionBase namespace:name (http://schemas.xmlsoap.org/soap/encoding/:Array)
1451 * @param typeClass (simpleType)
1452 * @param phpType: (scalar)
1456 function addSimpleType($name, $restrictionBase='', $typeClass='simpleType', $phpType='scalar') {
1457 $this->simpleTypes[$name] = array(
1459 'typeClass' => $typeClass,
1460 'phpType' => $phpType,
1461 'type' => $restrictionBase
1464 $this->xdebug("addSimpleType $name: " . $this->varDump($this->simpleTypes[$name]));
1475 * for creating serializable abstractions of native PHP types
1476 * NOTE: this is only really used when WSDL is not available.
1478 * @author Dietrich Ayala <dietrich@ganx4.com>
1482 class soapval extends nusoap_base {
1486 * @param string $name optional name
1487 * @param string $type optional type name
1488 * @param mixed $value optional value
1489 * @param string $namespace optional namespace of value
1490 * @param string $type_namespace optional namespace of type
1491 * @param array $attributes associative array of attributes to add to element serialization
1494 function soapval($name='soapval',$type=false,$value=-1,$element_ns=false,$type_ns=false,$attributes=false) {
1495 $this->name = $name;
1496 $this->value = $value;
1497 $this->type = $type;
1498 $this->element_ns = $element_ns;
1499 $this->type_ns = $type_ns;
1500 $this->attributes = $attributes;
1504 * return serialized value
1506 * @return string XML data
1509 function serialize($use='encoded') {
1510 return $this->serialize_val($this->value,$this->name,$this->type,$this->element_ns,$this->type_ns,$this->attributes,$use);
1514 * decodes a soapval object into a PHP native type
1516 * @param object $soapval optional SOAPx4 soapval object, else uses self
1521 return $this->value;
1532 * transport class for sending/receiving data via HTTP and HTTPS
1533 * NOTE: PHP must be compiled with the CURL extension for HTTPS support
1535 * @author Dietrich Ayala <dietrich@ganx4.com>
1539 class soap_transport_http extends nusoap_base {
1547 var $request_method = 'POST';
1548 var $protocol_version = '1.0';
1550 var $outgoing_headers = array();
1551 var $incoming_headers = array();
1552 var $outgoing_payload = '';
1553 var $incoming_payload = '';
1554 var $useSOAPAction = true;
1555 var $persistentConnection = false;
1556 var $ch = false; // cURL handle
1563 function soap_transport_http($url){
1566 $u = parse_url($url);
1567 foreach($u as $k => $v){
1568 $this->debug("$k = $v");
1572 // add any GET params to path
1573 if(isset($u['query']) && $u['query'] != ''){
1574 $this->path .= '?' . $u['query'];
1578 if(!isset($u['port'])){
1579 if($u['scheme'] == 'https'){
1586 $this->uri = $this->path;
1589 ereg('\$Revisio' . 'n: ([^ ]+)', $this->revision, $rev);
1590 $this->outgoing_headers['User-Agent'] = $this->title.'/'.$this->version.' ('.$rev[1].')';
1591 if (!isset($u['port'])) {
1592 $this->outgoing_headers['Host'] = $this->host;
1594 $this->outgoing_headers['Host'] = $this->host.':'.$this->port;
1597 if (isset($u['user']) && $u['user'] != '') {
1598 $this->setCredentials($u['user'], isset($u['pass']) ? $u['pass'] : '');
1602 function connect($connection_timeout=0,$response_timeout=30){
1603 // For PHP 4.3 with OpenSSL, change https scheme to ssl, then treat like
1604 // "regular" socket.
1605 // TODO: disabled for now because OpenSSL must be *compiled* in (not just
1606 // loaded), and until PHP5 stream_get_wrappers is not available.
1607 // if ($this->scheme == 'https') {
1608 // if (version_compare(phpversion(), '4.3.0') >= 0) {
1609 // if (extension_loaded('openssl')) {
1610 // $this->scheme = 'ssl';
1611 // $this->debug('Using SSL over OpenSSL');
1615 if ($this->scheme == 'http' || $this->scheme == 'ssl') {
1616 // use persistent connection
1617 if($this->persistentConnection && isset($this->fp) && is_resource($this->fp)){
1618 if (!feof($this->fp)) {
1619 $this->debug('Re-use persistent connection');
1623 $this->debug('Closed persistent connection at EOF');
1626 // munge host if using OpenSSL
1627 if ($this->scheme == 'ssl') {
1628 $host = 'ssl://' . $this->host;
1630 $host = $this->host;
1632 $this->debug('calling fsockopen with host ' . $host);
1635 if($connection_timeout > 0){
1636 $this->fp = @fsockopen( $host, $this->port, $this->errno, $this->error_str, $connection_timeout);
1638 $this->fp = @fsockopen( $host, $this->port, $this->errno, $this->error_str);
1643 $this->debug('Couldn\'t open socket connection to server '.$this->url.', Error ('.$this->errno.'): '.$this->error_str);
1644 $this->setError('Couldn\'t open socket connection to server: '.$this->url.', Error ('.$this->errno.'): '.$this->error_str);
1648 // set response timeout
1649 socket_set_timeout( $this->fp, $response_timeout);
1651 $this->debug('socket connected');
1653 } else if ($this->scheme == 'https') {
1654 if (!extension_loaded('curl')) {
1655 $this->setError('CURL Extension, or OpenSSL extension w/ PHP version >= 4.3 is required for HTTPS');
1658 $this->debug('connect using https');
1660 $this->ch = curl_init();
1662 $hostURL = ($this->port != '') ? "https://$this->host:$this->port" : "https://$this->host";
1664 $hostURL .= $this->path;
1665 curl_setopt($this->ch, CURLOPT_URL, $hostURL);
1666 // ask for headers in the response output
1667 curl_setopt($this->ch, CURLOPT_HEADER, 1);
1668 // ask for the response output as the return value
1669 curl_setopt($this->ch, CURLOPT_RETURNTRANSFER, 1);
1671 // We manage this ourselves through headers and encoding
1672 // if(function_exists('gzuncompress')){
1673 // curl_setopt($this->ch, CURLOPT_ENCODING, 'deflate');
1675 // persistent connection
1676 if ($this->persistentConnection) {
1677 // The way we send data, we cannot use persistent connections, since
1678 // there will be some "junk" at the end of our request.
1679 //curl_setopt($this->ch, CURL_HTTP_VERSION_1_1, true);
1680 $this->persistentConnection = false;
1681 $this->outgoing_headers['Connection'] = 'close';
1683 // set timeout (NOTE: cURL does not have separate connection and response timeouts)
1684 if ($connection_timeout != 0) {
1685 curl_setopt($this->ch, CURLOPT_TIMEOUT, $connection_timeout);
1688 // recent versions of cURL turn on peer/host checking by default,
1689 // while PHP binaries are not compiled with a default location for the
1690 // CA cert bundle, so disable peer/host checking.
1691 //curl_setopt($this->ch, CURLOPT_CAINFO, 'f:\php-4.3.2-win32\extensions\curl-ca-bundle.crt');
1692 curl_setopt($this->ch, CURLOPT_SSL_VERIFYPEER, 0);
1693 curl_setopt($this->ch, CURLOPT_SSL_VERIFYHOST, 0);
1696 TODO: support client certificates (thanks Tobias Boes)
1697 curl_setopt($this->ch, CURLOPT_CAINFO, '$pathToPemFiles/rootca.pem');
1698 curl_setopt($this->ch, CURLOPT_SSL_VERIFYPEER, 1);
1699 curl_setopt($this->ch, CURLOPT_SSL_VERIFYHOST, 1);
1700 curl_setopt($this->ch, CURLOPT_SSLCERT, '$pathToPemFiles/mycert.pem');
1701 curl_setopt($this->ch, CURLOPT_SSLKEY, '$pathToPemFiles/mykey.pem');
1703 $this->debug('cURL connection set up');
1706 $this->setError('Unknown scheme ' . $this->scheme);
1707 $this->debug('Unknown scheme ' . $this->scheme);
1713 * send the SOAP message via HTTP
1715 * @param string $data message data
1716 * @param integer $timeout set connection timeout in seconds
1717 * @param integer $response_timeout set response timeout in seconds
1718 * @return string data
1721 function send($data, $timeout=0, $response_timeout=30) {
1723 $this->debug('entered send() with data of length: '.strlen($data));
1725 $this->tryagain = true;
1727 while ($this->tryagain) {
1728 $this->tryagain = false;
1731 if (!$this->connect($timeout, $response_timeout)){
1736 if (!$this->sendRequest($data)){
1741 $respdata = $this->getResponse();
1743 $this->setError('Too many tries to get an OK response');
1746 $this->debug('end of send()');
1752 * send the SOAP message via HTTPS 1.0 using CURL
1754 * @param string $msg message data
1755 * @param integer $timeout set connection timeout in seconds
1756 * @param integer $response_timeout set response timeout in seconds
1757 * @return string data
1760 function sendHTTPS($data, $timeout=0, $response_timeout=30) {
1761 return $this->send($data, $timeout, $response_timeout);
1765 * if authenticating, set user credentials here
1767 * @param string $username
1768 * @param string $password
1769 * @param string $authtype
1770 * @param array $digestRequest
1773 function setCredentials($username, $password, $authtype = 'basic', $digestRequest = array()) {
1777 if ($authtype == 'basic') {
1778 $this->outgoing_headers['Authorization'] = 'Basic '.base64_encode($username.':'.$password);
1779 } elseif ($authtype == 'digest') {
1780 if (isset($digestRequest['nonce'])) {
1781 $digestRequest['nc'] = isset($digestRequest['nc']) ? $digestRequest['nc']++ : 1;
1783 // calculate the Digest hashes (calculate code based on digest implementation found at: http://www.rassoc.com/gregr/weblog/stories/2002/07/09/webServicesSecurityHttpDigestAuthenticationWithoutActiveDirectory.html)
1785 // A1 = unq(username-value) ":" unq(realm-value) ":" passwd
1786 $A1 = $username. ':' . $digestRequest['realm'] . ':' . $password;
1791 // A2 = Method ":" digest-uri-value
1792 $A2 = 'POST:' . $this->uri;
1797 // KD(secret, data) = H(concat(secret, ":", data))
1799 // request-digest = <"> < KD ( H(A1), unq(nonce-value)
1801 // ":" unq(cnonce-value)
1802 // ":" unq(qop-value)
1805 // if qop is missing,
1806 // request-digest = <"> < KD ( H(A1), unq(nonce-value) ":" H(A2) ) > <">
1808 $unhashedDigest = '';
1809 $nonce = isset($digestRequest['nonce']) ? $digestRequest['nonce'] : '';
1811 if ($digestRequest['qop'] != '') {
1812 $unhashedDigest = $HA1 . ':' . $nonce . ':' . sprintf("%08d", $digestRequest['nc']) . ':' . $cnonce . ':' . $digestRequest['qop'] . ':' . $HA2;
1814 $unhashedDigest = $HA1 . ':' . $nonce . ':' . $HA2;
1817 $hashedDigest = md5($unhashedDigest);
1819 $this->outgoing_headers['Authorization'] = 'Digest username="' . $username . '", realm="' . $digestRequest['realm'] . '", nonce="' . $nonce . '", uri="' . $this->uri . '", cnonce="' . $cnonce . '", nc=' . sprintf("%08x", $digestRequest['nc']) . ', qop="' . $digestRequest['qop'] . '", response="' . $hashedDigest . '"';
1822 $this->username = $username;
1823 $this->password = $password;
1824 $this->authtype = $authtype;
1825 $this->digestRequest = $digestRequest;
1829 * set the soapaction value
1831 * @param string $soapaction
1834 function setSOAPAction($soapaction) {
1835 $this->outgoing_headers['SOAPAction'] = '"' . $soapaction . '"';
1841 * @param string $enc encoding style. supported values: gzip, deflate, or both
1844 function setEncoding($enc='gzip, deflate'){
1845 $this->protocol_version = '1.1';
1846 $this->outgoing_headers['Accept-Encoding'] = $enc;
1847 $this->outgoing_headers['Connection'] = 'close';
1848 $this->persistentConnection = false;
1849 set_magic_quotes_runtime(0);
1851 $this->encoding = $enc;
1855 * set proxy info here
1857 * @param string $proxyhost
1858 * @param string $proxyport
1859 * @param string $proxyusername
1860 * @param string $proxypassword
1863 function setProxy($proxyhost, $proxyport, $proxyusername = '', $proxypassword = '') {
1864 $this->uri = $this->url;
1865 $this->host = $proxyhost;
1866 $this->port = $proxyport;
1867 if ($proxyusername != '' && $proxypassword != '') {
1868 $this->outgoing_headers['Proxy-Authorization'] = ' Basic '.base64_encode($proxyusername.':'.$proxypassword);
1873 * decode a string that is encoded w/ "chunked' transfer encoding
1874 * as defined in RFC2068 19.4.6
1876 * @param string $buffer
1881 function decodeChunked($buffer, $lb){
1886 // read chunk-size, chunk-extension (if any) and CRLF
1887 // get the position of the linebreak
1888 $chunkend = strpos($buffer, $lb);
1889 if ($chunkend == FALSE) {
1890 $this->debug('no linebreak found in decodeChunked');
1893 $temp = substr($buffer,0,$chunkend);
1894 $chunk_size = hexdec( trim($temp) );
1895 $chunkstart = $chunkend + strlen($lb);
1896 // while (chunk-size > 0) {
1897 while ($chunk_size > 0) {
1898 $this->debug("chunkstart: $chunkstart chunk_size: $chunk_size");
1899 $chunkend = strpos( $buffer, $lb, $chunkstart + $chunk_size);
1901 // Just in case we got a broken connection
1902 if ($chunkend == FALSE) {
1903 $chunk = substr($buffer,$chunkstart);
1904 // append chunk-data to entity-body
1906 $length += strlen($chunk);
1910 // read chunk-data and CRLF
1911 $chunk = substr($buffer,$chunkstart,$chunkend-$chunkstart);
1912 // append chunk-data to entity-body
1914 // length := length + chunk-size
1915 $length += strlen($chunk);
1916 // read chunk-size and CRLF
1917 $chunkstart = $chunkend + strlen($lb);
1919 $chunkend = strpos($buffer, $lb, $chunkstart) + strlen($lb);
1920 if ($chunkend == FALSE) {
1921 break; //Just in case we got a broken connection
1923 $temp = substr($buffer,$chunkstart,$chunkend-$chunkstart);
1924 $chunk_size = hexdec( trim($temp) );
1925 $chunkstart = $chunkend;
1931 * Writes payload, including HTTP headers, to $this->outgoing_payload.
1933 function buildPayload($data) {
1934 // add content-length header
1935 $this->outgoing_headers['Content-Length'] = strlen($data);
1937 // start building outgoing payload:
1938 $this->outgoing_payload = "$this->request_method $this->uri HTTP/$this->protocol_version\r\n";
1940 // loop thru headers, serializing
1941 foreach($this->outgoing_headers as $k => $v){
1942 $this->outgoing_payload .= $k.': '.$v."\r\n";
1945 // header/body separator
1946 $this->outgoing_payload .= "\r\n";
1949 $this->outgoing_payload .= $data;
1952 function sendRequest($data){
1954 $this->buildPayload($data);
1956 if ($this->scheme == 'http' || $this->scheme == 'ssl') {
1958 if(!fputs($this->fp, $this->outgoing_payload, strlen($this->outgoing_payload))) {
1959 $this->setError('couldn\'t write message data to socket');
1960 $this->debug('couldn\'t write message data to socket');
1963 $this->debug('wrote data to socket, length = ' . strlen($this->outgoing_payload));
1965 } else if ($this->scheme == 'https') {
1967 // TODO: cURL does say this should only be the verb, and in fact it
1968 // turns out that the URI and HTTP version are appended to this, which
1969 // some servers refuse to work with
1970 //curl_setopt($this->ch, CURLOPT_CUSTOMREQUEST, $this->outgoing_payload);
1971 foreach($this->outgoing_headers as $k => $v){
1972 $curl_headers[] = "$k: $v";
1974 curl_setopt($this->ch, CURLOPT_HTTPHEADER, $curl_headers);
1975 if ($this->request_method == "POST") {
1976 curl_setopt($this->ch, CURLOPT_POST, 1);
1977 curl_setopt($this->ch, CURLOPT_POSTFIELDS, $data);
1980 $this->debug('set cURL payload');
1985 function getResponse(){
1986 $this->incoming_payload = '';
1988 if ($this->scheme == 'http' || $this->scheme == 'ssl') {
1989 // loop until headers have been retrieved
1991 while (!isset($lb)){
1993 // We might EOF during header read.
1994 if(feof($this->fp)) {
1995 $this->incoming_payload = $data;
1996 $this->debug('found no headers before EOF after length ' . strlen($data));
1997 $this->debug("received before EOF:\n" . $data);
1998 $this->setError('server failed to send headers');
2002 $data .= fgets($this->fp, 256);
2003 $pos = strpos($data,"\r\n\r\n");
2007 $pos = strpos($data,"\n\n");
2012 // remove 100 header
2013 if(isset($lb) && ereg('^HTTP/1.1 100',$data)){
2018 // store header data
2019 $this->incoming_payload .= $data;
2020 $this->debug('found end of headers after length ' . strlen($data));
2022 $header_data = trim(substr($data,0,$pos));
2023 $header_array = explode($lb,$header_data);
2024 foreach($header_array as $header_line){
2025 $arr = explode(':',$header_line, 2);
2026 if(count($arr) > 1){
2027 $header_name = strtolower(trim($arr[0]));
2028 $this->incoming_headers[$header_name] = trim($arr[1]);
2029 } else if (isset($header_name)) {
2030 $this->incoming_headers[$header_name] .= $lb . ' ' . $header_line;
2034 // loop until msg has been received
2035 // TODO: handle chunking in this loop to allow persistent connections with chunking
2036 $content_length = isset($this->incoming_headers['content-length']) ? $this->incoming_headers['content-length'] : 2147483647;
2039 while (($strlen < $content_length) && (!feof($this->fp))) {
2040 $readlen = min(8192, $content_length - $strlen);
2041 $tmp = fread($this->fp, $readlen);
2042 $strlen += strlen($tmp);
2045 $this->debug('read body of length ' . strlen($data));
2046 $this->incoming_payload .= $data;
2047 $this->debug('received a total of '.strlen($this->incoming_payload).' bytes of data from server');
2049 // close filepointer
2051 //(isset($this->incoming_headers['connection']) && $this->incoming_headers['connection'] == 'close') ||
2052 (! $this->persistentConnection) || feof($this->fp)){
2055 $this->debug('closed socket');
2058 // connection was closed unexpectedly
2059 if($this->incoming_payload == ''){
2060 $this->setError('no response from server');
2064 // decode transfer-encoding
2065 if(isset($this->incoming_headers['transfer-encoding']) && strtolower($this->incoming_headers['transfer-encoding']) == 'chunked'){
2066 if(!$data = $this->decodeChunked($data, $lb)){
2067 $this->setError('Decoding of chunked data failed');
2070 //print "<pre>\nde-chunked:\n---------------\n$data\n\n---------------\n</pre>";
2071 // set decoded payload
2072 $this->incoming_payload = $header_data.$lb.$lb.$data;
2075 } else if ($this->scheme == 'https') {
2077 $this->debug('send and receive with cURL');
2078 $this->incoming_payload = curl_exec($this->ch);
2079 $data = $this->incoming_payload;
2081 $cErr = curl_error($this->ch);
2083 $err = 'cURL ERROR: '.curl_errno($this->ch).': '.$cErr.'<br>';
2084 foreach(curl_getinfo($this->ch) as $k => $v){
2085 $err .= "$k: $v<br>";
2088 $this->setError($err);
2089 curl_close($this->ch);
2093 //var_dump(curl_getinfo($this->ch));
2097 $this->debug('No cURL error, closing cURL');
2098 curl_close($this->ch);
2100 // remove 100 header
2101 if (ereg('^HTTP/1.1 100',$data)) {
2102 if ($pos = strpos($data,"\r\n\r\n")) {
2103 $data = ltrim(substr($data,$pos));
2104 } elseif($pos = strpos($data,"\n\n") ) {
2105 $data = ltrim(substr($data,$pos));
2109 // separate content from HTTP headers
2110 if ($pos = strpos($data,"\r\n\r\n")) {
2112 } elseif( $pos = strpos($data,"\n\n")) {
2115 $this->debug('no proper separation of headers and document');
2116 $this->setError('no proper separation of headers and document');
2119 $header_data = trim(substr($data,0,$pos));
2120 $header_array = explode($lb,$header_data);
2121 $data = ltrim(substr($data,$pos));
2122 $this->debug('found proper separation of headers and document');
2123 $this->debug('cleaned data, stringlen: '.strlen($data));
2125 foreach ($header_array as $header_line) {
2126 $arr = explode(':',$header_line,2);
2127 if (count($arr) > 1) {
2128 $this->incoming_headers[strtolower(trim($arr[0]))] = trim($arr[1]);
2133 // see if we need to resend the request with http digest authentication
2134 if (isset($this->incoming_headers['www-authenticate']) && strstr($header_array[0], '401 Unauthorized')) {
2135 if (substr("Digest ", $this->incoming_headers['www-authenticate'])) {
2136 // remove "Digest " from our elements
2137 $digestString = str_replace('Digest ', '', $this->incoming_headers['www-authenticate']);
2139 // parse elements into array
2140 $digestElements = explode(', ', $digestString);
2141 while (list($key, $val) = each($digestElements)) {
2142 $tempElement = explode('=', $val);
2143 $digestRequest[$tempElement[0]] = str_replace("\"", '', $tempElement[1]);
2146 // should have (at least) qop, realm, nonce
2147 if (isset($digestRequest['nonce'])) {
2148 $this->debug('found nonce in WWW-Authenticate: ' . $this->incoming_headers['www-authenticate']);
2149 $this->setCredentials($this->username, $this->password, 'digest', $digestRequest);
2150 $this->tryagain = true;
2154 $this->debug('HTTP authentication failed');
2155 $this->setError('HTTP authentication failed');
2159 // decode content-encoding
2160 if(isset($this->incoming_headers['content-encoding']) && $this->incoming_headers['content-encoding'] != ''){
2161 if(strtolower($this->incoming_headers['content-encoding']) == 'deflate' || strtolower($this->incoming_headers['content-encoding']) == 'gzip'){
2162 // if decoding works, use it. else assume data wasn't gzencoded
2163 if(function_exists('gzuncompress')){
2164 //$timer->setMarker('starting decoding of gzip/deflated content');
2165 if($this->incoming_headers['content-encoding'] == 'deflate' && $degzdata = @gzuncompress($data)){
2167 } elseif($this->incoming_headers['content-encoding'] == 'gzip' && $degzdata = gzinflate(substr($data, 10))){ // do our best
2170 $this->setError('Errors occurred when trying to decode the data');
2172 //$timer->setMarker('finished decoding of gzip/deflated content');
2173 //print "<xmp>\nde-inflated:\n---------------\n$data\n-------------\n</xmp>";
2174 // set decoded payload
2175 $this->incoming_payload = $header_data.$lb.$lb.$data;
2177 $this->setError('The server sent deflated data. Your php install must have the Zlib extension compiled in to support this.');
2182 if(strlen($data) == 0){
2183 $this->debug('no data after headers!');
2184 $this->setError('no data present after HTTP headers');
2191 function setContentType($type, $charset = false) {
2192 $this->outgoing_headers['Content-Type'] = $type . ($charset ? '; charset=' . $charset : '');
2195 function usePersistentConnection(){
2196 if (isset($this->outgoing_headers['Accept-Encoding'])) {
2199 $this->protocol_version = '1.1';
2200 $this->persistentConnection = true;
2201 $this->outgoing_headers['Connection'] = 'Keep-Alive';
2212 * soap_server allows the user to create a SOAP server
2213 * that is capable of receiving messages and returning responses
2215 * NOTE: WSDL functionality is experimental
2217 * @author Dietrich Ayala <dietrich@ganx4.com>
2221 class nusoap_server extends nusoap_base {
2222 var $headers = array(); // HTTP headers of request
2223 var $request = ''; // HTTP request
2224 var $requestHeaders = ''; // SOAP headers from request (incomplete namespace resolution) (text)
2225 var $document = ''; // SOAP body request portion (incomplete namespace resolution) (text)
2226 var $requestSOAP = ''; // SOAP payload for request (text)
2227 var $methodURI = ''; // requested method namespace URI
2228 var $methodname = ''; // name of method requested
2229 var $methodparams = array(); // method parameters from request
2230 var $xml_encoding = ''; // character set encoding of incoming (request) messages
2231 var $SOAPAction = ''; // SOAP Action from request
2233 var $outgoing_headers = array();// HTTP headers of response
2234 var $response = ''; // HTTP response
2235 var $responseHeaders = ''; // SOAP headers for response (text)
2236 var $responseSOAP = ''; // SOAP payload for response (text)
2237 var $methodreturn = false; // method return to place in response
2238 var $fault = false; // SOAP fault for response
2239 var $result = 'successful'; // text indication of result (for debugging)
2241 var $operations = array(); // assoc array of operations => opData
2242 var $wsdl = false; // wsdl instance
2243 var $externalWSDLURL = false; // URL for WSDL
2244 var $debug_flag = false; // whether to append debug to response as XML comment
2248 * the optional parameter is a path to a WSDL file that you'd like to bind the server instance to.
2250 * @param mixed $wsdl file path or URL (string), or wsdl instance (object)
2253 function soap_server($wsdl=false){
2255 // turn on debugging?
2259 global $HTTP_SERVER_VARS;
2261 if (isset($debug)) {
2262 $this->debug_flag = $debug;
2263 } else if (isset($_REQUEST['debug'])) {
2264 $this->debug_flag = $_REQUEST['debug'];
2265 } else if (isset($_SERVER['QUERY_STRING'])) {
2266 $qs = explode('&', $_SERVER['QUERY_STRING']);
2267 foreach ($qs as $v) {
2268 if (substr($v, 0, 6) == 'debug=') {
2269 $this->debug_flag = substr($v, 6);
2272 } else if (isset($HTTP_SERVER_VARS['QUERY_STRING'])) {
2273 $qs = explode('&', $HTTP_SERVER_VARS['QUERY_STRING']);
2274 foreach ($qs as $v) {
2275 if (substr($v, 0, 6) == 'debug=') {
2276 $this->debug_flag = substr($v, 6);
2283 if (is_object($wsdl) && is_a($wsdl, 'wsdl')) {
2284 $this->wsdl = $wsdl;
2285 $this->externalWSDLURL = $this->wsdl->wsdl;
2286 $this->debug('Use existing wsdl instance from ' . $this->externalWSDLURL);
2288 $this->debug('Create wsdl from ' . $wsdl);
2289 $this->wsdl = new wsdl($wsdl);
2290 $this->externalWSDLURL = $wsdl;
2292 $this->debug("wsdl...\n" . $this->wsdl->debug_str);
2293 $this->wsdl->debug_str = '';
2294 if($err = $this->wsdl->getError()){
2295 die('WSDL ERROR: '.$err);
2301 * processes request and returns response
2303 * @param string $data usually is the value of $HTTP_RAW_POST_DATA
2306 function service($data){
2307 global $QUERY_STRING;
2308 if(isset($_SERVER['QUERY_STRING'])){
2309 $qs = $_SERVER['QUERY_STRING'];
2310 } elseif(isset($GLOBALS['QUERY_STRING'])){
2311 $qs = $GLOBALS['QUERY_STRING'];
2312 } elseif(isset($QUERY_STRING) && $QUERY_STRING != ''){
2313 $qs = $QUERY_STRING;
2316 if(isset($qs) && ereg('wsdl', $qs) ){
2317 // This is a request for WSDL
2318 if($this->externalWSDLURL){
2319 if (strpos($this->externalWSDLURL,"://")!==false) { // assume URL
2320 header('Location: '.$this->externalWSDLURL);
2321 } else { // assume file
2322 header("Content-Type: text/xml\r\n");
2323 $fp = fopen($this->externalWSDLURL, 'r');
2327 header("Content-Type: text/xml; charset=ISO-8859-1\r\n");
2328 print $this->wsdl->serialize();
2330 } elseif($data == '' && $this->wsdl){
2331 // print web interface
2332 print $this->webDescription();
2334 // handle the request
2335 $this->parse_request($data);
2336 if (! $this->fault) {
2337 $this->invoke_method();
2339 if (! $this->fault) {
2340 $this->serialize_return();
2342 $this->send_response();
2347 * parses HTTP request headers.
2349 * The following fields are set by this function (when successful)
2358 function parse_http_headers() {
2359 global $HTTP_SERVER_VARS;
2362 $this->request = '';
2363 if(function_exists('getallheaders')){
2364 $this->headers = getallheaders();
2365 foreach($this->headers as $k=>$v){
2366 $this->request .= "$k: $v\r\n";
2367 $this->debug("$k: $v");
2369 // get SOAPAction header
2370 if(isset($this->headers['SOAPAction'])){
2371 $this->SOAPAction = str_replace('"','',$this->headers['SOAPAction']);
2373 // get the character encoding of the incoming request
2374 if(strpos($this->headers['Content-Type'],'=')){
2375 $enc = str_replace('"','',substr(strstr($this->headers["Content-Type"],'='),1));
2376 if(eregi('^(ISO-8859-1|US-ASCII|UTF-8)$',$enc)){
2377 $this->xml_encoding = strtoupper($enc);
2379 $this->xml_encoding = 'US-ASCII';
2382 // should be US-ASCII, but for XML, let's be pragmatic and admit UTF-8 is most common
2383 $this->xml_encoding = 'UTF-8';
2385 } elseif(isset($_SERVER) && is_array($_SERVER)){
2386 foreach ($_SERVER as $k => $v) {
2387 if (substr($k, 0, 5) == 'HTTP_') {
2388 $k = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($k, 5)))));
2390 $k = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', $k))));
2392 if ($k == 'Soapaction') {
2393 // get SOAPAction header
2395 $v = str_replace('"', '', $v);
2396 $v = str_replace('\\', '', $v);
2397 $this->SOAPAction = $v;
2398 } else if ($k == 'Content-Type') {
2399 // get the character encoding of the incoming request
2400 if (strpos($v, '=')) {
2401 $enc = substr(strstr($v, '='), 1);
2402 $enc = str_replace('"', '', $enc);
2403 $enc = str_replace('\\', '', $enc);
2404 if (eregi('^(ISO-8859-1|US-ASCII|UTF-8)$', $enc)) {
2405 $this->xml_encoding = strtoupper($enc);
2407 $this->xml_encoding = 'US-ASCII';
2410 // should be US-ASCII, but for XML, let's be pragmatic and admit UTF-8 is most common
2411 $this->xml_encoding = 'UTF-8';
2414 $this->headers[$k] = $v;
2415 $this->request .= "$k: $v\r\n";
2416 $this->debug("$k: $v");
2418 } elseif (is_array($HTTP_SERVER_VARS)) {
2419 foreach ($HTTP_SERVER_VARS as $k => $v) {
2420 if (substr($k, 0, 5) == 'HTTP_') {
2421 $k = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($k, 5)))));
2422 if ($k == 'Soapaction') {
2423 // get SOAPAction header
2425 $v = str_replace('"', '', $v);
2426 $v = str_replace('\\', '', $v);
2427 $this->SOAPAction = $v;
2428 } else if ($k == 'Content-Type') {
2429 // get the character encoding of the incoming request
2430 if (strpos($v, '=')) {
2431 $enc = substr(strstr($v, '='), 1);
2432 $enc = str_replace('"', '', $enc);
2433 $enc = str_replace('\\', '', $enc);
2434 if (eregi('^(ISO-8859-1|US-ASCII|UTF-8)$', $enc)) {
2435 $this->xml_encoding = strtoupper($enc);
2437 $this->xml_encoding = 'US-ASCII';
2440 // should be US-ASCII, but for XML, let's be pragmatic and admit UTF-8 is most common
2441 $this->xml_encoding = 'UTF-8';
2444 $this->headers[$k] = $v;
2445 $this->request .= "$k: $v\r\n";
2446 $this->debug("$k: $v");
2455 * The following fields are set by this function (when successful)
2469 * This sets the fault field on error
2471 * @param string $data XML string
2474 function parse_request($data='') {
2475 $this->debug('entering parse_request() on '.date('H:i Y-m-d'));
2476 $this->parse_http_headers();
2477 $this->debug('got character encoding: '.$this->xml_encoding);
2478 // uncompress if necessary
2479 if (isset($this->headers['Content-Encoding']) && $this->headers['Content-Encoding'] != '') {
2480 $this->debug('got content encoding: ' . $this->headers['Content-Encoding']);
2481 if ($this->headers['Content-Encoding'] == 'deflate' || $this->headers['Content-Encoding'] == 'gzip') {
2482 // if decoding works, use it. else assume data wasn't gzencoded
2483 if (function_exists('gzuncompress')) {
2484 if ($this->headers['Content-Encoding'] == 'deflate' && $degzdata = @gzuncompress($data)) {
2486 } elseif ($this->headers['Content-Encoding'] == 'gzip' && $degzdata = gzinflate(substr($data, 10))) {
2489 $this->fault('Server', 'Errors occurred when trying to decode the data');
2493 $this->fault('Server', 'This Server does not support compressed data');
2498 $this->request .= "\r\n".$data;
2499 $this->requestSOAP = $data;
2500 // parse response, get soap parser obj
2501 $parser = new soap_parser($data,$this->xml_encoding);
2503 $this->debug("parser debug: \n".$parser->debug_str);
2504 // if fault occurred during message parsing
2505 if($err = $parser->getError()){
2506 $this->result = 'fault: error in msg parsing: '.$err;
2507 $this->fault('Server',"error in msg parsing:\n".$err);
2508 // else successfully parsed request into soapval object
2510 // get/set methodname
2511 $this->methodURI = $parser->root_struct_namespace;
2512 $this->methodname = $parser->root_struct_name;
2513 $this->debug('method name: '.$this->methodname);
2514 $this->debug('calling parser->get_response()');
2515 $this->methodparams = $parser->get_response();
2517 $this->requestHeaders = $parser->getHeaders();
2518 // add document for doclit support
2519 $this->document = $parser->document;
2521 $this->debug('leaving parse_request() on '.date('H:i Y-m-d'));
2525 * invokes a PHP function for the requested SOAP method
2527 * The following fields are set by this function (when successful)
2531 * Note that the PHP function that is called may also set the following
2532 * fields to affect the response sent to the client
2537 * This sets the fault field on error
2541 function invoke_method() {
2542 $this->debug('entering invoke_method');
2543 // does method exist?
2544 if(!function_exists($this->methodname)){
2545 // "method not found" fault here
2546 $this->debug("method '$this->methodname' not found!");
2547 $this->result = 'fault: method not found';
2548 $this->fault('Server',"method '$this->methodname' not defined in service");
2552 if(!$this->opData = $this->wsdl->getOperationData($this->methodname)){
2554 $this->fault('Server',"Operation '$this->methodname' is not defined in the WSDL for this service");
2557 $this->debug('opData is ' . $this->varDump($this->opData));
2559 $this->debug("method '$this->methodname' exists");
2560 // evaluate message, getting back parameters
2561 // verify that request parameters match the method's signature
2562 if(! $this->verify_method($this->methodname,$this->methodparams)){
2564 $this->debug('ERROR: request not verified against method signature');
2565 $this->result = 'fault: request failed validation against method signature';
2567 $this->fault('Server',"Operation '$this->methodname' not defined in service.");
2571 // if there are parameters to pass
2572 $this->debug('params var dump '.$this->varDump($this->methodparams));
2573 if($this->methodparams){
2574 $this->debug("calling '$this->methodname' with params");
2575 if (! function_exists('call_user_func_array')) {
2576 $this->debug('calling method using eval()');
2577 $funcCall = $this->methodname.'(';
2578 foreach($this->methodparams as $param) {
2579 $funcCall .= "\"$param\",";
2581 $funcCall = substr($funcCall, 0, -1).')';
2582 $this->debug('function call:<br>'.$funcCall);
2583 @eval("\$this->methodreturn = $funcCall;");
2585 $this->debug('calling method using call_user_func_array()');
2586 $this->methodreturn = call_user_func_array("$this->methodname",$this->methodparams);
2588 $this->debug('response var dump'.$this->varDump($this->methodreturn));
2590 // call method w/ no parameters
2591 $this->debug("calling $this->methodname w/ no params");
2592 $m = $this->methodname;
2593 $this->methodreturn = @$m();
2595 $this->debug("leaving invoke_method: called method $this->methodname, received $this->methodreturn of type".gettype($this->methodreturn));
2599 * serializes the return value from a PHP function into a full SOAP Envelope
2601 * The following fields are set by this function (when successful)
2605 * This sets the fault field on error
2609 function serialize_return() {
2610 $this->debug("Entering serialize_return");
2611 // if we got nothing back. this might be ok (echoVoid)
2612 if(isset($this->methodreturn) && ($this->methodreturn != '' || is_bool($this->methodreturn))) {
2614 if(get_class($this->methodreturn) == 'soap_fault'){
2615 $this->debug('got a fault object from method');
2616 $this->fault = $this->methodreturn;
2618 // if return val is soapval object
2619 } elseif(get_class($this->methodreturn) == 'soapval'){
2620 $this->debug('got a soapval object from method');
2621 $return_val = $this->methodreturn->serialize();
2624 $this->debug('got a(n) '.gettype($this->methodreturn).' from method');
2625 $this->debug('serializing return value');
2627 // weak attempt at supporting multiple output params
2628 if(sizeof($this->opData['output']['parts']) > 1){
2629 $opParams = $this->methodreturn;
2631 $opParams = array($this->methodreturn);
2633 $return_val = $this->wsdl->serializeRPCParameters($this->methodname,'output',$opParams);
2634 if($errstr = $this->wsdl->getError()){
2635 $this->debug('got wsdl error: '.$errstr);
2636 $this->fault('Server', 'got wsdl error: '.$errstr);
2640 $return_val = $this->serialize_val($this->methodreturn, 'return');
2643 $this->debug('return val: '.$this->varDump($return_val));
2646 $this->debug('got no response from method');
2648 $this->debug('serializing response');
2650 if ($this->opData['style'] == 'rpc') {
2651 $payload = '<ns1:'.$this->methodname.'Response xmlns:ns1="'.$this->methodURI.'">'.$return_val.'</ns1:'.$this->methodname."Response>";
2653 $payload = $return_val;
2656 $payload = '<ns1:'.$this->methodname.'Response xmlns:ns1="'.$this->methodURI.'">'.$return_val.'</ns1:'.$this->methodname."Response>";
2658 $this->result = 'successful';
2660 //if($this->debug_flag){
2661 $this->debug("WSDL debug data:\n".$this->wsdl->debug_str);
2663 // Added: In case we use a WSDL, return a serialized env. WITH the usedNamespaces.
2664 $this->responseSOAP = $this->serializeEnvelope($payload,$this->responseHeaders,$this->wsdl->usedNamespaces,$this->opData['style']);
2666 $this->responseSOAP = $this->serializeEnvelope($payload,$this->responseHeaders);
2668 $this->debug("Leaving serialize_return");
2672 * sends an HTTP response
2674 * The following fields are set by this function (when successful)
2681 function send_response() {
2682 $this->debug('Enter send_response');
2684 $payload = $this->fault->serialize();
2685 $this->outgoing_headers[] = "HTTP/1.0 500 Internal Server Error";
2686 $this->outgoing_headers[] = "Status: 500 Internal Server Error";
2688 $payload = $this->responseSOAP;
2689 // Some combinations of PHP+Web server allow the Status
2690 // to come through as a header. Since OK is the default
2692 // $this->outgoing_headers[] = "HTTP/1.0 200 OK";
2693 // $this->outgoing_headers[] = "Status: 200 OK";
2695 // add debug data if in debug mode
2696 if(isset($this->debug_flag) && $this->debug_flag){
2697 while (strpos($this->debug_str, '--')) {
2698 $this->debug_str = str_replace('--', '- -', $this->debug_str);
2700 $payload .= "<!--\n" . $this->debug_str . "\n-->";
2702 $this->outgoing_headers[] = "Server: $this->title Server v$this->version";
2703 ereg('\$Revisio' . 'n: ([^ ]+)', $this->revision, $rev);
2704 $this->outgoing_headers[] = "X-SOAP-Server: $this->title/$this->version (".$rev[1].")";
2705 // Let the Web server decide about this
2706 //$this->outgoing_headers[] = "Connection: Close\r\n";
2707 $this->outgoing_headers[] = "Content-Type: text/xml; charset=$this->soap_defencoding";
2708 //begin code to compress payload - by John
2709 if (strlen($payload) > 1024 && isset($this->headers) && isset($this->headers['Accept-Encoding'])) {
2710 if (strstr($this->headers['Accept-Encoding'], 'deflate')) {
2711 if (function_exists('gzcompress')) {
2712 if (isset($this->debug_flag) && $this->debug_flag) {
2713 $payload .= "<!-- Content being deflated -->";
2715 $this->outgoing_headers[] = "Content-Encoding: deflate";
2716 $payload = gzcompress($payload);
2718 if (isset($this->debug_flag) && $this->debug_flag) {
2719 $payload .= "<!-- Content will not be deflated: no gzcompress -->";
2722 } else if (strstr($this->headers['Accept-Encoding'], 'gzip')) {
2723 if (function_exists('gzencode')) {
2724 if (isset($this->debug_flag) && $this->debug_flag) {
2725 $payload .= "<!-- Content being gzipped -->";
2727 $this->outgoing_headers[] = "Content-Encoding: gzip";
2728 $payload = gzencode($payload);
2730 if (isset($this->debug_flag) && $this->debug_flag) {
2731 $payload .= "<!-- Content will not be gzipped: no gzencode -->";
2737 $this->outgoing_headers[] = "Content-Length: ".strlen($payload);
2738 reset($this->outgoing_headers);
2739 foreach($this->outgoing_headers as $hdr){
2740 header($hdr, false);
2742 $this->response = join("\r\n",$this->outgoing_headers)."\r\n".$payload;
2747 * takes the value that was created by parsing the request
2748 * and compares to the method's signature, if available.
2754 function verify_method($operation,$request){
2755 if(isset($this->wsdl) && is_object($this->wsdl)){
2756 if($this->wsdl->getOperationData($operation)){
2759 } elseif(isset($this->operations[$operation])){
2766 * add a method to the dispatch map
2768 * @param string $methodname
2769 * @param string $in array of input values
2770 * @param string $out array of output values
2773 function add_to_map($methodname,$in,$out){
2774 $this->operations[$methodname] = array('name' => $methodname,'in' => $in,'out' => $out);
2778 * register a service with the server
2780 * @param string $methodname
2781 * @param string $in assoc array of input values: key = param name, value = param type
2782 * @param string $out assoc array of output values: key = param name, value = param type
2783 * @param string $namespace
2784 * @param string $soapaction
2785 * @param string $style optional (rpc|document)
2786 * @param string $use optional (encoded|literal)
2787 * @param string $documentation optional Description to include in WSDL
2790 function register($name,$in=false,$out=false,$namespace=false,$soapaction=false,$style=false,$use=false,$documentation=''){
2791 if($this->externalWSDLURL){
2792 die('You cannot bind to an external WSDL file, and register methods outside of it! Please choose either WSDL or no WSDL.');
2798 if(false == $namespace) {
2800 if(false == $soapaction) {
2801 $SERVER_NAME = isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : $GLOBALS['SERVER_NAME'];
2802 $SCRIPT_NAME = isset($_SERVER['SCRIPT_NAME']) ? $_SERVER['SCRIPT_NAME'] : $GLOBALS['SCRIPT_NAME'];
2803 $soapaction = "http://$SERVER_NAME$SCRIPT_NAME/$name";
2805 if(false == $style) {
2812 $this->operations[$name] = array(
2816 'namespace' => $namespace,
2817 'soapaction' => $soapaction,
2820 $this->wsdl->addOperation($name,$in,$out,$namespace,$soapaction,$style,$use,$documentation);
2826 * create a fault. this also acts as a flag to the server that a fault has occured.
2828 * @param string faultcode
2829 * @param string faultstring
2830 * @param string faultactor
2831 * @param string faultdetail
2834 function fault($faultcode,$faultstring,$faultactor='',$faultdetail=''){
2835 $this->fault = new soap_fault($faultcode,$faultactor,$faultstring,$faultdetail);
2839 * prints html description of services
2843 function webDescription(){
2845 <html><head><title>NuSOAP: '.$this->wsdl->serviceName.'</title>
2846 <style type="text/css">
2847 body { font-family: arial; color: #000000; background-color: #ffffff; margin: 0px 0px 0px 0px; }
2848 p { font-family: arial; color: #000000; margin-top: 0px; margin-bottom: 12px; }
2849 pre { background-color: silver; padding: 5px; font-family: Courier New; font-size: x-small; color: #000000;}
2850 ul { margin-top: 10px; margin-left: 20px; }
2851 li { list-style-type: none; margin-top: 10px; color: #000000; }
2853 margin-left: 0px; padding-bottom: 2em; }
2855 padding-top: 10px; padding-bottom: 10px; padding-left: 15px; font-size: .70em;
2856 margin-top: 10px; margin-left: 0px; color: #000000;
2857 background-color: #ccccff; width: 20%; margin-left: 20px; margin-top: 20px; }
2859 font-family: arial; font-size: 26px; color: #ffffff;
2860 background-color: #999999; width: 105%; margin-left: 0px;
2861 padding-top: 10px; padding-bottom: 10px; padding-left: 15px;}
2863 position: absolute; visibility: hidden; z-index: 200; left: 250px; top: 100px;
2864 font-family: arial; overflow: hidden; width: 600;
2865 padding: 20px; font-size: 10px; background-color: #999999;
2866 layer-background-color:#FFFFFF; }
2867 a,a:active { color: charcoal; font-weight: bold; }
2868 a:visited { color: #666666; font-weight: bold; }
2869 a:hover { color: cc3300; font-weight: bold; }
2871 <script language="JavaScript" type="text/javascript">
2873 // POP-UP CAPTIONS...
2874 function lib_bwcheck(){ //Browsercheck (needed)
2875 this.ver=navigator.appVersion
2876 this.agent=navigator.userAgent
2877 this.dom=document.getElementById?1:0
2878 this.opera5=this.agent.indexOf("Opera 5")>-1
2879 this.ie5=(this.ver.indexOf("MSIE 5")>-1 && this.dom && !this.opera5)?1:0;
2880 this.ie6=(this.ver.indexOf("MSIE 6")>-1 && this.dom && !this.opera5)?1:0;
2881 this.ie4=(document.all && !this.dom && !this.opera5)?1:0;
2882 this.ie=this.ie4||this.ie5||this.ie6
2883 this.mac=this.agent.indexOf("Mac")>-1
2884 this.ns6=(this.dom && parseInt(this.ver) >= 5) ?1:0;
2885 this.ns4=(document.layers && !this.dom)?1:0;
2886 this.bw=(this.ie6 || this.ie5 || this.ie4 || this.ns4 || this.ns6 || this.opera5)
2889 var bw = new lib_bwcheck()
2890 //Makes crossbrowser object.
2891 function makeObj(obj){
2892 this.evnt=bw.dom? document.getElementById(obj):bw.ie4?document.all[obj]:bw.ns4?document.layers[obj]:0;
2893 if(!this.evnt) return false
2894 this.css=bw.dom||bw.ie4?this.evnt.style:bw.ns4?this.evnt:0;
2895 this.wref=bw.dom||bw.ie4?this.evnt:bw.ns4?this.css.document:0;
2896 this.writeIt=b_writeIt;
2899 // A unit of measure that will be added when setting the position of a layer.
2900 //var px = bw.ns4||window.opera?"":"px";
2901 function b_writeIt(text){
2902 if (bw.ns4){this.wref.write(text);this.wref.close()}
2903 else this.wref.innerHTML = text
2905 //Shows the messages
2907 function popup(divid){
2908 if(oDesc = new makeObj(divid)){
2909 oDesc.css.visibility = "visible"
2912 function popout(){ // Hides message
2913 if(oDesc) oDesc.css.visibility = "hidden"
2921 <div class=title>'.$this->wsdl->serviceName.'</div>
2923 <p>View the <a href="'.(isset($GLOBALS['PHP_SELF']) ? $GLOBALS['PHP_SELF'] : $_SERVER['PHP_SELF']).'?wsdl">WSDL</a> for the service.
2924 Click on an operation name to view it's details.</p>
2926 foreach($this->wsdl->getOperations() as $op => $data){
2927 $b .= "<li><a href='#' onclick=\"popup('$op')\">$op</a></li>";
2928 // create hidden div
2929 $b .= "<div id='$op' class='hidden'>
2930 <a href='#' onclick='popout()'><font color='#ffffff'>Close</font></a><br><br>";
2931 foreach($data as $donnie => $marie){ // loop through opdata
2932 if($donnie == 'input' || $donnie == 'output'){ // show input/output data
2933 $b .= "<font color='white'>".ucfirst($donnie).':</font><br>';
2934 foreach($marie as $captain => $tenille){ // loop through data
2935 if($captain == 'parts'){ // loop thru parts
2936 $b .= " $captain:<br>";
2937 //if(is_array($tenille)){
2938 foreach($tenille as $joanie => $chachi){
2939 $b .= " $joanie: $chachi<br>";
2943 $b .= " $captain: $tenille<br>";
2947 $b .= "<font color='white'>".ucfirst($donnie).":</font> $marie<br>";
2955 </div></body></html>';
2960 * sets up wsdl object
2961 * this acts as a flag to enable internal WSDL generation
2963 * @param string $serviceName, name of the service
2964 * @param string $namespace optional tns namespace
2965 * @param string $endpoint optional URL of service endpoint
2966 * @param string $style optional (rpc|document) WSDL style (also specified by operation)
2967 * @param string $transport optional SOAP transport
2968 * @param string $schemaTargetNamespace optional targetNamespace for service schema
2970 function configureWSDL($serviceName,$namespace = false,$endpoint = false,$style='rpc', $transport = 'http://schemas.xmlsoap.org/soap/http', $schemaTargetNamespace = false)
2972 $SERVER_NAME = isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : $GLOBALS['SERVER_NAME'];
2973 $SERVER_PORT = isset($_SERVER['SERVER_PORT']) ? $_SERVER['SERVER_PORT'] : $GLOBALS['SERVER_PORT'];
2974 if ($SERVER_PORT == 80) {
2977 $SERVER_PORT = ':' . $SERVER_PORT;
2979 $SCRIPT_NAME = isset($_SERVER['SCRIPT_NAME']) ? $_SERVER['SCRIPT_NAME'] : $GLOBALS['SCRIPT_NAME'];
2980 if(false == $namespace) {
2981 $namespace = "http://$SERVER_NAME/soap/$serviceName";
2984 if(false == $endpoint) {
2985 if (isset($_SERVER['HTTPS'])) {
2986 $HTTPS = $_SERVER['HTTPS'];
2987 } elseif (isset($GLOBALS['HTTPS'])) {
2988 $HTTPS = $GLOBALS['HTTPS'];
2992 if ($HTTPS == '1' || $HTTPS == 'on') {
2997 $endpoint = "$SCHEME://$SERVER_NAME$SERVER_PORT$SCRIPT_NAME";
3000 if(false == $schemaTargetNamespace) {
3001 $schemaTargetNamespace = $namespace;
3004 $this->wsdl = new wsdl;
3005 $this->wsdl->serviceName = $serviceName;
3006 $this->wsdl->endpoint = $endpoint;
3007 $this->wsdl->namespaces['tns'] = $namespace;
3008 $this->wsdl->namespaces['soap'] = 'http://schemas.xmlsoap.org/wsdl/soap/';
3009 $this->wsdl->namespaces['wsdl'] = 'http://schemas.xmlsoap.org/wsdl/';
3010 if ($schemaTargetNamespace != $namespace) {
3011 $this->wsdl->namespaces['types'] = $schemaTargetNamespace;
3013 $this->wsdl->schemas[$schemaTargetNamespace][0] = new xmlschema('', '', $this->wsdl->namespaces);
3014 $this->wsdl->schemas[$schemaTargetNamespace][0]->schemaTargetNamespace = $schemaTargetNamespace;
3015 $this->wsdl->schemas[$schemaTargetNamespace][0]->imports['http://schemas.xmlsoap.org/soap/encoding/'][0] = array('location' => '', 'loaded' => true);
3016 $this->wsdl->schemas[$schemaTargetNamespace][0]->imports['http://schemas.xmlsoap.org/wsdl/'][0] = array('location' => '', 'loaded' => true);
3017 $this->wsdl->bindings[$serviceName.'Binding'] = array(
3018 'name'=>$serviceName.'Binding',
3020 'transport'=>$transport,
3021 'portType'=>$serviceName.'PortType');
3022 $this->wsdl->ports[$serviceName.'Port'] = array(
3023 'binding'=>$serviceName.'Binding',
3024 'location'=>$endpoint,
3025 'bindingType'=>'http://schemas.xmlsoap.org/wsdl/soap/');
3036 * parses a WSDL file, allows access to it's data, other utility methods
3038 * @author Dietrich Ayala <dietrich@ganx4.com>
3042 class wsdl extends nusoap_base {
3043 // URL or filename of the root of this WSDL
3045 // define internal arrays of bindings, ports, operations, messages, etc.
3046 var $schemas = array();
3048 var $message = array();
3049 var $complexTypes = array();
3050 var $messages = array();
3051 var $currentMessage;
3052 var $currentOperation;
3053 var $portTypes = array();
3054 var $currentPortType;
3055 var $bindings = array();
3056 var $currentBinding;
3057 var $ports = array();
3059 var $opData = array();
3061 var $documentation = false;
3063 // array of wsdl docs to import
3064 var $import = array();
3069 var $depth_array = array();
3071 var $proxyhost = '';
3072 var $proxyport = '';
3073 var $proxyusername = '';
3074 var $proxypassword = '';
3076 var $response_timeout = 30;
3081 * @param string $wsdl WSDL document URL
3082 * @param string $proxyhost
3083 * @param string $proxyport
3084 * @param string $proxyusername
3085 * @param string $proxypassword
3086 * @param integer $timeout set the connection timeout
3087 * @param integer $response_timeout set the response timeout
3090 function wsdl($wsdl = '',$proxyhost=false,$proxyport=false,$proxyusername=false,$proxypassword=false,$timeout=0,$response_timeout=30){
3091 $this->wsdl = $wsdl;
3092 $this->proxyhost = $proxyhost;
3093 $this->proxyport = $proxyport;
3094 $this->proxyusername = $proxyusername;
3095 $this->proxypassword = $proxypassword;
3096 $this->timeout = $timeout;
3097 $this->response_timeout = $response_timeout;
3101 $this->debug('initial wsdl URL: ' . $wsdl);
3102 $this->parseWSDL($wsdl);
3105 // TODO: handle imports more properly, grabbing them in-line and nesting them
3106 $imported_urls = array();
3108 while ($imported > 0) {
3111 foreach ($this->schemas as $ns => $list) {
3112 foreach ($list as $xs) {
3113 $wsdlparts = parse_url($this->wsdl); // this is bogusly simple!
3114 foreach ($xs->imports as $ns2 => $list2) {
3115 for ($ii = 0; $ii < count($list2); $ii++) {
3116 if (! $list2[$ii]['loaded']) {
3117 $this->schemas[$ns]->imports[$ns2][$ii]['loaded'] = true;
3118 $url = $list2[$ii]['location'];
3120 $urlparts = parse_url($url);
3121 if (!isset($urlparts['host'])) {
3122 $url = $wsdlparts['scheme'] . '://' . $wsdlparts['host'] .
3123 substr($wsdlparts['path'],0,strrpos($wsdlparts['path'],'/') + 1) .$urlparts['path'];
3125 if (! in_array($url, $imported_urls)) {
3126 $this->parseWSDL($url);
3128 $imported_urls[] = $url;
3131 $this->debug("Unexpected scenario: empty URL for unloaded import");
3139 $wsdlparts = parse_url($this->wsdl); // this is bogusly simple!
3140 foreach ($this->import as $ns => $list) {
3141 for ($ii = 0; $ii < count($list); $ii++) {
3142 if (! $list[$ii]['loaded']) {
3143 $this->import[$ns][$ii]['loaded'] = true;
3144 $url = $list[$ii]['location'];
3146 $urlparts = parse_url($url);
3147 if (!isset($urlparts['host'])) {
3148 $url = $wsdlparts['scheme'] . '://' . $wsdlparts['host'] .
3149 substr($wsdlparts['path'],0,strrpos($wsdlparts['path'],'/') + 1) .$urlparts['path'];
3151 if (! in_array($url, $imported_urls)) {
3152 $this->parseWSDL($url);
3154 $imported_urls[] = $url;
3157 $this->debug("Unexpected scenario: empty URL for unloaded import");
3163 // add new data to operation data
3164 foreach($this->bindings as $binding => $bindingData) {
3165 if (isset($bindingData['operations']) && is_array($bindingData['operations'])) {
3166 foreach($bindingData['operations'] as $operation => $data) {
3167 $this->debug('post-parse data gathering for ' . $operation);
3168 $this->bindings[$binding]['operations'][$operation]['input'] =
3169 isset($this->bindings[$binding]['operations'][$operation]['input']) ?
3170 array_merge($this->bindings[$binding]['operations'][$operation]['input'], $this->portTypes[ $bindingData['portType'] ][$operation]['input']) :
3171 $this->portTypes[ $bindingData['portType'] ][$operation]['input'];
3172 $this->bindings[$binding]['operations'][$operation]['output'] =
3173 isset($this->bindings[$binding]['operations'][$operation]['output']) ?
3174 array_merge($this->bindings[$binding]['operations'][$operation]['output'], $this->portTypes[ $bindingData['portType'] ][$operation]['output']) :
3175 $this->portTypes[ $bindingData['portType'] ][$operation]['output'];
3176 if(isset($this->messages[ $this->bindings[$binding]['operations'][$operation]['input']['message'] ])){
3177 $this->bindings[$binding]['operations'][$operation]['input']['parts'] = $this->messages[ $this->bindings[$binding]['operations'][$operation]['input']['message'] ];
3179 if(isset($this->messages[ $this->bindings[$binding]['operations'][$operation]['output']['message'] ])){
3180 $this->bindings[$binding]['operations'][$operation]['output']['parts'] = $this->messages[ $this->bindings[$binding]['operations'][$operation]['output']['message'] ];
3182 if (isset($bindingData['style'])) {
3183 $this->bindings[$binding]['operations'][$operation]['style'] = $bindingData['style'];
3185 $this->bindings[$binding]['operations'][$operation]['transport'] = isset($bindingData['transport']) ? $bindingData['transport'] : '';
3186 $this->bindings[$binding]['operations'][$operation]['documentation'] = isset($this->portTypes[ $bindingData['portType'] ][$operation]['documentation']) ? $this->portTypes[ $bindingData['portType'] ][$operation]['documentation'] : '';
3187 $this->bindings[$binding]['operations'][$operation]['endpoint'] = isset($bindingData['endpoint']) ? $bindingData['endpoint'] : '';
3194 * parses the wsdl document
3196 * @param string $wsdl path or URL
3199 function parseWSDL($wsdl = '')
3202 $this->debug('no wsdl passed to parseWSDL()!!');
3203 $this->setError('no wsdl passed to parseWSDL()!!');
3207 // parse $wsdl for url format
3208 $wsdl_props = parse_url($wsdl);
3210 if (isset($wsdl_props['scheme']) && ($wsdl_props['scheme'] == 'http' || $wsdl_props['scheme'] == 'https')) {
3211 $this->debug('getting WSDL http(s) URL ' . $wsdl);
3213 $tr = new soap_transport_http($wsdl);
3214 $tr->request_method = 'GET';
3215 $tr->useSOAPAction = false;
3216 if($this->proxyhost && $this->proxyport){
3217 $tr->setProxy($this->proxyhost,$this->proxyport,$this->proxyusername,$this->proxypassword);
3219 if (isset($wsdl_props['user'])) {
3220 $tr->setCredentials($wsdl_props['user'],$wsdl_props['pass']);
3222 $wsdl_string = $tr->send('', $this->timeout, $this->response_timeout);
3223 //$this->debug("WSDL request\n" . $tr->outgoing_payload);
3224 //$this->debug("WSDL response\n" . $tr->incoming_payload);
3225 $this->debug("transport debug data...\n" . $tr->debug_str);
3227 if($err = $tr->getError() ){
3228 $errstr = 'HTTP ERROR: '.$err;
3229 $this->debug($errstr);
3230 $this->setError($errstr);
3236 // $wsdl is not http(s), so treat it as a file URL or plain file path
3237 if (isset($wsdl_props['scheme']) && ($wsdl_props['scheme'] == 'file') && isset($wsdl_props['path'])) {
3238 $path = isset($wsdl_props['host']) ? ($wsdl_props['host'] . ':' . $wsdl_props['path']) : $wsdl_props['path'];
3242 $this->debug('getting WSDL file ' . $path);
3243 if ($fp = @fopen($path, 'r')) {
3245 while ($data = fread($fp, 32768)) {
3246 $wsdl_string .= $data;
3250 $errstr = "Bad path to WSDL file $path";
3251 $this->debug($errstr);
3252 $this->setError($errstr);
3256 // end new code added
3257 // Create an XML parser.
3258 $this->parser = xml_parser_create();
3259 // Set the options for parsing the XML data.
3260 // xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 1);
3261 xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING, 0);
3262 // Set the object for the parser.
3263 xml_set_object($this->parser, $this);
3264 // Set the element handlers for the parser.
3265 xml_set_element_handler($this->parser, 'start_element', 'end_element');
3266 xml_set_character_data_handler($this->parser, 'character_data');
3267 // Parse the XML file.
3268 if (!xml_parse($this->parser, $wsdl_string, true)) {
3269 // Display an error message.
3271 'XML error parsing WSDL from %s on line %d: %s',
3273 xml_get_current_line_number($this->parser),
3274 xml_error_string(xml_get_error_code($this->parser))
3276 $this->debug($errstr);
3277 $this->debug("XML payload:\n" . $wsdl_string);
3278 $this->setError($errstr);
3282 xml_parser_free($this->parser);
3283 // catch wsdl parse errors
3284 if($this->getError()){
3291 * start-element handler
3293 * @param string $parser XML parser object
3294 * @param string $name element name
3295 * @param string $attrs associative array of attributes
3298 function start_element($parser, $name, $attrs)
3300 if ($this->status == 'schema') {
3301 $this->currentSchema->schemaStartElement($parser, $name, $attrs);
3302 $this->debug_str .= $this->currentSchema->debug_str;
3303 $this->currentSchema->debug_str = '';
3304 } elseif (ereg('schema$', $name)) {
3305 // $this->debug("startElement for $name ($attrs[name]). status = $this->status (".$this->getLocalPart($name).")");
3306 $this->status = 'schema';
3307 $this->currentSchema = new xmlschema('', '', $this->namespaces);
3308 $this->currentSchema->schemaStartElement($parser, $name, $attrs);
3309 $this->debug_str .= $this->currentSchema->debug_str;
3310 $this->currentSchema->debug_str = '';
3312 // position in the total number of elements, starting from 0
3313 $pos = $this->position++;
3314 $depth = $this->depth++;
3315 // set self as current value for this depth
3316 $this->depth_array[$depth] = $pos;
3317 $this->message[$pos] = array('cdata' => '');
3318 // get element prefix
3319 if (ereg(':', $name)) {
3321 $prefix = substr($name, 0, strpos($name, ':'));
3323 $namespace = isset($this->namespaces[$prefix]) ? $this->namespaces[$prefix] : '';
3324 // get unqualified name
3325 $name = substr(strstr($name, ':'), 1);
3328 if (count($attrs) > 0) {
3329 foreach($attrs as $k => $v) {
3330 // if ns declarations, add to class level array of valid namespaces
3331 if (ereg("^xmlns", $k)) {
3332 if ($ns_prefix = substr(strrchr($k, ':'), 1)) {
3333 $this->namespaces[$ns_prefix] = $v;
3335 $this->namespaces['ns' . (count($this->namespaces) + 1)] = $v;
3337 if ($v == 'http://www.w3.org/2001/XMLSchema' || $v == 'http://www.w3.org/1999/XMLSchema') {
3338 $this->XMLSchemaVersion = $v;
3339 $this->namespaces['xsi'] = $v . '-instance';
3342 // expand each attribute
3343 $k = strpos($k, ':') ? $this->expandQname($k) : $k;
3344 if ($k != 'location' && $k != 'soapAction' && $k != 'namespace') {
3345 $v = strpos($v, ':') ? $this->expandQname($v) : $v;
3353 // find status, register data
3354 switch ($this->status) {
3356 if ($name == 'part') {
3357 if (isset($attrs['type'])) {
3358 $this->debug("msg " . $this->currentMessage . ": found part $attrs[name]: " . implode(',', $attrs));
3359 $this->messages[$this->currentMessage][$attrs['name']] = $attrs['type'];
3361 if (isset($attrs['element'])) {
3362 $this->messages[$this->currentMessage][$attrs['name']] = $attrs['element'];
3369 $this->currentPortOperation = $attrs['name'];
3370 $this->debug("portType $this->currentPortType operation: $this->currentPortOperation");
3371 if (isset($attrs['parameterOrder'])) {
3372 $this->portTypes[$this->currentPortType][$attrs['name']]['parameterOrder'] = $attrs['parameterOrder'];
3375 case 'documentation':
3376 $this->documentation = true;
3378 // merge input/output data
3380 $m = isset($attrs['message']) ? $this->getLocalPart($attrs['message']) : '';
3381 $this->portTypes[$this->currentPortType][$this->currentPortOperation][$name]['message'] = $m;
3389 if (isset($attrs['style'])) {
3390 $this->bindings[$this->currentBinding]['prefix'] = $prefix;
3392 $this->bindings[$this->currentBinding] = array_merge($this->bindings[$this->currentBinding], $attrs);
3395 $this->bindings[$this->currentBinding]['operations'][$this->currentOperation][$this->opStatus]['headers'][] = $attrs;
3398 if (isset($attrs['soapAction'])) {
3399 $this->bindings[$this->currentBinding]['operations'][$this->currentOperation]['soapAction'] = $attrs['soapAction'];
3401 if (isset($attrs['style'])) {
3402 $this->bindings[$this->currentBinding]['operations'][$this->currentOperation]['style'] = $attrs['style'];
3404 if (isset($attrs['name'])) {
3405 $this->currentOperation = $attrs['name'];
3406 $this->debug("current binding operation: $this->currentOperation");
3407 $this->bindings[$this->currentBinding]['operations'][$this->currentOperation]['name'] = $attrs['name'];
3408 $this->bindings[$this->currentBinding]['operations'][$this->currentOperation]['binding'] = $this->currentBinding;
3409 $this->bindings[$this->currentBinding]['operations'][$this->currentOperation]['endpoint'] = isset($this->bindings[$this->currentBinding]['endpoint']) ? $this->bindings[$this->currentBinding]['endpoint'] : '';
3413 $this->opStatus = 'input';
3416 $this->opStatus = 'output';
3419 if (isset($this->bindings[$this->currentBinding]['operations'][$this->currentOperation][$this->opStatus])) {
3420 $this->bindings[$this->currentBinding]['operations'][$this->currentOperation][$this->opStatus] = array_merge($this->bindings[$this->currentBinding]['operations'][$this->currentOperation][$this->opStatus], $attrs);
3422 $this->bindings[$this->currentBinding]['operations'][$this->currentOperation][$this->opStatus] = $attrs;
3430 $this->currentPort = $attrs['name'];
3431 $this->debug('current port: ' . $this->currentPort);
3432 $this->ports[$this->currentPort]['binding'] = $this->getLocalPart($attrs['binding']);
3436 $this->ports[$this->currentPort]['location'] = $attrs['location'];
3437 $this->ports[$this->currentPort]['bindingType'] = $namespace;
3438 $this->bindings[ $this->ports[$this->currentPort]['binding'] ]['bindingType'] = $namespace;
3439 $this->bindings[ $this->ports[$this->currentPort]['binding'] ]['endpoint'] = $attrs['location'];
3447 if (isset($attrs['location'])) {
3448 $this->import[$attrs['namespace']][] = array('location' => $attrs['location'], 'loaded' => false);
3449 $this->debug('parsing import ' . $attrs['namespace']. ' - ' . $attrs['location'] . ' (' . count($this->import[$attrs['namespace']]).')');
3451 $this->import[$attrs['namespace']][] = array('location' => '', 'loaded' => true);
3452 if (! $this->getPrefixFromNamespace($attrs['namespace'])) {
3453 $this->namespaces['ns'.(count($this->namespaces)+1)] = $attrs['namespace'];
3455 $this->debug('parsing import ' . $attrs['namespace']. ' - [no location] (' . count($this->import[$attrs['namespace']]).')');
3460 // $this->status = 'schema';
3463 $this->status = 'message';
3464 $this->messages[$attrs['name']] = array();
3465 $this->currentMessage = $attrs['name'];
3468 $this->status = 'portType';
3469 $this->portTypes[$attrs['name']] = array();
3470 $this->currentPortType = $attrs['name'];
3473 if (isset($attrs['name'])) {
3475 if (strpos($attrs['name'], ':')) {
3476 $this->currentBinding = $this->getLocalPart($attrs['name']);
3478 $this->currentBinding = $attrs['name'];
3480 $this->status = 'binding';
3481 $this->bindings[$this->currentBinding]['portType'] = $this->getLocalPart($attrs['type']);
3482 $this->debug("current binding: $this->currentBinding of portType: " . $attrs['type']);
3486 $this->serviceName = $attrs['name'];
3487 $this->status = 'service';
3488 $this->debug('current service: ' . $this->serviceName);
3491 foreach ($attrs as $name => $value) {
3492 $this->wsdl_info[$name] = $value;
3500 * end-element handler
3502 * @param string $parser XML parser object
3503 * @param string $name element name
3506 function end_element($parser, $name){
3507 // unset schema status
3508 if (/*ereg('types$', $name) ||*/ ereg('schema$', $name)) {
3510 $this->schemas[$this->currentSchema->schemaTargetNamespace][] = $this->currentSchema;
3512 if ($this->status == 'schema') {
3513 $this->currentSchema->schemaEndElement($parser, $name);
3515 // bring depth down a notch
3518 // end documentation
3519 if ($this->documentation) {
3520 //TODO: track the node to which documentation should be assigned; it can be a part, message, etc.
3521 //$this->portTypes[$this->currentPortType][$this->currentPortOperation]['documentation'] = $this->documentation;
3522 $this->documentation = false;
3527 * element content handler
3529 * @param string $parser XML parser object
3530 * @param string $data element content
3533 function character_data($parser, $data)
3535 $pos = isset($this->depth_array[$this->depth]) ? $this->depth_array[$this->depth] : 0;
3536 if (isset($this->message[$pos]['cdata'])) {
3537 $this->message[$pos]['cdata'] .= $data;
3539 if ($this->documentation) {
3540 $this->documentation .= $data;
3544 function getBindingData($binding)
3546 if (is_array($this->bindings[$binding])) {
3547 return $this->bindings[$binding];
3552 * returns an assoc array of operation names => operation data
3554 * @param string $bindingType eg: soap, smtp, dime (only soap is currently supported)
3558 function getOperations($bindingType = 'soap')
3561 if ($bindingType == 'soap') {
3562 $bindingType = 'http://schemas.xmlsoap.org/wsdl/soap/';
3565 foreach($this->ports as $port => $portData) {
3566 // binding type of port matches parameter
3567 if ($portData['bindingType'] == $bindingType) {
3568 //$this->debug("getOperations for port $port");
3569 //$this->debug("port data: " . $this->varDump($portData));
3570 //$this->debug("bindings: " . $this->varDump($this->bindings[ $portData['binding'] ]));
3572 if (isset($this->bindings[ $portData['binding'] ]['operations'])) {
3573 $ops = array_merge ($ops, $this->bindings[ $portData['binding'] ]['operations']);
3581 * returns an associative array of data necessary for calling an operation
3583 * @param string $operation , name of operation
3584 * @param string $bindingType , type of binding eg: soap
3588 function getOperationData($operation, $bindingType = 'soap')
3590 if ($bindingType == 'soap') {
3591 $bindingType = 'http://schemas.xmlsoap.org/wsdl/soap/';
3594 foreach($this->ports as $port => $portData) {
3595 // binding type of port matches parameter
3596 if ($portData['bindingType'] == $bindingType) {
3598 //foreach($this->bindings[ $portData['binding'] ]['operations'] as $bOperation => $opData) {
3599 foreach(array_keys($this->bindings[ $portData['binding'] ]['operations']) as $bOperation) {
3600 if ($operation == $bOperation) {
3601 $opData = $this->bindings[ $portData['binding'] ]['operations'][$operation];
3610 * returns an array of information about a given type
3611 * returns false if no type exists by the given name
3614 * 'elements' => array(), // refs to elements array
3615 * 'restrictionBase' => '',
3617 * 'order' => '(sequence|all)',
3618 * 'attrs' => array() // refs to attributes array
3621 * @param $type string
3627 function getTypeDef($type, $ns) {
3628 if ((! $ns) && isset($this->namespaces['tns'])) {
3629 $ns = $this->namespaces['tns'];
3631 if (isset($this->schemas[$ns])) {
3632 foreach ($this->schemas[$ns] as $xs) {
3633 $t = $xs->getTypeDef($type);
3634 $this->debug_str .= $xs->debug_str;
3635 $xs->debug_str = '';
3645 * serialize the parsed wsdl
3647 * @return string , serialization of WSDL
3650 function serialize()
3652 $xml = '<?xml version="1.0" encoding="ISO-8859-1"?><definitions';
3653 foreach($this->namespaces as $k => $v) {
3654 $xml .= " xmlns:$k=\"$v\"";
3656 // 10.9.02 - add poulter fix for wsdl and tns declarations
3657 if (isset($this->namespaces['wsdl'])) {
3658 $xml .= " xmlns=\"" . $this->namespaces['wsdl'] . "\"";
3660 if (isset($this->namespaces['tns'])) {
3661 $xml .= " targetNamespace=\"" . $this->namespaces['tns'] . "\"";
3665 if (sizeof($this->import) > 0) {
3666 foreach($this->import as $ns => $list) {
3667 foreach ($list as $ii) {
3668 if ($ii['location'] != '') {
3669 $xml .= '<import location="' . $ii['location'] . '" namespace="' . $ns . '" />';
3671 $xml .= '<import namespace="' . $ns . '" />';
3677 if (count($this->schemas)>=1) {
3679 foreach ($this->schemas as $ns => $list) {
3680 foreach ($list as $xs) {
3681 $xml .= $xs->serializeSchema();
3687 if (count($this->messages) >= 1) {
3688 foreach($this->messages as $msgName => $msgParts) {
3689 $xml .= '<message name="' . $msgName . '">';
3690 if(is_array($msgParts)){
3691 foreach($msgParts as $partName => $partType) {
3692 // print 'serializing '.$partType.', sv: '.$this->XMLSchemaVersion.'<br>';
3693 if (strpos($partType, ':')) {
3694 $typePrefix = $this->getPrefixFromNamespace($this->getPrefix($partType));
3695 } elseif (isset($this->typemap[$this->namespaces['xsd']][$partType])) {
3696 // print 'checking typemap: '.$this->XMLSchemaVersion.'<br>';
3697 $typePrefix = 'xsd';
3699 foreach($this->typemap as $ns => $types) {
3700 if (isset($types[$partType])) {
3701 $typePrefix = $this->getPrefixFromNamespace($ns);
3704 if (!isset($typePrefix)) {
3705 die("$partType has no namespace!");
3708 $xml .= '<part name="' . $partName . '" type="' . $typePrefix . ':' . $this->getLocalPart($partType) . '" />';
3711 $xml .= '</message>';
3714 // bindings & porttypes
3715 if (count($this->bindings) >= 1) {
3718 foreach($this->bindings as $bindingName => $attrs) {
3719 $binding_xml .= '<binding name="' . $bindingName . '" type="tns:' . $attrs['portType'] . '">';
3720 $binding_xml .= '<soap:binding style="' . $attrs['style'] . '" transport="' . $attrs['transport'] . '"/>';
3721 $portType_xml .= '<portType name="' . $attrs['portType'] . '">';
3722 foreach($attrs['operations'] as $opName => $opParts) {
3723 $binding_xml .= '<operation name="' . $opName . '">';
3724 $binding_xml .= '<soap:operation soapAction="' . $opParts['soapAction'] . '" style="'. $attrs['style'] . '"/>';
3725 if (isset($opParts['input']['encodingStyle']) && $opParts['input']['encodingStyle'] != '') {
3726 $enc_style = '" encodingStyle="' . $opParts['input']['encodingStyle'] . '"';
3730 $binding_xml .= '<input><soap:body use="' . $opParts['input']['use'] . '" namespace="' . $opParts['input']['namespace'] . $enc_style . '/></input>';
3731 if (isset($opParts['output']['encodingStyle']) && $opParts['output']['encodingStyle'] != '') {
3732 $enc_style = '" encodingStyle="' . $opParts['output']['encodingStyle'] . '"';
3736 $binding_xml .= '<output><soap:body use="' . $opParts['output']['use'] . '" namespace="' . $opParts['output']['namespace'] . $enc_style . '/></output>';
3737 $binding_xml .= '</operation>';
3738 $portType_xml .= '<operation name="' . $opParts['name'] . '"';
3739 if (isset($opParts['parameterOrder'])) {
3740 $portType_xml .= ' parameterOrder="' . $opParts['parameterOrder'] . '"';
3742 $portType_xml .= '>';
3743 if(isset($opParts['documentation']) && $opParts['documentation'] != '') {
3744 $portType_xml .= '<documentation>' . htmlspecialchars($opParts['documentation']) . '</documentation>';
3746 $portType_xml .= '<input message="tns:' . $opParts['input']['message'] . '"/>';
3747 $portType_xml .= '<output message="tns:' . $opParts['output']['message'] . '"/>';
3748 $portType_xml .= '</operation>';
3750 $portType_xml .= '</portType>';
3751 $binding_xml .= '</binding>';
3753 $xml .= $portType_xml . $binding_xml;
3756 $xml .= '<service name="' . $this->serviceName . '">';
3757 if (count($this->ports) >= 1) {
3758 foreach($this->ports as $pName => $attrs) {
3759 $xml .= '<port name="' . $pName . '" binding="tns:' . $attrs['binding'] . '">';
3760 $xml .= '<soap:address location="' . $attrs['location'] . '"/>';
3764 $xml .= '</service>';
3765 return $xml . '</definitions>';
3769 * serialize a PHP value according to a WSDL message definition
3772 * - multi-ref serialization
3773 * - validate PHP values against type definitions, return errors if invalid
3775 * @param string $ type name
3776 * @param mixed $ param value
3777 * @return mixed new param or false if initial value didn't validate
3779 function serializeRPCParameters($operation, $direction, $parameters)
3781 $this->debug('in serializeRPCParameters with operation '.$operation.', direction '.$direction.' and '.count($parameters).' param(s), and xml schema version ' . $this->XMLSchemaVersion);
3783 if ($direction != 'input' && $direction != 'output') {
3784 $this->debug('The value of the \$direction argument needs to be either "input" or "output"');
3785 $this->setError('The value of the \$direction argument needs to be either "input" or "output"');
3788 if (!$opData = $this->getOperationData($operation)) {
3789 $this->debug('Unable to retrieve WSDL data for operation: ' . $operation);
3790 $this->setError('Unable to retrieve WSDL data for operation: ' . $operation);
3793 $this->debug($this->varDump($opData));
3795 // Get encoding style for output and set to current
3796 $encodingStyle = 'http://schemas.xmlsoap.org/soap/encoding/';
3797 if(($direction == 'input') && isset($opData['output']['encodingStyle']) && ($opData['output']['encodingStyle'] != $encodingStyle)) {
3798 $encodingStyle = $opData['output']['encodingStyle'];
3799 $enc_style = $encodingStyle;
3804 if (isset($opData[$direction]['parts']) && sizeof($opData[$direction]['parts']) > 0) {
3806 $use = $opData[$direction]['use'];
3807 $this->debug("use=$use");
3808 $this->debug('got ' . count($opData[$direction]['parts']) . ' part(s)');
3809 if (is_array($parameters)) {
3810 $parametersArrayType = $this->isArraySimpleOrStruct($parameters);
3811 $this->debug('have ' . $parametersArrayType . ' parameters');
3812 foreach($opData[$direction]['parts'] as $name => $type) {
3813 $this->debug('serializing part "'.$name.'" of type "'.$type.'"');
3814 // Track encoding style
3815 if (isset($opData[$direction]['encodingStyle']) && $encodingStyle != $opData[$direction]['encodingStyle']) {
3816 $encodingStyle = $opData[$direction]['encodingStyle'];
3817 $enc_style = $encodingStyle;
3821 // NOTE: add error handling here
3822 // if serializeType returns false, then catch global error and fault
3823 if ($parametersArrayType == 'arraySimple') {
3824 $p = array_shift($parameters);
3825 $this->debug('calling serializeType w/indexed param');
3826 $xml .= $this->serializeType($name, $type, $p, $use, $enc_style);
3827 } elseif (isset($parameters[$name])) {
3828 $this->debug('calling serializeType w/named param');
3829 $xml .= $this->serializeType($name, $type, $parameters[$name], $use, $enc_style);
3831 // TODO: only send nillable
3832 $this->debug('calling serializeType w/null param');
3833 $xml .= $this->serializeType($name, $type, null, $use, $enc_style);
3837 $this->debug('no parameters passed.');
3844 * serialize a PHP value according to a WSDL message definition
3847 * - multi-ref serialization
3848 * - validate PHP values against type definitions, return errors if invalid
3850 * @param string $ type name
3851 * @param mixed $ param value
3852 * @return mixed new param or false if initial value didn't validate
3854 function serializeParameters($operation, $direction, $parameters)
3856 $this->debug('in serializeParameters with operation '.$operation.', direction '.$direction.' and '.count($parameters).' param(s), and xml schema version ' . $this->XMLSchemaVersion);
3858 if ($direction != 'input' && $direction != 'output') {
3859 $this->debug('The value of the \$direction argument needs to be either "input" or "output"');
3860 $this->setError('The value of the \$direction argument needs to be either "input" or "output"');
3863 if (!$opData = $this->getOperationData($operation)) {
3864 $this->debug('Unable to retrieve WSDL data for operation: ' . $operation);
3865 $this->setError('Unable to retrieve WSDL data for operation: ' . $operation);
3868 $this->debug($this->varDump($opData));
3870 // Get encoding style for output and set to current
3871 $encodingStyle = 'http://schemas.xmlsoap.org/soap/encoding/';
3872 if(($direction == 'input') && isset($opData['output']['encodingStyle']) && ($opData['output']['encodingStyle'] != $encodingStyle)) {
3873 $encodingStyle = $opData['output']['encodingStyle'];
3874 $enc_style = $encodingStyle;
3879 if (isset($opData[$direction]['parts']) && sizeof($opData[$direction]['parts']) > 0) {
3881 $use = $opData[$direction]['use'];
3882 $this->debug("use=$use");
3883 $this->debug('got ' . count($opData[$direction]['parts']) . ' part(s)');
3884 if (is_array($parameters)) {
3885 $parametersArrayType = $this->isArraySimpleOrStruct($parameters);
3886 $this->debug('have ' . $parametersArrayType . ' parameters');
3887 foreach($opData[$direction]['parts'] as $name => $type) {
3888 $this->debug('serializing part "'.$name.'" of type "'.$type.'"');
3889 // Track encoding style
3890 if(isset($opData[$direction]['encodingStyle']) && $encodingStyle != $opData[$direction]['encodingStyle']) {
3891 $encodingStyle = $opData[$direction]['encodingStyle'];
3892 $enc_style = $encodingStyle;
3896 // NOTE: add error handling here
3897 // if serializeType returns false, then catch global error and fault
3898 if ($parametersArrayType == 'arraySimple') {
3899 $p = array_shift($parameters);
3900 $this->debug('calling serializeType w/indexed param');
3901 $xml .= $this->serializeType($name, $type, $p, $use, $enc_style);
3902 } elseif (isset($parameters[$name])) {
3903 $this->debug('calling serializeType w/named param');
3904 $xml .= $this->serializeType($name, $type, $parameters[$name], $use, $enc_style);
3906 // TODO: only send nillable
3907 $this->debug('calling serializeType w/null param');
3908 $xml .= $this->serializeType($name, $type, null, $use, $enc_style);
3912 $this->debug('no parameters passed.');
3919 * serializes a PHP value according a given type definition
3921 * @param string $name , name of type (part)
3922 * @param string $type , type of type, heh (type or element)
3923 * @param mixed $value , a native PHP value (parameter value)
3924 * @param string $use , use for part (encoded|literal)
3925 * @param string $encodingStyle , use to add encoding changes to serialisation
3926 * @return string serialization
3929 function serializeType($name, $type, $value, $use='encoded', $encodingStyle=false)
3931 $this->debug("in serializeType: $name, $type, $value, $use, $encodingStyle");
3932 if($use == 'encoded' && $encodingStyle) {
3933 $encodingStyle = ' SOAP-ENV:encodingStyle="' . $encodingStyle . '"';
3936 // if a soap_val has been supplied, let its type override the WSDL
3937 if (is_object($value) && get_class($value) == 'soapval') {
3938 // TODO: get attributes from soapval?
3939 if ($value->type_ns) {
3940 $type = $value->type_ns . ':' . $value->type;
3942 $type = $value->type;
3944 $value = $value->value;
3946 $this->debug("in serializeType: soapval overrides type to $type, value to $value");
3952 if (strpos($type, ':')) {
3953 $uqType = substr($type, strrpos($type, ':') + 1);
3954 $ns = substr($type, 0, strrpos($type, ':'));
3955 $this->debug("got a prefixed type: $uqType, $ns");
3956 if ($this->getNamespaceFromPrefix($ns)) {
3957 $ns = $this->getNamespaceFromPrefix($ns);
3958 $this->debug("expanded prefixed type: $uqType, $ns");
3961 if($ns == $this->XMLSchemaVersion){
3963 if (is_null($value)) {
3964 if ($use == 'literal') {
3965 // TODO: depends on nillable
3968 return "<$name xsi:nil=\"true\"/>";
3971 if ($uqType == 'boolean' && !$value) {
3973 } elseif ($uqType == 'boolean') {
3976 if ($uqType == 'string' && gettype($value) == 'string') {
3977 $value = $this->expandEntities($value);
3980 // TODO: what about null/nil values?
3981 // check type isn't a custom type extending xmlschema namespace
3982 if (!$this->getTypeDef($uqType, $ns)) {
3983 if ($use == 'literal') {
3985 return "<$name xsi:type=\"" . $this->getPrefixFromNamespace($this->XMLSchemaVersion) . ":$uqType\">$value</$name>";
3987 return "<$name>$value</$name>";
3990 return "<$name xsi:type=\"" . $this->getPrefixFromNamespace($this->XMLSchemaVersion) . ":$uqType\"$encodingStyle>$value</$name>";
3993 } else if ($ns == 'http://xml.apache.org/xml-soap') {
3994 if ($uqType == 'Map') {
3996 foreach($value as $k => $v) {
3997 $this->debug("serializing map element: key $k, value $v");
3998 $contents .= '<item>';
3999 $contents .= $this->serialize_val($k,'key',false,false,false,false,$use);
4000 $contents .= $this->serialize_val($v,'value',false,false,false,false,$use);
4001 $contents .= '</item>';
4003 if ($use == 'literal') {
4005 return "<$name xsi:type=\"" . $this->getPrefixFromNamespace('http://xml.apache.org/xml-soap') . ":$uqType\">$contents</$name>";
4007 return "<$name>$contents</$name>";
4010 return "<$name xsi:type=\"" . $this->getPrefixFromNamespace('http://xml.apache.org/xml-soap') . ":$uqType\"$encodingStyle>$contents</$name>";
4015 $this->debug("No namespace for type $type");
4019 if(!$typeDef = $this->getTypeDef($uqType, $ns)){
4020 $this->setError("$type ($uqType) is not a supported type.");
4021 $this->debug("$type ($uqType) is not a supported type.");
4024 foreach($typeDef as $k => $v) {
4025 $this->debug("typedef, $k: $v");
4028 $phpType = $typeDef['phpType'];
4029 $this->debug("serializeType: uqType: $uqType, ns: $ns, phptype: $phpType, arrayType: " . (isset($typeDef['arrayType']) ? $typeDef['arrayType'] : '') );
4030 // if php type == struct, map value to the <all> element names
4031 if ($phpType == 'struct') {
4032 if (isset($typeDef['typeClass']) && $typeDef['typeClass'] == 'element') {
4033 $elementName = $uqType;
4034 if (isset($typeDef['form']) && ($typeDef['form'] == 'qualified')) {
4035 $elementNS = " xmlns=\"$ns\"";
4038 $elementName = $name;
4041 if (is_null($value)) {
4042 if ($use == 'literal') {
4043 // TODO: depends on nillable
4044 return "<$elementName$elementNS/>";
4046 return "<$elementName$elementNS xsi:nil=\"true\"/>";
4049 if ($use == 'literal') {
4051 $xml = "<$elementName$elementNS xsi:type=\"" . $this->getPrefixFromNamespace($ns) . ":$uqType\">";
4053 $xml = "<$elementName$elementNS>";
4056 $xml = "<$elementName$elementNS xsi:type=\"" . $this->getPrefixFromNamespace($ns) . ":$uqType\"$encodingStyle>";
4059 if (isset($typeDef['elements']) && is_array($typeDef['elements'])) {
4061 // toggle whether all elements are present - ideally should validate against schema
4062 if(count($typeDef['elements']) != count($value)){
4065 foreach($typeDef['elements'] as $eName => $attrs) {
4066 // if user took advantage of a minOccurs=0, then only serialize named parameters
4067 if(isset($optionals) && !isset($value[$eName])){
4070 // TODO: if maxOccurs > 1, then allow serialization of an array
4072 if (isset($value[$eName])) {
4073 $v = $value[$eName];
4077 if (isset($attrs['maxOccurs']) && $attrs['maxOccurs'] == 'unbounded' && isset($v) && is_array($v) && $this->isArraySimpleOrStruct($v) == 'arraySimple') {
4079 foreach ($vv as $k => $v) {
4080 if (isset($attrs['type'])) {
4081 // serialize schema-defined type
4082 $xml .= $this->serializeType($eName, $attrs['type'], $v, $use, $encodingStyle);
4084 // serialize generic type
4085 $this->debug("calling serialize_val() for $v, $eName, false, false, false, false, $use");
4086 $xml .= $this->serialize_val($v, $eName, false, false, false, false, $use);
4090 if (isset($attrs['type'])) {
4091 // serialize schema-defined type
4092 $xml .= $this->serializeType($eName, $attrs['type'], $v, $use, $encodingStyle);
4094 // serialize generic type
4095 $this->debug("calling serialize_val() for $v, $eName, false, false, false, false, $use");
4096 $xml .= $this->serialize_val($v, $eName, false, false, false, false, $use);
4104 $xml .= "</$elementName>";
4105 } elseif ($phpType == 'array') {
4106 if (isset($typeDef['form']) && ($typeDef['form'] == 'qualified')) {
4107 $elementNS = " xmlns=\"$ns\"";
4111 if (is_null($value)) {
4112 if ($use == 'literal') {
4113 // TODO: depends on nillable
4114 return "<$name$elementNS/>";
4116 return "<$name$elementNS xsi:nil=\"true\"/>";
4119 if (isset($typeDef['multidimensional'])) {
4121 foreach($value as $v) {
4122 $cols = ',' . sizeof($v);
4123 $nv = array_merge($nv, $v);
4129 if (is_array($value) && sizeof($value) >= 1) {
4130 $rows = sizeof($value);
4132 foreach($value as $k => $v) {
4133 $this->debug("serializing array element: $k, $v of type: $typeDef[arrayType]");
4134 //if (strpos($typeDef['arrayType'], ':') ) {
4135 if (!in_array($typeDef['arrayType'],$this->typemap['http://www.w3.org/2001/XMLSchema'])) {
4136 $contents .= $this->serializeType('item', $typeDef['arrayType'], $v, $use);
4138 $contents .= $this->serialize_val($v, 'item', $typeDef['arrayType'], null, $this->XMLSchemaVersion, false, $use);
4141 $this->debug('contents: '.$this->varDump($contents));
4146 // TODO: for now, an empty value will be serialized as a zero element
4147 // array. Revisit this when coding the handling of null/nil values.
4148 if ($use == 'literal') {
4149 $xml = "<$name$elementNS>"
4153 $xml = "<$name$elementNS xsi:type=\"".$this->getPrefixFromNamespace('http://schemas.xmlsoap.org/soap/encoding/').':Array" '.
4154 $this->getPrefixFromNamespace('http://schemas.xmlsoap.org/soap/encoding/')
4156 .$this->getPrefixFromNamespace($this->getPrefix($typeDef['arrayType']))
4157 .":".$this->getLocalPart($typeDef['arrayType'])."[$rows$cols]\">"
4161 } elseif ($phpType == 'scalar') {
4162 if (isset($typeDef['form']) && ($typeDef['form'] == 'qualified')) {
4163 $elementNS = " xmlns=\"$ns\"";
4167 if ($use == 'literal') {
4169 return "<$name$elementNS xsi:type=\"" . $this->getPrefixFromNamespace($ns) . ":$uqType\">$value</$name>";
4171 return "<$name$elementNS>$value</$name>";
4174 return "<$name$elementNS xsi:type=\"" . $this->getPrefixFromNamespace($ns) . ":$uqType\"$encodingStyle>$value</$name>";
4177 $this->debug('returning: '.$this->varDump($xml));
4182 * adds an XML Schema complex type to the WSDL types
4185 * @param typeClass (complexType|simpleType|attribute)
4186 * @param phpType: currently supported are array and struct (php assoc array)
4187 * @param compositor (all|sequence|choice)
4188 * @param restrictionBase namespace:name (http://schemas.xmlsoap.org/soap/encoding/:Array)
4189 * @param elements = array ( name = array(name=>'',type=>'') )
4190 * @param attrs = array(
4192 * 'ref' => "http://schemas.xmlsoap.org/soap/encoding/:arrayType",
4193 * "http://schemas.xmlsoap.org/wsdl/:arrayType" => "string[]"
4196 * @param arrayType: namespace:name (http://www.w3.org/2001/XMLSchema:string)
4200 function addComplexType($name,$typeClass='complexType',$phpType='array',$compositor='',$restrictionBase='',$elements=array(),$attrs=array(),$arrayType='') {
4201 if (count($elements) > 0) {
4202 foreach($elements as $n => $e){
4203 // expand each element
4204 foreach ($e as $k => $v) {
4205 $k = strpos($k,':') ? $this->expandQname($k) : $k;
4206 $v = strpos($v,':') ? $this->expandQname($v) : $v;
4209 $eElements[$n] = $ee;
4211 $elements = $eElements;
4214 if (count($attrs) > 0) {
4215 foreach($attrs as $n => $a){
4216 // expand each attribute
4217 foreach ($a as $k => $v) {
4218 $k = strpos($k,':') ? $this->expandQname($k) : $k;
4219 $v = strpos($v,':') ? $this->expandQname($v) : $v;
4227 $restrictionBase = strpos($restrictionBase,':') ? $this->expandQname($restrictionBase) : $restrictionBase;
4228 $arrayType = strpos($arrayType,':') ? $this->expandQname($arrayType) : $arrayType;
4230 $typens = isset($this->namespaces['types']) ? $this->namespaces['types'] : $this->namespaces['tns'];
4231 $this->schemas[$typens][0]->addComplexType($name,$typeClass,$phpType,$compositor,$restrictionBase,$elements,$attrs,$arrayType);
4235 * adds an XML Schema simple type to the WSDL types
4238 * @param restrictionBase namespace:name (http://schemas.xmlsoap.org/soap/encoding/:Array)
4239 * @param typeClass (simpleType)
4240 * @param phpType: (scalar)
4244 function addSimpleType($name, $restrictionBase='', $typeClass='simpleType', $phpType='scalar') {
4245 $restrictionBase = strpos($restrictionBase,':') ? $this->expandQname($restrictionBase) : $restrictionBase;
4247 $typens = isset($this->namespaces['types']) ? $this->namespaces['types'] : $this->namespaces['tns'];
4248 $this->schemas[$typens][0]->addSimpleType($name, $restrictionBase, $typeClass, $phpType);
4252 * register a service with the server
4254 * @param string $methodname
4255 * @param string $in assoc array of input values: key = param name, value = param type
4256 * @param string $out assoc array of output values: key = param name, value = param type
4257 * @param string $namespace optional The namespace for the operation
4258 * @param string $soapaction optional The soapaction for the operation
4259 * @param string $style (rpc|document) optional The style for the operation
4260 * @param string $use (encoded|literal) optional The use for the parameters (cannot mix right now)
4261 * @param string $documentation optional The description to include in the WSDL
4264 function addOperation($name, $in = false, $out = false, $namespace = false, $soapaction = false, $style = 'rpc', $use = 'encoded', $documentation = ''){
4265 if ($style == 'rpc' && $use == 'encoded') {
4266 $encodingStyle = 'http://schemas.xmlsoap.org/soap/encoding/';
4268 $encodingStyle = '';
4271 $this->bindings[ $this->serviceName . 'Binding' ]['operations'][$name] =
4274 'binding' => $this->serviceName . 'Binding',
4275 'endpoint' => $this->endpoint,
4276 'soapAction' => $soapaction,
4280 'namespace' => $namespace,
4281 'encodingStyle' => $encodingStyle,
4282 'message' => $name . 'Request',
4286 'namespace' => $namespace,
4287 'encodingStyle' => $encodingStyle,
4288 'message' => $name . 'Response',
4290 'namespace' => $namespace,
4291 'transport' => 'http://schemas.xmlsoap.org/soap/http',
4292 'documentation' => $documentation);
4297 foreach($in as $pName => $pType)
4299 if(strpos($pType,':')) {
4300 $pType = $this->getNamespaceFromPrefix($this->getPrefix($pType)).":".$this->getLocalPart($pType);
4302 $this->messages[$name.'Request'][$pName] = $pType;
4305 $this->messages[$name.'Request']= '0';
4309 foreach($out as $pName => $pType)
4311 if(strpos($pType,':')) {
4312 $pType = $this->getNamespaceFromPrefix($this->getPrefix($pType)).":".$this->getLocalPart($pType);
4314 $this->messages[$name.'Response'][$pName] = $pType;
4317 $this->messages[$name.'Response']= '0';
4328 * soap_parser class parses SOAP XML messages into native PHP values
4330 * @author Dietrich Ayala <dietrich@ganx4.com>
4334 class soap_parser extends nusoap_base {
4337 var $xml_encoding = '';
4339 var $root_struct = '';
4340 var $root_struct_name = '';
4341 var $root_struct_namespace = '';
4342 var $root_header = '';
4343 var $document = ''; // incoming SOAP body (text)
4344 // determines where in the message we are (envelope,header,body,method)
4348 var $default_namespace = '';
4349 var $namespaces = array();
4350 var $message = array();
4353 var $fault_code = '';
4354 var $fault_str = '';
4355 var $fault_detail = '';
4356 var $depth_array = array();
4357 var $debug_flag = true;
4358 var $soapresponse = NULL;
4359 var $responseHeaders = ''; // incoming SOAP headers (text)
4360 var $body_position = 0;
4361 // for multiref parsing:
4362 // array of id => pos
4364 // array of id => hrefs => pos
4365 var $multirefs = array();
4366 // toggle for auto-decoding element content
4367 var $decode_utf8 = true;
4372 * @param string $xml SOAP message
4373 * @param string $encoding character encoding scheme of message
4374 * @param string $method
4375 * @param string $decode_utf8 whether to decode UTF-8 to ISO-8859-1
4378 function soap_parser($xml,$encoding='UTF-8',$method='',$decode_utf8=true){
4380 $this->xml_encoding = $encoding;
4381 $this->method = $method;
4382 $this->decode_utf8 = $decode_utf8;
4384 // Check whether content has been read.
4386 $this->debug('Entering soap_parser(), length='.strlen($xml).', encoding='.$encoding);
4387 // Create an XML parser - why not xml_parser_create_ns?
4388 $this->parser = xml_parser_create($this->xml_encoding);
4389 // Set the options for parsing the XML data.
4390 //xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 1);
4391 xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING, 0);
4392 xml_parser_set_option($this->parser, XML_OPTION_TARGET_ENCODING, $this->xml_encoding);
4393 // Set the object for the parser.
4394 xml_set_object($this->parser, $this);
4395 // Set the element handlers for the parser.
4396 xml_set_element_handler($this->parser, 'start_element','end_element');
4397 xml_set_character_data_handler($this->parser,'character_data');
4399 // Parse the XML file.
4400 if(!xml_parse($this->parser,$xml,true)){
4401 // Display an error message.
4402 $err = sprintf('XML error parsing SOAP payload on line %d: %s',
4403 xml_get_current_line_number($this->parser),
4404 xml_error_string(xml_get_error_code($this->parser)));
4406 $this->debug("XML payload:\n" . $xml);
4407 $this->setError($err);
4409 $this->debug('parsed successfully, found root struct: '.$this->root_struct.' of name '.$this->root_struct_name);
4411 $this->soapresponse = $this->message[$this->root_struct]['result'];
4412 // get header value: no, because this is documented as XML string
4413 // if($this->root_header != '' && isset($this->message[$this->root_header]['result'])){
4414 // $this->responseHeaders = $this->message[$this->root_header]['result'];
4416 // resolve hrefs/ids
4417 if(sizeof($this->multirefs) > 0){
4418 foreach($this->multirefs as $id => $hrefs){
4419 $this->debug('resolving multirefs for id: '.$id);
4420 $idVal = $this->buildVal($this->ids[$id]);
4421 foreach($hrefs as $refPos => $ref){
4422 $this->debug('resolving href at pos '.$refPos);
4423 $this->multirefs[$id][$refPos] = $idVal;
4428 xml_parser_free($this->parser);
4430 $this->debug('xml was empty, didn\'t parse!');
4431 $this->setError('xml was empty, didn\'t parse!');
4436 * start-element handler
4438 * @param string $parser XML parser object
4439 * @param string $name element name
4440 * @param string $attrs associative array of attributes
4443 function start_element($parser, $name, $attrs) {
4444 // position in a total number of elements, starting from 0
4445 // update class level pos
4446 $pos = $this->position++;
4448 $this->message[$pos] = array('pos' => $pos,'children'=>'','cdata'=>'');
4449 // depth = how many levels removed from root?
4450 // set mine as current global depth and increment global depth value
4451 $this->message[$pos]['depth'] = $this->depth++;
4453 // else add self as child to whoever the current parent is
4455 $this->message[$this->parent]['children'] .= '|'.$pos;
4458 $this->message[$pos]['parent'] = $this->parent;
4459 // set self as current parent
4460 $this->parent = $pos;
4461 // set self as current value for this depth
4462 $this->depth_array[$this->depth] = $pos;
4463 // get element prefix
4464 if(strpos($name,':')){
4466 $prefix = substr($name,0,strpos($name,':'));
4467 // get unqualified name
4468 $name = substr(strstr($name,':'),1);
4471 if($name == 'Envelope'){
4472 $this->status = 'envelope';
4473 } elseif($name == 'Header'){
4474 $this->root_header = $pos;
4475 $this->status = 'header';
4476 } elseif($name == 'Body'){
4477 $this->status = 'body';
4478 $this->body_position = $pos;
4480 } elseif($this->status == 'body' && $pos == ($this->body_position+1)){
4481 $this->status = 'method';
4482 $this->root_struct_name = $name;
4483 $this->root_struct = $pos;
4484 $this->message[$pos]['type'] = 'struct';
4485 $this->debug("found root struct $this->root_struct_name, pos $this->root_struct");
4488 $this->message[$pos]['status'] = $this->status;
4490 $this->message[$pos]['name'] = htmlspecialchars($name);
4492 $this->message[$pos]['attrs'] = $attrs;
4494 // loop through atts, logging ns and type declarations
4496 foreach($attrs as $key => $value){
4497 $key_prefix = $this->getPrefix($key);
4498 $key_localpart = $this->getLocalPart($key);
4499 // if ns declarations, add to class level array of valid namespaces
4500 if($key_prefix == 'xmlns'){
4501 if(ereg('^http://www.w3.org/[0-9]{4}/XMLSchema$',$value)){
4502 $this->XMLSchemaVersion = $value;
4503 $this->namespaces['xsd'] = $this->XMLSchemaVersion;
4504 $this->namespaces['xsi'] = $this->XMLSchemaVersion.'-instance';
4506 $this->namespaces[$key_localpart] = $value;
4507 // set method namespace
4508 if($name == $this->root_struct_name){
4509 $this->methodNamespace = $value;
4511 // if it's a type declaration, set type
4512 } elseif($key_localpart == 'type'){
4513 $value_prefix = $this->getPrefix($value);
4514 $value_localpart = $this->getLocalPart($value);
4515 $this->message[$pos]['type'] = $value_localpart;
4516 $this->message[$pos]['typePrefix'] = $value_prefix;
4517 if(isset($this->namespaces[$value_prefix])){
4518 $this->message[$pos]['type_namespace'] = $this->namespaces[$value_prefix];
4519 } else if(isset($attrs['xmlns:'.$value_prefix])) {
4520 $this->message[$pos]['type_namespace'] = $attrs['xmlns:'.$value_prefix];
4522 // should do something here with the namespace of specified type?
4523 } elseif($key_localpart == 'arrayType'){
4524 $this->message[$pos]['type'] = 'array';
4525 /* do arrayType ereg here
4526 [1] arrayTypeValue ::= atype asize
4527 [2] atype ::= QName rank*
4528 [3] rank ::= '[' (',')* ']'
4529 [4] asize ::= '[' length~ ']'
4530 [5] length ::= nextDimension* Digit+
4531 [6] nextDimension ::= Digit+ ','
4533 $expr = '([A-Za-z0-9_]+):([A-Za-z]+[A-Za-z0-9_]+)\[([0-9]+),?([0-9]*)\]';
4534 if(ereg($expr,$value,$regs)){
4535 $this->message[$pos]['typePrefix'] = $regs[1];
4536 $this->message[$pos]['arrayTypePrefix'] = $regs[1];
4537 if (isset($this->namespaces[$regs[1]])) {
4538 $this->message[$pos]['arrayTypeNamespace'] = $this->namespaces[$regs[1]];
4539 } else if (isset($attrs['xmlns:'.$regs[1]])) {
4540 $this->message[$pos]['arrayTypeNamespace'] = $attrs['xmlns:'.$regs[1]];
4542 $this->message[$pos]['arrayType'] = $regs[2];
4543 $this->message[$pos]['arraySize'] = $regs[3];
4544 $this->message[$pos]['arrayCols'] = $regs[4];
4549 $this->ids[$value] = $pos;
4552 if($key_localpart == 'root' && $value == 1){
4553 $this->status = 'method';
4554 $this->root_struct_name = $name;
4555 $this->root_struct = $pos;
4556 $this->debug("found root struct $this->root_struct_name, pos $pos");
4559 $attstr .= " $key=\"$value\"";
4561 // get namespace - must be done after namespace atts are processed
4563 $this->message[$pos]['namespace'] = $this->namespaces[$prefix];
4564 $this->default_namespace = $this->namespaces[$prefix];
4566 $this->message[$pos]['namespace'] = $this->default_namespace;
4568 if($this->status == 'header'){
4569 if ($this->root_header != $pos) {
4570 $this->responseHeaders .= "<" . (isset($prefix) ? $prefix . ':' : '') . "$name$attstr>";
4572 } elseif($this->root_struct_name != ''){
4573 $this->document .= "<" . (isset($prefix) ? $prefix . ':' : '') . "$name$attstr>";
4578 * end-element handler
4580 * @param string $parser XML parser object
4581 * @param string $name element name
4584 function end_element($parser, $name) {
4585 // position of current element is equal to the last value left in depth_array for my depth
4586 $pos = $this->depth_array[$this->depth--];
4588 // get element prefix
4589 if(strpos($name,':')){
4591 $prefix = substr($name,0,strpos($name,':'));
4592 // get unqualified name
4593 $name = substr(strstr($name,':'),1);
4596 // build to native type
4597 if(isset($this->body_position) && $pos > $this->body_position){
4598 // deal w/ multirefs
4599 if(isset($this->message[$pos]['attrs']['href'])){
4601 $id = substr($this->message[$pos]['attrs']['href'],1);
4602 // add placeholder to href array
4603 $this->multirefs[$id][$pos] = 'placeholder';
4604 // add set a reference to it as the result value
4605 $this->message[$pos]['result'] =& $this->multirefs[$id][$pos];
4606 // build complex values
4607 } elseif($this->message[$pos]['children'] != ''){
4609 // if result has already been generated (struct/array
4610 if(!isset($this->message[$pos]['result'])){
4611 $this->message[$pos]['result'] = $this->buildVal($pos);
4614 // set value of simple type
4616 //$this->debug('adding data for scalar value '.$this->message[$pos]['name'].' of value '.$this->message[$pos]['cdata']);
4617 if (isset($this->message[$pos]['type'])) {
4618 $this->message[$pos]['result'] = $this->decodeSimple($this->message[$pos]['cdata'], $this->message[$pos]['type'], isset($this->message[$pos]['type_namespace']) ? $this->message[$pos]['type_namespace'] : '');
4620 $parent = $this->message[$pos]['parent'];
4621 if (isset($this->message[$parent]['type']) && ($this->message[$parent]['type'] == 'array') && isset($this->message[$parent]['arrayType'])) {
4622 $this->message[$pos]['result'] = $this->decodeSimple($this->message[$pos]['cdata'], $this->message[$parent]['arrayType'], isset($this->message[$parent]['arrayTypeNamespace']) ? $this->message[$parent]['arrayTypeNamespace'] : '');
4624 $this->message[$pos]['result'] = $this->message[$pos]['cdata'];
4628 /* add value to parent's result, if parent is struct/array
4629 $parent = $this->message[$pos]['parent'];
4630 if($this->message[$parent]['type'] != 'map'){
4631 if(strtolower($this->message[$parent]['type']) == 'array'){
4632 $this->message[$parent]['result'][] = $this->message[$pos]['result'];
4634 $this->message[$parent]['result'][$this->message[$pos]['name']] = $this->message[$pos]['result'];
4642 if($this->status == 'header'){
4643 if ($this->root_header != $pos) {
4644 $this->responseHeaders .= "</" . (isset($prefix) ? $prefix . ':' : '') . "$name>";
4646 } elseif($pos >= $this->root_struct){
4647 $this->document .= "</" . (isset($prefix) ? $prefix . ':' : '') . "$name>";
4650 if($pos == $this->root_struct){
4651 $this->status = 'body';
4652 $this->root_struct_namespace = $this->message[$pos]['namespace'];
4653 } elseif($name == 'Body'){
4654 $this->status = 'envelope';
4655 } elseif($name == 'Header'){
4656 $this->status = 'envelope';
4657 } elseif($name == 'Envelope'){
4660 // set parent back to my parent
4661 $this->parent = $this->message[$pos]['parent'];
4665 * element content handler
4667 * @param string $parser XML parser object
4668 * @param string $data element content
4671 function character_data($parser, $data){
4672 $pos = $this->depth_array[$this->depth];
4673 if ($this->xml_encoding=='UTF-8'){
4674 // TODO: add an option to disable this for folks who want
4675 // raw UTF-8 that, e.g., might not map to iso-8859-1
4676 // TODO: this can also be handled with xml_parser_set_option($this->parser, XML_OPTION_TARGET_ENCODING, "ISO-8859-1");
4677 if($this->decode_utf8){
4678 $data = utf8_decode($data);
4681 $this->message[$pos]['cdata'] .= $data;
4683 if($this->status == 'header'){
4684 $this->responseHeaders .= $data;
4686 $this->document .= $data;
4691 * get the parsed message
4696 function get_response(){
4697 return $this->soapresponse;
4701 * get the parsed headers
4703 * @return string XML or empty if no headers
4706 function getHeaders(){
4707 return $this->responseHeaders;
4713 * @param string $text string to translate
4716 function decode_entities($text){
4717 foreach($this->entities as $entity => $encoded){
4718 $text = str_replace($encoded,$entity,$text);
4724 * decodes simple types into PHP variables
4726 * @param string $value value to decode
4727 * @param string $type XML type to decode
4728 * @param string $typens XML type namespace to decode
4731 function decodeSimple($value, $type, $typens) {
4732 // TODO: use the namespace!
4733 if ((!isset($type)) || $type == 'string' || $type == 'long' || $type == 'unsignedLong') {
4734 return (string) $value;
4736 if ($type == 'int' || $type == 'integer' || $type == 'short' || $type == 'byte') {
4737 return (int) $value;
4739 if ($type == 'float' || $type == 'double' || $type == 'decimal') {
4740 return (double) $value;
4742 if ($type == 'boolean') {
4743 if (strtolower($value) == 'false' || strtolower($value) == 'f') {
4746 return (boolean) $value;
4748 if ($type == 'base64' || $type == 'base64Binary') {
4749 return base64_decode($value);
4751 // obscure numeric types
4752 if ($type == 'nonPositiveInteger' || $type == 'negativeInteger'
4753 || $type == 'nonNegativeInteger' || $type == 'positiveInteger'
4754 || $type == 'unsignedInt'
4755 || $type == 'unsignedShort' || $type == 'unsignedByte') {
4756 return (int) $value;
4759 return (string) $value;
4763 * builds response structures for compound values (arrays/structs)
4765 * @param string $pos position in node tree
4768 function buildVal($pos){
4769 if(!isset($this->message[$pos]['type'])){
4770 $this->message[$pos]['type'] = '';
4772 $this->debug('inside buildVal() for '.$this->message[$pos]['name']."(pos $pos) of type ".$this->message[$pos]['type']);
4773 // if there are children...
4774 if($this->message[$pos]['children'] != ''){
4775 $children = explode('|',$this->message[$pos]['children']);
4776 array_shift($children); // knock off empty
4778 if(isset($this->message[$pos]['arrayCols']) && $this->message[$pos]['arrayCols'] != ''){
4781 foreach($children as $child_pos){
4782 $this->debug("got an MD array element: $r, $c");
4783 $params[$r][] = $this->message[$child_pos]['result'];
4785 if($c == $this->message[$pos]['arrayCols']){
4791 } elseif($this->message[$pos]['type'] == 'array' || $this->message[$pos]['type'] == 'Array'){
4792 $this->debug('adding array '.$this->message[$pos]['name']);
4793 foreach($children as $child_pos){
4794 $params[] = &$this->message[$child_pos]['result'];
4796 // apache Map type: java hashtable
4797 } elseif($this->message[$pos]['type'] == 'Map' && $this->message[$pos]['type_namespace'] == 'http://xml.apache.org/xml-soap'){
4798 foreach($children as $child_pos){
4799 $kv = explode("|",$this->message[$child_pos]['children']);
4800 $params[$this->message[$kv[1]]['result']] = &$this->message[$kv[2]]['result'];
4802 // generic compound type
4803 //} elseif($this->message[$pos]['type'] == 'SOAPStruct' || $this->message[$pos]['type'] == 'struct') {
4805 // Apache Vector type: treat as an array
4806 if ($this->message[$pos]['type'] == 'Vector' && $this->message[$pos]['type_namespace'] == 'http://xml.apache.org/xml-soap') {
4809 // is array or struct?
4810 foreach($children as $child_pos){
4811 if(isset($keys) && isset($keys[$this->message[$child_pos]['name']])){
4815 $keys[$this->message[$child_pos]['name']] = 1;
4819 foreach($children as $child_pos){
4820 if(isset($notstruct)){
4821 $params[] = &$this->message[$child_pos]['result'];
4823 if (isset($params[$this->message[$child_pos]['name']])) {
4824 // de-serialize repeated element name into an array
4825 if (!is_array($params[$this->message[$child_pos]['name']])) {
4826 $params[$this->message[$child_pos]['name']] = array($params[$this->message[$child_pos]['name']]);
4828 $params[$this->message[$child_pos]['name']][] = &$this->message[$child_pos]['result'];
4830 $params[$this->message[$child_pos]['name']] = &$this->message[$child_pos]['result'];
4835 return is_array($params) ? $params : array();
4837 $this->debug('no children');
4838 if(strpos($this->message[$pos]['cdata'],'&')){
4839 return strtr($this->message[$pos]['cdata'],array_flip($this->entities));
4841 return $this->message[$pos]['cdata'];
4855 * nusoapclient higher level class for easy usage.
4859 * // instantiate client with server info
4860 * $soapclient = new nusoapclient( string path [ ,boolean wsdl] );
4862 * // call method, get results
4863 * echo $soapclient->call( string methodname [ ,array parameters] );
4866 * unset($soapclient);
4868 * @author Dietrich Ayala <dietrich@ganx4.com>
4872 class nusoapclient extends nusoap_base {
4876 var $requestHeaders = false; // SOAP headers in request (text)
4877 var $responseHeaders = ''; // SOAP headers from response (incomplete namespace resolution) (text)
4878 var $document = ''; // SOAP body response portion (incomplete namespace resolution) (text)
4880 var $error_str = false;
4881 var $proxyhost = '';
4882 var $proxyport = '';
4883 var $proxyusername = '';
4884 var $proxypassword = '';
4885 var $xml_encoding = ''; // character set encoding of incoming (response) messages
4886 var $http_encoding = false;
4888 var $response_timeout = 30;
4889 var $endpointType = '';
4890 var $persistentConnection = false;
4891 var $defaultRpcParams = false;
4892 var $request = ''; // HTTP request
4893 var $response = ''; // HTTP response
4894 var $responseData = ''; // SOAP payload of response
4895 // toggles whether the parser decodes element content w/ utf8_decode()
4896 var $decode_utf8 = true;
4899 * fault related variables
4907 var $fault, $faultcode, $faultstring, $faultdetail;
4912 * @param mixed $endpoint SOAP server or WSDL URL (string), or wsdl instance (object)
4913 * @param bool $wsdl optional, set to true if using WSDL
4914 * @param int $portName optional portName in WSDL document
4915 * @param string $proxyhost
4916 * @param string $proxyport
4917 * @param string $proxyusername
4918 * @param string $proxypassword
4919 * @param integer $timeout set the connection timeout
4920 * @param integer $response_timeout set the response timeout
4923 function nusoapclient($endpoint,$wsdl = false,$proxyhost = false,$proxyport = false,$proxyusername = false, $proxypassword = false, $timeout = 0, $response_timeout = 30){
4924 $this->endpoint = $endpoint;
4925 $this->proxyhost = $proxyhost;
4926 $this->proxyport = $proxyport;
4927 $this->proxyusername = $proxyusername;
4928 $this->proxypassword = $proxypassword;
4929 $this->timeout = $timeout;
4930 $this->response_timeout = $response_timeout;
4934 $this->endpointType = 'wsdl';
4935 if (is_object($endpoint) && is_a($endpoint, 'wsdl')) {
4936 $this->wsdl = $endpoint;
4937 $this->endpoint = $this->wsdl->wsdl;
4938 $this->wsdlFile = $this->endpoint;
4939 $this->debug('existing wsdl instance created from ' . $this->endpoint);
4941 $this->wsdlFile = $this->endpoint;
4943 // instantiate wsdl object and parse wsdl file
4944 $this->debug('instantiating wsdl class with doc: '.$endpoint);
4945 $this->wsdl = new wsdl($this->wsdlFile,$this->proxyhost,$this->proxyport,$this->proxyusername,$this->proxypassword,$this->timeout,$this->response_timeout);
4947 $this->debug("wsdl debug...\n".$this->wsdl->debug_str);
4948 $this->wsdl->debug_str = '';
4950 if($errstr = $this->wsdl->getError()){
4951 $this->debug('got wsdl error: '.$errstr);
4952 $this->setError('wsdl error: '.$errstr);
4953 } elseif($this->operations = $this->wsdl->getOperations()){
4954 $this->debug( 'got '.count($this->operations).' operations from wsdl '.$this->wsdlFile);
4956 $this->debug( 'getOperations returned false');
4957 $this->setError('no operations defined in the WSDL document!');
4963 * calls method, returns PHP native type
4965 * @param string $method SOAP server URL or path
4966 * @param array $params For RPC, array of parameters, can be associative or not.
4967 * For literal, either the stringized XML for the body,
4968 * or an array of parameters like the RPC case. The
4969 * $rpcParams parameter controls this treatment, or
4970 * the $defaultRpcParams field if $rpcParams is not
4971 * specified. IMPORTANT: most services with literal
4972 * parameters have document style, in which case there
4973 * is really one parameter, the root of the fragment
4974 * used in the call, which encloses what programmers
4975 * normally think of parameters. A parameter array
4976 * *must* include the wrapper.
4977 * @param string $namespace optional method namespace (WSDL can override)
4978 * @param string $soapAction optional SOAPAction value (WSDL can override)
4979 * @param boolean $headers optional array of soapval objects for headers
4980 * @param boolean $rpcParams optional treat params as RPC for use="literal"
4981 * This can be used on a per-call basis to overrider defaultRpcParams.
4982 * @param string $style optional (rpc|document) the style to use when serializing parameters (WSDL can override)
4983 * @param string $use optional (encoded|literal) the use when serializing parameters (WSDL can override)
4987 function call($operation,$params=array(),$namespace='',$soapAction='',$headers=false,$rpcParams=null,$style='rpc',$use='encoded'){
4988 $this->operation = $operation;
4989 $this->fault = false;
4990 $this->error_str = '';
4991 $this->request = '';
4992 $this->response = '';
4993 $this->responseData = '';
4994 $this->faultstring = '';
4995 $this->faultcode = '';
4996 $this->opData = array();
4998 $this->debug("call: $operation, $params, $namespace, $soapAction, $headers, $rpcParams");
4999 $this->debug("endpointType: $this->endpointType");
5001 $this->requestHeaders = $headers;
5003 // if wsdl, get operation data and process parameters
5004 if($this->endpointType == 'wsdl' && $opData = $this->getOperationData($operation)){
5006 $this->opData = $opData;
5007 foreach($opData as $key => $value){
5008 $this->debug("$key -> $value");
5010 if (isset($opData['soapAction'])) {
5011 $soapAction = $opData['soapAction'];
5013 $this->endpoint = $opData['endpoint'];
5014 $namespace = isset($opData['input']['namespace']) ? $opData['input']['namespace'] : ($namespace != '' ? $namespace : 'http://testuri.org');
5015 $style = $opData['style'];
5016 // add ns to ns array
5017 if($namespace != '' && !isset($this->wsdl->namespaces[$namespace])){
5018 $this->wsdl->namespaces['nu'] = $namespace;
5020 // serialize payload
5022 if($opData['input']['use'] == 'literal') {
5023 if (is_null($rpcParams)) {
5024 $rpcParams = $this->defaultRpcParams;
5027 $this->debug("serializing literal params for operation $operation");
5028 $payload = $this->wsdl->serializeRPCParameters($operation,'input',$params);
5029 $defaultNamespace = $this->wsdl->wsdl_info['targetNamespace'];
5030 //$this->debug($this->varDump($params));
5032 // TODO: what? We want to treat $params as a scalar....
5033 $this->debug("serializing literal document for operation $operation");
5034 //$payload = is_array($params) ? array_shift($params) : $params;
5035 $payload = $this->wsdl->serializeParameters($operation,'input',$params);
5038 $this->debug("serializing encoded params for operation $operation");
5040 // Partial fix for multiple encoding styles in the same function call
5041 $encodingStyle = 'http://schemas.xmlsoap.org/soap/encoding/';
5042 $payload = "<".$this->wsdl->getPrefixFromNamespace($namespace).":$operation";
5043 if(isset($opData['output']['encodingStyle']) && $encodingStyle != $opData['output']['encodingStyle']) {
5044 $payload .= (' SOAP-ENV:encodingStyle="' . $opData['output']['encodingStyle'] . '"');
5046 $payload .= ('>' . $this->wsdl->serializeRPCParameters($operation,'input',$params).
5047 '</'.$this->wsdl->getPrefixFromNamespace($namespace).":$operation>");
5049 $this->debug('payload size: '.strlen($payload));
5050 // serialize envelope
5051 $soapmsg = $this->serializeEnvelope($payload,$this->requestHeaders,$this->wsdl->usedNamespaces,$style);
5052 $this->debug("wsdl debug: \n".$this->wsdl->debug_str);
5053 $this->wsdl->debug_str = '';
5054 if ($errstr = $this->wsdl->getError()) {
5055 $this->debug('got wsdl error: '.$errstr);
5056 $this->setError('wsdl error: '.$errstr);
5059 } elseif($this->endpointType == 'wsdl') {
5060 $this->setError( 'operation '.$operation.' not present.');
5061 $this->debug("operation '$operation' not present.");
5062 $this->debug("wsdl debug: \n".$this->wsdl->debug_str);
5063 $this->wsdl->debug_str = '';
5068 if($namespace == ''){
5069 $namespace = 'http://testuri.org';
5070 $this->wsdl->namespaces['ns1'] = $namespace;
5072 // serialize envelope
5075 if(is_array($params)){
5076 foreach($params as $k => $v){
5077 $payload .= $this->serialize_val($v,$k,false,false,false,false,$use);
5080 $payload = "<ns1:$operation xmlns:ns1=\"$namespace\">".$payload."</ns1:$operation>";
5081 $soapmsg = $this->serializeEnvelope($payload,$this->requestHeaders,array(),$style,$use);
5083 $this->debug("endpoint: $this->endpoint, soapAction: $soapAction, namespace: $namespace, style: $style");
5085 $this->debug('sending msg (len: '.strlen($soapmsg).") w/ soapaction '$soapAction'...");
5086 $return = $this->send($this->getHTTPBody($soapmsg),$soapAction,$this->timeout,$this->response_timeout);
5087 if($errstr = $this->getError()){
5088 $this->debug('Error: '.$errstr);
5091 $this->return = $return;
5092 $this->debug('sent message successfully and got a(n) '.gettype($return).' back');
5095 if(is_array($return) && isset($return['faultcode'])){
5096 $this->debug('got fault');
5097 $this->setError($return['faultcode'].': '.$return['faultstring']);
5098 $this->fault = true;
5099 foreach($return as $k => $v){
5101 $this->debug("$k = $v<br>");
5105 // array of return values
5106 if(is_array($return)){
5107 // multiple 'out' parameters
5108 if(sizeof($return) > 1){
5111 // single 'out' parameter
5112 return array_shift($return);
5113 // nothing returned (ie, echoVoid)
5122 * get available data pertaining to an operation
5124 * @param string $operation operation name
5125 * @return array array of data pertaining to the operation
5128 function getOperationData($operation){
5129 if(isset($this->operations[$operation])){
5130 return $this->operations[$operation];
5132 $this->debug("No data for operation: $operation");
5136 * send the SOAP message
5138 * Note: if the operation has multiple return values
5139 * the return value of this method will be an array
5142 * @param string $msg a SOAPx4 soapmsg object
5143 * @param string $soapaction SOAPAction value
5144 * @param integer $timeout set connection timeout in seconds
5145 * @param integer $response_timeout set response timeout in seconds
5146 * @return mixed native PHP types.
5149 function send($msg, $soapaction = '', $timeout=0, $response_timeout=30) {
5153 case ereg('^http',$this->endpoint):
5154 $this->debug('transporting via HTTP');
5155 if($this->persistentConnection == true && is_object($this->persistentConnection)){
5156 $http =& $this->persistentConnection;
5158 $http = new soap_transport_http($this->endpoint);
5159 if ($this->persistentConnection) {
5160 $http->usePersistentConnection();
5163 $http->setContentType($this->getHTTPContentType(), $this->getHTTPContentTypeCharset());
5164 $http->setSOAPAction($soapaction);
5165 if($this->proxyhost && $this->proxyport){
5166 $http->setProxy($this->proxyhost,$this->proxyport,$this->proxyusername,$this->proxypassword);
5168 if($this->username != '' && $this->password != '') {
5169 $http->setCredentials($this->username,$this->password);
5171 if($this->http_encoding != ''){
5172 $http->setEncoding($this->http_encoding);
5174 $this->debug('sending message, length: '.strlen($msg));
5175 if(ereg('^http:',$this->endpoint)){
5176 //if(strpos($this->endpoint,'http:')){
5177 $this->responseData = $http->send($msg,$timeout,$response_timeout);
5178 } elseif(ereg('^https',$this->endpoint)){
5179 //} elseif(strpos($this->endpoint,'https:')){
5180 //if(phpversion() == '4.3.0-dev'){
5181 //$response = $http->send($msg,$timeout,$response_timeout);
5182 //$this->request = $http->outgoing_payload;
5183 //$this->response = $http->incoming_payload;
5185 if (extension_loaded('curl')) {
5186 $this->responseData = $http->sendHTTPS($msg,$timeout,$response_timeout);
5188 $this->setError('CURL Extension, or OpenSSL extension w/ PHP version >= 4.3 is required for HTTPS');
5191 $this->setError('no http/s in endpoint url');
5193 $this->request = $http->outgoing_payload;
5194 $this->response = $http->incoming_payload;
5195 $this->debug("transport debug data...\n".$http->debug_str);
5197 // save transport object if using persistent connections
5198 if ($this->persistentConnection) {
5199 $http->debug_str = '';
5200 if (!is_object($this->persistentConnection)) {
5201 $this->persistentConnection = $http;
5205 if($err = $http->getError()){
5206 $this->setError('HTTP Error: '.$err);
5208 } elseif($this->getError()){
5211 $this->debug('got response, length: '. strlen($this->responseData).' type: '.$http->incoming_headers['content-type']);
5212 return $this->parseResponse($http->incoming_headers, $this->responseData);
5216 $this->setError('no transport found, or selected transport is not yet supported!');
5223 * processes SOAP message returned from server
5225 * @param array $headers The HTTP headers
5226 * @param string $data unprocessed response data from server
5227 * @return mixed value of the message, decoded into a PHP type
5230 function parseResponse($headers, $data) {
5231 $this->debug('Entering parseResponse() for data of length ' . strlen($data) . ' and type ' . $headers['content-type']);
5232 if (!strstr($headers['content-type'], 'text/xml')) {
5233 $this->setError('Response not of type text/xml');
5236 if (strpos($headers['content-type'], '=')) {
5237 $enc = str_replace('"', '', substr(strstr($headers["content-type"], '='), 1));
5238 $this->debug('Got response encoding: ' . $enc);
5239 if(eregi('^(ISO-8859-1|US-ASCII|UTF-8)$',$enc)){
5240 $this->xml_encoding = strtoupper($enc);
5242 $this->xml_encoding = 'US-ASCII';
5245 // should be US-ASCII, but for XML, let's be pragmatic and admit UTF-8 is most common
5246 $this->xml_encoding = 'UTF-8';
5248 $this->debug('Use encoding: ' . $this->xml_encoding . ' when creating soap_parser');
5249 $parser = new soap_parser($data,$this->xml_encoding,$this->operation,$this->decode_utf8);
5250 // add parser debug data to our debug
5251 $this->debug($parser->debug_str);
5253 if($errstr = $parser->getError()){
5254 $this->setError( $errstr);
5255 // destroy the parser object
5260 $this->responseHeaders = $parser->getHeaders();
5261 // get decoded message
5262 $return = $parser->get_response();
5263 // add document for doclit support
5264 $this->document = $parser->document;
5265 // destroy the parser object
5267 // return decode message
5273 * set the SOAP headers
5275 * @param $headers string XML
5278 function setHeaders($headers){
5279 $this->requestHeaders = $headers;
5283 * get the response headers
5285 * @return mixed object SOAPx4 soapval object or empty if no headers
5288 function getHeaders(){
5289 if($this->responseHeaders != '') {
5290 return $this->responseHeaders;
5295 * set proxy info here
5297 * @param string $proxyhost
5298 * @param string $proxyport
5299 * @param string $proxyusername
5300 * @param string $proxypassword
5303 function setHTTPProxy($proxyhost, $proxyport, $proxyusername = '', $proxypassword = '') {
5304 $this->proxyhost = $proxyhost;
5305 $this->proxyport = $proxyport;
5306 $this->proxyusername = $proxyusername;
5307 $this->proxypassword = $proxypassword;
5311 * if authenticating, set user credentials here
5313 * @param string $username
5314 * @param string $password
5317 function setCredentials($username, $password) {
5318 $this->username = $username;
5319 $this->password = $password;
5325 * @param string $enc
5328 function setHTTPEncoding($enc='gzip, deflate'){
5329 $this->http_encoding = $enc;
5333 * use HTTP persistent connections if possible
5337 function useHTTPPersistentConnection(){
5338 $this->persistentConnection = true;
5342 * gets the default RPC parameter setting.
5343 * If true, default is that call params are like RPC even for document style.
5344 * Each call() can override this value.
5348 function getDefaultRpcParams() {
5349 return $this->defaultRpcParams;
5353 * sets the default RPC parameter setting.
5354 * If true, default is that call params are like RPC even for document style
5355 * Each call() can override this value.
5357 * @param boolean $rpcParams
5360 function setDefaultRpcParams($rpcParams) {
5361 $this->defaultRpcParams = $rpcParams;
5365 * dynamically creates proxy class, allowing user to directly call methods from wsdl
5367 * @return object soap_proxy object
5370 function getProxy(){
5372 foreach($this->operations as $operation => $opData){
5373 if($operation != ''){
5374 // create param string
5376 if(sizeof($opData['input']['parts']) > 0){
5377 foreach($opData['input']['parts'] as $name => $type){
5378 $paramStr .= "\$$name,";
5380 $paramStr = substr($paramStr,0,strlen($paramStr)-1);
5382 $opData['namespace'] = !isset($opData['namespace']) ? 'http://testuri.com' : $opData['namespace'];
5383 $evalStr .= "function $operation ($paramStr){
5384 // load params into array
5385 \$params = array($paramStr);
5386 return \$this->call('$operation',\$params,'".$opData['namespace']."','".(isset($opData['soapAction']) ? $opData['soapAction'] : '')."');
5392 $evalStr = 'class soap_proxy_'.$r.' extends nusoapclient {
5395 //print "proxy class:<pre>$evalStr</pre>";
5398 // instantiate proxy object
5399 eval("\$proxy = new soap_proxy_$r('');");
5400 // transfer current wsdl data to the proxy thereby avoiding parsing the wsdl twice
5401 $proxy->endpointType = 'wsdl';
5402 $proxy->wsdlFile = $this->wsdlFile;
5403 $proxy->wsdl = $this->wsdl;
5404 $proxy->operations = $this->operations;
5405 $proxy->defaultRpcParams = $this->defaultRpcParams;
5406 // transfer other state
5407 $proxy->username = $this->username;
5408 $proxy->password = $this->password;
5409 $proxy->proxyhost = $this->proxyhost;
5410 $proxy->proxyport = $this->proxyport;
5411 $proxy->proxyusername = $this->proxyusername;
5412 $proxy->proxypassword = $this->proxypassword;
5413 $proxy->timeout = $this->timeout;
5414 $proxy->response_timeout = $this->response_timeout;
5415 $proxy->http_encoding = $this->http_encoding;
5416 $proxy->persistentConnection = $this->persistentConnection;
5421 * gets the HTTP body for the current request.
5423 * @param string $soapmsg The SOAP payload
5424 * @return string The HTTP body, which includes the SOAP payload
5427 function getHTTPBody($soapmsg) {
5432 * gets the HTTP content type for the current request.
5434 * Note: getHTTPBody must be called before this.
5436 * @return string the HTTP content type for the current request.
5439 function getHTTPContentType() {
5444 * gets the HTTP content type charset for the current request.
5445 * returns false for non-text content types.
5447 * Note: getHTTPBody must be called before this.
5449 * @return string the HTTP content type charset for the current request.
5452 function getHTTPContentTypeCharset() {
5453 return $this->soap_defencoding;
5457 * whether or not parser should decode utf8 element content
5459 * @return always returns true
5462 function decodeUTF8($bool){
5463 $this->decode_utf8 = $bool;