4 NuSOAP - Web Services Toolkit for PHP
6 Copyright (c) 2002 NuSphere Corporation
8 This library is free software; you can redistribute it and/or
9 modify it under the terms of the GNU Lesser General Public
10 License as published by the Free Software Foundation; either
11 version 2.1 of the License, or (at your option) any later version.
13 This library is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
18 You should have received a copy of the GNU Lesser General Public
19 License along with this library; if not, write to the Free Software
20 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22 If you have any questions or comments, please email:
26 http://dietrich.ganx4.com/nusoap
29 http://www.nusphere.com
36 require_once 'class.soapclient.php';
37 require_once 'class.soap_val.php';
38 require_once 'class.soap_parser.php';
39 require_once 'class.soap_fault.php';
42 require_once 'class.soap_transport_http.php';
44 // optional add-on classes
45 require_once 'class.xmlschema.php';
46 require_once 'class.wsdl.php';
49 require_once 'class.soap_server.php';*/
55 * @author Dietrich Ayala <dietrich@ganx4.com>
60 var $title = 'NuSOAP';
61 var $version = '0.6.8';
62 var $revision = '$Revision$';
65 // toggles automatic encoding of special characters as entities
66 // (should always be true, I think)
67 var $charencoding = true;
72 * @var XMLSchemaVersion
75 var $XMLSchemaVersion = 'http://www.w3.org/2001/XMLSchema';
78 * set charset encoding for outgoing messages
80 * @var soap_defencoding
83 //var $soap_defencoding = 'UTF-8';
84 var $soap_defencoding = 'ISO-8859-1';
87 * load namespace uris into an array of uri => prefix
92 var $namespaces = array(
93 'SOAP-ENV' => 'http://schemas.xmlsoap.org/soap/envelope/',
94 'xsd' => 'http://www.w3.org/2001/XMLSchema',
95 'xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
96 'SOAP-ENC' => 'http://schemas.xmlsoap.org/soap/encoding/',
97 'si' => 'http://soapinterop.org/xsd');
98 var $usedNamespaces = array();
101 * load types into typemap array
102 * is this legacy yet?
103 * no, this is used by the xmlschema class to verify type => namespace mappings.
107 var $typemap = array(
108 'http://www.w3.org/2001/XMLSchema' => array(
109 'string'=>'string','boolean'=>'boolean','float'=>'double','double'=>'double','decimal'=>'double',
110 'duration'=>'','dateTime'=>'string','time'=>'string','date'=>'string','gYearMonth'=>'',
111 'gYear'=>'','gMonthDay'=>'','gDay'=>'','gMonth'=>'','hexBinary'=>'string','base64Binary'=>'string',
112 // abstract "any" types
113 'anyType'=>'string','anySimpleType'=>'string',
115 'normalizedString'=>'string','token'=>'string','language'=>'','NMTOKEN'=>'','NMTOKENS'=>'','Name'=>'','NCName'=>'','ID'=>'',
116 'IDREF'=>'','IDREFS'=>'','ENTITY'=>'','ENTITIES'=>'','integer'=>'integer','nonPositiveInteger'=>'integer',
117 'negativeInteger'=>'integer','long'=>'integer','int'=>'integer','short'=>'integer','byte'=>'integer','nonNegativeInteger'=>'integer',
118 'unsignedLong'=>'','unsignedInt'=>'','unsignedShort'=>'','unsignedByte'=>'','positiveInteger'=>''),
119 'http://www.w3.org/1999/XMLSchema' => array(
120 'i4'=>'','int'=>'integer','boolean'=>'boolean','string'=>'string','double'=>'double',
121 'float'=>'double','dateTime'=>'string',
122 'timeInstant'=>'string','base64Binary'=>'string','base64'=>'string','ur-type'=>'array'),
123 'http://soapinterop.org/xsd' => array('SOAPStruct'=>'struct'),
124 'http://schemas.xmlsoap.org/soap/encoding/' => array('base64'=>'string','array'=>'array','Array'=>'array'),
125 'http://xml.apache.org/xml-soap' => array('Map')
129 * entities to convert
134 var $xmlEntities = array('quot' => '"','amp' => '&',
135 'lt' => '<','gt' => '>','apos' => "'");
138 * adds debug data to the instance debug string with formatting
140 * @param string $string debug data
143 function debug($string){
144 $this->appendDebug($this->getmicrotime().' '.get_class($this).": $string\n");
148 * adds debug data to the instance debug string without formatting
150 * @param string $string debug data
153 function appendDebug($string){
154 // it would be nice to use a memory stream here to use
155 // memory more efficiently
156 $this->debug_str .= $string;
160 * clears the current debug data for this instance
164 function clearDebug() {
165 // it would be nice to use a memory stream here to use
166 // memory more efficiently
167 $this->debug_str = '';
171 * gets the current debug data for this instance
176 function &getDebug() {
177 // it would be nice to use a memory stream here to use
178 // memory more efficiently
179 return $this->debug_str;
183 * gets the current debug data for this instance as an XML comment
184 * this may change the contents of the debug data
186 * @return debug data as an XML comment
189 function &getDebugAsXMLComment() {
190 // it would be nice to use a memory stream here to use
191 // memory more efficiently
192 while (strpos($this->debug_str, '--')) {
193 $this->debug_str = str_replace('--', '- -', $this->debug_str);
195 return "<!--\n" . $this->debug_str . "\n-->";
199 * expands entities, e.g. changes '<' to '<'.
201 * @param string $val The string in which to expand entities.
204 function expandEntities($val) {
205 if ($this->charencoding) {
206 $val = str_replace('&', '&', $val);
207 $val = str_replace("'", ''', $val);
208 $val = str_replace('"', '"', $val);
209 $val = str_replace('<', '<', $val);
210 $val = str_replace('>', '>', $val);
216 * returns error string if present
218 * @return mixed error string or false
222 if($this->error_str != ''){
223 return $this->error_str;
231 * @return boolean $string error string
234 function setError($str){
235 $this->error_str = $str;
239 * detect if array is a simple array or a struct (associative array)
241 * @param $val The PHP array
242 * @return string (arraySimple|arrayStruct)
245 function isArraySimpleOrStruct($val) {
246 $keyList = array_keys($val);
247 foreach ($keyList as $keyListValue) {
248 if (!is_int($keyListValue)) {
249 return 'arrayStruct';
252 return 'arraySimple';
256 * serializes PHP values in accordance w/ section 5. Type information is
257 * not serialized if $use == 'literal'.
262 function serialize_val($val,$name=false,$type=false,$name_ns=false,$type_ns=false,$attributes=false,$use='encoded'){
263 if(is_object($val) && get_class($val) == 'soapval'){
264 return $val->serialize($use);
266 $this->debug( "in serialize_val: $val, $name, $type, $name_ns, $type_ns, $attributes, $use");
267 // if no name, use item
268 $name = (!$name|| is_numeric($name)) ? 'soapVal' : $name;
269 // if name has ns, add ns prefix to name
272 $prefix = 'nu'.rand(1000,9999);
273 $name = $prefix.':'.$name;
274 $xmlns .= " xmlns:$prefix=\"$name_ns\"";
276 // if type is prefixed, create type prefix
277 if($type_ns != '' && $type_ns == $this->namespaces['xsd']){
278 // need to fix this. shouldn't default to xsd if no ns specified
279 // w/o checking against typemap
280 $type_prefix = 'xsd';
282 $type_prefix = 'ns'.rand(1000,9999);
283 $xmlns .= " xmlns:$type_prefix=\"$type_ns\"";
285 // serialize attributes if present
288 foreach($attributes as $k => $v){
289 $atts .= " $k=\"$v\"";
292 // serialize if an xsd built-in primitive type
293 if($type != '' && isset($this->typemap[$this->XMLSchemaVersion][$type])){
295 if ($type == 'boolean') {
296 $val = $val ? 'true' : 'false';
300 } else if (is_string($val)) {
301 $val = $this->expandEntities($val);
303 if ($use == 'literal') {
304 return "<$name$xmlns>$val</$name>";
306 return "<$name$xmlns xsi:type=\"xsd:$type\">$val</$name>";
309 // detect type and serialize
312 case ($type == '' && is_null($val)):
313 if ($use == 'literal') {
314 // TODO: depends on nillable
315 $xml .= "<$name$xmlns/>";
317 $xml .= "<$name$xmlns xsi:nil=\"true\"/>";
320 case (is_bool($val) || $type == 'boolean'):
321 if ($type == 'boolean') {
322 $val = $val ? 'true' : 'false';
326 if ($use == 'literal') {
327 $xml .= "<$name$xmlns $atts>$val</$name>";
329 $xml .= "<$name$xmlns xsi:type=\"xsd:boolean\"$atts>$val</$name>";
332 case (is_int($val) || is_long($val) || $type == 'int'):
333 if ($use == 'literal') {
334 $xml .= "<$name$xmlns $atts>$val</$name>";
336 $xml .= "<$name$xmlns xsi:type=\"xsd:int\"$atts>$val</$name>";
339 case (is_float($val)|| is_double($val) || $type == 'float'):
340 if ($use == 'literal') {
341 $xml .= "<$name$xmlns $atts>$val</$name>";
343 $xml .= "<$name$xmlns xsi:type=\"xsd:float\"$atts>$val</$name>";
346 case (is_string($val) || $type == 'string'):
347 $val = $this->expandEntities($val);
348 if ($use == 'literal') {
349 $xml .= "<$name$xmlns $atts>$val</$name>";
351 $xml .= "<$name$xmlns xsi:type=\"xsd:string\"$atts>$val</$name>";
354 case is_object($val):
355 $name = get_class($val);
356 foreach(get_object_vars($val) as $k => $v){
357 $pXml = isset($pXml) ? $pXml.$this->serialize_val($v,$k,false,false,false,false,$use) : $this->serialize_val($v,$k,false,false,false,false,$use);
359 $xml .= '<'.$name.'>'.$pXml.'</'.$name.'>';
362 case (is_array($val) || $type):
363 // detect if struct or array
364 $valueType = $this->isArraySimpleOrStruct($val);
365 if($valueType=='arraySimple' || ereg('^ArrayOf',$type)){
367 if(is_array($val) && count($val)> 0){
369 if(is_object($v) && get_class($v) == 'soapval'){
370 $tt_ns = $v->type_ns;
372 } elseif (is_array($v)) {
373 $tt = $this->isArraySimpleOrStruct($v);
377 $array_types[$tt] = 1;
378 $xml .= $this->serialize_val($v,'item',false,false,false,false,$use);
381 if(count($array_types) > 1){
382 $array_typename = 'xsd:ur-type';
383 } elseif(isset($tt) && isset($this->typemap[$this->XMLSchemaVersion][$tt])) {
384 if ($tt == 'integer') {
387 $array_typename = 'xsd:'.$tt;
388 } elseif(isset($tt) && $tt == 'arraySimple'){
389 $array_typename = 'SOAP-ENC:Array';
390 } elseif(isset($tt) && $tt == 'arrayStruct'){
391 $array_typename = 'unnamed_struct_use_soapval';
393 // if type is prefixed, create type prefix
394 if ($tt_ns != '' && $tt_ns == $this->namespaces['xsd']){
395 $array_typename = 'xsd:' . $tt;
397 $tt_prefix = 'ns' . rand(1000, 9999);
398 $array_typename = "$tt_prefix:$tt";
399 $xmlns .= " xmlns:$tt_prefix=\"$tt_ns\"";
401 $array_typename = $tt;
405 if ($use == 'literal') {
407 } else if (isset($type) && isset($type_prefix)) {
408 $type_str = " xsi:type=\"$type_prefix:$type\"";
410 $type_str = " xsi:type=\"SOAP-ENC:Array\" SOAP-ENC:arrayType=\"".$array_typename."[$array_type]\"";
414 if ($use == 'literal') {
416 } else if (isset($type) && isset($type_prefix)) {
417 $type_str = " xsi:type=\"$type_prefix:$type\"";
419 $type_str = " xsi:type=\"SOAP-ENC:Array\"";
422 $xml = "<$name$xmlns$type_str$atts>".$xml."</$name>";
425 if(isset($type) && isset($type_prefix)){
426 $type_str = " xsi:type=\"$type_prefix:$type\"";
430 if ($use == 'literal') {
431 $xml .= "<$name$xmlns $atts>";
433 $xml .= "<$name$xmlns$type_str$atts>";
435 foreach($val as $k => $v){
437 if ($type == 'Map' && $type_ns == 'http://xml.apache.org/xml-soap') {
439 $xml .= $this->serialize_val($k,'key',false,false,false,false,$use);
440 $xml .= $this->serialize_val($v,'value',false,false,false,false,$use);
443 $xml .= $this->serialize_val($v,$k,false,false,false,false,$use);
450 $xml .= 'not detected, got '.gettype($val).' for '.$val;
460 * @param string headers optional
461 * @param array namespaces optional
462 * @param string style optional (rpc|document)
463 * @param string use optional (encoded|literal)
464 * @return string message
467 function serializeEnvelope($body,$headers=false,$namespaces=array(),$style='rpc',$use='encoded'){
468 // TODO: add an option to automatically run utf8_encode on $body and $headers
469 // if $this->soap_defencoding is UTF-8. Not doing this automatically allows
470 // one to send arbitrary UTF-8 characters, not just characters that map to ISO-8859-1
472 // serialize namespaces
474 foreach(array_merge($this->namespaces,$namespaces) as $k => $v){
475 $ns_string .= " xmlns:$k=\"$v\"";
477 if($style == 'rpc' && $use == 'encoded') {
478 $ns_string = ' SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"' . $ns_string;
483 $headers = "<SOAP-ENV:Header>".$headers."</SOAP-ENV:Header>";
485 // serialize envelope
487 '<?xml version="1.0" encoding="'.$this->soap_defencoding .'"?'.">".
488 '<SOAP-ENV:Envelope'.$ns_string.">".
493 "</SOAP-ENV:Envelope>";
496 function formatDump($str){
497 $str = htmlspecialchars($str);
502 * contracts a qualified name
504 * @param string $string qname
505 * @return string contracted qname
508 function contractQname($qname){
509 // get element namespace
510 //$this->xdebug("Contract $qname");
511 if (strrpos($qname, ':')) {
512 // get unqualified name
513 $name = substr($qname, strrpos($qname, ':') + 1);
515 $ns = substr($qname, 0, strrpos($qname, ':'));
516 $p = $this->getPrefixFromNamespace($ns);
518 return $p . ':' . $name;
527 * expands a qualified name
529 * @param string $string qname
530 * @return string expanded qname
533 function expandQname($qname){
534 // get element prefix
535 if(strpos($qname,':') && !ereg('^http://',$qname)){
536 // get unqualified name
537 $name = substr(strstr($qname,':'),1);
539 $prefix = substr($qname,0,strpos($qname,':'));
540 if(isset($this->namespaces[$prefix])){
541 return $this->namespaces[$prefix].':'.$name;
551 * returns the local part of a prefixed string
552 * returns the original string, if not prefixed
558 function getLocalPart($str){
559 if($sstr = strrchr($str,':')){
560 // get unqualified name
561 return substr( $sstr, 1 );
568 * returns the prefix part of a prefixed string
569 * returns false, if not prefixed
575 function getPrefix($str){
576 if($pos = strrpos($str,':')){
578 return substr($str,0,$pos);
584 * pass it a prefix, it returns a namespace
585 * returns false if no namespace registered with the given prefix
591 function getNamespaceFromPrefix($prefix){
592 if (isset($this->namespaces[$prefix])) {
593 return $this->namespaces[$prefix];
595 //$this->setError("No namespace registered for prefix '$prefix'");
600 * returns the prefix for a given namespace (or prefix)
601 * or false if no prefixes registered for the given namespace
607 function getPrefixFromNamespace($ns) {
608 foreach ($this->namespaces as $p => $n) {
609 if ($ns == $n || $ns == $p) {
610 $this->usedNamespaces[$p] = $n;
618 * returns the time in ODBC canonical form with microseconds
623 function getmicrotime() {
624 if (function_exists('gettimeofday')) {
625 $tod = gettimeofday();
627 $usec = $tod['usec'];
632 return strftime('%Y-%m-%d %H:%M:%S', $sec) . '.' . sprintf('%06d', $usec);
635 function varDump($data) {
638 $ret_val = ob_get_contents();
644 // XML Schema Datatype Helper Functions
646 //xsd:dateTime helpers
649 * convert unix timestamp to ISO 8601 compliant date string
651 * @param string $timestamp Unix time stamp
654 function timestamp_to_iso8601($timestamp,$utc=true){
655 $datestr = date('Y-m-d\TH:i:sO',$timestamp);
658 '([0-9]{4})-'. // centuries & years CCYY-
659 '([0-9]{2})-'. // months MM-
660 '([0-9]{2})'. // days DD
662 '([0-9]{2}):'. // hours hh:
663 '([0-9]{2}):'. // minutes mm:
664 '([0-9]{2})(\.[0-9]*)?'. // seconds ss.ss...
665 '(Z|[+\-][0-9]{2}:?[0-9]{2})?'; // Z to indicate UTC, -/+HH:MM:SS.SS... for local tz's
667 if(ereg($eregStr,$datestr,$regs)){
668 return sprintf('%04d-%02d-%02dT%02d:%02d:%02dZ',$regs[1],$regs[2],$regs[3],$regs[4],$regs[5],$regs[6]);
677 * convert ISO 8601 compliant date string to unix timestamp
679 * @param string $datestr ISO 8601 compliant date string
682 function iso8601_to_timestamp($datestr){
684 '([0-9]{4})-'. // centuries & years CCYY-
685 '([0-9]{2})-'. // months MM-
686 '([0-9]{2})'. // days DD
688 '([0-9]{2}):'. // hours hh:
689 '([0-9]{2}):'. // minutes mm:
690 '([0-9]{2})(\.[0-9]+)?'. // seconds ss.ss...
691 '(Z|[+\-][0-9]{2}:?[0-9]{2})?'; // Z to indicate UTC, -/+HH:MM:SS.SS... for local tz's
692 if(ereg($eregStr,$datestr,$regs)){
695 $op = substr($regs[8],0,1);
696 $h = substr($regs[8],1,2);
697 $m = substr($regs[8],strlen($regs[8])-2,2);
699 $regs[4] = $regs[4] + $h;
700 $regs[5] = $regs[5] + $m;
701 } elseif($op == '+'){
702 $regs[4] = $regs[4] - $h;
703 $regs[5] = $regs[5] - $m;
706 return strtotime("$regs[1]-$regs[2]-$regs[3] $regs[4]:$regs[5]:$regs[6]Z");
712 function usleepWindows($usec)
714 $start = gettimeofday();
718 $stop = gettimeofday();
719 $timePassed = 1000000 * ($stop['sec'] - $start['sec'])
720 + $stop['usec'] - $start['usec'];
722 while ($timePassed < $usec);
730 * soap_fault class, allows for creation of faults
731 * mainly used for returning faults from deployed functions
732 * in a server instance.
733 * @author Dietrich Ayala <dietrich@ganx4.com>
737 class soap_fault extends nusoap_base {
747 * @param string $faultcode (client | server)
748 * @param string $faultactor only used when msg routed between multiple actors
749 * @param string $faultstring human readable error message
750 * @param string $faultdetail
752 function soap_fault($faultcode,$faultactor='',$faultstring='',$faultdetail=''){
753 $this->faultcode = $faultcode;
754 $this->faultactor = $faultactor;
755 $this->faultstring = $faultstring;
756 $this->faultdetail = $faultdetail;
764 function serialize(){
766 foreach($this->namespaces as $k => $v){
767 $ns_string .= "\n xmlns:$k=\"$v\"";
770 '<?xml version="1.0" encoding="'.$this->soap_defencoding.'"?>'.
771 '<SOAP-ENV:Envelope SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"'.$ns_string.">\n".
774 '<faultcode>'.$this->expandEntities($this->faultcode).'</faultcode>'.
775 '<faultactor>'.$this->expandEntities($this->faultactor).'</faultactor>'.
776 '<faultstring>'.$this->expandEntities($this->faultstring).'</faultstring>'.
777 '<detail>'.$this->serialize_val($this->faultdetail).'</detail>'.
780 '</SOAP-ENV:Envelope>';
792 * parses an XML Schema, allows access to it's data, other utility methods
793 * no validation... yet.
794 * very experimental and limited. As is discussed on XML-DEV, I'm one of the people
795 * that just doesn't have time to read the spec(s) thoroughly, and just have a couple of trusty
796 * tutorials I refer to :)
798 * @author Dietrich Ayala <dietrich@ganx4.com>
802 class XMLSchema extends nusoap_base {
808 var $enclosingNamespaces;
810 var $schemaInfo = array();
811 var $schemaTargetNamespace = '';
812 // types, elements, attributes defined by the schema
813 var $attributes = array();
814 var $complexTypes = array();
815 var $currentComplexType = false;
816 var $elements = array();
817 var $currentElement = false;
818 var $simpleTypes = array();
819 var $currentSimpleType = false;
821 var $imports = array();
826 var $depth_array = array();
827 var $message = array();
828 var $defaultNamespace = array();
833 * @param string $schema schema document URI
834 * @param string $xml xml document URI
835 * @param string $namespaces namespaces defined in enclosing XML
838 function XMLSchema($schema='',$xml='',$namespaces=array()){
840 $this->debug('xmlschema class instantiated, inside constructor');
842 $this->schema = $schema;
846 $this->enclosingNamespaces = $namespaces;
847 $this->namespaces = array_merge($this->namespaces, $namespaces);
851 $this->debug('initial schema file: '.$schema);
852 $this->parseFile($schema, 'schema');
857 $this->debug('initial xml file: '.$xml);
858 $this->parseFile($xml, 'xml');
866 * @param string $xml, path/URL to XML file
867 * @param string $type, (schema | xml)
871 function parseFile($xml,$type){
874 $xmlStr = @join("",@file($xml));
876 $msg = 'Error reading XML from '.$xml;
877 $this->setError($msg);
881 $this->debug("parsing $xml");
882 $this->parseString($xmlStr,$type);
883 $this->debug("done parsing $xml");
891 * parse an XML string
893 * @param string $xml path or URL
894 * @param string $type, (schema|xml)
897 function parseString($xml,$type){
901 // Create an XML parser.
902 $this->parser = xml_parser_create();
903 // Set the options for parsing the XML data.
904 xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING, 0);
906 // Set the object for the parser.
907 xml_set_object($this->parser, $this);
909 // Set the element handlers for the parser.
910 if($type == "schema"){
911 xml_set_element_handler($this->parser, 'schemaStartElement','schemaEndElement');
912 xml_set_character_data_handler($this->parser,'schemaCharacterData');
913 } elseif($type == "xml"){
914 xml_set_element_handler($this->parser, 'xmlStartElement','xmlEndElement');
915 xml_set_character_data_handler($this->parser,'xmlCharacterData');
918 // Parse the XML file.
919 if(!xml_parse($this->parser,$xml,true)){
920 // Display an error message.
921 $errstr = sprintf('XML error parsing XML schema on line %d: %s',
922 xml_get_current_line_number($this->parser),
923 xml_error_string(xml_get_error_code($this->parser))
925 $this->debug($errstr);
926 $this->debug("XML payload:\n" . $xml);
927 $this->setError($errstr);
930 xml_parser_free($this->parser);
932 $this->debug('no xml passed to parseString()!!');
933 $this->setError('no xml passed to parseString()!!');
938 * start-element handler
940 * @param string $parser XML parser object
941 * @param string $name element name
942 * @param string $attrs associative array of attributes
945 function schemaStartElement($parser, $name, $attrs) {
947 // position in the total number of elements, starting from 0
948 $pos = $this->position++;
949 $depth = $this->depth++;
950 // set self as current value for this depth
951 $this->depth_array[$depth] = $pos;
952 $this->message[$pos] = array('cdata' => '');
954 $this->defaultNamespace[$pos] = $this->defaultNamespace[$this->depth_array[$depth - 1]];
956 $this->defaultNamespace[$pos] = false;
959 // get element prefix
960 if($prefix = $this->getPrefix($name)){
961 // get unqualified name
962 $name = $this->getLocalPart($name);
967 // loop thru attributes, expanding, and registering namespace declarations
968 if(count($attrs) > 0){
969 foreach($attrs as $k => $v){
970 // if ns declarations, add to class level array of valid namespaces
971 if(ereg("^xmlns",$k)){
972 //$this->xdebug("$k: $v");
973 //$this->xdebug('ns_prefix: '.$this->getPrefix($k));
974 if($ns_prefix = substr(strrchr($k,':'),1)){
975 //$this->xdebug("Add namespace[$ns_prefix] = $v");
976 $this->namespaces[$ns_prefix] = $v;
978 $this->defaultNamespace[$pos] = $v;
979 if (! $this->getPrefixFromNamespace($v)) {
980 $this->namespaces['ns'.(count($this->namespaces)+1)] = $v;
983 if($v == 'http://www.w3.org/2001/XMLSchema' || $v == 'http://www.w3.org/1999/XMLSchema'){
984 $this->XMLSchemaVersion = $v;
985 $this->namespaces['xsi'] = $v.'-instance';
989 foreach($attrs as $k => $v){
990 // expand each attribute
991 $k = strpos($k,':') ? $this->expandQname($k) : $k;
992 $v = strpos($v,':') ? $this->expandQname($v) : $v;
999 // find status, register data
1001 case 'all': // (optional) compositor content for a complexType
1005 //$this->xdebug("compositor $name for currentComplexType: $this->currentComplexType and currentElement: $this->currentElement");
1006 $this->complexTypes[$this->currentComplexType]['compositor'] = $name;
1007 if($name == 'all' || $name == 'sequence'){
1008 $this->complexTypes[$this->currentComplexType]['phpType'] = 'struct';
1011 case 'attribute': // complexType attribute
1012 //$this->xdebug("parsing attribute $attrs[name] $attrs[ref] of value: ".$attrs['http://schemas.xmlsoap.org/wsdl/:arrayType']);
1013 $this->xdebug("parsing attribute:");
1014 $this->appendDebug($this->varDump($attrs));
1015 if (!isset($attrs['form'])) {
1016 $attrs['form'] = $this->schemaInfo['attributeFormDefault'];
1018 if (isset($attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'])) {
1019 $v = $attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'];
1020 if (!strpos($v, ':')) {
1021 // no namespace in arrayType attribute value...
1022 if ($this->defaultNamespace[$pos]) {
1023 // ...so use the default
1024 $attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'] = $this->defaultNamespace[$pos] . ':' . $attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'];
1028 if(isset($attrs['name'])){
1029 $this->attributes[$attrs['name']] = $attrs;
1030 $aname = $attrs['name'];
1031 } elseif(isset($attrs['ref']) && $attrs['ref'] == 'http://schemas.xmlsoap.org/soap/encoding/:arrayType'){
1032 if (isset($attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'])) {
1033 $aname = $attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'];
1037 } elseif(isset($attrs['ref'])){
1038 $aname = $attrs['ref'];
1039 $this->attributes[$attrs['ref']] = $attrs;
1042 if(isset($this->currentComplexType)){ // This should *always* be set
1043 $this->complexTypes[$this->currentComplexType]['attrs'][$aname] = $attrs;
1045 // arrayType attribute
1046 if(isset($attrs['http://schemas.xmlsoap.org/wsdl/:arrayType']) || $this->getLocalPart($aname) == 'arrayType'){
1047 $this->complexTypes[$this->currentComplexType]['phpType'] = 'array';
1048 $prefix = $this->getPrefix($aname);
1049 if(isset($attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'])){
1050 $v = $attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'];
1054 if(strpos($v,'[,]')){
1055 $this->complexTypes[$this->currentComplexType]['multidimensional'] = true;
1057 $v = substr($v,0,strpos($v,'[')); // clip the []
1058 if(!strpos($v,':') && isset($this->typemap[$this->XMLSchemaVersion][$v])){
1059 $v = $this->XMLSchemaVersion.':'.$v;
1061 $this->complexTypes[$this->currentComplexType]['arrayType'] = $v;
1064 case 'complexContent': // (optional) content for a complexType
1067 if(isset($attrs['name'])){
1068 $this->xdebug('processing named complexType '.$attrs['name']);
1069 //$this->currentElement = false;
1070 $this->currentComplexType = $attrs['name'];
1071 $this->complexTypes[$this->currentComplexType] = $attrs;
1072 $this->complexTypes[$this->currentComplexType]['typeClass'] = 'complexType';
1073 if(isset($attrs['base']) && ereg(':Array$',$attrs['base'])){
1074 $this->complexTypes[$this->currentComplexType]['phpType'] = 'array';
1076 $this->complexTypes[$this->currentComplexType]['phpType'] = 'struct';
1079 $this->xdebug('processing unnamed complexType for element '.$this->currentElement);
1080 $this->currentComplexType = $this->currentElement . '_ContainedType';
1081 //$this->currentElement = false;
1082 $this->complexTypes[$this->currentComplexType] = $attrs;
1083 $this->complexTypes[$this->currentComplexType]['typeClass'] = 'complexType';
1084 if(isset($attrs['base']) && ereg(':Array$',$attrs['base'])){
1085 $this->complexTypes[$this->currentComplexType]['phpType'] = 'array';
1087 $this->complexTypes[$this->currentComplexType]['phpType'] = 'struct';
1092 // elements defined as part of a complex type should
1093 // not really be added to $this->elements, but for some
1095 if (!isset($attrs['form'])) {
1096 $attrs['form'] = $this->schemaInfo['elementFormDefault'];
1098 if(isset($attrs['type'])){
1099 $this->xdebug("processing typed element ".$attrs['name']." of type ".$attrs['type']);
1100 if (! $this->getPrefix($attrs['type'])) {
1101 if ($this->defaultNamespace[$pos]) {
1102 $attrs['type'] = $this->defaultNamespace[$pos] . ':' . $attrs['type'];
1103 $this->xdebug('used default namespace to make type ' . $attrs['type']);
1106 $this->currentElement = $attrs['name'];
1107 $this->elements[ $attrs['name'] ] = $attrs;
1108 $this->elements[ $attrs['name'] ]['typeClass'] = 'element';
1109 $ename = $attrs['name'];
1110 } elseif(isset($attrs['ref'])){
1111 $this->xdebug("processing element as ref to ".$attrs['ref']);
1112 $ename = $this->getLocalPart($attrs['ref']);
1114 $this->xdebug("processing untyped element ".$attrs['name']);
1115 $this->currentElement = $attrs['name'];
1116 $this->elements[ $attrs['name'] ] = $attrs;
1117 $this->elements[ $attrs['name'] ]['typeClass'] = 'element';
1118 $attrs['type'] = $this->schemaTargetNamespace . ':' . $attrs['name'] . '_ContainedType';
1119 $this->elements[ $attrs['name'] ]['type'] = $attrs['type'];
1120 $ename = $attrs['name'];
1122 if(isset($ename) && $this->currentComplexType){
1123 $this->complexTypes[$this->currentComplexType]['elements'][$ename] = $attrs;
1126 case 'enumeration': // restriction value list member
1128 case 'extension': // simpleContent or complexContent type extension
1129 $this->xdebug('extension ' . $attrs['base']);
1130 if ($this->currentComplexType) {
1131 $this->complexTypes[$this->currentComplexType]['extensionBase'] = $attrs['base'];
1135 if (isset($attrs['schemaLocation'])) {
1136 //$this->xdebug('import namespace ' . $attrs['namespace'] . ' from ' . $attrs['schemaLocation']);
1137 $this->imports[$attrs['namespace']][] = array('location' => $attrs['schemaLocation'], 'loaded' => false);
1139 //$this->xdebug('import namespace ' . $attrs['namespace']);
1140 $this->imports[$attrs['namespace']][] = array('location' => '', 'loaded' => true);
1141 if (! $this->getPrefixFromNamespace($attrs['namespace'])) {
1142 $this->namespaces['ns'.(count($this->namespaces)+1)] = $attrs['namespace'];
1146 case 'list': // simpleType value list
1148 case 'restriction': // simpleType, simpleContent or complexContent value restriction
1149 $this->xdebug('restriction ' . $attrs['base']);
1150 if($this->currentSimpleType){
1151 $this->simpleTypes[$this->currentSimpleType]['type'] = $attrs['base'];
1152 } elseif($this->currentComplexType){
1153 $this->complexTypes[$this->currentComplexType]['restrictionBase'] = $attrs['base'];
1154 if(strstr($attrs['base'],':') == ':Array'){
1155 $this->complexTypes[$this->currentComplexType]['phpType'] = 'array';
1160 $this->schemaInfo = $attrs;
1161 $this->schemaInfo['schemaVersion'] = $this->getNamespaceFromPrefix($prefix);
1162 if (isset($attrs['targetNamespace'])) {
1163 $this->schemaTargetNamespace = $attrs['targetNamespace'];
1165 if (!isset($attrs['elementFormDefault'])) {
1166 $this->schemaInfo['elementFormDefault'] = 'unqualified';
1168 if (!isset($attrs['attributeFormDefault'])) {
1169 $this->schemaInfo['attributeFormDefault'] = 'unqualified';
1172 case 'simpleContent': // (optional) content for a complexType
1175 if(isset($attrs['name'])){
1176 $this->xdebug("processing simpleType for name " . $attrs['name']);
1177 $this->currentSimpleType = $attrs['name'];
1178 $this->simpleTypes[ $attrs['name'] ] = $attrs;
1179 $this->simpleTypes[ $attrs['name'] ]['typeClass'] = 'simpleType';
1180 $this->simpleTypes[ $attrs['name'] ]['phpType'] = 'scalar';
1182 $this->xdebug('processing unnamed simpleType for element '.$this->currentElement);
1183 $this->currentSimpleType = $this->currentElement . '_ContainedType';
1184 //$this->currentElement = false;
1185 $this->simpleTypes[$this->currentSimpleType] = $attrs;
1186 $this->simpleTypes[$this->currentSimpleType]['phpType'] = 'scalar';
1189 case 'union': // simpleType type list
1192 //$this->xdebug("do not have anything to do for element $name");
1197 * end-element handler
1199 * @param string $parser XML parser object
1200 * @param string $name element name
1203 function schemaEndElement($parser, $name) {
1204 // bring depth down a notch
1206 // position of current element is equal to the last value left in depth_array for my depth
1207 if(isset($this->depth_array[$this->depth])){
1208 $pos = $this->depth_array[$this->depth];
1210 // get element prefix
1211 if ($prefix = $this->getPrefix($name)){
1212 // get unqualified name
1213 $name = $this->getLocalPart($name);
1218 // TODO: pop stacks here, since these things can be encapsulated recursively
1219 if($name == 'complexType'){
1220 $this->xdebug('done processing complexType ' . ($this->currentComplexType ? $this->currentComplexType : '(unknown)'));
1221 $this->currentComplexType = false;
1222 //$this->currentElement = false;
1224 if($name == 'element'){
1225 $this->xdebug('done processing element ' . ($this->currentElement ? $this->currentElement : '(unknown)'));
1226 $this->currentElement = false;
1228 if($name == 'simpleType'){
1229 $this->xdebug('done processing simpleType ' . ($this->currentSimpleType ? $this->currentSimpleType : '(unknown)'));
1230 $this->currentSimpleType = false;
1235 * element content handler
1237 * @param string $parser XML parser object
1238 * @param string $data element content
1241 function schemaCharacterData($parser, $data){
1242 $pos = $this->depth_array[$this->depth - 1];
1243 $this->message[$pos]['cdata'] .= $data;
1247 * serialize the schema
1251 function serializeSchema(){
1253 $schemaPrefix = $this->getPrefixFromNamespace($this->XMLSchemaVersion);
1256 if (sizeof($this->imports) > 0) {
1257 foreach($this->imports as $ns => $list) {
1258 foreach ($list as $ii) {
1259 if ($ii['location'] != '') {
1260 $xml .= " <$schemaPrefix:import location=\"" . $ii['location'] . '" namespace="' . $ns . "\" />\n";
1262 $xml .= " <$schemaPrefix:import namespace=\"" . $ns . "\" />\n";
1268 foreach($this->complexTypes as $typeName => $attrs){
1270 // serialize child elements
1271 if(isset($attrs['elements']) && (count($attrs['elements']) > 0)){
1272 foreach($attrs['elements'] as $element => $eParts){
1273 if(isset($eParts['ref'])){
1274 $contentStr .= " <$schemaPrefix:element ref=\"$element\"/>\n";
1276 $contentStr .= " <$schemaPrefix:element name=\"$element\" type=\"" . $this->contractQName($eParts['type']) . "\"";
1277 foreach ($eParts as $aName => $aValue) {
1278 // handle, e.g., abstract, default, form, minOccurs, maxOccurs, nillable
1279 if ($aName != 'name' && $aName != 'type') {
1280 $contentStr .= " $aName=\"$aValue\"";
1283 $contentStr .= "/>\n";
1288 if(isset($attrs['attrs']) && (count($attrs['attrs']) >= 1)){
1289 foreach($attrs['attrs'] as $attr => $aParts){
1290 $contentStr .= " <$schemaPrefix:attribute ref=\"".$this->contractQName($aParts['ref']).'"';
1291 if(isset($aParts['http://schemas.xmlsoap.org/wsdl/:arrayType'])){
1292 $this->usedNamespaces['wsdl'] = $this->namespaces['wsdl'];
1293 $contentStr .= ' wsdl:arrayType="'.$this->contractQName($aParts['http://schemas.xmlsoap.org/wsdl/:arrayType']).'"';
1295 $contentStr .= "/>\n";
1299 if( isset($attrs['restrictionBase']) && $attrs['restrictionBase'] != ''){
1300 $contentStr = " <$schemaPrefix:restriction base=\"".$this->contractQName($attrs['restrictionBase'])."\">\n".$contentStr." </$schemaPrefix:restriction>\n";
1302 // compositor obviates complex/simple content
1303 if(isset($attrs['compositor']) && ($attrs['compositor'] != '')){
1304 $contentStr = " <$schemaPrefix:$attrs[compositor]>\n".$contentStr." </$schemaPrefix:$attrs[compositor]>\n";
1306 // complex or simple content
1307 elseif((isset($attrs['elements']) && count($attrs['elements']) > 0) || (isset($attrs['attrs']) && count($attrs['attrs']) > 0)){
1308 $contentStr = " <$schemaPrefix:complexContent>\n".$contentStr." </$schemaPrefix:complexContent>\n";
1310 // finalize complex type
1311 if($contentStr != ''){
1312 $contentStr = " <$schemaPrefix:complexType name=\"$typeName\">\n".$contentStr." </$schemaPrefix:complexType>\n";
1314 $contentStr = " <$schemaPrefix:complexType name=\"$typeName\"/>\n";
1316 $xml .= $contentStr;
1319 if(isset($this->simpleTypes) && count($this->simpleTypes) > 0){
1320 foreach($this->simpleTypes as $typeName => $attr){
1321 $xml .= " <$schemaPrefix:simpleType name=\"$typeName\">\n <$schemaPrefix:restriction base=\"".$this->contractQName($eParts['type'])."\"/>\n </$schemaPrefix:simpleType>";
1325 if(isset($this->elements) && count($this->elements) > 0){
1326 foreach($this->elements as $element => $eParts){
1327 $xml .= " <$schemaPrefix:element name=\"$element\" type=\"".$this->contractQName($eParts['type'])."\"/>\n";
1331 if(isset($this->attributes) && count($this->attributes) > 0){
1332 foreach($this->attributes as $attr => $aParts){
1333 $xml .= " <$schemaPrefix:attribute name=\"$attr\" type=\"".$this->contractQName($aParts['type'])."\"\n/>";
1337 $el = "<$schemaPrefix:schema targetNamespace=\"$this->schemaTargetNamespace\"\n";
1338 foreach (array_diff($this->usedNamespaces, $this->enclosingNamespaces) as $nsp => $ns) {
1339 $el .= " xmlns:$nsp=\"$ns\"\n";
1341 $xml = $el . ">\n".$xml."</$schemaPrefix:schema>\n";
1346 * adds debug data to the clas level debug string
1348 * @param string $string debug data
1351 function xdebug($string){
1352 $this->debug('<' . $this->schemaTargetNamespace . '> '.$string);
1356 * get the PHP type of a user defined type in the schema
1357 * PHP type is kind of a misnomer since it actually returns 'struct' for assoc. arrays
1358 * returns false if no type exists, or not w/ the given namespace
1359 * else returns a string that is either a native php type, or 'struct'
1361 * @param string $type, name of defined type
1362 * @param string $ns, namespace of type
1366 function getPHPType($type,$ns){
1367 if(isset($this->typemap[$ns][$type])){
1368 //print "found type '$type' and ns $ns in typemap<br>";
1369 return $this->typemap[$ns][$type];
1370 } elseif(isset($this->complexTypes[$type])){
1371 //print "getting type '$type' and ns $ns from complexTypes array<br>";
1372 return $this->complexTypes[$type]['phpType'];
1378 * returns an array of information about a given type
1379 * returns false if no type exists by the given name
1382 * 'elements' => array(), // refs to elements array
1383 * 'restrictionBase' => '',
1385 * 'order' => '(sequence|all)',
1386 * 'attrs' => array() // refs to attributes array
1393 function getTypeDef($type){
1394 //$this->debug("in getTypeDef for type $type");
1395 if(isset($this->complexTypes[$type])){
1396 $this->xdebug("in getTypeDef, found complexType $type");
1397 return $this->complexTypes[$type];
1398 } elseif(isset($this->simpleTypes[$type])){
1399 $this->xdebug("in getTypeDef, found simpleType $type");
1400 if (!isset($this->simpleTypes[$type]['phpType'])) {
1401 // get info for type to tack onto the simple type
1402 // TODO: can this ever really apply (i.e. what is a simpleType really?)
1403 $uqType = substr($this->simpleTypes[$type]['type'], strrpos($this->simpleTypes[$type]['type'], ':') + 1);
1404 $ns = substr($this->simpleTypes[$type]['type'], 0, strrpos($this->simpleTypes[$type]['type'], ':'));
1405 $etype = $this->getTypeDef($uqType);
1407 if (isset($etype['phpType'])) {
1408 $this->simpleTypes[$type]['phpType'] = $etype['phpType'];
1410 if (isset($etype['elements'])) {
1411 $this->simpleTypes[$type]['elements'] = $etype['elements'];
1415 return $this->simpleTypes[$type];
1416 } elseif(isset($this->elements[$type])){
1417 $this->xdebug("in getTypeDef, found element $type");
1418 if (!isset($this->elements[$type]['phpType'])) {
1419 // get info for type to tack onto the element
1420 $uqType = substr($this->elements[$type]['type'], strrpos($this->elements[$type]['type'], ':') + 1);
1421 $ns = substr($this->elements[$type]['type'], 0, strrpos($this->elements[$type]['type'], ':'));
1422 $etype = $this->getTypeDef($uqType);
1424 if (isset($etype['phpType'])) {
1425 $this->elements[$type]['phpType'] = $etype['phpType'];
1427 if (isset($etype['elements'])) {
1428 $this->elements[$type]['elements'] = $etype['elements'];
1430 } elseif ($ns == 'http://www.w3.org/2001/XMLSchema') {
1431 $this->elements[$type]['phpType'] = 'scalar';
1434 return $this->elements[$type];
1435 } elseif(isset($this->attributes[$type])){
1436 $this->xdebug("in getTypeDef, found attribute $type");
1437 return $this->attributes[$type];
1438 } elseif (ereg('_ContainedType$', $type)) {
1439 $this->xdebug("in getTypeDef, have an untyped element $type");
1440 $typeDef['typeClass'] = 'simpleType';
1441 $typeDef['phpType'] = 'scalar';
1442 $typeDef['type'] = 'http://www.w3.org/2001/XMLSchema:string';
1445 $this->xdebug("in getTypeDef, did not find $type");
1450 * returns a sample serialization of a given type, or false if no type by the given name
1452 * @param string $type, name of type
1456 function serializeTypeDef($type){
1457 //print "in sTD() for type $type<br>";
1458 if($typeDef = $this->getTypeDef($type)){
1460 if(is_array($typeDef['attrs'])){
1461 foreach($attrs as $attName => $data){
1462 $str .= ' $attName="{type = '.$data['type'].'}"';
1465 $str .= ' xmlns="'.$this->schema['targetNamespace'].'"';
1466 if(count($typeDef['elements']) > 0){
1468 foreach($typeDef['elements'] as $element => $eData){
1469 $str .= $this->serializeTypeDef($element);
1472 } elseif($typeDef['typeClass'] == 'element') {
1473 $str .= "></$type>";
1483 * returns HTML form elements that allow a user
1484 * to enter values for creating an instance of the given type.
1486 * @param string $name, name for type instance
1487 * @param string $type, name of type
1491 function typeToForm($name,$type){
1493 if($typeDef = $this->getTypeDef($type)){
1495 if($typeDef['phpType'] == 'struct'){
1496 $buffer .= '<table>';
1497 foreach($typeDef['elements'] as $child => $childDef){
1499 <tr><td align='right'>$childDef[name] (type: ".$this->getLocalPart($childDef['type'])."):</td>
1500 <td><input type='text' name='parameters[".$name."][$childDef[name]]'></td></tr>";
1502 $buffer .= '</table>';
1504 } elseif($typeDef['phpType'] == 'array'){
1505 $buffer .= '<table>';
1506 for($i=0;$i < 3; $i++){
1508 <tr><td align='right'>array item (type: $typeDef[arrayType]):</td>
1509 <td><input type='text' name='parameters[".$name."][]'></td></tr>";
1511 $buffer .= '</table>';
1514 $buffer .= "<input type='text' name='parameters[$name]'>";
1517 $buffer .= "<input type='text' name='parameters[$name]'>";
1523 * adds a complex type to the schema
1533 * array('ref'=>'SOAP-ENC:arrayType','wsdl:arrayType'=>'string[]'),
1537 * example: PHP associative array ( SOAP Struct )
1544 * array('myVar'=> array('name'=>'myVar','type'=>'string')
1548 * @param typeClass (complexType|simpleType|attribute)
1549 * @param phpType: currently supported are array and struct (php assoc array)
1550 * @param compositor (all|sequence|choice)
1551 * @param restrictionBase namespace:name (http://schemas.xmlsoap.org/soap/encoding/:Array)
1552 * @param elements = array ( name = array(name=>'',type=>'') )
1553 * @param attrs = array(
1555 * 'ref' => "http://schemas.xmlsoap.org/soap/encoding/:arrayType",
1556 * "http://schemas.xmlsoap.org/wsdl/:arrayType" => "string[]"
1559 * @param arrayType: namespace:name (http://www.w3.org/2001/XMLSchema:string)
1562 function addComplexType($name,$typeClass='complexType',$phpType='array',$compositor='',$restrictionBase='',$elements=array(),$attrs=array(),$arrayType=''){
1563 $this->complexTypes[$name] = array(
1565 'typeClass' => $typeClass,
1566 'phpType' => $phpType,
1567 'compositor'=> $compositor,
1568 'restrictionBase' => $restrictionBase,
1569 'elements' => $elements,
1571 'arrayType' => $arrayType
1574 $this->xdebug("addComplexType $name:");
1575 $this->appendDebug($this->varDump($this->complexTypes[$name]));
1579 * adds a simple type to the schema
1582 * @param restrictionBase namespace:name (http://schemas.xmlsoap.org/soap/encoding/:Array)
1583 * @param typeClass (simpleType)
1584 * @param phpType: (scalar)
1588 function addSimpleType($name, $restrictionBase='', $typeClass='simpleType', $phpType='scalar') {
1589 $this->simpleTypes[$name] = array(
1591 'typeClass' => $typeClass,
1592 'phpType' => $phpType,
1593 'type' => $restrictionBase
1596 $this->xdebug("addSimpleType $name:");
1597 $this->appendDebug($this->varDump($this->simpleTypes[$name]));
1601 * adds an element to the schema
1604 * @param array $attrs attributes that must include name and type
1607 function addElement($attrs) {
1608 if (! $this->getPrefix($attrs['type'])) {
1609 $attrs['type'] = $this->schemaTargetNamespace . ':' . $attrs['type'];
1611 $this->elements[ $attrs['name'] ] = $attrs;
1612 $this->elements[ $attrs['name'] ]['typeClass'] = 'element';
1614 $this->xdebug("addElement " . $attrs['name']);
1615 $this->appendDebug($this->varDump($this->elements[ $attrs['name'] ]));
1626 * for creating serializable abstractions of native PHP types
1627 * NOTE: this is only really used when WSDL is not available.
1629 * @author Dietrich Ayala <dietrich@ganx4.com>
1633 class soapval extends nusoap_base {
1637 * @param string $name optional name
1638 * @param string $type optional type name
1639 * @param mixed $value optional value
1640 * @param string $namespace optional namespace of value
1641 * @param string $type_namespace optional namespace of type
1642 * @param array $attributes associative array of attributes to add to element serialization
1645 function soapval($name='soapval',$type=false,$value=-1,$element_ns=false,$type_ns=false,$attributes=false) {
1646 $this->name = $name;
1647 $this->value = $value;
1648 $this->type = $type;
1649 $this->element_ns = $element_ns;
1650 $this->type_ns = $type_ns;
1651 $this->attributes = $attributes;
1655 * return serialized value
1657 * @return string XML data
1660 function serialize($use='encoded') {
1661 return $this->serialize_val($this->value,$this->name,$this->type,$this->element_ns,$this->type_ns,$this->attributes,$use);
1665 * decodes a soapval object into a PHP native type
1667 * @param object $soapval optional SOAPx4 soapval object, else uses self
1672 return $this->value;
1683 * transport class for sending/receiving data via HTTP and HTTPS
1684 * NOTE: PHP must be compiled with the CURL extension for HTTPS support
1686 * @author Dietrich Ayala <dietrich@ganx4.com>
1690 class soap_transport_http extends nusoap_base {
1694 var $digest_uri = '';
1699 var $request_method = 'POST';
1700 var $protocol_version = '1.0';
1702 var $outgoing_headers = array();
1703 var $incoming_headers = array();
1704 var $incoming_cookies = array();
1705 var $outgoing_payload = '';
1706 var $incoming_payload = '';
1707 var $useSOAPAction = true;
1708 var $persistentConnection = false;
1709 var $ch = false; // cURL handle
1713 var $digestRequest = array();
1714 var $certRequest = array(); // keys must be cainfofile, sslcertfile, sslkeyfile, passphrase
1715 // cainfofile: certificate authority file, e.g. '$pathToPemFiles/rootca.pem'
1716 // sslcertfile: SSL certificate file, e.g. '$pathToPemFiles/mycert.pem'
1717 // sslkeyfile: SSL key file, e.g. '$pathToPemFiles/mykey.pem'
1718 // passphrase: SSL key password/passphrase
1723 function soap_transport_http($url){
1724 $this->setURL($url);
1725 ereg('\$Revisio' . 'n: ([^ ]+)', $this->revision, $rev);
1726 $this->outgoing_headers['User-Agent'] = $this->title.'/'.$this->version.' ('.$rev[1].')';
1729 function setURL($url) {
1732 $u = parse_url($url);
1733 foreach($u as $k => $v){
1734 $this->debug("$k = $v");
1738 // add any GET params to path
1739 if(isset($u['query']) && $u['query'] != ''){
1740 $this->path .= '?' . $u['query'];
1744 if(!isset($u['port'])){
1745 if($u['scheme'] == 'https'){
1752 $this->uri = $this->path;
1753 $this->digest_uri = $this->uri;
1756 if (!isset($u['port'])) {
1757 $this->outgoing_headers['Host'] = $this->host;
1759 $this->outgoing_headers['Host'] = $this->host.':'.$this->port;
1762 if (isset($u['user']) && $u['user'] != '') {
1763 $this->setCredentials(urldecode($u['user']), isset($u['pass']) ? urldecode($u['pass']) : '');
1767 function connect($connection_timeout=0,$response_timeout=30){
1768 // For PHP 4.3 with OpenSSL, change https scheme to ssl, then treat like
1769 // "regular" socket.
1770 // TODO: disabled for now because OpenSSL must be *compiled* in (not just
1771 // loaded), and until PHP5 stream_get_wrappers is not available.
1772 // if ($this->scheme == 'https') {
1773 // if (version_compare(phpversion(), '4.3.0') >= 0) {
1774 // if (extension_loaded('openssl')) {
1775 // $this->scheme = 'ssl';
1776 // $this->debug('Using SSL over OpenSSL');
1780 $this->debug("connect connection_timeout $connection_timeout, response_timeout $response_timeout, scheme $this->scheme, host $this->host, port $this->port");
1781 if ($this->scheme == 'http' || $this->scheme == 'ssl') {
1782 // use persistent connection
1783 if($this->persistentConnection && isset($this->fp) && is_resource($this->fp)){
1784 if (!feof($this->fp)) {
1785 $this->debug('Re-use persistent connection');
1789 $this->debug('Closed persistent connection at EOF');
1792 // munge host if using OpenSSL
1793 if ($this->scheme == 'ssl') {
1794 $host = 'ssl://' . $this->host;
1796 $host = $this->host;
1798 $this->debug('calling fsockopen with host ' . $host);
1801 if($connection_timeout > 0){
1802 $this->fp = @fsockopen( $host, $this->port, $this->errno, $this->error_str, $connection_timeout);
1804 $this->fp = @fsockopen( $host, $this->port, $this->errno, $this->error_str);
1809 $msg = 'Couldn\'t open socket connection to server ' . $this->url;
1811 $msg .= ', Error ('.$this->errno.'): '.$this->error_str;
1813 $msg .= ' prior to connect(). This is often a problem looking up the host name.';
1816 $this->setError($msg);
1820 // set response timeout
1821 socket_set_timeout( $this->fp, $response_timeout);
1823 $this->debug('socket connected');
1825 } else if ($this->scheme == 'https') {
1826 if (!extension_loaded('curl')) {
1827 $this->setError('CURL Extension, or OpenSSL extension w/ PHP version >= 4.3 is required for HTTPS');
1830 $this->debug('connect using https');
1832 $this->ch = curl_init();
1834 $hostURL = ($this->port != '') ? "https://$this->host:$this->port" : "https://$this->host";
1836 $hostURL .= $this->path;
1837 curl_setopt($this->ch, CURLOPT_URL, $hostURL);
1838 // follow location headers (re-directs)
1839 curl_setopt($this->ch, CURLOPT_FOLLOWLOCATION, 1);
1840 // ask for headers in the response output
1841 curl_setopt($this->ch, CURLOPT_HEADER, 1);
1842 // ask for the response output as the return value
1843 curl_setopt($this->ch, CURLOPT_RETURNTRANSFER, 1);
1845 // We manage this ourselves through headers and encoding
1846 // if(function_exists('gzuncompress')){
1847 // curl_setopt($this->ch, CURLOPT_ENCODING, 'deflate');
1849 // persistent connection
1850 if ($this->persistentConnection) {
1851 // The way we send data, we cannot use persistent connections, since
1852 // there will be some "junk" at the end of our request.
1853 //curl_setopt($this->ch, CURL_HTTP_VERSION_1_1, true);
1854 $this->persistentConnection = false;
1855 $this->outgoing_headers['Connection'] = 'close';
1857 // set timeout (NOTE: cURL does not have separate connection and response timeouts)
1858 if ($connection_timeout != 0) {
1859 curl_setopt($this->ch, CURLOPT_TIMEOUT, $connection_timeout);
1862 // recent versions of cURL turn on peer/host checking by default,
1863 // while PHP binaries are not compiled with a default location for the
1864 // CA cert bundle, so disable peer/host checking.
1865 //curl_setopt($this->ch, CURLOPT_CAINFO, 'f:\php-4.3.2-win32\extensions\curl-ca-bundle.crt');
1866 curl_setopt($this->ch, CURLOPT_SSL_VERIFYPEER, 0);
1867 curl_setopt($this->ch, CURLOPT_SSL_VERIFYHOST, 0);
1869 // support client certificates (thanks Tobias Boes)
1870 if ($this->authtype == 'certificate') {
1871 curl_setopt($this->ch, CURLOPT_CAINFO, $certRequest['cainfofile']);
1872 curl_setopt($this->ch, CURLOPT_SSL_VERIFYPEER, 1);
1873 curl_setopt($this->ch, CURLOPT_SSL_VERIFYHOST, 1);
1874 curl_setopt($this->ch, CURLOPT_SSLCERT, $certRequest['sslcertfile']);
1875 curl_setopt($this->ch, CURLOPT_SSLKEY, $certRequest['sslkeyfile']);
1876 curl_setopt($this->ch, CURLOPT_SSLKEYPASSWD , $certRequest['passphrase']);
1878 $this->debug('cURL connection set up');
1881 $this->setError('Unknown scheme ' . $this->scheme);
1882 $this->debug('Unknown scheme ' . $this->scheme);
1888 * send the SOAP message via HTTP
1890 * @param string $data message data
1891 * @param integer $timeout set connection timeout in seconds
1892 * @param integer $response_timeout set response timeout in seconds
1893 * @param array $cookies cookies to send
1894 * @return string data
1897 function send($data, $timeout=0, $response_timeout=30, $cookies=NULL) {
1899 $this->debug('entered send() with data of length: '.strlen($data));
1901 $this->tryagain = true;
1903 while ($this->tryagain) {
1904 $this->tryagain = false;
1907 if (!$this->connect($timeout, $response_timeout)){
1912 if (!$this->sendRequest($data, $cookies)){
1917 $respdata = $this->getResponse();
1919 $this->setError('Too many tries to get an OK response');
1922 $this->debug('end of send()');
1928 * send the SOAP message via HTTPS 1.0 using CURL
1930 * @param string $msg message data
1931 * @param integer $timeout set connection timeout in seconds
1932 * @param integer $response_timeout set response timeout in seconds
1933 * @param array $cookies cookies to send
1934 * @return string data
1937 function sendHTTPS($data, $timeout=0, $response_timeout=30, $cookies) {
1938 return $this->send($data, $timeout, $response_timeout, $cookies);
1942 * if authenticating, set user credentials here
1944 * @param string $username
1945 * @param string $password
1946 * @param string $authtype (basic, digest, certificate)
1947 * @param array $digestRequest (keys must be nonce, nc, realm, qop)
1948 * @param array $certRequest (keys must be cainfofile, sslcertfile, sslkeyfile, passphrase: see corresponding options in cURL docs)
1951 function setCredentials($username, $password, $authtype = 'basic', $digestRequest = array(), $certRequest = array()) {
1954 $this->debug("Set credentials for authtype $authtype");
1956 if ($authtype == 'basic') {
1957 $this->outgoing_headers['Authorization'] = 'Basic '.base64_encode(str_replace(':','',$username).':'.$password);
1958 } elseif ($authtype == 'digest') {
1959 if (isset($digestRequest['nonce'])) {
1960 $digestRequest['nc'] = isset($digestRequest['nc']) ? $digestRequest['nc']++ : 1;
1962 // calculate the Digest hashes (calculate code based on digest implementation found at: http://www.rassoc.com/gregr/weblog/stories/2002/07/09/webServicesSecurityHttpDigestAuthenticationWithoutActiveDirectory.html)
1964 // A1 = unq(username-value) ":" unq(realm-value) ":" passwd
1965 $A1 = $username. ':' . $digestRequest['realm'] . ':' . $password;
1970 // A2 = Method ":" digest-uri-value
1971 $A2 = 'POST:' . $this->digest_uri;
1976 // KD(secret, data) = H(concat(secret, ":", data))
1978 // request-digest = <"> < KD ( H(A1), unq(nonce-value)
1980 // ":" unq(cnonce-value)
1981 // ":" unq(qop-value)
1984 // if qop is missing,
1985 // request-digest = <"> < KD ( H(A1), unq(nonce-value) ":" H(A2) ) > <">
1987 $unhashedDigest = '';
1988 $nonce = isset($digestRequest['nonce']) ? $digestRequest['nonce'] : '';
1990 if ($digestRequest['qop'] != '') {
1991 $unhashedDigest = $HA1 . ':' . $nonce . ':' . sprintf("%08d", $digestRequest['nc']) . ':' . $cnonce . ':' . $digestRequest['qop'] . ':' . $HA2;
1993 $unhashedDigest = $HA1 . ':' . $nonce . ':' . $HA2;
1996 $hashedDigest = md5($unhashedDigest);
1998 $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 . '"';
2000 } elseif ($authtype == 'certificate') {
2001 $this->certRequest = $certRequest;
2003 $this->username = $username;
2004 $this->password = $password;
2005 $this->authtype = $authtype;
2006 $this->digestRequest = $digestRequest;
2008 if (isset($this->outgoing_headers['Authorization'])) {
2009 $this->debug('Authorization header set: ' . substr($this->outgoing_headers['Authorization'], 0, 12) . '...');
2011 $this->debug('Authorization header not set');
2016 * set the soapaction value
2018 * @param string $soapaction
2021 function setSOAPAction($soapaction) {
2022 $this->outgoing_headers['SOAPAction'] = '"' . $soapaction . '"';
2028 * @param string $enc encoding style. supported values: gzip, deflate, or both
2031 function setEncoding($enc='gzip, deflate'){
2032 $this->protocol_version = '1.1';
2033 $this->outgoing_headers['Accept-Encoding'] = $enc;
2034 if (!isset($this->outgoing_headers['Connection'])) {
2035 $this->outgoing_headers['Connection'] = 'close';
2036 $this->persistentConnection = false;
2038 set_magic_quotes_runtime(0);
2040 $this->encoding = $enc;
2044 * set proxy info here
2046 * @param string $proxyhost
2047 * @param string $proxyport
2048 * @param string $proxyusername
2049 * @param string $proxypassword
2052 function setProxy($proxyhost, $proxyport, $proxyusername = '', $proxypassword = '') {
2053 $this->uri = $this->url;
2054 $this->host = $proxyhost;
2055 $this->port = $proxyport;
2056 if ($proxyusername != '' && $proxypassword != '') {
2057 $this->outgoing_headers['Proxy-Authorization'] = ' Basic '.base64_encode($proxyusername.':'.$proxypassword);
2062 * decode a string that is encoded w/ "chunked' transfer encoding
2063 * as defined in RFC2068 19.4.6
2065 * @param string $buffer
2071 function decodeChunked($buffer, $lb){
2076 // read chunk-size, chunk-extension (if any) and CRLF
2077 // get the position of the linebreak
2078 $chunkend = strpos($buffer, $lb);
2079 if ($chunkend == FALSE) {
2080 $this->debug('no linebreak found in decodeChunked');
2083 $temp = substr($buffer,0,$chunkend);
2084 $chunk_size = hexdec( trim($temp) );
2085 $chunkstart = $chunkend + strlen($lb);
2086 // while (chunk-size > 0) {
2087 while ($chunk_size > 0) {
2088 $this->debug("chunkstart: $chunkstart chunk_size: $chunk_size");
2089 $chunkend = strpos( $buffer, $lb, $chunkstart + $chunk_size);
2091 // Just in case we got a broken connection
2092 if ($chunkend == FALSE) {
2093 $chunk = substr($buffer,$chunkstart);
2094 // append chunk-data to entity-body
2096 $length += strlen($chunk);
2100 // read chunk-data and CRLF
2101 $chunk = substr($buffer,$chunkstart,$chunkend-$chunkstart);
2102 // append chunk-data to entity-body
2104 // length := length + chunk-size
2105 $length += strlen($chunk);
2106 // read chunk-size and CRLF
2107 $chunkstart = $chunkend + strlen($lb);
2109 $chunkend = strpos($buffer, $lb, $chunkstart) + strlen($lb);
2110 if ($chunkend == FALSE) {
2111 break; //Just in case we got a broken connection
2113 $temp = substr($buffer,$chunkstart,$chunkend-$chunkstart);
2114 $chunk_size = hexdec( trim($temp) );
2115 $chunkstart = $chunkend;
2121 * Writes payload, including HTTP headers, to $this->outgoing_payload.
2123 function buildPayload($data, $cookie_str = '') {
2124 // add content-length header
2125 $this->outgoing_headers['Content-Length'] = strlen($data);
2127 // start building outgoing payload:
2128 $req = "$this->request_method $this->uri HTTP/$this->protocol_version";
2129 $this->debug("HTTP request: $req");
2130 $this->outgoing_payload = "$req\r\n";
2132 // loop thru headers, serializing
2133 foreach($this->outgoing_headers as $k => $v){
2135 $this->debug("HTTP header: $hdr");
2136 $this->outgoing_payload .= "$hdr\r\n";
2140 if ($cookie_str != '') {
2141 $hdr = 'Cookie: '.$cookie_str;
2142 $this->debug("HTTP header: $hdr");
2143 $this->outgoing_payload .= "$hdr\r\n";
2146 // header/body separator
2147 $this->outgoing_payload .= "\r\n";
2150 $this->outgoing_payload .= $data;
2153 function sendRequest($data, $cookies = NULL) {
2154 // build cookie string
2155 $cookie_str = $this->getCookiesForRequest($cookies, (($this->scheme == 'ssl') || ($this->scheme == 'https')));
2158 $this->buildPayload($data, $cookie_str);
2160 if ($this->scheme == 'http' || $this->scheme == 'ssl') {
2162 if(!fputs($this->fp, $this->outgoing_payload, strlen($this->outgoing_payload))) {
2163 $this->setError('couldn\'t write message data to socket');
2164 $this->debug('couldn\'t write message data to socket');
2167 $this->debug('wrote data to socket, length = ' . strlen($this->outgoing_payload));
2169 } else if ($this->scheme == 'https') {
2171 // TODO: cURL does say this should only be the verb, and in fact it
2172 // turns out that the URI and HTTP version are appended to this, which
2173 // some servers refuse to work with
2174 //curl_setopt($this->ch, CURLOPT_CUSTOMREQUEST, $this->outgoing_payload);
2175 foreach($this->outgoing_headers as $k => $v){
2176 $curl_headers[] = "$k: $v";
2178 if ($cookie_str != '') {
2179 $curl_headers[] = 'Cookie: ' . $cookie_str;
2181 curl_setopt($this->ch, CURLOPT_HTTPHEADER, $curl_headers);
2182 if ($this->request_method == "POST") {
2183 curl_setopt($this->ch, CURLOPT_POST, 1);
2184 curl_setopt($this->ch, CURLOPT_POSTFIELDS, $data);
2187 $this->debug('set cURL payload');
2192 function getResponse(){
2193 $this->incoming_payload = '';
2195 if ($this->scheme == 'http' || $this->scheme == 'ssl') {
2196 // loop until headers have been retrieved
2198 while (!isset($lb)){
2200 // We might EOF during header read.
2201 if(feof($this->fp)) {
2202 $this->incoming_payload = $data;
2203 $this->debug('found no headers before EOF after length ' . strlen($data));
2204 $this->debug("received before EOF:\n" . $data);
2205 $this->setError('server failed to send headers');
2209 $tmp = fgets($this->fp, 256);
2210 $tmplen = strlen($tmp);
2211 $this->debug("read line of $tmplen bytes: " . trim($tmp));
2214 $this->incoming_payload = $data;
2215 $this->debug('socket read of headers timed out after length ' . strlen($data));
2216 $this->debug("read before timeout:\n" . $data);
2217 $this->setError('socket read of headers timed out');
2222 $pos = strpos($data,"\r\n\r\n");
2226 $pos = strpos($data,"\n\n");
2231 // remove 100 header
2232 if(isset($lb) && ereg('^HTTP/1.1 100',$data)){
2237 // store header data
2238 $this->incoming_payload .= $data;
2239 $this->debug('found end of headers after length ' . strlen($data));
2241 $header_data = trim(substr($data,0,$pos));
2242 $header_array = explode($lb,$header_data);
2243 $this->incoming_headers = array();
2244 $this->incoming_cookies = array();
2245 foreach($header_array as $header_line){
2246 $arr = explode(':',$header_line, 2);
2247 if(count($arr) > 1){
2248 $header_name = strtolower(trim($arr[0]));
2249 $this->incoming_headers[$header_name] = trim($arr[1]);
2250 if ($header_name == 'set-cookie') {
2251 // TODO: allow multiple cookies from parseCookie
2252 $cookie = $this->parseCookie(trim($arr[1]));
2254 $this->incoming_cookies[] = $cookie;
2255 $this->debug('found cookie: ' . $cookie['name'] . ' = ' . $cookie['value']);
2257 $this->debug('did not find cookie in ' . trim($arr[1]));
2260 } else if (isset($header_name)) {
2261 // append continuation line to previous header
2262 $this->incoming_headers[$header_name] .= $lb . ' ' . $header_line;
2266 // loop until msg has been received
2267 if (isset($this->incoming_headers['transfer-encoding']) && strtolower($this->incoming_headers['transfer-encoding']) == 'chunked') {
2268 $content_length = 2147483647; // ignore any content-length header
2270 $this->debug("want to read chunked content");
2271 } elseif (isset($this->incoming_headers['content-length'])) {
2272 $content_length = $this->incoming_headers['content-length'];
2274 $this->debug("want to read content of length $content_length");
2276 $content_length = 2147483647;
2278 $this->debug("want to read content to EOF");
2283 $tmp = fgets($this->fp, 256);
2284 $tmplen = strlen($tmp);
2285 $this->debug("read chunk line of $tmplen bytes");
2287 $this->incoming_payload = $data;
2288 $this->debug('socket read of chunk length timed out after length ' . strlen($data));
2289 $this->debug("read before timeout:\n" . $data);
2290 $this->setError('socket read of chunk length timed out');
2293 $content_length = hexdec(trim($tmp));
2294 $this->debug("chunk length $content_length");
2297 while (($strlen < $content_length) && (!feof($this->fp))) {
2298 $readlen = min(8192, $content_length - $strlen);
2299 $tmp = fread($this->fp, $readlen);
2300 $tmplen = strlen($tmp);
2301 $this->debug("read buffer of $tmplen bytes");
2302 if (($tmplen == 0) && (!feof($this->fp))) {
2303 $this->incoming_payload = $data;
2304 $this->debug('socket read of body timed out after length ' . strlen($data));
2305 $this->debug("read before timeout:\n" . $data);
2306 $this->setError('socket read of body timed out');
2312 if ($chunked && ($content_length > 0)) {
2313 $tmp = fgets($this->fp, 256);
2314 $tmplen = strlen($tmp);
2315 $this->debug("read chunk terminator of $tmplen bytes");
2317 $this->incoming_payload = $data;
2318 $this->debug('socket read of chunk terminator timed out after length ' . strlen($data));
2319 $this->debug("read before timeout:\n" . $data);
2320 $this->setError('socket read of chunk terminator timed out');
2324 } while ($chunked && ($content_length > 0) && (!feof($this->fp)));
2325 if (feof($this->fp)) {
2326 $this->debug('read to EOF');
2328 $this->debug('read body of length ' . strlen($data));
2329 $this->incoming_payload .= $data;
2330 $this->debug('received a total of '.strlen($this->incoming_payload).' bytes of data from server');
2332 // close filepointer
2334 (isset($this->incoming_headers['connection']) && strtolower($this->incoming_headers['connection']) == 'close') ||
2335 (! $this->persistentConnection) || feof($this->fp)){
2338 $this->debug('closed socket');
2341 // connection was closed unexpectedly
2342 if($this->incoming_payload == ''){
2343 $this->setError('no response from server');
2347 // decode transfer-encoding
2348 // if(isset($this->incoming_headers['transfer-encoding']) && strtolower($this->incoming_headers['transfer-encoding']) == 'chunked'){
2349 // if(!$data = $this->decodeChunked($data, $lb)){
2350 // $this->setError('Decoding of chunked data failed');
2353 //print "<pre>\nde-chunked:\n---------------\n$data\n\n---------------\n</pre>";
2354 // set decoded payload
2355 // $this->incoming_payload = $header_data.$lb.$lb.$data;
2358 } else if ($this->scheme == 'https') {
2360 $this->debug('send and receive with cURL');
2361 $this->incoming_payload = curl_exec($this->ch);
2362 $data = $this->incoming_payload;
2364 $cErr = curl_error($this->ch);
2366 $err = 'cURL ERROR: '.curl_errno($this->ch).': '.$cErr.'<br>';
2367 foreach(curl_getinfo($this->ch) as $k => $v){
2368 $err .= "$k: $v<br>";
2371 $this->setError($err);
2372 curl_close($this->ch);
2376 //var_dump(curl_getinfo($this->ch));
2380 $this->debug('No cURL error, closing cURL');
2381 curl_close($this->ch);
2383 // remove 100 header
2384 if (ereg('^HTTP/1.1 100',$data)) {
2385 if ($pos = strpos($data,"\r\n\r\n")) {
2386 $data = ltrim(substr($data,$pos));
2387 } elseif($pos = strpos($data,"\n\n") ) {
2388 $data = ltrim(substr($data,$pos));
2392 // separate content from HTTP headers
2393 if ($pos = strpos($data,"\r\n\r\n")) {
2395 } elseif( $pos = strpos($data,"\n\n")) {
2398 $this->debug('no proper separation of headers and document');
2399 $this->setError('no proper separation of headers and document');
2402 $header_data = trim(substr($data,0,$pos));
2403 $header_array = explode($lb,$header_data);
2404 $data = ltrim(substr($data,$pos));
2405 $this->debug('found proper separation of headers and document');
2406 $this->debug('cleaned data, stringlen: '.strlen($data));
2408 foreach ($header_array as $header_line) {
2409 $arr = explode(':',$header_line,2);
2410 if(count($arr) > 1){
2411 $header_name = strtolower(trim($arr[0]));
2412 $this->incoming_headers[$header_name] = trim($arr[1]);
2413 if ($header_name == 'set-cookie') {
2414 // TODO: allow multiple cookies from parseCookie
2415 $cookie = $this->parseCookie(trim($arr[1]));
2417 $this->incoming_cookies[] = $cookie;
2418 $this->debug('found cookie: ' . $cookie['name'] . ' = ' . $cookie['value']);
2420 $this->debug('did not find cookie in ' . trim($arr[1]));
2423 } else if (isset($header_name)) {
2424 // append continuation line to previous header
2425 $this->incoming_headers[$header_name] .= $lb . ' ' . $header_line;
2430 $arr = explode(' ', $header_array[0], 3);
2431 $http_version = $arr[0];
2432 $http_status = intval($arr[1]);
2433 $http_reason = count($arr) > 2 ? $arr[2] : '';
2435 // see if we need to resend the request with http digest authentication
2436 if (isset($this->incoming_headers['location']) && $http_status == 301) {
2437 $this->debug("Got 301 $http_reason with Location: " . $this->incoming_headers['location']);
2438 $this->setURL($this->incoming_headers['location']);
2439 $this->tryagain = true;
2443 // see if we need to resend the request with http digest authentication
2444 if (isset($this->incoming_headers['www-authenticate']) && $http_status == 401) {
2445 $this->debug("Got 401 $http_reason with WWW-Authenticate: " . $this->incoming_headers['www-authenticate']);
2446 if (substr("Digest ", $this->incoming_headers['www-authenticate'])) {
2447 $this->debug('Server wants digest authentication');
2448 // remove "Digest " from our elements
2449 $digestString = str_replace('Digest ', '', $this->incoming_headers['www-authenticate']);
2451 // parse elements into array
2452 $digestElements = explode(',', $digestString);
2453 foreach ($digestElements as $val) {
2454 $tempElement = explode('=', trim($val), 2);
2455 $digestRequest[$tempElement[0]] = str_replace("\"", '', $tempElement[1]);
2458 // should have (at least) qop, realm, nonce
2459 if (isset($digestRequest['nonce'])) {
2460 $this->setCredentials($this->username, $this->password, 'digest', $digestRequest);
2461 $this->tryagain = true;
2465 $this->debug('HTTP authentication failed');
2466 $this->setError('HTTP authentication failed');
2471 ($http_status >= 300 && $http_status <= 307) ||
2472 ($http_status >= 400 && $http_status <= 417) ||
2473 ($http_status >= 501 && $http_status <= 505)
2475 $this->setError("Unsupported HTTP response status $http_status $http_reason (soapclient->response has contents of the response)");
2479 // decode content-encoding
2480 if(isset($this->incoming_headers['content-encoding']) && $this->incoming_headers['content-encoding'] != ''){
2481 if(strtolower($this->incoming_headers['content-encoding']) == 'deflate' || strtolower($this->incoming_headers['content-encoding']) == 'gzip'){
2482 // if decoding works, use it. else assume data wasn't gzencoded
2483 if(function_exists('gzinflate')){
2484 //$timer->setMarker('starting decoding of gzip/deflated content');
2485 // IIS 5 requires gzinflate instead of gzuncompress (similar to IE 5 and gzdeflate v. gzcompress)
2486 // this means there are no Zlib headers, although there should be
2487 $this->debug('The gzinflate function exists');
2488 $datalen = strlen($data);
2489 if ($this->incoming_headers['content-encoding'] == 'deflate') {
2490 if ($degzdata = @gzinflate($data)) {
2492 $this->debug('The payload has been inflated to ' . strlen($data) . ' bytes');
2493 if (strlen($data) < $datalen) {
2494 // test for the case that the payload has been compressed twice
2495 $this->debug('The inflated payload is smaller than the gzipped one; try again');
2496 if ($degzdata = @gzinflate($data)) {
2498 $this->debug('The payload has been inflated again to ' . strlen($data) . ' bytes');
2502 $this->debug('Error using gzinflate to inflate the payload');
2503 $this->setError('Error using gzinflate to inflate the payload');
2505 } elseif ($this->incoming_headers['content-encoding'] == 'gzip') {
2506 if ($degzdata = @gzinflate(substr($data, 10))) { // do our best
2508 $this->debug('The payload has been un-gzipped to ' . strlen($data) . ' bytes');
2509 if (strlen($data) < $datalen) {
2510 // test for the case that the payload has been compressed twice
2511 $this->debug('The un-gzipped payload is smaller than the gzipped one; try again');
2512 if ($degzdata = @gzinflate(substr($data, 10))) {
2514 $this->debug('The payload has been un-gzipped again to ' . strlen($data) . ' bytes');
2518 $this->debug('Error using gzinflate to un-gzip the payload');
2519 $this->setError('Error using gzinflate to un-gzip the payload');
2522 //$timer->setMarker('finished decoding of gzip/deflated content');
2523 //print "<xmp>\nde-inflated:\n---------------\n$data\n-------------\n</xmp>";
2524 // set decoded payload
2525 $this->incoming_payload = $header_data.$lb.$lb.$data;
2527 $this->debug('The server sent compressed data. Your php install must have the Zlib extension compiled in to support this.');
2528 $this->setError('The server sent compressed data. Your php install must have the Zlib extension compiled in to support this.');
2531 $this->debug('Unsupported Content-Encoding ' . $this->incoming_headers['content-encoding']);
2532 $this->setError('Unsupported Content-Encoding ' . $this->incoming_headers['content-encoding']);
2535 $this->debug('No Content-Encoding header');
2538 if(strlen($data) == 0){
2539 $this->debug('no data after headers!');
2540 $this->setError('no data present after HTTP headers');
2547 function setContentType($type, $charset = false) {
2548 $this->outgoing_headers['Content-Type'] = $type . ($charset ? '; charset=' . $charset : '');
2551 function usePersistentConnection(){
2552 if (isset($this->outgoing_headers['Accept-Encoding'])) {
2555 $this->protocol_version = '1.1';
2556 $this->persistentConnection = true;
2557 $this->outgoing_headers['Connection'] = 'Keep-Alive';
2562 * parse an incoming Cookie into it's parts
2564 * @param string $cookie_str content of cookie
2565 * @return array with data of that cookie
2568 * TODO: allow a Set-Cookie string to be parsed into multiple cookies
2570 function parseCookie($cookie_str) {
2571 $cookie_str = str_replace('; ', ';', $cookie_str) . ';';
2572 $data = split(';', $cookie_str);
2573 $value_str = $data[0];
2575 $cookie_param = 'domain=';
2576 $start = strpos($cookie_str, $cookie_param);
2578 $domain = substr($cookie_str, $start + strlen($cookie_param));
2579 $domain = substr($domain, 0, strpos($domain, ';'));
2584 $cookie_param = 'expires=';
2585 $start = strpos($cookie_str, $cookie_param);
2587 $expires = substr($cookie_str, $start + strlen($cookie_param));
2588 $expires = substr($expires, 0, strpos($expires, ';'));
2593 $cookie_param = 'path=';
2594 $start = strpos($cookie_str, $cookie_param);
2596 $path = substr($cookie_str, $start + strlen($cookie_param));
2597 $path = substr($path, 0, strpos($path, ';'));
2602 $cookie_param = ';secure;';
2603 if (strpos($cookie_str, $cookie_param) !== FALSE) {
2609 $sep_pos = strpos($value_str, '=');
2612 $name = substr($value_str, 0, $sep_pos);
2613 $value = substr($value_str, $sep_pos + 1);
2614 $cookie= array( 'name' => $name,
2616 'domain' => $domain,
2618 'expires' => $expires,
2627 * sort out cookies for the current request
2629 * @param array $cookies array with all cookies
2630 * @param boolean $secure is the send-content secure or not?
2631 * @return string for Cookie-HTTP-Header
2634 function getCookiesForRequest($cookies, $secure=false) {
2636 if ((! is_null($cookies)) && (is_array($cookies))) {
2637 foreach ($cookies as $cookie) {
2638 if (! is_array($cookie)) {
2641 $this->debug("check cookie for validity: ".$cookie['name'].'='.$cookie['value']);
2642 if ((isset($cookie['expires'])) && (! empty($cookie['expires']))) {
2643 if (strtotime($cookie['expires']) <= time()) {
2644 $this->debug('cookie has expired');
2648 if ((isset($cookie['domain'])) && (! empty($cookie['domain']))) {
2649 $domain = preg_quote($cookie['domain']);
2650 if (! preg_match("'.*$domain$'i", $this->host)) {
2651 $this->debug('cookie has different domain');
2655 if ((isset($cookie['path'])) && (! empty($cookie['path']))) {
2656 $path = preg_quote($cookie['path']);
2657 if (! preg_match("'^$path.*'i", $this->path)) {
2658 $this->debug('cookie is for a different path');
2662 if ((! $secure) && (isset($cookie['secure'])) && ($cookie['secure'])) {
2663 $this->debug('cookie is secure, transport is not');
2666 $cookie_str .= $cookie['name'] . '=' . $cookie['value'] . '; ';
2667 $this->debug('add cookie to Cookie-String: ' . $cookie['name'] . '=' . $cookie['value']);
2680 * soap_server allows the user to create a SOAP server
2681 * that is capable of receiving messages and returning responses
2683 * NOTE: WSDL functionality is experimental
2685 * @author Dietrich Ayala <dietrich@ganx4.com>
2689 class soap_server extends nusoap_base {
2690 var $headers = array(); // HTTP headers of request
2691 var $request = ''; // HTTP request
2692 var $requestHeaders = ''; // SOAP headers from request (incomplete namespace resolution; special characters not escaped) (text)
2693 var $document = ''; // SOAP body request portion (incomplete namespace resolution; special characters not escaped) (text)
2694 var $requestSOAP = ''; // SOAP payload for request (text)
2695 var $methodURI = ''; // requested method namespace URI
2696 var $methodname = ''; // name of method requested
2697 var $methodparams = array(); // method parameters from request
2698 var $xml_encoding = ''; // character set encoding of incoming (request) messages
2699 var $SOAPAction = ''; // SOAP Action from request
2700 var $decode_utf8 = true; // toggles whether the parser decodes element content w/ utf8_decode()
2702 var $outgoing_headers = array();// HTTP headers of response
2703 var $response = ''; // HTTP response
2704 var $responseHeaders = ''; // SOAP headers for response (text)
2705 var $responseSOAP = ''; // SOAP payload for response (text)
2706 var $methodreturn = false; // method return to place in response
2707 var $methodreturnisliteralxml = false; // whether $methodreturn is a string of literal XML
2708 var $fault = false; // SOAP fault for response
2709 var $result = 'successful'; // text indication of result (for debugging)
2711 var $operations = array(); // assoc array of operations => opData
2712 var $wsdl = false; // wsdl instance
2713 var $externalWSDLURL = false; // URL for WSDL
2714 var $debug_flag = false; // whether to append debug to response as XML comment
2719 * the optional parameter is a path to a WSDL file that you'd like to bind the server instance to.
2721 * @param mixed $wsdl file path or URL (string), or wsdl instance (object)
2724 function soap_server($wsdl=false){
2726 // turn on debugging?
2730 global $HTTP_SERVER_VARS;
2732 if (isset($debug)) {
2733 $this->debug_flag = $debug;
2734 } else if (isset($_REQUEST['debug'])) {
2735 $this->debug_flag = $_REQUEST['debug'];
2736 } else if (isset($_SERVER['QUERY_STRING'])) {
2737 $qs = explode('&', $_SERVER['QUERY_STRING']);
2738 foreach ($qs as $v) {
2739 if (substr($v, 0, 6) == 'debug=') {
2740 $this->debug_flag = substr($v, 6);
2743 } else if (isset($HTTP_SERVER_VARS['QUERY_STRING'])) {
2744 $qs = explode('&', $HTTP_SERVER_VARS['QUERY_STRING']);
2745 foreach ($qs as $v) {
2746 if (substr($v, 0, 6) == 'debug=') {
2747 $this->debug_flag = substr($v, 6);
2754 if (is_object($wsdl) && is_a($wsdl, 'wsdl')) {
2755 $this->wsdl = $wsdl;
2756 $this->externalWSDLURL = $this->wsdl->wsdl;
2757 $this->debug('Use existing wsdl instance from ' . $this->externalWSDLURL);
2759 $this->debug('Create wsdl from ' . $wsdl);
2760 $this->wsdl = new wsdl($wsdl);
2761 $this->externalWSDLURL = $wsdl;
2763 $this->appendDebug($this->wsdl->getDebug());
2764 $this->wsdl->clearDebug();
2765 if($err = $this->wsdl->getError()){
2766 die('WSDL ERROR: '.$err);
2772 * processes request and returns response
2774 * @param string $data usually is the value of $HTTP_RAW_POST_DATA
2777 function service($data){
2778 global $QUERY_STRING;
2781 if(isset($_SERVER['QUERY_STRING'])){
2782 $qs = $_SERVER['QUERY_STRING'];
2783 } elseif(isset($GLOBALS['QUERY_STRING'])){
2784 $qs = $GLOBALS['QUERY_STRING'];
2785 } elseif(isset($QUERY_STRING) && $QUERY_STRING != ''){
2786 $qs = $QUERY_STRING;
2789 if(isset($qs) && ereg('wsdl', $qs) ){
2790 // This is a request for WSDL
2791 if($this->externalWSDLURL){
2792 if (strpos($this->externalWSDLURL,"://")!==false) { // assume URL
2793 header('Location: '.$this->externalWSDLURL);
2794 } else { // assume file
2795 header("Content-Type: text/xml\r\n");
2796 $fp = fopen($this->externalWSDLURL, 'r');
2799 } elseif ($this->wsdl) {
2800 header("Content-Type: text/xml; charset=ISO-8859-1\r\n");
2801 print $this->wsdl->serialize($this->debug_flag);
2803 header("Content-Type: text/html; charset=ISO-8859-1\r\n");
2804 print "This service does not provide WSDL";
2806 } elseif ($data == '' && $this->wsdl) {
2807 // print web interface
2808 print $this->wsdl->webDescription();
2810 // handle the request
2811 $this->parse_request($data);
2812 if (! $this->fault) {
2813 $this->invoke_method();
2815 if (! $this->fault) {
2816 $this->serialize_return();
2818 $this->send_response();
2823 * parses HTTP request headers.
2825 * The following fields are set by this function (when successful)
2834 function parse_http_headers() {
2835 global $HTTP_SERVER_VARS;
2838 $this->request = '';
2839 if(function_exists('getallheaders')){
2840 $this->headers = getallheaders();
2841 foreach($this->headers as $k=>$v){
2842 $this->request .= "$k: $v\r\n";
2843 $this->debug("$k: $v");
2845 // get SOAPAction header
2846 if(isset($this->headers['SOAPAction'])){
2847 $this->SOAPAction = str_replace('"','',$this->headers['SOAPAction']);
2849 // get the character encoding of the incoming request
2850 if(strpos($this->headers['Content-Type'],'=')){
2851 $enc = str_replace('"','',substr(strstr($this->headers["Content-Type"],'='),1));
2852 if(eregi('^(ISO-8859-1|US-ASCII|UTF-8)$',$enc)){
2853 $this->xml_encoding = strtoupper($enc);
2855 $this->xml_encoding = 'US-ASCII';
2858 // should be US-ASCII for HTTP 1.0 or ISO-8859-1 for HTTP 1.1
2859 $this->xml_encoding = 'ISO-8859-1';
2861 } elseif(isset($_SERVER) && is_array($_SERVER)){
2862 foreach ($_SERVER as $k => $v) {
2863 if (substr($k, 0, 5) == 'HTTP_') {
2864 $k = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($k, 5)))));
2866 $k = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', $k))));
2868 if ($k == 'Soapaction') {
2869 // get SOAPAction header
2871 $v = str_replace('"', '', $v);
2872 $v = str_replace('\\', '', $v);
2873 $this->SOAPAction = $v;
2874 } else if ($k == 'Content-Type') {
2875 // get the character encoding of the incoming request
2876 if (strpos($v, '=')) {
2877 $enc = substr(strstr($v, '='), 1);
2878 $enc = str_replace('"', '', $enc);
2879 $enc = str_replace('\\', '', $enc);
2880 if (eregi('^(ISO-8859-1|US-ASCII|UTF-8)$', $enc)) {
2881 $this->xml_encoding = strtoupper($enc);
2883 $this->xml_encoding = 'US-ASCII';
2886 // should be US-ASCII for HTTP 1.0 or ISO-8859-1 for HTTP 1.1
2887 $this->xml_encoding = 'ISO-8859-1';
2890 $this->headers[$k] = $v;
2891 $this->request .= "$k: $v\r\n";
2892 $this->debug("$k: $v");
2894 } elseif (is_array($HTTP_SERVER_VARS)) {
2895 foreach ($HTTP_SERVER_VARS as $k => $v) {
2896 if (substr($k, 0, 5) == 'HTTP_') {
2897 $k = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($k, 5)))));
2898 if ($k == 'Soapaction') {
2899 // get SOAPAction header
2901 $v = str_replace('"', '', $v);
2902 $v = str_replace('\\', '', $v);
2903 $this->SOAPAction = $v;
2904 } else if ($k == 'Content-Type') {
2905 // get the character encoding of the incoming request
2906 if (strpos($v, '=')) {
2907 $enc = substr(strstr($v, '='), 1);
2908 $enc = str_replace('"', '', $enc);
2909 $enc = str_replace('\\', '', $enc);
2910 if (eregi('^(ISO-8859-1|US-ASCII|UTF-8)$', $enc)) {
2911 $this->xml_encoding = strtoupper($enc);
2913 $this->xml_encoding = 'US-ASCII';
2916 // should be US-ASCII for HTTP 1.0 or ISO-8859-1 for HTTP 1.1
2917 $this->xml_encoding = 'ISO-8859-1';
2920 $this->headers[$k] = $v;
2921 $this->request .= "$k: $v\r\n";
2922 $this->debug("$k: $v");
2931 * The following fields are set by this function (when successful)
2945 * This sets the fault field on error
2947 * @param string $data XML string
2950 function parse_request($data='') {
2951 $this->debug('entering parse_request()');
2952 $this->parse_http_headers();
2953 $this->debug('got character encoding: '.$this->xml_encoding);
2954 // uncompress if necessary
2955 if (isset($this->headers['Content-Encoding']) && $this->headers['Content-Encoding'] != '') {
2956 $this->debug('got content encoding: ' . $this->headers['Content-Encoding']);
2957 if ($this->headers['Content-Encoding'] == 'deflate' || $this->headers['Content-Encoding'] == 'gzip') {
2958 // if decoding works, use it. else assume data wasn't gzencoded
2959 if (function_exists('gzuncompress')) {
2960 if ($this->headers['Content-Encoding'] == 'deflate' && $degzdata = @gzuncompress($data)) {
2962 } elseif ($this->headers['Content-Encoding'] == 'gzip' && $degzdata = gzinflate(substr($data, 10))) {
2965 $this->fault('Client', 'Errors occurred when trying to decode the data');
2969 $this->fault('Client', 'This Server does not support compressed data');
2974 $this->request .= "\r\n".$data;
2975 $this->requestSOAP = $data;
2976 // parse response, get soap parser obj
2977 $parser = new soap_parser($data,$this->xml_encoding,'',$this->decode_utf8);
2979 $this->debug("parser debug: \n".$parser->getDebug());
2980 // if fault occurred during message parsing
2981 if($err = $parser->getError()){
2982 $this->result = 'fault: error in msg parsing: '.$err;
2983 $this->fault('Client',"error in msg parsing:\n".$err);
2984 // else successfully parsed request into soapval object
2986 // get/set methodname
2987 $this->methodURI = $parser->root_struct_namespace;
2988 $this->methodname = $parser->root_struct_name;
2989 $this->debug('methodname: '.$this->methodname.' methodURI: '.$this->methodURI);
2990 $this->debug('calling parser->get_response()');
2991 $this->methodparams = $parser->get_response();
2993 $this->requestHeaders = $parser->getHeaders();
2994 // add document for doclit support
2995 $this->document = $parser->document;
2997 $this->debug('leaving parse_request');
3001 * invokes a PHP function for the requested SOAP method
3003 * The following fields are set by this function (when successful)
3007 * Note that the PHP function that is called may also set the following
3008 * fields to affect the response sent to the client
3013 * This sets the fault field on error
3017 function invoke_method() {
3018 $this->debug('entering invoke_method methodname: ' . $this->methodname . ' methodURI: ' . $this->methodURI);
3019 // if a . is present in $this->methodname, we see if there is a class in scope,
3020 // which could be referred to. We will also distinguish between two deliminators,
3021 // to allow methods to be called a the class or an instance
3024 if (strpos($this->methodname, '..') > 0) {
3026 } else if (strpos($this->methodname, '.') > 0) {
3032 if (strlen($delim) > 0 && substr_count($this->methodname, $delim) == 1 &&
3033 class_exists(substr($this->methodname, 0, strpos($this->methodname, $delim)))) {
3034 // get the class and method name
3035 $class = substr($this->methodname, 0, strpos($this->methodname, $delim));
3036 $method = substr($this->methodname, strpos($this->methodname, $delim) + strlen($delim));
3037 $this->debug("class: $class method: $method delim: $delim");
3040 // does method exist?
3041 if ($class == '' && !function_exists($this->methodname)) {
3042 $this->debug("function '$this->methodname' not found!");
3043 $this->result = 'fault: method not found';
3044 $this->fault('Client',"method '$this->methodname' not defined in service");
3047 if ($class != '' && !in_array(strtolower($method), get_class_methods($class))) {
3048 $this->debug("method '$this->methodname' not found in class '$class'!");
3049 $this->result = 'fault: method not found';
3050 $this->fault('Client',"method '$this->methodname' not defined in service");
3055 if(!$this->opData = $this->wsdl->getOperationData($this->methodname)){
3057 $this->fault('Client',"Operation '$this->methodname' is not defined in the WSDL for this service");
3060 $this->debug('opData:');
3061 $this->appendDebug($this->varDump($this->opData));
3063 $this->debug("method '$this->methodname' exists");
3064 // evaluate message, getting back parameters
3065 // verify that request parameters match the method's signature
3066 if(! $this->verify_method($this->methodname,$this->methodparams)){
3068 $this->debug('ERROR: request not verified against method signature');
3069 $this->result = 'fault: request failed validation against method signature';
3071 $this->fault('Client',"Operation '$this->methodname' not defined in service.");
3075 // if there are parameters to pass
3076 $this->debug('params:');
3077 $this->appendDebug($this->varDump($this->methodparams));
3078 $this->debug("calling '$this->methodname'");
3079 if (!function_exists('call_user_func_array')) {
3081 $this->debug('calling function using eval()');
3082 $funcCall = "\$this->methodreturn = $this->methodname(";
3084 if ($delim == '..') {
3085 $this->debug('calling class method using eval()');
3086 $funcCall = "\$this->methodreturn = ".$class."::".$method."(";
3088 $this->debug('calling instance method using eval()');
3089 // generate unique instance name
3090 $instname = "\$inst_".time();
3091 $funcCall = $instname." = new ".$class."(); ";
3092 $funcCall .= "\$this->methodreturn = ".$instname."->".$method."(";
3095 if ($this->methodparams) {
3096 foreach ($this->methodparams as $param) {
3097 if (is_array($param)) {
3098 $this->fault('Client', 'NuSOAP does not handle complexType parameters correctly when using eval; call_user_func_array must be available');
3101 $funcCall .= "\"$param\",";
3103 $funcCall = substr($funcCall, 0, -1);
3106 $this->debug('function call: '.$funcCall);
3110 $this->debug('calling function using call_user_func_array()');
3111 $call_arg = "$this->methodname"; // straight assignment changes $this->methodname to lower case after call_user_func_array()
3112 } elseif ($delim == '..') {
3113 $this->debug('calling class method using call_user_func_array()');
3114 $call_arg = array ($class, $method);
3116 $this->debug('calling instance method using call_user_func_array()');
3117 $instance = new $class ();
3118 $call_arg = array(&$instance, $method);
3120 $this->methodreturn = call_user_func_array($call_arg, $this->methodparams);
3122 $this->debug('methodreturn:');
3123 $this->appendDebug($this->varDump($this->methodreturn));
3124 $this->debug("leaving invoke_method: called method $this->methodname, received $this->methodreturn of type ".gettype($this->methodreturn));
3128 * serializes the return value from a PHP function into a full SOAP Envelope
3130 * The following fields are set by this function (when successful)
3134 * This sets the fault field on error
3138 function serialize_return() {
3139 $this->debug('Entering serialize_return methodname: ' . $this->methodname . ' methodURI: ' . $this->methodURI);
3140 // if we got nothing back. this might be ok (echoVoid)
3141 if(isset($this->methodreturn) && ($this->methodreturn != '' || is_bool($this->methodreturn))) {
3143 if(get_class($this->methodreturn) == 'soap_fault'){
3144 $this->debug('got a fault object from method');
3145 $this->fault = $this->methodreturn;
3147 } elseif ($this->methodreturnisliteralxml) {
3148 $return_val = $this->methodreturn;
3149 // returned value(s)
3151 $this->debug('got a(n) '.gettype($this->methodreturn).' from method');
3152 $this->debug('serializing return value');
3154 // weak attempt at supporting multiple output params
3155 if(sizeof($this->opData['output']['parts']) > 1){
3156 $opParams = $this->methodreturn;
3158 // TODO: is this really necessary?
3159 $opParams = array($this->methodreturn);
3161 $return_val = $this->wsdl->serializeRPCParameters($this->methodname,'output',$opParams);
3162 $this->appendDebug($this->wsdl->getDebug());
3163 $this->wsdl->clearDebug();
3164 if($errstr = $this->wsdl->getError()){
3165 $this->debug('got wsdl error: '.$errstr);
3166 $this->fault('Server', 'unable to serialize result');
3170 $return_val = $this->serialize_val($this->methodreturn, 'return');
3173 $this->debug('return value:');
3174 $this->appendDebug($this->varDump($return_val));
3177 $this->debug('got no response from method');
3179 $this->debug('serializing response');
3181 $this->debug('have WSDL for serialization: style is ' . $this->opData['style']);
3182 if ($this->opData['style'] == 'rpc') {
3183 $this->debug('style is rpc for serialization: use is ' . $this->opData['output']['use']);
3184 if ($this->opData['output']['use'] == 'literal') {
3185 $payload = '<'.$this->methodname.'Response xmlns="'.$this->methodURI.'">'.$return_val.'</'.$this->methodname."Response>";
3187 $payload = '<ns1:'.$this->methodname.'Response xmlns:ns1="'.$this->methodURI.'">'.$return_val.'</ns1:'.$this->methodname."Response>";
3190 $this->debug('style is not rpc for serialization: assume document');
3191 $payload = $return_val;
3194 $this->debug('do not have WSDL for serialization: assume rpc/encoded');
3195 $payload = '<ns1:'.$this->methodname.'Response xmlns:ns1="'.$this->methodURI.'">'.$return_val.'</ns1:'.$this->methodname."Response>";
3197 $this->result = 'successful';
3199 //if($this->debug_flag){
3200 $this->appendDebug($this->wsdl->getDebug());
3202 // Added: In case we use a WSDL, return a serialized env. WITH the usedNamespaces.
3203 $this->responseSOAP = $this->serializeEnvelope($payload,$this->responseHeaders,$this->wsdl->usedNamespaces,$this->opData['style']);
3205 $this->responseSOAP = $this->serializeEnvelope($payload,$this->responseHeaders);
3207 $this->debug("Leaving serialize_return");
3211 * sends an HTTP response
3213 * The following fields are set by this function (when successful)
3220 function send_response() {
3221 $this->debug('Enter send_response');
3223 $payload = $this->fault->serialize();
3224 $this->outgoing_headers[] = "HTTP/1.0 500 Internal Server Error";
3225 $this->outgoing_headers[] = "Status: 500 Internal Server Error";
3227 $payload = $this->responseSOAP;
3228 // Some combinations of PHP+Web server allow the Status
3229 // to come through as a header. Since OK is the default
3231 // $this->outgoing_headers[] = "HTTP/1.0 200 OK";
3232 // $this->outgoing_headers[] = "Status: 200 OK";
3234 // add debug data if in debug mode
3235 if(isset($this->debug_flag) && $this->debug_flag){
3236 $payload .= $this->getDebugAsXMLComment();
3238 $this->outgoing_headers[] = "Server: $this->title Server v$this->version";
3239 ereg('\$Revisio' . 'n: ([^ ]+)', $this->revision, $rev);
3240 $this->outgoing_headers[] = "X-SOAP-Server: $this->title/$this->version (".$rev[1].")";
3241 // Let the Web server decide about this
3242 //$this->outgoing_headers[] = "Connection: Close\r\n";
3243 $this->outgoing_headers[] = "Content-Type: text/xml; charset=$this->soap_defencoding";
3244 //begin code to compress payload - by John
3245 // NOTE: there is no way to know whether the Web server will also compress
3247 if (strlen($payload) > 1024 && isset($this->headers) && isset($this->headers['Accept-Encoding'])) {
3248 if (strstr($this->headers['Accept-Encoding'], 'gzip')) {
3249 if (function_exists('gzencode')) {
3250 if (isset($this->debug_flag) && $this->debug_flag) {
3251 $payload .= "<!-- Content being gzipped -->";
3253 $this->outgoing_headers[] = "Content-Encoding: gzip";
3254 $payload = gzencode($payload);
3256 if (isset($this->debug_flag) && $this->debug_flag) {
3257 $payload .= "<!-- Content will not be gzipped: no gzencode -->";
3260 } elseif (strstr($this->headers['Accept-Encoding'], 'deflate')) {
3261 // Note: MSIE requires gzdeflate output (no Zlib header and checksum),
3262 // instead of gzcompress output,
3263 // which conflicts with HTTP 1.1 spec (http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.5)
3264 if (function_exists('gzdeflate')) {
3265 if (isset($this->debug_flag) && $this->debug_flag) {
3266 $payload .= "<!-- Content being deflated -->";
3268 $this->outgoing_headers[] = "Content-Encoding: deflate";
3269 $payload = gzdeflate($payload);
3271 if (isset($this->debug_flag) && $this->debug_flag) {
3272 $payload .= "<!-- Content will not be deflated: no gzcompress -->";
3278 $this->outgoing_headers[] = "Content-Length: ".strlen($payload);
3279 reset($this->outgoing_headers);
3280 foreach($this->outgoing_headers as $hdr){
3281 header($hdr, false);
3284 $this->response = join("\r\n",$this->outgoing_headers)."\r\n\r\n".$payload;
3288 * takes the value that was created by parsing the request
3289 * and compares to the method's signature, if available.
3295 function verify_method($operation,$request){
3296 if(isset($this->wsdl) && is_object($this->wsdl)){
3297 if($this->wsdl->getOperationData($operation)){
3300 } elseif(isset($this->operations[$operation])){
3307 * add a method to the dispatch map
3309 * @param string $methodname
3310 * @param string $in array of input values
3311 * @param string $out array of output values
3314 function add_to_map($methodname,$in,$out){
3315 $this->operations[$methodname] = array('name' => $methodname,'in' => $in,'out' => $out);
3319 * register a service with the server
3321 * @param string $methodname
3322 * @param string $in assoc array of input values: key = param name, value = param type
3323 * @param string $out assoc array of output values: key = param name, value = param type
3324 * @param string $namespace
3325 * @param string $soapaction
3326 * @param string $style optional (rpc|document)
3327 * @param string $use optional (encoded|literal)
3328 * @param string $documentation optional Description to include in WSDL
3331 function register($name,$in=array(),$out=array(),$namespace=false,$soapaction=false,$style=false,$use=false,$documentation=''){
3332 if($this->externalWSDLURL){
3333 die('You cannot bind to an external WSDL file, and register methods outside of it! Please choose either WSDL or no WSDL.');
3335 if(false == $namespace) {
3337 if(false == $soapaction) {
3338 $SERVER_NAME = isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : $GLOBALS['SERVER_NAME'];
3339 $SCRIPT_NAME = isset($_SERVER['SCRIPT_NAME']) ? $_SERVER['SCRIPT_NAME'] : $GLOBALS['SCRIPT_NAME'];
3340 $soapaction = "http://$SERVER_NAME$SCRIPT_NAME/$name";
3342 if(false == $style) {
3349 $this->operations[$name] = array(
3353 'namespace' => $namespace,
3354 'soapaction' => $soapaction,
3357 $this->wsdl->addOperation($name,$in,$out,$namespace,$soapaction,$style,$use,$documentation);
3363 * create a fault. this also acts as a flag to the server that a fault has occured.
3365 * @param string faultcode
3366 * @param string faultstring
3367 * @param string faultactor
3368 * @param string faultdetail
3371 function fault($faultcode,$faultstring,$faultactor='',$faultdetail=''){
3372 $this->fault = new soap_fault($faultcode,$faultactor,$faultstring,$faultdetail);
3376 * sets up wsdl object
3377 * this acts as a flag to enable internal WSDL generation
3379 * @param string $serviceName, name of the service
3380 * @param string $namespace optional tns namespace
3381 * @param string $endpoint optional URL of service endpoint
3382 * @param string $style optional (rpc|document) WSDL style (also specified by operation)
3383 * @param string $transport optional SOAP transport
3384 * @param string $schemaTargetNamespace optional targetNamespace for service schema
3386 function configureWSDL($serviceName,$namespace = false,$endpoint = false,$style='rpc', $transport = 'http://schemas.xmlsoap.org/soap/http', $schemaTargetNamespace = false)
3388 $SERVER_NAME = isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : $GLOBALS['SERVER_NAME'];
3389 $SERVER_PORT = isset($_SERVER['SERVER_PORT']) ? $_SERVER['SERVER_PORT'] : $GLOBALS['SERVER_PORT'];
3390 if ($SERVER_PORT == 80) {
3393 $SERVER_PORT = ':' . $SERVER_PORT;
3395 $SCRIPT_NAME = isset($_SERVER['SCRIPT_NAME']) ? $_SERVER['SCRIPT_NAME'] : $GLOBALS['SCRIPT_NAME'];
3396 if(false == $namespace) {
3397 $namespace = "http://$SERVER_NAME/soap/$serviceName";
3400 if(false == $endpoint) {
3401 if (isset($_SERVER['HTTPS'])) {
3402 $HTTPS = $_SERVER['HTTPS'];
3403 } elseif (isset($GLOBALS['HTTPS'])) {
3404 $HTTPS = $GLOBALS['HTTPS'];
3408 if ($HTTPS == '1' || $HTTPS == 'on') {
3413 $endpoint = "$SCHEME://$SERVER_NAME$SERVER_PORT$SCRIPT_NAME";
3416 if(false == $schemaTargetNamespace) {
3417 $schemaTargetNamespace = $namespace;
3420 $this->wsdl = new wsdl;
3421 $this->wsdl->serviceName = $serviceName;
3422 $this->wsdl->endpoint = $endpoint;
3423 $this->wsdl->namespaces['tns'] = $namespace;
3424 $this->wsdl->namespaces['soap'] = 'http://schemas.xmlsoap.org/wsdl/soap/';
3425 $this->wsdl->namespaces['wsdl'] = 'http://schemas.xmlsoap.org/wsdl/';
3426 if ($schemaTargetNamespace != $namespace) {
3427 $this->wsdl->namespaces['types'] = $schemaTargetNamespace;
3429 $this->wsdl->schemas[$schemaTargetNamespace][0] = new xmlschema('', '', $this->wsdl->namespaces);
3430 $this->wsdl->schemas[$schemaTargetNamespace][0]->schemaTargetNamespace = $schemaTargetNamespace;
3431 $this->wsdl->schemas[$schemaTargetNamespace][0]->imports['http://schemas.xmlsoap.org/soap/encoding/'][0] = array('location' => '', 'loaded' => true);
3432 $this->wsdl->schemas[$schemaTargetNamespace][0]->imports['http://schemas.xmlsoap.org/wsdl/'][0] = array('location' => '', 'loaded' => true);
3433 $this->wsdl->bindings[$serviceName.'Binding'] = array(
3434 'name'=>$serviceName.'Binding',
3436 'transport'=>$transport,
3437 'portType'=>$serviceName.'PortType');
3438 $this->wsdl->ports[$serviceName.'Port'] = array(
3439 'binding'=>$serviceName.'Binding',
3440 'location'=>$endpoint,
3441 'bindingType'=>'http://schemas.xmlsoap.org/wsdl/soap/');
3452 * parses a WSDL file, allows access to it's data, other utility methods
3454 * @author Dietrich Ayala <dietrich@ganx4.com>
3458 class wsdl extends nusoap_base {
3459 // URL or filename of the root of this WSDL
3461 // define internal arrays of bindings, ports, operations, messages, etc.
3462 var $schemas = array();
3464 var $message = array();
3465 var $complexTypes = array();
3466 var $messages = array();
3467 var $currentMessage;
3468 var $currentOperation;
3469 var $portTypes = array();
3470 var $currentPortType;
3471 var $bindings = array();
3472 var $currentBinding;
3473 var $ports = array();
3475 var $opData = array();
3477 var $documentation = false;
3479 // array of wsdl docs to import
3480 var $import = array();
3485 var $depth_array = array();
3487 var $proxyhost = '';
3488 var $proxyport = '';
3489 var $proxyusername = '';
3490 var $proxypassword = '';
3492 var $response_timeout = 30;
3497 * @param string $wsdl WSDL document URL
3498 * @param string $proxyhost
3499 * @param string $proxyport
3500 * @param string $proxyusername
3501 * @param string $proxypassword
3502 * @param integer $timeout set the connection timeout
3503 * @param integer $response_timeout set the response timeout
3506 function wsdl($wsdl = '',$proxyhost=false,$proxyport=false,$proxyusername=false,$proxypassword=false,$timeout=0,$response_timeout=30){
3507 $this->wsdl = $wsdl;
3508 $this->proxyhost = $proxyhost;
3509 $this->proxyport = $proxyport;
3510 $this->proxyusername = $proxyusername;
3511 $this->proxypassword = $proxypassword;
3512 $this->timeout = $timeout;
3513 $this->response_timeout = $response_timeout;
3517 $this->debug('initial wsdl URL: ' . $wsdl);
3518 $this->parseWSDL($wsdl);
3521 // TODO: handle imports more properly, grabbing them in-line and nesting them
3522 $imported_urls = array();
3524 while ($imported > 0) {
3527 foreach ($this->schemas as $ns => $list) {
3528 foreach ($list as $xs) {
3529 $wsdlparts = parse_url($this->wsdl); // this is bogusly simple!
3530 foreach ($xs->imports as $ns2 => $list2) {
3531 for ($ii = 0; $ii < count($list2); $ii++) {
3532 if (! $list2[$ii]['loaded']) {
3533 $this->schemas[$ns]->imports[$ns2][$ii]['loaded'] = true;
3534 $url = $list2[$ii]['location'];
3536 $urlparts = parse_url($url);
3537 if (!isset($urlparts['host'])) {
3538 $url = $wsdlparts['scheme'] . '://' . $wsdlparts['host'] .
3539 substr($wsdlparts['path'],0,strrpos($wsdlparts['path'],'/') + 1) .$urlparts['path'];
3541 if (! in_array($url, $imported_urls)) {
3542 $this->parseWSDL($url);
3544 $imported_urls[] = $url;
3547 $this->debug("Unexpected scenario: empty URL for unloaded import");
3555 $wsdlparts = parse_url($this->wsdl); // this is bogusly simple!
3556 foreach ($this->import as $ns => $list) {
3557 for ($ii = 0; $ii < count($list); $ii++) {
3558 if (! $list[$ii]['loaded']) {
3559 $this->import[$ns][$ii]['loaded'] = true;
3560 $url = $list[$ii]['location'];
3562 $urlparts = parse_url($url);
3563 if (!isset($urlparts['host'])) {
3564 $url = $wsdlparts['scheme'] . '://' . $wsdlparts['host'] .
3565 substr($wsdlparts['path'],0,strrpos($wsdlparts['path'],'/') + 1) .$urlparts['path'];
3567 if (! in_array($url, $imported_urls)) {
3568 $this->parseWSDL($url);
3570 $imported_urls[] = $url;
3573 $this->debug("Unexpected scenario: empty URL for unloaded import");
3579 // add new data to operation data
3580 foreach($this->bindings as $binding => $bindingData) {
3581 if (isset($bindingData['operations']) && is_array($bindingData['operations'])) {
3582 foreach($bindingData['operations'] as $operation => $data) {
3583 $this->debug('post-parse data gathering for ' . $operation);
3584 $this->bindings[$binding]['operations'][$operation]['input'] =
3585 isset($this->bindings[$binding]['operations'][$operation]['input']) ?
3586 array_merge($this->bindings[$binding]['operations'][$operation]['input'], $this->portTypes[ $bindingData['portType'] ][$operation]['input']) :
3587 $this->portTypes[ $bindingData['portType'] ][$operation]['input'];
3588 $this->bindings[$binding]['operations'][$operation]['output'] =
3589 isset($this->bindings[$binding]['operations'][$operation]['output']) ?
3590 array_merge($this->bindings[$binding]['operations'][$operation]['output'], $this->portTypes[ $bindingData['portType'] ][$operation]['output']) :
3591 $this->portTypes[ $bindingData['portType'] ][$operation]['output'];
3592 if(isset($this->messages[ $this->bindings[$binding]['operations'][$operation]['input']['message'] ])){
3593 $this->bindings[$binding]['operations'][$operation]['input']['parts'] = $this->messages[ $this->bindings[$binding]['operations'][$operation]['input']['message'] ];
3595 if(isset($this->messages[ $this->bindings[$binding]['operations'][$operation]['output']['message'] ])){
3596 $this->bindings[$binding]['operations'][$operation]['output']['parts'] = $this->messages[ $this->bindings[$binding]['operations'][$operation]['output']['message'] ];
3598 if (isset($bindingData['style'])) {
3599 $this->bindings[$binding]['operations'][$operation]['style'] = $bindingData['style'];
3601 $this->bindings[$binding]['operations'][$operation]['transport'] = isset($bindingData['transport']) ? $bindingData['transport'] : '';
3602 $this->bindings[$binding]['operations'][$operation]['documentation'] = isset($this->portTypes[ $bindingData['portType'] ][$operation]['documentation']) ? $this->portTypes[ $bindingData['portType'] ][$operation]['documentation'] : '';
3603 $this->bindings[$binding]['operations'][$operation]['endpoint'] = isset($bindingData['endpoint']) ? $bindingData['endpoint'] : '';
3610 * parses the wsdl document
3612 * @param string $wsdl path or URL
3615 function parseWSDL($wsdl = '')
3618 $this->debug('no wsdl passed to parseWSDL()!!');
3619 $this->setError('no wsdl passed to parseWSDL()!!');
3623 // parse $wsdl for url format
3624 $wsdl_props = parse_url($wsdl);
3626 if (isset($wsdl_props['scheme']) && ($wsdl_props['scheme'] == 'http' || $wsdl_props['scheme'] == 'https')) {
3627 $this->debug('getting WSDL http(s) URL ' . $wsdl);
3629 $tr = new soap_transport_http($wsdl);
3630 $tr->request_method = 'GET';
3631 $tr->useSOAPAction = false;
3632 if($this->proxyhost && $this->proxyport){
3633 $tr->setProxy($this->proxyhost,$this->proxyport,$this->proxyusername,$this->proxypassword);
3635 $tr->setEncoding('gzip, deflate');
3636 $wsdl_string = $tr->send('', $this->timeout, $this->response_timeout);
3637 //$this->debug("WSDL request\n" . $tr->outgoing_payload);
3638 //$this->debug("WSDL response\n" . $tr->incoming_payload);
3639 $this->appendDebug($tr->getDebug());
3641 if($err = $tr->getError() ){
3642 $errstr = 'HTTP ERROR: '.$err;
3643 $this->debug($errstr);
3644 $this->setError($errstr);
3649 $this->debug("got WSDL URL");
3651 // $wsdl is not http(s), so treat it as a file URL or plain file path
3652 if (isset($wsdl_props['scheme']) && ($wsdl_props['scheme'] == 'file') && isset($wsdl_props['path'])) {
3653 $path = isset($wsdl_props['host']) ? ($wsdl_props['host'] . ':' . $wsdl_props['path']) : $wsdl_props['path'];
3657 $this->debug('getting WSDL file ' . $path);
3658 if ($fp = @fopen($path, 'r')) {
3660 while ($data = fread($fp, 32768)) {
3661 $wsdl_string .= $data;
3665 $errstr = "Bad path to WSDL file $path";
3666 $this->debug($errstr);
3667 $this->setError($errstr);
3671 $this->debug('Parse WSDL');
3672 // end new code added
3673 // Create an XML parser.
3674 $this->parser = xml_parser_create();
3675 // Set the options for parsing the XML data.
3676 // xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 1);
3677 xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING, 0);
3678 // Set the object for the parser.
3679 xml_set_object($this->parser, $this);
3680 // Set the element handlers for the parser.
3681 xml_set_element_handler($this->parser, 'start_element', 'end_element');
3682 xml_set_character_data_handler($this->parser, 'character_data');
3683 // Parse the XML file.
3684 if (!xml_parse($this->parser, $wsdl_string, true)) {
3685 // Display an error message.
3687 'XML error parsing WSDL from %s on line %d: %s',
3689 xml_get_current_line_number($this->parser),
3690 xml_error_string(xml_get_error_code($this->parser))
3692 $this->debug($errstr);
3693 $this->debug("XML payload:\n" . $wsdl_string);
3694 $this->setError($errstr);
3698 xml_parser_free($this->parser);
3699 $this->debug('Parsing WSDL done');
3700 // catch wsdl parse errors
3701 if($this->getError()){
3708 * start-element handler
3710 * @param string $parser XML parser object
3711 * @param string $name element name
3712 * @param string $attrs associative array of attributes
3715 function start_element($parser, $name, $attrs)
3717 if ($this->status == 'schema') {
3718 $this->currentSchema->schemaStartElement($parser, $name, $attrs);
3719 $this->appendDebug($this->currentSchema->getDebug());
3720 $this->currentSchema->clearDebug();
3721 } elseif (ereg('schema$', $name)) {
3722 $this->debug('Parsing WSDL schema');
3723 // $this->debug("startElement for $name ($attrs[name]). status = $this->status (".$this->getLocalPart($name).")");
3724 $this->status = 'schema';
3725 $this->currentSchema = new xmlschema('', '', $this->namespaces);
3726 $this->currentSchema->schemaStartElement($parser, $name, $attrs);
3727 $this->appendDebug($this->currentSchema->getDebug());
3728 $this->currentSchema->clearDebug();
3730 // position in the total number of elements, starting from 0
3731 $pos = $this->position++;
3732 $depth = $this->depth++;
3733 // set self as current value for this depth
3734 $this->depth_array[$depth] = $pos;
3735 $this->message[$pos] = array('cdata' => '');
3736 // get element prefix
3737 if (ereg(':', $name)) {
3739 $prefix = substr($name, 0, strpos($name, ':'));
3741 $namespace = isset($this->namespaces[$prefix]) ? $this->namespaces[$prefix] : '';
3742 // get unqualified name
3743 $name = substr(strstr($name, ':'), 1);
3746 if (count($attrs) > 0) {
3747 foreach($attrs as $k => $v) {
3748 // if ns declarations, add to class level array of valid namespaces
3749 if (ereg("^xmlns", $k)) {
3750 if ($ns_prefix = substr(strrchr($k, ':'), 1)) {
3751 $this->namespaces[$ns_prefix] = $v;
3753 $this->namespaces['ns' . (count($this->namespaces) + 1)] = $v;
3755 if ($v == 'http://www.w3.org/2001/XMLSchema' || $v == 'http://www.w3.org/1999/XMLSchema') {
3756 $this->XMLSchemaVersion = $v;
3757 $this->namespaces['xsi'] = $v . '-instance';
3760 // expand each attribute
3761 $k = strpos($k, ':') ? $this->expandQname($k) : $k;
3762 if ($k != 'location' && $k != 'soapAction' && $k != 'namespace') {
3763 $v = strpos($v, ':') ? $this->expandQname($v) : $v;
3771 // find status, register data
3772 switch ($this->status) {
3774 if ($name == 'part') {
3775 if (isset($attrs['type'])) {
3776 $this->debug("msg " . $this->currentMessage . ": found part $attrs[name]: " . implode(',', $attrs));
3777 $this->messages[$this->currentMessage][$attrs['name']] = $attrs['type'];
3779 if (isset($attrs['element'])) {
3780 $this->debug("msg " . $this->currentMessage . ": found part $attrs[name]: " . implode(',', $attrs));
3781 $this->messages[$this->currentMessage][$attrs['name']] = $attrs['element'];
3788 $this->currentPortOperation = $attrs['name'];
3789 $this->debug("portType $this->currentPortType operation: $this->currentPortOperation");
3790 if (isset($attrs['parameterOrder'])) {
3791 $this->portTypes[$this->currentPortType][$attrs['name']]['parameterOrder'] = $attrs['parameterOrder'];
3794 case 'documentation':
3795 $this->documentation = true;
3797 // merge input/output data
3799 $m = isset($attrs['message']) ? $this->getLocalPart($attrs['message']) : '';
3800 $this->portTypes[$this->currentPortType][$this->currentPortOperation][$name]['message'] = $m;
3808 if (isset($attrs['style'])) {
3809 $this->bindings[$this->currentBinding]['prefix'] = $prefix;
3811 $this->bindings[$this->currentBinding] = array_merge($this->bindings[$this->currentBinding], $attrs);
3814 $this->bindings[$this->currentBinding]['operations'][$this->currentOperation][$this->opStatus]['headers'][] = $attrs;
3817 if (isset($attrs['soapAction'])) {
3818 $this->bindings[$this->currentBinding]['operations'][$this->currentOperation]['soapAction'] = $attrs['soapAction'];
3820 if (isset($attrs['style'])) {
3821 $this->bindings[$this->currentBinding]['operations'][$this->currentOperation]['style'] = $attrs['style'];
3823 if (isset($attrs['name'])) {
3824 $this->currentOperation = $attrs['name'];
3825 $this->debug("current binding operation: $this->currentOperation");
3826 $this->bindings[$this->currentBinding]['operations'][$this->currentOperation]['name'] = $attrs['name'];
3827 $this->bindings[$this->currentBinding]['operations'][$this->currentOperation]['binding'] = $this->currentBinding;
3828 $this->bindings[$this->currentBinding]['operations'][$this->currentOperation]['endpoint'] = isset($this->bindings[$this->currentBinding]['endpoint']) ? $this->bindings[$this->currentBinding]['endpoint'] : '';
3832 $this->opStatus = 'input';
3835 $this->opStatus = 'output';
3838 if (isset($this->bindings[$this->currentBinding]['operations'][$this->currentOperation][$this->opStatus])) {
3839 $this->bindings[$this->currentBinding]['operations'][$this->currentOperation][$this->opStatus] = array_merge($this->bindings[$this->currentBinding]['operations'][$this->currentOperation][$this->opStatus], $attrs);
3841 $this->bindings[$this->currentBinding]['operations'][$this->currentOperation][$this->opStatus] = $attrs;
3849 $this->currentPort = $attrs['name'];
3850 $this->debug('current port: ' . $this->currentPort);
3851 $this->ports[$this->currentPort]['binding'] = $this->getLocalPart($attrs['binding']);
3855 $this->ports[$this->currentPort]['location'] = $attrs['location'];
3856 $this->ports[$this->currentPort]['bindingType'] = $namespace;
3857 $this->bindings[ $this->ports[$this->currentPort]['binding'] ]['bindingType'] = $namespace;
3858 $this->bindings[ $this->ports[$this->currentPort]['binding'] ]['endpoint'] = $attrs['location'];
3866 if (isset($attrs['location'])) {
3867 $this->import[$attrs['namespace']][] = array('location' => $attrs['location'], 'loaded' => false);
3868 $this->debug('parsing import ' . $attrs['namespace']. ' - ' . $attrs['location'] . ' (' . count($this->import[$attrs['namespace']]).')');
3870 $this->import[$attrs['namespace']][] = array('location' => '', 'loaded' => true);
3871 if (! $this->getPrefixFromNamespace($attrs['namespace'])) {
3872 $this->namespaces['ns'.(count($this->namespaces)+1)] = $attrs['namespace'];
3874 $this->debug('parsing import ' . $attrs['namespace']. ' - [no location] (' . count($this->import[$attrs['namespace']]).')');
3879 // $this->status = 'schema';
3882 $this->status = 'message';
3883 $this->messages[$attrs['name']] = array();
3884 $this->currentMessage = $attrs['name'];
3887 $this->status = 'portType';
3888 $this->portTypes[$attrs['name']] = array();
3889 $this->currentPortType = $attrs['name'];
3892 if (isset($attrs['name'])) {
3894 if (strpos($attrs['name'], ':')) {
3895 $this->currentBinding = $this->getLocalPart($attrs['name']);
3897 $this->currentBinding = $attrs['name'];
3899 $this->status = 'binding';
3900 $this->bindings[$this->currentBinding]['portType'] = $this->getLocalPart($attrs['type']);
3901 $this->debug("current binding: $this->currentBinding of portType: " . $attrs['type']);
3905 $this->serviceName = $attrs['name'];
3906 $this->status = 'service';
3907 $this->debug('current service: ' . $this->serviceName);
3910 foreach ($attrs as $name => $value) {
3911 $this->wsdl_info[$name] = $value;
3919 * end-element handler
3921 * @param string $parser XML parser object
3922 * @param string $name element name
3925 function end_element($parser, $name){
3926 // unset schema status
3927 if (/*ereg('types$', $name) ||*/ ereg('schema$', $name)) {
3929 $this->schemas[$this->currentSchema->schemaTargetNamespace][] = $this->currentSchema;
3930 $this->debug('Parsing WSDL schema done');
3932 if ($this->status == 'schema') {
3933 $this->currentSchema->schemaEndElement($parser, $name);
3935 // bring depth down a notch
3938 // end documentation
3939 if ($this->documentation) {
3940 //TODO: track the node to which documentation should be assigned; it can be a part, message, etc.
3941 //$this->portTypes[$this->currentPortType][$this->currentPortOperation]['documentation'] = $this->documentation;
3942 $this->documentation = false;
3947 * element content handler
3949 * @param string $parser XML parser object
3950 * @param string $data element content
3953 function character_data($parser, $data)
3955 $pos = isset($this->depth_array[$this->depth]) ? $this->depth_array[$this->depth] : 0;
3956 if (isset($this->message[$pos]['cdata'])) {
3957 $this->message[$pos]['cdata'] .= $data;
3959 if ($this->documentation) {
3960 $this->documentation .= $data;
3964 function getBindingData($binding)
3966 if (is_array($this->bindings[$binding])) {
3967 return $this->bindings[$binding];
3972 * returns an assoc array of operation names => operation data
3974 * @param string $bindingType eg: soap, smtp, dime (only soap is currently supported)
3978 function getOperations($bindingType = 'soap')
3981 if ($bindingType == 'soap') {
3982 $bindingType = 'http://schemas.xmlsoap.org/wsdl/soap/';
3985 foreach($this->ports as $port => $portData) {
3986 // binding type of port matches parameter
3987 if ($portData['bindingType'] == $bindingType) {
3988 //$this->debug("getOperations for port $port");
3989 //$this->debug("port data: " . $this->varDump($portData));
3990 //$this->debug("bindings: " . $this->varDump($this->bindings[ $portData['binding'] ]));
3992 if (isset($this->bindings[ $portData['binding'] ]['operations'])) {
3993 $ops = array_merge ($ops, $this->bindings[ $portData['binding'] ]['operations']);
4001 * returns an associative array of data necessary for calling an operation
4003 * @param string $operation , name of operation
4004 * @param string $bindingType , type of binding eg: soap
4008 function getOperationData($operation, $bindingType = 'soap')
4010 if ($bindingType == 'soap') {
4011 $bindingType = 'http://schemas.xmlsoap.org/wsdl/soap/';
4014 foreach($this->ports as $port => $portData) {
4015 // binding type of port matches parameter
4016 if ($portData['bindingType'] == $bindingType) {
4018 //foreach($this->bindings[ $portData['binding'] ]['operations'] as $bOperation => $opData) {
4019 foreach(array_keys($this->bindings[ $portData['binding'] ]['operations']) as $bOperation) {
4020 if ($operation == $bOperation) {
4021 $opData = $this->bindings[ $portData['binding'] ]['operations'][$operation];
4030 * returns an array of information about a given type
4031 * returns false if no type exists by the given name
4034 * 'elements' => array(), // refs to elements array
4035 * 'restrictionBase' => '',
4037 * 'order' => '(sequence|all)',
4038 * 'attrs' => array() // refs to attributes array
4041 * @param $type string
4047 function getTypeDef($type, $ns) {
4048 if ((! $ns) && isset($this->namespaces['tns'])) {
4049 $ns = $this->namespaces['tns'];
4050 $this->debug("type namespace forced to $ns");
4052 if (isset($this->schemas[$ns])) {
4053 $this->debug("have schema for namespace $ns");
4054 for ($i = 0; $i < count($this->schemas[$ns]); $i++) {
4055 $xs = &$this->schemas[$ns][$i];
4056 $t = $xs->getTypeDef($type);
4057 $this->appendDebug($xs->getDebug());
4064 $this->debug("do not have schema for namespace $ns");
4070 * prints html description of services
4074 function webDescription(){
4076 <html><head><title>NuSOAP: '.$this->serviceName.'</title>
4077 <style type="text/css">
4078 body { font-family: arial; color: #000000; background-color: #ffffff; margin: 0px 0px 0px 0px; }
4079 p { font-family: arial; color: #000000; margin-top: 0px; margin-bottom: 12px; }
4080 pre { background-color: silver; padding: 5px; font-family: Courier New; font-size: x-small; color: #000000;}
4081 ul { margin-top: 10px; margin-left: 20px; }
4082 li { list-style-type: none; margin-top: 10px; color: #000000; }
4084 margin-left: 0px; padding-bottom: 2em; }
4086 padding-top: 10px; padding-bottom: 10px; padding-left: 15px; font-size: .70em;
4087 margin-top: 10px; margin-left: 0px; color: #000000;
4088 background-color: #ccccff; width: 20%; margin-left: 20px; margin-top: 20px; }
4090 font-family: arial; font-size: 26px; color: #ffffff;
4091 background-color: #999999; width: 105%; margin-left: 0px;
4092 padding-top: 10px; padding-bottom: 10px; padding-left: 15px;}
4094 position: absolute; visibility: hidden; z-index: 200; left: 250px; top: 100px;
4095 font-family: arial; overflow: hidden; width: 600;
4096 padding: 20px; font-size: 10px; background-color: #999999;
4097 layer-background-color:#FFFFFF; }
4098 a,a:active { color: charcoal; font-weight: bold; }
4099 a:visited { color: #666666; font-weight: bold; }
4100 a:hover { color: cc3300; font-weight: bold; }
4102 <script language="JavaScript" type="text/javascript">
4104 // POP-UP CAPTIONS...
4105 function lib_bwcheck(){ //Browsercheck (needed)
4106 this.ver=navigator.appVersion
4107 this.agent=navigator.userAgent
4108 this.dom=document.getElementById?1:0
4109 this.opera5=this.agent.indexOf("Opera 5")>-1
4110 this.ie5=(this.ver.indexOf("MSIE 5")>-1 && this.dom && !this.opera5)?1:0;
4111 this.ie6=(this.ver.indexOf("MSIE 6")>-1 && this.dom && !this.opera5)?1:0;
4112 this.ie4=(document.all && !this.dom && !this.opera5)?1:0;
4113 this.ie=this.ie4||this.ie5||this.ie6
4114 this.mac=this.agent.indexOf("Mac")>-1
4115 this.ns6=(this.dom && parseInt(this.ver) >= 5) ?1:0;
4116 this.ns4=(document.layers && !this.dom)?1:0;
4117 this.bw=(this.ie6 || this.ie5 || this.ie4 || this.ns4 || this.ns6 || this.opera5)
4120 var bw = new lib_bwcheck()
4121 //Makes crossbrowser object.
4122 function makeObj(obj){
4123 this.evnt=bw.dom? document.getElementById(obj):bw.ie4?document.all[obj]:bw.ns4?document.layers[obj]:0;
4124 if(!this.evnt) return false
4125 this.css=bw.dom||bw.ie4?this.evnt.style:bw.ns4?this.evnt:0;
4126 this.wref=bw.dom||bw.ie4?this.evnt:bw.ns4?this.css.document:0;
4127 this.writeIt=b_writeIt;
4130 // A unit of measure that will be added when setting the position of a layer.
4131 //var px = bw.ns4||window.opera?"":"px";
4132 function b_writeIt(text){
4133 if (bw.ns4){this.wref.write(text);this.wref.close()}
4134 else this.wref.innerHTML = text
4136 //Shows the messages
4138 function popup(divid){
4139 if(oDesc = new makeObj(divid)){
4140 oDesc.css.visibility = "visible"
4143 function popout(){ // Hides message
4144 if(oDesc) oDesc.css.visibility = "hidden"
4152 <div class=title>'.$this->serviceName.'</div>
4154 <p>View the <a href="'.(isset($GLOBALS['PHP_SELF']) ? $GLOBALS['PHP_SELF'] : $_SERVER['PHP_SELF']).'?wsdl">WSDL</a> for the service.
4155 Click on an operation name to view it's details.</p>
4157 foreach($this->getOperations() as $op => $data){
4158 $b .= "<li><a href='#' onclick=\"popup('$op')\">$op</a></li>";
4159 // create hidden div
4160 $b .= "<div id='$op' class='hidden'>
4161 <a href='#' onclick='popout()'><font color='#ffffff'>Close</font></a><br><br>";
4162 foreach($data as $donnie => $marie){ // loop through opdata
4163 if($donnie == 'input' || $donnie == 'output'){ // show input/output data
4164 $b .= "<font color='white'>".ucfirst($donnie).':</font><br>';
4165 foreach($marie as $captain => $tenille){ // loop through data
4166 if($captain == 'parts'){ // loop thru parts
4167 $b .= " $captain:<br>";
4168 //if(is_array($tenille)){
4169 foreach($tenille as $joanie => $chachi){
4170 $b .= " $joanie: $chachi<br>";
4174 $b .= " $captain: $tenille<br>";
4178 $b .= "<font color='white'>".ucfirst($donnie).":</font> $marie<br>";
4186 </div></body></html>';
4191 * serialize the parsed wsdl
4193 * @param $debug mixed whether to put debug=1 in endpoint URL
4194 * @return string , serialization of WSDL
4197 function serialize($debug = 0)
4199 $xml = '<?xml version="1.0" encoding="ISO-8859-1"?><definitions';
4200 foreach($this->namespaces as $k => $v) {
4201 $xml .= " xmlns:$k=\"$v\"";
4203 // 10.9.02 - add poulter fix for wsdl and tns declarations
4204 if (isset($this->namespaces['wsdl'])) {
4205 $xml .= " xmlns=\"" . $this->namespaces['wsdl'] . "\"";
4207 if (isset($this->namespaces['tns'])) {
4208 $xml .= " targetNamespace=\"" . $this->namespaces['tns'] . "\"";
4212 if (sizeof($this->import) > 0) {
4213 foreach($this->import as $ns => $list) {
4214 foreach ($list as $ii) {
4215 if ($ii['location'] != '') {
4216 $xml .= '<import location="' . $ii['location'] . '" namespace="' . $ns . '" />';
4218 $xml .= '<import namespace="' . $ns . '" />';
4224 if (count($this->schemas)>=1) {
4226 foreach ($this->schemas as $ns => $list) {
4227 foreach ($list as $xs) {
4228 $xml .= $xs->serializeSchema();
4234 if (count($this->messages) >= 1) {
4235 foreach($this->messages as $msgName => $msgParts) {
4236 $xml .= '<message name="' . $msgName . '">';
4237 if(is_array($msgParts)){
4238 foreach($msgParts as $partName => $partType) {
4239 // print 'serializing '.$partType.', sv: '.$this->XMLSchemaVersion.'<br>';
4240 if (strpos($partType, ':')) {
4241 $typePrefix = $this->getPrefixFromNamespace($this->getPrefix($partType));
4242 } elseif (isset($this->typemap[$this->namespaces['xsd']][$partType])) {
4243 // print 'checking typemap: '.$this->XMLSchemaVersion.'<br>';
4244 $typePrefix = 'xsd';
4246 foreach($this->typemap as $ns => $types) {
4247 if (isset($types[$partType])) {
4248 $typePrefix = $this->getPrefixFromNamespace($ns);
4251 if (!isset($typePrefix)) {
4252 die("$partType has no namespace!");
4255 $typeDef = $this->getTypeDef($this->getLocalPart($partType), $typePrefix);
4256 if ($typeDef['typeClass'] == 'element') {
4257 $elementortype = 'element';
4259 $elementortype = 'type';
4261 $xml .= '<part name="' . $partName . '" ' . $elementortype . '="' . $typePrefix . ':' . $this->getLocalPart($partType) . '" />';
4264 $xml .= '</message>';
4267 // bindings & porttypes
4268 if (count($this->bindings) >= 1) {
4271 foreach($this->bindings as $bindingName => $attrs) {
4272 $binding_xml .= '<binding name="' . $bindingName . '" type="tns:' . $attrs['portType'] . '">';
4273 $binding_xml .= '<soap:binding style="' . $attrs['style'] . '" transport="' . $attrs['transport'] . '"/>';
4274 $portType_xml .= '<portType name="' . $attrs['portType'] . '">';
4275 foreach($attrs['operations'] as $opName => $opParts) {
4276 $binding_xml .= '<operation name="' . $opName . '">';
4277 $binding_xml .= '<soap:operation soapAction="' . $opParts['soapAction'] . '" style="'. $opParts['style'] . '"/>';
4278 if (isset($opParts['input']['encodingStyle']) && $opParts['input']['encodingStyle'] != '') {
4279 $enc_style = ' encodingStyle="' . $opParts['input']['encodingStyle'] . '"';
4283 $binding_xml .= '<input><soap:body use="' . $opParts['input']['use'] . '" namespace="' . $opParts['input']['namespace'] . '"' . $enc_style . '/></input>';
4284 if (isset($opParts['output']['encodingStyle']) && $opParts['output']['encodingStyle'] != '') {
4285 $enc_style = ' encodingStyle="' . $opParts['output']['encodingStyle'] . '"';
4289 $binding_xml .= '<output><soap:body use="' . $opParts['output']['use'] . '" namespace="' . $opParts['output']['namespace'] . '"' . $enc_style . '/></output>';
4290 $binding_xml .= '</operation>';
4291 $portType_xml .= '<operation name="' . $opParts['name'] . '"';
4292 if (isset($opParts['parameterOrder'])) {
4293 $portType_xml .= ' parameterOrder="' . $opParts['parameterOrder'] . '"';
4295 $portType_xml .= '>';
4296 if(isset($opParts['documentation']) && $opParts['documentation'] != '') {
4297 $portType_xml .= '<documentation>' . htmlspecialchars($opParts['documentation']) . '</documentation>';
4299 $portType_xml .= '<input message="tns:' . $opParts['input']['message'] . '"/>';
4300 $portType_xml .= '<output message="tns:' . $opParts['output']['message'] . '"/>';
4301 $portType_xml .= '</operation>';
4303 $portType_xml .= '</portType>';
4304 $binding_xml .= '</binding>';
4306 $xml .= $portType_xml . $binding_xml;
4309 $xml .= '<service name="' . $this->serviceName . '">';
4310 if (count($this->ports) >= 1) {
4311 foreach($this->ports as $pName => $attrs) {
4312 $xml .= '<port name="' . $pName . '" binding="tns:' . $attrs['binding'] . '">';
4313 $xml .= '<soap:address location="' . $attrs['location'] . ($debug ? '?debug=1' : '') . '"/>';
4317 $xml .= '</service>';
4318 return $xml . '</definitions>';
4322 * serialize a PHP value according to a WSDL message definition
4325 * - multi-ref serialization
4326 * - validate PHP values against type definitions, return errors if invalid
4328 * @param string $ type name
4329 * @param mixed $ param value
4330 * @return mixed new param or false if initial value didn't validate
4332 function serializeRPCParameters($operation, $direction, $parameters)
4334 $this->debug('in serializeRPCParameters with operation '.$operation.', direction '.$direction.' and '.count($parameters).' param(s), and xml schema version ' . $this->XMLSchemaVersion);
4336 if ($direction != 'input' && $direction != 'output') {
4337 $this->debug('The value of the \$direction argument needs to be either "input" or "output"');
4338 $this->setError('The value of the \$direction argument needs to be either "input" or "output"');
4341 if (!$opData = $this->getOperationData($operation)) {
4342 $this->debug('Unable to retrieve WSDL data for operation: ' . $operation);
4343 $this->setError('Unable to retrieve WSDL data for operation: ' . $operation);
4346 $this->debug('opData:');
4347 $this->appendDebug($this->varDump($opData));
4349 // Get encoding style for output and set to current
4350 $encodingStyle = 'http://schemas.xmlsoap.org/soap/encoding/';
4351 if(($direction == 'input') && isset($opData['output']['encodingStyle']) && ($opData['output']['encodingStyle'] != $encodingStyle)) {
4352 $encodingStyle = $opData['output']['encodingStyle'];
4353 $enc_style = $encodingStyle;
4358 if (isset($opData[$direction]['parts']) && sizeof($opData[$direction]['parts']) > 0) {
4360 $use = $opData[$direction]['use'];
4361 $this->debug('have ' . count($opData[$direction]['parts']) . ' part(s) to serialize');
4362 if (is_array($parameters)) {
4363 $parametersArrayType = $this->isArraySimpleOrStruct($parameters);
4364 $this->debug('have ' . count($parameters) . ' parameter(s) provided as ' . $parametersArrayType . ' to serialize');
4365 foreach($opData[$direction]['parts'] as $name => $type) {
4366 $this->debug('serializing part "'.$name.'" of type "'.$type.'"');
4367 // Track encoding style
4368 if (isset($opData[$direction]['encodingStyle']) && $encodingStyle != $opData[$direction]['encodingStyle']) {
4369 $encodingStyle = $opData[$direction]['encodingStyle'];
4370 $enc_style = $encodingStyle;
4374 // NOTE: add error handling here
4375 // if serializeType returns false, then catch global error and fault
4376 if ($parametersArrayType == 'arraySimple') {
4377 $p = array_shift($parameters);
4378 $this->debug('calling serializeType w/indexed param');
4379 $xml .= $this->serializeType($name, $type, $p, $use, $enc_style);
4380 } elseif (isset($parameters[$name])) {
4381 $this->debug('calling serializeType w/named param');
4382 $xml .= $this->serializeType($name, $type, $parameters[$name], $use, $enc_style);
4384 // TODO: only send nillable
4385 $this->debug('calling serializeType w/null param');
4386 $xml .= $this->serializeType($name, $type, null, $use, $enc_style);
4390 $this->debug('no parameters passed.');
4393 $this->debug("serializeRPCParameters returning: $xml");
4398 * serialize a PHP value according to a WSDL message definition
4401 * - multi-ref serialization
4402 * - validate PHP values against type definitions, return errors if invalid
4404 * @param string $ type name
4405 * @param mixed $ param value
4406 * @return mixed new param or false if initial value didn't validate
4408 function serializeParameters($operation, $direction, $parameters)
4410 $this->debug('in serializeParameters with operation '.$operation.', direction '.$direction.' and '.count($parameters).' param(s), and xml schema version ' . $this->XMLSchemaVersion);
4412 if ($direction != 'input' && $direction != 'output') {
4413 $this->debug('The value of the \$direction argument needs to be either "input" or "output"');
4414 $this->setError('The value of the \$direction argument needs to be either "input" or "output"');
4417 if (!$opData = $this->getOperationData($operation)) {
4418 $this->debug('Unable to retrieve WSDL data for operation: ' . $operation);
4419 $this->setError('Unable to retrieve WSDL data for operation: ' . $operation);
4422 $this->debug('opData:');
4423 $this->appendDebug($this->varDump($opData));
4425 // Get encoding style for output and set to current
4426 $encodingStyle = 'http://schemas.xmlsoap.org/soap/encoding/';
4427 if(($direction == 'input') && isset($opData['output']['encodingStyle']) && ($opData['output']['encodingStyle'] != $encodingStyle)) {
4428 $encodingStyle = $opData['output']['encodingStyle'];
4429 $enc_style = $encodingStyle;
4434 if (isset($opData[$direction]['parts']) && sizeof($opData[$direction]['parts']) > 0) {
4436 $use = $opData[$direction]['use'];
4437 $this->debug("use=$use");
4438 $this->debug('got ' . count($opData[$direction]['parts']) . ' part(s)');
4439 if (is_array($parameters)) {
4440 $parametersArrayType = $this->isArraySimpleOrStruct($parameters);
4441 $this->debug('have ' . $parametersArrayType . ' parameters');
4442 foreach($opData[$direction]['parts'] as $name => $type) {
4443 $this->debug('serializing part "'.$name.'" of type "'.$type.'"');
4444 // Track encoding style
4445 if(isset($opData[$direction]['encodingStyle']) && $encodingStyle != $opData[$direction]['encodingStyle']) {
4446 $encodingStyle = $opData[$direction]['encodingStyle'];
4447 $enc_style = $encodingStyle;
4451 // NOTE: add error handling here
4452 // if serializeType returns false, then catch global error and fault
4453 if ($parametersArrayType == 'arraySimple') {
4454 $p = array_shift($parameters);
4455 $this->debug('calling serializeType w/indexed param');
4456 $xml .= $this->serializeType($name, $type, $p, $use, $enc_style);
4457 } elseif (isset($parameters[$name])) {
4458 $this->debug('calling serializeType w/named param');
4459 $xml .= $this->serializeType($name, $type, $parameters[$name], $use, $enc_style);
4461 // TODO: only send nillable
4462 $this->debug('calling serializeType w/null param');
4463 $xml .= $this->serializeType($name, $type, null, $use, $enc_style);
4467 $this->debug('no parameters passed.');
4470 $this->debug("serializeParameters returning: $xml");
4475 * serializes a PHP value according a given type definition
4477 * @param string $name , name of type (part)
4478 * @param string $type , type of type, heh (type or element)
4479 * @param mixed $value , a native PHP value (parameter value)
4480 * @param string $use , use for part (encoded|literal)
4481 * @param string $encodingStyle , use to add encoding changes to serialisation
4482 * @param boolean $unqualified , a kludge for what should be form handling
4483 * @return string serialization
4486 function serializeType($name, $type, $value, $use='encoded', $encodingStyle=false, $unqualified=false)
4488 $this->debug("in serializeType: $name, $type, $use, $encodingStyle, " . ($unqualified ? "unqualified" : "qualified"));
4489 $this->debug("value:");
4490 $this->appendDebug($this->varDump($value));
4491 if($use == 'encoded' && $encodingStyle) {
4492 $encodingStyle = ' SOAP-ENV:encodingStyle="' . $encodingStyle . '"';
4495 // if a soap_val has been supplied, let its type override the WSDL
4496 if (is_object($value) && get_class($value) == 'soapval') {
4497 // TODO: get attributes from soapval?
4498 if ($value->type_ns) {
4499 $type = $value->type_ns . ':' . $value->type;
4501 $this->debug("in serializeType: soapval overrides type to $type");
4502 } elseif ($value->type) {
4503 $type = $value->type;
4505 $this->debug("in serializeType: soapval overrides type to $type");
4508 $this->debug("in serializeType: soapval does not override type");
4510 $attrs = $value->attributes;
4511 $value = $value->value;
4512 $this->debug("in serializeType: soapval overrides value to $value");
4514 foreach ($attrs as $n => $v) {
4515 $value['!' . $n] = $v;
4517 $this->debug("in serializeType: soapval provides attributes");
4524 if (strpos($type, ':')) {
4525 $uqType = substr($type, strrpos($type, ':') + 1);
4526 $ns = substr($type, 0, strrpos($type, ':'));
4527 $this->debug("got a prefixed type: $uqType, $ns");
4528 if ($this->getNamespaceFromPrefix($ns)) {
4529 $ns = $this->getNamespaceFromPrefix($ns);
4530 $this->debug("expanded prefixed type: $uqType, $ns");
4533 if($ns == $this->XMLSchemaVersion || $ns == 'http://schemas.xmlsoap.org/soap/encoding/'){
4534 $this->debug('type namespace indicates XML Schema or SOAP Encoding type');
4536 $elementNS = " xmlns=\"\"";
4540 if (is_null($value)) {
4541 if ($use == 'literal') {
4542 // TODO: depends on nillable
4543 $xml = "<$name$elementNS/>";
4545 $xml = "<$name$elementNS xsi:nil=\"true\"/>";
4547 $this->debug("serializeType returning: $xml");
4550 if ($uqType == 'boolean' && !$value) {
4552 } elseif ($uqType == 'boolean') {
4555 if ($uqType == 'string' && gettype($value) == 'string') {
4556 $value = $this->expandEntities($value);
4558 if (($uqType == 'long' || $uqType == 'unsignedLong') && gettype($value) == 'double') {
4559 $value = sprintf("%.0lf", $value);
4562 // TODO: what about null/nil values?
4563 // check type isn't a custom type extending xmlschema namespace
4564 if (!$this->getTypeDef($uqType, $ns)) {
4565 if ($use == 'literal') {
4567 $xml = "<$name$elementNS xsi:type=\"" . $this->getPrefixFromNamespace($ns) . ":$uqType\">$value</$name>";
4569 $xml = "<$name$elementNS>$value</$name>";
4572 $xml = "<$name$elementNS xsi:type=\"" . $this->getPrefixFromNamespace($ns) . ":$uqType\"$encodingStyle>$value</$name>";
4574 $this->debug("serializeType returning: $xml");
4577 $this->debug('custom type extends XML Schema or SOAP Encoding namespace (yuck)');
4578 } else if ($ns == 'http://xml.apache.org/xml-soap') {
4579 if ($uqType == 'Map') {
4581 foreach($value as $k => $v) {
4582 $this->debug("serializing map element: key $k, value $v");
4583 $contents .= '<item>';
4584 $contents .= $this->serialize_val($k,'key',false,false,false,false,$use);
4585 $contents .= $this->serialize_val($v,'value',false,false,false,false,$use);
4586 $contents .= '</item>';
4588 if ($use == 'literal') {
4590 $xml = "<$name xsi:type=\"" . $this->getPrefixFromNamespace('http://xml.apache.org/xml-soap') . ":$uqType\">$contents</$name>";
4592 $xml = "<$name>$contents</$name>";
4595 $xml = "<$name xsi:type=\"" . $this->getPrefixFromNamespace('http://xml.apache.org/xml-soap') . ":$uqType\"$encodingStyle>$contents</$name>";
4597 $this->debug("serializeType returning: $xml");
4602 $this->debug("No namespace for type $type");
4606 if(!$typeDef = $this->getTypeDef($uqType, $ns)){
4607 $this->setError("$type ($uqType) is not a supported type.");
4608 $this->debug("$type ($uqType) is not a supported type.");
4611 $this->debug("typedef:");
4612 $this->appendDebug($this->varDump($typeDef));
4614 $phpType = $typeDef['phpType'];
4615 $this->debug("serializeType: uqType: $uqType, ns: $ns, phptype: $phpType, arrayType: " . (isset($typeDef['arrayType']) ? $typeDef['arrayType'] : '') );
4616 // if php type == struct, map value to the <all> element names
4617 if ($phpType == 'struct') {
4618 if (isset($typeDef['typeClass']) && $typeDef['typeClass'] == 'element') {
4619 $elementName = $uqType;
4620 if (isset($typeDef['form']) && ($typeDef['form'] == 'qualified')) {
4621 $elementNS = " xmlns=\"$ns\"";
4623 $elementNS = " xmlns=\"\"";
4626 $elementName = $name;
4628 $elementNS = " xmlns=\"\"";
4633 if (is_null($value)) {
4634 if ($use == 'literal') {
4635 // TODO: depends on nillable
4636 $xml = "<$elementName$elementNS/>";
4638 $xml = "<$elementName$elementNS xsi:nil=\"true\"/>";
4640 $this->debug("serializeType returning: $xml");
4643 $elementAttrs = $this->serializeComplexTypeAttributes($typeDef, $value, $ns, $uqType);
4644 if ($use == 'literal') {
4646 $xml = "<$elementName$elementNS$elementAttrs xsi:type=\"" . $this->getPrefixFromNamespace($ns) . ":$uqType\">";
4648 $xml = "<$elementName$elementNS$elementAttrs>";
4651 $xml = "<$elementName$elementNS$elementAttrs xsi:type=\"" . $this->getPrefixFromNamespace($ns) . ":$uqType\"$encodingStyle>";
4654 $xml .= $this->serializeComplexTypeElements($typeDef, $value, $ns, $uqType, $use, $encodingStyle);
4655 $xml .= "</$elementName>";
4656 } elseif ($phpType == 'array') {
4657 if (isset($typeDef['form']) && ($typeDef['form'] == 'qualified')) {
4658 $elementNS = " xmlns=\"$ns\"";
4661 $elementNS = " xmlns=\"\"";
4666 if (is_null($value)) {
4667 if ($use == 'literal') {
4668 // TODO: depends on nillable
4669 $xml = "<$name$elementNS/>";
4671 $xml = "<$name$elementNS xsi:nil=\"true\"/>";
4673 $this->debug("serializeType returning: $xml");
4676 if (isset($typeDef['multidimensional'])) {
4678 foreach($value as $v) {
4679 $cols = ',' . sizeof($v);
4680 $nv = array_merge($nv, $v);
4686 if (is_array($value) && sizeof($value) >= 1) {
4687 $rows = sizeof($value);
4689 foreach($value as $k => $v) {
4690 $this->debug("serializing array element: $k, $v of type: $typeDef[arrayType]");
4691 //if (strpos($typeDef['arrayType'], ':') ) {
4692 if (!in_array($typeDef['arrayType'],$this->typemap['http://www.w3.org/2001/XMLSchema'])) {
4693 $contents .= $this->serializeType('item', $typeDef['arrayType'], $v, $use);
4695 $contents .= $this->serialize_val($v, 'item', $typeDef['arrayType'], null, $this->XMLSchemaVersion, false, $use);
4702 // TODO: for now, an empty value will be serialized as a zero element
4703 // array. Revisit this when coding the handling of null/nil values.
4704 if ($use == 'literal') {
4705 $xml = "<$name$elementNS>"
4709 $xml = "<$name$elementNS xsi:type=\"".$this->getPrefixFromNamespace('http://schemas.xmlsoap.org/soap/encoding/').':Array" '.
4710 $this->getPrefixFromNamespace('http://schemas.xmlsoap.org/soap/encoding/')
4712 .$this->getPrefixFromNamespace($this->getPrefix($typeDef['arrayType']))
4713 .":".$this->getLocalPart($typeDef['arrayType'])."[$rows$cols]\">"
4717 } elseif ($phpType == 'scalar') {
4718 if (isset($typeDef['form']) && ($typeDef['form'] == 'qualified')) {
4719 $elementNS = " xmlns=\"$ns\"";
4722 $elementNS = " xmlns=\"\"";
4727 if ($use == 'literal') {
4729 $xml = "<$name$elementNS xsi:type=\"" . $this->getPrefixFromNamespace($ns) . ":$uqType\">$value</$name>";
4731 $xml = "<$name$elementNS>$value</$name>";
4734 $xml = "<$name$elementNS xsi:type=\"" . $this->getPrefixFromNamespace($ns) . ":$uqType\"$encodingStyle>$value</$name>";
4737 $this->debug("serializeType returning: $xml");
4742 * serializes the attributes for a complexType
4744 * @param array $typeDef
4745 * @param mixed $value , a native PHP value (parameter value)
4746 * @param string $ns the namespace of the type
4747 * @param string $uqType the local part of the type
4748 * @return string serialization
4751 function serializeComplexTypeAttributes($typeDef, $value, $ns, $uqType) {
4753 if (isset($typeDef['attrs']) && is_array($typeDef['attrs'])) {
4754 $this->debug("serialize attributes for XML Schema type $ns:$uqType");
4755 if (is_array($value)) {
4757 } elseif (is_object($value)) {
4758 $xvalue = get_object_vars($value);
4760 $this->debug("value is neither an array nor an object for XML Schema type $ns:$uqType");
4763 foreach ($typeDef['attrs'] as $aName => $attrs) {
4764 if (isset($xvalue['!' . $aName])) {
4765 $xname = '!' . $aName;
4766 $this->debug("value provided for attribute $aName with key $xname");
4767 } elseif (isset($xvalue[$aName])) {
4769 $this->debug("value provided for attribute $aName with key $xname");
4770 } elseif (isset($attrs['default'])) {
4771 $xname = '!' . $aName;
4772 $xvalue[$xname] = $attrs['default'];
4773 $this->debug('use default value of ' . $xvalue[$aName] . ' for attribute ' . $aName);
4776 $this->debug("no value provided for attribute $aName");
4779 $xml .= " $aName=\"" . $xvalue[$xname] . "\"";
4783 $this->debug("no attributes to serialize for XML Schema type $ns:$uqType");
4785 if (isset($typeDef['extensionBase'])) {
4786 $ns = $this->getPrefix($typeDef['extensionBase']);
4787 $uqType = $this->getLocalPart($typeDef['extensionBase']);
4788 if ($this->getNamespaceFromPrefix($ns)) {
4789 $ns = $this->getNamespaceFromPrefix($ns);
4791 if ($typeDef = $this->getTypeDef($uqType, $ns)) {
4792 $this->debug("serialize attributes for extension base $ns:$uqType");
4793 $xml .= $this->serializeComplexTypeElements($typeDef, $value, $ns, $uqType);
4795 $this->debug("extension base $ns:$uqType is not a supported type");
4802 * serializes the elements for a complexType
4804 * @param array $typeDef
4805 * @param mixed $value , a native PHP value (parameter value)
4806 * @param string $ns the namespace of the type
4807 * @param string $uqType the local part of the type
4808 * @param string $use , use for part (encoded|literal)
4809 * @param string $encodingStyle , use to add encoding changes to serialisation
4810 * @return string serialization
4813 function serializeComplexTypeElements($typeDef, $value, $ns, $uqType, $use='encoded', $encodingStyle=false) {
4815 if (isset($typeDef['elements']) && is_array($typeDef['elements'])) {
4816 $this->debug("serialize elements for XML Schema type $ns:$uqType");
4817 if (is_array($value)) {
4819 } elseif (is_object($value)) {
4820 $xvalue = get_object_vars($value);
4822 $this->debug("value is neither an array nor an object for XML Schema type $ns:$uqType");
4825 // toggle whether all elements are present - ideally should validate against schema
4826 if (count($typeDef['elements']) != count($xvalue)){
4829 foreach ($typeDef['elements'] as $eName => $attrs) {
4830 if (!isset($xvalue[$eName])) {
4831 if (isset($attrs['default'])) {
4832 $xvalue[$eName] = $attrs['default'];
4833 $this->debug('use default value of ' . $xvalue[$eName] . ' for element ' . $eName);
4836 // if user took advantage of a minOccurs=0, then only serialize named parameters
4837 if (isset($optionals) && !isset($xvalue[$eName])){
4839 $this->debug("no value provided for complexType element $eName, so serialize nothing");
4842 if (isset($xvalue[$eName])) {
4843 $v = $xvalue[$eName];
4847 if (isset($attrs['form'])) {
4848 $unqualified = ($attrs['form'] == 'unqualified');
4850 $unqualified = false;
4852 // TODO: if maxOccurs > 1 (not just unbounded), then allow serialization of an array
4853 if (isset($attrs['maxOccurs']) && $attrs['maxOccurs'] == 'unbounded' && isset($v) && is_array($v) && $this->isArraySimpleOrStruct($v) == 'arraySimple') {
4855 foreach ($vv as $k => $v) {
4856 if (isset($attrs['type'])) {
4857 // serialize schema-defined type
4858 $xml .= $this->serializeType($eName, $attrs['type'], $v, $use, $encodingStyle, $unqualified);
4860 // serialize generic type
4861 $this->debug("calling serialize_val() for $v, $eName, false, false, false, false, $use");
4862 $xml .= $this->serialize_val($v, $eName, false, false, false, false, $use);
4866 if (isset($attrs['type'])) {
4867 // serialize schema-defined type
4868 $xml .= $this->serializeType($eName, $attrs['type'], $v, $use, $encodingStyle, $unqualified);
4870 // serialize generic type
4871 $this->debug("calling serialize_val() for $v, $eName, false, false, false, false, $use");
4872 $xml .= $this->serialize_val($v, $eName, false, false, false, false, $use);
4878 $this->debug("no elements to serialize for XML Schema type $ns:$uqType");
4880 if (isset($typeDef['extensionBase'])) {
4881 $ns = $this->getPrefix($typeDef['extensionBase']);
4882 $uqType = $this->getLocalPart($typeDef['extensionBase']);
4883 if ($this->getNamespaceFromPrefix($ns)) {
4884 $ns = $this->getNamespaceFromPrefix($ns);
4886 if ($typeDef = $this->getTypeDef($uqType, $ns)) {
4887 $this->debug("serialize elements for extension base $ns:$uqType");
4888 $xml .= $this->serializeComplexTypeElements($typeDef, $value, $ns, $uqType, $use, $encodingStyle);
4890 $this->debug("extension base $ns:$uqType is not a supported type");
4897 * adds an XML Schema complex type to the WSDL types
4900 * @param typeClass (complexType|simpleType|attribute)
4901 * @param phpType: currently supported are array and struct (php assoc array)
4902 * @param compositor (all|sequence|choice)
4903 * @param restrictionBase namespace:name (http://schemas.xmlsoap.org/soap/encoding/:Array)
4904 * @param elements = array ( name = array(name=>'',type=>'') )
4905 * @param attrs = array(
4907 * 'ref' => "http://schemas.xmlsoap.org/soap/encoding/:arrayType",
4908 * "http://schemas.xmlsoap.org/wsdl/:arrayType" => "string[]"
4911 * @param arrayType: namespace:name (http://www.w3.org/2001/XMLSchema:string)
4915 function addComplexType($name,$typeClass='complexType',$phpType='array',$compositor='',$restrictionBase='',$elements=array(),$attrs=array(),$arrayType='') {
4916 if (count($elements) > 0) {
4917 foreach($elements as $n => $e){
4918 // expand each element
4919 foreach ($e as $k => $v) {
4920 $k = strpos($k,':') ? $this->expandQname($k) : $k;
4921 $v = strpos($v,':') ? $this->expandQname($v) : $v;
4924 $eElements[$n] = $ee;
4926 $elements = $eElements;
4929 if (count($attrs) > 0) {
4930 foreach($attrs as $n => $a){
4931 // expand each attribute
4932 foreach ($a as $k => $v) {
4933 $k = strpos($k,':') ? $this->expandQname($k) : $k;
4934 $v = strpos($v,':') ? $this->expandQname($v) : $v;
4942 $restrictionBase = strpos($restrictionBase,':') ? $this->expandQname($restrictionBase) : $restrictionBase;
4943 $arrayType = strpos($arrayType,':') ? $this->expandQname($arrayType) : $arrayType;
4945 $typens = isset($this->namespaces['types']) ? $this->namespaces['types'] : $this->namespaces['tns'];
4946 $this->schemas[$typens][0]->addComplexType($name,$typeClass,$phpType,$compositor,$restrictionBase,$elements,$attrs,$arrayType);
4950 * adds an XML Schema simple type to the WSDL types
4953 * @param restrictionBase namespace:name (http://schemas.xmlsoap.org/soap/encoding/:Array)
4954 * @param typeClass (simpleType)
4955 * @param phpType: (scalar)
4959 function addSimpleType($name, $restrictionBase='', $typeClass='simpleType', $phpType='scalar') {
4960 $restrictionBase = strpos($restrictionBase,':') ? $this->expandQname($restrictionBase) : $restrictionBase;
4962 $typens = isset($this->namespaces['types']) ? $this->namespaces['types'] : $this->namespaces['tns'];
4963 $this->schemas[$typens][0]->addSimpleType($name, $restrictionBase, $typeClass, $phpType);
4967 * adds an element to the WSDL types
4970 * @param array $attrs attributes that must include name and type
4973 function addElement($attrs) {
4974 $typens = isset($this->namespaces['types']) ? $this->namespaces['types'] : $this->namespaces['tns'];
4975 $this->schemas[$typens][0]->addElement($attrs);
4979 * register a service with the server
4981 * @param string $methodname
4982 * @param string $in assoc array of input values: key = param name, value = param type
4983 * @param string $out assoc array of output values: key = param name, value = param type
4984 * @param string $namespace optional The namespace for the operation
4985 * @param string $soapaction optional The soapaction for the operation
4986 * @param string $style (rpc|document) optional The style for the operation
4987 * @param string $use (encoded|literal) optional The use for the parameters (cannot mix right now)
4988 * @param string $documentation optional The description to include in the WSDL
4991 function addOperation($name, $in = false, $out = false, $namespace = false, $soapaction = false, $style = 'rpc', $use = 'encoded', $documentation = ''){
4992 if ($style == 'rpc' && $use == 'encoded') {
4993 $encodingStyle = 'http://schemas.xmlsoap.org/soap/encoding/';
4995 $encodingStyle = '';
4998 if ($style == 'document') {
4999 $elements = array();
5000 foreach ($in as $n => $t) {
5001 $elements[$n] = array('name' => $n, 'type' => $t);
5003 $this->addComplexType($name . 'RequestType', 'complexType', 'struct', 'all', '', $elements);
5004 $this->addElement(array('name' => $name, 'type' => $name . 'RequestType'));
5005 $in = array('parameters' => 'tns:' . $name);
5007 $elements = array();
5008 foreach ($out as $n => $t) {
5009 $elements[$n] = array('name' => $n, 'type' => $t);
5011 $this->addComplexType($name . 'ResponseType', 'complexType', 'struct', 'all', '', $elements);
5012 $this->addElement(array('name' => $name . 'Response', 'type' => $name . 'ResponseType'));
5013 $out = array('parameters' => 'tns:' . $name . 'Response');
5017 $this->bindings[ $this->serviceName . 'Binding' ]['operations'][$name] =
5020 'binding' => $this->serviceName . 'Binding',
5021 'endpoint' => $this->endpoint,
5022 'soapAction' => $soapaction,
5026 'namespace' => $namespace,
5027 'encodingStyle' => $encodingStyle,
5028 'message' => $name . 'Request',
5032 'namespace' => $namespace,
5033 'encodingStyle' => $encodingStyle,
5034 'message' => $name . 'Response',
5036 'namespace' => $namespace,
5037 'transport' => 'http://schemas.xmlsoap.org/soap/http',
5038 'documentation' => $documentation);
5043 foreach($in as $pName => $pType)
5045 if(strpos($pType,':')) {
5046 $pType = $this->getNamespaceFromPrefix($this->getPrefix($pType)).":".$this->getLocalPart($pType);
5048 $this->messages[$name.'Request'][$pName] = $pType;
5051 $this->messages[$name.'Request']= '0';
5055 foreach($out as $pName => $pType)
5057 if(strpos($pType,':')) {
5058 $pType = $this->getNamespaceFromPrefix($this->getPrefix($pType)).":".$this->getLocalPart($pType);
5060 $this->messages[$name.'Response'][$pName] = $pType;
5063 $this->messages[$name.'Response']= '0';
5074 * soap_parser class parses SOAP XML messages into native PHP values
5076 * @author Dietrich Ayala <dietrich@ganx4.com>
5080 class soap_parser extends nusoap_base {
5083 var $xml_encoding = '';
5085 var $root_struct = '';
5086 var $root_struct_name = '';
5087 var $root_struct_namespace = '';
5088 var $root_header = '';
5089 var $document = ''; // incoming SOAP body (text)
5090 // determines where in the message we are (envelope,header,body,method)
5094 var $default_namespace = '';
5095 var $namespaces = array();
5096 var $message = array();
5099 var $fault_code = '';
5100 var $fault_str = '';
5101 var $fault_detail = '';
5102 var $depth_array = array();
5103 var $debug_flag = true;
5104 var $soapresponse = NULL;
5105 var $responseHeaders = ''; // incoming SOAP headers (text)
5106 var $body_position = 0;
5107 // for multiref parsing:
5108 // array of id => pos
5110 // array of id => hrefs => pos
5111 var $multirefs = array();
5112 // toggle for auto-decoding element content
5113 var $decode_utf8 = true;
5118 * @param string $xml SOAP message
5119 * @param string $encoding character encoding scheme of message
5120 * @param string $method
5121 * @param string $decode_utf8 whether to decode UTF-8 to ISO-8859-1
5124 function soap_parser($xml,$encoding='UTF-8',$method='',$decode_utf8=true){
5126 $this->xml_encoding = $encoding;
5127 $this->method = $method;
5128 $this->decode_utf8 = $decode_utf8;
5130 // Check whether content has been read.
5132 // Check XML encoding
5133 $pos_xml = strpos($xml, '<?xml');
5134 if ($pos_xml !== FALSE) {
5135 $xml_decl = substr($xml, $pos_xml, strpos($xml, '?>', $pos_xml + 2) - $pos_xml + 1);
5136 if (preg_match("/encoding=[\"']([^\"']*)[\"']/", $xml_decl, $res)) {
5137 $xml_encoding = $res[1];
5138 if (strtoupper($xml_encoding) != $encoding) {
5139 $err = "Charset from HTTP Content-Type '" . $encoding . "' does not match encoding from XML declaration '" . $xml_encoding . "'";
5141 $this->setError($err);
5144 $this->debug('Charset from HTTP Content-Type matches encoding from XML declaration');
5147 $this->debug('No encoding specified in XML declaration');
5150 $this->debug('No XML declaration');
5152 $this->debug('Entering soap_parser(), length='.strlen($xml).', encoding='.$encoding);
5153 // Create an XML parser - why not xml_parser_create_ns?
5154 $this->parser = xml_parser_create($this->xml_encoding);
5155 // Set the options for parsing the XML data.
5156 //xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 1);
5157 xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING, 0);
5158 xml_parser_set_option($this->parser, XML_OPTION_TARGET_ENCODING, $this->xml_encoding);
5159 // Set the object for the parser.
5160 xml_set_object($this->parser, $this);
5161 // Set the element handlers for the parser.
5162 xml_set_element_handler($this->parser, 'start_element','end_element');
5163 xml_set_character_data_handler($this->parser,'character_data');
5165 // Parse the XML file.
5166 if(!xml_parse($this->parser,$xml,true)){
5167 // Display an error message.
5168 $err = sprintf('XML error parsing SOAP payload on line %d: %s',
5169 xml_get_current_line_number($this->parser),
5170 xml_error_string(xml_get_error_code($this->parser)));
5172 $this->debug("XML payload:\n" . $xml);
5173 $this->setError($err);
5175 $this->debug('parsed successfully, found root struct: '.$this->root_struct.' of name '.$this->root_struct_name);
5177 $this->soapresponse = $this->message[$this->root_struct]['result'];
5178 // get header value: no, because this is documented as XML string
5179 // if($this->root_header != '' && isset($this->message[$this->root_header]['result'])){
5180 // $this->responseHeaders = $this->message[$this->root_header]['result'];
5182 // resolve hrefs/ids
5183 if(sizeof($this->multirefs) > 0){
5184 foreach($this->multirefs as $id => $hrefs){
5185 $this->debug('resolving multirefs for id: '.$id);
5186 $idVal = $this->buildVal($this->ids[$id]);
5187 unset($idVal['!id']);
5188 foreach($hrefs as $refPos => $ref){
5189 $this->debug('resolving href at pos '.$refPos);
5190 $this->multirefs[$id][$refPos] = $idVal;
5195 xml_parser_free($this->parser);
5197 $this->debug('xml was empty, didn\'t parse!');
5198 $this->setError('xml was empty, didn\'t parse!');
5203 * start-element handler
5205 * @param string $parser XML parser object
5206 * @param string $name element name
5207 * @param string $attrs associative array of attributes
5210 function start_element($parser, $name, $attrs) {
5211 // position in a total number of elements, starting from 0
5212 // update class level pos
5213 $pos = $this->position++;
5215 $this->message[$pos] = array('pos' => $pos,'children'=>'','cdata'=>'');
5216 // depth = how many levels removed from root?
5217 // set mine as current global depth and increment global depth value
5218 $this->message[$pos]['depth'] = $this->depth++;
5220 // else add self as child to whoever the current parent is
5222 $this->message[$this->parent]['children'] .= '|'.$pos;
5225 $this->message[$pos]['parent'] = $this->parent;
5226 // set self as current parent
5227 $this->parent = $pos;
5228 // set self as current value for this depth
5229 $this->depth_array[$this->depth] = $pos;
5230 // get element prefix
5231 if(strpos($name,':')){
5233 $prefix = substr($name,0,strpos($name,':'));
5234 // get unqualified name
5235 $name = substr(strstr($name,':'),1);
5238 if($name == 'Envelope'){
5239 $this->status = 'envelope';
5240 } elseif($name == 'Header'){
5241 $this->root_header = $pos;
5242 $this->status = 'header';
5243 } elseif($name == 'Body'){
5244 $this->status = 'body';
5245 $this->body_position = $pos;
5247 } elseif($this->status == 'body' && $pos == ($this->body_position+1)){
5248 $this->status = 'method';
5249 $this->root_struct_name = $name;
5250 $this->root_struct = $pos;
5251 $this->message[$pos]['type'] = 'struct';
5252 $this->debug("found root struct $this->root_struct_name, pos $this->root_struct");
5255 $this->message[$pos]['status'] = $this->status;
5257 $this->message[$pos]['name'] = htmlspecialchars($name);
5259 $this->message[$pos]['attrs'] = $attrs;
5261 // loop through atts, logging ns and type declarations
5263 foreach($attrs as $key => $value){
5264 $key_prefix = $this->getPrefix($key);
5265 $key_localpart = $this->getLocalPart($key);
5266 // if ns declarations, add to class level array of valid namespaces
5267 if($key_prefix == 'xmlns'){
5268 if(ereg('^http://www.w3.org/[0-9]{4}/XMLSchema$',$value)){
5269 $this->XMLSchemaVersion = $value;
5270 $this->namespaces['xsd'] = $this->XMLSchemaVersion;
5271 $this->namespaces['xsi'] = $this->XMLSchemaVersion.'-instance';
5273 $this->namespaces[$key_localpart] = $value;
5274 // set method namespace
5275 if($name == $this->root_struct_name){
5276 $this->methodNamespace = $value;
5278 // if it's a type declaration, set type
5279 } elseif($key_localpart == 'type'){
5280 $value_prefix = $this->getPrefix($value);
5281 $value_localpart = $this->getLocalPart($value);
5282 $this->message[$pos]['type'] = $value_localpart;
5283 $this->message[$pos]['typePrefix'] = $value_prefix;
5284 if(isset($this->namespaces[$value_prefix])){
5285 $this->message[$pos]['type_namespace'] = $this->namespaces[$value_prefix];
5286 } else if(isset($attrs['xmlns:'.$value_prefix])) {
5287 $this->message[$pos]['type_namespace'] = $attrs['xmlns:'.$value_prefix];
5289 // should do something here with the namespace of specified type?
5290 } elseif($key_localpart == 'arrayType'){
5291 $this->message[$pos]['type'] = 'array';
5292 /* do arrayType ereg here
5293 [1] arrayTypeValue ::= atype asize
5294 [2] atype ::= QName rank*
5295 [3] rank ::= '[' (',')* ']'
5296 [4] asize ::= '[' length~ ']'
5297 [5] length ::= nextDimension* Digit+
5298 [6] nextDimension ::= Digit+ ','
5300 $expr = '([A-Za-z0-9_]+):([A-Za-z]+[A-Za-z0-9_]+)\[([0-9]+),?([0-9]*)\]';
5301 if(ereg($expr,$value,$regs)){
5302 $this->message[$pos]['typePrefix'] = $regs[1];
5303 $this->message[$pos]['arrayTypePrefix'] = $regs[1];
5304 if (isset($this->namespaces[$regs[1]])) {
5305 $this->message[$pos]['arrayTypeNamespace'] = $this->namespaces[$regs[1]];
5306 } else if (isset($attrs['xmlns:'.$regs[1]])) {
5307 $this->message[$pos]['arrayTypeNamespace'] = $attrs['xmlns:'.$regs[1]];
5309 $this->message[$pos]['arrayType'] = $regs[2];
5310 $this->message[$pos]['arraySize'] = $regs[3];
5311 $this->message[$pos]['arrayCols'] = $regs[4];
5313 } elseif ($key != 'href' && $key != 'xmlns' && $key_localpart != 'encodingStyle' && $key_localpart != 'root') {
5314 $this->message[$pos]['xattrs']['!' . $key] = $value;
5317 if ($key == 'xmlns') {
5318 $this->default_namespace = $value;
5322 $this->ids[$value] = $pos;
5325 if($key_localpart == 'root' && $value == 1){
5326 $this->status = 'method';
5327 $this->root_struct_name = $name;
5328 $this->root_struct = $pos;
5329 $this->debug("found root struct $this->root_struct_name, pos $pos");
5332 $attstr .= " $key=\"$value\"";
5334 // get namespace - must be done after namespace atts are processed
5336 $this->message[$pos]['namespace'] = $this->namespaces[$prefix];
5337 $this->default_namespace = $this->namespaces[$prefix];
5339 $this->message[$pos]['namespace'] = $this->default_namespace;
5341 if($this->status == 'header'){
5342 if ($this->root_header != $pos) {
5343 $this->responseHeaders .= "<" . (isset($prefix) ? $prefix . ':' : '') . "$name$attstr>";
5345 } elseif($this->root_struct_name != ''){
5346 $this->document .= "<" . (isset($prefix) ? $prefix . ':' : '') . "$name$attstr>";
5351 * end-element handler
5353 * @param string $parser XML parser object
5354 * @param string $name element name
5357 function end_element($parser, $name) {
5358 // position of current element is equal to the last value left in depth_array for my depth
5359 $pos = $this->depth_array[$this->depth--];
5361 // get element prefix
5362 if(strpos($name,':')){
5364 $prefix = substr($name,0,strpos($name,':'));
5365 // get unqualified name
5366 $name = substr(strstr($name,':'),1);
5369 // build to native type
5370 if(isset($this->body_position) && $pos > $this->body_position){
5371 // deal w/ multirefs
5372 if(isset($this->message[$pos]['attrs']['href'])){
5374 $id = substr($this->message[$pos]['attrs']['href'],1);
5375 // add placeholder to href array
5376 $this->multirefs[$id][$pos] = 'placeholder';
5377 // add set a reference to it as the result value
5378 $this->message[$pos]['result'] =& $this->multirefs[$id][$pos];
5379 // build complex values
5380 } elseif($this->message[$pos]['children'] != ''){
5382 // if result has already been generated (struct/array)
5383 if(!isset($this->message[$pos]['result'])){
5384 $this->message[$pos]['result'] = $this->buildVal($pos);
5386 // build complex values of attributes and possibly simpleContent
5387 } elseif (isset($this->message[$pos]['xattrs'])) {
5388 if (isset($this->message[$pos]['cdata']) && $this->message[$pos]['cdata'] != '') {
5389 if (isset($this->message[$pos]['type'])) {
5390 $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'] : '');
5392 $parent = $this->message[$pos]['parent'];
5393 if (isset($this->message[$parent]['type']) && ($this->message[$parent]['type'] == 'array') && isset($this->message[$parent]['arrayType'])) {
5394 $this->message[$pos]['xattrs']['!'] = $this->decodeSimple($this->message[$pos]['cdata'], $this->message[$parent]['arrayType'], isset($this->message[$parent]['arrayTypeNamespace']) ? $this->message[$parent]['arrayTypeNamespace'] : '');
5396 $this->message[$pos]['xattrs']['!'] = $this->message[$pos]['cdata'];
5400 $this->message[$pos]['result'] = $this->message[$pos]['xattrs'];
5401 // set value of simple type
5403 //$this->debug('adding data for scalar value '.$this->message[$pos]['name'].' of value '.$this->message[$pos]['cdata']);
5404 if (isset($this->message[$pos]['type'])) {
5405 $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'] : '');
5407 $parent = $this->message[$pos]['parent'];
5408 if (isset($this->message[$parent]['type']) && ($this->message[$parent]['type'] == 'array') && isset($this->message[$parent]['arrayType'])) {
5409 $this->message[$pos]['result'] = $this->decodeSimple($this->message[$pos]['cdata'], $this->message[$parent]['arrayType'], isset($this->message[$parent]['arrayTypeNamespace']) ? $this->message[$parent]['arrayTypeNamespace'] : '');
5411 $this->message[$pos]['result'] = $this->message[$pos]['cdata'];
5415 /* add value to parent's result, if parent is struct/array
5416 $parent = $this->message[$pos]['parent'];
5417 if($this->message[$parent]['type'] != 'map'){
5418 if(strtolower($this->message[$parent]['type']) == 'array'){
5419 $this->message[$parent]['result'][] = $this->message[$pos]['result'];
5421 $this->message[$parent]['result'][$this->message[$pos]['name']] = $this->message[$pos]['result'];
5429 if($this->status == 'header'){
5430 if ($this->root_header != $pos) {
5431 $this->responseHeaders .= "</" . (isset($prefix) ? $prefix . ':' : '') . "$name>";
5433 } elseif($pos >= $this->root_struct){
5434 $this->document .= "</" . (isset($prefix) ? $prefix . ':' : '') . "$name>";
5437 if($pos == $this->root_struct){
5438 $this->status = 'body';
5439 $this->root_struct_namespace = $this->message[$pos]['namespace'];
5440 } elseif($name == 'Body'){
5441 $this->status = 'envelope';
5442 } elseif($name == 'Header'){
5443 $this->status = 'envelope';
5444 } elseif($name == 'Envelope'){
5447 // set parent back to my parent
5448 $this->parent = $this->message[$pos]['parent'];
5452 * element content handler
5454 * @param string $parser XML parser object
5455 * @param string $data element content
5458 function character_data($parser, $data){
5459 $pos = $this->depth_array[$this->depth];
5460 if ($this->xml_encoding=='UTF-8'){
5461 // TODO: add an option to disable this for folks who want
5462 // raw UTF-8 that, e.g., might not map to iso-8859-1
5463 // TODO: this can also be handled with xml_parser_set_option($this->parser, XML_OPTION_TARGET_ENCODING, "ISO-8859-1");
5464 if($this->decode_utf8){
5465 $data = addslashes(utf8_decode($data));
5468 $this->message[$pos]['cdata'] .= $data;
5470 if($this->status == 'header'){
5471 $this->responseHeaders .= $data;
5473 $this->document .= $data;
5478 * get the parsed message
5483 function get_response(){
5484 return $this->soapresponse;
5488 * get the parsed headers
5490 * @return string XML or empty if no headers
5493 function getHeaders(){
5494 return $this->responseHeaders;
5500 * @param string $text string to translate
5503 function decode_entities($text){
5504 foreach($this->entities as $entity => $encoded){
5505 $text = str_replace($encoded,$entity,$text);
5511 * decodes simple types into PHP variables
5513 * @param string $value value to decode
5514 * @param string $type XML type to decode
5515 * @param string $typens XML type namespace to decode
5518 function decodeSimple($value, $type, $typens) {
5519 // TODO: use the namespace!
5520 if ((!isset($type)) || $type == 'string' || $type == 'long' || $type == 'unsignedLong') {
5521 return (string) $value;
5523 if ($type == 'int' || $type == 'integer' || $type == 'short' || $type == 'byte') {
5524 return (int) $value;
5526 if ($type == 'float' || $type == 'double' || $type == 'decimal') {
5527 return (double) $value;
5529 if ($type == 'boolean') {
5530 if (strtolower($value) == 'false' || strtolower($value) == 'f') {
5533 return (boolean) $value;
5535 if ($type == 'base64' || $type == 'base64Binary') {
5536 return base64_decode($value);
5538 // obscure numeric types
5539 if ($type == 'nonPositiveInteger' || $type == 'negativeInteger'
5540 || $type == 'nonNegativeInteger' || $type == 'positiveInteger'
5541 || $type == 'unsignedInt'
5542 || $type == 'unsignedShort' || $type == 'unsignedByte') {
5543 return (int) $value;
5546 return (string) $value;
5550 * builds response structures for compound values (arrays/structs)
5552 * @param string $pos position in node tree
5555 function buildVal($pos){
5556 if(!isset($this->message[$pos]['type'])){
5557 $this->message[$pos]['type'] = '';
5559 $this->debug('inside buildVal() for '.$this->message[$pos]['name']."(pos $pos) of type ".$this->message[$pos]['type']);
5560 // if there are children...
5561 if($this->message[$pos]['children'] != ''){
5562 $children = explode('|',$this->message[$pos]['children']);
5563 array_shift($children); // knock off empty
5565 if(isset($this->message[$pos]['arrayCols']) && $this->message[$pos]['arrayCols'] != ''){
5568 foreach($children as $child_pos){
5569 $this->debug("got an MD array element: $r, $c");
5570 $params[$r][] = $this->message[$child_pos]['result'];
5572 if($c == $this->message[$pos]['arrayCols']){
5578 } elseif($this->message[$pos]['type'] == 'array' || $this->message[$pos]['type'] == 'Array'){
5579 $this->debug('adding array '.$this->message[$pos]['name']);
5580 foreach($children as $child_pos){
5581 $params[] = &$this->message[$child_pos]['result'];
5583 // apache Map type: java hashtable
5584 } elseif($this->message[$pos]['type'] == 'Map' && $this->message[$pos]['type_namespace'] == 'http://xml.apache.org/xml-soap'){
5585 foreach($children as $child_pos){
5586 $kv = explode("|",$this->message[$child_pos]['children']);
5587 $params[$this->message[$kv[1]]['result']] = &$this->message[$kv[2]]['result'];
5589 // generic compound type
5590 //} elseif($this->message[$pos]['type'] == 'SOAPStruct' || $this->message[$pos]['type'] == 'struct') {
5592 // Apache Vector type: treat as an array
5593 if ($this->message[$pos]['type'] == 'Vector' && $this->message[$pos]['type_namespace'] == 'http://xml.apache.org/xml-soap') {
5599 foreach($children as $child_pos){
5601 $params[] = &$this->message[$child_pos]['result'];
5603 if (isset($params[$this->message[$child_pos]['name']])) {
5604 // de-serialize repeated element name into an array
5605 if ((!is_array($params[$this->message[$child_pos]['name']])) || (!isset($params[$this->message[$child_pos]['name']][0]))) {
5606 $params[$this->message[$child_pos]['name']] = array($params[$this->message[$child_pos]['name']]);
5608 $params[$this->message[$child_pos]['name']][] = &$this->message[$child_pos]['result'];
5610 $params[$this->message[$child_pos]['name']] = &$this->message[$child_pos]['result'];
5615 if (isset($this->message[$pos]['xattrs'])) {
5616 foreach ($this->message[$pos]['xattrs'] as $n => $v) {
5620 return is_array($params) ? $params : array();
5622 $this->debug('no children');
5623 if(strpos($this->message[$pos]['cdata'],'&')){
5624 return strtr($this->message[$pos]['cdata'],array_flip($this->entities));
5626 return $this->message[$pos]['cdata'];
5640 * gfsoapclient higher level class for easy usage.
5644 * // instantiate client with server info
5645 * $gfsoapclient = new gfsoapclient( string path [ ,boolean wsdl] );
5647 * // call method, get results
5648 * echo $gfsoapclient->call( string methodname [ ,array parameters] );
5651 * unset($gfsoapclient);
5653 * @author Dietrich Ayala <dietrich@ganx4.com>
5657 class gfsoapclient extends nusoap_base {
5662 var $certRequest = array();
5663 var $requestHeaders = false; // SOAP headers in request (text)
5664 var $responseHeaders = ''; // SOAP headers from response (incomplete namespace resolution) (text)
5665 var $document = ''; // SOAP body response portion (incomplete namespace resolution) (text)
5667 var $proxyhost = '';
5668 var $proxyport = '';
5669 var $proxyusername = '';
5670 var $proxypassword = '';
5671 var $xml_encoding = ''; // character set encoding of incoming (response) messages
5672 var $http_encoding = false;
5673 var $timeout = 0; // HTTP connection timeout
5674 var $response_timeout = 30; // HTTP response timeout
5675 var $endpointType = ''; // soap|wsdl, empty for WSDL initialization error
5676 var $persistentConnection = false;
5677 var $defaultRpcParams = false; // This is no longer used
5678 var $request = ''; // HTTP request
5679 var $response = ''; // HTTP response
5680 var $responseData = ''; // SOAP payload of response
5681 var $cookies = array(); // Cookies from response or for request
5682 var $decode_utf8 = true; // toggles whether the parser decodes element content w/ utf8_decode()
5683 var $operations = array(); // WSDL operations, empty for WSDL initialization error
5686 * fault related variables
5694 var $fault, $faultcode, $faultstring, $faultdetail;
5699 * @param mixed $endpoint SOAP server or WSDL URL (string), or wsdl instance (object)
5700 * @param bool $wsdl optional, set to true if using WSDL
5701 * @param int $portName optional portName in WSDL document
5702 * @param string $proxyhost
5703 * @param string $proxyport
5704 * @param string $proxyusername
5705 * @param string $proxypassword
5706 * @param integer $timeout set the connection timeout
5707 * @param integer $response_timeout set the response timeout
5710 function gfsoapclient($endpoint,$wsdl = false,$proxyhost = false,$proxyport = false,$proxyusername = false, $proxypassword = false, $timeout = 0, $response_timeout = 30){
5711 $this->endpoint = $endpoint;
5712 $this->proxyhost = $proxyhost;
5713 $this->proxyport = $proxyport;
5714 $this->proxyusername = $proxyusername;
5715 $this->proxypassword = $proxypassword;
5716 $this->timeout = $timeout;
5717 $this->response_timeout = $response_timeout;
5721 if (is_object($endpoint) && is_a($endpoint, 'wsdl')) {
5722 $this->wsdl = $endpoint;
5723 $this->endpoint = $this->wsdl->wsdl;
5724 $this->wsdlFile = $this->endpoint;
5725 $this->debug('existing wsdl instance created from ' . $this->endpoint);
5727 $this->wsdlFile = $this->endpoint;
5729 // instantiate wsdl object and parse wsdl file
5730 $this->debug('instantiating wsdl class with doc: '.$endpoint);
5731 $this->wsdl =& new wsdl($this->wsdlFile,$this->proxyhost,$this->proxyport,$this->proxyusername,$this->proxypassword,$this->timeout,$this->response_timeout);
5733 $this->appendDebug($this->wsdl->getDebug());
5734 $this->wsdl->clearDebug();
5736 if($errstr = $this->wsdl->getError()){
5737 $this->debug('got wsdl error: '.$errstr);
5738 $this->setError('wsdl error: '.$errstr);
5739 } elseif($this->operations = $this->wsdl->getOperations()){
5740 $this->debug( 'got '.count($this->operations).' operations from wsdl '.$this->wsdlFile);
5741 $this->endpointType = 'wsdl';
5743 $this->debug( 'getOperations returned false');
5744 $this->setError('no operations defined in the WSDL document!');
5747 $this->endpointType = 'soap';
5752 * calls method, returns PHP native type
5754 * @param string $method SOAP server URL or path
5755 * @param array $params An array, associative or simple, of the parameters
5756 * for the method call, or a string that is the XML
5757 * for the call. For rpc style, this call will
5758 * wrap the XML in a tag named after the method, as
5759 * well as the SOAP Envelope and Body. For document
5760 * style, this will only wrap with the Envelope and Body.
5761 * IMPORTANT: when using an array with document style,
5762 * in which case there
5763 * is really one parameter, the root of the fragment
5764 * used in the call, which encloses what programmers
5765 * normally think of parameters. A parameter array
5766 * *must* include the wrapper.
5767 * @param string $namespace optional method namespace (WSDL can override)
5768 * @param string $soapAction optional SOAPAction value (WSDL can override)
5769 * @param boolean $headers optional array of soapval objects for headers
5770 * @param boolean $rpcParams optional (no longer used)
5771 * @param string $style optional (rpc|document) the style to use when serializing parameters (WSDL can override)
5772 * @param string $use optional (encoded|literal) the use when serializing parameters (WSDL can override)
5776 function call($operation,$params=array(),$namespace='http://tempuri.org',$soapAction='',$headers=false,$rpcParams=null,$style='rpc',$use='encoded'){
5777 $this->operation = $operation;
5778 $this->fault = false;
5779 $this->setError('');
5780 $this->request = '';
5781 $this->response = '';
5782 $this->responseData = '';
5783 $this->faultstring = '';
5784 $this->faultcode = '';
5785 $this->opData = array();
5787 $this->debug("call: $operation, $params, $namespace, $soapAction, $headers, $style, $use; endpointType: $this->endpointType");
5789 $this->requestHeaders = $headers;
5791 // serialize parameters
5792 if($this->endpointType == 'wsdl' && $opData = $this->getOperationData($operation)){
5793 // use WSDL for operation
5794 $this->opData = $opData;
5795 $this->debug("opData:");
5796 $this->appendDebug($this->varDump($opData));
5797 if (isset($opData['soapAction'])) {
5798 $soapAction = $opData['soapAction'];
5800 $this->endpoint = $opData['endpoint'];
5801 $namespace = isset($opData['input']['namespace']) ? $opData['input']['namespace'] : $namespace;
5802 $style = $opData['style'];
5803 $use = $opData['input']['use'];
5804 // add ns to ns array
5805 if($namespace != '' && !isset($this->wsdl->namespaces[$namespace])){
5806 $nsPrefix = 'ns' . rand(1000, 9999);
5807 $this->wsdl->namespaces[$nsPrefix] = $namespace;
5809 $nsPrefix = $this->wsdl->getPrefixFromNamespace($namespace);
5810 // serialize payload
5811 if (is_string($params)) {
5812 $this->debug("serializing param string for WSDL operation $operation");
5814 } elseif (is_array($params)) {
5815 $this->debug("serializing param array for WSDL operation $operation");
5816 $payload = $this->wsdl->serializeRPCParameters($operation,'input',$params);
5818 $this->debug('params must be array or string');
5819 $this->setError('params must be array or string');
5822 $usedNamespaces = $this->wsdl->usedNamespaces;
5823 // Partial fix for multiple encoding styles in the same function call
5824 $encodingStyle = 'http://schemas.xmlsoap.org/soap/encoding/';
5825 if (isset($opData['output']['encodingStyle']) && $encodingStyle != $opData['output']['encodingStyle']) {
5826 $methodEncodingStyle = ' SOAP-ENV:encodingStyle="' . $opData['output']['encodingStyle'] . '"';
5828 $methodEncodingStyle = '';
5830 $this->appendDebug($this->wsdl->getDebug());
5831 $this->wsdl->clearDebug();
5832 if ($errstr = $this->wsdl->getError()) {
5833 $this->debug('got wsdl error: '.$errstr);
5834 $this->setError('wsdl error: '.$errstr);
5837 } elseif($this->endpointType == 'wsdl') {
5838 // operation not in WSDL
5839 $this->appendDebug($this->wsdl->getDebug());
5840 $this->wsdl->clearDebug();
5841 $this->setError( 'operation '.$operation.' not present.');
5842 $this->debug("operation '$operation' not present.");
5846 //$this->namespaces['ns1'] = $namespace;
5847 $nsPrefix = 'ns' . rand(1000, 9999);
5850 if (is_string($params)) {
5851 $this->debug("serializing param string for operation $operation");
5853 } elseif (is_array($params)) {
5854 $this->debug("serializing param array for operation $operation");
5855 foreach($params as $k => $v){
5856 $payload .= $this->serialize_val($v,$k,false,false,false,false,$use);
5859 $this->debug('params must be array or string');
5860 $this->setError('params must be array or string');
5863 $usedNamespaces = array();
5864 $methodEncodingStyle = '';
5866 // wrap RPC calls with method element
5867 if ($style == 'rpc') {
5868 if ($use == 'literal') {
5869 $this->debug("wrapping RPC request with literal method element");
5871 $payload = "<$operation xmlns=\"$namespace\">" . $payload . "</$operation>";
5873 $payload = "<$operation>" . $payload . "</$operation>";
5876 $this->debug("wrapping RPC request with encoded method element");
5878 $payload = "<$nsPrefix:$operation$methodEncodingStyle xmlns:$nsPrefix=\"$namespace\">" .
5880 "</$nsPrefix:$operation>";
5882 $payload = "<$operation$methodEncodingStyle>" .
5888 // serialize envelope
5889 $soapmsg = $this->serializeEnvelope($payload,$this->requestHeaders,$usedNamespaces,$style,$use);
5890 $this->debug("endpoint: $this->endpoint, soapAction: $soapAction, namespace: $namespace, style: $style, use: $use");
5891 $this->debug('SOAP message length: ' . strlen($soapmsg) . ' contents: ' . substr($soapmsg, 0, 1000));
5893 $return = $this->send($this->getHTTPBody($soapmsg),$soapAction,$this->timeout,$this->response_timeout);
5894 if($errstr = $this->getError()){
5895 $this->debug('Error: '.$errstr);
5898 $this->return = $return;
5899 $this->debug('sent message successfully and got a(n) '.gettype($return).' back');
5902 if(is_array($return) && isset($return['faultcode'])){
5903 $this->debug('got fault');
5904 $this->setError($return['faultcode'].': '.$return['faultstring']);
5905 $this->fault = true;
5906 foreach($return as $k => $v){
5908 $this->debug("$k = $v<br>");
5912 // array of return values
5913 if(is_array($return)){
5914 // multiple 'out' parameters
5915 if(sizeof($return) > 1){
5918 // single 'out' parameter
5919 return array_shift($return);
5920 // nothing returned (ie, echoVoid)
5929 * get available data pertaining to an operation
5931 * @param string $operation operation name
5932 * @return array array of data pertaining to the operation
5935 function getOperationData($operation){
5936 if(isset($this->operations[$operation])){
5937 return $this->operations[$operation];
5939 $this->debug("No data for operation: $operation");
5943 * send the SOAP message
5945 * Note: if the operation has multiple return values
5946 * the return value of this method will be an array
5949 * @param string $msg a SOAPx4 soapmsg object
5950 * @param string $soapaction SOAPAction value
5951 * @param integer $timeout set connection timeout in seconds
5952 * @param integer $response_timeout set response timeout in seconds
5953 * @return mixed native PHP types.
5956 function send($msg, $soapaction = '', $timeout=0, $response_timeout=30) {
5957 $this->checkCookies();
5961 case ereg('^http',$this->endpoint):
5962 $this->debug('transporting via HTTP');
5963 if($this->persistentConnection == true && is_object($this->persistentConnection)){
5964 $http =& $this->persistentConnection;
5966 $http = new soap_transport_http($this->endpoint);
5967 if ($this->persistentConnection) {
5968 $http->usePersistentConnection();
5971 $http->setContentType($this->getHTTPContentType(), $this->getHTTPContentTypeCharset());
5972 $http->setSOAPAction($soapaction);
5973 if($this->proxyhost && $this->proxyport){
5974 $http->setProxy($this->proxyhost,$this->proxyport,$this->proxyusername,$this->proxypassword);
5976 if($this->authtype != '') {
5977 $http->setCredentials($this->username, $this->password, $this->authtype, array(), $this->certRequest);
5979 if($this->http_encoding != ''){
5980 $http->setEncoding($this->http_encoding);
5982 $this->debug('sending message, length: '.strlen($msg));
5983 if(ereg('^http:',$this->endpoint)){
5984 //if(strpos($this->endpoint,'http:')){
5985 $this->responseData = $http->send($msg,$timeout,$response_timeout,$this->cookies);
5986 } elseif(ereg('^https',$this->endpoint)){
5987 //} elseif(strpos($this->endpoint,'https:')){
5988 //if(phpversion() == '4.3.0-dev'){
5989 //$response = $http->send($msg,$timeout,$response_timeout);
5990 //$this->request = $http->outgoing_payload;
5991 //$this->response = $http->incoming_payload;
5993 $this->responseData = $http->sendHTTPS($msg,$timeout,$response_timeout,$this->cookies);
5995 $this->setError('no http/s in endpoint url');
5997 $this->request = $http->outgoing_payload;
5998 $this->response = $http->incoming_payload;
5999 $this->appendDebug($http->getDebug());
6000 $this->UpdateCookies($http->incoming_cookies);
6002 // save transport object if using persistent connections
6003 if ($this->persistentConnection) {
6004 $http->clearDebug();
6005 if (!is_object($this->persistentConnection)) {
6006 $this->persistentConnection = $http;
6010 if($err = $http->getError()){
6011 $this->setError('HTTP Error: '.$err);
6013 } elseif($this->getError()){
6016 $this->debug('got response, length: '. strlen($this->responseData).' type: '.$http->incoming_headers['content-type']);
6017 return $this->parseResponse($http->incoming_headers, $this->responseData);
6021 $this->setError('no transport found, or selected transport is not yet supported!');
6028 * processes SOAP message returned from server
6030 * @param array $headers The HTTP headers
6031 * @param string $data unprocessed response data from server
6032 * @return mixed value of the message, decoded into a PHP type
6035 function parseResponse($headers, $data) {
6036 $this->debug('Entering parseResponse() for data of length ' . strlen($data) . ' and type ' . $headers['content-type']);
6037 if (!strstr($headers['content-type'], 'text/xml')) {
6038 $this->setError('Response not of type text/xml');
6041 if (strpos($headers['content-type'], '=')) {
6042 $enc = str_replace('"', '', substr(strstr($headers["content-type"], '='), 1));
6043 $this->debug('Got response encoding: ' . $enc);
6044 if(eregi('^(ISO-8859-1|US-ASCII|UTF-8)$',$enc)){
6045 $this->xml_encoding = strtoupper($enc);
6047 $this->xml_encoding = 'US-ASCII';
6050 // should be US-ASCII for HTTP 1.0 or ISO-8859-1 for HTTP 1.1
6051 $this->xml_encoding = 'ISO-8859-1';
6053 $this->debug('Use encoding: ' . $this->xml_encoding . ' when creating soap_parser');
6054 $parser = new soap_parser($data,$this->xml_encoding,$this->operation,$this->decode_utf8);
6055 // add parser debug data to our debug
6056 $this->appendDebug($parser->getDebug());
6058 if($errstr = $parser->getError()){
6059 $this->setError( $errstr);
6060 // destroy the parser object
6065 $this->responseHeaders = $parser->getHeaders();
6066 // get decoded message
6067 $return = $parser->get_response();
6068 // add document for doclit support
6069 $this->document = $parser->document;
6070 // destroy the parser object
6072 // return decode message
6078 * set the SOAP headers
6080 * @param $headers string XML
6083 function setHeaders($headers){
6084 $this->requestHeaders = $headers;
6088 * get the response headers
6093 function getHeaders(){
6094 return $this->responseHeaders;
6098 * set proxy info here
6100 * @param string $proxyhost
6101 * @param string $proxyport
6102 * @param string $proxyusername
6103 * @param string $proxypassword
6106 function setHTTPProxy($proxyhost, $proxyport, $proxyusername = '', $proxypassword = '') {
6107 $this->proxyhost = $proxyhost;
6108 $this->proxyport = $proxyport;
6109 $this->proxyusername = $proxyusername;
6110 $this->proxypassword = $proxypassword;
6114 * if authenticating, set user credentials here
6116 * @param string $username
6117 * @param string $password
6118 * @param string $authtype (basic|digest|certificate)
6119 * @param array $certRequest (keys must be cainfofile, sslcertfile, sslkeyfile, passphrase: see corresponding options in cURL docs)
6122 function setCredentials($username, $password, $authtype = 'basic', $certRequest = array()) {
6123 $this->username = $username;
6124 $this->password = $password;
6125 $this->authtype = $authtype;
6126 $this->certRequest = $certRequest;
6132 * @param string $enc
6135 function setHTTPEncoding($enc='gzip, deflate'){
6136 $this->http_encoding = $enc;
6140 * use HTTP persistent connections if possible
6144 function useHTTPPersistentConnection(){
6145 $this->persistentConnection = true;
6149 * gets the default RPC parameter setting.
6150 * If true, default is that call params are like RPC even for document style.
6151 * Each call() can override this value.
6153 * This is no longer used.
6158 function getDefaultRpcParams() {
6159 return $this->defaultRpcParams;
6163 * sets the default RPC parameter setting.
6164 * If true, default is that call params are like RPC even for document style
6165 * Each call() can override this value.
6167 * This is no longer used.
6169 * @param boolean $rpcParams
6173 function setDefaultRpcParams($rpcParams) {
6174 $this->defaultRpcParams = $rpcParams;
6178 * dynamically creates proxy class, allowing user to directly call methods from wsdl
6180 * @return object soap_proxy object
6183 function getProxy(){
6184 if ($this->endpointType != 'wsdl') {
6185 $this->setError('The getProxy method only works for a WSDL client');
6189 foreach($this->operations as $operation => $opData){
6190 if($operation != ''){
6191 // create param string
6193 if(sizeof($opData['input']['parts']) > 0){
6194 foreach($opData['input']['parts'] as $name => $type){
6195 $paramStr .= "\$$name,";
6197 $paramStr = substr($paramStr,0,strlen($paramStr)-1);
6199 $opData['namespace'] = !isset($opData['namespace']) ? 'http://testuri.com' : $opData['namespace'];
6200 $evalStr .= "function " . str_replace('.', '__', $operation) . " ($paramStr) {
6201 // load params into array
6202 \$params = array($paramStr);
6203 return \$this->call('$operation',\$params,'".$opData['namespace']."','".(isset($opData['soapAction']) ? $opData['soapAction'] : '')."');
6209 $evalStr = 'class soap_proxy_'.$r.' extends gfsoapclient {
6212 //print "proxy class:<pre>$evalStr</pre>";
6215 // instantiate proxy object
6216 eval("\$proxy = new soap_proxy_$r('');");
6217 // transfer current wsdl data to the proxy thereby avoiding parsing the wsdl twice
6218 $proxy->endpointType = 'wsdl';
6219 $proxy->wsdlFile = $this->wsdlFile;
6220 $proxy->wsdl = $this->wsdl;
6221 $proxy->operations = $this->operations;
6222 $proxy->defaultRpcParams = $this->defaultRpcParams;
6223 // transfer other state
6224 $proxy->username = $this->username;
6225 $proxy->password = $this->password;
6226 $proxy->authtype = $this->authtype;
6227 $proxy->proxyhost = $this->proxyhost;
6228 $proxy->proxyport = $this->proxyport;
6229 $proxy->proxyusername = $this->proxyusername;
6230 $proxy->proxypassword = $this->proxypassword;
6231 $proxy->timeout = $this->timeout;
6232 $proxy->response_timeout = $this->response_timeout;
6233 $proxy->http_encoding = $this->http_encoding;
6234 $proxy->persistentConnection = $this->persistentConnection;
6235 $proxy->requestHeaders = $this->requestHeaders;
6236 $proxy->soap_defencoding = $this->soap_defencoding;
6241 * gets the HTTP body for the current request.
6243 * @param string $soapmsg The SOAP payload
6244 * @return string The HTTP body, which includes the SOAP payload
6247 function getHTTPBody($soapmsg) {
6252 * gets the HTTP content type for the current request.
6254 * Note: getHTTPBody must be called before this.
6256 * @return string the HTTP content type for the current request.
6259 function getHTTPContentType() {
6264 * gets the HTTP content type charset for the current request.
6265 * returns false for non-text content types.
6267 * Note: getHTTPBody must be called before this.
6269 * @return string the HTTP content type charset for the current request.
6272 function getHTTPContentTypeCharset() {
6273 return $this->soap_defencoding;
6277 * whether or not parser should decode utf8 element content
6279 * @return always returns true
6282 function decodeUTF8($bool){
6283 $this->decode_utf8 = $bool;
6288 * adds a new Cookie into $this->cookies array
6290 * @param string $name Cookie Name
6291 * @param string $value Cookie Value
6292 * @return if cookie-set was successful returns true, else false
6295 function setCookie($name, $value) {
6296 if (strlen($name) == 0) {
6299 $this->cookies[] = array('name' => $name, 'value' => $value);
6306 * @return array with all internal cookies
6309 function getCookies() {
6310 return $this->cookies;
6314 * checks all Cookies and delete those which are expired
6316 * @return always return true
6319 function checkCookies() {
6320 if (sizeof($this->cookies) == 0) {
6323 $this->debug('checkCookie: check ' . sizeof($this->cookies) . ' cookies');
6324 $curr_cookies = $this->cookies;
6325 $this->cookies = array();
6326 foreach ($curr_cookies as $cookie) {
6327 if (! is_array($cookie)) {
6328 $this->debug('Remove cookie that is not an array');
6331 if ((isset($cookie['expires'])) && (! empty($cookie['expires']))) {
6332 if (strtotime($cookie['expires']) > time()) {
6333 $this->cookies[] = $cookie;
6335 $this->debug('Remove expired cookie ' . $cookie['name']);
6338 $this->cookies[] = $cookie;
6341 $this->debug('checkCookie: '.sizeof($this->cookies).' cookies left in array');
6346 * updates the current cookies with a new set
6348 * @param array $cookies new cookies with which to update current ones
6349 * @return always return true
6352 function UpdateCookies($cookies) {
6353 if (sizeof($this->cookies) == 0) {
6354 // no existing cookies: take whatever is new
6355 if (sizeof($cookies) > 0) {
6356 $this->debug('Setting new cookie(s)');
6357 $this->cookies = $cookies;
6361 if (sizeof($cookies) == 0) {
6362 // no new cookies: keep what we've got
6366 foreach ($cookies as $newCookie) {
6367 if (!is_array($newCookie)) {
6370 if ((!isset($newCookie['name'])) || (!isset($newCookie['value']))) {
6373 $newName = $newCookie['name'];
6376 for ($i = 0; $i < count($this->cookies); $i++) {
6377 $cookie = $this->cookies[$i];
6378 if (!is_array($cookie)) {
6381 if (!isset($cookie['name'])) {
6384 if ($newName != $cookie['name']) {
6387 $newDomain = isset($newCookie['domain']) ? $newCookie['domain'] : 'NODOMAIN';
6388 $domain = isset($cookie['domain']) ? $cookie['domain'] : 'NODOMAIN';
6389 if ($newDomain != $domain) {
6392 $newPath = isset($newCookie['path']) ? $newCookie['path'] : 'NOPATH';
6393 $path = isset($cookie['path']) ? $cookie['path'] : 'NOPATH';
6394 if ($newPath != $path) {
6397 $this->cookies[$i] = $newCookie;
6399 $this->debug('Update cookie ' . $newName . '=' . $newCookie['value']);
6403 $this->debug('Add cookie ' . $newName . '=' . $newCookie['value']);
6404 $this->cookies[] = $newCookie;