4 $Id: nusoap.php,v 1.3 2008/07/10 16:06:24 ppollet Exp $
6 NuSOAP - Web Services Toolkit for PHP
8 Copyright (c) 2002 NuSphere Corporation
10 This library is free software; you can redistribute it and/or
11 modify it under the terms of the GNU Lesser General Public
12 License as published by the Free Software Foundation; either
13 version 2.1 of the License, or (at your option) any later version.
15 This library is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 Lesser General Public License for more details.
20 You should have received a copy of the GNU Lesser General Public
21 License along with this library; if not, write to the Free Software
22 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 If you have any questions or comments, please email:
28 http://dietrich.ganx4.com/nusoap
31 http://www.nusphere.com
38 require_once('class.soap_client.php');
39 require_once('class.soap_val.php');
40 require_once('class.soap_parser.php');
41 require_once('class.soap_fault.php');
44 require_once('class.soap_transport_http.php');
46 // optional add-on classes
47 require_once('class.xmlschema.php');
48 require_once('class.wsdl.php');
51 require_once('class.soap_server.php');*/
53 // class variable emulation
54 // cf. http://www.webkreator.com/php/techniques/php-static-class-variables.html
55 $GLOBALS['_transient']['static']['nusoap_base']->globalDebugLevel = 9;
61 * @author Dietrich Ayala <dietrich@ganx4.com>
62 * @version $Id: nusoap.php,v 1.3 2008/07/10 16:06:24 ppollet Exp $
67 * Identification for HTTP headers.
72 var $title = 'NuSOAP';
74 * Version for HTTP headers.
79 var $version = '0.7.2';
81 * CVS revision for HTTP headers.
86 var $revision = '$Revision: 1.3 $';
88 * Current error string (manipulated by getError/setError)
95 * Current debug string (manipulated by debug/appendDebug/clearDebug/getDebug/getDebugAsXMLComment)
102 * toggles automatic encoding of special characters as entities
103 * (should always be true, I think)
108 var $charencoding = true;
110 * the debug level for this instance
123 var $XMLSchemaVersion = 'http://www.w3.org/2001/XMLSchema';
126 * charset encoding for outgoing messages
131 var $soap_defencoding = 'ISO-8859-1';
132 //var $soap_defencoding = 'UTF-8';
135 * namespaces in an array of prefix => uri
137 * this is "seeded" by a set of constants, but it may be altered by code
142 var $namespaces = array(
143 'SOAP-ENV' => 'http://schemas.xmlsoap.org/soap/envelope/',
144 'xsd' => 'http://www.w3.org/2001/XMLSchema',
145 'xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
146 'SOAP-ENC' => 'http://schemas.xmlsoap.org/soap/encoding/'
150 * namespaces used in the current context, e.g. during serialization
155 var $usedNamespaces = array();
158 * XML Schema types in an array of uri => (array of xml type => php type)
159 * is this legacy yet?
160 * no, this is used by the xmlschema class to verify type => namespace mappings.
164 var $typemap = array(
165 'http://www.w3.org/2001/XMLSchema' => array(
166 'string'=>'string','boolean'=>'boolean','float'=>'double','double'=>'double','decimal'=>'double',
167 'duration'=>'','dateTime'=>'string','time'=>'string','date'=>'string','gYearMonth'=>'',
168 'gYear'=>'','gMonthDay'=>'','gDay'=>'','gMonth'=>'','hexBinary'=>'string','base64Binary'=>'string',
169 // abstract "any" types
170 'anyType'=>'string','anySimpleType'=>'string',
172 'normalizedString'=>'string','token'=>'string','language'=>'','NMTOKEN'=>'','NMTOKENS'=>'','Name'=>'','NCName'=>'','ID'=>'',
173 'IDREF'=>'','IDREFS'=>'','ENTITY'=>'','ENTITIES'=>'','integer'=>'integer','nonPositiveInteger'=>'integer',
174 'negativeInteger'=>'integer','long'=>'integer','int'=>'integer','short'=>'integer','byte'=>'integer','nonNegativeInteger'=>'integer',
175 'unsignedLong'=>'','unsignedInt'=>'','unsignedShort'=>'','unsignedByte'=>'','positiveInteger'=>''),
176 'http://www.w3.org/2000/10/XMLSchema' => array(
177 'i4'=>'','int'=>'integer','boolean'=>'boolean','string'=>'string','double'=>'double',
178 'float'=>'double','dateTime'=>'string',
179 'timeInstant'=>'string','base64Binary'=>'string','base64'=>'string','ur-type'=>'array'),
180 'http://www.w3.org/1999/XMLSchema' => array(
181 'i4'=>'','int'=>'integer','boolean'=>'boolean','string'=>'string','double'=>'double',
182 'float'=>'double','dateTime'=>'string',
183 'timeInstant'=>'string','base64Binary'=>'string','base64'=>'string','ur-type'=>'array'),
184 'http://soapinterop.org/xsd' => array('SOAPStruct'=>'struct'),
185 'http://schemas.xmlsoap.org/soap/encoding/' => array('base64'=>'string','array'=>'array','Array'=>'array'),
186 'http://xml.apache.org/xml-soap' => array('Map')
190 * XML entities to convert
195 * @see expandEntities
197 var $xmlEntities = array('quot' => '"','amp' => '&',
198 'lt' => '<','gt' => '>','apos' => "'");
205 function nusoap_base() {
206 $this->debugLevel = $GLOBALS['_transient']['static']['nusoap_base']->globalDebugLevel;
210 * gets the global debug level, which applies to future instances
212 * @return integer Debug level 0-9, where 0 turns off
215 function getGlobalDebugLevel() {
216 return $GLOBALS['_transient']['static']['nusoap_base']->globalDebugLevel;
220 * sets the global debug level, which applies to future instances
222 * @param int $level Debug level 0-9, where 0 turns off
225 function setGlobalDebugLevel($level) {
226 $GLOBALS['_transient']['static']['nusoap_base']->globalDebugLevel = $level;
230 * gets the debug level for this instance
232 * @return int Debug level 0-9, where 0 turns off
235 function getDebugLevel() {
236 return $this->debugLevel;
240 * sets the debug level for this instance
242 * @param int $level Debug level 0-9, where 0 turns off
245 function setDebugLevel($level) {
246 $this->debugLevel = $level;
250 * adds debug data to the instance debug string with formatting
252 * @param string $string debug data
255 function debug($string){
256 if ($this->debugLevel > 0) {
257 $this->appendDebug($this->getmicrotime().' '.get_class($this).": $string\n");
259 //file_put_contents('pp_debug.log',$string."\n",FILE_APPEND);
263 * adds debug data to the instance debug string without formatting
265 * @param string $string debug data
268 function appendDebug($string){
269 if ($this->debugLevel > 0) {
270 // it would be nice to use a memory stream here to use
271 // memory more efficiently
272 $this->debug_str .= $string;
277 * clears the current debug data for this instance
281 function clearDebug() {
282 // it would be nice to use a memory stream here to use
283 // memory more efficiently
284 $this->debug_str = '';
288 * gets the current debug data for this instance
293 function &getDebug() {
294 // it would be nice to use a memory stream here to use
295 // memory more efficiently
296 return $this->debug_str;
300 * gets the current debug data for this instance as an XML comment
301 * this may change the contents of the debug data
303 * @return debug data as an XML comment
306 function &getDebugAsXMLComment() {
307 // it would be nice to use a memory stream here to use
308 // memory more efficiently
309 while (strpos($this->debug_str, '--')) {
310 $this->debug_str = str_replace('--', '- -', $this->debug_str);
312 return "<!--\n" . $this->debug_str . "\n-->";
316 * expands entities, e.g. changes '<' to '<'.
318 * @param string $val The string in which to expand entities.
321 function expandEntities($val) {
322 if ($this->charencoding) {
323 $val = str_replace('&', '&', $val);
324 $val = str_replace("'", ''', $val);
325 $val = str_replace('"', '"', $val);
326 $val = str_replace('<', '<', $val);
327 $val = str_replace('>', '>', $val);
333 * returns error string if present
335 * @return mixed error string or false
339 if($this->error_str != ''){
340 return $this->error_str;
348 * @return boolean $string error string
351 function setError($str){
352 $this->error_str = $str;
356 * detect if array is a simple array or a struct (associative array)
358 * @param mixed $val The PHP array
359 * @return string (arraySimple|arrayStruct)
362 function isArraySimpleOrStruct($val) {
363 $keyList = array_keys($val);
364 foreach ($keyList as $keyListValue) {
365 if (!is_int($keyListValue)) {
366 return 'arrayStruct';
369 return 'arraySimple';
373 * serializes PHP values in accordance w/ section 5. Type information is
374 * not serialized if $use == 'literal'.
376 * @param mixed $val The value to serialize
377 * @param string $name The name (local part) of the XML element
378 * @param string $type The XML schema type (local part) for the element
379 * @param string $name_ns The namespace for the name of the XML element
380 * @param string $type_ns The namespace for the type of the element
381 * @param array $attributes The attributes to serialize as name=>value pairs
382 * @param string $use The WSDL "use" (encoded|literal)
383 * @return string The serialized element, possibly with child elements
386 function serialize_val($val,$name=false,$type=false,$name_ns=false,$type_ns=false,$attributes=false,$use='encoded'){
387 $this->debug("in serialize_val: name=$name, type=$type, name_ns=$name_ns, type_ns=$type_ns, use=$use");
388 $this->appendDebug('value=' . $this->varDump($val));
389 $this->appendDebug('attributes=' . $this->varDump($attributes));
391 if(is_object($val) && get_class($val) == 'soapval'){
392 return $val->serialize($use);
394 // force valid name if necessary
395 if (is_numeric($name)) {
396 $name = '__numeric_' . $name;
400 // if name has ns, add ns prefix to name
403 $prefix = 'nu'.rand(1000,9999);
404 $name = $prefix.':'.$name;
405 $xmlns .= " xmlns:$prefix=\"$name_ns\"";
407 // if type is prefixed, create type prefix
408 if($type_ns != '' && $type_ns == $this->namespaces['xsd']){
409 // need to fix this. shouldn't default to xsd if no ns specified
410 // w/o checking against typemap
411 $type_prefix = 'xsd';
413 $type_prefix = 'ns'.rand(1000,9999);
414 $xmlns .= " xmlns:$type_prefix=\"$type_ns\"";
416 // serialize attributes if present
419 foreach($attributes as $k => $v){
420 $atts .= " $k=\"".$this->expandEntities($v).'"';
423 // serialize null value
425 if ($use == 'literal') {
426 // TODO: depends on minOccurs
427 return "<$name$xmlns $atts/>";
429 if (isset($type) && isset($type_prefix)) {
430 $type_str = " xsi:type=\"$type_prefix:$type\"";
434 return "<$name$xmlns$type_str $atts xsi:nil=\"true\"/>";
437 // serialize if an xsd built-in primitive type
438 if($type != '' && isset($this->typemap[$this->XMLSchemaVersion][$type])){
440 if ($type == 'boolean') {
441 $val = $val ? 'true' : 'false';
445 } else if (is_string($val)) {
446 $val = $this->expandEntities($val);
448 if ($use == 'literal') {
449 return "<$name$xmlns $atts>$val</$name>";
451 return "<$name$xmlns $atts xsi:type=\"xsd:$type\">$val</$name>";
454 // detect type and serialize
457 case (is_bool($val) || $type == 'boolean'):
458 if ($type == 'boolean') {
459 $val = $val ? 'true' : 'false';
463 if ($use == 'literal') {
464 $xml .= "<$name$xmlns $atts>$val</$name>";
466 $xml .= "<$name$xmlns xsi:type=\"xsd:boolean\"$atts>$val</$name>";
469 case (is_int($val) || is_long($val) || $type == 'int'):
470 if ($use == 'literal') {
471 $xml .= "<$name$xmlns $atts>$val</$name>";
473 $xml .= "<$name$xmlns xsi:type=\"xsd:int\"$atts>$val</$name>";
476 case (is_float($val)|| is_double($val) || $type == 'float'):
477 if ($use == 'literal') {
478 $xml .= "<$name$xmlns $atts>$val</$name>";
480 $xml .= "<$name$xmlns xsi:type=\"xsd:float\"$atts>$val</$name>";
483 case (is_string($val) || $type == 'string'):
484 $val = $this->expandEntities($val);
485 if ($use == 'literal') {
486 $xml .= "<$name$xmlns $atts>$val</$name>";
488 $xml .= "<$name$xmlns xsi:type=\"xsd:string\"$atts>$val</$name>";
491 case is_object($val):
493 $name = get_class($val);
494 $this->debug("In serialize_val, used class name $name as element name");
496 $this->debug("In serialize_val, do not override name $name for element name for class " . get_class($val));
498 foreach(get_object_vars($val) as $k => $v){
499 $pXml = isset($pXml) ? $pXml.$this->serialize_val($v,$k,false,false,false,false,$use) : $this->serialize_val($v,$k,false,false,false,false,$use);
501 $xml .= '<'.$name.'>'.$pXml.'</'.$name.'>';
504 case (is_array($val) || $type):
505 // detect if struct or array
506 $valueType = $this->isArraySimpleOrStruct($val);
507 if($valueType=='arraySimple' || ereg('^ArrayOf',$type)){
509 if(is_array($val) && count($val)> 0){
511 if(is_object($v) && get_class($v) == 'soapval'){
512 $tt_ns = $v->type_ns;
514 } elseif (is_array($v)) {
515 $tt = $this->isArraySimpleOrStruct($v);
519 $array_types[$tt] = 1;
520 // TODO: for literal, the name should be $name
521 $xml .= $this->serialize_val($v,'item',false,false,false,false,$use);
524 if(count($array_types) > 1){
525 $array_typename = 'xsd:anyType';
526 } elseif(isset($tt) && isset($this->typemap[$this->XMLSchemaVersion][$tt])) {
527 if ($tt == 'integer') {
530 $array_typename = 'xsd:'.$tt;
531 } elseif(isset($tt) && $tt == 'arraySimple'){
532 $array_typename = 'SOAP-ENC:Array';
533 } elseif(isset($tt) && $tt == 'arrayStruct'){
534 $array_typename = 'unnamed_struct_use_soapval';
536 // if type is prefixed, create type prefix
537 if ($tt_ns != '' && $tt_ns == $this->namespaces['xsd']){
538 $array_typename = 'xsd:' . $tt;
540 $tt_prefix = 'ns' . rand(1000, 9999);
541 $array_typename = "$tt_prefix:$tt";
542 $xmlns .= " xmlns:$tt_prefix=\"$tt_ns\"";
544 $array_typename = $tt;
548 if ($use == 'literal') {
550 } else if (isset($type) && isset($type_prefix)) {
551 $type_str = " xsi:type=\"$type_prefix:$type\"";
553 $type_str = " xsi:type=\"SOAP-ENC:Array\" SOAP-ENC:arrayType=\"".$array_typename."[$array_type]\"";
557 if ($use == 'literal') {
559 } else if (isset($type) && isset($type_prefix)) {
560 $type_str = " xsi:type=\"$type_prefix:$type\"";
562 $type_str = " xsi:type=\"SOAP-ENC:Array\" SOAP-ENC:arrayType=\"xsd:anyType[0]\"";
565 // TODO: for array in literal, there is no wrapper here
566 $xml = "<$name$xmlns$type_str$atts>".$xml."</$name>";
569 if(isset($type) && isset($type_prefix)){
570 $type_str = " xsi:type=\"$type_prefix:$type\"";
574 if ($use == 'literal') {
575 $xml .= "<$name$xmlns $atts>";
577 $xml .= "<$name$xmlns$type_str$atts>";
579 foreach($val as $k => $v){
581 if ($type == 'Map' && $type_ns == 'http://xml.apache.org/xml-soap') {
583 $xml .= $this->serialize_val($k,'key',false,false,false,false,$use);
584 $xml .= $this->serialize_val($v,'value',false,false,false,false,$use);
587 $xml .= $this->serialize_val($v,$k,false,false,false,false,$use);
594 $xml .= 'not detected, got '.gettype($val).' for '.$val;
601 * serializes a message
603 * @param string $body the XML of the SOAP body
604 * @param mixed $headers optional string of XML with SOAP header content, or array of soapval objects for SOAP headers
605 * @param array $namespaces optional the namespaces used in generating the body and headers
606 * @param string $style optional (rpc|document)
607 * @param string $use optional (encoded|literal)
608 * @param string $encodingStyle optional (usually 'http://schemas.xmlsoap.org/soap/encoding/' for encoded)
609 * @return string the message
612 function serializeEnvelope($body,$headers=false,$namespaces=array(),$style='rpc',$use='encoded',$encodingStyle='http://schemas.xmlsoap.org/soap/encoding/'){
613 // TODO: add an option to automatically run utf8_encode on $body and $headers
614 // if $this->soap_defencoding is UTF-8. Not doing this automatically allows
615 // one to send arbitrary UTF-8 characters, not just characters that map to ISO-8859-1
617 $this->debug("In serializeEnvelope length=" . strlen($body) . " body (max 1000 characters)=" . substr($body, 0, 1000) . " style=$style use=$use encodingStyle=$encodingStyle");
618 $this->debug("headers:");
619 $this->appendDebug($this->varDump($headers));
620 $this->debug("namespaces:");
621 $this->appendDebug($this->varDump($namespaces));
623 // serialize namespaces
625 foreach(array_merge($this->namespaces,$namespaces) as $k => $v){
626 $ns_string .= " xmlns:$k=\"$v\"";
629 $ns_string = " SOAP-ENV:encodingStyle=\"$encodingStyle\"$ns_string";
634 if (is_array($headers)) {
636 foreach ($headers as $header) {
637 $xml .= $this->serialize_val($header, false, false, false, false, false, $use);
640 $this->debug("In serializeEnvelope, serialzied array of headers to $headers");
642 $headers = "<SOAP-ENV:Header>".$headers."</SOAP-ENV:Header>";
644 // serialize envelope
646 '<?xml version="1.0" encoding="'.$this->soap_defencoding .'"?'.">".
647 '<SOAP-ENV:Envelope'.$ns_string.">".
652 "</SOAP-ENV:Envelope>";
656 * formats a string to be inserted into an HTML stream
658 * @param string $str The string to format
659 * @return string The formatted string
663 function formatDump($str){
664 $str = htmlspecialchars($str);
669 * contracts (changes namespace to prefix) a qualified name
671 * @param string $qname qname
672 * @return string contracted qname
675 function contractQname($qname){
676 // get element namespace
677 //$this->xdebug("Contract $qname");
678 if (strrpos($qname, ':')) {
679 // get unqualified name
680 $name = substr($qname, strrpos($qname, ':') + 1);
682 $ns = substr($qname, 0, strrpos($qname, ':'));
683 $p = $this->getPrefixFromNamespace($ns);
685 return $p . ':' . $name;
694 * expands (changes prefix to namespace) a qualified name
696 * @param string $string qname
697 * @return string expanded qname
700 function expandQname($qname){
701 // get element prefix
702 if(strpos($qname,':') && !ereg('^http://',$qname)){
703 // get unqualified name
704 $name = substr(strstr($qname,':'),1);
706 $prefix = substr($qname,0,strpos($qname,':'));
707 if(isset($this->namespaces[$prefix])){
708 return $this->namespaces[$prefix].':'.$name;
718 * returns the local part of a prefixed string
719 * returns the original string, if not prefixed
721 * @param string $str The prefixed string
722 * @return string The local part
725 function getLocalPart($str){
726 if($sstr = strrchr($str,':')){
727 // get unqualified name
728 return substr( $sstr, 1 );
735 * returns the prefix part of a prefixed string
736 * returns false, if not prefixed
738 * @param string $str The prefixed string
739 * @return mixed The prefix or false if there is no prefix
742 function getPrefix($str){
743 if($pos = strrpos($str,':')){
745 return substr($str,0,$pos);
751 * pass it a prefix, it returns a namespace
753 * @param string $prefix The prefix
754 * @return mixed The namespace, false if no namespace has the specified prefix
757 function getNamespaceFromPrefix($prefix){
758 if (isset($this->namespaces[$prefix])) {
759 return $this->namespaces[$prefix];
761 //$this->setError("No namespace registered for prefix '$prefix'");
766 * returns the prefix for a given namespace (or prefix)
767 * or false if no prefixes registered for the given namespace
769 * @param string $ns The namespace
770 * @return mixed The prefix, false if the namespace has no prefixes
773 function getPrefixFromNamespace($ns) {
774 foreach ($this->namespaces as $p => $n) {
775 if ($ns == $n || $ns == $p) {
776 $this->usedNamespaces[$p] = $n;
784 * returns the time in ODBC canonical form with microseconds
786 * @return string The time in ODBC canonical form with microseconds
789 function getmicrotime() {
790 if (function_exists('gettimeofday')) {
791 $tod = gettimeofday();
793 $usec = $tod['usec'];
798 return strftime('%Y-%m-%d %H:%M:%S', $sec) . '.' . sprintf('%06d', $usec);
802 * Returns a string with the output of var_dump
804 * @param mixed $data The variable to var_dump
805 * @return string The output of var_dump
808 function varDump($data) {
811 $ret_val = ob_get_contents();
817 // XML Schema Datatype Helper Functions
819 //xsd:dateTime helpers
822 * convert unix timestamp to ISO 8601 compliant date string
824 * @param string $timestamp Unix time stamp
827 function timestamp_to_iso8601($timestamp,$utc=true){
828 $datestr = date('Y-m-d\TH:i:sO',$timestamp);
831 '([0-9]{4})-'. // centuries & years CCYY-
832 '([0-9]{2})-'. // months MM-
833 '([0-9]{2})'. // days DD
835 '([0-9]{2}):'. // hours hh:
836 '([0-9]{2}):'. // minutes mm:
837 '([0-9]{2})(\.[0-9]*)?'. // seconds ss.ss...
838 '(Z|[+\-][0-9]{2}:?[0-9]{2})?'; // Z to indicate UTC, -/+HH:MM:SS.SS... for local tz's
840 if(ereg($eregStr,$datestr,$regs)){
841 return sprintf('%04d-%02d-%02dT%02d:%02d:%02dZ',$regs[1],$regs[2],$regs[3],$regs[4],$regs[5],$regs[6]);
850 * convert ISO 8601 compliant date string to unix timestamp
852 * @param string $datestr ISO 8601 compliant date string
855 function iso8601_to_timestamp($datestr){
857 '([0-9]{4})-'. // centuries & years CCYY-
858 '([0-9]{2})-'. // months MM-
859 '([0-9]{2})'. // days DD
861 '([0-9]{2}):'. // hours hh:
862 '([0-9]{2}):'. // minutes mm:
863 '([0-9]{2})(\.[0-9]+)?'. // seconds ss.ss...
864 '(Z|[+\-][0-9]{2}:?[0-9]{2})?'; // Z to indicate UTC, -/+HH:MM:SS.SS... for local tz's
865 if(ereg($eregStr,$datestr,$regs)){
868 $op = substr($regs[8],0,1);
869 $h = substr($regs[8],1,2);
870 $m = substr($regs[8],strlen($regs[8])-2,2);
872 $regs[4] = $regs[4] + $h;
873 $regs[5] = $regs[5] + $m;
874 } elseif($op == '+'){
875 $regs[4] = $regs[4] - $h;
876 $regs[5] = $regs[5] - $m;
879 return strtotime("$regs[1]-$regs[2]-$regs[3] $regs[4]:$regs[5]:$regs[6]Z");
886 * sleeps some number of microseconds
888 * @param string $usec the number of microseconds to sleep
892 function usleepWindows($usec)
894 $start = gettimeofday();
898 $stop = gettimeofday();
899 $timePassed = 1000000 * ($stop['sec'] - $start['sec'])
900 + $stop['usec'] - $start['usec'];
902 while ($timePassed < $usec);
910 * Contains information for a SOAP fault.
911 * Mainly used for returning faults from deployed functions
912 * in a server instance.
913 * @author Dietrich Ayala <dietrich@ganx4.com>
914 * @version $Id: nusoap.php,v 1.3 2008/07/10 16:06:24 ppollet Exp $
917 class soap_fault extends nusoap_base {
919 * The fault code (client|server)
931 * The fault string, a description of the fault
937 * The fault detail, typically a string or array of string
946 * @param string $faultcode (SOAP-ENV:Client | SOAP-ENV:Server)
947 * @param string $faultactor only used when msg routed between multiple actors
948 * @param string $faultstring human readable error message
949 * @param mixed $faultdetail detail, typically a string or array of string
951 function soap_fault($faultcode,$faultactor='',$faultstring='',$faultdetail=''){
952 parent::nusoap_base();
953 $this->faultcode = $faultcode;
954 $this->faultactor = $faultactor;
955 $this->faultstring = $faultstring;
956 $this->faultdetail = $faultdetail;
962 * @return string The serialization of the fault instance.
965 function serialize(){
967 foreach($this->namespaces as $k => $v){
968 $ns_string .= "\n xmlns:$k=\"$v\"";
971 '<?xml version="1.0" encoding="'.$this->soap_defencoding.'"?>'.
972 '<SOAP-ENV:Envelope SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"'.$ns_string.">\n".
975 $this->serialize_val($this->faultcode, 'faultcode').
976 $this->serialize_val($this->faultactor, 'faultactor').
977 $this->serialize_val($this->faultstring, 'faultstring').
978 $this->serialize_val($this->faultdetail, 'detail').
981 '</SOAP-ENV:Envelope>';
993 * parses an XML Schema, allows access to it's data, other utility methods
994 * no validation... yet.
995 * very experimental and limited. As is discussed on XML-DEV, I'm one of the people
996 * that just doesn't have time to read the spec(s) thoroughly, and just have a couple of trusty
997 * tutorials I refer to :)
999 * @author Dietrich Ayala <dietrich@ganx4.com>
1000 * @version $Id: nusoap.php,v 1.3 2008/07/10 16:06:24 ppollet Exp $
1003 class XMLSchema extends nusoap_base {
1009 var $enclosingNamespaces;
1011 var $schemaInfo = array();
1012 var $schemaTargetNamespace = '';
1013 // types, elements, attributes defined by the schema
1014 var $attributes = array();
1015 var $complexTypes = array();
1016 var $complexTypeStack = array();
1017 var $currentComplexType = null;
1018 var $elements = array();
1019 var $elementStack = array();
1020 var $currentElement = null;
1021 var $simpleTypes = array();
1022 var $simpleTypeStack = array();
1023 var $currentSimpleType = null;
1025 var $imports = array();
1030 var $depth_array = array();
1031 var $message = array();
1032 var $defaultNamespace = array();
1037 * @param string $schema schema document URI
1038 * @param string $xml xml document URI
1039 * @param string $namespaces namespaces defined in enclosing XML
1042 function XMLSchema($schema='',$xml='',$namespaces=array()){
1043 parent::nusoap_base();
1044 $this->debug('xmlschema class instantiated, inside constructor');
1046 $this->schema = $schema;
1050 $this->enclosingNamespaces = $namespaces;
1051 $this->namespaces = array_merge($this->namespaces, $namespaces);
1053 // parse schema file
1055 $this->debug('initial schema file: '.$schema);
1056 $this->parseFile($schema, 'schema');
1061 $this->debug('initial xml file: '.$xml);
1062 $this->parseFile($xml, 'xml');
1070 * @param string $xml, path/URL to XML file
1071 * @param string $type, (schema | xml)
1075 function parseFile($xml,$type){
1078 $xmlStr = @join("",@file($xml));
1080 $msg = 'Error reading XML from '.$xml;
1081 $this->setError($msg);
1085 $this->debug("parsing $xml");
1086 $this->parseString($xmlStr,$type);
1087 $this->debug("done parsing $xml");
1095 * parse an XML string
1097 * @param string $xml path or URL
1098 * @param string $type, (schema|xml)
1101 function parseString($xml,$type){
1105 // Create an XML parser.
1106 $this->parser = xml_parser_create();
1107 // Set the options for parsing the XML data.
1108 xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING, 0);
1110 // Set the object for the parser.
1111 xml_set_object($this->parser, $this);
1113 // Set the element handlers for the parser.
1114 if($type == "schema"){
1115 xml_set_element_handler($this->parser, 'schemaStartElement','schemaEndElement');
1116 xml_set_character_data_handler($this->parser,'schemaCharacterData');
1117 } elseif($type == "xml"){
1118 xml_set_element_handler($this->parser, 'xmlStartElement','xmlEndElement');
1119 xml_set_character_data_handler($this->parser,'xmlCharacterData');
1122 // Parse the XML file.
1123 if(!xml_parse($this->parser,$xml,true)){
1124 // Display an error message.
1125 $errstr = sprintf('XML error parsing XML schema on line %d: %s',
1126 xml_get_current_line_number($this->parser),
1127 xml_error_string(xml_get_error_code($this->parser))
1129 $this->debug($errstr);
1130 $this->debug("XML payload:\n" . $xml);
1131 $this->setError($errstr);
1134 xml_parser_free($this->parser);
1136 $this->debug('no xml passed to parseString()!!');
1137 $this->setError('no xml passed to parseString()!!');
1142 * start-element handler
1144 * @param string $parser XML parser object
1145 * @param string $name element name
1146 * @param string $attrs associative array of attributes
1149 function schemaStartElement($parser, $name, $attrs) {
1151 // position in the total number of elements, starting from 0
1152 $pos = $this->position++;
1153 $depth = $this->depth++;
1154 // set self as current value for this depth
1155 $this->depth_array[$depth] = $pos;
1156 $this->message[$pos] = array('cdata' => '');
1158 $this->defaultNamespace[$pos] = $this->defaultNamespace[$this->depth_array[$depth - 1]];
1160 $this->defaultNamespace[$pos] = false;
1163 // get element prefix
1164 if($prefix = $this->getPrefix($name)){
1165 // get unqualified name
1166 $name = $this->getLocalPart($name);
1171 // loop thru attributes, expanding, and registering namespace declarations
1172 if(count($attrs) > 0){
1173 foreach($attrs as $k => $v){
1174 // if ns declarations, add to class level array of valid namespaces
1175 if(ereg("^xmlns",$k)){
1176 //$this->xdebug("$k: $v");
1177 //$this->xdebug('ns_prefix: '.$this->getPrefix($k));
1178 if($ns_prefix = substr(strrchr($k,':'),1)){
1179 //$this->xdebug("Add namespace[$ns_prefix] = $v");
1180 $this->namespaces[$ns_prefix] = $v;
1182 $this->defaultNamespace[$pos] = $v;
1183 if (! $this->getPrefixFromNamespace($v)) {
1184 $this->namespaces['ns'.(count($this->namespaces)+1)] = $v;
1187 if($v == 'http://www.w3.org/2001/XMLSchema' || $v == 'http://www.w3.org/1999/XMLSchema' || $v == 'http://www.w3.org/2000/10/XMLSchema'){
1188 $this->XMLSchemaVersion = $v;
1189 $this->namespaces['xsi'] = $v.'-instance';
1193 foreach($attrs as $k => $v){
1194 // expand each attribute
1195 $k = strpos($k,':') ? $this->expandQname($k) : $k;
1196 $v = strpos($v,':') ? $this->expandQname($v) : $v;
1203 // find status, register data
1205 case 'all': // (optional) compositor content for a complexType
1209 //$this->xdebug("compositor $name for currentComplexType: $this->currentComplexType and currentElement: $this->currentElement");
1210 $this->complexTypes[$this->currentComplexType]['compositor'] = $name;
1211 //if($name == 'all' || $name == 'sequence'){
1212 // $this->complexTypes[$this->currentComplexType]['phpType'] = 'struct';
1215 case 'attribute': // complexType attribute
1216 //$this->xdebug("parsing attribute $attrs[name] $attrs[ref] of value: ".$attrs['http://schemas.xmlsoap.org/wsdl/:arrayType']);
1217 $this->xdebug("parsing attribute:");
1218 $this->appendDebug($this->varDump($attrs));
1219 if (!isset($attrs['form'])) {
1220 $attrs['form'] = $this->schemaInfo['attributeFormDefault'];
1222 if (isset($attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'])) {
1223 $v = $attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'];
1224 if (!strpos($v, ':')) {
1225 // no namespace in arrayType attribute value...
1226 if ($this->defaultNamespace[$pos]) {
1227 // ...so use the default
1228 $attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'] = $this->defaultNamespace[$pos] . ':' . $attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'];
1232 if(isset($attrs['name'])){
1233 $this->attributes[$attrs['name']] = $attrs;
1234 $aname = $attrs['name'];
1235 } elseif(isset($attrs['ref']) && $attrs['ref'] == 'http://schemas.xmlsoap.org/soap/encoding/:arrayType'){
1236 if (isset($attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'])) {
1237 $aname = $attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'];
1241 } elseif(isset($attrs['ref'])){
1242 $aname = $attrs['ref'];
1243 $this->attributes[$attrs['ref']] = $attrs;
1246 if($this->currentComplexType){ // This should *always* be
1247 $this->complexTypes[$this->currentComplexType]['attrs'][$aname] = $attrs;
1249 // arrayType attribute
1250 if(isset($attrs['http://schemas.xmlsoap.org/wsdl/:arrayType']) || $this->getLocalPart($aname) == 'arrayType'){
1251 $this->complexTypes[$this->currentComplexType]['phpType'] = 'array';
1252 $prefix = $this->getPrefix($aname);
1253 if(isset($attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'])){
1254 $v = $attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'];
1258 if(strpos($v,'[,]')){
1259 $this->complexTypes[$this->currentComplexType]['multidimensional'] = true;
1261 $v = substr($v,0,strpos($v,'[')); // clip the []
1262 if(!strpos($v,':') && isset($this->typemap[$this->XMLSchemaVersion][$v])){
1263 $v = $this->XMLSchemaVersion.':'.$v;
1265 $this->complexTypes[$this->currentComplexType]['arrayType'] = $v;
1268 case 'complexContent': // (optional) content for a complexType
1271 array_push($this->complexTypeStack, $this->currentComplexType);
1272 if(isset($attrs['name'])){
1273 $this->xdebug('processing named complexType '.$attrs['name']);
1274 //$this->currentElement = false;
1275 $this->currentComplexType = $attrs['name'];
1276 $this->complexTypes[$this->currentComplexType] = $attrs;
1277 $this->complexTypes[$this->currentComplexType]['typeClass'] = 'complexType';
1278 // This is for constructs like
1279 // <complexType name="ListOfString" base="soap:Array">
1281 // <element name="string" type="xsd:string"
1282 // minOccurs="0" maxOccurs="unbounded" />
1285 if(isset($attrs['base']) && ereg(':Array$',$attrs['base'])){
1286 $this->xdebug('complexType is unusual array');
1287 $this->complexTypes[$this->currentComplexType]['phpType'] = 'array';
1289 $this->complexTypes[$this->currentComplexType]['phpType'] = 'struct';
1292 $this->xdebug('processing unnamed complexType for element '.$this->currentElement);
1293 $this->currentComplexType = $this->currentElement . '_ContainedType';
1294 //$this->currentElement = false;
1295 $this->complexTypes[$this->currentComplexType] = $attrs;
1296 $this->complexTypes[$this->currentComplexType]['typeClass'] = 'complexType';
1297 // This is for constructs like
1298 // <complexType name="ListOfString" base="soap:Array">
1300 // <element name="string" type="xsd:string"
1301 // minOccurs="0" maxOccurs="unbounded" />
1304 if(isset($attrs['base']) && ereg(':Array$',$attrs['base'])){
1305 $this->xdebug('complexType is unusual array');
1306 $this->complexTypes[$this->currentComplexType]['phpType'] = 'array';
1308 $this->complexTypes[$this->currentComplexType]['phpType'] = 'struct';
1313 array_push($this->elementStack, $this->currentElement);
1314 // elements defined as part of a complex type should
1315 // not really be added to $this->elements, but for some
1317 if (!isset($attrs['form'])) {
1318 $attrs['form'] = $this->schemaInfo['elementFormDefault'];
1320 if(isset($attrs['type'])){
1321 $this->xdebug("processing typed element ".$attrs['name']." of type ".$attrs['type']);
1322 if (! $this->getPrefix($attrs['type'])) {
1323 if ($this->defaultNamespace[$pos]) {
1324 $attrs['type'] = $this->defaultNamespace[$pos] . ':' . $attrs['type'];
1325 $this->xdebug('used default namespace to make type ' . $attrs['type']);
1328 // This is for constructs like
1329 // <complexType name="ListOfString" base="soap:Array">
1331 // <element name="string" type="xsd:string"
1332 // minOccurs="0" maxOccurs="unbounded" />
1335 if ($this->currentComplexType && $this->complexTypes[$this->currentComplexType]['phpType'] == 'array') {
1336 $this->xdebug('arrayType for unusual array is ' . $attrs['type']);
1337 $this->complexTypes[$this->currentComplexType]['arrayType'] = $attrs['type'];
1339 $this->currentElement = $attrs['name'];
1340 $this->elements[ $attrs['name'] ] = $attrs;
1341 $this->elements[ $attrs['name'] ]['typeClass'] = 'element';
1342 $ename = $attrs['name'];
1343 } elseif(isset($attrs['ref'])){
1344 $this->xdebug("processing element as ref to ".$attrs['ref']);
1345 $this->currentElement = "ref to ".$attrs['ref'];
1346 $ename = $this->getLocalPart($attrs['ref']);
1348 $this->xdebug("processing untyped element ".$attrs['name']);
1349 $this->currentElement = $attrs['name'];
1350 $this->elements[ $attrs['name'] ] = $attrs;
1351 $this->elements[ $attrs['name'] ]['typeClass'] = 'element';
1352 $attrs['type'] = $this->schemaTargetNamespace . ':' . $attrs['name'] . '_ContainedType';
1353 $this->elements[ $attrs['name'] ]['type'] = $attrs['type'];
1354 $ename = $attrs['name'];
1356 if(isset($ename) && $this->currentComplexType){
1357 $this->complexTypes[$this->currentComplexType]['elements'][$ename] = $attrs;
1360 case 'enumeration': // restriction value list member
1361 $this->xdebug('enumeration ' . $attrs['value']);
1362 if ($this->currentSimpleType) {
1363 $this->simpleTypes[$this->currentSimpleType]['enumeration'][] = $attrs['value'];
1364 } elseif ($this->currentComplexType) {
1365 $this->complexTypes[$this->currentComplexType]['enumeration'][] = $attrs['value'];
1368 case 'extension': // simpleContent or complexContent type extension
1369 $this->xdebug('extension ' . $attrs['base']);
1370 if ($this->currentComplexType) {
1371 $this->complexTypes[$this->currentComplexType]['extensionBase'] = $attrs['base'];
1375 if (isset($attrs['schemaLocation'])) {
1376 //$this->xdebug('import namespace ' . $attrs['namespace'] . ' from ' . $attrs['schemaLocation']);
1377 $this->imports[$attrs['namespace']][] = array('location' => $attrs['schemaLocation'], 'loaded' => false);
1379 //$this->xdebug('import namespace ' . $attrs['namespace']);
1380 $this->imports[$attrs['namespace']][] = array('location' => '', 'loaded' => true);
1381 if (! $this->getPrefixFromNamespace($attrs['namespace'])) {
1382 $this->namespaces['ns'.(count($this->namespaces)+1)] = $attrs['namespace'];
1386 case 'list': // simpleType value list
1388 case 'restriction': // simpleType, simpleContent or complexContent value restriction
1389 $this->xdebug('restriction ' . $attrs['base']);
1390 if($this->currentSimpleType){
1391 $this->simpleTypes[$this->currentSimpleType]['type'] = $attrs['base'];
1392 } elseif($this->currentComplexType){
1393 $this->complexTypes[$this->currentComplexType]['restrictionBase'] = $attrs['base'];
1394 if(strstr($attrs['base'],':') == ':Array'){
1395 $this->complexTypes[$this->currentComplexType]['phpType'] = 'array';
1400 $this->schemaInfo = $attrs;
1401 $this->schemaInfo['schemaVersion'] = $this->getNamespaceFromPrefix($prefix);
1402 if (isset($attrs['targetNamespace'])) {
1403 $this->schemaTargetNamespace = $attrs['targetNamespace'];
1405 if (!isset($attrs['elementFormDefault'])) {
1406 $this->schemaInfo['elementFormDefault'] = 'unqualified';
1408 if (!isset($attrs['attributeFormDefault'])) {
1409 $this->schemaInfo['attributeFormDefault'] = 'unqualified';
1412 case 'simpleContent': // (optional) content for a complexType
1415 array_push($this->simpleTypeStack, $this->currentSimpleType);
1416 if(isset($attrs['name'])){
1417 $this->xdebug("processing simpleType for name " . $attrs['name']);
1418 $this->currentSimpleType = $attrs['name'];
1419 $this->simpleTypes[ $attrs['name'] ] = $attrs;
1420 $this->simpleTypes[ $attrs['name'] ]['typeClass'] = 'simpleType';
1421 $this->simpleTypes[ $attrs['name'] ]['phpType'] = 'scalar';
1423 $this->xdebug('processing unnamed simpleType for element '.$this->currentElement);
1424 $this->currentSimpleType = $this->currentElement . '_ContainedType';
1425 //$this->currentElement = false;
1426 $this->simpleTypes[$this->currentSimpleType] = $attrs;
1427 $this->simpleTypes[$this->currentSimpleType]['phpType'] = 'scalar';
1430 case 'union': // simpleType type list
1433 //$this->xdebug("do not have anything to do for element $name");
1438 * end-element handler
1440 * @param string $parser XML parser object
1441 * @param string $name element name
1444 function schemaEndElement($parser, $name) {
1445 // bring depth down a notch
1447 // position of current element is equal to the last value left in depth_array for my depth
1448 if(isset($this->depth_array[$this->depth])){
1449 $pos = $this->depth_array[$this->depth];
1451 // get element prefix
1452 if ($prefix = $this->getPrefix($name)){
1453 // get unqualified name
1454 $name = $this->getLocalPart($name);
1459 if($name == 'complexType'){
1460 $this->xdebug('done processing complexType ' . ($this->currentComplexType ? $this->currentComplexType : '(unknown)'));
1461 $this->currentComplexType = array_pop($this->complexTypeStack);
1462 //$this->currentElement = false;
1464 if($name == 'element'){
1465 $this->xdebug('done processing element ' . ($this->currentElement ? $this->currentElement : '(unknown)'));
1466 $this->currentElement = array_pop($this->elementStack);
1468 if($name == 'simpleType'){
1469 $this->xdebug('done processing simpleType ' . ($this->currentSimpleType ? $this->currentSimpleType : '(unknown)'));
1470 $this->currentSimpleType = array_pop($this->simpleTypeStack);
1475 * element content handler
1477 * @param string $parser XML parser object
1478 * @param string $data element content
1481 function schemaCharacterData($parser, $data){
1482 $pos = $this->depth_array[$this->depth - 1];
1483 $this->message[$pos]['cdata'] .= $data;
1487 * serialize the schema
1491 function serializeSchema(){
1493 $schemaPrefix = $this->getPrefixFromNamespace($this->XMLSchemaVersion);
1496 if (sizeof($this->imports) > 0) {
1497 foreach($this->imports as $ns => $list) {
1498 foreach ($list as $ii) {
1499 if ($ii['location'] != '') {
1500 $xml .= " <$schemaPrefix:import location=\"" . $ii['location'] . '" namespace="' . $ns . "\" />\n";
1502 $xml .= " <$schemaPrefix:import namespace=\"" . $ns . "\" />\n";
1508 foreach($this->complexTypes as $typeName => $attrs){
1510 // serialize child elements
1511 if(isset($attrs['elements']) && (count($attrs['elements']) > 0)){
1512 foreach($attrs['elements'] as $element => $eParts){
1513 if(isset($eParts['ref'])){
1514 $contentStr .= " <$schemaPrefix:element ref=\"$element\"/>\n";
1516 $contentStr .= " <$schemaPrefix:element name=\"$element\" type=\"" . $this->contractQName($eParts['type']) . "\"";
1517 foreach ($eParts as $aName => $aValue) {
1518 // handle, e.g., abstract, default, form, minOccurs, maxOccurs, nillable
1519 if ($aName != 'name' && $aName != 'type') {
1520 $contentStr .= " $aName=\"$aValue\"";
1523 $contentStr .= "/>\n";
1526 // compositor wraps elements
1527 if (isset($attrs['compositor']) && ($attrs['compositor'] != '')) {
1528 $contentStr = " <$schemaPrefix:$attrs[compositor]>\n".$contentStr." </$schemaPrefix:$attrs[compositor]>\n";
1532 if(isset($attrs['attrs']) && (count($attrs['attrs']) >= 1)){
1533 foreach($attrs['attrs'] as $attr => $aParts){
1534 $contentStr .= " <$schemaPrefix:attribute";
1535 foreach ($aParts as $a => $v) {
1536 if ($a == 'ref' || $a == 'type') {
1537 $contentStr .= " $a=\"".$this->contractQName($v).'"';
1538 } elseif ($a == 'http://schemas.xmlsoap.org/wsdl/:arrayType') {
1539 $this->usedNamespaces['wsdl'] = $this->namespaces['wsdl'];
1540 $contentStr .= ' wsdl:arrayType="'.$this->contractQName($v).'"';
1542 $contentStr .= " $a=\"$v\"";
1545 $contentStr .= "/>\n";
1549 if (isset($attrs['restrictionBase']) && $attrs['restrictionBase'] != ''){
1550 $contentStr = " <$schemaPrefix:restriction base=\"".$this->contractQName($attrs['restrictionBase'])."\">\n".$contentStr." </$schemaPrefix:restriction>\n";
1551 // complex or simple content
1552 if ((isset($attrs['elements']) && count($attrs['elements']) > 0) || (isset($attrs['attrs']) && count($attrs['attrs']) > 0)){
1553 $contentStr = " <$schemaPrefix:complexContent>\n".$contentStr." </$schemaPrefix:complexContent>\n";
1556 // finalize complex type
1557 if($contentStr != ''){
1558 $contentStr = " <$schemaPrefix:complexType name=\"$typeName\">\n".$contentStr." </$schemaPrefix:complexType>\n";
1560 $contentStr = " <$schemaPrefix:complexType name=\"$typeName\"/>\n";
1562 $xml .= $contentStr;
1565 if(isset($this->simpleTypes) && count($this->simpleTypes) > 0){
1566 foreach($this->simpleTypes as $typeName => $eParts){
1567 $xml .= " <$schemaPrefix:simpleType name=\"$typeName\">\n <$schemaPrefix:restriction base=\"".$this->contractQName($eParts['type'])."\"/>\n";
1568 if (isset($eParts['enumeration'])) {
1569 foreach ($eParts['enumeration'] as $e) {
1570 $xml .= " <$schemaPrefix:enumeration value=\"$e\"/>\n";
1573 $xml .= " </$schemaPrefix:simpleType>";
1577 if(isset($this->elements) && count($this->elements) > 0){
1578 foreach($this->elements as $element => $eParts){
1579 $xml .= " <$schemaPrefix:element name=\"$element\" type=\"".$this->contractQName($eParts['type'])."\"/>\n";
1583 if(isset($this->attributes) && count($this->attributes) > 0){
1584 foreach($this->attributes as $attr => $aParts){
1585 $xml .= " <$schemaPrefix:attribute name=\"$attr\" type=\"".$this->contractQName($aParts['type'])."\"\n/>";
1589 $el = "<$schemaPrefix:schema targetNamespace=\"$this->schemaTargetNamespace\"\n";
1590 foreach (array_diff($this->usedNamespaces, $this->enclosingNamespaces) as $nsp => $ns) {
1591 $el .= " xmlns:$nsp=\"$ns\"";
1593 $xml = $el . ">\n".$xml."</$schemaPrefix:schema>\n";
1598 * adds debug data to the clas level debug string
1600 * @param string $string debug data
1603 function xdebug($string){
1604 $this->debug('<' . $this->schemaTargetNamespace . '> '.$string);
1608 * get the PHP type of a user defined type in the schema
1609 * PHP type is kind of a misnomer since it actually returns 'struct' for assoc. arrays
1610 * returns false if no type exists, or not w/ the given namespace
1611 * else returns a string that is either a native php type, or 'struct'
1613 * @param string $type, name of defined type
1614 * @param string $ns, namespace of type
1619 function getPHPType($type,$ns){
1620 if(isset($this->typemap[$ns][$type])){
1621 //print "found type '$type' and ns $ns in typemap<br>";
1622 return $this->typemap[$ns][$type];
1623 } elseif(isset($this->complexTypes[$type])){
1624 //print "getting type '$type' and ns $ns from complexTypes array<br>";
1625 return $this->complexTypes[$type]['phpType'];
1631 * returns an associative array of information about a given type
1632 * returns false if no type exists by the given name
1634 * For a complexType typeDef = array(
1635 * 'restrictionBase' => '',
1637 * 'compositor' => '(sequence|all)',
1638 * 'elements' => array(), // refs to elements array
1639 * 'attrs' => array() // refs to attributes array
1640 * ... and so on (see addComplexType)
1643 * For simpleType or element, the array has different keys.
1648 * @see addComplexType
1649 * @see addSimpleType
1652 function getTypeDef($type){
1653 //$this->debug("in getTypeDef for type $type");
1654 if(isset($this->complexTypes[$type])){
1655 $this->xdebug("in getTypeDef, found complexType $type");
1656 return $this->complexTypes[$type];
1657 } elseif(isset($this->simpleTypes[$type])){
1658 $this->xdebug("in getTypeDef, found simpleType $type");
1659 if (!isset($this->simpleTypes[$type]['phpType'])) {
1660 // get info for type to tack onto the simple type
1661 // TODO: can this ever really apply (i.e. what is a simpleType really?)
1662 $uqType = substr($this->simpleTypes[$type]['type'], strrpos($this->simpleTypes[$type]['type'], ':') + 1);
1663 $ns = substr($this->simpleTypes[$type]['type'], 0, strrpos($this->simpleTypes[$type]['type'], ':'));
1664 $etype = $this->getTypeDef($uqType);
1666 $this->xdebug("in getTypeDef, found type for simpleType $type:");
1667 $this->xdebug($this->varDump($etype));
1668 if (isset($etype['phpType'])) {
1669 $this->simpleTypes[$type]['phpType'] = $etype['phpType'];
1671 if (isset($etype['elements'])) {
1672 $this->simpleTypes[$type]['elements'] = $etype['elements'];
1676 return $this->simpleTypes[$type];
1677 } elseif(isset($this->elements[$type])){
1678 $this->xdebug("in getTypeDef, found element $type");
1679 if (!isset($this->elements[$type]['phpType'])) {
1680 // get info for type to tack onto the element
1681 $uqType = substr($this->elements[$type]['type'], strrpos($this->elements[$type]['type'], ':') + 1);
1682 $ns = substr($this->elements[$type]['type'], 0, strrpos($this->elements[$type]['type'], ':'));
1683 $etype = $this->getTypeDef($uqType);
1685 $this->xdebug("in getTypeDef, found type for element $type:");
1686 $this->xdebug($this->varDump($etype));
1687 if (isset($etype['phpType'])) {
1688 $this->elements[$type]['phpType'] = $etype['phpType'];
1690 if (isset($etype['elements'])) {
1691 $this->elements[$type]['elements'] = $etype['elements'];
1693 } elseif ($ns == 'http://www.w3.org/2001/XMLSchema') {
1694 $this->xdebug("in getTypeDef, element $type is an XSD type");
1695 $this->elements[$type]['phpType'] = 'scalar';
1698 return $this->elements[$type];
1699 } elseif(isset($this->attributes[$type])){
1700 $this->xdebug("in getTypeDef, found attribute $type");
1701 return $this->attributes[$type];
1702 } elseif (ereg('_ContainedType$', $type)) {
1703 $this->xdebug("in getTypeDef, have an untyped element $type");
1704 $typeDef['typeClass'] = 'simpleType';
1705 $typeDef['phpType'] = 'scalar';
1706 $typeDef['type'] = 'http://www.w3.org/2001/XMLSchema:string';
1709 $this->xdebug("in getTypeDef, did not find $type");
1714 * returns a sample serialization of a given type, or false if no type by the given name
1716 * @param string $type, name of type
1721 function serializeTypeDef($type){
1722 //print "in sTD() for type $type<br>";
1723 if($typeDef = $this->getTypeDef($type)){
1725 if(is_array($typeDef['attrs'])){
1726 foreach($attrs as $attName => $data){
1727 $str .= " $attName=\"{type = ".$data['type']."}\"";
1730 $str .= " xmlns=\"".$this->schema['targetNamespace']."\"";
1731 if(count($typeDef['elements']) > 0){
1733 foreach($typeDef['elements'] as $element => $eData){
1734 $str .= $this->serializeTypeDef($element);
1737 } elseif($typeDef['typeClass'] == 'element') {
1738 $str .= "></$type>";
1748 * returns HTML form elements that allow a user
1749 * to enter values for creating an instance of the given type.
1751 * @param string $name, name for type instance
1752 * @param string $type, name of type
1757 function typeToForm($name,$type){
1759 if($typeDef = $this->getTypeDef($type)){
1761 if($typeDef['phpType'] == 'struct'){
1762 $buffer .= '<table>';
1763 foreach($typeDef['elements'] as $child => $childDef){
1765 <tr><td align='right'>$childDef[name] (type: ".$this->getLocalPart($childDef['type'])."):</td>
1766 <td><input type='text' name='parameters[".$name."][$childDef[name]]'></td></tr>";
1768 $buffer .= '</table>';
1770 } elseif($typeDef['phpType'] == 'array'){
1771 $buffer .= '<table>';
1772 for($i=0;$i < 3; $i++){
1774 <tr><td align='right'>array item (type: $typeDef[arrayType]):</td>
1775 <td><input type='text' name='parameters[".$name."][]'></td></tr>";
1777 $buffer .= '</table>';
1780 $buffer .= "<input type='text' name='parameters[$name]'>";
1783 $buffer .= "<input type='text' name='parameters[$name]'>";
1789 * adds a complex type to the schema
1799 * array('ref'=>'SOAP-ENC:arrayType','wsdl:arrayType'=>'string[]'),
1803 * example: PHP associative array ( SOAP Struct )
1810 * array('myVar'=> array('name'=>'myVar','type'=>'string')
1814 * @param typeClass (complexType|simpleType|attribute)
1815 * @param phpType: currently supported are array and struct (php assoc array)
1816 * @param compositor (all|sequence|choice)
1817 * @param restrictionBase namespace:name (http://schemas.xmlsoap.org/soap/encoding/:Array)
1818 * @param elements = array ( name = array(name=>'',type=>'') )
1819 * @param attrs = array(
1821 * 'ref' => "http://schemas.xmlsoap.org/soap/encoding/:arrayType",
1822 * "http://schemas.xmlsoap.org/wsdl/:arrayType" => "string[]"
1825 * @param arrayType: namespace:name (http://www.w3.org/2001/XMLSchema:string)
1829 function addComplexType($name,$typeClass='complexType',$phpType='array',$compositor='',$restrictionBase='',$elements=array(),$attrs=array(),$arrayType=''){
1830 $this->complexTypes[$name] = array(
1832 'typeClass' => $typeClass,
1833 'phpType' => $phpType,
1834 'compositor'=> $compositor,
1835 'restrictionBase' => $restrictionBase,
1836 'elements' => $elements,
1838 'arrayType' => $arrayType
1841 $this->xdebug("addComplexType $name:");
1842 $this->appendDebug($this->varDump($this->complexTypes[$name]));
1846 * adds a simple type to the schema
1848 * @param string $name
1849 * @param string $restrictionBase namespace:name (http://schemas.xmlsoap.org/soap/encoding/:Array)
1850 * @param string $typeClass (should always be simpleType)
1851 * @param string $phpType (should always be scalar)
1852 * @param array $enumeration array of values
1857 function addSimpleType($name, $restrictionBase='', $typeClass='simpleType', $phpType='scalar', $enumeration=array()) {
1858 $this->simpleTypes[$name] = array(
1860 'typeClass' => $typeClass,
1861 'phpType' => $phpType,
1862 'type' => $restrictionBase,
1863 'enumeration' => $enumeration
1866 $this->xdebug("addSimpleType $name:");
1867 $this->appendDebug($this->varDump($this->simpleTypes[$name]));
1871 * adds an element to the schema
1873 * @param array $attrs attributes that must include name and type
1877 function addElement($attrs) {
1878 if (! $this->getPrefix($attrs['type'])) {
1879 $attrs['type'] = $this->schemaTargetNamespace . ':' . $attrs['type'];
1881 $this->elements[ $attrs['name'] ] = $attrs;
1882 $this->elements[ $attrs['name'] ]['typeClass'] = 'element';
1884 $this->xdebug("addElement " . $attrs['name']);
1885 $this->appendDebug($this->varDump($this->elements[ $attrs['name'] ]));
1896 * For creating serializable abstractions of native PHP types. This class
1897 * allows element name/namespace, XSD type, and XML attributes to be
1898 * associated with a value. This is extremely useful when WSDL is not
1899 * used, but is also useful when WSDL is used with polymorphic types, including
1900 * xsd:anyType and user-defined types.
1902 * @author Dietrich Ayala <dietrich@ganx4.com>
1903 * @version $Id: nusoap.php,v 1.3 2008/07/10 16:06:24 ppollet Exp $
1906 class soapval extends nusoap_base {
1908 * The XML element name
1915 * The XML type name (string or false)
1929 * The XML element namespace (string or false)
1936 * The XML type namespace (string or false)
1943 * The XML element attributes (array or false)
1953 * @param string $name optional name
1954 * @param mixed $type optional type name
1955 * @param mixed $value optional value
1956 * @param mixed $element_ns optional namespace of value
1957 * @param mixed $type_ns optional namespace of type
1958 * @param mixed $attributes associative array of attributes to add to element serialization
1961 function soapval($name='soapval',$type=false,$value=-1,$element_ns=false,$type_ns=false,$attributes=false) {
1962 parent::nusoap_base();
1963 $this->name = $name;
1964 $this->type = $type;
1965 $this->value = $value;
1966 $this->element_ns = $element_ns;
1967 $this->type_ns = $type_ns;
1968 $this->attributes = $attributes;
1972 * return serialized value
1974 * @param string $use The WSDL use value (encoded|literal)
1975 * @return string XML data
1978 function serialize($use='encoded') {
1979 return $this->serialize_val($this->value,$this->name,$this->type,$this->element_ns,$this->type_ns,$this->attributes,$use);
1983 * decodes a soapval object into a PHP native type
1989 return $this->value;
2000 * transport class for sending/receiving data via HTTP and HTTPS
2001 * NOTE: PHP must be compiled with the CURL extension for HTTPS support
2003 * @author Dietrich Ayala <dietrich@ganx4.com>
2004 * @version $Id: nusoap.php,v 1.3 2008/07/10 16:06:24 ppollet Exp $
2007 class soap_transport_http extends nusoap_base {
2011 var $digest_uri = '';
2016 var $request_method = 'POST';
2017 var $protocol_version = '1.0';
2019 var $outgoing_headers = array();
2020 var $incoming_headers = array();
2021 var $incoming_cookies = array();
2022 var $outgoing_payload = '';
2023 var $incoming_payload = '';
2024 var $useSOAPAction = true;
2025 var $persistentConnection = false;
2026 var $ch = false; // cURL handle
2030 var $digestRequest = array();
2031 var $certRequest = array(); // keys must be cainfofile (optional), sslcertfile, sslkeyfile, passphrase, verifypeer (optional), verifyhost (optional)
2032 // cainfofile: certificate authority file, e.g. '$pathToPemFiles/rootca.pem'
2033 // sslcertfile: SSL certificate file, e.g. '$pathToPemFiles/mycert.pem'
2034 // sslkeyfile: SSL key file, e.g. '$pathToPemFiles/mykey.pem'
2035 // passphrase: SSL key password/passphrase
2036 // verifypeer: default is 1
2037 // verifyhost: default is 1
2042 function soap_transport_http($url){
2043 parent::nusoap_base();
2044 $this->setURL($url);
2045 ereg('\$Revisio' . 'n: ([^ ]+)', $this->revision, $rev);
2046 $this->outgoing_headers['User-Agent'] = $this->title.'/'.$this->version.' ('.$rev[1].')';
2047 $this->debug('set User-Agent: ' . $this->outgoing_headers['User-Agent']);
2050 function setURL($url) {
2053 $u = parse_url($url);
2054 foreach($u as $k => $v){
2055 $this->debug("$k = $v");
2059 // add any GET params to path
2060 if(isset($u['query']) && $u['query'] != ''){
2061 $this->path .= '?' . $u['query'];
2065 if(!isset($u['port'])){
2066 if($u['scheme'] == 'https'){
2073 $this->uri = $this->path;
2074 $this->digest_uri = $this->uri;
2077 if (!isset($u['port'])) {
2078 $this->outgoing_headers['Host'] = $this->host;
2080 $this->outgoing_headers['Host'] = $this->host.':'.$this->port;
2082 $this->debug('set Host: ' . $this->outgoing_headers['Host']);
2084 if (isset($u['user']) && $u['user'] != '') {
2085 $this->setCredentials(urldecode($u['user']), isset($u['pass']) ? urldecode($u['pass']) : '');
2089 function connect($connection_timeout=0,$response_timeout=30){
2090 // For PHP 4.3 with OpenSSL, change https scheme to ssl, then treat like
2091 // "regular" socket.
2092 // TODO: disabled for now because OpenSSL must be *compiled* in (not just
2093 // loaded), and until PHP5 stream_get_wrappers is not available.
2094 // if ($this->scheme == 'https') {
2095 // if (version_compare(phpversion(), '4.3.0') >= 0) {
2096 // if (extension_loaded('openssl')) {
2097 // $this->scheme = 'ssl';
2098 // $this->debug('Using SSL over OpenSSL');
2102 $this->debug("connect connection_timeout $connection_timeout, response_timeout $response_timeout, scheme $this->scheme, host $this->host, port $this->port");
2103 if ($this->scheme == 'http' || $this->scheme == 'ssl') {
2104 // use persistent connection
2105 if($this->persistentConnection && isset($this->fp) && is_resource($this->fp)){
2106 if (!feof($this->fp)) {
2107 $this->debug('Re-use persistent connection');
2111 $this->debug('Closed persistent connection at EOF');
2114 // munge host if using OpenSSL
2115 if ($this->scheme == 'ssl') {
2116 $host = 'ssl://' . $this->host;
2118 $host = $this->host;
2120 $this->debug('calling fsockopen with host ' . $host . ' connection_timeout ' . $connection_timeout);
2123 if($connection_timeout > 0){
2124 $this->fp = @fsockopen( $host, $this->port, $this->errno, $this->error_str, $connection_timeout);
2126 $this->fp = @fsockopen( $host, $this->port, $this->errno, $this->error_str);
2131 $msg = 'Couldn\'t open socket connection to server ' . $this->url;
2133 $msg .= ', Error ('.$this->errno.'): '.$this->error_str;
2135 $msg .= ' prior to connect(). This is often a problem looking up the host name.';
2138 $this->setError($msg);
2142 // set response timeout
2143 $this->debug('set response timeout to ' . $response_timeout);
2144 socket_set_timeout( $this->fp, $response_timeout);
2146 $this->debug('socket connected');
2148 } else if ($this->scheme == 'https') {
2149 if (!extension_loaded('curl')) {
2150 $this->setError('CURL Extension, or OpenSSL extension w/ PHP version >= 4.3 is required for HTTPS');
2153 $this->debug('connect using https');
2155 $this->ch = curl_init();
2157 $hostURL = ($this->port != '') ? "https://$this->host:$this->port" : "https://$this->host";
2159 $hostURL .= $this->path;
2160 curl_setopt($this->ch, CURLOPT_URL, $hostURL);
2161 // follow location headers (re-directs)
2162 curl_setopt($this->ch, CURLOPT_FOLLOWLOCATION, 1);
2163 // ask for headers in the response output
2164 curl_setopt($this->ch, CURLOPT_HEADER, 1);
2165 // ask for the response output as the return value
2166 curl_setopt($this->ch, CURLOPT_RETURNTRANSFER, 1);
2168 // We manage this ourselves through headers and encoding
2169 // if(function_exists('gzuncompress')){
2170 // curl_setopt($this->ch, CURLOPT_ENCODING, 'deflate');
2172 // persistent connection
2173 if ($this->persistentConnection) {
2174 // The way we send data, we cannot use persistent connections, since
2175 // there will be some "junk" at the end of our request.
2176 //curl_setopt($this->ch, CURL_HTTP_VERSION_1_1, true);
2177 $this->persistentConnection = false;
2178 $this->outgoing_headers['Connection'] = 'close';
2179 $this->debug('set Connection: ' . $this->outgoing_headers['Connection']);
2182 if ($connection_timeout != 0) {
2183 curl_setopt($this->ch, CURLOPT_TIMEOUT, $connection_timeout);
2185 // TODO: cURL has added a connection timeout separate from the response timeout
2186 //if ($connection_timeout != 0) {
2187 // curl_setopt($this->ch, CURLOPT_CONNECTIONTIMEOUT, $connection_timeout);
2189 //if ($response_timeout != 0) {
2190 // curl_setopt($this->ch, CURLOPT_TIMEOUT, $response_timeout);
2193 // recent versions of cURL turn on peer/host checking by default,
2194 // while PHP binaries are not compiled with a default location for the
2195 // CA cert bundle, so disable peer/host checking.
2196 //curl_setopt($this->ch, CURLOPT_CAINFO, 'f:\php-4.3.2-win32\extensions\curl-ca-bundle.crt');
2197 curl_setopt($this->ch, CURLOPT_SSL_VERIFYPEER, 0);
2198 curl_setopt($this->ch, CURLOPT_SSL_VERIFYHOST, 0);
2200 // support client certificates (thanks Tobias Boes, Doug Anarino, Eryan Ariobowo)
2201 if ($this->authtype == 'certificate') {
2202 if (isset($this->certRequest['cainfofile'])) {
2203 curl_setopt($this->ch, CURLOPT_CAINFO, $this->certRequest['cainfofile']);
2205 if (isset($this->certRequest['verifypeer'])) {
2206 curl_setopt($this->ch, CURLOPT_SSL_VERIFYPEER, $this->certRequest['verifypeer']);
2208 curl_setopt($this->ch, CURLOPT_SSL_VERIFYPEER, 1);
2210 if (isset($this->certRequest['verifyhost'])) {
2211 curl_setopt($this->ch, CURLOPT_SSL_VERIFYHOST, $this->certRequest['verifyhost']);
2213 curl_setopt($this->ch, CURLOPT_SSL_VERIFYHOST, 1);
2215 if (isset($this->certRequest['sslcertfile'])) {
2216 curl_setopt($this->ch, CURLOPT_SSLCERT, $this->certRequest['sslcertfile']);
2218 if (isset($this->certRequest['sslkeyfile'])) {
2219 curl_setopt($this->ch, CURLOPT_SSLKEY, $this->certRequest['sslkeyfile']);
2221 if (isset($this->certRequest['passphrase'])) {
2222 curl_setopt($this->ch, CURLOPT_SSLKEYPASSWD , $this->certRequest['passphrase']);
2225 $this->debug('cURL connection set up');
2228 $this->setError('Unknown scheme ' . $this->scheme);
2229 $this->debug('Unknown scheme ' . $this->scheme);
2235 * send the SOAP message via HTTP
2237 * @param string $data message data
2238 * @param integer $timeout set connection timeout in seconds
2239 * @param integer $response_timeout set response timeout in seconds
2240 * @param array $cookies cookies to send
2241 * @return string data
2244 function send($data, $timeout=0, $response_timeout=30, $cookies=NULL) {
2246 $this->debug('entered send() with data of length: '.strlen($data));
2248 $this->tryagain = true;
2250 while ($this->tryagain) {
2251 $this->tryagain = false;
2254 if (!$this->connect($timeout, $response_timeout)){
2259 if (!$this->sendRequest($data, $cookies)){
2264 $respdata = $this->getResponse();
2266 $this->setError('Too many tries to get an OK response');
2269 $this->debug('end of send()');
2275 * send the SOAP message via HTTPS 1.0 using CURL
2277 * @param string $msg message data
2278 * @param integer $timeout set connection timeout in seconds
2279 * @param integer $response_timeout set response timeout in seconds
2280 * @param array $cookies cookies to send
2281 * @return string data
2284 function sendHTTPS($data, $timeout=0, $response_timeout=30, $cookies) {
2285 return $this->send($data, $timeout, $response_timeout, $cookies);
2289 * if authenticating, set user credentials here
2291 * @param string $username
2292 * @param string $password
2293 * @param string $authtype (basic, digest, certificate)
2294 * @param array $digestRequest (keys must be nonce, nc, realm, qop)
2295 * @param array $certRequest (keys must be cainfofile (optional), sslcertfile, sslkeyfile, passphrase, verifypeer (optional), verifyhost (optional): see corresponding options in cURL docs)
2298 function setCredentials($username, $password, $authtype = 'basic', $digestRequest = array(), $certRequest = array()) {
2299 $this->debug("Set credentials for authtype $authtype");
2301 if ($authtype == 'basic') {
2302 $this->outgoing_headers['Authorization'] = 'Basic '.base64_encode(str_replace(':','',$username).':'.$password);
2303 } elseif ($authtype == 'digest') {
2304 if (isset($digestRequest['nonce'])) {
2305 $digestRequest['nc'] = isset($digestRequest['nc']) ? $digestRequest['nc']++ : 1;
2307 // calculate the Digest hashes (calculate code based on digest implementation found at: http://www.rassoc.com/gregr/weblog/stories/2002/07/09/webServicesSecurityHttpDigestAuthenticationWithoutActiveDirectory.html)
2309 // A1 = unq(username-value) ":" unq(realm-value) ":" passwd
2310 $A1 = $username. ':' . (isset($digestRequest['realm']) ? $digestRequest['realm'] : '') . ':' . $password;
2315 // A2 = Method ":" digest-uri-value
2316 $A2 = 'POST:' . $this->digest_uri;
2321 // KD(secret, data) = H(concat(secret, ":", data))
2323 // request-digest = <"> < KD ( H(A1), unq(nonce-value)
2325 // ":" unq(cnonce-value)
2326 // ":" unq(qop-value)
2329 // if qop is missing,
2330 // request-digest = <"> < KD ( H(A1), unq(nonce-value) ":" H(A2) ) > <">
2332 $unhashedDigest = '';
2333 $nonce = isset($digestRequest['nonce']) ? $digestRequest['nonce'] : '';
2335 if ($digestRequest['qop'] != '') {
2336 $unhashedDigest = $HA1 . ':' . $nonce . ':' . sprintf("%08d", $digestRequest['nc']) . ':' . $cnonce . ':' . $digestRequest['qop'] . ':' . $HA2;
2338 $unhashedDigest = $HA1 . ':' . $nonce . ':' . $HA2;
2341 $hashedDigest = md5($unhashedDigest);
2343 $this->outgoing_headers['Authorization'] = 'Digest username="' . $username . '", realm="' . $digestRequest['realm'] . '", nonce="' . $nonce . '", uri="' . $this->digest_uri . '", cnonce="' . $cnonce . '", nc=' . sprintf("%08x", $digestRequest['nc']) . ', qop="' . $digestRequest['qop'] . '", response="' . $hashedDigest . '"';
2345 } elseif ($authtype == 'certificate') {
2346 $this->certRequest = $certRequest;
2348 $this->username = $username;
2349 $this->password = $password;
2350 $this->authtype = $authtype;
2351 $this->digestRequest = $digestRequest;
2353 if (isset($this->outgoing_headers['Authorization'])) {
2354 $this->debug('set Authorization: ' . substr($this->outgoing_headers['Authorization'], 0, 12) . '...');
2356 $this->debug('Authorization header not set');
2361 * set the soapaction value
2363 * @param string $soapaction
2366 function setSOAPAction($soapaction) {
2367 $this->outgoing_headers['SOAPAction'] = '"' . $soapaction . '"';
2368 $this->debug('set SOAPAction: ' . $this->outgoing_headers['SOAPAction']);
2374 * @param string $enc encoding style. supported values: gzip, deflate, or both
2377 function setEncoding($enc='gzip, deflate') {
2378 if (function_exists('gzdeflate')) {
2379 $this->protocol_version = '1.1';
2380 $this->outgoing_headers['Accept-Encoding'] = $enc;
2381 $this->debug('set Accept-Encoding: ' . $this->outgoing_headers['Accept-Encoding']);
2382 if (!isset($this->outgoing_headers['Connection'])) {
2383 $this->outgoing_headers['Connection'] = 'close';
2384 $this->persistentConnection = false;
2385 $this->debug('set Connection: ' . $this->outgoing_headers['Connection']);
2387 set_magic_quotes_runtime(0);
2389 $this->encoding = $enc;
2394 * set proxy info here
2396 * @param string $proxyhost
2397 * @param string $proxyport
2398 * @param string $proxyusername
2399 * @param string $proxypassword
2402 function setProxy($proxyhost, $proxyport, $proxyusername = '', $proxypassword = '') {
2403 $this->uri = $this->url;
2404 $this->host = $proxyhost;
2405 $this->port = $proxyport;
2406 if ($proxyusername != '' && $proxypassword != '') {
2407 $this->outgoing_headers['Proxy-Authorization'] = ' Basic '.base64_encode($proxyusername.':'.$proxypassword);
2408 $this->debug('set Proxy-Authorization: ' . $this->outgoing_headers['Proxy-Authorization']);
2413 * decode a string that is encoded w/ "chunked' transfer encoding
2414 * as defined in RFC2068 19.4.6
2416 * @param string $buffer
2422 function decodeChunked($buffer, $lb){
2427 // read chunk-size, chunk-extension (if any) and CRLF
2428 // get the position of the linebreak
2429 $chunkend = strpos($buffer, $lb);
2430 if ($chunkend == FALSE) {
2431 $this->debug('no linebreak found in decodeChunked');
2434 $temp = substr($buffer,0,$chunkend);
2435 $chunk_size = hexdec( trim($temp) );
2436 $chunkstart = $chunkend + strlen($lb);
2437 // while (chunk-size > 0) {
2438 while ($chunk_size > 0) {
2439 $this->debug("chunkstart: $chunkstart chunk_size: $chunk_size");
2440 $chunkend = strpos( $buffer, $lb, $chunkstart + $chunk_size);
2442 // Just in case we got a broken connection
2443 if ($chunkend == FALSE) {
2444 $chunk = substr($buffer,$chunkstart);
2445 // append chunk-data to entity-body
2447 $length += strlen($chunk);
2451 // read chunk-data and CRLF
2452 $chunk = substr($buffer,$chunkstart,$chunkend-$chunkstart);
2453 // append chunk-data to entity-body
2455 // length := length + chunk-size
2456 $length += strlen($chunk);
2457 // read chunk-size and CRLF
2458 $chunkstart = $chunkend + strlen($lb);
2460 $chunkend = strpos($buffer, $lb, $chunkstart) + strlen($lb);
2461 if ($chunkend == FALSE) {
2462 break; //Just in case we got a broken connection
2464 $temp = substr($buffer,$chunkstart,$chunkend-$chunkstart);
2465 $chunk_size = hexdec( trim($temp) );
2466 $chunkstart = $chunkend;
2472 * Writes payload, including HTTP headers, to $this->outgoing_payload.
2474 function buildPayload($data, $cookie_str = '') {
2475 // add content-length header
2476 $this->outgoing_headers['Content-Length'] = strlen($data);
2477 $this->debug('set Content-Length: ' . $this->outgoing_headers['Content-Length']);
2479 // start building outgoing payload:
2480 $req = "$this->request_method $this->uri HTTP/$this->protocol_version";
2481 $this->debug("HTTP request: $req");
2482 $this->outgoing_payload = "$req\r\n";
2484 // loop thru headers, serializing
2485 foreach($this->outgoing_headers as $k => $v){
2487 $this->debug("HTTP header: $hdr");
2488 $this->outgoing_payload .= "$hdr\r\n";
2492 if ($cookie_str != '') {
2493 $hdr = 'Cookie: '.$cookie_str;
2494 $this->debug("HTTP header: $hdr");
2495 $this->outgoing_payload .= "$hdr\r\n";
2498 // header/body separator
2499 $this->outgoing_payload .= "\r\n";
2502 $this->outgoing_payload .= $data;
2505 function sendRequest($data, $cookies = NULL) {
2506 // build cookie string
2507 $cookie_str = $this->getCookiesForRequest($cookies, (($this->scheme == 'ssl') || ($this->scheme == 'https')));
2510 $this->buildPayload($data, $cookie_str);
2512 if ($this->scheme == 'http' || $this->scheme == 'ssl') {
2514 if(!fputs($this->fp, $this->outgoing_payload, strlen($this->outgoing_payload))) {
2515 $this->setError('couldn\'t write message data to socket');
2516 $this->debug('couldn\'t write message data to socket');
2519 $this->debug('wrote data to socket, length = ' . strlen($this->outgoing_payload));
2521 } else if ($this->scheme == 'https') {
2523 // TODO: cURL does say this should only be the verb, and in fact it
2524 // turns out that the URI and HTTP version are appended to this, which
2525 // some servers refuse to work with
2526 //curl_setopt($this->ch, CURLOPT_CUSTOMREQUEST, $this->outgoing_payload);
2527 foreach($this->outgoing_headers as $k => $v){
2528 $curl_headers[] = "$k: $v";
2530 if ($cookie_str != '') {
2531 $curl_headers[] = 'Cookie: ' . $cookie_str;
2533 curl_setopt($this->ch, CURLOPT_HTTPHEADER, $curl_headers);
2534 if ($this->request_method == "POST") {
2535 curl_setopt($this->ch, CURLOPT_POST, 1);
2536 curl_setopt($this->ch, CURLOPT_POSTFIELDS, $data);
2539 $this->debug('set cURL payload');
2544 function getResponse(){
2545 $this->incoming_payload = '';
2547 if ($this->scheme == 'http' || $this->scheme == 'ssl') {
2548 // loop until headers have been retrieved
2550 while (!isset($lb)){
2552 // We might EOF during header read.
2553 if(feof($this->fp)) {
2554 $this->incoming_payload = $data;
2555 $this->debug('found no headers before EOF after length ' . strlen($data));
2556 $this->debug("received before EOF:\n" . $data);
2557 $this->setError('server failed to send headers');
2561 $tmp = fgets($this->fp, 256);
2562 $tmplen = strlen($tmp);
2563 $this->debug("read line of $tmplen bytes: " . trim($tmp));
2566 $this->incoming_payload = $data;
2567 $this->debug('socket read of headers timed out after length ' . strlen($data));
2568 $this->debug("read before timeout: " . $data);
2569 $this->setError('socket read of headers timed out');
2574 $pos = strpos($data,"\r\n\r\n");
2578 $pos = strpos($data,"\n\n");
2583 // remove 100 header
2584 if(isset($lb) && ereg('^HTTP/1.1 100',$data)){
2589 // store header data
2590 $this->incoming_payload .= $data;
2591 $this->debug('found end of headers after length ' . strlen($data));
2593 $header_data = trim(substr($data,0,$pos));
2594 $header_array = explode($lb,$header_data);
2595 $this->incoming_headers = array();
2596 $this->incoming_cookies = array();
2597 foreach($header_array as $header_line){
2598 $arr = explode(':',$header_line, 2);
2599 if(count($arr) > 1){
2600 $header_name = strtolower(trim($arr[0]));
2601 $this->incoming_headers[$header_name] = trim($arr[1]);
2602 if ($header_name == 'set-cookie') {
2603 // TODO: allow multiple cookies from parseCookie
2604 $cookie = $this->parseCookie(trim($arr[1]));
2606 $this->incoming_cookies[] = $cookie;
2607 $this->debug('found cookie: ' . $cookie['name'] . ' = ' . $cookie['value']);
2609 $this->debug('did not find cookie in ' . trim($arr[1]));
2612 } else if (isset($header_name)) {
2613 // append continuation line to previous header
2614 $this->incoming_headers[$header_name] .= $lb . ' ' . $header_line;
2618 // loop until msg has been received
2619 if (isset($this->incoming_headers['transfer-encoding']) && strtolower($this->incoming_headers['transfer-encoding']) == 'chunked') {
2620 $content_length = 2147483647; // ignore any content-length header
2622 $this->debug("want to read chunked content");
2623 } elseif (isset($this->incoming_headers['content-length'])) {
2624 $content_length = $this->incoming_headers['content-length'];
2626 $this->debug("want to read content of length $content_length");
2628 $content_length = 2147483647;
2630 $this->debug("want to read content to EOF");
2635 $tmp = fgets($this->fp, 256);
2636 $tmplen = strlen($tmp);
2637 $this->debug("read chunk line of $tmplen bytes");
2639 $this->incoming_payload = $data;
2640 $this->debug('socket read of chunk length timed out after length ' . strlen($data));
2641 $this->debug("read before timeout:\n" . $data);
2642 $this->setError('socket read of chunk length timed out');
2645 $content_length = hexdec(trim($tmp));
2646 $this->debug("chunk length $content_length");
2649 while (($strlen < $content_length) && (!feof($this->fp))) {
2650 $readlen = min(8192, $content_length - $strlen);
2651 $tmp = fread($this->fp, $readlen);
2652 $tmplen = strlen($tmp);
2653 $this->debug("read buffer of $tmplen bytes");
2654 if (($tmplen == 0) && (!feof($this->fp))) {
2655 $this->incoming_payload = $data;
2656 $this->debug('socket read of body timed out after length ' . strlen($data));
2657 $this->debug("read before timeout:\n" . $data);
2658 $this->setError('socket read of body timed out');
2664 if ($chunked && ($content_length > 0)) {
2665 $tmp = fgets($this->fp, 256);
2666 $tmplen = strlen($tmp);
2667 $this->debug("read chunk terminator of $tmplen bytes");
2669 $this->incoming_payload = $data;
2670 $this->debug('socket read of chunk terminator timed out after length ' . strlen($data));
2671 $this->debug("read before timeout:\n" . $data);
2672 $this->setError('socket read of chunk terminator timed out');
2676 } while ($chunked && ($content_length > 0) && (!feof($this->fp)));
2677 if (feof($this->fp)) {
2678 $this->debug('read to EOF');
2680 $this->debug('read body of length ' . strlen($data));
2681 $this->incoming_payload .= $data;
2682 $this->debug('received a total of '.strlen($this->incoming_payload).' bytes of data from server');
2684 // close filepointer
2686 (isset($this->incoming_headers['connection']) && strtolower($this->incoming_headers['connection']) == 'close') ||
2687 (! $this->persistentConnection) || feof($this->fp)){
2690 $this->debug('closed socket');
2693 // connection was closed unexpectedly
2694 if($this->incoming_payload == ''){
2695 $this->setError('no response from server');
2699 // decode transfer-encoding
2700 // if(isset($this->incoming_headers['transfer-encoding']) && strtolower($this->incoming_headers['transfer-encoding']) == 'chunked'){
2701 // if(!$data = $this->decodeChunked($data, $lb)){
2702 // $this->setError('Decoding of chunked data failed');
2705 //print "<pre>\nde-chunked:\n---------------\n$data\n\n---------------\n</pre>";
2706 // set decoded payload
2707 // $this->incoming_payload = $header_data.$lb.$lb.$data;
2710 } else if ($this->scheme == 'https') {
2712 $this->debug('send and receive with cURL');
2713 $this->incoming_payload = curl_exec($this->ch);
2714 $data = $this->incoming_payload;
2716 $cErr = curl_error($this->ch);
2718 $err = 'cURL ERROR: '.curl_errno($this->ch).': '.$cErr.'<br>';
2719 // TODO: there is a PHP bug that can cause this to SEGV for CURLINFO_CONTENT_TYPE
2720 foreach(curl_getinfo($this->ch) as $k => $v){
2721 $err .= "$k: $v<br>";
2724 $this->setError($err);
2725 curl_close($this->ch);
2729 //var_dump(curl_getinfo($this->ch));
2733 $this->debug('No cURL error, closing cURL');
2734 curl_close($this->ch);
2736 // remove 100 header(s)
2737 while (ereg('^HTTP/1.1 100',$data)) {
2738 if ($pos = strpos($data,"\r\n\r\n")) {
2739 $data = ltrim(substr($data,$pos));
2740 } elseif($pos = strpos($data,"\n\n") ) {
2741 $data = ltrim(substr($data,$pos));
2745 // separate content from HTTP headers
2746 if ($pos = strpos($data,"\r\n\r\n")) {
2748 } elseif( $pos = strpos($data,"\n\n")) {
2751 $this->debug('no proper separation of headers and document');
2752 $this->setError('no proper separation of headers and document');
2755 $header_data = trim(substr($data,0,$pos));
2756 $header_array = explode($lb,$header_data);
2757 $data = ltrim(substr($data,$pos));
2758 $this->debug('found proper separation of headers and document');
2759 $this->debug('cleaned data, stringlen: '.strlen($data));
2761 foreach ($header_array as $header_line) {
2762 $arr = explode(':',$header_line,2);
2763 if(count($arr) > 1){
2764 $header_name = strtolower(trim($arr[0]));
2765 $this->incoming_headers[$header_name] = trim($arr[1]);
2766 if ($header_name == 'set-cookie') {
2767 // TODO: allow multiple cookies from parseCookie
2768 $cookie = $this->parseCookie(trim($arr[1]));
2770 $this->incoming_cookies[] = $cookie;
2771 $this->debug('found cookie: ' . $cookie['name'] . ' = ' . $cookie['value']);
2773 $this->debug('did not find cookie in ' . trim($arr[1]));
2776 } else if (isset($header_name)) {
2777 // append continuation line to previous header
2778 $this->incoming_headers[$header_name] .= $lb . ' ' . $header_line;
2783 $arr = explode(' ', $header_array[0], 3);
2784 $http_version = $arr[0];
2785 $http_status = intval($arr[1]);
2786 $http_reason = count($arr) > 2 ? $arr[2] : '';
2788 // see if we need to resend the request with http digest authentication
2789 if (isset($this->incoming_headers['location']) && $http_status == 301) {
2790 $this->debug("Got 301 $http_reason with Location: " . $this->incoming_headers['location']);
2791 $this->setURL($this->incoming_headers['location']);
2792 $this->tryagain = true;
2796 // see if we need to resend the request with http digest authentication
2797 if (isset($this->incoming_headers['www-authenticate']) && $http_status == 401) {
2798 $this->debug("Got 401 $http_reason with WWW-Authenticate: " . $this->incoming_headers['www-authenticate']);
2799 if (strstr($this->incoming_headers['www-authenticate'], "Digest ")) {
2800 $this->debug('Server wants digest authentication');
2801 // remove "Digest " from our elements
2802 $digestString = str_replace('Digest ', '', $this->incoming_headers['www-authenticate']);
2804 // parse elements into array
2805 $digestElements = explode(',', $digestString);
2806 foreach ($digestElements as $val) {
2807 $tempElement = explode('=', trim($val), 2);
2808 $digestRequest[$tempElement[0]] = str_replace("\"", '', $tempElement[1]);
2811 // should have (at least) qop, realm, nonce
2812 if (isset($digestRequest['nonce'])) {
2813 $this->setCredentials($this->username, $this->password, 'digest', $digestRequest);
2814 $this->tryagain = true;
2818 $this->debug('HTTP authentication failed');
2819 $this->setError('HTTP authentication failed');
2824 ($http_status >= 300 && $http_status <= 307) ||
2825 ($http_status >= 400 && $http_status <= 417) ||
2826 ($http_status >= 501 && $http_status <= 505)
2828 $this->setError("Unsupported HTTP response status $http_status $http_reason (soap_client->response has contents of the response)");
2832 // decode content-encoding
2833 if(isset($this->incoming_headers['content-encoding']) && $this->incoming_headers['content-encoding'] != ''){
2834 if(strtolower($this->incoming_headers['content-encoding']) == 'deflate' || strtolower($this->incoming_headers['content-encoding']) == 'gzip'){
2835 // if decoding works, use it. else assume data wasn't gzencoded
2836 if(function_exists('gzinflate')){
2837 //$timer->setMarker('starting decoding of gzip/deflated content');
2838 // IIS 5 requires gzinflate instead of gzuncompress (similar to IE 5 and gzdeflate v. gzcompress)
2839 // this means there are no Zlib headers, although there should be
2840 $this->debug('The gzinflate function exists');
2841 $datalen = strlen($data);
2842 if ($this->incoming_headers['content-encoding'] == 'deflate') {
2843 if ($degzdata = @gzinflate($data)) {
2845 $this->debug('The payload has been inflated to ' . strlen($data) . ' bytes');
2846 if (strlen($data) < $datalen) {
2847 // test for the case that the payload has been compressed twice
2848 $this->debug('The inflated payload is smaller than the gzipped one; try again');
2849 if ($degzdata = @gzinflate($data)) {
2851 $this->debug('The payload has been inflated again to ' . strlen($data) . ' bytes');
2855 $this->debug('Error using gzinflate to inflate the payload');
2856 $this->setError('Error using gzinflate to inflate the payload');
2858 } elseif ($this->incoming_headers['content-encoding'] == 'gzip') {
2859 if ($degzdata = @gzinflate(substr($data, 10))) { // do our best
2861 $this->debug('The payload has been un-gzipped to ' . strlen($data) . ' bytes');
2862 if (strlen($data) < $datalen) {
2863 // test for the case that the payload has been compressed twice
2864 $this->debug('The un-gzipped payload is smaller than the gzipped one; try again');
2865 if ($degzdata = @gzinflate(substr($data, 10))) {
2867 $this->debug('The payload has been un-gzipped again to ' . strlen($data) . ' bytes');
2871 $this->debug('Error using gzinflate to un-gzip the payload');
2872 $this->setError('Error using gzinflate to un-gzip the payload');
2875 //$timer->setMarker('finished decoding of gzip/deflated content');
2876 //print "<xmp>\nde-inflated:\n---------------\n$data\n-------------\n</xmp>";
2877 // set decoded payload
2878 $this->incoming_payload = $header_data.$lb.$lb.$data;
2880 $this->debug('The server sent compressed data. Your php install must have the Zlib extension compiled in to support this.');
2881 $this->setError('The server sent compressed data. Your php install must have the Zlib extension compiled in to support this.');
2884 $this->debug('Unsupported Content-Encoding ' . $this->incoming_headers['content-encoding']);
2885 $this->setError('Unsupported Content-Encoding ' . $this->incoming_headers['content-encoding']);
2888 $this->debug('No Content-Encoding header');
2891 if(strlen($data) == 0){
2892 $this->debug('no data after headers!');
2893 $this->setError('no data present after HTTP headers');
2900 function setContentType($type, $charset = false) {
2901 $this->outgoing_headers['Content-Type'] = $type . ($charset ? '; charset=' . $charset : '');
2902 $this->debug('set Content-Type: ' . $this->outgoing_headers['Content-Type']);
2905 function usePersistentConnection(){
2906 if (isset($this->outgoing_headers['Accept-Encoding'])) {
2909 $this->protocol_version = '1.1';
2910 $this->persistentConnection = true;
2911 $this->outgoing_headers['Connection'] = 'Keep-Alive';
2912 $this->debug('set Connection: ' . $this->outgoing_headers['Connection']);
2917 * parse an incoming Cookie into it's parts
2919 * @param string $cookie_str content of cookie
2920 * @return array with data of that cookie
2924 * TODO: allow a Set-Cookie string to be parsed into multiple cookies
2926 function parseCookie($cookie_str) {
2927 $cookie_str = str_replace('; ', ';', $cookie_str) . ';';
2928 $data = split(';', $cookie_str);
2929 $value_str = $data[0];
2931 $cookie_param = 'domain=';
2932 $start = strpos($cookie_str, $cookie_param);
2934 $domain = substr($cookie_str, $start + strlen($cookie_param));
2935 $domain = substr($domain, 0, strpos($domain, ';'));
2940 $cookie_param = 'expires=';
2941 $start = strpos($cookie_str, $cookie_param);
2943 $expires = substr($cookie_str, $start + strlen($cookie_param));
2944 $expires = substr($expires, 0, strpos($expires, ';'));
2949 $cookie_param = 'path=';
2950 $start = strpos($cookie_str, $cookie_param);
2952 $path = substr($cookie_str, $start + strlen($cookie_param));
2953 $path = substr($path, 0, strpos($path, ';'));
2958 $cookie_param = ';secure;';
2959 if (strpos($cookie_str, $cookie_param) !== FALSE) {
2965 $sep_pos = strpos($value_str, '=');
2968 $name = substr($value_str, 0, $sep_pos);
2969 $value = substr($value_str, $sep_pos + 1);
2970 $cookie= array( 'name' => $name,
2972 'domain' => $domain,
2974 'expires' => $expires,
2983 * sort out cookies for the current request
2985 * @param array $cookies array with all cookies
2986 * @param boolean $secure is the send-content secure or not?
2987 * @return string for Cookie-HTTP-Header
2990 function getCookiesForRequest($cookies, $secure=false) {
2992 if ((! is_null($cookies)) && (is_array($cookies))) {
2993 foreach ($cookies as $cookie) {
2994 if (! is_array($cookie)) {
2997 $this->debug("check cookie for validity: ".$cookie['name'].'='.$cookie['value']);
2998 if ((isset($cookie['expires'])) && (! empty($cookie['expires']))) {
2999 if (strtotime($cookie['expires']) <= time()) {
3000 $this->debug('cookie has expired');
3004 if ((isset($cookie['domain'])) && (! empty($cookie['domain']))) {
3005 $domain = preg_quote($cookie['domain']);
3006 if (! preg_match("'.*$domain$'i", $this->host)) {
3007 $this->debug('cookie has different domain');
3011 if ((isset($cookie['path'])) && (! empty($cookie['path']))) {
3012 $path = preg_quote($cookie['path']);
3013 if (! preg_match("'^$path.*'i", $this->path)) {
3014 $this->debug('cookie is for a different path');
3018 if ((! $secure) && (isset($cookie['secure'])) && ($cookie['secure'])) {
3019 $this->debug('cookie is secure, transport is not');
3022 $cookie_str .= $cookie['name'] . '=' . $cookie['value'] . '; ';
3023 $this->debug('add cookie to Cookie-String: ' . $cookie['name'] . '=' . $cookie['value']);
3036 * soap_server allows the user to create a SOAP server
3037 * that is capable of receiving messages and returning responses
3039 * NOTE: WSDL functionality is experimental
3041 * @author Dietrich Ayala <dietrich@ganx4.com>
3042 * @version $Id: nusoap.php,v 1.3 2008/07/10 16:06:24 ppollet Exp $
3045 class soap_server extends nusoap_base {
3047 * HTTP headers of request
3051 var $headers = array();
3059 * SOAP headers from request (incomplete namespace resolution; special characters not escaped) (text)
3063 var $requestHeaders = '';
3065 * SOAP body request portion (incomplete namespace resolution; special characters not escaped) (text)
3071 * SOAP payload for request (text)
3075 var $requestSOAP = '';
3077 * requested method namespace URI
3081 var $methodURI = '';
3083 * name of method requested
3087 var $methodname = '';
3089 * method parameters from request
3093 var $methodparams = array();
3095 * SOAP Action from request
3099 var $SOAPAction = '';
3101 * character set encoding of incoming (request) messages
3105 var $xml_encoding = '';
3107 * toggles whether the parser decodes element content w/ utf8_decode()
3111 var $decode_utf8 = true;
3114 * HTTP headers of response
3118 var $outgoing_headers = array();
3126 * SOAP headers for response (text)
3130 var $responseHeaders = '';
3132 * SOAP payload for response (text)
3136 var $responseSOAP = '';
3138 * method return value to place in response
3142 var $methodreturn = false;
3144 * whether $methodreturn is a string of literal XML
3148 var $methodreturnisliteralxml = false;
3150 * SOAP fault for response (or false)
3156 * text indication of result (for debugging)
3160 var $result = 'successful';
3163 * assoc array of operations => opData; operations are added by the register()
3164 * method or by parsing an external WSDL definition
3168 var $operations = array();
3170 * wsdl instance (if one)
3176 * URL for WSDL (if one)
3180 var $externalWSDLURL = false;
3182 * whether to append debug to response as XML comment
3186 var $debug_flag = false;
3191 * the optional parameter is a path to a WSDL file that you'd like to bind the server instance to.
3193 * @param mixed $wsdl file path or URL (string), or wsdl instance (object)
3196 function soap_server($wsdl=false){
3197 parent::nusoap_base();
3198 // turn on debugging?
3200 global $HTTP_SERVER_VARS;
3202 if (isset($_SERVER)) {
3203 $this->debug("_SERVER is defined:");
3204 $this->appendDebug($this->varDump($_SERVER));
3205 } elseif (isset($HTTP_SERVER_VARS)) {
3206 $this->debug("HTTP_SERVER_VARS is defined:");
3207 $this->appendDebug($this->varDump($HTTP_SERVER_VARS));
3209 $this->debug("Neither _SERVER nor HTTP_SERVER_VARS is defined.");
3212 if (isset($debug)) {
3213 $this->debug("In soap_server, set debug_flag=$debug based on global flag");
3214 $this->debug_flag = $debug;
3215 } elseif (isset($_SERVER['QUERY_STRING'])) {
3216 $qs = explode('&', $_SERVER['QUERY_STRING']);
3217 foreach ($qs as $v) {
3218 if (substr($v, 0, 6) == 'debug=') {
3219 $this->debug("In soap_server, set debug_flag=" . substr($v, 6) . " based on query string #1");
3220 $this->debug_flag = substr($v, 6);
3223 } elseif (isset($HTTP_SERVER_VARS['QUERY_STRING'])) {
3224 $qs = explode('&', $HTTP_SERVER_VARS['QUERY_STRING']);
3225 foreach ($qs as $v) {
3226 if (substr($v, 0, 6) == 'debug=') {
3227 $this->debug("In soap_server, set debug_flag=" . substr($v, 6) . " based on query string #2");
3228 $this->debug_flag = substr($v, 6);
3235 $this->debug("In soap_server, WSDL is specified");
3236 if (is_object($wsdl) && (get_class($wsdl) == 'wsdl')) {
3237 $this->wsdl = $wsdl;
3238 $this->externalWSDLURL = $this->wsdl->wsdl;
3239 $this->debug('Use existing wsdl instance from ' . $this->externalWSDLURL);
3241 $this->debug('Create wsdl from ' . $wsdl);
3242 $this->wsdl = new wsdl($wsdl);
3243 $this->externalWSDLURL = $wsdl;
3245 $this->appendDebug($this->wsdl->getDebug());
3246 $this->wsdl->clearDebug();
3247 if($err = $this->wsdl->getError()){
3248 die('WSDL ERROR: '.$err);
3254 * processes request and returns response
3256 * @param string $data usually is the value of $HTTP_RAW_POST_DATA
3259 function service($data){
3260 global $HTTP_SERVER_VARS;
3262 if (isset($_SERVER['QUERY_STRING'])) {
3263 $qs = $_SERVER['QUERY_STRING'];
3264 } elseif (isset($HTTP_SERVER_VARS['QUERY_STRING'])) {
3265 $qs = $HTTP_SERVER_VARS['QUERY_STRING'];
3269 $this->debug("In service, query string=$qs");
3271 if (ereg('wsdl', $qs) ){
3272 $this->debug("In service, this is a request for WSDL");
3273 if($this->externalWSDLURL){
3274 if (strpos($this->externalWSDLURL,"://")!==false) { // assume URL
3275 header('Location: '.$this->externalWSDLURL);
3276 } else { // assume file
3277 header("Content-Type: text/xml\r\n");
3278 $fp = fopen($this->externalWSDLURL, 'r');
3281 } elseif ($this->wsdl) {
3282 header("Content-Type: text/xml; charset=ISO-8859-1\r\n");
3283 print $this->wsdl->serialize($this->debug_flag);
3284 if ($this->debug_flag) {
3285 $this->debug('wsdl:');
3286 $this->appendDebug($this->varDump($this->wsdl));
3287 print $this->getDebugAsXMLComment();
3290 header("Content-Type: text/html; charset=ISO-8859-1\r\n");
3291 print "This service does not provide WSDL";
3293 } elseif ($data == '' && $this->wsdl) {
3294 $this->debug("In service, there is no data, so return Web description");
3295 print $this->wsdl->webDescription();
3297 $this->debug("In service, invoke the request");
3298 $this->parse_request($data);
3299 if (! $this->fault) {
3300 $this->invoke_method();
3302 if (! $this->fault) {
3303 $this->serialize_return();
3305 $this->send_response();
3310 * parses HTTP request headers.
3312 * The following fields are set by this function (when successful)
3321 function parse_http_headers() {
3322 global $HTTP_SERVER_VARS;
3324 $this->request = '';
3325 $this->SOAPAction = '';
3326 if(function_exists('getallheaders')){
3327 $this->debug("In parse_http_headers, use getallheaders");
3328 $headers = getallheaders();
3329 foreach($headers as $k=>$v){
3330 $k = strtolower($k);
3331 $this->headers[$k] = $v;
3332 $this->request .= "$k: $v\r\n";
3333 $this->debug("$k: $v");
3335 // get SOAPAction header
3336 if(isset($this->headers['soapaction'])){
3337 $this->SOAPAction = str_replace('"','',$this->headers['soapaction']);
3339 // get the character encoding of the incoming request
3340 if(isset($this->headers['content-type']) && strpos($this->headers['content-type'],'=')){
3341 $enc = str_replace('"','',substr(strstr($this->headers["content-type"],'='),1));
3342 if(eregi('^(ISO-8859-1|US-ASCII|UTF-8)$',$enc)){
3343 $this->xml_encoding = strtoupper($enc);
3345 $this->xml_encoding = 'US-ASCII';
3348 // should be US-ASCII for HTTP 1.0 or ISO-8859-1 for HTTP 1.1
3349 $this->xml_encoding = 'ISO-8859-1';
3351 } elseif(isset($_SERVER) && is_array($_SERVER)){
3352 $this->debug("In parse_http_headers, use _SERVER");
3353 foreach ($_SERVER as $k => $v) {
3354 if (substr($k, 0, 5) == 'HTTP_') {
3355 $k = str_replace(' ', '-', strtolower(str_replace('_', ' ', substr($k, 5)))); $k = strtolower(substr($k, 5));
3357 $k = str_replace(' ', '-', strtolower(str_replace('_', ' ', $k))); $k = strtolower($k);
3359 if ($k == 'soapaction') {
3360 // get SOAPAction header
3362 $v = str_replace('"', '', $v);
3363 $v = str_replace('\\', '', $v);
3364 $this->SOAPAction = $v;
3365 } else if ($k == 'content-type') {
3366 // get the character encoding of the incoming request
3367 if (strpos($v, '=')) {
3368 $enc = substr(strstr($v, '='), 1);
3369 $enc = str_replace('"', '', $enc);
3370 $enc = str_replace('\\', '', $enc);
3371 if (eregi('^(ISO-8859-1|US-ASCII|UTF-8)$', $enc)) {
3372 $this->xml_encoding = strtoupper($enc);
3374 $this->xml_encoding = 'US-ASCII';
3377 // should be US-ASCII for HTTP 1.0 or ISO-8859-1 for HTTP 1.1
3378 $this->xml_encoding = 'ISO-8859-1';
3381 $this->headers[$k] = $v;
3382 $this->request .= "$k: $v\r\n";
3383 $this->debug("$k: $v");
3385 } elseif (is_array($HTTP_SERVER_VARS)) {
3386 $this->debug("In parse_http_headers, use HTTP_SERVER_VARS");
3387 foreach ($HTTP_SERVER_VARS as $k => $v) {
3388 if (substr($k, 0, 5) == 'HTTP_') {
3389 $k = str_replace(' ', '-', strtolower(str_replace('_', ' ', substr($k, 5)))); $k = strtolower(substr($k, 5));
3391 $k = str_replace(' ', '-', strtolower(str_replace('_', ' ', $k))); $k = strtolower($k);
3393 if ($k == 'soapaction') {
3394 // get SOAPAction header
3396 $v = str_replace('"', '', $v);
3397 $v = str_replace('\\', '', $v);
3398 $this->SOAPAction = $v;
3399 } else if ($k == 'content-type') {
3400 // get the character encoding of the incoming request
3401 if (strpos($v, '=')) {
3402 $enc = substr(strstr($v, '='), 1);
3403 $enc = str_replace('"', '', $enc);
3404 $enc = str_replace('\\', '', $enc);
3405 if (eregi('^(ISO-8859-1|US-ASCII|UTF-8)$', $enc)) {
3406 $this->xml_encoding = strtoupper($enc);
3408 $this->xml_encoding = 'US-ASCII';
3411 // should be US-ASCII for HTTP 1.0 or ISO-8859-1 for HTTP 1.1
3412 $this->xml_encoding = 'ISO-8859-1';
3415 $this->headers[$k] = $v;
3416 $this->request .= "$k: $v\r\n";
3417 $this->debug("$k: $v");
3420 $this->debug("In parse_http_headers, HTTP headers not accessible");
3421 $this->setError("HTTP headers not accessible");
3428 * The following fields are set by this function (when successful)
3442 * This sets the fault field on error
3444 * @param string $data XML string
3447 function parse_request($data='') {
3448 $this->debug('entering parse_request()');
3449 $this->parse_http_headers();
3450 $this->debug('got character encoding: '.$this->xml_encoding);
3451 // uncompress if necessary
3452 if (isset($this->headers['content-encoding']) && $this->headers['content-encoding'] != '') {
3453 $this->debug('got content encoding: ' . $this->headers['content-encoding']);
3454 if ($this->headers['content-encoding'] == 'deflate' || $this->headers['content-encoding'] == 'gzip') {
3455 // if decoding works, use it. else assume data wasn't gzencoded
3456 if (function_exists('gzuncompress')) {
3457 if ($this->headers['content-encoding'] == 'deflate' && $degzdata = @gzuncompress($data)) {
3459 } elseif ($this->headers['content-encoding'] == 'gzip' && $degzdata = gzinflate(substr($data, 10))) {
3462 $this->fault('SOAP-ENV:Client', 'Errors occurred when trying to decode the data');
3466 $this->fault('SOAP-ENV:Client', 'This Server does not support compressed data');
3471 $this->request .= "\r\n".$data;
3472 $data = $this->parseRequest($this->headers, $data);
3473 $this->requestSOAP = $data;
3474 $this->debug('leaving parse_request');
3478 * invokes a PHP function for the requested SOAP method
3480 * The following fields are set by this function (when successful)
3484 * Note that the PHP function that is called may also set the following
3485 * fields to affect the response sent to the client
3490 * This sets the fault field on error
3494 function invoke_method() {
3495 $this->debug('in invoke_method, methodname=' . $this->methodname . ' methodURI=' . $this->methodURI . ' SOAPAction=' . $this->SOAPAction);
3498 if ($this->opData = $this->wsdl->getOperationData($this->methodname)) {
3499 $this->debug('in invoke_method, found WSDL operation=' . $this->methodname);
3500 $this->appendDebug('opData=' . $this->varDump($this->opData));
3501 } elseif ($this->opData = $this->wsdl->getOperationDataForSoapAction($this->SOAPAction)) {
3502 // Note: hopefully this case will only be used for doc/lit, since rpc services should have wrapper element
3503 $this->debug('in invoke_method, found WSDL soapAction=' . $this->SOAPAction . ' for operation=' . $this->opData['name']);
3504 $this->appendDebug('opData=' . $this->varDump($this->opData));
3505 $this->methodname = $this->opData['name'];
3507 $this->debug('in invoke_method, no WSDL for operation=' . $this->methodname);
3508 $this->fault('SOAP-ENV:Client', "Operation '" . $this->methodname . "' is not defined in the WSDL for this service");
3512 $this->debug('in invoke_method, no WSDL to validate method');
3515 // if a . is present in $this->methodname, we see if there is a class in scope,
3516 // which could be referred to. We will also distinguish between two deliminators,
3517 // to allow methods to be called a the class or an instance
3520 if (strpos($this->methodname, '..') > 0) {
3522 } else if (strpos($this->methodname, '.') > 0) {
3528 if (strlen($delim) > 0 && substr_count($this->methodname, $delim) == 1 &&
3529 class_exists(substr($this->methodname, 0, strpos($this->methodname, $delim)))) {
3530 // get the class and method name
3531 $class = substr($this->methodname, 0, strpos($this->methodname, $delim));
3532 $method = substr($this->methodname, strpos($this->methodname, $delim) + strlen($delim));
3533 $this->debug("in invoke_method, class=$class method=$method delim=$delim");
3536 // does method exist?
3538 if (!function_exists($this->methodname)) {
3539 $this->debug("in invoke_method, function '$this->methodname' not found!");
3540 $this->result = 'fault: method not found';
3541 $this->fault('SOAP-ENV:Client',"method '$this->methodname' not defined in service");
3545 $method_to_compare = (substr(phpversion(), 0, 2) == '4.') ? strtolower($method) : $method;
3546 if (!in_array($method_to_compare, get_class_methods($class))) {
3547 $this->debug("in invoke_method, method '$this->methodname' not found in class '$class'!");
3548 $this->result = 'fault: method not found';
3549 $this->fault('SOAP-ENV:Client',"method '$this->methodname' not defined in service");
3554 // evaluate message, getting back parameters
3555 // verify that request parameters match the method's signature
3556 if(! $this->verify_method($this->methodname,$this->methodparams)){
3558 $this->debug('ERROR: request not verified against method signature');
3559 $this->result = 'fault: request failed validation against method signature';
3561 $this->fault('SOAP-ENV:Client',"Operation '$this->methodname' not defined in service.");
3565 // if there are parameters to pass
3566 $this->debug('in invoke_method, params:');
3567 $this->appendDebug($this->varDump($this->methodparams));
3568 $this->debug("in invoke_method, calling '$this->methodname'");
3569 if (!function_exists('call_user_func_array')) {
3571 $this->debug('in invoke_method, calling function using eval()');
3572 $funcCall = "\$this->methodreturn = $this->methodname(";
3574 if ($delim == '..') {
3575 $this->debug('in invoke_method, calling class method using eval()');
3576 $funcCall = "\$this->methodreturn = ".$class."::".$method."(";
3578 $this->debug('in invoke_method, calling instance method using eval()');
3579 // generate unique instance name
3580 $instname = "\$inst_".time();
3581 $funcCall = $instname." = new ".$class."(); ";
3582 $funcCall .= "\$this->methodreturn = ".$instname."->".$method."(";
3585 if ($this->methodparams) {
3586 foreach ($this->methodparams as $param) {
3587 if (is_array($param)) {
3588 $this->fault('SOAP-ENV:Client', 'NuSOAP does not handle complexType parameters correctly when using eval; call_user_func_array must be available');
3591 $funcCall .= "\"$param\",";
3593 $funcCall = substr($funcCall, 0, -1);
3596 $this->debug('in invoke_method, function call: '.$funcCall);
3600 $this->debug('in invoke_method, calling function using call_user_func_array()');
3601 $call_arg = "$this->methodname"; // straight assignment changes $this->methodname to lower case after call_user_func_array()
3602 } elseif ($delim == '..') {
3603 $this->debug('in invoke_method, calling class method using call_user_func_array()');
3604 $call_arg = array ($class, $method);
3606 $this->debug('in invoke_method, calling instance method using call_user_func_array()');
3607 $instance = new $class ();
3608 $call_arg = array(&$instance, $method);
3610 $this->methodreturn = call_user_func_array($call_arg, array_values($this->methodparams));
3612 $this->debug('in invoke_method, methodreturn:');
3613 $this->appendDebug($this->varDump($this->methodreturn));
3614 $this->debug("in invoke_method, called method $this->methodname, received $this->methodreturn of type ".gettype($this->methodreturn));
3618 * serializes the return value from a PHP function into a full SOAP Envelope
3620 * The following fields are set by this function (when successful)
3624 * This sets the fault field on error
3628 function serialize_return() {
3629 $this->debug('Entering serialize_return methodname: ' . $this->methodname . ' methodURI: ' . $this->methodURI);
3631 if (isset($this->methodreturn) && (get_class($this->methodreturn) == 'soap_fault')) {
3632 $this->debug('got a fault object from method');
3633 $this->fault = $this->methodreturn;
3635 } elseif ($this->methodreturnisliteralxml) {
3636 $return_val = $this->methodreturn;
3637 // returned value(s)
3639 $this->debug('got a(n) '.gettype($this->methodreturn).' from method');
3640 $this->debug('serializing return value');
3642 // weak attempt at supporting multiple output params
3643 if(sizeof($this->opData['output']['parts']) > 1){
3644 $opParams = $this->methodreturn;
3646 // TODO: is this really necessary?
3647 $opParams = array($this->methodreturn);
3649 $return_val = $this->wsdl->serializeRPCParameters($this->methodname,'output',$opParams);
3650 $this->appendDebug($this->wsdl->getDebug());
3651 $this->wsdl->clearDebug();
3652 if($errstr = $this->wsdl->getError()){
3653 $this->debug('got wsdl error: '.$errstr);
3654 $this->fault('SOAP-ENV:Server', 'unable to serialize result');
3658 if (isset($this->methodreturn)) {
3659 $return_val = $this->serialize_val($this->methodreturn, 'return');
3662 $this->debug('in absence of WSDL, assume void return for backward compatibility');
3666 $this->debug('return value:');
3667 $this->appendDebug($this->varDump($return_val));
3669 $this->debug('serializing response');
3671 $this->debug('have WSDL for serialization: style is ' . $this->opData['style']);
3672 if ($this->opData['style'] == 'rpc') {
3673 $this->debug('style is rpc for serialization: use is ' . $this->opData['output']['use']);
3674 if ($this->opData['output']['use'] == 'literal') {
3675 $payload = '<'.$this->methodname.'Response xmlns="'.$this->methodURI.'">'.$return_val.'</'.$this->methodname."Response>";
3677 $payload = '<ns1:'.$this->methodname.'Response xmlns:ns1="'.$this->methodURI.'">'.$return_val.'</ns1:'.$this->methodname."Response>";
3680 $this->debug('style is not rpc for serialization: assume document');
3681 $payload = $return_val;
3684 $this->debug('do not have WSDL for serialization: assume rpc/encoded');
3685 $payload = '<ns1:'.$this->methodname.'Response xmlns:ns1="'.$this->methodURI.'">'.$return_val.'</ns1:'.$this->methodname."Response>";
3687 $this->result = 'successful';
3689 //if($this->debug_flag){
3690 $this->appendDebug($this->wsdl->getDebug());
3692 if (isset($opData['output']['encodingStyle'])) {
3693 $encodingStyle = $opData['output']['encodingStyle'];
3695 $encodingStyle = '';
3697 // Added: In case we use a WSDL, return a serialized env. WITH the usedNamespaces.
3698 $this->responseSOAP = $this->serializeEnvelope($payload,$this->responseHeaders,$this->wsdl->usedNamespaces,$this->opData['style'],$encodingStyle);
3700 $this->responseSOAP = $this->serializeEnvelope($payload,$this->responseHeaders);
3702 $this->debug("Leaving serialize_return");
3706 * sends an HTTP response
3708 * The following fields are set by this function (when successful)
3715 function send_response() {
3716 $this->debug('Enter send_response');
3718 $payload = $this->fault->serialize();
3719 $this->outgoing_headers[] = "HTTP/1.0 500 Internal Server Error";
3720 $this->outgoing_headers[] = "Status: 500 Internal Server Error";
3722 $payload = $this->responseSOAP;
3723 // Some combinations of PHP+Web server allow the Status
3724 // to come through as a header. Since OK is the default
3726 // $this->outgoing_headers[] = "HTTP/1.0 200 OK";
3727 // $this->outgoing_headers[] = "Status: 200 OK";
3729 // add debug data if in debug mode
3730 if(isset($this->debug_flag) && $this->debug_flag){
3731 $payload .= $this->getDebugAsXMLComment();
3733 $this->outgoing_headers[] = "Server: $this->title Server v$this->version";
3734 ereg('\$Revisio' . 'n: ([^ ]+)', $this->revision, $rev);
3735 $this->outgoing_headers[] = "X-SOAP-Server: $this->title/$this->version (".$rev[1].")";
3736 // Let the Web server decide about this
3737 //$this->outgoing_headers[] = "Connection: Close\r\n";
3738 $payload = $this->getHTTPBody($payload);
3739 $type = $this->getHTTPContentType();
3740 $charset = $this->getHTTPContentTypeCharset();
3741 $this->outgoing_headers[] = "Content-Type: $type" . ($charset ? '; charset=' . $charset : '');
3742 //begin code to compress payload - by John
3743 // NOTE: there is no way to know whether the Web server will also compress
3745 if (strlen($payload) > 1024 && isset($this->headers) && isset($this->headers['accept-encoding'])) {
3746 if (strstr($this->headers['accept-encoding'], 'gzip')) {
3747 if (function_exists('gzencode')) {
3748 if (isset($this->debug_flag) && $this->debug_flag) {
3749 $payload .= "<!-- Content being gzipped -->";
3751 $this->outgoing_headers[] = "Content-Encoding: gzip";
3752 $payload = gzencode($payload);
3754 if (isset($this->debug_flag) && $this->debug_flag) {
3755 $payload .= "<!-- Content will not be gzipped: no gzencode -->";
3758 } elseif (strstr($this->headers['accept-encoding'], 'deflate')) {
3759 // Note: MSIE requires gzdeflate output (no Zlib header and checksum),
3760 // instead of gzcompress output,
3761 // which conflicts with HTTP 1.1 spec (http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.5)
3762 if (function_exists('gzdeflate')) {
3763 if (isset($this->debug_flag) && $this->debug_flag) {
3764 $payload .= "<!-- Content being deflated -->";
3766 $this->outgoing_headers[] = "Content-Encoding: deflate";
3767 $payload = gzdeflate($payload);
3769 if (isset($this->debug_flag) && $this->debug_flag) {
3770 $payload .= "<!-- Content will not be deflated: no gzcompress -->";
3776 $this->outgoing_headers[] = "Content-Length: ".strlen($payload);
3777 reset($this->outgoing_headers);
3778 foreach($this->outgoing_headers as $hdr){
3779 header($hdr, false);
3782 $this->response = join("\r\n",$this->outgoing_headers)."\r\n\r\n".$payload;
3786 * takes the value that was created by parsing the request
3787 * and compares to the method's signature, if available.
3789 * @param string $operation The operation to be invoked
3790 * @param array $request The array of parameter values
3791 * @return boolean Whether the operation was found
3794 function verify_method($operation,$request){
3795 if(isset($this->wsdl) && is_object($this->wsdl)){
3796 if($this->wsdl->getOperationData($operation)){
3799 } elseif(isset($this->operations[$operation])){
3806 * processes SOAP message received from client
3808 * @param array $headers The HTTP headers
3809 * @param string $data unprocessed request data from client
3810 * @return mixed value of the message, decoded into a PHP type
3813 function parseRequest($headers, $data) {
3814 $this->debug('Entering parseRequest() for data of length ' . strlen($data) . ' and type ' . $headers['content-type']);
3815 if (!strstr($headers['content-type'], 'text/xml')) {
3816 $this->setError('Request not of type text/xml');
3819 if (strpos($headers['content-type'], '=')) {
3820 $enc = str_replace('"', '', substr(strstr($headers["content-type"], '='), 1));
3821 $this->debug('Got response encoding: ' . $enc);
3822 if(eregi('^(ISO-8859-1|US-ASCII|UTF-8)$',$enc)){
3823 $this->xml_encoding = strtoupper($enc);
3825 $this->xml_encoding = 'US-ASCII';
3828 // should be US-ASCII for HTTP 1.0 or ISO-8859-1 for HTTP 1.1
3829 $this->xml_encoding = 'ISO-8859-1';
3831 $this->debug('Use encoding: ' . $this->xml_encoding . ' when creating soap_parser');
3832 // parse response, get soap parser obj
3833 $parser = new soap_parser($data,$this->xml_encoding,'',$this->decode_utf8);
3835 $this->debug("parser debug: \n".$parser->getDebug());
3836 // if fault occurred during message parsing
3837 if($err = $parser->getError()){
3838 $this->result = 'fault: error in msg parsing: '.$err;
3839 $this->fault('SOAP-ENV:Client',"error in msg parsing:\n".$err);
3840 // else successfully parsed request into soapval object
3842 // get/set methodname
3843 $this->methodURI = $parser->root_struct_namespace;
3844 $this->methodname = $parser->root_struct_name;
3845 $this->debug('methodname: '.$this->methodname.' methodURI: '.$this->methodURI);
3846 $this->debug('calling parser->get_response()');
3847 $this->methodparams = $parser->get_response();
3849 $this->requestHeaders = $parser->getHeaders();
3850 // add document for doclit support
3851 $this->document = $parser->document;
3856 * gets the HTTP body for the current response.
3858 * @param string $soapmsg The SOAP payload
3859 * @return string The HTTP body, which includes the SOAP payload
3862 function getHTTPBody($soapmsg) {
3867 * gets the HTTP content type for the current response.
3869 * Note: getHTTPBody must be called before this.
3871 * @return string the HTTP content type for the current response.
3874 function getHTTPContentType() {
3879 * gets the HTTP content type charset for the current response.
3880 * returns false for non-text content types.
3882 * Note: getHTTPBody must be called before this.
3884 * @return string the HTTP content type charset for the current response.
3887 function getHTTPContentTypeCharset() {
3888 return $this->soap_defencoding;
3892 * add a method to the dispatch map (this has been replaced by the register method)
3894 * @param string $methodname
3895 * @param string $in array of input values
3896 * @param string $out array of output values
3900 function add_to_map($methodname,$in,$out){
3901 $this->operations[$methodname] = array('name' => $methodname,'in' => $in,'out' => $out);
3905 * register a service function with the server
3907 * @param string $name the name of the PHP function, class.method or class..method
3908 * @param array $in assoc array of input values: key = param name, value = param type
3909 * @param array $out assoc array of output values: key = param name, value = param type
3910 * @param mixed $namespace the element namespace for the method or false
3911 * @param mixed $soapaction the soapaction for the method or false
3912 * @param mixed $style optional (rpc|document) or false Note: when 'document' is specified, parameter and return wrappers are created for you automatically
3913 * @param mixed $use optional (encoded|literal) or false
3914 * @param string $documentation optional Description to include in WSDL
3915 * @param string $encodingStyle optional (usually 'http://schemas.xmlsoap.org/soap/encoding/' for encoded)
3918 function register($name,$in=array(),$out=array(),$namespace=false,$soapaction=false,$style=false,$use=false,$documentation='',$encodingStyle=''){
3919 global $HTTP_SERVER_VARS;
3921 if($this->externalWSDLURL){
3922 die('You cannot bind to an external WSDL file, and register methods outside of it! Please choose either WSDL or no WSDL.');
3925 die('You must specify a name when you register an operation');
3927 if (!is_array($in)) {
3928 die('You must provide an array for operation inputs');
3930 if (!is_array($out)) {
3931 die('You must provide an array for operation outputs');
3933 if(false == $namespace) {
3935 if(false == $soapaction) {
3936 if (isset($_SERVER)) {
3937 $SERVER_NAME = $_SERVER['SERVER_NAME'];
3938 $SCRIPT_NAME = isset($_SERVER['PHP_SELF']) ? $_SERVER['PHP_SELF'] : $_SERVER['SCRIPT_NAME'];
3939 } elseif (isset($HTTP_SERVER_VARS)) {
3940 $SERVER_NAME = $HTTP_SERVER_VARS['SERVER_NAME'];
3941 $SCRIPT_NAME = isset($HTTP_SERVER_VARS['PHP_SELF']) ? $HTTP_SERVER_VARS['PHP_SELF'] : $HTTP_SERVER_VARS['SCRIPT_NAME'];
3943 $this->setError("Neither _SERVER nor HTTP_SERVER_VARS is available");
3945 $soapaction = "http://$SERVER_NAME$SCRIPT_NAME/$name";
3947 if(false == $style) {
3953 if ($use == 'encoded' && $encodingStyle = '') {
3954 $encodingStyle = 'http://schemas.xmlsoap.org/soap/encoding/';
3957 $this->operations[$name] = array(
3961 'namespace' => $namespace,
3962 'soapaction' => $soapaction,
3965 $this->wsdl->addOperation($name,$in,$out,$namespace,$soapaction,$style,$use,$documentation,$encodingStyle);
3971 * Specify a fault to be returned to the client.
3972 * This also acts as a flag to the server that a fault has occured.
3974 * @param string $faultcode
3975 * @param string $faultstring
3976 * @param string $faultactor
3977 * @param string $faultdetail
3980 function fault($faultcode,$faultstring,$faultactor='',$faultdetail=''){
3981 if ($faultdetail == '' && $this->debug_flag) {
3982 $faultdetail = $this->getDebug();
3984 $this->fault = new soap_fault($faultcode,$faultactor,$faultstring,$faultdetail);
3985 $this->fault->soap_defencoding = $this->soap_defencoding;
3989 * Sets up wsdl object.
3990 * Acts as a flag to enable internal WSDL generation
3992 * @param string $serviceName, name of the service
3993 * @param mixed $namespace optional 'tns' service namespace or false
3994 * @param mixed $endpoint optional URL of service endpoint or false
3995 * @param string $style optional (rpc|document) WSDL style (also specified by operation)
3996 * @param string $transport optional SOAP transport
3997 * @param mixed $schemaTargetNamespace optional 'types' targetNamespace for service schema or false
3999 function configureWSDL($serviceName,$namespace = false,$endpoint = false,$style='rpc', $transport = 'http://schemas.xmlsoap.org/soap/http', $schemaTargetNamespace = false)
4001 global $HTTP_SERVER_VARS;
4003 if (isset($_SERVER)) {
4004 $SERVER_NAME = $_SERVER['SERVER_NAME'];
4005 $SERVER_PORT = $_SERVER['SERVER_PORT'];
4006 $SCRIPT_NAME = isset($_SERVER['PHP_SELF']) ? $_SERVER['PHP_SELF'] : $_SERVER['SCRIPT_NAME'];
4007 $HTTPS = isset($_SERVER['HTTPS']) ? $_SERVER['HTTPS'] : (isset($HTTP_SERVER_VARS['HTTPS']) ? $HTTP_SERVER_VARS['HTTPS'] : 'off');
4008 } elseif (isset($HTTP_SERVER_VARS)) {
4009 $SERVER_NAME = $HTTP_SERVER_VARS['SERVER_NAME'];
4010 $SERVER_PORT = $HTTP_SERVER_VARS['SERVER_PORT'];
4011 $SCRIPT_NAME = isset($HTTP_SERVER_VARS['PHP_SELF']) ? $HTTP_SERVER_VARS['PHP_SELF'] : $HTTP_SERVER_VARS['SCRIPT_NAME'];
4012 $HTTPS = isset($HTTP_SERVER_VARS['HTTPS']) ? $HTTP_SERVER_VARS['HTTPS'] : 'off';
4014 $this->setError("Neither _SERVER nor HTTP_SERVER_VARS is available");
4016 if ($SERVER_PORT == 80) {
4019 $SERVER_PORT = ':' . $SERVER_PORT;
4021 if(false == $namespace) {
4022 $namespace = "http://$SERVER_NAME/soap/$serviceName";
4025 if(false == $endpoint) {
4026 if ($HTTPS == '1' || $HTTPS == 'on') {
4031 $endpoint = "$SCHEME://$SERVER_NAME$SERVER_PORT$SCRIPT_NAME";
4034 if(false == $schemaTargetNamespace) {
4035 $schemaTargetNamespace = $namespace;
4038 $this->wsdl = new wsdl;
4039 $this->wsdl->serviceName = $serviceName;
4040 $this->wsdl->endpoint = $endpoint;
4041 $this->wsdl->namespaces['tns'] = $namespace;
4042 $this->wsdl->namespaces['soap'] = 'http://schemas.xmlsoap.org/wsdl/soap/';
4043 $this->wsdl->namespaces['wsdl'] = 'http://schemas.xmlsoap.org/wsdl/';
4044 if ($schemaTargetNamespace != $namespace) {
4045 $this->wsdl->namespaces['types'] = $schemaTargetNamespace;
4047 $this->wsdl->schemas[$schemaTargetNamespace][0] = new xmlschema('', '', $this->wsdl->namespaces);
4048 $this->wsdl->schemas[$schemaTargetNamespace][0]->schemaTargetNamespace = $schemaTargetNamespace;
4049 $this->wsdl->schemas[$schemaTargetNamespace][0]->imports['http://schemas.xmlsoap.org/soap/encoding/'][0] = array('location' => '', 'loaded' => true);
4050 $this->wsdl->schemas[$schemaTargetNamespace][0]->imports['http://schemas.xmlsoap.org/wsdl/'][0] = array('location' => '', 'loaded' => true);
4051 $this->wsdl->bindings[$serviceName.'Binding'] = array(
4052 'name'=>$serviceName.'Binding',
4054 'transport'=>$transport,
4055 'portType'=>$serviceName.'PortType');
4056 $this->wsdl->ports[$serviceName.'Port'] = array(
4057 'binding'=>$serviceName.'Binding',
4058 'location'=>$endpoint,
4059 'bindingType'=>'http://schemas.xmlsoap.org/wsdl/soap/');
4070 * parses a WSDL file, allows access to it's data, other utility methods
4072 * @author Dietrich Ayala <dietrich@ganx4.com>
4073 * @version $Id: nusoap.php,v 1.3 2008/07/10 16:06:24 ppollet Exp $
4076 class wsdl extends nusoap_base {
4077 // URL or filename of the root of this WSDL
4079 // define internal arrays of bindings, ports, operations, messages, etc.
4080 var $schemas = array();
4082 var $message = array();
4083 var $complexTypes = array();
4084 var $messages = array();
4085 var $currentMessage;
4086 var $currentOperation;
4087 var $portTypes = array();
4088 var $currentPortType;
4089 var $bindings = array();
4090 var $currentBinding;
4091 var $ports = array();
4093 var $opData = array();
4095 var $documentation = false;
4097 // array of wsdl docs to import
4098 var $import = array();
4103 var $depth_array = array();
4105 var $proxyhost = '';
4106 var $proxyport = '';
4107 var $proxyusername = '';
4108 var $proxypassword = '';
4110 var $response_timeout = 30;
4115 * @param string $wsdl WSDL document URL
4116 * @param string $proxyhost
4117 * @param string $proxyport
4118 * @param string $proxyusername
4119 * @param string $proxypassword
4120 * @param integer $timeout set the connection timeout
4121 * @param integer $response_timeout set the response timeout
4124 function wsdl($wsdl = '',$proxyhost=false,$proxyport=false,$proxyusername=false,$proxypassword=false,$timeout=0,$response_timeout=30){
4125 parent::nusoap_base();
4126 $this->wsdl = $wsdl;
4127 $this->proxyhost = $proxyhost;
4128 $this->proxyport = $proxyport;
4129 $this->proxyusername = $proxyusername;
4130 $this->proxypassword = $proxypassword;
4131 $this->timeout = $timeout;
4132 $this->response_timeout = $response_timeout;
4136 $this->debug('initial wsdl URL: ' . $wsdl);
4137 $this->parseWSDL($wsdl);
4140 // TODO: handle imports more properly, grabbing them in-line and nesting them
4141 $imported_urls = array();
4143 while ($imported > 0) {
4146 foreach ($this->schemas as $ns => $list) {
4147 foreach ($list as $xs) {
4148 $wsdlparts = parse_url($this->wsdl); // this is bogusly simple!
4149 foreach ($xs->imports as $ns2 => $list2) {
4150 for ($ii = 0; $ii < count($list2); $ii++) {
4151 if (! $list2[$ii]['loaded']) {
4152 $this->schemas[$ns]->imports[$ns2][$ii]['loaded'] = true;
4153 $url = $list2[$ii]['location'];
4155 $urlparts = parse_url($url);
4156 if (!isset($urlparts['host'])) {
4157 $url = $wsdlparts['scheme'] . '://' . $wsdlparts['host'] . (isset($wsdlparts['port']) ? ':' .$wsdlparts['port'] : '') .
4158 substr($wsdlparts['path'],0,strrpos($wsdlparts['path'],'/') + 1) .$urlparts['path'];
4160 if (! in_array($url, $imported_urls)) {
4161 $this->parseWSDL($url);
4163 $imported_urls[] = $url;
4166 $this->debug("Unexpected scenario: empty URL for unloaded import");
4174 $wsdlparts = parse_url($this->wsdl); // this is bogusly simple!
4175 foreach ($this->import as $ns => $list) {
4176 for ($ii = 0; $ii < count($list); $ii++) {
4177 if (! $list[$ii]['loaded']) {
4178 $this->import[$ns][$ii]['loaded'] = true;
4179 $url = $list[$ii]['location'];
4181 $urlparts = parse_url($url);
4182 if (!isset($urlparts['host'])) {
4183 $url = $wsdlparts['scheme'] . '://' . $wsdlparts['host'] . (isset($wsdlparts['port']) ? ':' . $wsdlparts['port'] : '') .
4184 substr($wsdlparts['path'],0,strrpos($wsdlparts['path'],'/') + 1) .$urlparts['path'];
4186 if (! in_array($url, $imported_urls)) {
4187 $this->parseWSDL($url);
4189 $imported_urls[] = $url;
4192 $this->debug("Unexpected scenario: empty URL for unloaded import");
4198 // add new data to operation data
4199 foreach($this->bindings as $binding => $bindingData) {
4200 if (isset($bindingData['operations']) && is_array($bindingData['operations'])) {
4201 foreach($bindingData['operations'] as $operation => $data) {
4202 $this->debug('post-parse data gathering for ' . $operation);
4203 $this->bindings[$binding]['operations'][$operation]['input'] =
4204 isset($this->bindings[$binding]['operations'][$operation]['input']) ?
4205 array_merge($this->bindings[$binding]['operations'][$operation]['input'], $this->portTypes[ $bindingData['portType'] ][$operation]['input']) :
4206 $this->portTypes[ $bindingData['portType'] ][$operation]['input'];
4207 $this->bindings[$binding]['operations'][$operation]['output'] =
4208 isset($this->bindings[$binding]['operations'][$operation]['output']) ?
4209 array_merge($this->bindings[$binding]['operations'][$operation]['output'], $this->portTypes[ $bindingData['portType'] ][$operation]['output']) :
4210 $this->portTypes[ $bindingData['portType'] ][$operation]['output'];
4211 if(isset($this->messages[ $this->bindings[$binding]['operations'][$operation]['input']['message'] ])){
4212 $this->bindings[$binding]['operations'][$operation]['input']['parts'] = $this->messages[ $this->bindings[$binding]['operations'][$operation]['input']['message'] ];
4214 if(isset($this->messages[ $this->bindings[$binding]['operations'][$operation]['output']['message'] ])){
4215 $this->bindings[$binding]['operations'][$operation]['output']['parts'] = $this->messages[ $this->bindings[$binding]['operations'][$operation]['output']['message'] ];
4217 if (isset($bindingData['style'])) {
4218 $this->bindings[$binding]['operations'][$operation]['style'] = $bindingData['style'];
4220 $this->bindings[$binding]['operations'][$operation]['transport'] = isset($bindingData['transport']) ? $bindingData['transport'] : '';
4221 $this->bindings[$binding]['operations'][$operation]['documentation'] = isset($this->portTypes[ $bindingData['portType'] ][$operation]['documentation']) ? $this->portTypes[ $bindingData['portType'] ][$operation]['documentation'] : '';
4222 $this->bindings[$binding]['operations'][$operation]['endpoint'] = isset($bindingData['endpoint']) ? $bindingData['endpoint'] : '';
4229 * parses the wsdl document
4231 * @param string $wsdl path or URL
4234 function parseWSDL($wsdl = '')
4237 $this->debug('no wsdl passed to parseWSDL()!!');
4238 $this->setError('no wsdl passed to parseWSDL()!!');
4242 // parse $wsdl for url format
4243 $wsdl_props = parse_url($wsdl);
4245 if (isset($wsdl_props['scheme']) && ($wsdl_props['scheme'] == 'http' || $wsdl_props['scheme'] == 'https')) {
4246 $this->debug('getting WSDL http(s) URL ' . $wsdl);
4248 $tr = new soap_transport_http($wsdl);
4249 $tr->request_method = 'GET';
4250 $tr->useSOAPAction = false;
4251 if($this->proxyhost && $this->proxyport){
4252 $tr->setProxy($this->proxyhost,$this->proxyport,$this->proxyusername,$this->proxypassword);
4254 $tr->setEncoding('gzip, deflate');
4255 $wsdl_string = $tr->send('', $this->timeout, $this->response_timeout);
4256 //$this->debug("WSDL request\n" . $tr->outgoing_payload);
4257 //$this->debug("WSDL response\n" . $tr->incoming_payload);
4258 $this->appendDebug($tr->getDebug());
4260 if($err = $tr->getError() ){
4261 $errstr = 'HTTP ERROR: '.$err;
4262 $this->debug($errstr);
4263 $this->setError($errstr);
4268 $this->debug("got WSDL URL");
4270 // $wsdl is not http(s), so treat it as a file URL or plain file path
4271 if (isset($wsdl_props['scheme']) && ($wsdl_props['scheme'] == 'file') && isset($wsdl_props['path'])) {
4272 $path = isset($wsdl_props['host']) ? ($wsdl_props['host'] . ':' . $wsdl_props['path']) : $wsdl_props['path'];
4276 $this->debug('getting WSDL file ' . $path);
4277 if ($fp = @fopen($path, 'r')) {
4279 while ($data = fread($fp, 32768)) {
4280 $wsdl_string .= $data;
4284 $errstr = "Bad path to WSDL file $path";
4285 $this->debug($errstr);
4286 $this->setError($errstr);
4290 $this->debug('Parse WSDL');
4291 // end new code added
4292 // Create an XML parser.
4293 $this->parser = xml_parser_create();
4294 // Set the options for parsing the XML data.
4295 // xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 1);
4296 xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING, 0);
4297 // Set the object for the parser.
4298 xml_set_object($this->parser, $this);
4299 // Set the element handlers for the parser.
4300 xml_set_element_handler($this->parser, 'start_element', 'end_element');
4301 xml_set_character_data_handler($this->parser, 'character_data');
4302 // Parse the XML file.
4303 if (!xml_parse($this->parser, $wsdl_string, true)) {
4304 // Display an error message.
4306 'XML error parsing WSDL from %s on line %d: %s',
4308 xml_get_current_line_number($this->parser),
4309 xml_error_string(xml_get_error_code($this->parser))
4311 $this->debug($errstr);
4312 $this->debug("XML payload:\n" . $wsdl_string);
4313 $this->setError($errstr);
4317 xml_parser_free($this->parser);
4318 $this->debug('Parsing WSDL done');
4319 // catch wsdl parse errors
4320 if($this->getError()){
4327 * start-element handler
4329 * @param string $parser XML parser object
4330 * @param string $name element name
4331 * @param string $attrs associative array of attributes
4334 function start_element($parser, $name, $attrs)
4336 if ($this->status == 'schema') {
4337 $this->currentSchema->schemaStartElement($parser, $name, $attrs);
4338 $this->appendDebug($this->currentSchema->getDebug());
4339 $this->currentSchema->clearDebug();
4340 } elseif (ereg('schema$', $name)) {
4341 $this->debug('Parsing WSDL schema');
4342 // $this->debug("startElement for $name ($attrs[name]). status = $this->status (".$this->getLocalPart($name).")");
4343 $this->status = 'schema';
4344 $this->currentSchema = new xmlschema('', '', $this->namespaces);
4345 $this->currentSchema->schemaStartElement($parser, $name, $attrs);
4346 $this->appendDebug($this->currentSchema->getDebug());
4347 $this->currentSchema->clearDebug();
4349 // position in the total number of elements, starting from 0
4350 $pos = $this->position++;
4351 $depth = $this->depth++;
4352 // set self as current value for this depth
4353 $this->depth_array[$depth] = $pos;
4354 $this->message[$pos] = array('cdata' => '');
4355 // process attributes
4356 if (count($attrs) > 0) {
4357 // register namespace declarations
4358 foreach($attrs as $k => $v) {
4359 if (ereg("^xmlns", $k)) {
4360 if ($ns_prefix = substr(strrchr($k, ':'), 1)) {
4361 $this->namespaces[$ns_prefix] = $v;
4363 $this->namespaces['ns' . (count($this->namespaces) + 1)] = $v;
4365 if ($v == 'http://www.w3.org/2001/XMLSchema' || $v == 'http://www.w3.org/1999/XMLSchema' || $v == 'http://www.w3.org/2000/10/XMLSchema') {
4366 $this->XMLSchemaVersion = $v;
4367 $this->namespaces['xsi'] = $v . '-instance';
4371 // expand each attribute prefix to its namespace
4372 foreach($attrs as $k => $v) {
4373 $k = strpos($k, ':') ? $this->expandQname($k) : $k;
4374 if ($k != 'location' && $k != 'soapAction' && $k != 'namespace') {
4375 $v = strpos($v, ':') ? $this->expandQname($v) : $v;
4383 // get element prefix, namespace and name
4384 if (ereg(':', $name)) {
4386 $prefix = substr($name, 0, strpos($name, ':'));
4388 $namespace = isset($this->namespaces[$prefix]) ? $this->namespaces[$prefix] : '';
4389 // get unqualified name
4390 $name = substr(strstr($name, ':'), 1);
4392 // process attributes, expanding any prefixes to namespaces
4393 // find status, register data
4394 switch ($this->status) {
4396 if ($name == 'part') {
4397 if (isset($attrs['type'])) {
4398 $this->debug("msg " . $this->currentMessage . ": found part $attrs[name]: " . implode(',', $attrs));
4399 $this->messages[$this->currentMessage][$attrs['name']] = $attrs['type'];
4401 if (isset($attrs['element'])) {
4402 $this->debug("msg " . $this->currentMessage . ": found part $attrs[name]: " . implode(',', $attrs));
4403 $this->messages[$this->currentMessage][$attrs['name']] = $attrs['element'];
4410 $this->currentPortOperation = $attrs['name'];
4411 $this->debug("portType $this->currentPortType operation: $this->currentPortOperation");
4412 if (isset($attrs['parameterOrder'])) {
4413 $this->portTypes[$this->currentPortType][$attrs['name']]['parameterOrder'] = $attrs['parameterOrder'];
4416 case 'documentation':
4417 $this->documentation = true;
4419 // merge input/output data
4421 $m = isset($attrs['message']) ? $this->getLocalPart($attrs['message']) : '';
4422 $this->portTypes[$this->currentPortType][$this->currentPortOperation][$name]['message'] = $m;
4430 if (isset($attrs['style'])) {
4431 $this->bindings[$this->currentBinding]['prefix'] = $prefix;
4433 $this->bindings[$this->currentBinding] = array_merge($this->bindings[$this->currentBinding], $attrs);
4436 $this->bindings[$this->currentBinding]['operations'][$this->currentOperation][$this->opStatus]['headers'][] = $attrs;
4439 if (isset($attrs['soapAction'])) {
4440 $this->bindings[$this->currentBinding]['operations'][$this->currentOperation]['soapAction'] = $attrs['soapAction'];
4442 if (isset($attrs['style'])) {
4443 $this->bindings[$this->currentBinding]['operations'][$this->currentOperation]['style'] = $attrs['style'];
4445 if (isset($attrs['name'])) {
4446 $this->currentOperation = $attrs['name'];
4447 $this->debug("current binding operation: $this->currentOperation");
4448 $this->bindings[$this->currentBinding]['operations'][$this->currentOperation]['name'] = $attrs['name'];
4449 $this->bindings[$this->currentBinding]['operations'][$this->currentOperation]['binding'] = $this->currentBinding;
4450 $this->bindings[$this->currentBinding]['operations'][$this->currentOperation]['endpoint'] = isset($this->bindings[$this->currentBinding]['endpoint']) ? $this->bindings[$this->currentBinding]['endpoint'] : '';
4454 $this->opStatus = 'input';
4457 $this->opStatus = 'output';
4460 if (isset($this->bindings[$this->currentBinding]['operations'][$this->currentOperation][$this->opStatus])) {
4461 $this->bindings[$this->currentBinding]['operations'][$this->currentOperation][$this->opStatus] = array_merge($this->bindings[$this->currentBinding]['operations'][$this->currentOperation][$this->opStatus], $attrs);
4463 $this->bindings[$this->currentBinding]['operations'][$this->currentOperation][$this->opStatus] = $attrs;
4471 $this->currentPort = $attrs['name'];
4472 $this->debug('current port: ' . $this->currentPort);
4473 $this->ports[$this->currentPort]['binding'] = $this->getLocalPart($attrs['binding']);
4477 $this->ports[$this->currentPort]['location'] = $attrs['location'];
4478 $this->ports[$this->currentPort]['bindingType'] = $namespace;
4479 $this->bindings[ $this->ports[$this->currentPort]['binding'] ]['bindingType'] = $namespace;
4480 $this->bindings[ $this->ports[$this->currentPort]['binding'] ]['endpoint'] = $attrs['location'];
4488 if (isset($attrs['location'])) {
4489 $this->import[$attrs['namespace']][] = array('location' => $attrs['location'], 'loaded' => false);
4490 $this->debug('parsing import ' . $attrs['namespace']. ' - ' . $attrs['location'] . ' (' . count($this->import[$attrs['namespace']]).')');
4492 $this->import[$attrs['namespace']][] = array('location' => '', 'loaded' => true);
4493 if (! $this->getPrefixFromNamespace($attrs['namespace'])) {
4494 $this->namespaces['ns'.(count($this->namespaces)+1)] = $attrs['namespace'];
4496 $this->debug('parsing import ' . $attrs['namespace']. ' - [no location] (' . count($this->import[$attrs['namespace']]).')');
4501 // $this->status = 'schema';
4504 $this->status = 'message';
4505 $this->messages[$attrs['name']] = array();
4506 $this->currentMessage = $attrs['name'];
4509 $this->status = 'portType';
4510 $this->portTypes[$attrs['name']] = array();
4511 $this->currentPortType = $attrs['name'];
4514 if (isset($attrs['name'])) {
4516 if (strpos($attrs['name'], ':')) {
4517 $this->currentBinding = $this->getLocalPart($attrs['name']);
4519 $this->currentBinding = $attrs['name'];
4521 $this->status = 'binding';
4522 $this->bindings[$this->currentBinding]['portType'] = $this->getLocalPart($attrs['type']);
4523 $this->debug("current binding: $this->currentBinding of portType: " . $attrs['type']);
4527 $this->serviceName = $attrs['name'];
4528 $this->status = 'service';
4529 $this->debug('current service: ' . $this->serviceName);
4532 foreach ($attrs as $name => $value) {
4533 $this->wsdl_info[$name] = $value;
4541 * end-element handler
4543 * @param string $parser XML parser object
4544 * @param string $name element name
4547 function end_element($parser, $name){
4548 // unset schema status
4549 if (/*ereg('types$', $name) ||*/ ereg('schema$', $name)) {
4551 $this->appendDebug($this->currentSchema->getDebug());
4552 $this->currentSchema->clearDebug();
4553 $this->schemas[$this->currentSchema->schemaTargetNamespace][] = $this->currentSchema;
4554 $this->debug('Parsing WSDL schema done');
4556 if ($this->status == 'schema') {
4557 $this->currentSchema->schemaEndElement($parser, $name);
4559 // bring depth down a notch
4562 // end documentation
4563 if ($this->documentation) {
4564 //TODO: track the node to which documentation should be assigned; it can be a part, message, etc.
4565 //$this->portTypes[$this->currentPortType][$this->currentPortOperation]['documentation'] = $this->documentation;
4566 $this->documentation = false;
4571 * element content handler
4573 * @param string $parser XML parser object
4574 * @param string $data element content
4577 function character_data($parser, $data)
4579 $pos = isset($this->depth_array[$this->depth]) ? $this->depth_array[$this->depth] : 0;
4580 if (isset($this->message[$pos]['cdata'])) {
4581 $this->message[$pos]['cdata'] .= $data;
4583 if ($this->documentation) {
4584 $this->documentation .= $data;
4588 function getBindingData($binding)
4590 if (is_array($this->bindings[$binding])) {
4591 return $this->bindings[$binding];
4596 * returns an assoc array of operation names => operation data
4598 * @param string $bindingType eg: soap, smtp, dime (only soap is currently supported)
4602 function getOperations($bindingType = 'soap')
4605 if ($bindingType == 'soap') {
4606 $bindingType = 'http://schemas.xmlsoap.org/wsdl/soap/';
4609 foreach($this->ports as $port => $portData) {
4610 // binding type of port matches parameter
4611 if ($portData['bindingType'] == $bindingType) {
4612 //$this->debug("getOperations for port $port");
4613 //$this->debug("port data: " . $this->varDump($portData));
4614 //$this->debug("bindings: " . $this->varDump($this->bindings[ $portData['binding'] ]));
4616 if (isset($this->bindings[ $portData['binding'] ]['operations'])) {
4617 $ops = array_merge ($ops, $this->bindings[ $portData['binding'] ]['operations']);
4625 * returns an associative array of data necessary for calling an operation
4627 * @param string $operation , name of operation
4628 * @param string $bindingType , type of binding eg: soap
4632 function getOperationData($operation, $bindingType = 'soap')
4634 if ($bindingType == 'soap') {
4635 $bindingType = 'http://schemas.xmlsoap.org/wsdl/soap/';
4638 foreach($this->ports as $port => $portData) {
4639 // binding type of port matches parameter
4640 if ($portData['bindingType'] == $bindingType) {
4642 //foreach($this->bindings[ $portData['binding'] ]['operations'] as $bOperation => $opData) {
4643 foreach(array_keys($this->bindings[ $portData['binding'] ]['operations']) as $bOperation) {
4644 // note that we could/should also check the namespace here
4645 if ($operation == $bOperation) {
4646 $opData = $this->bindings[ $portData['binding'] ]['operations'][$operation];
4655 * returns an associative array of data necessary for calling an operation
4657 * @param string $soapAction soapAction for operation
4658 * @param string $bindingType type of binding eg: soap
4662 function getOperationDataForSoapAction($soapAction, $bindingType = 'soap') {
4663 if ($bindingType == 'soap') {
4664 $bindingType = 'http://schemas.xmlsoap.org/wsdl/soap/';
4667 foreach($this->ports as $port => $portData) {
4668 // binding type of port matches parameter
4669 if ($portData['bindingType'] == $bindingType) {
4670 // loop through operations for the binding
4671 foreach ($this->bindings[ $portData['binding'] ]['operations'] as $bOperation => $opData) {
4672 if ($opData['soapAction'] == $soapAction) {
4681 * returns an array of information about a given type
4682 * returns false if no type exists by the given name
4685 * 'elements' => array(), // refs to elements array
4686 * 'restrictionBase' => '',
4688 * 'order' => '(sequence|all)',
4689 * 'attrs' => array() // refs to attributes array
4692 * @param $type string the type
4693 * @param $ns string namespace (not prefix) of the type
4698 function getTypeDef($type, $ns) {
4699 $this->debug("in getTypeDef: type=$type, ns=$ns");
4700 if ((! $ns) && isset($this->namespaces['tns'])) {
4701 $ns = $this->namespaces['tns'];
4702 $this->debug("in getTypeDef: type namespace forced to $ns");
4704 if (isset($this->schemas[$ns])) {
4705 $this->debug("in getTypeDef: have schema for namespace $ns");
4706 for ($i = 0; $i < count($this->schemas[$ns]); $i++) {
4707 $xs = &$this->schemas[$ns][$i];
4708 $t = $xs->getTypeDef($type);
4709 $this->appendDebug($xs->getDebug());
4712 if (!isset($t['phpType'])) {
4713 // get info for type to tack onto the element
4714 $uqType = substr($t['type'], strrpos($t['type'], ':') + 1);
4715 $ns = substr($t['type'], 0, strrpos($t['type'], ':'));
4716 $etype = $this->getTypeDef($uqType, $ns);
4718 $this->debug("found type for [element] $type:");
4719 $this->debug($this->varDump($etype));
4720 if (isset($etype['phpType'])) {
4721 $t['phpType'] = $etype['phpType'];
4723 if (isset($etype['elements'])) {
4724 $t['elements'] = $etype['elements'];
4726 if (isset($etype['attrs'])) {
4727 $t['attrs'] = $etype['attrs'];
4735 $this->debug("in getTypeDef: do not have schema for namespace $ns");
4741 * prints html description of services
4745 function webDescription(){
4746 global $HTTP_SERVER_VARS;
4748 if (isset($_SERVER)) {
4749 $PHP_SELF = $_SERVER['PHP_SELF'];
4750 } elseif (isset($HTTP_SERVER_VARS)) {
4751 $PHP_SELF = $HTTP_SERVER_VARS['PHP_SELF'];
4753 $this->setError("Neither _SERVER nor HTTP_SERVER_VARS is available");
4757 <html><head><title>NuSOAP: '.$this->serviceName.'</title>
4758 <style type="text/css">
4759 body { font-family: arial; color: #000000; background-color: #ffffff; margin: 0px 0px 0px 0px; }
4760 p { font-family: arial; color: #000000; margin-top: 0px; margin-bottom: 12px; }
4761 pre { background-color: silver; padding: 5px; font-family: Courier New; font-size: x-small; color: #000000;}
4762 ul { margin-top: 10px; margin-left: 20px; }
4763 li { list-style-type: none; margin-top: 10px; color: #000000; }
4765 margin-left: 0px; padding-bottom: 2em; }
4767 padding-top: 10px; padding-bottom: 10px; padding-left: 15px; font-size: .70em;
4768 margin-top: 10px; margin-left: 0px; color: #000000;
4769 background-color: #ccccff; width: 20%; margin-left: 20px; margin-top: 20px; }
4771 font-family: arial; font-size: 26px; color: #ffffff;
4772 background-color: #999999; width: 105%; margin-left: 0px;
4773 padding-top: 10px; padding-bottom: 10px; padding-left: 15px;}
4775 position: absolute; visibility: hidden; z-index: 200; left: 250px; top: 100px;
4776 font-family: arial; overflow: hidden; width: 600;
4777 padding: 20px; font-size: 10px; background-color: #999999;
4778 layer-background-color:#FFFFFF; }
4779 a,a:active { color: charcoal; font-weight: bold; }
4780 a:visited { color: #666666; font-weight: bold; }
4781 a:hover { color: cc3300; font-weight: bold; }
4783 <script language="JavaScript" type="text/javascript">
4785 // POP-UP CAPTIONS...
4786 function lib_bwcheck(){ //Browsercheck (needed)
4787 this.ver=navigator.appVersion
4788 this.agent=navigator.userAgent
4789 this.dom=document.getElementById?1:0
4790 this.opera5=this.agent.indexOf("Opera 5")>-1
4791 this.ie5=(this.ver.indexOf("MSIE 5")>-1 && this.dom && !this.opera5)?1:0;
4792 this.ie6=(this.ver.indexOf("MSIE 6")>-1 && this.dom && !this.opera5)?1:0;
4793 this.ie4=(document.all && !this.dom && !this.opera5)?1:0;
4794 this.ie=this.ie4||this.ie5||this.ie6
4795 this.mac=this.agent.indexOf("Mac")>-1
4796 this.ns6=(this.dom && parseInt(this.ver) >= 5) ?1:0;
4797 this.ns4=(document.layers && !this.dom)?1:0;
4798 this.bw=(this.ie6 || this.ie5 || this.ie4 || this.ns4 || this.ns6 || this.opera5)
4801 var bw = new lib_bwcheck()
4802 //Makes crossbrowser object.
4803 function makeObj(obj){
4804 this.evnt=bw.dom? document.getElementById(obj):bw.ie4?document.all[obj]:bw.ns4?document.layers[obj]:0;
4805 if(!this.evnt) return false
4806 this.css=bw.dom||bw.ie4?this.evnt.style:bw.ns4?this.evnt:0;
4807 this.wref=bw.dom||bw.ie4?this.evnt:bw.ns4?this.css.document:0;
4808 this.writeIt=b_writeIt;
4811 // A unit of measure that will be added when setting the position of a layer.
4812 //var px = bw.ns4||window.opera?"":"px";
4813 function b_writeIt(text){
4814 if (bw.ns4){this.wref.write(text);this.wref.close()}
4815 else this.wref.innerHTML = text
4817 //Shows the messages
4819 function popup(divid){
4820 if(oDesc = new makeObj(divid)){
4821 oDesc.css.visibility = "visible"
4824 function popout(){ // Hides message
4825 if(oDesc) oDesc.css.visibility = "hidden"
4833 <div class=title>'.$this->serviceName.'</div>
4835 <p>View the <a href="'.$PHP_SELF.'?wsdl">WSDL</a> for the service.
4836 Click on an operation name to view it's details.</p>
4838 foreach($this->getOperations() as $op => $data){
4839 $b .= "<li><a href='#' onclick=\"popout();popup('$op')\">$op</a></li>";
4840 // create hidden div
4841 $b .= "<div id='$op' class='hidden'>
4842 <a href='#' onclick='popout()'><font color='#ffffff'>Close</font></a><br><br>";
4843 foreach($data as $donnie => $marie){ // loop through opdata
4844 if($donnie == 'input' || $donnie == 'output'){ // show input/output data
4845 $b .= "<font color='white'>".ucfirst($donnie).':</font><br>';
4846 foreach($marie as $captain => $tenille){ // loop through data
4847 if($captain == 'parts'){ // loop thru parts
4848 $b .= " $captain:<br>";
4849 //if(is_array($tenille)){
4850 foreach($tenille as $joanie => $chachi){
4851 $b .= " $joanie: $chachi<br>";
4855 $b .= " $captain: $tenille<br>";
4859 $b .= "<font color='white'>".ucfirst($donnie).":</font> $marie<br>";
4867 </div></body></html>';
4872 * serialize the parsed wsdl
4874 * @param mixed $debug whether to put debug=1 in endpoint URL
4875 * @return string serialization of WSDL
4878 function serialize($debug = 0)
4880 $xml = '<?xml version="1.0" encoding="ISO-8859-1"?>';
4881 $xml .= "\n<definitions";
4882 foreach($this->namespaces as $k => $v) {
4883 $xml .= " xmlns:$k=\"$v\"";
4885 // 10.9.02 - add poulter fix for wsdl and tns declarations
4886 if (isset($this->namespaces['wsdl'])) {
4887 $xml .= " xmlns=\"" . $this->namespaces['wsdl'] . "\"";
4889 if (isset($this->namespaces['tns'])) {
4890 $xml .= " targetNamespace=\"" . $this->namespaces['tns'] . "\"";
4894 if (sizeof($this->import) > 0) {
4895 foreach($this->import as $ns => $list) {
4896 foreach ($list as $ii) {
4897 if ($ii['location'] != '') {
4898 $xml .= '<import location="' . $ii['location'] . '" namespace="' . $ns . '" />';
4900 $xml .= '<import namespace="' . $ns . '" />';
4906 if (count($this->schemas)>=1) {
4907 $xml .= "\n<types>\n";
4908 foreach ($this->schemas as $ns => $list) {
4909 foreach ($list as $xs) {
4910 $xml .= $xs->serializeSchema();
4916 if (count($this->messages) >= 1) {
4917 foreach($this->messages as $msgName => $msgParts) {
4918 $xml .= "\n<message name=\"" . $msgName . '">';
4919 if(is_array($msgParts)){
4920 foreach($msgParts as $partName => $partType) {
4921 // print 'serializing '.$partType.', sv: '.$this->XMLSchemaVersion.'<br>';
4922 if (strpos($partType, ':')) {
4923 $typePrefix = $this->getPrefixFromNamespace($this->getPrefix($partType));
4924 } elseif (isset($this->typemap[$this->namespaces['xsd']][$partType])) {
4925 // print 'checking typemap: '.$this->XMLSchemaVersion.'<br>';
4926 $typePrefix = 'xsd';
4928 foreach($this->typemap as $ns => $types) {
4929 if (isset($types[$partType])) {
4930 $typePrefix = $this->getPrefixFromNamespace($ns);
4933 if (!isset($typePrefix)) {
4934 die("$partType has no namespace!");
4937 $ns = $this->getNamespaceFromPrefix($typePrefix);
4938 $typeDef = $this->getTypeDef($this->getLocalPart($partType), $ns);
4939 if ($typeDef['typeClass'] == 'element') {
4940 $elementortype = 'element';
4942 $elementortype = 'type';
4944 $xml .= "\n" . ' <part name="' . $partName . '" ' . $elementortype . '="' . $typePrefix . ':' . $this->getLocalPart($partType) . '" />';
4947 $xml .= '</message>';
4950 // bindings & porttypes
4951 if (count($this->bindings) >= 1) {
4954 foreach($this->bindings as $bindingName => $attrs) {
4955 $binding_xml .= "\n<binding name=\"" . $bindingName . '" type="tns:' . $attrs['portType'] . '">';
4956 $binding_xml .= "\n" . ' <soap:binding style="' . $attrs['style'] . '" transport="' . $attrs['transport'] . '"/>';
4957 $portType_xml .= "\n<portType name=\"" . $attrs['portType'] . '">';
4958 foreach($attrs['operations'] as $opName => $opParts) {
4959 $binding_xml .= "\n" . ' <operation name="' . $opName . '">';
4960 $binding_xml .= "\n" . ' <soap:operation soapAction="' . $opParts['soapAction'] . '" style="'. $opParts['style'] . '"/>';
4961 if (isset($opParts['input']['encodingStyle']) && $opParts['input']['encodingStyle'] != '') {
4962 $enc_style = ' encodingStyle="' . $opParts['input']['encodingStyle'] . '"';
4966 $binding_xml .= "\n" . ' <input><soap:body use="' . $opParts['input']['use'] . '" namespace="' . $opParts['input']['namespace'] . '"' . $enc_style . '/></input>';
4967 if (isset($opParts['output']['encodingStyle']) && $opParts['output']['encodingStyle'] != '') {
4968 $enc_style = ' encodingStyle="' . $opParts['output']['encodingStyle'] . '"';
4972 $binding_xml .= "\n" . ' <output><soap:body use="' . $opParts['output']['use'] . '" namespace="' . $opParts['output']['namespace'] . '"' . $enc_style . '/></output>';
4973 $binding_xml .= "\n" . ' </operation>';
4974 $portType_xml .= "\n" . ' <operation name="' . $opParts['name'] . '"';
4975 if (isset($opParts['parameterOrder'])) {
4976 $portType_xml .= ' parameterOrder="' . $opParts['parameterOrder'] . '"';
4978 $portType_xml .= '>';
4979 if(isset($opParts['documentation']) && $opParts['documentation'] != '') {
4980 $portType_xml .= "\n" . ' <documentation>' . htmlspecialchars($opParts['documentation']) . '</documentation>';
4982 $portType_xml .= "\n" . ' <input message="tns:' . $opParts['input']['message'] . '"/>';
4983 $portType_xml .= "\n" . ' <output message="tns:' . $opParts['output']['message'] . '"/>';
4984 $portType_xml .= "\n" . ' </operation>';
4986 $portType_xml .= "\n" . '</portType>';
4987 $binding_xml .= "\n" . '</binding>';
4989 $xml .= $portType_xml . $binding_xml;
4992 $xml .= "\n<service name=\"" . $this->serviceName . '">';
4993 if (count($this->ports) >= 1) {
4994 foreach($this->ports as $pName => $attrs) {
4995 $xml .= "\n" . ' <port name="' . $pName . '" binding="tns:' . $attrs['binding'] . '">';
4996 $xml .= "\n" . ' <soap:address location="' . $attrs['location'] . ($debug ? '?debug=1' : '') . '"/>';
4997 $xml .= "\n" . ' </port>';
5000 $xml .= "\n" . '</service>';
5001 return $xml . "\n</definitions>";
5005 * serialize PHP values according to a WSDL message definition
5008 * - multi-ref serialization
5009 * - validate PHP values against type definitions, return errors if invalid
5011 * @param string $operation operation name
5012 * @param string $direction (input|output)
5013 * @param mixed $parameters parameter value(s)
5014 * @return mixed parameters serialized as XML or false on error (e.g. operation not found)
5017 function serializeRPCParameters($operation, $direction, $parameters)
5019 $this->debug("in serializeRPCParameters: operation=$operation, direction=$direction, XMLSchemaVersion=$this->XMLSchemaVersion");
5020 $this->appendDebug('parameters=' . $this->varDump($parameters));
5022 if ($direction != 'input' && $direction != 'output') {
5023 $this->debug('The value of the \$direction argument needs to be either "input" or "output"');
5024 $this->setError('The value of the \$direction argument needs to be either "input" or "output"');
5027 if (!$opData = $this->getOperationData($operation)) {
5028 $this->debug('Unable to retrieve WSDL data for operation: ' . $operation);
5029 $this->setError('Unable to retrieve WSDL data for operation: ' . $operation);
5032 $this->debug('opData:');
5033 $this->appendDebug($this->varDump($opData));
5035 // Get encoding style for output and set to current
5036 $encodingStyle = 'http://schemas.xmlsoap.org/soap/encoding/';
5037 if(($direction == 'input') && isset($opData['output']['encodingStyle']) && ($opData['output']['encodingStyle'] != $encodingStyle)) {
5038 $encodingStyle = $opData['output']['encodingStyle'];
5039 $enc_style = $encodingStyle;
5044 if (isset($opData[$direction]['parts']) && sizeof($opData[$direction]['parts']) > 0) {
5046 $use = $opData[$direction]['use'];
5047 $this->debug('have ' . count($opData[$direction]['parts']) . ' part(s) to serialize');
5048 if (is_array($parameters)) {
5049 $parametersArrayType = $this->isArraySimpleOrStruct($parameters);
5050 $this->debug('have ' . count($parameters) . ' parameter(s) provided as ' . $parametersArrayType . ' to serialize');
5051 foreach($opData[$direction]['parts'] as $name => $type) {
5052 $this->debug('serializing part "'.$name.'" of type "'.$type.'"');
5053 // Track encoding style
5054 if (isset($opData[$direction]['encodingStyle']) && $encodingStyle != $opData[$direction]['encodingStyle']) {
5055 $encodingStyle = $opData[$direction]['encodingStyle'];
5056 $enc_style = $encodingStyle;
5060 // NOTE: add error handling here
5061 // if serializeType returns false, then catch global error and fault
5062 if ($parametersArrayType == 'arraySimple') {
5063 $p = array_shift($parameters);
5064 $this->debug('calling serializeType w/indexed param');
5065 $xml .= $this->serializeType($name, $type, $p, $use, $enc_style);
5066 } elseif (isset($parameters[$name])) {
5067 $this->debug('calling serializeType w/named param');
5068 $xml .= $this->serializeType($name, $type, $parameters[$name], $use, $enc_style);
5070 // TODO: only send nillable
5071 $this->debug('calling serializeType w/null param');
5072 $xml .= $this->serializeType($name, $type, null, $use, $enc_style);
5076 $this->debug('no parameters passed.');
5079 $this->debug("serializeRPCParameters returning: $xml");
5084 * serialize a PHP value according to a WSDL message definition
5087 * - multi-ref serialization
5088 * - validate PHP values against type definitions, return errors if invalid
5090 * @param string $ type name
5091 * @param mixed $ param value
5092 * @return mixed new param or false if initial value didn't validate
5096 function serializeParameters($operation, $direction, $parameters)
5098 $this->debug("in serializeParameters: operation=$operation, direction=$direction, XMLSchemaVersion=$this->XMLSchemaVersion");
5099 $this->appendDebug('parameters=' . $this->varDump($parameters));
5101 if ($direction != 'input' && $direction != 'output') {
5102 $this->debug('The value of the \$direction argument needs to be either "input" or "output"');
5103 $this->setError('The value of the \$direction argument needs to be either "input" or "output"');
5106 if (!$opData = $this->getOperationData($operation)) {
5107 $this->debug('Unable to retrieve WSDL data for operation: ' . $operation);
5108 $this->setError('Unable to retrieve WSDL data for operation: ' . $operation);
5111 $this->debug('opData:');
5112 $this->appendDebug($this->varDump($opData));
5114 // Get encoding style for output and set to current
5115 $encodingStyle = 'http://schemas.xmlsoap.org/soap/encoding/';
5116 if(($direction == 'input') && isset($opData['output']['encodingStyle']) && ($opData['output']['encodingStyle'] != $encodingStyle)) {
5117 $encodingStyle = $opData['output']['encodingStyle'];
5118 $enc_style = $encodingStyle;
5123 if (isset($opData[$direction]['parts']) && sizeof($opData[$direction]['parts']) > 0) {
5125 $use = $opData[$direction]['use'];
5126 $this->debug("use=$use");
5127 $this->debug('got ' . count($opData[$direction]['parts']) . ' part(s)');
5128 if (is_array($parameters)) {
5129 $parametersArrayType = $this->isArraySimpleOrStruct($parameters);
5130 $this->debug('have ' . $parametersArrayType . ' parameters');
5131 foreach($opData[$direction]['parts'] as $name => $type) {
5132 $this->debug('serializing part "'.$name.'" of type "'.$type.'"');
5133 // Track encoding style
5134 if(isset($opData[$direction]['encodingStyle']) && $encodingStyle != $opData[$direction]['encodingStyle']) {
5135 $encodingStyle = $opData[$direction]['encodingStyle'];
5136 $enc_style = $encodingStyle;
5140 // NOTE: add error handling here
5141 // if serializeType returns false, then catch global error and fault
5142 if ($parametersArrayType == 'arraySimple') {
5143 $p = array_shift($parameters);
5144 $this->debug('calling serializeType w/indexed param');
5145 $xml .= $this->serializeType($name, $type, $p, $use, $enc_style);
5146 } elseif (isset($parameters[$name])) {
5147 $this->debug('calling serializeType w/named param');
5148 $xml .= $this->serializeType($name, $type, $parameters[$name], $use, $enc_style);
5150 // TODO: only send nillable
5151 $this->debug('calling serializeType w/null param');
5152 $xml .= $this->serializeType($name, $type, null, $use, $enc_style);
5156 $this->debug('no parameters passed.');
5159 $this->debug("serializeParameters returning: $xml");
5164 * serializes a PHP value according a given type definition
5166 * @param string $name name of value (part or element)
5167 * @param string $type XML schema type of value (type or element)
5168 * @param mixed $value a native PHP value (parameter value)
5169 * @param string $use use for part (encoded|literal)
5170 * @param string $encodingStyle SOAP encoding style for the value (if different than the enclosing style)
5171 * @param boolean $unqualified a kludge for what should be XML namespace form handling
5172 * @return string value serialized as an XML string
5175 function serializeType($name, $type, $value, $use='encoded', $encodingStyle=false, $unqualified=false)
5177 $this->debug("in serializeType: name=$name, type=$type, use=$use, encodingStyle=$encodingStyle, unqualified=" . ($unqualified ? "unqualified" : "qualified"));
5178 $this->appendDebug("value=" . $this->varDump($value));
5179 if($use == 'encoded' && $encodingStyle) {
5180 $encodingStyle = ' SOAP-ENV:encodingStyle="' . $encodingStyle . '"';
5183 // if a soapval has been supplied, let its type override the WSDL
5184 if (is_object($value) && get_class($value) == 'soapval') {
5185 if ($value->type_ns) {
5186 $type = $value->type_ns . ':' . $value->type;
5188 $this->debug("in serializeType: soapval overrides type to $type");
5189 } elseif ($value->type) {
5190 $type = $value->type;
5192 $this->debug("in serializeType: soapval overrides type to $type");
5195 $this->debug("in serializeType: soapval does not override type");
5197 $attrs = $value->attributes;
5198 $value = $value->value;
5199 $this->debug("in serializeType: soapval overrides value to $value");
5201 if (!is_array($value)) {
5202 $value['!'] = $value;
5204 foreach ($attrs as $n => $v) {
5205 $value['!' . $n] = $v;
5207 $this->debug("in serializeType: soapval provides attributes");
5214 if (strpos($type, ':')) {
5215 $uqType = substr($type, strrpos($type, ':') + 1);
5216 $ns = substr($type, 0, strrpos($type, ':'));
5217 $this->debug("in serializeType: got a prefixed type: $uqType, $ns");
5218 if ($this->getNamespaceFromPrefix($ns)) {
5219 $ns = $this->getNamespaceFromPrefix($ns);
5220 $this->debug("in serializeType: expanded prefixed type: $uqType, $ns");
5223 if($ns == $this->XMLSchemaVersion || $ns == 'http://schemas.xmlsoap.org/soap/encoding/'){
5224 $this->debug('in serializeType: type namespace indicates XML Schema or SOAP Encoding type');
5225 if ($unqualified && $use == 'literal') {
5226 $elementNS = " xmlns=\"\"";
5230 if (is_null($value)) {
5231 if ($use == 'literal') {
5232 // TODO: depends on minOccurs
5233 $xml = "<$name$elementNS/>";
5235 // TODO: depends on nillable, which should be checked before calling this method
5236 $xml = "<$name$elementNS xsi:nil=\"true\" xsi:type=\"" . $this->getPrefixFromNamespace($ns) . ":$uqType\"/>";
5238 $this->debug("in serializeType: returning: $xml");
5241 if ($uqType == 'Array') {
5242 // JBoss/Axis does this sometimes
5243 return $this->serialize_val($value, $name, false, false, false, false, $use);
5245 if ($uqType == 'boolean') {
5246 if ((is_string($value) && $value == 'false') || (! $value)) {
5252 if ($uqType == 'string' && gettype($value) == 'string') {
5253 $value = $this->expandEntities($value);
5255 if (($uqType == 'long' || $uqType == 'unsignedLong') && gettype($value) == 'double') {
5256 $value = sprintf("%.0lf", $value);
5259 // TODO: what about null/nil values?
5260 // check type isn't a custom type extending xmlschema namespace
5261 if (!$this->getTypeDef($uqType, $ns)) {
5262 if ($use == 'literal') {
5264 $xml = "<$name$elementNS xsi:type=\"" . $this->getPrefixFromNamespace($ns) . ":$uqType\">$value</$name>";
5266 $xml = "<$name$elementNS>$value</$name>";
5269 $xml = "<$name$elementNS xsi:type=\"" . $this->getPrefixFromNamespace($ns) . ":$uqType\"$encodingStyle>$value</$name>";
5271 $this->debug("in serializeType: returning: $xml");
5274 $this->debug('custom type extends XML Schema or SOAP Encoding namespace (yuck)');
5275 } else if ($ns == 'http://xml.apache.org/xml-soap') {
5276 $this->debug('in serializeType: appears to be Apache SOAP type');
5277 if ($uqType == 'Map') {
5278 $tt_prefix = $this->getPrefixFromNamespace('http://xml.apache.org/xml-soap');
5280 $this->debug('in serializeType: Add namespace for Apache SOAP type');
5281 $tt_prefix = 'ns' . rand(1000, 9999);
5282 $this->namespaces[$tt_prefix] = 'http://xml.apache.org/xml-soap';
5283 // force this to be added to usedNamespaces
5284 $tt_prefix = $this->getPrefixFromNamespace('http://xml.apache.org/xml-soap');
5287 foreach($value as $k => $v) {
5288 $this->debug("serializing map element: key $k, value $v");
5289 $contents .= '<item>';
5290 $contents .= $this->serialize_val($k,'key',false,false,false,false,$use);
5291 $contents .= $this->serialize_val($v,'value',false,false,false,false,$use);
5292 $contents .= '</item>';
5294 if ($use == 'literal') {
5296 $xml = "<$name xsi:type=\"" . $tt_prefix . ":$uqType\">$contents</$name>";
5298 $xml = "<$name>$contents</$name>";
5301 $xml = "<$name xsi:type=\"" . $tt_prefix . ":$uqType\"$encodingStyle>$contents</$name>";
5303 $this->debug("in serializeType: returning: $xml");
5306 $this->debug('in serializeType: Apache SOAP type, but only support Map');
5309 // TODO: should the type be compared to types in XSD, and the namespace
5310 // set to XSD if the type matches?
5311 $this->debug("in serializeType: No namespace for type $type");
5315 if(!$typeDef = $this->getTypeDef($uqType, $ns)){
5316 $this->setError("$type ($uqType) is not a supported type.");
5317 $this->debug("in serializeType: $type ($uqType) is not a supported type.");
5320 $this->debug("in serializeType: found typeDef");
5321 $this->appendDebug('typeDef=' . $this->varDump($typeDef));
5323 $phpType = $typeDef['phpType'];
5324 $this->debug("in serializeType: uqType: $uqType, ns: $ns, phptype: $phpType, arrayType: " . (isset($typeDef['arrayType']) ? $typeDef['arrayType'] : '') );
5325 // if php type == struct, map value to the <all> element names
5326 if ($phpType == 'struct') {
5327 if (isset($typeDef['typeClass']) && $typeDef['typeClass'] == 'element') {
5328 $elementName = $uqType;
5329 if (isset($typeDef['form']) && ($typeDef['form'] == 'qualified')) {
5330 $elementNS = " xmlns=\"$ns\"";
5332 $elementNS = " xmlns=\"\"";
5335 $elementName = $name;
5337 $elementNS = " xmlns=\"\"";
5342 if (is_null($value)) {
5343 if ($use == 'literal') {
5344 // TODO: depends on minOccurs
5345 $xml = "<$elementName$elementNS/>";
5347 $xml = "<$elementName$elementNS xsi:nil=\"true\" xsi:type=\"" . $this->getPrefixFromNamespace($ns) . ":$uqType\"/>";
5349 $this->debug("in serializeType: returning: $xml");
5352 if (is_object($value)) {
5353 $value = get_object_vars($value);
5355 if (is_array($value)) {
5356 $elementAttrs = $this->serializeComplexTypeAttributes($typeDef, $value, $ns, $uqType);
5357 if ($use == 'literal') {
5359 $xml = "<$elementName$elementNS$elementAttrs xsi:type=\"" . $this->getPrefixFromNamespace($ns) . ":$uqType\">";
5361 $xml = "<$elementName$elementNS$elementAttrs>";
5364 $xml = "<$elementName$elementNS$elementAttrs xsi:type=\"" . $this->getPrefixFromNamespace($ns) . ":$uqType\"$encodingStyle>";
5367 $xml .= $this->serializeComplexTypeElements($typeDef, $value, $ns, $uqType, $use, $encodingStyle);
5368 $xml .= "</$elementName>";
5370 $this->debug("in serializeType: phpType is struct, but value is not an array");
5371 $this->setError("phpType is struct, but value is not an array: see debug output for details");
5374 } elseif ($phpType == 'array') {
5375 if (isset($typeDef['form']) && ($typeDef['form'] == 'qualified')) {
5376 $elementNS = " xmlns=\"$ns\"";
5379 $elementNS = " xmlns=\"\"";
5384 if (is_null($value)) {
5385 if ($use == 'literal') {
5386 // TODO: depends on minOccurs
5387 $xml = "<$name$elementNS/>";
5389 $xml = "<$name$elementNS xsi:nil=\"true\" xsi:type=\"" .
5390 $this->getPrefixFromNamespace('http://schemas.xmlsoap.org/soap/encoding/') .
5392 $this->getPrefixFromNamespace('http://schemas.xmlsoap.org/soap/encoding/') .
5394 $this->getPrefixFromNamespace($this->getPrefix($typeDef['arrayType'])) .
5396 $this->getLocalPart($typeDef['arrayType'])."[0]\"/>";
5398 $this->debug("in serializeType: returning: $xml");
5401 if (isset($typeDef['multidimensional'])) {
5403 foreach($value as $v) {
5404 $cols = ',' . sizeof($v);
5405 $nv = array_merge($nv, $v);
5411 if (is_array($value) && sizeof($value) >= 1) {
5412 $rows = sizeof($value);
5414 foreach($value as $k => $v) {
5415 $this->debug("serializing array element: $k, $v of type: $typeDef[arrayType]");
5416 //if (strpos($typeDef['arrayType'], ':') ) {
5417 if (!in_array($typeDef['arrayType'],$this->typemap['http://www.w3.org/2001/XMLSchema'])) {
5418 $contents .= $this->serializeType('item', $typeDef['arrayType'], $v, $use);
5420 $contents .= $this->serialize_val($v, 'item', $typeDef['arrayType'], null, $this->XMLSchemaVersion, false, $use);
5427 // TODO: for now, an empty value will be serialized as a zero element
5428 // array. Revisit this when coding the handling of null/nil values.
5429 if ($use == 'literal') {
5430 $xml = "<$name$elementNS>"
5434 $xml = "<$name$elementNS xsi:type=\"".$this->getPrefixFromNamespace('http://schemas.xmlsoap.org/soap/encoding/').':Array" '.
5435 $this->getPrefixFromNamespace('http://schemas.xmlsoap.org/soap/encoding/')
5437 .$this->getPrefixFromNamespace($this->getPrefix($typeDef['arrayType']))
5438 .":".$this->getLocalPart($typeDef['arrayType'])."[$rows$cols]\">"
5442 } elseif ($phpType == 'scalar') {
5443 if (isset($typeDef['form']) && ($typeDef['form'] == 'qualified')) {
5444 $elementNS = " xmlns=\"$ns\"";
5447 $elementNS = " xmlns=\"\"";
5452 if ($use == 'literal') {
5454 $xml = "<$name$elementNS xsi:type=\"" . $this->getPrefixFromNamespace($ns) . ":$uqType\">$value</$name>";
5456 $xml = "<$name$elementNS>$value</$name>";
5459 $xml = "<$name$elementNS xsi:type=\"" . $this->getPrefixFromNamespace($ns) . ":$uqType\"$encodingStyle>$value</$name>";
5462 $this->debug("in serializeType: returning: $xml");
5467 * serializes the attributes for a complexType
5469 * @param array $typeDef our internal representation of an XML schema type (or element)
5470 * @param mixed $value a native PHP value (parameter value)
5471 * @param string $ns the namespace of the type
5472 * @param string $uqType the local part of the type
5473 * @return string value serialized as an XML string
5476 function serializeComplexTypeAttributes($typeDef, $value, $ns, $uqType) {
5478 if (isset($typeDef['attrs']) && is_array($typeDef['attrs'])) {
5479 $this->debug("serialize attributes for XML Schema type $ns:$uqType");
5480 if (is_array($value)) {
5482 } elseif (is_object($value)) {
5483 $xvalue = get_object_vars($value);
5485 $this->debug("value is neither an array nor an object for XML Schema type $ns:$uqType");
5488 foreach ($typeDef['attrs'] as $aName => $attrs) {
5489 if (isset($xvalue['!' . $aName])) {
5490 $xname = '!' . $aName;
5491 $this->debug("value provided for attribute $aName with key $xname");
5492 } elseif (isset($xvalue[$aName])) {
5494 $this->debug("value provided for attribute $aName with key $xname");
5495 } elseif (isset($attrs['default'])) {
5496 $xname = '!' . $aName;
5497 $xvalue[$xname] = $attrs['default'];
5498 $this->debug('use default value of ' . $xvalue[$aName] . ' for attribute ' . $aName);
5501 $this->debug("no value provided for attribute $aName");
5504 $xml .= " $aName=\"" . $this->expandEntities($xvalue[$xname]) . "\"";
5508 $this->debug("no attributes to serialize for XML Schema type $ns:$uqType");
5510 if (isset($typeDef['extensionBase'])) {
5511 $ns = $this->getPrefix($typeDef['extensionBase']);
5512 $uqType = $this->getLocalPart($typeDef['extensionBase']);
5513 if ($this->getNamespaceFromPrefix($ns)) {
5514 $ns = $this->getNamespaceFromPrefix($ns);
5516 if ($typeDef = $this->getTypeDef($uqType, $ns)) {
5517 $this->debug("serialize attributes for extension base $ns:$uqType");
5518 $xml .= $this->serializeComplexTypeAttributes($typeDef, $value, $ns, $uqType);
5520 $this->debug("extension base $ns:$uqType is not a supported type");
5527 * serializes the elements for a complexType
5529 * @param array $typeDef our internal representation of an XML schema type (or element)
5530 * @param mixed $value a native PHP value (parameter value)
5531 * @param string $ns the namespace of the type
5532 * @param string $uqType the local part of the type
5533 * @param string $use use for part (encoded|literal)
5534 * @param string $encodingStyle SOAP encoding style for the value (if different than the enclosing style)
5535 * @return string value serialized as an XML string
5538 function serializeComplexTypeElements($typeDef, $value, $ns, $uqType, $use='encoded', $encodingStyle=false) {
5540 if (isset($typeDef['elements']) && is_array($typeDef['elements'])) {
5541 $this->debug("in serializeComplexTypeElements, serialize elements for XML Schema type $ns:$uqType");
5542 if (is_array($value)) {
5544 } elseif (is_object($value)) {
5545 $xvalue = get_object_vars($value);
5547 $this->debug("value is neither an array nor an object for XML Schema type $ns:$uqType");
5550 // toggle whether all elements are present - ideally should validate against schema
5551 if (count($typeDef['elements']) != count($xvalue)){
5554 foreach ($typeDef['elements'] as $eName => $attrs) {
5555 if (!isset($xvalue[$eName])) {
5556 if (isset($attrs['default'])) {
5557 $xvalue[$eName] = $attrs['default'];
5558 $this->debug('use default value of ' . $xvalue[$eName] . ' for element ' . $eName);
5561 // if user took advantage of a minOccurs=0, then only serialize named parameters
5562 if (isset($optionals)
5563 && (!isset($xvalue[$eName]))
5564 && ( (!isset($attrs['nillable'])) || $attrs['nillable'] != 'true')
5566 if (isset($attrs['minOccurs']) && $attrs['minOccurs'] <> '0') {
5567 $this->debug("apparent error: no value provided for element $eName with minOccurs=" . $attrs['minOccurs']);
5570 $this->debug("no value provided for complexType element $eName and element is not nillable, so serialize nothing");
5573 if (isset($xvalue[$eName])) {
5574 $v = $xvalue[$eName];
5578 if (isset($attrs['form'])) {
5579 $unqualified = ($attrs['form'] == 'unqualified');
5581 $unqualified = false;
5583 if (isset($attrs['maxOccurs']) && ($attrs['maxOccurs'] == 'unbounded' || $attrs['maxOccurs'] > 1) && isset($v) && is_array($v) && $this->isArraySimpleOrStruct($v) == 'arraySimple') {
5585 foreach ($vv as $k => $v) {
5586 if (isset($attrs['type']) || isset($attrs['ref'])) {
5587 // serialize schema-defined type
5588 $xml .= $this->serializeType($eName, isset($attrs['type']) ? $attrs['type'] : $attrs['ref'], $v, $use, $encodingStyle, $unqualified);
5590 // serialize generic type (can this ever really happen?)
5591 $this->debug("calling serialize_val() for $v, $eName, false, false, false, false, $use");
5592 $xml .= $this->serialize_val($v, $eName, false, false, false, false, $use);
5596 if (isset($attrs['type']) || isset($attrs['ref'])) {
5597 // serialize schema-defined type
5598 $xml .= $this->serializeType($eName, isset($attrs['type']) ? $attrs['type'] : $attrs['ref'], $v, $use, $encodingStyle, $unqualified);
5600 // serialize generic type (can this ever really happen?)
5601 $this->debug("calling serialize_val() for $v, $eName, false, false, false, false, $use");
5602 $xml .= $this->serialize_val($v, $eName, false, false, false, false, $use);
5608 $this->debug("no elements to serialize for XML Schema type $ns:$uqType");
5610 if (isset($typeDef['extensionBase'])) {
5611 $ns = $this->getPrefix($typeDef['extensionBase']);
5612 $uqType = $this->getLocalPart($typeDef['extensionBase']);
5613 if ($this->getNamespaceFromPrefix($ns)) {
5614 $ns = $this->getNamespaceFromPrefix($ns);
5616 if ($typeDef = $this->getTypeDef($uqType, $ns)) {
5617 $this->debug("serialize elements for extension base $ns:$uqType");
5618 $xml .= $this->serializeComplexTypeElements($typeDef, $value, $ns, $uqType, $use, $encodingStyle);
5620 $this->debug("extension base $ns:$uqType is not a supported type");
5627 * adds an XML Schema complex type to the WSDL types
5629 * @param string name
5630 * @param string typeClass (complexType|simpleType|attribute)
5631 * @param string phpType: currently supported are array and struct (php assoc array)
5632 * @param string compositor (all|sequence|choice)
5633 * @param string restrictionBase namespace:name (http://schemas.xmlsoap.org/soap/encoding/:Array)
5634 * @param array elements = array ( name => array(name=>'',type=>'') )
5635 * @param array attrs = array(array('ref'=>'SOAP-ENC:arrayType','wsdl:arrayType'=>'xsd:string[]'))
5636 * @param string arrayType: namespace:name (xsd:string)
5640 function addComplexType($name,$typeClass='complexType',$phpType='array',$compositor='',$restrictionBase='',$elements=array(),$attrs=array(),$arrayType='') {
5641 if (count($elements) > 0) {
5642 foreach($elements as $n => $e){
5643 // expand each element
5644 foreach ($e as $k => $v) {
5645 $k = strpos($k,':') ? $this->expandQname($k) : $k;
5646 $v = strpos($v,':') ? $this->expandQname($v) : $v;
5649 $eElements[$n] = $ee;
5651 $elements = $eElements;
5654 if (count($attrs) > 0) {
5655 foreach($attrs as $n => $a){
5656 // expand each attribute
5657 foreach ($a as $k => $v) {
5658 $k = strpos($k,':') ? $this->expandQname($k) : $k;
5659 $v = strpos($v,':') ? $this->expandQname($v) : $v;
5667 $restrictionBase = strpos($restrictionBase,':') ? $this->expandQname($restrictionBase) : $restrictionBase;
5668 $arrayType = strpos($arrayType,':') ? $this->expandQname($arrayType) : $arrayType;
5670 $typens = isset($this->namespaces['types']) ? $this->namespaces['types'] : $this->namespaces['tns'];
5671 $this->schemas[$typens][0]->addComplexType($name,$typeClass,$phpType,$compositor,$restrictionBase,$elements,$attrs,$arrayType);
5675 * adds an XML Schema simple type to the WSDL types
5677 * @param string $name
5678 * @param string $restrictionBase namespace:name (http://schemas.xmlsoap.org/soap/encoding/:Array)
5679 * @param string $typeClass (should always be simpleType)
5680 * @param string $phpType (should always be scalar)
5681 * @param array $enumeration array of values
5685 function addSimpleType($name, $restrictionBase='', $typeClass='simpleType', $phpType='scalar', $enumeration=array()) {
5686 $restrictionBase = strpos($restrictionBase,':') ? $this->expandQname($restrictionBase) : $restrictionBase;
5688 $typens = isset($this->namespaces['types']) ? $this->namespaces['types'] : $this->namespaces['tns'];
5689 $this->schemas[$typens][0]->addSimpleType($name, $restrictionBase, $typeClass, $phpType, $enumeration);
5693 * adds an element to the WSDL types
5695 * @param array $attrs attributes that must include name and type
5699 function addElement($attrs) {
5700 $typens = isset($this->namespaces['types']) ? $this->namespaces['types'] : $this->namespaces['tns'];
5701 $this->schemas[$typens][0]->addElement($attrs);
5705 * register an operation with the server
5707 * @param string $name operation (method) name
5708 * @param array $in assoc array of input values: key = param name, value = param type
5709 * @param array $out assoc array of output values: key = param name, value = param type
5710 * @param string $namespace optional The namespace for the operation
5711 * @param string $soapaction optional The soapaction for the operation
5712 * @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
5713 * @param string $use (encoded|literal) optional The use for the parameters (cannot mix right now)
5714 * @param string $documentation optional The description to include in the WSDL
5715 * @param string $encodingStyle optional (usually 'http://schemas.xmlsoap.org/soap/encoding/' for encoded)
5718 function addOperation($name, $in = false, $out = false, $namespace = false, $soapaction = false, $style = 'rpc', $use = 'encoded', $documentation = '', $encodingStyle = ''){
5719 if ($use == 'encoded' && $encodingStyle == '') {
5720 $encodingStyle = 'http://schemas.xmlsoap.org/soap/encoding/';
5723 if ($style == 'document') {
5724 $elements = array();
5725 foreach ($in as $n => $t) {
5726 $elements[$n] = array('name' => $n, 'type' => $t);
5728 $this->addComplexType($name . 'RequestType', 'complexType', 'struct', 'all', '', $elements);
5729 $this->addElement(array('name' => $name, 'type' => $name . 'RequestType'));
5730 $in = array('parameters' => 'tns:' . $name);
5732 $elements = array();
5733 foreach ($out as $n => $t) {
5734 $elements[$n] = array('name' => $n, 'type' => $t);
5736 $this->addComplexType($name . 'ResponseType', 'complexType', 'struct', 'all', '', $elements);
5737 $this->addElement(array('name' => $name . 'Response', 'type' => $name . 'ResponseType'));
5738 $out = array('parameters' => 'tns:' . $name . 'Response');
5742 $this->bindings[ $this->serviceName . 'Binding' ]['operations'][$name] =
5745 'binding' => $this->serviceName . 'Binding',
5746 'endpoint' => $this->endpoint,
5747 'soapAction' => $soapaction,
5751 'namespace' => $namespace,
5752 'encodingStyle' => $encodingStyle,
5753 'message' => $name . 'Request',
5757 'namespace' => $namespace,
5758 'encodingStyle' => $encodingStyle,
5759 'message' => $name . 'Response',
5761 'namespace' => $namespace,
5762 'transport' => 'http://schemas.xmlsoap.org/soap/http',
5763 'documentation' => $documentation);
5768 foreach($in as $pName => $pType)
5770 if(strpos($pType,':')) {
5771 $pType = $this->getNamespaceFromPrefix($this->getPrefix($pType)).":".$this->getLocalPart($pType);
5773 $this->messages[$name.'Request'][$pName] = $pType;
5776 $this->messages[$name.'Request']= '0';
5780 foreach($out as $pName => $pType)
5782 if(strpos($pType,':')) {
5783 $pType = $this->getNamespaceFromPrefix($this->getPrefix($pType)).":".$this->getLocalPart($pType);
5785 $this->messages[$name.'Response'][$pName] = $pType;
5788 $this->messages[$name.'Response']= '0';
5799 * soap_parser class parses SOAP XML messages into native PHP values
5801 * @author Dietrich Ayala <dietrich@ganx4.com>
5802 * @version $Id: nusoap.php,v 1.3 2008/07/10 16:06:24 ppollet Exp $
5805 class soap_parser extends nusoap_base {
5808 var $xml_encoding = '';
5810 var $root_struct = '';
5811 var $root_struct_name = '';
5812 var $root_struct_namespace = '';
5813 var $root_header = '';
5814 var $document = ''; // incoming SOAP body (text)
5815 // determines where in the message we are (envelope,header,body,method)
5819 var $default_namespace = '';
5820 var $namespaces = array();
5821 var $message = array();
5824 var $fault_code = '';
5825 var $fault_str = '';
5826 var $fault_detail = '';
5827 var $depth_array = array();
5828 var $debug_flag = true;
5829 var $soapresponse = NULL;
5830 var $responseHeaders = ''; // incoming SOAP headers (text)
5831 var $body_position = 0;
5832 // for multiref parsing:
5833 // array of id => pos
5835 // array of id => hrefs => pos
5836 var $multirefs = array();
5837 // toggle for auto-decoding element content
5838 var $decode_utf8 = true;
5841 * constructor that actually does the parsing
5843 * @param string $xml SOAP message
5844 * @param string $encoding character encoding scheme of message
5845 * @param string $method method for which XML is parsed (unused?)
5846 * @param string $decode_utf8 whether to decode UTF-8 to ISO-8859-1
5849 function soap_parser($xml,$encoding='UTF-8',$method='',$decode_utf8=true){
5850 parent::nusoap_base();
5852 $this->xml_encoding = $encoding;
5853 $this->method = $method;
5854 $this->decode_utf8 = $decode_utf8;
5856 // Check whether content has been read.
5858 // Check XML encoding
5859 $pos_xml = strpos($xml, '<?xml');
5860 if ($pos_xml !== FALSE) {
5861 $xml_decl = substr($xml, $pos_xml, strpos($xml, '?>', $pos_xml + 2) - $pos_xml + 1);
5862 if (preg_match("/encoding=[\"']([^\"']*)[\"']/", $xml_decl, $res)) {
5863 $xml_encoding = $res[1];
5864 if (strtoupper($xml_encoding) != $encoding) {
5865 $err = "Charset from HTTP Content-Type '" . $encoding . "' does not match encoding from XML declaration '" . $xml_encoding . "'";
5867 if ($encoding != 'ISO-8859-1' || strtoupper($xml_encoding) != 'UTF-8') {
5868 $this->setError($err);
5871 // when HTTP says ISO-8859-1 (the default) and XML says UTF-8 (the typical), assume the other endpoint is just sloppy and proceed
5873 $this->debug('Charset from HTTP Content-Type matches encoding from XML declaration');
5876 $this->debug('No encoding specified in XML declaration');
5879 $this->debug('No XML declaration');
5881 $this->debug('Entering soap_parser(), length='.strlen($xml).', encoding='.$encoding);
5882 // Create an XML parser - why not xml_parser_create_ns?
5883 $this->parser = xml_parser_create($this->xml_encoding);
5884 // Set the options for parsing the XML data.
5885 //xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 1);
5886 xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING, 0);
5887 xml_parser_set_option($this->parser, XML_OPTION_TARGET_ENCODING, $this->xml_encoding);
5888 // Set the object for the parser.
5889 xml_set_object($this->parser, $this);
5890 // Set the element handlers for the parser.
5891 xml_set_element_handler($this->parser, 'start_element','end_element');
5892 xml_set_character_data_handler($this->parser,'character_data');
5894 // Parse the XML file.
5895 if(!xml_parse($this->parser,$xml,true)){
5896 // Display an error message.
5897 $err = sprintf('XML error parsing SOAP payload on line %d: %s',
5898 xml_get_current_line_number($this->parser),
5899 xml_error_string(xml_get_error_code($this->parser)));
5901 $this->debug("XML payload:\n" . $xml);
5902 $this->setError($err);
5904 $this->debug('parsed successfully, found root struct: '.$this->root_struct.' of name '.$this->root_struct_name);
5906 $this->soapresponse = $this->message[$this->root_struct]['result'];
5907 // get header value: no, because this is documented as XML string
5908 // if($this->root_header != '' && isset($this->message[$this->root_header]['result'])){
5909 // $this->responseHeaders = $this->message[$this->root_header]['result'];
5911 // resolve hrefs/ids
5912 if(sizeof($this->multirefs) > 0){
5913 foreach($this->multirefs as $id => $hrefs){
5914 $this->debug('resolving multirefs for id: '.$id);
5915 $idVal = $this->buildVal($this->ids[$id]);
5916 if (is_array($idVal) && isset($idVal['!id'])) {
5917 unset($idVal['!id']);
5919 foreach($hrefs as $refPos => $ref){
5920 $this->debug('resolving href at pos '.$refPos);
5921 $this->multirefs[$id][$refPos] = $idVal;
5926 xml_parser_free($this->parser);
5928 $this->debug('xml was empty, didn\'t parse!');
5929 $this->setError('xml was empty, didn\'t parse!');
5934 * start-element handler
5936 * @param resource $parser XML parser object
5937 * @param string $name element name
5938 * @param array $attrs associative array of attributes
5941 function start_element($parser, $name, $attrs) {
5942 // position in a total number of elements, starting from 0
5943 // update class level pos
5944 $pos = $this->position++;
5946 $this->message[$pos] = array('pos' => $pos,'children'=>'','cdata'=>'');
5947 // depth = how many levels removed from root?
5948 // set mine as current global depth and increment global depth value
5949 $this->message[$pos]['depth'] = $this->depth++;
5951 // else add self as child to whoever the current parent is
5953 $this->message[$this->parent]['children'] .= '|'.$pos;
5956 $this->message[$pos]['parent'] = $this->parent;
5957 // set self as current parent
5958 $this->parent = $pos;
5959 // set self as current value for this depth
5960 $this->depth_array[$this->depth] = $pos;
5961 // get element prefix
5962 if(strpos($name,':')){
5964 $prefix = substr($name,0,strpos($name,':'));
5965 // get unqualified name
5966 $name = substr(strstr($name,':'),1);
5969 if($name == 'Envelope'){
5970 $this->status = 'envelope';
5971 } elseif($name == 'Header'){
5972 $this->root_header = $pos;
5973 $this->status = 'header';
5974 } elseif($name == 'Body'){
5975 $this->status = 'body';
5976 $this->body_position = $pos;
5978 } elseif($this->status == 'body' && $pos == ($this->body_position+1)){
5979 $this->status = 'method';
5980 $this->root_struct_name = $name;
5981 $this->root_struct = $pos;
5982 $this->message[$pos]['type'] = 'struct';
5983 $this->debug("found root struct $this->root_struct_name, pos $this->root_struct");
5986 $this->message[$pos]['status'] = $this->status;
5988 $this->message[$pos]['name'] = htmlspecialchars($name);
5990 $this->message[$pos]['attrs'] = $attrs;
5992 // loop through atts, logging ns and type declarations
5994 foreach($attrs as $key => $value){
5995 $key_prefix = $this->getPrefix($key);
5996 $key_localpart = $this->getLocalPart($key);
5997 // if ns declarations, add to class level array of valid namespaces
5998 if($key_prefix == 'xmlns'){
5999 if(ereg('^http://www.w3.org/[0-9]{4}/XMLSchema$',$value)){
6000 $this->XMLSchemaVersion = $value;
6001 $this->namespaces['xsd'] = $this->XMLSchemaVersion;
6002 $this->namespaces['xsi'] = $this->XMLSchemaVersion.'-instance';
6004 $this->namespaces[$key_localpart] = $value;
6005 // set method namespace
6006 if($name == $this->root_struct_name){
6007 $this->methodNamespace = $value;
6009 // if it's a type declaration, set type
6010 } elseif($key_localpart == 'type'){
6011 if (isset($this->message[$pos]['type']) && $this->message[$pos]['type'] == 'array') {
6012 // do nothing: already processed arrayType
6014 $value_prefix = $this->getPrefix($value);
6015 $value_localpart = $this->getLocalPart($value);
6016 $this->message[$pos]['type'] = $value_localpart;
6017 $this->message[$pos]['typePrefix'] = $value_prefix;
6018 if(isset($this->namespaces[$value_prefix])){
6019 $this->message[$pos]['type_namespace'] = $this->namespaces[$value_prefix];
6020 } else if(isset($attrs['xmlns:'.$value_prefix])) {
6021 $this->message[$pos]['type_namespace'] = $attrs['xmlns:'.$value_prefix];
6023 // should do something here with the namespace of specified type?
6025 } elseif($key_localpart == 'arrayType'){
6026 $this->message[$pos]['type'] = 'array';
6027 /* do arrayType ereg here
6028 [1] arrayTypeValue ::= atype asize
6029 [2] atype ::= QName rank*
6030 [3] rank ::= '[' (',')* ']'
6031 [4] asize ::= '[' length~ ']'
6032 [5] length ::= nextDimension* Digit+
6033 [6] nextDimension ::= Digit+ ','
6035 $expr = '([A-Za-z0-9_]+):([A-Za-z]+[A-Za-z0-9_]+)\[([0-9]+),?([0-9]*)\]';
6036 if(ereg($expr,$value,$regs)){
6037 $this->message[$pos]['typePrefix'] = $regs[1];
6038 $this->message[$pos]['arrayTypePrefix'] = $regs[1];
6039 if (isset($this->namespaces[$regs[1]])) {
6040 $this->message[$pos]['arrayTypeNamespace'] = $this->namespaces[$regs[1]];
6041 } else if (isset($attrs['xmlns:'.$regs[1]])) {
6042 $this->message[$pos]['arrayTypeNamespace'] = $attrs['xmlns:'.$regs[1]];
6044 $this->message[$pos]['arrayType'] = $regs[2];
6045 $this->message[$pos]['arraySize'] = $regs[3];
6046 $this->message[$pos]['arrayCols'] = $regs[4];
6048 // specifies nil value (or not)
6049 } elseif ($key_localpart == 'nil'){
6050 $this->message[$pos]['nil'] = ($value == 'true' || $value == '1');
6051 // some other attribute
6052 } elseif ($key != 'href' && $key != 'xmlns' && $key_localpart != 'encodingStyle' && $key_localpart != 'root') {
6053 $this->message[$pos]['xattrs']['!' . $key] = $value;
6056 if ($key == 'xmlns') {
6057 $this->default_namespace = $value;
6061 $this->ids[$value] = $pos;
6064 if($key_localpart == 'root' && $value == 1){
6065 $this->status = 'method';
6066 $this->root_struct_name = $name;
6067 $this->root_struct = $pos;
6068 $this->debug("found root struct $this->root_struct_name, pos $pos");
6071 $attstr .= " $key=\"$value\"";
6073 // get namespace - must be done after namespace atts are processed
6075 $this->message[$pos]['namespace'] = $this->namespaces[$prefix];
6076 $this->default_namespace = $this->namespaces[$prefix];
6078 $this->message[$pos]['namespace'] = $this->default_namespace;
6080 if($this->status == 'header'){
6081 if ($this->root_header != $pos) {
6082 $this->responseHeaders .= "<" . (isset($prefix) ? $prefix . ':' : '') . "$name$attstr>";
6084 } elseif($this->root_struct_name != ''){
6085 $this->document .= "<" . (isset($prefix) ? $prefix . ':' : '') . "$name$attstr>";
6090 * end-element handler
6092 * @param resource $parser XML parser object
6093 * @param string $name element name
6096 function end_element($parser, $name) {
6097 // position of current element is equal to the last value left in depth_array for my depth
6098 $pos = $this->depth_array[$this->depth--];
6100 // get element prefix
6101 if(strpos($name,':')){
6103 $prefix = substr($name,0,strpos($name,':'));
6104 // get unqualified name
6105 $name = substr(strstr($name,':'),1);
6108 // build to native type
6109 if(isset($this->body_position) && $pos > $this->body_position){
6110 // deal w/ multirefs
6111 if(isset($this->message[$pos]['attrs']['href'])){
6113 $id = substr($this->message[$pos]['attrs']['href'],1);
6114 // add placeholder to href array
6115 $this->multirefs[$id][$pos] = 'placeholder';
6116 // add set a reference to it as the result value
6117 $this->message[$pos]['result'] =& $this->multirefs[$id][$pos];
6118 // build complexType values
6119 } elseif($this->message[$pos]['children'] != ''){
6120 // if result has already been generated (struct/array)
6121 if(!isset($this->message[$pos]['result'])){
6122 $this->message[$pos]['result'] = $this->buildVal($pos);
6124 // build complexType values of attributes and possibly simpleContent
6125 } elseif (isset($this->message[$pos]['xattrs'])) {
6126 if (isset($this->message[$pos]['nil']) && $this->message[$pos]['nil']) {
6127 $this->message[$pos]['xattrs']['!'] = null;
6128 } elseif (isset($this->message[$pos]['cdata']) && trim($this->message[$pos]['cdata']) != '') {
6129 if (isset($this->message[$pos]['type'])) {
6130 $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'] : '');
6132 $parent = $this->message[$pos]['parent'];
6133 if (isset($this->message[$parent]['type']) && ($this->message[$parent]['type'] == 'array') && isset($this->message[$parent]['arrayType'])) {
6134 $this->message[$pos]['xattrs']['!'] = $this->decodeSimple($this->message[$pos]['cdata'], $this->message[$parent]['arrayType'], isset($this->message[$parent]['arrayTypeNamespace']) ? $this->message[$parent]['arrayTypeNamespace'] : '');
6136 $this->message[$pos]['xattrs']['!'] = $this->message[$pos]['cdata'];
6140 $this->message[$pos]['result'] = $this->message[$pos]['xattrs'];
6141 // set value of simpleType (or nil complexType)
6143 //$this->debug('adding data for scalar value '.$this->message[$pos]['name'].' of value '.$this->message[$pos]['cdata']);
6144 if (isset($this->message[$pos]['nil']) && $this->message[$pos]['nil']) {
6145 $this->message[$pos]['xattrs']['!'] = null;
6146 } elseif (isset($this->message[$pos]['type'])) {
6147 $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'] : '');
6149 $parent = $this->message[$pos]['parent'];
6150 if (isset($this->message[$parent]['type']) && ($this->message[$parent]['type'] == 'array') && isset($this->message[$parent]['arrayType'])) {
6151 $this->message[$pos]['result'] = $this->decodeSimple($this->message[$pos]['cdata'], $this->message[$parent]['arrayType'], isset($this->message[$parent]['arrayTypeNamespace']) ? $this->message[$parent]['arrayTypeNamespace'] : '');
6153 $this->message[$pos]['result'] = $this->message[$pos]['cdata'];
6157 /* add value to parent's result, if parent is struct/array
6158 $parent = $this->message[$pos]['parent'];
6159 if($this->message[$parent]['type'] != 'map'){
6160 if(strtolower($this->message[$parent]['type']) == 'array'){
6161 $this->message[$parent]['result'][] = $this->message[$pos]['result'];
6163 $this->message[$parent]['result'][$this->message[$pos]['name']] = $this->message[$pos]['result'];
6171 if($this->status == 'header'){
6172 if ($this->root_header != $pos) {
6173 $this->responseHeaders .= "</" . (isset($prefix) ? $prefix . ':' : '') . "$name>";
6175 } elseif($pos >= $this->root_struct){
6176 $this->document .= "</" . (isset($prefix) ? $prefix . ':' : '') . "$name>";
6179 if($pos == $this->root_struct){
6180 $this->status = 'body';
6181 $this->root_struct_namespace = $this->message[$pos]['namespace'];
6182 } elseif($name == 'Body'){
6183 $this->status = 'envelope';
6184 } elseif($name == 'Header'){
6185 $this->status = 'envelope';
6186 } elseif($name == 'Envelope'){
6189 // set parent back to my parent
6190 $this->parent = $this->message[$pos]['parent'];
6194 * element content handler
6196 * @param resource $parser XML parser object
6197 * @param string $data element content
6200 function character_data($parser, $data){
6201 $pos = $this->depth_array[$this->depth];
6202 if ($this->xml_encoding=='UTF-8'){
6203 // TODO: add an option to disable this for folks who want
6204 // raw UTF-8 that, e.g., might not map to iso-8859-1
6205 // TODO: this can also be handled with xml_parser_set_option($this->parser, XML_OPTION_TARGET_ENCODING, "ISO-8859-1");
6206 if($this->decode_utf8){
6207 $data = utf8_decode($data);
6210 $this->message[$pos]['cdata'] .= $data;
6212 if($this->status == 'header'){
6213 $this->responseHeaders .= $data;
6215 $this->document .= $data;
6220 * get the parsed message
6225 function get_response(){
6226 return $this->soapresponse;
6230 * get the parsed headers
6232 * @return string XML or empty if no headers
6235 function getHeaders(){
6236 return $this->responseHeaders;
6240 * decodes simple types into PHP variables
6242 * @param string $value value to decode
6243 * @param string $type XML type to decode
6244 * @param string $typens XML type namespace to decode
6245 * @return mixed PHP value
6248 function decodeSimple($value, $type, $typens) {
6249 // TODO: use the namespace!
6250 if ((!isset($type)) || $type == 'string' || $type == 'long' || $type == 'unsignedLong') {
6251 return (string) $value;
6253 if ($type == 'int' || $type == 'integer' || $type == 'short' || $type == 'byte') {
6254 return (int) $value;
6256 if ($type == 'float' || $type == 'double' || $type == 'decimal') {
6257 return (double) $value;
6259 if ($type == 'boolean') {
6260 if (strtolower($value) == 'false' || strtolower($value) == 'f') {
6263 return (boolean) $value;
6265 if ($type == 'base64' || $type == 'base64Binary') {
6266 $this->debug('Decode base64 value');
6267 return base64_decode($value);
6269 // obscure numeric types
6270 if ($type == 'nonPositiveInteger' || $type == 'negativeInteger'
6271 || $type == 'nonNegativeInteger' || $type == 'positiveInteger'
6272 || $type == 'unsignedInt'
6273 || $type == 'unsignedShort' || $type == 'unsignedByte') {
6274 return (int) $value;
6276 // bogus: parser treats array with no elements as a simple type
6277 if ($type == 'array') {
6281 return (string) $value;
6285 * builds response structures for compound values (arrays/structs)
6288 * @param integer $pos position in node tree
6289 * @return mixed PHP value
6292 function buildVal($pos){
6293 if(!isset($this->message[$pos]['type'])){
6294 $this->message[$pos]['type'] = '';
6296 $this->debug('in buildVal() for '.$this->message[$pos]['name']."(pos $pos) of type ".$this->message[$pos]['type']);
6297 // if there are children...
6298 if($this->message[$pos]['children'] != ''){
6299 $this->debug('in buildVal, there are children');
6300 $children = explode('|',$this->message[$pos]['children']);
6301 array_shift($children); // knock off empty
6303 if(isset($this->message[$pos]['arrayCols']) && $this->message[$pos]['arrayCols'] != ''){
6306 foreach($children as $child_pos){
6307 $this->debug("in buildVal, got an MD array element: $r, $c");
6308 $params[$r][] = $this->message[$child_pos]['result'];
6310 if($c == $this->message[$pos]['arrayCols']){
6316 } elseif($this->message[$pos]['type'] == 'array' || $this->message[$pos]['type'] == 'Array'){
6317 $this->debug('in buildVal, adding array '.$this->message[$pos]['name']);
6318 foreach($children as $child_pos){
6319 $params[] = &$this->message[$child_pos]['result'];
6321 // apache Map type: java hashtable
6322 } elseif($this->message[$pos]['type'] == 'Map' && $this->message[$pos]['type_namespace'] == 'http://xml.apache.org/xml-soap'){
6323 $this->debug('in buildVal, Java Map '.$this->message[$pos]['name']);
6324 foreach($children as $child_pos){
6325 $kv = explode("|",$this->message[$child_pos]['children']);
6326 $params[$this->message[$kv[1]]['result']] = &$this->message[$kv[2]]['result'];
6328 // generic compound type
6329 //} elseif($this->message[$pos]['type'] == 'SOAPStruct' || $this->message[$pos]['type'] == 'struct') {
6331 // Apache Vector type: treat as an array
6332 $this->debug('in buildVal, adding Java Vector or generic compound type '.$this->message[$pos]['name']);
6333 if ($this->message[$pos]['type'] == 'Vector' && $this->message[$pos]['type_namespace'] == 'http://xml.apache.org/xml-soap') {
6339 foreach($children as $child_pos){
6341 $params[] = &$this->message[$child_pos]['result'];
6343 if (isset($params[$this->message[$child_pos]['name']])) {
6344 // de-serialize repeated element name into an array
6345 if ((!is_array($params[$this->message[$child_pos]['name']])) || (!isset($params[$this->message[$child_pos]['name']][0]))) {
6346 $params[$this->message[$child_pos]['name']] = array($params[$this->message[$child_pos]['name']]);
6348 $params[$this->message[$child_pos]['name']][] = &$this->message[$child_pos]['result'];
6350 $params[$this->message[$child_pos]['name']] = &$this->message[$child_pos]['result'];
6355 if (isset($this->message[$pos]['xattrs'])) {
6356 $this->debug('in buildVal, handling attributes');
6357 foreach ($this->message[$pos]['xattrs'] as $n => $v) {
6361 // handle simpleContent
6362 if (isset($this->message[$pos]['cdata']) && trim($this->message[$pos]['cdata']) != '') {
6363 $this->debug('in buildVal, handling simpleContent');
6364 if (isset($this->message[$pos]['type'])) {
6365 $params['!'] = $this->decodeSimple($this->message[$pos]['cdata'], $this->message[$pos]['type'], isset($this->message[$pos]['type_namespace']) ? $this->message[$pos]['type_namespace'] : '');
6367 $parent = $this->message[$pos]['parent'];
6368 if (isset($this->message[$parent]['type']) && ($this->message[$parent]['type'] == 'array') && isset($this->message[$parent]['arrayType'])) {
6369 $params['!'] = $this->decodeSimple($this->message[$pos]['cdata'], $this->message[$parent]['arrayType'], isset($this->message[$parent]['arrayTypeNamespace']) ? $this->message[$parent]['arrayTypeNamespace'] : '');
6371 $params['!'] = $this->message[$pos]['cdata'];
6375 $ret = is_array($params) ? $params : array();
6376 $this->debug('in buildVal, return:');
6377 $this->appendDebug($this->varDump($ret));
6380 $this->debug('in buildVal, no children, building scalar');
6381 $cdata = isset($this->message[$pos]['cdata']) ? $this->message[$pos]['cdata'] : '';
6382 if (isset($this->message[$pos]['type'])) {
6383 $ret = $this->decodeSimple($cdata, $this->message[$pos]['type'], isset($this->message[$pos]['type_namespace']) ? $this->message[$pos]['type_namespace'] : '');
6384 $this->debug("in buildVal, return: $ret");
6387 $parent = $this->message[$pos]['parent'];
6388 if (isset($this->message[$parent]['type']) && ($this->message[$parent]['type'] == 'array') && isset($this->message[$parent]['arrayType'])) {
6389 $ret = $this->decodeSimple($cdata, $this->message[$parent]['arrayType'], isset($this->message[$parent]['arrayTypeNamespace']) ? $this->message[$parent]['arrayTypeNamespace'] : '');
6390 $this->debug("in buildVal, return: $ret");
6393 $ret = $this->message[$pos]['cdata'];
6394 $this->debug("in buildVal, return: $ret");
6408 * soap_client higher level class for easy usage.
6412 * // instantiate client with server info
6413 * $soap_client = new soap_client( string path [ ,boolean wsdl] );
6415 * // call method, get results
6416 * echo $soap_client->call( string methodname [ ,array parameters] );
6419 * unset($soap_client);
6421 * @author Dietrich Ayala <dietrich@ganx4.com>
6422 * @version $Id: nusoap.php,v 1.3 2008/07/10 16:06:24 ppollet Exp $
6425 class soap_client extends nusoap_base {
6430 var $certRequest = array();
6431 var $requestHeaders = false; // SOAP headers in request (text)
6432 var $responseHeaders = ''; // SOAP headers from response (incomplete namespace resolution) (text)
6433 var $document = ''; // SOAP body response portion (incomplete namespace resolution) (text)
6435 var $forceEndpoint = ''; // overrides WSDL endpoint
6436 var $proxyhost = '';
6437 var $proxyport = '';
6438 var $proxyusername = '';
6439 var $proxypassword = '';
6440 var $xml_encoding = ''; // character set encoding of incoming (response) messages
6441 var $http_encoding = false;
6442 var $timeout = 0; // HTTP connection timeout
6443 var $response_timeout = 30; // HTTP response timeout
6444 var $endpointType = ''; // soap|wsdl, empty for WSDL initialization error
6445 var $persistentConnection = false;
6446 var $defaultRpcParams = false; // This is no longer used
6447 var $request = ''; // HTTP request
6448 var $response = ''; // HTTP response
6449 var $responseData = ''; // SOAP payload of response
6450 var $cookies = array(); // Cookies from response or for request
6451 var $decode_utf8 = true; // toggles whether the parser decodes element content w/ utf8_decode()
6452 var $operations = array(); // WSDL operations, empty for WSDL initialization error
6455 * fault related variables
6481 * @param mixed $endpoint SOAP server or WSDL URL (string), or wsdl instance (object)
6482 * @param bool $wsdl optional, set to true if using WSDL
6483 * @param int $portName optional portName in WSDL document
6484 * @param string $proxyhost
6485 * @param string $proxyport
6486 * @param string $proxyusername
6487 * @param string $proxypassword
6488 * @param integer $timeout set the connection timeout
6489 * @param integer $response_timeout set the response timeout
6492 function soap_client($endpoint,$wsdl = false,$proxyhost = false,$proxyport = false,$proxyusername = false, $proxypassword = false, $timeout = 0, $response_timeout = 30){
6493 parent::nusoap_base();
6494 $this->endpoint = $endpoint;
6495 $this->proxyhost = $proxyhost;
6496 $this->proxyport = $proxyport;
6497 $this->proxyusername = $proxyusername;
6498 $this->proxypassword = $proxypassword;
6499 $this->timeout = $timeout;
6500 $this->response_timeout = $response_timeout;
6504 if (is_object($endpoint) && (get_class($endpoint) == 'wsdl')) {
6505 $this->wsdl = $endpoint;
6506 $this->endpoint = $this->wsdl->wsdl;
6507 $this->wsdlFile = $this->endpoint;
6508 $this->debug('existing wsdl instance created from ' . $this->endpoint);
6510 $this->wsdlFile = $this->endpoint;
6512 // instantiate wsdl object and parse wsdl file
6513 $this->debug('instantiating wsdl class with doc: '.$endpoint);
6514 $this->wsdl =& new wsdl($this->wsdlFile,$this->proxyhost,$this->proxyport,$this->proxyusername,$this->proxypassword,$this->timeout,$this->response_timeout);
6516 $this->appendDebug($this->wsdl->getDebug());
6517 $this->wsdl->clearDebug();
6519 if($errstr = $this->wsdl->getError()){
6520 $this->debug('got wsdl error: '.$errstr);
6521 $this->setError('wsdl error: '.$errstr);
6522 } elseif($this->operations = $this->wsdl->getOperations()){
6523 $this->debug( 'got '.count($this->operations).' operations from wsdl '.$this->wsdlFile);
6524 $this->endpointType = 'wsdl';
6526 $this->debug( 'getOperations returned false');
6527 $this->setError('no operations defined in the WSDL document!');
6530 $this->debug("instantiate SOAP with endpoint at $endpoint");
6531 $this->endpointType = 'soap';
6536 * calls method, returns PHP native type
6538 * @param string $method SOAP server URL or path
6539 * @param mixed $params An array, associative or simple, of the parameters
6540 * for the method call, or a string that is the XML
6541 * for the call. For rpc style, this call will
6542 * wrap the XML in a tag named after the method, as
6543 * well as the SOAP Envelope and Body. For document
6544 * style, this will only wrap with the Envelope and Body.
6545 * IMPORTANT: when using an array with document style,
6546 * in which case there
6547 * is really one parameter, the root of the fragment
6548 * used in the call, which encloses what programmers
6549 * normally think of parameters. A parameter array
6550 * *must* include the wrapper.
6551 * @param string $namespace optional method namespace (WSDL can override)
6552 * @param string $soapAction optional SOAPAction value (WSDL can override)
6553 * @param mixed $headers optional string of XML with SOAP header content, or array of soapval objects for SOAP headers
6554 * @param boolean $rpcParams optional (no longer used)
6555 * @param string $style optional (rpc|document) the style to use when serializing parameters (WSDL can override)
6556 * @param string $use optional (encoded|literal) the use when serializing parameters (WSDL can override)
6557 * @return mixed response from SOAP call
6560 function call($operation,$params=array(),$namespace='http://tempuri.org',$soapAction='',$headers=false,$rpcParams=null,$style='rpc',$use='encoded'){
6561 $this->operation = $operation;
6562 $this->fault = false;
6563 $this->setError('');
6564 $this->request = '';
6565 $this->response = '';
6566 $this->responseData = '';
6567 $this->faultstring = '';
6568 $this->faultcode = '';
6569 $this->opData = array();
6571 $this->debug("call: operation=$operation, namespace=$namespace, soapAction=$soapAction, rpcParams=$rpcParams, style=$style, use=$use, endpointType=$this->endpointType");
6572 $this->appendDebug('params=' . $this->varDump($params));
6573 $this->appendDebug('headers=' . $this->varDump($headers));
6575 $this->requestHeaders = $headers;
6577 // serialize parameters
6578 if($this->endpointType == 'wsdl' && $opData = $this->getOperationData($operation)){
6579 // use WSDL for operation
6580 $this->opData = $opData;
6581 $this->debug("found operation");
6582 $this->appendDebug('opData=' . $this->varDump($opData));
6583 if (isset($opData['soapAction'])) {
6584 $soapAction = $opData['soapAction'];
6586 if (! $this->forceEndpoint) {
6587 $this->endpoint = $opData['endpoint'];
6589 $this->endpoint = $this->forceEndpoint;
6591 $namespace = isset($opData['input']['namespace']) ? $opData['input']['namespace'] : $namespace;
6592 $style = $opData['style'];
6593 $use = $opData['input']['use'];
6594 // add ns to ns array
6595 if($namespace != '' && !isset($this->wsdl->namespaces[$namespace])){
6596 $nsPrefix = 'ns' . rand(1000, 9999);
6597 $this->wsdl->namespaces[$nsPrefix] = $namespace;
6599 $nsPrefix = $this->wsdl->getPrefixFromNamespace($namespace);
6600 // serialize payload
6601 if (is_string($params)) {
6602 $this->debug("serializing param string for WSDL operation $operation");
6604 } elseif (is_array($params)) {
6605 $this->debug("serializing param array for WSDL operation $operation");
6606 $payload = $this->wsdl->serializeRPCParameters($operation,'input',$params);
6608 $this->debug('params must be array or string');
6609 $this->setError('params must be array or string');
6612 $usedNamespaces = $this->wsdl->usedNamespaces;
6613 if (isset($opData['input']['encodingStyle'])) {
6614 $encodingStyle = $opData['input']['encodingStyle'];
6616 $encodingStyle = '';
6618 $this->appendDebug($this->wsdl->getDebug());
6619 $this->wsdl->clearDebug();
6620 if ($errstr = $this->wsdl->getError()) {
6621 $this->debug('got wsdl error: '.$errstr);
6622 $this->setError('wsdl error: '.$errstr);
6625 } elseif($this->endpointType == 'wsdl') {
6626 // operation not in WSDL
6627 $this->appendDebug($this->wsdl->getDebug());
6628 $this->wsdl->clearDebug();
6629 $this->setError( 'operation '.$operation.' not present.');
6630 $this->debug("operation '$operation' not present.");
6634 //$this->namespaces['ns1'] = $namespace;
6635 $nsPrefix = 'ns' . rand(1000, 9999);
6638 if (is_string($params)) {
6639 $this->debug("serializing param string for operation $operation");
6641 } elseif (is_array($params)) {
6642 $this->debug("serializing param array for operation $operation");
6643 foreach($params as $k => $v){
6644 $payload .= $this->serialize_val($v,$k,false,false,false,false,$use);
6647 $this->debug('params must be array or string');
6648 $this->setError('params must be array or string');
6651 $usedNamespaces = array();
6652 if ($use == 'encoded') {
6653 $encodingStyle = 'http://schemas.xmlsoap.org/soap/encoding/';
6655 $encodingStyle = '';
6658 // wrap RPC calls with method element
6659 if ($style == 'rpc') {
6660 if ($use == 'literal') {
6661 $this->debug("wrapping RPC request with literal method element");
6663 $payload = "<$operation xmlns=\"$namespace\">" . $payload . "</$operation>";
6665 $payload = "<$operation>" . $payload . "</$operation>";
6668 $this->debug("wrapping RPC request with encoded method element");
6670 $payload = "<$nsPrefix:$operation xmlns:$nsPrefix=\"$namespace\">" .
6672 "</$nsPrefix:$operation>";
6674 $payload = "<$operation>" .
6680 // serialize envelope
6681 $soapmsg = $this->serializeEnvelope($payload,$this->requestHeaders,$usedNamespaces,$style,$use,$encodingStyle);
6682 $this->debug("endpoint=$this->endpoint, soapAction=$soapAction, namespace=$namespace, style=$style, use=$use, encodingStyle=$encodingStyle");
6683 $this->debug('SOAP message length=' . strlen($soapmsg) . ' contents (max 1000 bytes)=' . substr($soapmsg, 0, 1000));
6685 $return = $this->send($this->getHTTPBody($soapmsg),$soapAction,$this->timeout,$this->response_timeout);
6686 if($errstr = $this->getError()){
6687 $this->debug('Error: '.$errstr);
6690 $this->return = $return;
6691 $this->debug('sent message successfully and got a(n) '.gettype($return));
6692 $this->appendDebug('return=' . $this->varDump($return));
6695 if(is_array($return) && isset($return['faultcode'])){
6696 $this->debug('got fault');
6697 $this->setError($return['faultcode'].': '.$return['faultstring']);
6698 $this->fault = true;
6699 foreach($return as $k => $v){
6701 $this->debug("$k = $v<br>");
6704 } elseif ($style == 'document') {
6705 // NOTE: if the response is defined to have multiple parts (i.e. unwrapped),
6706 // we are only going to return the first part here...sorry about that
6709 // array of return values
6710 if(is_array($return)){
6711 // multiple 'out' parameters, which we return wrapped up
6713 if(sizeof($return) > 1){
6716 // single 'out' parameter (normally the return value)
6717 $return = array_shift($return);
6718 $this->debug('return shifted value: ');
6719 $this->appendDebug($this->varDump($return));
6721 // nothing returned (ie, echoVoid)
6730 * get available data pertaining to an operation
6732 * @param string $operation operation name
6733 * @return array array of data pertaining to the operation
6736 function getOperationData($operation){
6737 if(isset($this->operations[$operation])){
6738 return $this->operations[$operation];
6740 $this->debug("No data for operation: $operation");
6744 * send the SOAP message
6746 * Note: if the operation has multiple return values
6747 * the return value of this method will be an array
6750 * @param string $msg a SOAPx4 soapmsg object
6751 * @param string $soapaction SOAPAction value
6752 * @param integer $timeout set connection timeout in seconds
6753 * @param integer $response_timeout set response timeout in seconds
6754 * @return mixed native PHP types.
6757 function send($msg, $soapaction = '', $timeout=0, $response_timeout=30) {
6758 $this->checkCookies();
6762 case ereg('^http',$this->endpoint):
6763 $this->debug('transporting via HTTP');
6764 if($this->persistentConnection == true && is_object($this->persistentConnection)){
6765 $http =& $this->persistentConnection;
6767 $http = new soap_transport_http($this->endpoint);
6768 if ($this->persistentConnection) {
6769 $http->usePersistentConnection();
6772 $http->setContentType($this->getHTTPContentType(), $this->getHTTPContentTypeCharset());
6773 $http->setSOAPAction($soapaction);
6774 if($this->proxyhost && $this->proxyport){
6775 $http->setProxy($this->proxyhost,$this->proxyport,$this->proxyusername,$this->proxypassword);
6777 if($this->authtype != '') {
6778 $http->setCredentials($this->username, $this->password, $this->authtype, array(), $this->certRequest);
6780 if($this->http_encoding != ''){
6781 $http->setEncoding($this->http_encoding);
6783 $this->debug('sending message, length='.strlen($msg));
6784 if(ereg('^http:',$this->endpoint)){
6785 //if(strpos($this->endpoint,'http:')){
6786 $this->responseData = $http->send($msg,$timeout,$response_timeout,$this->cookies);
6787 } elseif(ereg('^https',$this->endpoint)){
6788 //} elseif(strpos($this->endpoint,'https:')){
6789 //if(phpversion() == '4.3.0-dev'){
6790 //$response = $http->send($msg,$timeout,$response_timeout);
6791 //$this->request = $http->outgoing_payload;
6792 //$this->response = $http->incoming_payload;
6794 $this->responseData = $http->sendHTTPS($msg,$timeout,$response_timeout,$this->cookies);
6796 $this->setError('no http/s in endpoint url');
6798 $this->request = $http->outgoing_payload;
6799 $this->response = $http->incoming_payload;
6800 $this->appendDebug($http->getDebug());
6801 $this->UpdateCookies($http->incoming_cookies);
6803 // save transport object if using persistent connections
6804 if ($this->persistentConnection) {
6805 $http->clearDebug();
6806 if (!is_object($this->persistentConnection)) {
6807 $this->persistentConnection = $http;
6811 if($err = $http->getError()){
6812 $this->setError('HTTP Error: '.$err);
6814 } elseif($this->getError()){
6817 $this->debug('got response, length='. strlen($this->responseData).' type='.$http->incoming_headers['content-type']);
6818 return $this->parseResponse($http->incoming_headers, $this->responseData);
6822 $this->setError('no transport found, or selected transport is not yet supported!');
6829 * processes SOAP message returned from server
6831 * @param array $headers The HTTP headers
6832 * @param string $data unprocessed response data from server
6833 * @return mixed value of the message, decoded into a PHP type
6836 function parseResponse($headers, $data) {
6837 $this->debug('Entering parseResponse() for data of length ' . strlen($data) . ' and type ' . $headers['content-type']);
6838 $this->debug ('parsing :'.$data);
6839 if (!strstr($headers['content-type'], 'text/xml')) {
6840 $this->setError('Response not of type text/xml');
6843 if (strpos($headers['content-type'], '=')) {
6844 $enc = str_replace('"', '', substr(strstr($headers["content-type"], '='), 1));
6845 $this->debug('Got response encoding: ' . $enc);
6846 if(eregi('^(ISO-8859-1|US-ASCII|UTF-8)$',$enc)){
6847 $this->xml_encoding = strtoupper($enc);
6849 $this->xml_encoding = 'US-ASCII';
6852 // should be US-ASCII for HTTP 1.0 or ISO-8859-1 for HTTP 1.1
6853 $this->xml_encoding = 'ISO-8859-1';
6855 $this->debug('Use encoding: ' . $this->xml_encoding . ' when creating soap_parser');
6856 $parser = new soap_parser($data,$this->xml_encoding,$this->operation,$this->decode_utf8);
6857 // add parser debug data to our debug
6858 $this->appendDebug($parser->getDebug());
6860 if($errstr = $parser->getError()){
6861 $this->setError( $errstr);
6862 // destroy the parser object
6867 $this->responseHeaders = $parser->getHeaders();
6868 // get decoded message
6869 $return = $parser->get_response();
6870 // add document for doclit support
6871 $this->document = $parser->document;
6872 // destroy the parser object
6874 // return decode message
6880 * sets the SOAP endpoint, which can override WSDL
6882 * @param $endpoint string The endpoint URL to use, or empty string or false to prevent override
6885 function setEndpoint($endpoint) {
6886 $this->forceEndpoint = $endpoint;
6890 * set the SOAP headers
6892 * @param $headers mixed String of XML with SOAP header content, or array of soapval objects for SOAP headers
6895 function setHeaders($headers){
6896 $this->requestHeaders = $headers;
6900 * get the SOAP response headers (namespace resolution incomplete)
6905 function getHeaders(){
6906 return $this->responseHeaders;
6910 * set proxy info here
6912 * @param string $proxyhost
6913 * @param string $proxyport
6914 * @param string $proxyusername
6915 * @param string $proxypassword
6918 function setHTTPProxy($proxyhost, $proxyport, $proxyusername = '', $proxypassword = '') {
6919 $this->proxyhost = $proxyhost;
6920 $this->proxyport = $proxyport;
6921 $this->proxyusername = $proxyusername;
6922 $this->proxypassword = $proxypassword;
6926 * if authenticating, set user credentials here
6928 * @param string $username
6929 * @param string $password
6930 * @param string $authtype (basic|digest|certificate)
6931 * @param array $certRequest (keys must be cainfofile (optional), sslcertfile, sslkeyfile, passphrase, verifypeer (optional), verifyhost (optional): see corresponding options in cURL docs)
6934 function setCredentials($username, $password, $authtype = 'basic', $certRequest = array()) {
6935 $this->username = $username;
6936 $this->password = $password;
6937 $this->authtype = $authtype;
6938 $this->certRequest = $certRequest;
6944 * @param string $enc
6947 function setHTTPEncoding($enc='gzip, deflate'){
6948 $this->http_encoding = $enc;
6952 * use HTTP persistent connections if possible
6956 function useHTTPPersistentConnection(){
6957 $this->persistentConnection = true;
6961 * gets the default RPC parameter setting.
6962 * If true, default is that call params are like RPC even for document style.
6963 * Each call() can override this value.
6965 * This is no longer used.
6971 function getDefaultRpcParams() {
6972 return $this->defaultRpcParams;
6976 * sets the default RPC parameter setting.
6977 * If true, default is that call params are like RPC even for document style
6978 * Each call() can override this value.
6980 * This is no longer used.
6982 * @param boolean $rpcParams
6986 function setDefaultRpcParams($rpcParams) {
6987 $this->defaultRpcParams = $rpcParams;
6991 * dynamically creates an instance of a proxy class,
6992 * allowing user to directly call methods from wsdl
6994 * @return object soap_proxy object
6997 function getProxy(){
6999 $evalStr = $this->_getProxyClassCode($r);
7000 //$this->debug("proxy class: $evalStr";
7003 // instantiate proxy object
7004 eval("\$proxy = new soap_proxy_$r('');");
7005 // transfer current wsdl data to the proxy thereby avoiding parsing the wsdl twice
7006 $proxy->endpointType = 'wsdl';
7007 $proxy->wsdlFile = $this->wsdlFile;
7008 $proxy->wsdl = $this->wsdl;
7009 $proxy->operations = $this->operations;
7010 $proxy->defaultRpcParams = $this->defaultRpcParams;
7011 // transfer other state
7012 $proxy->username = $this->username;
7013 $proxy->password = $this->password;
7014 $proxy->authtype = $this->authtype;
7015 $proxy->proxyhost = $this->proxyhost;
7016 $proxy->proxyport = $this->proxyport;
7017 $proxy->proxyusername = $this->proxyusername;
7018 $proxy->proxypassword = $this->proxypassword;
7019 $proxy->timeout = $this->timeout;
7020 $proxy->response_timeout = $this->response_timeout;
7021 $proxy->http_encoding = $this->http_encoding;
7022 $proxy->persistentConnection = $this->persistentConnection;
7023 $proxy->requestHeaders = $this->requestHeaders;
7024 $proxy->soap_defencoding = $this->soap_defencoding;
7025 $proxy->endpoint = $this->endpoint;
7026 $proxy->forceEndpoint = $this->forceEndpoint;
7031 * dynamically creates proxy class code
7033 * @return string PHP/NuSOAP code for the proxy class
7036 function _getProxyClassCode($r) {
7037 if ($this->endpointType != 'wsdl') {
7038 $evalStr = 'A proxy can only be created for a WSDL client';
7039 $this->setError($evalStr);
7043 foreach ($this->operations as $operation => $opData) {
7044 if ($operation != '') {
7045 // create param string and param comment string
7046 if (sizeof($opData['input']['parts']) > 0) {
7048 $paramArrayStr = '';
7049 $paramCommentStr = '';
7050 foreach ($opData['input']['parts'] as $name => $type) {
7051 $paramStr .= "\$$name, ";
7052 $paramArrayStr .= "'$name' => \$$name, ";
7053 $paramCommentStr .= "$type \$$name, ";
7055 $paramStr = substr($paramStr, 0, strlen($paramStr)-2);
7056 $paramArrayStr = substr($paramArrayStr, 0, strlen($paramArrayStr)-2);
7057 $paramCommentStr = substr($paramCommentStr, 0, strlen($paramCommentStr)-2);
7060 $paramArrayStr = '';
7061 $paramCommentStr = 'void';
7063 $opData['namespace'] = !isset($opData['namespace']) ? 'http://testuri.com' : $opData['namespace'];
7064 $evalStr .= "// $paramCommentStr
7065 function " . str_replace('.', '__', $operation) . "($paramStr) {
7066 \$params = array($paramArrayStr);
7067 return \$this->call('$operation', \$params, '".$opData['namespace']."', '".(isset($opData['soapAction']) ? $opData['soapAction'] : '')."');
7071 unset($paramCommentStr);
7074 $evalStr = 'class soap_proxy_'.$r.' extends soap_client {
7081 * dynamically creates proxy class code
7083 * @return string PHP/NuSOAP code for the proxy class
7086 function getProxyClassCode() {
7088 return $this->_getProxyClassCode($r);
7092 * gets the HTTP body for the current request.
7094 * @param string $soapmsg The SOAP payload
7095 * @return string The HTTP body, which includes the SOAP payload
7098 function getHTTPBody($soapmsg) {
7103 * gets the HTTP content type for the current request.
7105 * Note: getHTTPBody must be called before this.
7107 * @return string the HTTP content type for the current request.
7110 function getHTTPContentType() {
7115 * gets the HTTP content type charset for the current request.
7116 * returns false for non-text content types.
7118 * Note: getHTTPBody must be called before this.
7120 * @return string the HTTP content type charset for the current request.
7123 function getHTTPContentTypeCharset() {
7124 return $this->soap_defencoding;
7128 * whether or not parser should decode utf8 element content
7130 * @return always returns true
7133 function decodeUTF8($bool){
7134 $this->decode_utf8 = $bool;
7139 * adds a new Cookie into $this->cookies array
7141 * @param string $name Cookie Name
7142 * @param string $value Cookie Value
7143 * @return if cookie-set was successful returns true, else false
7146 function setCookie($name, $value) {
7147 if (strlen($name) == 0) {
7150 $this->cookies[] = array('name' => $name, 'value' => $value);
7157 * @return array with all internal cookies
7160 function getCookies() {
7161 return $this->cookies;
7165 * checks all Cookies and delete those which are expired
7167 * @return always return true
7170 function checkCookies() {
7171 if (sizeof($this->cookies) == 0) {
7174 $this->debug('checkCookie: check ' . sizeof($this->cookies) . ' cookies');
7175 $curr_cookies = $this->cookies;
7176 $this->cookies = array();
7177 foreach ($curr_cookies as $cookie) {
7178 if (! is_array($cookie)) {
7179 $this->debug('Remove cookie that is not an array');
7182 if ((isset($cookie['expires'])) && (! empty($cookie['expires']))) {
7183 if (strtotime($cookie['expires']) > time()) {
7184 $this->cookies[] = $cookie;
7186 $this->debug('Remove expired cookie ' . $cookie['name']);
7189 $this->cookies[] = $cookie;
7192 $this->debug('checkCookie: '.sizeof($this->cookies).' cookies left in array');
7197 * updates the current cookies with a new set
7199 * @param array $cookies new cookies with which to update current ones
7200 * @return always return true
7203 function UpdateCookies($cookies) {
7204 if (sizeof($this->cookies) == 0) {
7205 // no existing cookies: take whatever is new
7206 if (sizeof($cookies) > 0) {
7207 $this->debug('Setting new cookie(s)');
7208 $this->cookies = $cookies;
7212 if (sizeof($cookies) == 0) {
7213 // no new cookies: keep what we've got
7217 foreach ($cookies as $newCookie) {
7218 if (!is_array($newCookie)) {
7221 if ((!isset($newCookie['name'])) || (!isset($newCookie['value']))) {
7224 $newName = $newCookie['name'];
7227 for ($i = 0; $i < count($this->cookies); $i++) {
7228 $cookie = $this->cookies[$i];
7229 if (!is_array($cookie)) {
7232 if (!isset($cookie['name'])) {
7235 if ($newName != $cookie['name']) {
7238 $newDomain = isset($newCookie['domain']) ? $newCookie['domain'] : 'NODOMAIN';
7239 $domain = isset($cookie['domain']) ? $cookie['domain'] : 'NODOMAIN';
7240 if ($newDomain != $domain) {
7243 $newPath = isset($newCookie['path']) ? $newCookie['path'] : 'NOPATH';
7244 $path = isset($cookie['path']) ? $cookie['path'] : 'NOPATH';
7245 if ($newPath != $path) {
7248 $this->cookies[$i] = $newCookie;
7250 $this->debug('Update cookie ' . $newName . '=' . $newCookie['value']);
7254 $this->debug('Add cookie ' . $newName . '=' . $newCookie['value']);
7255 $this->cookies[] = $newCookie;