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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
24 The NuSOAP project home is:
25 http://sourceforge.net/projects/nusoap/
27 The primary support for NuSOAP is the Help forum on the project home page.
29 If you have any questions or comments, please email:
33 http://dietrich.ganx4.com/nusoap
36 http://www.nusphere.com
41 * Some of the standards implmented in whole or part by NuSOAP:
43 * SOAP 1.1 (http://www.w3.org/TR/2000/NOTE-SOAP-20000508/)
44 * WSDL 1.1 (http://www.w3.org/TR/2001/NOTE-wsdl-20010315)
45 * SOAP Messages With Attachments (http://www.w3.org/TR/SOAP-attachments)
46 * XML 1.0 (http://www.w3.org/TR/2006/REC-xml-20060816/)
47 * Namespaces in XML 1.0 (http://www.w3.org/TR/2006/REC-xml-names-20060816/)
48 * XML Schema 1.0 (http://www.w3.org/TR/xmlschema-0/)
49 * RFC 2045 Multipurpose Internet Mail Extensions (MIME) Part One: Format of Internet Message Bodies
50 * RFC 2068 Hypertext Transfer Protocol -- HTTP/1.1
51 * RFC 2617 HTTP Authentication: Basic and Digest Access Authentication
57 require_once 'class.soapclient.php';
58 require_once 'class.soap_val.php';
59 require_once 'class.soap_parser.php';
60 require_once 'class.soap_fault.php';
63 require_once 'class.soap_transport_http.php';
65 // optional add-on classes
66 require_once 'class.xmlschema.php';
67 require_once 'class.wsdl.php';
70 require_once 'class.soap_server.php';*/
72 // class variable emulation
73 // cf. http://www.webkreator.com/php/techniques/php-static-class-variables.html
74 $GLOBALS['_transient']['static']['nusoap_base']['globalDebugLevel'] = 9;
80 * @author Dietrich Ayala <dietrich@ganx4.com>
81 * @author Scott Nichol <snichol@users.sourceforge.net>
87 * Identification for HTTP headers.
92 var $title = 'NuSOAP';
94 * Version for HTTP headers.
99 var $version = '0.9.5';
101 * CVS revision for HTTP headers.
106 var $revision = '$Revision$';
108 * Current error string (manipulated by getError/setError)
115 * Current debug string (manipulated by debug/appendDebug/clearDebug/getDebug/getDebugAsXMLComment)
122 * toggles automatic encoding of special characters as entities
123 * (should always be true, I think)
128 var $charencoding = true;
130 * the debug level for this instance
143 var $XMLSchemaVersion = 'http://www.w3.org/2001/XMLSchema';
146 * charset encoding for outgoing messages
151 var $soap_defencoding = 'ISO-8859-1';
152 //var $soap_defencoding = 'UTF-8';
155 * namespaces in an array of prefix => uri
157 * this is "seeded" by a set of constants, but it may be altered by code
162 var $namespaces = array(
163 'SOAP-ENV' => 'http://schemas.xmlsoap.org/soap/envelope/',
164 'xsd' => 'http://www.w3.org/2001/XMLSchema',
165 'xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
166 'SOAP-ENC' => 'http://schemas.xmlsoap.org/soap/encoding/'
170 * namespaces used in the current context, e.g. during serialization
175 var $usedNamespaces = array();
178 * XML Schema types in an array of uri => (array of xml type => php type)
179 * is this legacy yet?
180 * no, this is used by the nusoap_xmlschema class to verify type => namespace mappings.
184 var $typemap = array(
185 'http://www.w3.org/2001/XMLSchema' => array(
186 'string'=>'string','boolean'=>'boolean','float'=>'double','double'=>'double','decimal'=>'double',
187 'duration'=>'','dateTime'=>'string','time'=>'string','date'=>'string','gYearMonth'=>'',
188 'gYear'=>'','gMonthDay'=>'','gDay'=>'','gMonth'=>'','hexBinary'=>'string','base64Binary'=>'string',
189 // abstract "any" types
190 'anyType'=>'string','anySimpleType'=>'string',
192 'normalizedString'=>'string','token'=>'string','language'=>'','NMTOKEN'=>'','NMTOKENS'=>'','Name'=>'','NCName'=>'','ID'=>'',
193 'IDREF'=>'','IDREFS'=>'','ENTITY'=>'','ENTITIES'=>'','integer'=>'integer','nonPositiveInteger'=>'integer',
194 'negativeInteger'=>'integer','long'=>'integer','int'=>'integer','short'=>'integer','byte'=>'integer','nonNegativeInteger'=>'integer',
195 'unsignedLong'=>'','unsignedInt'=>'','unsignedShort'=>'','unsignedByte'=>'','positiveInteger'=>''),
196 'http://www.w3.org/2000/10/XMLSchema' => array(
197 'i4'=>'','int'=>'integer','boolean'=>'boolean','string'=>'string','double'=>'double',
198 'float'=>'double','dateTime'=>'string',
199 'timeInstant'=>'string','base64Binary'=>'string','base64'=>'string','ur-type'=>'array'),
200 'http://www.w3.org/1999/XMLSchema' => array(
201 'i4'=>'','int'=>'integer','boolean'=>'boolean','string'=>'string','double'=>'double',
202 'float'=>'double','dateTime'=>'string',
203 'timeInstant'=>'string','base64Binary'=>'string','base64'=>'string','ur-type'=>'array'),
204 'http://soapinterop.org/xsd' => array('SOAPStruct'=>'struct'),
205 'http://schemas.xmlsoap.org/soap/encoding/' => array('base64'=>'string','array'=>'array','Array'=>'array'),
206 'http://xml.apache.org/xml-soap' => array('Map')
210 * XML entities to convert
215 * @see expandEntities
217 var $xmlEntities = array('quot' => '"','amp' => '&',
218 'lt' => '<','gt' => '>','apos' => "'");
225 function nusoap_base() {
226 $this->debugLevel = $GLOBALS['_transient']['static']['nusoap_base']['globalDebugLevel'];
230 * gets the global debug level, which applies to future instances
232 * @return integer Debug level 0-9, where 0 turns off
235 function getGlobalDebugLevel() {
236 return $GLOBALS['_transient']['static']['nusoap_base']['globalDebugLevel'];
240 * sets the global debug level, which applies to future instances
242 * @param int $level Debug level 0-9, where 0 turns off
245 function setGlobalDebugLevel($level) {
246 $GLOBALS['_transient']['static']['nusoap_base']['globalDebugLevel'] = $level;
250 * gets the debug level for this instance
252 * @return int Debug level 0-9, where 0 turns off
255 function getDebugLevel() {
256 return $this->debugLevel;
260 * sets the debug level for this instance
262 * @param int $level Debug level 0-9, where 0 turns off
265 function setDebugLevel($level) {
266 $this->debugLevel = $level;
270 * adds debug data to the instance debug string with formatting
272 * @param string $string debug data
275 function debug($string){
276 if ($this->debugLevel > 0) {
277 $this->appendDebug($this->getmicrotime().' '.get_class($this).": $string\n");
282 * adds debug data to the instance debug string without formatting
284 * @param string $string debug data
287 function appendDebug($string){
288 if ($this->debugLevel > 0) {
289 // it would be nice to use a memory stream here to use
290 // memory more efficiently
291 $this->debug_str .= $string;
296 * clears the current debug data for this instance
300 function clearDebug() {
301 // it would be nice to use a memory stream here to use
302 // memory more efficiently
303 $this->debug_str = '';
307 * gets the current debug data for this instance
312 function &getDebug() {
313 // it would be nice to use a memory stream here to use
314 // memory more efficiently
315 return $this->debug_str;
319 * gets the current debug data for this instance as an XML comment
320 * this may change the contents of the debug data
322 * @return debug data as an XML comment
325 function &getDebugAsXMLComment() {
326 // it would be nice to use a memory stream here to use
327 // memory more efficiently
328 while (strpos($this->debug_str, '--')) {
329 $this->debug_str = str_replace('--', '- -', $this->debug_str);
331 $ret = "<!--\n" . $this->debug_str . "\n-->";
336 * expands entities, e.g. changes '<' to '<'.
338 * @param string $val The string in which to expand entities.
341 function expandEntities($val) {
342 if ($this->charencoding) {
343 $val = str_replace('&', '&', $val);
344 $val = str_replace("'", ''', $val);
345 $val = str_replace('"', '"', $val);
346 $val = str_replace('<', '<', $val);
347 $val = str_replace('>', '>', $val);
353 * returns error string if present
355 * @return mixed error string or false
359 if($this->error_str != ''){
360 return $this->error_str;
368 * @return boolean $string error string
371 function setError($str){
372 $this->error_str = $str;
376 * detect if array is a simple array or a struct (associative array)
378 * @param mixed $val The PHP array
379 * @return string (arraySimple|arrayStruct)
382 function isArraySimpleOrStruct($val) {
383 $keyList = array_keys($val);
384 foreach ($keyList as $keyListValue) {
385 if (!is_int($keyListValue)) {
386 return 'arrayStruct';
389 return 'arraySimple';
393 * serializes PHP values in accordance w/ section 5. Type information is
394 * not serialized if $use == 'literal'.
396 * @param mixed $val The value to serialize
397 * @param string $name The name (local part) of the XML element
398 * @param string $type The XML schema type (local part) for the element
399 * @param string $name_ns The namespace for the name of the XML element
400 * @param string $type_ns The namespace for the type of the element
401 * @param array $attributes The attributes to serialize as name=>value pairs
402 * @param string $use The WSDL "use" (encoded|literal)
403 * @param boolean $soapval Whether this is called from soapval.
404 * @return string The serialized element, possibly with child elements
407 function serialize_val($val,$name=false,$type=false,$name_ns=false,$type_ns=false,$attributes=false,$use='encoded',$soapval=false) {
408 $this->debug("in serialize_val: name=$name, type=$type, name_ns=$name_ns, type_ns=$type_ns, use=$use, soapval=$soapval");
409 $this->appendDebug('value=' . $this->varDump($val));
410 $this->appendDebug('attributes=' . $this->varDump($attributes));
412 if (is_object($val) && get_class($val) == 'soapval' && (! $soapval)) {
413 $this->debug("serialize_val: serialize soapval");
414 $xml = $val->serialize($use);
415 $this->appendDebug($val->getDebug());
417 $this->debug("serialize_val of soapval returning $xml");
420 // force valid name if necessary
421 if (is_numeric($name)) {
422 $name = '__numeric_' . $name;
426 // if name has ns, add ns prefix to name
429 $prefix = 'nu'.util_randnum(1000,9999);
430 $name = $prefix.':'.$name;
431 $xmlns .= " xmlns:$prefix=\"$name_ns\"";
433 // if type is prefixed, create type prefix
434 if($type_ns != '' && $type_ns == $this->namespaces['xsd']){
435 // need to fix this. shouldn't default to xsd if no ns specified
436 // w/o checking against typemap
437 $type_prefix = 'xsd';
439 $type_prefix = 'ns'.util_randnum(1000,9999);
440 $xmlns .= " xmlns:$type_prefix=\"$type_ns\"";
442 // serialize attributes if present
445 foreach($attributes as $k => $v){
446 $atts .= " $k=\"".$this->expandEntities($v).'"';
449 // serialize null value
451 $this->debug("serialize_val: serialize null");
452 if ($use == 'literal') {
453 // TODO: depends on minOccurs
454 $xml = "<$name$xmlns$atts/>";
455 $this->debug("serialize_val returning $xml");
458 if (isset($type) && isset($type_prefix)) {
459 $type_str = " xsi:type=\"$type_prefix:$type\"";
463 $xml = "<$name$xmlns$type_str$atts xsi:nil=\"true\"/>";
464 $this->debug("serialize_val returning $xml");
468 // serialize if an xsd built-in primitive type
469 if($type != '' && isset($this->typemap[$this->XMLSchemaVersion][$type])){
470 $this->debug("serialize_val: serialize xsd built-in primitive type");
472 if ($type == 'boolean') {
473 $val = $val ? 'true' : 'false';
477 } elseif (is_string($val)) {
478 $val = $this->expandEntities($val);
480 if ($use == 'literal') {
481 $xml = "<$name$xmlns$atts>$val</$name>";
482 $this->debug("serialize_val returning $xml");
485 $xml = "<$name$xmlns xsi:type=\"xsd:$type\"$atts>$val</$name>";
486 $this->debug("serialize_val returning $xml");
490 // detect type and serialize
493 case (is_bool($val) || $type == 'boolean'):
494 $this->debug("serialize_val: serialize boolean");
495 if ($type == 'boolean') {
496 $val = $val ? 'true' : 'false';
500 if ($use == 'literal') {
501 $xml .= "<$name$xmlns$atts>$val</$name>";
503 $xml .= "<$name$xmlns xsi:type=\"xsd:boolean\"$atts>$val</$name>";
506 case (is_int($val) || is_long($val) || $type == 'int'):
507 $this->debug("serialize_val: serialize int");
508 if ($use == 'literal') {
509 $xml .= "<$name$xmlns$atts>$val</$name>";
511 $xml .= "<$name$xmlns xsi:type=\"xsd:int\"$atts>$val</$name>";
514 case (is_float($val)|| is_double($val) || $type == 'float'):
515 $this->debug("serialize_val: serialize float");
516 if ($use == 'literal') {
517 $xml .= "<$name$xmlns$atts>$val</$name>";
519 $xml .= "<$name$xmlns xsi:type=\"xsd:float\"$atts>$val</$name>";
522 case (is_string($val) || $type == 'string'):
523 $this->debug("serialize_val: serialize string");
524 $val = $this->expandEntities($val);
525 if ($use == 'literal') {
526 $xml .= "<$name$xmlns$atts>$val</$name>";
528 $xml .= "<$name$xmlns xsi:type=\"xsd:string\"$atts>$val</$name>";
531 case is_object($val):
532 $this->debug("serialize_val: serialize object");
533 if (get_class($val) == 'soapval') {
534 $this->debug("serialize_val: serialize soapval object");
535 $pXml = $val->serialize($use);
536 $this->appendDebug($val->getDebug());
540 $name = get_class($val);
541 $this->debug("In serialize_val, used class name $name as element name");
543 $this->debug("In serialize_val, do not override name $name for element name for class " . get_class($val));
545 foreach(get_object_vars($val) as $k => $v){
546 $pXml = isset($pXml) ? $pXml.$this->serialize_val($v,$k,false,false,false,false,$use) : $this->serialize_val($v,$k,false,false,false,false,$use);
549 if(isset($type) && isset($type_prefix)){
550 $type_str = " xsi:type=\"$type_prefix:$type\"";
554 if ($use == 'literal') {
555 $xml .= "<$name$xmlns$atts>$pXml</$name>";
557 $xml .= "<$name$xmlns$type_str$atts>$pXml</$name>";
561 case (is_array($val) || $type):
562 // detect if struct or array
563 $valueType = $this->isArraySimpleOrStruct($val);
564 if($valueType=='arraySimple' || preg_match('/^ArrayOf/',$type)){
565 $this->debug("serialize_val: serialize array");
567 if(is_array($val) && count($val)> 0){
569 if(is_object($v) && get_class($v) == 'soapval'){
570 $tt_ns = $v->type_ns;
572 } elseif (is_array($v)) {
573 $tt = $this->isArraySimpleOrStruct($v);
577 $array_types[$tt] = 1;
578 // TODO: for literal, the name should be $name
579 $xml .= $this->serialize_val($v,'item',false,false,false,false,$use);
582 if(count($array_types) > 1){
583 $array_typename = 'xsd:anyType';
584 } elseif(isset($tt) && isset($this->typemap[$this->XMLSchemaVersion][$tt])) {
585 if ($tt == 'integer') {
588 $array_typename = 'xsd:'.$tt;
589 } elseif(isset($tt) && $tt == 'arraySimple'){
590 $array_typename = 'SOAP-ENC:Array';
591 } elseif(isset($tt) && $tt == 'arrayStruct'){
592 $array_typename = 'unnamed_struct_use_soapval';
594 // if type is prefixed, create type prefix
595 if ($tt_ns != '' && $tt_ns == $this->namespaces['xsd']){
596 $array_typename = 'xsd:' . $tt;
598 $tt_prefix = 'ns' . util_randnum(1000, 9999);
599 $array_typename = "$tt_prefix:$tt";
600 $xmlns .= " xmlns:$tt_prefix=\"$tt_ns\"";
602 $array_typename = $tt;
606 if ($use == 'literal') {
608 } elseif (isset($type) && isset($type_prefix)) {
609 $type_str = " xsi:type=\"$type_prefix:$type\"";
611 $type_str = " xsi:type=\"SOAP-ENC:Array\" SOAP-ENC:arrayType=\"".$array_typename."[$array_type]\"";
615 if ($use == 'literal') {
617 } elseif (isset($type) && isset($type_prefix)) {
618 $type_str = " xsi:type=\"$type_prefix:$type\"";
620 $type_str = " xsi:type=\"SOAP-ENC:Array\" SOAP-ENC:arrayType=\"xsd:anyType[0]\"";
623 // TODO: for array in literal, there is no wrapper here
624 $xml = "<$name$xmlns$type_str$atts>".$xml."</$name>";
627 $this->debug("serialize_val: serialize struct");
628 if(isset($type) && isset($type_prefix)){
629 $type_str = " xsi:type=\"$type_prefix:$type\"";
633 if ($use == 'literal') {
634 $xml .= "<$name$xmlns$atts>";
636 $xml .= "<$name$xmlns$type_str$atts>";
638 foreach($val as $k => $v){
640 if ($type == 'Map' && $type_ns == 'http://xml.apache.org/xml-soap') {
642 $xml .= $this->serialize_val($k,'key',false,false,false,false,$use);
643 $xml .= $this->serialize_val($v,'value',false,false,false,false,$use);
646 $xml .= $this->serialize_val($v,$k,false,false,false,false,$use);
653 $this->debug("serialize_val: serialize unknown");
654 $xml .= 'not detected, got '.gettype($val).' for '.$val;
657 $this->debug("serialize_val returning $xml");
662 * serializes a message
664 * @param string $body the XML of the SOAP body
665 * @param mixed $headers optional string of XML with SOAP header content, or array of soapval objects for SOAP headers, or associative array
666 * @param array $namespaces optional the namespaces used in generating the body and headers
667 * @param string $style optional (rpc|document)
668 * @param string $use optional (encoded|literal)
669 * @param string $encodingStyle optional (usually 'http://schemas.xmlsoap.org/soap/encoding/' for encoded)
670 * @return string the message
673 function serializeEnvelope($body,$headers=false,$namespaces=array(),$style='rpc',$use='encoded',$encodingStyle='http://schemas.xmlsoap.org/soap/encoding/'){
674 // TODO: add an option to automatically run utf8_encode on $body and $headers
675 // if $this->soap_defencoding is UTF-8. Not doing this automatically allows
676 // one to send arbitrary UTF-8 characters, not just characters that map to ISO-8859-1
678 $this->debug("In serializeEnvelope length=" . strlen($body) . " body (max 1000 characters)=" . substr($body, 0, 1000) . " style=$style use=$use encodingStyle=$encodingStyle");
679 $this->debug("headers:");
680 $this->appendDebug($this->varDump($headers));
681 $this->debug("namespaces:");
682 $this->appendDebug($this->varDump($namespaces));
684 // serialize namespaces
686 foreach(array_merge($this->namespaces,$namespaces) as $k => $v){
687 $ns_string .= " xmlns:$k=\"$v\"";
690 $ns_string = " SOAP-ENV:encodingStyle=\"$encodingStyle\"$ns_string";
695 if (is_array($headers)) {
697 foreach ($headers as $k => $v) {
698 if (is_object($v) && get_class($v) == 'soapval') {
699 $xml .= $this->serialize_val($v, false, false, false, false, false, $use);
701 $xml .= $this->serialize_val($v, $k, false, false, false, false, $use);
705 $this->debug("In serializeEnvelope, serialized array of headers to $headers");
707 $headers = "<SOAP-ENV:Header>".$headers."</SOAP-ENV:Header>";
709 // serialize envelope
711 '<?xml version="1.0" encoding="'.$this->soap_defencoding .'"?'.">".
712 '<SOAP-ENV:Envelope'.$ns_string.">".
717 "</SOAP-ENV:Envelope>";
721 * formats a string to be inserted into an HTML stream
723 * @param string $str The string to format
724 * @return string The formatted string
728 function formatDump($str){
729 $str = htmlspecialchars($str);
734 * contracts (changes namespace to prefix) a qualified name
736 * @param string $qname qname
737 * @return string contracted qname
740 function contractQname($qname){
741 // get element namespace
742 //$this->xdebug("Contract $qname");
743 if (strrpos($qname, ':')) {
744 // get unqualified name
745 $name = substr($qname, strrpos($qname, ':') + 1);
747 $ns = substr($qname, 0, strrpos($qname, ':'));
748 $p = $this->getPrefixFromNamespace($ns);
750 return $p . ':' . $name;
759 * expands (changes prefix to namespace) a qualified name
761 * @param string $qname qname
762 * @return string expanded qname
765 function expandQname($qname){
766 // get element prefix
767 if(strpos($qname,':') && !preg_match('/^http:\/\//',$qname)){
768 // get unqualified name
769 $name = substr(strstr($qname,':'),1);
771 $prefix = substr($qname,0,strpos($qname,':'));
772 if(isset($this->namespaces[$prefix])){
773 return $this->namespaces[$prefix].':'.$name;
783 * returns the local part of a prefixed string
784 * returns the original string, if not prefixed
786 * @param string $str The prefixed string
787 * @return string The local part
790 function getLocalPart($str){
791 if($sstr = strrchr($str,':')){
792 // get unqualified name
793 return substr( $sstr, 1 );
800 * returns the prefix part of a prefixed string
801 * returns false, if not prefixed
803 * @param string $str The prefixed string
804 * @return mixed The prefix or false if there is no prefix
807 function getPrefix($str){
808 if($pos = strrpos($str,':')){
810 return substr($str,0,$pos);
816 * pass it a prefix, it returns a namespace
818 * @param string $prefix The prefix
819 * @return mixed The namespace, false if no namespace has the specified prefix
822 function getNamespaceFromPrefix($prefix){
823 if (isset($this->namespaces[$prefix])) {
824 return $this->namespaces[$prefix];
826 //$this->setError("No namespace registered for prefix '$prefix'");
831 * returns the prefix for a given namespace (or prefix)
832 * or false if no prefixes registered for the given namespace
834 * @param string $ns The namespace
835 * @return mixed The prefix, false if the namespace has no prefixes
838 function getPrefixFromNamespace($ns) {
839 foreach ($this->namespaces as $p => $n) {
840 if ($ns == $n || $ns == $p) {
841 $this->usedNamespaces[$p] = $n;
849 * returns the time in ODBC canonical form with microseconds
851 * @return string The time in ODBC canonical form with microseconds
854 function getmicrotime() {
855 if (function_exists('gettimeofday')) {
856 $tod = gettimeofday();
858 $usec = $tod['usec'];
863 return strftime('%Y-%m-%d %H:%M:%S', $sec) . '.' . sprintf('%06d', $usec);
867 * Returns a string with the output of var_dump
869 * @param mixed $data The variable to var_dump
870 * @return string The output of var_dump
873 function varDump($data) {
876 $ret_val = ob_get_contents();
882 * represents the object as a string
887 function __toString() {
888 return $this->varDump($this);
892 // XML Schema Datatype Helper Functions
894 //xsd:dateTime helpers
897 * convert unix timestamp to ISO 8601 compliant date string
899 * @param int $timestamp Unix time stamp
900 * @param boolean $utc Whether the time stamp is UTC or local
901 * @return mixed ISO 8601 date string or false
904 function timestamp_to_iso8601($timestamp,$utc=true){
905 $datestr = date('Y-m-d\TH:i:sO',$timestamp);
906 $pos = strrpos($datestr, "+");
907 if ($pos === FALSE) {
908 $pos = strrpos($datestr, "-");
910 if ($pos !== FALSE) {
911 if (strlen($datestr) == $pos + 5) {
912 $datestr = substr($datestr, 0, $pos + 3) . ':' . substr($datestr, -2);
917 '([0-9]{4})-'. // centuries & years CCYY-
918 '([0-9]{2})-'. // months MM-
919 '([0-9]{2})'. // days DD
921 '([0-9]{2}):'. // hours hh:
922 '([0-9]{2}):'. // minutes mm:
923 '([0-9]{2})(\.[0-9]*)?'. // seconds ss.ss...
924 '(Z|[+\-][0-9]{2}:?[0-9]{2})?'. // Z to indicate UTC, -/+HH:MM:SS.SS... for local tz's
927 if(preg_match($pattern,$datestr,$regs)){
928 return sprintf('%04d-%02d-%02dT%02d:%02d:%02dZ',$regs[1],$regs[2],$regs[3],$regs[4],$regs[5],$regs[6]);
937 * convert ISO 8601 compliant date string to unix timestamp
939 * @param string $datestr ISO 8601 compliant date string
940 * @return mixed Unix timestamp (int) or false
943 function iso8601_to_timestamp($datestr){
945 '([0-9]{4})-'. // centuries & years CCYY-
946 '([0-9]{2})-'. // months MM-
947 '([0-9]{2})'. // days DD
949 '([0-9]{2}):'. // hours hh:
950 '([0-9]{2}):'. // minutes mm:
951 '([0-9]{2})(\.[0-9]+)?'. // seconds ss.ss...
952 '(Z|[+\-][0-9]{2}:?[0-9]{2})?'. // Z to indicate UTC, -/+HH:MM:SS.SS... for local tz's
954 if(preg_match($pattern,$datestr,$regs)){
957 $op = substr($regs[8],0,1);
958 $h = substr($regs[8],1,2);
959 $m = substr($regs[8],strlen($regs[8])-2,2);
961 $regs[4] = $regs[4] + $h;
962 $regs[5] = $regs[5] + $m;
963 } elseif($op == '+'){
964 $regs[4] = $regs[4] - $h;
965 $regs[5] = $regs[5] - $m;
968 return gmmktime($regs[4], $regs[5], $regs[6], $regs[2], $regs[3], $regs[1]);
969 // return strtotime("$regs[1]-$regs[2]-$regs[3] $regs[4]:$regs[5]:$regs[6]Z");
976 * sleeps some number of microseconds
978 * @param string $usec the number of microseconds to sleep
982 function usleepWindows($usec)
984 $start = gettimeofday();
988 $stop = gettimeofday();
989 $timePassed = 1000000 * ($stop['sec'] - $start['sec'])
990 + $stop['usec'] - $start['usec'];
992 while ($timePassed < $usec);
998 * Contains information for a SOAP fault.
999 * Mainly used for returning faults from deployed functions
1000 * in a server instance.
1001 * @author Dietrich Ayala <dietrich@ganx4.com>
1005 class nusoap_fault extends nusoap_base {
1007 * The fault code (client|server)
1019 * The fault string, a description of the fault
1025 * The fault detail, typically a string or array of string
1034 * @param string $faultcode (SOAP-ENV:Client | SOAP-ENV:Server)
1035 * @param string $faultactor only used when msg routed between multiple actors
1036 * @param string $faultstring human readable error message
1037 * @param mixed $faultdetail detail, typically a string or array of string
1039 function nusoap_fault($faultcode,$faultactor='',$faultstring='',$faultdetail=''){
1040 parent::nusoap_base();
1041 $this->faultcode = $faultcode;
1042 $this->faultactor = $faultactor;
1043 $this->faultstring = $faultstring;
1044 $this->faultdetail = $faultdetail;
1050 * @return string The serialization of the fault instance.
1053 function serialize(){
1055 foreach($this->namespaces as $k => $v){
1056 $ns_string .= "\n xmlns:$k=\"$v\"";
1059 '<?xml version="1.0" encoding="'.$this->soap_defencoding.'"?>'.
1060 '<SOAP-ENV:Envelope SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"'.$ns_string.">\n".
1063 $this->serialize_val($this->faultcode, 'faultcode').
1064 $this->serialize_val($this->faultactor, 'faultactor').
1065 $this->serialize_val($this->faultstring, 'faultstring').
1066 $this->serialize_val($this->faultdetail, 'detail').
1067 '</SOAP-ENV:Fault>'.
1069 '</SOAP-ENV:Envelope>';
1075 * Backward compatibility
1077 class soap_fault extends nusoap_fault {
1083 * parses an XML Schema, allows access to it's data, other utility methods.
1084 * imperfect, no validation... yet, but quite functional.
1086 * @author Dietrich Ayala <dietrich@ganx4.com>
1087 * @author Scott Nichol <snichol@users.sourceforge.net>
1091 class nusoap_xmlschema extends nusoap_base {
1097 var $enclosingNamespaces;
1099 var $schemaInfo = array();
1100 var $schemaTargetNamespace = '';
1101 // types, elements, attributes defined by the schema
1102 var $attributes = array();
1103 var $complexTypes = array();
1104 var $complexTypeStack = array();
1105 var $currentComplexType = null;
1106 var $elements = array();
1107 var $elementStack = array();
1108 var $currentElement = null;
1109 var $simpleTypes = array();
1110 var $simpleTypeStack = array();
1111 var $currentSimpleType = null;
1113 var $imports = array();
1118 var $depth_array = array();
1119 var $message = array();
1120 var $defaultNamespace = array();
1125 * @param string $schema schema document URI
1126 * @param string $xml xml document URI
1127 * @param string $namespaces namespaces defined in enclosing XML
1130 function nusoap_xmlschema($schema='',$xml='',$namespaces=array()){
1131 parent::nusoap_base();
1132 $this->debug('nusoap_xmlschema class instantiated, inside constructor');
1134 $this->schema = $schema;
1138 $this->enclosingNamespaces = $namespaces;
1139 $this->namespaces = array_merge($this->namespaces, $namespaces);
1141 // parse schema file
1143 $this->debug('initial schema file: '.$schema);
1144 $this->parseFile($schema, 'schema');
1149 $this->debug('initial xml file: '.$xml);
1150 $this->parseFile($xml, 'xml');
1158 * @param string $xml path/URL to XML file
1159 * @param string $type (schema | xml)
1163 function parseFile($xml,$type){
1166 $xmlStr = @join("",@file($xml));
1168 $msg = 'Error reading XML from '.$xml;
1169 $this->setError($msg);
1173 $this->debug("parsing $xml");
1174 $this->parseString($xmlStr,$type);
1175 $this->debug("done parsing $xml");
1183 * parse an XML string
1185 * @param string $xml path or URL
1186 * @param string $type (schema|xml)
1189 function parseString($xml,$type){
1193 // Create an XML parser.
1194 $this->parser = xml_parser_create();
1195 // Set the options for parsing the XML data.
1196 xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING, 0);
1198 // Set the object for the parser.
1199 xml_set_object($this->parser, $this);
1201 // Set the element handlers for the parser.
1202 if($type == "schema"){
1203 xml_set_element_handler($this->parser, 'schemaStartElement','schemaEndElement');
1204 xml_set_character_data_handler($this->parser,'schemaCharacterData');
1205 } elseif($type == "xml"){
1206 xml_set_element_handler($this->parser, 'xmlStartElement','xmlEndElement');
1207 xml_set_character_data_handler($this->parser,'xmlCharacterData');
1210 // Parse the XML file.
1211 if(!xml_parse($this->parser,$xml,true)){
1212 // Display an error message.
1213 $errstr = sprintf('XML error parsing XML schema on line %d: %s',
1214 xml_get_current_line_number($this->parser),
1215 xml_error_string(xml_get_error_code($this->parser))
1217 $this->debug($errstr);
1218 $this->debug("XML payload:\n" . $xml);
1219 $this->setError($errstr);
1222 xml_parser_free($this->parser);
1224 $this->debug('no xml passed to parseString()!!');
1225 $this->setError('no xml passed to parseString()!!');
1230 * gets a type name for an unnamed type
1232 * @param string Element name
1233 * @return string A type name for an unnamed type
1236 function CreateTypeName($ename) {
1238 for ($i = 0; $i < count($this->complexTypeStack); $i++) {
1239 $scope .= $this->complexTypeStack[$i] . '_';
1241 return $scope . $ename . '_ContainedType';
1245 * start-element handler
1247 * @param string $parser XML parser object
1248 * @param string $name element name
1249 * @param string $attrs associative array of attributes
1252 function schemaStartElement($parser, $name, $attrs) {
1254 // position in the total number of elements, starting from 0
1255 $pos = $this->position++;
1256 $depth = $this->depth++;
1257 // set self as current value for this depth
1258 $this->depth_array[$depth] = $pos;
1259 $this->message[$pos] = array('cdata' => '');
1261 $this->defaultNamespace[$pos] = $this->defaultNamespace[$this->depth_array[$depth - 1]];
1263 $this->defaultNamespace[$pos] = false;
1266 // get element prefix
1267 if($prefix = $this->getPrefix($name)){
1268 // get unqualified name
1269 $name = $this->getLocalPart($name);
1274 // loop thru attributes, expanding, and registering namespace declarations
1275 if(count($attrs) > 0){
1276 foreach($attrs as $k => $v){
1277 // if ns declarations, add to class level array of valid namespaces
1278 if(preg_match('/^xmlns/',$k)){
1279 //$this->xdebug("$k: $v");
1280 //$this->xdebug('ns_prefix: '.$this->getPrefix($k));
1281 if($ns_prefix = substr(strrchr($k,':'),1)){
1282 //$this->xdebug("Add namespace[$ns_prefix] = $v");
1283 $this->namespaces[$ns_prefix] = $v;
1285 $this->defaultNamespace[$pos] = $v;
1286 if (! $this->getPrefixFromNamespace($v)) {
1287 $this->namespaces['ns'.(count($this->namespaces)+1)] = $v;
1290 if($v == 'http://www.w3.org/2001/XMLSchema' || $v == 'http://www.w3.org/1999/XMLSchema' || $v == 'http://www.w3.org/2000/10/XMLSchema'){
1291 $this->XMLSchemaVersion = $v;
1292 $this->namespaces['xsi'] = $v.'-instance';
1296 foreach($attrs as $k => $v){
1297 // expand each attribute
1298 $k = strpos($k,':') ? $this->expandQname($k) : $k;
1299 $v = strpos($v,':') ? $this->expandQname($v) : $v;
1306 // find status, register data
1308 case 'all': // (optional) compositor content for a complexType
1312 //$this->xdebug("compositor $name for currentComplexType: $this->currentComplexType and currentElement: $this->currentElement");
1313 $this->complexTypes[$this->currentComplexType]['compositor'] = $name;
1314 //if($name == 'all' || $name == 'sequence'){
1315 // $this->complexTypes[$this->currentComplexType]['phpType'] = 'struct';
1318 case 'attribute': // complexType attribute
1319 //$this->xdebug("parsing attribute $attrs[name] $attrs[ref] of value: ".$attrs['http://schemas.xmlsoap.org/wsdl/:arrayType']);
1320 $this->xdebug("parsing attribute:");
1321 $this->appendDebug($this->varDump($attrs));
1322 if (!isset($attrs['form'])) {
1323 // TODO: handle globals
1324 $attrs['form'] = $this->schemaInfo['attributeFormDefault'];
1326 if (isset($attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'])) {
1327 $v = $attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'];
1328 if (!strpos($v, ':')) {
1329 // no namespace in arrayType attribute value...
1330 if ($this->defaultNamespace[$pos]) {
1331 // ...so use the default
1332 $attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'] = $this->defaultNamespace[$pos] . ':' . $attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'];
1336 if(isset($attrs['name'])){
1337 $this->attributes[$attrs['name']] = $attrs;
1338 $aname = $attrs['name'];
1339 } elseif(isset($attrs['ref']) && $attrs['ref'] == 'http://schemas.xmlsoap.org/soap/encoding/:arrayType'){
1340 if (isset($attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'])) {
1341 $aname = $attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'];
1345 } elseif(isset($attrs['ref'])){
1346 $aname = $attrs['ref'];
1347 $this->attributes[$attrs['ref']] = $attrs;
1350 if($this->currentComplexType){ // This should *always* be
1351 $this->complexTypes[$this->currentComplexType]['attrs'][$aname] = $attrs;
1353 // arrayType attribute
1354 if(isset($attrs['http://schemas.xmlsoap.org/wsdl/:arrayType']) || $this->getLocalPart($aname) == 'arrayType'){
1355 $this->complexTypes[$this->currentComplexType]['phpType'] = 'array';
1356 $prefix = $this->getPrefix($aname);
1357 if(isset($attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'])){
1358 $v = $attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'];
1362 if(strpos($v,'[,]')){
1363 $this->complexTypes[$this->currentComplexType]['multidimensional'] = true;
1365 $v = substr($v,0,strpos($v,'[')); // clip the []
1366 if(!strpos($v,':') && isset($this->typemap[$this->XMLSchemaVersion][$v])){
1367 $v = $this->XMLSchemaVersion.':'.$v;
1369 $this->complexTypes[$this->currentComplexType]['arrayType'] = $v;
1372 case 'complexContent': // (optional) content for a complexType
1373 $this->xdebug("do nothing for element $name");
1376 array_push($this->complexTypeStack, $this->currentComplexType);
1377 if(isset($attrs['name'])){
1378 // TODO: what is the scope of named complexTypes that appear
1379 // nested within other c complexTypes?
1380 $this->xdebug('processing named complexType '.$attrs['name']);
1381 //$this->currentElement = false;
1382 $this->currentComplexType = $attrs['name'];
1383 $this->complexTypes[$this->currentComplexType] = $attrs;
1384 $this->complexTypes[$this->currentComplexType]['typeClass'] = 'complexType';
1385 // This is for constructs like
1386 // <complexType name="ListOfString" base="soap:Array">
1388 // <element name="string" type="xsd:string"
1389 // minOccurs="0" maxOccurs="unbounded" />
1392 if(isset($attrs['base']) && preg_match('/:Array$/',$attrs['base'])){
1393 $this->xdebug('complexType is unusual array');
1394 $this->complexTypes[$this->currentComplexType]['phpType'] = 'array';
1396 $this->complexTypes[$this->currentComplexType]['phpType'] = 'struct';
1399 $name = $this->CreateTypeName($this->currentElement);
1400 $this->xdebug('processing unnamed complexType for element ' . $this->currentElement . ' named ' . $name);
1401 $this->currentComplexType = $name;
1402 //$this->currentElement = false;
1403 $this->complexTypes[$this->currentComplexType] = $attrs;
1404 $this->complexTypes[$this->currentComplexType]['typeClass'] = 'complexType';
1405 // This is for constructs like
1406 // <complexType name="ListOfString" base="soap:Array">
1408 // <element name="string" type="xsd:string"
1409 // minOccurs="0" maxOccurs="unbounded" />
1412 if(isset($attrs['base']) && preg_match('/:Array$/',$attrs['base'])){
1413 $this->xdebug('complexType is unusual array');
1414 $this->complexTypes[$this->currentComplexType]['phpType'] = 'array';
1416 $this->complexTypes[$this->currentComplexType]['phpType'] = 'struct';
1419 $this->complexTypes[$this->currentComplexType]['simpleContent'] = 'false';
1422 array_push($this->elementStack, $this->currentElement);
1423 if (!isset($attrs['form'])) {
1424 if ($this->currentComplexType) {
1425 $attrs['form'] = $this->schemaInfo['elementFormDefault'];
1428 $attrs['form'] = 'qualified';
1431 if(isset($attrs['type'])){
1432 $this->xdebug("processing typed element ".$attrs['name']." of type ".$attrs['type']);
1433 if (! $this->getPrefix($attrs['type'])) {
1434 if ($this->defaultNamespace[$pos]) {
1435 $attrs['type'] = $this->defaultNamespace[$pos] . ':' . $attrs['type'];
1436 $this->xdebug('used default namespace to make type ' . $attrs['type']);
1439 // This is for constructs like
1440 // <complexType name="ListOfString" base="soap:Array">
1442 // <element name="string" type="xsd:string"
1443 // minOccurs="0" maxOccurs="unbounded" />
1446 if ($this->currentComplexType && $this->complexTypes[$this->currentComplexType]['phpType'] == 'array') {
1447 $this->xdebug('arrayType for unusual array is ' . $attrs['type']);
1448 $this->complexTypes[$this->currentComplexType]['arrayType'] = $attrs['type'];
1450 $this->currentElement = $attrs['name'];
1451 $ename = $attrs['name'];
1452 } elseif(isset($attrs['ref'])){
1453 $this->xdebug("processing element as ref to ".$attrs['ref']);
1454 $this->currentElement = "ref to ".$attrs['ref'];
1455 $ename = $this->getLocalPart($attrs['ref']);
1457 $type = $this->CreateTypeName($this->currentComplexType . '_' . $attrs['name']);
1458 $this->xdebug("processing untyped element " . $attrs['name'] . ' type ' . $type);
1459 $this->currentElement = $attrs['name'];
1460 $attrs['type'] = $this->schemaTargetNamespace . ':' . $type;
1461 $ename = $attrs['name'];
1463 if (isset($ename) && $this->currentComplexType) {
1464 $this->xdebug("add element $ename to complexType $this->currentComplexType");
1465 $this->complexTypes[$this->currentComplexType]['elements'][$ename] = $attrs;
1466 } elseif (!isset($attrs['ref'])) {
1467 $this->xdebug("add element $ename to elements array");
1468 $this->elements[ $attrs['name'] ] = $attrs;
1469 $this->elements[ $attrs['name'] ]['typeClass'] = 'element';
1472 case 'enumeration': // restriction value list member
1473 $this->xdebug('enumeration ' . $attrs['value']);
1474 if ($this->currentSimpleType) {
1475 $this->simpleTypes[$this->currentSimpleType]['enumeration'][] = $attrs['value'];
1476 } elseif ($this->currentComplexType) {
1477 $this->complexTypes[$this->currentComplexType]['enumeration'][] = $attrs['value'];
1480 case 'extension': // simpleContent or complexContent type extension
1481 $this->xdebug('extension ' . $attrs['base']);
1482 if ($this->currentComplexType) {
1483 $ns = $this->getPrefix($attrs['base']);
1485 $this->complexTypes[$this->currentComplexType]['extensionBase'] = $this->schemaTargetNamespace . ':' . $attrs['base'];
1487 $this->complexTypes[$this->currentComplexType]['extensionBase'] = $attrs['base'];
1490 $this->xdebug('no current complexType to set extensionBase');
1494 if (isset($attrs['schemaLocation'])) {
1495 $this->xdebug('import namespace ' . $attrs['namespace'] . ' from ' . $attrs['schemaLocation']);
1496 $this->imports[$attrs['namespace']][] = array('location' => $attrs['schemaLocation'], 'loaded' => false);
1498 $this->xdebug('import namespace ' . $attrs['namespace']);
1499 $this->imports[$attrs['namespace']][] = array('location' => '', 'loaded' => true);
1500 if (! $this->getPrefixFromNamespace($attrs['namespace'])) {
1501 $this->namespaces['ns'.(count($this->namespaces)+1)] = $attrs['namespace'];
1506 if (isset($attrs['schemaLocation'])) {
1507 $this->xdebug('include into namespace ' . $this->schemaTargetNamespace . ' from ' . $attrs['schemaLocation']);
1508 $this->imports[$this->schemaTargetNamespace][] = array('location' => $attrs['schemaLocation'], 'loaded' => false);
1510 $this->xdebug('ignoring invalid XML Schema construct: include without schemaLocation attribute');
1513 case 'list': // simpleType value list
1514 $this->xdebug("do nothing for element $name");
1516 case 'restriction': // simpleType, simpleContent or complexContent value restriction
1517 $this->xdebug('restriction ' . $attrs['base']);
1518 if($this->currentSimpleType){
1519 $this->simpleTypes[$this->currentSimpleType]['type'] = $attrs['base'];
1520 } elseif($this->currentComplexType){
1521 $this->complexTypes[$this->currentComplexType]['restrictionBase'] = $attrs['base'];
1522 if(strstr($attrs['base'],':') == ':Array'){
1523 $this->complexTypes[$this->currentComplexType]['phpType'] = 'array';
1528 $this->schemaInfo = $attrs;
1529 $this->schemaInfo['schemaVersion'] = $this->getNamespaceFromPrefix($prefix);
1530 if (isset($attrs['targetNamespace'])) {
1531 $this->schemaTargetNamespace = $attrs['targetNamespace'];
1533 if (!isset($attrs['elementFormDefault'])) {
1534 $this->schemaInfo['elementFormDefault'] = 'unqualified';
1536 if (!isset($attrs['attributeFormDefault'])) {
1537 $this->schemaInfo['attributeFormDefault'] = 'unqualified';
1540 case 'simpleContent': // (optional) content for a complexType
1541 if ($this->currentComplexType) { // This should *always* be
1542 $this->complexTypes[$this->currentComplexType]['simpleContent'] = 'true';
1544 $this->xdebug("do nothing for element $name because there is no current complexType");
1548 array_push($this->simpleTypeStack, $this->currentSimpleType);
1549 if(isset($attrs['name'])){
1550 $this->xdebug("processing simpleType for name " . $attrs['name']);
1551 $this->currentSimpleType = $attrs['name'];
1552 $this->simpleTypes[ $attrs['name'] ] = $attrs;
1553 $this->simpleTypes[ $attrs['name'] ]['typeClass'] = 'simpleType';
1554 $this->simpleTypes[ $attrs['name'] ]['phpType'] = 'scalar';
1556 $name = $this->CreateTypeName($this->currentComplexType . '_' . $this->currentElement);
1557 $this->xdebug('processing unnamed simpleType for element ' . $this->currentElement . ' named ' . $name);
1558 $this->currentSimpleType = $name;
1559 //$this->currentElement = false;
1560 $this->simpleTypes[$this->currentSimpleType] = $attrs;
1561 $this->simpleTypes[$this->currentSimpleType]['phpType'] = 'scalar';
1564 case 'union': // simpleType type list
1565 $this->xdebug("do nothing for element $name");
1568 $this->xdebug("do not have any logic to process element $name");
1573 * end-element handler
1575 * @param string $parser XML parser object
1576 * @param string $name element name
1579 function schemaEndElement($parser, $name) {
1580 // bring depth down a notch
1582 // position of current element is equal to the last value left in depth_array for my depth
1583 if(isset($this->depth_array[$this->depth])){
1584 $pos = $this->depth_array[$this->depth];
1586 // get element prefix
1587 if ($prefix = $this->getPrefix($name)){
1588 // get unqualified name
1589 $name = $this->getLocalPart($name);
1594 if($name == 'complexType'){
1595 $this->xdebug('done processing complexType ' . ($this->currentComplexType ? $this->currentComplexType : '(unknown)'));
1596 $this->xdebug($this->varDump($this->complexTypes[$this->currentComplexType]));
1597 $this->currentComplexType = array_pop($this->complexTypeStack);
1598 //$this->currentElement = false;
1600 if($name == 'element'){
1601 $this->xdebug('done processing element ' . ($this->currentElement ? $this->currentElement : '(unknown)'));
1602 $this->currentElement = array_pop($this->elementStack);
1604 if($name == 'simpleType'){
1605 $this->xdebug('done processing simpleType ' . ($this->currentSimpleType ? $this->currentSimpleType : '(unknown)'));
1606 $this->xdebug($this->varDump($this->simpleTypes[$this->currentSimpleType]));
1607 $this->currentSimpleType = array_pop($this->simpleTypeStack);
1612 * element content handler
1614 * @param string $parser XML parser object
1615 * @param string $data element content
1618 function schemaCharacterData($parser, $data){
1619 $pos = $this->depth_array[$this->depth - 1];
1620 $this->message[$pos]['cdata'] .= $data;
1624 * serialize the schema
1628 function serializeSchema(){
1630 $schemaPrefix = $this->getPrefixFromNamespace($this->XMLSchemaVersion);
1633 if (sizeof($this->imports) > 0) {
1634 foreach($this->imports as $ns => $list) {
1635 foreach ($list as $ii) {
1636 if ($ii['location'] != '') {
1637 $xml .= " <$schemaPrefix:import location=\"" . $ii['location'] . '" namespace="' . $ns . "\" />\n";
1639 $xml .= " <$schemaPrefix:import namespace=\"" . $ns . "\" />\n";
1645 foreach($this->complexTypes as $typeName => $attrs){
1647 // serialize child elements
1648 if(isset($attrs['elements']) && (count($attrs['elements']) > 0)){
1649 foreach($attrs['elements'] as $element => $eParts){
1650 if(isset($eParts['ref'])){
1651 $contentStr .= " <$schemaPrefix:element ref=\"$element\"/>\n";
1653 $contentStr .= " <$schemaPrefix:element name=\"$element\" type=\"" . $this->contractQName($eParts['type']) . "\"";
1654 foreach ($eParts as $aName => $aValue) {
1655 // handle, e.g., abstract, default, form, minOccurs, maxOccurs, nillable
1656 if ($aName != 'name' && $aName != 'type') {
1657 $contentStr .= " $aName=\"$aValue\"";
1660 $contentStr .= "/>\n";
1663 // compositor wraps elements
1664 if (isset($attrs['compositor']) && ($attrs['compositor'] != '')) {
1665 $contentStr = " <$schemaPrefix:$attrs[compositor]>\n".$contentStr." </$schemaPrefix:$attrs[compositor]>\n";
1669 if(isset($attrs['attrs']) && (count($attrs['attrs']) >= 1)){
1670 foreach($attrs['attrs'] as $attr => $aParts){
1671 $contentStr .= " <$schemaPrefix:attribute";
1672 foreach ($aParts as $a => $v) {
1673 if ($a == 'ref' || $a == 'type') {
1674 $contentStr .= " $a=\"".$this->contractQName($v).'"';
1675 } elseif ($a == 'http://schemas.xmlsoap.org/wsdl/:arrayType') {
1676 $this->usedNamespaces['wsdl'] = $this->namespaces['wsdl'];
1677 $contentStr .= ' wsdl:arrayType="'.$this->contractQName($v).'"';
1679 $contentStr .= " $a=\"$v\"";
1682 $contentStr .= "/>\n";
1686 if (isset($attrs['restrictionBase']) && $attrs['restrictionBase'] != ''){
1687 $contentStr = " <$schemaPrefix:restriction base=\"".$this->contractQName($attrs['restrictionBase'])."\">\n".$contentStr." </$schemaPrefix:restriction>\n";
1688 // complex or simple content
1689 if ((isset($attrs['elements']) && count($attrs['elements']) > 0) || (isset($attrs['attrs']) && count($attrs['attrs']) > 0)){
1690 $contentStr = " <$schemaPrefix:complexContent>\n".$contentStr." </$schemaPrefix:complexContent>\n";
1693 // finalize complex type
1694 if($contentStr != ''){
1695 $contentStr = " <$schemaPrefix:complexType name=\"$typeName\">\n".$contentStr." </$schemaPrefix:complexType>\n";
1697 $contentStr = " <$schemaPrefix:complexType name=\"$typeName\"/>\n";
1699 $xml .= $contentStr;
1702 if(isset($this->simpleTypes) && count($this->simpleTypes) > 0){
1703 foreach($this->simpleTypes as $typeName => $eParts){
1704 $xml .= " <$schemaPrefix:simpleType name=\"$typeName\">\n <$schemaPrefix:restriction base=\"".$this->contractQName($eParts['type'])."\">\n";
1705 if (isset($eParts['enumeration'])) {
1706 foreach ($eParts['enumeration'] as $e) {
1707 $xml .= " <$schemaPrefix:enumeration value=\"$e\"/>\n";
1710 $xml .= " </$schemaPrefix:restriction>\n </$schemaPrefix:simpleType>";
1714 if(isset($this->elements) && count($this->elements) > 0){
1715 foreach($this->elements as $element => $eParts){
1716 $xml .= " <$schemaPrefix:element name=\"$element\" type=\"".$this->contractQName($eParts['type'])."\"/>\n";
1720 if(isset($this->attributes) && count($this->attributes) > 0){
1721 foreach($this->attributes as $attr => $aParts){
1722 $xml .= " <$schemaPrefix:attribute name=\"$attr\" type=\"".$this->contractQName($aParts['type'])."\"\n/>";
1727 foreach ($this->schemaInfo as $k => $v) {
1728 if ($k == 'elementFormDefault' || $k == 'attributeFormDefault') {
1729 $attr .= " $k=\"$v\"";
1732 $el = "<$schemaPrefix:schema$attr targetNamespace=\"$this->schemaTargetNamespace\"\n";
1733 foreach (array_diff($this->usedNamespaces, $this->enclosingNamespaces) as $nsp => $ns) {
1734 $el .= " xmlns:$nsp=\"$ns\"";
1736 $xml = $el . ">\n".$xml."</$schemaPrefix:schema>\n";
1741 * adds debug data to the clas level debug string
1743 * @param string $string debug data
1746 function xdebug($string){
1747 $this->debug('<' . $this->schemaTargetNamespace . '> '.$string);
1751 * get the PHP type of a user defined type in the schema
1752 * PHP type is kind of a misnomer since it actually returns 'struct' for assoc. arrays
1753 * returns false if no type exists, or not w/ the given namespace
1754 * else returns a string that is either a native php type, or 'struct'
1756 * @param string $type name of defined type
1757 * @param string $ns namespace of type
1762 function getPHPType($type,$ns){
1763 if(isset($this->typemap[$ns][$type])){
1764 //print "found type '$type' and ns $ns in typemap<br>";
1765 return $this->typemap[$ns][$type];
1766 } elseif(isset($this->complexTypes[$type])){
1767 //print "getting type '$type' and ns $ns from complexTypes array<br>";
1768 return $this->complexTypes[$type]['phpType'];
1774 * returns an associative array of information about a given type
1775 * returns false if no type exists by the given name
1777 * For a complexType typeDef = array(
1778 * 'restrictionBase' => '',
1780 * 'compositor' => '(sequence|all)',
1781 * 'elements' => array(), // refs to elements array
1782 * 'attrs' => array() // refs to attributes array
1783 * ... and so on (see addComplexType)
1786 * For simpleType or element, the array has different keys.
1788 * @param string $type
1791 * @see addComplexType
1792 * @see addSimpleType
1795 function getTypeDef($type){
1796 //$this->debug("in getTypeDef for type $type");
1797 if (substr($type, -1) == '^') {
1799 $type = substr($type, 0, -1);
1804 if((! $is_element) && isset($this->complexTypes[$type])){
1805 $this->xdebug("in getTypeDef, found complexType $type");
1806 return $this->complexTypes[$type];
1807 } elseif((! $is_element) && isset($this->simpleTypes[$type])){
1808 $this->xdebug("in getTypeDef, found simpleType $type");
1809 if (!isset($this->simpleTypes[$type]['phpType'])) {
1810 // get info for type to tack onto the simple type
1811 // TODO: can this ever really apply (i.e. what is a simpleType really?)
1812 $uqType = substr($this->simpleTypes[$type]['type'], strrpos($this->simpleTypes[$type]['type'], ':') + 1);
1813 $ns = substr($this->simpleTypes[$type]['type'], 0, strrpos($this->simpleTypes[$type]['type'], ':'));
1814 $etype = $this->getTypeDef($uqType);
1816 $this->xdebug("in getTypeDef, found type for simpleType $type:");
1817 $this->xdebug($this->varDump($etype));
1818 if (isset($etype['phpType'])) {
1819 $this->simpleTypes[$type]['phpType'] = $etype['phpType'];
1821 if (isset($etype['elements'])) {
1822 $this->simpleTypes[$type]['elements'] = $etype['elements'];
1826 return $this->simpleTypes[$type];
1827 } elseif(isset($this->elements[$type])){
1828 $this->xdebug("in getTypeDef, found element $type");
1829 if (!isset($this->elements[$type]['phpType'])) {
1830 // get info for type to tack onto the element
1831 $uqType = substr($this->elements[$type]['type'], strrpos($this->elements[$type]['type'], ':') + 1);
1832 $ns = substr($this->elements[$type]['type'], 0, strrpos($this->elements[$type]['type'], ':'));
1833 $etype = $this->getTypeDef($uqType);
1835 $this->xdebug("in getTypeDef, found type for element $type:");
1836 $this->xdebug($this->varDump($etype));
1837 if (isset($etype['phpType'])) {
1838 $this->elements[$type]['phpType'] = $etype['phpType'];
1840 if (isset($etype['elements'])) {
1841 $this->elements[$type]['elements'] = $etype['elements'];
1843 if (isset($etype['extensionBase'])) {
1844 $this->elements[$type]['extensionBase'] = $etype['extensionBase'];
1846 } elseif ($ns == 'http://www.w3.org/2001/XMLSchema') {
1847 $this->xdebug("in getTypeDef, element $type is an XSD type");
1848 $this->elements[$type]['phpType'] = 'scalar';
1851 return $this->elements[$type];
1852 } elseif(isset($this->attributes[$type])){
1853 $this->xdebug("in getTypeDef, found attribute $type");
1854 return $this->attributes[$type];
1855 } elseif (preg_match('/_ContainedType$/', $type)) {
1856 $this->xdebug("in getTypeDef, have an untyped element $type");
1857 $typeDef['typeClass'] = 'simpleType';
1858 $typeDef['phpType'] = 'scalar';
1859 $typeDef['type'] = 'http://www.w3.org/2001/XMLSchema:string';
1862 $this->xdebug("in getTypeDef, did not find $type");
1867 * returns a sample serialization of a given type, or false if no type by the given name
1869 * @param string $type name of type
1874 function serializeTypeDef($type){
1875 //print "in sTD() for type $type<br>";
1876 if($typeDef = $this->getTypeDef($type)){
1878 if(is_array($typeDef['attrs'])){
1879 foreach($typeDef['attrs'] as $attName => $data){
1880 $str .= " $attName=\"{type = ".$data['type']."}\"";
1883 $str .= " xmlns=\"".$this->schema['targetNamespace']."\"";
1884 if(count($typeDef['elements']) > 0){
1886 foreach($typeDef['elements'] as $element => $eData){
1887 $str .= $this->serializeTypeDef($element);
1890 } elseif($typeDef['typeClass'] == 'element') {
1891 $str .= "></$type>";
1901 * returns HTML form elements that allow a user
1902 * to enter values for creating an instance of the given type.
1904 * @param string $name name for type instance
1905 * @param string $type name of type
1910 function typeToForm($name,$type){
1912 if($typeDef = $this->getTypeDef($type)){
1914 if($typeDef['phpType'] == 'struct'){
1915 $buffer .= '<table>';
1916 foreach($typeDef['elements'] as $child => $childDef){
1918 <tr><td align='right'>$childDef[name] (type: ".$this->getLocalPart($childDef['type'])."):</td>
1919 <td><input type='text' name='parameters[".$name."][$childDef[name]]'></td></tr>";
1921 $buffer .= '</table>';
1923 } elseif($typeDef['phpType'] == 'array'){
1924 $buffer .= '<table>';
1925 for($i=0;$i < 3; $i++){
1927 <tr><td align='right'>array item (type: $typeDef[arrayType]):</td>
1928 <td><input type='text' name='parameters[".$name."][]'></td></tr>";
1930 $buffer .= '</table>';
1933 $buffer .= "<input type='text' name='parameters[$name]'>";
1936 $buffer .= "<input type='text' name='parameters[$name]'>";
1942 * adds a complex type to the schema
1952 * array('ref'=>'SOAP-ENC:arrayType','wsdl:arrayType'=>'string[]'),
1956 * example: PHP associative array ( SOAP Struct )
1963 * array('myVar'=> array('name'=>'myVar','type'=>'string')
1967 * @param typeClass (complexType|simpleType|attribute)
1968 * @param phpType: currently supported are array and struct (php assoc array)
1969 * @param compositor (all|sequence|choice)
1970 * @param restrictionBase namespace:name (http://schemas.xmlsoap.org/soap/encoding/:Array)
1971 * @param elements = array ( name = array(name=>'',type=>'') )
1972 * @param attrs = array(
1974 * 'ref' => "http://schemas.xmlsoap.org/soap/encoding/:arrayType",
1975 * "http://schemas.xmlsoap.org/wsdl/:arrayType" => "string[]"
1978 * @param arrayType: namespace:name (http://www.w3.org/2001/XMLSchema:string)
1982 function addComplexType($name,$typeClass='complexType',$phpType='array',$compositor='',$restrictionBase='',$elements=array(),$attrs=array(),$arrayType=''){
1983 $this->complexTypes[$name] = array(
1985 'typeClass' => $typeClass,
1986 'phpType' => $phpType,
1987 'compositor'=> $compositor,
1988 'restrictionBase' => $restrictionBase,
1989 'elements' => $elements,
1991 'arrayType' => $arrayType
1994 $this->xdebug("addComplexType $name:");
1995 $this->appendDebug($this->varDump($this->complexTypes[$name]));
1999 * adds a simple type to the schema
2001 * @param string $name
2002 * @param string $restrictionBase namespace:name (http://schemas.xmlsoap.org/soap/encoding/:Array)
2003 * @param string $typeClass (should always be simpleType)
2004 * @param string $phpType (should always be scalar)
2005 * @param array $enumeration array of values
2007 * @see nusoap_xmlschema
2010 function addSimpleType($name, $restrictionBase='', $typeClass='simpleType', $phpType='scalar', $enumeration=array()) {
2011 $this->simpleTypes[$name] = array(
2013 'typeClass' => $typeClass,
2014 'phpType' => $phpType,
2015 'type' => $restrictionBase,
2016 'enumeration' => $enumeration
2019 $this->xdebug("addSimpleType $name:");
2020 $this->appendDebug($this->varDump($this->simpleTypes[$name]));
2024 * adds an element to the schema
2026 * @param array $attrs attributes that must include name and type
2027 * @see nusoap_xmlschema
2030 function addElement($attrs) {
2031 if (! $this->getPrefix($attrs['type'])) {
2032 $attrs['type'] = $this->schemaTargetNamespace . ':' . $attrs['type'];
2034 $this->elements[ $attrs['name'] ] = $attrs;
2035 $this->elements[ $attrs['name'] ]['typeClass'] = 'element';
2037 $this->xdebug("addElement " . $attrs['name']);
2038 $this->appendDebug($this->varDump($this->elements[ $attrs['name'] ]));
2043 * Backward compatibility
2045 class XMLSchema extends nusoap_xmlschema {
2053 * For creating serializable abstractions of native PHP types. This class
2054 * allows element name/namespace, XSD type, and XML attributes to be
2055 * associated with a value. This is extremely useful when WSDL is not
2056 * used, but is also useful when WSDL is used with polymorphic types, including
2057 * xsd:anyType and user-defined types.
2059 * @author Dietrich Ayala <dietrich@ganx4.com>
2063 class soapval extends nusoap_base {
2065 * The XML element name
2072 * The XML type name (string or false)
2086 * The XML element namespace (string or false)
2093 * The XML type namespace (string or false)
2100 * The XML element attributes (array or false)
2110 * @param string $name optional name
2111 * @param mixed $type optional type name
2112 * @param mixed $value optional value
2113 * @param mixed $element_ns optional namespace of value
2114 * @param mixed $type_ns optional namespace of type
2115 * @param mixed $attributes associative array of attributes to add to element serialization
2118 function soapval($name='soapval',$type=false,$value=-1,$element_ns=false,$type_ns=false,$attributes=false) {
2119 parent::nusoap_base();
2120 $this->name = $name;
2121 $this->type = $type;
2122 $this->value = $value;
2123 $this->element_ns = $element_ns;
2124 $this->type_ns = $type_ns;
2125 $this->attributes = $attributes;
2129 * return serialized value
2131 * @param string $use The WSDL use value (encoded|literal)
2132 * @return string XML data
2135 function serialize($use='encoded') {
2136 return $this->serialize_val($this->value, $this->name, $this->type, $this->element_ns, $this->type_ns, $this->attributes, $use, true);
2140 * decodes a soapval object into a PHP native type
2146 return $this->value;
2157 * transport class for sending/receiving data via HTTP and HTTPS
2158 * NOTE: PHP must be compiled with the CURL extension for HTTPS support
2160 * @author Dietrich Ayala <dietrich@ganx4.com>
2161 * @author Scott Nichol <snichol@users.sourceforge.net>
2165 class soap_transport_http extends nusoap_base {
2169 var $digest_uri = '';
2174 var $request_method = 'POST';
2175 var $protocol_version = '1.0';
2177 var $outgoing_headers = array();
2178 var $incoming_headers = array();
2179 var $incoming_cookies = array();
2180 var $outgoing_payload = '';
2181 var $incoming_payload = '';
2182 var $response_status_line; // HTTP response status line
2183 var $useSOAPAction = true;
2184 var $persistentConnection = false;
2185 var $ch = false; // cURL handle
2186 var $ch_options = array(); // cURL custom options
2187 var $use_curl = false; // force cURL use
2188 var $proxy = null; // proxy information (associative array)
2192 var $digestRequest = array();
2193 var $certRequest = array(); // keys must be cainfofile (optional), sslcertfile, sslkeyfile, passphrase, certpassword (optional), verifypeer (optional), verifyhost (optional)
2194 // cainfofile: certificate authority file, e.g. '$pathToPemFiles/rootca.pem'
2195 // sslcertfile: SSL certificate file, e.g. '$pathToPemFiles/mycert.pem'
2196 // sslkeyfile: SSL key file, e.g. '$pathToPemFiles/mykey.pem'
2197 // passphrase: SSL key password/passphrase
2198 // certpassword: SSL certificate password
2199 // verifypeer: default is 1
2200 // verifyhost: default is 1
2205 * @param string $url The URL to which to connect
2206 * @param array $curl_options User-specified cURL options
2207 * @param boolean $use_curl Whether to try to force cURL use
2210 function soap_transport_http($url, $curl_options = NULL, $use_curl = false){
2211 parent::nusoap_base();
2212 $this->debug("ctor url=$url use_curl=$use_curl curl_options:");
2213 $this->appendDebug($this->varDump($curl_options));
2214 $this->setURL($url);
2215 if (is_array($curl_options)) {
2216 $this->ch_options = $curl_options;
2218 $this->use_curl = $use_curl;
2219 preg_match('/\$Revisio' . 'n: ([^ ]+)/', $this->revision, $rev);
2220 $this->setHeader('User-Agent', $this->title.'/'.$this->version.' ('.$rev[1].')');
2224 * sets a cURL option
2226 * @param mixed $option The cURL option (always integer?)
2227 * @param mixed $value The cURL option value
2230 function setCurlOption($option, $value) {
2231 $this->debug("setCurlOption option=$option, value=");
2232 $this->appendDebug($this->varDump($value));
2233 curl_setopt($this->ch, $option, $value);
2237 * sets an HTTP header
2239 * @param string $name The name of the header
2240 * @param string $value The value of the header
2243 function setHeader($name, $value) {
2244 $this->outgoing_headers[$name] = $value;
2245 $this->debug("set header $name: $value");
2249 * unsets an HTTP header
2251 * @param string $name The name of the header
2254 function unsetHeader($name) {
2255 if (isset($this->outgoing_headers[$name])) {
2256 $this->debug("unset header $name");
2257 unset($this->outgoing_headers[$name]);
2262 * sets the URL to which to connect
2264 * @param string $url The URL to which to connect
2267 function setURL($url) {
2270 $u = parse_url($url);
2271 foreach($u as $k => $v){
2272 $this->debug("parsed URL $k = $v");
2276 // add any GET params to path
2277 if(isset($u['query']) && $u['query'] != ''){
2278 $this->path .= '?' . $u['query'];
2282 if(!isset($u['port'])){
2283 if($u['scheme'] == 'https'){
2290 $this->uri = $this->path;
2291 $this->digest_uri = $this->uri;
2294 if (!isset($u['port'])) {
2295 $this->setHeader('Host', $this->host);
2297 $this->setHeader('Host', $this->host.':'.$this->port);
2300 if (isset($u['user']) && $u['user'] != '') {
2301 $this->setCredentials(urldecode($u['user']), isset($u['pass']) ? urldecode($u['pass']) : '');
2306 * gets the I/O method to use
2308 * @return string I/O method to use (socket|curl|unknown)
2311 function io_method() {
2312 if ($this->use_curl || ($this->scheme == 'https') || ($this->scheme == 'http' && $this->authtype == 'ntlm') || ($this->scheme == 'http' && is_array($this->proxy) && $this->proxy['authtype'] == 'ntlm'))
2314 if (($this->scheme == 'http' || $this->scheme == 'ssl') && $this->authtype != 'ntlm' && (!is_array($this->proxy) || $this->proxy['authtype'] != 'ntlm'))
2320 * establish an HTTP connection
2322 * @param integer $timeout set connection timeout in seconds
2323 * @param integer $response_timeout set response timeout in seconds
2324 * @return boolean true if connected, false if not
2327 function connect($connection_timeout=0,$response_timeout=30){
2328 // For PHP 4.3 with OpenSSL, change https scheme to ssl, then treat like
2329 // "regular" socket.
2330 // TODO: disabled for now because OpenSSL must be *compiled* in (not just
2331 // loaded), and until PHP5 stream_get_wrappers is not available.
2332 // if ($this->scheme == 'https') {
2333 // if (version_compare(phpversion(), '4.3.0') >= 0) {
2334 // if (extension_loaded('openssl')) {
2335 // $this->scheme = 'ssl';
2336 // $this->debug('Using SSL over OpenSSL');
2340 $this->debug("connect connection_timeout $connection_timeout, response_timeout $response_timeout, scheme $this->scheme, host $this->host, port $this->port");
2341 if ($this->io_method() == 'socket') {
2342 if (!is_array($this->proxy)) {
2343 $host = $this->host;
2344 $port = $this->port;
2346 $host = $this->proxy['host'];
2347 $port = $this->proxy['port'];
2350 // use persistent connection
2351 if($this->persistentConnection && isset($this->fp) && is_resource($this->fp)){
2352 if (!feof($this->fp)) {
2353 $this->debug('Re-use persistent connection');
2357 $this->debug('Closed persistent connection at EOF');
2360 // munge host if using OpenSSL
2361 if ($this->scheme == 'ssl') {
2362 $host = 'ssl://' . $host;
2364 $this->debug('calling fsockopen with host ' . $host . ' connection_timeout ' . $connection_timeout);
2367 if($connection_timeout > 0){
2368 $this->fp = @fsockopen( $host, $this->port, $this->errno, $this->error_str, $connection_timeout);
2370 $this->fp = @fsockopen( $host, $this->port, $this->errno, $this->error_str);
2375 $msg = 'Couldn\'t open socket connection to server ' . $this->url;
2377 $msg .= ', Error ('.$this->errno.'): '.$this->error_str;
2379 $msg .= ' prior to connect(). This is often a problem looking up the host name.';
2382 $this->setError($msg);
2386 // set response timeout
2387 $this->debug('set response timeout to ' . $response_timeout);
2388 socket_set_timeout( $this->fp, $response_timeout);
2390 $this->debug('socket connected');
2392 } elseif ($this->io_method() == 'curl') {
2393 if (!extension_loaded('curl')) {
2394 // $this->setError('cURL Extension, or OpenSSL extension w/ PHP version >= 4.3 is required for HTTPS');
2395 $this->setError('The PHP cURL Extension is required for HTTPS or NLTM. You will need to re-build or update your PHP to include cURL or change php.ini to load the PHP cURL extension.');
2398 // Avoid warnings when PHP does not have these options
2399 if (defined('CURLOPT_CONNECTIONTIMEOUT'))
2400 $CURLOPT_CONNECTIONTIMEOUT = CURLOPT_CONNECTIONTIMEOUT;
2402 $CURLOPT_CONNECTIONTIMEOUT = 78;
2403 if (defined('CURLOPT_HTTPAUTH'))
2404 $CURLOPT_HTTPAUTH = CURLOPT_HTTPAUTH;
2406 $CURLOPT_HTTPAUTH = 107;
2407 if (defined('CURLOPT_PROXYAUTH'))
2408 $CURLOPT_PROXYAUTH = CURLOPT_PROXYAUTH;
2410 $CURLOPT_PROXYAUTH = 111;
2411 if (defined('CURLAUTH_BASIC'))
2412 $CURLAUTH_BASIC = CURLAUTH_BASIC;
2414 $CURLAUTH_BASIC = 1;
2415 if (defined('CURLAUTH_DIGEST'))
2416 $CURLAUTH_DIGEST = CURLAUTH_DIGEST;
2418 $CURLAUTH_DIGEST = 2;
2419 if (defined('CURLAUTH_NTLM'))
2420 $CURLAUTH_NTLM = CURLAUTH_NTLM;
2424 $this->debug('connect using cURL');
2426 $this->ch = curl_init();
2428 $hostURL = ($this->port != '') ? "$this->scheme://$this->host:$this->port" : "$this->scheme://$this->host";
2430 $hostURL .= $this->path;
2431 $this->setCurlOption(CURLOPT_URL, $hostURL);
2432 // follow location headers (re-directs)
2433 if (ini_get('safe_mode') || ini_get('open_basedir')) {
2434 $this->debug('safe_mode or open_basedir set, so do not set CURLOPT_FOLLOWLOCATION');
2435 $this->debug('safe_mode = ');
2436 $this->appendDebug($this->varDump(ini_get('safe_mode')));
2437 $this->debug('open_basedir = ');
2438 $this->appendDebug($this->varDump(ini_get('open_basedir')));
2440 $this->setCurlOption(CURLOPT_FOLLOWLOCATION, 1);
2442 // ask for headers in the response output
2443 $this->setCurlOption(CURLOPT_HEADER, 1);
2444 // ask for the response output as the return value
2445 $this->setCurlOption(CURLOPT_RETURNTRANSFER, 1);
2447 // We manage this ourselves through headers and encoding
2448 // if(function_exists('gzuncompress')){
2449 // $this->setCurlOption(CURLOPT_ENCODING, 'deflate');
2451 // persistent connection
2452 if ($this->persistentConnection) {
2453 // I believe the following comment is now bogus, having applied to
2454 // the code when it used CURLOPT_CUSTOMREQUEST to send the request.
2455 // The way we send data, we cannot use persistent connections, since
2456 // there will be some "junk" at the end of our request.
2457 //$this->setCurlOption(CURL_HTTP_VERSION_1_1, true);
2458 $this->persistentConnection = false;
2459 $this->setHeader('Connection', 'close');
2462 if ($connection_timeout != 0) {
2463 $this->setCurlOption($CURLOPT_CONNECTIONTIMEOUT, $connection_timeout);
2465 if ($response_timeout != 0) {
2466 $this->setCurlOption(CURLOPT_TIMEOUT, $response_timeout);
2469 if ($this->scheme == 'https') {
2470 $this->debug('set cURL SSL verify options');
2471 // recent versions of cURL turn on peer/host checking by default,
2472 // while PHP binaries are not compiled with a default location for the
2473 // CA cert bundle, so disable peer/host checking.
2474 //$this->setCurlOption(CURLOPT_CAINFO, 'f:\php-4.3.2-win32\extensions\curl-ca-bundle.crt');
2475 $this->setCurlOption(CURLOPT_SSL_VERIFYPEER, 0);
2476 $this->setCurlOption(CURLOPT_SSL_VERIFYHOST, 0);
2478 // support client certificates (thanks Tobias Boes, Doug Anarino, Eryan Ariobowo)
2479 if ($this->authtype == 'certificate') {
2480 $this->debug('set cURL certificate options');
2481 if (isset($this->certRequest['cainfofile'])) {
2482 $this->setCurlOption(CURLOPT_CAINFO, $this->certRequest['cainfofile']);
2484 if (isset($this->certRequest['verifypeer'])) {
2485 $this->setCurlOption(CURLOPT_SSL_VERIFYPEER, $this->certRequest['verifypeer']);
2487 $this->setCurlOption(CURLOPT_SSL_VERIFYPEER, 1);
2489 if (isset($this->certRequest['verifyhost'])) {
2490 $this->setCurlOption(CURLOPT_SSL_VERIFYHOST, $this->certRequest['verifyhost']);
2492 $this->setCurlOption(CURLOPT_SSL_VERIFYHOST, 1);
2494 if (isset($this->certRequest['sslcertfile'])) {
2495 $this->setCurlOption(CURLOPT_SSLCERT, $this->certRequest['sslcertfile']);
2497 if (isset($this->certRequest['sslkeyfile'])) {
2498 $this->setCurlOption(CURLOPT_SSLKEY, $this->certRequest['sslkeyfile']);
2500 if (isset($this->certRequest['passphrase'])) {
2501 $this->setCurlOption(CURLOPT_SSLKEYPASSWD, $this->certRequest['passphrase']);
2503 if (isset($this->certRequest['certpassword'])) {
2504 $this->setCurlOption(CURLOPT_SSLCERTPASSWD, $this->certRequest['certpassword']);
2508 if ($this->authtype && ($this->authtype != 'certificate')) {
2509 if ($this->username) {
2510 $this->debug('set cURL username/password');
2511 $this->setCurlOption(CURLOPT_USERPWD, "$this->username:$this->password");
2513 if ($this->authtype == 'basic') {
2514 $this->debug('set cURL for Basic authentication');
2515 $this->setCurlOption($CURLOPT_HTTPAUTH, $CURLAUTH_BASIC);
2517 if ($this->authtype == 'digest') {
2518 $this->debug('set cURL for digest authentication');
2519 $this->setCurlOption($CURLOPT_HTTPAUTH, $CURLAUTH_DIGEST);
2521 if ($this->authtype == 'ntlm') {
2522 $this->debug('set cURL for NTLM authentication');
2523 $this->setCurlOption($CURLOPT_HTTPAUTH, $CURLAUTH_NTLM);
2526 if (is_array($this->proxy)) {
2527 $this->debug('set cURL proxy options');
2528 if ($this->proxy['port'] != '') {
2529 $this->setCurlOption(CURLOPT_PROXY, $this->proxy['host'].':'.$this->proxy['port']);
2531 $this->setCurlOption(CURLOPT_PROXY, $this->proxy['host']);
2533 if ($this->proxy['username'] || $this->proxy['password']) {
2534 $this->debug('set cURL proxy authentication options');
2535 $this->setCurlOption(CURLOPT_PROXYUSERPWD, $this->proxy['username'].':'.$this->proxy['password']);
2536 if ($this->proxy['authtype'] == 'basic') {
2537 $this->setCurlOption($CURLOPT_PROXYAUTH, $CURLAUTH_BASIC);
2539 if ($this->proxy['authtype'] == 'ntlm') {
2540 $this->setCurlOption($CURLOPT_PROXYAUTH, $CURLAUTH_NTLM);
2544 $this->debug('cURL connection set up');
2547 $this->setError('Unknown scheme ' . $this->scheme);
2548 $this->debug('Unknown scheme ' . $this->scheme);
2554 * sends the SOAP request and gets the SOAP response via HTTP[S]
2556 * @param string $data message data
2557 * @param integer $timeout set connection timeout in seconds
2558 * @param integer $response_timeout set response timeout in seconds
2559 * @param array $cookies cookies to send
2560 * @return string data
2563 function send($data, $timeout=0, $response_timeout=30, $cookies=NULL) {
2565 $this->debug('entered send() with data of length: '.strlen($data));
2567 $this->tryagain = true;
2569 while ($this->tryagain) {
2570 $this->tryagain = false;
2573 if (!$this->connect($timeout, $response_timeout)){
2578 if (!$this->sendRequest($data, $cookies)){
2583 $respdata = $this->getResponse();
2585 $this->setError("Too many tries to get an OK response ($this->response_status_line)");
2588 $this->debug('end of send()');
2593 * sends the SOAP request and gets the SOAP response via HTTPS using CURL
2595 * @param string $data message data
2596 * @param integer $timeout set connection timeout in seconds
2597 * @param integer $response_timeout set response timeout in seconds
2598 * @param array $cookies cookies to send
2599 * @return string data
2603 function sendHTTPS($data, $timeout=0, $response_timeout=30, $cookies) {
2604 return $this->send($data, $timeout, $response_timeout, $cookies);
2608 * if authenticating, set user credentials here
2610 * @param string $username
2611 * @param string $password
2612 * @param string $authtype (basic|digest|certificate|ntlm)
2613 * @param array $digestRequest (keys must be nonce, nc, realm, qop)
2614 * @param array $certRequest (keys must be cainfofile (optional), sslcertfile, sslkeyfile, passphrase, certpassword (optional), verifypeer (optional), verifyhost (optional): see corresponding options in cURL docs)
2617 function setCredentials($username, $password, $authtype = 'basic', $digestRequest = array(), $certRequest = array()) {
2618 $this->debug("setCredentials username=$username authtype=$authtype digestRequest=");
2619 $this->appendDebug($this->varDump($digestRequest));
2620 $this->debug("certRequest=");
2621 $this->appendDebug($this->varDump($certRequest));
2623 if ($authtype == 'basic') {
2624 $this->setHeader('Authorization', 'Basic '.base64_encode(str_replace(':','',$username).':'.$password));
2625 } elseif ($authtype == 'digest') {
2626 if (isset($digestRequest['nonce'])) {
2627 $digestRequest['nc'] = isset($digestRequest['nc']) ? $digestRequest['nc']++ : 1;
2629 // calculate the Digest hashes (calculate code based on digest implementation found at: http://www.rassoc.com/gregr/weblog/stories/2002/07/09/webServicesSecurityHttpDigestAuthenticationWithoutActiveDirectory.html)
2631 // A1 = unq(username-value) ":" unq(realm-value) ":" passwd
2632 $A1 = $username. ':' . (isset($digestRequest['realm']) ? $digestRequest['realm'] : '') . ':' . $password;
2637 // A2 = Method ":" digest-uri-value
2638 $A2 = $this->request_method . ':' . $this->digest_uri;
2643 // KD(secret, data) = H(concat(secret, ":", data))
2645 // request-digest = <"> < KD ( H(A1), unq(nonce-value)
2647 // ":" unq(cnonce-value)
2648 // ":" unq(qop-value)
2651 // if qop is missing,
2652 // request-digest = <"> < KD ( H(A1), unq(nonce-value) ":" H(A2) ) > <">
2654 $unhashedDigest = '';
2655 $nonce = isset($digestRequest['nonce']) ? $digestRequest['nonce'] : '';
2657 if ($digestRequest['qop'] != '') {
2658 $unhashedDigest = $HA1 . ':' . $nonce . ':' . sprintf("%08d", $digestRequest['nc']) . ':' . $cnonce . ':' . $digestRequest['qop'] . ':' . $HA2;
2660 $unhashedDigest = $HA1 . ':' . $nonce . ':' . $HA2;
2663 $hashedDigest = md5($unhashedDigest);
2666 if (isset($digestRequest['opaque'])) {
2667 $opaque = ', opaque="' . $digestRequest['opaque'] . '"';
2670 $this->setHeader('Authorization', 'Digest username="' . $username . '", realm="' . $digestRequest['realm'] . '", nonce="' . $nonce . '", uri="' . $this->digest_uri . $opaque . '", cnonce="' . $cnonce . '", nc=' . sprintf("%08x", $digestRequest['nc']) . ', qop="' . $digestRequest['qop'] . '", response="' . $hashedDigest . '"');
2672 } elseif ($authtype == 'certificate') {
2673 $this->certRequest = $certRequest;
2674 $this->debug('Authorization header not set for certificate');
2675 } elseif ($authtype == 'ntlm') {
2677 $this->debug('Authorization header not set for ntlm');
2679 $this->username = $username;
2680 $this->password = $password;
2681 $this->authtype = $authtype;
2682 $this->digestRequest = $digestRequest;
2686 * set the soapaction value
2688 * @param string $soapaction
2691 function setSOAPAction($soapaction) {
2692 $this->setHeader('SOAPAction', '"' . $soapaction . '"');
2698 * @param string $enc encoding style. supported values: gzip, deflate, or both
2701 function setEncoding($enc='gzip, deflate') {
2702 if (function_exists('gzdeflate')) {
2703 $this->protocol_version = '1.1';
2704 $this->setHeader('Accept-Encoding', $enc);
2705 if (!isset($this->outgoing_headers['Connection'])) {
2706 $this->setHeader('Connection', 'close');
2707 $this->persistentConnection = false;
2709 // deprecated as of PHP 5.3.0
2710 //set_magic_quotes_runtime(0);
2711 $this->encoding = $enc;
2716 * set proxy info here
2718 * @param string $proxyhost use an empty string to remove proxy
2719 * @param string $proxyport
2720 * @param string $proxyusername
2721 * @param string $proxypassword
2722 * @param string $proxyauthtype (basic|ntlm)
2725 function setProxy($proxyhost, $proxyport, $proxyusername = '', $proxypassword = '', $proxyauthtype = 'basic') {
2727 $this->proxy = array(
2728 'host' => $proxyhost,
2729 'port' => $proxyport,
2730 'username' => $proxyusername,
2731 'password' => $proxypassword,
2732 'authtype' => $proxyauthtype
2734 if ($proxyusername != '' && $proxypassword != '' && $proxyauthtype = 'basic') {
2735 $this->setHeader('Proxy-Authorization', ' Basic '.base64_encode($proxyusername.':'.$proxypassword));
2738 $this->debug('remove proxy');
2740 unsetHeader('Proxy-Authorization');
2745 * Test if the given string starts with a header that is to be skipped.
2746 * Skippable headers result from chunked transfer and proxy requests.
2748 * @param string $data The string to check.
2749 * @returns boolean Whether a skippable header was found.
2752 function isSkippableCurlHeader(&$data) {
2753 $skipHeaders = array( 'HTTP/1.1 100',
2760 'HTTP/1.0 200 Connection established');
2761 foreach ($skipHeaders as $hd) {
2762 $prefix = substr($data, 0, strlen($hd));
2763 if ($prefix == $hd) return true;
2770 * decode a string that is encoded w/ "chunked' transfer encoding
2771 * as defined in RFC2068 19.4.6
2773 * @param string $buffer
2779 function decodeChunked($buffer, $lb){
2784 // read chunk-size, chunk-extension (if any) and CRLF
2785 // get the position of the linebreak
2786 $chunkend = strpos($buffer, $lb);
2787 if ($chunkend == FALSE) {
2788 $this->debug('no linebreak found in decodeChunked');
2791 $temp = substr($buffer,0,$chunkend);
2792 $chunk_size = hexdec( trim($temp) );
2793 $chunkstart = $chunkend + strlen($lb);
2794 // while (chunk-size > 0) {
2795 while ($chunk_size > 0) {
2796 $this->debug("chunkstart: $chunkstart chunk_size: $chunk_size");
2797 $chunkend = strpos( $buffer, $lb, $chunkstart + $chunk_size);
2799 // Just in case we got a broken connection
2800 if ($chunkend == FALSE) {
2801 $chunk = substr($buffer,$chunkstart);
2802 // append chunk-data to entity-body
2804 $length += strlen($chunk);
2808 // read chunk-data and CRLF
2809 $chunk = substr($buffer,$chunkstart,$chunkend-$chunkstart);
2810 // append chunk-data to entity-body
2812 // length := length + chunk-size
2813 $length += strlen($chunk);
2814 // read chunk-size and CRLF
2815 $chunkstart = $chunkend + strlen($lb);
2817 $chunkend = strpos($buffer, $lb, $chunkstart) + strlen($lb);
2818 if ($chunkend == FALSE) {
2819 break; //Just in case we got a broken connection
2821 $temp = substr($buffer,$chunkstart,$chunkend-$chunkstart);
2822 $chunk_size = hexdec( trim($temp) );
2823 $chunkstart = $chunkend;
2829 * Writes the payload, including HTTP headers, to $this->outgoing_payload.
2831 * @param string $data HTTP body
2832 * @param string $cookie_str data for HTTP Cookie header
2836 function buildPayload($data, $cookie_str = '') {
2837 // Note: for cURL connections, $this->outgoing_payload is ignored,
2838 // as is the Content-Length header, but these are still created as
2839 // debugging guides.
2841 // add content-length header
2842 if ($this->request_method != 'GET') {
2843 $this->setHeader('Content-Length', strlen($data));
2846 // start building outgoing payload:
2852 $req = "$this->request_method $uri HTTP/$this->protocol_version";
2853 $this->debug("HTTP request: $req");
2854 $this->outgoing_payload = "$req\r\n";
2856 // loop thru headers, serializing
2857 foreach($this->outgoing_headers as $k => $v){
2859 $this->debug("HTTP header: $hdr");
2860 $this->outgoing_payload .= "$hdr\r\n";
2864 if ($cookie_str != '') {
2865 $hdr = 'Cookie: '.$cookie_str;
2866 $this->debug("HTTP header: $hdr");
2867 $this->outgoing_payload .= "$hdr\r\n";
2870 // header/body separator
2871 $this->outgoing_payload .= "\r\n";
2874 $this->outgoing_payload .= $data;
2878 * sends the SOAP request via HTTP[S]
2880 * @param string $data message data
2881 * @param array $cookies cookies to send
2882 * @return boolean true if OK, false if problem
2885 function sendRequest($data, $cookies = NULL) {
2886 // build cookie string
2887 $cookie_str = $this->getCookiesForRequest($cookies, (($this->scheme == 'ssl') || ($this->scheme == 'https')));
2890 $this->buildPayload($data, $cookie_str);
2892 if ($this->io_method() == 'socket') {
2894 if(!fputs($this->fp, $this->outgoing_payload, strlen($this->outgoing_payload))) {
2895 $this->setError('couldn\'t write message data to socket');
2896 $this->debug('couldn\'t write message data to socket');
2899 $this->debug('wrote data to socket, length = ' . strlen($this->outgoing_payload));
2901 } elseif ($this->io_method() == 'curl') {
2903 // cURL does say this should only be the verb, and in fact it
2904 // turns out that the URI and HTTP version are appended to this, which
2905 // some servers refuse to work with (so we no longer use this method!)
2906 //$this->setCurlOption(CURLOPT_CUSTOMREQUEST, $this->outgoing_payload);
2907 $curl_headers = array();
2908 foreach($this->outgoing_headers as $k => $v){
2909 if ($k == 'Connection' || $k == 'Content-Length' || $k == 'Host' || $k == 'Authorization' || $k == 'Proxy-Authorization') {
2910 $this->debug("Skip cURL header $k: $v");
2912 $curl_headers[] = "$k: $v";
2915 if ($cookie_str != '') {
2916 $curl_headers[] = 'Cookie: ' . $cookie_str;
2918 $this->setCurlOption(CURLOPT_HTTPHEADER, $curl_headers);
2919 $this->debug('set cURL HTTP headers');
2920 if ($this->request_method == "POST") {
2921 $this->setCurlOption(CURLOPT_POST, 1);
2922 $this->setCurlOption(CURLOPT_POSTFIELDS, $data);
2923 $this->debug('set cURL POST data');
2926 // insert custom user-set cURL options
2927 foreach ($this->ch_options as $key => $val) {
2928 $this->setCurlOption($key, $val);
2931 $this->debug('set cURL payload');
2937 * gets the SOAP response via HTTP[S]
2939 * @return string the response (also sets member variables like incoming_payload)
2942 function getResponse(){
2943 $this->incoming_payload = '';
2945 if ($this->io_method() == 'socket') {
2946 // loop until headers have been retrieved
2948 while (!isset($lb)){
2950 // We might EOF during header read.
2951 if(feof($this->fp)) {
2952 $this->incoming_payload = $data;
2953 $this->debug('found no headers before EOF after length ' . strlen($data));
2954 $this->debug("received before EOF:\n" . $data);
2955 $this->setError('server failed to send headers');
2959 $tmp = fgets($this->fp, 256);
2960 $tmplen = strlen($tmp);
2961 $this->debug("read line of $tmplen bytes: " . trim($tmp));
2964 $this->incoming_payload = $data;
2965 $this->debug('socket read of headers timed out after length ' . strlen($data));
2966 $this->debug("read before timeout: " . $data);
2967 $this->setError('socket read of headers timed out');
2972 $pos = strpos($data,"\r\n\r\n");
2976 $pos = strpos($data,"\n\n");
2981 // remove 100 headers
2982 if (isset($lb) && preg_match('/^HTTP\/1.1 100/',$data)) {
2987 // store header data
2988 $this->incoming_payload .= $data;
2989 $this->debug('found end of headers after length ' . strlen($data));
2991 $header_data = trim(substr($data,0,$pos));
2992 $header_array = explode($lb,$header_data);
2993 $this->incoming_headers = array();
2994 $this->incoming_cookies = array();
2995 foreach($header_array as $header_line){
2996 $arr = explode(':',$header_line, 2);
2997 if(count($arr) > 1){
2998 $header_name = strtolower(trim($arr[0]));
2999 $this->incoming_headers[$header_name] = trim($arr[1]);
3000 if ($header_name == 'set-cookie') {
3001 // TODO: allow multiple cookies from parseCookie
3002 $cookie = $this->parseCookie(trim($arr[1]));
3004 $this->incoming_cookies[] = $cookie;
3005 $this->debug('found cookie: ' . $cookie['name'] . ' = ' . $cookie['value']);
3007 $this->debug('did not find cookie in ' . trim($arr[1]));
3010 } elseif (isset($header_name)) {
3011 // append continuation line to previous header
3012 $this->incoming_headers[$header_name] .= $lb . ' ' . $header_line;
3016 // loop until msg has been received
3017 if (isset($this->incoming_headers['transfer-encoding']) && strtolower($this->incoming_headers['transfer-encoding']) == 'chunked') {
3018 $content_length = 2147483647; // ignore any content-length header
3020 $this->debug("want to read chunked content");
3021 } elseif (isset($this->incoming_headers['content-length'])) {
3022 $content_length = $this->incoming_headers['content-length'];
3024 $this->debug("want to read content of length $content_length");
3026 $content_length = 2147483647;
3028 $this->debug("want to read content to EOF");
3033 $tmp = fgets($this->fp, 256);
3034 $tmplen = strlen($tmp);
3035 $this->debug("read chunk line of $tmplen bytes");
3037 $this->incoming_payload = $data;
3038 $this->debug('socket read of chunk length timed out after length ' . strlen($data));
3039 $this->debug("read before timeout:\n" . $data);
3040 $this->setError('socket read of chunk length timed out');
3043 $content_length = hexdec(trim($tmp));
3044 $this->debug("chunk length $content_length");
3047 while (($strlen < $content_length) && (!feof($this->fp))) {
3048 $readlen = min(8192, $content_length - $strlen);
3049 $tmp = fread($this->fp, $readlen);
3050 $tmplen = strlen($tmp);
3051 $this->debug("read buffer of $tmplen bytes");
3052 if (($tmplen == 0) && (!feof($this->fp))) {
3053 $this->incoming_payload = $data;
3054 $this->debug('socket read of body timed out after length ' . strlen($data));
3055 $this->debug("read before timeout:\n" . $data);
3056 $this->setError('socket read of body timed out');
3062 if ($chunked && ($content_length > 0)) {
3063 $tmp = fgets($this->fp, 256);
3064 $tmplen = strlen($tmp);
3065 $this->debug("read chunk terminator of $tmplen bytes");
3067 $this->incoming_payload = $data;
3068 $this->debug('socket read of chunk terminator timed out after length ' . strlen($data));
3069 $this->debug("read before timeout:\n" . $data);
3070 $this->setError('socket read of chunk terminator timed out');
3074 } while ($chunked && ($content_length > 0) && (!feof($this->fp)));
3075 if (feof($this->fp)) {
3076 $this->debug('read to EOF');
3078 $this->debug('read body of length ' . strlen($data));
3079 $this->incoming_payload .= $data;
3080 $this->debug('received a total of '.strlen($this->incoming_payload).' bytes of data from server');
3082 // close filepointer
3084 (isset($this->incoming_headers['connection']) && strtolower($this->incoming_headers['connection']) == 'close') ||
3085 (! $this->persistentConnection) || feof($this->fp)){
3088 $this->debug('closed socket');
3091 // connection was closed unexpectedly
3092 if($this->incoming_payload == ''){
3093 $this->setError('no response from server');
3097 // decode transfer-encoding
3098 // if(isset($this->incoming_headers['transfer-encoding']) && strtolower($this->incoming_headers['transfer-encoding']) == 'chunked'){
3099 // if(!$data = $this->decodeChunked($data, $lb)){
3100 // $this->setError('Decoding of chunked data failed');
3103 //print "<pre>\nde-chunked:\n---------------\n$data\n\n---------------\n</pre>";
3104 // set decoded payload
3105 // $this->incoming_payload = $header_data.$lb.$lb.$data;
3108 } elseif ($this->io_method() == 'curl') {
3110 $this->debug('send and receive with cURL');
3111 $this->incoming_payload = curl_exec($this->ch);
3112 $data = $this->incoming_payload;
3114 $cErr = curl_error($this->ch);
3116 $err = 'cURL ERROR: '.curl_errno($this->ch).': '.$cErr.'<br>';
3117 // TODO: there is a PHP bug that can cause this to SEGV for CURLINFO_CONTENT_TYPE
3118 foreach(curl_getinfo($this->ch) as $k => $v){
3119 $err .= "$k: $v<br>";
3122 $this->setError($err);
3123 curl_close($this->ch);
3127 //var_dump(curl_getinfo($this->ch));
3131 $this->debug('No cURL error, closing cURL');
3132 curl_close($this->ch);
3134 // try removing skippable headers
3136 while ($this->isSkippableCurlHeader($data)) {
3137 $this->debug("Found HTTP header to skip");
3138 if ($pos = strpos($data,"\r\n\r\n")) {
3139 $data = ltrim(substr($data,$pos));
3140 } elseif($pos = strpos($data,"\n\n") ) {
3141 $data = ltrim(substr($data,$pos));
3146 // have nothing left; just remove 100 header(s)
3148 while (preg_match('/^HTTP\/1.1 100/',$data)) {
3149 if ($pos = strpos($data,"\r\n\r\n")) {
3150 $data = ltrim(substr($data,$pos));
3151 } elseif($pos = strpos($data,"\n\n") ) {
3152 $data = ltrim(substr($data,$pos));
3157 // separate content from HTTP headers
3158 if ($pos = strpos($data,"\r\n\r\n")) {
3160 } elseif( $pos = strpos($data,"\n\n")) {
3163 $this->debug('no proper separation of headers and document');
3164 $this->setError('no proper separation of headers and document');
3167 $header_data = trim(substr($data,0,$pos));
3168 $header_array = explode($lb,$header_data);
3169 $data = ltrim(substr($data,$pos));
3170 $this->debug('found proper separation of headers and document');
3171 $this->debug('cleaned data, stringlen: '.strlen($data));
3173 foreach ($header_array as $header_line) {
3174 $arr = explode(':',$header_line,2);
3175 if(count($arr) > 1){
3176 $header_name = strtolower(trim($arr[0]));
3177 $this->incoming_headers[$header_name] = trim($arr[1]);
3178 if ($header_name == 'set-cookie') {
3179 // TODO: allow multiple cookies from parseCookie
3180 $cookie = $this->parseCookie(trim($arr[1]));
3182 $this->incoming_cookies[] = $cookie;
3183 $this->debug('found cookie: ' . $cookie['name'] . ' = ' . $cookie['value']);
3185 $this->debug('did not find cookie in ' . trim($arr[1]));
3188 } elseif (isset($header_name)) {
3189 // append continuation line to previous header
3190 $this->incoming_headers[$header_name] .= $lb . ' ' . $header_line;
3195 $this->response_status_line = $header_array[0];
3196 $arr = explode(' ', $this->response_status_line, 3);
3197 $http_version = $arr[0];
3198 $http_status = intval($arr[1]);
3199 $http_reason = count($arr) > 2 ? $arr[2] : '';
3201 // see if we need to resend the request with http digest authentication
3202 if (isset($this->incoming_headers['location']) && ($http_status == 301 || $http_status == 302)) {
3203 $this->debug("Got $http_status $http_reason with Location: " . $this->incoming_headers['location']);
3204 $this->setURL($this->incoming_headers['location']);
3205 $this->tryagain = true;
3209 // see if we need to resend the request with http digest authentication
3210 if (isset($this->incoming_headers['www-authenticate']) && $http_status == 401) {
3211 $this->debug("Got 401 $http_reason with WWW-Authenticate: " . $this->incoming_headers['www-authenticate']);
3212 if (strstr($this->incoming_headers['www-authenticate'], "Digest ")) {
3213 $this->debug('Server wants digest authentication');
3214 // remove "Digest " from our elements
3215 $digestString = str_replace('Digest ', '', $this->incoming_headers['www-authenticate']);
3217 // parse elements into array
3218 $digestElements = explode(',', $digestString);
3219 foreach ($digestElements as $val) {
3220 $tempElement = explode('=', trim($val), 2);
3221 $digestRequest[$tempElement[0]] = str_replace("\"", '', $tempElement[1]);
3224 // should have (at least) qop, realm, nonce
3225 if (isset($digestRequest['nonce'])) {
3226 $this->setCredentials($this->username, $this->password, 'digest', $digestRequest);
3227 $this->tryagain = true;
3231 $this->debug('HTTP authentication failed');
3232 $this->setError('HTTP authentication failed');
3237 ($http_status >= 300 && $http_status <= 307) ||
3238 ($http_status >= 400 && $http_status <= 417) ||
3239 ($http_status >= 501 && $http_status <= 505)
3241 $this->setError("Unsupported HTTP response status $http_status $http_reason (soapclient->response has contents of the response)");
3245 // decode content-encoding
3246 if(isset($this->incoming_headers['content-encoding']) && $this->incoming_headers['content-encoding'] != ''){
3247 if(strtolower($this->incoming_headers['content-encoding']) == 'deflate' || strtolower($this->incoming_headers['content-encoding']) == 'gzip'){
3248 // if decoding works, use it. else assume data wasn't gzencoded
3249 if(function_exists('gzinflate')){
3250 //$timer->setMarker('starting decoding of gzip/deflated content');
3251 // IIS 5 requires gzinflate instead of gzuncompress (similar to IE 5 and gzdeflate v. gzcompress)
3252 // this means there are no Zlib headers, although there should be
3253 $this->debug('The gzinflate function exists');
3254 $datalen = strlen($data);
3255 if ($this->incoming_headers['content-encoding'] == 'deflate') {
3256 if ($degzdata = @gzinflate($data)) {
3258 $this->debug('The payload has been inflated to ' . strlen($data) . ' bytes');
3259 if (strlen($data) < $datalen) {
3260 // test for the case that the payload has been compressed twice
3261 $this->debug('The inflated payload is smaller than the gzipped one; try again');
3262 if ($degzdata = @gzinflate($data)) {
3264 $this->debug('The payload has been inflated again to ' . strlen($data) . ' bytes');
3268 $this->debug('Error using gzinflate to inflate the payload');
3269 $this->setError('Error using gzinflate to inflate the payload');
3271 } elseif ($this->incoming_headers['content-encoding'] == 'gzip') {
3272 if ($degzdata = @gzinflate(substr($data, 10))) { // do our best
3274 $this->debug('The payload has been un-gzipped to ' . strlen($data) . ' bytes');
3275 if (strlen($data) < $datalen) {
3276 // test for the case that the payload has been compressed twice
3277 $this->debug('The un-gzipped payload is smaller than the gzipped one; try again');
3278 if ($degzdata = @gzinflate(substr($data, 10))) {
3280 $this->debug('The payload has been un-gzipped again to ' . strlen($data) . ' bytes');
3284 $this->debug('Error using gzinflate to un-gzip the payload');
3285 $this->setError('Error using gzinflate to un-gzip the payload');
3288 //$timer->setMarker('finished decoding of gzip/deflated content');
3289 //print "<xmp>\nde-inflated:\n---------------\n$data\n-------------\n</xmp>";
3290 // set decoded payload
3291 $this->incoming_payload = $header_data.$lb.$lb.$data;
3293 $this->debug('The server sent compressed data. Your php install must have the Zlib extension compiled in to support this.');
3294 $this->setError('The server sent compressed data. Your php install must have the Zlib extension compiled in to support this.');
3297 $this->debug('Unsupported Content-Encoding ' . $this->incoming_headers['content-encoding']);
3298 $this->setError('Unsupported Content-Encoding ' . $this->incoming_headers['content-encoding']);
3301 $this->debug('No Content-Encoding header');
3304 if(strlen($data) == 0){
3305 $this->debug('no data after headers!');
3306 $this->setError('no data present after HTTP headers');
3314 * sets the content-type for the SOAP message to be sent
3316 * @param string $type the content type, MIME style
3317 * @param mixed $charset character set used for encoding (or false)
3320 function setContentType($type, $charset = false) {
3321 $this->setHeader('Content-Type', $type . ($charset ? '; charset=' . $charset : ''));
3325 * specifies that an HTTP persistent connection should be used
3327 * @return boolean whether the request was honored by this method.
3330 function usePersistentConnection(){
3331 if (isset($this->outgoing_headers['Accept-Encoding'])) {
3334 $this->protocol_version = '1.1';
3335 $this->persistentConnection = true;
3336 $this->setHeader('Connection', 'Keep-Alive');
3341 * parse an incoming Cookie into it's parts
3343 * @param string $cookie_str content of cookie
3344 * @return array with data of that cookie
3348 * TODO: allow a Set-Cookie string to be parsed into multiple cookies
3350 function parseCookie($cookie_str) {
3351 $cookie_str = str_replace('; ', ';', $cookie_str) . ';';
3352 $data = preg_split('/;/', $cookie_str);
3353 $value_str = $data[0];
3355 $cookie_param = 'domain=';
3356 $start = strpos($cookie_str, $cookie_param);
3358 $domain = substr($cookie_str, $start + strlen($cookie_param));
3359 $domain = substr($domain, 0, strpos($domain, ';'));
3364 $cookie_param = 'expires=';
3365 $start = strpos($cookie_str, $cookie_param);
3367 $expires = substr($cookie_str, $start + strlen($cookie_param));
3368 $expires = substr($expires, 0, strpos($expires, ';'));
3373 $cookie_param = 'path=';
3374 $start = strpos($cookie_str, $cookie_param);
3376 $path = substr($cookie_str, $start + strlen($cookie_param));
3377 $path = substr($path, 0, strpos($path, ';'));
3382 $cookie_param = ';secure;';
3383 if (strpos($cookie_str, $cookie_param) !== FALSE) {
3389 $sep_pos = strpos($value_str, '=');
3392 $name = substr($value_str, 0, $sep_pos);
3393 $value = substr($value_str, $sep_pos + 1);
3394 $cookie= array( 'name' => $name,
3396 'domain' => $domain,
3398 'expires' => $expires,
3407 * sort out cookies for the current request
3409 * @param array $cookies array with all cookies
3410 * @param boolean $secure is the send-content secure or not?
3411 * @return string for Cookie-HTTP-Header
3414 function getCookiesForRequest($cookies, $secure=false) {
3416 if ((! is_null($cookies)) && (is_array($cookies))) {
3417 foreach ($cookies as $cookie) {
3418 if (! is_array($cookie)) {
3421 $this->debug("check cookie for validity: ".$cookie['name'].'='.$cookie['value']);
3422 if ((isset($cookie['expires'])) && (! empty($cookie['expires']))) {
3423 if (strtotime($cookie['expires']) <= time()) {
3424 $this->debug('cookie has expired');
3428 if ((isset($cookie['domain'])) && (! empty($cookie['domain']))) {
3429 $domain = preg_quote($cookie['domain']);
3430 if (! preg_match("'.*$domain$'i", $this->host)) {
3431 $this->debug('cookie has different domain');
3435 if ((isset($cookie['path'])) && (! empty($cookie['path']))) {
3436 $path = preg_quote($cookie['path']);
3437 if (! preg_match("'^$path.*'i", $this->path)) {
3438 $this->debug('cookie is for a different path');
3442 if ((! $secure) && (isset($cookie['secure'])) && ($cookie['secure'])) {
3443 $this->debug('cookie is secure, transport is not');
3446 $cookie_str .= $cookie['name'] . '=' . $cookie['value'] . '; ';
3447 $this->debug('add cookie to Cookie-String: ' . $cookie['name'] . '=' . $cookie['value']);
3458 * nusoap_server allows the user to create a SOAP server
3459 * that is capable of receiving messages and returning responses
3461 * @author Dietrich Ayala <dietrich@ganx4.com>
3462 * @author Scott Nichol <snichol@users.sourceforge.net>
3466 class nusoap_server extends nusoap_base {
3468 * HTTP headers of request
3472 var $headers = array();
3480 * SOAP headers from request (incomplete namespace resolution; special characters not escaped) (text)
3484 var $requestHeaders = '';
3486 * SOAP Headers from request (parsed)
3490 var $requestHeader = NULL;
3492 * SOAP body request portion (incomplete namespace resolution; special characters not escaped) (text)
3498 * SOAP payload for request (text)
3502 var $requestSOAP = '';
3504 * requested method namespace URI
3508 var $methodURI = '';
3510 * name of method requested
3514 var $methodname = '';
3516 * method parameters from request
3520 var $methodparams = array();
3522 * SOAP Action from request
3526 var $SOAPAction = '';
3528 * character set encoding of incoming (request) messages
3532 var $xml_encoding = '';
3534 * toggles whether the parser decodes element content w/ utf8_decode()
3538 var $decode_utf8 = true;
3541 * HTTP headers of response
3545 var $outgoing_headers = array();
3553 * SOAP headers for response (text or array of soapval or associative array)
3557 var $responseHeaders = '';
3559 * SOAP payload for response (text)
3563 var $responseSOAP = '';
3565 * method return value to place in response
3569 var $methodreturn = false;
3571 * whether $methodreturn is a string of literal XML
3575 var $methodreturnisliteralxml = false;
3577 * SOAP fault for response (or false)
3583 * text indication of result (for debugging)
3587 var $result = 'successful';
3590 * assoc array of operations => opData; operations are added by the register()
3591 * method or by parsing an external WSDL definition
3595 var $operations = array();
3597 * wsdl instance (if one)
3603 * URL for WSDL (if one)
3607 var $externalWSDLURL = false;
3609 * whether to append debug to response as XML comment
3613 var $debug_flag = false;
3617 * the optional parameter is a path to a WSDL file that you'd like to bind the server instance to.
3619 * @param mixed $wsdl file path or URL (string), or wsdl instance (object)
3622 function nusoap_server($wsdl=false){
3623 parent::nusoap_base();
3624 // turn on debugging?
3626 global $HTTP_SERVER_VARS;
3628 if (isset($_SERVER)) {
3629 $this->debug("_SERVER is defined:");
3630 $this->appendDebug($this->varDump($_SERVER));
3631 } elseif (isset($HTTP_SERVER_VARS)) {
3632 $this->debug("HTTP_SERVER_VARS is defined:");
3633 $this->appendDebug($this->varDump($HTTP_SERVER_VARS));
3635 $this->debug("Neither _SERVER nor HTTP_SERVER_VARS is defined.");
3638 if (isset($debug)) {
3639 $this->debug("In nusoap_server, set debug_flag=$debug based on global flag");
3640 $this->debug_flag = $debug;
3641 } elseif (isset($_SERVER['QUERY_STRING'])) {
3642 $qs = explode('&', $_SERVER['QUERY_STRING']);
3643 foreach ($qs as $v) {
3644 if (substr($v, 0, 6) == 'debug=') {
3645 $this->debug("In nusoap_server, set debug_flag=" . substr($v, 6) . " based on query string #1");
3646 $this->debug_flag = substr($v, 6);
3649 } elseif (isset($HTTP_SERVER_VARS['QUERY_STRING'])) {
3650 $qs = explode('&', $HTTP_SERVER_VARS['QUERY_STRING']);
3651 foreach ($qs as $v) {
3652 if (substr($v, 0, 6) == 'debug=') {
3653 $this->debug("In nusoap_server, set debug_flag=" . substr($v, 6) . " based on query string #2");
3654 $this->debug_flag = substr($v, 6);
3661 $this->debug("In nusoap_server, WSDL is specified");
3662 if (is_object($wsdl) && (get_class($wsdl) == 'wsdl')) {
3663 $this->wsdl = $wsdl;
3664 $this->externalWSDLURL = $this->wsdl->wsdl;
3665 $this->debug('Use existing wsdl instance from ' . $this->externalWSDLURL);
3667 $this->debug('Create wsdl from ' . $wsdl);
3668 $this->wsdl = new wsdl($wsdl);
3669 $this->externalWSDLURL = $wsdl;
3671 $this->appendDebug($this->wsdl->getDebug());
3672 $this->wsdl->clearDebug();
3673 if($err = $this->wsdl->getError()){
3674 die('WSDL ERROR: '.$err);
3680 * processes request and returns response
3682 * @param string $data usually is the value of $HTTP_RAW_POST_DATA
3685 function service($data){
3686 global $HTTP_SERVER_VARS;
3688 if (isset($_SERVER['REQUEST_METHOD'])) {
3689 $rm = $_SERVER['REQUEST_METHOD'];
3690 } elseif (isset($HTTP_SERVER_VARS['REQUEST_METHOD'])) {
3691 $rm = $HTTP_SERVER_VARS['REQUEST_METHOD'];
3696 if (isset($_SERVER['QUERY_STRING'])) {
3697 $qs = $_SERVER['QUERY_STRING'];
3698 } elseif (isset($HTTP_SERVER_VARS['QUERY_STRING'])) {
3699 $qs = $HTTP_SERVER_VARS['QUERY_STRING'];
3703 $this->debug("In service, request method=$rm query string=$qs strlen(\$data)=" . strlen($data));
3705 if ($rm == 'POST') {
3706 $this->debug("In service, invoke the request");
3707 $this->parse_request($data);
3708 if (! $this->fault) {
3709 $this->invoke_method();
3711 if (! $this->fault) {
3712 $this->serialize_return();
3714 $this->send_response();
3715 } elseif (preg_match('/wsdl/', $qs) ){
3716 $this->debug("In service, this is a request for WSDL");
3717 if($this->externalWSDLURL){
3718 if (strpos($this->externalWSDLURL, "http://") !== false) { // assume URL
3719 $this->debug("In service, re-direct for WSDL");
3720 header('Location: '.$this->externalWSDLURL);
3721 } else { // assume file
3722 $this->debug("In service, use file passthru for WSDL");
3723 header("Content-Type: text/xml\r\n");
3724 $pos = strpos($this->externalWSDLURL, "file://");
3725 if ($pos === false) {
3726 $filename = $this->externalWSDLURL;
3728 $filename = substr($this->externalWSDLURL, $pos + 7);
3730 $fp = fopen($this->externalWSDLURL, 'r');
3733 } elseif ($this->wsdl) {
3734 $this->debug("In service, serialize WSDL");
3735 header("Content-Type: text/xml; charset=ISO-8859-1\r\n");
3736 print $this->wsdl->serialize($this->debug_flag);
3737 if ($this->debug_flag) {
3738 $this->debug('wsdl:');
3739 $this->appendDebug($this->varDump($this->wsdl));
3740 print $this->getDebugAsXMLComment();
3743 $this->debug("In service, there is no WSDL");
3744 header("Content-Type: text/html; charset=ISO-8859-1\r\n");
3745 print "This service does not provide WSDL";
3747 } elseif ($this->wsdl) {
3748 $this->debug("In service, return Web description");
3749 print $this->wsdl->webDescription();
3751 $this->debug("In service, no Web description");
3752 header("Content-Type: text/html; charset=ISO-8859-1\r\n");
3753 print "This service does not provide a Web description";
3758 * parses HTTP request headers.
3760 * The following fields are set by this function (when successful)
3769 function parse_http_headers() {
3770 global $HTTP_SERVER_VARS;
3772 $this->request = '';
3773 $this->SOAPAction = '';
3774 if(function_exists('getallheaders')){
3775 $this->debug("In parse_http_headers, use getallheaders");
3776 $headers = getallheaders();
3777 foreach($headers as $k=>$v){
3778 $k = strtolower($k);
3779 $this->headers[$k] = $v;
3780 $this->request .= "$k: $v\r\n";
3781 $this->debug("$k: $v");
3783 // get SOAPAction header
3784 if(isset($this->headers['soapaction'])){
3785 $this->SOAPAction = str_replace('"','',$this->headers['soapaction']);
3787 // get the character encoding of the incoming request
3788 if(isset($this->headers['content-type']) && strpos($this->headers['content-type'],'=')){
3789 $enc = str_replace('"','',substr(strstr($this->headers["content-type"],'='),1));
3790 if (preg_match('/^(ISO-8859-1|US-ASCII|UTF-8)$/i', $enc)) {
3791 $this->xml_encoding = strtoupper($enc);
3793 $this->xml_encoding = 'US-ASCII';
3796 // should be US-ASCII for HTTP 1.0 or ISO-8859-1 for HTTP 1.1
3797 $this->xml_encoding = 'ISO-8859-1';
3799 } elseif(isset($_SERVER) && is_array($_SERVER)){
3800 $this->debug("In parse_http_headers, use _SERVER");
3801 foreach ($_SERVER as $k => $v) {
3802 if (substr($k, 0, 5) == 'HTTP_') {
3803 $k = str_replace(' ', '-', strtolower(str_replace('_', ' ', substr($k, 5))));
3805 $k = str_replace(' ', '-', strtolower(str_replace('_', ' ', $k)));
3807 if ($k == 'soapaction') {
3808 // get SOAPAction header
3810 $v = str_replace('"', '', $v);
3811 $v = str_replace('\\', '', $v);
3812 $this->SOAPAction = $v;
3813 } elseif ($k == 'content-type') {
3814 // get the character encoding of the incoming request
3815 if (strpos($v, '=')) {
3816 $enc = substr(strstr($v, '='), 1);
3817 $enc = str_replace('"', '', $enc);
3818 $enc = str_replace('\\', '', $enc);
3819 if (preg_match('/^(ISO-8859-1|US-ASCII|UTF-8)$/i', $enc)) {
3820 $this->xml_encoding = strtoupper($enc);
3822 $this->xml_encoding = 'US-ASCII';
3825 // should be US-ASCII for HTTP 1.0 or ISO-8859-1 for HTTP 1.1
3826 $this->xml_encoding = 'ISO-8859-1';
3829 $this->headers[$k] = $v;
3830 $this->request .= "$k: $v\r\n";
3831 $this->debug("$k: $v");
3833 } elseif (is_array($HTTP_SERVER_VARS)) {
3834 $this->debug("In parse_http_headers, use HTTP_SERVER_VARS");
3835 foreach ($HTTP_SERVER_VARS as $k => $v) {
3836 if (substr($k, 0, 5) == 'HTTP_') {
3837 $k = str_replace(' ', '-', strtolower(str_replace('_', ' ', substr($k, 5)))); $k = strtolower(substr($k, 5));
3839 $k = str_replace(' ', '-', strtolower(str_replace('_', ' ', $k))); $k = strtolower($k);
3841 if ($k == 'soapaction') {
3842 // get SOAPAction header
3844 $v = str_replace('"', '', $v);
3845 $v = str_replace('\\', '', $v);
3846 $this->SOAPAction = $v;
3847 } elseif ($k == 'content-type') {
3848 // get the character encoding of the incoming request
3849 if (strpos($v, '=')) {
3850 $enc = substr(strstr($v, '='), 1);
3851 $enc = str_replace('"', '', $enc);
3852 $enc = str_replace('\\', '', $enc);
3853 if(preg_match('/^(ISO-8859-1|US-ASCII|UTF-8)$/i',$enc)){
3854 $this->xml_encoding = strtoupper($enc);
3856 $this->xml_encoding = 'US-ASCII';
3859 // should be US-ASCII for HTTP 1.0 or ISO-8859-1 for HTTP 1.1
3860 $this->xml_encoding = 'ISO-8859-1';
3863 $this->headers[$k] = $v;
3864 $this->request .= "$k: $v\r\n";
3865 $this->debug("$k: $v");
3868 $this->debug("In parse_http_headers, HTTP headers not accessible");
3869 $this->setError("HTTP headers not accessible");
3876 * The following fields are set by this function (when successful)
3890 * This sets the fault field on error
3892 * @param string $data XML string
3895 function parse_request($data='') {
3896 $this->debug('entering parse_request()');
3897 $this->parse_http_headers();
3898 $this->debug('got character encoding: '.$this->xml_encoding);
3899 // uncompress if necessary
3900 if (isset($this->headers['content-encoding']) && $this->headers['content-encoding'] != '') {
3901 $this->debug('got content encoding: ' . $this->headers['content-encoding']);
3902 if ($this->headers['content-encoding'] == 'deflate' || $this->headers['content-encoding'] == 'gzip') {
3903 // if decoding works, use it. else assume data wasn't gzencoded
3904 if (function_exists('gzuncompress')) {
3905 if ($this->headers['content-encoding'] == 'deflate' && $degzdata = @gzuncompress($data)) {
3907 } elseif ($this->headers['content-encoding'] == 'gzip' && $degzdata = gzinflate(substr($data, 10))) {
3910 $this->fault('SOAP-ENV:Client', 'Errors occurred when trying to decode the data');
3914 $this->fault('SOAP-ENV:Client', 'This Server does not support compressed data');
3919 $this->request .= "\r\n".$data;
3920 $data = $this->parseRequest($this->headers, $data);
3921 $this->requestSOAP = $data;
3922 $this->debug('leaving parse_request');
3926 * invokes a PHP function for the requested SOAP method
3928 * The following fields are set by this function (when successful)
3932 * Note that the PHP function that is called may also set the following
3933 * fields to affect the response sent to the client
3938 * This sets the fault field on error
3942 function invoke_method() {
3943 $this->debug('in invoke_method, methodname=' . $this->methodname . ' methodURI=' . $this->methodURI . ' SOAPAction=' . $this->SOAPAction);
3946 // if you are debugging in this area of the code, your service uses a class to implement methods,
3947 // you use SOAP RPC, and the client is .NET, please be aware of the following...
3948 // when the .NET wsdl.exe utility generates a proxy, it will remove the '.' or '..' from the
3949 // method name. that is fine for naming the .NET methods. it is not fine for properly constructing
3950 // the XML request and reading the XML response. you need to add the RequestElementName and
3951 // ResponseElementName to the System.Web.Services.Protocols.SoapRpcMethodAttribute that wsdl.exe
3952 // generates for the method. these parameters are used to specify the correct XML element names
3953 // for .NET to use, i.e. the names with the '.' in them.
3955 $orig_methodname = $this->methodname;
3957 if ($this->opData = $this->wsdl->getOperationData($this->methodname)) {
3958 $this->debug('in invoke_method, found WSDL operation=' . $this->methodname);
3959 $this->appendDebug('opData=' . $this->varDump($this->opData));
3960 } elseif ($this->opData = $this->wsdl->getOperationDataForSoapAction($this->SOAPAction)) {
3961 // Note: hopefully this case will only be used for doc/lit, since rpc services should have wrapper element
3962 $this->debug('in invoke_method, found WSDL soapAction=' . $this->SOAPAction . ' for operation=' . $this->opData['name']);
3963 $this->appendDebug('opData=' . $this->varDump($this->opData));
3964 $this->methodname = $this->opData['name'];
3966 $this->debug('in invoke_method, no WSDL for operation=' . $this->methodname);
3967 $this->fault('SOAP-ENV:Client', "Operation '" . $this->methodname . "' is not defined in the WSDL for this service");
3971 $this->debug('in invoke_method, no WSDL to validate method');
3974 // if a . is present in $this->methodname, we see if there is a class in scope,
3975 // which could be referred to. We will also distinguish between two deliminators,
3976 // to allow methods to be called a the class or an instance
3977 if (strpos($this->methodname, '..') > 0) {
3979 } elseif (strpos($this->methodname, '.') > 0) {
3984 $this->debug("in invoke_method, delim=$delim");
3988 if (strlen($delim) > 0 && substr_count($this->methodname, $delim) == 1) {
3989 $try_class = substr($this->methodname, 0, strpos($this->methodname, $delim));
3990 if (class_exists($try_class)) {
3991 // get the class and method name
3992 $class = $try_class;
3993 $method = substr($this->methodname, strpos($this->methodname, $delim) + strlen($delim));
3994 $this->debug("in invoke_method, class=$class method=$method delim=$delim");
3996 $this->debug("in invoke_method, class=$try_class not found");
4000 $this->debug("in invoke_method, no class to try");
4003 // does method exist?
4005 if (!function_exists($this->methodname)) {
4006 $this->debug("in invoke_method, function '$this->methodname' not found!");
4007 $this->result = 'fault: method not found';
4008 $this->fault('SOAP-ENV:Client',"method '$this->methodname'('$orig_methodname') not defined in service('$try_class' '$delim')");
4012 $method_to_compare = (substr(phpversion(), 0, 2) == '4.') ? strtolower($method) : $method;
4013 if (!in_array($method_to_compare, get_class_methods($class))) {
4014 $this->debug("in invoke_method, method '$this->methodname' not found in class '$class'!");
4015 $this->result = 'fault: method not found';
4016 $this->fault('SOAP-ENV:Client',"method '$this->methodname'/'$method_to_compare'('$orig_methodname') not defined in service/'$class'('$try_class' '$delim')");
4021 // evaluate message, getting back parameters
4022 // verify that request parameters match the method's signature
4023 if(! $this->verify_method($this->methodname,$this->methodparams)){
4025 $this->debug('ERROR: request not verified against method signature');
4026 $this->result = 'fault: request failed validation against method signature';
4028 $this->fault('SOAP-ENV:Client',"Operation '$this->methodname' not defined in service.");
4032 // if there are parameters to pass
4033 $this->debug('in invoke_method, params:');
4034 $this->appendDebug($this->varDump($this->methodparams));
4035 $this->debug("in invoke_method, calling '$this->methodname'");
4036 if (!function_exists('call_user_func_array')) {
4038 $this->debug('in invoke_method, calling function using eval()');
4039 $funcCall = "\$this->methodreturn = $this->methodname(";
4041 if ($delim == '..') {
4042 $this->debug('in invoke_method, calling class method using eval()');
4043 $funcCall = "\$this->methodreturn = ".$class."::".$method."(";
4045 $this->debug('in invoke_method, calling instance method using eval()');
4046 // generate unique instance name
4047 $instname = "\$inst_".time();
4048 $funcCall = $instname." = new ".$class."(); ";
4049 $funcCall .= "\$this->methodreturn = ".$instname."->".$method."(";
4052 if ($this->methodparams) {
4053 foreach ($this->methodparams as $param) {
4054 if (is_array($param) || is_object($param)) {
4055 $this->fault('SOAP-ENV:Client', 'NuSOAP does not handle complexType parameters correctly when using eval; call_user_func_array must be available');
4058 $funcCall .= "\"$param\",";
4060 $funcCall = substr($funcCall, 0, -1);
4063 $this->debug('in invoke_method, function call: '.$funcCall);
4067 $this->debug('in invoke_method, calling function using call_user_func_array()');
4068 $call_arg = "$this->methodname"; // straight assignment changes $this->methodname to lower case after call_user_func_array()
4069 } elseif ($delim == '..') {
4070 $this->debug('in invoke_method, calling class method using call_user_func_array()');
4071 $call_arg = array ($class, $method);
4073 $this->debug('in invoke_method, calling instance method using call_user_func_array()');
4074 $instance = new $class ();
4075 $call_arg = array(&$instance, $method);
4077 if (is_array($this->methodparams)) {
4078 $this->methodreturn = call_user_func_array($call_arg, array_values($this->methodparams));
4080 $this->methodreturn = call_user_func_array($call_arg, array());
4083 $this->debug('in invoke_method, methodreturn:');
4084 $this->appendDebug($this->varDump($this->methodreturn));
4085 $this->debug("in invoke_method, called method $this->methodname, received data of type ".gettype($this->methodreturn));
4089 * serializes the return value from a PHP function into a full SOAP Envelope
4091 * The following fields are set by this function (when successful)
4095 * This sets the fault field on error
4099 function serialize_return() {
4100 $this->debug('Entering serialize_return methodname: ' . $this->methodname . ' methodURI: ' . $this->methodURI);
4102 if (isset($this->methodreturn) && is_object($this->methodreturn) && ((get_class($this->methodreturn) == 'soap_fault') || (get_class($this->methodreturn) == 'nusoap_fault'))) {
4103 $this->debug('got a fault object from method');
4104 $this->fault = $this->methodreturn;
4106 } elseif ($this->methodreturnisliteralxml) {
4107 $return_val = $this->methodreturn;
4108 // returned value(s)
4110 $this->debug('got a(n) '.gettype($this->methodreturn).' from method');
4111 $this->debug('serializing return value');
4113 if (sizeof($this->opData['output']['parts']) > 1) {
4114 $this->debug('more than one output part, so use the method return unchanged');
4115 $opParams = $this->methodreturn;
4116 } elseif (sizeof($this->opData['output']['parts']) == 1) {
4117 $this->debug('exactly one output part, so wrap the method return in a simple array');
4118 // TODO: verify that it is not already wrapped!
4119 //foreach ($this->opData['output']['parts'] as $name => $type) {
4120 // $this->debug('wrap in element named ' . $name);
4122 $opParams = array($this->methodreturn);
4124 $return_val = $this->wsdl->serializeRPCParameters($this->methodname,'output',$opParams);
4125 $this->appendDebug($this->wsdl->getDebug());
4126 $this->wsdl->clearDebug();
4127 if($errstr = $this->wsdl->getError()){
4128 $this->debug('got wsdl error: '.$errstr);
4129 $this->fault('SOAP-ENV:Server', 'unable to serialize result');
4133 if (isset($this->methodreturn)) {
4134 $return_val = $this->serialize_val($this->methodreturn, 'return');
4137 $this->debug('in absence of WSDL, assume void return for backward compatibility');
4141 $this->debug('return value:');
4142 $this->appendDebug($this->varDump($return_val));
4144 $this->debug('serializing response');
4146 $this->debug('have WSDL for serialization: style is ' . $this->opData['style']);
4147 if ($this->opData['style'] == 'rpc') {
4148 $this->debug('style is rpc for serialization: use is ' . $this->opData['output']['use']);
4149 if ($this->opData['output']['use'] == 'literal') {
4150 // http://www.ws-i.org/Profiles/BasicProfile-1.1-2004-08-24.html R2735 says rpc/literal accessor elements should not be in a namespace
4151 if ($this->methodURI) {
4152 $payload = '<ns1:'.$this->methodname.'Response xmlns:ns1="'.$this->methodURI.'">'.$return_val.'</ns1:'.$this->methodname."Response>";
4154 $payload = '<'.$this->methodname.'Response>'.$return_val.'</'.$this->methodname.'Response>';
4157 if ($this->methodURI) {
4158 $payload = '<ns1:'.$this->methodname.'Response xmlns:ns1="'.$this->methodURI.'">'.$return_val.'</ns1:'.$this->methodname."Response>";
4160 $payload = '<'.$this->methodname.'Response>'.$return_val.'</'.$this->methodname.'Response>';
4164 $this->debug('style is not rpc for serialization: assume document');
4165 $payload = $return_val;
4168 $this->debug('do not have WSDL for serialization: assume rpc/encoded');
4169 $payload = '<ns1:'.$this->methodname.'Response xmlns:ns1="'.$this->methodURI.'">'.$return_val.'</ns1:'.$this->methodname."Response>";
4171 $this->result = 'successful';
4173 //if($this->debug_flag){
4174 $this->appendDebug($this->wsdl->getDebug());
4176 if (isset($this->opData['output']['encodingStyle'])) {
4177 $encodingStyle = $this->opData['output']['encodingStyle'];
4179 $encodingStyle = '';
4181 // Added: In case we use a WSDL, return a serialized env. WITH the usedNamespaces.
4182 $this->responseSOAP = $this->serializeEnvelope($payload,$this->responseHeaders,$this->wsdl->usedNamespaces,$this->opData['style'],$this->opData['output']['use'],$encodingStyle);
4184 $this->responseSOAP = $this->serializeEnvelope($payload,$this->responseHeaders);
4186 $this->debug("Leaving serialize_return");
4190 * sends an HTTP response
4192 * The following fields are set by this function (when successful)
4199 function send_response() {
4200 $this->debug('Enter send_response');
4202 $payload = $this->fault->serialize();
4203 $this->outgoing_headers[] = "HTTP/1.0 500 Internal Server Error";
4204 $this->outgoing_headers[] = "Status: 500 Internal Server Error";
4206 $payload = $this->responseSOAP;
4207 // Some combinations of PHP+Web server allow the Status
4208 // to come through as a header. Since OK is the default
4210 // $this->outgoing_headers[] = "HTTP/1.0 200 OK";
4211 // $this->outgoing_headers[] = "Status: 200 OK";
4213 // add debug data if in debug mode
4214 if(isset($this->debug_flag) && $this->debug_flag){
4215 $payload .= $this->getDebugAsXMLComment();
4217 $this->outgoing_headers[] = "Server: $this->title Server v$this->version";
4218 preg_match('/\$Revisio' . 'n: ([^ ]+)/', $this->revision, $rev);
4219 $this->outgoing_headers[] = "X-SOAP-Server: $this->title/$this->version (".$rev[1].")";
4220 // Let the Web server decide about this
4221 //$this->outgoing_headers[] = "Connection: Close\r\n";
4222 $payload = $this->getHTTPBody($payload);
4223 $type = $this->getHTTPContentType();
4224 $charset = $this->getHTTPContentTypeCharset();
4225 $this->outgoing_headers[] = "Content-Type: $type" . ($charset ? '; charset=' . $charset : '');
4226 //begin code to compress payload - by John
4227 // NOTE: there is no way to know whether the Web server will also compress
4229 if (strlen($payload) > 1024 && isset($this->headers) && isset($this->headers['accept-encoding'])) {
4230 if (strstr($this->headers['accept-encoding'], 'gzip')) {
4231 if (function_exists('gzencode')) {
4232 if (isset($this->debug_flag) && $this->debug_flag) {
4233 $payload .= "<!-- Content being gzipped -->";
4235 $this->outgoing_headers[] = "Content-Encoding: gzip";
4236 $payload = gzencode($payload);
4238 if (isset($this->debug_flag) && $this->debug_flag) {
4239 $payload .= "<!-- Content will not be gzipped: no gzencode -->";
4242 } elseif (strstr($this->headers['accept-encoding'], 'deflate')) {
4243 // Note: MSIE requires gzdeflate output (no Zlib header and checksum),
4244 // instead of gzcompress output,
4245 // which conflicts with HTTP 1.1 spec (http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.5)
4246 if (function_exists('gzdeflate')) {
4247 if (isset($this->debug_flag) && $this->debug_flag) {
4248 $payload .= "<!-- Content being deflated -->";
4250 $this->outgoing_headers[] = "Content-Encoding: deflate";
4251 $payload = gzdeflate($payload);
4253 if (isset($this->debug_flag) && $this->debug_flag) {
4254 $payload .= "<!-- Content will not be deflated: no gzcompress -->";
4260 $this->outgoing_headers[] = "Content-Length: ".strlen($payload);
4261 reset($this->outgoing_headers);
4262 foreach($this->outgoing_headers as $hdr){
4263 header($hdr, false);
4266 $this->response = join("\r\n",$this->outgoing_headers)."\r\n\r\n".$payload;
4270 * takes the value that was created by parsing the request
4271 * and compares to the method's signature, if available.
4273 * @param string $operation The operation to be invoked
4274 * @param array $request The array of parameter values
4275 * @return boolean Whether the operation was found
4278 function verify_method($operation,$request){
4279 if(isset($this->wsdl) && is_object($this->wsdl)){
4280 if($this->wsdl->getOperationData($operation)){
4283 } elseif(isset($this->operations[$operation])){
4290 * processes SOAP message received from client
4292 * @param array $headers The HTTP headers
4293 * @param string $data unprocessed request data from client
4294 * @return mixed value of the message, decoded into a PHP type
4297 function parseRequest($headers, $data) {
4298 $this->debug('Entering parseRequest() for data of length ' . strlen($data) . ' headers:');
4299 $this->appendDebug($this->varDump($headers));
4300 if (!isset($headers['content-type'])) {
4301 $this->setError('Request not of type text/xml (no content-type header)');
4304 if (!strstr($headers['content-type'], 'text/xml')) {
4305 $this->setError('Request not of type text/xml');
4308 if (strpos($headers['content-type'], '=')) {
4309 $enc = str_replace('"', '', substr(strstr($headers["content-type"], '='), 1));
4310 $this->debug('Got response encoding: ' . $enc);
4311 if(preg_match('/^(ISO-8859-1|US-ASCII|UTF-8)$/i',$enc)){
4312 $this->xml_encoding = strtoupper($enc);
4314 $this->xml_encoding = 'US-ASCII';
4317 // should be US-ASCII for HTTP 1.0 or ISO-8859-1 for HTTP 1.1
4318 $this->xml_encoding = 'ISO-8859-1';
4320 $this->debug('Use encoding: ' . $this->xml_encoding . ' when creating nusoap_parser');
4321 // parse response, get soap parser obj
4322 $parser = new nusoap_parser($data,$this->xml_encoding,'',$this->decode_utf8);
4324 $this->debug("parser debug: \n".$parser->getDebug());
4325 // if fault occurred during message parsing
4326 if($err = $parser->getError()){
4327 $this->result = 'fault: error in msg parsing: '.$err;
4328 $this->fault('SOAP-ENV:Client',"error in msg parsing:\n".$err);
4329 // else successfully parsed request into soapval object
4331 // get/set methodname
4332 $this->methodURI = $parser->root_struct_namespace;
4333 $this->methodname = $parser->root_struct_name;
4334 $this->debug('methodname: '.$this->methodname.' methodURI: '.$this->methodURI);
4335 $this->debug('calling parser->get_soapbody()');
4336 $this->methodparams = $parser->get_soapbody();
4338 $this->requestHeaders = $parser->getHeaders();
4340 $this->requestHeader = $parser->get_soapheader();
4341 // add document for doclit support
4342 $this->document = $parser->document;
4347 * gets the HTTP body for the current response.
4349 * @param string $soapmsg The SOAP payload
4350 * @return string The HTTP body, which includes the SOAP payload
4353 function getHTTPBody($soapmsg) {
4358 * gets the HTTP content type for the current response.
4360 * Note: getHTTPBody must be called before this.
4362 * @return string the HTTP content type for the current response.
4365 function getHTTPContentType() {
4370 * gets the HTTP content type charset for the current response.
4371 * returns false for non-text content types.
4373 * Note: getHTTPBody must be called before this.
4375 * @return string the HTTP content type charset for the current response.
4378 function getHTTPContentTypeCharset() {
4379 return $this->soap_defencoding;
4383 * add a method to the dispatch map (this has been replaced by the register method)
4385 * @param string $methodname
4386 * @param string $in array of input values
4387 * @param string $out array of output values
4391 function add_to_map($methodname,$in,$out){
4392 $this->operations[$methodname] = array('name' => $methodname,'in' => $in,'out' => $out);
4396 * register a service function with the server
4398 * @param string $name the name of the PHP function, class.method or class..method
4399 * @param array $in assoc array of input values: key = param name, value = param type
4400 * @param array $out assoc array of output values: key = param name, value = param type
4401 * @param mixed $namespace the element namespace for the method or false
4402 * @param mixed $soapaction the soapaction for the method or false
4403 * @param mixed $style optional (rpc|document) or false Note: when 'document' is specified, parameter and return wrappers are created for you automatically
4404 * @param mixed $use optional (encoded|literal) or false
4405 * @param string $documentation optional Description to include in WSDL
4406 * @param string $encodingStyle optional (usually 'http://schemas.xmlsoap.org/soap/encoding/' for encoded)
4409 function register($name,$in=array(),$out=array(),$namespace=false,$soapaction=false,$style=false,$use=false,$documentation='',$encodingStyle=''){
4410 global $HTTP_SERVER_VARS;
4412 if($this->externalWSDLURL){
4413 die('You cannot bind to an external WSDL file, and register methods outside of it! Please choose either WSDL or no WSDL.');
4416 die('You must specify a name when you register an operation');
4418 if (!is_array($in)) {
4419 die('You must provide an array for operation inputs');
4421 if (!is_array($out)) {
4422 die('You must provide an array for operation outputs');
4424 if(false == $namespace) {
4426 if(false == $soapaction) {
4427 if (isset($_SERVER)) {
4428 $SERVER_NAME = $_SERVER['SERVER_NAME'];
4429 $SCRIPT_NAME = isset($_SERVER['PHP_SELF']) ? $_SERVER['PHP_SELF'] : $_SERVER['SCRIPT_NAME'];
4430 $HTTPS = isset($_SERVER['HTTPS']) ? $_SERVER['HTTPS'] : (isset($HTTP_SERVER_VARS['HTTPS']) ? $HTTP_SERVER_VARS['HTTPS'] : 'off');
4431 } elseif (isset($HTTP_SERVER_VARS)) {
4432 $SERVER_NAME = $HTTP_SERVER_VARS['SERVER_NAME'];
4433 $SCRIPT_NAME = isset($HTTP_SERVER_VARS['PHP_SELF']) ? $HTTP_SERVER_VARS['PHP_SELF'] : $HTTP_SERVER_VARS['SCRIPT_NAME'];
4434 $HTTPS = isset($HTTP_SERVER_VARS['HTTPS']) ? $HTTP_SERVER_VARS['HTTPS'] : 'off';
4436 $this->setError("Neither _SERVER nor HTTP_SERVER_VARS is available");
4438 if ($HTTPS == '1' || $HTTPS == 'on') {
4443 $soapaction = "$SCHEME://$SERVER_NAME$SCRIPT_NAME/$name";
4445 if(false == $style) {
4451 if ($use == 'encoded' && $encodingStyle == '') {
4452 $encodingStyle = 'http://schemas.xmlsoap.org/soap/encoding/';
4455 $this->operations[$name] = array(
4459 'namespace' => $namespace,
4460 'soapaction' => $soapaction,
4463 $this->wsdl->addOperation($name,$in,$out,$namespace,$soapaction,$style,$use,$documentation,$encodingStyle);
4469 * Specify a fault to be returned to the client.
4470 * This also acts as a flag to the server that a fault has occured.
4472 * @param string $faultcode
4473 * @param string $faultstring
4474 * @param string $faultactor
4475 * @param string $faultdetail
4478 function fault($faultcode,$faultstring,$faultactor='',$faultdetail=''){
4479 if ($faultdetail == '' && $this->debug_flag) {
4480 $faultdetail = $this->getDebug();
4482 $this->fault = new nusoap_fault($faultcode,$faultactor,$faultstring,$faultdetail);
4483 $this->fault->soap_defencoding = $this->soap_defencoding;
4487 * Sets up wsdl object.
4488 * Acts as a flag to enable internal WSDL generation
4490 * @param string $serviceName, name of the service
4491 * @param mixed $namespace optional 'tns' service namespace or false
4492 * @param mixed $endpoint optional URL of service endpoint or false
4493 * @param string $style optional (rpc|document) WSDL style (also specified by operation)
4494 * @param string $transport optional SOAP transport
4495 * @param mixed $schemaTargetNamespace optional 'types' targetNamespace for service schema or false
4497 function configureWSDL($serviceName,$namespace = false,$endpoint = false,$style='rpc', $transport = 'http://schemas.xmlsoap.org/soap/http', $schemaTargetNamespace = false)
4499 global $HTTP_SERVER_VARS;
4501 if (isset($_SERVER)) {
4502 $SERVER_NAME = $_SERVER['SERVER_NAME'];
4503 $SERVER_PORT = $_SERVER['SERVER_PORT'];
4504 $SCRIPT_NAME = isset($_SERVER['PHP_SELF']) ? $_SERVER['PHP_SELF'] : $_SERVER['SCRIPT_NAME'];
4505 $HTTPS = isset($_SERVER['HTTPS']) ? $_SERVER['HTTPS'] : (isset($HTTP_SERVER_VARS['HTTPS']) ? $HTTP_SERVER_VARS['HTTPS'] : 'off');
4506 } elseif (isset($HTTP_SERVER_VARS)) {
4507 $SERVER_NAME = $HTTP_SERVER_VARS['SERVER_NAME'];
4508 $SERVER_PORT = $HTTP_SERVER_VARS['SERVER_PORT'];
4509 $SCRIPT_NAME = isset($HTTP_SERVER_VARS['PHP_SELF']) ? $HTTP_SERVER_VARS['PHP_SELF'] : $HTTP_SERVER_VARS['SCRIPT_NAME'];
4510 $HTTPS = isset($HTTP_SERVER_VARS['HTTPS']) ? $HTTP_SERVER_VARS['HTTPS'] : 'off';
4512 $this->setError("Neither _SERVER nor HTTP_SERVER_VARS is available");
4514 // If server name has port number attached then strip it (else port number gets duplicated in WSDL output) (occurred using lighttpd and FastCGI)
4515 $colon = strpos($SERVER_NAME,":");
4517 $SERVER_NAME = substr($SERVER_NAME, 0, $colon);
4519 if ($SERVER_PORT == 80) {
4522 $SERVER_PORT = ':' . $SERVER_PORT;
4524 if(false == $namespace) {
4525 $namespace = "http://$SERVER_NAME/soap/$serviceName";
4528 if(false == $endpoint) {
4529 if ($HTTPS == '1' || $HTTPS == 'on') {
4534 $endpoint = "$SCHEME://$SERVER_NAME$SERVER_PORT$SCRIPT_NAME";
4537 if(false == $schemaTargetNamespace) {
4538 $schemaTargetNamespace = $namespace;
4541 $this->wsdl = new wsdl;
4542 $this->wsdl->serviceName = $serviceName;
4543 $this->wsdl->endpoint = $endpoint;
4544 $this->wsdl->namespaces['tns'] = $namespace;
4545 $this->wsdl->namespaces['soap'] = 'http://schemas.xmlsoap.org/wsdl/soap/';
4546 $this->wsdl->namespaces['wsdl'] = 'http://schemas.xmlsoap.org/wsdl/';
4547 if ($schemaTargetNamespace != $namespace) {
4548 $this->wsdl->namespaces['types'] = $schemaTargetNamespace;
4550 $this->wsdl->schemas[$schemaTargetNamespace][0] = new nusoap_xmlschema('', '', $this->wsdl->namespaces);
4551 if ($style == 'document') {
4552 $this->wsdl->schemas[$schemaTargetNamespace][0]->schemaInfo['elementFormDefault'] = 'qualified';
4554 $this->wsdl->schemas[$schemaTargetNamespace][0]->schemaTargetNamespace = $schemaTargetNamespace;
4555 $this->wsdl->schemas[$schemaTargetNamespace][0]->imports['http://schemas.xmlsoap.org/soap/encoding/'][0] = array('location' => '', 'loaded' => true);
4556 $this->wsdl->schemas[$schemaTargetNamespace][0]->imports['http://schemas.xmlsoap.org/wsdl/'][0] = array('location' => '', 'loaded' => true);
4557 $this->wsdl->bindings[$serviceName.'Binding'] = array(
4558 'name'=>$serviceName.'Binding',
4560 'transport'=>$transport,
4561 'portType'=>$serviceName.'PortType');
4562 $this->wsdl->ports[$serviceName.'Port'] = array(
4563 'binding'=>$serviceName.'Binding',
4564 'location'=>$endpoint,
4565 'bindingType'=>'http://schemas.xmlsoap.org/wsdl/soap/');
4570 * Backward compatibility
4572 class soap_server extends nusoap_server {
4578 * parses a WSDL file, allows access to it's data, other utility methods.
4579 * also builds WSDL structures programmatically.
4581 * @author Dietrich Ayala <dietrich@ganx4.com>
4582 * @author Scott Nichol <snichol@users.sourceforge.net>
4586 class wsdl extends nusoap_base {
4587 // URL or filename of the root of this WSDL
4589 // define internal arrays of bindings, ports, operations, messages, etc.
4590 var $schemas = array();
4592 var $message = array();
4593 var $complexTypes = array();
4594 var $messages = array();
4595 var $currentMessage;
4596 var $currentOperation;
4597 var $portTypes = array();
4598 var $currentPortType;
4599 var $bindings = array();
4600 var $currentBinding;
4601 var $ports = array();
4603 var $opData = array();
4605 var $documentation = false;
4607 // array of wsdl docs to import
4608 var $import = array();
4613 var $depth_array = array();
4615 var $proxyhost = '';
4616 var $proxyport = '';
4617 var $proxyusername = '';
4618 var $proxypassword = '';
4620 var $response_timeout = 30;
4621 var $curl_options = array(); // User-specified cURL options
4622 var $use_curl = false; // whether to always try to use cURL
4623 // for HTTP authentication
4624 var $username = ''; // Username for HTTP authentication
4625 var $password = ''; // Password for HTTP authentication
4626 var $authtype = ''; // Type of HTTP authentication
4627 var $certRequest = array(); // Certificate for HTTP SSL authentication
4632 * @param string $wsdl WSDL document URL
4633 * @param string $proxyhost
4634 * @param string $proxyport
4635 * @param string $proxyusername
4636 * @param string $proxypassword
4637 * @param integer $timeout set the connection timeout
4638 * @param integer $response_timeout set the response timeout
4639 * @param array $curl_options user-specified cURL options
4640 * @param boolean $use_curl try to use cURL
4643 function wsdl($wsdl = '',$proxyhost=false,$proxyport=false,$proxyusername=false,$proxypassword=false,$timeout=0,$response_timeout=30,$curl_options=null,$use_curl=false){
4644 parent::nusoap_base();
4645 $this->debug("ctor wsdl=$wsdl timeout=$timeout response_timeout=$response_timeout");
4646 $this->proxyhost = $proxyhost;
4647 $this->proxyport = $proxyport;
4648 $this->proxyusername = $proxyusername;
4649 $this->proxypassword = $proxypassword;
4650 $this->timeout = $timeout;
4651 $this->response_timeout = $response_timeout;
4652 if (is_array($curl_options))
4653 $this->curl_options = $curl_options;
4654 $this->use_curl = $use_curl;
4655 $this->fetchWSDL($wsdl);
4659 * fetches the WSDL document and parses it
4663 function fetchWSDL($wsdl) {
4664 $this->debug("parse and process WSDL path=$wsdl");
4665 $this->wsdl = $wsdl;
4667 if ($this->wsdl != "") {
4668 $this->parseWSDL($this->wsdl);
4671 // TODO: handle imports more properly, grabbing them in-line and nesting them
4672 $imported_urls = array();
4674 while ($imported > 0) {
4677 foreach ($this->schemas as $ns => $list) {
4678 foreach ($list as $xs) {
4679 $wsdlparts = parse_url($this->wsdl); // this is bogusly simple!
4680 foreach ($xs->imports as $ns2 => $list2) {
4681 for ($ii = 0; $ii < count($list2); $ii++) {
4682 if (! $list2[$ii]['loaded']) {
4683 $this->schemas[$ns]->imports[$ns2][$ii]['loaded'] = true;
4684 $url = $list2[$ii]['location'];
4686 $urlparts = parse_url($url);
4687 if (!isset($urlparts['host'])) {
4688 $url = $wsdlparts['scheme'] . '://' . $wsdlparts['host'] . (isset($wsdlparts['port']) ? ':' .$wsdlparts['port'] : '') .
4689 substr($wsdlparts['path'],0,strrpos($wsdlparts['path'],'/') + 1) .$urlparts['path'];
4691 if (! in_array($url, $imported_urls)) {
4692 $this->parseWSDL($url);
4694 $imported_urls[] = $url;
4697 $this->debug("Unexpected scenario: empty URL for unloaded import");
4705 $wsdlparts = parse_url($this->wsdl); // this is bogusly simple!
4706 foreach ($this->import as $ns => $list) {
4707 for ($ii = 0; $ii < count($list); $ii++) {
4708 if (! $list[$ii]['loaded']) {
4709 $this->import[$ns][$ii]['loaded'] = true;
4710 $url = $list[$ii]['location'];
4712 $urlparts = parse_url($url);
4713 if (!isset($urlparts['host'])) {
4714 $url = $wsdlparts['scheme'] . '://' . $wsdlparts['host'] . (isset($wsdlparts['port']) ? ':' . $wsdlparts['port'] : '') .
4715 substr($wsdlparts['path'],0,strrpos($wsdlparts['path'],'/') + 1) .$urlparts['path'];
4717 if (! in_array($url, $imported_urls)) {
4718 $this->parseWSDL($url);
4720 $imported_urls[] = $url;
4723 $this->debug("Unexpected scenario: empty URL for unloaded import");
4729 // add new data to operation data
4730 foreach($this->bindings as $binding => $bindingData) {
4731 if (isset($bindingData['operations']) && is_array($bindingData['operations'])) {
4732 foreach($bindingData['operations'] as $operation => $data) {
4733 $this->debug('post-parse data gathering for ' . $operation);
4734 $this->bindings[$binding]['operations'][$operation]['input'] =
4735 isset($this->bindings[$binding]['operations'][$operation]['input']) ?
4736 array_merge($this->bindings[$binding]['operations'][$operation]['input'], $this->portTypes[ $bindingData['portType'] ][$operation]['input']) :
4737 $this->portTypes[ $bindingData['portType'] ][$operation]['input'];
4738 $this->bindings[$binding]['operations'][$operation]['output'] =
4739 isset($this->bindings[$binding]['operations'][$operation]['output']) ?
4740 array_merge($this->bindings[$binding]['operations'][$operation]['output'], $this->portTypes[ $bindingData['portType'] ][$operation]['output']) :
4741 $this->portTypes[ $bindingData['portType'] ][$operation]['output'];
4742 if(isset($this->messages[ $this->bindings[$binding]['operations'][$operation]['input']['message'] ])){
4743 $this->bindings[$binding]['operations'][$operation]['input']['parts'] = $this->messages[ $this->bindings[$binding]['operations'][$operation]['input']['message'] ];
4745 if(isset($this->messages[ $this->bindings[$binding]['operations'][$operation]['output']['message'] ])){
4746 $this->bindings[$binding]['operations'][$operation]['output']['parts'] = $this->messages[ $this->bindings[$binding]['operations'][$operation]['output']['message'] ];
4748 // Set operation style if necessary, but do not override one already provided
4749 if (isset($bindingData['style']) && !isset($this->bindings[$binding]['operations'][$operation]['style'])) {
4750 $this->bindings[$binding]['operations'][$operation]['style'] = $bindingData['style'];
4752 $this->bindings[$binding]['operations'][$operation]['transport'] = isset($bindingData['transport']) ? $bindingData['transport'] : '';
4753 $this->bindings[$binding]['operations'][$operation]['documentation'] = isset($this->portTypes[ $bindingData['portType'] ][$operation]['documentation']) ? $this->portTypes[ $bindingData['portType'] ][$operation]['documentation'] : '';
4754 $this->bindings[$binding]['operations'][$operation]['endpoint'] = isset($bindingData['endpoint']) ? $bindingData['endpoint'] : '';
4761 * parses the wsdl document
4763 * @param string $wsdl path or URL
4766 function parseWSDL($wsdl = '') {
4767 $this->debug("parse WSDL at path=$wsdl");
4770 $this->debug('no wsdl passed to parseWSDL()!!');
4771 $this->setError('no wsdl passed to parseWSDL()!!');
4775 // parse $wsdl for url format
4776 $wsdl_props = parse_url($wsdl);
4778 if (isset($wsdl_props['scheme']) && ($wsdl_props['scheme'] == 'http' || $wsdl_props['scheme'] == 'https')) {
4779 $this->debug('getting WSDL http(s) URL ' . $wsdl);
4781 $tr = new soap_transport_http($wsdl, $this->curl_options, $this->use_curl);
4782 $tr->request_method = 'GET';
4783 $tr->useSOAPAction = false;
4784 if($this->proxyhost && $this->proxyport){
4785 $tr->setProxy($this->proxyhost,$this->proxyport,$this->proxyusername,$this->proxypassword);
4787 if ($this->authtype != '') {
4788 $tr->setCredentials($this->username, $this->password, $this->authtype, array(), $this->certRequest);
4790 $tr->setEncoding('gzip, deflate');
4791 $wsdl_string = $tr->send('', $this->timeout, $this->response_timeout);
4792 //$this->debug("WSDL request\n" . $tr->outgoing_payload);
4793 //$this->debug("WSDL response\n" . $tr->incoming_payload);
4794 $this->appendDebug($tr->getDebug());
4796 if($err = $tr->getError() ){
4797 $errstr = 'Getting ' . $wsdl . ' - HTTP ERROR: '.$err;
4798 $this->debug($errstr);
4799 $this->setError($errstr);
4804 $this->debug("got WSDL URL");
4806 // $wsdl is not http(s), so treat it as a file URL or plain file path
4807 if (isset($wsdl_props['scheme']) && ($wsdl_props['scheme'] == 'file') && isset($wsdl_props['path'])) {
4808 $path = isset($wsdl_props['host']) ? ($wsdl_props['host'] . ':' . $wsdl_props['path']) : $wsdl_props['path'];
4812 $this->debug('getting WSDL file ' . $path);
4813 if ($fp = @fopen($path, 'r')) {
4815 while ($data = fread($fp, 32768)) {
4816 $wsdl_string .= $data;
4820 $errstr = "Bad path to WSDL file $path";
4821 $this->debug($errstr);
4822 $this->setError($errstr);
4826 $this->debug('Parse WSDL');
4827 // end new code added
4828 // Create an XML parser.
4829 $this->parser = xml_parser_create();
4830 // Set the options for parsing the XML data.
4831 // xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 1);
4832 xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING, 0);
4833 // Set the object for the parser.
4834 xml_set_object($this->parser, $this);
4835 // Set the element handlers for the parser.
4836 xml_set_element_handler($this->parser, 'start_element', 'end_element');
4837 xml_set_character_data_handler($this->parser, 'character_data');
4838 // Parse the XML file.
4839 if (!xml_parse($this->parser, $wsdl_string, true)) {
4840 // Display an error message.
4842 'XML error parsing WSDL from %s on line %d: %s',
4844 xml_get_current_line_number($this->parser),
4845 xml_error_string(xml_get_error_code($this->parser))
4847 $this->debug($errstr);
4848 $this->debug("XML payload:\n" . $wsdl_string);
4849 $this->setError($errstr);
4853 xml_parser_free($this->parser);
4854 $this->debug('Parsing WSDL done');
4855 // catch wsdl parse errors
4856 if($this->getError()){
4863 * start-element handler
4865 * @param string $parser XML parser object
4866 * @param string $name element name
4867 * @param string $attrs associative array of attributes
4870 function start_element($parser, $name, $attrs)
4872 if ($this->status == 'schema') {
4873 $this->currentSchema->schemaStartElement($parser, $name, $attrs);
4874 $this->appendDebug($this->currentSchema->getDebug());
4875 $this->currentSchema->clearDebug();
4876 } elseif (preg_match('/schema$/', $name)) {
4877 $this->debug('Parsing WSDL schema');
4878 // $this->debug("startElement for $name ($attrs[name]). status = $this->status (".$this->getLocalPart($name).")");
4879 $this->status = 'schema';
4880 $this->currentSchema = new nusoap_xmlschema('', '', $this->namespaces);
4881 $this->currentSchema->schemaStartElement($parser, $name, $attrs);
4882 $this->appendDebug($this->currentSchema->getDebug());
4883 $this->currentSchema->clearDebug();
4885 // position in the total number of elements, starting from 0
4886 $pos = $this->position++;
4887 $depth = $this->depth++;
4888 // set self as current value for this depth
4889 $this->depth_array[$depth] = $pos;
4890 $this->message[$pos] = array('cdata' => '');
4891 // process attributes
4892 if (count($attrs) > 0) {
4893 // register namespace declarations
4894 foreach($attrs as $k => $v) {
4895 if (preg_match('/^xmlns/',$k)) {
4896 if ($ns_prefix = substr(strrchr($k, ':'), 1)) {
4897 $this->namespaces[$ns_prefix] = $v;
4899 $this->namespaces['ns' . (count($this->namespaces) + 1)] = $v;
4901 if ($v == 'http://www.w3.org/2001/XMLSchema' || $v == 'http://www.w3.org/1999/XMLSchema' || $v == 'http://www.w3.org/2000/10/XMLSchema') {
4902 $this->XMLSchemaVersion = $v;
4903 $this->namespaces['xsi'] = $v . '-instance';
4907 // expand each attribute prefix to its namespace
4908 foreach($attrs as $k => $v) {
4909 $k = strpos($k, ':') ? $this->expandQname($k) : $k;
4910 if ($k != 'location' && $k != 'soapAction' && $k != 'namespace') {
4911 $v = strpos($v, ':') ? $this->expandQname($v) : $v;
4919 // get element prefix, namespace and name
4920 if (preg_match('/:/', $name)) {
4922 $prefix = substr($name, 0, strpos($name, ':'));
4924 $namespace = isset($this->namespaces[$prefix]) ? $this->namespaces[$prefix] : '';
4925 // get unqualified name
4926 $name = substr(strstr($name, ':'), 1);
4928 // process attributes, expanding any prefixes to namespaces
4929 // find status, register data
4930 switch ($this->status) {
4932 if ($name == 'part') {
4933 if (isset($attrs['type'])) {
4934 $this->debug("msg " . $this->currentMessage . ": found part (with type) $attrs[name]: " . implode(',', $attrs));
4935 $this->messages[$this->currentMessage][$attrs['name']] = $attrs['type'];
4937 if (isset($attrs['element'])) {
4938 $this->debug("msg " . $this->currentMessage . ": found part (with element) $attrs[name]: " . implode(',', $attrs));
4939 $this->messages[$this->currentMessage][$attrs['name']] = $attrs['element'] . '^';
4946 $this->currentPortOperation = $attrs['name'];
4947 $this->debug("portType $this->currentPortType operation: $this->currentPortOperation");
4948 if (isset($attrs['parameterOrder'])) {
4949 $this->portTypes[$this->currentPortType][$attrs['name']]['parameterOrder'] = $attrs['parameterOrder'];
4952 case 'documentation':
4953 $this->documentation = true;
4955 // merge input/output data
4957 $m = isset($attrs['message']) ? $this->getLocalPart($attrs['message']) : '';
4958 $this->portTypes[$this->currentPortType][$this->currentPortOperation][$name]['message'] = $m;
4966 if (isset($attrs['style'])) {
4967 $this->bindings[$this->currentBinding]['prefix'] = $prefix;
4969 $this->bindings[$this->currentBinding] = array_merge($this->bindings[$this->currentBinding], $attrs);
4972 $this->bindings[$this->currentBinding]['operations'][$this->currentOperation][$this->opStatus]['headers'][] = $attrs;
4975 if (isset($attrs['soapAction'])) {
4976 $this->bindings[$this->currentBinding]['operations'][$this->currentOperation]['soapAction'] = $attrs['soapAction'];
4978 if (isset($attrs['style'])) {
4979 $this->bindings[$this->currentBinding]['operations'][$this->currentOperation]['style'] = $attrs['style'];
4981 if (isset($attrs['name'])) {
4982 $this->currentOperation = $attrs['name'];
4983 $this->debug("current binding operation: $this->currentOperation");
4984 $this->bindings[$this->currentBinding]['operations'][$this->currentOperation]['name'] = $attrs['name'];
4985 $this->bindings[$this->currentBinding]['operations'][$this->currentOperation]['binding'] = $this->currentBinding;
4986 $this->bindings[$this->currentBinding]['operations'][$this->currentOperation]['endpoint'] = isset($this->bindings[$this->currentBinding]['endpoint']) ? $this->bindings[$this->currentBinding]['endpoint'] : '';
4990 $this->opStatus = 'input';
4993 $this->opStatus = 'output';
4996 if (isset($this->bindings[$this->currentBinding]['operations'][$this->currentOperation][$this->opStatus])) {
4997 $this->bindings[$this->currentBinding]['operations'][$this->currentOperation][$this->opStatus] = array_merge($this->bindings[$this->currentBinding]['operations'][$this->currentOperation][$this->opStatus], $attrs);
4999 $this->bindings[$this->currentBinding]['operations'][$this->currentOperation][$this->opStatus] = $attrs;
5007 $this->currentPort = $attrs['name'];
5008 $this->debug('current port: ' . $this->currentPort);
5009 $this->ports[$this->currentPort]['binding'] = $this->getLocalPart($attrs['binding']);
5013 $this->ports[$this->currentPort]['location'] = $attrs['location'];
5014 $this->ports[$this->currentPort]['bindingType'] = $namespace;
5015 $this->bindings[ $this->ports[$this->currentPort]['binding'] ]['bindingType'] = $namespace;
5016 $this->bindings[ $this->ports[$this->currentPort]['binding'] ]['endpoint'] = $attrs['location'];
5024 if (isset($attrs['location'])) {
5025 $this->import[$attrs['namespace']][] = array('location' => $attrs['location'], 'loaded' => false);
5026 $this->debug('parsing import ' . $attrs['namespace']. ' - ' . $attrs['location'] . ' (' . count($this->import[$attrs['namespace']]).')');
5028 $this->import[$attrs['namespace']][] = array('location' => '', 'loaded' => true);
5029 if (! $this->getPrefixFromNamespace($attrs['namespace'])) {
5030 $this->namespaces['ns'.(count($this->namespaces)+1)] = $attrs['namespace'];
5032 $this->debug('parsing import ' . $attrs['namespace']. ' - [no location] (' . count($this->import[$attrs['namespace']]).')');
5037 // $this->status = 'schema';
5040 $this->status = 'message';
5041 $this->messages[$attrs['name']] = array();
5042 $this->currentMessage = $attrs['name'];
5045 $this->status = 'portType';
5046 $this->portTypes[$attrs['name']] = array();
5047 $this->currentPortType = $attrs['name'];
5050 if (isset($attrs['name'])) {
5052 if (strpos($attrs['name'], ':')) {
5053 $this->currentBinding = $this->getLocalPart($attrs['name']);
5055 $this->currentBinding = $attrs['name'];
5057 $this->status = 'binding';
5058 $this->bindings[$this->currentBinding]['portType'] = $this->getLocalPart($attrs['type']);
5059 $this->debug("current binding: $this->currentBinding of portType: " . $attrs['type']);
5063 $this->serviceName = $attrs['name'];
5064 $this->status = 'service';
5065 $this->debug('current service: ' . $this->serviceName);
5068 foreach ($attrs as $name => $value) {
5069 $this->wsdl_info[$name] = $value;
5077 * end-element handler
5079 * @param string $parser XML parser object
5080 * @param string $name element name
5083 function end_element($parser, $name){
5084 // unset schema status
5085 if (/*preg_match('/types$/', $name) ||*/ preg_match('/schema$/', $name)) {
5087 $this->appendDebug($this->currentSchema->getDebug());
5088 $this->currentSchema->clearDebug();
5089 $this->schemas[$this->currentSchema->schemaTargetNamespace][] = $this->currentSchema;
5090 $this->debug('Parsing WSDL schema done');
5092 if ($this->status == 'schema') {
5093 $this->currentSchema->schemaEndElement($parser, $name);
5095 // bring depth down a notch
5098 // end documentation
5099 if ($this->documentation) {
5100 //TODO: track the node to which documentation should be assigned; it can be a part, message, etc.
5101 //$this->portTypes[$this->currentPortType][$this->currentPortOperation]['documentation'] = $this->documentation;
5102 $this->documentation = false;
5107 * element content handler
5109 * @param string $parser XML parser object
5110 * @param string $data element content
5113 function character_data($parser, $data)
5115 $pos = isset($this->depth_array[$this->depth]) ? $this->depth_array[$this->depth] : 0;
5116 if (isset($this->message[$pos]['cdata'])) {
5117 $this->message[$pos]['cdata'] .= $data;
5119 if ($this->documentation) {
5120 $this->documentation .= $data;
5125 * if authenticating, set user credentials here
5127 * @param string $username
5128 * @param string $password
5129 * @param string $authtype (basic|digest|certificate|ntlm)
5130 * @param array $certRequest (keys must be cainfofile (optional), sslcertfile, sslkeyfile, passphrase, certpassword (optional), verifypeer (optional), verifyhost (optional): see corresponding options in cURL docs)
5133 function setCredentials($username, $password, $authtype = 'basic', $certRequest = array()) {
5134 $this->debug("setCredentials username=$username authtype=$authtype certRequest=");
5135 $this->appendDebug($this->varDump($certRequest));
5136 $this->username = $username;
5137 $this->password = $password;
5138 $this->authtype = $authtype;
5139 $this->certRequest = $certRequest;
5142 function getBindingData($binding)
5144 if (is_array($this->bindings[$binding])) {
5145 return $this->bindings[$binding];
5150 * returns an assoc array of operation names => operation data
5152 * @param string $portName WSDL port name
5153 * @param string $bindingType eg: soap, smtp, dime (only soap and soap12 are currently supported)
5157 function getOperations($portName = '', $bindingType = 'soap') {
5159 if ($bindingType == 'soap') {
5160 $bindingType = 'http://schemas.xmlsoap.org/wsdl/soap/';
5161 } elseif ($bindingType == 'soap12') {
5162 $bindingType = 'http://schemas.xmlsoap.org/wsdl/soap12/';
5164 $this->debug("getOperations bindingType $bindingType may not be supported");
5166 $this->debug("getOperations for port '$portName' bindingType $bindingType");
5168 foreach($this->ports as $port => $portData) {
5169 $this->debug("getOperations checking port $port bindingType " . $portData['bindingType']);
5170 if ($portName == '' || $port == $portName) {
5171 // binding type of port matches parameter
5172 if ($portData['bindingType'] == $bindingType) {
5173 $this->debug("getOperations found port $port bindingType $bindingType");
5174 //$this->debug("port data: " . $this->varDump($portData));
5175 //$this->debug("bindings: " . $this->varDump($this->bindings[ $portData['binding'] ]));
5177 if (isset($this->bindings[ $portData['binding'] ]['operations'])) {
5178 $ops = array_merge ($ops, $this->bindings[ $portData['binding'] ]['operations']);
5183 if (count($ops) == 0) {
5184 $this->debug("getOperations found no operations for port '$portName' bindingType $bindingType");
5190 * returns an associative array of data necessary for calling an operation
5192 * @param string $operation name of operation
5193 * @param string $bindingType type of binding eg: soap, soap12
5197 function getOperationData($operation, $bindingType = 'soap')
5199 if ($bindingType == 'soap') {
5200 $bindingType = 'http://schemas.xmlsoap.org/wsdl/soap/';
5201 } elseif ($bindingType == 'soap12') {
5202 $bindingType = 'http://schemas.xmlsoap.org/wsdl/soap12/';
5205 foreach($this->ports as $port => $portData) {
5206 // binding type of port matches parameter
5207 if ($portData['bindingType'] == $bindingType) {
5209 //foreach($this->bindings[ $portData['binding'] ]['operations'] as $bOperation => $opData) {
5210 foreach(array_keys($this->bindings[ $portData['binding'] ]['operations']) as $bOperation) {
5211 // note that we could/should also check the namespace here
5212 if ($operation == $bOperation) {
5213 $opData = $this->bindings[ $portData['binding'] ]['operations'][$operation];
5222 * returns an associative array of data necessary for calling an operation
5224 * @param string $soapAction soapAction for operation
5225 * @param string $bindingType type of binding eg: soap, soap12
5229 function getOperationDataForSoapAction($soapAction, $bindingType = 'soap') {
5230 if ($bindingType == 'soap') {
5231 $bindingType = 'http://schemas.xmlsoap.org/wsdl/soap/';
5232 } elseif ($bindingType == 'soap12') {
5233 $bindingType = 'http://schemas.xmlsoap.org/wsdl/soap12/';
5236 foreach($this->ports as $port => $portData) {
5237 // binding type of port matches parameter
5238 if ($portData['bindingType'] == $bindingType) {
5239 // loop through operations for the binding
5240 foreach ($this->bindings[ $portData['binding'] ]['operations'] as $bOperation => $opData) {
5241 if ($opData['soapAction'] == $soapAction) {
5250 * returns an array of information about a given type
5251 * returns false if no type exists by the given name
5254 * 'elements' => array(), // refs to elements array
5255 * 'restrictionBase' => '',
5257 * 'order' => '(sequence|all)',
5258 * 'attrs' => array() // refs to attributes array
5261 * @param string $type the type
5262 * @param string $ns namespace (not prefix) of the type
5265 * @see nusoap_xmlschema
5267 function getTypeDef($type, $ns) {
5268 $this->debug("in getTypeDef: type=$type, ns=$ns");
5269 if ((! $ns) && isset($this->namespaces['tns'])) {
5270 $ns = $this->namespaces['tns'];
5271 $this->debug("in getTypeDef: type namespace forced to $ns");
5273 if (!isset($this->schemas[$ns])) {
5274 foreach ($this->schemas as $ns0 => $schema0) {
5275 if (strcasecmp($ns, $ns0) == 0) {
5276 $this->debug("in getTypeDef: replacing schema namespace $ns with $ns0");
5282 if (isset($this->schemas[$ns])) {
5283 $this->debug("in getTypeDef: have schema for namespace $ns");
5284 for ($i = 0; $i < count($this->schemas[$ns]); $i++) {
5285 $xs = &$this->schemas[$ns][$i];
5286 $t = $xs->getTypeDef($type);
5287 $this->appendDebug($xs->getDebug());
5290 $this->debug("in getTypeDef: found type $type");
5291 if (!isset($t['phpType'])) {
5292 // get info for type to tack onto the element
5293 $uqType = substr($t['type'], strrpos($t['type'], ':') + 1);
5294 $ns = substr($t['type'], 0, strrpos($t['type'], ':'));
5295 $etype = $this->getTypeDef($uqType, $ns);
5297 $this->debug("found type for [element] $type:");
5298 $this->debug($this->varDump($etype));
5299 if (isset($etype['phpType'])) {
5300 $t['phpType'] = $etype['phpType'];
5302 if (isset($etype['elements'])) {
5303 $t['elements'] = $etype['elements'];
5305 if (isset($etype['attrs'])) {
5306 $t['attrs'] = $etype['attrs'];
5309 $this->debug("did not find type for [element] $type");
5315 $this->debug("in getTypeDef: did not find type $type");
5317 $this->debug("in getTypeDef: do not have schema for namespace $ns");
5323 * prints html description of services
5327 function webDescription(){
5328 global $HTTP_SERVER_VARS;
5330 if (isset($_SERVER)) {
5331 $PHP_SELF = $_SERVER['PHP_SELF'];
5332 } elseif (isset($HTTP_SERVER_VARS)) {
5333 $PHP_SELF = $HTTP_SERVER_VARS['PHP_SELF'];
5335 $this->setError("Neither _SERVER nor HTTP_SERVER_VARS is available");
5339 <html><head><title>NuSOAP: '.$this->serviceName.'</title>
5340 <style type="text/css">
5341 body { font-family: arial; color: #000000; background-color: #ffffff; margin: 0px 0px 0px 0px; }
5342 p { font-family: arial; color: #000000; margin-top: 0px; margin-bottom: 12px; }
5343 pre { background-color: silver; padding: 5px; font-family: Courier New; font-size: x-small; color: #000000;}
5344 ul { margin-top: 10px; margin-left: 20px; }
5345 li { list-style-type: none; margin-top: 10px; color: #000000; }
5347 margin-left: 0px; padding-bottom: 2em; }
5349 padding-top: 10px; padding-bottom: 10px; padding-left: 15px; font-size: .70em;
5350 margin-top: 10px; margin-left: 0px; color: #000000;
5351 background-color: #ccccff; width: 20%; margin-left: 20px; margin-top: 20px; }
5353 font-family: arial; font-size: 26px; color: #ffffff;
5354 background-color: #999999; width: 100%;
5355 margin-left: 0px; margin-right: 0px;
5356 padding-top: 10px; padding-bottom: 10px;}
5358 position: absolute; visibility: hidden; z-index: 200; left: 250px; top: 100px;
5359 font-family: arial; overflow: hidden; width: 600;
5360 padding: 20px; font-size: 10px; background-color: #999999;
5361 layer-background-color:#FFFFFF; }
5362 a,a:active { color: charcoal; font-weight: bold; }
5363 a:visited { color: #666666; font-weight: bold; }
5364 a:hover { color: cc3300; font-weight: bold; }
5366 <script type="text/javascript">
5368 // POP-UP CAPTIONS...
5369 function lib_bwcheck(){ //Browsercheck (needed)
5370 this.ver=navigator.appVersion
5371 this.agent=navigator.userAgent
5372 this.dom=document.getElementById?1:0
5373 this.opera5=this.agent.indexOf("Opera 5")>-1
5374 this.ie5=(this.ver.indexOf("MSIE 5")>-1 && this.dom && !this.opera5)?1:0;
5375 this.ie6=(this.ver.indexOf("MSIE 6")>-1 && this.dom && !this.opera5)?1:0;
5376 this.ie4=(document.all && !this.dom && !this.opera5)?1:0;
5377 this.ie=this.ie4||this.ie5||this.ie6
5378 this.mac=this.agent.indexOf("Mac")>-1
5379 this.ns6=(this.dom && parseInt(this.ver) >= 5) ?1:0;
5380 this.ns4=(document.layers && !this.dom)?1:0;
5381 this.bw=(this.ie6 || this.ie5 || this.ie4 || this.ns4 || this.ns6 || this.opera5)
5384 var bw = new lib_bwcheck()
5385 //Makes crossbrowser object.
5386 function makeObj(obj){
5387 this.evnt=bw.dom? document.getElementById(obj):bw.ie4?document.all[obj]:bw.ns4?document.layers[obj]:0;
5388 if(!this.evnt) return false
5389 this.css=bw.dom||bw.ie4?this.evnt.style:bw.ns4?this.evnt:0;
5390 this.wref=bw.dom||bw.ie4?this.evnt:bw.ns4?this.css.document:0;
5391 this.writeIt=b_writeIt;
5394 // A unit of measure that will be added when setting the position of a layer.
5395 //var px = bw.ns4||window.opera?"":"px";
5396 function b_writeIt(text){
5397 if (bw.ns4){this.wref.write(text);this.wref.close()}
5398 else this.wref.innerHTML = text
5400 //Shows the messages
5402 function popup(divid){
5403 if(oDesc = new makeObj(divid)){
5404 oDesc.css.visibility = "visible"
5407 function popout(){ // Hides message
5408 if(oDesc) oDesc.css.visibility = "hidden"
5416 <div class=title>'.$this->serviceName.'</div>
5418 <p>View the <a href="'.$PHP_SELF.'?wsdl">WSDL</a> for the service.
5419 Click on an operation name to view it's details.</p>
5421 foreach($this->getOperations() as $op => $data){
5422 $b .= "<li><a href='#' onclick=\"popout();popup('$op')\">$op</a></li>";
5423 // create hidden div
5424 $b .= "<div id='$op' class='hidden'>
5425 <a href='#' onclick='popout()'><font color='#ffffff'>Close</font></a><br><br>";
5426 foreach($data as $donnie => $marie){ // loop through opdata
5427 if($donnie == 'input' || $donnie == 'output'){ // show input/output data
5428 $b .= "<font color='white'>".ucfirst($donnie).':</font><br>';
5429 foreach($marie as $captain => $tenille){ // loop through data
5430 if($captain == 'parts'){ // loop thru parts
5431 $b .= " $captain:<br>";
5432 //if(is_array($tenille)){
5433 foreach($tenille as $joanie => $chachi){
5434 $b .= " $joanie: $chachi<br>";
5438 $b .= " $captain: $tenille<br>";
5442 $b .= "<font color='white'>".ucfirst($donnie).":</font> $marie<br>";
5450 </div></body></html>';
5455 * serialize the parsed wsdl
5457 * @param mixed $debug whether to put debug=1 in endpoint URL
5458 * @return string serialization of WSDL
5461 function serialize($debug = 0)
5463 $xml = '<?xml version="1.0" encoding="ISO-8859-1"?>';
5464 $xml .= "\n<definitions";
5465 foreach($this->namespaces as $k => $v) {
5466 $xml .= " xmlns:$k=\"$v\"";
5468 // 10.9.02 - add poulter fix for wsdl and tns declarations
5469 if (isset($this->namespaces['wsdl'])) {
5470 $xml .= " xmlns=\"" . $this->namespaces['wsdl'] . "\"";
5472 if (isset($this->namespaces['tns'])) {
5473 $xml .= " targetNamespace=\"" . $this->namespaces['tns'] . "\"";
5477 if (sizeof($this->import) > 0) {
5478 foreach($this->import as $ns => $list) {
5479 foreach ($list as $ii) {
5480 if ($ii['location'] != '') {
5481 $xml .= '<import location="' . $ii['location'] . '" namespace="' . $ns . '" />';
5483 $xml .= '<import namespace="' . $ns . '" />';
5489 if (count($this->schemas)>=1) {
5490 $xml .= "\n<types>\n";
5491 foreach ($this->schemas as $ns => $list) {
5492 foreach ($list as $xs) {
5493 $xml .= $xs->serializeSchema();
5499 if (count($this->messages) >= 1) {
5500 foreach($this->messages as $msgName => $msgParts) {
5501 $xml .= "\n<message name=\"" . $msgName . '">';
5502 if(is_array($msgParts)){
5503 foreach($msgParts as $partName => $partType) {
5504 // print 'serializing '.$partType.', sv: '.$this->XMLSchemaVersion.'<br>';
5505 if (strpos($partType, ':')) {
5506 $typePrefix = $this->getPrefixFromNamespace($this->getPrefix($partType));
5507 } elseif (isset($this->typemap[$this->namespaces['xsd']][$partType])) {
5508 // print 'checking typemap: '.$this->XMLSchemaVersion.'<br>';
5509 $typePrefix = 'xsd';
5511 foreach($this->typemap as $ns => $types) {
5512 if (isset($types[$partType])) {
5513 $typePrefix = $this->getPrefixFromNamespace($ns);
5516 if (!isset($typePrefix)) {
5517 die("$partType has no namespace!");
5520 $ns = $this->getNamespaceFromPrefix($typePrefix);
5521 $localPart = $this->getLocalPart($partType);
5522 $typeDef = $this->getTypeDef($localPart, $ns);
5523 if ($typeDef['typeClass'] == 'element') {
5524 $elementortype = 'element';
5525 if (substr($localPart, -1) == '^') {
5526 $localPart = substr($localPart, 0, -1);
5529 $elementortype = 'type';
5531 $xml .= "\n" . ' <part name="' . $partName . '" ' . $elementortype . '="' . $typePrefix . ':' . $localPart . '" />';
5534 $xml .= '</message>';
5537 // bindings & porttypes
5538 if (count($this->bindings) >= 1) {
5541 foreach($this->bindings as $bindingName => $attrs) {
5542 $binding_xml .= "\n<binding name=\"" . $bindingName . '" type="tns:' . $attrs['portType'] . '">';
5543 $binding_xml .= "\n" . ' <soap:binding style="' . $attrs['style'] . '" transport="' . $attrs['transport'] . '"/>';
5544 $portType_xml .= "\n<portType name=\"" . $attrs['portType'] . '">';
5545 foreach($attrs['operations'] as $opName => $opParts) {
5546 $binding_xml .= "\n" . ' <operation name="' . $opName . '">';
5547 $binding_xml .= "\n" . ' <soap:operation soapAction="' . $opParts['soapAction'] . '" style="'. $opParts['style'] . '"/>';
5548 if (isset($opParts['input']['encodingStyle']) && $opParts['input']['encodingStyle'] != '') {
5549 $enc_style = ' encodingStyle="' . $opParts['input']['encodingStyle'] . '"';
5553 $binding_xml .= "\n" . ' <input><soap:body use="' . $opParts['input']['use'] . '" namespace="' . $opParts['input']['namespace'] . '"' . $enc_style . '/></input>';
5554 if (isset($opParts['output']['encodingStyle']) && $opParts['output']['encodingStyle'] != '') {
5555 $enc_style = ' encodingStyle="' . $opParts['output']['encodingStyle'] . '"';
5559 $binding_xml .= "\n" . ' <output><soap:body use="' . $opParts['output']['use'] . '" namespace="' . $opParts['output']['namespace'] . '"' . $enc_style . '/></output>';
5560 $binding_xml .= "\n" . ' </operation>';
5561 $portType_xml .= "\n" . ' <operation name="' . $opParts['name'] . '"';
5562 if (isset($opParts['parameterOrder'])) {
5563 $portType_xml .= ' parameterOrder="' . $opParts['parameterOrder'] . '"';
5565 $portType_xml .= '>';
5566 if(isset($opParts['documentation']) && $opParts['documentation'] != '') {
5567 $portType_xml .= "\n" . ' <documentation>' . htmlspecialchars($opParts['documentation']) . '</documentation>';
5569 $portType_xml .= "\n" . ' <input message="tns:' . $opParts['input']['message'] . '"/>';
5570 $portType_xml .= "\n" . ' <output message="tns:' . $opParts['output']['message'] . '"/>';
5571 $portType_xml .= "\n" . ' </operation>';
5573 $portType_xml .= "\n" . '</portType>';
5574 $binding_xml .= "\n" . '</binding>';
5576 $xml .= $portType_xml . $binding_xml;
5579 $xml .= "\n<service name=\"" . $this->serviceName . '">';
5580 if (count($this->ports) >= 1) {
5581 foreach($this->ports as $pName => $attrs) {
5582 $xml .= "\n" . ' <port name="' . $pName . '" binding="tns:' . $attrs['binding'] . '">';
5583 $xml .= "\n" . ' <soap:address location="' . $attrs['location'] . ($debug ? '?debug=1' : '') . '"/>';
5584 $xml .= "\n" . ' </port>';
5587 $xml .= "\n" . '</service>';
5588 return $xml . "\n</definitions>";
5592 * determine whether a set of parameters are unwrapped
5593 * when they are expect to be wrapped, Microsoft-style.
5595 * @param string $type the type (element name) of the wrapper
5596 * @param array $parameters the parameter values for the SOAP call
5597 * @return boolean whether they parameters are unwrapped (and should be wrapped)
5600 function parametersMatchWrapped($type, &$parameters) {
5601 $this->debug("in parametersMatchWrapped type=$type, parameters=");
5602 $this->appendDebug($this->varDump($parameters));
5604 // split type into namespace:unqualified-type
5605 if (strpos($type, ':')) {
5606 $uqType = substr($type, strrpos($type, ':') + 1);
5607 $ns = substr($type, 0, strrpos($type, ':'));
5608 $this->debug("in parametersMatchWrapped: got a prefixed type: $uqType, $ns");
5609 if ($this->getNamespaceFromPrefix($ns)) {
5610 $ns = $this->getNamespaceFromPrefix($ns);
5611 $this->debug("in parametersMatchWrapped: expanded prefixed type: $uqType, $ns");
5614 // TODO: should the type be compared to types in XSD, and the namespace
5615 // set to XSD if the type matches?
5616 $this->debug("in parametersMatchWrapped: No namespace for type $type");
5621 // get the type information
5622 if (!$typeDef = $this->getTypeDef($uqType, $ns)) {
5623 $this->debug("in parametersMatchWrapped: $type ($uqType) is not a supported type.");
5626 $this->debug("in parametersMatchWrapped: found typeDef=");
5627 $this->appendDebug($this->varDump($typeDef));
5628 if (substr($uqType, -1) == '^') {
5629 $uqType = substr($uqType, 0, -1);
5631 $phpType = $typeDef['phpType'];
5632 $arrayType = (isset($typeDef['arrayType']) ? $typeDef['arrayType'] : '');
5633 $this->debug("in parametersMatchWrapped: uqType: $uqType, ns: $ns, phptype: $phpType, arrayType: $arrayType");
5635 // we expect a complexType or element of complexType
5636 if ($phpType != 'struct') {
5637 $this->debug("in parametersMatchWrapped: not a struct");
5641 // see whether the parameter names match the elements
5642 if (isset($typeDef['elements']) && is_array($typeDef['elements'])) {
5645 foreach ($typeDef['elements'] as $name => $attrs) {
5646 if (isset($parameters[$name])) {
5647 $this->debug("in parametersMatchWrapped: have parameter named $name");
5650 $this->debug("in parametersMatchWrapped: do not have parameter named $name");
5655 $this->debug("in parametersMatchWrapped: $matches parameter names match $elements wrapped parameter names");
5656 if ($matches == 0) {
5662 // since there are no elements for the type, if the user passed no
5663 // parameters, the parameters match wrapped.
5664 $this->debug("in parametersMatchWrapped: no elements type $ns:$uqType");
5665 return count($parameters) == 0;
5669 * serialize PHP values according to a WSDL message definition
5670 * contrary to the method name, this is not limited to RPC
5673 * - multi-ref serialization
5674 * - validate PHP values against type definitions, return errors if invalid
5676 * @param string $operation operation name
5677 * @param string $direction (input|output)
5678 * @param mixed $parameters parameter value(s)
5679 * @param string $bindingType (soap|soap12)
5680 * @return mixed parameters serialized as XML or false on error (e.g. operation not found)
5683 function serializeRPCParameters($operation, $direction, $parameters, $bindingType = 'soap') {
5684 $this->debug("in serializeRPCParameters: operation=$operation, direction=$direction, XMLSchemaVersion=$this->XMLSchemaVersion, bindingType=$bindingType");
5685 $this->appendDebug('parameters=' . $this->varDump($parameters));
5687 if ($direction != 'input' && $direction != 'output') {
5688 $this->debug('The value of the \$direction argument needs to be either "input" or "output"');
5689 $this->setError('The value of the \$direction argument needs to be either "input" or "output"');
5692 if (!$opData = $this->getOperationData($operation, $bindingType)) {
5693 $this->debug('Unable to retrieve WSDL data for operation: ' . $operation . ' bindingType: ' . $bindingType);
5694 $this->setError('Unable to retrieve WSDL data for operation: ' . $operation . ' bindingType: ' . $bindingType);
5697 $this->debug('in serializeRPCParameters: opData:');
5698 $this->appendDebug($this->varDump($opData));
5700 // Get encoding style for output and set to current
5701 $encodingStyle = 'http://schemas.xmlsoap.org/soap/encoding/';
5702 if(($direction == 'input') && isset($opData['output']['encodingStyle']) && ($opData['output']['encodingStyle'] != $encodingStyle)) {
5703 $encodingStyle = $opData['output']['encodingStyle'];
5704 $enc_style = $encodingStyle;
5709 if (isset($opData[$direction]['parts']) && sizeof($opData[$direction]['parts']) > 0) {
5710 $parts = &$opData[$direction]['parts'];
5711 $part_count = sizeof($parts);
5712 $style = $opData['style'];
5713 $use = $opData[$direction]['use'];
5714 $this->debug("have $part_count part(s) to serialize using $style/$use");
5715 if (is_array($parameters)) {
5716 $parametersArrayType = $this->isArraySimpleOrStruct($parameters);
5717 $parameter_count = count($parameters);
5718 $this->debug("have $parameter_count parameter(s) provided as $parametersArrayType to serialize");
5719 // check for Microsoft-style wrapped parameters
5720 if ($style == 'document' && $use == 'literal' && $part_count == 1 && isset($parts['parameters'])) {
5721 $this->debug('check whether the caller has wrapped the parameters');
5722 if ($direction == 'output' && $parametersArrayType == 'arraySimple' && $parameter_count == 1) {
5723 // TODO: consider checking here for double-wrapping, when
5724 // service function wraps, then NuSOAP wraps again
5725 $this->debug("change simple array to associative with 'parameters' element");
5726 $parameters['parameters'] = $parameters[0];
5727 unset($parameters[0]);
5729 if (($parametersArrayType == 'arrayStruct' || $parameter_count == 0) && !isset($parameters['parameters'])) {
5730 $this->debug('check whether caller\'s parameters match the wrapped ones');
5731 if ($this->parametersMatchWrapped($parts['parameters'], $parameters)) {
5732 $this->debug('wrap the parameters for the caller');
5733 $parameters = array('parameters' => $parameters);
5734 $parameter_count = 1;
5738 foreach ($parts as $name => $type) {
5739 $this->debug("serializing part $name of type $type");
5740 // Track encoding style
5741 if (isset($opData[$direction]['encodingStyle']) && $encodingStyle != $opData[$direction]['encodingStyle']) {
5742 $encodingStyle = $opData[$direction]['encodingStyle'];
5743 $enc_style = $encodingStyle;
5747 // NOTE: add error handling here
5748 // if serializeType returns false, then catch global error and fault
5749 if ($parametersArrayType == 'arraySimple') {
5750 $p = array_shift($parameters);
5751 $this->debug('calling serializeType w/indexed param');
5752 $xml .= $this->serializeType($name, $type, $p, $use, $enc_style);
5753 } elseif (isset($parameters[$name])) {
5754 $this->debug('calling serializeType w/named param');
5755 $xml .= $this->serializeType($name, $type, $parameters[$name], $use, $enc_style);
5757 // TODO: only send nillable
5758 $this->debug('calling serializeType w/null param');
5759 $xml .= $this->serializeType($name, $type, null, $use, $enc_style);
5763 $this->debug('no parameters passed.');
5766 $this->debug("serializeRPCParameters returning: $xml");
5771 * serialize a PHP value according to a WSDL message definition
5774 * - multi-ref serialization
5775 * - validate PHP values against type definitions, return errors if invalid
5777 * @param string $operation operation name
5778 * @param string $direction (input|output)
5779 * @param mixed $parameters parameter value(s)
5780 * @return mixed parameters serialized as XML or false on error (e.g. operation not found)
5784 function serializeParameters($operation, $direction, $parameters)
5786 $this->debug("in serializeParameters: operation=$operation, direction=$direction, XMLSchemaVersion=$this->XMLSchemaVersion");
5787 $this->appendDebug('parameters=' . $this->varDump($parameters));
5789 if ($direction != 'input' && $direction != 'output') {
5790 $this->debug('The value of the \$direction argument needs to be either "input" or "output"');
5791 $this->setError('The value of the \$direction argument needs to be either "input" or "output"');
5794 if (!$opData = $this->getOperationData($operation)) {
5795 $this->debug('Unable to retrieve WSDL data for operation: ' . $operation);
5796 $this->setError('Unable to retrieve WSDL data for operation: ' . $operation);
5799 $this->debug('opData:');
5800 $this->appendDebug($this->varDump($opData));
5802 // Get encoding style for output and set to current
5803 $encodingStyle = 'http://schemas.xmlsoap.org/soap/encoding/';
5804 if(($direction == 'input') && isset($opData['output']['encodingStyle']) && ($opData['output']['encodingStyle'] != $encodingStyle)) {
5805 $encodingStyle = $opData['output']['encodingStyle'];
5806 $enc_style = $encodingStyle;
5811 if (isset($opData[$direction]['parts']) && sizeof($opData[$direction]['parts']) > 0) {
5813 $use = $opData[$direction]['use'];
5814 $this->debug("use=$use");
5815 $this->debug('got ' . count($opData[$direction]['parts']) . ' part(s)');
5816 if (is_array($parameters)) {
5817 $parametersArrayType = $this->isArraySimpleOrStruct($parameters);
5818 $this->debug('have ' . $parametersArrayType . ' parameters');
5819 foreach($opData[$direction]['parts'] as $name => $type) {
5820 $this->debug('serializing part "'.$name.'" of type "'.$type.'"');
5821 // Track encoding style
5822 if(isset($opData[$direction]['encodingStyle']) && $encodingStyle != $opData[$direction]['encodingStyle']) {
5823 $encodingStyle = $opData[$direction]['encodingStyle'];
5824 $enc_style = $encodingStyle;
5828 // NOTE: add error handling here
5829 // if serializeType returns false, then catch global error and fault
5830 if ($parametersArrayType == 'arraySimple') {
5831 $p = array_shift($parameters);
5832 $this->debug('calling serializeType w/indexed param');
5833 $xml .= $this->serializeType($name, $type, $p, $use, $enc_style);
5834 } elseif (isset($parameters[$name])) {
5835 $this->debug('calling serializeType w/named param');
5836 $xml .= $this->serializeType($name, $type, $parameters[$name], $use, $enc_style);
5838 // TODO: only send nillable
5839 $this->debug('calling serializeType w/null param');
5840 $xml .= $this->serializeType($name, $type, null, $use, $enc_style);
5844 $this->debug('no parameters passed.');
5847 $this->debug("serializeParameters returning: $xml");
5852 * serializes a PHP value according a given type definition
5854 * @param string $name name of value (part or element)
5855 * @param string $type XML schema type of value (type or element)
5856 * @param mixed $value a native PHP value (parameter value)
5857 * @param string $use use for part (encoded|literal)
5858 * @param string $encodingStyle SOAP encoding style for the value (if different than the enclosing style)
5859 * @param boolean $unqualified a kludge for what should be XML namespace form handling
5860 * @return string value serialized as an XML string
5863 function serializeType($name, $type, $value, $use='encoded', $encodingStyle=false, $unqualified=false)
5865 $this->debug("in serializeType: name=$name, type=$type, use=$use, encodingStyle=$encodingStyle, unqualified=" . ($unqualified ? "unqualified" : "qualified"));
5866 $this->appendDebug("value=" . $this->varDump($value));
5867 if($use == 'encoded' && $encodingStyle) {
5868 $encodingStyle = ' SOAP-ENV:encodingStyle="' . $encodingStyle . '"';
5871 // if a soapval has been supplied, let its type override the WSDL
5872 if (is_object($value) && get_class($value) == 'soapval') {
5873 if ($value->type_ns) {
5874 $type = $value->type_ns . ':' . $value->type;
5876 $this->debug("in serializeType: soapval overrides type to $type");
5877 } elseif ($value->type) {
5878 $type = $value->type;
5880 $this->debug("in serializeType: soapval overrides type to $type");
5883 $this->debug("in serializeType: soapval does not override type");
5885 $attrs = $value->attributes;
5886 $value = $value->value;
5887 $this->debug("in serializeType: soapval overrides value to $value");
5889 if (!is_array($value)) {
5890 $value['!'] = $value;
5892 foreach ($attrs as $n => $v) {
5893 $value['!' . $n] = $v;
5895 $this->debug("in serializeType: soapval provides attributes");
5902 if (strpos($type, ':')) {
5903 $uqType = substr($type, strrpos($type, ':') + 1);
5904 $ns = substr($type, 0, strrpos($type, ':'));
5905 $this->debug("in serializeType: got a prefixed type: $uqType, $ns");
5906 if ($this->getNamespaceFromPrefix($ns)) {
5907 $ns = $this->getNamespaceFromPrefix($ns);
5908 $this->debug("in serializeType: expanded prefixed type: $uqType, $ns");
5911 if($ns == $this->XMLSchemaVersion || $ns == 'http://schemas.xmlsoap.org/soap/encoding/'){
5912 $this->debug('in serializeType: type namespace indicates XML Schema or SOAP Encoding type');
5913 if ($unqualified && $use == 'literal') {
5914 $elementNS = " xmlns=\"\"";
5918 if (is_null($value)) {
5919 if ($use == 'literal') {
5920 // TODO: depends on minOccurs
5921 $xml = "<$name$elementNS/>";
5923 // TODO: depends on nillable, which should be checked before calling this method
5924 $xml = "<$name$elementNS xsi:nil=\"true\" xsi:type=\"" . $this->getPrefixFromNamespace($ns) . ":$uqType\"/>";
5926 $this->debug("in serializeType: returning: $xml");
5929 if ($uqType == 'Array') {
5930 // JBoss/Axis does this sometimes
5931 return $this->serialize_val($value, $name, false, false, false, false, $use);
5933 if ($uqType == 'boolean') {
5934 if ((is_string($value) && $value == 'false') || (! $value)) {
5940 if ($uqType == 'string' && gettype($value) == 'string') {
5941 $value = $this->expandEntities($value);
5943 if (($uqType == 'long' || $uqType == 'unsignedLong') && gettype($value) == 'double') {
5944 $value = sprintf("%.0lf", $value);
5947 // TODO: what about null/nil values?
5948 // check type isn't a custom type extending xmlschema namespace
5949 if (!$this->getTypeDef($uqType, $ns)) {
5950 if ($use == 'literal') {
5952 $xml = "<$name$elementNS xsi:type=\"" . $this->getPrefixFromNamespace($ns) . ":$uqType\">$value</$name>";
5954 $xml = "<$name$elementNS>$value</$name>";
5957 $xml = "<$name$elementNS xsi:type=\"" . $this->getPrefixFromNamespace($ns) . ":$uqType\"$encodingStyle>$value</$name>";
5959 $this->debug("in serializeType: returning: $xml");
5962 $this->debug('custom type extends XML Schema or SOAP Encoding namespace (yuck)');
5963 } elseif ($ns == 'http://xml.apache.org/xml-soap') {
5964 $this->debug('in serializeType: appears to be Apache SOAP type');
5965 if ($uqType == 'Map') {
5966 $tt_prefix = $this->getPrefixFromNamespace('http://xml.apache.org/xml-soap');
5968 $this->debug('in serializeType: Add namespace for Apache SOAP type');
5969 $tt_prefix = 'ns' . util_randnum(1000, 9999);
5970 $this->namespaces[$tt_prefix] = 'http://xml.apache.org/xml-soap';
5971 // force this to be added to usedNamespaces
5972 $tt_prefix = $this->getPrefixFromNamespace('http://xml.apache.org/xml-soap');
5975 foreach($value as $k => $v) {
5976 $this->debug("serializing map element: key $k, value $v");
5977 $contents .= '<item>';
5978 $contents .= $this->serialize_val($k,'key',false,false,false,false,$use);
5979 $contents .= $this->serialize_val($v,'value',false,false,false,false,$use);
5980 $contents .= '</item>';
5982 if ($use == 'literal') {
5984 $xml = "<$name xsi:type=\"" . $tt_prefix . ":$uqType\">$contents</$name>";
5986 $xml = "<$name>$contents</$name>";
5989 $xml = "<$name xsi:type=\"" . $tt_prefix . ":$uqType\"$encodingStyle>$contents</$name>";
5991 $this->debug("in serializeType: returning: $xml");
5994 $this->debug('in serializeType: Apache SOAP type, but only support Map');
5997 // TODO: should the type be compared to types in XSD, and the namespace
5998 // set to XSD if the type matches?
5999 $this->debug("in serializeType: No namespace for type $type");
6003 if(!$typeDef = $this->getTypeDef($uqType, $ns)){
6004 $this->setError("$type ($uqType) is not a supported type.");
6005 $this->debug("in serializeType: $type ($uqType) is not a supported type.");
6008 $this->debug("in serializeType: found typeDef");
6009 $this->appendDebug('typeDef=' . $this->varDump($typeDef));
6010 if (substr($uqType, -1) == '^') {
6011 $uqType = substr($uqType, 0, -1);
6014 if (!isset($typeDef['phpType'])) {
6015 $this->setError("$type ($uqType) has no phpType.");
6016 $this->debug("in serializeType: $type ($uqType) has no phpType.");
6019 $phpType = $typeDef['phpType'];
6020 $this->debug("in serializeType: uqType: $uqType, ns: $ns, phptype: $phpType, arrayType: " . (isset($typeDef['arrayType']) ? $typeDef['arrayType'] : '') );
6021 // if php type == struct, map value to the <all> element names
6022 if ($phpType == 'struct') {
6023 if (isset($typeDef['typeClass']) && $typeDef['typeClass'] == 'element') {
6024 $elementName = $uqType;
6025 if (isset($typeDef['form']) && ($typeDef['form'] == 'qualified')) {
6026 $elementNS = " xmlns=\"$ns\"";
6028 $elementNS = " xmlns=\"\"";
6031 $elementName = $name;
6033 $elementNS = " xmlns=\"\"";
6038 if (is_null($value)) {
6039 if ($use == 'literal') {
6040 // TODO: depends on minOccurs and nillable
6041 $xml = "<$elementName$elementNS/>";
6043 $xml = "<$elementName$elementNS xsi:nil=\"true\" xsi:type=\"" . $this->getPrefixFromNamespace($ns) . ":$uqType\"/>";
6045 $this->debug("in serializeType: returning: $xml");
6048 if (is_object($value)) {
6049 $value = get_object_vars($value);
6051 if (is_array($value)) {
6052 $elementAttrs = $this->serializeComplexTypeAttributes($typeDef, $value, $ns, $uqType);
6053 if ($use == 'literal') {
6055 $xml = "<$elementName$elementNS$elementAttrs xsi:type=\"" . $this->getPrefixFromNamespace($ns) . ":$uqType\">";
6057 $xml = "<$elementName$elementNS$elementAttrs>";
6060 $xml = "<$elementName$elementNS$elementAttrs xsi:type=\"" . $this->getPrefixFromNamespace($ns) . ":$uqType\"$encodingStyle>";
6063 if (isset($typeDef['simpleContent']) && $typeDef['simpleContent'] == 'true') {
6064 if (isset($value['!'])) {
6065 $xml .= $value['!'];
6066 $this->debug("in serializeType: serialized simpleContent for type $type");
6068 $this->debug("in serializeType: no simpleContent to serialize for type $type");
6072 $xml .= $this->serializeComplexTypeElements($typeDef, $value, $ns, $uqType, $use, $encodingStyle);
6074 $xml .= "</$elementName>";
6076 $this->debug("in serializeType: phpType is struct, but value is not an array");
6077 $this->setError("phpType is struct, but value is not an array: see debug output for details");
6080 } elseif ($phpType == 'array') {
6081 if (isset($typeDef['form']) && ($typeDef['form'] == 'qualified')) {
6082 $elementNS = " xmlns=\"$ns\"";
6085 $elementNS = " xmlns=\"\"";
6090 if (is_null($value)) {
6091 if ($use == 'literal') {
6092 // TODO: depends on minOccurs
6093 $xml = "<$name$elementNS/>";
6095 $xml = "<$name$elementNS xsi:nil=\"true\" xsi:type=\"" .
6096 $this->getPrefixFromNamespace('http://schemas.xmlsoap.org/soap/encoding/') .
6098 $this->getPrefixFromNamespace('http://schemas.xmlsoap.org/soap/encoding/') .
6100 $this->getPrefixFromNamespace($this->getPrefix($typeDef['arrayType'])) .
6102 $this->getLocalPart($typeDef['arrayType'])."[0]\"/>";
6104 $this->debug("in serializeType: returning: $xml");
6107 if (isset($typeDef['multidimensional'])) {
6109 foreach($value as $v) {
6110 $cols = ',' . sizeof($v);
6111 $nv = array_merge($nv, $v);
6117 if (is_array($value) && sizeof($value) >= 1) {
6118 $rows = sizeof($value);
6120 foreach($value as $k => $v) {
6121 $this->debug("serializing array element: $k, $v of type: $typeDef[arrayType]");
6122 //if (strpos($typeDef['arrayType'], ':') ) {
6123 if (!in_array($typeDef['arrayType'],$this->typemap['http://www.w3.org/2001/XMLSchema'])) {
6124 $contents .= $this->serializeType('item', $typeDef['arrayType'], $v, $use);
6126 $contents .= $this->serialize_val($v, 'item', $typeDef['arrayType'], null, $this->XMLSchemaVersion, false, $use);
6133 // TODO: for now, an empty value will be serialized as a zero element
6134 // array. Revisit this when coding the handling of null/nil values.
6135 if ($use == 'literal') {
6136 $xml = "<$name$elementNS>"
6140 $xml = "<$name$elementNS xsi:type=\"".$this->getPrefixFromNamespace('http://schemas.xmlsoap.org/soap/encoding/').':Array" '.
6141 $this->getPrefixFromNamespace('http://schemas.xmlsoap.org/soap/encoding/')
6143 .$this->getPrefixFromNamespace($this->getPrefix($typeDef['arrayType']))
6144 .":".$this->getLocalPart($typeDef['arrayType'])."[$rows$cols]\">"
6148 } elseif ($phpType == 'scalar') {
6149 if (isset($typeDef['form']) && ($typeDef['form'] == 'qualified')) {
6150 $elementNS = " xmlns=\"$ns\"";
6153 $elementNS = " xmlns=\"\"";
6158 if ($use == 'literal') {
6160 $xml = "<$name$elementNS xsi:type=\"" . $this->getPrefixFromNamespace($ns) . ":$uqType\">$value</$name>";
6162 $xml = "<$name$elementNS>$value</$name>";
6165 $xml = "<$name$elementNS xsi:type=\"" . $this->getPrefixFromNamespace($ns) . ":$uqType\"$encodingStyle>$value</$name>";
6168 $this->debug("in serializeType: returning: $xml");
6173 * serializes the attributes for a complexType
6175 * @param array $typeDef our internal representation of an XML schema type (or element)
6176 * @param mixed $value a native PHP value (parameter value)
6177 * @param string $ns the namespace of the type
6178 * @param string $uqType the local part of the type
6179 * @return string value serialized as an XML string
6182 function serializeComplexTypeAttributes($typeDef, $value, $ns, $uqType) {
6183 $this->debug("serializeComplexTypeAttributes for XML Schema type $ns:$uqType");
6185 if (isset($typeDef['extensionBase'])) {
6186 $nsx = $this->getPrefix($typeDef['extensionBase']);
6187 $uqTypex = $this->getLocalPart($typeDef['extensionBase']);
6188 if ($this->getNamespaceFromPrefix($nsx)) {
6189 $nsx = $this->getNamespaceFromPrefix($nsx);
6191 if ($typeDefx = $this->getTypeDef($uqTypex, $nsx)) {
6192 $this->debug("serialize attributes for extension base $nsx:$uqTypex");
6193 $xml .= $this->serializeComplexTypeAttributes($typeDefx, $value, $nsx, $uqTypex);
6195 $this->debug("extension base $nsx:$uqTypex is not a supported type");
6198 if (isset($typeDef['attrs']) && is_array($typeDef['attrs'])) {
6199 $this->debug("serialize attributes for XML Schema type $ns:$uqType");
6200 if (is_array($value)) {
6202 } elseif (is_object($value)) {
6203 $xvalue = get_object_vars($value);
6205 $this->debug("value is neither an array nor an object for XML Schema type $ns:$uqType");
6208 foreach ($typeDef['attrs'] as $aName => $attrs) {
6209 if (isset($xvalue['!' . $aName])) {
6210 $xname = '!' . $aName;
6211 $this->debug("value provided for attribute $aName with key $xname");
6212 } elseif (isset($xvalue[$aName])) {
6214 $this->debug("value provided for attribute $aName with key $xname");
6215 } elseif (isset($attrs['default'])) {
6216 $xname = '!' . $aName;
6217 $xvalue[$xname] = $attrs['default'];
6218 $this->debug('use default value of ' . $xvalue[$aName] . ' for attribute ' . $aName);
6221 $this->debug("no value provided for attribute $aName");
6224 $xml .= " $aName=\"" . $this->expandEntities($xvalue[$xname]) . "\"";
6228 $this->debug("no attributes to serialize for XML Schema type $ns:$uqType");
6234 * serializes the elements for a complexType
6236 * @param array $typeDef our internal representation of an XML schema type (or element)
6237 * @param mixed $value a native PHP value (parameter value)
6238 * @param string $ns the namespace of the type
6239 * @param string $uqType the local part of the type
6240 * @param string $use use for part (encoded|literal)
6241 * @param string $encodingStyle SOAP encoding style for the value (if different than the enclosing style)
6242 * @return string value serialized as an XML string
6245 function serializeComplexTypeElements($typeDef, $value, $ns, $uqType, $use='encoded', $encodingStyle=false) {
6246 $this->debug("in serializeComplexTypeElements for XML Schema type $ns:$uqType");
6248 if (isset($typeDef['extensionBase'])) {
6249 $nsx = $this->getPrefix($typeDef['extensionBase']);
6250 $uqTypex = $this->getLocalPart($typeDef['extensionBase']);
6251 if ($this->getNamespaceFromPrefix($nsx)) {
6252 $nsx = $this->getNamespaceFromPrefix($nsx);
6254 if ($typeDefx = $this->getTypeDef($uqTypex, $nsx)) {
6255 $this->debug("serialize elements for extension base $nsx:$uqTypex");
6256 $xml .= $this->serializeComplexTypeElements($typeDefx, $value, $nsx, $uqTypex, $use, $encodingStyle);
6258 $this->debug("extension base $nsx:$uqTypex is not a supported type");
6261 if (isset($typeDef['elements']) && is_array($typeDef['elements'])) {
6262 $this->debug("in serializeComplexTypeElements, serialize elements for XML Schema type $ns:$uqType");
6263 if (is_array($value)) {
6265 } elseif (is_object($value)) {
6266 $xvalue = get_object_vars($value);
6268 $this->debug("value is neither an array nor an object for XML Schema type $ns:$uqType");
6271 // toggle whether all elements are present - ideally should validate against schema
6272 if (count($typeDef['elements']) != count($xvalue)){
6275 foreach ($typeDef['elements'] as $eName => $attrs) {
6276 if (!isset($xvalue[$eName])) {
6277 if (isset($attrs['default'])) {
6278 $xvalue[$eName] = $attrs['default'];
6279 $this->debug('use default value of ' . $xvalue[$eName] . ' for element ' . $eName);
6282 // if user took advantage of a minOccurs=0, then only serialize named parameters
6283 if (isset($optionals)
6284 && (!isset($xvalue[$eName]))
6285 && ( (!isset($attrs['nillable'])) || $attrs['nillable'] != 'true')
6287 if (isset($attrs['minOccurs']) && $attrs['minOccurs'] <> '0') {
6288 $this->debug("apparent error: no value provided for element $eName with minOccurs=" . $attrs['minOccurs']);
6291 $this->debug("no value provided for complexType element $eName and element is not nillable, so serialize nothing");
6294 if (isset($xvalue[$eName])) {
6295 $v = $xvalue[$eName];
6299 if (isset($attrs['form'])) {
6300 $unqualified = ($attrs['form'] == 'unqualified');
6302 $unqualified = false;
6304 if (isset($attrs['maxOccurs']) && ($attrs['maxOccurs'] == 'unbounded' || $attrs['maxOccurs'] > 1) && isset($v) && is_array($v) && $this->isArraySimpleOrStruct($v) == 'arraySimple') {
6306 foreach ($vv as $k => $v) {
6307 if (isset($attrs['type']) || isset($attrs['ref'])) {
6308 // serialize schema-defined type
6309 $xml .= $this->serializeType($eName, isset($attrs['type']) ? $attrs['type'] : $attrs['ref'], $v, $use, $encodingStyle, $unqualified);
6311 // serialize generic type (can this ever really happen?)
6312 $this->debug("calling serialize_val() for $v, $eName, false, false, false, false, $use");
6313 $xml .= $this->serialize_val($v, $eName, false, false, false, false, $use);
6317 if (is_null($v) && isset($attrs['minOccurs']) && $attrs['minOccurs'] == '0') {
6319 } elseif (is_null($v) && isset($attrs['nillable']) && $attrs['nillable'] == 'true') {
6320 // TODO: serialize a nil correctly, but for now serialize schema-defined type
6321 $xml .= $this->serializeType($eName, isset($attrs['type']) ? $attrs['type'] : $attrs['ref'], $v, $use, $encodingStyle, $unqualified);
6322 } elseif (isset($attrs['type']) || isset($attrs['ref'])) {
6323 // serialize schema-defined type
6324 $xml .= $this->serializeType($eName, isset($attrs['type']) ? $attrs['type'] : $attrs['ref'], $v, $use, $encodingStyle, $unqualified);
6326 // serialize generic type (can this ever really happen?)
6327 $this->debug("calling serialize_val() for $v, $eName, false, false, false, false, $use");
6328 $xml .= $this->serialize_val($v, $eName, false, false, false, false, $use);
6334 $this->debug("no elements to serialize for XML Schema type $ns:$uqType");
6340 * adds an XML Schema complex type to the WSDL types
6342 * @param string $name
6343 * @param string $typeClass (complexType|simpleType|attribute)
6344 * @param string $phpType currently supported are array and struct (php assoc array)
6345 * @param string $compositor (all|sequence|choice)
6346 * @param string $restrictionBase namespace:name (http://schemas.xmlsoap.org/soap/encoding/:Array)
6347 * @param array $elements e.g. array ( name => array(name=>'',type=>'') )
6348 * @param array $attrs e.g. array(array('ref'=>'SOAP-ENC:arrayType','wsdl:arrayType'=>'xsd:string[]'))
6349 * @param string $arrayType as namespace:name (xsd:string)
6350 * @see nusoap_xmlschema
6353 function addComplexType($name,$typeClass='complexType',$phpType='array',$compositor='',$restrictionBase='',$elements=array(),$attrs=array(),$arrayType='') {
6354 if (count($elements) > 0) {
6355 $eElements = array();
6356 foreach($elements as $n => $e){
6357 // expand each element
6359 foreach ($e as $k => $v) {
6360 $k = strpos($k,':') ? $this->expandQname($k) : $k;
6361 $v = strpos($v,':') ? $this->expandQname($v) : $v;
6364 $eElements[$n] = $ee;
6366 $elements = $eElements;
6369 if (count($attrs) > 0) {
6370 foreach($attrs as $n => $a){
6371 // expand each attribute
6372 foreach ($a as $k => $v) {
6373 $k = strpos($k,':') ? $this->expandQname($k) : $k;
6374 $v = strpos($v,':') ? $this->expandQname($v) : $v;
6382 $restrictionBase = strpos($restrictionBase,':') ? $this->expandQname($restrictionBase) : $restrictionBase;
6383 $arrayType = strpos($arrayType,':') ? $this->expandQname($arrayType) : $arrayType;
6385 $typens = isset($this->namespaces['types']) ? $this->namespaces['types'] : $this->namespaces['tns'];
6386 $this->schemas[$typens][0]->addComplexType($name,$typeClass,$phpType,$compositor,$restrictionBase,$elements,$attrs,$arrayType);
6390 * adds an XML Schema simple type to the WSDL types
6392 * @param string $name
6393 * @param string $restrictionBase namespace:name (http://schemas.xmlsoap.org/soap/encoding/:Array)
6394 * @param string $typeClass (should always be simpleType)
6395 * @param string $phpType (should always be scalar)
6396 * @param array $enumeration array of values
6397 * @see nusoap_xmlschema
6400 function addSimpleType($name, $restrictionBase='', $typeClass='simpleType', $phpType='scalar', $enumeration=array()) {
6401 $restrictionBase = strpos($restrictionBase,':') ? $this->expandQname($restrictionBase) : $restrictionBase;
6403 $typens = isset($this->namespaces['types']) ? $this->namespaces['types'] : $this->namespaces['tns'];
6404 $this->schemas[$typens][0]->addSimpleType($name, $restrictionBase, $typeClass, $phpType, $enumeration);
6408 * adds an element to the WSDL types
6410 * @param array $attrs attributes that must include name and type
6411 * @see nusoap_xmlschema
6414 function addElement($attrs) {
6415 $typens = isset($this->namespaces['types']) ? $this->namespaces['types'] : $this->namespaces['tns'];
6416 $this->schemas[$typens][0]->addElement($attrs);
6420 * register an operation with the server
6422 * @param string $name operation (method) name
6423 * @param array $in assoc array of input values: key = param name, value = param type
6424 * @param array $out assoc array of output values: key = param name, value = param type
6425 * @param string $namespace optional The namespace for the operation
6426 * @param string $soapaction optional The soapaction for the operation
6427 * @param string $style (rpc|document) optional The style for the operation Note: when 'document' is specified, parameter and return wrappers are created for you automatically
6428 * @param string $use (encoded|literal) optional The use for the parameters (cannot mix right now)
6429 * @param string $documentation optional The description to include in the WSDL
6430 * @param string $encodingStyle optional (usually 'http://schemas.xmlsoap.org/soap/encoding/' for encoded)
6433 function addOperation($name, $in = false, $out = false, $namespace = false, $soapaction = false, $style = 'rpc', $use = 'encoded', $documentation = '', $encodingStyle = ''){
6434 if ($use == 'encoded' && $encodingStyle == '') {
6435 $encodingStyle = 'http://schemas.xmlsoap.org/soap/encoding/';
6438 if ($style == 'document') {
6439 $elements = array();
6440 foreach ($in as $n => $t) {
6441 $elements[$n] = array('name' => $n, 'type' => $t, 'form' => 'unqualified');
6443 $this->addComplexType($name . 'RequestType', 'complexType', 'struct', 'all', '', $elements);
6444 $this->addElement(array('name' => $name, 'type' => $name . 'RequestType'));
6445 $in = array('parameters' => 'tns:' . $name . '^');
6447 $elements = array();
6448 foreach ($out as $n => $t) {
6449 $elements[$n] = array('name' => $n, 'type' => $t, 'form' => 'unqualified');
6451 $this->addComplexType($name . 'ResponseType', 'complexType', 'struct', 'all', '', $elements);
6452 $this->addElement(array('name' => $name . 'Response', 'type' => $name . 'ResponseType', 'form' => 'qualified'));
6453 $out = array('parameters' => 'tns:' . $name . 'Response' . '^');
6457 $this->bindings[ $this->serviceName . 'Binding' ]['operations'][$name] =
6460 'binding' => $this->serviceName . 'Binding',
6461 'endpoint' => $this->endpoint,
6462 'soapAction' => $soapaction,
6466 'namespace' => $namespace,
6467 'encodingStyle' => $encodingStyle,
6468 'message' => $name . 'Request',
6472 'namespace' => $namespace,
6473 'encodingStyle' => $encodingStyle,
6474 'message' => $name . 'Response',
6476 'namespace' => $namespace,
6477 'transport' => 'http://schemas.xmlsoap.org/soap/http',
6478 'documentation' => $documentation);
6483 foreach($in as $pName => $pType)
6485 if(strpos($pType,':')) {
6486 $pType = $this->getNamespaceFromPrefix($this->getPrefix($pType)).":".$this->getLocalPart($pType);
6488 $this->messages[$name.'Request'][$pName] = $pType;
6491 $this->messages[$name.'Request']= '0';
6495 foreach($out as $pName => $pType)
6497 if(strpos($pType,':')) {
6498 $pType = $this->getNamespaceFromPrefix($this->getPrefix($pType)).":".$this->getLocalPart($pType);
6500 $this->messages[$name.'Response'][$pName] = $pType;
6503 $this->messages[$name.'Response']= '0';
6512 * nusoap_parser class parses SOAP XML messages into native PHP values
6514 * @author Dietrich Ayala <dietrich@ganx4.com>
6515 * @author Scott Nichol <snichol@users.sourceforge.net>
6519 class nusoap_parser extends nusoap_base {
6522 var $xml_encoding = '';
6524 var $root_struct = '';
6525 var $root_struct_name = '';
6526 var $root_struct_namespace = '';
6527 var $root_header = '';
6528 var $document = ''; // incoming SOAP body (text)
6529 // determines where in the message we are (envelope,header,body,method)
6533 var $default_namespace = '';
6534 var $namespaces = array();
6535 var $message = array();
6538 var $fault_code = '';
6539 var $fault_str = '';
6540 var $fault_detail = '';
6541 var $depth_array = array();
6542 var $debug_flag = true;
6543 var $soapresponse = NULL; // parsed SOAP Body
6544 var $soapheader = NULL; // parsed SOAP Header
6545 var $responseHeaders = ''; // incoming SOAP headers (text)
6546 var $body_position = 0;
6547 // for multiref parsing:
6548 // array of id => pos
6550 // array of id => hrefs => pos
6551 var $multirefs = array();
6552 // toggle for auto-decoding element content
6553 var $decode_utf8 = true;
6556 * constructor that actually does the parsing
6558 * @param string $xml SOAP message
6559 * @param string $encoding character encoding scheme of message
6560 * @param string $method method for which XML is parsed (unused?)
6561 * @param string $decode_utf8 whether to decode UTF-8 to ISO-8859-1
6564 function nusoap_parser($xml,$encoding='UTF-8',$method='',$decode_utf8=true){
6565 parent::nusoap_base();
6567 $this->xml_encoding = $encoding;
6568 $this->method = $method;
6569 $this->decode_utf8 = $decode_utf8;
6571 // Check whether content has been read.
6573 // Check XML encoding
6574 $pos_xml = strpos($xml, '<?xml');
6575 if ($pos_xml !== FALSE) {
6576 $xml_decl = substr($xml, $pos_xml, strpos($xml, '?>', $pos_xml + 2) - $pos_xml + 1);
6577 if (preg_match("/encoding=[\"']([^\"']*)[\"']/", $xml_decl, $res)) {
6578 $xml_encoding = $res[1];
6579 if (strtoupper($xml_encoding) != $encoding) {
6580 $err = "Charset from HTTP Content-Type '" . $encoding . "' does not match encoding from XML declaration '" . $xml_encoding . "'";
6582 if ($encoding != 'ISO-8859-1' || strtoupper($xml_encoding) != 'UTF-8') {
6583 $this->setError($err);
6586 // when HTTP says ISO-8859-1 (the default) and XML says UTF-8 (the typical), assume the other endpoint is just sloppy and proceed
6588 $this->debug('Charset from HTTP Content-Type matches encoding from XML declaration');
6591 $this->debug('No encoding specified in XML declaration');
6594 $this->debug('No XML declaration');
6596 $this->debug('Entering nusoap_parser(), length='.strlen($xml).', encoding='.$encoding);
6597 // Create an XML parser - why not xml_parser_create_ns?
6598 $this->parser = xml_parser_create($this->xml_encoding);
6599 // Set the options for parsing the XML data.
6600 //xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 1);
6601 xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING, 0);
6602 xml_parser_set_option($this->parser, XML_OPTION_TARGET_ENCODING, $this->xml_encoding);
6603 // Set the object for the parser.
6604 xml_set_object($this->parser, $this);
6605 // Set the element handlers for the parser.
6606 xml_set_element_handler($this->parser, 'start_element','end_element');
6607 xml_set_character_data_handler($this->parser,'character_data');
6609 // Parse the XML file.
6610 if(!xml_parse($this->parser,$xml,true)){
6611 // Display an error message.
6612 $err = sprintf('XML error parsing SOAP payload on line %d: %s',
6613 xml_get_current_line_number($this->parser),
6614 xml_error_string(xml_get_error_code($this->parser)));
6616 $this->debug("XML payload:\n" . $xml);
6617 $this->setError($err);
6619 $this->debug('in nusoap_parser ctor, message:');
6620 $this->appendDebug($this->varDump($this->message));
6621 $this->debug('parsed successfully, found root struct: '.$this->root_struct.' of name '.$this->root_struct_name);
6623 $this->soapresponse = $this->message[$this->root_struct]['result'];
6625 if($this->root_header != '' && isset($this->message[$this->root_header]['result'])){
6626 $this->soapheader = $this->message[$this->root_header]['result'];
6628 // resolve hrefs/ids
6629 if(sizeof($this->multirefs) > 0){
6630 foreach($this->multirefs as $id => $hrefs){
6631 $this->debug('resolving multirefs for id: '.$id);
6632 $idVal = $this->buildVal($this->ids[$id]);
6633 if (is_array($idVal) && isset($idVal['!id'])) {
6634 unset($idVal['!id']);
6636 foreach($hrefs as $refPos => $ref){
6637 $this->debug('resolving href at pos '.$refPos);
6638 $this->multirefs[$id][$refPos] = $idVal;
6643 xml_parser_free($this->parser);
6645 $this->debug('xml was empty, didn\'t parse!');
6646 $this->setError('xml was empty, didn\'t parse!');
6651 * start-element handler
6653 * @param resource $parser XML parser object
6654 * @param string $name element name
6655 * @param array $attrs associative array of attributes
6658 function start_element($parser, $name, $attrs) {
6659 // position in a total number of elements, starting from 0
6660 // update class level pos
6661 $pos = $this->position++;
6663 $this->message[$pos] = array('pos' => $pos,'children'=>'','cdata'=>'');
6664 // depth = how many levels removed from root?
6665 // set mine as current global depth and increment global depth value
6666 $this->message[$pos]['depth'] = $this->depth++;
6668 // else add self as child to whoever the current parent is
6670 $this->message[$this->parent]['children'] .= '|'.$pos;
6673 $this->message[$pos]['parent'] = $this->parent;
6674 // set self as current parent
6675 $this->parent = $pos;
6676 // set self as current value for this depth
6677 $this->depth_array[$this->depth] = $pos;
6678 // get element prefix
6679 if(strpos($name,':')){
6681 $prefix = substr($name,0,strpos($name,':'));
6682 // get unqualified name
6683 $name = substr(strstr($name,':'),1);
6686 if ($name == 'Envelope' && $this->status == '') {
6687 $this->status = 'envelope';
6688 } elseif ($name == 'Header' && $this->status == 'envelope') {
6689 $this->root_header = $pos;
6690 $this->status = 'header';
6691 } elseif ($name == 'Body' && $this->status == 'envelope'){
6692 $this->status = 'body';
6693 $this->body_position = $pos;
6695 } elseif($this->status == 'body' && $pos == ($this->body_position+1)){
6696 $this->status = 'method';
6697 $this->root_struct_name = $name;
6698 $this->root_struct = $pos;
6699 $this->message[$pos]['type'] = 'struct';
6700 $this->debug("found root struct $this->root_struct_name, pos $this->root_struct");
6703 $this->message[$pos]['status'] = $this->status;
6705 $this->message[$pos]['name'] = htmlspecialchars($name);
6707 $this->message[$pos]['attrs'] = $attrs;
6709 // loop through atts, logging ns and type declarations
6711 foreach($attrs as $key => $value){
6712 $key_prefix = $this->getPrefix($key);
6713 $key_localpart = $this->getLocalPart($key);
6714 // if ns declarations, add to class level array of valid namespaces
6715 if($key_prefix == 'xmlns'){
6716 if(preg_match('/^http:\/\/www.w3.org\/[0-9]{4}\/XMLSchema$/',$value)){
6717 $this->XMLSchemaVersion = $value;
6718 $this->namespaces['xsd'] = $this->XMLSchemaVersion;
6719 $this->namespaces['xsi'] = $this->XMLSchemaVersion.'-instance';
6721 $this->namespaces[$key_localpart] = $value;
6722 // set method namespace
6723 if($name == $this->root_struct_name){
6724 $this->methodNamespace = $value;
6726 // if it's a type declaration, set type
6727 } elseif($key_localpart == 'type'){
6728 if (isset($this->message[$pos]['type']) && $this->message[$pos]['type'] == 'array') {
6729 // do nothing: already processed arrayType
6731 $value_prefix = $this->getPrefix($value);
6732 $value_localpart = $this->getLocalPart($value);
6733 $this->message[$pos]['type'] = $value_localpart;
6734 $this->message[$pos]['typePrefix'] = $value_prefix;
6735 if(isset($this->namespaces[$value_prefix])){
6736 $this->message[$pos]['type_namespace'] = $this->namespaces[$value_prefix];
6737 } else if(isset($attrs['xmlns:'.$value_prefix])) {
6738 $this->message[$pos]['type_namespace'] = $attrs['xmlns:'.$value_prefix];
6740 // should do something here with the namespace of specified type?
6742 } elseif($key_localpart == 'arrayType'){
6743 $this->message[$pos]['type'] = 'array';
6744 /* do arrayType ereg here
6745 [1] arrayTypeValue ::= atype asize
6746 [2] atype ::= QName rank*
6747 [3] rank ::= '[' (',')* ']'
6748 [4] asize ::= '[' length~ ']'
6749 [5] length ::= nextDimension* Digit+
6750 [6] nextDimension ::= Digit+ ','
6752 $expr = '/([A-Za-z0-9_]+):([A-Za-z]+[A-Za-z0-9_]+)\[([0-9]+),?([0-9]*)\]/';
6753 if(preg_match($expr,$value,$regs)){
6754 $this->message[$pos]['typePrefix'] = $regs[1];
6755 $this->message[$pos]['arrayTypePrefix'] = $regs[1];
6756 if (isset($this->namespaces[$regs[1]])) {
6757 $this->message[$pos]['arrayTypeNamespace'] = $this->namespaces[$regs[1]];
6758 } elseif (isset($attrs['xmlns:'.$regs[1]])) {
6759 $this->message[$pos]['arrayTypeNamespace'] = $attrs['xmlns:'.$regs[1]];
6761 $this->message[$pos]['arrayType'] = $regs[2];
6762 $this->message[$pos]['arraySize'] = $regs[3];
6763 $this->message[$pos]['arrayCols'] = $regs[4];
6765 // specifies nil value (or not)
6766 } elseif ($key_localpart == 'nil'){
6767 $this->message[$pos]['nil'] = ($value == 'true' || $value == '1');
6768 // some other attribute
6769 } elseif ($key != 'href' && $key != 'xmlns' && $key_localpart != 'encodingStyle' && $key_localpart != 'root') {
6770 $this->message[$pos]['xattrs']['!' . $key] = $value;
6773 if ($key == 'xmlns') {
6774 $this->default_namespace = $value;
6778 $this->ids[$value] = $pos;
6781 if($key_localpart == 'root' && $value == 1){
6782 $this->status = 'method';
6783 $this->root_struct_name = $name;
6784 $this->root_struct = $pos;
6785 $this->debug("found root struct $this->root_struct_name, pos $pos");
6788 $attstr .= " $key=\"$value\"";
6790 // get namespace - must be done after namespace atts are processed
6792 $this->message[$pos]['namespace'] = $this->namespaces[$prefix];
6793 $this->default_namespace = $this->namespaces[$prefix];
6795 $this->message[$pos]['namespace'] = $this->default_namespace;
6797 if($this->status == 'header'){
6798 if ($this->root_header != $pos) {
6799 $this->responseHeaders .= "<" . (isset($prefix) ? $prefix . ':' : '') . "$name$attstr>";
6801 } elseif($this->root_struct_name != ''){
6802 $this->document .= "<" . (isset($prefix) ? $prefix . ':' : '') . "$name$attstr>";
6807 * end-element handler
6809 * @param resource $parser XML parser object
6810 * @param string $name element name
6813 function end_element($parser, $name) {
6814 // position of current element is equal to the last value left in depth_array for my depth
6815 $pos = $this->depth_array[$this->depth--];
6817 // get element prefix
6818 if(strpos($name,':')){
6820 $prefix = substr($name,0,strpos($name,':'));
6821 // get unqualified name
6822 $name = substr(strstr($name,':'),1);
6825 // build to native type
6826 if(isset($this->body_position) && $pos > $this->body_position){
6827 // deal w/ multirefs
6828 if(isset($this->message[$pos]['attrs']['href'])){
6830 $id = substr($this->message[$pos]['attrs']['href'],1);
6831 // add placeholder to href array
6832 $this->multirefs[$id][$pos] = 'placeholder';
6833 // add set a reference to it as the result value
6834 $this->message[$pos]['result'] =& $this->multirefs[$id][$pos];
6835 // build complexType values
6836 } elseif($this->message[$pos]['children'] != ''){
6837 // if result has already been generated (struct/array)
6838 if(!isset($this->message[$pos]['result'])){
6839 $this->message[$pos]['result'] = $this->buildVal($pos);
6841 // build complexType values of attributes and possibly simpleContent
6842 } elseif (isset($this->message[$pos]['xattrs'])) {
6843 if (isset($this->message[$pos]['nil']) && $this->message[$pos]['nil']) {
6844 $this->message[$pos]['xattrs']['!'] = null;
6845 } elseif (isset($this->message[$pos]['cdata']) && trim($this->message[$pos]['cdata']) != '') {
6846 if (isset($this->message[$pos]['type'])) {
6847 $this->message[$pos]['xattrs']['!'] = $this->decodeSimple($this->message[$pos]['cdata'], $this->message[$pos]['type'], isset($this->message[$pos]['type_namespace']) ? $this->message[$pos]['type_namespace'] : '');
6849 $parent = $this->message[$pos]['parent'];
6850 if (isset($this->message[$parent]['type']) && ($this->message[$parent]['type'] == 'array') && isset($this->message[$parent]['arrayType'])) {
6851 $this->message[$pos]['xattrs']['!'] = $this->decodeSimple($this->message[$pos]['cdata'], $this->message[$parent]['arrayType'], isset($this->message[$parent]['arrayTypeNamespace']) ? $this->message[$parent]['arrayTypeNamespace'] : '');
6853 $this->message[$pos]['xattrs']['!'] = $this->message[$pos]['cdata'];
6857 $this->message[$pos]['result'] = $this->message[$pos]['xattrs'];
6858 // set value of simpleType (or nil complexType)
6860 //$this->debug('adding data for scalar value '.$this->message[$pos]['name'].' of value '.$this->message[$pos]['cdata']);
6861 if (isset($this->message[$pos]['nil']) && $this->message[$pos]['nil']) {
6862 $this->message[$pos]['xattrs']['!'] = null;
6863 } elseif (isset($this->message[$pos]['type'])) {
6864 $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'] : '');
6866 $parent = $this->message[$pos]['parent'];
6867 if (isset($this->message[$parent]['type']) && ($this->message[$parent]['type'] == 'array') && isset($this->message[$parent]['arrayType'])) {
6868 $this->message[$pos]['result'] = $this->decodeSimple($this->message[$pos]['cdata'], $this->message[$parent]['arrayType'], isset($this->message[$parent]['arrayTypeNamespace']) ? $this->message[$parent]['arrayTypeNamespace'] : '');
6870 $this->message[$pos]['result'] = $this->message[$pos]['cdata'];
6874 /* add value to parent's result, if parent is struct/array
6875 $parent = $this->message[$pos]['parent'];
6876 if($this->message[$parent]['type'] != 'map'){
6877 if(strtolower($this->message[$parent]['type']) == 'array'){
6878 $this->message[$parent]['result'][] = $this->message[$pos]['result'];
6880 $this->message[$parent]['result'][$this->message[$pos]['name']] = $this->message[$pos]['result'];
6888 if($this->status == 'header'){
6889 if ($this->root_header != $pos) {
6890 $this->responseHeaders .= "</" . (isset($prefix) ? $prefix . ':' : '') . "$name>";
6892 } elseif($pos >= $this->root_struct){
6893 $this->document .= "</" . (isset($prefix) ? $prefix . ':' : '') . "$name>";
6896 if($pos == $this->root_struct){
6897 $this->status = 'body';
6898 $this->root_struct_namespace = $this->message[$pos]['namespace'];
6899 } elseif ($pos == $this->root_header) {
6900 $this->status = 'envelope';
6901 } elseif ($name == 'Body' && $this->status == 'body') {
6902 $this->status = 'envelope';
6903 } elseif ($name == 'Header' && $this->status == 'header') { // will never happen
6904 $this->status = 'envelope';
6905 } elseif ($name == 'Envelope' && $this->status == 'envelope') {
6908 // set parent back to my parent
6909 $this->parent = $this->message[$pos]['parent'];
6913 * element content handler
6915 * @param resource $parser XML parser object
6916 * @param string $data element content
6919 function character_data($parser, $data){
6920 $pos = $this->depth_array[$this->depth];
6921 if ($this->xml_encoding=='UTF-8'){
6922 // TODO: add an option to disable this for folks who want
6923 // raw UTF-8 that, e.g., might not map to iso-8859-1
6924 // TODO: this can also be handled with xml_parser_set_option($this->parser, XML_OPTION_TARGET_ENCODING, "ISO-8859-1");
6925 if($this->decode_utf8){
6926 $data = utf8_decode($data);
6929 $this->message[$pos]['cdata'] .= $data;
6931 if($this->status == 'header'){
6932 $this->responseHeaders .= $data;
6934 $this->document .= $data;
6939 * get the parsed message (SOAP Body)
6943 * @deprecated use get_soapbody instead
6945 function get_response(){
6946 return $this->soapresponse;
6950 * get the parsed SOAP Body (NULL if there was none)
6955 function get_soapbody(){
6956 return $this->soapresponse;
6960 * get the parsed SOAP Header (NULL if there was none)
6965 function get_soapheader(){
6966 return $this->soapheader;
6970 * get the unparsed SOAP Header
6972 * @return string XML or empty if no Header
6975 function getHeaders(){
6976 return $this->responseHeaders;
6980 * decodes simple types into PHP variables
6982 * @param string $value value to decode
6983 * @param string $type XML type to decode
6984 * @param string $typens XML type namespace to decode
6985 * @return mixed PHP value
6988 function decodeSimple($value, $type, $typens) {
6989 // TODO: use the namespace!
6990 if ((!isset($type)) || $type == 'string' || $type == 'long' || $type == 'unsignedLong') {
6991 return (string) $value;
6993 if ($type == 'int' || $type == 'integer' || $type == 'short' || $type == 'byte') {
6994 return (int) $value;
6996 if ($type == 'float' || $type == 'double' || $type == 'decimal') {
6997 return (double) $value;
6999 if ($type == 'boolean') {
7000 if (strtolower($value) == 'false' || strtolower($value) == 'f') {
7003 return (boolean) $value;
7005 if ($type == 'base64' || $type == 'base64Binary') {
7006 $this->debug('Decode base64 value');
7007 return base64_decode($value);
7009 // obscure numeric types
7010 if ($type == 'nonPositiveInteger' || $type == 'negativeInteger'
7011 || $type == 'nonNegativeInteger' || $type == 'positiveInteger'
7012 || $type == 'unsignedInt'
7013 || $type == 'unsignedShort' || $type == 'unsignedByte') {
7014 return (int) $value;
7016 // bogus: parser treats array with no elements as a simple type
7017 if ($type == 'array') {
7021 return (string) $value;
7025 * builds response structures for compound values (arrays/structs)
7028 * @param integer $pos position in node tree
7029 * @return mixed PHP value
7032 function buildVal($pos){
7033 if(!isset($this->message[$pos]['type'])){
7034 $this->message[$pos]['type'] = '';
7036 $this->debug('in buildVal() for '.$this->message[$pos]['name']."(pos $pos) of type ".$this->message[$pos]['type']);
7037 // if there are children...
7038 if($this->message[$pos]['children'] != ''){
7039 $this->debug('in buildVal, there are children');
7040 $children = explode('|',$this->message[$pos]['children']);
7041 array_shift($children); // knock off empty
7043 if(isset($this->message[$pos]['arrayCols']) && $this->message[$pos]['arrayCols'] != ''){
7046 foreach($children as $child_pos){
7047 $this->debug("in buildVal, got an MD array element: $r, $c");
7048 $params[$r][] = $this->message[$child_pos]['result'];
7050 if($c == $this->message[$pos]['arrayCols']){
7056 } elseif($this->message[$pos]['type'] == 'array' || $this->message[$pos]['type'] == 'Array'){
7057 $this->debug('in buildVal, adding array '.$this->message[$pos]['name']);
7058 foreach($children as $child_pos){
7059 $params[] = &$this->message[$child_pos]['result'];
7061 // apache Map type: java hashtable
7062 } elseif($this->message[$pos]['type'] == 'Map' && $this->message[$pos]['type_namespace'] == 'http://xml.apache.org/xml-soap'){
7063 $this->debug('in buildVal, Java Map '.$this->message[$pos]['name']);
7064 foreach($children as $child_pos){
7065 $kv = explode("|",$this->message[$child_pos]['children']);
7066 $params[$this->message[$kv[1]]['result']] = &$this->message[$kv[2]]['result'];
7068 // generic compound type
7069 //} elseif($this->message[$pos]['type'] == 'SOAPStruct' || $this->message[$pos]['type'] == 'struct') {
7071 // Apache Vector type: treat as an array
7072 $this->debug('in buildVal, adding Java Vector or generic compound type '.$this->message[$pos]['name']);
7073 if ($this->message[$pos]['type'] == 'Vector' && $this->message[$pos]['type_namespace'] == 'http://xml.apache.org/xml-soap') {
7079 foreach($children as $child_pos){
7081 $params[] = &$this->message[$child_pos]['result'];
7083 if (isset($params[$this->message[$child_pos]['name']])) {
7084 // de-serialize repeated element name into an array
7085 if ((!is_array($params[$this->message[$child_pos]['name']])) || (!isset($params[$this->message[$child_pos]['name']][0]))) {
7086 $params[$this->message[$child_pos]['name']] = array($params[$this->message[$child_pos]['name']]);
7088 $params[$this->message[$child_pos]['name']][] = &$this->message[$child_pos]['result'];
7090 $params[$this->message[$child_pos]['name']] = &$this->message[$child_pos]['result'];
7095 if (isset($this->message[$pos]['xattrs'])) {
7096 $this->debug('in buildVal, handling attributes');
7097 foreach ($this->message[$pos]['xattrs'] as $n => $v) {
7101 // handle simpleContent
7102 if (isset($this->message[$pos]['cdata']) && trim($this->message[$pos]['cdata']) != '') {
7103 $this->debug('in buildVal, handling simpleContent');
7104 if (isset($this->message[$pos]['type'])) {
7105 $params['!'] = $this->decodeSimple($this->message[$pos]['cdata'], $this->message[$pos]['type'], isset($this->message[$pos]['type_namespace']) ? $this->message[$pos]['type_namespace'] : '');
7107 $parent = $this->message[$pos]['parent'];
7108 if (isset($this->message[$parent]['type']) && ($this->message[$parent]['type'] == 'array') && isset($this->message[$parent]['arrayType'])) {
7109 $params['!'] = $this->decodeSimple($this->message[$pos]['cdata'], $this->message[$parent]['arrayType'], isset($this->message[$parent]['arrayTypeNamespace']) ? $this->message[$parent]['arrayTypeNamespace'] : '');
7111 $params['!'] = $this->message[$pos]['cdata'];
7115 $ret = is_array($params) ? $params : array();
7116 $this->debug('in buildVal, return:');
7117 $this->appendDebug($this->varDump($ret));
7120 $this->debug('in buildVal, no children, building scalar');
7121 $cdata = isset($this->message[$pos]['cdata']) ? $this->message[$pos]['cdata'] : '';
7122 if (isset($this->message[$pos]['type'])) {
7123 $ret = $this->decodeSimple($cdata, $this->message[$pos]['type'], isset($this->message[$pos]['type_namespace']) ? $this->message[$pos]['type_namespace'] : '');
7124 $this->debug("in buildVal, return: $ret");
7127 $parent = $this->message[$pos]['parent'];
7128 if (isset($this->message[$parent]['type']) && ($this->message[$parent]['type'] == 'array') && isset($this->message[$parent]['arrayType'])) {
7129 $ret = $this->decodeSimple($cdata, $this->message[$parent]['arrayType'], isset($this->message[$parent]['arrayTypeNamespace']) ? $this->message[$parent]['arrayTypeNamespace'] : '');
7130 $this->debug("in buildVal, return: $ret");
7133 $ret = $this->message[$pos]['cdata'];
7134 $this->debug("in buildVal, return: $ret");
7141 * Backward compatibility
7143 class soap_parser extends nusoap_parser {
7150 * [nu]soapclient higher level class for easy usage.
7154 * // instantiate client with server info
7155 * $soapclient = new nusoap_client( string path [ ,mixed wsdl] );
7157 * // call method, get results
7158 * echo $soapclient->call( string methodname [ ,array parameters] );
7161 * unset($soapclient);
7163 * @author Dietrich Ayala <dietrich@ganx4.com>
7164 * @author Scott Nichol <snichol@users.sourceforge.net>
7168 class nusoap_client extends nusoap_base {
7170 var $username = ''; // Username for HTTP authentication
7171 var $password = ''; // Password for HTTP authentication
7172 var $authtype = ''; // Type of HTTP authentication
7173 var $certRequest = array(); // Certificate for HTTP SSL authentication
7174 var $requestHeaders = false; // SOAP headers in request (text)
7175 var $responseHeaders = ''; // SOAP headers from response (incomplete namespace resolution) (text)
7176 var $responseHeader = NULL; // SOAP Header from response (parsed)
7177 var $document = ''; // SOAP body response portion (incomplete namespace resolution) (text)
7179 var $forceEndpoint = ''; // overrides WSDL endpoint
7180 var $proxyhost = '';
7181 var $proxyport = '';
7182 var $proxyusername = '';
7183 var $proxypassword = '';
7184 var $portName = ''; // port name to use in WSDL
7185 var $xml_encoding = ''; // character set encoding of incoming (response) messages
7186 var $http_encoding = false;
7187 var $timeout = 0; // HTTP connection timeout
7188 var $response_timeout = 30; // HTTP response timeout
7189 var $endpointType = ''; // soap|wsdl, empty for WSDL initialization error
7190 var $persistentConnection = false;
7191 var $defaultRpcParams = false; // This is no longer used
7192 var $request = ''; // HTTP request
7193 var $response = ''; // HTTP response
7194 var $responseData = ''; // SOAP payload of response
7195 var $cookies = array(); // Cookies from response or for request
7196 var $decode_utf8 = true; // toggles whether the parser decodes element content w/ utf8_decode()
7197 var $operations = array(); // WSDL operations, empty for WSDL initialization error
7198 var $curl_options = array(); // User-specified cURL options
7199 var $bindingType = ''; // WSDL operation binding type
7200 var $use_curl = false; // whether to always try to use cURL
7203 * fault related variables
7229 * @param mixed $endpoint SOAP server or WSDL URL (string), or wsdl instance (object)
7230 * @param mixed $wsdl optional, set to 'wsdl' or true if using WSDL
7231 * @param string $proxyhost optional
7232 * @param string $proxyport optional
7233 * @param string $proxyusername optional
7234 * @param string $proxypassword optional
7235 * @param integer $timeout set the connection timeout
7236 * @param integer $response_timeout set the response timeout
7237 * @param string $portName optional portName in WSDL document
7240 function nusoap_client($endpoint,$wsdl = false,$proxyhost = false,$proxyport = false,$proxyusername = false, $proxypassword = false, $timeout = 0, $response_timeout = 30, $portName = ''){
7241 parent::nusoap_base();
7242 $this->endpoint = $endpoint;
7243 $this->proxyhost = $proxyhost;
7244 $this->proxyport = $proxyport;
7245 $this->proxyusername = $proxyusername;
7246 $this->proxypassword = $proxypassword;
7247 $this->timeout = $timeout;
7248 $this->response_timeout = $response_timeout;
7249 $this->portName = $portName;
7251 $this->debug("ctor wsdl=$wsdl timeout=$timeout response_timeout=$response_timeout");
7252 $this->appendDebug('endpoint=' . $this->varDump($endpoint));
7256 if (is_object($endpoint) && (get_class($endpoint) == 'wsdl')) {
7257 $this->wsdl = $endpoint;
7258 $this->endpoint = $this->wsdl->wsdl;
7259 $this->wsdlFile = $this->endpoint;
7260 $this->debug('existing wsdl instance created from ' . $this->endpoint);
7263 $this->wsdlFile = $this->endpoint;
7265 $this->debug('will use lazy evaluation of wsdl from ' . $this->endpoint);
7267 $this->endpointType = 'wsdl';
7269 $this->debug("instantiate SOAP with endpoint at $endpoint");
7270 $this->endpointType = 'soap';
7275 * calls method, returns PHP native type
7277 * @param string $operation SOAP server URL or path
7278 * @param mixed $params An array, associative or simple, of the parameters
7279 * for the method call, or a string that is the XML
7280 * for the call. For rpc style, this call will
7281 * wrap the XML in a tag named after the method, as
7282 * well as the SOAP Envelope and Body. For document
7283 * style, this will only wrap with the Envelope and Body.
7284 * IMPORTANT: when using an array with document style,
7285 * in which case there
7286 * is really one parameter, the root of the fragment
7287 * used in the call, which encloses what programmers
7288 * normally think of parameters. A parameter array
7289 * *must* include the wrapper.
7290 * @param string $namespace optional method namespace (WSDL can override)
7291 * @param string $soapAction optional SOAPAction value (WSDL can override)
7292 * @param mixed $headers optional string of XML with SOAP header content, or array of soapval objects for SOAP headers, or associative array
7293 * @param boolean $rpcParams optional (no longer used)
7294 * @param string $style optional (rpc|document) the style to use when serializing parameters (WSDL can override)
7295 * @param string $use optional (encoded|literal) the use when serializing parameters (WSDL can override)
7296 * @return mixed response from SOAP call, normally an associative array mirroring the structure of the XML response, false for certain fatal errors
7299 function call($operation,$params=array(),$namespace='http://tempuri.org',$soapAction='',$headers=false,$rpcParams=null,$style='rpc',$use='encoded'){
7300 $this->operation = $operation;
7301 $this->fault = false;
7302 $this->setError('');
7303 $this->request = '';
7304 $this->response = '';
7305 $this->responseData = '';
7306 $this->faultstring = '';
7307 $this->faultcode = '';
7308 $this->opData = array();
7310 $this->debug("call: operation=$operation, namespace=$namespace, soapAction=$soapAction, rpcParams=$rpcParams, style=$style, use=$use, endpointType=$this->endpointType");
7311 $this->appendDebug('params=' . $this->varDump($params));
7312 $this->appendDebug('headers=' . $this->varDump($headers));
7314 $this->requestHeaders = $headers;
7316 if ($this->endpointType == 'wsdl' && is_null($this->wsdl)) {
7318 if ($this->getError())
7321 // serialize parameters
7322 if($this->endpointType == 'wsdl' && $opData = $this->getOperationData($operation)){
7323 // use WSDL for operation
7324 $this->opData = $opData;
7325 $this->debug("found operation");
7326 $this->appendDebug('opData=' . $this->varDump($opData));
7327 if (isset($opData['soapAction'])) {
7328 $soapAction = $opData['soapAction'];
7330 if (! $this->forceEndpoint) {
7331 $this->endpoint = $opData['endpoint'];
7333 $this->endpoint = $this->forceEndpoint;
7335 $namespace = isset($opData['input']['namespace']) ? $opData['input']['namespace'] : $namespace;
7336 $style = $opData['style'];
7337 $use = $opData['input']['use'];
7338 // add ns to ns array
7339 if($namespace != '' && !isset($this->wsdl->namespaces[$namespace])){
7340 $nsPrefix = 'ns' . util_randnum(1000, 9999);
7341 $this->wsdl->namespaces[$nsPrefix] = $namespace;
7343 $nsPrefix = $this->wsdl->getPrefixFromNamespace($namespace);
7344 // serialize payload
7345 if (is_string($params)) {
7346 $this->debug("serializing param string for WSDL operation $operation");
7348 } elseif (is_array($params)) {
7349 $this->debug("serializing param array for WSDL operation $operation");
7350 $payload = $this->wsdl->serializeRPCParameters($operation,'input',$params,$this->bindingType);
7352 $this->debug('params must be array or string');
7353 $this->setError('params must be array or string');
7356 $usedNamespaces = $this->wsdl->usedNamespaces;
7357 if (isset($opData['input']['encodingStyle'])) {
7358 $encodingStyle = $opData['input']['encodingStyle'];
7360 $encodingStyle = '';
7362 $this->appendDebug($this->wsdl->getDebug());
7363 $this->wsdl->clearDebug();
7364 if ($errstr = $this->wsdl->getError()) {
7365 $this->debug('got wsdl error: '.$errstr);
7366 $this->setError('wsdl error: '.$errstr);
7369 } elseif($this->endpointType == 'wsdl') {
7370 // operation not in WSDL
7371 $this->appendDebug($this->wsdl->getDebug());
7372 $this->wsdl->clearDebug();
7373 $this->setError('operation '.$operation.' not present in WSDL.');
7374 $this->debug("operation '$operation' not present in WSDL.");
7378 //$this->namespaces['ns1'] = $namespace;
7379 $nsPrefix = 'ns' . util_randnum(1000, 9999);
7382 if (is_string($params)) {
7383 $this->debug("serializing param string for operation $operation");
7385 } elseif (is_array($params)) {
7386 $this->debug("serializing param array for operation $operation");
7387 foreach($params as $k => $v){
7388 $payload .= $this->serialize_val($v,$k,false,false,false,false,$use);
7391 $this->debug('params must be array or string');
7392 $this->setError('params must be array or string');
7395 $usedNamespaces = array();
7396 if ($use == 'encoded') {
7397 $encodingStyle = 'http://schemas.xmlsoap.org/soap/encoding/';
7399 $encodingStyle = '';
7402 // wrap RPC calls with method element
7403 if ($style == 'rpc') {
7404 if ($use == 'literal') {
7405 $this->debug("wrapping RPC request with literal method element");
7407 // http://www.ws-i.org/Profiles/BasicProfile-1.1-2004-08-24.html R2735 says rpc/literal accessor elements should not be in a namespace
7408 $payload = "<$nsPrefix:$operation xmlns:$nsPrefix=\"$namespace\">" .
7410 "</$nsPrefix:$operation>";
7412 $payload = "<$operation>" . $payload . "</$operation>";
7415 $this->debug("wrapping RPC request with encoded method element");
7417 $payload = "<$nsPrefix:$operation xmlns:$nsPrefix=\"$namespace\">" .
7419 "</$nsPrefix:$operation>";
7421 $payload = "<$operation>" .
7427 // serialize envelope
7428 $soapmsg = $this->serializeEnvelope($payload,$this->requestHeaders,$usedNamespaces,$style,$use,$encodingStyle);
7429 $this->debug("endpoint=$this->endpoint, soapAction=$soapAction, namespace=$namespace, style=$style, use=$use, encodingStyle=$encodingStyle");
7430 $this->debug('SOAP message length=' . strlen($soapmsg) . ' contents (max 1000 bytes)=' . substr($soapmsg, 0, 1000));
7432 $return = $this->send($this->getHTTPBody($soapmsg),$soapAction,$this->timeout,$this->response_timeout);
7433 if($errstr = $this->getError()){
7434 $this->debug('Error: '.$errstr);
7437 $this->return = $return;
7438 $this->debug('sent message successfully and got a(n) '.gettype($return));
7439 $this->appendDebug('return=' . $this->varDump($return));
7442 if(is_array($return) && isset($return['faultcode'])){
7443 $this->debug('got fault');
7444 $this->setError($return['faultcode'].': '.$return['faultstring']);
7445 $this->fault = true;
7446 foreach($return as $k => $v){
7448 $this->debug("$k = $v<br>");
7451 } elseif ($style == 'document') {
7452 // NOTE: if the response is defined to have multiple parts (i.e. unwrapped),
7453 // we are only going to return the first part here...sorry about that
7456 // array of return values
7457 if(is_array($return)){
7458 // multiple 'out' parameters, which we return wrapped up
7460 if(sizeof($return) > 1){
7463 // single 'out' parameter (normally the return value)
7464 $return = array_shift($return);
7465 $this->debug('return shifted value: ');
7466 $this->appendDebug($this->varDump($return));
7468 // nothing returned (ie, echoVoid)
7477 * check WSDL passed as an instance or pulled from an endpoint
7481 function checkWSDL() {
7482 $this->appendDebug($this->wsdl->getDebug());
7483 $this->wsdl->clearDebug();
7484 $this->debug('checkWSDL');
7486 if ($errstr = $this->wsdl->getError()) {
7487 $this->appendDebug($this->wsdl->getDebug());
7488 $this->wsdl->clearDebug();
7489 $this->debug('got wsdl error: '.$errstr);
7490 $this->setError('wsdl error: '.$errstr);
7491 } elseif ($this->operations = $this->wsdl->getOperations($this->portName, 'soap')) {
7492 $this->appendDebug($this->wsdl->getDebug());
7493 $this->wsdl->clearDebug();
7494 $this->bindingType = 'soap';
7495 $this->debug('got '.count($this->operations).' operations from wsdl '.$this->wsdlFile.' for binding type '.$this->bindingType);
7496 } elseif ($this->operations = $this->wsdl->getOperations($this->portName, 'soap12')) {
7497 $this->appendDebug($this->wsdl->getDebug());
7498 $this->wsdl->clearDebug();
7499 $this->bindingType = 'soap12';
7500 $this->debug('got '.count($this->operations).' operations from wsdl '.$this->wsdlFile.' for binding type '.$this->bindingType);
7501 $this->debug('**************** WARNING: SOAP 1.2 BINDING *****************');
7503 $this->appendDebug($this->wsdl->getDebug());
7504 $this->wsdl->clearDebug();
7505 $this->debug('getOperations returned false');
7506 $this->setError('no operations defined in the WSDL document!');
7511 * instantiate wsdl object and parse wsdl file
7515 function loadWSDL() {
7516 $this->debug('instantiating wsdl class with doc: '.$this->wsdlFile);
7517 $this->wsdl = new wsdl('',$this->proxyhost,$this->proxyport,$this->proxyusername,$this->proxypassword,$this->timeout,$this->response_timeout,$this->curl_options,$this->use_curl);
7518 $this->wsdl->setCredentials($this->username, $this->password, $this->authtype, $this->certRequest);
7519 $this->wsdl->fetchWSDL($this->wsdlFile);
7524 * get available data pertaining to an operation
7526 * @param string $operation operation name
7527 * @return array array of data pertaining to the operation
7530 function getOperationData($operation){
7531 if ($this->endpointType == 'wsdl' && is_null($this->wsdl)) {
7533 if ($this->getError())
7536 if(isset($this->operations[$operation])){
7537 return $this->operations[$operation];
7539 $this->debug("No data for operation: $operation");
7543 * send the SOAP message
7545 * Note: if the operation has multiple return values
7546 * the return value of this method will be an array
7549 * @param string $msg a SOAPx4 soapmsg object
7550 * @param string $soapaction SOAPAction value
7551 * @param integer $timeout set connection timeout in seconds
7552 * @param integer $response_timeout set response timeout in seconds
7553 * @return mixed native PHP types.
7556 function send($msg, $soapaction = '', $timeout=0, $response_timeout=30) {
7557 $this->checkCookies();
7561 case preg_match('/^http/',$this->endpoint):
7562 $this->debug('transporting via HTTP');
7563 if($this->persistentConnection == true && is_object($this->persistentConnection)){
7564 $http =& $this->persistentConnection;
7566 $http = new soap_transport_http($this->endpoint, $this->curl_options, $this->use_curl);
7567 if ($this->persistentConnection) {
7568 $http->usePersistentConnection();
7571 $http->setContentType($this->getHTTPContentType(), $this->getHTTPContentTypeCharset());
7572 $http->setSOAPAction($soapaction);
7573 if($this->proxyhost && $this->proxyport){
7574 $http->setProxy($this->proxyhost,$this->proxyport,$this->proxyusername,$this->proxypassword);
7576 if($this->authtype != '') {
7577 $http->setCredentials($this->username, $this->password, $this->authtype, array(), $this->certRequest);
7579 if($this->http_encoding != ''){
7580 $http->setEncoding($this->http_encoding);
7582 $this->debug('sending message, length='.strlen($msg));
7583 if(preg_match('/^http:/',$this->endpoint)){
7584 //if(strpos($this->endpoint,'http:')){
7585 $this->responseData = $http->send($msg,$timeout,$response_timeout,$this->cookies);
7586 } elseif(preg_match('/^https/',$this->endpoint)){
7587 //} elseif(strpos($this->endpoint,'https:')){
7588 //if(phpversion() == '4.3.0-dev'){
7589 //$response = $http->send($msg,$timeout,$response_timeout);
7590 //$this->request = $http->outgoing_payload;
7591 //$this->response = $http->incoming_payload;
7593 $this->responseData = $http->sendHTTPS($msg,$timeout,$response_timeout,$this->cookies);
7595 $this->setError('no http/s in endpoint url');
7597 $this->request = $http->outgoing_payload;
7598 $this->response = $http->incoming_payload;
7599 $this->appendDebug($http->getDebug());
7600 $this->UpdateCookies($http->incoming_cookies);
7602 // save transport object if using persistent connections
7603 if ($this->persistentConnection) {
7604 $http->clearDebug();
7605 if (!is_object($this->persistentConnection)) {
7606 $this->persistentConnection = $http;
7610 if($err = $http->getError()){
7611 $this->setError('HTTP Error: '.$err);
7613 } elseif($this->getError()){
7616 $this->debug('got response, length='. strlen($this->responseData).' type='.$http->incoming_headers['content-type']);
7617 return $this->parseResponse($http->incoming_headers, $this->responseData);
7621 $this->setError('no transport found, or selected transport is not yet supported!');
7628 * processes SOAP message returned from server
7630 * @param array $headers The HTTP headers
7631 * @param string $data unprocessed response data from server
7632 * @return mixed value of the message, decoded into a PHP type
7635 function parseResponse($headers, $data) {
7636 $this->debug('Entering parseResponse() for data of length ' . strlen($data) . ' headers:');
7637 $this->appendDebug($this->varDump($headers));
7638 if (!isset($headers['content-type'])) {
7639 $this->setError('Response not of type text/xml (no content-type header)');
7642 if (!strstr($headers['content-type'], 'text/xml')) {
7643 $this->setError('Response not of type text/xml: ' . $headers['content-type']);
7646 if (strpos($headers['content-type'], '=')) {
7647 $enc = str_replace('"', '', substr(strstr($headers["content-type"], '='), 1));
7648 $this->debug('Got response encoding: ' . $enc);
7649 if(preg_match('/^(ISO-8859-1|US-ASCII|UTF-8)$/i',$enc)){
7650 $this->xml_encoding = strtoupper($enc);
7652 $this->xml_encoding = 'US-ASCII';
7655 // should be US-ASCII for HTTP 1.0 or ISO-8859-1 for HTTP 1.1
7656 $this->xml_encoding = 'ISO-8859-1';
7658 $this->debug('Use encoding: ' . $this->xml_encoding . ' when creating nusoap_parser');
7659 $parser = new nusoap_parser($data,$this->xml_encoding,$this->operation,$this->decode_utf8);
7660 // add parser debug data to our debug
7661 $this->appendDebug($parser->getDebug());
7663 if($errstr = $parser->getError()){
7664 $this->setError( $errstr);
7665 // destroy the parser object
7670 $this->responseHeaders = $parser->getHeaders();
7672 $this->responseHeader = $parser->get_soapheader();
7673 // get decoded message
7674 $return = $parser->get_soapbody();
7675 // add document for doclit support
7676 $this->document = $parser->document;
7677 // destroy the parser object
7679 // return decode message
7685 * sets user-specified cURL options
7687 * @param mixed $option The cURL option (always integer?)
7688 * @param mixed $value The cURL option value
7691 function setCurlOption($option, $value) {
7692 $this->debug("setCurlOption option=$option, value=");
7693 $this->appendDebug($this->varDump($value));
7694 $this->curl_options[$option] = $value;
7698 * sets the SOAP endpoint, which can override WSDL
7700 * @param string $endpoint The endpoint URL to use, or empty string or false to prevent override
7703 function setEndpoint($endpoint) {
7704 $this->debug("setEndpoint(\"$endpoint\")");
7705 $this->forceEndpoint = $endpoint;
7709 * set the SOAP headers
7711 * @param mixed $headers String of XML with SOAP header content, or array of soapval objects for SOAP headers
7714 function setHeaders($headers){
7715 $this->debug("setHeaders headers=");
7716 $this->appendDebug($this->varDump($headers));
7717 $this->requestHeaders = $headers;
7721 * get the SOAP response headers (namespace resolution incomplete)
7726 function getHeaders(){
7727 return $this->responseHeaders;
7731 * get the SOAP response Header (parsed)
7736 function getHeader(){
7737 return $this->responseHeader;
7741 * set proxy info here
7743 * @param string $proxyhost
7744 * @param string $proxyport
7745 * @param string $proxyusername
7746 * @param string $proxypassword
7749 function setHTTPProxy($proxyhost, $proxyport, $proxyusername = '', $proxypassword = '') {
7750 $this->proxyhost = $proxyhost;
7751 $this->proxyport = $proxyport;
7752 $this->proxyusername = $proxyusername;
7753 $this->proxypassword = $proxypassword;
7757 * if authenticating, set user credentials here
7759 * @param string $username
7760 * @param string $password
7761 * @param string $authtype (basic|digest|certificate|ntlm)
7762 * @param array $certRequest (keys must be cainfofile (optional), sslcertfile, sslkeyfile, passphrase, verifypeer (optional), verifyhost (optional): see corresponding options in cURL docs)
7765 function setCredentials($username, $password, $authtype = 'basic', $certRequest = array()) {
7766 $this->debug("setCredentials username=$username authtype=$authtype certRequest=");
7767 $this->appendDebug($this->varDump($certRequest));
7768 $this->username = $username;
7769 $this->password = $password;
7770 $this->authtype = $authtype;
7771 $this->certRequest = $certRequest;
7777 * @param string $enc HTTP encoding
7780 function setHTTPEncoding($enc='gzip, deflate'){
7781 $this->debug("setHTTPEncoding(\"$enc\")");
7782 $this->http_encoding = $enc;
7786 * Set whether to try to use cURL connections if possible
7788 * @param boolean $use Whether to try to use cURL
7791 function setUseCURL($use) {
7792 $this->debug("setUseCURL($use)");
7793 $this->use_curl = $use;
7797 * use HTTP persistent connections if possible
7801 function useHTTPPersistentConnection(){
7802 $this->debug("useHTTPPersistentConnection");
7803 $this->persistentConnection = true;
7807 * gets the default RPC parameter setting.
7808 * If true, default is that call params are like RPC even for document style.
7809 * Each call() can override this value.
7811 * This is no longer used.
7817 function getDefaultRpcParams() {
7818 return $this->defaultRpcParams;
7822 * sets the default RPC parameter setting.
7823 * If true, default is that call params are like RPC even for document style
7824 * Each call() can override this value.
7826 * This is no longer used.
7828 * @param boolean $rpcParams
7832 function setDefaultRpcParams($rpcParams) {
7833 $this->defaultRpcParams = $rpcParams;
7837 * dynamically creates an instance of a proxy class,
7838 * allowing user to directly call methods from wsdl
7840 * @return object soap_proxy object
7843 function getProxy() {
7844 $r = util_randnum();
7845 $evalStr = $this->_getProxyClassCode($r);
7846 //$this->debug("proxy class: $evalStr");
7847 if ($this->getError()) {
7848 $this->debug("Error from _getProxyClassCode, so return NULL");
7853 // instantiate proxy object
7854 eval("\$proxy = new nusoap_proxy_$r('');");
7855 // transfer current wsdl data to the proxy thereby avoiding parsing the wsdl twice
7856 $proxy->endpointType = 'wsdl';
7857 $proxy->wsdlFile = $this->wsdlFile;
7858 $proxy->wsdl = $this->wsdl;
7859 $proxy->operations = $this->operations;
7860 $proxy->defaultRpcParams = $this->defaultRpcParams;
7861 // transfer other state
7862 $proxy->soap_defencoding = $this->soap_defencoding;
7863 $proxy->username = $this->username;
7864 $proxy->password = $this->password;
7865 $proxy->authtype = $this->authtype;
7866 $proxy->certRequest = $this->certRequest;
7867 $proxy->requestHeaders = $this->requestHeaders;
7868 $proxy->endpoint = $this->endpoint;
7869 $proxy->forceEndpoint = $this->forceEndpoint;
7870 $proxy->proxyhost = $this->proxyhost;
7871 $proxy->proxyport = $this->proxyport;
7872 $proxy->proxyusername = $this->proxyusername;
7873 $proxy->proxypassword = $this->proxypassword;
7874 $proxy->http_encoding = $this->http_encoding;
7875 $proxy->timeout = $this->timeout;
7876 $proxy->response_timeout = $this->response_timeout;
7877 $proxy->persistentConnection = &$this->persistentConnection;
7878 $proxy->decode_utf8 = $this->decode_utf8;
7879 $proxy->curl_options = $this->curl_options;
7880 $proxy->bindingType = $this->bindingType;
7881 $proxy->use_curl = $this->use_curl;
7886 * dynamically creates proxy class code
7888 * @return string PHP/NuSOAP code for the proxy class
7891 function _getProxyClassCode($r) {
7892 $this->debug("in getProxy endpointType=$this->endpointType");
7893 $this->appendDebug("wsdl=" . $this->varDump($this->wsdl));
7894 if ($this->endpointType != 'wsdl') {
7895 $evalStr = 'A proxy can only be created for a WSDL client';
7896 $this->setError($evalStr);
7897 $evalStr = "echo \"$evalStr\";";
7900 if ($this->endpointType == 'wsdl' && is_null($this->wsdl)) {
7902 if ($this->getError()) {
7903 return "echo \"" . $this->getError() . "\";";
7907 foreach ($this->operations as $operation => $opData) {
7908 if ($operation != '') {
7909 // create param string and param comment string
7910 if (sizeof($opData['input']['parts']) > 0) {
7912 $paramArrayStr = '';
7913 $paramCommentStr = '';
7914 foreach ($opData['input']['parts'] as $name => $type) {
7915 $paramStr .= "\$$name, ";
7916 $paramArrayStr .= "'$name' => \$$name, ";
7917 $paramCommentStr .= "$type \$$name, ";
7919 $paramStr = substr($paramStr, 0, strlen($paramStr)-2);
7920 $paramArrayStr = substr($paramArrayStr, 0, strlen($paramArrayStr)-2);
7921 $paramCommentStr = substr($paramCommentStr, 0, strlen($paramCommentStr)-2);
7924 $paramArrayStr = '';
7925 $paramCommentStr = 'void';
7927 $opData['namespace'] = !isset($opData['namespace']) ? 'http://testuri.com' : $opData['namespace'];
7928 $evalStr .= "// $paramCommentStr
7929 function " . str_replace('.', '__', $operation) . "($paramStr) {
7930 \$params = array($paramArrayStr);
7931 return \$this->call('$operation', \$params, '".$opData['namespace']."', '".(isset($opData['soapAction']) ? $opData['soapAction'] : '')."');
7935 unset($paramCommentStr);
7938 $evalStr = 'class nusoap_proxy_'.$r.' extends nusoap_client {
7945 * dynamically creates proxy class code
7947 * @return string PHP/NuSOAP code for the proxy class
7950 function getProxyClassCode() {
7951 $r = util_randnum();
7952 return $this->_getProxyClassCode($r);
7956 * gets the HTTP body for the current request.
7958 * @param string $soapmsg The SOAP payload
7959 * @return string The HTTP body, which includes the SOAP payload
7962 function getHTTPBody($soapmsg) {
7967 * gets the HTTP content type for the current request.
7969 * Note: getHTTPBody must be called before this.
7971 * @return string the HTTP content type for the current request.
7974 function getHTTPContentType() {
7979 * gets the HTTP content type charset for the current request.
7980 * returns false for non-text content types.
7982 * Note: getHTTPBody must be called before this.
7984 * @return string the HTTP content type charset for the current request.
7987 function getHTTPContentTypeCharset() {
7988 return $this->soap_defencoding;
7992 * whether or not parser should decode utf8 element content
7994 * @return always returns true
7997 function decodeUTF8($bool){
7998 $this->decode_utf8 = $bool;
8003 * adds a new Cookie into $this->cookies array
8005 * @param string $name Cookie Name
8006 * @param string $value Cookie Value
8007 * @return boolean if cookie-set was successful returns true, else false
8010 function setCookie($name, $value) {
8011 if (strlen($name) == 0) {
8014 $this->cookies[] = array('name' => $name, 'value' => $value);
8021 * @return array with all internal cookies
8024 function getCookies() {
8025 return $this->cookies;
8029 * checks all Cookies and delete those which are expired
8031 * @return boolean always return true
8034 function checkCookies() {
8035 if (sizeof($this->cookies) == 0) {
8038 $this->debug('checkCookie: check ' . sizeof($this->cookies) . ' cookies');
8039 $curr_cookies = $this->cookies;
8040 $this->cookies = array();
8041 foreach ($curr_cookies as $cookie) {
8042 if (! is_array($cookie)) {
8043 $this->debug('Remove cookie that is not an array');
8046 if ((isset($cookie['expires'])) && (! empty($cookie['expires']))) {
8047 if (strtotime($cookie['expires']) > time()) {
8048 $this->cookies[] = $cookie;
8050 $this->debug('Remove expired cookie ' . $cookie['name']);
8053 $this->cookies[] = $cookie;
8056 $this->debug('checkCookie: '.sizeof($this->cookies).' cookies left in array');
8061 * updates the current cookies with a new set
8063 * @param array $cookies new cookies with which to update current ones
8064 * @return boolean always return true
8067 function UpdateCookies($cookies) {
8068 if (sizeof($this->cookies) == 0) {
8069 // no existing cookies: take whatever is new
8070 if (sizeof($cookies) > 0) {
8071 $this->debug('Setting new cookie(s)');
8072 $this->cookies = $cookies;
8076 if (sizeof($cookies) == 0) {
8077 // no new cookies: keep what we've got
8081 foreach ($cookies as $newCookie) {
8082 if (!is_array($newCookie)) {
8085 if ((!isset($newCookie['name'])) || (!isset($newCookie['value']))) {
8088 $newName = $newCookie['name'];
8091 for ($i = 0; $i < count($this->cookies); $i++) {
8092 $cookie = $this->cookies[$i];
8093 if (!is_array($cookie)) {
8096 if (!isset($cookie['name'])) {
8099 if ($newName != $cookie['name']) {
8102 $newDomain = isset($newCookie['domain']) ? $newCookie['domain'] : 'NODOMAIN';
8103 $domain = isset($cookie['domain']) ? $cookie['domain'] : 'NODOMAIN';
8104 if ($newDomain != $domain) {
8107 $newPath = isset($newCookie['path']) ? $newCookie['path'] : 'NOPATH';
8108 $path = isset($cookie['path']) ? $cookie['path'] : 'NOPATH';
8109 if ($newPath != $path) {
8112 $this->cookies[$i] = $newCookie;
8114 $this->debug('Update cookie ' . $newName . '=' . $newCookie['value']);
8118 $this->debug('Add cookie ' . $newName . '=' . $newCookie['value']);
8119 $this->cookies[] = $newCookie;
8126 if (!extension_loaded('soap')) {
8128 * For backwards compatiblity, define soapclient unless the PHP SOAP extension is loaded.
8130 class soapclient extends nusoap_client {