6 NuSOAP - Web Services Toolkit for PHP
8 Copyright (c) 2002 NuSphere Corporation
10 This library is free software; you can redistribute it and/or
11 modify it under the terms of the GNU Lesser General Public
12 License as published by the Free Software Foundation; either
13 version 2.1 of the License, or (at your option) any later version.
15 This library is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 Lesser General Public License for more details.
20 You should have received a copy of the GNU Lesser General Public
21 License along with this library; if not, write to the Free Software
22 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 If you have any questions or comments, please email:
28 http://dietrich.ganx4.com/nusoap
31 http://www.nusphere.com
38 require_once('class.soapclient.php');
39 require_once('class.soap_val.php');
40 require_once('class.soap_parser.php');
41 require_once('class.soap_fault.php');
44 require_once('class.soap_transport_http.php');
46 // optional add-on classes
47 require_once('class.xmlschema.php');
48 require_once('class.wsdl.php');
51 require_once('class.soap_server.php');*/
57 * @author Dietrich Ayala <dietrich@ganx4.com>
63 var $title = 'NuSOAP';
64 var $version = '0.6.7';
65 var $revision = '$Revision$';
66 var $error_str = false;
68 // toggles automatic encoding of special characters as entities
69 // (should always be true, I think)
70 var $charencoding = true;
75 * @var XMLSchemaVersion
78 var $XMLSchemaVersion = 'http://www.w3.org/2001/XMLSchema';
81 * set charset encoding for outgoing messages
83 * @var soap_defencoding
86 //var $soap_defencoding = 'UTF-8';
87 var $soap_defencoding = 'ISO-8859-1';
90 * load namespace uris into an array of uri => prefix
95 var $namespaces = array(
96 'SOAP-ENV' => 'http://schemas.xmlsoap.org/soap/envelope/',
97 'xsd' => 'http://www.w3.org/2001/XMLSchema',
98 'xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
99 'SOAP-ENC' => 'http://schemas.xmlsoap.org/soap/encoding/',
100 'si' => 'http://soapinterop.org/xsd');
101 var $usedNamespaces = array();
104 * load types into typemap array
105 * is this legacy yet?
106 * no, this is used by the xmlschema class to verify type => namespace mappings.
110 var $typemap = array(
111 'http://www.w3.org/2001/XMLSchema' => array(
112 'string'=>'string','boolean'=>'boolean','float'=>'double','double'=>'double','decimal'=>'double',
113 'duration'=>'','dateTime'=>'string','time'=>'string','date'=>'string','gYearMonth'=>'',
114 'gYear'=>'','gMonthDay'=>'','gDay'=>'','gMonth'=>'','hexBinary'=>'string','base64Binary'=>'string',
116 'normalizedString'=>'string','token'=>'string','language'=>'','NMTOKEN'=>'','NMTOKENS'=>'','Name'=>'','NCName'=>'','ID'=>'',
117 'IDREF'=>'','IDREFS'=>'','ENTITY'=>'','ENTITIES'=>'','integer'=>'integer','nonPositiveInteger'=>'integer',
118 'negativeInteger'=>'integer','long'=>'integer','int'=>'integer','short'=>'integer','byte'=>'integer','nonNegativeInteger'=>'integer',
119 'unsignedLong'=>'','unsignedInt'=>'','unsignedShort'=>'','unsignedByte'=>'','positiveInteger'=>''),
120 'http://www.w3.org/1999/XMLSchema' => array(
121 'i4'=>'','int'=>'integer','boolean'=>'boolean','string'=>'string','double'=>'double',
122 'float'=>'double','dateTime'=>'string',
123 'timeInstant'=>'string','base64Binary'=>'string','base64'=>'string','ur-type'=>'array'),
124 'http://soapinterop.org/xsd' => array('SOAPStruct'=>'struct'),
125 'http://schemas.xmlsoap.org/soap/encoding/' => array('base64'=>'string','array'=>'array','Array'=>'array'),
126 'http://xml.apache.org/xml-soap' => array('Map')
130 * entities to convert
135 var $xmlEntities = array('quot' => '"','amp' => '&',
136 'lt' => '<','gt' => '>','apos' => "'");
139 * adds debug data to the class level debug string
141 * @param string $string debug data
144 function debug($string){
145 $this->debug_str
.= get_class($this).": $string\n";
149 * expands entities, e.g. changes '<' to '<'.
151 * @param string $val The string in which to expand entities.
154 function expandEntities($val) {
155 if ($this->charencoding
) {
156 $val = str_replace('&', '&', $val);
157 $val = str_replace("'", ''', $val);
158 $val = str_replace('"', '"', $val);
159 $val = str_replace('<', '<', $val);
160 $val = str_replace('>', '>', $val);
166 * returns error string if present
168 * @return boolean $string error string
172 if($this->error_str
!= ''){
173 return $this->error_str
;
181 * @return boolean $string error string
184 function setError($str){
185 $this->error_str
= $str;
189 * detect if array is a simple array or a struct (associative array)
191 * @param $val The PHP array
192 * @return string (arraySimple|arrayStruct)
195 function isArraySimpleOrStruct($val) {
196 $keyList = array_keys($val);
197 foreach ($keyList as $keyListValue) {
198 if (!is_int($keyListValue)) {
199 return 'arrayStruct';
202 return 'arraySimple';
206 * serializes PHP values in accordance w/ section 5. Type information is
207 * not serialized if $use == 'literal'.
212 function serialize_val($val,$name=false,$type=false,$name_ns=false,$type_ns=false,$attributes=false,$use='encoded'){
213 if(is_object($val) && get_class($val) == 'soapval'){
214 return $val->serialize($use);
216 $this->debug( "in serialize_val: $val, $name, $type, $name_ns, $type_ns, $attributes, $use");
217 // if no name, use item
218 $name = (!$name||
is_numeric($name)) ?
'soapVal' : $name;
219 // if name has ns, add ns prefix to name
222 $prefix = 'nu'.rand(1000,9999);
223 $name = $prefix.':'.$name;
224 $xmlns .= " xmlns:$prefix=\"$name_ns\"";
226 // if type is prefixed, create type prefix
227 if($type_ns != '' && $type_ns == $this->namespaces
['xsd']){
228 // need to fix this. shouldn't default to xsd if no ns specified
229 // w/o checking against typemap
230 $type_prefix = 'xsd';
232 $type_prefix = 'ns'.rand(1000,9999);
233 $xmlns .= " xmlns:$type_prefix=\"$type_ns\"";
235 // serialize attributes if present
238 foreach($attributes as $k => $v){
239 $atts .= " $k=\"$v\"";
242 // serialize if an xsd built-in primitive type
243 if($type != '' && isset($this->typemap
[$this->XMLSchemaVersion
][$type])){
245 if ($type == 'boolean') {
246 $val = $val ?
'true' : 'false';
250 } else if (is_string($val)) {
251 $val = $this->expandEntities($val);
253 if ($use == 'literal') {
254 return "<$name$xmlns>$val</$name>";
256 return "<$name$xmlns xsi:type=\"xsd:$type\">$val</$name>";
259 // detect type and serialize
262 case ($type == '' && is_null($val)):
263 if ($use == 'literal') {
264 // TODO: depends on nillable
265 $xml .= "<$name$xmlns/>";
267 $xml .= "<$name$xmlns xsi:nil=\"true\"/>";
270 case (is_bool($val) ||
$type == 'boolean'):
271 if ($type == 'boolean') {
272 $val = $val ?
'true' : 'false';
276 if ($use == 'literal') {
277 $xml .= "<$name$xmlns $atts>$val</$name>";
279 $xml .= "<$name$xmlns xsi:type=\"xsd:boolean\"$atts>$val</$name>";
282 case (is_int($val) ||
is_long($val) ||
$type == 'int'):
283 if ($use == 'literal') {
284 $xml .= "<$name$xmlns $atts>$val</$name>";
286 $xml .= "<$name$xmlns xsi:type=\"xsd:int\"$atts>$val</$name>";
289 case (is_float($val)||
is_double($val) ||
$type == 'float'):
290 if ($use == 'literal') {
291 $xml .= "<$name$xmlns $atts>$val</$name>";
293 $xml .= "<$name$xmlns xsi:type=\"xsd:float\"$atts>$val</$name>";
296 case (is_string($val) ||
$type == 'string'):
297 $val = $this->expandEntities($val);
298 if ($use == 'literal') {
299 $xml .= "<$name$xmlns $atts>$val</$name>";
301 $xml .= "<$name$xmlns xsi:type=\"xsd:string\"$atts>$val</$name>";
304 case is_object($val):
305 $name = get_class($val);
306 foreach(get_object_vars($val) as $k => $v){
307 $pXml = isset($pXml) ?
$pXml.$this->serialize_val($v,$k,false,false,false,false,$use) : $this->serialize_val($v,$k,false,false,false,false,$use);
309 $xml .= '<'.$name.'>'.$pXml.'</'.$name.'>';
312 case (is_array($val) ||
$type):
313 // detect if struct or array
314 $valueType = $this->isArraySimpleOrStruct($val);
315 if($valueType=='arraySimple' ||
ereg('^ArrayOf',$type)){
317 if(is_array($val) && count($val)> 0){
319 if(is_object($v) && get_class($v) == 'soapval'){
320 $tt_ns = $v->type_ns
;
322 } elseif (is_array($v)) {
323 $tt = $this->isArraySimpleOrStruct($v);
327 $array_types[$tt] = 1;
328 $xml .= $this->serialize_val($v,'item',false,false,false,false,$use);
331 if(count($array_types) > 1){
332 $array_typename = 'xsd:ur-type';
333 } elseif(isset($tt) && isset($this->typemap
[$this->XMLSchemaVersion
][$tt])) {
334 if ($tt == 'integer') {
337 $array_typename = 'xsd:'.$tt;
338 } elseif(isset($tt) && $tt == 'arraySimple'){
339 $array_typename = 'SOAP-ENC:Array';
340 } elseif(isset($tt) && $tt == 'arrayStruct'){
341 $array_typename = 'unnamed_struct_use_soapval';
343 // if type is prefixed, create type prefix
344 if ($tt_ns != '' && $tt_ns == $this->namespaces
['xsd']){
345 $array_typename = 'xsd:' . $tt;
347 $tt_prefix = 'ns' . rand(1000, 9999);
348 $array_typename = "$tt_prefix:$tt";
349 $xmlns .= " xmlns:$tt_prefix=\"$tt_ns\"";
351 $array_typename = $tt;
355 if ($use == 'literal') {
357 } else if (isset($type) && isset($type_prefix)) {
358 $type_str = " xsi:type=\"$type_prefix:$type\"";
360 $type_str = " xsi:type=\"SOAP-ENC:Array\" SOAP-ENC:arrayType=\"".$array_typename."[$array_type]\"";
364 if ($use == 'literal') {
366 } else if (isset($type) && isset($type_prefix)) {
367 $type_str = " xsi:type=\"$type_prefix:$type\"";
369 $type_str = " xsi:type=\"SOAP-ENC:Array\"";
372 $xml = "<$name$xmlns$type_str$atts>".$xml."</$name>";
375 if(isset($type) && isset($type_prefix)){
376 $type_str = " xsi:type=\"$type_prefix:$type\"";
380 if ($use == 'literal') {
381 $xml .= "<$name$xmlns $atts>";
383 $xml .= "<$name$xmlns$type_str$atts>";
385 foreach($val as $k => $v){
387 if ($type == 'Map' && $type_ns == 'http://xml.apache.org/xml-soap') {
389 $xml .= $this->serialize_val($k,'key',false,false,false,false,$use);
390 $xml .= $this->serialize_val($v,'value',false,false,false,false,$use);
393 $xml .= $this->serialize_val($v,$k,false,false,false,false,$use);
400 $xml .= 'not detected, got '.gettype($val).' for '.$val;
410 * @param string headers optional
411 * @param array namespaces optional
412 * @param string style optional (rpc|document)
413 * @param string use optional (encoded|literal)
414 * @return string message
417 function serializeEnvelope($body,$headers=false,$namespaces=array(),$style='rpc',$use='encoded'){
418 // TODO: add an option to automatically run utf8_encode on $body and $headers
419 // if $this->soap_defencoding is UTF-8. Not doing this automatically allows
420 // one to send arbitrary UTF-8 characters, not just characters that map to ISO-8859-1
422 // serialize namespaces
424 foreach(array_merge($this->namespaces
,$namespaces) as $k => $v){
425 $ns_string .= " xmlns:$k=\"$v\"";
427 if($style == 'rpc' && $use == 'encoded') {
428 $ns_string = ' SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"' . $ns_string;
433 $headers = "<SOAP-ENV:Header>".$headers."</SOAP-ENV:Header>";
435 // serialize envelope
437 '<?xml version="1.0" encoding="'.$this->soap_defencoding
.'"?'.">".
438 '<SOAP-ENV:Envelope'.$ns_string.">".
443 "</SOAP-ENV:Envelope>";
446 function formatDump($str){
447 $str = htmlspecialchars($str);
452 * contracts a qualified name
454 * @param string $string qname
455 * @return string contracted qname
458 function contractQname($qname){
459 // get element namespace
460 //$this->xdebug("Contract $qname");
461 if (strrpos($qname, ':')) {
462 // get unqualified name
463 $name = substr($qname, strrpos($qname, ':') +
1);
465 $ns = substr($qname, 0, strrpos($qname, ':'));
466 $p = $this->getPrefixFromNamespace($ns);
468 return $p . ':' . $name;
477 * expands a qualified name
479 * @param string $string qname
480 * @return string expanded qname
483 function expandQname($qname){
484 // get element prefix
485 if(strpos($qname,':') && !ereg('^http://',$qname)){
486 // get unqualified name
487 $name = substr(strstr($qname,':'),1);
489 $prefix = substr($qname,0,strpos($qname,':'));
490 if(isset($this->namespaces
[$prefix])){
491 return $this->namespaces
[$prefix].':'.$name;
501 * returns the local part of a prefixed string
502 * returns the original string, if not prefixed
508 function getLocalPart($str){
509 if($sstr = strrchr($str,':')){
510 // get unqualified name
511 return substr( $sstr, 1 );
518 * returns the prefix part of a prefixed string
519 * returns false, if not prefixed
525 function getPrefix($str){
526 if($pos = strrpos($str,':')){
528 return substr($str,0,$pos);
534 * pass it a prefix, it returns a namespace
535 * returns false if no namespace registered with the given prefix
541 function getNamespaceFromPrefix($prefix){
542 if (isset($this->namespaces
[$prefix])) {
543 return $this->namespaces
[$prefix];
545 //$this->setError("No namespace registered for prefix '$prefix'");
550 * returns the prefix for a given namespace (or prefix)
551 * or false if no prefixes registered for the given namespace
557 function getPrefixFromNamespace($ns) {
558 foreach ($this->namespaces
as $p => $n) {
559 if ($ns == $n ||
$ns == $p) {
560 $this->usedNamespaces
[$p] = $n;
567 function varDump($data) {
570 $ret_val = ob_get_contents();
576 // XML Schema Datatype Helper Functions
578 //xsd:dateTime helpers
581 * convert unix timestamp to ISO 8601 compliant date string
583 * @param string $timestamp Unix time stamp
586 function timestamp_to_iso8601($timestamp,$utc=true){
587 $datestr = date('Y-m-d\TH:i:sO',$timestamp);
590 '([0-9]{4})-'. // centuries & years CCYY-
591 '([0-9]{2})-'. // months MM-
592 '([0-9]{2})'. // days DD
594 '([0-9]{2}):'. // hours hh:
595 '([0-9]{2}):'. // minutes mm:
596 '([0-9]{2})(\.[0-9]*)?'. // seconds ss.ss...
597 '(Z|[+\-][0-9]{2}:?[0-9]{2})?'; // Z to indicate UTC, -/+HH:MM:SS.SS... for local tz's
599 if(ereg($eregStr,$datestr,$regs)){
600 return sprintf('%04d-%02d-%02dT%02d:%02d:%02dZ',$regs[1],$regs[2],$regs[3],$regs[4],$regs[5],$regs[6]);
609 * convert ISO 8601 compliant date string to unix timestamp
611 * @param string $datestr ISO 8601 compliant date string
614 function iso8601_to_timestamp($datestr){
616 '([0-9]{4})-'. // centuries & years CCYY-
617 '([0-9]{2})-'. // months MM-
618 '([0-9]{2})'. // days DD
620 '([0-9]{2}):'. // hours hh:
621 '([0-9]{2}):'. // minutes mm:
622 '([0-9]{2})(\.[0-9]+)?'. // seconds ss.ss...
623 '(Z|[+\-][0-9]{2}:?[0-9]{2})?'; // Z to indicate UTC, -/+HH:MM:SS.SS... for local tz's
624 if(ereg($eregStr,$datestr,$regs)){
627 $op = substr($regs[8],0,1);
628 $h = substr($regs[8],1,2);
629 $m = substr($regs[8],strlen($regs[8])-2,2);
631 $regs[4] = $regs[4] +
$h;
632 $regs[5] = $regs[5] +
$m;
633 } elseif($op == '+'){
634 $regs[4] = $regs[4] - $h;
635 $regs[5] = $regs[5] - $m;
638 return strtotime("$regs[1]-$regs[2]-$regs[3] $regs[4]:$regs[5]:$regs[6]Z");
644 function usleepWindows($usec)
646 $start = gettimeofday();
650 $stop = gettimeofday();
651 $timePassed = 1000000 * ($stop['sec'] - $start['sec'])
652 +
$stop['usec'] - $start['usec'];
654 while ($timePassed < $usec);
662 * soap_fault class, allows for creation of faults
663 * mainly used for returning faults from deployed functions
664 * in a server instance.
665 * @author Dietrich Ayala <dietrich@ganx4.com>
669 class soap_fault
extends nusoap_base
{
679 * @param string $faultcode (client | server)
680 * @param string $faultactor only used when msg routed between multiple actors
681 * @param string $faultstring human readable error message
682 * @param string $faultdetail
684 function soap_fault($faultcode,$faultactor='',$faultstring='',$faultdetail=''){
685 $this->faultcode
= $faultcode;
686 $this->faultactor
= $faultactor;
687 $this->faultstring
= $faultstring;
688 $this->faultdetail
= $faultdetail;
696 function serialize(){
698 foreach($this->namespaces
as $k => $v){
699 $ns_string .= "\n xmlns:$k=\"$v\"";
702 '<?xml version="1.0" encoding="'.$this->soap_defencoding
.'"?>'.
703 '<SOAP-ENV:Envelope SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"'.$ns_string.">\n".
706 '<faultcode>'.$this->expandEntities($this->faultcode
).'</faultcode>'.
707 '<faultactor>'.$this->expandEntities($this->faultactor
).'</faultactor>'.
708 '<faultstring>'.$this->expandEntities($this->faultstring
).'</faultstring>'.
709 '<detail>'.$this->serialize_val($this->faultdetail
).'</detail>'.
712 '</SOAP-ENV:Envelope>';
724 * parses an XML Schema, allows access to it's data, other utility methods
725 * no validation... yet.
726 * very experimental and limited. As is discussed on XML-DEV, I'm one of the people
727 * that just doesn't have time to read the spec(s) thoroughly, and just have a couple of trusty
728 * tutorials I refer to :)
730 * @author Dietrich Ayala <dietrich@ganx4.com>
734 class XMLSchema
extends nusoap_base
{
740 var $enclosingNamespaces;
742 var $schemaInfo = array();
743 var $schemaTargetNamespace = '';
744 // types, elements, attributes defined by the schema
745 var $attributes = array();
746 var $complexTypes = array();
747 var $currentComplexType = false;
748 var $elements = array();
749 var $currentElement = false;
750 var $simpleTypes = array();
751 var $currentSimpleType = false;
753 var $imports = array();
758 var $depth_array = array();
759 var $message = array();
760 var $defaultNamespace = array();
765 * @param string $schema schema document URI
766 * @param string $xml xml document URI
767 * @param string $namespaces namespaces defined in enclosing XML
770 function XMLSchema($schema='',$xml='',$namespaces=array()){
772 $this->debug('xmlschema class instantiated, inside constructor');
774 $this->schema
= $schema;
778 $this->enclosingNamespaces
= $namespaces;
779 $this->namespaces
= array_merge($this->namespaces
, $namespaces);
783 $this->debug('initial schema file: '.$schema);
784 $this->parseFile($schema, 'schema');
789 $this->debug('initial xml file: '.$xml);
790 $this->parseFile($xml, 'xml');
798 * @param string $xml, path/URL to XML file
799 * @param string $type, (schema | xml)
803 function parseFile($xml,$type){
806 $xmlStr = @join
("",@file
($xml));
808 $msg = 'Error reading XML from '.$xml;
809 $this->setError($msg);
813 $this->debug("parsing $xml");
814 $this->parseString($xmlStr,$type);
815 $this->debug("done parsing $xml");
823 * parse an XML string
825 * @param string $xml path or URL
826 * @param string $type, (schema|xml)
829 function parseString($xml,$type){
833 // Create an XML parser.
834 $this->parser
= xml_parser_create();
835 // Set the options for parsing the XML data.
836 xml_parser_set_option($this->parser
, XML_OPTION_CASE_FOLDING
, 0);
838 // Set the object for the parser.
839 xml_set_object($this->parser
, $this);
841 // Set the element handlers for the parser.
842 if($type == "schema"){
843 xml_set_element_handler($this->parser
, 'schemaStartElement','schemaEndElement');
844 xml_set_character_data_handler($this->parser
,'schemaCharacterData');
845 } elseif($type == "xml"){
846 xml_set_element_handler($this->parser
, 'xmlStartElement','xmlEndElement');
847 xml_set_character_data_handler($this->parser
,'xmlCharacterData');
850 // Parse the XML file.
851 if(!xml_parse($this->parser
,$xml,true)){
852 // Display an error message.
853 $errstr = sprintf('XML error parsing XML schema on line %d: %s',
854 xml_get_current_line_number($this->parser
),
855 xml_error_string(xml_get_error_code($this->parser
))
857 $this->debug($errstr);
858 $this->debug("XML payload:\n" . $xml);
859 $this->setError($errstr);
862 xml_parser_free($this->parser
);
864 $this->debug('no xml passed to parseString()!!');
865 $this->setError('no xml passed to parseString()!!');
870 * start-element handler
872 * @param string $parser XML parser object
873 * @param string $name element name
874 * @param string $attrs associative array of attributes
877 function schemaStartElement($parser, $name, $attrs) {
879 // position in the total number of elements, starting from 0
880 $pos = $this->position++
;
881 $depth = $this->depth++
;
882 // set self as current value for this depth
883 $this->depth_array
[$depth] = $pos;
884 $this->message
[$pos] = array('cdata' => '');
886 $this->defaultNamespace
[$pos] = $this->defaultNamespace
[$this->depth_array
[$depth - 1]];
888 $this->defaultNamespace
[$pos] = false;
891 // get element prefix
892 if($prefix = $this->getPrefix($name)){
893 // get unqualified name
894 $name = $this->getLocalPart($name);
899 // loop thru attributes, expanding, and registering namespace declarations
900 if(count($attrs) > 0){
901 foreach($attrs as $k => $v){
902 // if ns declarations, add to class level array of valid namespaces
903 if(ereg("^xmlns",$k)){
904 //$this->xdebug("$k: $v");
905 //$this->xdebug('ns_prefix: '.$this->getPrefix($k));
906 if($ns_prefix = substr(strrchr($k,':'),1)){
907 //$this->xdebug("Add namespace[$ns_prefix] = $v");
908 $this->namespaces
[$ns_prefix] = $v;
910 $this->defaultNamespace
[$pos] = $v;
911 if (! $this->getPrefixFromNamespace($v)) {
912 $this->namespaces
['ns'.(count($this->namespaces
)+
1)] = $v;
915 if($v == 'http://www.w3.org/2001/XMLSchema' ||
$v == 'http://www.w3.org/1999/XMLSchema'){
916 $this->XMLSchemaVersion
= $v;
917 $this->namespaces
['xsi'] = $v.'-instance';
921 foreach($attrs as $k => $v){
922 // expand each attribute
923 $k = strpos($k,':') ?
$this->expandQname($k) : $k;
924 $v = strpos($v,':') ?
$this->expandQname($v) : $v;
931 // find status, register data
936 //$this->xdebug("compositor $name for currentComplexType: $this->currentComplexType and currentElement: $this->currentElement");
937 $this->complexTypes
[$this->currentComplexType
]['compositor'] = $name;
938 if($name == 'all' ||
$name == 'sequence'){
939 $this->complexTypes
[$this->currentComplexType
]['phpType'] = 'struct';
943 //$this->xdebug("parsing attribute $attrs[name] $attrs[ref] of value: ".$attrs['http://schemas.xmlsoap.org/wsdl/:arrayType']);
944 $this->xdebug("parsing attribute " . $this->varDump($attrs));
945 if (isset($attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'])) {
946 $v = $attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'];
947 if (!strpos($v, ':')) {
948 // no namespace in arrayType attribute value...
949 if ($this->defaultNamespace
[$pos]) {
950 // ...so use the default
951 $attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'] = $this->defaultNamespace
[$pos] . ':' . $attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'];
955 if(isset($attrs['name'])){
956 $this->attributes
[$attrs['name']] = $attrs;
957 $aname = $attrs['name'];
958 } elseif(isset($attrs['ref']) && $attrs['ref'] == 'http://schemas.xmlsoap.org/soap/encoding/:arrayType'){
959 if (isset($attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'])) {
960 $aname = $attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'];
964 } elseif(isset($attrs['ref'])){
965 $aname = $attrs['ref'];
966 $this->attributes
[$attrs['ref']] = $attrs;
969 if(isset($this->currentComplexType
)){
970 $this->complexTypes
[$this->currentComplexType
]['attrs'][$aname] = $attrs;
971 } elseif(isset($this->currentElement
)){
972 $this->elements
[$this->currentElement
]['attrs'][$aname] = $attrs;
974 // arrayType attribute
975 if(isset($attrs['http://schemas.xmlsoap.org/wsdl/:arrayType']) ||
$this->getLocalPart($aname) == 'arrayType'){
976 $this->complexTypes
[$this->currentComplexType
]['phpType'] = 'array';
977 $prefix = $this->getPrefix($aname);
978 if(isset($attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'])){
979 $v = $attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'];
983 if(strpos($v,'[,]')){
984 $this->complexTypes
[$this->currentComplexType
]['multidimensional'] = true;
986 $v = substr($v,0,strpos($v,'[')); // clip the []
987 if(!strpos($v,':') && isset($this->typemap
[$this->XMLSchemaVersion
][$v])){
988 $v = $this->XMLSchemaVersion
.':'.$v;
990 $this->complexTypes
[$this->currentComplexType
]['arrayType'] = $v;
994 if(isset($attrs['name'])){
995 $this->xdebug('processing named complexType '.$attrs['name']);
996 $this->currentElement
= false;
997 $this->currentComplexType
= $attrs['name'];
998 $this->complexTypes
[$this->currentComplexType
] = $attrs;
999 $this->complexTypes
[$this->currentComplexType
]['typeClass'] = 'complexType';
1000 if(isset($attrs['base']) && ereg(':Array$',$attrs['base'])){
1001 $this->complexTypes
[$this->currentComplexType
]['phpType'] = 'array';
1003 $this->complexTypes
[$this->currentComplexType
]['phpType'] = 'struct';
1006 $this->xdebug('processing unnamed complexType for element '.$this->currentElement
);
1007 $this->currentComplexType
= $this->currentElement
. '_ContainedType';
1008 $this->currentElement
= false;
1009 $this->complexTypes
[$this->currentComplexType
] = $attrs;
1010 $this->complexTypes
[$this->currentComplexType
]['typeClass'] = 'complexType';
1011 if(isset($attrs['base']) && ereg(':Array$',$attrs['base'])){
1012 $this->complexTypes
[$this->currentComplexType
]['phpType'] = 'array';
1014 $this->complexTypes
[$this->currentComplexType
]['phpType'] = 'struct';
1019 // elements defined as part of a complex type should
1020 // not really be added to $this->elements, but for some
1022 if(isset($attrs['type'])){
1023 $this->xdebug("processing typed element ".$attrs['name']." of type ".$attrs['type']);
1024 $this->currentElement
= $attrs['name'];
1025 $this->elements
[ $attrs['name'] ] = $attrs;
1026 $this->elements
[ $attrs['name'] ]['typeClass'] = 'element';
1027 if (!isset($this->elements
[ $attrs['name'] ]['form'])) {
1028 $this->elements
[ $attrs['name'] ]['form'] = $this->schemaInfo
['elementFormDefault'];
1030 $ename = $attrs['name'];
1031 } elseif(isset($attrs['ref'])){
1032 $ename = $attrs['ref'];
1034 $this->xdebug("processing untyped element ".$attrs['name']);
1035 $this->currentElement
= $attrs['name'];
1036 $this->elements
[ $attrs['name'] ] = $attrs;
1037 $this->elements
[ $attrs['name'] ]['typeClass'] = 'element';
1038 $this->elements
[ $attrs['name'] ]['type'] = $this->schemaTargetNamespace
. ':' . $attrs['name'] . '_ContainedType';
1039 if (!isset($this->elements
[ $attrs['name'] ]['form'])) {
1040 $this->elements
[ $attrs['name'] ]['form'] = $this->schemaInfo
['elementFormDefault'];
1043 if(isset($ename) && $this->currentComplexType
){
1044 $this->complexTypes
[$this->currentComplexType
]['elements'][$ename] = $attrs;
1047 // we ignore enumeration values
1048 //case 'enumeration':
1051 if (isset($attrs['schemaLocation'])) {
1052 //$this->xdebug('import namespace ' . $attrs['namespace'] . ' from ' . $attrs['schemaLocation']);
1053 $this->imports
[$attrs['namespace']][] = array('location' => $attrs['schemaLocation'], 'loaded' => false);
1055 //$this->xdebug('import namespace ' . $attrs['namespace']);
1056 $this->imports
[$attrs['namespace']][] = array('location' => '', 'loaded' => true);
1057 if (! $this->getPrefixFromNamespace($attrs['namespace'])) {
1058 $this->namespaces
['ns'.(count($this->namespaces
)+
1)] = $attrs['namespace'];
1063 //$this->xdebug("in restriction for currentComplexType: $this->currentComplexType and currentElement: $this->currentElement");
1064 if($this->currentElement
){
1065 $this->elements
[$this->currentElement
]['type'] = $attrs['base'];
1066 } elseif($this->currentSimpleType
){
1067 $this->simpleTypes
[$this->currentSimpleType
]['type'] = $attrs['base'];
1068 } elseif($this->currentComplexType
){
1069 $this->complexTypes
[$this->currentComplexType
]['restrictionBase'] = $attrs['base'];
1070 if(strstr($attrs['base'],':') == ':Array'){
1071 $this->complexTypes
[$this->currentComplexType
]['phpType'] = 'array';
1076 $this->schemaInfo
= $attrs;
1077 $this->schemaInfo
['schemaVersion'] = $this->getNamespaceFromPrefix($prefix);
1078 if (isset($attrs['targetNamespace'])) {
1079 $this->schemaTargetNamespace
= $attrs['targetNamespace'];
1081 if (!isset($attrs['elementFormDefault'])) {
1082 $this->schemaInfo
['elementFormDefault'] = 'unqualified';
1086 if(isset($attrs['name'])){
1087 $this->xdebug("processing simpleType for name " . $attrs['name']);
1088 $this->currentSimpleType
= $attrs['name'];
1089 $this->simpleTypes
[ $attrs['name'] ] = $attrs;
1090 $this->simpleTypes
[ $attrs['name'] ]['typeClass'] = 'simpleType';
1091 $this->simpleTypes
[ $attrs['name'] ]['phpType'] = 'scalar';
1093 //echo 'not parsing: '.$name;
1098 //$this->xdebug("do not have anything to do for element $name");
1103 * end-element handler
1105 * @param string $parser XML parser object
1106 * @param string $name element name
1109 function schemaEndElement($parser, $name) {
1110 // bring depth down a notch
1112 // position of current element is equal to the last value left in depth_array for my depth
1113 if(isset($this->depth_array
[$this->depth
])){
1114 $pos = $this->depth_array
[$this->depth
];
1117 if($name == 'complexType'){
1118 $this->currentComplexType
= false;
1119 $this->currentElement
= false;
1121 if($name == 'element'){
1122 $this->currentElement
= false;
1124 if($name == 'simpleType'){
1125 $this->currentSimpleType
= false;
1130 * element content handler
1132 * @param string $parser XML parser object
1133 * @param string $data element content
1136 function schemaCharacterData($parser, $data){
1137 $pos = $this->depth_array
[$this->depth
- 1];
1138 $this->message
[$pos]['cdata'] .= $data;
1142 * serialize the schema
1146 function serializeSchema(){
1148 $schemaPrefix = $this->getPrefixFromNamespace($this->XMLSchemaVersion
);
1151 if (sizeof($this->imports
) > 0) {
1152 foreach($this->imports
as $ns => $list) {
1153 foreach ($list as $ii) {
1154 if ($ii['location'] != '') {
1155 $xml .= " <$schemaPrefix:import location=\"" . $ii['location'] . '" namespace="' . $ns . "\" />\n";
1157 $xml .= " <$schemaPrefix:import namespace=\"" . $ns . "\" />\n";
1163 foreach($this->complexTypes
as $typeName => $attrs){
1165 // serialize child elements
1166 if(isset($attrs['elements']) && (count($attrs['elements']) > 0)){
1167 foreach($attrs['elements'] as $element => $eParts){
1168 if(isset($eParts['ref'])){
1169 $contentStr .= " <$schemaPrefix:element ref=\"$element\"/>\n";
1171 $contentStr .= " <$schemaPrefix:element name=\"$element\" type=\"" . $this->contractQName($eParts['type']) . "\"/>\n";
1176 if(isset($attrs['attrs']) && (count($attrs['attrs']) >= 1)){
1177 foreach($attrs['attrs'] as $attr => $aParts){
1178 $contentStr .= " <$schemaPrefix:attribute ref=\"".$this->contractQName($aParts['ref']).'"';
1179 if(isset($aParts['http://schemas.xmlsoap.org/wsdl/:arrayType'])){
1180 $this->usedNamespaces
['wsdl'] = $this->namespaces
['wsdl'];
1181 $contentStr .= ' wsdl:arrayType="'.$this->contractQName($aParts['http://schemas.xmlsoap.org/wsdl/:arrayType']).'"';
1183 $contentStr .= "/>\n";
1187 if( isset($attrs['restrictionBase']) && $attrs['restrictionBase'] != ''){
1188 $contentStr = " <$schemaPrefix:restriction base=\"".$this->contractQName($attrs['restrictionBase'])."\">\n".$contentStr." </$schemaPrefix:restriction>\n";
1190 // compositor obviates complex/simple content
1191 if(isset($attrs['compositor']) && ($attrs['compositor'] != '')){
1192 $contentStr = " <$schemaPrefix:$attrs[compositor]>\n".$contentStr." </$schemaPrefix:$attrs[compositor]>\n";
1194 // complex or simple content
1195 elseif((isset($attrs['elements']) && count($attrs['elements']) > 0) ||
(isset($attrs['attrs']) && count($attrs['attrs']) > 0)){
1196 $contentStr = " <$schemaPrefix:complexContent>\n".$contentStr." </$schemaPrefix:complexContent>\n";
1198 // finalize complex type
1199 if($contentStr != ''){
1200 $contentStr = " <$schemaPrefix:complexType name=\"$typeName\">\n".$contentStr." </$schemaPrefix:complexType>\n";
1202 $contentStr = " <$schemaPrefix:complexType name=\"$typeName\"/>\n";
1204 $xml .= $contentStr;
1207 if(isset($this->simpleTypes
) && count($this->simpleTypes
) > 0){
1208 foreach($this->simpleTypes
as $typeName => $attr){
1209 $xml .= " <$schemaPrefix:simpleType name=\"$typeName\">\n <restriction base=\"".$this->contractQName($eParts['type'])."\"/>\n </$schemaPrefix:simpleType>";
1213 if(isset($this->elements
) && count($this->elements
) > 0){
1214 foreach($this->elements
as $element => $eParts){
1215 $xml .= " <$schemaPrefix:element name=\"$element\" type=\"".$this->contractQName($eParts['type'])."\"/>\n";
1219 if(isset($this->attributes
) && count($this->attributes
) > 0){
1220 foreach($this->attributes
as $attr => $aParts){
1221 $xml .= " <$schemaPrefix:attribute name=\"$attr\" type=\"".$this->contractQName($aParts['type'])."\"\n/>";
1225 $el = "<$schemaPrefix:schema targetNamespace=\"$this->schemaTargetNamespace\"\n";
1226 foreach (array_diff($this->usedNamespaces
, $this->enclosingNamespaces
) as $nsp => $ns) {
1227 $el .= " xmlns:$nsp=\"$ns\"\n";
1229 $xml = $el . ">\n".$xml."</$schemaPrefix:schema>\n";
1234 * adds debug data to the clas level debug string
1236 * @param string $string debug data
1239 function xdebug($string){
1240 $this->debug('<' . $this->schemaTargetNamespace
. '> '.$string);
1244 * get the PHP type of a user defined type in the schema
1245 * PHP type is kind of a misnomer since it actually returns 'struct' for assoc. arrays
1246 * returns false if no type exists, or not w/ the given namespace
1247 * else returns a string that is either a native php type, or 'struct'
1249 * @param string $type, name of defined type
1250 * @param string $ns, namespace of type
1254 function getPHPType($type,$ns){
1255 if(isset($this->typemap
[$ns][$type])){
1256 //print "found type '$type' and ns $ns in typemap<br>";
1257 return $this->typemap
[$ns][$type];
1258 } elseif(isset($this->complexTypes
[$type])){
1259 //print "getting type '$type' and ns $ns from complexTypes array<br>";
1260 return $this->complexTypes
[$type]['phpType'];
1266 * returns an array of information about a given type
1267 * returns false if no type exists by the given name
1270 * 'elements' => array(), // refs to elements array
1271 * 'restrictionBase' => '',
1273 * 'order' => '(sequence|all)',
1274 * 'attrs' => array() // refs to attributes array
1281 function getTypeDef($type){
1282 //$this->debug("in getTypeDef for type $type");
1283 if(isset($this->complexTypes
[$type])){
1284 $this->xdebug("in getTypeDef, found complexType $type");
1285 return $this->complexTypes
[$type];
1286 } elseif(isset($this->simpleTypes
[$type])){
1287 $this->xdebug("in getTypeDef, found simpleType $type");
1288 if (!isset($this->simpleTypes
[$type]['phpType'])) {
1289 // get info for type to tack onto the simple type
1290 // TODO: can this ever really apply (i.e. what is a simpleType really?)
1291 $uqType = substr($this->simpleTypes
[$type]['type'], strrpos($this->simpleTypes
[$type]['type'], ':') +
1);
1292 $ns = substr($this->simpleTypes
[$type]['type'], 0, strrpos($this->simpleTypes
[$type]['type'], ':'));
1293 $etype = $this->getTypeDef($uqType);
1295 if (isset($etype['phpType'])) {
1296 $this->simpleTypes
[$type]['phpType'] = $etype['phpType'];
1298 if (isset($etype['elements'])) {
1299 $this->simpleTypes
[$type]['elements'] = $etype['elements'];
1303 return $this->simpleTypes
[$type];
1304 } elseif(isset($this->elements
[$type])){
1305 $this->xdebug("in getTypeDef, found element $type");
1306 if (!isset($this->elements
[$type]['phpType'])) {
1307 // get info for type to tack onto the element
1308 $uqType = substr($this->elements
[$type]['type'], strrpos($this->elements
[$type]['type'], ':') +
1);
1309 $ns = substr($this->elements
[$type]['type'], 0, strrpos($this->elements
[$type]['type'], ':'));
1310 $etype = $this->getTypeDef($uqType);
1312 if (isset($etype['phpType'])) {
1313 $this->elements
[$type]['phpType'] = $etype['phpType'];
1315 if (isset($etype['elements'])) {
1316 $this->elements
[$type]['elements'] = $etype['elements'];
1318 } elseif ($ns == 'http://www.w3.org/2001/XMLSchema') {
1319 $this->elements
[$type]['phpType'] = 'scalar';
1322 return $this->elements
[$type];
1323 } elseif(isset($this->attributes
[$type])){
1324 $this->xdebug("in getTypeDef, found attribute $type");
1325 return $this->attributes
[$type];
1327 $this->xdebug("in getTypeDef, did not find $type");
1332 * returns a sample serialization of a given type, or false if no type by the given name
1334 * @param string $type, name of type
1338 function serializeTypeDef($type){
1339 //print "in sTD() for type $type<br>";
1340 if($typeDef = $this->getTypeDef($type)){
1342 if(is_array($typeDef['attrs'])){
1343 foreach($attrs as $attName => $data){
1344 $str .= " $attName=\"{type = ".$data['type']."}\"";
1347 $str .= " xmlns=\"".$this->schema
['targetNamespace']."\"";
1348 if(count($typeDef['elements']) > 0){
1350 foreach($typeDef['elements'] as $element => $eData){
1351 $str .= $this->serializeTypeDef($element);
1354 } elseif($typeDef['typeClass'] == 'element') {
1355 $str .= "></$type>";
1365 * returns HTML form elements that allow a user
1366 * to enter values for creating an instance of the given type.
1368 * @param string $name, name for type instance
1369 * @param string $type, name of type
1373 function typeToForm($name,$type){
1375 if($typeDef = $this->getTypeDef($type)){
1377 if($typeDef['phpType'] == 'struct'){
1378 $buffer .= '<table>';
1379 foreach($typeDef['elements'] as $child => $childDef){
1381 <tr><td align='right'>$childDef[name] (type: ".$this->getLocalPart($childDef['type'])."):</td>
1382 <td><input type='text' name='parameters[".$name."][$childDef[name]]'></td></tr>";
1384 $buffer .= '</table>';
1386 } elseif($typeDef['phpType'] == 'array'){
1387 $buffer .= '<table>';
1388 for($i=0;$i < 3; $i++
){
1390 <tr><td align='right'>array item (type: $typeDef[arrayType]):</td>
1391 <td><input type='text' name='parameters[".$name."][]'></td></tr>";
1393 $buffer .= '</table>';
1396 $buffer .= "<input type='text' name='parameters[$name]'>";
1399 $buffer .= "<input type='text' name='parameters[$name]'>";
1405 * adds a complex type to the schema
1415 * array('ref'=>'SOAP-ENC:arrayType','wsdl:arrayType'=>'string[]'),
1419 * example: PHP associative array ( SOAP Struct )
1426 * array('myVar'=> array('name'=>'myVar','type'=>'string')
1430 * @param typeClass (complexType|simpleType|attribute)
1431 * @param phpType: currently supported are array and struct (php assoc array)
1432 * @param compositor (all|sequence|choice)
1433 * @param restrictionBase namespace:name (http://schemas.xmlsoap.org/soap/encoding/:Array)
1434 * @param elements = array ( name = array(name=>'',type=>'') )
1435 * @param attrs = array(
1437 * 'ref' => "http://schemas.xmlsoap.org/soap/encoding/:arrayType",
1438 * "http://schemas.xmlsoap.org/wsdl/:arrayType" => "string[]"
1441 * @param arrayType: namespace:name (http://www.w3.org/2001/XMLSchema:string)
1444 function addComplexType($name,$typeClass='complexType',$phpType='array',$compositor='',$restrictionBase='',$elements=array(),$attrs=array(),$arrayType=''){
1445 $this->complexTypes
[$name] = array(
1447 'typeClass' => $typeClass,
1448 'phpType' => $phpType,
1449 'compositor'=> $compositor,
1450 'restrictionBase' => $restrictionBase,
1451 'elements' => $elements,
1453 'arrayType' => $arrayType
1456 $this->xdebug("addComplexType $name: " . $this->varDump($this->complexTypes
[$name]));
1460 * adds a simple type to the schema
1463 * @param restrictionBase namespace:name (http://schemas.xmlsoap.org/soap/encoding/:Array)
1464 * @param typeClass (simpleType)
1465 * @param phpType: (scalar)
1469 function addSimpleType($name, $restrictionBase='', $typeClass='simpleType', $phpType='scalar') {
1470 $this->simpleTypes
[$name] = array(
1472 'typeClass' => $typeClass,
1473 'phpType' => $phpType,
1474 'type' => $restrictionBase
1477 $this->xdebug("addSimpleType $name: " . $this->varDump($this->simpleTypes
[$name]));
1488 * for creating serializable abstractions of native PHP types
1489 * NOTE: this is only really used when WSDL is not available.
1491 * @author Dietrich Ayala <dietrich@ganx4.com>
1495 class soapval
extends nusoap_base
{
1499 * @param string $name optional name
1500 * @param string $type optional type name
1501 * @param mixed $value optional value
1502 * @param string $namespace optional namespace of value
1503 * @param string $type_namespace optional namespace of type
1504 * @param array $attributes associative array of attributes to add to element serialization
1507 function soapval($name='soapval',$type=false,$value=-1,$element_ns=false,$type_ns=false,$attributes=false) {
1508 $this->name
= $name;
1509 $this->value
= $value;
1510 $this->type
= $type;
1511 $this->element_ns
= $element_ns;
1512 $this->type_ns
= $type_ns;
1513 $this->attributes
= $attributes;
1517 * return serialized value
1519 * @return string XML data
1522 function serialize($use='encoded') {
1523 return $this->serialize_val($this->value
,$this->name
,$this->type
,$this->element_ns
,$this->type_ns
,$this->attributes
,$use);
1527 * decodes a soapval object into a PHP native type
1529 * @param object $soapval optional SOAPx4 soapval object, else uses self
1534 return $this->value
;
1545 * transport class for sending/receiving data via HTTP and HTTPS
1546 * NOTE: PHP must be compiled with the CURL extension for HTTPS support
1548 * @author Dietrich Ayala <dietrich@ganx4.com>
1552 class soap_transport_http
extends nusoap_base
{
1560 var $request_method = 'POST';
1561 var $protocol_version = '1.0';
1563 var $outgoing_headers = array();
1564 var $incoming_headers = array();
1565 var $outgoing_payload = '';
1566 var $incoming_payload = '';
1567 var $useSOAPAction = true;
1568 var $persistentConnection = false;
1569 var $ch = false; // cURL handle
1576 function soap_transport_http($url){
1579 $u = parse_url($url);
1580 foreach($u as $k => $v){
1581 $this->debug("$k = $v");
1585 // add any GET params to path
1586 if(isset($u['query']) && $u['query'] != ''){
1587 $this->path
.= '?' . $u['query'];
1591 if(!isset($u['port'])){
1592 if($u['scheme'] == 'https'){
1599 $this->uri
= $this->path
;
1602 ereg('\$Revisio' . 'n: ([^ ]+)', $this->revision
, $rev);
1603 $this->outgoing_headers
['User-Agent'] = $this->title
.'/'.$this->version
.' ('.$rev[1].')';
1604 if (!isset($u['port'])) {
1605 $this->outgoing_headers
['Host'] = $this->host
;
1607 $this->outgoing_headers
['Host'] = $this->host
.':'.$this->port
;
1610 if (isset($u['user']) && $u['user'] != '') {
1611 $this->setCredentials($u['user'], isset($u['pass']) ?
$u['pass'] : '');
1615 function connect($connection_timeout=0,$response_timeout=30){
1616 // For PHP 4.3 with OpenSSL, change https scheme to ssl, then treat like
1617 // "regular" socket.
1618 // TODO: disabled for now because OpenSSL must be *compiled* in (not just
1619 // loaded), and until PHP5 stream_get_wrappers is not available.
1620 // if ($this->scheme == 'https') {
1621 // if (version_compare(phpversion(), '4.3.0') >= 0) {
1622 // if (extension_loaded('openssl')) {
1623 // $this->scheme = 'ssl';
1624 // $this->debug('Using SSL over OpenSSL');
1628 $this->debug("connect connection_timeout $connection_timeout, response_timeout $response_timeout, scheme $this->scheme, host $this->host, port $this->port");
1629 if ($this->scheme
== 'http' ||
$this->scheme
== 'ssl') {
1630 // use persistent connection
1631 if($this->persistentConnection
&& isset($this->fp
) && is_resource($this->fp
)){
1632 if (!feof($this->fp
)) {
1633 $this->debug('Re-use persistent connection');
1637 $this->debug('Closed persistent connection at EOF');
1640 // munge host if using OpenSSL
1641 if ($this->scheme
== 'ssl') {
1642 $host = 'ssl://' . $this->host
;
1644 $host = $this->host
;
1646 $this->debug('calling fsockopen with host ' . $host);
1649 if($connection_timeout > 0){
1650 $this->fp
= @fsockopen
( $host, $this->port
, $this->errno
, $this->error_str
, $connection_timeout);
1652 $this->fp
= @fsockopen
( $host, $this->port
, $this->errno
, $this->error_str
);
1657 $msg = 'Couldn\'t open socket connection to server ' . $this->url
;
1659 $msg .= ', Error ('.$this->errno
.'): '.$this->error_str
;
1661 $msg .= ' prior to connect(). This is often a problem looking up the host name.';
1664 $this->setError($msg);
1668 // set response timeout
1669 socket_set_timeout( $this->fp
, $response_timeout);
1671 $this->debug('socket connected');
1673 } else if ($this->scheme
== 'https') {
1674 if (!extension_loaded('curl')) {
1675 $this->setError('CURL Extension, or OpenSSL extension w/ PHP version >= 4.3 is required for HTTPS');
1678 $this->debug('connect using https');
1680 $this->ch
= curl_init();
1682 $hostURL = ($this->port
!= '') ?
"https://$this->host:$this->port" : "https://$this->host";
1684 $hostURL .= $this->path
;
1685 curl_setopt($this->ch
, CURLOPT_URL
, $hostURL);
1686 // ask for headers in the response output
1687 curl_setopt($this->ch
, CURLOPT_HEADER
, 1);
1688 // ask for the response output as the return value
1689 curl_setopt($this->ch
, CURLOPT_RETURNTRANSFER
, 1);
1691 // We manage this ourselves through headers and encoding
1692 // if(function_exists('gzuncompress')){
1693 // curl_setopt($this->ch, CURLOPT_ENCODING, 'deflate');
1695 // persistent connection
1696 if ($this->persistentConnection
) {
1697 // The way we send data, we cannot use persistent connections, since
1698 // there will be some "junk" at the end of our request.
1699 //curl_setopt($this->ch, CURL_HTTP_VERSION_1_1, true);
1700 $this->persistentConnection
= false;
1701 $this->outgoing_headers
['Connection'] = 'close';
1703 // set timeout (NOTE: cURL does not have separate connection and response timeouts)
1704 if ($connection_timeout != 0) {
1705 curl_setopt($this->ch
, CURLOPT_TIMEOUT
, $connection_timeout);
1708 // recent versions of cURL turn on peer/host checking by default,
1709 // while PHP binaries are not compiled with a default location for the
1710 // CA cert bundle, so disable peer/host checking.
1711 //curl_setopt($this->ch, CURLOPT_CAINFO, 'f:\php-4.3.2-win32\extensions\curl-ca-bundle.crt');
1712 curl_setopt($this->ch
, CURLOPT_SSL_VERIFYPEER
, 0);
1713 curl_setopt($this->ch
, CURLOPT_SSL_VERIFYHOST
, 0);
1716 TODO: support client certificates (thanks Tobias Boes)
1717 curl_setopt($this->ch, CURLOPT_CAINFO, '$pathToPemFiles/rootca.pem');
1718 curl_setopt($this->ch, CURLOPT_SSL_VERIFYPEER, 1);
1719 curl_setopt($this->ch, CURLOPT_SSL_VERIFYHOST, 1);
1720 curl_setopt($this->ch, CURLOPT_SSLCERT, '$pathToPemFiles/mycert.pem');
1721 curl_setopt($this->ch, CURLOPT_SSLKEY, '$pathToPemFiles/mykey.pem');
1723 $this->debug('cURL connection set up');
1726 $this->setError('Unknown scheme ' . $this->scheme
);
1727 $this->debug('Unknown scheme ' . $this->scheme
);
1733 * send the SOAP message via HTTP
1735 * @param string $data message data
1736 * @param integer $timeout set connection timeout in seconds
1737 * @param integer $response_timeout set response timeout in seconds
1738 * @return string data
1741 function send($data, $timeout=0, $response_timeout=30) {
1743 $this->debug('entered send() with data of length: '.strlen($data));
1745 $this->tryagain
= true;
1747 while ($this->tryagain
) {
1748 $this->tryagain
= false;
1751 if (!$this->connect($timeout, $response_timeout)){
1756 if (!$this->sendRequest($data)){
1761 $respdata = $this->getResponse();
1763 $this->setError('Too many tries to get an OK response');
1766 $this->debug('end of send()');
1772 * send the SOAP message via HTTPS 1.0 using CURL
1774 * @param string $msg message data
1775 * @param integer $timeout set connection timeout in seconds
1776 * @param integer $response_timeout set response timeout in seconds
1777 * @return string data
1780 function sendHTTPS($data, $timeout=0, $response_timeout=30) {
1781 return $this->send($data, $timeout, $response_timeout);
1785 * if authenticating, set user credentials here
1787 * @param string $username
1788 * @param string $password
1789 * @param string $authtype
1790 * @param array $digestRequest
1793 function setCredentials($username, $password, $authtype = 'basic', $digestRequest = array()) {
1796 $this->debug("Set credentials for authtype $authtype");
1798 if ($authtype == 'basic') {
1799 $this->outgoing_headers
['Authorization'] = 'Basic '.base64_encode($username.':'.$password);
1800 } elseif ($authtype == 'digest') {
1801 if (isset($digestRequest['nonce'])) {
1802 $digestRequest['nc'] = isset($digestRequest['nc']) ?
$digestRequest['nc']++
: 1;
1804 // calculate the Digest hashes (calculate code based on digest implementation found at: http://www.rassoc.com/gregr/weblog/stories/2002/07/09/webServicesSecurityHttpDigestAuthenticationWithoutActiveDirectory.html)
1806 // A1 = unq(username-value) ":" unq(realm-value) ":" passwd
1807 $A1 = $username. ':' . $digestRequest['realm'] . ':' . $password;
1812 // A2 = Method ":" digest-uri-value
1813 $A2 = 'POST:' . $this->uri
;
1818 // KD(secret, data) = H(concat(secret, ":", data))
1820 // request-digest = <"> < KD ( H(A1), unq(nonce-value)
1822 // ":" unq(cnonce-value)
1823 // ":" unq(qop-value)
1826 // if qop is missing,
1827 // request-digest = <"> < KD ( H(A1), unq(nonce-value) ":" H(A2) ) > <">
1829 $unhashedDigest = '';
1830 $nonce = isset($digestRequest['nonce']) ?
$digestRequest['nonce'] : '';
1832 if ($digestRequest['qop'] != '') {
1833 $unhashedDigest = $HA1 . ':' . $nonce . ':' . sprintf("%08d", $digestRequest['nc']) . ':' . $cnonce . ':' . $digestRequest['qop'] . ':' . $HA2;
1835 $unhashedDigest = $HA1 . ':' . $nonce . ':' . $HA2;
1838 $hashedDigest = md5($unhashedDigest);
1840 $this->outgoing_headers
['Authorization'] = 'Digest username="' . $username . '", realm="' . $digestRequest['realm'] . '", nonce="' . $nonce . '", uri="' . $this->uri
. '", cnonce="' . $cnonce . '", nc=' . sprintf("%08x", $digestRequest['nc']) . ', qop="' . $digestRequest['qop'] . '", response="' . $hashedDigest . '"';
1843 $this->username
= $username;
1844 $this->password
= $password;
1845 $this->authtype
= $authtype;
1846 $this->digestRequest
= $digestRequest;
1848 if (isset($this->outgoing_headers
['Authorization'])) {
1849 $this->debug('Authorization header set: ' . substr($this->outgoing_headers
['Authorization'], 0, 12) . '...');
1851 $this->debug('Authorization header not set');
1856 * set the soapaction value
1858 * @param string $soapaction
1861 function setSOAPAction($soapaction) {
1862 $this->outgoing_headers
['SOAPAction'] = '"' . $soapaction . '"';
1868 * @param string $enc encoding style. supported values: gzip, deflate, or both
1871 function setEncoding($enc='gzip, deflate'){
1872 $this->protocol_version
= '1.1';
1873 $this->outgoing_headers
['Accept-Encoding'] = $enc;
1874 $this->outgoing_headers
['Connection'] = 'close';
1875 $this->persistentConnection
= false;
1876 set_magic_quotes_runtime(0);
1878 $this->encoding
= $enc;
1882 * set proxy info here
1884 * @param string $proxyhost
1885 * @param string $proxyport
1886 * @param string $proxyusername
1887 * @param string $proxypassword
1890 function setProxy($proxyhost, $proxyport, $proxyusername = '', $proxypassword = '') {
1891 $this->uri
= $this->url
;
1892 $this->host
= $proxyhost;
1893 $this->port
= $proxyport;
1894 if ($proxyusername != '' && $proxypassword != '') {
1895 $this->outgoing_headers
['Proxy-Authorization'] = ' Basic '.base64_encode($proxyusername.':'.$proxypassword);
1900 * decode a string that is encoded w/ "chunked' transfer encoding
1901 * as defined in RFC2068 19.4.6
1903 * @param string $buffer
1908 function decodeChunked($buffer, $lb){
1913 // read chunk-size, chunk-extension (if any) and CRLF
1914 // get the position of the linebreak
1915 $chunkend = strpos($buffer, $lb);
1916 if ($chunkend == FALSE) {
1917 $this->debug('no linebreak found in decodeChunked');
1920 $temp = substr($buffer,0,$chunkend);
1921 $chunk_size = hexdec( trim($temp) );
1922 $chunkstart = $chunkend +
strlen($lb);
1923 // while (chunk-size > 0) {
1924 while ($chunk_size > 0) {
1925 $this->debug("chunkstart: $chunkstart chunk_size: $chunk_size");
1926 $chunkend = strpos( $buffer, $lb, $chunkstart +
$chunk_size);
1928 // Just in case we got a broken connection
1929 if ($chunkend == FALSE) {
1930 $chunk = substr($buffer,$chunkstart);
1931 // append chunk-data to entity-body
1933 $length +
= strlen($chunk);
1937 // read chunk-data and CRLF
1938 $chunk = substr($buffer,$chunkstart,$chunkend-$chunkstart);
1939 // append chunk-data to entity-body
1941 // length := length + chunk-size
1942 $length +
= strlen($chunk);
1943 // read chunk-size and CRLF
1944 $chunkstart = $chunkend +
strlen($lb);
1946 $chunkend = strpos($buffer, $lb, $chunkstart) +
strlen($lb);
1947 if ($chunkend == FALSE) {
1948 break; //Just in case we got a broken connection
1950 $temp = substr($buffer,$chunkstart,$chunkend-$chunkstart);
1951 $chunk_size = hexdec( trim($temp) );
1952 $chunkstart = $chunkend;
1958 * Writes payload, including HTTP headers, to $this->outgoing_payload.
1960 function buildPayload($data) {
1961 // add content-length header
1962 $this->outgoing_headers
['Content-Length'] = strlen($data);
1964 // start building outgoing payload:
1965 $this->outgoing_payload
= "$this->request_method $this->uri HTTP/$this->protocol_version\r\n";
1967 // loop thru headers, serializing
1968 foreach($this->outgoing_headers
as $k => $v){
1969 $this->outgoing_payload
.= $k.': '.$v."\r\n";
1972 // header/body separator
1973 $this->outgoing_payload
.= "\r\n";
1976 $this->outgoing_payload
.= $data;
1979 function sendRequest($data){
1981 $this->buildPayload($data);
1983 if ($this->scheme
== 'http' ||
$this->scheme
== 'ssl') {
1985 if(!fputs($this->fp
, $this->outgoing_payload
, strlen($this->outgoing_payload
))) {
1986 $this->setError('couldn\'t write message data to socket');
1987 $this->debug('couldn\'t write message data to socket');
1990 $this->debug('wrote data to socket, length = ' . strlen($this->outgoing_payload
));
1992 } else if ($this->scheme
== 'https') {
1994 // TODO: cURL does say this should only be the verb, and in fact it
1995 // turns out that the URI and HTTP version are appended to this, which
1996 // some servers refuse to work with
1997 //curl_setopt($this->ch, CURLOPT_CUSTOMREQUEST, $this->outgoing_payload);
1998 foreach($this->outgoing_headers
as $k => $v){
1999 $curl_headers[] = "$k: $v";
2001 curl_setopt($this->ch
, CURLOPT_HTTPHEADER
, $curl_headers);
2002 if ($this->request_method
== "POST") {
2003 curl_setopt($this->ch
, CURLOPT_POST
, 1);
2004 curl_setopt($this->ch
, CURLOPT_POSTFIELDS
, $data);
2007 $this->debug('set cURL payload');
2012 function getResponse(){
2013 $this->incoming_payload
= '';
2015 if ($this->scheme
== 'http' ||
$this->scheme
== 'ssl') {
2016 // loop until headers have been retrieved
2018 while (!isset($lb)){
2020 // We might EOF during header read.
2021 if(feof($this->fp
)) {
2022 $this->incoming_payload
= $data;
2023 $this->debug('found no headers before EOF after length ' . strlen($data));
2024 $this->debug("received before EOF:\n" . $data);
2025 $this->setError('server failed to send headers');
2029 $tmp = fgets($this->fp
, 256);
2030 $tmplen = strlen($tmp);
2031 $this->debug("read line of $tmplen bytes: " . trim($tmp));
2034 $this->incoming_payload
= $data;
2035 $this->debug('socket read of headers timed out after length ' . strlen($data));
2036 $this->debug("read before timeout:\n" . $data);
2037 $this->setError('socket read of headers timed out');
2042 $pos = strpos($data,"\r\n\r\n");
2046 $pos = strpos($data,"\n\n");
2051 // remove 100 header
2052 if(isset($lb) && ereg('^HTTP/1.1 100',$data)){
2057 // store header data
2058 $this->incoming_payload
.= $data;
2059 $this->debug('found end of headers after length ' . strlen($data));
2061 $header_data = trim(substr($data,0,$pos));
2062 $header_array = explode($lb,$header_data);
2063 $this->incoming_headers
= array();
2064 foreach($header_array as $header_line){
2065 $arr = explode(':',$header_line, 2);
2066 if(count($arr) > 1){
2067 $header_name = strtolower(trim($arr[0]));
2068 $this->incoming_headers
[$header_name] = trim($arr[1]);
2069 } else if (isset($header_name)) {
2070 $this->incoming_headers
[$header_name] .= $lb . ' ' . $header_line;
2074 // loop until msg has been received
2075 if (isset($this->incoming_headers
['content-length'])) {
2076 $content_length = $this->incoming_headers
['content-length'];
2078 $this->debug("want to read content of length $content_length");
2080 $content_length = 2147483647;
2081 if (isset($this->incoming_headers
['transfer-encoding']) && strtolower($this->incoming_headers
['transfer-encoding']) == 'chunked') {
2083 $this->debug("want to read chunked content");
2086 $this->debug("want to read content to EOF");
2092 $tmp = fgets($this->fp
, 256);
2093 $tmplen = strlen($tmp);
2094 $this->debug("read chunk line of $tmplen bytes");
2096 $this->incoming_payload
= $data;
2097 $this->debug('socket read of chunk length timed out after length ' . strlen($data));
2098 $this->debug("read before timeout:\n" . $data);
2099 $this->setError('socket read of chunk length timed out');
2102 $content_length = hexdec(trim($tmp));
2103 $this->debug("chunk length $content_length");
2106 while (($strlen < $content_length) && (!feof($this->fp
))) {
2107 $readlen = min(8192, $content_length - $strlen);
2108 $tmp = fread($this->fp
, $readlen);
2109 $tmplen = strlen($tmp);
2110 $this->debug("read buffer of $tmplen bytes");
2111 if (($tmplen == 0) && (!feof($this->fp
))) {
2112 $this->incoming_payload
= $data;
2113 $this->debug('socket read of body timed out after length ' . strlen($data));
2114 $this->debug("read before timeout:\n" . $data);
2115 $this->setError('socket read of body timed out');
2121 if ($chunked && ($content_length > 0)) {
2122 $tmp = fgets($this->fp
, 256);
2123 $tmplen = strlen($tmp);
2124 $this->debug("read chunk terminator of $tmplen bytes");
2126 $this->incoming_payload
= $data;
2127 $this->debug('socket read of chunk terminator timed out after length ' . strlen($data));
2128 $this->debug("read before timeout:\n" . $data);
2129 $this->setError('socket read of chunk terminator timed out');
2133 } while ($chunked && ($content_length > 0) && (!feof($this->fp
)));
2134 if (feof($this->fp
)) {
2135 $this->debug('read to EOF');
2137 $this->debug('read body of length ' . strlen($data));
2138 $this->incoming_payload
.= $data;
2139 $this->debug('received a total of '.strlen($this->incoming_payload
).' bytes of data from server');
2141 // close filepointer
2143 (isset($this->incoming_headers
['connection']) && strtolower($this->incoming_headers
['connection']) == 'close') ||
2144 (! $this->persistentConnection
) ||
feof($this->fp
)){
2147 $this->debug('closed socket');
2150 // connection was closed unexpectedly
2151 if($this->incoming_payload
== ''){
2152 $this->setError('no response from server');
2156 // decode transfer-encoding
2157 // if(isset($this->incoming_headers['transfer-encoding']) && strtolower($this->incoming_headers['transfer-encoding']) == 'chunked'){
2158 // if(!$data = $this->decodeChunked($data, $lb)){
2159 // $this->setError('Decoding of chunked data failed');
2162 //print "<pre>\nde-chunked:\n---------------\n$data\n\n---------------\n</pre>";
2163 // set decoded payload
2164 // $this->incoming_payload = $header_data.$lb.$lb.$data;
2167 } else if ($this->scheme
== 'https') {
2169 $this->debug('send and receive with cURL');
2170 $this->incoming_payload
= curl_exec($this->ch
);
2171 $data = $this->incoming_payload
;
2173 $cErr = curl_error($this->ch
);
2175 $err = 'cURL ERROR: '.curl_errno($this->ch
).': '.$cErr.'<br>';
2176 foreach(curl_getinfo($this->ch
) as $k => $v){
2177 $err .= "$k: $v<br>";
2180 $this->setError($err);
2181 curl_close($this->ch
);
2185 //var_dump(curl_getinfo($this->ch));
2189 $this->debug('No cURL error, closing cURL');
2190 curl_close($this->ch
);
2192 // remove 100 header
2193 if (ereg('^HTTP/1.1 100',$data)) {
2194 if ($pos = strpos($data,"\r\n\r\n")) {
2195 $data = ltrim(substr($data,$pos));
2196 } elseif($pos = strpos($data,"\n\n") ) {
2197 $data = ltrim(substr($data,$pos));
2201 // separate content from HTTP headers
2202 if ($pos = strpos($data,"\r\n\r\n")) {
2204 } elseif( $pos = strpos($data,"\n\n")) {
2207 $this->debug('no proper separation of headers and document');
2208 $this->setError('no proper separation of headers and document');
2211 $header_data = trim(substr($data,0,$pos));
2212 $header_array = explode($lb,$header_data);
2213 $data = ltrim(substr($data,$pos));
2214 $this->debug('found proper separation of headers and document');
2215 $this->debug('cleaned data, stringlen: '.strlen($data));
2217 foreach ($header_array as $header_line) {
2218 $arr = explode(':',$header_line,2);
2219 if (count($arr) > 1) {
2220 $this->incoming_headers
[strtolower(trim($arr[0]))] = trim($arr[1]);
2225 // see if we need to resend the request with http digest authentication
2226 if (isset($this->incoming_headers
['www-authenticate']) && strstr($header_array[0], '401 Unauthorized')) {
2227 $this->debug('Got 401 Unauthorized with WWW-Authenticate: ' . $this->incoming_headers
['www-authenticate']);
2228 if (substr("Digest ", $this->incoming_headers
['www-authenticate'])) {
2229 $this->debug('Server wants digest authentication');
2230 // remove "Digest " from our elements
2231 $digestString = str_replace('Digest ', '', $this->incoming_headers
['www-authenticate']);
2233 // parse elements into array
2234 $digestElements = explode(',', $digestString);
2235 foreach ($digestElements as $val) {
2236 $tempElement = explode('=', trim($val));
2237 $digestRequest[$tempElement[0]] = str_replace("\"", '', $tempElement[1]);
2240 // should have (at least) qop, realm, nonce
2241 if (isset($digestRequest['nonce'])) {
2242 $this->setCredentials($this->username
, $this->password
, 'digest', $digestRequest);
2243 $this->tryagain
= true;
2247 $this->debug('HTTP authentication failed');
2248 $this->setError('HTTP authentication failed');
2252 // decode content-encoding
2253 if(isset($this->incoming_headers
['content-encoding']) && $this->incoming_headers
['content-encoding'] != ''){
2254 if(strtolower($this->incoming_headers
['content-encoding']) == 'deflate' ||
strtolower($this->incoming_headers
['content-encoding']) == 'gzip'){
2255 // if decoding works, use it. else assume data wasn't gzencoded
2256 if(function_exists('gzuncompress')){
2257 //$timer->setMarker('starting decoding of gzip/deflated content');
2258 if($this->incoming_headers
['content-encoding'] == 'deflate' && $degzdata = @gzuncompress
($data)){
2260 } elseif($this->incoming_headers
['content-encoding'] == 'gzip' && $degzdata = gzinflate(substr($data, 10))){ // do our best
2263 $this->setError('Errors occurred when trying to decode the data');
2265 //$timer->setMarker('finished decoding of gzip/deflated content');
2266 //print "<xmp>\nde-inflated:\n---------------\n$data\n-------------\n</xmp>";
2267 // set decoded payload
2268 $this->incoming_payload
= $header_data.$lb.$lb.$data;
2270 $this->setError('The server sent deflated data. Your php install must have the Zlib extension compiled in to support this.');
2275 if(strlen($data) == 0){
2276 $this->debug('no data after headers!');
2277 $this->setError('no data present after HTTP headers');
2284 function setContentType($type, $charset = false) {
2285 $this->outgoing_headers
['Content-Type'] = $type . ($charset ?
'; charset=' . $charset : '');
2288 function usePersistentConnection(){
2289 if (isset($this->outgoing_headers
['Accept-Encoding'])) {
2292 $this->protocol_version
= '1.1';
2293 $this->persistentConnection
= true;
2294 $this->outgoing_headers
['Connection'] = 'Keep-Alive';
2305 * soap_server allows the user to create a SOAP server
2306 * that is capable of receiving messages and returning responses
2308 * NOTE: WSDL functionality is experimental
2310 * @author Dietrich Ayala <dietrich@ganx4.com>
2314 class soap_server
extends nusoap_base
{
2315 var $headers = array(); // HTTP headers of request
2316 var $request = ''; // HTTP request
2317 var $requestHeaders = ''; // SOAP headers from request (incomplete namespace resolution) (text)
2318 var $document = ''; // SOAP body request portion (incomplete namespace resolution) (text)
2319 var $requestSOAP = ''; // SOAP payload for request (text)
2320 var $methodURI = ''; // requested method namespace URI
2321 var $methodname = ''; // name of method requested
2322 var $methodparams = array(); // method parameters from request
2323 var $xml_encoding = ''; // character set encoding of incoming (request) messages
2324 var $SOAPAction = ''; // SOAP Action from request
2326 var $outgoing_headers = array();// HTTP headers of response
2327 var $response = ''; // HTTP response
2328 var $responseHeaders = ''; // SOAP headers for response (text)
2329 var $responseSOAP = ''; // SOAP payload for response (text)
2330 var $methodreturn = false; // method return to place in response
2331 var $methodreturnisliteralxml = false; // whether $methodreturn is a string of literal XML
2332 var $fault = false; // SOAP fault for response
2333 var $result = 'successful'; // text indication of result (for debugging)
2335 var $operations = array(); // assoc array of operations => opData
2336 var $wsdl = false; // wsdl instance
2337 var $externalWSDLURL = false; // URL for WSDL
2338 var $debug_flag = false; // whether to append debug to response as XML comment
2342 * the optional parameter is a path to a WSDL file that you'd like to bind the server instance to.
2344 * @param mixed $wsdl file path or URL (string), or wsdl instance (object)
2347 function soap_server($wsdl=false){
2349 // turn on debugging?
2353 global $HTTP_SERVER_VARS;
2355 if (isset($debug)) {
2356 $this->debug_flag
= $debug;
2357 } else if (isset($_REQUEST['debug'])) {
2358 $this->debug_flag
= $_REQUEST['debug'];
2359 } else if (isset($_SERVER['QUERY_STRING'])) {
2360 $qs = explode('&', $_SERVER['QUERY_STRING']);
2361 foreach ($qs as $v) {
2362 if (substr($v, 0, 6) == 'debug=') {
2363 $this->debug_flag
= substr($v, 6);
2366 } else if (isset($HTTP_SERVER_VARS['QUERY_STRING'])) {
2367 $qs = explode('&', $HTTP_SERVER_VARS['QUERY_STRING']);
2368 foreach ($qs as $v) {
2369 if (substr($v, 0, 6) == 'debug=') {
2370 $this->debug_flag
= substr($v, 6);
2377 if (is_object($wsdl) && is_a($wsdl, 'wsdl')) {
2378 $this->wsdl
= $wsdl;
2379 $this->externalWSDLURL
= $this->wsdl
->wsdl
;
2380 $this->debug('Use existing wsdl instance from ' . $this->externalWSDLURL
);
2382 $this->debug('Create wsdl from ' . $wsdl);
2383 $this->wsdl
= new wsdl($wsdl);
2384 $this->externalWSDLURL
= $wsdl;
2386 $this->debug("wsdl...\n" . $this->wsdl
->debug_str
);
2387 $this->wsdl
->debug_str
= '';
2388 if($err = $this->wsdl
->getError()){
2389 die('WSDL ERROR: '.$err);
2395 * processes request and returns response
2397 * @param string $data usually is the value of $HTTP_RAW_POST_DATA
2400 function service($data){
2401 global $QUERY_STRING;
2402 if(isset($_SERVER['QUERY_STRING'])){
2403 $qs = $_SERVER['QUERY_STRING'];
2404 } elseif(isset($GLOBALS['QUERY_STRING'])){
2405 $qs = $GLOBALS['QUERY_STRING'];
2406 } elseif(isset($QUERY_STRING) && $QUERY_STRING != ''){
2407 $qs = $QUERY_STRING;
2410 if(isset($qs) && ereg('wsdl', $qs) ){
2411 // This is a request for WSDL
2412 if($this->externalWSDLURL
){
2413 if (strpos($this->externalWSDLURL
,"://")!==false) { // assume URL
2414 header('Location: '.$this->externalWSDLURL
);
2415 } else { // assume file
2416 header("Content-Type: text/xml\r\n");
2417 $fp = fopen($this->externalWSDLURL
, 'r');
2421 header("Content-Type: text/xml; charset=ISO-8859-1\r\n");
2422 print $this->wsdl
->serialize();
2424 } elseif($data == '' && $this->wsdl
){
2425 // print web interface
2426 print $this->webDescription();
2428 // handle the request
2429 $this->parse_request($data);
2430 if (! $this->fault
) {
2431 $this->invoke_method();
2433 if (! $this->fault
) {
2434 $this->serialize_return();
2436 $this->send_response();
2441 * parses HTTP request headers.
2443 * The following fields are set by this function (when successful)
2452 function parse_http_headers() {
2453 global $HTTP_SERVER_VARS;
2456 $this->request
= '';
2457 if(function_exists('getallheaders')){
2458 $this->headers
= getallheaders();
2459 foreach($this->headers
as $k=>$v){
2460 $this->request
.= "$k: $v\r\n";
2461 $this->debug("$k: $v");
2463 // get SOAPAction header
2464 if(isset($this->headers
['SOAPAction'])){
2465 $this->SOAPAction
= str_replace('"','',$this->headers
['SOAPAction']);
2467 // get the character encoding of the incoming request
2468 if(strpos($this->headers
['Content-Type'],'=')){
2469 $enc = str_replace('"','',substr(strstr($this->headers
["Content-Type"],'='),1));
2470 if(eregi('^(ISO-8859-1|US-ASCII|UTF-8)$',$enc)){
2471 $this->xml_encoding
= strtoupper($enc);
2473 $this->xml_encoding
= 'US-ASCII';
2476 // should be US-ASCII, but for XML, let's be pragmatic and admit UTF-8 is most common
2477 $this->xml_encoding
= 'UTF-8';
2479 } elseif(isset($_SERVER) && is_array($_SERVER)){
2480 foreach ($_SERVER as $k => $v) {
2481 if (substr($k, 0, 5) == 'HTTP_') {
2482 $k = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($k, 5)))));
2484 $k = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', $k))));
2486 if ($k == 'Soapaction') {
2487 // get SOAPAction header
2489 $v = str_replace('"', '', $v);
2490 $v = str_replace('\\', '', $v);
2491 $this->SOAPAction
= $v;
2492 } else if ($k == 'Content-Type') {
2493 // get the character encoding of the incoming request
2494 if (strpos($v, '=')) {
2495 $enc = substr(strstr($v, '='), 1);
2496 $enc = str_replace('"', '', $enc);
2497 $enc = str_replace('\\', '', $enc);
2498 if (eregi('^(ISO-8859-1|US-ASCII|UTF-8)$', $enc)) {
2499 $this->xml_encoding
= strtoupper($enc);
2501 $this->xml_encoding
= 'US-ASCII';
2504 // should be US-ASCII, but for XML, let's be pragmatic and admit UTF-8 is most common
2505 $this->xml_encoding
= 'UTF-8';
2508 $this->headers
[$k] = $v;
2509 $this->request
.= "$k: $v\r\n";
2510 $this->debug("$k: $v");
2512 } elseif (is_array($HTTP_SERVER_VARS)) {
2513 foreach ($HTTP_SERVER_VARS as $k => $v) {
2514 if (substr($k, 0, 5) == 'HTTP_') {
2515 $k = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($k, 5)))));
2516 if ($k == 'Soapaction') {
2517 // get SOAPAction header
2519 $v = str_replace('"', '', $v);
2520 $v = str_replace('\\', '', $v);
2521 $this->SOAPAction
= $v;
2522 } else if ($k == 'Content-Type') {
2523 // get the character encoding of the incoming request
2524 if (strpos($v, '=')) {
2525 $enc = substr(strstr($v, '='), 1);
2526 $enc = str_replace('"', '', $enc);
2527 $enc = str_replace('\\', '', $enc);
2528 if (eregi('^(ISO-8859-1|US-ASCII|UTF-8)$', $enc)) {
2529 $this->xml_encoding
= strtoupper($enc);
2531 $this->xml_encoding
= 'US-ASCII';
2534 // should be US-ASCII, but for XML, let's be pragmatic and admit UTF-8 is most common
2535 $this->xml_encoding
= 'UTF-8';
2538 $this->headers
[$k] = $v;
2539 $this->request
.= "$k: $v\r\n";
2540 $this->debug("$k: $v");
2549 * The following fields are set by this function (when successful)
2563 * This sets the fault field on error
2565 * @param string $data XML string
2568 function parse_request($data='') {
2569 $this->debug('entering parse_request() on '.date('H:i Y-m-d'));
2570 $this->parse_http_headers();
2571 $this->debug('got character encoding: '.$this->xml_encoding
);
2572 // uncompress if necessary
2573 if (isset($this->headers
['Content-Encoding']) && $this->headers
['Content-Encoding'] != '') {
2574 $this->debug('got content encoding: ' . $this->headers
['Content-Encoding']);
2575 if ($this->headers
['Content-Encoding'] == 'deflate' ||
$this->headers
['Content-Encoding'] == 'gzip') {
2576 // if decoding works, use it. else assume data wasn't gzencoded
2577 if (function_exists('gzuncompress')) {
2578 if ($this->headers
['Content-Encoding'] == 'deflate' && $degzdata = @gzuncompress
($data)) {
2580 } elseif ($this->headers
['Content-Encoding'] == 'gzip' && $degzdata = gzinflate(substr($data, 10))) {
2583 $this->fault('Server', 'Errors occurred when trying to decode the data');
2587 $this->fault('Server', 'This Server does not support compressed data');
2592 $this->request
.= "\r\n".$data;
2593 $this->requestSOAP
= $data;
2594 // parse response, get soap parser obj
2595 $parser = new soap_parser($data,$this->xml_encoding
);
2597 $this->debug("parser debug: \n".$parser->debug_str
);
2598 // if fault occurred during message parsing
2599 if($err = $parser->getError()){
2600 $this->result
= 'fault: error in msg parsing: '.$err;
2601 $this->fault('Server',"error in msg parsing:\n".$err);
2602 // else successfully parsed request into soapval object
2604 // get/set methodname
2605 $this->methodURI
= $parser->root_struct_namespace
;
2606 $this->methodname
= $parser->root_struct_name
;
2607 $this->debug('method name: '.$this->methodname
);
2608 $this->debug('calling parser->get_response()');
2609 $this->methodparams
= $parser->get_response();
2611 $this->requestHeaders
= $parser->getHeaders();
2612 // add document for doclit support
2613 $this->document
= $parser->document
;
2615 $this->debug('leaving parse_request() on '.date('H:i Y-m-d'));
2619 * invokes a PHP function for the requested SOAP method
2621 * The following fields are set by this function (when successful)
2625 * Note that the PHP function that is called may also set the following
2626 * fields to affect the response sent to the client
2631 * This sets the fault field on error
2635 function invoke_method() {
2636 $this->debug('entering invoke_method');
2637 // does method exist?
2638 if(!function_exists($this->methodname
)){
2639 // "method not found" fault here
2640 $this->debug("method '$this->methodname' not found!");
2641 $this->result
= 'fault: method not found';
2642 $this->fault('Server',"method '$this->methodname' not defined in service");
2646 if(!$this->opData
= $this->wsdl
->getOperationData($this->methodname
)){
2648 $this->fault('Server',"Operation '$this->methodname' is not defined in the WSDL for this service");
2651 $this->debug('opData is ' . $this->varDump($this->opData
));
2653 $this->debug("method '$this->methodname' exists");
2654 // evaluate message, getting back parameters
2655 // verify that request parameters match the method's signature
2656 if(! $this->verify_method($this->methodname
,$this->methodparams
)){
2658 $this->debug('ERROR: request not verified against method signature');
2659 $this->result
= 'fault: request failed validation against method signature';
2661 $this->fault('Server',"Operation '$this->methodname' not defined in service.");
2665 // if there are parameters to pass
2666 $this->debug('params var dump '.$this->varDump($this->methodparams
));
2667 if($this->methodparams
){
2668 $this->debug("calling '$this->methodname' with params");
2669 if (! function_exists('call_user_func_array')) {
2670 $this->debug('calling method using eval()');
2671 $funcCall = $this->methodname
.'(';
2672 foreach($this->methodparams
as $param) {
2673 $funcCall .= "\"$param\",";
2675 $funcCall = substr($funcCall, 0, -1).')';
2676 $this->debug('function call:<br>'.$funcCall);
2677 @eval
("\$this->methodreturn = $funcCall;");
2679 $this->debug('calling method using call_user_func_array()');
2680 $this->methodreturn
= call_user_func_array("$this->methodname",$this->methodparams
);
2683 // call method w/ no parameters
2684 $this->debug("calling $this->methodname w/ no params");
2685 $m = $this->methodname
;
2686 $this->methodreturn
= @$m();
2688 $this->debug('methodreturn var dump'.$this->varDump($this->methodreturn
));
2689 $this->debug("leaving invoke_method: called method $this->methodname, received $this->methodreturn of type ".gettype($this->methodreturn
));
2693 * serializes the return value from a PHP function into a full SOAP Envelope
2695 * The following fields are set by this function (when successful)
2699 * This sets the fault field on error
2703 function serialize_return() {
2704 $this->debug("Entering serialize_return");
2705 // if we got nothing back. this might be ok (echoVoid)
2706 if(isset($this->methodreturn
) && ($this->methodreturn
!= '' ||
is_bool($this->methodreturn
))) {
2708 if(get_class($this->methodreturn
) == 'soap_fault'){
2709 $this->debug('got a fault object from method');
2710 $this->fault
= $this->methodreturn
;
2712 } elseif ($this->methodreturnisliteralxml
) {
2713 $return_val = $this->methodreturn
;
2714 // returned value(s)
2716 $this->debug('got a(n) '.gettype($this->methodreturn
).' from method');
2717 $this->debug('serializing return value');
2719 // weak attempt at supporting multiple output params
2720 if(sizeof($this->opData
['output']['parts']) > 1){
2721 $opParams = $this->methodreturn
;
2723 // TODO: is this really necessary?
2724 $opParams = array($this->methodreturn
);
2726 $return_val = $this->wsdl
->serializeRPCParameters($this->methodname
,'output',$opParams);
2727 if($errstr = $this->wsdl
->getError()){
2728 $this->debug('got wsdl error: '.$errstr);
2729 $this->fault('Server', 'got wsdl error: '.$errstr);
2733 $return_val = $this->serialize_val($this->methodreturn
, 'return');
2736 $this->debug('return val: '.$this->varDump($return_val));
2739 $this->debug('got no response from method');
2741 $this->debug('serializing response');
2743 if ($this->opData
['style'] == 'rpc') {
2744 $payload = '<ns1:'.$this->methodname
.'Response xmlns:ns1="'.$this->methodURI
.'">'.$return_val.'</ns1:'.$this->methodname
."Response>";
2746 $payload = $return_val;
2749 $payload = '<ns1:'.$this->methodname
.'Response xmlns:ns1="'.$this->methodURI
.'">'.$return_val.'</ns1:'.$this->methodname
."Response>";
2751 $this->result
= 'successful';
2753 //if($this->debug_flag){
2754 $this->debug("WSDL debug data:\n".$this->wsdl
->debug_str
);
2756 // Added: In case we use a WSDL, return a serialized env. WITH the usedNamespaces.
2757 $this->responseSOAP
= $this->serializeEnvelope($payload,$this->responseHeaders
,$this->wsdl
->usedNamespaces
,$this->opData
['style']);
2759 $this->responseSOAP
= $this->serializeEnvelope($payload,$this->responseHeaders
);
2761 $this->debug("Leaving serialize_return");
2765 * sends an HTTP response
2767 * The following fields are set by this function (when successful)
2774 function send_response() {
2775 $this->debug('Enter send_response');
2777 $payload = $this->fault
->serialize();
2778 $this->outgoing_headers
[] = "HTTP/1.0 500 Internal Server Error";
2779 $this->outgoing_headers
[] = "Status: 500 Internal Server Error";
2781 $payload = $this->responseSOAP
;
2782 // Some combinations of PHP+Web server allow the Status
2783 // to come through as a header. Since OK is the default
2785 // $this->outgoing_headers[] = "HTTP/1.0 200 OK";
2786 // $this->outgoing_headers[] = "Status: 200 OK";
2788 // add debug data if in debug mode
2789 if(isset($this->debug_flag
) && $this->debug_flag
){
2790 while (strpos($this->debug_str
, '--')) {
2791 $this->debug_str
= str_replace('--', '- -', $this->debug_str
);
2793 $payload .= "<!--\n" . $this->debug_str
. "\n-->";
2795 $this->outgoing_headers
[] = "Server: $this->title Server v$this->version";
2796 ereg('\$Revisio' . 'n: ([^ ]+)', $this->revision
, $rev);
2797 $this->outgoing_headers
[] = "X-SOAP-Server: $this->title/$this->version (".$rev[1].")";
2798 // Let the Web server decide about this
2799 //$this->outgoing_headers[] = "Connection: Close\r\n";
2800 $this->outgoing_headers
[] = "Content-Type: text/xml; charset=$this->soap_defencoding";
2801 //begin code to compress payload - by John
2802 if (strlen($payload) > 1024 && isset($this->headers
) && isset($this->headers
['Accept-Encoding'])) {
2803 if (strstr($this->headers
['Accept-Encoding'], 'deflate')) {
2804 if (function_exists('gzcompress')) {
2805 if (isset($this->debug_flag
) && $this->debug_flag
) {
2806 $payload .= "<!-- Content being deflated -->";
2808 $this->outgoing_headers
[] = "Content-Encoding: deflate";
2809 $payload = gzcompress($payload);
2811 if (isset($this->debug_flag
) && $this->debug_flag
) {
2812 $payload .= "<!-- Content will not be deflated: no gzcompress -->";
2815 } else if (strstr($this->headers
['Accept-Encoding'], 'gzip')) {
2816 if (function_exists('gzencode')) {
2817 if (isset($this->debug_flag
) && $this->debug_flag
) {
2818 $payload .= "<!-- Content being gzipped -->";
2820 $this->outgoing_headers
[] = "Content-Encoding: gzip";
2821 $payload = gzencode($payload);
2823 if (isset($this->debug_flag
) && $this->debug_flag
) {
2824 $payload .= "<!-- Content will not be gzipped: no gzencode -->";
2830 $this->outgoing_headers
[] = "Content-Length: ".strlen($payload);
2831 reset($this->outgoing_headers
);
2832 foreach($this->outgoing_headers
as $hdr){
2833 header($hdr, false);
2835 $this->response
= join("\r\n",$this->outgoing_headers
)."\r\n".$payload;
2840 * takes the value that was created by parsing the request
2841 * and compares to the method's signature, if available.
2847 function verify_method($operation,$request){
2848 if(isset($this->wsdl
) && is_object($this->wsdl
)){
2849 if($this->wsdl
->getOperationData($operation)){
2852 } elseif(isset($this->operations
[$operation])){
2859 * add a method to the dispatch map
2861 * @param string $methodname
2862 * @param string $in array of input values
2863 * @param string $out array of output values
2866 function add_to_map($methodname,$in,$out){
2867 $this->operations
[$methodname] = array('name' => $methodname,'in' => $in,'out' => $out);
2871 * register a service with the server
2873 * @param string $methodname
2874 * @param string $in assoc array of input values: key = param name, value = param type
2875 * @param string $out assoc array of output values: key = param name, value = param type
2876 * @param string $namespace
2877 * @param string $soapaction
2878 * @param string $style optional (rpc|document)
2879 * @param string $use optional (encoded|literal)
2880 * @param string $documentation optional Description to include in WSDL
2883 function register($name,$in=false,$out=false,$namespace=false,$soapaction=false,$style=false,$use=false,$documentation=''){
2884 if($this->externalWSDLURL
){
2885 die('You cannot bind to an external WSDL file, and register methods outside of it! Please choose either WSDL or no WSDL.');
2891 if(false == $namespace) {
2893 if(false == $soapaction) {
2894 $SERVER_NAME = isset($_SERVER['SERVER_NAME']) ?
$_SERVER['SERVER_NAME'] : $GLOBALS['SERVER_NAME'];
2895 $SCRIPT_NAME = isset($_SERVER['SCRIPT_NAME']) ?
$_SERVER['SCRIPT_NAME'] : $GLOBALS['SCRIPT_NAME'];
2896 $soapaction = "http://$SERVER_NAME$SCRIPT_NAME/$name";
2898 if(false == $style) {
2905 $this->operations
[$name] = array(
2909 'namespace' => $namespace,
2910 'soapaction' => $soapaction,
2913 $this->wsdl
->addOperation($name,$in,$out,$namespace,$soapaction,$style,$use,$documentation);
2919 * create a fault. this also acts as a flag to the server that a fault has occured.
2921 * @param string faultcode
2922 * @param string faultstring
2923 * @param string faultactor
2924 * @param string faultdetail
2927 function fault($faultcode,$faultstring,$faultactor='',$faultdetail=''){
2928 $this->fault
= new soap_fault($faultcode,$faultactor,$faultstring,$faultdetail);
2932 * prints html description of services
2936 function webDescription(){
2938 <html><head><title>NuSOAP: '.$this->wsdl
->serviceName
.'</title>
2939 <style type="text/css">
2940 body { font-family: arial; color: #000000; background-color: #ffffff; margin: 0px 0px 0px 0px; }
2941 p { font-family: arial; color: #000000; margin-top: 0px; margin-bottom: 12px; }
2942 pre { background-color: silver; padding: 5px; font-family: Courier New; font-size: x-small; color: #000000;}
2943 ul { margin-top: 10px; margin-left: 20px; }
2944 li { list-style-type: none; margin-top: 10px; color: #000000; }
2946 margin-left: 0px; padding-bottom: 2em; }
2948 padding-top: 10px; padding-bottom: 10px; padding-left: 15px; font-size: .70em;
2949 margin-top: 10px; margin-left: 0px; color: #000000;
2950 background-color: #ccccff; width: 20%; margin-left: 20px; margin-top: 20px; }
2952 font-family: arial; font-size: 26px; color: #ffffff;
2953 background-color: #999999; width: 105%; margin-left: 0px;
2954 padding-top: 10px; padding-bottom: 10px; padding-left: 15px;}
2956 position: absolute; visibility: hidden; z-index: 200; left: 250px; top: 100px;
2957 font-family: arial; overflow: hidden; width: 600;
2958 padding: 20px; font-size: 10px; background-color: #999999;
2959 layer-background-color:#FFFFFF; }
2960 a,a:active { color: charcoal; font-weight: bold; }
2961 a:visited { color: #666666; font-weight: bold; }
2962 a:hover { color: cc3300; font-weight: bold; }
2964 <script language="JavaScript" type="text/javascript">
2966 // POP-UP CAPTIONS...
2967 function lib_bwcheck(){ //Browsercheck (needed)
2968 this.ver=navigator.appVersion
2969 this.agent=navigator.userAgent
2970 this.dom=document.getElementById?1:0
2971 this.opera5=this.agent.indexOf("Opera 5")>-1
2972 this.ie5=(this.ver.indexOf("MSIE 5")>-1 && this.dom && !this.opera5)?1:0;
2973 this.ie6=(this.ver.indexOf("MSIE 6")>-1 && this.dom && !this.opera5)?1:0;
2974 this.ie4=(document.all && !this.dom && !this.opera5)?1:0;
2975 this.ie=this.ie4||this.ie5||this.ie6
2976 this.mac=this.agent.indexOf("Mac")>-1
2977 this.ns6=(this.dom && parseInt(this.ver) >= 5) ?1:0;
2978 this.ns4=(document.layers && !this.dom)?1:0;
2979 this.bw=(this.ie6 || this.ie5 || this.ie4 || this.ns4 || this.ns6 || this.opera5)
2982 var bw = new lib_bwcheck()
2983 //Makes crossbrowser object.
2984 function makeObj(obj){
2985 this.evnt=bw.dom? document.getElementById(obj):bw.ie4?document.all[obj]:bw.ns4?document.layers[obj]:0;
2986 if(!this.evnt) return false
2987 this.css=bw.dom||bw.ie4?this.evnt.style:bw.ns4?this.evnt:0;
2988 this.wref=bw.dom||bw.ie4?this.evnt:bw.ns4?this.css.document:0;
2989 this.writeIt=b_writeIt;
2992 // A unit of measure that will be added when setting the position of a layer.
2993 //var px = bw.ns4||window.opera?"":"px";
2994 function b_writeIt(text){
2995 if (bw.ns4){this.wref.write(text);this.wref.close()}
2996 else this.wref.innerHTML = text
2998 //Shows the messages
3000 function popup(divid){
3001 if(oDesc = new makeObj(divid)){
3002 oDesc.css.visibility = "visible"
3005 function popout(){ // Hides message
3006 if(oDesc) oDesc.css.visibility = "hidden"
3014 <div class=title>'.$this->wsdl
->serviceName
.'</div>
3016 <p>View the <a href="'.(isset($GLOBALS['PHP_SELF']) ?
$GLOBALS['PHP_SELF'] : $_SERVER['PHP_SELF']).'?wsdl">WSDL</a> for the service.
3017 Click on an operation name to view it's details.</p>
3019 foreach($this->wsdl
->getOperations() as $op => $data){
3020 $b .= "<li><a href='#' onclick=\"popup('$op')\">$op</a></li>";
3021 // create hidden div
3022 $b .= "<div id='$op' class='hidden'>
3023 <a href='#' onclick='popout()'><font color='#ffffff'>Close</font></a><br><br>";
3024 foreach($data as $donnie => $marie){ // loop through opdata
3025 if($donnie == 'input' ||
$donnie == 'output'){ // show input/output data
3026 $b .= "<font color='white'>".ucfirst($donnie).':</font><br>';
3027 foreach($marie as $captain => $tenille){ // loop through data
3028 if($captain == 'parts'){ // loop thru parts
3029 $b .= " $captain:<br>";
3030 //if(is_array($tenille)){
3031 foreach($tenille as $joanie => $chachi){
3032 $b .= " $joanie: $chachi<br>";
3036 $b .= " $captain: $tenille<br>";
3040 $b .= "<font color='white'>".ucfirst($donnie).":</font> $marie<br>";
3048 </div></body></html>';
3053 * sets up wsdl object
3054 * this acts as a flag to enable internal WSDL generation
3056 * @param string $serviceName, name of the service
3057 * @param string $namespace optional tns namespace
3058 * @param string $endpoint optional URL of service endpoint
3059 * @param string $style optional (rpc|document) WSDL style (also specified by operation)
3060 * @param string $transport optional SOAP transport
3061 * @param string $schemaTargetNamespace optional targetNamespace for service schema
3063 function configureWSDL($serviceName,$namespace = false,$endpoint = false,$style='rpc', $transport = 'http://schemas.xmlsoap.org/soap/http', $schemaTargetNamespace = false)
3065 $SERVER_NAME = isset($_SERVER['SERVER_NAME']) ?
$_SERVER['SERVER_NAME'] : $GLOBALS['SERVER_NAME'];
3066 $SERVER_PORT = isset($_SERVER['SERVER_PORT']) ?
$_SERVER['SERVER_PORT'] : $GLOBALS['SERVER_PORT'];
3067 if ($SERVER_PORT == 80) {
3070 $SERVER_PORT = ':' . $SERVER_PORT;
3072 $SCRIPT_NAME = isset($_SERVER['SCRIPT_NAME']) ?
$_SERVER['SCRIPT_NAME'] : $GLOBALS['SCRIPT_NAME'];
3073 if(false == $namespace) {
3074 $namespace = "http://$SERVER_NAME/soap/$serviceName";
3077 if(false == $endpoint) {
3078 if (isset($_SERVER['HTTPS'])) {
3079 $HTTPS = $_SERVER['HTTPS'];
3080 } elseif (isset($GLOBALS['HTTPS'])) {
3081 $HTTPS = $GLOBALS['HTTPS'];
3085 if ($HTTPS == '1' ||
$HTTPS == 'on') {
3090 $endpoint = "$SCHEME://$SERVER_NAME$SERVER_PORT$SCRIPT_NAME";
3093 if(false == $schemaTargetNamespace) {
3094 $schemaTargetNamespace = $namespace;
3097 $this->wsdl
= new wsdl
;
3098 $this->wsdl
->serviceName
= $serviceName;
3099 $this->wsdl
->endpoint
= $endpoint;
3100 $this->wsdl
->namespaces
['tns'] = $namespace;
3101 $this->wsdl
->namespaces
['soap'] = 'http://schemas.xmlsoap.org/wsdl/soap/';
3102 $this->wsdl
->namespaces
['wsdl'] = 'http://schemas.xmlsoap.org/wsdl/';
3103 if ($schemaTargetNamespace != $namespace) {
3104 $this->wsdl
->namespaces
['types'] = $schemaTargetNamespace;
3106 $this->wsdl
->schemas
[$schemaTargetNamespace][0] = new xmlschema('', '', $this->wsdl
->namespaces
);
3107 $this->wsdl
->schemas
[$schemaTargetNamespace][0]->schemaTargetNamespace
= $schemaTargetNamespace;
3108 $this->wsdl
->schemas
[$schemaTargetNamespace][0]->imports
['http://schemas.xmlsoap.org/soap/encoding/'][0] = array('location' => '', 'loaded' => true);
3109 $this->wsdl
->schemas
[$schemaTargetNamespace][0]->imports
['http://schemas.xmlsoap.org/wsdl/'][0] = array('location' => '', 'loaded' => true);
3110 $this->wsdl
->bindings
[$serviceName.'Binding'] = array(
3111 'name'=>$serviceName.'Binding',
3113 'transport'=>$transport,
3114 'portType'=>$serviceName.'PortType');
3115 $this->wsdl
->ports
[$serviceName.'Port'] = array(
3116 'binding'=>$serviceName.'Binding',
3117 'location'=>$endpoint,
3118 'bindingType'=>'http://schemas.xmlsoap.org/wsdl/soap/');
3129 * parses a WSDL file, allows access to it's data, other utility methods
3131 * @author Dietrich Ayala <dietrich@ganx4.com>
3135 class wsdl
extends nusoap_base
{
3136 // URL or filename of the root of this WSDL
3138 // define internal arrays of bindings, ports, operations, messages, etc.
3139 var $schemas = array();
3141 var $message = array();
3142 var $complexTypes = array();
3143 var $messages = array();
3144 var $currentMessage;
3145 var $currentOperation;
3146 var $portTypes = array();
3147 var $currentPortType;
3148 var $bindings = array();
3149 var $currentBinding;
3150 var $ports = array();
3152 var $opData = array();
3154 var $documentation = false;
3156 // array of wsdl docs to import
3157 var $import = array();
3162 var $depth_array = array();
3164 var $proxyhost = '';
3165 var $proxyport = '';
3166 var $proxyusername = '';
3167 var $proxypassword = '';
3169 var $response_timeout = 30;
3174 * @param string $wsdl WSDL document URL
3175 * @param string $proxyhost
3176 * @param string $proxyport
3177 * @param string $proxyusername
3178 * @param string $proxypassword
3179 * @param integer $timeout set the connection timeout
3180 * @param integer $response_timeout set the response timeout
3183 function wsdl($wsdl = '',$proxyhost=false,$proxyport=false,$proxyusername=false,$proxypassword=false,$timeout=0,$response_timeout=30){
3184 $this->wsdl
= $wsdl;
3185 $this->proxyhost
= $proxyhost;
3186 $this->proxyport
= $proxyport;
3187 $this->proxyusername
= $proxyusername;
3188 $this->proxypassword
= $proxypassword;
3189 $this->timeout
= $timeout;
3190 $this->response_timeout
= $response_timeout;
3194 $this->debug('initial wsdl URL: ' . $wsdl);
3195 $this->parseWSDL($wsdl);
3198 // TODO: handle imports more properly, grabbing them in-line and nesting them
3199 $imported_urls = array();
3201 while ($imported > 0) {
3204 foreach ($this->schemas
as $ns => $list) {
3205 foreach ($list as $xs) {
3206 $wsdlparts = parse_url($this->wsdl
); // this is bogusly simple!
3207 foreach ($xs->imports
as $ns2 => $list2) {
3208 for ($ii = 0; $ii < count($list2); $ii++
) {
3209 if (! $list2[$ii]['loaded']) {
3210 $this->schemas
[$ns]->imports
[$ns2][$ii]['loaded'] = true;
3211 $url = $list2[$ii]['location'];
3213 $urlparts = parse_url($url);
3214 if (!isset($urlparts['host'])) {
3215 $url = $wsdlparts['scheme'] . '://' . $wsdlparts['host'] .
3216 substr($wsdlparts['path'],0,strrpos($wsdlparts['path'],'/') +
1) .$urlparts['path'];
3218 if (! in_array($url, $imported_urls)) {
3219 $this->parseWSDL($url);
3221 $imported_urls[] = $url;
3224 $this->debug("Unexpected scenario: empty URL for unloaded import");
3232 $wsdlparts = parse_url($this->wsdl
); // this is bogusly simple!
3233 foreach ($this->import
as $ns => $list) {
3234 for ($ii = 0; $ii < count($list); $ii++
) {
3235 if (! $list[$ii]['loaded']) {
3236 $this->import
[$ns][$ii]['loaded'] = true;
3237 $url = $list[$ii]['location'];
3239 $urlparts = parse_url($url);
3240 if (!isset($urlparts['host'])) {
3241 $url = $wsdlparts['scheme'] . '://' . $wsdlparts['host'] .
3242 substr($wsdlparts['path'],0,strrpos($wsdlparts['path'],'/') +
1) .$urlparts['path'];
3244 if (! in_array($url, $imported_urls)) {
3245 $this->parseWSDL($url);
3247 $imported_urls[] = $url;
3250 $this->debug("Unexpected scenario: empty URL for unloaded import");
3256 // add new data to operation data
3257 foreach($this->bindings
as $binding => $bindingData) {
3258 if (isset($bindingData['operations']) && is_array($bindingData['operations'])) {
3259 foreach($bindingData['operations'] as $operation => $data) {
3260 $this->debug('post-parse data gathering for ' . $operation);
3261 $this->bindings
[$binding]['operations'][$operation]['input'] =
3262 isset($this->bindings
[$binding]['operations'][$operation]['input']) ?
3263 array_merge($this->bindings
[$binding]['operations'][$operation]['input'], $this->portTypes
[ $bindingData['portType'] ][$operation]['input']) :
3264 $this->portTypes
[ $bindingData['portType'] ][$operation]['input'];
3265 $this->bindings
[$binding]['operations'][$operation]['output'] =
3266 isset($this->bindings
[$binding]['operations'][$operation]['output']) ?
3267 array_merge($this->bindings
[$binding]['operations'][$operation]['output'], $this->portTypes
[ $bindingData['portType'] ][$operation]['output']) :
3268 $this->portTypes
[ $bindingData['portType'] ][$operation]['output'];
3269 if(isset($this->messages
[ $this->bindings
[$binding]['operations'][$operation]['input']['message'] ])){
3270 $this->bindings
[$binding]['operations'][$operation]['input']['parts'] = $this->messages
[ $this->bindings
[$binding]['operations'][$operation]['input']['message'] ];
3272 if(isset($this->messages
[ $this->bindings
[$binding]['operations'][$operation]['output']['message'] ])){
3273 $this->bindings
[$binding]['operations'][$operation]['output']['parts'] = $this->messages
[ $this->bindings
[$binding]['operations'][$operation]['output']['message'] ];
3275 if (isset($bindingData['style'])) {
3276 $this->bindings
[$binding]['operations'][$operation]['style'] = $bindingData['style'];
3278 $this->bindings
[$binding]['operations'][$operation]['transport'] = isset($bindingData['transport']) ?
$bindingData['transport'] : '';
3279 $this->bindings
[$binding]['operations'][$operation]['documentation'] = isset($this->portTypes
[ $bindingData['portType'] ][$operation]['documentation']) ?
$this->portTypes
[ $bindingData['portType'] ][$operation]['documentation'] : '';
3280 $this->bindings
[$binding]['operations'][$operation]['endpoint'] = isset($bindingData['endpoint']) ?
$bindingData['endpoint'] : '';
3287 * parses the wsdl document
3289 * @param string $wsdl path or URL
3292 function parseWSDL($wsdl = '')
3295 $this->debug('no wsdl passed to parseWSDL()!!');
3296 $this->setError('no wsdl passed to parseWSDL()!!');
3300 // parse $wsdl for url format
3301 $wsdl_props = parse_url($wsdl);
3303 if (isset($wsdl_props['scheme']) && ($wsdl_props['scheme'] == 'http' ||
$wsdl_props['scheme'] == 'https')) {
3304 $this->debug('getting WSDL http(s) URL ' . $wsdl);
3306 $tr = new soap_transport_http($wsdl);
3307 $tr->request_method
= 'GET';
3308 $tr->useSOAPAction
= false;
3309 if($this->proxyhost
&& $this->proxyport
){
3310 $tr->setProxy($this->proxyhost
,$this->proxyport
,$this->proxyusername
,$this->proxypassword
);
3312 if (isset($wsdl_props['user'])) {
3313 $tr->setCredentials($wsdl_props['user'],$wsdl_props['pass']);
3315 $wsdl_string = $tr->send('', $this->timeout
, $this->response_timeout
);
3316 //$this->debug("WSDL request\n" . $tr->outgoing_payload);
3317 //$this->debug("WSDL response\n" . $tr->incoming_payload);
3318 $this->debug("transport debug data...\n" . $tr->debug_str
);
3320 if($err = $tr->getError() ){
3321 $errstr = 'HTTP ERROR: '.$err;
3322 $this->debug($errstr);
3323 $this->setError($errstr);
3329 // $wsdl is not http(s), so treat it as a file URL or plain file path
3330 if (isset($wsdl_props['scheme']) && ($wsdl_props['scheme'] == 'file') && isset($wsdl_props['path'])) {
3331 $path = isset($wsdl_props['host']) ?
($wsdl_props['host'] . ':' . $wsdl_props['path']) : $wsdl_props['path'];
3335 $this->debug('getting WSDL file ' . $path);
3336 if ($fp = @fopen
($path, 'r')) {
3338 while ($data = fread($fp, 32768)) {
3339 $wsdl_string .= $data;
3343 $errstr = "Bad path to WSDL file $path";
3344 $this->debug($errstr);
3345 $this->setError($errstr);
3349 // end new code added
3350 // Create an XML parser.
3351 $this->parser
= xml_parser_create();
3352 // Set the options for parsing the XML data.
3353 // xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 1);
3354 xml_parser_set_option($this->parser
, XML_OPTION_CASE_FOLDING
, 0);
3355 // Set the object for the parser.
3356 xml_set_object($this->parser
, $this);
3357 // Set the element handlers for the parser.
3358 xml_set_element_handler($this->parser
, 'start_element', 'end_element');
3359 xml_set_character_data_handler($this->parser
, 'character_data');
3360 // Parse the XML file.
3361 if (!xml_parse($this->parser
, $wsdl_string, true)) {
3362 // Display an error message.
3364 'XML error parsing WSDL from %s on line %d: %s',
3366 xml_get_current_line_number($this->parser
),
3367 xml_error_string(xml_get_error_code($this->parser
))
3369 $this->debug($errstr);
3370 $this->debug("XML payload:\n" . $wsdl_string);
3371 $this->setError($errstr);
3375 xml_parser_free($this->parser
);
3376 // catch wsdl parse errors
3377 if($this->getError()){
3384 * start-element handler
3386 * @param string $parser XML parser object
3387 * @param string $name element name
3388 * @param string $attrs associative array of attributes
3391 function start_element($parser, $name, $attrs)
3393 if ($this->status
== 'schema') {
3394 $this->currentSchema
->schemaStartElement($parser, $name, $attrs);
3395 $this->debug_str
.= $this->currentSchema
->debug_str
;
3396 $this->currentSchema
->debug_str
= '';
3397 } elseif (ereg('schema$', $name)) {
3398 // $this->debug("startElement for $name ($attrs[name]). status = $this->status (".$this->getLocalPart($name).")");
3399 $this->status
= 'schema';
3400 $this->currentSchema
= new xmlschema('', '', $this->namespaces
);
3401 $this->currentSchema
->schemaStartElement($parser, $name, $attrs);
3402 $this->debug_str
.= $this->currentSchema
->debug_str
;
3403 $this->currentSchema
->debug_str
= '';
3405 // position in the total number of elements, starting from 0
3406 $pos = $this->position++
;
3407 $depth = $this->depth++
;
3408 // set self as current value for this depth
3409 $this->depth_array
[$depth] = $pos;
3410 $this->message
[$pos] = array('cdata' => '');
3411 // get element prefix
3412 if (ereg(':', $name)) {
3414 $prefix = substr($name, 0, strpos($name, ':'));
3416 $namespace = isset($this->namespaces
[$prefix]) ?
$this->namespaces
[$prefix] : '';
3417 // get unqualified name
3418 $name = substr(strstr($name, ':'), 1);
3421 if (count($attrs) > 0) {
3422 foreach($attrs as $k => $v) {
3423 // if ns declarations, add to class level array of valid namespaces
3424 if (ereg("^xmlns", $k)) {
3425 if ($ns_prefix = substr(strrchr($k, ':'), 1)) {
3426 $this->namespaces
[$ns_prefix] = $v;
3428 $this->namespaces
['ns' . (count($this->namespaces
) +
1)] = $v;
3430 if ($v == 'http://www.w3.org/2001/XMLSchema' ||
$v == 'http://www.w3.org/1999/XMLSchema') {
3431 $this->XMLSchemaVersion
= $v;
3432 $this->namespaces
['xsi'] = $v . '-instance';
3435 // expand each attribute
3436 $k = strpos($k, ':') ?
$this->expandQname($k) : $k;
3437 if ($k != 'location' && $k != 'soapAction' && $k != 'namespace') {
3438 $v = strpos($v, ':') ?
$this->expandQname($v) : $v;
3446 // find status, register data
3447 switch ($this->status
) {
3449 if ($name == 'part') {
3450 if (isset($attrs['type'])) {
3451 $this->debug("msg " . $this->currentMessage
. ": found part $attrs[name]: " . implode(',', $attrs));
3452 $this->messages
[$this->currentMessage
][$attrs['name']] = $attrs['type'];
3454 if (isset($attrs['element'])) {
3455 $this->messages
[$this->currentMessage
][$attrs['name']] = $attrs['element'];
3462 $this->currentPortOperation
= $attrs['name'];
3463 $this->debug("portType $this->currentPortType operation: $this->currentPortOperation");
3464 if (isset($attrs['parameterOrder'])) {
3465 $this->portTypes
[$this->currentPortType
][$attrs['name']]['parameterOrder'] = $attrs['parameterOrder'];
3468 case 'documentation':
3469 $this->documentation
= true;
3471 // merge input/output data
3473 $m = isset($attrs['message']) ?
$this->getLocalPart($attrs['message']) : '';
3474 $this->portTypes
[$this->currentPortType
][$this->currentPortOperation
][$name]['message'] = $m;
3482 if (isset($attrs['style'])) {
3483 $this->bindings
[$this->currentBinding
]['prefix'] = $prefix;
3485 $this->bindings
[$this->currentBinding
] = array_merge($this->bindings
[$this->currentBinding
], $attrs);
3488 $this->bindings
[$this->currentBinding
]['operations'][$this->currentOperation
][$this->opStatus
]['headers'][] = $attrs;
3491 if (isset($attrs['soapAction'])) {
3492 $this->bindings
[$this->currentBinding
]['operations'][$this->currentOperation
]['soapAction'] = $attrs['soapAction'];
3494 if (isset($attrs['style'])) {
3495 $this->bindings
[$this->currentBinding
]['operations'][$this->currentOperation
]['style'] = $attrs['style'];
3497 if (isset($attrs['name'])) {
3498 $this->currentOperation
= $attrs['name'];
3499 $this->debug("current binding operation: $this->currentOperation");
3500 $this->bindings
[$this->currentBinding
]['operations'][$this->currentOperation
]['name'] = $attrs['name'];
3501 $this->bindings
[$this->currentBinding
]['operations'][$this->currentOperation
]['binding'] = $this->currentBinding
;
3502 $this->bindings
[$this->currentBinding
]['operations'][$this->currentOperation
]['endpoint'] = isset($this->bindings
[$this->currentBinding
]['endpoint']) ?
$this->bindings
[$this->currentBinding
]['endpoint'] : '';
3506 $this->opStatus
= 'input';
3509 $this->opStatus
= 'output';
3512 if (isset($this->bindings
[$this->currentBinding
]['operations'][$this->currentOperation
][$this->opStatus
])) {
3513 $this->bindings
[$this->currentBinding
]['operations'][$this->currentOperation
][$this->opStatus
] = array_merge($this->bindings
[$this->currentBinding
]['operations'][$this->currentOperation
][$this->opStatus
], $attrs);
3515 $this->bindings
[$this->currentBinding
]['operations'][$this->currentOperation
][$this->opStatus
] = $attrs;
3523 $this->currentPort
= $attrs['name'];
3524 $this->debug('current port: ' . $this->currentPort
);
3525 $this->ports
[$this->currentPort
]['binding'] = $this->getLocalPart($attrs['binding']);
3529 $this->ports
[$this->currentPort
]['location'] = $attrs['location'];
3530 $this->ports
[$this->currentPort
]['bindingType'] = $namespace;
3531 $this->bindings
[ $this->ports
[$this->currentPort
]['binding'] ]['bindingType'] = $namespace;
3532 $this->bindings
[ $this->ports
[$this->currentPort
]['binding'] ]['endpoint'] = $attrs['location'];
3540 if (isset($attrs['location'])) {
3541 $this->import
[$attrs['namespace']][] = array('location' => $attrs['location'], 'loaded' => false);
3542 $this->debug('parsing import ' . $attrs['namespace']. ' - ' . $attrs['location'] . ' (' . count($this->import
[$attrs['namespace']]).')');
3544 $this->import
[$attrs['namespace']][] = array('location' => '', 'loaded' => true);
3545 if (! $this->getPrefixFromNamespace($attrs['namespace'])) {
3546 $this->namespaces
['ns'.(count($this->namespaces
)+
1)] = $attrs['namespace'];
3548 $this->debug('parsing import ' . $attrs['namespace']. ' - [no location] (' . count($this->import
[$attrs['namespace']]).')');
3553 // $this->status = 'schema';
3556 $this->status
= 'message';
3557 $this->messages
[$attrs['name']] = array();
3558 $this->currentMessage
= $attrs['name'];
3561 $this->status
= 'portType';
3562 $this->portTypes
[$attrs['name']] = array();
3563 $this->currentPortType
= $attrs['name'];
3566 if (isset($attrs['name'])) {
3568 if (strpos($attrs['name'], ':')) {
3569 $this->currentBinding
= $this->getLocalPart($attrs['name']);
3571 $this->currentBinding
= $attrs['name'];
3573 $this->status
= 'binding';
3574 $this->bindings
[$this->currentBinding
]['portType'] = $this->getLocalPart($attrs['type']);
3575 $this->debug("current binding: $this->currentBinding of portType: " . $attrs['type']);
3579 $this->serviceName
= $attrs['name'];
3580 $this->status
= 'service';
3581 $this->debug('current service: ' . $this->serviceName
);
3584 foreach ($attrs as $name => $value) {
3585 $this->wsdl_info
[$name] = $value;
3593 * end-element handler
3595 * @param string $parser XML parser object
3596 * @param string $name element name
3599 function end_element($parser, $name){
3600 // unset schema status
3601 if (/*ereg('types$', $name) ||*/ ereg('schema$', $name)) {
3603 $this->schemas
[$this->currentSchema
->schemaTargetNamespace
][] = $this->currentSchema
;
3605 if ($this->status
== 'schema') {
3606 $this->currentSchema
->schemaEndElement($parser, $name);
3608 // bring depth down a notch
3611 // end documentation
3612 if ($this->documentation
) {
3613 //TODO: track the node to which documentation should be assigned; it can be a part, message, etc.
3614 //$this->portTypes[$this->currentPortType][$this->currentPortOperation]['documentation'] = $this->documentation;
3615 $this->documentation
= false;
3620 * element content handler
3622 * @param string $parser XML parser object
3623 * @param string $data element content
3626 function character_data($parser, $data)
3628 $pos = isset($this->depth_array
[$this->depth
]) ?
$this->depth_array
[$this->depth
] : 0;
3629 if (isset($this->message
[$pos]['cdata'])) {
3630 $this->message
[$pos]['cdata'] .= $data;
3632 if ($this->documentation
) {
3633 $this->documentation
.= $data;
3637 function getBindingData($binding)
3639 if (is_array($this->bindings
[$binding])) {
3640 return $this->bindings
[$binding];
3645 * returns an assoc array of operation names => operation data
3647 * @param string $bindingType eg: soap, smtp, dime (only soap is currently supported)
3651 function getOperations($bindingType = 'soap')
3654 if ($bindingType == 'soap') {
3655 $bindingType = 'http://schemas.xmlsoap.org/wsdl/soap/';
3658 foreach($this->ports
as $port => $portData) {
3659 // binding type of port matches parameter
3660 if ($portData['bindingType'] == $bindingType) {
3661 //$this->debug("getOperations for port $port");
3662 //$this->debug("port data: " . $this->varDump($portData));
3663 //$this->debug("bindings: " . $this->varDump($this->bindings[ $portData['binding'] ]));
3665 if (isset($this->bindings
[ $portData['binding'] ]['operations'])) {
3666 $ops = array_merge ($ops, $this->bindings
[ $portData['binding'] ]['operations']);
3674 * returns an associative array of data necessary for calling an operation
3676 * @param string $operation , name of operation
3677 * @param string $bindingType , type of binding eg: soap
3681 function getOperationData($operation, $bindingType = 'soap')
3683 if ($bindingType == 'soap') {
3684 $bindingType = 'http://schemas.xmlsoap.org/wsdl/soap/';
3687 foreach($this->ports
as $port => $portData) {
3688 // binding type of port matches parameter
3689 if ($portData['bindingType'] == $bindingType) {
3691 //foreach($this->bindings[ $portData['binding'] ]['operations'] as $bOperation => $opData) {
3692 foreach(array_keys($this->bindings
[ $portData['binding'] ]['operations']) as $bOperation) {
3693 if ($operation == $bOperation) {
3694 $opData = $this->bindings
[ $portData['binding'] ]['operations'][$operation];
3703 * returns an array of information about a given type
3704 * returns false if no type exists by the given name
3707 * 'elements' => array(), // refs to elements array
3708 * 'restrictionBase' => '',
3710 * 'order' => '(sequence|all)',
3711 * 'attrs' => array() // refs to attributes array
3714 * @param $type string
3720 function getTypeDef($type, $ns) {
3721 if ((! $ns) && isset($this->namespaces
['tns'])) {
3722 $ns = $this->namespaces
['tns'];
3724 if (isset($this->schemas
[$ns])) {
3725 foreach ($this->schemas
[$ns] as $xs) {
3726 $t = $xs->getTypeDef($type);
3727 $this->debug_str
.= $xs->debug_str
;
3728 $xs->debug_str
= '';
3738 * serialize the parsed wsdl
3740 * @return string , serialization of WSDL
3743 function serialize()
3745 $xml = '<?xml version="1.0" encoding="ISO-8859-1"?><definitions';
3746 foreach($this->namespaces
as $k => $v) {
3747 $xml .= " xmlns:$k=\"$v\"";
3749 // 10.9.02 - add poulter fix for wsdl and tns declarations
3750 if (isset($this->namespaces
['wsdl'])) {
3751 $xml .= " xmlns=\"" . $this->namespaces
['wsdl'] . "\"";
3753 if (isset($this->namespaces
['tns'])) {
3754 $xml .= " targetNamespace=\"" . $this->namespaces
['tns'] . "\"";
3758 if (sizeof($this->import
) > 0) {
3759 foreach($this->import
as $ns => $list) {
3760 foreach ($list as $ii) {
3761 if ($ii['location'] != '') {
3762 $xml .= '<import location="' . $ii['location'] . '" namespace="' . $ns . '" />';
3764 $xml .= '<import namespace="' . $ns . '" />';
3770 if (count($this->schemas
)>=1) {
3772 foreach ($this->schemas
as $ns => $list) {
3773 foreach ($list as $xs) {
3774 $xml .= $xs->serializeSchema();
3780 if (count($this->messages
) >= 1) {
3781 foreach($this->messages
as $msgName => $msgParts) {
3782 $xml .= '<message name="' . $msgName . '">';
3783 if(is_array($msgParts)){
3784 foreach($msgParts as $partName => $partType) {
3785 // print 'serializing '.$partType.', sv: '.$this->XMLSchemaVersion.'<br>';
3786 if (strpos($partType, ':')) {
3787 $typePrefix = $this->getPrefixFromNamespace($this->getPrefix($partType));
3788 } elseif (isset($this->typemap
[$this->namespaces
['xsd']][$partType])) {
3789 // print 'checking typemap: '.$this->XMLSchemaVersion.'<br>';
3790 $typePrefix = 'xsd';
3792 foreach($this->typemap
as $ns => $types) {
3793 if (isset($types[$partType])) {
3794 $typePrefix = $this->getPrefixFromNamespace($ns);
3797 if (!isset($typePrefix)) {
3798 die("$partType has no namespace!");
3801 $xml .= '<part name="' . $partName . '" type="' . $typePrefix . ':' . $this->getLocalPart($partType) . '" />';
3804 $xml .= '</message>';
3807 // bindings & porttypes
3808 if (count($this->bindings
) >= 1) {
3811 foreach($this->bindings
as $bindingName => $attrs) {
3812 $binding_xml .= '<binding name="' . $bindingName . '" type="tns:' . $attrs['portType'] . '">';
3813 $binding_xml .= '<soap:binding style="' . $attrs['style'] . '" transport="' . $attrs['transport'] . '"/>';
3814 $portType_xml .= '<portType name="' . $attrs['portType'] . '">';
3815 foreach($attrs['operations'] as $opName => $opParts) {
3816 $binding_xml .= '<operation name="' . $opName . '">';
3817 $binding_xml .= '<soap:operation soapAction="' . $opParts['soapAction'] . '" style="'. $attrs['style'] . '"/>';
3818 if (isset($opParts['input']['encodingStyle']) && $opParts['input']['encodingStyle'] != '') {
3819 $enc_style = ' encodingStyle="' . $opParts['input']['encodingStyle'] . '"';
3823 $binding_xml .= '<input><soap:body use="' . $opParts['input']['use'] . '" namespace="' . $opParts['input']['namespace'] . '"' . $enc_style . '/></input>';
3824 if (isset($opParts['output']['encodingStyle']) && $opParts['output']['encodingStyle'] != '') {
3825 $enc_style = ' encodingStyle="' . $opParts['output']['encodingStyle'] . '"';
3829 $binding_xml .= '<output><soap:body use="' . $opParts['output']['use'] . '" namespace="' . $opParts['output']['namespace'] . '"' . $enc_style . '/></output>';
3830 $binding_xml .= '</operation>';
3831 $portType_xml .= '<operation name="' . $opParts['name'] . '"';
3832 if (isset($opParts['parameterOrder'])) {
3833 $portType_xml .= ' parameterOrder="' . $opParts['parameterOrder'] . '"';
3835 $portType_xml .= '>';
3836 if(isset($opParts['documentation']) && $opParts['documentation'] != '') {
3837 $portType_xml .= '<documentation>' . htmlspecialchars($opParts['documentation']) . '</documentation>';
3839 $portType_xml .= '<input message="tns:' . $opParts['input']['message'] . '"/>';
3840 $portType_xml .= '<output message="tns:' . $opParts['output']['message'] . '"/>';
3841 $portType_xml .= '</operation>';
3843 $portType_xml .= '</portType>';
3844 $binding_xml .= '</binding>';
3846 $xml .= $portType_xml . $binding_xml;
3849 $xml .= '<service name="' . $this->serviceName
. '">';
3850 if (count($this->ports
) >= 1) {
3851 foreach($this->ports
as $pName => $attrs) {
3852 $xml .= '<port name="' . $pName . '" binding="tns:' . $attrs['binding'] . '">';
3853 $xml .= '<soap:address location="' . $attrs['location'] . '"/>';
3857 $xml .= '</service>';
3858 return $xml . '</definitions>';
3862 * serialize a PHP value according to a WSDL message definition
3865 * - multi-ref serialization
3866 * - validate PHP values against type definitions, return errors if invalid
3868 * @param string $ type name
3869 * @param mixed $ param value
3870 * @return mixed new param or false if initial value didn't validate
3872 function serializeRPCParameters($operation, $direction, $parameters)
3874 $this->debug('in serializeRPCParameters with operation '.$operation.', direction '.$direction.' and '.count($parameters).' param(s), and xml schema version ' . $this->XMLSchemaVersion
);
3876 if ($direction != 'input' && $direction != 'output') {
3877 $this->debug('The value of the \$direction argument needs to be either "input" or "output"');
3878 $this->setError('The value of the \$direction argument needs to be either "input" or "output"');
3881 if (!$opData = $this->getOperationData($operation)) {
3882 $this->debug('Unable to retrieve WSDL data for operation: ' . $operation);
3883 $this->setError('Unable to retrieve WSDL data for operation: ' . $operation);
3886 $this->debug($this->varDump($opData));
3888 // Get encoding style for output and set to current
3889 $encodingStyle = 'http://schemas.xmlsoap.org/soap/encoding/';
3890 if(($direction == 'input') && isset($opData['output']['encodingStyle']) && ($opData['output']['encodingStyle'] != $encodingStyle)) {
3891 $encodingStyle = $opData['output']['encodingStyle'];
3892 $enc_style = $encodingStyle;
3897 if (isset($opData[$direction]['parts']) && sizeof($opData[$direction]['parts']) > 0) {
3899 $use = $opData[$direction]['use'];
3900 $this->debug("use=$use");
3901 $this->debug('got ' . count($opData[$direction]['parts']) . ' part(s)');
3902 if (is_array($parameters)) {
3903 $parametersArrayType = $this->isArraySimpleOrStruct($parameters);
3904 $this->debug('have ' . $parametersArrayType . ' parameters');
3905 foreach($opData[$direction]['parts'] as $name => $type) {
3906 $this->debug('serializing part "'.$name.'" of type "'.$type.'"');
3907 // Track encoding style
3908 if (isset($opData[$direction]['encodingStyle']) && $encodingStyle != $opData[$direction]['encodingStyle']) {
3909 $encodingStyle = $opData[$direction]['encodingStyle'];
3910 $enc_style = $encodingStyle;
3914 // NOTE: add error handling here
3915 // if serializeType returns false, then catch global error and fault
3916 if ($parametersArrayType == 'arraySimple') {
3917 $p = array_shift($parameters);
3918 $this->debug('calling serializeType w/indexed param');
3919 $xml .= $this->serializeType($name, $type, $p, $use, $enc_style);
3920 } elseif (isset($parameters[$name])) {
3921 $this->debug('calling serializeType w/named param');
3922 $xml .= $this->serializeType($name, $type, $parameters[$name], $use, $enc_style);
3924 // TODO: only send nillable
3925 $this->debug('calling serializeType w/null param');
3926 $xml .= $this->serializeType($name, $type, null, $use, $enc_style);
3930 $this->debug('no parameters passed.');
3937 * serialize a PHP value according to a WSDL message definition
3940 * - multi-ref serialization
3941 * - validate PHP values against type definitions, return errors if invalid
3943 * @param string $ type name
3944 * @param mixed $ param value
3945 * @return mixed new param or false if initial value didn't validate
3947 function serializeParameters($operation, $direction, $parameters)
3949 $this->debug('in serializeParameters with operation '.$operation.', direction '.$direction.' and '.count($parameters).' param(s), and xml schema version ' . $this->XMLSchemaVersion
);
3951 if ($direction != 'input' && $direction != 'output') {
3952 $this->debug('The value of the \$direction argument needs to be either "input" or "output"');
3953 $this->setError('The value of the \$direction argument needs to be either "input" or "output"');
3956 if (!$opData = $this->getOperationData($operation)) {
3957 $this->debug('Unable to retrieve WSDL data for operation: ' . $operation);
3958 $this->setError('Unable to retrieve WSDL data for operation: ' . $operation);
3961 $this->debug($this->varDump($opData));
3963 // Get encoding style for output and set to current
3964 $encodingStyle = 'http://schemas.xmlsoap.org/soap/encoding/';
3965 if(($direction == 'input') && isset($opData['output']['encodingStyle']) && ($opData['output']['encodingStyle'] != $encodingStyle)) {
3966 $encodingStyle = $opData['output']['encodingStyle'];
3967 $enc_style = $encodingStyle;
3972 if (isset($opData[$direction]['parts']) && sizeof($opData[$direction]['parts']) > 0) {
3974 $use = $opData[$direction]['use'];
3975 $this->debug("use=$use");
3976 $this->debug('got ' . count($opData[$direction]['parts']) . ' part(s)');
3977 if (is_array($parameters)) {
3978 $parametersArrayType = $this->isArraySimpleOrStruct($parameters);
3979 $this->debug('have ' . $parametersArrayType . ' parameters');
3980 foreach($opData[$direction]['parts'] as $name => $type) {
3981 $this->debug('serializing part "'.$name.'" of type "'.$type.'"');
3982 // Track encoding style
3983 if(isset($opData[$direction]['encodingStyle']) && $encodingStyle != $opData[$direction]['encodingStyle']) {
3984 $encodingStyle = $opData[$direction]['encodingStyle'];
3985 $enc_style = $encodingStyle;
3989 // NOTE: add error handling here
3990 // if serializeType returns false, then catch global error and fault
3991 if ($parametersArrayType == 'arraySimple') {
3992 $p = array_shift($parameters);
3993 $this->debug('calling serializeType w/indexed param');
3994 $xml .= $this->serializeType($name, $type, $p, $use, $enc_style);
3995 } elseif (isset($parameters[$name])) {
3996 $this->debug('calling serializeType w/named param');
3997 $xml .= $this->serializeType($name, $type, $parameters[$name], $use, $enc_style);
3999 // TODO: only send nillable
4000 $this->debug('calling serializeType w/null param');
4001 $xml .= $this->serializeType($name, $type, null, $use, $enc_style);
4005 $this->debug('no parameters passed.');
4012 * serializes a PHP value according a given type definition
4014 * @param string $name , name of type (part)
4015 * @param string $type , type of type, heh (type or element)
4016 * @param mixed $value , a native PHP value (parameter value)
4017 * @param string $use , use for part (encoded|literal)
4018 * @param string $encodingStyle , use to add encoding changes to serialisation
4019 * @return string serialization
4022 function serializeType($name, $type, $value, $use='encoded', $encodingStyle=false)
4024 $this->debug("in serializeType: $name, $type, $value, $use, $encodingStyle");
4025 if($use == 'encoded' && $encodingStyle) {
4026 $encodingStyle = ' SOAP-ENV:encodingStyle="' . $encodingStyle . '"';
4029 // if a soap_val has been supplied, let its type override the WSDL
4030 if (is_object($value) && get_class($value) == 'soapval') {
4031 // TODO: get attributes from soapval?
4032 if ($value->type_ns
) {
4033 $type = $value->type_ns
. ':' . $value->type
;
4035 $type = $value->type
;
4037 $value = $value->value
;
4039 $this->debug("in serializeType: soapval overrides type to $type, value to $value");
4045 if (strpos($type, ':')) {
4046 $uqType = substr($type, strrpos($type, ':') +
1);
4047 $ns = substr($type, 0, strrpos($type, ':'));
4048 $this->debug("got a prefixed type: $uqType, $ns");
4049 if ($this->getNamespaceFromPrefix($ns)) {
4050 $ns = $this->getNamespaceFromPrefix($ns);
4051 $this->debug("expanded prefixed type: $uqType, $ns");
4054 if($ns == $this->XMLSchemaVersion
){
4056 if (is_null($value)) {
4057 if ($use == 'literal') {
4058 // TODO: depends on nillable
4061 return "<$name xsi:nil=\"true\"/>";
4064 if ($uqType == 'boolean' && !$value) {
4066 } elseif ($uqType == 'boolean') {
4069 if ($uqType == 'string' && gettype($value) == 'string') {
4070 $value = $this->expandEntities($value);
4073 // TODO: what about null/nil values?
4074 // check type isn't a custom type extending xmlschema namespace
4075 if (!$this->getTypeDef($uqType, $ns)) {
4076 if ($use == 'literal') {
4078 return "<$name xsi:type=\"" . $this->getPrefixFromNamespace($this->XMLSchemaVersion
) . ":$uqType\">$value</$name>";
4080 return "<$name>$value</$name>";
4083 return "<$name xsi:type=\"" . $this->getPrefixFromNamespace($this->XMLSchemaVersion
) . ":$uqType\"$encodingStyle>$value</$name>";
4086 } else if ($ns == 'http://xml.apache.org/xml-soap') {
4087 if ($uqType == 'Map') {
4089 foreach($value as $k => $v) {
4090 $this->debug("serializing map element: key $k, value $v");
4091 $contents .= '<item>';
4092 $contents .= $this->serialize_val($k,'key',false,false,false,false,$use);
4093 $contents .= $this->serialize_val($v,'value',false,false,false,false,$use);
4094 $contents .= '</item>';
4096 if ($use == 'literal') {
4098 return "<$name xsi:type=\"" . $this->getPrefixFromNamespace('http://xml.apache.org/xml-soap') . ":$uqType\">$contents</$name>";
4100 return "<$name>$contents</$name>";
4103 return "<$name xsi:type=\"" . $this->getPrefixFromNamespace('http://xml.apache.org/xml-soap') . ":$uqType\"$encodingStyle>$contents</$name>";
4108 $this->debug("No namespace for type $type");
4112 if(!$typeDef = $this->getTypeDef($uqType, $ns)){
4113 $this->setError("$type ($uqType) is not a supported type.");
4114 $this->debug("$type ($uqType) is not a supported type.");
4117 foreach($typeDef as $k => $v) {
4118 $this->debug("typedef, $k: $v");
4121 $phpType = $typeDef['phpType'];
4122 $this->debug("serializeType: uqType: $uqType, ns: $ns, phptype: $phpType, arrayType: " . (isset($typeDef['arrayType']) ?
$typeDef['arrayType'] : '') );
4123 // if php type == struct, map value to the <all> element names
4124 if ($phpType == 'struct') {
4125 if (isset($typeDef['typeClass']) && $typeDef['typeClass'] == 'element') {
4126 $elementName = $uqType;
4127 if (isset($typeDef['form']) && ($typeDef['form'] == 'qualified')) {
4128 $elementNS = " xmlns=\"$ns\"";
4131 $elementName = $name;
4134 if (is_null($value)) {
4135 if ($use == 'literal') {
4136 // TODO: depends on nillable
4137 return "<$elementName$elementNS/>";
4139 return "<$elementName$elementNS xsi:nil=\"true\"/>";
4142 if ($use == 'literal') {
4144 $xml = "<$elementName$elementNS xsi:type=\"" . $this->getPrefixFromNamespace($ns) . ":$uqType\">";
4146 $xml = "<$elementName$elementNS>";
4149 $xml = "<$elementName$elementNS xsi:type=\"" . $this->getPrefixFromNamespace($ns) . ":$uqType\"$encodingStyle>";
4152 if (isset($typeDef['elements']) && is_array($typeDef['elements'])) {
4153 if (is_array($value)) {
4155 } elseif (is_object($value)) {
4156 $xvalue = get_object_vars($value);
4158 $this->debug("value is neither an array nor an object for XML Schema type $ns:$uqType");
4161 // toggle whether all elements are present - ideally should validate against schema
4162 if(count($typeDef['elements']) != count($xvalue)){
4165 foreach($typeDef['elements'] as $eName => $attrs) {
4166 // if user took advantage of a minOccurs=0, then only serialize named parameters
4167 if(isset($optionals) && !isset($xvalue[$eName])){
4171 if (isset($xvalue[$eName])) {
4172 $v = $xvalue[$eName];
4176 // TODO: if maxOccurs > 1 (not just unbounded), then allow serialization of an array
4177 if (isset($attrs['maxOccurs']) && $attrs['maxOccurs'] == 'unbounded' && isset($v) && is_array($v) && $this->isArraySimpleOrStruct($v) == 'arraySimple') {
4179 foreach ($vv as $k => $v) {
4180 if (isset($attrs['type'])) {
4181 // serialize schema-defined type
4182 $xml .= $this->serializeType($eName, $attrs['type'], $v, $use, $encodingStyle);
4184 // serialize generic type
4185 $this->debug("calling serialize_val() for $v, $eName, false, false, false, false, $use");
4186 $xml .= $this->serialize_val($v, $eName, false, false, false, false, $use);
4190 if (isset($attrs['type'])) {
4191 // serialize schema-defined type
4192 $xml .= $this->serializeType($eName, $attrs['type'], $v, $use, $encodingStyle);
4194 // serialize generic type
4195 $this->debug("calling serialize_val() for $v, $eName, false, false, false, false, $use");
4196 $xml .= $this->serialize_val($v, $eName, false, false, false, false, $use);
4202 $this->debug("Expected elements for XML Schema type $ns:$uqType");
4204 $xml .= "</$elementName>";
4205 } elseif ($phpType == 'array') {
4206 if (isset($typeDef['form']) && ($typeDef['form'] == 'qualified')) {
4207 $elementNS = " xmlns=\"$ns\"";
4211 if (is_null($value)) {
4212 if ($use == 'literal') {
4213 // TODO: depends on nillable
4214 return "<$name$elementNS/>";
4216 return "<$name$elementNS xsi:nil=\"true\"/>";
4219 if (isset($typeDef['multidimensional'])) {
4221 foreach($value as $v) {
4222 $cols = ',' . sizeof($v);
4223 $nv = array_merge($nv, $v);
4229 if (is_array($value) && sizeof($value) >= 1) {
4230 $rows = sizeof($value);
4232 foreach($value as $k => $v) {
4233 $this->debug("serializing array element: $k, $v of type: $typeDef[arrayType]");
4234 //if (strpos($typeDef['arrayType'], ':') ) {
4235 if (!in_array($typeDef['arrayType'],$this->typemap
['http://www.w3.org/2001/XMLSchema'])) {
4236 $contents .= $this->serializeType('item', $typeDef['arrayType'], $v, $use);
4238 $contents .= $this->serialize_val($v, 'item', $typeDef['arrayType'], null, $this->XMLSchemaVersion
, false, $use);
4241 $this->debug('contents: '.$this->varDump($contents));
4246 // TODO: for now, an empty value will be serialized as a zero element
4247 // array. Revisit this when coding the handling of null/nil values.
4248 if ($use == 'literal') {
4249 $xml = "<$name$elementNS>"
4253 $xml = "<$name$elementNS xsi:type=\"".$this->getPrefixFromNamespace('http://schemas.xmlsoap.org/soap/encoding/').':Array" '.
4254 $this->getPrefixFromNamespace('http://schemas.xmlsoap.org/soap/encoding/')
4256 .$this->getPrefixFromNamespace($this->getPrefix($typeDef['arrayType']))
4257 .":".$this->getLocalPart($typeDef['arrayType'])."[$rows$cols]\">"
4261 } elseif ($phpType == 'scalar') {
4262 if (isset($typeDef['form']) && ($typeDef['form'] == 'qualified')) {
4263 $elementNS = " xmlns=\"$ns\"";
4267 if ($use == 'literal') {
4269 return "<$name$elementNS xsi:type=\"" . $this->getPrefixFromNamespace($ns) . ":$uqType\">$value</$name>";
4271 return "<$name$elementNS>$value</$name>";
4274 return "<$name$elementNS xsi:type=\"" . $this->getPrefixFromNamespace($ns) . ":$uqType\"$encodingStyle>$value</$name>";
4277 $this->debug('returning: '.$this->varDump($xml));
4282 * adds an XML Schema complex type to the WSDL types
4285 * @param typeClass (complexType|simpleType|attribute)
4286 * @param phpType: currently supported are array and struct (php assoc array)
4287 * @param compositor (all|sequence|choice)
4288 * @param restrictionBase namespace:name (http://schemas.xmlsoap.org/soap/encoding/:Array)
4289 * @param elements = array ( name = array(name=>'',type=>'') )
4290 * @param attrs = array(
4292 * 'ref' => "http://schemas.xmlsoap.org/soap/encoding/:arrayType",
4293 * "http://schemas.xmlsoap.org/wsdl/:arrayType" => "string[]"
4296 * @param arrayType: namespace:name (http://www.w3.org/2001/XMLSchema:string)
4300 function addComplexType($name,$typeClass='complexType',$phpType='array',$compositor='',$restrictionBase='',$elements=array(),$attrs=array(),$arrayType='') {
4301 if (count($elements) > 0) {
4302 foreach($elements as $n => $e){
4303 // expand each element
4304 foreach ($e as $k => $v) {
4305 $k = strpos($k,':') ?
$this->expandQname($k) : $k;
4306 $v = strpos($v,':') ?
$this->expandQname($v) : $v;
4309 $eElements[$n] = $ee;
4311 $elements = $eElements;
4314 if (count($attrs) > 0) {
4315 foreach($attrs as $n => $a){
4316 // expand each attribute
4317 foreach ($a as $k => $v) {
4318 $k = strpos($k,':') ?
$this->expandQname($k) : $k;
4319 $v = strpos($v,':') ?
$this->expandQname($v) : $v;
4327 $restrictionBase = strpos($restrictionBase,':') ?
$this->expandQname($restrictionBase) : $restrictionBase;
4328 $arrayType = strpos($arrayType,':') ?
$this->expandQname($arrayType) : $arrayType;
4330 $typens = isset($this->namespaces
['types']) ?
$this->namespaces
['types'] : $this->namespaces
['tns'];
4331 $this->schemas
[$typens][0]->addComplexType($name,$typeClass,$phpType,$compositor,$restrictionBase,$elements,$attrs,$arrayType);
4335 * adds an XML Schema simple type to the WSDL types
4338 * @param restrictionBase namespace:name (http://schemas.xmlsoap.org/soap/encoding/:Array)
4339 * @param typeClass (simpleType)
4340 * @param phpType: (scalar)
4344 function addSimpleType($name, $restrictionBase='', $typeClass='simpleType', $phpType='scalar') {
4345 $restrictionBase = strpos($restrictionBase,':') ?
$this->expandQname($restrictionBase) : $restrictionBase;
4347 $typens = isset($this->namespaces
['types']) ?
$this->namespaces
['types'] : $this->namespaces
['tns'];
4348 $this->schemas
[$typens][0]->addSimpleType($name, $restrictionBase, $typeClass, $phpType);
4352 * register a service with the server
4354 * @param string $methodname
4355 * @param string $in assoc array of input values: key = param name, value = param type
4356 * @param string $out assoc array of output values: key = param name, value = param type
4357 * @param string $namespace optional The namespace for the operation
4358 * @param string $soapaction optional The soapaction for the operation
4359 * @param string $style (rpc|document) optional The style for the operation
4360 * @param string $use (encoded|literal) optional The use for the parameters (cannot mix right now)
4361 * @param string $documentation optional The description to include in the WSDL
4364 function addOperation($name, $in = false, $out = false, $namespace = false, $soapaction = false, $style = 'rpc', $use = 'encoded', $documentation = ''){
4365 if ($style == 'rpc' && $use == 'encoded') {
4366 $encodingStyle = 'http://schemas.xmlsoap.org/soap/encoding/';
4368 $encodingStyle = '';
4371 $this->bindings
[ $this->serviceName
. 'Binding' ]['operations'][$name] =
4374 'binding' => $this->serviceName
. 'Binding',
4375 'endpoint' => $this->endpoint
,
4376 'soapAction' => $soapaction,
4380 'namespace' => $namespace,
4381 'encodingStyle' => $encodingStyle,
4382 'message' => $name . 'Request',
4386 'namespace' => $namespace,
4387 'encodingStyle' => $encodingStyle,
4388 'message' => $name . 'Response',
4390 'namespace' => $namespace,
4391 'transport' => 'http://schemas.xmlsoap.org/soap/http',
4392 'documentation' => $documentation);
4397 foreach($in as $pName => $pType)
4399 if(strpos($pType,':')) {
4400 $pType = $this->getNamespaceFromPrefix($this->getPrefix($pType)).":".$this->getLocalPart($pType);
4402 $this->messages
[$name.'Request'][$pName] = $pType;
4405 $this->messages
[$name.'Request']= '0';
4409 foreach($out as $pName => $pType)
4411 if(strpos($pType,':')) {
4412 $pType = $this->getNamespaceFromPrefix($this->getPrefix($pType)).":".$this->getLocalPart($pType);
4414 $this->messages
[$name.'Response'][$pName] = $pType;
4417 $this->messages
[$name.'Response']= '0';
4428 * soap_parser class parses SOAP XML messages into native PHP values
4430 * @author Dietrich Ayala <dietrich@ganx4.com>
4434 class soap_parser
extends nusoap_base
{
4437 var $xml_encoding = '';
4439 var $root_struct = '';
4440 var $root_struct_name = '';
4441 var $root_struct_namespace = '';
4442 var $root_header = '';
4443 var $document = ''; // incoming SOAP body (text)
4444 // determines where in the message we are (envelope,header,body,method)
4448 var $default_namespace = '';
4449 var $namespaces = array();
4450 var $message = array();
4453 var $fault_code = '';
4454 var $fault_str = '';
4455 var $fault_detail = '';
4456 var $depth_array = array();
4457 var $debug_flag = true;
4458 var $soapresponse = NULL;
4459 var $responseHeaders = ''; // incoming SOAP headers (text)
4460 var $body_position = 0;
4461 // for multiref parsing:
4462 // array of id => pos
4464 // array of id => hrefs => pos
4465 var $multirefs = array();
4466 // toggle for auto-decoding element content
4467 var $decode_utf8 = true;
4472 * @param string $xml SOAP message
4473 * @param string $encoding character encoding scheme of message
4474 * @param string $method
4475 * @param string $decode_utf8 whether to decode UTF-8 to ISO-8859-1
4478 function soap_parser($xml,$encoding='UTF-8',$method='',$decode_utf8=true){
4480 $this->xml_encoding
= $encoding;
4481 $this->method
= $method;
4482 $this->decode_utf8
= $decode_utf8;
4484 // Check whether content has been read.
4486 $this->debug('Entering soap_parser(), length='.strlen($xml).', encoding='.$encoding);
4487 // Create an XML parser - why not xml_parser_create_ns?
4488 $this->parser
= xml_parser_create($this->xml_encoding
);
4489 // Set the options for parsing the XML data.
4490 //xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 1);
4491 xml_parser_set_option($this->parser
, XML_OPTION_CASE_FOLDING
, 0);
4492 xml_parser_set_option($this->parser
, XML_OPTION_TARGET_ENCODING
, $this->xml_encoding
);
4493 // Set the object for the parser.
4494 xml_set_object($this->parser
, $this);
4495 // Set the element handlers for the parser.
4496 xml_set_element_handler($this->parser
, 'start_element','end_element');
4497 xml_set_character_data_handler($this->parser
,'character_data');
4499 // Parse the XML file.
4500 if(!xml_parse($this->parser
,$xml,true)){
4501 // Display an error message.
4502 $err = sprintf('XML error parsing SOAP payload on line %d: %s',
4503 xml_get_current_line_number($this->parser
),
4504 xml_error_string(xml_get_error_code($this->parser
)));
4506 $this->debug("XML payload:\n" . $xml);
4507 $this->setError($err);
4509 $this->debug('parsed successfully, found root struct: '.$this->root_struct
.' of name '.$this->root_struct_name
);
4511 $this->soapresponse
= $this->message
[$this->root_struct
]['result'];
4512 // get header value: no, because this is documented as XML string
4513 // if($this->root_header != '' && isset($this->message[$this->root_header]['result'])){
4514 // $this->responseHeaders = $this->message[$this->root_header]['result'];
4516 // resolve hrefs/ids
4517 if(sizeof($this->multirefs
) > 0){
4518 foreach($this->multirefs
as $id => $hrefs){
4519 $this->debug('resolving multirefs for id: '.$id);
4520 $idVal = $this->buildVal($this->ids
[$id]);
4521 foreach($hrefs as $refPos => $ref){
4522 $this->debug('resolving href at pos '.$refPos);
4523 $this->multirefs
[$id][$refPos] = $idVal;
4528 xml_parser_free($this->parser
);
4530 $this->debug('xml was empty, didn\'t parse!');
4531 $this->setError('xml was empty, didn\'t parse!');
4536 * start-element handler
4538 * @param string $parser XML parser object
4539 * @param string $name element name
4540 * @param string $attrs associative array of attributes
4543 function start_element($parser, $name, $attrs) {
4544 // position in a total number of elements, starting from 0
4545 // update class level pos
4546 $pos = $this->position++
;
4548 $this->message
[$pos] = array('pos' => $pos,'children'=>'','cdata'=>'');
4549 // depth = how many levels removed from root?
4550 // set mine as current global depth and increment global depth value
4551 $this->message
[$pos]['depth'] = $this->depth++
;
4553 // else add self as child to whoever the current parent is
4555 $this->message
[$this->parent
]['children'] .= '|'.$pos;
4558 $this->message
[$pos]['parent'] = $this->parent
;
4559 // set self as current parent
4560 $this->parent
= $pos;
4561 // set self as current value for this depth
4562 $this->depth_array
[$this->depth
] = $pos;
4563 // get element prefix
4564 if(strpos($name,':')){
4566 $prefix = substr($name,0,strpos($name,':'));
4567 // get unqualified name
4568 $name = substr(strstr($name,':'),1);
4571 if($name == 'Envelope'){
4572 $this->status
= 'envelope';
4573 } elseif($name == 'Header'){
4574 $this->root_header
= $pos;
4575 $this->status
= 'header';
4576 } elseif($name == 'Body'){
4577 $this->status
= 'body';
4578 $this->body_position
= $pos;
4580 } elseif($this->status
== 'body' && $pos == ($this->body_position+
1)){
4581 $this->status
= 'method';
4582 $this->root_struct_name
= $name;
4583 $this->root_struct
= $pos;
4584 $this->message
[$pos]['type'] = 'struct';
4585 $this->debug("found root struct $this->root_struct_name, pos $this->root_struct");
4588 $this->message
[$pos]['status'] = $this->status
;
4590 $this->message
[$pos]['name'] = htmlspecialchars($name);
4592 $this->message
[$pos]['attrs'] = $attrs;
4594 // loop through atts, logging ns and type declarations
4596 foreach($attrs as $key => $value){
4597 $key_prefix = $this->getPrefix($key);
4598 $key_localpart = $this->getLocalPart($key);
4599 // if ns declarations, add to class level array of valid namespaces
4600 if($key_prefix == 'xmlns'){
4601 if(ereg('^http://www.w3.org/[0-9]{4}/XMLSchema$',$value)){
4602 $this->XMLSchemaVersion
= $value;
4603 $this->namespaces
['xsd'] = $this->XMLSchemaVersion
;
4604 $this->namespaces
['xsi'] = $this->XMLSchemaVersion
.'-instance';
4606 $this->namespaces
[$key_localpart] = $value;
4607 // set method namespace
4608 if($name == $this->root_struct_name
){
4609 $this->methodNamespace
= $value;
4611 // if it's a type declaration, set type
4612 } elseif($key_localpart == 'type'){
4613 $value_prefix = $this->getPrefix($value);
4614 $value_localpart = $this->getLocalPart($value);
4615 $this->message
[$pos]['type'] = $value_localpart;
4616 $this->message
[$pos]['typePrefix'] = $value_prefix;
4617 if(isset($this->namespaces
[$value_prefix])){
4618 $this->message
[$pos]['type_namespace'] = $this->namespaces
[$value_prefix];
4619 } else if(isset($attrs['xmlns:'.$value_prefix])) {
4620 $this->message
[$pos]['type_namespace'] = $attrs['xmlns:'.$value_prefix];
4622 // should do something here with the namespace of specified type?
4623 } elseif($key_localpart == 'arrayType'){
4624 $this->message
[$pos]['type'] = 'array';
4625 /* do arrayType ereg here
4626 [1] arrayTypeValue ::= atype asize
4627 [2] atype ::= QName rank*
4628 [3] rank ::= '[' (',')* ']'
4629 [4] asize ::= '[' length~ ']'
4630 [5] length ::= nextDimension* Digit+
4631 [6] nextDimension ::= Digit+ ','
4633 $expr = '([A-Za-z0-9_]+):([A-Za-z]+[A-Za-z0-9_]+)\[([0-9]+),?([0-9]*)\]';
4634 if(ereg($expr,$value,$regs)){
4635 $this->message
[$pos]['typePrefix'] = $regs[1];
4636 $this->message
[$pos]['arrayTypePrefix'] = $regs[1];
4637 if (isset($this->namespaces
[$regs[1]])) {
4638 $this->message
[$pos]['arrayTypeNamespace'] = $this->namespaces
[$regs[1]];
4639 } else if (isset($attrs['xmlns:'.$regs[1]])) {
4640 $this->message
[$pos]['arrayTypeNamespace'] = $attrs['xmlns:'.$regs[1]];
4642 $this->message
[$pos]['arrayType'] = $regs[2];
4643 $this->message
[$pos]['arraySize'] = $regs[3];
4644 $this->message
[$pos]['arrayCols'] = $regs[4];
4649 $this->ids
[$value] = $pos;
4652 if($key_localpart == 'root' && $value == 1){
4653 $this->status
= 'method';
4654 $this->root_struct_name
= $name;
4655 $this->root_struct
= $pos;
4656 $this->debug("found root struct $this->root_struct_name, pos $pos");
4659 $attstr .= " $key=\"$value\"";
4661 // get namespace - must be done after namespace atts are processed
4663 $this->message
[$pos]['namespace'] = $this->namespaces
[$prefix];
4664 $this->default_namespace
= $this->namespaces
[$prefix];
4666 $this->message
[$pos]['namespace'] = $this->default_namespace
;
4668 if($this->status
== 'header'){
4669 if ($this->root_header
!= $pos) {
4670 $this->responseHeaders
.= "<" . (isset($prefix) ?
$prefix . ':' : '') . "$name$attstr>";
4672 } elseif($this->root_struct_name
!= ''){
4673 $this->document
.= "<" . (isset($prefix) ?
$prefix . ':' : '') . "$name$attstr>";
4678 * end-element handler
4680 * @param string $parser XML parser object
4681 * @param string $name element name
4684 function end_element($parser, $name) {
4685 // position of current element is equal to the last value left in depth_array for my depth
4686 $pos = $this->depth_array
[$this->depth
--];
4688 // get element prefix
4689 if(strpos($name,':')){
4691 $prefix = substr($name,0,strpos($name,':'));
4692 // get unqualified name
4693 $name = substr(strstr($name,':'),1);
4696 // build to native type
4697 if(isset($this->body_position
) && $pos > $this->body_position
){
4698 // deal w/ multirefs
4699 if(isset($this->message
[$pos]['attrs']['href'])){
4701 $id = substr($this->message
[$pos]['attrs']['href'],1);
4702 // add placeholder to href array
4703 $this->multirefs
[$id][$pos] = 'placeholder';
4704 // add set a reference to it as the result value
4705 $this->message
[$pos]['result'] =& $this->multirefs
[$id][$pos];
4706 // build complex values
4707 } elseif($this->message
[$pos]['children'] != ''){
4709 // if result has already been generated (struct/array
4710 if(!isset($this->message
[$pos]['result'])){
4711 $this->message
[$pos]['result'] = $this->buildVal($pos);
4714 // set value of simple type
4716 //$this->debug('adding data for scalar value '.$this->message[$pos]['name'].' of value '.$this->message[$pos]['cdata']);
4717 if (isset($this->message
[$pos]['type'])) {
4718 $this->message
[$pos]['result'] = $this->decodeSimple($this->message
[$pos]['cdata'], $this->message
[$pos]['type'], isset($this->message
[$pos]['type_namespace']) ?
$this->message
[$pos]['type_namespace'] : '');
4720 $parent = $this->message
[$pos]['parent'];
4721 if (isset($this->message
[$parent]['type']) && ($this->message
[$parent]['type'] == 'array') && isset($this->message
[$parent]['arrayType'])) {
4722 $this->message
[$pos]['result'] = $this->decodeSimple($this->message
[$pos]['cdata'], $this->message
[$parent]['arrayType'], isset($this->message
[$parent]['arrayTypeNamespace']) ?
$this->message
[$parent]['arrayTypeNamespace'] : '');
4724 $this->message
[$pos]['result'] = $this->message
[$pos]['cdata'];
4728 /* add value to parent's result, if parent is struct/array
4729 $parent = $this->message[$pos]['parent'];
4730 if($this->message[$parent]['type'] != 'map'){
4731 if(strtolower($this->message[$parent]['type']) == 'array'){
4732 $this->message[$parent]['result'][] = $this->message[$pos]['result'];
4734 $this->message[$parent]['result'][$this->message[$pos]['name']] = $this->message[$pos]['result'];
4742 if($this->status
== 'header'){
4743 if ($this->root_header
!= $pos) {
4744 $this->responseHeaders
.= "</" . (isset($prefix) ?
$prefix . ':' : '') . "$name>";
4746 } elseif($pos >= $this->root_struct
){
4747 $this->document
.= "</" . (isset($prefix) ?
$prefix . ':' : '') . "$name>";
4750 if($pos == $this->root_struct
){
4751 $this->status
= 'body';
4752 $this->root_struct_namespace
= $this->message
[$pos]['namespace'];
4753 } elseif($name == 'Body'){
4754 $this->status
= 'envelope';
4755 } elseif($name == 'Header'){
4756 $this->status
= 'envelope';
4757 } elseif($name == 'Envelope'){
4760 // set parent back to my parent
4761 $this->parent
= $this->message
[$pos]['parent'];
4765 * element content handler
4767 * @param string $parser XML parser object
4768 * @param string $data element content
4771 function character_data($parser, $data){
4772 $pos = $this->depth_array
[$this->depth
];
4773 if ($this->xml_encoding
=='UTF-8'){
4774 // TODO: add an option to disable this for folks who want
4775 // raw UTF-8 that, e.g., might not map to iso-8859-1
4776 // TODO: this can also be handled with xml_parser_set_option($this->parser, XML_OPTION_TARGET_ENCODING, "ISO-8859-1");
4777 if($this->decode_utf8
){
4778 $data = utf8_decode($data);
4781 $this->message
[$pos]['cdata'] .= $data;
4783 if($this->status
== 'header'){
4784 $this->responseHeaders
.= $data;
4786 $this->document
.= $data;
4791 * get the parsed message
4796 function get_response(){
4797 return $this->soapresponse
;
4801 * get the parsed headers
4803 * @return string XML or empty if no headers
4806 function getHeaders(){
4807 return $this->responseHeaders
;
4813 * @param string $text string to translate
4816 function decode_entities($text){
4817 foreach($this->entities
as $entity => $encoded){
4818 $text = str_replace($encoded,$entity,$text);
4824 * decodes simple types into PHP variables
4826 * @param string $value value to decode
4827 * @param string $type XML type to decode
4828 * @param string $typens XML type namespace to decode
4831 function decodeSimple($value, $type, $typens) {
4832 // TODO: use the namespace!
4833 if ((!isset($type)) ||
$type == 'string' ||
$type == 'long' ||
$type == 'unsignedLong') {
4834 return (string) $value;
4836 if ($type == 'int' ||
$type == 'integer' ||
$type == 'short' ||
$type == 'byte') {
4837 return (int) $value;
4839 if ($type == 'float' ||
$type == 'double' ||
$type == 'decimal') {
4840 return (double) $value;
4842 if ($type == 'boolean') {
4843 if (strtolower($value) == 'false' ||
strtolower($value) == 'f') {
4846 return (boolean
) $value;
4848 if ($type == 'base64' ||
$type == 'base64Binary') {
4849 return base64_decode($value);
4851 // obscure numeric types
4852 if ($type == 'nonPositiveInteger' ||
$type == 'negativeInteger'
4853 ||
$type == 'nonNegativeInteger' ||
$type == 'positiveInteger'
4854 ||
$type == 'unsignedInt'
4855 ||
$type == 'unsignedShort' ||
$type == 'unsignedByte') {
4856 return (int) $value;
4859 return (string) $value;
4863 * builds response structures for compound values (arrays/structs)
4865 * @param string $pos position in node tree
4868 function buildVal($pos){
4869 if(!isset($this->message
[$pos]['type'])){
4870 $this->message
[$pos]['type'] = '';
4872 $this->debug('inside buildVal() for '.$this->message
[$pos]['name']."(pos $pos) of type ".$this->message
[$pos]['type']);
4873 // if there are children...
4874 if($this->message
[$pos]['children'] != ''){
4875 $children = explode('|',$this->message
[$pos]['children']);
4876 array_shift($children); // knock off empty
4878 if(isset($this->message
[$pos]['arrayCols']) && $this->message
[$pos]['arrayCols'] != ''){
4881 foreach($children as $child_pos){
4882 $this->debug("got an MD array element: $r, $c");
4883 $params[$r][] = $this->message
[$child_pos]['result'];
4885 if($c == $this->message
[$pos]['arrayCols']){
4891 } elseif($this->message
[$pos]['type'] == 'array' ||
$this->message
[$pos]['type'] == 'Array'){
4892 $this->debug('adding array '.$this->message
[$pos]['name']);
4893 foreach($children as $child_pos){
4894 $params[] = &$this->message
[$child_pos]['result'];
4896 // apache Map type: java hashtable
4897 } elseif($this->message
[$pos]['type'] == 'Map' && $this->message
[$pos]['type_namespace'] == 'http://xml.apache.org/xml-soap'){
4898 foreach($children as $child_pos){
4899 $kv = explode("|",$this->message
[$child_pos]['children']);
4900 $params[$this->message
[$kv[1]]['result']] = &$this->message
[$kv[2]]['result'];
4902 // generic compound type
4903 //} elseif($this->message[$pos]['type'] == 'SOAPStruct' || $this->message[$pos]['type'] == 'struct') {
4905 // Apache Vector type: treat as an array
4906 if ($this->message
[$pos]['type'] == 'Vector' && $this->message
[$pos]['type_namespace'] == 'http://xml.apache.org/xml-soap') {
4909 // is array or struct?
4910 foreach($children as $child_pos){
4911 if(isset($keys) && isset($keys[$this->message
[$child_pos]['name']])){
4915 $keys[$this->message
[$child_pos]['name']] = 1;
4919 foreach($children as $child_pos){
4920 if(isset($notstruct)){
4921 $params[] = &$this->message
[$child_pos]['result'];
4923 if (isset($params[$this->message
[$child_pos]['name']])) {
4924 // de-serialize repeated element name into an array
4925 if (!is_array($params[$this->message
[$child_pos]['name']])) {
4926 $params[$this->message
[$child_pos]['name']] = array($params[$this->message
[$child_pos]['name']]);
4928 $params[$this->message
[$child_pos]['name']][] = &$this->message
[$child_pos]['result'];
4930 $params[$this->message
[$child_pos]['name']] = &$this->message
[$child_pos]['result'];
4935 return is_array($params) ?
$params : array();
4937 $this->debug('no children');
4938 if(strpos($this->message
[$pos]['cdata'],'&')){
4939 return strtr($this->message
[$pos]['cdata'],array_flip($this->entities
));
4941 return $this->message
[$pos]['cdata'];
4955 * soapclient higher level class for easy usage.
4959 * // instantiate client with server info
4960 * $soapclient = new soapclient( string path [ ,boolean wsdl] );
4962 * // call method, get results
4963 * echo $soapclient->call( string methodname [ ,array parameters] );
4966 * unset($soapclient);
4968 * @author Dietrich Ayala <dietrich@ganx4.com>
4972 class soapclient
extends nusoap_base
{
4977 var $requestHeaders = false; // SOAP headers in request (text)
4978 var $responseHeaders = ''; // SOAP headers from response (incomplete namespace resolution) (text)
4979 var $document = ''; // SOAP body response portion (incomplete namespace resolution) (text)
4981 var $error_str = false;
4982 var $proxyhost = '';
4983 var $proxyport = '';
4984 var $proxyusername = '';
4985 var $proxypassword = '';
4986 var $xml_encoding = ''; // character set encoding of incoming (response) messages
4987 var $http_encoding = false;
4988 var $timeout = 0; // HTTP connection timeout
4989 var $response_timeout = 30; // HTTP response timeout
4990 var $endpointType = '';
4991 var $persistentConnection = false;
4992 var $defaultRpcParams = false; // This is no longer used
4993 var $request = ''; // HTTP request
4994 var $response = ''; // HTTP response
4995 var $responseData = ''; // SOAP payload of response
4996 // toggles whether the parser decodes element content w/ utf8_decode()
4997 var $decode_utf8 = true;
5000 * fault related variables
5008 var $fault, $faultcode, $faultstring, $faultdetail;
5013 * @param mixed $endpoint SOAP server or WSDL URL (string), or wsdl instance (object)
5014 * @param bool $wsdl optional, set to true if using WSDL
5015 * @param int $portName optional portName in WSDL document
5016 * @param string $proxyhost
5017 * @param string $proxyport
5018 * @param string $proxyusername
5019 * @param string $proxypassword
5020 * @param integer $timeout set the connection timeout
5021 * @param integer $response_timeout set the response timeout
5024 function soapclient($endpoint,$wsdl = false,$proxyhost = false,$proxyport = false,$proxyusername = false, $proxypassword = false, $timeout = 0, $response_timeout = 30){
5025 $this->endpoint
= $endpoint;
5026 $this->proxyhost
= $proxyhost;
5027 $this->proxyport
= $proxyport;
5028 $this->proxyusername
= $proxyusername;
5029 $this->proxypassword
= $proxypassword;
5030 $this->timeout
= $timeout;
5031 $this->response_timeout
= $response_timeout;
5035 $this->endpointType
= 'wsdl';
5036 if (is_object($endpoint) && is_a($endpoint, 'wsdl')) {
5037 $this->wsdl
= $endpoint;
5038 $this->endpoint
= $this->wsdl
->wsdl
;
5039 $this->wsdlFile
= $this->endpoint
;
5040 $this->debug('existing wsdl instance created from ' . $this->endpoint
);
5042 $this->wsdlFile
= $this->endpoint
;
5044 // instantiate wsdl object and parse wsdl file
5045 $this->debug('instantiating wsdl class with doc: '.$endpoint);
5046 $this->wsdl
=& new wsdl($this->wsdlFile
,$this->proxyhost
,$this->proxyport
,$this->proxyusername
,$this->proxypassword
,$this->timeout
,$this->response_timeout
);
5048 $this->debug("wsdl debug...\n".$this->wsdl
->debug_str
);
5049 $this->wsdl
->debug_str
= '';
5051 if($errstr = $this->wsdl
->getError()){
5052 $this->debug('got wsdl error: '.$errstr);
5053 $this->setError('wsdl error: '.$errstr);
5054 } elseif($this->operations
= $this->wsdl
->getOperations()){
5055 $this->debug( 'got '.count($this->operations
).' operations from wsdl '.$this->wsdlFile
);
5057 $this->debug( 'getOperations returned false');
5058 $this->setError('no operations defined in the WSDL document!');
5064 * calls method, returns PHP native type
5066 * @param string $method SOAP server URL or path
5067 * @param array $params An array, associative or simple, of the parameters
5068 * for the method call, or a string that is the XML
5069 * for the call. For rpc style, this call will
5070 * wrap the XML in a tag named after the method, as
5071 * well as the SOAP Envelope and Body. For document
5072 * style, this will only wrap with the Envelope and Body.
5073 * IMPORTANT: when using an array with document style,
5074 * in which case there
5075 * is really one parameter, the root of the fragment
5076 * used in the call, which encloses what programmers
5077 * normally think of parameters. A parameter array
5078 * *must* include the wrapper.
5079 * @param string $namespace optional method namespace (WSDL can override)
5080 * @param string $soapAction optional SOAPAction value (WSDL can override)
5081 * @param boolean $headers optional array of soapval objects for headers
5082 * @param boolean $rpcParams optional no longer used
5083 * @param string $style optional (rpc|document) the style to use when serializing parameters (WSDL can override)
5084 * @param string $use optional (encoded|literal) the use when serializing parameters (WSDL can override)
5088 function call($operation,$params=array(),$namespace='',$soapAction='',$headers=false,$rpcParams=null,$style='rpc',$use='encoded'){
5089 $this->operation
= $operation;
5090 $this->fault
= false;
5091 $this->error_str
= '';
5092 $this->request
= '';
5093 $this->response
= '';
5094 $this->responseData
= '';
5095 $this->faultstring
= '';
5096 $this->faultcode
= '';
5097 $this->opData
= array();
5099 $this->debug("call: $operation, $params, $namespace, $soapAction, $headers, $style, $use; endpointType: $this->endpointType");
5101 $this->requestHeaders
= $headers;
5103 // serialize parameters
5104 if($this->endpointType
== 'wsdl' && $opData = $this->getOperationData($operation)){
5105 // use WSDL for operation
5106 $this->opData
= $opData;
5107 foreach($opData as $key => $value){
5108 $this->debug("$key -> $value");
5110 if (isset($opData['soapAction'])) {
5111 $soapAction = $opData['soapAction'];
5113 $this->endpoint
= $opData['endpoint'];
5114 $namespace = isset($opData['input']['namespace']) ?
$opData['input']['namespace'] : ($namespace != '' ?
$namespace : 'http://testuri.org');
5115 $style = $opData['style'];
5116 $use = $opData['input']['use'];
5117 // add ns to ns array
5118 if($namespace != '' && !isset($this->wsdl
->namespaces
[$namespace])){
5119 $this->wsdl
->namespaces
['nu'] = $namespace;
5121 $nsPrefix = $this->wsdl
->getPrefixFromNamespace($namespace);
5122 // serialize payload
5123 if (is_string($params)) {
5124 $this->debug("serializing param string for WSDL operation $operation");
5126 } elseif (is_array($params)) {
5127 $this->debug("serializing param array for WSDL operation $operation");
5128 $payload = $this->wsdl
->serializeRPCParameters($operation,'input',$params);
5130 $this->debug('params must be array or string');
5131 $this->setError('params must be array or string');
5134 $usedNamespaces = $this->wsdl
->usedNamespaces
;
5135 // Partial fix for multiple encoding styles in the same function call
5136 $encodingStyle = 'http://schemas.xmlsoap.org/soap/encoding/';
5137 if (isset($opData['output']['encodingStyle']) && $encodingStyle != $opData['output']['encodingStyle']) {
5138 $methodEncodingStyle = ' SOAP-ENV:encodingStyle="' . $opData['output']['encodingStyle'] . '"';
5140 $methodEncodingStyle = '';
5142 $this->debug("wsdl debug: \n".$this->wsdl
->debug_str
);
5143 $this->wsdl
->debug_str
= '';
5144 if ($errstr = $this->wsdl
->getError()) {
5145 $this->debug('got wsdl error: '.$errstr);
5146 $this->setError('wsdl error: '.$errstr);
5149 } elseif($this->endpointType
== 'wsdl') {
5150 // operation not in WSDL
5151 $this->setError( 'operation '.$operation.' not present.');
5152 $this->debug("operation '$operation' not present.");
5153 $this->debug("wsdl debug: \n".$this->wsdl
->debug_str
);
5154 $this->wsdl
->debug_str
= '';
5158 if($namespace == ''){
5159 $namespace = 'http://testuri.org';
5161 //$this->namespaces['ns1'] = $namespace;
5165 if (is_string($params)) {
5166 $this->debug("serializing param string for operation $operation");
5168 } elseif (is_array($params)) {
5169 $this->debug("serializing param array for operation $operation");
5170 foreach($params as $k => $v){
5171 $payload .= $this->serialize_val($v,$k,false,false,false,false,$use);
5174 $this->debug('params must be array or string');
5175 $this->setError('params must be array or string');
5178 $usedNamespaces = array();
5179 $methodEncodingStyle = '';
5181 // wrap RPC calls with method element
5182 if ($style == 'rpc') {
5183 if ($use == 'literal') {
5184 $this->debug("wrapping RPC request with literal method element");
5185 $payload = "<$operation xmlns=\"$namespace\">" . $payload . "</$operation>";
5187 $this->debug("wrapping RPC request with encoded method element");
5188 $payload = "<$nsPrefix:$operation$methodEncodingStyle xmlns:$nsPrefix=\"$namespace\">" .
5190 "</$nsPrefix:$operation>";
5193 // serialize envelope
5194 $soapmsg = $this->serializeEnvelope($payload,$this->requestHeaders
,$usedNamespaces,$style,$use);
5195 $this->debug("endpoint: $this->endpoint, soapAction: $soapAction, namespace: $namespace, style: $style, use: $use");
5196 $this->debug('SOAP message length: ' . strlen($soapmsg) . ' contents: ' . substr($soapmsg, 0, 1000));
5198 $return = $this->send($this->getHTTPBody($soapmsg),$soapAction,$this->timeout
,$this->response_timeout
);
5199 if($errstr = $this->getError()){
5200 $this->debug('Error: '.$errstr);
5203 $this->return = $return;
5204 $this->debug('sent message successfully and got a(n) '.gettype($return).' back');
5207 if(is_array($return) && isset($return['faultcode'])){
5208 $this->debug('got fault');
5209 $this->setError($return['faultcode'].': '.$return['faultstring']);
5210 $this->fault
= true;
5211 foreach($return as $k => $v){
5213 $this->debug("$k = $v<br>");
5217 // array of return values
5218 if(is_array($return)){
5219 // multiple 'out' parameters
5220 if(sizeof($return) > 1){
5223 // single 'out' parameter
5224 return array_shift($return);
5225 // nothing returned (ie, echoVoid)
5234 * get available data pertaining to an operation
5236 * @param string $operation operation name
5237 * @return array array of data pertaining to the operation
5240 function getOperationData($operation){
5241 if(isset($this->operations
[$operation])){
5242 return $this->operations
[$operation];
5244 $this->debug("No data for operation: $operation");
5248 * send the SOAP message
5250 * Note: if the operation has multiple return values
5251 * the return value of this method will be an array
5254 * @param string $msg a SOAPx4 soapmsg object
5255 * @param string $soapaction SOAPAction value
5256 * @param integer $timeout set connection timeout in seconds
5257 * @param integer $response_timeout set response timeout in seconds
5258 * @return mixed native PHP types.
5261 function send($msg, $soapaction = '', $timeout=0, $response_timeout=30) {
5265 case ereg('^http',$this->endpoint
):
5266 $this->debug('transporting via HTTP');
5267 if($this->persistentConnection
== true && is_object($this->persistentConnection
)){
5268 $http =& $this->persistentConnection
;
5270 $http = new soap_transport_http($this->endpoint
);
5271 if ($this->persistentConnection
) {
5272 $http->usePersistentConnection();
5275 $http->setContentType($this->getHTTPContentType(), $this->getHTTPContentTypeCharset());
5276 $http->setSOAPAction($soapaction);
5277 if($this->proxyhost
&& $this->proxyport
){
5278 $http->setProxy($this->proxyhost
,$this->proxyport
,$this->proxyusername
,$this->proxypassword
);
5280 if($this->username
!= '' && $this->password
!= '') {
5281 $http->setCredentials($this->username
, $this->password
, $this->authtype
);
5283 if($this->http_encoding
!= ''){
5284 $http->setEncoding($this->http_encoding
);
5286 $this->debug('sending message, length: '.strlen($msg));
5287 if(ereg('^http:',$this->endpoint
)){
5288 //if(strpos($this->endpoint,'http:')){
5289 $this->responseData
= $http->send($msg,$timeout,$response_timeout);
5290 } elseif(ereg('^https',$this->endpoint
)){
5291 //} elseif(strpos($this->endpoint,'https:')){
5292 //if(phpversion() == '4.3.0-dev'){
5293 //$response = $http->send($msg,$timeout,$response_timeout);
5294 //$this->request = $http->outgoing_payload;
5295 //$this->response = $http->incoming_payload;
5297 if (extension_loaded('curl')) {
5298 $this->responseData
= $http->sendHTTPS($msg,$timeout,$response_timeout);
5300 $this->setError('CURL Extension, or OpenSSL extension w/ PHP version >= 4.3 is required for HTTPS');
5303 $this->setError('no http/s in endpoint url');
5305 $this->request
= $http->outgoing_payload
;
5306 $this->response
= $http->incoming_payload
;
5307 $this->debug("transport debug data...\n".$http->debug_str
);
5309 // save transport object if using persistent connections
5310 if ($this->persistentConnection
) {
5311 $http->debug_str
= '';
5312 if (!is_object($this->persistentConnection
)) {
5313 $this->persistentConnection
= $http;
5317 if($err = $http->getError()){
5318 $this->setError('HTTP Error: '.$err);
5320 } elseif($this->getError()){
5323 $this->debug('got response, length: '. strlen($this->responseData
).' type: '.$http->incoming_headers
['content-type']);
5324 return $this->parseResponse($http->incoming_headers
, $this->responseData
);
5328 $this->setError('no transport found, or selected transport is not yet supported!');
5335 * processes SOAP message returned from server
5337 * @param array $headers The HTTP headers
5338 * @param string $data unprocessed response data from server
5339 * @return mixed value of the message, decoded into a PHP type
5342 function parseResponse($headers, $data) {
5343 $this->debug('Entering parseResponse() for data of length ' . strlen($data) . ' and type ' . $headers['content-type']);
5344 if (!strstr($headers['content-type'], 'text/xml')) {
5345 $this->setError('Response not of type text/xml');
5348 if (strpos($headers['content-type'], '=')) {
5349 $enc = str_replace('"', '', substr(strstr($headers["content-type"], '='), 1));
5350 $this->debug('Got response encoding: ' . $enc);
5351 if(eregi('^(ISO-8859-1|US-ASCII|UTF-8)$',$enc)){
5352 $this->xml_encoding
= strtoupper($enc);
5354 $this->xml_encoding
= 'US-ASCII';
5357 // should be US-ASCII, but for XML, let's be pragmatic and admit UTF-8 is most common
5358 $this->xml_encoding
= 'UTF-8';
5360 $this->debug('Use encoding: ' . $this->xml_encoding
. ' when creating soap_parser');
5361 $parser = new soap_parser($data,$this->xml_encoding
,$this->operation
,$this->decode_utf8
);
5362 // add parser debug data to our debug
5363 $this->debug($parser->debug_str
);
5365 if($errstr = $parser->getError()){
5366 $this->setError( $errstr);
5367 // destroy the parser object
5372 $this->responseHeaders
= $parser->getHeaders();
5373 // get decoded message
5374 $return = $parser->get_response();
5375 // add document for doclit support
5376 $this->document
= $parser->document
;
5377 // destroy the parser object
5379 // return decode message
5385 * set the SOAP headers
5387 * @param $headers string XML
5390 function setHeaders($headers){
5391 $this->requestHeaders
= $headers;
5395 * get the response headers
5397 * @return mixed object SOAPx4 soapval object or empty if no headers
5400 function getHeaders(){
5401 if($this->responseHeaders
!= '') {
5402 return $this->responseHeaders
;
5407 * set proxy info here
5409 * @param string $proxyhost
5410 * @param string $proxyport
5411 * @param string $proxyusername
5412 * @param string $proxypassword
5415 function setHTTPProxy($proxyhost, $proxyport, $proxyusername = '', $proxypassword = '') {
5416 $this->proxyhost
= $proxyhost;
5417 $this->proxyport
= $proxyport;
5418 $this->proxyusername
= $proxyusername;
5419 $this->proxypassword
= $proxypassword;
5423 * if authenticating, set user credentials here
5425 * @param string $username
5426 * @param string $password
5427 * @param string $authtype (basic|digest)
5430 function setCredentials($username, $password, $authtype = 'basic') {
5431 $this->username
= $username;
5432 $this->password
= $password;
5433 $this->authtype
= $authtype;
5439 * @param string $enc
5442 function setHTTPEncoding($enc='gzip, deflate'){
5443 $this->http_encoding
= $enc;
5447 * use HTTP persistent connections if possible
5451 function useHTTPPersistentConnection(){
5452 $this->persistentConnection
= true;
5456 * gets the default RPC parameter setting.
5457 * If true, default is that call params are like RPC even for document style.
5458 * Each call() can override this value.
5460 * This is no longer used.
5465 function getDefaultRpcParams() {
5466 return $this->defaultRpcParams
;
5470 * sets the default RPC parameter setting.
5471 * If true, default is that call params are like RPC even for document style
5472 * Each call() can override this value.
5474 * @param boolean $rpcParams
5477 function setDefaultRpcParams($rpcParams) {
5478 $this->defaultRpcParams
= $rpcParams;
5482 * dynamically creates proxy class, allowing user to directly call methods from wsdl
5484 * @return object soap_proxy object
5487 function getProxy(){
5489 foreach($this->operations
as $operation => $opData){
5490 if($operation != ''){
5491 // create param string
5493 if(sizeof($opData['input']['parts']) > 0){
5494 foreach($opData['input']['parts'] as $name => $type){
5495 $paramStr .= "\$$name,";
5497 $paramStr = substr($paramStr,0,strlen($paramStr)-1);
5499 $opData['namespace'] = !isset($opData['namespace']) ?
'http://testuri.com' : $opData['namespace'];
5500 $evalStr .= "function $operation ($paramStr){
5501 // load params into array
5502 \$params = array($paramStr);
5503 return \$this->call('$operation',\$params,'".$opData['namespace']."','".(isset($opData['soapAction']) ?
$opData['soapAction'] : '')."');
5509 $evalStr = 'class soap_proxy_'.$r.' extends soapclient {
5512 //print "proxy class:<pre>$evalStr</pre>";
5515 // instantiate proxy object
5516 eval("\$proxy = new soap_proxy_$r('');");
5517 // transfer current wsdl data to the proxy thereby avoiding parsing the wsdl twice
5518 $proxy->endpointType
= 'wsdl';
5519 $proxy->wsdlFile
= $this->wsdlFile
;
5520 $proxy->wsdl
= $this->wsdl
;
5521 $proxy->operations
= $this->operations
;
5522 $proxy->defaultRpcParams
= $this->defaultRpcParams
;
5523 // transfer other state
5524 $proxy->username
= $this->username
;
5525 $proxy->password
= $this->password
;
5526 $proxy->proxyhost
= $this->proxyhost
;
5527 $proxy->proxyport
= $this->proxyport
;
5528 $proxy->proxyusername
= $this->proxyusername
;
5529 $proxy->proxypassword
= $this->proxypassword
;
5530 $proxy->timeout
= $this->timeout
;
5531 $proxy->response_timeout
= $this->response_timeout
;
5532 $proxy->http_encoding
= $this->http_encoding
;
5533 $proxy->persistentConnection
= $this->persistentConnection
;
5538 * gets the HTTP body for the current request.
5540 * @param string $soapmsg The SOAP payload
5541 * @return string The HTTP body, which includes the SOAP payload
5544 function getHTTPBody($soapmsg) {
5549 * gets the HTTP content type for the current request.
5551 * Note: getHTTPBody must be called before this.
5553 * @return string the HTTP content type for the current request.
5556 function getHTTPContentType() {
5561 * gets the HTTP content type charset for the current request.
5562 * returns false for non-text content types.
5564 * Note: getHTTPBody must be called before this.
5566 * @return string the HTTP content type charset for the current request.
5569 function getHTTPContentTypeCharset() {
5570 return $this->soap_defencoding
;
5574 * whether or not parser should decode utf8 element content
5576 * @return always returns true
5579 function decodeUTF8($bool){
5580 $this->decode_utf8
= $bool;