5 NuSOAP - Web Services Toolkit for PHP
7 Copyright (c) 2002 NuSphere Corporation
9 This library is free software; you can redistribute it and/or
10 modify it under the terms of the GNU Lesser General Public
11 License as published by the Free Software Foundation; either
12 version 2.1 of the License, or (at your option) any later version.
14 This library is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 Lesser General Public License for more details.
19 You should have received a copy of the GNU Lesser General Public
20 License along with this library; if not, write to the Free Software
21 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23 If you have any questions or comments, please email:
27 http://dietrich.ganx4.com/nusoap
30 http://www.nusphere.com
37 require_once('class.soapclient.php');
38 require_once('class.soap_val.php');
39 require_once('class.soap_parser.php');
40 require_once('class.soap_fault.php');
43 require_once('class.soap_transport_http.php');
45 // optional add-on classes
46 require_once('class.xmlschema.php');
47 require_once('class.wsdl.php');
50 // class variable emulation
51 // cf. http://www.webkreator.com/php/techniques/php-static-class-variables.html
52 $GLOBALS['_transient']['static']['nusoap_base']->globalDebugLevel
= 9;
58 * @author Dietrich Ayala <dietrich@ganx4.com>
64 * Identification for HTTP headers.
69 var $title = 'NuSOAP';
71 * Version for HTTP headers.
76 var $version = '0.7.2';
78 * CVS revision for HTTP headers.
83 var $revision = '$Revision$';
85 * Current error string (manipulated by getError/setError)
92 * Current debug string (manipulated by debug/appendDebug/clearDebug/getDebug/getDebugAsXMLComment)
99 * toggles automatic encoding of special characters as entities
100 * (should always be true, I think)
105 var $charencoding = true;
107 * the debug level for this instance
120 var $XMLSchemaVersion = 'http://www.w3.org/2001/XMLSchema';
123 * charset encoding for outgoing messages
128 var $soap_defencoding = 'ISO-8859-1';
129 //var $soap_defencoding = 'UTF-8';
132 * namespaces in an array of prefix => uri
134 * this is "seeded" by a set of constants, but it may be altered by code
139 var $namespaces = array(
140 'SOAP-ENV' => 'http://schemas.xmlsoap.org/soap/envelope/',
141 'xsd' => 'http://www.w3.org/2001/XMLSchema',
142 'xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
143 'SOAP-ENC' => 'http://schemas.xmlsoap.org/soap/encoding/'
147 * namespaces used in the current context, e.g. during serialization
152 var $usedNamespaces = array();
155 * XML Schema types in an array of uri => (array of xml type => php type)
156 * is this legacy yet?
157 * no, this is used by the xmlschema class to verify type => namespace mappings.
161 var $typemap = array(
162 'http://www.w3.org/2001/XMLSchema' => array(
163 'string'=>'string','boolean'=>'boolean','float'=>'double','double'=>'double','decimal'=>'double',
164 'duration'=>'','dateTime'=>'string','time'=>'string','date'=>'string','gYearMonth'=>'',
165 'gYear'=>'','gMonthDay'=>'','gDay'=>'','gMonth'=>'','hexBinary'=>'string','base64Binary'=>'string',
166 // abstract "any" types
167 'anyType'=>'string','anySimpleType'=>'string',
169 'normalizedString'=>'string','token'=>'string','language'=>'','NMTOKEN'=>'','NMTOKENS'=>'','Name'=>'','NCName'=>'','ID'=>'',
170 'IDREF'=>'','IDREFS'=>'','ENTITY'=>'','ENTITIES'=>'','integer'=>'integer','nonPositiveInteger'=>'integer',
171 'negativeInteger'=>'integer','long'=>'integer','int'=>'integer','short'=>'integer','byte'=>'integer','nonNegativeInteger'=>'integer',
172 'unsignedLong'=>'','unsignedInt'=>'','unsignedShort'=>'','unsignedByte'=>'','positiveInteger'=>''),
173 'http://www.w3.org/2000/10/XMLSchema' => array(
174 'i4'=>'','int'=>'integer','boolean'=>'boolean','string'=>'string','double'=>'double',
175 'float'=>'double','dateTime'=>'string',
176 'timeInstant'=>'string','base64Binary'=>'string','base64'=>'string','ur-type'=>'array'),
177 'http://www.w3.org/1999/XMLSchema' => array(
178 'i4'=>'','int'=>'integer','boolean'=>'boolean','string'=>'string','double'=>'double',
179 'float'=>'double','dateTime'=>'string',
180 'timeInstant'=>'string','base64Binary'=>'string','base64'=>'string','ur-type'=>'array'),
181 'http://soapinterop.org/xsd' => array('SOAPStruct'=>'struct'),
182 'http://schemas.xmlsoap.org/soap/encoding/' => array('base64'=>'string','array'=>'array','Array'=>'array'),
183 'http://xml.apache.org/xml-soap' => array('Map')
187 * XML entities to convert
192 * @see expandEntities
194 var $xmlEntities = array('quot' => '"','amp' => '&',
195 'lt' => '<','gt' => '>','apos' => "'");
202 function nusoap_base() {
203 $this->debugLevel
= $GLOBALS['_transient']['static']['nusoap_base']->globalDebugLevel
;
207 * gets the global debug level, which applies to future instances
209 * @return integer Debug level 0-9, where 0 turns off
212 function getGlobalDebugLevel() {
213 return $GLOBALS['_transient']['static']['nusoap_base']->globalDebugLevel
;
217 * sets the global debug level, which applies to future instances
219 * @param int $level Debug level 0-9, where 0 turns off
222 function setGlobalDebugLevel($level) {
223 $GLOBALS['_transient']['static']['nusoap_base']->globalDebugLevel
= $level;
227 * gets the debug level for this instance
229 * @return int Debug level 0-9, where 0 turns off
232 function getDebugLevel() {
233 return $this->debugLevel
;
237 * sets the debug level for this instance
239 * @param int $level Debug level 0-9, where 0 turns off
242 function setDebugLevel($level) {
243 $this->debugLevel
= $level;
247 * adds debug data to the instance debug string with formatting
249 * @param string $string debug data
252 function debug($string){
253 if ($this->debugLevel
> 0) {
254 $this->appendDebug($this->getmicrotime().' '.get_class($this).": $string\n");
259 * adds debug data to the instance debug string without formatting
261 * @param string $string debug data
264 function appendDebug($string){
265 if ($this->debugLevel
> 0) {
266 // it would be nice to use a memory stream here to use
267 // memory more efficiently
268 $this->debug_str
.= $string;
273 * clears the current debug data for this instance
277 function clearDebug() {
278 // it would be nice to use a memory stream here to use
279 // memory more efficiently
280 $this->debug_str
= '';
284 * gets the current debug data for this instance
289 function &getDebug() {
290 // it would be nice to use a memory stream here to use
291 // memory more efficiently
292 return $this->debug_str
;
296 * gets the current debug data for this instance as an XML comment
297 * this may change the contents of the debug data
299 * @return debug data as an XML comment
302 function &getDebugAsXMLComment() {
303 // it would be nice to use a memory stream here to use
304 // memory more efficiently
305 while (strpos($this->debug_str
, '--')) {
306 $this->debug_str
= str_replace('--', '- -', $this->debug_str
);
308 return "<!--\n" . $this->debug_str
. "\n-->";
312 * expands entities, e.g. changes '<' to '<'.
314 * @param string $val The string in which to expand entities.
317 function expandEntities($val) {
318 if ($this->charencoding
) {
319 $val = str_replace('&', '&', $val);
320 $val = str_replace("'", ''', $val);
321 $val = str_replace('"', '"', $val);
322 $val = str_replace('<', '<', $val);
323 $val = str_replace('>', '>', $val);
329 * returns error string if present
331 * @return mixed error string or false
335 if($this->error_str
!= ''){
336 return $this->error_str
;
344 * @return boolean $string error string
347 function setError($str){
348 $this->error_str
= $str;
352 * detect if array is a simple array or a struct (associative array)
354 * @param mixed $val The PHP array
355 * @return string (arraySimple|arrayStruct)
358 function isArraySimpleOrStruct($val) {
359 $keyList = array_keys($val);
360 foreach ($keyList as $keyListValue) {
361 if (!is_int($keyListValue)) {
362 return 'arrayStruct';
365 return 'arraySimple';
369 * serializes PHP values in accordance w/ section 5. Type information is
370 * not serialized if $use == 'literal'.
372 * @param mixed $val The value to serialize
373 * @param string $name The name (local part) of the XML element
374 * @param string $type The XML schema type (local part) for the element
375 * @param string $name_ns The namespace for the name of the XML element
376 * @param string $type_ns The namespace for the type of the element
377 * @param array $attributes The attributes to serialize as name=>value pairs
378 * @param string $use The WSDL "use" (encoded|literal)
379 * @return string The serialized element, possibly with child elements
382 function serialize_val($val,$name=false,$type=false,$name_ns=false,$type_ns=false,$attributes=false,$use='encoded'){
383 $this->debug("in serialize_val: name=$name, type=$type, name_ns=$name_ns, type_ns=$type_ns, use=$use");
384 $this->appendDebug('value=' . $this->varDump($val));
385 $this->appendDebug('attributes=' . $this->varDump($attributes));
387 if(is_object($val) && get_class($val) == 'soapval'){
388 return $val->serialize($use);
390 // force valid name if necessary
391 if (is_numeric($name)) {
392 $name = '__numeric_' . $name;
396 // if name has ns, add ns prefix to name
399 $prefix = 'nu'.rand(1000,9999);
400 $name = $prefix.':'.$name;
401 $xmlns .= " xmlns:$prefix=\"$name_ns\"";
403 // if type is prefixed, create type prefix
404 if($type_ns != '' && $type_ns == $this->namespaces
['xsd']){
405 // need to fix this. shouldn't default to xsd if no ns specified
406 // w/o checking against typemap
407 $type_prefix = 'xsd';
409 $type_prefix = 'ns'.rand(1000,9999);
410 $xmlns .= " xmlns:$type_prefix=\"$type_ns\"";
412 // serialize attributes if present
415 foreach($attributes as $k => $v){
416 $atts .= " $k=\"".$this->expandEntities($v).'"';
419 // serialize null value
421 if ($use == 'literal') {
422 // TODO: depends on minOccurs
423 return "<$name$xmlns $atts/>";
425 if (isset($type) && isset($type_prefix)) {
426 $type_str = " xsi:type=\"$type_prefix:$type\"";
430 return "<$name$xmlns$type_str $atts xsi:nil=\"true\"/>";
433 // serialize if an xsd built-in primitive type
434 if($type != '' && isset($this->typemap
[$this->XMLSchemaVersion
][$type])){
436 if ($type == 'boolean') {
437 $val = $val ?
'true' : 'false';
441 } else if (is_string($val)) {
442 $val = $this->expandEntities($val);
444 if ($use == 'literal') {
445 return "<$name$xmlns $atts>$val</$name>";
447 return "<$name$xmlns $atts xsi:type=\"xsd:$type\">$val</$name>";
450 // detect type and serialize
453 case (is_bool($val) ||
$type == 'boolean'):
454 if ($type == 'boolean') {
455 $val = $val ?
'true' : 'false';
459 if ($use == 'literal') {
460 $xml .= "<$name$xmlns $atts>$val</$name>";
462 $xml .= "<$name$xmlns xsi:type=\"xsd:boolean\"$atts>$val</$name>";
465 case (is_int($val) ||
is_long($val) ||
$type == 'int'):
466 if ($use == 'literal') {
467 $xml .= "<$name$xmlns $atts>$val</$name>";
469 $xml .= "<$name$xmlns xsi:type=\"xsd:int\"$atts>$val</$name>";
472 case (is_float($val)||
is_double($val) ||
$type == 'float'):
473 if ($use == 'literal') {
474 $xml .= "<$name$xmlns $atts>$val</$name>";
476 $xml .= "<$name$xmlns xsi:type=\"xsd:float\"$atts>$val</$name>";
479 case (is_string($val) ||
$type == 'string'):
480 $val = $this->expandEntities($val);
481 if ($use == 'literal') {
482 $xml .= "<$name$xmlns $atts>$val</$name>";
484 $xml .= "<$name$xmlns xsi:type=\"xsd:string\"$atts>$val</$name>";
487 case is_object($val):
489 $name = get_class($val);
490 $this->debug("In serialize_val, used class name $name as element name");
492 $this->debug("In serialize_val, do not override name $name for element name for class " . get_class($val));
494 foreach(get_object_vars($val) as $k => $v){
495 $pXml = isset($pXml) ?
$pXml.$this->serialize_val($v,$k,false,false,false,false,$use) : $this->serialize_val($v,$k,false,false,false,false,$use);
497 $xml .= '<'.$name.'>'.$pXml.'</'.$name.'>';
500 case (is_array($val) ||
$type):
501 // detect if struct or array
502 $valueType = $this->isArraySimpleOrStruct($val);
503 if($valueType=='arraySimple' ||
ereg('^ArrayOf',$type)){
505 if(is_array($val) && count($val)> 0){
507 if(is_object($v) && get_class($v) == 'soapval'){
508 $tt_ns = $v->type_ns
;
510 } elseif (is_array($v)) {
511 $tt = $this->isArraySimpleOrStruct($v);
515 $array_types[$tt] = 1;
516 // TODO: for literal, the name should be $name
517 $xml .= $this->serialize_val($v,'item',false,false,false,false,$use);
520 if(count($array_types) > 1){
521 $array_typename = 'xsd:anyType';
522 } elseif(isset($tt) && isset($this->typemap
[$this->XMLSchemaVersion
][$tt])) {
523 if ($tt == 'integer') {
526 $array_typename = 'xsd:'.$tt;
527 } elseif(isset($tt) && $tt == 'arraySimple'){
528 $array_typename = 'SOAP-ENC:Array';
529 } elseif(isset($tt) && $tt == 'arrayStruct'){
530 $array_typename = 'unnamed_struct_use_soapval';
532 // if type is prefixed, create type prefix
533 if ($tt_ns != '' && $tt_ns == $this->namespaces
['xsd']){
534 $array_typename = 'xsd:' . $tt;
536 $tt_prefix = 'ns' . rand(1000, 9999);
537 $array_typename = "$tt_prefix:$tt";
538 $xmlns .= " xmlns:$tt_prefix=\"$tt_ns\"";
540 $array_typename = $tt;
544 if ($use == 'literal') {
546 } else if (isset($type) && isset($type_prefix)) {
547 $type_str = " xsi:type=\"$type_prefix:$type\"";
549 $type_str = " xsi:type=\"SOAP-ENC:Array\" SOAP-ENC:arrayType=\"".$array_typename."[$array_type]\"";
553 if ($use == 'literal') {
555 } else if (isset($type) && isset($type_prefix)) {
556 $type_str = " xsi:type=\"$type_prefix:$type\"";
558 $type_str = " xsi:type=\"SOAP-ENC:Array\" SOAP-ENC:arrayType=\"xsd:anyType[0]\"";
561 // TODO: for array in literal, there is no wrapper here
562 $xml = "<$name$xmlns$type_str$atts>".$xml."</$name>";
565 if(isset($type) && isset($type_prefix)){
566 $type_str = " xsi:type=\"$type_prefix:$type\"";
570 if ($use == 'literal') {
571 $xml .= "<$name$xmlns $atts>";
573 $xml .= "<$name$xmlns$type_str$atts>";
575 foreach($val as $k => $v){
577 if ($type == 'Map' && $type_ns == 'http://xml.apache.org/xml-soap') {
579 $xml .= $this->serialize_val($k,'key',false,false,false,false,$use);
580 $xml .= $this->serialize_val($v,'value',false,false,false,false,$use);
583 $xml .= $this->serialize_val($v,$k,false,false,false,false,$use);
590 $xml .= 'not detected, got '.gettype($val).' for '.$val;
597 * serializes a message
599 * @param string $body the XML of the SOAP body
600 * @param mixed $headers optional string of XML with SOAP header content, or array of soapval objects for SOAP headers
601 * @param array $namespaces optional the namespaces used in generating the body and headers
602 * @param string $style optional (rpc|document)
603 * @param string $use optional (encoded|literal)
604 * @param string $encodingStyle optional (usually 'http://schemas.xmlsoap.org/soap/encoding/' for encoded)
605 * @return string the message
608 function serializeEnvelope($body,$headers=false,$namespaces=array(),$style='rpc',$use='encoded',$encodingStyle='http://schemas.xmlsoap.org/soap/encoding/'){
609 // TODO: add an option to automatically run utf8_encode on $body and $headers
610 // if $this->soap_defencoding is UTF-8. Not doing this automatically allows
611 // one to send arbitrary UTF-8 characters, not just characters that map to ISO-8859-1
613 $this->debug("In serializeEnvelope length=" . strlen($body) . " body (max 1000 characters)=" . substr($body, 0, 1000) . " style=$style use=$use encodingStyle=$encodingStyle");
614 $this->debug("headers:");
615 $this->appendDebug($this->varDump($headers));
616 $this->debug("namespaces:");
617 $this->appendDebug($this->varDump($namespaces));
619 // serialize namespaces
621 foreach(array_merge($this->namespaces
,$namespaces) as $k => $v){
622 $ns_string .= " xmlns:$k=\"$v\"";
625 $ns_string = " SOAP-ENV:encodingStyle=\"$encodingStyle\"$ns_string";
630 if (is_array($headers)) {
632 foreach ($headers as $header) {
633 $xml .= $this->serialize_val($header, false, false, false, false, false, $use);
636 $this->debug("In serializeEnvelope, serialzied array of headers to $headers");
638 $headers = "<SOAP-ENV:Header>".$headers."</SOAP-ENV:Header>";
640 // serialize envelope
642 '<?xml version="1.0" encoding="'.$this->soap_defencoding
.'"?'.">".
643 '<SOAP-ENV:Envelope'.$ns_string.">".
648 "</SOAP-ENV:Envelope>";
652 * formats a string to be inserted into an HTML stream
654 * @param string $str The string to format
655 * @return string The formatted string
659 function formatDump($str){
660 $str = htmlspecialchars($str);
665 * contracts (changes namespace to prefix) a qualified name
667 * @param string $qname qname
668 * @return string contracted qname
671 function contractQname($qname){
672 // get element namespace
673 //$this->xdebug("Contract $qname");
674 if (strrpos($qname, ':')) {
675 // get unqualified name
676 $name = substr($qname, strrpos($qname, ':') +
1);
678 $ns = substr($qname, 0, strrpos($qname, ':'));
679 $p = $this->getPrefixFromNamespace($ns);
681 return $p . ':' . $name;
690 * expands (changes prefix to namespace) a qualified name
692 * @param string $string qname
693 * @return string expanded qname
696 function expandQname($qname){
697 // get element prefix
698 if(strpos($qname,':') && !ereg('^http://',$qname)){
699 // get unqualified name
700 $name = substr(strstr($qname,':'),1);
702 $prefix = substr($qname,0,strpos($qname,':'));
703 if(isset($this->namespaces
[$prefix])){
704 return $this->namespaces
[$prefix].':'.$name;
714 * returns the local part of a prefixed string
715 * returns the original string, if not prefixed
717 * @param string $str The prefixed string
718 * @return string The local part
721 function getLocalPart($str){
722 if($sstr = strrchr($str,':')){
723 // get unqualified name
724 return substr( $sstr, 1 );
731 * returns the prefix part of a prefixed string
732 * returns false, if not prefixed
734 * @param string $str The prefixed string
735 * @return mixed The prefix or false if there is no prefix
738 function getPrefix($str){
739 if($pos = strrpos($str,':')){
741 return substr($str,0,$pos);
747 * pass it a prefix, it returns a namespace
749 * @param string $prefix The prefix
750 * @return mixed The namespace, false if no namespace has the specified prefix
753 function getNamespaceFromPrefix($prefix){
754 if (isset($this->namespaces
[$prefix])) {
755 return $this->namespaces
[$prefix];
757 //$this->setError("No namespace registered for prefix '$prefix'");
762 * returns the prefix for a given namespace (or prefix)
763 * or false if no prefixes registered for the given namespace
765 * @param string $ns The namespace
766 * @return mixed The prefix, false if the namespace has no prefixes
769 function getPrefixFromNamespace($ns) {
770 foreach ($this->namespaces
as $p => $n) {
771 if ($ns == $n ||
$ns == $p) {
772 $this->usedNamespaces
[$p] = $n;
780 * returns the time in ODBC canonical form with microseconds
782 * @return string The time in ODBC canonical form with microseconds
785 function getmicrotime() {
786 if (function_exists('gettimeofday')) {
787 $tod = gettimeofday();
789 $usec = $tod['usec'];
794 return strftime('%Y-%m-%d %H:%M:%S', $sec) . '.' . sprintf('%06d', $usec);
798 * Returns a string with the output of var_dump
800 * @param mixed $data The variable to var_dump
801 * @return string The output of var_dump
804 function varDump($data) {
807 $ret_val = ob_get_contents();
813 // XML Schema Datatype Helper Functions
815 //xsd:dateTime helpers
818 * convert unix timestamp to ISO 8601 compliant date string
820 * @param string $timestamp Unix time stamp
823 function timestamp_to_iso8601($timestamp,$utc=true){
824 $datestr = date('Y-m-d\TH:i:sO',$timestamp);
827 '([0-9]{4})-'. // centuries & years CCYY-
828 '([0-9]{2})-'. // months MM-
829 '([0-9]{2})'. // days DD
831 '([0-9]{2}):'. // hours hh:
832 '([0-9]{2}):'. // minutes mm:
833 '([0-9]{2})(\.[0-9]*)?'. // seconds ss.ss...
834 '(Z|[+\-][0-9]{2}:?[0-9]{2})?'; // Z to indicate UTC, -/+HH:MM:SS.SS... for local tz's
836 if(ereg($eregStr,$datestr,$regs)){
837 return sprintf('%04d-%02d-%02dT%02d:%02d:%02dZ',$regs[1],$regs[2],$regs[3],$regs[4],$regs[5],$regs[6]);
846 * convert ISO 8601 compliant date string to unix timestamp
848 * @param string $datestr ISO 8601 compliant date string
851 function iso8601_to_timestamp($datestr){
853 '([0-9]{4})-'. // centuries & years CCYY-
854 '([0-9]{2})-'. // months MM-
855 '([0-9]{2})'. // days DD
857 '([0-9]{2}):'. // hours hh:
858 '([0-9]{2}):'. // minutes mm:
859 '([0-9]{2})(\.[0-9]+)?'. // seconds ss.ss...
860 '(Z|[+\-][0-9]{2}:?[0-9]{2})?'; // Z to indicate UTC, -/+HH:MM:SS.SS... for local tz's
861 if(ereg($eregStr,$datestr,$regs)){
864 $op = substr($regs[8],0,1);
865 $h = substr($regs[8],1,2);
866 $m = substr($regs[8],strlen($regs[8])-2,2);
868 $regs[4] = $regs[4] +
$h;
869 $regs[5] = $regs[5] +
$m;
870 } elseif($op == '+'){
871 $regs[4] = $regs[4] - $h;
872 $regs[5] = $regs[5] - $m;
875 return strtotime("$regs[1]-$regs[2]-$regs[3] $regs[4]:$regs[5]:$regs[6]Z");
882 * sleeps some number of microseconds
884 * @param string $usec the number of microseconds to sleep
888 function usleepWindows($usec)
890 $start = gettimeofday();
894 $stop = gettimeofday();
895 $timePassed = 1000000 * ($stop['sec'] - $start['sec'])
896 +
$stop['usec'] - $start['usec'];
898 while ($timePassed < $usec);
904 * Contains information for a SOAP fault.
905 * Mainly used for returning faults from deployed functions
906 * in a server instance.
907 * @author Dietrich Ayala <dietrich@ganx4.com>
911 class soap_fault
extends nusoap_base
{
913 * The fault code (client|server)
925 * The fault string, a description of the fault
931 * The fault detail, typically a string or array of string
940 * @param string $faultcode (client | server)
941 * @param string $faultactor only used when msg routed between multiple actors
942 * @param string $faultstring human readable error message
943 * @param mixed $faultdetail detail, typically a string or array of string
945 function soap_fault($faultcode,$faultactor='',$faultstring='',$faultdetail=''){
946 parent
::nusoap_base();
947 $this->faultcode
= $faultcode;
948 $this->faultactor
= $faultactor;
949 $this->faultstring
= $faultstring;
950 $this->faultdetail
= $faultdetail;
956 * @return string The serialization of the fault instance.
959 function serialize(){
961 foreach($this->namespaces
as $k => $v){
962 $ns_string .= "\n xmlns:$k=\"$v\"";
965 '<?xml version="1.0" encoding="'.$this->soap_defencoding
.'"?>'.
966 '<SOAP-ENV:Envelope SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"'.$ns_string.">\n".
969 $this->serialize_val($this->faultcode
, 'faultcode').
970 $this->serialize_val($this->faultactor
, 'faultactor').
971 $this->serialize_val($this->faultstring
, 'faultstring').
972 $this->serialize_val($this->faultdetail
, 'detail').
975 '</SOAP-ENV:Envelope>';
983 * parses an XML Schema, allows access to it's data, other utility methods
984 * no validation... yet.
985 * very experimental and limited. As is discussed on XML-DEV, I'm one of the people
986 * that just doesn't have time to read the spec(s) thoroughly, and just have a couple of trusty
987 * tutorials I refer to :)
989 * @author Dietrich Ayala <dietrich@ganx4.com>
993 class XMLSchema
extends nusoap_base
{
999 var $enclosingNamespaces;
1001 var $schemaInfo = array();
1002 var $schemaTargetNamespace = '';
1003 // types, elements, attributes defined by the schema
1004 var $attributes = array();
1005 var $complexTypes = array();
1006 var $complexTypeStack = array();
1007 var $currentComplexType = null;
1008 var $elements = array();
1009 var $elementStack = array();
1010 var $currentElement = null;
1011 var $simpleTypes = array();
1012 var $simpleTypeStack = array();
1013 var $currentSimpleType = null;
1015 var $imports = array();
1020 var $depth_array = array();
1021 var $message = array();
1022 var $defaultNamespace = array();
1027 * @param string $schema schema document URI
1028 * @param string $xml xml document URI
1029 * @param string $namespaces namespaces defined in enclosing XML
1032 function XMLSchema($schema='',$xml='',$namespaces=array()){
1033 parent
::nusoap_base();
1034 $this->debug('xmlschema class instantiated, inside constructor');
1036 $this->schema
= $schema;
1040 $this->enclosingNamespaces
= $namespaces;
1041 $this->namespaces
= array_merge($this->namespaces
, $namespaces);
1043 // parse schema file
1045 $this->debug('initial schema file: '.$schema);
1046 $this->parseFile($schema, 'schema');
1051 $this->debug('initial xml file: '.$xml);
1052 $this->parseFile($xml, 'xml');
1060 * @param string $xml, path/URL to XML file
1061 * @param string $type, (schema | xml)
1065 function parseFile($xml,$type){
1068 $xmlStr = @join
("",@file
($xml));
1070 $msg = 'Error reading XML from '.$xml;
1071 $this->setError($msg);
1075 $this->debug("parsing $xml");
1076 $this->parseString($xmlStr,$type);
1077 $this->debug("done parsing $xml");
1085 * parse an XML string
1087 * @param string $xml path or URL
1088 * @param string $type, (schema|xml)
1091 function parseString($xml,$type){
1095 // Create an XML parser.
1096 $this->parser
= xml_parser_create();
1097 // Set the options for parsing the XML data.
1098 xml_parser_set_option($this->parser
, XML_OPTION_CASE_FOLDING
, 0);
1100 // Set the object for the parser.
1101 xml_set_object($this->parser
, $this);
1103 // Set the element handlers for the parser.
1104 if($type == "schema"){
1105 xml_set_element_handler($this->parser
, 'schemaStartElement','schemaEndElement');
1106 xml_set_character_data_handler($this->parser
,'schemaCharacterData');
1107 } elseif($type == "xml"){
1108 xml_set_element_handler($this->parser
, 'xmlStartElement','xmlEndElement');
1109 xml_set_character_data_handler($this->parser
,'xmlCharacterData');
1112 // Parse the XML file.
1113 if(!xml_parse($this->parser
,$xml,true)){
1114 // Display an error message.
1115 $errstr = sprintf('XML error parsing XML schema on line %d: %s',
1116 xml_get_current_line_number($this->parser
),
1117 xml_error_string(xml_get_error_code($this->parser
))
1119 $this->debug($errstr);
1120 $this->debug("XML payload:\n" . $xml);
1121 $this->setError($errstr);
1124 xml_parser_free($this->parser
);
1126 $this->debug('no xml passed to parseString()!!');
1127 $this->setError('no xml passed to parseString()!!');
1132 * start-element handler
1134 * @param string $parser XML parser object
1135 * @param string $name element name
1136 * @param string $attrs associative array of attributes
1139 function schemaStartElement($parser, $name, $attrs) {
1141 // position in the total number of elements, starting from 0
1142 $pos = $this->position++
;
1143 $depth = $this->depth++
;
1144 // set self as current value for this depth
1145 $this->depth_array
[$depth] = $pos;
1146 $this->message
[$pos] = array('cdata' => '');
1148 $this->defaultNamespace
[$pos] = $this->defaultNamespace
[$this->depth_array
[$depth - 1]];
1150 $this->defaultNamespace
[$pos] = false;
1153 // get element prefix
1154 if($prefix = $this->getPrefix($name)){
1155 // get unqualified name
1156 $name = $this->getLocalPart($name);
1161 // loop thru attributes, expanding, and registering namespace declarations
1162 if(count($attrs) > 0){
1163 foreach($attrs as $k => $v){
1164 // if ns declarations, add to class level array of valid namespaces
1165 if(ereg("^xmlns",$k)){
1166 //$this->xdebug("$k: $v");
1167 //$this->xdebug('ns_prefix: '.$this->getPrefix($k));
1168 if($ns_prefix = substr(strrchr($k,':'),1)){
1169 //$this->xdebug("Add namespace[$ns_prefix] = $v");
1170 $this->namespaces
[$ns_prefix] = $v;
1172 $this->defaultNamespace
[$pos] = $v;
1173 if (! $this->getPrefixFromNamespace($v)) {
1174 $this->namespaces
['ns'.(count($this->namespaces
)+
1)] = $v;
1177 if($v == 'http://www.w3.org/2001/XMLSchema' ||
$v == 'http://www.w3.org/1999/XMLSchema' ||
$v == 'http://www.w3.org/2000/10/XMLSchema'){
1178 $this->XMLSchemaVersion
= $v;
1179 $this->namespaces
['xsi'] = $v.'-instance';
1183 foreach($attrs as $k => $v){
1184 // expand each attribute
1185 $k = strpos($k,':') ?
$this->expandQname($k) : $k;
1186 $v = strpos($v,':') ?
$this->expandQname($v) : $v;
1193 // find status, register data
1195 case 'all': // (optional) compositor content for a complexType
1199 //$this->xdebug("compositor $name for currentComplexType: $this->currentComplexType and currentElement: $this->currentElement");
1200 $this->complexTypes
[$this->currentComplexType
]['compositor'] = $name;
1201 //if($name == 'all' || $name == 'sequence'){
1202 // $this->complexTypes[$this->currentComplexType]['phpType'] = 'struct';
1205 case 'attribute': // complexType attribute
1206 //$this->xdebug("parsing attribute $attrs[name] $attrs[ref] of value: ".$attrs['http://schemas.xmlsoap.org/wsdl/:arrayType']);
1207 $this->xdebug("parsing attribute:");
1208 $this->appendDebug($this->varDump($attrs));
1209 if (!isset($attrs['form'])) {
1210 $attrs['form'] = $this->schemaInfo
['attributeFormDefault'];
1212 if (isset($attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'])) {
1213 $v = $attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'];
1214 if (!strpos($v, ':')) {
1215 // no namespace in arrayType attribute value...
1216 if ($this->defaultNamespace
[$pos]) {
1217 // ...so use the default
1218 $attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'] = $this->defaultNamespace
[$pos] . ':' . $attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'];
1222 if(isset($attrs['name'])){
1223 $this->attributes
[$attrs['name']] = $attrs;
1224 $aname = $attrs['name'];
1225 } elseif(isset($attrs['ref']) && $attrs['ref'] == 'http://schemas.xmlsoap.org/soap/encoding/:arrayType'){
1226 if (isset($attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'])) {
1227 $aname = $attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'];
1231 } elseif(isset($attrs['ref'])){
1232 $aname = $attrs['ref'];
1233 $this->attributes
[$attrs['ref']] = $attrs;
1236 if($this->currentComplexType
){ // This should *always* be
1237 $this->complexTypes
[$this->currentComplexType
]['attrs'][$aname] = $attrs;
1239 // arrayType attribute
1240 if(isset($attrs['http://schemas.xmlsoap.org/wsdl/:arrayType']) ||
$this->getLocalPart($aname) == 'arrayType'){
1241 $this->complexTypes
[$this->currentComplexType
]['phpType'] = 'array';
1242 $prefix = $this->getPrefix($aname);
1243 if(isset($attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'])){
1244 $v = $attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'];
1248 if(strpos($v,'[,]')){
1249 $this->complexTypes
[$this->currentComplexType
]['multidimensional'] = true;
1251 $v = substr($v,0,strpos($v,'[')); // clip the []
1252 if(!strpos($v,':') && isset($this->typemap
[$this->XMLSchemaVersion
][$v])){
1253 $v = $this->XMLSchemaVersion
.':'.$v;
1255 $this->complexTypes
[$this->currentComplexType
]['arrayType'] = $v;
1258 case 'complexContent': // (optional) content for a complexType
1261 array_push($this->complexTypeStack
, $this->currentComplexType
);
1262 if(isset($attrs['name'])){
1263 $this->xdebug('processing named complexType '.$attrs['name']);
1264 //$this->currentElement = false;
1265 $this->currentComplexType
= $attrs['name'];
1266 $this->complexTypes
[$this->currentComplexType
] = $attrs;
1267 $this->complexTypes
[$this->currentComplexType
]['typeClass'] = 'complexType';
1268 // This is for constructs like
1269 // <complexType name="ListOfString" base="soap:Array">
1271 // <element name="string" type="xsd:string"
1272 // minOccurs="0" maxOccurs="unbounded" />
1275 if(isset($attrs['base']) && ereg(':Array$',$attrs['base'])){
1276 $this->xdebug('complexType is unusual array');
1277 $this->complexTypes
[$this->currentComplexType
]['phpType'] = 'array';
1279 $this->complexTypes
[$this->currentComplexType
]['phpType'] = 'struct';
1282 $this->xdebug('processing unnamed complexType for element '.$this->currentElement
);
1283 $this->currentComplexType
= $this->currentElement
. '_ContainedType';
1284 //$this->currentElement = false;
1285 $this->complexTypes
[$this->currentComplexType
] = $attrs;
1286 $this->complexTypes
[$this->currentComplexType
]['typeClass'] = 'complexType';
1287 // This is for constructs like
1288 // <complexType name="ListOfString" base="soap:Array">
1290 // <element name="string" type="xsd:string"
1291 // minOccurs="0" maxOccurs="unbounded" />
1294 if(isset($attrs['base']) && ereg(':Array$',$attrs['base'])){
1295 $this->xdebug('complexType is unusual array');
1296 $this->complexTypes
[$this->currentComplexType
]['phpType'] = 'array';
1298 $this->complexTypes
[$this->currentComplexType
]['phpType'] = 'struct';
1303 array_push($this->elementStack
, $this->currentElement
);
1304 // elements defined as part of a complex type should
1305 // not really be added to $this->elements, but for some
1307 if (!isset($attrs['form'])) {
1308 $attrs['form'] = $this->schemaInfo
['elementFormDefault'];
1310 if(isset($attrs['type'])){
1311 $this->xdebug("processing typed element ".$attrs['name']." of type ".$attrs['type']);
1312 if (! $this->getPrefix($attrs['type'])) {
1313 if ($this->defaultNamespace
[$pos]) {
1314 $attrs['type'] = $this->defaultNamespace
[$pos] . ':' . $attrs['type'];
1315 $this->xdebug('used default namespace to make type ' . $attrs['type']);
1318 // This is for constructs like
1319 // <complexType name="ListOfString" base="soap:Array">
1321 // <element name="string" type="xsd:string"
1322 // minOccurs="0" maxOccurs="unbounded" />
1325 if ($this->currentComplexType
&& $this->complexTypes
[$this->currentComplexType
]['phpType'] == 'array') {
1326 $this->xdebug('arrayType for unusual array is ' . $attrs['type']);
1327 $this->complexTypes
[$this->currentComplexType
]['arrayType'] = $attrs['type'];
1329 $this->currentElement
= $attrs['name'];
1330 $this->elements
[ $attrs['name'] ] = $attrs;
1331 $this->elements
[ $attrs['name'] ]['typeClass'] = 'element';
1332 $ename = $attrs['name'];
1333 } elseif(isset($attrs['ref'])){
1334 $this->xdebug("processing element as ref to ".$attrs['ref']);
1335 $this->currentElement
= "ref to ".$attrs['ref'];
1336 $ename = $this->getLocalPart($attrs['ref']);
1338 $this->xdebug("processing untyped element ".$attrs['name']);
1339 $this->currentElement
= $attrs['name'];
1340 $this->elements
[ $attrs['name'] ] = $attrs;
1341 $this->elements
[ $attrs['name'] ]['typeClass'] = 'element';
1342 $attrs['type'] = $this->schemaTargetNamespace
. ':' . $attrs['name'] . '_ContainedType';
1343 $this->elements
[ $attrs['name'] ]['type'] = $attrs['type'];
1344 $ename = $attrs['name'];
1346 if(isset($ename) && $this->currentComplexType
){
1347 $this->complexTypes
[$this->currentComplexType
]['elements'][$ename] = $attrs;
1350 case 'enumeration': // restriction value list member
1351 $this->xdebug('enumeration ' . $attrs['value']);
1352 if ($this->currentSimpleType
) {
1353 $this->simpleTypes
[$this->currentSimpleType
]['enumeration'][] = $attrs['value'];
1354 } elseif ($this->currentComplexType
) {
1355 $this->complexTypes
[$this->currentComplexType
]['enumeration'][] = $attrs['value'];
1358 case 'extension': // simpleContent or complexContent type extension
1359 $this->xdebug('extension ' . $attrs['base']);
1360 if ($this->currentComplexType
) {
1361 $this->complexTypes
[$this->currentComplexType
]['extensionBase'] = $attrs['base'];
1365 if (isset($attrs['schemaLocation'])) {
1366 //$this->xdebug('import namespace ' . $attrs['namespace'] . ' from ' . $attrs['schemaLocation']);
1367 $this->imports
[$attrs['namespace']][] = array('location' => $attrs['schemaLocation'], 'loaded' => false);
1369 //$this->xdebug('import namespace ' . $attrs['namespace']);
1370 $this->imports
[$attrs['namespace']][] = array('location' => '', 'loaded' => true);
1371 if (! $this->getPrefixFromNamespace($attrs['namespace'])) {
1372 $this->namespaces
['ns'.(count($this->namespaces
)+
1)] = $attrs['namespace'];
1376 case 'list': // simpleType value list
1378 case 'restriction': // simpleType, simpleContent or complexContent value restriction
1379 $this->xdebug('restriction ' . $attrs['base']);
1380 if($this->currentSimpleType
){
1381 $this->simpleTypes
[$this->currentSimpleType
]['type'] = $attrs['base'];
1382 } elseif($this->currentComplexType
){
1383 $this->complexTypes
[$this->currentComplexType
]['restrictionBase'] = $attrs['base'];
1384 if(strstr($attrs['base'],':') == ':Array'){
1385 $this->complexTypes
[$this->currentComplexType
]['phpType'] = 'array';
1390 $this->schemaInfo
= $attrs;
1391 $this->schemaInfo
['schemaVersion'] = $this->getNamespaceFromPrefix($prefix);
1392 if (isset($attrs['targetNamespace'])) {
1393 $this->schemaTargetNamespace
= $attrs['targetNamespace'];
1395 if (!isset($attrs['elementFormDefault'])) {
1396 $this->schemaInfo
['elementFormDefault'] = 'unqualified';
1398 if (!isset($attrs['attributeFormDefault'])) {
1399 $this->schemaInfo
['attributeFormDefault'] = 'unqualified';
1402 case 'simpleContent': // (optional) content for a complexType
1405 array_push($this->simpleTypeStack
, $this->currentSimpleType
);
1406 if(isset($attrs['name'])){
1407 $this->xdebug("processing simpleType for name " . $attrs['name']);
1408 $this->currentSimpleType
= $attrs['name'];
1409 $this->simpleTypes
[ $attrs['name'] ] = $attrs;
1410 $this->simpleTypes
[ $attrs['name'] ]['typeClass'] = 'simpleType';
1411 $this->simpleTypes
[ $attrs['name'] ]['phpType'] = 'scalar';
1413 $this->xdebug('processing unnamed simpleType for element '.$this->currentElement
);
1414 $this->currentSimpleType
= $this->currentElement
. '_ContainedType';
1415 //$this->currentElement = false;
1416 $this->simpleTypes
[$this->currentSimpleType
] = $attrs;
1417 $this->simpleTypes
[$this->currentSimpleType
]['phpType'] = 'scalar';
1420 case 'union': // simpleType type list
1423 //$this->xdebug("do not have anything to do for element $name");
1428 * end-element handler
1430 * @param string $parser XML parser object
1431 * @param string $name element name
1434 function schemaEndElement($parser, $name) {
1435 // bring depth down a notch
1437 // position of current element is equal to the last value left in depth_array for my depth
1438 if(isset($this->depth_array
[$this->depth
])){
1439 $pos = $this->depth_array
[$this->depth
];
1441 // get element prefix
1442 if ($prefix = $this->getPrefix($name)){
1443 // get unqualified name
1444 $name = $this->getLocalPart($name);
1449 if($name == 'complexType'){
1450 $this->xdebug('done processing complexType ' . ($this->currentComplexType ?
$this->currentComplexType
: '(unknown)'));
1451 $this->currentComplexType
= array_pop($this->complexTypeStack
);
1452 //$this->currentElement = false;
1454 if($name == 'element'){
1455 $this->xdebug('done processing element ' . ($this->currentElement ?
$this->currentElement
: '(unknown)'));
1456 $this->currentElement
= array_pop($this->elementStack
);
1458 if($name == 'simpleType'){
1459 $this->xdebug('done processing simpleType ' . ($this->currentSimpleType ?
$this->currentSimpleType
: '(unknown)'));
1460 $this->currentSimpleType
= array_pop($this->simpleTypeStack
);
1465 * element content handler
1467 * @param string $parser XML parser object
1468 * @param string $data element content
1471 function schemaCharacterData($parser, $data){
1472 $pos = $this->depth_array
[$this->depth
- 1];
1473 $this->message
[$pos]['cdata'] .= $data;
1477 * serialize the schema
1481 function serializeSchema(){
1483 $schemaPrefix = $this->getPrefixFromNamespace($this->XMLSchemaVersion
);
1486 if (sizeof($this->imports
) > 0) {
1487 foreach($this->imports
as $ns => $list) {
1488 foreach ($list as $ii) {
1489 if ($ii['location'] != '') {
1490 $xml .= " <$schemaPrefix:import location=\"" . $ii['location'] . '" namespace="' . $ns . "\" />\n";
1492 $xml .= " <$schemaPrefix:import namespace=\"" . $ns . "\" />\n";
1498 foreach($this->complexTypes
as $typeName => $attrs){
1500 // serialize child elements
1501 if(isset($attrs['elements']) && (count($attrs['elements']) > 0)){
1502 foreach($attrs['elements'] as $element => $eParts){
1503 if(isset($eParts['ref'])){
1504 $contentStr .= " <$schemaPrefix:element ref=\"$element\"/>\n";
1506 $contentStr .= " <$schemaPrefix:element name=\"$element\" type=\"" . $this->contractQName($eParts['type']) . "\"";
1507 foreach ($eParts as $aName => $aValue) {
1508 // handle, e.g., abstract, default, form, minOccurs, maxOccurs, nillable
1509 if ($aName != 'name' && $aName != 'type') {
1510 $contentStr .= " $aName=\"$aValue\"";
1513 $contentStr .= "/>\n";
1516 // compositor wraps elements
1517 if (isset($attrs['compositor']) && ($attrs['compositor'] != '')) {
1518 $contentStr = " <$schemaPrefix:$attrs[compositor]>\n".$contentStr." </$schemaPrefix:$attrs[compositor]>\n";
1522 if(isset($attrs['attrs']) && (count($attrs['attrs']) >= 1)){
1523 foreach($attrs['attrs'] as $attr => $aParts){
1524 $contentStr .= " <$schemaPrefix:attribute";
1525 foreach ($aParts as $a => $v) {
1526 if ($a == 'ref' ||
$a == 'type') {
1527 $contentStr .= " $a=\"".$this->contractQName($v).'"';
1528 } elseif ($a == 'http://schemas.xmlsoap.org/wsdl/:arrayType') {
1529 $this->usedNamespaces
['wsdl'] = $this->namespaces
['wsdl'];
1530 $contentStr .= ' wsdl:arrayType="'.$this->contractQName($v).'"';
1532 $contentStr .= " $a=\"$v\"";
1535 $contentStr .= "/>\n";
1539 if (isset($attrs['restrictionBase']) && $attrs['restrictionBase'] != ''){
1540 $contentStr = " <$schemaPrefix:restriction base=\"".$this->contractQName($attrs['restrictionBase'])."\">\n".$contentStr." </$schemaPrefix:restriction>\n";
1541 // complex or simple content
1542 if ((isset($attrs['elements']) && count($attrs['elements']) > 0) ||
(isset($attrs['attrs']) && count($attrs['attrs']) > 0)){
1543 $contentStr = " <$schemaPrefix:complexContent>\n".$contentStr." </$schemaPrefix:complexContent>\n";
1546 // finalize complex type
1547 if($contentStr != ''){
1548 $contentStr = " <$schemaPrefix:complexType name=\"$typeName\">\n".$contentStr." </$schemaPrefix:complexType>\n";
1550 $contentStr = " <$schemaPrefix:complexType name=\"$typeName\"/>\n";
1552 $xml .= $contentStr;
1555 if(isset($this->simpleTypes
) && count($this->simpleTypes
) > 0){
1556 foreach($this->simpleTypes
as $typeName => $eParts){
1557 $xml .= " <$schemaPrefix:simpleType name=\"$typeName\">\n <$schemaPrefix:restriction base=\"".$this->contractQName($eParts['type'])."\"/>\n";
1558 if (isset($eParts['enumeration'])) {
1559 foreach ($eParts['enumeration'] as $e) {
1560 $xml .= " <$schemaPrefix:enumeration value=\"$e\"/>\n";
1563 $xml .= " </$schemaPrefix:simpleType>";
1567 if(isset($this->elements
) && count($this->elements
) > 0){
1568 foreach($this->elements
as $element => $eParts){
1569 $xml .= " <$schemaPrefix:element name=\"$element\" type=\"".$this->contractQName($eParts['type'])."\"/>\n";
1573 if(isset($this->attributes
) && count($this->attributes
) > 0){
1574 foreach($this->attributes
as $attr => $aParts){
1575 $xml .= " <$schemaPrefix:attribute name=\"$attr\" type=\"".$this->contractQName($aParts['type'])."\"\n/>";
1579 $el = "<$schemaPrefix:schema targetNamespace=\"$this->schemaTargetNamespace\"\n";
1580 foreach (array_diff($this->usedNamespaces
, $this->enclosingNamespaces
) as $nsp => $ns) {
1581 $el .= " xmlns:$nsp=\"$ns\"\n";
1583 $xml = $el . ">\n".$xml."</$schemaPrefix:schema>\n";
1588 * adds debug data to the clas level debug string
1590 * @param string $string debug data
1593 function xdebug($string){
1594 $this->debug('<' . $this->schemaTargetNamespace
. '> '.$string);
1598 * get the PHP type of a user defined type in the schema
1599 * PHP type is kind of a misnomer since it actually returns 'struct' for assoc. arrays
1600 * returns false if no type exists, or not w/ the given namespace
1601 * else returns a string that is either a native php type, or 'struct'
1603 * @param string $type, name of defined type
1604 * @param string $ns, namespace of type
1609 function getPHPType($type,$ns){
1610 if(isset($this->typemap
[$ns][$type])){
1611 //print "found type '$type' and ns $ns in typemap<br>";
1612 return $this->typemap
[$ns][$type];
1613 } elseif(isset($this->complexTypes
[$type])){
1614 //print "getting type '$type' and ns $ns from complexTypes array<br>";
1615 return $this->complexTypes
[$type]['phpType'];
1621 * returns an associative array of information about a given type
1622 * returns false if no type exists by the given name
1624 * For a complexType typeDef = array(
1625 * 'restrictionBase' => '',
1627 * 'compositor' => '(sequence|all)',
1628 * 'elements' => array(), // refs to elements array
1629 * 'attrs' => array() // refs to attributes array
1630 * ... and so on (see addComplexType)
1633 * For simpleType or element, the array has different keys.
1638 * @see addComplexType
1639 * @see addSimpleType
1642 function getTypeDef($type){
1643 //$this->debug("in getTypeDef for type $type");
1644 if(isset($this->complexTypes
[$type])){
1645 $this->xdebug("in getTypeDef, found complexType $type");
1646 return $this->complexTypes
[$type];
1647 } elseif(isset($this->simpleTypes
[$type])){
1648 $this->xdebug("in getTypeDef, found simpleType $type");
1649 if (!isset($this->simpleTypes
[$type]['phpType'])) {
1650 // get info for type to tack onto the simple type
1651 // TODO: can this ever really apply (i.e. what is a simpleType really?)
1652 $uqType = substr($this->simpleTypes
[$type]['type'], strrpos($this->simpleTypes
[$type]['type'], ':') +
1);
1653 $ns = substr($this->simpleTypes
[$type]['type'], 0, strrpos($this->simpleTypes
[$type]['type'], ':'));
1654 $etype = $this->getTypeDef($uqType);
1656 $this->xdebug("in getTypeDef, found type for simpleType $type:");
1657 $this->xdebug($this->varDump($etype));
1658 if (isset($etype['phpType'])) {
1659 $this->simpleTypes
[$type]['phpType'] = $etype['phpType'];
1661 if (isset($etype['elements'])) {
1662 $this->simpleTypes
[$type]['elements'] = $etype['elements'];
1666 return $this->simpleTypes
[$type];
1667 } elseif(isset($this->elements
[$type])){
1668 $this->xdebug("in getTypeDef, found element $type");
1669 if (!isset($this->elements
[$type]['phpType'])) {
1670 // get info for type to tack onto the element
1671 $uqType = substr($this->elements
[$type]['type'], strrpos($this->elements
[$type]['type'], ':') +
1);
1672 $ns = substr($this->elements
[$type]['type'], 0, strrpos($this->elements
[$type]['type'], ':'));
1673 $etype = $this->getTypeDef($uqType);
1675 $this->xdebug("in getTypeDef, found type for element $type:");
1676 $this->xdebug($this->varDump($etype));
1677 if (isset($etype['phpType'])) {
1678 $this->elements
[$type]['phpType'] = $etype['phpType'];
1680 if (isset($etype['elements'])) {
1681 $this->elements
[$type]['elements'] = $etype['elements'];
1683 } elseif ($ns == 'http://www.w3.org/2001/XMLSchema') {
1684 $this->xdebug("in getTypeDef, element $type is an XSD type");
1685 $this->elements
[$type]['phpType'] = 'scalar';
1688 return $this->elements
[$type];
1689 } elseif(isset($this->attributes
[$type])){
1690 $this->xdebug("in getTypeDef, found attribute $type");
1691 return $this->attributes
[$type];
1692 } elseif (ereg('_ContainedType$', $type)) {
1693 $this->xdebug("in getTypeDef, have an untyped element $type");
1694 $typeDef['typeClass'] = 'simpleType';
1695 $typeDef['phpType'] = 'scalar';
1696 $typeDef['type'] = 'http://www.w3.org/2001/XMLSchema:string';
1699 $this->xdebug("in getTypeDef, did not find $type");
1704 * returns a sample serialization of a given type, or false if no type by the given name
1706 * @param string $type, name of type
1711 function serializeTypeDef($type){
1712 //print "in sTD() for type $type<br>";
1713 if($typeDef = $this->getTypeDef($type)){
1715 if(is_array($typeDef['attrs'])){
1716 foreach($attrs as $attName => $data){
1717 $str .= " $attName=\"{type = ".$data['type']."}\"";
1720 $str .= " xmlns=\"".$this->schema
['targetNamespace']."\"";
1721 if(count($typeDef['elements']) > 0){
1723 foreach($typeDef['elements'] as $element => $eData){
1724 $str .= $this->serializeTypeDef($element);
1727 } elseif($typeDef['typeClass'] == 'element') {
1728 $str .= "></$type>";
1738 * returns HTML form elements that allow a user
1739 * to enter values for creating an instance of the given type.
1741 * @param string $name, name for type instance
1742 * @param string $type, name of type
1747 function typeToForm($name,$type){
1749 if($typeDef = $this->getTypeDef($type)){
1751 if($typeDef['phpType'] == 'struct'){
1752 $buffer .= '<table>';
1753 foreach($typeDef['elements'] as $child => $childDef){
1755 <tr><td align='right'>$childDef[name] (type: ".$this->getLocalPart($childDef['type'])."):</td>
1756 <td><input type='text' name='parameters[".$name."][$childDef[name]]'></td></tr>";
1758 $buffer .= '</table>';
1760 } elseif($typeDef['phpType'] == 'array'){
1761 $buffer .= '<table>';
1762 for($i=0;$i < 3; $i++
){
1764 <tr><td align='right'>array item (type: $typeDef[arrayType]):</td>
1765 <td><input type='text' name='parameters[".$name."][]'></td></tr>";
1767 $buffer .= '</table>';
1770 $buffer .= "<input type='text' name='parameters[$name]'>";
1773 $buffer .= "<input type='text' name='parameters[$name]'>";
1779 * adds a complex type to the schema
1789 * array('ref'=>'SOAP-ENC:arrayType','wsdl:arrayType'=>'string[]'),
1793 * example: PHP associative array ( SOAP Struct )
1800 * array('myVar'=> array('name'=>'myVar','type'=>'string')
1804 * @param typeClass (complexType|simpleType|attribute)
1805 * @param phpType: currently supported are array and struct (php assoc array)
1806 * @param compositor (all|sequence|choice)
1807 * @param restrictionBase namespace:name (http://schemas.xmlsoap.org/soap/encoding/:Array)
1808 * @param elements = array ( name = array(name=>'',type=>'') )
1809 * @param attrs = array(
1811 * 'ref' => "http://schemas.xmlsoap.org/soap/encoding/:arrayType",
1812 * "http://schemas.xmlsoap.org/wsdl/:arrayType" => "string[]"
1815 * @param arrayType: namespace:name (http://www.w3.org/2001/XMLSchema:string)
1819 function addComplexType($name,$typeClass='complexType',$phpType='array',$compositor='',$restrictionBase='',$elements=array(),$attrs=array(),$arrayType=''){
1820 $this->complexTypes
[$name] = array(
1822 'typeClass' => $typeClass,
1823 'phpType' => $phpType,
1824 'compositor'=> $compositor,
1825 'restrictionBase' => $restrictionBase,
1826 'elements' => $elements,
1828 'arrayType' => $arrayType
1831 $this->xdebug("addComplexType $name:");
1832 $this->appendDebug($this->varDump($this->complexTypes
[$name]));
1836 * adds a simple type to the schema
1838 * @param string $name
1839 * @param string $restrictionBase namespace:name (http://schemas.xmlsoap.org/soap/encoding/:Array)
1840 * @param string $typeClass (should always be simpleType)
1841 * @param string $phpType (should always be scalar)
1842 * @param array $enumeration array of values
1847 function addSimpleType($name, $restrictionBase='', $typeClass='simpleType', $phpType='scalar', $enumeration=array()) {
1848 $this->simpleTypes
[$name] = array(
1850 'typeClass' => $typeClass,
1851 'phpType' => $phpType,
1852 'type' => $restrictionBase,
1853 'enumeration' => $enumeration
1856 $this->xdebug("addSimpleType $name:");
1857 $this->appendDebug($this->varDump($this->simpleTypes
[$name]));
1861 * adds an element to the schema
1863 * @param array $attrs attributes that must include name and type
1867 function addElement($attrs) {
1868 if (! $this->getPrefix($attrs['type'])) {
1869 $attrs['type'] = $this->schemaTargetNamespace
. ':' . $attrs['type'];
1871 $this->elements
[ $attrs['name'] ] = $attrs;
1872 $this->elements
[ $attrs['name'] ]['typeClass'] = 'element';
1874 $this->xdebug("addElement " . $attrs['name']);
1875 $this->appendDebug($this->varDump($this->elements
[ $attrs['name'] ]));
1882 * For creating serializable abstractions of native PHP types. This class
1883 * allows element name/namespace, XSD type, and XML attributes to be
1884 * associated with a value. This is extremely useful when WSDL is not
1885 * used, but is also useful when WSDL is used with polymorphic types, including
1886 * xsd:anyType and user-defined types.
1888 * @author Dietrich Ayala <dietrich@ganx4.com>
1892 class soapval
extends nusoap_base
{
1894 * The XML element name
1901 * The XML type name (string or false)
1915 * The XML element namespace (string or false)
1922 * The XML type namespace (string or false)
1929 * The XML element attributes (array or false)
1939 * @param string $name optional name
1940 * @param mixed $type optional type name
1941 * @param mixed $value optional value
1942 * @param mixed $element_ns optional namespace of value
1943 * @param mixed $type_ns optional namespace of type
1944 * @param mixed $attributes associative array of attributes to add to element serialization
1947 function soapval($name='soapval',$type=false,$value=-1,$element_ns=false,$type_ns=false,$attributes=false) {
1948 parent
::nusoap_base();
1949 $this->name
= $name;
1950 $this->type
= $type;
1951 $this->value
= $value;
1952 $this->element_ns
= $element_ns;
1953 $this->type_ns
= $type_ns;
1954 $this->attributes
= $attributes;
1958 * return serialized value
1960 * @param string $use The WSDL use value (encoded|literal)
1961 * @return string XML data
1964 function serialize($use='encoded') {
1965 return $this->serialize_val($this->value
,$this->name
,$this->type
,$this->element_ns
,$this->type_ns
,$this->attributes
,$use);
1969 * decodes a soapval object into a PHP native type
1975 return $this->value
;
1982 * transport class for sending/receiving data via HTTP and HTTPS
1983 * NOTE: PHP must be compiled with the CURL extension for HTTPS support
1985 * @author Dietrich Ayala <dietrich@ganx4.com>
1989 class soap_transport_http
extends nusoap_base
{
1993 var $digest_uri = '';
1998 var $request_method = 'POST';
1999 var $protocol_version = '1.0';
2001 var $outgoing_headers = array();
2002 var $incoming_headers = array();
2003 var $incoming_cookies = array();
2004 var $outgoing_payload = '';
2005 var $incoming_payload = '';
2006 var $useSOAPAction = true;
2007 var $persistentConnection = false;
2008 var $ch = false; // cURL handle
2012 var $digestRequest = array();
2013 var $certRequest = array(); // keys must be cainfofile (optional), sslcertfile, sslkeyfile, passphrase, verifypeer (optional), verifyhost (optional)
2014 // cainfofile: certificate authority file, e.g. '$pathToPemFiles/rootca.pem'
2015 // sslcertfile: SSL certificate file, e.g. '$pathToPemFiles/mycert.pem'
2016 // sslkeyfile: SSL key file, e.g. '$pathToPemFiles/mykey.pem'
2017 // passphrase: SSL key password/passphrase
2018 // verifypeer: default is 1
2019 // verifyhost: default is 1
2024 function soap_transport_http($url){
2025 parent
::nusoap_base();
2026 $this->setURL($url);
2027 ereg('\$Revisio' . 'n: ([^ ]+)', $this->revision
, $rev);
2028 $this->outgoing_headers
['User-Agent'] = $this->title
.'/'.$this->version
.' ('.$rev[1].')';
2029 $this->debug('set User-Agent: ' . $this->outgoing_headers
['User-Agent']);
2032 function setURL($url) {
2035 $u = parse_url($url);
2036 foreach($u as $k => $v){
2037 $this->debug("$k = $v");
2041 // add any GET params to path
2042 if(isset($u['query']) && $u['query'] != ''){
2043 $this->path
.= '?' . $u['query'];
2047 if(!isset($u['port'])){
2048 if($u['scheme'] == 'https'){
2055 $this->uri
= $this->path
;
2056 $this->digest_uri
= $this->uri
;
2059 if (!isset($u['port'])) {
2060 $this->outgoing_headers
['Host'] = $this->host
;
2062 $this->outgoing_headers
['Host'] = $this->host
.':'.$this->port
;
2064 $this->debug('set Host: ' . $this->outgoing_headers
['Host']);
2066 if (isset($u['user']) && $u['user'] != '') {
2067 $this->setCredentials(urldecode($u['user']), isset($u['pass']) ?
urldecode($u['pass']) : '');
2071 function connect($connection_timeout=0,$response_timeout=30){
2072 // For PHP 4.3 with OpenSSL, change https scheme to ssl, then treat like
2073 // "regular" socket.
2074 // TODO: disabled for now because OpenSSL must be *compiled* in (not just
2075 // loaded), and until PHP5 stream_get_wrappers is not available.
2076 // if ($this->scheme == 'https') {
2077 // if (version_compare(phpversion(), '4.3.0') >= 0) {
2078 // if (extension_loaded('openssl')) {
2079 // $this->scheme = 'ssl';
2080 // $this->debug('Using SSL over OpenSSL');
2084 $this->debug("connect connection_timeout $connection_timeout, response_timeout $response_timeout, scheme $this->scheme, host $this->host, port $this->port");
2085 if ($this->scheme
== 'http' ||
$this->scheme
== 'ssl') {
2086 // use persistent connection
2087 if($this->persistentConnection
&& isset($this->fp
) && is_resource($this->fp
)){
2088 if (!feof($this->fp
)) {
2089 $this->debug('Re-use persistent connection');
2093 $this->debug('Closed persistent connection at EOF');
2096 // munge host if using OpenSSL
2097 if ($this->scheme
== 'ssl') {
2098 $host = 'ssl://' . $this->host
;
2100 $host = $this->host
;
2102 $this->debug('calling fsockopen with host ' . $host . ' connection_timeout ' . $connection_timeout);
2105 if($connection_timeout > 0){
2106 $this->fp
= @fsockopen
( $host, $this->port
, $this->errno
, $this->error_str
, $connection_timeout);
2108 $this->fp
= @fsockopen
( $host, $this->port
, $this->errno
, $this->error_str
);
2113 $msg = 'Couldn\'t open socket connection to server ' . $this->url
;
2115 $msg .= ', Error ('.$this->errno
.'): '.$this->error_str
;
2117 $msg .= ' prior to connect(). This is often a problem looking up the host name.';
2120 $this->setError($msg);
2124 // set response timeout
2125 $this->debug('set response timeout to ' . $response_timeout);
2126 socket_set_timeout( $this->fp
, $response_timeout);
2128 $this->debug('socket connected');
2130 } else if ($this->scheme
== 'https') {
2131 if (!extension_loaded('curl')) {
2132 $this->setError('CURL Extension, or OpenSSL extension w/ PHP version >= 4.3 is required for HTTPS');
2135 $this->debug('connect using https');
2137 $this->ch
= curl_init();
2139 $hostURL = ($this->port
!= '') ?
"https://$this->host:$this->port" : "https://$this->host";
2141 $hostURL .= $this->path
;
2142 curl_setopt($this->ch
, CURLOPT_URL
, $hostURL);
2143 // follow location headers (re-directs)
2144 curl_setopt($this->ch
, CURLOPT_FOLLOWLOCATION
, 1);
2145 // ask for headers in the response output
2146 curl_setopt($this->ch
, CURLOPT_HEADER
, 1);
2147 // ask for the response output as the return value
2148 curl_setopt($this->ch
, CURLOPT_RETURNTRANSFER
, 1);
2150 // We manage this ourselves through headers and encoding
2151 // if(function_exists('gzuncompress')){
2152 // curl_setopt($this->ch, CURLOPT_ENCODING, 'deflate');
2154 // persistent connection
2155 if ($this->persistentConnection
) {
2156 // The way we send data, we cannot use persistent connections, since
2157 // there will be some "junk" at the end of our request.
2158 //curl_setopt($this->ch, CURL_HTTP_VERSION_1_1, true);
2159 $this->persistentConnection
= false;
2160 $this->outgoing_headers
['Connection'] = 'close';
2161 $this->debug('set Connection: ' . $this->outgoing_headers
['Connection']);
2164 if ($connection_timeout != 0) {
2165 curl_setopt($this->ch
, CURLOPT_TIMEOUT
, $connection_timeout);
2167 // TODO: cURL has added a connection timeout separate from the response timeout
2168 //if ($connection_timeout != 0) {
2169 // curl_setopt($this->ch, CURLOPT_CONNECTIONTIMEOUT, $connection_timeout);
2171 //if ($response_timeout != 0) {
2172 // curl_setopt($this->ch, CURLOPT_TIMEOUT, $response_timeout);
2175 // recent versions of cURL turn on peer/host checking by default,
2176 // while PHP binaries are not compiled with a default location for the
2177 // CA cert bundle, so disable peer/host checking.
2178 //curl_setopt($this->ch, CURLOPT_CAINFO, 'f:\php-4.3.2-win32\extensions\curl-ca-bundle.crt');
2179 curl_setopt($this->ch
, CURLOPT_SSL_VERIFYPEER
, 0);
2180 curl_setopt($this->ch
, CURLOPT_SSL_VERIFYHOST
, 0);
2182 // support client certificates (thanks Tobias Boes, Doug Anarino, Eryan Ariobowo)
2183 if ($this->authtype
== 'certificate') {
2184 if (isset($this->certRequest
['cainfofile'])) {
2185 curl_setopt($this->ch
, CURLOPT_CAINFO
, $this->certRequest
['cainfofile']);
2187 if (isset($this->certRequest
['verifypeer'])) {
2188 curl_setopt($this->ch
, CURLOPT_SSL_VERIFYPEER
, $this->certRequest
['verifypeer']);
2190 curl_setopt($this->ch
, CURLOPT_SSL_VERIFYPEER
, 1);
2192 if (isset($this->certRequest
['verifyhost'])) {
2193 curl_setopt($this->ch
, CURLOPT_SSL_VERIFYHOST
, $this->certRequest
['verifyhost']);
2195 curl_setopt($this->ch
, CURLOPT_SSL_VERIFYHOST
, 1);
2197 if (isset($this->certRequest
['sslcertfile'])) {
2198 curl_setopt($this->ch
, CURLOPT_SSLCERT
, $this->certRequest
['sslcertfile']);
2200 if (isset($this->certRequest
['sslkeyfile'])) {
2201 curl_setopt($this->ch
, CURLOPT_SSLKEY
, $this->certRequest
['sslkeyfile']);
2203 if (isset($this->certRequest
['passphrase'])) {
2204 curl_setopt($this->ch
, CURLOPT_SSLKEYPASSWD
, $this->certRequest
['passphrase']);
2207 $this->debug('cURL connection set up');
2210 $this->setError('Unknown scheme ' . $this->scheme
);
2211 $this->debug('Unknown scheme ' . $this->scheme
);
2217 * send the SOAP message via HTTP
2219 * @param string $data message data
2220 * @param integer $timeout set connection timeout in seconds
2221 * @param integer $response_timeout set response timeout in seconds
2222 * @param array $cookies cookies to send
2223 * @return string data
2226 function send($data, $timeout=0, $response_timeout=30, $cookies=NULL) {
2228 $this->debug('entered send() with data of length: '.strlen($data));
2230 $this->tryagain
= true;
2232 while ($this->tryagain
) {
2233 $this->tryagain
= false;
2236 if (!$this->connect($timeout, $response_timeout)){
2241 if (!$this->sendRequest($data, $cookies)){
2246 $respdata = $this->getResponse();
2248 $this->setError('Too many tries to get an OK response');
2251 $this->debug('end of send()');
2257 * send the SOAP message via HTTPS 1.0 using CURL
2259 * @param string $msg message data
2260 * @param integer $timeout set connection timeout in seconds
2261 * @param integer $response_timeout set response timeout in seconds
2262 * @param array $cookies cookies to send
2263 * @return string data
2266 function sendHTTPS($data, $timeout=0, $response_timeout=30, $cookies) {
2267 return $this->send($data, $timeout, $response_timeout, $cookies);
2271 * if authenticating, set user credentials here
2273 * @param string $username
2274 * @param string $password
2275 * @param string $authtype (basic, digest, certificate)
2276 * @param array $digestRequest (keys must be nonce, nc, realm, qop)
2277 * @param array $certRequest (keys must be cainfofile (optional), sslcertfile, sslkeyfile, passphrase, verifypeer (optional), verifyhost (optional): see corresponding options in cURL docs)
2280 function setCredentials($username, $password, $authtype = 'basic', $digestRequest = array(), $certRequest = array()) {
2281 $this->debug("Set credentials for authtype $authtype");
2283 if ($authtype == 'basic') {
2284 $this->outgoing_headers
['Authorization'] = 'Basic '.base64_encode(str_replace(':','',$username).':'.$password);
2285 } elseif ($authtype == 'digest') {
2286 if (isset($digestRequest['nonce'])) {
2287 $digestRequest['nc'] = isset($digestRequest['nc']) ?
$digestRequest['nc']++
: 1;
2289 // calculate the Digest hashes (calculate code based on digest implementation found at: http://www.rassoc.com/gregr/weblog/stories/2002/07/09/webServicesSecurityHttpDigestAuthenticationWithoutActiveDirectory.html)
2291 // A1 = unq(username-value) ":" unq(realm-value) ":" passwd
2292 $A1 = $username. ':' . (isset($digestRequest['realm']) ?
$digestRequest['realm'] : '') . ':' . $password;
2297 // A2 = Method ":" digest-uri-value
2298 $A2 = 'POST:' . $this->digest_uri
;
2303 // KD(secret, data) = H(concat(secret, ":", data))
2305 // request-digest = <"> < KD ( H(A1), unq(nonce-value)
2307 // ":" unq(cnonce-value)
2308 // ":" unq(qop-value)
2311 // if qop is missing,
2312 // request-digest = <"> < KD ( H(A1), unq(nonce-value) ":" H(A2) ) > <">
2314 $unhashedDigest = '';
2315 $nonce = isset($digestRequest['nonce']) ?
$digestRequest['nonce'] : '';
2317 if ($digestRequest['qop'] != '') {
2318 $unhashedDigest = $HA1 . ':' . $nonce . ':' . sprintf("%08d", $digestRequest['nc']) . ':' . $cnonce . ':' . $digestRequest['qop'] . ':' . $HA2;
2320 $unhashedDigest = $HA1 . ':' . $nonce . ':' . $HA2;
2323 $hashedDigest = md5($unhashedDigest);
2325 $this->outgoing_headers
['Authorization'] = 'Digest username="' . $username . '", realm="' . $digestRequest['realm'] . '", nonce="' . $nonce . '", uri="' . $this->digest_uri
. '", cnonce="' . $cnonce . '", nc=' . sprintf("%08x", $digestRequest['nc']) . ', qop="' . $digestRequest['qop'] . '", response="' . $hashedDigest . '"';
2327 } elseif ($authtype == 'certificate') {
2328 $this->certRequest
= $certRequest;
2330 $this->username
= $username;
2331 $this->password
= $password;
2332 $this->authtype
= $authtype;
2333 $this->digestRequest
= $digestRequest;
2335 if (isset($this->outgoing_headers
['Authorization'])) {
2336 $this->debug('set Authorization: ' . substr($this->outgoing_headers
['Authorization'], 0, 12) . '...');
2338 $this->debug('Authorization header not set');
2343 * set the soapaction value
2345 * @param string $soapaction
2348 function setSOAPAction($soapaction) {
2349 $this->outgoing_headers
['SOAPAction'] = '"' . $soapaction . '"';
2350 $this->debug('set SOAPAction: ' . $this->outgoing_headers
['SOAPAction']);
2356 * @param string $enc encoding style. supported values: gzip, deflate, or both
2359 function setEncoding($enc='gzip, deflate') {
2360 if (function_exists('gzdeflate')) {
2361 $this->protocol_version
= '1.1';
2362 $this->outgoing_headers
['Accept-Encoding'] = $enc;
2363 $this->debug('set Accept-Encoding: ' . $this->outgoing_headers
['Accept-Encoding']);
2364 if (!isset($this->outgoing_headers
['Connection'])) {
2365 $this->outgoing_headers
['Connection'] = 'close';
2366 $this->persistentConnection
= false;
2367 $this->debug('set Connection: ' . $this->outgoing_headers
['Connection']);
2369 set_magic_quotes_runtime(0);
2371 $this->encoding
= $enc;
2376 * set proxy info here
2378 * @param string $proxyhost
2379 * @param string $proxyport
2380 * @param string $proxyusername
2381 * @param string $proxypassword
2384 function setProxy($proxyhost, $proxyport, $proxyusername = '', $proxypassword = '') {
2385 $this->uri
= $this->url
;
2386 $this->host
= $proxyhost;
2387 $this->port
= $proxyport;
2388 if ($proxyusername != '' && $proxypassword != '') {
2389 $this->outgoing_headers
['Proxy-Authorization'] = ' Basic '.base64_encode($proxyusername.':'.$proxypassword);
2390 $this->debug('set Proxy-Authorization: ' . $this->outgoing_headers
['Proxy-Authorization']);
2395 * decode a string that is encoded w/ "chunked' transfer encoding
2396 * as defined in RFC2068 19.4.6
2398 * @param string $buffer
2404 function decodeChunked($buffer, $lb){
2409 // read chunk-size, chunk-extension (if any) and CRLF
2410 // get the position of the linebreak
2411 $chunkend = strpos($buffer, $lb);
2412 if ($chunkend == FALSE) {
2413 $this->debug('no linebreak found in decodeChunked');
2416 $temp = substr($buffer,0,$chunkend);
2417 $chunk_size = hexdec( trim($temp) );
2418 $chunkstart = $chunkend +
strlen($lb);
2419 // while (chunk-size > 0) {
2420 while ($chunk_size > 0) {
2421 $this->debug("chunkstart: $chunkstart chunk_size: $chunk_size");
2422 $chunkend = strpos( $buffer, $lb, $chunkstart +
$chunk_size);
2424 // Just in case we got a broken connection
2425 if ($chunkend == FALSE) {
2426 $chunk = substr($buffer,$chunkstart);
2427 // append chunk-data to entity-body
2429 $length +
= strlen($chunk);
2433 // read chunk-data and CRLF
2434 $chunk = substr($buffer,$chunkstart,$chunkend-$chunkstart);
2435 // append chunk-data to entity-body
2437 // length := length + chunk-size
2438 $length +
= strlen($chunk);
2439 // read chunk-size and CRLF
2440 $chunkstart = $chunkend +
strlen($lb);
2442 $chunkend = strpos($buffer, $lb, $chunkstart) +
strlen($lb);
2443 if ($chunkend == FALSE) {
2444 break; //Just in case we got a broken connection
2446 $temp = substr($buffer,$chunkstart,$chunkend-$chunkstart);
2447 $chunk_size = hexdec( trim($temp) );
2448 $chunkstart = $chunkend;
2454 * Writes payload, including HTTP headers, to $this->outgoing_payload.
2456 function buildPayload($data, $cookie_str = '') {
2457 // add content-length header
2458 $this->outgoing_headers
['Content-Length'] = strlen($data);
2459 $this->debug('set Content-Length: ' . $this->outgoing_headers
['Content-Length']);
2461 // start building outgoing payload:
2462 $req = "$this->request_method $this->uri HTTP/$this->protocol_version";
2463 $this->debug("HTTP request: $req");
2464 $this->outgoing_payload
= "$req\r\n";
2466 // loop thru headers, serializing
2467 foreach($this->outgoing_headers
as $k => $v){
2469 $this->debug("HTTP header: $hdr");
2470 $this->outgoing_payload
.= "$hdr\r\n";
2474 if ($cookie_str != '') {
2475 $hdr = 'Cookie: '.$cookie_str;
2476 $this->debug("HTTP header: $hdr");
2477 $this->outgoing_payload
.= "$hdr\r\n";
2480 // header/body separator
2481 $this->outgoing_payload
.= "\r\n";
2484 $this->outgoing_payload
.= $data;
2487 function sendRequest($data, $cookies = NULL) {
2488 // build cookie string
2489 $cookie_str = $this->getCookiesForRequest($cookies, (($this->scheme
== 'ssl') ||
($this->scheme
== 'https')));
2492 $this->buildPayload($data, $cookie_str);
2494 if ($this->scheme
== 'http' ||
$this->scheme
== 'ssl') {
2496 if(!fputs($this->fp
, $this->outgoing_payload
, strlen($this->outgoing_payload
))) {
2497 $this->setError('couldn\'t write message data to socket');
2498 $this->debug('couldn\'t write message data to socket');
2501 $this->debug('wrote data to socket, length = ' . strlen($this->outgoing_payload
));
2503 } else if ($this->scheme
== 'https') {
2505 // TODO: cURL does say this should only be the verb, and in fact it
2506 // turns out that the URI and HTTP version are appended to this, which
2507 // some servers refuse to work with
2508 //curl_setopt($this->ch, CURLOPT_CUSTOMREQUEST, $this->outgoing_payload);
2509 foreach($this->outgoing_headers
as $k => $v){
2510 $curl_headers[] = "$k: $v";
2512 if ($cookie_str != '') {
2513 $curl_headers[] = 'Cookie: ' . $cookie_str;
2515 curl_setopt($this->ch
, CURLOPT_HTTPHEADER
, $curl_headers);
2516 if ($this->request_method
== "POST") {
2517 curl_setopt($this->ch
, CURLOPT_POST
, 1);
2518 curl_setopt($this->ch
, CURLOPT_POSTFIELDS
, $data);
2521 $this->debug('set cURL payload');
2526 function getResponse(){
2527 $this->incoming_payload
= '';
2529 if ($this->scheme
== 'http' ||
$this->scheme
== 'ssl') {
2530 // loop until headers have been retrieved
2532 while (!isset($lb)){
2534 // We might EOF during header read.
2535 if(feof($this->fp
)) {
2536 $this->incoming_payload
= $data;
2537 $this->debug('found no headers before EOF after length ' . strlen($data));
2538 $this->debug("received before EOF:\n" . $data);
2539 $this->setError('server failed to send headers');
2543 $tmp = fgets($this->fp
, 256);
2544 $tmplen = strlen($tmp);
2545 $this->debug("read line of $tmplen bytes: " . trim($tmp));
2548 $this->incoming_payload
= $data;
2549 $this->debug('socket read of headers timed out after length ' . strlen($data));
2550 $this->debug("read before timeout: " . $data);
2551 $this->setError('socket read of headers timed out');
2556 $pos = strpos($data,"\r\n\r\n");
2560 $pos = strpos($data,"\n\n");
2565 // remove 100 header
2566 if(isset($lb) && ereg('^HTTP/1.1 100',$data)){
2571 // store header data
2572 $this->incoming_payload
.= $data;
2573 $this->debug('found end of headers after length ' . strlen($data));
2575 $header_data = trim(substr($data,0,$pos));
2576 $header_array = explode($lb,$header_data);
2577 $this->incoming_headers
= array();
2578 $this->incoming_cookies
= array();
2579 foreach($header_array as $header_line){
2580 $arr = explode(':',$header_line, 2);
2581 if(count($arr) > 1){
2582 $header_name = strtolower(trim($arr[0]));
2583 $this->incoming_headers
[$header_name] = trim($arr[1]);
2584 if ($header_name == 'set-cookie') {
2585 // TODO: allow multiple cookies from parseCookie
2586 $cookie = $this->parseCookie(trim($arr[1]));
2588 $this->incoming_cookies
[] = $cookie;
2589 $this->debug('found cookie: ' . $cookie['name'] . ' = ' . $cookie['value']);
2591 $this->debug('did not find cookie in ' . trim($arr[1]));
2594 } else if (isset($header_name)) {
2595 // append continuation line to previous header
2596 $this->incoming_headers
[$header_name] .= $lb . ' ' . $header_line;
2600 // loop until msg has been received
2601 if (isset($this->incoming_headers
['transfer-encoding']) && strtolower($this->incoming_headers
['transfer-encoding']) == 'chunked') {
2602 $content_length = 2147483647; // ignore any content-length header
2604 $this->debug("want to read chunked content");
2605 } elseif (isset($this->incoming_headers
['content-length'])) {
2606 $content_length = $this->incoming_headers
['content-length'];
2608 $this->debug("want to read content of length $content_length");
2610 $content_length = 2147483647;
2612 $this->debug("want to read content to EOF");
2617 $tmp = fgets($this->fp
, 256);
2618 $tmplen = strlen($tmp);
2619 $this->debug("read chunk line of $tmplen bytes");
2621 $this->incoming_payload
= $data;
2622 $this->debug('socket read of chunk length timed out after length ' . strlen($data));
2623 $this->debug("read before timeout:\n" . $data);
2624 $this->setError('socket read of chunk length timed out');
2627 $content_length = hexdec(trim($tmp));
2628 $this->debug("chunk length $content_length");
2631 while (($strlen < $content_length) && (!feof($this->fp
))) {
2632 $readlen = min(8192, $content_length - $strlen);
2633 $tmp = fread($this->fp
, $readlen);
2634 $tmplen = strlen($tmp);
2635 $this->debug("read buffer of $tmplen bytes");
2636 if (($tmplen == 0) && (!feof($this->fp
))) {
2637 $this->incoming_payload
= $data;
2638 $this->debug('socket read of body timed out after length ' . strlen($data));
2639 $this->debug("read before timeout:\n" . $data);
2640 $this->setError('socket read of body timed out');
2646 if ($chunked && ($content_length > 0)) {
2647 $tmp = fgets($this->fp
, 256);
2648 $tmplen = strlen($tmp);
2649 $this->debug("read chunk terminator of $tmplen bytes");
2651 $this->incoming_payload
= $data;
2652 $this->debug('socket read of chunk terminator timed out after length ' . strlen($data));
2653 $this->debug("read before timeout:\n" . $data);
2654 $this->setError('socket read of chunk terminator timed out');
2658 } while ($chunked && ($content_length > 0) && (!feof($this->fp
)));
2659 if (feof($this->fp
)) {
2660 $this->debug('read to EOF');
2662 $this->debug('read body of length ' . strlen($data));
2663 $this->incoming_payload
.= $data;
2664 $this->debug('received a total of '.strlen($this->incoming_payload
).' bytes of data from server');
2666 // close filepointer
2668 (isset($this->incoming_headers
['connection']) && strtolower($this->incoming_headers
['connection']) == 'close') ||
2669 (! $this->persistentConnection
) ||
feof($this->fp
)){
2672 $this->debug('closed socket');
2675 // connection was closed unexpectedly
2676 if($this->incoming_payload
== ''){
2677 $this->setError('no response from server');
2681 // decode transfer-encoding
2682 // if(isset($this->incoming_headers['transfer-encoding']) && strtolower($this->incoming_headers['transfer-encoding']) == 'chunked'){
2683 // if(!$data = $this->decodeChunked($data, $lb)){
2684 // $this->setError('Decoding of chunked data failed');
2687 //print "<pre>\nde-chunked:\n---------------\n$data\n\n---------------\n</pre>";
2688 // set decoded payload
2689 // $this->incoming_payload = $header_data.$lb.$lb.$data;
2692 } else if ($this->scheme
== 'https') {
2694 $this->debug('send and receive with cURL');
2695 $this->incoming_payload
= curl_exec($this->ch
);
2696 $data = $this->incoming_payload
;
2698 $cErr = curl_error($this->ch
);
2700 $err = 'cURL ERROR: '.curl_errno($this->ch
).': '.$cErr.'<br>';
2701 // TODO: there is a PHP bug that can cause this to SEGV for CURLINFO_CONTENT_TYPE
2702 foreach(curl_getinfo($this->ch
) as $k => $v){
2703 $err .= "$k: $v<br>";
2706 $this->setError($err);
2707 curl_close($this->ch
);
2711 //var_dump(curl_getinfo($this->ch));
2715 $this->debug('No cURL error, closing cURL');
2716 curl_close($this->ch
);
2718 // remove 100 header(s)
2719 while (ereg('^HTTP/1.1 100',$data)) {
2720 if ($pos = strpos($data,"\r\n\r\n")) {
2721 $data = ltrim(substr($data,$pos));
2722 } elseif($pos = strpos($data,"\n\n") ) {
2723 $data = ltrim(substr($data,$pos));
2727 // separate content from HTTP headers
2728 if ($pos = strpos($data,"\r\n\r\n")) {
2730 } elseif( $pos = strpos($data,"\n\n")) {
2733 $this->debug('no proper separation of headers and document');
2734 $this->setError('no proper separation of headers and document');
2737 $header_data = trim(substr($data,0,$pos));
2738 $header_array = explode($lb,$header_data);
2739 $data = ltrim(substr($data,$pos));
2740 $this->debug('found proper separation of headers and document');
2741 $this->debug('cleaned data, stringlen: '.strlen($data));
2743 foreach ($header_array as $header_line) {
2744 $arr = explode(':',$header_line,2);
2745 if(count($arr) > 1){
2746 $header_name = strtolower(trim($arr[0]));
2747 $this->incoming_headers
[$header_name] = trim($arr[1]);
2748 if ($header_name == 'set-cookie') {
2749 // TODO: allow multiple cookies from parseCookie
2750 $cookie = $this->parseCookie(trim($arr[1]));
2752 $this->incoming_cookies
[] = $cookie;
2753 $this->debug('found cookie: ' . $cookie['name'] . ' = ' . $cookie['value']);
2755 $this->debug('did not find cookie in ' . trim($arr[1]));
2758 } else if (isset($header_name)) {
2759 // append continuation line to previous header
2760 $this->incoming_headers
[$header_name] .= $lb . ' ' . $header_line;
2765 $arr = explode(' ', $header_array[0], 3);
2766 $http_version = $arr[0];
2767 $http_status = intval($arr[1]);
2768 $http_reason = count($arr) > 2 ?
$arr[2] : '';
2770 // see if we need to resend the request with http digest authentication
2771 if (isset($this->incoming_headers
['location']) && $http_status == 301) {
2772 $this->debug("Got 301 $http_reason with Location: " . $this->incoming_headers
['location']);
2773 $this->setURL($this->incoming_headers
['location']);
2774 $this->tryagain
= true;
2778 // see if we need to resend the request with http digest authentication
2779 if (isset($this->incoming_headers
['www-authenticate']) && $http_status == 401) {
2780 $this->debug("Got 401 $http_reason with WWW-Authenticate: " . $this->incoming_headers
['www-authenticate']);
2781 if (strstr($this->incoming_headers
['www-authenticate'], "Digest ")) {
2782 $this->debug('Server wants digest authentication');
2783 // remove "Digest " from our elements
2784 $digestString = str_replace('Digest ', '', $this->incoming_headers
['www-authenticate']);
2786 // parse elements into array
2787 $digestElements = explode(',', $digestString);
2788 foreach ($digestElements as $val) {
2789 $tempElement = explode('=', trim($val), 2);
2790 $digestRequest[$tempElement[0]] = str_replace("\"", '', $tempElement[1]);
2793 // should have (at least) qop, realm, nonce
2794 if (isset($digestRequest['nonce'])) {
2795 $this->setCredentials($this->username
, $this->password
, 'digest', $digestRequest);
2796 $this->tryagain
= true;
2800 $this->debug('HTTP authentication failed');
2801 $this->setError('HTTP authentication failed');
2806 ($http_status >= 300 && $http_status <= 307) ||
2807 ($http_status >= 400 && $http_status <= 417) ||
2808 ($http_status >= 501 && $http_status <= 505)
2810 $this->setError("Unsupported HTTP response status $http_status $http_reason (soapclient->response has contents of the response)");
2814 // decode content-encoding
2815 if(isset($this->incoming_headers
['content-encoding']) && $this->incoming_headers
['content-encoding'] != ''){
2816 if(strtolower($this->incoming_headers
['content-encoding']) == 'deflate' ||
strtolower($this->incoming_headers
['content-encoding']) == 'gzip'){
2817 // if decoding works, use it. else assume data wasn't gzencoded
2818 if(function_exists('gzinflate')){
2819 //$timer->setMarker('starting decoding of gzip/deflated content');
2820 // IIS 5 requires gzinflate instead of gzuncompress (similar to IE 5 and gzdeflate v. gzcompress)
2821 // this means there are no Zlib headers, although there should be
2822 $this->debug('The gzinflate function exists');
2823 $datalen = strlen($data);
2824 if ($this->incoming_headers
['content-encoding'] == 'deflate') {
2825 if ($degzdata = @gzinflate
($data)) {
2827 $this->debug('The payload has been inflated to ' . strlen($data) . ' bytes');
2828 if (strlen($data) < $datalen) {
2829 // test for the case that the payload has been compressed twice
2830 $this->debug('The inflated payload is smaller than the gzipped one; try again');
2831 if ($degzdata = @gzinflate
($data)) {
2833 $this->debug('The payload has been inflated again to ' . strlen($data) . ' bytes');
2837 $this->debug('Error using gzinflate to inflate the payload');
2838 $this->setError('Error using gzinflate to inflate the payload');
2840 } elseif ($this->incoming_headers
['content-encoding'] == 'gzip') {
2841 if ($degzdata = @gzinflate
(substr($data, 10))) { // do our best
2843 $this->debug('The payload has been un-gzipped to ' . strlen($data) . ' bytes');
2844 if (strlen($data) < $datalen) {
2845 // test for the case that the payload has been compressed twice
2846 $this->debug('The un-gzipped payload is smaller than the gzipped one; try again');
2847 if ($degzdata = @gzinflate
(substr($data, 10))) {
2849 $this->debug('The payload has been un-gzipped again to ' . strlen($data) . ' bytes');
2853 $this->debug('Error using gzinflate to un-gzip the payload');
2854 $this->setError('Error using gzinflate to un-gzip the payload');
2857 //$timer->setMarker('finished decoding of gzip/deflated content');
2858 //print "<xmp>\nde-inflated:\n---------------\n$data\n-------------\n</xmp>";
2859 // set decoded payload
2860 $this->incoming_payload
= $header_data.$lb.$lb.$data;
2862 $this->debug('The server sent compressed data. Your php install must have the Zlib extension compiled in to support this.');
2863 $this->setError('The server sent compressed data. Your php install must have the Zlib extension compiled in to support this.');
2866 $this->debug('Unsupported Content-Encoding ' . $this->incoming_headers
['content-encoding']);
2867 $this->setError('Unsupported Content-Encoding ' . $this->incoming_headers
['content-encoding']);
2870 $this->debug('No Content-Encoding header');
2873 if(strlen($data) == 0){
2874 $this->debug('no data after headers!');
2875 $this->setError('no data present after HTTP headers');
2882 function setContentType($type, $charset = false) {
2883 $this->outgoing_headers
['Content-Type'] = $type . ($charset ?
'; charset=' . $charset : '');
2884 $this->debug('set Content-Type: ' . $this->outgoing_headers
['Content-Type']);
2887 function usePersistentConnection(){
2888 if (isset($this->outgoing_headers
['Accept-Encoding'])) {
2891 $this->protocol_version
= '1.1';
2892 $this->persistentConnection
= true;
2893 $this->outgoing_headers
['Connection'] = 'Keep-Alive';
2894 $this->debug('set Connection: ' . $this->outgoing_headers
['Connection']);
2899 * parse an incoming Cookie into it's parts
2901 * @param string $cookie_str content of cookie
2902 * @return array with data of that cookie
2906 * TODO: allow a Set-Cookie string to be parsed into multiple cookies
2908 function parseCookie($cookie_str) {
2909 $cookie_str = str_replace('; ', ';', $cookie_str) . ';';
2910 $data = split(';', $cookie_str);
2911 $value_str = $data[0];
2913 $cookie_param = 'domain=';
2914 $start = strpos($cookie_str, $cookie_param);
2916 $domain = substr($cookie_str, $start +
strlen($cookie_param));
2917 $domain = substr($domain, 0, strpos($domain, ';'));
2922 $cookie_param = 'expires=';
2923 $start = strpos($cookie_str, $cookie_param);
2925 $expires = substr($cookie_str, $start +
strlen($cookie_param));
2926 $expires = substr($expires, 0, strpos($expires, ';'));
2931 $cookie_param = 'path=';
2932 $start = strpos($cookie_str, $cookie_param);
2934 $path = substr($cookie_str, $start +
strlen($cookie_param));
2935 $path = substr($path, 0, strpos($path, ';'));
2940 $cookie_param = ';secure;';
2941 if (strpos($cookie_str, $cookie_param) !== FALSE) {
2947 $sep_pos = strpos($value_str, '=');
2950 $name = substr($value_str, 0, $sep_pos);
2951 $value = substr($value_str, $sep_pos +
1);
2952 $cookie= array( 'name' => $name,
2954 'domain' => $domain,