3 * NuSOAP - Web Services Toolkit for PHP
5 * @author: Dietrich Ayala
9 Copyright (c) 2002 NuSphere Corporation
11 This library is free software; you can redistribute it and/or
12 modify it under the terms of the GNU Lesser General Public
13 License as published by the Free Software Foundation; either
14 version 2.1 of the License, or (at your option) any later version.
16 This library is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 Lesser General Public License for more details.
21 You should have received a copy of the GNU Lesser General Public
22 License along with this library; if not, write to the Free Software
23 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
25 If you have any questions or comments, please email:
29 http://dietrich.ganx4.com/nusoap
32 http://www.nusphere.com
39 require_once('class.soapclient.php');
40 require_once('class.soap_val.php');
41 require_once('class.soap_parser.php');
42 require_once('class.soap_fault.php');
45 require_once('class.soap_transport_http.php');
47 // optional add-on classes
48 require_once('class.xmlschema.php');
49 require_once('class.wsdl.php');
52 require_once('class.soap_server.php');*/
58 * @author Dietrich Ayala <dietrich@ganx4.com>
64 var $title = 'NuSOAP';
65 var $version = '0.6.3';
66 var $error_str = false;
68 // toggles automatic encoding of special characters
69 var $charencoding = true;
74 * @var XMLSchemaVersion
77 var $XMLSchemaVersion = 'http://www.w3.org/2001/XMLSchema';
80 * set default encoding
82 * @var soap_defencoding
85 //var $soap_defencoding = 'UTF-8';
86 var $soap_defencoding = 'ISO-8859-1';
89 * load namespace uris into an array of uri => prefix
94 var $namespaces = array(
95 'SOAP-ENV' => 'http://schemas.xmlsoap.org/soap/envelope/',
96 'xsd' => 'http://www.w3.org/2001/XMLSchema',
97 'xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
98 'SOAP-ENC' => 'http://schemas.xmlsoap.org/soap/encoding/',
99 'si' => 'http://soapinterop.org/xsd');
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.
109 'http://www.w3.org/2001/XMLSchema' =>
111 'string'=>'string','boolean'=>'boolean','float'=>'double','double'=>'double','decimal'=>'double',
112 'duration'=>'','dateTime'=>'string','time'=>'string','date'=>'string','gYearMonth'=>'',
113 'gYear'=>'','gMonthDay'=>'','gDay'=>'','gMonth'=>'','hexBinary'=>'string','base64Binary'=>'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' =>
121 'i4'=>'','int'=>'integer','boolean'=>'boolean','string'=>'string','double'=>'double',
122 'float'=>'double','dateTime'=>'string',
123 'timeInstant'=>'string','base64Binary'=>'string','base64'=>'string','ur-type'=>'array'),
124 'http://soapinterop.org/xsd' => array('SOAPStruct'=>'struct'),
125 'http://schemas.xmlsoap.org/soap/encoding/' => array('base64'=>'string','array'=>'array','Array'=>'array'),
126 'http://xml.apache.org/xml-soap' => array('Map')
130 * entities to convert
135 var $xmlEntities = array('quot' => '"','amp' => '&',
136 'lt' => '<','gt' => '>','apos' => "'");
139 * adds debug data to the class level debug string
141 * @param string $string debug data
144 function debug($string){
145 $this->debug_str .= get_class($this).": $string\n";
149 * returns error string if present
151 * @return boolean $string error string
155 if($this->error_str != ''){
156 return $this->error_str;
164 * @return boolean $string error string
167 function setError($str){
168 $this->error_str = $str;
172 * serializes PHP values in accordance w/ section 5. Type information is
173 * not serialized if $use == 'literal'.
178 function serialize_val($val,$name=false,$type=false,$name_ns=false,$type_ns=false,$attributes=false,$use='encoded'){
179 if(is_object($val) && get_class($val) == 'soapval'){
180 return $val->serialize($use);
182 $this->debug( "in serialize_val: $val, $name, $type, $name_ns, $type_ns, $attributes, $use");
183 // if no name, use item
184 $name = (!$name|| is_numeric($name)) ? 'soapVal' : $name;
185 // if name has ns, add ns prefix to name
188 $prefix = 'nu'.rand(1000,9999);
189 $name = $prefix.':'.$name;
190 $xmlns .= " xmlns:$prefix=\"$name_ns\"";
192 // if type is prefixed, create type prefix
193 if($type_ns != '' && $type_ns == $this->namespaces['xsd']){
194 // need to fix this. shouldn't default to xsd if no ns specified
195 // w/o checking against typemap
196 $type_prefix = 'xsd';
198 $type_prefix = 'ns'.rand(1000,9999);
199 $xmlns .= " xmlns:$type_prefix=\"$type_ns\"";
201 // serialize attributes if present
203 foreach($attributes as $k => $v){
204 $atts .= " $k=\"$v\"";
207 // serialize if an xsd built-in primitive type
208 if ($type != '' && isset($this->typemap[$this->XMLSchemaVersion][$type])){
209 if ($use == 'literal') {
210 return "<$name$xmlns>$val</$name>";
212 return "<$name$xmlns xsi:type=\"xsd:$type\">$val</$name>";
215 // detect type and serialize
219 case ($type == '' && is_null($val)):
220 if ($use == 'literal') {
221 // TODO: depends on nillable
222 $xml .= "<$name$xmlns/>";
224 $xml .= "<$name$xmlns xsi:type=\"xsd:nil\"/>";
227 case (is_bool($val) || $type == 'boolean'):
231 if ($use == 'literal') {
232 $xml .= "<$name$xmlns $atts>$val</$name>";
234 $xml .= "<$name$xmlns xsi:type=\"xsd:boolean\"$atts>$val</$name>";
237 case (is_int($val) || is_long($val) || $type == 'int'):
238 if ($use == 'literal') {
239 $xml .= "<$name$xmlns $atts>$val</$name>";
241 $xml .= "<$name$xmlns xsi:type=\"xsd:int\"$atts>$val</$name>";
244 case (is_float($val)|| is_double($val) || $type == 'float'):
245 if ($use == 'literal') {
246 $xml .= "<$name$xmlns $atts>$val</$name>";
248 $xml .= "<$name$xmlns xsi:type=\"xsd:float\"$atts>$val</$name>";
251 case (is_string($val) || $type == 'string'):
252 if($this->charencoding){
253 $val = htmlspecialchars($val, ENT_QUOTES);
255 if ($use == 'literal') {
256 $xml .= "<$name$xmlns $atts>$val</$name>";
258 $xml .= "<$name$xmlns xsi:type=\"xsd:string\"$atts>$val</$name>";
261 case is_object($val):
262 $name = get_class($val);
263 foreach(get_object_vars($val) as $k => $v){
264 $pXml = isset($pXml) ? $pXml.$this->serialize_val($v,$k,false,false,false,false,$use) : $this->serialize_val($v,$k,false,false,false,false,$use);
266 $xml .= '<'.$name.'>'.$pXml.'</'.$name.'>';
269 case (is_array($val) || $type):
270 // detect if struct or array
271 $keyList = array_keys($val);
272 $valueType = 'arraySimple';
273 foreach($keyList as $keyListValue){
274 if(!is_int($keyListValue)){
275 $valueType = 'arrayStruct';
279 if($valueType=='arraySimple' || ereg('^ArrayOf',$type)){
281 if(is_array($val) && count($val)> 0){
283 if(is_object($v) && get_class($v) == 'soapval'){
288 $array_types[$tt] = 1;
289 $xml .= $this->serialize_val($v,'item',false,false,false,false,$use);
290 if(is_array($v) && is_numeric(key($v))){
296 if(count($array_types) > 1){
297 $array_typename = 'xsd:ur-type';
298 } elseif(isset($tt) && isset($this->typemap[$this->XMLSchemaVersion][$tt])) {
299 $array_typename = 'xsd:'.$tt;
300 } elseif($tt == 'array' || $tt == 'Array'){
301 $array_typename = 'SOAP-ENC:Array';
303 $array_typename = $tt;
305 if(isset($array_types['array'])){
306 $array_type = $i.",".$i;
310 if ($use == 'literal') {
311 $xml = "<$name $atts>".$xml."</$name>";
313 $xml = "<$name xsi:type=\"SOAP-ENC:Array\" SOAP-ENC:arrayType=\"".$array_typename."[$array_type]\"$atts>".$xml."</$name>";
317 if ($use == 'literal') {
318 $xml = "<$name $atts>".$xml."</$name>";;
320 $xml = "<$name xsi:type=\"SOAP-ENC:Array\" $atts>".$xml."</$name>";;
325 if(isset($type) && isset($type_prefix)){
326 $type_str = " xsi:type=\"$type_prefix:$type\"";
330 if ($use == 'literal') {
331 $xml .= "<$name$xmlns $atts>";
333 $xml .= "<$name$xmlns$type_str$atts>";
335 foreach($val as $k => $v){
336 $xml .= $this->serialize_val($v,$k,false,false,false,false,$use);
342 $xml .= 'not detected, got '.gettype($val).' for '.$val;
352 * @param string headers
353 * @param array namespaces
354 * @param string style
355 * @return string message
358 function serializeEnvelope($body,$headers=false,$namespaces=array(),$style='rpc'){
359 // serialize namespaces
361 foreach(array_merge($this->namespaces,$namespaces) as $k => $v){
362 $ns_string .= " xmlns:$k=\"$v\"";
364 if($style == 'rpc') {
365 $ns_string = ' SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"' . $ns_string;
370 $headers = "<SOAP-ENV:Header>".$headers."</SOAP-ENV:Header>";
372 // serialize envelope
374 '<?xml version="1.0" encoding="'.$this->soap_defencoding .'"?'.">".
375 '<SOAP-ENV:Envelope'.$ns_string.">".
380 "</SOAP-ENV:Envelope>";
383 function formatDump($str){
384 $str = htmlspecialchars($str);
389 * returns the local part of a prefixed string
390 * returns the original string, if not prefixed
396 function getLocalPart($str){
397 if($sstr = strrchr($str,':')){
398 // get unqualified name
399 return substr( $sstr, 1 );
406 * returns the prefix part of a prefixed string
407 * returns false, if not prefixed
413 function getPrefix($str){
414 if($pos = strrpos($str,':')){
416 return substr($str,0,$pos);
421 function varDump($data) {
424 $ret_val = ob_get_contents();
430 // XML Schema Datatype Helper Functions
432 //xsd:dateTime helpers
435 * convert unix timestamp to ISO 8601 compliant date string
437 * @param string $timestamp Unix time stamp
440 function timestamp_to_iso8601($timestamp,$utc=true){
441 $datestr = date('Y-m-d\TH:i:sO',$timestamp);
444 '([0-9]{4})-'. // centuries & years CCYY-
445 '([0-9]{2})-'. // months MM-
446 '([0-9]{2})'. // days DD
448 '([0-9]{2}):'. // hours hh:
449 '([0-9]{2}):'. // minutes mm:
450 '([0-9]{2})(\.[0-9]*)?'. // seconds ss.ss...
451 '(Z|[+\-][0-9]{2}:?[0-9]{2})?'; // Z to indicate UTC, -/+HH:MM:SS.SS... for local tz's
453 if(ereg($eregStr,$datestr,$regs)){
454 return sprintf('%04d-%02d-%02dT%02d:%02d:%02dZ',$regs[1],$regs[2],$regs[3],$regs[4],$regs[5],$regs[6]);
463 * convert ISO 8601 compliant date string to unix timestamp
465 * @param string $datestr ISO 8601 compliant date string
468 function iso8601_to_timestamp($datestr){
470 '([0-9]{4})-'. // centuries & years CCYY-
471 '([0-9]{2})-'. // months MM-
472 '([0-9]{2})'. // days DD
474 '([0-9]{2}):'. // hours hh:
475 '([0-9]{2}):'. // minutes mm:
476 '([0-9]{2})(\.[0-9]+)?'. // seconds ss.ss...
477 '(Z|[+\-][0-9]{2}:?[0-9]{2})?'; // Z to indicate UTC, -/+HH:MM:SS.SS... for local tz's
478 if(ereg($eregStr,$datestr,$regs)){
481 $op = substr($regs[8],0,1);
482 $h = substr($regs[8],1,2);
483 $m = substr($regs[8],strlen($regs[8])-2,2);
485 $regs[4] = $regs[4] + $h;
486 $regs[5] = $regs[5] + $m;
487 } elseif($op == '+'){
488 $regs[4] = $regs[4] - $h;
489 $regs[5] = $regs[5] - $m;
492 return strtotime("$regs[1]-$regs[2]-$regs[3] $regs[4]:$regs[5]:$regs[6]Z");
501 * soap_fault class, allows for creation of faults
502 * mainly used for returning faults from deployed functions
503 * in a server instance.
504 * @author Dietrich Ayala <dietrich@ganx4.com>
508 class soap_fault extends nusoap_base {
518 * @param string $faultcode (client | server)
519 * @param string $faultactor only used when msg routed between multiple actors
520 * @param string $faultstring human readable error message
521 * @param string $faultdetail
523 function soap_fault($faultcode,$faultactor='',$faultstring='',$faultdetail=''){
524 $this->faultcode = $faultcode;
525 $this->faultactor = $faultactor;
526 $this->faultstring = $faultstring;
527 $this->faultdetail = $faultdetail;
535 function serialize(){
537 foreach($this->namespaces as $k => $v){
538 $ns_string .= "\n xmlns:$k=\"$v\"";
541 '<?xml version="1.0"?'.">\n".
542 '<SOAP-ENV:Envelope SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"'.$ns_string.">\n".
545 '<faultcode>'.$this->faultcode.'</faultcode>'.
546 '<faultactor>'.$this->faultactor.'</faultactor>'.
547 '<faultstring>'.$this->faultstring.'</faultstring>'.
548 '<detail>'.$this->serialize_val($this->faultdetail).'</detail>'.
551 '</SOAP-ENV:Envelope>';
559 * parses an XML Schema, allows access to it's data, other utility methods
560 * no validation... yet.
561 * very experimental and limited. As is discussed on XML-DEV, I'm one of the people
562 * that just doesn't have time to read the spec(s) thoroughly, and just have a couple of trusty
563 * tutorials I refer to :)
565 * @author Dietrich Ayala <dietrich@ganx4.com>
569 class XMLSchema extends nusoap_base {
574 // define internal arrays of bindings, ports, operations, messages, etc.
575 var $complexTypes = array();
577 var $schemaTargetNamespace = '';
582 var $depth_array = array();
587 * @param string $schema schema document URI
588 * @param string $xml xml document URI
591 function XMLSchema($schema='',$xml=''){
593 $this->debug('xmlschema class instantiated, inside constructor');
595 $this->schema = $schema;
600 $this->debug('initial schema file: '.$schema);
601 $this->parseFile($schema);
606 $this->debug('initial xml file: '.$xml);
607 $this->parseFile($xml);
615 * @param string $xml, path/URL to XML file
616 * @param string $type, (schema | xml)
620 function parseFile($xml,$type){
623 $this->debug('parsing $xml');
624 $xmlStr = @join("",@file($xml));
626 $this->setError('No file at the specified URL: '.$xml);
629 $this->parseString($xmlStr,$type);
637 * parse an XML string
639 * @param string $xml path or URL
640 * @param string $type, (schema|xml)
643 function parseString($xml,$type){
647 // Create an XML parser.
648 $this->parser = xml_parser_create();
649 // Set the options for parsing the XML data.
650 xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING, 0);
652 // Set the object for the parser.
653 xml_set_object($this->parser, $this);
655 // Set the element handlers for the parser.
656 if($type == "schema"){
657 xml_set_element_handler($this->parser, 'schemaStartElement','schemaEndElement');
658 xml_set_character_data_handler($this->parser,'schemaCharacterData');
659 } elseif($type == "xml"){
660 xml_set_element_handler($this->parser, 'xmlStartElement','xmlEndElement');
661 xml_set_character_data_handler($this->parser,'xmlCharacterData');
664 // Parse the XML file.
665 if(!xml_parse($this->parser,$xml,true)){
666 // Display an error message.
667 $errstr = sprintf('XML error on line %d: %s',
668 xml_get_current_line_number($this->parser),
669 xml_error_string(xml_get_error_code($this->parser))
671 $this->debug('XML parse error: '.$errstr);
672 $this->setError('Parser error: '.$errstr);
675 xml_parser_free($this->parser);
677 $this->debug('no xml passed to parseString()!!');
678 $this->setError('no xml passed to parseString()!!');
683 * start-element handler
685 * @param string $parser XML parser object
686 * @param string $name element name
687 * @param string $attrs associative array of attributes
690 function schemaStartElement($parser, $name, $attrs) {
692 // position in the total number of elements, starting from 0
693 $pos = $this->position++;
694 $depth = $this->depth++;
695 // set self as current value for this depth
696 $this->depth_array[$depth] = $pos;
698 // get element prefix
699 if($prefix = $this->getPrefix($name)){
700 // get unqualified name
701 $name = $this->getLocalPart($name);
706 // loop thru attributes, expanding, and registering namespace declarations
707 if(count($attrs) > 0){
708 foreach($attrs as $k => $v){
709 // if ns declarations, add to class level array of valid namespaces
710 if(ereg("^xmlns",$k)){
711 //$this->xdebug("$k: $v");
712 //$this->xdebug('ns_prefix: '.$this->getPrefix($k));
713 if($ns_prefix = substr(strrchr($k,':'),1)){
714 $this->namespaces[$ns_prefix] = $v;
716 $this->namespaces['ns'.(count($this->namespaces)+1)] = $v;
718 if($v == 'http://www.w3.org/2001/XMLSchema' || $v == 'http://www.w3.org/1999/XMLSchema'){
719 $this->XMLSchemaVersion = $v;
720 $this->namespaces['xsi'] = $v.'-instance';
724 foreach($attrs as $k => $v) {
725 // expand each attribute
726 $k = strpos($k,':') ? $this->expandQname($k) : $k;
727 $v = strpos($v,':') ? $this->expandQname($v) : $v;
734 // find status, register data
736 case ('all'|'choice'|'sequence'):
737 //$this->complexTypes[$this->currentComplexType]['compositor'] = 'all';
738 $this->complexTypes[$this->currentComplexType]['compositor'] = $name;
740 $this->complexTypes[$this->currentComplexType]['phpType'] = 'struct';
744 //$this->xdebug("parsing attribute $attrs[name] $attrs[ref] of value: ".$attrs['http://schemas.xmlsoap.org/wsdl/:arrayType']);
745 if(isset($attrs['name'])){
746 $this->attributes[$attrs['name']] = $attrs;
747 $aname = $attrs['name'];
748 } elseif(isset($attrs['ref']) && $attrs['ref'] == 'http://schemas.xmlsoap.org/soap/encoding/:arrayType'){
749 $aname = $attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'];
750 } elseif(isset($attrs['ref'])){
751 $aname = $attrs['ref'];
752 $this->attributes[$attrs['ref']] = $attrs;
755 if(isset($this->currentComplexType)){
756 $this->complexTypes[$this->currentComplexType]['attrs'][$aname] = $attrs;
757 } elseif(isset($this->currentElement)){
758 $this->elements[$this->currentElement]['attrs'][$aname] = $attrs;
760 // arrayType attribute
761 if(isset($attrs['http://schemas.xmlsoap.org/wsdl/:arrayType']) || $this->getLocalPart($aname) == 'arrayType'){
762 $this->complexTypes[$this->currentComplexType]['phpType'] = 'array';
763 $prefix = $this->getPrefix($aname);
764 if(isset($attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'])){
765 $v = $attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'];
769 if(strpos($v,'[,]')){
770 $this->complexTypes[$this->currentComplexType]['multidimensional'] = true;
772 $v = substr($v,0,strpos($v,'[')); // clip the []
773 if(!strpos($v,':') && isset($this->typemap[$this->XMLSchemaVersion][$v])){
774 $v = $this->XMLSchemaVersion.':'.$v;
776 $this->complexTypes[$this->currentComplexType]['arrayType'] = $v;
780 if(isset($attrs['name'])){
781 $this->currentElement = false;
782 $this->currentComplexType = $attrs['name'];
783 $this->complexTypes[$this->currentComplexType] = $attrs;
784 $this->complexTypes[$this->currentComplexType]['typeClass'] = 'complexType';
785 if(isset($attrs['base']) && ereg(':Array$',$attrs['base'])){
786 $this->complexTypes[$this->currentComplexType]['phpType'] = 'array';
788 $this->complexTypes[$this->currentComplexType]['phpType'] = 'struct';
790 $this->xdebug('processing complexType '.$attrs['name']);
794 if(isset($attrs['type'])){
795 $this->xdebug("processing element ".$attrs['name']);
796 $this->currentElement = $attrs['name'];
797 $this->elements[ $attrs['name'] ] = $attrs;
798 $this->elements[ $attrs['name'] ]['typeClass'] = 'element';
799 $ename = $attrs['name'];
800 } elseif(isset($attrs['ref'])){
801 $ename = $attrs['ref'];
803 $this->xdebug('adding complexType '.$attrs['name']);
804 $this->currentComplexType = $attrs['name'];
805 $this->complexTypes[ $attrs['name'] ] = $attrs;
806 $this->complexTypes[ $attrs['name'] ]['element'] = 1;
807 $this->complexTypes[$this->currentComplexType]['phpType'] = 'struct';
809 if(isset($ename) && $this->currentComplexType){
810 $this->complexTypes[$this->currentComplexType]['elements'][$ename] = $attrs;
814 $this->xdebug("in restriction for ct: $this->currentComplexType and ce: $this->currentElement");
815 if($this->currentElement){
816 $this->elements[$this->currentElement]['type'] = $attrs['base'];
817 } elseif($this->currentComplexType){
818 $this->complexTypes[$this->currentComplexType]['restrictionBase'] = $attrs['base'];
819 if(strstr($attrs['base'],':') == ':Array'){
820 $this->complexTypes[$this->currentComplexType]['phpType'] = 'array';
825 $this->schema = $attrs;
826 $this->schema['schemaVersion'] = $this->getNamespaceFromPrefix($prefix);
829 $this->currentElement = $attrs['name'];
830 $this->elements[ $attrs['name'] ] = $attrs;
831 $this->elements[ $attrs['name'] ]['typeClass'] = 'element';
837 * end-element handler
839 * @param string $parser XML parser object
840 * @param string $name element name
843 function schemaEndElement($parser, $name) {
844 // position of current element is equal to the last value left in depth_array for my depth
845 if(isset($this->depth_array[$this->depth])){
846 $pos = $this->depth_array[$this->depth];
848 // bring depth down a notch
851 if($name == 'complexType'){
852 $this->currentComplexType = false;
853 $this->currentElement = false;
855 if($name == 'element'){
856 $this->currentElement = false;
861 * element content handler
863 * @param string $parser XML parser object
864 * @param string $data element content
867 function schemaCharacterData($parser, $data){
868 $pos = $this->depth_array[$this->depth];
869 $this->message[$pos]['cdata'] .= $data;
873 * serialize the schema
877 function serializeSchema(){
879 $schemaPrefix = $this->getPrefixFromNamespace($this->XMLSchemaVersion);
882 foreach($this->complexTypes as $typeName => $attrs){
884 // serialize child elements
885 if(count($attrs['elements']) > 0){
886 foreach($attrs['elements'] as $element => $eParts){
887 if(isset($eParts['ref'])){
888 $contentStr .= "<element ref=\"$element\"/>";
890 $contentStr .= "<element name=\"$element\" type=\"$eParts[type]\"/>";
895 if(count($attrs['attrs']) >= 1){
896 foreach($attrs['attrs'] as $attr => $aParts){
897 $contentStr .= '<attribute ref="'.$aParts['ref'].'"';
898 if(isset($aParts['wsdl:arrayType'])){
899 $contentStr .= ' wsdl:arrayType="'.$aParts['wsdl:arrayType'].'"';
905 if( isset($attrs['restrictionBase']) && $attrs['restrictionBase'] != ''){
906 $contentStr = "<$schemaPrefix:restriction base=\"".$attrs['restrictionBase']."\">".$contentStr."</$schemaPrefix:restriction>";
908 // "all" compositor obviates complex/simple content
909 if(isset($attrs['compositor']) && $attrs['compositor'] == 'all'){
910 $contentStr = "<$schemaPrefix:$attrs[compositor]>".$contentStr."</$schemaPrefix:$attrs[compositor]>";
912 // complex or simple content
913 elseif( count($attrs['elements']) > 0 || count($attrs['attrs']) > 0){
914 $contentStr = "<$schemaPrefix:complexContent>".$contentStr."</$schemaPrefix:complexContent>";
917 if(isset($attrs['compositor']) && $attrs['compositor'] != '' && $attrs['compositor'] != 'all'){
918 $contentStr = "<$schemaPrefix:$attrs[compositor]>".$contentStr."</$schemaPrefix:$attrs[compositor]>";
920 // finalize complex type
921 if($contentStr != ''){
922 $contentStr = "<$schemaPrefix:complexType name=\"$typeName\">".$contentStr."</$schemaPrefix:complexType>";
924 $contentStr = "<$schemaPrefix:complexType name=\"$typeName\"/>";
929 if(isset($this->elements) && count($this->elements) > 0){
930 foreach($this->elements as $element => $eParts){
931 $xml .= "<$schemaPrefix:element name=\"$element\" type=\"".$eParts['type']."\"/>";
935 if(isset($this->attributes) && count($this->attributes) > 0){
936 foreach($this->attributes as $attr => $aParts){
937 $xml .= "<$schemaPrefix:attribute name=\"$attr\" type=\"".$aParts['type']."\"/>";
941 $xml = "<$schemaPrefix:schema xmlns=\"$this->XMLSchemaVersion\" targetNamespace=\"$this->schemaTargetNamespace\">".$xml."</$schemaPrefix:schema>";
946 * expands a qualified name
948 * @param string $string qname
949 * @return string expanded qname
952 function expandQname($qname){
953 // get element prefix
954 if(strpos($qname,':') && !ereg('^http://',$qname)){
955 // get unqualified name
956 $name = substr(strstr($qname,':'),1);
958 $prefix = substr($qname,0,strpos($qname,':'));
959 if(isset($this->namespaces[$prefix])){
960 return $this->namespaces[$prefix].':'.$name;
970 * adds debug data to the clas level debug string
972 * @param string $string debug data
975 function xdebug($string){
976 $this->debug(' xmlschema: '.$string);
980 * get the PHP type of a user defined type in the schema
981 * PHP type is kind of a misnomer since it actually returns 'struct' for assoc. arrays
982 * returns false if no type exists, or not w/ the given namespace
983 * else returns a string that is either a native php type, or 'struct'
985 * @param string $type, name of defined type
986 * @param string $ns, namespace of type
990 function getPHPType($type,$ns){
992 if(isset($typemap[$ns][$type])){
993 //print "found type '$type' and ns $ns in typemap<br>";
994 return $typemap[$ns][$type];
995 } elseif(isset($this->complexTypes[$type])){
996 //print "getting type '$type' and ns $ns from complexTypes array<br>";
997 return $this->complexTypes[$type]['phpType'];
1003 * returns the local part of a prefixed string
1004 * returns the original string, if not prefixed
1010 function getLocalPart($str){
1011 if($sstr = strrchr($str,':')){
1012 // get unqualified name
1013 return substr( $sstr, 1 );
1020 * returns the prefix part of a prefixed string
1021 * returns false, if not prefixed
1027 function getPrefix($str){
1028 if($pos = strrpos($str,':')){
1030 return substr($str,0,$pos);
1036 * pass it a prefix, it returns a namespace
1037 * returns false if no namespace registered with the given prefix
1043 function getNamespaceFromPrefix($prefix){
1044 if(isset($this->namespaces[$prefix])){
1045 return $this->namespaces[$prefix];
1047 //$this->setError("No namespace registered for prefix '$prefix'");
1052 * returns the prefix for a given namespace (or prefix)
1053 * or false if no prefixes registered for the given namespace
1059 function getPrefixFromNamespace($ns){
1060 foreach($this->namespaces as $p => $n){
1061 if($ns == $n || $ns == $p){
1062 $this->usedNamespaces[$p] = $n;
1070 * returns an array of information about a given type
1071 * returns false if no type exists by the given name
1074 * 'elements' => array(), // refs to elements array
1075 * 'restrictionBase' => '',
1077 * 'order' => '(sequence|all)',
1078 * 'attrs' => array() // refs to attributes array
1085 function getTypeDef($type){
1086 if(isset($this->complexTypes[$type])){
1087 return $this->complexTypes[$type];
1088 } elseif(isset($this->elements[$type])){
1089 return $this->elements[$type];
1090 } elseif(isset($this->attributes[$type])){
1091 return $this->attributes[$type];
1097 * returns a sample serialization of a given type, or false if no type by the given name
1099 * @param string $type, name of type
1103 function serializeTypeDef($type){
1104 //print "in sTD() for type $type<br>";
1105 if($typeDef = $this->getTypeDef($type)){
1107 if(is_array($typeDef['attrs'])){
1108 foreach($attrs as $attName => $data){
1109 $str .= " $attName=\"{type = ".$data['type']."}\"";
1112 $str .= " xmlns=\"".$this->schema['targetNamespace']."\"";
1113 if(count($typeDef['elements']) > 0){
1115 foreach($typeDef['elements'] as $element => $eData){
1116 $str .= $this->serializeTypeDef($element);
1119 } elseif($typeDef['typeClass'] == 'element') {
1120 $str .= "></$type>";
1130 * returns HTML form elements that allow a user
1131 * to enter values for creating an instance of the given type.
1133 * @param string $name, name for type instance
1134 * @param string $type, name of type
1138 function typeToForm($name,$type){
1140 if($typeDef = $this->getTypeDef($type)){
1142 if($typeDef['phpType'] == 'struct'){
1143 $buffer .= '<table>';
1144 foreach($typeDef['elements'] as $child => $childDef){
1145 $buffer .= "<tr><td align='right'>$childDef[name] (type: ".$this->getLocalPart($childDef['type'])."):</td>".
1146 "<td><input type='text' name='parameters[".$name."][$childDef[name]]'></td></tr>";
1148 $buffer .= '</table>';
1150 } elseif($typeDef['phpType'] == 'array'){
1151 $buffer .= '<table>';
1152 for($i=0;$i < 3; $i++){
1153 $buffer .= "<tr><td align='right'>array item (type: $typeDef[arrayType]):</td>".
1154 "<td><input type='text' name='parameters[".$name."][]'></td></tr>";
1156 $buffer .= '</table>';
1159 $buffer .= "<input type='text' name='parameters[$name]'>";
1162 $buffer .= "<input type='text' name='parameters[$name]'>";
1168 * adds an XML Schema complex type to the WSDL types
1178 * array('ref'=>'SOAP-ENC:arrayType','wsdl:arrayType'=>'string[]'),
1182 * example: PHP associative array ( SOAP Struct )
1189 * array('myVar'=> array('name'=>'myVar','type'=>'string')
1193 * @param typeClass (complexType|simpleType|attribute)
1194 * @param phpType: currently supported are array and struct (php assoc array)
1195 * @param compositor (all|sequence|choice)
1196 * @param restrictionBase namespace:name (http://schemas.xmlsoap.org/soap/encoding/:Array)
1197 * @param elements = array ( name = array(name=>'',type=>'') )
1198 * @param attrs = array(
1200 * 'ref' => "http://schemas.xmlsoap.org/soap/encoding/:arrayType",
1201 * "http://schemas.xmlsoap.org/wsdl/:arrayType" => "string[]"
1204 * @param arrayType: namespace:name (http://www.w3.org/2001/XMLSchema:string)
1207 function addComplexType($name,$typeClass='complexType',$phpType='array',$compositor='',$restrictionBase='',$elements=array(),$attrs=array(),$arrayType=''){
1208 $this->complexTypes[$name] = array(
1210 'typeClass' => $typeClass,
1211 'phpType' => $phpType,
1212 'compositor'=> $compositor,
1213 'restrictionBase' => $restrictionBase,
1214 'elements' => $elements,
1216 'arrayType' => $arrayType
1224 * for creating serializable abstractions of native PHP types
1225 * NOTE: this is only really used when WSDL is not available.
1227 * @author Dietrich Ayala <dietrich@ganx4.com>
1231 class soapval extends nusoap_base {
1235 * @param string $name optional name
1236 * @param string $type optional type name
1237 * @param mixed $value optional value
1238 * @param string $namespace optional namespace of value
1239 * @param string $type_namespace optional namespace of type
1240 * @param array $attributes associative array of attributes to add to element serialization
1243 function soapval($name='soapval',$type=false,$value=-1,$element_ns=false,$type_ns=false,$attributes=false) {
1244 $this->name = $name;
1245 $this->value = $value;
1246 $this->type = $type;
1247 $this->element_ns = $element_ns;
1248 $this->type_ns = $type_ns;
1249 $this->attributes = $attributes;
1253 * return serialized value
1255 * @return string XML data
1258 function serialize($use='encoded') {
1259 return $this->serialize_val($this->value,$this->name,$this->type,$this->element_ns,$this->type_ns,$this->attributes,$use);
1263 * decodes a soapval object into a PHP native type
1265 * @param object $soapval optional SOAPx4 soapval object, else uses self
1270 return $this->value;
1277 * transport class for sending/receiving data via HTTP and HTTPS
1278 * NOTE: PHP must be compiled with the CURL extension for HTTPS support
1280 * @author Dietrich Ayala <dietrich@ganx4.com>
1284 class soap_transport_http extends nusoap_base {
1289 var $proxyhost = '';
1290 var $proxyport = '';
1292 var $request_method = 'POST';
1293 var $protocol_version = '1.0';
1295 var $outgoing_headers = array();
1296 var $incoming_headers = array();
1297 var $outgoing_payload = '';
1298 var $incoming_payload = '';
1299 var $useSOAPAction = true;
1304 function soap_transport_http($url){
1306 $u = parse_url($url);
1307 foreach($u as $k => $v){
1308 $this->debug("$k = $v");
1311 if(isset($u['query']) && $u['query'] != ''){
1312 $this->path .= '?' . $u['query'];
1314 if(!isset($u['port']) && $u['scheme'] == 'http'){
1319 function connect($timeout){
1322 if($this->proxyhost != '' && $this->proxyport != ''){
1323 $host = $this->proxyhost;
1324 $port = $this->proxyport;
1325 $this->debug("using http proxy: $host, $port");
1327 $host = $this->host;
1328 $port = $this->port;
1331 if($this->scheme == 'https'){
1332 $host = 'ssl://'.$host;
1336 $this->debug("connection params: $host, $port");
1339 $fp = fsockopen($host, $port, $this->errno, $this->error_str, $timeout);
1341 $fp = fsockopen($host, $port, $this->errno, $this->error_str);
1346 $this->debug('Couldn\'t open socket connection to server '.$this->url.', Error: '.$this->error_str);
1347 $this->setError('Couldn\'t open socket connection to server: '.$this->url.', Error: '.$this->error_str);
1354 * send the SOAP message via HTTP
1356 * @param string $data message data
1357 * @param integer $timeout set timeout in seconds
1358 * @return string data
1361 function send($data, $timeout=0) {
1362 $this->debug('entered send() with data of length: '.strlen($data));
1364 if(!$fp = $this->connect($timeout)){
1367 $this->debug('socket connected');
1369 // start building outgoing payload:
1370 // swap url for path if going through a proxy
1371 if($this->proxyhost != '' && $this->proxyport != ''){
1372 $this->outgoing_payload = "$this->request_method $this->url ".strtoupper($this->scheme)."/$this->protocol_version\r\n";
1374 $this->outgoing_payload = "$this->request_method $this->path ".strtoupper($this->scheme)."/$this->protocol_version\r\n";
1377 $this->outgoing_payload .=
1378 "User-Agent: $this->title/$this->version\r\n".
1379 "Host: ".$this->host."\r\n";
1382 if($this->username != '') {
1383 $this->debug('setting http auth credentials');
1384 $this->outgoing_payload .= 'Authorization: Basic '.base64_encode("$this->username:$this->password")."\r\n";
1387 $this->outgoing_payload .= 'Content-Type: text/xml; charset='.$this->soap_defencoding."\r\nContent-Length: ".strlen($data)."\r\n";
1389 if($this->encoding != '' && function_exists('gzdeflate')){
1390 $this->outgoing_payload .= "Accept-Encoding: $this->encoding\r\n".
1391 "Connection: close\r\n";
1392 set_magic_quotes_runtime(0);
1395 if($this->useSOAPAction){
1396 $this->outgoing_payload .= "SOAPAction: \"$this->soapaction\""."\r\n";
1398 $this->outgoing_payload .= "\r\n";
1400 $this->outgoing_payload .= $data;
1403 if(!fputs($fp, $this->outgoing_payload, strlen($this->outgoing_payload))) {
1404 $this->setError('couldn\'t write message data to socket');
1405 $this->debug('Write error');
1407 $this->debug('wrote data to socket');
1410 $this->incoming_payload = '';
1412 while( $data = fread($fp, 32768) ){
1413 $this->incoming_payload .= $data;
1414 //$strlen += strlen($data);
1416 $this->debug('received '.strlen($this->incoming_payload).' bytes of data from server');
1418 // close filepointer
1420 $this->debug('closed socket');
1422 // connection was closed unexpectedly
1423 if($this->incoming_payload == ''){
1424 $this->setError('no response from server');
1428 $this->debug('received incoming payload: '.strlen($this->incoming_payload));
1429 $data = $this->incoming_payload."\r\n\r\n\r\n\r\n";
1431 // remove 100 header
1432 if(ereg('^HTTP/1.1 100',$data)){
1433 if($pos = strpos($data,"\r\n\r\n") ){
1434 $data = ltrim(substr($data,$pos));
1435 } elseif($pos = strpos($data,"\n\n") ){
1436 $data = ltrim(substr($data,$pos));
1440 // separate content from HTTP headers
1441 if( $pos = strpos($data,"\r\n\r\n") ){
1443 } elseif( $pos = strpos($data,"\n\n") ){
1446 $this->setError('no proper separation of headers and document');
1449 $header_data = trim(substr($data,0,$pos));
1450 $header_array = explode($lb,$header_data);
1451 $data = ltrim(substr($data,$pos));
1452 $this->debug('found proper separation of headers and document');
1453 $this->debug('cleaned data, stringlen: '.strlen($data));
1455 foreach($header_array as $header_line){
1456 $arr = explode(':',$header_line);
1457 if(count($arr) >= 2){
1458 $headers[trim($arr[0])] = trim($arr[1]);
1461 //print "headers: <pre>$header_data</pre><br>";
1462 //print "data: <pre>$data</pre><br>";
1464 // decode transfer-encoding
1465 if(isset($headers['Transfer-Encoding']) && $headers['Transfer-Encoding'] == 'chunked'){
1466 //$timer->setMarker('starting to decode chunked content');
1467 if(!$data = $this->decodeChunked($data)){
1468 $this->setError('Decoding of chunked data failed');
1471 //$timer->setMarker('finished decoding of chunked content');
1472 //print "<pre>\nde-chunked:\n---------------\n$data\n\n---------------\n</pre>";
1475 // decode content-encoding
1476 if(isset($headers['Content-Encoding']) && $headers['Content-Encoding'] != ''){
1477 if($headers['Content-Encoding'] == 'deflate' || $headers['Content-Encoding'] == 'gzip'){
1478 // if decoding works, use it. else assume data wasn't gzencoded
1479 if(function_exists('gzinflate')){
1480 //$timer->setMarker('starting decoding of gzip/deflated content');
1481 if($headers['Content-Encoding'] == 'deflate' && $degzdata = @gzinflate($data)){
1483 } elseif($headers['Content-Encoding'] == 'gzip' && $degzdata = gzinflate(substr($data, 10))){
1486 $this->setError('Errors occurred when trying to decode the data');
1488 //$timer->setMarker('finished decoding of gzip/deflated content');
1489 //print "<xmp>\nde-inflated:\n---------------\n$data\n-------------\n</xmp>";
1491 $this->setError('The server sent deflated data. Your php install must have the Zlib extension compiled in to support this.');
1496 if(strlen($data) == 0){
1497 $this->debug('no data after headers!');
1498 $this->setError('no data present after HTTP headers');
1501 $this->debug('end of send()');
1507 * send the SOAP message via HTTPS 1.0 using CURL
1509 * @param string $msg message data
1510 * @param integer $timeout set timeout in seconds
1511 * @return string data
1514 function sendHTTPS($data, $timeout=0) {
1516 //$t->setMarker('inside sendHTTPS()');
1517 $this->debug('entered sendHTTPS() with data of length: '.strlen($data));
1520 //$t->setMarker('got curl handle');
1522 if($this->proxyhost && $this->proxyport){
1523 $host = $this->proxyhost;
1524 $port = $this->proxyport;
1526 $host = $this->host;
1527 $port = $this->port;
1530 $hostURL = ($port != '') ? "https://$host:$port" : "https://$host";
1532 $hostURL .= $this->path;
1533 curl_setopt($ch, CURLOPT_URL, $hostURL);
1534 // set other options
1535 curl_setopt($ch, CURLOPT_HEADER, 1);
1536 curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
1538 if(function_exists('gzinflate')){
1539 curl_setopt($ch, CURLOPT_ENCODING, 'deflate');
1541 // persistent connection
1542 //curl_setopt($ch, CURL_HTTP_VERSION_1_1, true);
1546 curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
1550 if($this->username != '') {
1551 $credentials = 'Authorization: Basic '.base64_encode("$this->username:$this->password").'\r\n';
1554 if($this->encoding != ''){
1555 if(function_exists('gzdeflate')){
1556 $encoding_headers = "Accept-Encoding: $this->encoding\r\n".
1557 "Connection: close\r\n";
1558 set_magic_quotes_runtime(0);
1562 if($this->proxyhost && $this->proxyport){
1563 $this->outgoing_payload = "POST $this->url HTTP/$this->protocol_version\r\n";
1565 $this->outgoing_payload = "POST $this->path HTTP/$this->protocol_version\r\n";
1568 $this->outgoing_payload .=
1569 "User-Agent: $this->title v$this->version\r\n".
1570 "Host: ".$this->host."\r\n".
1573 "Content-Type: text/xml; charset=\"$this->soap_defencoding\"\r\n".
1574 "Content-Length: ".strlen($data)."\r\n".
1575 "SOAPAction: \"$this->soapaction\""."\r\n\r\n".
1579 curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $this->outgoing_payload);
1580 //$t->setMarker('set curl options, executing...');
1582 $this->incoming_payload = curl_exec($ch);
1583 //$t->setMarker('executed transfer');
1584 $data = $this->incoming_payload;
1586 $cErr = curl_error($ch);
1589 $err = 'cURL ERROR: '.curl_errno($ch).': '.$cErr.'<br>';
1590 foreach(curl_getinfo($ch) as $k => $v){
1591 $err .= "$k: $v<br>";
1593 $this->setError($err);
1598 //var_dump(curl_getinfo($ch));
1603 //$t->setMarker('closed curl');
1605 // remove 100 header
1606 if(ereg('^HTTP/1.1 100',$data)){
1607 if($pos = strpos($data,"\r\n\r\n") ){
1608 $data = ltrim(substr($data,$pos));
1609 } elseif($pos = strpos($data,"\n\n") ){
1610 $data = ltrim(substr($data,$pos));
1614 // separate content from HTTP headers
1615 if( $pos = strpos($data,"\r\n\r\n") ){
1617 } elseif( $pos = strpos($data,"\n\n") ){
1620 $this->setError('no proper separation of headers and document');
1623 $header_data = trim(substr($data,0,$pos));
1624 $header_array = explode($lb,$header_data);
1625 $data = ltrim(substr($data,$pos));
1626 $this->debug('found proper separation of headers and document');
1627 $this->debug('cleaned data, stringlen: '.strlen($data));
1629 foreach($header_array as $header_line){
1630 $arr = explode(':',$header_line);
1631 $headers[trim($arr[0])] = trim($arr[1]);
1633 if(strlen($data) == 0){
1634 $this->debug('no data after headers!');
1635 $this->setError('no data present after HTTP headers.');
1639 // decode transfer-encoding
1640 if($headers['Transfer-Encoding'] == 'chunked'){
1641 if(!$data = $this->decodeChunked($data)){
1642 $this->setError('Decoding of chunked data failed');
1646 // decode content-encoding
1647 if($headers['Content-Encoding'] != ''){
1648 if($headers['Content-Encoding'] == 'deflate' || $headers['Content-Encoding'] == 'gzip'){
1649 // if decoding works, use it. else assume data wasn't gzencoded
1650 if(function_exists('gzinflate')){
1651 if($headers['Content-Encoding'] == 'deflate' && $degzdata = @gzinflate($data)){
1653 } elseif($headers['Content-Encoding'] == 'gzip' && $degzdata = gzinflate(substr($data, 10))){
1656 $this->setError('Errors occurred when trying to decode the data');
1659 $this->setError('The server sent deflated data. Your php install must have the Zlib extension compiled in to support this.');
1663 // set decoded payload
1664 $this->incoming_payload = $header_data."\r\n\r\n".$data;
1669 * if authenticating, set user credentials here
1671 * @param string $user
1672 * @param string $pass
1675 function setCredentials($username, $password) {
1676 $this->username = $username;
1677 $this->password = $password;
1681 * set the soapaction value
1683 * @param string $soapaction
1686 function setSOAPAction($soapaction) {
1687 $this->soapaction = $soapaction;
1693 * @param string $enc encoding style. supported values: gzip, deflate, or both
1696 function setEncoding($enc='gzip, deflate'){
1697 $this->encoding = $enc;
1698 $this->protocol_version = '1.1';
1702 * set proxy info here
1704 * @param string $proxyhost
1705 * @param string $proxyport
1708 function setProxy($proxyhost, $proxyport) {
1709 $this->proxyhost = $proxyhost;
1710 $this->proxyport = $proxyport;
1714 * decode a string that is encoded w/ "chunked' transfer encoding
1715 * as defined in RFC2068 19.4.6
1717 * @param string $buffer
1721 function decodeChunked($buffer){
1726 // read chunk-size, chunk-extension (if any) and CRLF
1727 // get the position of the linebreak
1728 $chunkend = strpos($buffer,"\r\n") + 2;
1729 $temp = substr($buffer,0,$chunkend);
1730 $chunk_size = hexdec( trim($temp) );
1731 $chunkstart = $chunkend;
1732 // while (chunk-size > 0) {
1733 while ($chunk_size > 0) {
1735 $chunkend = strpos( $buffer, "\r\n", $chunkstart + $chunk_size);
1737 // Just in case we got a broken connection
1738 if ($chunkend == FALSE) {
1739 $chunk = substr($buffer,$chunkstart);
1740 // append chunk-data to entity-body
1742 $length += strlen($chunk);
1746 // read chunk-data and CRLF
1747 $chunk = substr($buffer,$chunkstart,$chunkend-$chunkstart);
1748 // append chunk-data to entity-body
1750 // length := length + chunk-size
1751 $length += strlen($chunk);
1752 // read chunk-size and CRLF
1753 $chunkstart = $chunkend + 2;
1755 $chunkend = strpos($buffer,"\r\n",$chunkstart)+2;
1756 if ($chunkend == FALSE) {
1757 break; //Just in case we got a broken connection
1759 $temp = substr($buffer,$chunkstart,$chunkend-$chunkstart);
1760 $chunk_size = hexdec( trim($temp) );
1761 $chunkstart = $chunkend;
1764 //$this->Header['content-length'] = $length;
1765 //unset($this->Header['transfer-encoding']);
1775 * soap_server allows the user to create a SOAP server
1776 * that is capable of receiving messages and returning responses
1778 * NOTE: WSDL functionality is experimental
1780 * @author Dietrich Ayala <dietrich@ganx4.com>
1784 class soap_server extends nusoap_base {
1786 var $service = ''; // service name
1787 var $operations = array(); // assoc array of operations => opData
1788 var $responseHeaders = false;
1791 var $charset_encoding = 'UTF-8';
1793 var $result = 'successful';
1795 var $externalWSDLURL = false;
1796 var $debug_flag = true;
1800 * the optional parameter is a path to a WSDL file that you'd like to bind the server instance to.
1802 * @param string $wsdl path or URL to a WSDL file
1805 function soap_server($wsdl=false){
1807 // turn on debugging?
1810 $this->debug_flag = 1;
1815 $this->wsdl = new wsdl($wsdl);
1816 $this->externalWSDLURL = $wsdl;
1817 if($err = $this->wsdl->getError()){
1818 die('WSDL ERROR: '.$err);
1824 * processes request and returns response
1826 * @param string $data usually is the value of $HTTP_RAW_POST_DATA
1829 function service($data){
1831 global $QUERY_STRING, $HTTP_SERVER_VARS;
1832 if(isset($HTTP_SERVER_VARS['QUERY_STRING'])){
1833 $qs = $HTTP_SERVER_VARS['QUERY_STRING'];
1834 } elseif(isset($GLOBALS['QUERY_STRING'])){
1835 $qs = $GLOBALS['QUERY_STRING'];
1836 } elseif(isset($QUERY_STRING) && $QUERY_STRING != ''){
1837 $qs = $QUERY_STRING;
1840 if(isset($qs) && ereg('wsdl', $qs) ){
1841 if($this->externalWSDLURL){
1842 header('Location: '.$this->externalWSDLURL);
1845 header("Content-Type: text/xml\r\n");
1846 print $this->wsdl->serialize();
1851 // print web interface
1852 if($data == '' && $this->wsdl){
1853 print $this->webDescription();
1856 // $response is the serialized response message
1857 $response = $this->parse_request($data);
1858 $this->debug('server sending...');
1859 $payload = $response;
1860 // add debug data if in debug mode
1861 if(isset($this->debug_flag) && $this->debug_flag == 1){
1862 $payload .= "<!--\n".str_replace('--','- -',$this->debug_str)."\n-->";
1866 $header[] = "HTTP/1.0 500 Internal Server Error\r\n";
1867 $header[] = "Status: 500 Internal Server Error\r\n";
1869 $header[] = "Status: 200 OK\r\n";
1871 $header[] = "Server: $this->title Server v$this->version\r\n";
1872 $header[] = "Connection: Close\r\n";
1873 $header[] = "Content-Type: text/xml; charset=$this->charset_encoding\r\n";
1874 $header[] = "Content-Length: ".strlen($payload)."\r\n\r\n";
1876 foreach($header as $hdr){
1879 $this->response = join("\r\n",$header).$payload;
1885 * parses request and posts response
1887 * @param string $data XML string
1888 * @return string XML response msg
1891 function parse_request($data='') {
1892 if (!isset($_SERVER))
1893 $_SERVER =& $GLOBALS['HTTP_SERVER_VARS'];
1894 $this->debug('entering parseRequest() on '.date('H:i Y-m-d'));
1897 if(function_exists('getallheaders')){
1898 $this->headers = getallheaders();
1899 foreach($this->headers as $k=>$v){
1900 $dump .= "$k: $v\r\n";
1901 $this->debug("$k: $v");
1903 // get SOAPAction header
1904 if(isset($this->headers['SOAPAction'])){
1905 $this->SOAPAction = str_replace('"','',$this->headers['SOAPAction']);
1907 // get the character encoding of the incoming request
1908 if(strpos($this->headers['Content-Type'],'=')){
1909 $enc = str_replace('"','',substr(strstr($this->headers["Content-Type"],'='),1));
1910 if(eregi('^(ISO-8859-1|US-ASCII|UTF-8)$',$enc)){
1911 $this->xml_encoding = $enc;
1913 $this->xml_encoding = 'us-ascii';
1916 $this->debug('got encoding: '.$this->charset_encoding);
1917 } elseif(is_array($_SERVER)){
1918 $this->headers['User-Agent'] = $_SERVER['HTTP_USER_AGENT'];
1919 $this->SOAPAction = isset($_SERVER['SOAPAction']) ? $_SERVER['SOAPAction'] : '';
1921 $this->request = $dump."\r\n\r\n".$data;
1922 // parse response, get soap parser obj
1923 $parser = new soap_parser($data,$this->charset_encoding);
1924 // if fault occurred during message parsing
1925 if($err = $parser->getError()){
1927 $this->debug("parser debug: \n".$parser->debug_str);
1928 $this->result = 'fault: error in msg parsing: '.$err;
1929 $this->fault('Server',"error in msg parsing:\n".$err);
1931 return $this->fault->serialize();
1932 // else successfully parsed request into soapval object
1934 // get/set methodname
1935 $this->methodname = $parser->root_struct_name;
1936 $this->debug('method name: '.$this->methodname);
1937 // does method exist?
1938 if(!function_exists($this->methodname)){
1939 // "method not found" fault here
1940 $this->debug("method '$this->methodname' not found!");
1941 $this->debug("parser debug: \n".$parser->debug_str);
1942 $this->result = 'fault: method not found';
1943 $this->fault('Server',"method '$this->methodname' not defined in service '$this->service'");
1944 return $this->fault->serialize();
1947 if(!$this->opData = $this->wsdl->getOperationData($this->methodname)){
1949 $this->fault('Server',"Operation '$this->methodname' is not defined in the WSDL for this service");
1950 return $this->fault->serialize();
1953 $this->debug("method '$this->methodname' exists");
1954 // evaluate message, getting back parameters
1955 $this->debug('calling parser->get_response()');
1956 $request_data = $parser->get_response();
1958 $this->debug("parser debug: \n".$parser->debug_str);
1959 // verify that request parameters match the method's signature
1960 if($this->verify_method($this->methodname,$request_data)){
1961 // if there are parameters to pass
1962 $this->debug('params var dump '.$this->varDump($request_data));
1964 $this->debug("calling '$this->methodname' with params");
1965 if (! function_exists('call_user_func_array')) {
1966 $this->debug('calling method using eval()');
1967 $funcCall = $this->methodname.'(';
1968 foreach($request_data as $param) {
1969 $funcCall .= "\"$param\",";
1971 $funcCall = substr($funcCall, 0, -1).')';
1972 $this->debug('function call:<br>'.$funcCall);
1973 @eval("\$method_response = $funcCall;");
1975 $this->debug('calling method using call_user_func_array()');
1976 $method_response = call_user_func_array("$this->methodname",$request_data);
1978 $this->debug('response var dump'.$this->varDump($method_response));
1980 // call method w/ no parameters
1981 $this->debug("calling $this->methodname w/ no params");
1982 $m = $this->methodname;
1983 $method_response = @$m();
1985 $this->debug("done calling method: $this->methodname, received $method_response of type".gettype($method_response));
1986 // if we got nothing back. this might be ok (echoVoid)
1987 if(isset($method_response) && $method_response != '' || is_bool($method_response)) {
1989 if(get_class($method_response) == 'soap_fault'){
1990 $this->debug('got a fault object from method');
1991 $this->fault = $method_response;
1992 return $method_response->serialize();
1993 // if return val is soapval object
1994 } elseif(get_class($method_response) == 'soapval'){
1995 $this->debug('got a soapval object from method');
1996 $return_val = $method_response->serialize();
1999 $this->debug('got a(n) '.gettype($method_response).' from method');
2000 $this->debug('serializing return value');
2002 // weak attempt at supporting multiple output params
2003 if(sizeof($this->opData['output']['parts']) > 1){
2004 $opParams = $method_response;
2006 $opParams = array($method_response);
2008 $return_val = $this->wsdl->serializeRPCParameters($this->methodname,'output',$opParams);
2010 $return_val = $this->serialize_val($method_response);
2013 $this->debug('return val:'.$this->varDump($return_val));
2016 $this->debug('got no response from method');
2018 $this->debug('serializing response');
2019 $payload = '<'.$this->methodname."Response>".$return_val.'</'.$this->methodname."Response>";
2020 $this->result = 'successful';
2022 //if($this->debug_flag){
2023 $this->debug("WSDL debug data:\n".$this->wsdl->debug_str);
2025 // Added: In case we use a WSDL, return a serialized env. WITH the usedNamespaces.
2026 return $this->serializeEnvelope($payload,$this->responseHeaders,$this->wsdl->usedNamespaces,$this->opData['style']);
2028 return $this->serializeEnvelope($payload,$this->responseHeaders);
2032 $this->debug('ERROR: request not verified against method signature');
2033 $this->result = 'fault: request failed validation against method signature';
2035 $this->fault('Server',"Operation '$this->methodname' not defined in service.");
2036 return $this->fault->serialize();
2042 * takes the value that was created by parsing the request
2043 * and compares to the method's signature, if available.
2049 function verify_method($operation,$request){
2050 if(isset($this->wsdl) && is_object($this->wsdl)){
2051 if($this->wsdl->getOperationData($operation)){
2054 } elseif(isset($this->operations[$operation])){
2061 * add a method to the dispatch map
2063 * @param string $methodname
2064 * @param string $in array of input values
2065 * @param string $out array of output values
2068 function add_to_map($methodname,$in,$out){
2069 $this->operations[$methodname] = array('name' => $methodname,'in' => $in,'out' => $out);
2073 * register a service with the server
2075 * @param string $methodname
2076 * @param string $in assoc array of input values: key = param name, value = param type
2077 * @param string $out assoc array of output values: key = param name, value = param type
2078 * @param string $namespace
2079 * @param string $soapaction
2080 * @param string $style (rpc|literal)
2083 function register($name,$in=false,$out=false,$namespace=false,$soapaction=false,$style=false,$use=false){
2084 if($this->externalWSDLURL){
2085 die('You cannot bind to an external WSDL file, and register methods outside of it! Please choose either WSDL or no WSDL.');
2091 if(false == $namespace) {
2093 if(false == $soapaction) {
2094 global $SERVER_NAME, $SCRIPT_NAME;
2095 $soapaction = "http://$SERVER_NAME$SCRIPT_NAME";
2097 if(false == $style) {
2104 $this->operations[] = array($name => array());
2105 $this->operations[$name] = array(
2109 'namespace' => $namespace,
2110 'soapaction' => $soapaction,
2113 $this->wsdl->addOperation($name,$in,$out,$namespace,$soapaction,$style,$use);
2119 * create a fault. this also acts as a flag to the server that a fault has occured.
2121 * @param string faultcode
2122 * @param string faultactor
2123 * @param string faultstring
2124 * @param string faultdetail
2127 function fault($faultcode,$faultactor,$faultstring='',$faultdetail=''){
2128 $this->fault = new soap_fault($faultcode,$faultactor,$faultstring,$faultdetail);
2132 * prints html description of services
2136 function webDescription(){
2138 <html><head><title>NuSOAP: '.$this->wsdl->serviceName.'</title>
2139 <style type="text/css">
2140 body { font-family: arial; color: #000000; background-color: #ffffff; margin: 0px 0px 0px 0px; }
2141 p { font-family: arial; color: #000000; margin-top: 0px; margin-bottom: 12px; }
2142 pre { background-color: silver; padding: 5px; font-family: Courier New; font-size: x-small; color: #000000;}
2143 ul { margin-top: 10px; margin-left: 20px; }
2144 li { list-style-type: none; margin-top: 10px; color: #000000; }
2146 margin-left: 0px; padding-bottom: 2em; }
2148 padding-top: 10px; padding-bottom: 10px; padding-left: 15px; font-size: .70em;
2149 margin-top: 10px; margin-left: 0px; color: #000000;
2150 background-color: #ccccff; width: 20%; margin-left: 20px; margin-top: 20px; }
2152 font-family: arial; font-size: 26px; color: #ffffff;
2153 background-color: #999999; width: 105%; margin-left: 0px;
2154 padding-top: 10px; padding-bottom: 10px; padding-left: 15px;}
2156 position: absolute; visibility: hidden; z-index: 200; left: 250px; top: 100px;
2157 font-family: arial; overflow: hidden; width: 600;
2158 padding: 20px; font-size: 10px; background-color: #999999;
2159 layer-background-color:#FFFFFF; }
2160 a,a:active { color: charcoal; font-weight: bold; }
2161 a:visited { color: #666666; font-weight: bold; }
2162 a:hover { color: cc3300; font-weight: bold; }
2164 <script language="JavaScript" type="text/javascript">
2166 // POP-UP CAPTIONS...
2167 function lib_bwcheck(){ //Browsercheck (needed)
2168 this.ver=navigator.appVersion
2169 this.agent=navigator.userAgent
2170 this.dom=document.getElementById?1:0
2171 this.opera5=this.agent.indexOf("Opera 5")>-1
2172 this.ie5=(this.ver.indexOf("MSIE 5")>-1 && this.dom && !this.opera5)?1:0;
2173 this.ie6=(this.ver.indexOf("MSIE 6")>-1 && this.dom && !this.opera5)?1:0;
2174 this.ie4=(document.all && !this.dom && !this.opera5)?1:0;
2175 this.ie=this.ie4||this.ie5||this.ie6
2176 this.mac=this.agent.indexOf("Mac")>-1
2177 this.ns6=(this.dom && parseInt(this.ver) >= 5) ?1:0;
2178 this.ns4=(document.layers && !this.dom)?1:0;
2179 this.bw=(this.ie6 || this.ie5 || this.ie4 || this.ns4 || this.ns6 || this.opera5)
2182 var bw = new lib_bwcheck()
2183 //Makes crossbrowser object.
2184 function makeObj(obj){
2185 this.evnt=bw.dom? document.getElementById(obj):bw.ie4?document.all[obj]:bw.ns4?document.layers[obj]:0;
2186 if(!this.evnt) return false
2187 this.css=bw.dom||bw.ie4?this.evnt.style:bw.ns4?this.evnt:0;
2188 this.wref=bw.dom||bw.ie4?this.evnt:bw.ns4?this.css.document:0;
2189 this.writeIt=b_writeIt;
2192 // A unit of measure that will be added when setting the position of a layer.
2193 //var px = bw.ns4||window.opera?"":"px";
2194 function b_writeIt(text){
2195 if (bw.ns4){this.wref.write(text);this.wref.close()}
2196 else this.wref.innerHTML = text
2198 //Shows the messages
2200 function popup(divid){
2201 if(oDesc = new makeObj(divid)){
2202 oDesc.css.visibility = "visible"
2205 function popout(){ // Hides message
2206 if(oDesc) oDesc.css.visibility = "hidden"
2214 <div class=title>'.$this->wsdl->serviceName.'</div>
2216 <p>View the <a href="'.$GLOBALS['PHP_SELF'].'?wsdl">WSDL</a> for the service.
2217 Click on an operation name to view it's details.</p>
2219 foreach($this->wsdl->getOperations() as $op => $data){
2220 $b .= "<li><a href='#' onclick=\"popup('$op')\">$op</a></li>";
2221 // create hidden div
2222 $b .= "<div id='$op' class='hidden'>
2223 <a href='#' onclick='popout()'><font color='#ffffff'>Close</font></a><br><br>";
2224 foreach($data as $donnie => $marie){ // loop through opdata
2225 if($donnie == 'input' || $donnie == 'output'){ // show input/output data
2226 $b .= "<font color='white'>".ucfirst($donnie).':</font><br>';
2227 foreach($marie as $captain => $tenille){ // loop through data
2228 if($captain == 'parts'){ // loop thru parts
2229 $b .= " $captain:<br>";
2230 //if(is_array($tenille)){
2231 foreach($tenille as $joanie => $chachi){
2232 $b .= " $joanie: $chachi<br>";
2236 $b .= " $captain: $tenille<br>";
2240 $b .= "<font color='white'>".ucfirst($donnie).":</font> $marie<br>";
2248 </div></body></html>';
2253 * sets up wsdl object
2254 * this acts as a flag to enable internal WSDL generation
2255 * NOTE: NOT FUNCTIONAL
2257 * @param string $serviceName, name of the service
2258 * @param string $namespace, tns namespace
2260 function configureWSDL($serviceName,$namespace = false,$endpoint = false,$style='rpc', $transport = 'http://schemas.xmlsoap.org/soap/http') {
2261 if (!isset($_SERVER))
2262 $_SERVER =& $GLOBALS['HTTP_SERVER_VARS'];
2263 $SERVER_NAME = isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : $GLOBALS['SERVER_NAME'];
2264 $SCRIPT_NAME = isset($_SERVER['SCRIPT_NAME']) ? $_SERVER['SCRIPT_NAME'] : $GLOBALS['SCRIPT_NAME'];
2265 if(false == $namespace) {
2266 $namespace = "http://$SERVER_NAME/soap/$serviceName";
2269 if(false == $endpoint) {
2270 $endpoint = "http://$SERVER_NAME$SCRIPT_NAME";
2273 $this->wsdl = new wsdl;
2274 $this->wsdl->serviceName = $serviceName;
2275 $this->wsdl->endpoint = $endpoint;
2276 $this->wsdl->namespaces['tns'] = $namespace;
2277 $this->wsdl->namespaces['soap'] = 'http://schemas.xmlsoap.org/wsdl/soap/';
2278 $this->wsdl->namespaces['wsdl'] = 'http://schemas.xmlsoap.org/wsdl/';
2279 $this->wsdl->bindings[$serviceName.'Binding'] = array(
2280 'name'=>$serviceName.'Binding',
2282 'transport'=>$transport,
2283 'portType'=>$serviceName.'PortType');
2284 $this->wsdl->ports[$serviceName.'Port'] = array(
2285 'binding'=>$serviceName.'Binding',
2286 'location'=>$endpoint,
2287 'bindingType'=>'http://schemas.xmlsoap.org/wsdl/soap/');
2294 * parses a WSDL file, allows access to it's data, other utility methods
2296 * @author Dietrich Ayala <dietrich@ganx4.com>
2300 class wsdl extends XMLSchema {
2302 // define internal arrays of bindings, ports, operations, messages, etc.
2303 var $message = array();
2304 var $complexTypes = array();
2305 var $messages = array();
2306 var $currentMessage;
2307 var $currentOperation;
2308 var $portTypes = array();
2309 var $currentPortType;
2310 var $bindings = array();
2311 var $currentBinding;
2312 var $ports = array();
2314 var $opData = array();
2316 var $documentation = false;
2318 // array of wsdl docs to import
2319 var $import = array();
2324 var $depth_array = array();
2325 var $usedNamespaces = array();
2327 var $proxyhost = '';
2328 var $proxyport = '';
2333 * @param string $wsdl WSDL document URL
2336 function wsdl($wsdl = '',$proxyhost=false,$proxyport=false){
2337 $this->wsdl = $wsdl;
2338 $this->proxyhost = $proxyhost;
2339 $this->proxyport = $proxyport;
2343 $this->debug('initial wsdl file: ' . $wsdl);
2344 $this->parseWSDL($wsdl);
2347 if (sizeof($this->import) > 0) {
2348 foreach($this->import as $ns => $url) {
2349 $this->debug('importing wsdl from ' . $url);
2350 $this->parseWSDL($url);
2356 * parses the wsdl document
2358 * @param string $wsdl path or URL
2361 function parseWSDL($wsdl = '')
2364 $this->debug('no wsdl passed to parseWSDL()!!');
2365 $this->setError('no wsdl passed to parseWSDL()!!');
2369 $this->debug('getting ' . $wsdl);
2371 // parse $wsdl for url format
2372 $wsdl_props = parse_url($wsdl);
2374 if (isset($wsdl_props['host'])) {
2377 $tr = new soap_transport_http($wsdl);
2378 $tr->request_method = 'GET';
2379 $tr->useSOAPAction = false;
2380 if($this->proxyhost && $this->proxyport){
2381 $tr->setProxy($this->proxyhost,$this->proxyport);
2383 if (isset($wsdl_props['user'])) {
2384 $tr->setCredentials($wsdl_props['user'],$wsdl_props['pass']);
2386 $wsdl_string = $tr->send('');
2388 if($err = $tr->getError() ){
2389 $this->debug('HTTP ERROR: '.$err);
2390 $this->setError('HTTP ERROR: '.$err);
2394 /* $wsdl seems to be a valid url, not a file path, do an fsockopen/HTTP GET
2395 $fsockopen_timeout = 30;
2396 // check if a port value is supplied in url
2397 if (isset($wsdl_props['port'])) {
2399 $wsdl_url_port = $wsdl_props['port'];
2401 // no, assign port number, based on url protocol (scheme)
2402 switch ($wsdl_props['scheme']) {
2406 $wsdl_url_port = 443;
2410 $wsdl_url_port = 80;
2413 // FIXME: should implement SSL/TLS support here if CURL is available
2414 if ($fp = fsockopen($wsdl_props['host'], $wsdl_url_port, $fsockopen_errnum, $fsockopen_errstr, $fsockopen_timeout)) {
2415 // perform HTTP GET for WSDL file
2416 // 10.9.02 - added poulter fix for doing this properly
2417 $sHeader = "GET " . $wsdl_props['path'];
2418 if (isset($wsdl_props['query'])) {
2419 $sHeader .= "?" . $wsdl_props['query'];
2421 $sHeader .= " HTTP/1.0\r\n";
2423 if (isset($wsdl_props['user'])) {
2424 $base64auth = base64_encode($wsdl_props['user'] . ":" . $wsdl_props['pass']);
2425 $sHeader .= "Authorization: Basic $base64auth\r\n";
2427 $sHeader .= "Host: " . $wsdl_props['host'] . ( isset($wsdl_props['port']) ? ":".$wsdl_props['port'] : "" ) . "\r\n\r\n";
2428 fputs($fp, $sHeader);
2430 while (fgets($fp, 1024) != "\r\n") {
2431 // do nothing, just read/skip past HTTP headers
2432 // FIXME: should actually detect HTTP response code, and act accordingly if error
2433 // HTTP headers end with extra CRLF before content body
2435 // read in WSDL just like regular fopen()
2437 while ($data = fread($fp, 32768)) {
2438 $wsdl_string .= $data;
2442 $this->setError('bad path to WSDL file.');
2447 // $wsdl seems to be a non-url file path, do the regular fopen
2448 if ($fp = @fopen($wsdl, 'r')) {
2450 while ($data = fread($fp, 32768)) {
2451 $wsdl_string .= $data;
2455 $this->setError('bad path to WSDL file.');
2459 // end new code added
2460 // Create an XML parser.
2461 $this->parser = xml_parser_create();
2462 // Set the options for parsing the XML data.
2463 // xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 1);
2464 xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING, 0);
2465 // Set the object for the parser.
2466 xml_set_object($this->parser, $this);
2467 // Set the element handlers for the parser.
2468 xml_set_element_handler($this->parser, 'start_element', 'end_element');
2469 xml_set_character_data_handler($this->parser, 'character_data');
2470 // Parse the XML file.
2471 if (!xml_parse($this->parser, $wsdl_string, true)) {
2472 // Display an error message.
2474 'XML error on line %d: %s',
2475 xml_get_current_line_number($this->parser),
2476 xml_error_string(xml_get_error_code($this->parser))
2478 $this->debug('XML parse error: ' . $errstr);
2479 $this->setError('Parser error: ' . $errstr);
2483 xml_parser_free($this->parser);
2484 // catch wsdl parse errors
2485 if($this->getError()){
2488 // add new data to operation data
2489 foreach($this->bindings as $binding => $bindingData) {
2490 if (isset($bindingData['operations']) && is_array($bindingData['operations'])) {
2491 foreach($bindingData['operations'] as $operation => $data) {
2492 $this->debug('post-parse data gathering for ' . $operation);
2493 $this->bindings[$binding]['operations'][$operation]['input'] =
2494 isset($this->bindings[$binding]['operations'][$operation]['input']) ?
2495 array_merge($this->bindings[$binding]['operations'][$operation]['input'], $this->portTypes[ $bindingData['portType'] ][$operation]['input']) :
2496 $this->portTypes[ $bindingData['portType'] ][$operation]['input'];
2497 $this->bindings[$binding]['operations'][$operation]['output'] =
2498 isset($this->bindings[$binding]['operations'][$operation]['output']) ?
2499 array_merge($this->bindings[$binding]['operations'][$operation]['output'], $this->portTypes[ $bindingData['portType'] ][$operation]['output']) :
2500 $this->portTypes[ $bindingData['portType'] ][$operation]['output'];
2501 if(isset($this->messages[ $this->bindings[$binding]['operations'][$operation]['input']['message'] ])){
2502 $this->bindings[$binding]['operations'][$operation]['input']['parts'] = $this->messages[ $this->bindings[$binding]['operations'][$operation]['input']['message'] ];
2504 if(isset($this->messages[ $this->bindings[$binding]['operations'][$operation]['output']['message'] ])){
2505 $this->bindings[$binding]['operations'][$operation]['output']['parts'] = $this->messages[ $this->bindings[$binding]['operations'][$operation]['output']['message'] ];
2507 if (isset($bindingData['style'])) {
2508 $this->bindings[$binding]['operations'][$operation]['style'] = $bindingData['style'];
2510 $this->bindings[$binding]['operations'][$operation]['transport'] = isset($bindingData['transport']) ? $bindingData['transport'] : '';
2511 $this->bindings[$binding]['operations'][$operation]['documentation'] = isset($this->portTypes[ $bindingData['portType'] ][$operation]['documentation']) ? $this->portTypes[ $bindingData['portType'] ][$operation]['documentation'] : '';
2512 $this->bindings[$binding]['operations'][$operation]['endpoint'] = isset($bindingData['endpoint']) ? $bindingData['endpoint'] : '';
2520 * start-element handler
2522 * @param string $parser XML parser object
2523 * @param string $name element name
2524 * @param string $attrs associative array of attributes
2527 function start_element($parser, $name, $attrs)
2529 if ($this->status == 'schema' || ereg('schema$', $name)) {
2530 // $this->debug("startElement for $name ($attrs[name]). status = $this->status (".$this->getLocalPart($name).")");
2531 $this->status = 'schema';
2532 $this->schemaStartElement($parser, $name, $attrs);
2534 // position in the total number of elements, starting from 0
2535 $pos = $this->position++;
2536 $depth = $this->depth++;
2537 // set self as current value for this depth
2538 $this->depth_array[$depth] = $pos;
2539 $this->message[$pos] = array('cdata' => '');
2540 // get element prefix
2541 if (ereg(':', $name)) {
2543 $prefix = substr($name, 0, strpos($name, ':'));
2545 $namespace = isset($this->namespaces[$prefix]) ? $this->namespaces[$prefix] : '';
2546 // get unqualified name
2547 $name = substr(strstr($name, ':'), 1);
2550 if (count($attrs) > 0) {
2551 foreach($attrs as $k => $v) {
2552 // if ns declarations, add to class level array of valid namespaces
2553 if (ereg("^xmlns", $k)) {
2554 if ($ns_prefix = substr(strrchr($k, ':'), 1)) {
2555 $this->namespaces[$ns_prefix] = $v;
2557 $this->namespaces['ns' . (count($this->namespaces) + 1)] = $v;
2559 if ($v == 'http://www.w3.org/2001/XMLSchema' || $v == 'http://www.w3.org/1999/XMLSchema') {
2560 $this->XMLSchemaVersion = $v;
2561 $this->namespaces['xsi'] = $v . '-instance';
2564 // expand each attribute
2565 $k = strpos($k, ':') ? $this->expandQname($k) : $k;
2566 if ($k != 'location' && $k != 'soapAction' && $k != 'namespace') {
2567 $v = strpos($v, ':') ? $this->expandQname($v) : $v;
2575 // find status, register data
2576 switch ($this->status) {
2578 if ($name == 'part') {
2579 if (isset($attrs['type'])) {
2580 $this->debug("msg " . $this->currentMessage . ": found part $attrs[name]: " . implode(',', $attrs));
2581 $this->messages[$this->currentMessage][$attrs['name']] = $attrs['type'];
2583 if (isset($attrs['element'])) {
2584 $this->messages[$this->currentMessage][$attrs['name']] = $attrs['element'];
2591 $this->currentPortOperation = $attrs['name'];
2592 $this->debug("portType $this->currentPortType operation: $this->currentPortOperation");
2593 if (isset($attrs['parameterOrder'])) {
2594 $this->portTypes[$this->currentPortType][$attrs['name']]['parameterOrder'] = $attrs['parameterOrder'];
2597 case 'documentation':
2598 $this->documentation = true;
2600 // merge input/output data
2602 $m = isset($attrs['message']) ? $this->getLocalPart($attrs['message']) : '';
2603 $this->portTypes[$this->currentPortType][$this->currentPortOperation][$name]['message'] = $m;
2611 if (isset($attrs['style'])) {
2612 $this->bindings[$this->currentBinding]['prefix'] = $prefix;
2614 $this->bindings[$this->currentBinding] = array_merge($this->bindings[$this->currentBinding], $attrs);
2617 $this->bindings[$this->currentBinding]['operations'][$this->currentOperation][$this->opStatus]['headers'][] = $attrs;
2620 if (isset($attrs['soapAction'])) {
2621 $this->bindings[$this->currentBinding]['operations'][$this->currentOperation]['soapAction'] = $attrs['soapAction'];
2623 if (isset($attrs['style'])) {
2624 $this->bindings[$this->currentBinding]['operations'][$this->currentOperation]['style'] = $attrs['style'];
2626 if (isset($attrs['name'])) {
2627 $this->currentOperation = $attrs['name'];
2628 $this->debug("current binding operation: $this->currentOperation");
2629 $this->bindings[$this->currentBinding]['operations'][$this->currentOperation]['name'] = $attrs['name'];
2630 $this->bindings[$this->currentBinding]['operations'][$this->currentOperation]['binding'] = $this->currentBinding;
2631 $this->bindings[$this->currentBinding]['operations'][$this->currentOperation]['endpoint'] = isset($this->bindings[$this->currentBinding]['endpoint']) ? $this->bindings[$this->currentBinding]['endpoint'] : '';
2635 $this->opStatus = 'input';
2638 $this->opStatus = 'output';
2641 if (isset($this->bindings[$this->currentBinding]['operations'][$this->currentOperation][$this->opStatus])) {
2642 $this->bindings[$this->currentBinding]['operations'][$this->currentOperation][$this->opStatus] = array_merge($this->bindings[$this->currentBinding]['operations'][$this->currentOperation][$this->opStatus], $attrs);
2644 $this->bindings[$this->currentBinding]['operations'][$this->currentOperation][$this->opStatus] = $attrs;
2652 $this->currentPort = $attrs['name'];
2653 $this->debug('current port: ' . $this->currentPort);
2654 $this->ports[$this->currentPort]['binding'] = $this->getLocalPart($attrs['binding']);
2658 $this->ports[$this->currentPort]['location'] = $attrs['location'];
2659 $this->ports[$this->currentPort]['bindingType'] = $namespace;
2660 $this->bindings[ $this->ports[$this->currentPort]['binding'] ]['bindingType'] = $namespace;
2661 $this->bindings[ $this->ports[$this->currentPort]['binding'] ]['endpoint'] = $attrs['location'];
2669 if (isset($attrs['location'])) {
2670 $this->import[$attrs['namespace']] = $attrs['location'];
2674 $this->status = 'schema';
2677 $this->status = 'message';
2678 $this->messages[$attrs['name']] = array();
2679 $this->currentMessage = $attrs['name'];
2682 $this->status = 'portType';
2683 $this->portTypes[$attrs['name']] = array();
2684 $this->currentPortType = $attrs['name'];
2687 if (isset($attrs['name'])) {
2689 if (strpos($attrs['name'], ':')) {
2690 $this->currentBinding = $this->getLocalPart($attrs['name']);
2692 $this->currentBinding = $attrs['name'];
2694 $this->status = 'binding';
2695 $this->bindings[$this->currentBinding]['portType'] = $this->getLocalPart($attrs['type']);
2696 $this->debug("current binding: $this->currentBinding of portType: " . $attrs['type']);
2700 $this->serviceName = $attrs['name'];
2701 $this->status = 'service';
2702 $this->debug('current service: ' . $this->serviceName);
2705 foreach ($attrs as $name => $value) {
2706 $this->wsdl_info[$name] = $value;
2714 * end-element handler
2716 * @param string $parser XML parser object
2717 * @param string $name element name
2720 function end_element($parser, $name){
2721 // unset schema status
2722 if (ereg('types$', $name) || ereg('schema$', $name)) {
2725 if ($this->status == 'schema') {
2726 $this->schemaEndElement($parser, $name);
2728 // bring depth down a notch
2731 // end documentation
2732 if ($this->documentation) {
2733 $this->portTypes[$this->currentPortType][$this->currentPortOperation]['documentation'] = $this->documentation;
2734 $this->documentation = false;
2739 * element content handler
2741 * @param string $parser XML parser object
2742 * @param string $data element content
2745 function character_data($parser, $data)
2747 $pos = isset($this->depth_array[$this->depth]) ? $this->depth_array[$this->depth] : 0;
2748 if (isset($this->message[$pos]['cdata'])) {
2749 $this->message[$pos]['cdata'] .= $data;
2751 if ($this->documentation) {
2752 $this->documentation .= $data;
2756 function getBindingData($binding)
2758 if (is_array($this->bindings[$binding])) {
2759 return $this->bindings[$binding];
2764 * returns an assoc array of operation names => operation data
2765 * NOTE: currently only supports multiple services of differing binding types
2766 * This method needs some work
2768 * @param string $bindingType eg: soap, smtp, dime (only soap is currently supported)
2772 function getOperations($bindingType = 'soap')
2774 if ($bindingType == 'soap') {
2775 $bindingType = 'http://schemas.xmlsoap.org/wsdl/soap/';
2778 foreach($this->ports as $port => $portData) {
2779 // binding type of port matches parameter
2780 if ($portData['bindingType'] == $bindingType) {
2782 return $this->bindings[ $portData['binding'] ]['operations'];
2789 * returns an associative array of data necessary for calling an operation
2791 * @param string $operation , name of operation
2792 * @param string $bindingType , type of binding eg: soap
2796 function getOperationData($operation, $bindingType = 'soap')
2798 if ($bindingType == 'soap') {
2799 $bindingType = 'http://schemas.xmlsoap.org/wsdl/soap/';
2802 foreach($this->ports as $port => $portData) {
2803 // binding type of port matches parameter
2804 if ($portData['bindingType'] == $bindingType) {
2806 //foreach($this->bindings[ $portData['binding'] ]['operations'] as $bOperation => $opData) {
2807 foreach(array_keys($this->bindings[ $portData['binding'] ]['operations']) as $bOperation) {
2808 if ($operation == $bOperation) {
2809 $opData = $this->bindings[ $portData['binding'] ]['operations'][$operation];
2818 * serialize the parsed wsdl
2820 * @return string , serialization of WSDL
2823 function serialize()
2825 $xml = '<?xml version="1.0"?><definitions';
2826 foreach($this->namespaces as $k => $v) {
2827 $xml .= " xmlns:$k=\"$v\"";
2829 // 10.9.02 - add poulter fix for wsdl and tns declarations
2830 if (isset($this->namespaces['wsdl'])) {
2831 $xml .= " xmlns=\"" . $this->namespaces['wsdl'] . "\"";
2833 if (isset($this->namespaces['tns'])) {
2834 $xml .= " targetNamespace=\"" . $this->namespaces['tns'] . "\"";
2838 if (sizeof($this->import) > 0) {
2839 foreach($this->import as $ns => $url) {
2840 $xml .= '<import location="' . $url . '" namespace="' . $ns . '" />';
2844 if (count($this->complexTypes)>=1) {
2846 $xml .= $this->serializeSchema();
2850 if (count($this->messages) >= 1) {
2851 foreach($this->messages as $msgName => $msgParts) {
2852 $xml .= '<message name="' . $msgName . '">';
2853 foreach($msgParts as $partName => $partType) {
2854 // print 'serializing '.$partType.', sv: '.$this->XMLSchemaVersion.'<br>';
2855 if (strpos($partType, ':')) {
2856 $typePrefix = $this->getPrefixFromNamespace($this->getPrefix($partType));
2857 } elseif (isset($this->typemap[$this->namespaces['xsd']][$partType])) {
2858 // print 'checking typemap: '.$this->XMLSchemaVersion.'<br>';
2859 $typePrefix = 'xsd';
2861 foreach($this->typemap as $ns => $types) {
2862 if (isset($types[$partType])) {
2863 $typePrefix = $this->getPrefixFromNamespace($ns);
2866 if (!isset($typePrefix)) {
2867 die("$partType has no namespace!");
2870 $xml .= '<part name="' . $partName . '" type="' . $typePrefix . ':' . $this->getLocalPart($partType) . '" />';
2872 $xml .= '</message>';
2875 // bindings & porttypes
2876 if (count($this->bindings) >= 1) {
2879 foreach($this->bindings as $bindingName => $attrs) {
2880 $binding_xml .= '<binding name="' . $bindingName . '" type="tns:' . $attrs['portType'] . '">';
2881 $binding_xml .= '<soap:binding style="' . $attrs['style'] . '" transport="' . $attrs['transport'] . '"/>';
2882 $portType_xml .= '<portType name="' . $attrs['portType'] . '">';
2883 foreach($attrs['operations'] as $opName => $opParts) {
2884 $binding_xml .= '<operation name="' . $opName . '">';
2885 $binding_xml .= '<soap:operation soapAction="' . $opParts['soapAction'] . '" style="'. $attrs['style'] . '"/>';
2886 $binding_xml .= '<input><soap:body use="' . $opParts['input']['use'] . '" namespace="' . $opParts['input']['namespace'] . '" encodingStyle="' . $opParts['input']['encodingStyle'] . '"/></input>';
2887 $binding_xml .= '<output><soap:body use="' . $opParts['output']['use'] . '" namespace="' . $opParts['output']['namespace'] . '" encodingStyle="' . $opParts['output']['encodingStyle'] . '"/></output>';
2888 $binding_xml .= '</operation>';
2889 $portType_xml .= '<operation name="' . $opParts['name'] . '"';
2890 if (isset($opParts['parameterOrder'])) {
2891 $portType_xml .= ' parameterOrder="' . $opParts['parameterOrder'] . '"';
2893 $portType_xml .= '>';
2894 $portType_xml .= '<input message="tns:' . $opParts['input']['message'] . '"/>';
2895 $portType_xml .= '<output message="tns:' . $opParts['output']['message'] . '"/>';
2896 $portType_xml .= '</operation>';
2898 $portType_xml .= '</portType>';
2899 $binding_xml .= '</binding>';
2901 $xml .= $portType_xml . $binding_xml;
2904 $xml .= '<service name="' . $this->serviceName . '">';
2905 if (count($this->ports) >= 1) {
2906 foreach($this->ports as $pName => $attrs) {
2907 $xml .= '<port name="' . $pName . '" binding="tns:' . $attrs['binding'] . '">';
2908 $xml .= '<soap:address location="' . $attrs['location'] . '"/>';
2912 $xml .= '</service>';
2913 return $xml . '</definitions>';
2917 * serialize a PHP value according to a WSDL message definition
2920 * - multi-ref serialization
2921 * - validate PHP values against type definitions, return errors if invalid
2923 * @param string $ type name
2924 * @param mixed $ param value
2925 * @return mixed new param or false if initial value didn't validate
2927 function serializeRPCParameters($operation, $direction, $parameters)
2929 $this->debug('in serializeRPCParameters with operation '.$operation.', direction '.$direction.' and '.count($parameters).' param(s), and xml schema version ' . $this->XMLSchemaVersion);
2931 if ($direction != 'input' && $direction != 'output') {
2932 $this->debug('The value of the \$direction argument needs to be either "input" or "output"');
2933 $this->setError('The value of the \$direction argument needs to be either "input" or "output"');
2936 if (!$opData = $this->getOperationData($operation)) {
2937 $this->debug('Unable to retrieve WSDL data for operation: ' . $operation);
2938 $this->setError('Unable to retrieve WSDL data for operation: ' . $operation);
2941 $this->debug($this->varDump($opData));
2944 if (isset($opData[$direction]['parts']) && sizeof($opData[$direction]['parts']) > 0) {
2946 $use = $opData[$direction]['use'];
2947 $this->debug("use=$use");
2948 $this->debug('got ' . count($opData[$direction]['parts']) . ' part(s)');
2949 foreach($opData[$direction]['parts'] as $name => $type) {
2950 $this->debug('serializing part "'.$name.'" of type "'.$type.'"');
2951 // NOTE: add error handling here
2952 // if serializeType returns false, then catch global error and fault
2953 if (isset($parameters[$name])) {
2954 $this->debug('calling serializeType w/ named param');
2955 $xml .= $this->serializeType($name, $type, $parameters[$name], $use);
2956 } elseif(is_array($parameters)) {
2957 $this->debug('calling serializeType w/ unnamed param');
2958 $xml .= $this->serializeType($name, $type, array_shift($parameters), $use);
2960 $this->debug('no parameters passed.');
2968 * serializes a PHP value according a given type definition
2970 * @param string $name , name of type (part)
2971 * @param string $type , type of type, heh (type or element)
2972 * @param mixed $value , a native PHP value (parameter value)
2973 * @param string $use , use for part (encoded|literal)
2974 * @return string serialization
2977 function serializeType($name, $type, $value, $use='encoded')
2979 $this->debug("in serializeType: $name, $type, $value, $use");
2981 if (strpos($type, ':')) {
2982 $uqType = substr($type, strrpos($type, ':') + 1);
2983 $ns = substr($type, 0, strrpos($type, ':'));
2984 $this->debug("got a prefixed type: $uqType, $ns");
2986 if($ns == $this->XMLSchemaVersion ||
2987 ($this->getNamespaceFromPrefix($ns)) == $this->XMLSchemaVersion){
2989 if ($uqType == 'boolean' && !$value) {
2991 } elseif ($uqType == 'boolean') {
2994 if ($this->charencoding && $uqType == 'string' && gettype($value) == 'string') {
2995 $value = htmlspecialchars($value);
2998 if ($use == 'literal') {
2999 return "<$name>$value</$name>";
3001 return "<$name xsi:type=\"" . $this->getPrefixFromNamespace($this->XMLSchemaVersion) . ":$uqType\">$value</$name>";
3007 if(!$typeDef = $this->getTypeDef($uqType)){
3008 $this->setError("$uqType is not a supported type.");
3011 //foreach($typeDef as $k => $v) {
3012 //$this->debug("typedef, $k: $v");
3015 $phpType = $typeDef['phpType'];
3016 $this->debug("serializeType: uqType: $uqType, ns: $ns, phptype: $phpType, arrayType: " . (isset($typeDef['arrayType']) ? $typeDef['arrayType'] : '') );
3017 // if php type == struct, map value to the <all> element names
3018 if ($phpType == 'struct') {
3019 if (isset($typeDef['element']) && $typeDef['element']) {
3020 $elementName = $uqType;
3021 // TODO: use elementFormDefault="qualified|unqualified" to determine
3022 // how to scope the namespace
3023 $elementNS = " xmlns=\"$ns\"";
3025 $elementName = $name;
3028 if ($use == 'literal') {
3029 $xml = "<$elementName$elementNS>";
3031 $xml = "<$elementName$elementNS xsi:type=\"" . $this->getPrefixFromNamespace($ns) . ":$uqType\">";
3034 if (isset($this->complexTypes[$uqType]['elements']) && is_array($this->complexTypes[$uqType]['elements'])) {
3036 //if (is_array($this->complexTypes[$uqType]['elements'])) {
3037 // toggle whether all elements are present - ideally should validate against schema
3038 if(count($this->complexTypes[$uqType]['elements']) != count($value)){
3041 foreach($this->complexTypes[$uqType]['elements'] as $eName => $attrs) {
3042 // if user took advantage of a minOccurs=0, then only serialize named parameters
3043 if(isset($optionals) && !isset($value[$eName])){
3047 if (isset($value[$eName])) {
3048 $v = $value[$eName];
3049 } elseif (is_array($value)) {
3050 $v = array_shift($value);
3052 // serialize schema-defined type
3053 if (!isset($attrs['type'])) {
3054 $xml .= $this->serializeType($eName, $attrs['name'], $v, $use);
3055 // serialize generic type
3057 $this->debug("calling serialize_val() for $eName, $v, " . $this->getLocalPart($attrs['type']), false, $use);
3058 $xml .= $this->serialize_val($v, $eName, $this->getLocalPart($attrs['type']), null, $this->getNamespaceFromPrefix($this->getPrefix($attrs['type'])), false, $use);
3063 $xml .= "</$elementName>";
3064 } elseif ($phpType == 'array') {
3065 $rows = sizeof($value);
3066 if (isset($typeDef['multidimensional'])) {
3068 foreach($value as $v) {
3069 $cols = ',' . sizeof($v);
3070 $nv = array_merge($nv, $v);
3076 if (is_array($value) && sizeof($value) >= 1) {
3078 foreach($value as $k => $v) {
3079 $this->debug("serializing array element: $k, $v of type: $typeDef[arrayType]");
3080 //if (strpos($typeDef['arrayType'], ':') ) {
3081 if (!in_array($typeDef['arrayType'],$this->typemap['http://www.w3.org/2001/XMLSchema'])) {
3082 $contents .= $this->serializeType('item', $typeDef['arrayType'], $v, $use);
3084 $contents .= $this->serialize_val($v, 'item', $typeDef['arrayType'], null, $this->XMLSchemaVersion, false, $use);
3087 $this->debug('contents: '.$this->varDump($contents));
3091 if ($use == 'literal') {
3096 $xml = "<$name xsi:type=\"".$this->getPrefixFromNamespace('http://schemas.xmlsoap.org/soap/encoding/').':Array" '.
3097 $this->getPrefixFromNamespace('http://schemas.xmlsoap.org/soap/encoding/')
3099 .$this->getPrefixFromNamespace($this->getPrefix($typeDef['arrayType']))
3100 .":".$this->getLocalPart($typeDef['arrayType'])."[$rows$cols]\">"
3105 $this->debug('returning: '.$this->varDump($xml));
3110 * register a service with the server
3112 * @param string $methodname
3113 * @param string $in assoc array of input values: key = param name, value = param type
3114 * @param string $out assoc array of output values: key = param name, value = param type
3115 * @param string $namespace
3116 * @param string $soapaction
3117 * @param string $style (rpc|literal)
3120 function addOperation($name, $in = false, $out = false, $namespace = false, $soapaction = false, $style = 'rpc', $use = 'encoded', $documentation = '')
3122 if ($style == 'rpc' && $use == 'encoded') {
3123 $encodingStyle = 'http://schemas.xmlsoap.org/soap/encoding/';
3125 $encodingStyle = '';
3128 $this->bindings[ $this->serviceName . 'Binding' ]['operations'][$name] =
3131 'binding' => $this->serviceName . 'Binding',
3132 'endpoint' => $this->endpoint,
3133 'soapAction' => $soapaction,
3137 'namespace' => $namespace,
3138 'encodingStyle' => $encodingStyle,
3139 'message' => $name . 'Request',
3143 'namespace' => $namespace,
3144 'encodingStyle' => $encodingStyle,
3145 'message' => $name . 'Response',
3147 'namespace' => $namespace,
3148 'transport' => 'http://schemas.xmlsoap.org/soap/http',
3149 'documentation' => $documentation);
3154 foreach($in as $pName => $pType)
3156 if(strpos($pType,':')) {
3157 $pType = $this->getNamespaceFromPrefix($this->getPrefix($pType)).":".$this->getLocalPart($pType);
3159 $this->messages[$name.'Request'][$pName] = $pType;
3165 foreach($out as $pName => $pType)
3167 if(strpos($pType,':')) {
3168 $pType = $this->getNamespaceFromPrefix($this->getPrefix($pType)).":".$this->getLocalPart($pType);
3170 $this->messages[$name.'Response'][$pName] = $pType;
3181 * soap_parser class parses SOAP XML messages into native PHP values
3183 * @author Dietrich Ayala <dietrich@ganx4.com>
3187 class soap_parser extends nusoap_base {
3190 var $xml_encoding = '';
3192 var $root_struct = '';
3193 var $root_struct_name = '';
3194 var $root_header = '';
3196 // determines where in the message we are (envelope,header,body,method)
3200 var $default_namespace = '';
3201 var $namespaces = array();
3202 var $message = array();
3205 var $fault_code = '';
3206 var $fault_str = '';
3207 var $fault_detail = '';
3208 var $depth_array = array();
3209 var $debug_flag = true;
3210 var $soapresponse = NULL;
3211 var $responseHeaders = '';
3212 var $body_position = 0;
3213 // for multiref parsing:
3214 // array of id => pos
3216 // array of id => hrefs => pos
3217 var $multirefs = array();
3222 * @param string $xml SOAP message
3223 * @param string $encoding character encoding scheme of message
3226 function soap_parser($xml,$encoding='UTF-8',$method=''){
3228 $this->xml_encoding = $encoding;
3229 $this->method = $method;
3231 // Check whether content has been read.
3233 $this->debug('Entering soap_parser()');
3234 // Create an XML parser.
3235 $this->parser = xml_parser_create($this->xml_encoding);
3236 // Set the options for parsing the XML data.
3237 //xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 1);
3238 xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING, 0);
3239 // Set the object for the parser.
3240 xml_set_object($this->parser, $this);
3241 // Set the element handlers for the parser.
3242 xml_set_element_handler($this->parser, 'start_element','end_element');
3243 xml_set_character_data_handler($this->parser,'character_data');
3245 // Parse the XML file.
3246 if(!xml_parse($this->parser,$xml,true)){
3247 // Display an error message.
3248 $err = sprintf('XML error on line %d: %s',
3249 xml_get_current_line_number($this->parser),
3250 xml_error_string(xml_get_error_code($this->parser)));
3251 $this->debug('parse error: '.$err);
3252 $this->errstr = $err;
3254 $this->debug('parsed successfully, found root struct: '.$this->root_struct.' of name '.$this->root_struct_name);
3256 $this->soapresponse = $this->message[$this->root_struct]['result'];
3258 if($this->root_header != '' && isset($this->message[$this->root_header]['result'])){
3259 $this->responseHeaders = $this->message[$this->root_header]['result'];
3261 // resolve hrefs/ids
3262 if(sizeof($this->multirefs) > 0){
3263 foreach($this->multirefs as $id => $hrefs){
3264 $this->debug('resolving multirefs for id: '.$id);
3265 $idVal = $this->buildVal($this->ids[$id]);
3266 foreach($hrefs as $refPos => $ref){
3267 $this->debug('resolving href at pos '.$refPos);
3268 $this->multirefs[$id][$refPos] = $idVal;
3273 xml_parser_free($this->parser);
3275 $this->debug('xml was empty, didn\'t parse!');
3276 $this->errstr = 'xml was empty, didn\'t parse!';
3281 * start-element handler
3283 * @param string $parser XML parser object
3284 * @param string $name element name
3285 * @param string $attrs associative array of attributes
3288 function start_element($parser, $name, $attrs) {
3289 // position in a total number of elements, starting from 0
3290 // update class level pos
3291 $pos = $this->position++;
3293 $this->message[$pos] = array('pos' => $pos,'children'=>'','cdata'=>'');
3294 // depth = how many levels removed from root?
3295 // set mine as current global depth and increment global depth value
3296 $this->message[$pos]['depth'] = $this->depth++;
3298 // else add self as child to whoever the current parent is
3300 $this->message[$this->parent]['children'] .= '|'.$pos;
3303 $this->message[$pos]['parent'] = $this->parent;
3304 // set self as current parent
3305 $this->parent = $pos;
3306 // set self as current value for this depth
3307 $this->depth_array[$this->depth] = $pos;
3308 // get element prefix
3309 if(strpos($name,':')){
3311 $prefix = substr($name,0,strpos($name,':'));
3312 // get unqualified name
3313 $name = substr(strstr($name,':'),1);
3316 if($name == 'Envelope'){
3317 $this->status = 'envelope';
3318 } elseif($name == 'Header'){
3319 $this->root_header = $pos;
3320 $this->status = 'header';
3321 } elseif($name == 'Body'){
3322 $this->status = 'body';
3323 $this->body_position = $pos;
3325 } elseif($this->status == 'body' && $pos == ($this->body_position+1)){
3326 $this->status = 'method';
3327 $this->root_struct_name = $name;
3328 $this->root_struct = $pos;
3329 $this->message[$pos]['type'] = 'struct';
3330 $this->debug("found root struct $this->root_struct_name, pos $this->root_struct");
3333 $this->message[$pos]['status'] = $this->status;
3335 $this->message[$pos]['name'] = htmlspecialchars($name);
3337 $this->message[$pos]['attrs'] = $attrs;
3339 // loop through atts, logging ns and type declarations
3341 foreach($attrs as $key => $value){
3342 $key_prefix = $this->getPrefix($key);
3343 $key_localpart = $this->getLocalPart($key);
3344 // if ns declarations, add to class level array of valid namespaces
3345 if($key_prefix == 'xmlns'){
3346 if(ereg('^http://www.w3.org/[0-9]{4}/XMLSchema$',$value)){
3347 $this->XMLSchemaVersion = $value;
3348 $this->namespaces['xsd'] = $this->XMLSchemaVersion;
3349 $this->namespaces['xsi'] = $this->XMLSchemaVersion.'-instance';
3351 $this->namespaces[$key_localpart] = $value;
3352 // set method namespace
3353 if($name == $this->root_struct_name){
3354 $this->methodNamespace = $value;
3356 // if it's a type declaration, set type
3357 } elseif($key_localpart == 'type'){
3358 $value_prefix = $this->getPrefix($value);
3359 $value_localpart = $this->getLocalPart($value);
3360 $this->message[$pos]['type'] = $value_localpart;
3361 $this->message[$pos]['typePrefix'] = $value_prefix;
3362 if(isset($this->namespaces[$value_prefix])){
3363 $this->message[$pos]['type_namespace'] = $this->namespaces[$value_prefix];
3364 } else if(isset($attrs['xmlns:'.$value_prefix])) {
3365 $this->message[$pos]['type_namespace'] = $attrs['xmlns:'.$value_prefix];
3367 // should do something here with the namespace of specified type?
3368 } elseif($key_localpart == 'arrayType'){
3369 $this->message[$pos]['type'] = 'array';
3370 /* do arrayType ereg here
3371 [1] arrayTypeValue ::= atype asize
3372 [2] atype ::= QName rank*
3373 [3] rank ::= '[' (',')* ']'
3374 [4] asize ::= '[' length~ ']'
3375 [5] length ::= nextDimension* Digit+
3376 [6] nextDimension ::= Digit+ ','
3378 $expr = '([A-Za-z0-9_]+):([A-Za-z]+[A-Za-z0-9_]+)\[([0-9]+),?([0-9]*)\]';
3379 if(ereg($expr,$value,$regs)){
3380 $this->message[$pos]['typePrefix'] = $regs[1];
3381 $this->message[$pos]['arraySize'] = $regs[3];
3382 $this->message[$pos]['arrayCols'] = $regs[4];
3387 $this->ids[$value] = $pos;
3390 if($key_localpart == 'root' && $value == 1){
3391 $this->status = 'method';
3392 $this->root_struct_name = $name;
3393 $this->root_struct = $pos;
3394 $this->debug("found root struct $this->root_struct_name, pos $pos");
3397 $attstr .= " $key=\"$value\"";
3399 // get namespace - must be done after namespace atts are processed
3401 $this->message[$pos]['namespace'] = $this->namespaces[$prefix];
3402 $this->default_namespace = $this->namespaces[$prefix];
3404 $this->message[$pos]['namespace'] = $this->default_namespace;
3406 if($this->status == 'header'){
3407 $this->responseHeaders .= "<$name$attstr>";
3408 } elseif($this->root_struct_name != ''){
3409 $this->document .= "<$name$attstr>";
3414 * end-element handler
3416 * @param string $parser XML parser object
3417 * @param string $name element name
3420 function end_element($parser, $name) {
3421 // position of current element is equal to the last value left in depth_array for my depth
3422 $pos = $this->depth_array[$this->depth--];
3424 // get element prefix
3425 if(strpos($name,':')){
3427 $prefix = substr($name,0,strpos($name,':'));
3428 // get unqualified name
3429 $name = substr(strstr($name,':'),1);
3432 // build to native type
3433 if(isset($this->body_position) && $pos > $this->body_position){
3434 // deal w/ multirefs
3435 if(isset($this->message[$pos]['attrs']['href'])){
3437 $id = substr($this->message[$pos]['attrs']['href'],1);
3438 // add placeholder to href array
3439 $this->multirefs[$id][$pos] = "placeholder";
3440 // add set a reference to it as the result value
3441 $this->message[$pos]['result'] =& $this->multirefs[$id][$pos];
3442 // build complex values
3443 } elseif($this->message[$pos]['children'] != ""){
3444 $this->message[$pos]['result'] = $this->buildVal($pos);
3446 $this->debug('adding data for scalar value '.$this->message[$pos]['name'].' of value '.$this->message[$pos]['cdata']);
3447 if(is_numeric($this->message[$pos]['cdata']) ){
3448 if( strpos($this->message[$pos]['cdata'],'.') ){
3449 $this->message[$pos]['result'] = doubleval($this->message[$pos]['cdata']);
3451 $this->message[$pos]['result'] = intval($this->message[$pos]['cdata']);
3454 $this->message[$pos]['result'] = $this->message[$pos]['cdata'];
3460 if($pos == $this->root_struct){
3461 $this->status = 'body';
3462 } elseif($name == 'Body'){
3463 $this->status = 'header';
3464 } elseif($name == 'Header'){
3465 $this->status = 'envelope';
3466 } elseif($name == 'Envelope'){
3469 // set parent back to my parent
3470 $this->parent = $this->message[$pos]['parent'];
3472 if($this->status == 'header'){
3473 $this->responseHeaders .= "</$name>";
3474 } elseif($pos >= $this->root_struct){
3475 $this->document .= "</$name>";
3480 * element content handler
3482 * @param string $parser XML parser object
3483 * @param string $data element content
3486 function character_data($parser, $data){
3487 $pos = $this->depth_array[$this->depth];
3488 if ($this->xml_encoding=='UTF-8'){
3489 $data = utf8_decode($data);
3491 $this->message[$pos]['cdata'] .= $data;
3493 if($this->status == 'header'){
3494 $this->responseHeaders .= $data;
3496 $this->document .= $data;
3501 * get the parsed message
3506 function get_response(){
3507 return $this->soapresponse;
3511 * get the parsed headers
3513 * @return string XML or empty if no headers
3516 function getHeaders(){
3517 return $this->responseHeaders;
3523 * @param string $text string to translate
3526 function decode_entities($text){
3527 foreach($this->entities as $entity => $encoded){
3528 $text = str_replace($encoded,$entity,$text);
3534 * builds response structures for compound values (arrays/structs)
3536 * @param string $pos position in node tree
3539 function buildVal($pos){
3540 if(!isset($this->message[$pos]['type'])){
3541 $this->message[$pos]['type'] = '';
3543 $this->debug('inside buildVal() for '.$this->message[$pos]['name']."(pos $pos) of type ".$this->message[$pos]['type']);
3544 // if there are children...
3545 if($this->message[$pos]['children'] != ''){
3546 $children = explode('|',$this->message[$pos]['children']);
3547 array_shift($children); // knock off empty
3549 if(isset($this->message[$pos]['arrayCols']) && $this->message[$pos]['arrayCols'] != ''){
3552 foreach($children as $child_pos){
3553 $this->debug("got an MD array element: $r, $c");
3554 $params[$r][] = $this->message[$child_pos]['result'];
3556 if($c == $this->message[$pos]['arrayCols']){
3562 } elseif($this->message[$pos]['type'] == 'array' || $this->message[$pos]['type'] == 'Array'){
3563 $this->debug('adding array '.$this->message[$pos]['name']);
3564 foreach($children as $child_pos){
3565 $params[] = &$this->message[$child_pos]['result'];
3567 // apache Map type: java hashtable
3568 } elseif($this->message[$pos]['type'] == 'Map' && $this->message[$pos]['type_namespace'] == 'http://xml.apache.org/xml-soap'){
3569 foreach($children as $child_pos){
3570 $kv = explode("|",$this->message[$child_pos]['children']);
3571 $params[$this->message[$kv[1]]['result']] = &$this->message[$kv[2]]['result'];
3573 // generic compound type
3574 //} elseif($this->message[$pos]['type'] == 'SOAPStruct' || $this->message[$pos]['type'] == 'struct') {
3576 // is array or struct? better way to do this probably
3577 foreach($children as $child_pos){
3578 if(isset($keys) && isset($keys[$this->message[$child_pos]['name']])){
3582 $keys[$this->message[$child_pos]['name']] = 1;
3585 foreach($children as $child_pos){
3587 $params[] = &$this->message[$child_pos]['result'];
3589 $params[$this->message[$child_pos]['name']] = &$this->message[$child_pos]['result'];
3593 return is_array($params) ? $params : array();
3595 $this->debug('no children');
3596 if(strpos($this->message[$pos]['cdata'],'&')){
3597 return strtr($this->message[$pos]['cdata'],array_flip($this->entities));
3599 return $this->message[$pos]['cdata'];
3613 * soapclient higher level class for easy usage.
3617 * // instantiate client with server info
3618 * $soapclient = new soapclient( string path [ ,boolean wsdl] );
3620 * // call method, get results
3621 * echo $soapclient->call( string methodname [ ,array parameters] );
3624 * unset($soapclient);
3626 * @author Dietrich Ayala <dietrich@ganx4.com>
3630 class soapclient extends nusoap_base {
3634 var $requestHeaders = false;
3635 var $responseHeaders;
3637 var $error_str = false;
3638 var $proxyhost = '';
3639 var $proxyport = '';
3640 var $xml_encoding = '';
3641 var $http_encoding = false;
3643 var $endpointType = '';
3644 var $persistentConnection = false;
3645 var $defaultRpcParams = false;
3648 * fault related variables
3656 var $fault, $faultcode, $faultstring, $faultdetail;
3661 * @param string $endpoint SOAP server or WSDL URL
3662 * @param bool $wsdl optional, set to true if using WSDL
3663 * @param int $portName optional portName in WSDL document
3666 function soapclient($endpoint,$wsdl = false){
3667 $this->endpoint = $endpoint;
3671 $this->endpointType = 'wsdl';
3672 $this->wsdlFile = $this->endpoint;
3674 // instantiate wsdl object and parse wsdl file
3675 $this->debug('instantiating wsdl class with doc: '.$endpoint);
3676 $this->wsdl =& new wsdl($this->wsdlFile,$this->proxyhost,$this->proxyport);
3677 $this->debug("wsdl debug: \n".$this->wsdl->debug_str);
3678 $this->wsdl->debug_str = '';
3680 if($errstr = $this->wsdl->getError()){
3681 $this->debug('got wsdl error: '.$errstr);
3682 $this->setError('wsdl error: '.$errstr);
3683 } elseif($this->operations = $this->wsdl->getOperations()){
3684 $this->debug( 'got '.count($this->operations).' operations from wsdl '.$this->wsdlFile);
3686 $this->debug( 'getOperations returned false');
3687 $this->setError('no operations defined in the WSDL document!');
3693 * calls method, returns PHP native type
3695 * @param string $method SOAP server URL or path
3696 * @param array $params array of parameters, can be associative or not
3697 * @param string $namespace optional method namespace
3698 * @param string $soapAction optional SOAPAction value
3699 * @param boolean $headers optional array of soapval objects for headers
3700 * @param boolean $rpcParams optional treat params as RPC for use="literal"
3701 * This can be used on a per-call basis to overrider defaultRpcParams.
3705 function call($operation,$params=array(),$namespace='',$soapAction='',$headers=false,$rpcParams=null){
3706 $this->operation = $operation;
3707 $this->fault = false;
3708 $this->error_str = '';
3709 $this->request = '';
3710 $this->response = '';
3711 $this->faultstring = '';
3712 $this->faultcode = '';
3713 $this->opData = array();
3715 $this->debug("call: $operation, $params, $namespace, $soapAction, $headers, $rpcParams");
3716 $this->debug("endpointType: $this->endpointType");
3717 // if wsdl, get operation data and process parameters
3718 if($this->endpointType == 'wsdl' && $opData = $this->getOperationData($operation)){
3720 $this->opData = $opData;
3721 foreach($opData as $key => $value){
3722 $this->debug("$key -> $value");
3724 $soapAction = $opData['soapAction'];
3725 $this->endpoint = $opData['endpoint'];
3726 $namespace = isset($opData['input']['namespace']) ? $opData['input']['namespace'] : 'http://testuri.org';
3727 $style = $opData['style'];
3728 // add ns to ns array
3729 if($namespace != '' && !isset($this->wsdl->namespaces[$namespace])){
3730 $this->wsdl->namespaces['nu'] = $namespace;
3732 // serialize payload
3734 if($opData['input']['use'] == 'literal') {
3735 if (is_null($rpcParams)) {
3736 $rpcParams = $this->defaultRpcParams;
3739 $this->debug("serializing literal params for operation $operation");
3740 $payload = $this->wsdl->serializeRPCParameters($operation,'input',$params);
3741 $defaultNamespace = $this->wsdl->wsdl_info['targetNamespace'];
3743 $this->debug("serializing literal document for operation $operation");
3744 $payload = is_array($params) ? array_shift($params) : $params;
3747 $this->debug("serializing encoded params for operation $operation");
3748 $payload = "<".$this->wsdl->getPrefixFromNamespace($namespace).":$operation>".
3749 $this->wsdl->serializeRPCParameters($operation,'input',$params).
3750 '</'.$this->wsdl->getPrefixFromNamespace($namespace).":$operation>";
3752 $this->debug('payload size: '.strlen($payload));
3753 // serialize envelope
3754 $soapmsg = $this->serializeEnvelope($payload,$this->requestHeaders,$this->wsdl->usedNamespaces,$style);
3755 $this->debug("wsdl debug: \n".$this->wsdl->debug_str);
3756 $this->wsdl->debug_str = '';
3757 } elseif($this->endpointType == 'wsdl') {
3758 $this->setError( 'operation '.$operation.' not present.');
3759 $this->debug("operation '$operation' not present.");
3760 $this->debug("wsdl debug: \n".$this->wsdl->debug_str);
3768 if($namespace == ''){
3769 $namespace = 'http://testuri.org';
3770 $this->wsdl->namespaces['ns1'] = $namespace;
3772 // serialize envelope
3774 foreach($params as $k => $v){
3775 $payload .= $this->serialize_val($v,$k);
3777 $payload = "<ns1:$operation xmlns:ns1=\"$namespace\">\n".$payload."</ns1:$operation>\n";
3778 $soapmsg = $this->serializeEnvelope($payload,$this->requestHeaders);
3780 $this->debug("endpoint: $this->endpoint, soapAction: $soapAction, namespace: $namespace");
3782 $this->debug('sending msg (len: '.strlen($soapmsg).") w/ soapaction '$soapAction'...");
3783 $return = $this->send($soapmsg,$soapAction,$this->timeout);
3784 if($errstr = $this->getError()){
3785 $this->debug('Error: '.$errstr);
3788 $this->return = $return;
3789 $this->debug('sent message successfully and got a(n) '.gettype($return).' back');
3792 if(is_array($return) && isset($return['faultcode'])){
3793 $this->debug('got fault');
3794 $this->setError($return['faultcode'].': '.$return['faultstring']);
3795 $this->fault = true;
3796 foreach($return as $k => $v){
3798 $this->debug("$k = $v<br>");
3802 // array of return values
3803 if(is_array($return)){
3804 // multiple 'out' parameters
3805 if(sizeof($return) > 1){
3808 // single 'out' parameter
3809 return array_shift($return);
3810 // nothing returned (ie, echoVoid)
3819 * get available data pertaining to an operation
3821 * @param string $operation operation name
3822 * @return array array of data pertaining to the operation
3825 function getOperationData($operation){
3826 if(isset($this->operations[$operation])){
3827 return $this->operations[$operation];
3829 $this->debug("No data for operation: $operation");
3833 * send the SOAP message
3835 * Note: if the operation has multiple return values
3836 * the return value of this method will be an array
3839 * @param string $msg a SOAPx4 soapmsg object
3840 * @param string $soapaction SOAPAction value
3841 * @param integer $timeout set timeout in seconds
3842 * @return mixed native PHP types.
3845 function send($msg, $soapaction = '', $timeout=0) {
3849 case ereg('^http',$this->endpoint):
3850 $this->debug('transporting via HTTP');
3851 if($this->persistentConnection && is_object($this->persistentConnection)){
3852 $http =& $this->persistentConnection;
3854 $http = new soap_transport_http($this->endpoint);
3855 // pass encoding into transport layer, so appropriate http headers are sent
3856 $http->soap_defencoding = $this->soap_defencoding;
3858 $http->setSOAPAction($soapaction);
3859 if($this->proxyhost && $this->proxyport){
3860 $http->setProxy($this->proxyhost,$this->proxyport);
3862 if($this->username != '' && $this->password != '') {
3863 $http->setCredentials($this->username,$this->password);
3865 if($this->http_encoding != ''){
3866 $http->setEncoding($this->http_encoding);
3868 $this->debug('sending message, length: '.strlen($msg));
3869 if(ereg('^http:',$this->endpoint)){
3870 //if(strpos($this->endpoint,'http:')){
3871 $response = $http->send($msg,$timeout);
3872 } elseif(ereg('^https',$this->endpoint)){
3873 //} elseif(strpos($this->endpoint,'https:')){
3874 //if(phpversion() == '4.3.0-dev'){
3875 //$response = $http->send($msg,$timeout);
3876 //$this->request = $http->outgoing_payload;
3877 //$this->response = $http->incoming_payload;
3879 if (extension_loaded('curl')) {
3880 $response = $http->sendHTTPS($msg,$timeout);
3882 $this->setError('CURL Extension, or OpenSSL extension w/ PHP version >= 4.3 is required for HTTPS');
3885 $this->setError('no http/s in endpoint url');
3887 $this->request = $http->outgoing_payload;
3888 $this->response = $http->incoming_payload;
3889 $this->debug("transport debug data...\n".$http->debug_str);
3890 // save transport object if using persistent connections
3891 if($this->persistentConnection && !is_object($this->persistentConnection)){
3892 $this->persistentConnection = $http;
3894 if($err = $http->getError()){
3895 $this->setError('HTTP Error: '.$err);
3897 } elseif($this->getError()){
3900 $this->debug('got response, length: '.strlen($response));
3901 return $this->parseResponse($response);
3905 $this->setError('no transport found, or selected transport is not yet supported!');
3912 * processes SOAP message returned from server
3914 * @param string unprocessed response data from server
3915 * @return mixed value of the message, decoded into a PHP type
3918 function parseResponse($data) {
3919 $this->debug('Entering parseResponse(), about to create soap_parser instance');
3920 $parser = new soap_parser($data,$this->xml_encoding,$this->operation);
3922 if($errstr = $parser->getError()){
3923 $this->setError( $errstr);
3924 // destroy the parser object
3929 $this->responseHeaders = $parser->getHeaders();
3930 // get decoded message
3931 $return = $parser->get_response();
3932 // add parser debug data to our debug
3933 $this->debug($parser->debug_str);
3934 // add document for doclit support
3935 $this->document = $parser->document;
3936 // destroy the parser object
3938 // return decode message
3944 * set the SOAP headers
3946 * @param $headers string XML
3949 function setHeaders($headers){
3950 $this->requestHeaders = $headers;
3954 * get the response headers
3956 * @return mixed object SOAPx4 soapval object or empty if no headers
3959 function getHeaders(){
3960 if($this->responseHeaders != '') {
3961 return $this->responseHeaders;
3966 * set proxy info here
3968 * @param string $proxyhost
3969 * @param string $proxyport
3972 function setHTTPProxy($proxyhost, $proxyport) {
3973 $this->proxyhost = $proxyhost;
3974 $this->proxyport = $proxyport;
3978 * if authenticating, set user credentials here
3980 * @param string $username
3981 * @param string $password
3984 function setCredentials($username, $password) {
3985 $this->username = $username;
3986 $this->password = $password;
3992 * @param string $enc
3995 function setHTTPEncoding($enc='gzip, deflate'){
3996 $this->http_encoding = $enc;
4000 * use HTTP persistent connections if possible
4004 function useHTTPPersistentConnection(){
4005 $this->persistentConnection = true;
4009 * gets the default RPC parameter setting.
4010 * If true, default is that call params are like RPC even for document style.
4011 * Each call() can override this value.
4015 function getDefaultRpcParams() {
4016 return $this->defaultRpcParams;
4020 * sets the default RPC parameter setting.
4021 * If true, default is that call params are like RPC even for document style
4022 * Each call() can override this value.
4024 * @param boolean $rpcParams
4027 function setDefaultRpcParams($rpcParams) {
4028 $this->defaultRpcParams = $rpcParams;
4032 * dynamically creates proxy class, allowing user to directly call methods from wsdl
4034 * @return object soap_proxy object
4037 function getProxy(){
4039 foreach($this->operations as $operation => $opData){
4040 if($operation != ''){
4041 // create param string
4043 if(sizeof($opData['input']['parts']) > 0){
4044 foreach($opData['input']['parts'] as $name => $type){
4045 $paramStr .= "\$$name,";
4047 $paramStr = substr($paramStr,0,strlen($paramStr)-1);
4049 $opData['namespace'] = !isset($opData['namespace']) ? 'http://testuri.com' : $opData['namespace'];
4050 $evalStr .= "function $operation ($paramStr){
4051 // load params into array
4052 \$params = array($paramStr);
4053 return \$this->call('$operation',\$params,'".$opData['namespace']."','".$opData['soapAction']."');
4059 $evalStr = 'class soap_proxy_'.$r.' extends soapclient {
4062 //print "proxy class:<pre>$evalStr</pre>";
4065 // instantiate proxy object
4066 eval("\$proxy = new soap_proxy_$r('');");
4067 // transfer current wsdl data to the proxy thereby avoiding parsing the wsdl twice
4068 $proxy->endpointType = 'wsdl';
4069 $proxy->wsdlFile = $this->wsdlFile;
4070 $proxy->wsdl = $this->wsdl;
4071 $proxy->operations = $this->operations;
4072 $proxy->defaultRpcParams = $this->defaultRpcParams;
4080 // c-basic-offset: 4
4081 // c-hanging-comment-ender-p: nil
4082 // indent-tabs-mode: nil