MDL-15476
[moodle-linuxchix.git] / lib / soap / nusoap.php
blobd7ad5d71425fd6e4add98f8bd8d073e64d3595d4
1 <?php
3 /*
4 $Id$
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:
26 Dietrich Ayala
27 dietrich@ganx4.com
28 http://dietrich.ganx4.com/nusoap
30 NuSphere Corporation
31 http://www.nusphere.com
35 /* load classes
37 // necessary classes
38 require_once('class.soap_client.php');
39 require_once('class.soap_val.php');
40 require_once('class.soap_parser.php');
41 require_once('class.soap_fault.php');
43 // transport classes
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');
50 // server class
51 require_once('class.soap_server.php');*/
53 // class variable emulation
54 // cf. http://www.webkreator.com/php/techniques/php-static-class-variables.html
55 $GLOBALS['_transient']['static']['nusoap_base']->globalDebugLevel = 9;
57 /**
59 * nusoap_base
61 * @author Dietrich Ayala <dietrich@ganx4.com>
62 * @version $Id$
63 * @access public
65 class nusoap_base {
66 /**
67 * Identification for HTTP headers.
69 * @var string
70 * @access private
72 var $title = 'NuSOAP';
73 /**
74 * Version for HTTP headers.
76 * @var string
77 * @access private
79 var $version = '0.7.2';
80 /**
81 * CVS revision for HTTP headers.
83 * @var string
84 * @access private
86 var $revision = '$Revision$';
87 /**
88 * Current error string (manipulated by getError/setError)
90 * @var string
91 * @access private
93 var $error_str = '';
94 /**
95 * Current debug string (manipulated by debug/appendDebug/clearDebug/getDebug/getDebugAsXMLComment)
97 * @var string
98 * @access private
100 var $debug_str = '';
102 * toggles automatic encoding of special characters as entities
103 * (should always be true, I think)
105 * @var boolean
106 * @access private
108 var $charencoding = true;
110 * the debug level for this instance
112 * @var integer
113 * @access private
115 var $debugLevel;
118 * set schema version
120 * @var string
121 * @access public
123 var $XMLSchemaVersion = 'http://www.w3.org/2001/XMLSchema';
126 * charset encoding for outgoing messages
128 * @var string
129 * @access public
131 var $soap_defencoding = 'ISO-8859-1';
132 //var $soap_defencoding = 'UTF-8';
135 * namespaces in an array of prefix => uri
137 * this is "seeded" by a set of constants, but it may be altered by code
139 * @var array
140 * @access public
142 var $namespaces = array(
143 'SOAP-ENV' => 'http://schemas.xmlsoap.org/soap/envelope/',
144 'xsd' => 'http://www.w3.org/2001/XMLSchema',
145 'xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
146 'SOAP-ENC' => 'http://schemas.xmlsoap.org/soap/encoding/'
150 * namespaces used in the current context, e.g. during serialization
152 * @var array
153 * @access private
155 var $usedNamespaces = array();
158 * XML Schema types in an array of uri => (array of xml type => php type)
159 * is this legacy yet?
160 * no, this is used by the xmlschema class to verify type => namespace mappings.
161 * @var array
162 * @access public
164 var $typemap = array(
165 'http://www.w3.org/2001/XMLSchema' => array(
166 'string'=>'string','boolean'=>'boolean','float'=>'double','double'=>'double','decimal'=>'double',
167 'duration'=>'','dateTime'=>'string','time'=>'string','date'=>'string','gYearMonth'=>'',
168 'gYear'=>'','gMonthDay'=>'','gDay'=>'','gMonth'=>'','hexBinary'=>'string','base64Binary'=>'string',
169 // abstract "any" types
170 'anyType'=>'string','anySimpleType'=>'string',
171 // derived datatypes
172 'normalizedString'=>'string','token'=>'string','language'=>'','NMTOKEN'=>'','NMTOKENS'=>'','Name'=>'','NCName'=>'','ID'=>'',
173 'IDREF'=>'','IDREFS'=>'','ENTITY'=>'','ENTITIES'=>'','integer'=>'integer','nonPositiveInteger'=>'integer',
174 'negativeInteger'=>'integer','long'=>'integer','int'=>'integer','short'=>'integer','byte'=>'integer','nonNegativeInteger'=>'integer',
175 'unsignedLong'=>'','unsignedInt'=>'','unsignedShort'=>'','unsignedByte'=>'','positiveInteger'=>''),
176 'http://www.w3.org/2000/10/XMLSchema' => array(
177 'i4'=>'','int'=>'integer','boolean'=>'boolean','string'=>'string','double'=>'double',
178 'float'=>'double','dateTime'=>'string',
179 'timeInstant'=>'string','base64Binary'=>'string','base64'=>'string','ur-type'=>'array'),
180 'http://www.w3.org/1999/XMLSchema' => array(
181 'i4'=>'','int'=>'integer','boolean'=>'boolean','string'=>'string','double'=>'double',
182 'float'=>'double','dateTime'=>'string',
183 'timeInstant'=>'string','base64Binary'=>'string','base64'=>'string','ur-type'=>'array'),
184 'http://soapinterop.org/xsd' => array('SOAPStruct'=>'struct'),
185 'http://schemas.xmlsoap.org/soap/encoding/' => array('base64'=>'string','array'=>'array','Array'=>'array'),
186 'http://xml.apache.org/xml-soap' => array('Map')
190 * XML entities to convert
192 * @var array
193 * @access public
194 * @deprecated
195 * @see expandEntities
197 var $xmlEntities = array('quot' => '"','amp' => '&',
198 'lt' => '<','gt' => '>','apos' => "'");
201 * constructor
203 * @access public
205 function nusoap_base() {
206 $this->debugLevel = $GLOBALS['_transient']['static']['nusoap_base']->globalDebugLevel;
210 * gets the global debug level, which applies to future instances
212 * @return integer Debug level 0-9, where 0 turns off
213 * @access public
215 function getGlobalDebugLevel() {
216 return $GLOBALS['_transient']['static']['nusoap_base']->globalDebugLevel;
220 * sets the global debug level, which applies to future instances
222 * @param int $level Debug level 0-9, where 0 turns off
223 * @access public
225 function setGlobalDebugLevel($level) {
226 $GLOBALS['_transient']['static']['nusoap_base']->globalDebugLevel = $level;
230 * gets the debug level for this instance
232 * @return int Debug level 0-9, where 0 turns off
233 * @access public
235 function getDebugLevel() {
236 return $this->debugLevel;
240 * sets the debug level for this instance
242 * @param int $level Debug level 0-9, where 0 turns off
243 * @access public
245 function setDebugLevel($level) {
246 $this->debugLevel = $level;
250 * adds debug data to the instance debug string with formatting
252 * @param string $string debug data
253 * @access private
255 function debug($string){
256 if ($this->debugLevel > 0) {
257 $this->appendDebug($this->getmicrotime().' '.get_class($this).": $string\n");
262 * adds debug data to the instance debug string without formatting
264 * @param string $string debug data
265 * @access public
267 function appendDebug($string){
268 if ($this->debugLevel > 0) {
269 // it would be nice to use a memory stream here to use
270 // memory more efficiently
271 $this->debug_str .= $string;
276 * clears the current debug data for this instance
278 * @access public
280 function clearDebug() {
281 // it would be nice to use a memory stream here to use
282 // memory more efficiently
283 $this->debug_str = '';
287 * gets the current debug data for this instance
289 * @return debug data
290 * @access public
292 function &getDebug() {
293 // it would be nice to use a memory stream here to use
294 // memory more efficiently
295 return $this->debug_str;
299 * gets the current debug data for this instance as an XML comment
300 * this may change the contents of the debug data
302 * @return debug data as an XML comment
303 * @access public
305 function &getDebugAsXMLComment() {
306 // it would be nice to use a memory stream here to use
307 // memory more efficiently
308 while (strpos($this->debug_str, '--')) {
309 $this->debug_str = str_replace('--', '- -', $this->debug_str);
311 return "<!--\n" . $this->debug_str . "\n-->";
315 * expands entities, e.g. changes '<' to '&lt;'.
317 * @param string $val The string in which to expand entities.
318 * @access private
320 function expandEntities($val) {
321 if ($this->charencoding) {
322 $val = str_replace('&', '&amp;', $val);
323 $val = str_replace("'", '&apos;', $val);
324 $val = str_replace('"', '&quot;', $val);
325 $val = str_replace('<', '&lt;', $val);
326 $val = str_replace('>', '&gt;', $val);
328 return $val;
332 * returns error string if present
334 * @return mixed error string or false
335 * @access public
337 function getError(){
338 if($this->error_str != ''){
339 return $this->error_str;
341 return false;
345 * sets error string
347 * @return boolean $string error string
348 * @access private
350 function setError($str){
351 $this->error_str = $str;
355 * detect if array is a simple array or a struct (associative array)
357 * @param mixed $val The PHP array
358 * @return string (arraySimple|arrayStruct)
359 * @access private
361 function isArraySimpleOrStruct($val) {
362 $keyList = array_keys($val);
363 foreach ($keyList as $keyListValue) {
364 if (!is_int($keyListValue)) {
365 return 'arrayStruct';
368 return 'arraySimple';
372 * serializes PHP values in accordance w/ section 5. Type information is
373 * not serialized if $use == 'literal'.
375 * @param mixed $val The value to serialize
376 * @param string $name The name (local part) of the XML element
377 * @param string $type The XML schema type (local part) for the element
378 * @param string $name_ns The namespace for the name of the XML element
379 * @param string $type_ns The namespace for the type of the element
380 * @param array $attributes The attributes to serialize as name=>value pairs
381 * @param string $use The WSDL "use" (encoded|literal)
382 * @return string The serialized element, possibly with child elements
383 * @access public
385 function serialize_val($val,$name=false,$type=false,$name_ns=false,$type_ns=false,$attributes=false,$use='encoded'){
386 $this->debug("in serialize_val: name=$name, type=$type, name_ns=$name_ns, type_ns=$type_ns, use=$use");
387 $this->appendDebug('value=' . $this->varDump($val));
388 $this->appendDebug('attributes=' . $this->varDump($attributes));
390 if(is_object($val) && get_class($val) == 'soapval'){
391 return $val->serialize($use);
393 // force valid name if necessary
394 if (is_numeric($name)) {
395 $name = '__numeric_' . $name;
396 } elseif (! $name) {
397 $name = 'noname';
399 // if name has ns, add ns prefix to name
400 $xmlns = '';
401 if($name_ns){
402 $prefix = 'nu'.rand(1000,9999);
403 $name = $prefix.':'.$name;
404 $xmlns .= " xmlns:$prefix=\"$name_ns\"";
406 // if type is prefixed, create type prefix
407 if($type_ns != '' && $type_ns == $this->namespaces['xsd']){
408 // need to fix this. shouldn't default to xsd if no ns specified
409 // w/o checking against typemap
410 $type_prefix = 'xsd';
411 } elseif($type_ns){
412 $type_prefix = 'ns'.rand(1000,9999);
413 $xmlns .= " xmlns:$type_prefix=\"$type_ns\"";
415 // serialize attributes if present
416 $atts = '';
417 if($attributes){
418 foreach($attributes as $k => $v){
419 $atts .= " $k=\"".$this->expandEntities($v).'"';
422 // serialize null value
423 if (is_null($val)) {
424 if ($use == 'literal') {
425 // TODO: depends on minOccurs
426 return "<$name$xmlns $atts/>";
427 } else {
428 if (isset($type) && isset($type_prefix)) {
429 $type_str = " xsi:type=\"$type_prefix:$type\"";
430 } else {
431 $type_str = '';
433 return "<$name$xmlns$type_str $atts xsi:nil=\"true\"/>";
436 // serialize if an xsd built-in primitive type
437 if($type != '' && isset($this->typemap[$this->XMLSchemaVersion][$type])){
438 if (is_bool($val)) {
439 if ($type == 'boolean') {
440 $val = $val ? 'true' : 'false';
441 } elseif (! $val) {
442 $val = 0;
444 } else if (is_string($val)) {
445 $val = $this->expandEntities($val);
447 if ($use == 'literal') {
448 return "<$name$xmlns $atts>$val</$name>";
449 } else {
450 return "<$name$xmlns $atts xsi:type=\"xsd:$type\">$val</$name>";
453 // detect type and serialize
454 $xml = '';
455 switch(true) {
456 case (is_bool($val) || $type == 'boolean'):
457 if ($type == 'boolean') {
458 $val = $val ? 'true' : 'false';
459 } elseif (! $val) {
460 $val = 0;
462 if ($use == 'literal') {
463 $xml .= "<$name$xmlns $atts>$val</$name>";
464 } else {
465 $xml .= "<$name$xmlns xsi:type=\"xsd:boolean\"$atts>$val</$name>";
467 break;
468 case (is_int($val) || is_long($val) || $type == 'int'):
469 if ($use == 'literal') {
470 $xml .= "<$name$xmlns $atts>$val</$name>";
471 } else {
472 $xml .= "<$name$xmlns xsi:type=\"xsd:int\"$atts>$val</$name>";
474 break;
475 case (is_float($val)|| is_double($val) || $type == 'float'):
476 if ($use == 'literal') {
477 $xml .= "<$name$xmlns $atts>$val</$name>";
478 } else {
479 $xml .= "<$name$xmlns xsi:type=\"xsd:float\"$atts>$val</$name>";
481 break;
482 case (is_string($val) || $type == 'string'):
483 $val = $this->expandEntities($val);
484 if ($use == 'literal') {
485 $xml .= "<$name$xmlns $atts>$val</$name>";
486 } else {
487 $xml .= "<$name$xmlns xsi:type=\"xsd:string\"$atts>$val</$name>";
489 break;
490 case is_object($val):
491 if (! $name) {
492 $name = get_class($val);
493 $this->debug("In serialize_val, used class name $name as element name");
494 } else {
495 $this->debug("In serialize_val, do not override name $name for element name for class " . get_class($val));
497 foreach(get_object_vars($val) as $k => $v){
498 $pXml = isset($pXml) ? $pXml.$this->serialize_val($v,$k,false,false,false,false,$use) : $this->serialize_val($v,$k,false,false,false,false,$use);
500 $xml .= '<'.$name.'>'.$pXml.'</'.$name.'>';
501 break;
502 break;
503 case (is_array($val) || $type):
504 // detect if struct or array
505 $valueType = $this->isArraySimpleOrStruct($val);
506 if($valueType=='arraySimple' || ereg('^ArrayOf',$type)){
507 $i = 0;
508 if(is_array($val) && count($val)> 0){
509 foreach($val as $v){
510 if(is_object($v) && get_class($v) == 'soapval'){
511 $tt_ns = $v->type_ns;
512 $tt = $v->type;
513 } elseif (is_array($v)) {
514 $tt = $this->isArraySimpleOrStruct($v);
515 } else {
516 $tt = gettype($v);
518 $array_types[$tt] = 1;
519 // TODO: for literal, the name should be $name
520 $xml .= $this->serialize_val($v,'item',false,false,false,false,$use);
521 ++$i;
523 if(count($array_types) > 1){
524 $array_typename = 'xsd:anyType';
525 } elseif(isset($tt) && isset($this->typemap[$this->XMLSchemaVersion][$tt])) {
526 if ($tt == 'integer') {
527 $tt = 'int';
529 $array_typename = 'xsd:'.$tt;
530 } elseif(isset($tt) && $tt == 'arraySimple'){
531 $array_typename = 'SOAP-ENC:Array';
532 } elseif(isset($tt) && $tt == 'arrayStruct'){
533 $array_typename = 'unnamed_struct_use_soapval';
534 } else {
535 // if type is prefixed, create type prefix
536 if ($tt_ns != '' && $tt_ns == $this->namespaces['xsd']){
537 $array_typename = 'xsd:' . $tt;
538 } elseif ($tt_ns) {
539 $tt_prefix = 'ns' . rand(1000, 9999);
540 $array_typename = "$tt_prefix:$tt";
541 $xmlns .= " xmlns:$tt_prefix=\"$tt_ns\"";
542 } else {
543 $array_typename = $tt;
546 $array_type = $i;
547 if ($use == 'literal') {
548 $type_str = '';
549 } else if (isset($type) && isset($type_prefix)) {
550 $type_str = " xsi:type=\"$type_prefix:$type\"";
551 } else {
552 $type_str = " xsi:type=\"SOAP-ENC:Array\" SOAP-ENC:arrayType=\"".$array_typename."[$array_type]\"";
554 // empty array
555 } else {
556 if ($use == 'literal') {
557 $type_str = '';
558 } else if (isset($type) && isset($type_prefix)) {
559 $type_str = " xsi:type=\"$type_prefix:$type\"";
560 } else {
561 $type_str = " xsi:type=\"SOAP-ENC:Array\" SOAP-ENC:arrayType=\"xsd:anyType[0]\"";
564 // TODO: for array in literal, there is no wrapper here
565 $xml = "<$name$xmlns$type_str$atts>".$xml."</$name>";
566 } else {
567 // got a struct
568 if(isset($type) && isset($type_prefix)){
569 $type_str = " xsi:type=\"$type_prefix:$type\"";
570 } else {
571 $type_str = '';
573 if ($use == 'literal') {
574 $xml .= "<$name$xmlns $atts>";
575 } else {
576 $xml .= "<$name$xmlns$type_str$atts>";
578 foreach($val as $k => $v){
579 // Apache Map
580 if ($type == 'Map' && $type_ns == 'http://xml.apache.org/xml-soap') {
581 $xml .= '<item>';
582 $xml .= $this->serialize_val($k,'key',false,false,false,false,$use);
583 $xml .= $this->serialize_val($v,'value',false,false,false,false,$use);
584 $xml .= '</item>';
585 } else {
586 $xml .= $this->serialize_val($v,$k,false,false,false,false,$use);
589 $xml .= "</$name>";
591 break;
592 default:
593 $xml .= 'not detected, got '.gettype($val).' for '.$val;
594 break;
596 return $xml;
600 * serializes a message
602 * @param string $body the XML of the SOAP body
603 * @param mixed $headers optional string of XML with SOAP header content, or array of soapval objects for SOAP headers
604 * @param array $namespaces optional the namespaces used in generating the body and headers
605 * @param string $style optional (rpc|document)
606 * @param string $use optional (encoded|literal)
607 * @param string $encodingStyle optional (usually 'http://schemas.xmlsoap.org/soap/encoding/' for encoded)
608 * @return string the message
609 * @access public
611 function serializeEnvelope($body,$headers=false,$namespaces=array(),$style='rpc',$use='encoded',$encodingStyle='http://schemas.xmlsoap.org/soap/encoding/'){
612 // TODO: add an option to automatically run utf8_encode on $body and $headers
613 // if $this->soap_defencoding is UTF-8. Not doing this automatically allows
614 // one to send arbitrary UTF-8 characters, not just characters that map to ISO-8859-1
616 $this->debug("In serializeEnvelope length=" . strlen($body) . " body (max 1000 characters)=" . substr($body, 0, 1000) . " style=$style use=$use encodingStyle=$encodingStyle");
617 $this->debug("headers:");
618 $this->appendDebug($this->varDump($headers));
619 $this->debug("namespaces:");
620 $this->appendDebug($this->varDump($namespaces));
622 // serialize namespaces
623 $ns_string = '';
624 foreach(array_merge($this->namespaces,$namespaces) as $k => $v){
625 $ns_string .= " xmlns:$k=\"$v\"";
627 if($encodingStyle) {
628 $ns_string = " SOAP-ENV:encodingStyle=\"$encodingStyle\"$ns_string";
631 // serialize headers
632 if($headers){
633 if (is_array($headers)) {
634 $xml = '';
635 foreach ($headers as $header) {
636 $xml .= $this->serialize_val($header, false, false, false, false, false, $use);
638 $headers = $xml;
639 $this->debug("In serializeEnvelope, serialzied array of headers to $headers");
641 $headers = "<SOAP-ENV:Header>".$headers."</SOAP-ENV:Header>";
643 // serialize envelope
644 return
645 '<?xml version="1.0" encoding="'.$this->soap_defencoding .'"?'.">".
646 '<SOAP-ENV:Envelope'.$ns_string.">".
647 $headers.
648 "<SOAP-ENV:Body>".
649 $body.
650 "</SOAP-ENV:Body>".
651 "</SOAP-ENV:Envelope>";
655 * formats a string to be inserted into an HTML stream
657 * @param string $str The string to format
658 * @return string The formatted string
659 * @access public
660 * @deprecated
662 function formatDump($str){
663 $str = htmlspecialchars($str);
664 return nl2br($str);
668 * contracts (changes namespace to prefix) a qualified name
670 * @param string $qname qname
671 * @return string contracted qname
672 * @access private
674 function contractQname($qname){
675 // get element namespace
676 //$this->xdebug("Contract $qname");
677 if (strrpos($qname, ':')) {
678 // get unqualified name
679 $name = substr($qname, strrpos($qname, ':') + 1);
680 // get ns
681 $ns = substr($qname, 0, strrpos($qname, ':'));
682 $p = $this->getPrefixFromNamespace($ns);
683 if ($p) {
684 return $p . ':' . $name;
686 return $qname;
687 } else {
688 return $qname;
693 * expands (changes prefix to namespace) a qualified name
695 * @param string $string qname
696 * @return string expanded qname
697 * @access private
699 function expandQname($qname){
700 // get element prefix
701 if(strpos($qname,':') && !ereg('^http://',$qname)){
702 // get unqualified name
703 $name = substr(strstr($qname,':'),1);
704 // get ns prefix
705 $prefix = substr($qname,0,strpos($qname,':'));
706 if(isset($this->namespaces[$prefix])){
707 return $this->namespaces[$prefix].':'.$name;
708 } else {
709 return $qname;
711 } else {
712 return $qname;
717 * returns the local part of a prefixed string
718 * returns the original string, if not prefixed
720 * @param string $str The prefixed string
721 * @return string The local part
722 * @access public
724 function getLocalPart($str){
725 if($sstr = strrchr($str,':')){
726 // get unqualified name
727 return substr( $sstr, 1 );
728 } else {
729 return $str;
734 * returns the prefix part of a prefixed string
735 * returns false, if not prefixed
737 * @param string $str The prefixed string
738 * @return mixed The prefix or false if there is no prefix
739 * @access public
741 function getPrefix($str){
742 if($pos = strrpos($str,':')){
743 // get prefix
744 return substr($str,0,$pos);
746 return false;
750 * pass it a prefix, it returns a namespace
752 * @param string $prefix The prefix
753 * @return mixed The namespace, false if no namespace has the specified prefix
754 * @access public
756 function getNamespaceFromPrefix($prefix){
757 if (isset($this->namespaces[$prefix])) {
758 return $this->namespaces[$prefix];
760 //$this->setError("No namespace registered for prefix '$prefix'");
761 return false;
765 * returns the prefix for a given namespace (or prefix)
766 * or false if no prefixes registered for the given namespace
768 * @param string $ns The namespace
769 * @return mixed The prefix, false if the namespace has no prefixes
770 * @access public
772 function getPrefixFromNamespace($ns) {
773 foreach ($this->namespaces as $p => $n) {
774 if ($ns == $n || $ns == $p) {
775 $this->usedNamespaces[$p] = $n;
776 return $p;
779 return false;
783 * returns the time in ODBC canonical form with microseconds
785 * @return string The time in ODBC canonical form with microseconds
786 * @access public
788 function getmicrotime() {
789 if (function_exists('gettimeofday')) {
790 $tod = gettimeofday();
791 $sec = $tod['sec'];
792 $usec = $tod['usec'];
793 } else {
794 $sec = time();
795 $usec = 0;
797 return strftime('%Y-%m-%d %H:%M:%S', $sec) . '.' . sprintf('%06d', $usec);
801 * Returns a string with the output of var_dump
803 * @param mixed $data The variable to var_dump
804 * @return string The output of var_dump
805 * @access public
807 function varDump($data) {
808 ob_start();
809 var_dump($data);
810 $ret_val = ob_get_contents();
811 ob_end_clean();
812 return $ret_val;
816 // XML Schema Datatype Helper Functions
818 //xsd:dateTime helpers
821 * convert unix timestamp to ISO 8601 compliant date string
823 * @param string $timestamp Unix time stamp
824 * @access public
826 function timestamp_to_iso8601($timestamp,$utc=true){
827 $datestr = date('Y-m-d\TH:i:sO',$timestamp);
828 if($utc){
829 $eregStr =
830 '([0-9]{4})-'. // centuries & years CCYY-
831 '([0-9]{2})-'. // months MM-
832 '([0-9]{2})'. // days DD
833 'T'. // separator T
834 '([0-9]{2}):'. // hours hh:
835 '([0-9]{2}):'. // minutes mm:
836 '([0-9]{2})(\.[0-9]*)?'. // seconds ss.ss...
837 '(Z|[+\-][0-9]{2}:?[0-9]{2})?'; // Z to indicate UTC, -/+HH:MM:SS.SS... for local tz's
839 if(ereg($eregStr,$datestr,$regs)){
840 return sprintf('%04d-%02d-%02dT%02d:%02d:%02dZ',$regs[1],$regs[2],$regs[3],$regs[4],$regs[5],$regs[6]);
842 return false;
843 } else {
844 return $datestr;
849 * convert ISO 8601 compliant date string to unix timestamp
851 * @param string $datestr ISO 8601 compliant date string
852 * @access public
854 function iso8601_to_timestamp($datestr){
855 $eregStr =
856 '([0-9]{4})-'. // centuries & years CCYY-
857 '([0-9]{2})-'. // months MM-
858 '([0-9]{2})'. // days DD
859 'T'. // separator T
860 '([0-9]{2}):'. // hours hh:
861 '([0-9]{2}):'. // minutes mm:
862 '([0-9]{2})(\.[0-9]+)?'. // seconds ss.ss...
863 '(Z|[+\-][0-9]{2}:?[0-9]{2})?'; // Z to indicate UTC, -/+HH:MM:SS.SS... for local tz's
864 if(ereg($eregStr,$datestr,$regs)){
865 // not utc
866 if($regs[8] != 'Z'){
867 $op = substr($regs[8],0,1);
868 $h = substr($regs[8],1,2);
869 $m = substr($regs[8],strlen($regs[8])-2,2);
870 if($op == '-'){
871 $regs[4] = $regs[4] + $h;
872 $regs[5] = $regs[5] + $m;
873 } elseif($op == '+'){
874 $regs[4] = $regs[4] - $h;
875 $regs[5] = $regs[5] - $m;
878 return strtotime("$regs[1]-$regs[2]-$regs[3] $regs[4]:$regs[5]:$regs[6]Z");
879 } else {
880 return false;
885 * sleeps some number of microseconds
887 * @param string $usec the number of microseconds to sleep
888 * @access public
889 * @deprecated
891 function usleepWindows($usec)
893 $start = gettimeofday();
897 $stop = gettimeofday();
898 $timePassed = 1000000 * ($stop['sec'] - $start['sec'])
899 + $stop['usec'] - $start['usec'];
901 while ($timePassed < $usec);
904 ?><?php
909 * Contains information for a SOAP fault.
910 * Mainly used for returning faults from deployed functions
911 * in a server instance.
912 * @author Dietrich Ayala <dietrich@ganx4.com>
913 * @version $Id$
914 * @access public
916 class soap_fault extends nusoap_base {
918 * The fault code (client|server)
919 * @var string
920 * @access private
922 var $faultcode;
924 * The fault actor
925 * @var string
926 * @access private
928 var $faultactor;
930 * The fault string, a description of the fault
931 * @var string
932 * @access private
934 var $faultstring;
936 * The fault detail, typically a string or array of string
937 * @var mixed
938 * @access private
940 var $faultdetail;
943 * constructor
945 * @param string $faultcode (SOAP-ENV:Client | SOAP-ENV:Server)
946 * @param string $faultactor only used when msg routed between multiple actors
947 * @param string $faultstring human readable error message
948 * @param mixed $faultdetail detail, typically a string or array of string
950 function soap_fault($faultcode,$faultactor='',$faultstring='',$faultdetail=''){
951 parent::nusoap_base();
952 $this->faultcode = $faultcode;
953 $this->faultactor = $faultactor;
954 $this->faultstring = $faultstring;
955 $this->faultdetail = $faultdetail;
959 * serialize a fault
961 * @return string The serialization of the fault instance.
962 * @access public
964 function serialize(){
965 $ns_string = '';
966 foreach($this->namespaces as $k => $v){
967 $ns_string .= "\n xmlns:$k=\"$v\"";
969 $return_msg =
970 '<?xml version="1.0" encoding="'.$this->soap_defencoding.'"?>'.
971 '<SOAP-ENV:Envelope SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"'.$ns_string.">\n".
972 '<SOAP-ENV:Body>'.
973 '<SOAP-ENV:Fault>'.
974 $this->serialize_val($this->faultcode, 'faultcode').
975 $this->serialize_val($this->faultactor, 'faultactor').
976 $this->serialize_val($this->faultstring, 'faultstring').
977 $this->serialize_val($this->faultdetail, 'detail').
978 '</SOAP-ENV:Fault>'.
979 '</SOAP-ENV:Body>'.
980 '</SOAP-ENV:Envelope>';
981 return $return_msg;
987 ?><?php
992 * parses an XML Schema, allows access to it's data, other utility methods
993 * no validation... yet.
994 * very experimental and limited. As is discussed on XML-DEV, I'm one of the people
995 * that just doesn't have time to read the spec(s) thoroughly, and just have a couple of trusty
996 * tutorials I refer to :)
998 * @author Dietrich Ayala <dietrich@ganx4.com>
999 * @version $Id$
1000 * @access public
1002 class XMLSchema extends nusoap_base {
1004 // files
1005 var $schema = '';
1006 var $xml = '';
1007 // namespaces
1008 var $enclosingNamespaces;
1009 // schema info
1010 var $schemaInfo = array();
1011 var $schemaTargetNamespace = '';
1012 // types, elements, attributes defined by the schema
1013 var $attributes = array();
1014 var $complexTypes = array();
1015 var $complexTypeStack = array();
1016 var $currentComplexType = null;
1017 var $elements = array();
1018 var $elementStack = array();
1019 var $currentElement = null;
1020 var $simpleTypes = array();
1021 var $simpleTypeStack = array();
1022 var $currentSimpleType = null;
1023 // imports
1024 var $imports = array();
1025 // parser vars
1026 var $parser;
1027 var $position = 0;
1028 var $depth = 0;
1029 var $depth_array = array();
1030 var $message = array();
1031 var $defaultNamespace = array();
1034 * constructor
1036 * @param string $schema schema document URI
1037 * @param string $xml xml document URI
1038 * @param string $namespaces namespaces defined in enclosing XML
1039 * @access public
1041 function XMLSchema($schema='',$xml='',$namespaces=array()){
1042 parent::nusoap_base();
1043 $this->debug('xmlschema class instantiated, inside constructor');
1044 // files
1045 $this->schema = $schema;
1046 $this->xml = $xml;
1048 // namespaces
1049 $this->enclosingNamespaces = $namespaces;
1050 $this->namespaces = array_merge($this->namespaces, $namespaces);
1052 // parse schema file
1053 if($schema != ''){
1054 $this->debug('initial schema file: '.$schema);
1055 $this->parseFile($schema, 'schema');
1058 // parse xml file
1059 if($xml != ''){
1060 $this->debug('initial xml file: '.$xml);
1061 $this->parseFile($xml, 'xml');
1067 * parse an XML file
1069 * @param string $xml, path/URL to XML file
1070 * @param string $type, (schema | xml)
1071 * @return boolean
1072 * @access public
1074 function parseFile($xml,$type){
1075 // parse xml file
1076 if($xml != ""){
1077 $xmlStr = @join("",@file($xml));
1078 if($xmlStr == ""){
1079 $msg = 'Error reading XML from '.$xml;
1080 $this->setError($msg);
1081 $this->debug($msg);
1082 return false;
1083 } else {
1084 $this->debug("parsing $xml");
1085 $this->parseString($xmlStr,$type);
1086 $this->debug("done parsing $xml");
1087 return true;
1090 return false;
1094 * parse an XML string
1096 * @param string $xml path or URL
1097 * @param string $type, (schema|xml)
1098 * @access private
1100 function parseString($xml,$type){
1101 // parse xml string
1102 if($xml != ""){
1104 // Create an XML parser.
1105 $this->parser = xml_parser_create();
1106 // Set the options for parsing the XML data.
1107 xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING, 0);
1109 // Set the object for the parser.
1110 xml_set_object($this->parser, $this);
1112 // Set the element handlers for the parser.
1113 if($type == "schema"){
1114 xml_set_element_handler($this->parser, 'schemaStartElement','schemaEndElement');
1115 xml_set_character_data_handler($this->parser,'schemaCharacterData');
1116 } elseif($type == "xml"){
1117 xml_set_element_handler($this->parser, 'xmlStartElement','xmlEndElement');
1118 xml_set_character_data_handler($this->parser,'xmlCharacterData');
1121 // Parse the XML file.
1122 if(!xml_parse($this->parser,$xml,true)){
1123 // Display an error message.
1124 $errstr = sprintf('XML error parsing XML schema on line %d: %s',
1125 xml_get_current_line_number($this->parser),
1126 xml_error_string(xml_get_error_code($this->parser))
1128 $this->debug($errstr);
1129 $this->debug("XML payload:\n" . $xml);
1130 $this->setError($errstr);
1133 xml_parser_free($this->parser);
1134 } else{
1135 $this->debug('no xml passed to parseString()!!');
1136 $this->setError('no xml passed to parseString()!!');
1141 * start-element handler
1143 * @param string $parser XML parser object
1144 * @param string $name element name
1145 * @param string $attrs associative array of attributes
1146 * @access private
1148 function schemaStartElement($parser, $name, $attrs) {
1150 // position in the total number of elements, starting from 0
1151 $pos = $this->position++;
1152 $depth = $this->depth++;
1153 // set self as current value for this depth
1154 $this->depth_array[$depth] = $pos;
1155 $this->message[$pos] = array('cdata' => '');
1156 if ($depth > 0) {
1157 $this->defaultNamespace[$pos] = $this->defaultNamespace[$this->depth_array[$depth - 1]];
1158 } else {
1159 $this->defaultNamespace[$pos] = false;
1162 // get element prefix
1163 if($prefix = $this->getPrefix($name)){
1164 // get unqualified name
1165 $name = $this->getLocalPart($name);
1166 } else {
1167 $prefix = '';
1170 // loop thru attributes, expanding, and registering namespace declarations
1171 if(count($attrs) > 0){
1172 foreach($attrs as $k => $v){
1173 // if ns declarations, add to class level array of valid namespaces
1174 if(ereg("^xmlns",$k)){
1175 //$this->xdebug("$k: $v");
1176 //$this->xdebug('ns_prefix: '.$this->getPrefix($k));
1177 if($ns_prefix = substr(strrchr($k,':'),1)){
1178 //$this->xdebug("Add namespace[$ns_prefix] = $v");
1179 $this->namespaces[$ns_prefix] = $v;
1180 } else {
1181 $this->defaultNamespace[$pos] = $v;
1182 if (! $this->getPrefixFromNamespace($v)) {
1183 $this->namespaces['ns'.(count($this->namespaces)+1)] = $v;
1186 if($v == 'http://www.w3.org/2001/XMLSchema' || $v == 'http://www.w3.org/1999/XMLSchema' || $v == 'http://www.w3.org/2000/10/XMLSchema'){
1187 $this->XMLSchemaVersion = $v;
1188 $this->namespaces['xsi'] = $v.'-instance';
1192 foreach($attrs as $k => $v){
1193 // expand each attribute
1194 $k = strpos($k,':') ? $this->expandQname($k) : $k;
1195 $v = strpos($v,':') ? $this->expandQname($v) : $v;
1196 $eAttrs[$k] = $v;
1198 $attrs = $eAttrs;
1199 } else {
1200 $attrs = array();
1202 // find status, register data
1203 switch($name){
1204 case 'all': // (optional) compositor content for a complexType
1205 case 'choice':
1206 case 'group':
1207 case 'sequence':
1208 //$this->xdebug("compositor $name for currentComplexType: $this->currentComplexType and currentElement: $this->currentElement");
1209 $this->complexTypes[$this->currentComplexType]['compositor'] = $name;
1210 //if($name == 'all' || $name == 'sequence'){
1211 // $this->complexTypes[$this->currentComplexType]['phpType'] = 'struct';
1213 break;
1214 case 'attribute': // complexType attribute
1215 //$this->xdebug("parsing attribute $attrs[name] $attrs[ref] of value: ".$attrs['http://schemas.xmlsoap.org/wsdl/:arrayType']);
1216 $this->xdebug("parsing attribute:");
1217 $this->appendDebug($this->varDump($attrs));
1218 if (!isset($attrs['form'])) {
1219 $attrs['form'] = $this->schemaInfo['attributeFormDefault'];
1221 if (isset($attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'])) {
1222 $v = $attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'];
1223 if (!strpos($v, ':')) {
1224 // no namespace in arrayType attribute value...
1225 if ($this->defaultNamespace[$pos]) {
1226 // ...so use the default
1227 $attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'] = $this->defaultNamespace[$pos] . ':' . $attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'];
1231 if(isset($attrs['name'])){
1232 $this->attributes[$attrs['name']] = $attrs;
1233 $aname = $attrs['name'];
1234 } elseif(isset($attrs['ref']) && $attrs['ref'] == 'http://schemas.xmlsoap.org/soap/encoding/:arrayType'){
1235 if (isset($attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'])) {
1236 $aname = $attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'];
1237 } else {
1238 $aname = '';
1240 } elseif(isset($attrs['ref'])){
1241 $aname = $attrs['ref'];
1242 $this->attributes[$attrs['ref']] = $attrs;
1245 if($this->currentComplexType){ // This should *always* be
1246 $this->complexTypes[$this->currentComplexType]['attrs'][$aname] = $attrs;
1248 // arrayType attribute
1249 if(isset($attrs['http://schemas.xmlsoap.org/wsdl/:arrayType']) || $this->getLocalPart($aname) == 'arrayType'){
1250 $this->complexTypes[$this->currentComplexType]['phpType'] = 'array';
1251 $prefix = $this->getPrefix($aname);
1252 if(isset($attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'])){
1253 $v = $attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'];
1254 } else {
1255 $v = '';
1257 if(strpos($v,'[,]')){
1258 $this->complexTypes[$this->currentComplexType]['multidimensional'] = true;
1260 $v = substr($v,0,strpos($v,'[')); // clip the []
1261 if(!strpos($v,':') && isset($this->typemap[$this->XMLSchemaVersion][$v])){
1262 $v = $this->XMLSchemaVersion.':'.$v;
1264 $this->complexTypes[$this->currentComplexType]['arrayType'] = $v;
1266 break;
1267 case 'complexContent': // (optional) content for a complexType
1268 break;
1269 case 'complexType':
1270 array_push($this->complexTypeStack, $this->currentComplexType);
1271 if(isset($attrs['name'])){
1272 $this->xdebug('processing named complexType '.$attrs['name']);
1273 //$this->currentElement = false;
1274 $this->currentComplexType = $attrs['name'];
1275 $this->complexTypes[$this->currentComplexType] = $attrs;
1276 $this->complexTypes[$this->currentComplexType]['typeClass'] = 'complexType';
1277 // This is for constructs like
1278 // <complexType name="ListOfString" base="soap:Array">
1279 // <sequence>
1280 // <element name="string" type="xsd:string"
1281 // minOccurs="0" maxOccurs="unbounded" />
1282 // </sequence>
1283 // </complexType>
1284 if(isset($attrs['base']) && ereg(':Array$',$attrs['base'])){
1285 $this->xdebug('complexType is unusual array');
1286 $this->complexTypes[$this->currentComplexType]['phpType'] = 'array';
1287 } else {
1288 $this->complexTypes[$this->currentComplexType]['phpType'] = 'struct';
1290 }else{
1291 $this->xdebug('processing unnamed complexType for element '.$this->currentElement);
1292 $this->currentComplexType = $this->currentElement . '_ContainedType';
1293 //$this->currentElement = false;
1294 $this->complexTypes[$this->currentComplexType] = $attrs;
1295 $this->complexTypes[$this->currentComplexType]['typeClass'] = 'complexType';
1296 // This is for constructs like
1297 // <complexType name="ListOfString" base="soap:Array">
1298 // <sequence>
1299 // <element name="string" type="xsd:string"
1300 // minOccurs="0" maxOccurs="unbounded" />
1301 // </sequence>
1302 // </complexType>
1303 if(isset($attrs['base']) && ereg(':Array$',$attrs['base'])){
1304 $this->xdebug('complexType is unusual array');
1305 $this->complexTypes[$this->currentComplexType]['phpType'] = 'array';
1306 } else {
1307 $this->complexTypes[$this->currentComplexType]['phpType'] = 'struct';
1310 break;
1311 case 'element':
1312 array_push($this->elementStack, $this->currentElement);
1313 // elements defined as part of a complex type should
1314 // not really be added to $this->elements, but for some
1315 // reason, they are
1316 if (!isset($attrs['form'])) {
1317 $attrs['form'] = $this->schemaInfo['elementFormDefault'];
1319 if(isset($attrs['type'])){
1320 $this->xdebug("processing typed element ".$attrs['name']." of type ".$attrs['type']);
1321 if (! $this->getPrefix($attrs['type'])) {
1322 if ($this->defaultNamespace[$pos]) {
1323 $attrs['type'] = $this->defaultNamespace[$pos] . ':' . $attrs['type'];
1324 $this->xdebug('used default namespace to make type ' . $attrs['type']);
1327 // This is for constructs like
1328 // <complexType name="ListOfString" base="soap:Array">
1329 // <sequence>
1330 // <element name="string" type="xsd:string"
1331 // minOccurs="0" maxOccurs="unbounded" />
1332 // </sequence>
1333 // </complexType>
1334 if ($this->currentComplexType && $this->complexTypes[$this->currentComplexType]['phpType'] == 'array') {
1335 $this->xdebug('arrayType for unusual array is ' . $attrs['type']);
1336 $this->complexTypes[$this->currentComplexType]['arrayType'] = $attrs['type'];
1338 $this->currentElement = $attrs['name'];
1339 $this->elements[ $attrs['name'] ] = $attrs;
1340 $this->elements[ $attrs['name'] ]['typeClass'] = 'element';
1341 $ename = $attrs['name'];
1342 } elseif(isset($attrs['ref'])){
1343 $this->xdebug("processing element as ref to ".$attrs['ref']);
1344 $this->currentElement = "ref to ".$attrs['ref'];
1345 $ename = $this->getLocalPart($attrs['ref']);
1346 } else {
1347 $this->xdebug("processing untyped element ".$attrs['name']);
1348 $this->currentElement = $attrs['name'];
1349 $this->elements[ $attrs['name'] ] = $attrs;
1350 $this->elements[ $attrs['name'] ]['typeClass'] = 'element';
1351 $attrs['type'] = $this->schemaTargetNamespace . ':' . $attrs['name'] . '_ContainedType';
1352 $this->elements[ $attrs['name'] ]['type'] = $attrs['type'];
1353 $ename = $attrs['name'];
1355 if(isset($ename) && $this->currentComplexType){
1356 $this->complexTypes[$this->currentComplexType]['elements'][$ename] = $attrs;
1358 break;
1359 case 'enumeration': // restriction value list member
1360 $this->xdebug('enumeration ' . $attrs['value']);
1361 if ($this->currentSimpleType) {
1362 $this->simpleTypes[$this->currentSimpleType]['enumeration'][] = $attrs['value'];
1363 } elseif ($this->currentComplexType) {
1364 $this->complexTypes[$this->currentComplexType]['enumeration'][] = $attrs['value'];
1366 break;
1367 case 'extension': // simpleContent or complexContent type extension
1368 $this->xdebug('extension ' . $attrs['base']);
1369 if ($this->currentComplexType) {
1370 $this->complexTypes[$this->currentComplexType]['extensionBase'] = $attrs['base'];
1372 break;
1373 case 'import':
1374 if (isset($attrs['schemaLocation'])) {
1375 //$this->xdebug('import namespace ' . $attrs['namespace'] . ' from ' . $attrs['schemaLocation']);
1376 $this->imports[$attrs['namespace']][] = array('location' => $attrs['schemaLocation'], 'loaded' => false);
1377 } else {
1378 //$this->xdebug('import namespace ' . $attrs['namespace']);
1379 $this->imports[$attrs['namespace']][] = array('location' => '', 'loaded' => true);
1380 if (! $this->getPrefixFromNamespace($attrs['namespace'])) {
1381 $this->namespaces['ns'.(count($this->namespaces)+1)] = $attrs['namespace'];
1384 break;
1385 case 'list': // simpleType value list
1386 break;
1387 case 'restriction': // simpleType, simpleContent or complexContent value restriction
1388 $this->xdebug('restriction ' . $attrs['base']);
1389 if($this->currentSimpleType){
1390 $this->simpleTypes[$this->currentSimpleType]['type'] = $attrs['base'];
1391 } elseif($this->currentComplexType){
1392 $this->complexTypes[$this->currentComplexType]['restrictionBase'] = $attrs['base'];
1393 if(strstr($attrs['base'],':') == ':Array'){
1394 $this->complexTypes[$this->currentComplexType]['phpType'] = 'array';
1397 break;
1398 case 'schema':
1399 $this->schemaInfo = $attrs;
1400 $this->schemaInfo['schemaVersion'] = $this->getNamespaceFromPrefix($prefix);
1401 if (isset($attrs['targetNamespace'])) {
1402 $this->schemaTargetNamespace = $attrs['targetNamespace'];
1404 if (!isset($attrs['elementFormDefault'])) {
1405 $this->schemaInfo['elementFormDefault'] = 'unqualified';
1407 if (!isset($attrs['attributeFormDefault'])) {
1408 $this->schemaInfo['attributeFormDefault'] = 'unqualified';
1410 break;
1411 case 'simpleContent': // (optional) content for a complexType
1412 break;
1413 case 'simpleType':
1414 array_push($this->simpleTypeStack, $this->currentSimpleType);
1415 if(isset($attrs['name'])){
1416 $this->xdebug("processing simpleType for name " . $attrs['name']);
1417 $this->currentSimpleType = $attrs['name'];
1418 $this->simpleTypes[ $attrs['name'] ] = $attrs;
1419 $this->simpleTypes[ $attrs['name'] ]['typeClass'] = 'simpleType';
1420 $this->simpleTypes[ $attrs['name'] ]['phpType'] = 'scalar';
1421 } else {
1422 $this->xdebug('processing unnamed simpleType for element '.$this->currentElement);
1423 $this->currentSimpleType = $this->currentElement . '_ContainedType';
1424 //$this->currentElement = false;
1425 $this->simpleTypes[$this->currentSimpleType] = $attrs;
1426 $this->simpleTypes[$this->currentSimpleType]['phpType'] = 'scalar';
1428 break;
1429 case 'union': // simpleType type list
1430 break;
1431 default:
1432 //$this->xdebug("do not have anything to do for element $name");
1437 * end-element handler
1439 * @param string $parser XML parser object
1440 * @param string $name element name
1441 * @access private
1443 function schemaEndElement($parser, $name) {
1444 // bring depth down a notch
1445 $this->depth--;
1446 // position of current element is equal to the last value left in depth_array for my depth
1447 if(isset($this->depth_array[$this->depth])){
1448 $pos = $this->depth_array[$this->depth];
1450 // get element prefix
1451 if ($prefix = $this->getPrefix($name)){
1452 // get unqualified name
1453 $name = $this->getLocalPart($name);
1454 } else {
1455 $prefix = '';
1457 // move on...
1458 if($name == 'complexType'){
1459 $this->xdebug('done processing complexType ' . ($this->currentComplexType ? $this->currentComplexType : '(unknown)'));
1460 $this->currentComplexType = array_pop($this->complexTypeStack);
1461 //$this->currentElement = false;
1463 if($name == 'element'){
1464 $this->xdebug('done processing element ' . ($this->currentElement ? $this->currentElement : '(unknown)'));
1465 $this->currentElement = array_pop($this->elementStack);
1467 if($name == 'simpleType'){
1468 $this->xdebug('done processing simpleType ' . ($this->currentSimpleType ? $this->currentSimpleType : '(unknown)'));
1469 $this->currentSimpleType = array_pop($this->simpleTypeStack);
1474 * element content handler
1476 * @param string $parser XML parser object
1477 * @param string $data element content
1478 * @access private
1480 function schemaCharacterData($parser, $data){
1481 $pos = $this->depth_array[$this->depth - 1];
1482 $this->message[$pos]['cdata'] .= $data;
1486 * serialize the schema
1488 * @access public
1490 function serializeSchema(){
1492 $schemaPrefix = $this->getPrefixFromNamespace($this->XMLSchemaVersion);
1493 $xml = '';
1494 // imports
1495 if (sizeof($this->imports) > 0) {
1496 foreach($this->imports as $ns => $list) {
1497 foreach ($list as $ii) {
1498 if ($ii['location'] != '') {
1499 $xml .= " <$schemaPrefix:import location=\"" . $ii['location'] . '" namespace="' . $ns . "\" />\n";
1500 } else {
1501 $xml .= " <$schemaPrefix:import namespace=\"" . $ns . "\" />\n";
1506 // complex types
1507 foreach($this->complexTypes as $typeName => $attrs){
1508 $contentStr = '';
1509 // serialize child elements
1510 if(isset($attrs['elements']) && (count($attrs['elements']) > 0)){
1511 foreach($attrs['elements'] as $element => $eParts){
1512 if(isset($eParts['ref'])){
1513 $contentStr .= " <$schemaPrefix:element ref=\"$element\"/>\n";
1514 } else {
1515 $contentStr .= " <$schemaPrefix:element name=\"$element\" type=\"" . $this->contractQName($eParts['type']) . "\"";
1516 foreach ($eParts as $aName => $aValue) {
1517 // handle, e.g., abstract, default, form, minOccurs, maxOccurs, nillable
1518 if ($aName != 'name' && $aName != 'type') {
1519 $contentStr .= " $aName=\"$aValue\"";
1522 $contentStr .= "/>\n";
1525 // compositor wraps elements
1526 if (isset($attrs['compositor']) && ($attrs['compositor'] != '')) {
1527 $contentStr = " <$schemaPrefix:$attrs[compositor]>\n".$contentStr." </$schemaPrefix:$attrs[compositor]>\n";
1530 // attributes
1531 if(isset($attrs['attrs']) && (count($attrs['attrs']) >= 1)){
1532 foreach($attrs['attrs'] as $attr => $aParts){
1533 $contentStr .= " <$schemaPrefix:attribute";
1534 foreach ($aParts as $a => $v) {
1535 if ($a == 'ref' || $a == 'type') {
1536 $contentStr .= " $a=\"".$this->contractQName($v).'"';
1537 } elseif ($a == 'http://schemas.xmlsoap.org/wsdl/:arrayType') {
1538 $this->usedNamespaces['wsdl'] = $this->namespaces['wsdl'];
1539 $contentStr .= ' wsdl:arrayType="'.$this->contractQName($v).'"';
1540 } else {
1541 $contentStr .= " $a=\"$v\"";
1544 $contentStr .= "/>\n";
1547 // if restriction
1548 if (isset($attrs['restrictionBase']) && $attrs['restrictionBase'] != ''){
1549 $contentStr = " <$schemaPrefix:restriction base=\"".$this->contractQName($attrs['restrictionBase'])."\">\n".$contentStr." </$schemaPrefix:restriction>\n";
1550 // complex or simple content
1551 if ((isset($attrs['elements']) && count($attrs['elements']) > 0) || (isset($attrs['attrs']) && count($attrs['attrs']) > 0)){
1552 $contentStr = " <$schemaPrefix:complexContent>\n".$contentStr." </$schemaPrefix:complexContent>\n";
1555 // finalize complex type
1556 if($contentStr != ''){
1557 $contentStr = " <$schemaPrefix:complexType name=\"$typeName\">\n".$contentStr." </$schemaPrefix:complexType>\n";
1558 } else {
1559 $contentStr = " <$schemaPrefix:complexType name=\"$typeName\"/>\n";
1561 $xml .= $contentStr;
1563 // simple types
1564 if(isset($this->simpleTypes) && count($this->simpleTypes) > 0){
1565 foreach($this->simpleTypes as $typeName => $eParts){
1566 $xml .= " <$schemaPrefix:simpleType name=\"$typeName\">\n <$schemaPrefix:restriction base=\"".$this->contractQName($eParts['type'])."\"/>\n";
1567 if (isset($eParts['enumeration'])) {
1568 foreach ($eParts['enumeration'] as $e) {
1569 $xml .= " <$schemaPrefix:enumeration value=\"$e\"/>\n";
1572 $xml .= " </$schemaPrefix:simpleType>";
1575 // elements
1576 if(isset($this->elements) && count($this->elements) > 0){
1577 foreach($this->elements as $element => $eParts){
1578 $xml .= " <$schemaPrefix:element name=\"$element\" type=\"".$this->contractQName($eParts['type'])."\"/>\n";
1581 // attributes
1582 if(isset($this->attributes) && count($this->attributes) > 0){
1583 foreach($this->attributes as $attr => $aParts){
1584 $xml .= " <$schemaPrefix:attribute name=\"$attr\" type=\"".$this->contractQName($aParts['type'])."\"\n/>";
1587 // finish 'er up
1588 $el = "<$schemaPrefix:schema targetNamespace=\"$this->schemaTargetNamespace\"\n";
1589 foreach (array_diff($this->usedNamespaces, $this->enclosingNamespaces) as $nsp => $ns) {
1590 $el .= " xmlns:$nsp=\"$ns\"";
1592 $xml = $el . ">\n".$xml."</$schemaPrefix:schema>\n";
1593 return $xml;
1597 * adds debug data to the clas level debug string
1599 * @param string $string debug data
1600 * @access private
1602 function xdebug($string){
1603 $this->debug('<' . $this->schemaTargetNamespace . '> '.$string);
1607 * get the PHP type of a user defined type in the schema
1608 * PHP type is kind of a misnomer since it actually returns 'struct' for assoc. arrays
1609 * returns false if no type exists, or not w/ the given namespace
1610 * else returns a string that is either a native php type, or 'struct'
1612 * @param string $type, name of defined type
1613 * @param string $ns, namespace of type
1614 * @return mixed
1615 * @access public
1616 * @deprecated
1618 function getPHPType($type,$ns){
1619 if(isset($this->typemap[$ns][$type])){
1620 //print "found type '$type' and ns $ns in typemap<br>";
1621 return $this->typemap[$ns][$type];
1622 } elseif(isset($this->complexTypes[$type])){
1623 //print "getting type '$type' and ns $ns from complexTypes array<br>";
1624 return $this->complexTypes[$type]['phpType'];
1626 return false;
1630 * returns an associative array of information about a given type
1631 * returns false if no type exists by the given name
1633 * For a complexType typeDef = array(
1634 * 'restrictionBase' => '',
1635 * 'phpType' => '',
1636 * 'compositor' => '(sequence|all)',
1637 * 'elements' => array(), // refs to elements array
1638 * 'attrs' => array() // refs to attributes array
1639 * ... and so on (see addComplexType)
1642 * For simpleType or element, the array has different keys.
1644 * @param string
1645 * @return mixed
1646 * @access public
1647 * @see addComplexType
1648 * @see addSimpleType
1649 * @see addElement
1651 function getTypeDef($type){
1652 //$this->debug("in getTypeDef for type $type");
1653 if(isset($this->complexTypes[$type])){
1654 $this->xdebug("in getTypeDef, found complexType $type");
1655 return $this->complexTypes[$type];
1656 } elseif(isset($this->simpleTypes[$type])){
1657 $this->xdebug("in getTypeDef, found simpleType $type");
1658 if (!isset($this->simpleTypes[$type]['phpType'])) {
1659 // get info for type to tack onto the simple type
1660 // TODO: can this ever really apply (i.e. what is a simpleType really?)
1661 $uqType = substr($this->simpleTypes[$type]['type'], strrpos($this->simpleTypes[$type]['type'], ':') + 1);
1662 $ns = substr($this->simpleTypes[$type]['type'], 0, strrpos($this->simpleTypes[$type]['type'], ':'));
1663 $etype = $this->getTypeDef($uqType);
1664 if ($etype) {
1665 $this->xdebug("in getTypeDef, found type for simpleType $type:");
1666 $this->xdebug($this->varDump($etype));
1667 if (isset($etype['phpType'])) {
1668 $this->simpleTypes[$type]['phpType'] = $etype['phpType'];
1670 if (isset($etype['elements'])) {
1671 $this->simpleTypes[$type]['elements'] = $etype['elements'];
1675 return $this->simpleTypes[$type];
1676 } elseif(isset($this->elements[$type])){
1677 $this->xdebug("in getTypeDef, found element $type");
1678 if (!isset($this->elements[$type]['phpType'])) {
1679 // get info for type to tack onto the element
1680 $uqType = substr($this->elements[$type]['type'], strrpos($this->elements[$type]['type'], ':') + 1);
1681 $ns = substr($this->elements[$type]['type'], 0, strrpos($this->elements[$type]['type'], ':'));
1682 $etype = $this->getTypeDef($uqType);
1683 if ($etype) {
1684 $this->xdebug("in getTypeDef, found type for element $type:");
1685 $this->xdebug($this->varDump($etype));
1686 if (isset($etype['phpType'])) {
1687 $this->elements[$type]['phpType'] = $etype['phpType'];
1689 if (isset($etype['elements'])) {
1690 $this->elements[$type]['elements'] = $etype['elements'];
1692 } elseif ($ns == 'http://www.w3.org/2001/XMLSchema') {
1693 $this->xdebug("in getTypeDef, element $type is an XSD type");
1694 $this->elements[$type]['phpType'] = 'scalar';
1697 return $this->elements[$type];
1698 } elseif(isset($this->attributes[$type])){
1699 $this->xdebug("in getTypeDef, found attribute $type");
1700 return $this->attributes[$type];
1701 } elseif (ereg('_ContainedType$', $type)) {
1702 $this->xdebug("in getTypeDef, have an untyped element $type");
1703 $typeDef['typeClass'] = 'simpleType';
1704 $typeDef['phpType'] = 'scalar';
1705 $typeDef['type'] = 'http://www.w3.org/2001/XMLSchema:string';
1706 return $typeDef;
1708 $this->xdebug("in getTypeDef, did not find $type");
1709 return false;
1713 * returns a sample serialization of a given type, or false if no type by the given name
1715 * @param string $type, name of type
1716 * @return mixed
1717 * @access public
1718 * @deprecated
1720 function serializeTypeDef($type){
1721 //print "in sTD() for type $type<br>";
1722 if($typeDef = $this->getTypeDef($type)){
1723 $str .= '<'.$type;
1724 if(is_array($typeDef['attrs'])){
1725 foreach($attrs as $attName => $data){
1726 $str .= " $attName=\"{type = ".$data['type']."}\"";
1729 $str .= " xmlns=\"".$this->schema['targetNamespace']."\"";
1730 if(count($typeDef['elements']) > 0){
1731 $str .= ">";
1732 foreach($typeDef['elements'] as $element => $eData){
1733 $str .= $this->serializeTypeDef($element);
1735 $str .= "</$type>";
1736 } elseif($typeDef['typeClass'] == 'element') {
1737 $str .= "></$type>";
1738 } else {
1739 $str .= "/>";
1741 return $str;
1743 return false;
1747 * returns HTML form elements that allow a user
1748 * to enter values for creating an instance of the given type.
1750 * @param string $name, name for type instance
1751 * @param string $type, name of type
1752 * @return string
1753 * @access public
1754 * @deprecated
1756 function typeToForm($name,$type){
1757 // get typedef
1758 if($typeDef = $this->getTypeDef($type)){
1759 // if struct
1760 if($typeDef['phpType'] == 'struct'){
1761 $buffer .= '<table>';
1762 foreach($typeDef['elements'] as $child => $childDef){
1763 $buffer .= "
1764 <tr><td align='right'>$childDef[name] (type: ".$this->getLocalPart($childDef['type'])."):</td>
1765 <td><input type='text' name='parameters[".$name."][$childDef[name]]'></td></tr>";
1767 $buffer .= '</table>';
1768 // if array
1769 } elseif($typeDef['phpType'] == 'array'){
1770 $buffer .= '<table>';
1771 for($i=0;$i < 3; $i++){
1772 $buffer .= "
1773 <tr><td align='right'>array item (type: $typeDef[arrayType]):</td>
1774 <td><input type='text' name='parameters[".$name."][]'></td></tr>";
1776 $buffer .= '</table>';
1777 // if scalar
1778 } else {
1779 $buffer .= "<input type='text' name='parameters[$name]'>";
1781 } else {
1782 $buffer .= "<input type='text' name='parameters[$name]'>";
1784 return $buffer;
1788 * adds a complex type to the schema
1790 * example: array
1792 * addType(
1793 * 'ArrayOfstring',
1794 * 'complexType',
1795 * 'array',
1796 * '',
1797 * 'SOAP-ENC:Array',
1798 * array('ref'=>'SOAP-ENC:arrayType','wsdl:arrayType'=>'string[]'),
1799 * 'xsd:string'
1800 * );
1802 * example: PHP associative array ( SOAP Struct )
1804 * addType(
1805 * 'SOAPStruct',
1806 * 'complexType',
1807 * 'struct',
1808 * 'all',
1809 * array('myVar'=> array('name'=>'myVar','type'=>'string')
1810 * );
1812 * @param name
1813 * @param typeClass (complexType|simpleType|attribute)
1814 * @param phpType: currently supported are array and struct (php assoc array)
1815 * @param compositor (all|sequence|choice)
1816 * @param restrictionBase namespace:name (http://schemas.xmlsoap.org/soap/encoding/:Array)
1817 * @param elements = array ( name = array(name=>'',type=>'') )
1818 * @param attrs = array(
1819 * array(
1820 * 'ref' => "http://schemas.xmlsoap.org/soap/encoding/:arrayType",
1821 * "http://schemas.xmlsoap.org/wsdl/:arrayType" => "string[]"
1824 * @param arrayType: namespace:name (http://www.w3.org/2001/XMLSchema:string)
1825 * @access public
1826 * @see getTypeDef
1828 function addComplexType($name,$typeClass='complexType',$phpType='array',$compositor='',$restrictionBase='',$elements=array(),$attrs=array(),$arrayType=''){
1829 $this->complexTypes[$name] = array(
1830 'name' => $name,
1831 'typeClass' => $typeClass,
1832 'phpType' => $phpType,
1833 'compositor'=> $compositor,
1834 'restrictionBase' => $restrictionBase,
1835 'elements' => $elements,
1836 'attrs' => $attrs,
1837 'arrayType' => $arrayType
1840 $this->xdebug("addComplexType $name:");
1841 $this->appendDebug($this->varDump($this->complexTypes[$name]));
1845 * adds a simple type to the schema
1847 * @param string $name
1848 * @param string $restrictionBase namespace:name (http://schemas.xmlsoap.org/soap/encoding/:Array)
1849 * @param string $typeClass (should always be simpleType)
1850 * @param string $phpType (should always be scalar)
1851 * @param array $enumeration array of values
1852 * @access public
1853 * @see xmlschema
1854 * @see getTypeDef
1856 function addSimpleType($name, $restrictionBase='', $typeClass='simpleType', $phpType='scalar', $enumeration=array()) {
1857 $this->simpleTypes[$name] = array(
1858 'name' => $name,
1859 'typeClass' => $typeClass,
1860 'phpType' => $phpType,
1861 'type' => $restrictionBase,
1862 'enumeration' => $enumeration
1865 $this->xdebug("addSimpleType $name:");
1866 $this->appendDebug($this->varDump($this->simpleTypes[$name]));
1870 * adds an element to the schema
1872 * @param array $attrs attributes that must include name and type
1873 * @see xmlschema
1874 * @access public
1876 function addElement($attrs) {
1877 if (! $this->getPrefix($attrs['type'])) {
1878 $attrs['type'] = $this->schemaTargetNamespace . ':' . $attrs['type'];
1880 $this->elements[ $attrs['name'] ] = $attrs;
1881 $this->elements[ $attrs['name'] ]['typeClass'] = 'element';
1883 $this->xdebug("addElement " . $attrs['name']);
1884 $this->appendDebug($this->varDump($this->elements[ $attrs['name'] ]));
1890 ?><?php
1895 * For creating serializable abstractions of native PHP types. This class
1896 * allows element name/namespace, XSD type, and XML attributes to be
1897 * associated with a value. This is extremely useful when WSDL is not
1898 * used, but is also useful when WSDL is used with polymorphic types, including
1899 * xsd:anyType and user-defined types.
1901 * @author Dietrich Ayala <dietrich@ganx4.com>
1902 * @version $Id$
1903 * @access public
1905 class soapval extends nusoap_base {
1907 * The XML element name
1909 * @var string
1910 * @access private
1912 var $name;
1914 * The XML type name (string or false)
1916 * @var mixed
1917 * @access private
1919 var $type;
1921 * The PHP value
1923 * @var mixed
1924 * @access private
1926 var $value;
1928 * The XML element namespace (string or false)
1930 * @var mixed
1931 * @access private
1933 var $element_ns;
1935 * The XML type namespace (string or false)
1937 * @var mixed
1938 * @access private
1940 var $type_ns;
1942 * The XML element attributes (array or false)
1944 * @var mixed
1945 * @access private
1947 var $attributes;
1950 * constructor
1952 * @param string $name optional name
1953 * @param mixed $type optional type name
1954 * @param mixed $value optional value
1955 * @param mixed $element_ns optional namespace of value
1956 * @param mixed $type_ns optional namespace of type
1957 * @param mixed $attributes associative array of attributes to add to element serialization
1958 * @access public
1960 function soapval($name='soapval',$type=false,$value=-1,$element_ns=false,$type_ns=false,$attributes=false) {
1961 parent::nusoap_base();
1962 $this->name = $name;
1963 $this->type = $type;
1964 $this->value = $value;
1965 $this->element_ns = $element_ns;
1966 $this->type_ns = $type_ns;
1967 $this->attributes = $attributes;
1971 * return serialized value
1973 * @param string $use The WSDL use value (encoded|literal)
1974 * @return string XML data
1975 * @access public
1977 function serialize($use='encoded') {
1978 return $this->serialize_val($this->value,$this->name,$this->type,$this->element_ns,$this->type_ns,$this->attributes,$use);
1982 * decodes a soapval object into a PHP native type
1984 * @return mixed
1985 * @access public
1987 function decode(){
1988 return $this->value;
1994 ?><?php
1999 * transport class for sending/receiving data via HTTP and HTTPS
2000 * NOTE: PHP must be compiled with the CURL extension for HTTPS support
2002 * @author Dietrich Ayala <dietrich@ganx4.com>
2003 * @version $Id$
2004 * @access public
2006 class soap_transport_http extends nusoap_base {
2008 var $url = '';
2009 var $uri = '';
2010 var $digest_uri = '';
2011 var $scheme = '';
2012 var $host = '';
2013 var $port = '';
2014 var $path = '';
2015 var $request_method = 'POST';
2016 var $protocol_version = '1.0';
2017 var $encoding = '';
2018 var $outgoing_headers = array();
2019 var $incoming_headers = array();
2020 var $incoming_cookies = array();
2021 var $outgoing_payload = '';
2022 var $incoming_payload = '';
2023 var $useSOAPAction = true;
2024 var $persistentConnection = false;
2025 var $ch = false; // cURL handle
2026 var $username = '';
2027 var $password = '';
2028 var $authtype = '';
2029 var $digestRequest = array();
2030 var $certRequest = array(); // keys must be cainfofile (optional), sslcertfile, sslkeyfile, passphrase, verifypeer (optional), verifyhost (optional)
2031 // cainfofile: certificate authority file, e.g. '$pathToPemFiles/rootca.pem'
2032 // sslcertfile: SSL certificate file, e.g. '$pathToPemFiles/mycert.pem'
2033 // sslkeyfile: SSL key file, e.g. '$pathToPemFiles/mykey.pem'
2034 // passphrase: SSL key password/passphrase
2035 // verifypeer: default is 1
2036 // verifyhost: default is 1
2039 * constructor
2041 function soap_transport_http($url){
2042 parent::nusoap_base();
2043 $this->setURL($url);
2044 ereg('\$Revisio' . 'n: ([^ ]+)', $this->revision, $rev);
2045 $this->outgoing_headers['User-Agent'] = $this->title.'/'.$this->version.' ('.$rev[1].')';
2046 $this->debug('set User-Agent: ' . $this->outgoing_headers['User-Agent']);
2049 function setURL($url) {
2050 $this->url = $url;
2052 $u = parse_url($url);
2053 foreach($u as $k => $v){
2054 $this->debug("$k = $v");
2055 $this->$k = $v;
2058 // add any GET params to path
2059 if(isset($u['query']) && $u['query'] != ''){
2060 $this->path .= '?' . $u['query'];
2063 // set default port
2064 if(!isset($u['port'])){
2065 if($u['scheme'] == 'https'){
2066 $this->port = 443;
2067 } else {
2068 $this->port = 80;
2072 $this->uri = $this->path;
2073 $this->digest_uri = $this->uri;
2075 // build headers
2076 if (!isset($u['port'])) {
2077 $this->outgoing_headers['Host'] = $this->host;
2078 } else {
2079 $this->outgoing_headers['Host'] = $this->host.':'.$this->port;
2081 $this->debug('set Host: ' . $this->outgoing_headers['Host']);
2083 if (isset($u['user']) && $u['user'] != '') {
2084 $this->setCredentials(urldecode($u['user']), isset($u['pass']) ? urldecode($u['pass']) : '');
2088 function connect($connection_timeout=0,$response_timeout=30){
2089 // For PHP 4.3 with OpenSSL, change https scheme to ssl, then treat like
2090 // "regular" socket.
2091 // TODO: disabled for now because OpenSSL must be *compiled* in (not just
2092 // loaded), and until PHP5 stream_get_wrappers is not available.
2093 // if ($this->scheme == 'https') {
2094 // if (version_compare(phpversion(), '4.3.0') >= 0) {
2095 // if (extension_loaded('openssl')) {
2096 // $this->scheme = 'ssl';
2097 // $this->debug('Using SSL over OpenSSL');
2098 // }
2099 // }
2100 // }
2101 $this->debug("connect connection_timeout $connection_timeout, response_timeout $response_timeout, scheme $this->scheme, host $this->host, port $this->port");
2102 if ($this->scheme == 'http' || $this->scheme == 'ssl') {
2103 // use persistent connection
2104 if($this->persistentConnection && isset($this->fp) && is_resource($this->fp)){
2105 if (!feof($this->fp)) {
2106 $this->debug('Re-use persistent connection');
2107 return true;
2109 fclose($this->fp);
2110 $this->debug('Closed persistent connection at EOF');
2113 // munge host if using OpenSSL
2114 if ($this->scheme == 'ssl') {
2115 $host = 'ssl://' . $this->host;
2116 } else {
2117 $host = $this->host;
2119 $this->debug('calling fsockopen with host ' . $host . ' connection_timeout ' . $connection_timeout);
2121 // open socket
2122 if($connection_timeout > 0){
2123 $this->fp = @fsockopen( $host, $this->port, $this->errno, $this->error_str, $connection_timeout);
2124 } else {
2125 $this->fp = @fsockopen( $host, $this->port, $this->errno, $this->error_str);
2128 // test pointer
2129 if(!$this->fp) {
2130 $msg = 'Couldn\'t open socket connection to server ' . $this->url;
2131 if ($this->errno) {
2132 $msg .= ', Error ('.$this->errno.'): '.$this->error_str;
2133 } else {
2134 $msg .= ' prior to connect(). This is often a problem looking up the host name.';
2136 $this->debug($msg);
2137 $this->setError($msg);
2138 return false;
2141 // set response timeout
2142 $this->debug('set response timeout to ' . $response_timeout);
2143 socket_set_timeout( $this->fp, $response_timeout);
2145 $this->debug('socket connected');
2146 return true;
2147 } else if ($this->scheme == 'https') {
2148 if (!extension_loaded('curl')) {
2149 $this->setError('CURL Extension, or OpenSSL extension w/ PHP version >= 4.3 is required for HTTPS');
2150 return false;
2152 $this->debug('connect using https');
2153 // init CURL
2154 $this->ch = curl_init();
2155 // set url
2156 $hostURL = ($this->port != '') ? "https://$this->host:$this->port" : "https://$this->host";
2157 // add path
2158 $hostURL .= $this->path;
2159 curl_setopt($this->ch, CURLOPT_URL, $hostURL);
2160 // follow location headers (re-directs)
2161 curl_setopt($this->ch, CURLOPT_FOLLOWLOCATION, 1);
2162 // ask for headers in the response output
2163 curl_setopt($this->ch, CURLOPT_HEADER, 1);
2164 // ask for the response output as the return value
2165 curl_setopt($this->ch, CURLOPT_RETURNTRANSFER, 1);
2166 // encode
2167 // We manage this ourselves through headers and encoding
2168 // if(function_exists('gzuncompress')){
2169 // curl_setopt($this->ch, CURLOPT_ENCODING, 'deflate');
2170 // }
2171 // persistent connection
2172 if ($this->persistentConnection) {
2173 // The way we send data, we cannot use persistent connections, since
2174 // there will be some "junk" at the end of our request.
2175 //curl_setopt($this->ch, CURL_HTTP_VERSION_1_1, true);
2176 $this->persistentConnection = false;
2177 $this->outgoing_headers['Connection'] = 'close';
2178 $this->debug('set Connection: ' . $this->outgoing_headers['Connection']);
2180 // set timeout
2181 if ($connection_timeout != 0) {
2182 curl_setopt($this->ch, CURLOPT_TIMEOUT, $connection_timeout);
2184 // TODO: cURL has added a connection timeout separate from the response timeout
2185 //if ($connection_timeout != 0) {
2186 // curl_setopt($this->ch, CURLOPT_CONNECTIONTIMEOUT, $connection_timeout);
2188 //if ($response_timeout != 0) {
2189 // curl_setopt($this->ch, CURLOPT_TIMEOUT, $response_timeout);
2192 // recent versions of cURL turn on peer/host checking by default,
2193 // while PHP binaries are not compiled with a default location for the
2194 // CA cert bundle, so disable peer/host checking.
2195 //curl_setopt($this->ch, CURLOPT_CAINFO, 'f:\php-4.3.2-win32\extensions\curl-ca-bundle.crt');
2196 curl_setopt($this->ch, CURLOPT_SSL_VERIFYPEER, 0);
2197 curl_setopt($this->ch, CURLOPT_SSL_VERIFYHOST, 0);
2199 // support client certificates (thanks Tobias Boes, Doug Anarino, Eryan Ariobowo)
2200 if ($this->authtype == 'certificate') {
2201 if (isset($this->certRequest['cainfofile'])) {
2202 curl_setopt($this->ch, CURLOPT_CAINFO, $this->certRequest['cainfofile']);
2204 if (isset($this->certRequest['verifypeer'])) {
2205 curl_setopt($this->ch, CURLOPT_SSL_VERIFYPEER, $this->certRequest['verifypeer']);
2206 } else {
2207 curl_setopt($this->ch, CURLOPT_SSL_VERIFYPEER, 1);
2209 if (isset($this->certRequest['verifyhost'])) {
2210 curl_setopt($this->ch, CURLOPT_SSL_VERIFYHOST, $this->certRequest['verifyhost']);
2211 } else {
2212 curl_setopt($this->ch, CURLOPT_SSL_VERIFYHOST, 1);
2214 if (isset($this->certRequest['sslcertfile'])) {
2215 curl_setopt($this->ch, CURLOPT_SSLCERT, $this->certRequest['sslcertfile']);
2217 if (isset($this->certRequest['sslkeyfile'])) {
2218 curl_setopt($this->ch, CURLOPT_SSLKEY, $this->certRequest['sslkeyfile']);
2220 if (isset($this->certRequest['passphrase'])) {
2221 curl_setopt($this->ch, CURLOPT_SSLKEYPASSWD , $this->certRequest['passphrase']);
2224 $this->debug('cURL connection set up');
2225 return true;
2226 } else {
2227 $this->setError('Unknown scheme ' . $this->scheme);
2228 $this->debug('Unknown scheme ' . $this->scheme);
2229 return false;
2234 * send the SOAP message via HTTP
2236 * @param string $data message data
2237 * @param integer $timeout set connection timeout in seconds
2238 * @param integer $response_timeout set response timeout in seconds
2239 * @param array $cookies cookies to send
2240 * @return string data
2241 * @access public
2243 function send($data, $timeout=0, $response_timeout=30, $cookies=NULL) {
2245 $this->debug('entered send() with data of length: '.strlen($data));
2247 $this->tryagain = true;
2248 $tries = 0;
2249 while ($this->tryagain) {
2250 $this->tryagain = false;
2251 if ($tries++ < 2) {
2252 // make connnection
2253 if (!$this->connect($timeout, $response_timeout)){
2254 return false;
2257 // send request
2258 if (!$this->sendRequest($data, $cookies)){
2259 return false;
2262 // get response
2263 $respdata = $this->getResponse();
2264 } else {
2265 $this->setError('Too many tries to get an OK response');
2268 $this->debug('end of send()');
2269 return $respdata;
2274 * send the SOAP message via HTTPS 1.0 using CURL
2276 * @param string $msg message data
2277 * @param integer $timeout set connection timeout in seconds
2278 * @param integer $response_timeout set response timeout in seconds
2279 * @param array $cookies cookies to send
2280 * @return string data
2281 * @access public
2283 function sendHTTPS($data, $timeout=0, $response_timeout=30, $cookies) {
2284 return $this->send($data, $timeout, $response_timeout, $cookies);
2288 * if authenticating, set user credentials here
2290 * @param string $username
2291 * @param string $password
2292 * @param string $authtype (basic, digest, certificate)
2293 * @param array $digestRequest (keys must be nonce, nc, realm, qop)
2294 * @param array $certRequest (keys must be cainfofile (optional), sslcertfile, sslkeyfile, passphrase, verifypeer (optional), verifyhost (optional): see corresponding options in cURL docs)
2295 * @access public
2297 function setCredentials($username, $password, $authtype = 'basic', $digestRequest = array(), $certRequest = array()) {
2298 $this->debug("Set credentials for authtype $authtype");
2299 // cf. RFC 2617
2300 if ($authtype == 'basic') {
2301 $this->outgoing_headers['Authorization'] = 'Basic '.base64_encode(str_replace(':','',$username).':'.$password);
2302 } elseif ($authtype == 'digest') {
2303 if (isset($digestRequest['nonce'])) {
2304 $digestRequest['nc'] = isset($digestRequest['nc']) ? $digestRequest['nc']++ : 1;
2306 // calculate the Digest hashes (calculate code based on digest implementation found at: http://www.rassoc.com/gregr/weblog/stories/2002/07/09/webServicesSecurityHttpDigestAuthenticationWithoutActiveDirectory.html)
2308 // A1 = unq(username-value) ":" unq(realm-value) ":" passwd
2309 $A1 = $username. ':' . (isset($digestRequest['realm']) ? $digestRequest['realm'] : '') . ':' . $password;
2311 // H(A1) = MD5(A1)
2312 $HA1 = md5($A1);
2314 // A2 = Method ":" digest-uri-value
2315 $A2 = 'POST:' . $this->digest_uri;
2317 // H(A2)
2318 $HA2 = md5($A2);
2320 // KD(secret, data) = H(concat(secret, ":", data))
2321 // if qop == auth:
2322 // request-digest = <"> < KD ( H(A1), unq(nonce-value)
2323 // ":" nc-value
2324 // ":" unq(cnonce-value)
2325 // ":" unq(qop-value)
2326 // ":" H(A2)
2327 // ) <">
2328 // if qop is missing,
2329 // request-digest = <"> < KD ( H(A1), unq(nonce-value) ":" H(A2) ) > <">
2331 $unhashedDigest = '';
2332 $nonce = isset($digestRequest['nonce']) ? $digestRequest['nonce'] : '';
2333 $cnonce = $nonce;
2334 if ($digestRequest['qop'] != '') {
2335 $unhashedDigest = $HA1 . ':' . $nonce . ':' . sprintf("%08d", $digestRequest['nc']) . ':' . $cnonce . ':' . $digestRequest['qop'] . ':' . $HA2;
2336 } else {
2337 $unhashedDigest = $HA1 . ':' . $nonce . ':' . $HA2;
2340 $hashedDigest = md5($unhashedDigest);
2342 $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 . '"';
2344 } elseif ($authtype == 'certificate') {
2345 $this->certRequest = $certRequest;
2347 $this->username = $username;
2348 $this->password = $password;
2349 $this->authtype = $authtype;
2350 $this->digestRequest = $digestRequest;
2352 if (isset($this->outgoing_headers['Authorization'])) {
2353 $this->debug('set Authorization: ' . substr($this->outgoing_headers['Authorization'], 0, 12) . '...');
2354 } else {
2355 $this->debug('Authorization header not set');
2360 * set the soapaction value
2362 * @param string $soapaction
2363 * @access public
2365 function setSOAPAction($soapaction) {
2366 $this->outgoing_headers['SOAPAction'] = '"' . $soapaction . '"';
2367 $this->debug('set SOAPAction: ' . $this->outgoing_headers['SOAPAction']);
2371 * use http encoding
2373 * @param string $enc encoding style. supported values: gzip, deflate, or both
2374 * @access public
2376 function setEncoding($enc='gzip, deflate') {
2377 if (function_exists('gzdeflate')) {
2378 $this->protocol_version = '1.1';
2379 $this->outgoing_headers['Accept-Encoding'] = $enc;
2380 $this->debug('set Accept-Encoding: ' . $this->outgoing_headers['Accept-Encoding']);
2381 if (!isset($this->outgoing_headers['Connection'])) {
2382 $this->outgoing_headers['Connection'] = 'close';
2383 $this->persistentConnection = false;
2384 $this->debug('set Connection: ' . $this->outgoing_headers['Connection']);
2386 set_magic_quotes_runtime(0);
2387 // deprecated
2388 $this->encoding = $enc;
2393 * set proxy info here
2395 * @param string $proxyhost
2396 * @param string $proxyport
2397 * @param string $proxyusername
2398 * @param string $proxypassword
2399 * @access public
2401 function setProxy($proxyhost, $proxyport, $proxyusername = '', $proxypassword = '') {
2402 $this->uri = $this->url;
2403 $this->host = $proxyhost;
2404 $this->port = $proxyport;
2405 if ($proxyusername != '' && $proxypassword != '') {
2406 $this->outgoing_headers['Proxy-Authorization'] = ' Basic '.base64_encode($proxyusername.':'.$proxypassword);
2407 $this->debug('set Proxy-Authorization: ' . $this->outgoing_headers['Proxy-Authorization']);
2412 * decode a string that is encoded w/ "chunked' transfer encoding
2413 * as defined in RFC2068 19.4.6
2415 * @param string $buffer
2416 * @param string $lb
2417 * @returns string
2418 * @access public
2419 * @deprecated
2421 function decodeChunked($buffer, $lb){
2422 // length := 0
2423 $length = 0;
2424 $new = '';
2426 // read chunk-size, chunk-extension (if any) and CRLF
2427 // get the position of the linebreak
2428 $chunkend = strpos($buffer, $lb);
2429 if ($chunkend == FALSE) {
2430 $this->debug('no linebreak found in decodeChunked');
2431 return $new;
2433 $temp = substr($buffer,0,$chunkend);
2434 $chunk_size = hexdec( trim($temp) );
2435 $chunkstart = $chunkend + strlen($lb);
2436 // while (chunk-size > 0) {
2437 while ($chunk_size > 0) {
2438 $this->debug("chunkstart: $chunkstart chunk_size: $chunk_size");
2439 $chunkend = strpos( $buffer, $lb, $chunkstart + $chunk_size);
2441 // Just in case we got a broken connection
2442 if ($chunkend == FALSE) {
2443 $chunk = substr($buffer,$chunkstart);
2444 // append chunk-data to entity-body
2445 $new .= $chunk;
2446 $length += strlen($chunk);
2447 break;
2450 // read chunk-data and CRLF
2451 $chunk = substr($buffer,$chunkstart,$chunkend-$chunkstart);
2452 // append chunk-data to entity-body
2453 $new .= $chunk;
2454 // length := length + chunk-size
2455 $length += strlen($chunk);
2456 // read chunk-size and CRLF
2457 $chunkstart = $chunkend + strlen($lb);
2459 $chunkend = strpos($buffer, $lb, $chunkstart) + strlen($lb);
2460 if ($chunkend == FALSE) {
2461 break; //Just in case we got a broken connection
2463 $temp = substr($buffer,$chunkstart,$chunkend-$chunkstart);
2464 $chunk_size = hexdec( trim($temp) );
2465 $chunkstart = $chunkend;
2467 return $new;
2471 * Writes payload, including HTTP headers, to $this->outgoing_payload.
2473 function buildPayload($data, $cookie_str = '') {
2474 // add content-length header
2475 $this->outgoing_headers['Content-Length'] = strlen($data);
2476 $this->debug('set Content-Length: ' . $this->outgoing_headers['Content-Length']);
2478 // start building outgoing payload:
2479 $req = "$this->request_method $this->uri HTTP/$this->protocol_version";
2480 $this->debug("HTTP request: $req");
2481 $this->outgoing_payload = "$req\r\n";
2483 // loop thru headers, serializing
2484 foreach($this->outgoing_headers as $k => $v){
2485 $hdr = $k.': '.$v;
2486 $this->debug("HTTP header: $hdr");
2487 $this->outgoing_payload .= "$hdr\r\n";
2490 // add any cookies
2491 if ($cookie_str != '') {
2492 $hdr = 'Cookie: '.$cookie_str;
2493 $this->debug("HTTP header: $hdr");
2494 $this->outgoing_payload .= "$hdr\r\n";
2497 // header/body separator
2498 $this->outgoing_payload .= "\r\n";
2500 // add data
2501 $this->outgoing_payload .= $data;
2504 function sendRequest($data, $cookies = NULL) {
2505 // build cookie string
2506 $cookie_str = $this->getCookiesForRequest($cookies, (($this->scheme == 'ssl') || ($this->scheme == 'https')));
2508 // build payload
2509 $this->buildPayload($data, $cookie_str);
2511 if ($this->scheme == 'http' || $this->scheme == 'ssl') {
2512 // send payload
2513 if(!fputs($this->fp, $this->outgoing_payload, strlen($this->outgoing_payload))) {
2514 $this->setError('couldn\'t write message data to socket');
2515 $this->debug('couldn\'t write message data to socket');
2516 return false;
2518 $this->debug('wrote data to socket, length = ' . strlen($this->outgoing_payload));
2519 return true;
2520 } else if ($this->scheme == 'https') {
2521 // set payload
2522 // TODO: cURL does say this should only be the verb, and in fact it
2523 // turns out that the URI and HTTP version are appended to this, which
2524 // some servers refuse to work with
2525 //curl_setopt($this->ch, CURLOPT_CUSTOMREQUEST, $this->outgoing_payload);
2526 foreach($this->outgoing_headers as $k => $v){
2527 $curl_headers[] = "$k: $v";
2529 if ($cookie_str != '') {
2530 $curl_headers[] = 'Cookie: ' . $cookie_str;
2532 curl_setopt($this->ch, CURLOPT_HTTPHEADER, $curl_headers);
2533 if ($this->request_method == "POST") {
2534 curl_setopt($this->ch, CURLOPT_POST, 1);
2535 curl_setopt($this->ch, CURLOPT_POSTFIELDS, $data);
2536 } else {
2538 $this->debug('set cURL payload');
2539 return true;
2543 function getResponse(){
2544 $this->incoming_payload = '';
2546 if ($this->scheme == 'http' || $this->scheme == 'ssl') {
2547 // loop until headers have been retrieved
2548 $data = '';
2549 while (!isset($lb)){
2551 // We might EOF during header read.
2552 if(feof($this->fp)) {
2553 $this->incoming_payload = $data;
2554 $this->debug('found no headers before EOF after length ' . strlen($data));
2555 $this->debug("received before EOF:\n" . $data);
2556 $this->setError('server failed to send headers');
2557 return false;
2560 $tmp = fgets($this->fp, 256);
2561 $tmplen = strlen($tmp);
2562 $this->debug("read line of $tmplen bytes: " . trim($tmp));
2564 if ($tmplen == 0) {
2565 $this->incoming_payload = $data;
2566 $this->debug('socket read of headers timed out after length ' . strlen($data));
2567 $this->debug("read before timeout: " . $data);
2568 $this->setError('socket read of headers timed out');
2569 return false;
2572 $data .= $tmp;
2573 $pos = strpos($data,"\r\n\r\n");
2574 if($pos > 1){
2575 $lb = "\r\n";
2576 } else {
2577 $pos = strpos($data,"\n\n");
2578 if($pos > 1){
2579 $lb = "\n";
2582 // remove 100 header
2583 if(isset($lb) && ereg('^HTTP/1.1 100',$data)){
2584 unset($lb);
2585 $data = '';
2588 // store header data
2589 $this->incoming_payload .= $data;
2590 $this->debug('found end of headers after length ' . strlen($data));
2591 // process headers
2592 $header_data = trim(substr($data,0,$pos));
2593 $header_array = explode($lb,$header_data);
2594 $this->incoming_headers = array();
2595 $this->incoming_cookies = array();
2596 foreach($header_array as $header_line){
2597 $arr = explode(':',$header_line, 2);
2598 if(count($arr) > 1){
2599 $header_name = strtolower(trim($arr[0]));
2600 $this->incoming_headers[$header_name] = trim($arr[1]);
2601 if ($header_name == 'set-cookie') {
2602 // TODO: allow multiple cookies from parseCookie
2603 $cookie = $this->parseCookie(trim($arr[1]));
2604 if ($cookie) {
2605 $this->incoming_cookies[] = $cookie;
2606 $this->debug('found cookie: ' . $cookie['name'] . ' = ' . $cookie['value']);
2607 } else {
2608 $this->debug('did not find cookie in ' . trim($arr[1]));
2611 } else if (isset($header_name)) {
2612 // append continuation line to previous header
2613 $this->incoming_headers[$header_name] .= $lb . ' ' . $header_line;
2617 // loop until msg has been received
2618 if (isset($this->incoming_headers['transfer-encoding']) && strtolower($this->incoming_headers['transfer-encoding']) == 'chunked') {
2619 $content_length = 2147483647; // ignore any content-length header
2620 $chunked = true;
2621 $this->debug("want to read chunked content");
2622 } elseif (isset($this->incoming_headers['content-length'])) {
2623 $content_length = $this->incoming_headers['content-length'];
2624 $chunked = false;
2625 $this->debug("want to read content of length $content_length");
2626 } else {
2627 $content_length = 2147483647;
2628 $chunked = false;
2629 $this->debug("want to read content to EOF");
2631 $data = '';
2632 do {
2633 if ($chunked) {
2634 $tmp = fgets($this->fp, 256);
2635 $tmplen = strlen($tmp);
2636 $this->debug("read chunk line of $tmplen bytes");
2637 if ($tmplen == 0) {
2638 $this->incoming_payload = $data;
2639 $this->debug('socket read of chunk length timed out after length ' . strlen($data));
2640 $this->debug("read before timeout:\n" . $data);
2641 $this->setError('socket read of chunk length timed out');
2642 return false;
2644 $content_length = hexdec(trim($tmp));
2645 $this->debug("chunk length $content_length");
2647 $strlen = 0;
2648 while (($strlen < $content_length) && (!feof($this->fp))) {
2649 $readlen = min(8192, $content_length - $strlen);
2650 $tmp = fread($this->fp, $readlen);
2651 $tmplen = strlen($tmp);
2652 $this->debug("read buffer of $tmplen bytes");
2653 if (($tmplen == 0) && (!feof($this->fp))) {
2654 $this->incoming_payload = $data;
2655 $this->debug('socket read of body timed out after length ' . strlen($data));
2656 $this->debug("read before timeout:\n" . $data);
2657 $this->setError('socket read of body timed out');
2658 return false;
2660 $strlen += $tmplen;
2661 $data .= $tmp;
2663 if ($chunked && ($content_length > 0)) {
2664 $tmp = fgets($this->fp, 256);
2665 $tmplen = strlen($tmp);
2666 $this->debug("read chunk terminator of $tmplen bytes");
2667 if ($tmplen == 0) {
2668 $this->incoming_payload = $data;
2669 $this->debug('socket read of chunk terminator timed out after length ' . strlen($data));
2670 $this->debug("read before timeout:\n" . $data);
2671 $this->setError('socket read of chunk terminator timed out');
2672 return false;
2675 } while ($chunked && ($content_length > 0) && (!feof($this->fp)));
2676 if (feof($this->fp)) {
2677 $this->debug('read to EOF');
2679 $this->debug('read body of length ' . strlen($data));
2680 $this->incoming_payload .= $data;
2681 $this->debug('received a total of '.strlen($this->incoming_payload).' bytes of data from server');
2683 // close filepointer
2685 (isset($this->incoming_headers['connection']) && strtolower($this->incoming_headers['connection']) == 'close') ||
2686 (! $this->persistentConnection) || feof($this->fp)){
2687 fclose($this->fp);
2688 $this->fp = false;
2689 $this->debug('closed socket');
2692 // connection was closed unexpectedly
2693 if($this->incoming_payload == ''){
2694 $this->setError('no response from server');
2695 return false;
2698 // decode transfer-encoding
2699 // if(isset($this->incoming_headers['transfer-encoding']) && strtolower($this->incoming_headers['transfer-encoding']) == 'chunked'){
2700 // if(!$data = $this->decodeChunked($data, $lb)){
2701 // $this->setError('Decoding of chunked data failed');
2702 // return false;
2703 // }
2704 //print "<pre>\nde-chunked:\n---------------\n$data\n\n---------------\n</pre>";
2705 // set decoded payload
2706 // $this->incoming_payload = $header_data.$lb.$lb.$data;
2707 // }
2709 } else if ($this->scheme == 'https') {
2710 // send and receive
2711 $this->debug('send and receive with cURL');
2712 $this->incoming_payload = curl_exec($this->ch);
2713 $data = $this->incoming_payload;
2715 $cErr = curl_error($this->ch);
2716 if ($cErr != '') {
2717 $err = 'cURL ERROR: '.curl_errno($this->ch).': '.$cErr.'<br>';
2718 // TODO: there is a PHP bug that can cause this to SEGV for CURLINFO_CONTENT_TYPE
2719 foreach(curl_getinfo($this->ch) as $k => $v){
2720 $err .= "$k: $v<br>";
2722 $this->debug($err);
2723 $this->setError($err);
2724 curl_close($this->ch);
2725 return false;
2726 } else {
2727 //echo '<pre>';
2728 //var_dump(curl_getinfo($this->ch));
2729 //echo '</pre>';
2731 // close curl
2732 $this->debug('No cURL error, closing cURL');
2733 curl_close($this->ch);
2735 // remove 100 header(s)
2736 while (ereg('^HTTP/1.1 100',$data)) {
2737 if ($pos = strpos($data,"\r\n\r\n")) {
2738 $data = ltrim(substr($data,$pos));
2739 } elseif($pos = strpos($data,"\n\n") ) {
2740 $data = ltrim(substr($data,$pos));
2744 // separate content from HTTP headers
2745 if ($pos = strpos($data,"\r\n\r\n")) {
2746 $lb = "\r\n";
2747 } elseif( $pos = strpos($data,"\n\n")) {
2748 $lb = "\n";
2749 } else {
2750 $this->debug('no proper separation of headers and document');
2751 $this->setError('no proper separation of headers and document');
2752 return false;
2754 $header_data = trim(substr($data,0,$pos));
2755 $header_array = explode($lb,$header_data);
2756 $data = ltrim(substr($data,$pos));
2757 $this->debug('found proper separation of headers and document');
2758 $this->debug('cleaned data, stringlen: '.strlen($data));
2759 // clean headers
2760 foreach ($header_array as $header_line) {
2761 $arr = explode(':',$header_line,2);
2762 if(count($arr) > 1){
2763 $header_name = strtolower(trim($arr[0]));
2764 $this->incoming_headers[$header_name] = trim($arr[1]);
2765 if ($header_name == 'set-cookie') {
2766 // TODO: allow multiple cookies from parseCookie
2767 $cookie = $this->parseCookie(trim($arr[1]));
2768 if ($cookie) {
2769 $this->incoming_cookies[] = $cookie;
2770 $this->debug('found cookie: ' . $cookie['name'] . ' = ' . $cookie['value']);
2771 } else {
2772 $this->debug('did not find cookie in ' . trim($arr[1]));
2775 } else if (isset($header_name)) {
2776 // append continuation line to previous header
2777 $this->incoming_headers[$header_name] .= $lb . ' ' . $header_line;
2782 $arr = explode(' ', $header_array[0], 3);
2783 $http_version = $arr[0];
2784 $http_status = intval($arr[1]);
2785 $http_reason = count($arr) > 2 ? $arr[2] : '';
2787 // see if we need to resend the request with http digest authentication
2788 if (isset($this->incoming_headers['location']) && $http_status == 301) {
2789 $this->debug("Got 301 $http_reason with Location: " . $this->incoming_headers['location']);
2790 $this->setURL($this->incoming_headers['location']);
2791 $this->tryagain = true;
2792 return false;
2795 // see if we need to resend the request with http digest authentication
2796 if (isset($this->incoming_headers['www-authenticate']) && $http_status == 401) {
2797 $this->debug("Got 401 $http_reason with WWW-Authenticate: " . $this->incoming_headers['www-authenticate']);
2798 if (strstr($this->incoming_headers['www-authenticate'], "Digest ")) {
2799 $this->debug('Server wants digest authentication');
2800 // remove "Digest " from our elements
2801 $digestString = str_replace('Digest ', '', $this->incoming_headers['www-authenticate']);
2803 // parse elements into array
2804 $digestElements = explode(',', $digestString);
2805 foreach ($digestElements as $val) {
2806 $tempElement = explode('=', trim($val), 2);
2807 $digestRequest[$tempElement[0]] = str_replace("\"", '', $tempElement[1]);
2810 // should have (at least) qop, realm, nonce
2811 if (isset($digestRequest['nonce'])) {
2812 $this->setCredentials($this->username, $this->password, 'digest', $digestRequest);
2813 $this->tryagain = true;
2814 return false;
2817 $this->debug('HTTP authentication failed');
2818 $this->setError('HTTP authentication failed');
2819 return false;
2822 if (
2823 ($http_status >= 300 && $http_status <= 307) ||
2824 ($http_status >= 400 && $http_status <= 417) ||
2825 ($http_status >= 501 && $http_status <= 505)
2827 $this->setError("Unsupported HTTP response status $http_status $http_reason (soap_client->response has contents of the response)");
2828 return false;
2831 // decode content-encoding
2832 if(isset($this->incoming_headers['content-encoding']) && $this->incoming_headers['content-encoding'] != ''){
2833 if(strtolower($this->incoming_headers['content-encoding']) == 'deflate' || strtolower($this->incoming_headers['content-encoding']) == 'gzip'){
2834 // if decoding works, use it. else assume data wasn't gzencoded
2835 if(function_exists('gzinflate')){
2836 //$timer->setMarker('starting decoding of gzip/deflated content');
2837 // IIS 5 requires gzinflate instead of gzuncompress (similar to IE 5 and gzdeflate v. gzcompress)
2838 // this means there are no Zlib headers, although there should be
2839 $this->debug('The gzinflate function exists');
2840 $datalen = strlen($data);
2841 if ($this->incoming_headers['content-encoding'] == 'deflate') {
2842 if ($degzdata = @gzinflate($data)) {
2843 $data = $degzdata;
2844 $this->debug('The payload has been inflated to ' . strlen($data) . ' bytes');
2845 if (strlen($data) < $datalen) {
2846 // test for the case that the payload has been compressed twice
2847 $this->debug('The inflated payload is smaller than the gzipped one; try again');
2848 if ($degzdata = @gzinflate($data)) {
2849 $data = $degzdata;
2850 $this->debug('The payload has been inflated again to ' . strlen($data) . ' bytes');
2853 } else {
2854 $this->debug('Error using gzinflate to inflate the payload');
2855 $this->setError('Error using gzinflate to inflate the payload');
2857 } elseif ($this->incoming_headers['content-encoding'] == 'gzip') {
2858 if ($degzdata = @gzinflate(substr($data, 10))) { // do our best
2859 $data = $degzdata;
2860 $this->debug('The payload has been un-gzipped to ' . strlen($data) . ' bytes');
2861 if (strlen($data) < $datalen) {
2862 // test for the case that the payload has been compressed twice
2863 $this->debug('The un-gzipped payload is smaller than the gzipped one; try again');
2864 if ($degzdata = @gzinflate(substr($data, 10))) {
2865 $data = $degzdata;
2866 $this->debug('The payload has been un-gzipped again to ' . strlen($data) . ' bytes');
2869 } else {
2870 $this->debug('Error using gzinflate to un-gzip the payload');
2871 $this->setError('Error using gzinflate to un-gzip the payload');
2874 //$timer->setMarker('finished decoding of gzip/deflated content');
2875 //print "<xmp>\nde-inflated:\n---------------\n$data\n-------------\n</xmp>";
2876 // set decoded payload
2877 $this->incoming_payload = $header_data.$lb.$lb.$data;
2878 } else {
2879 $this->debug('The server sent compressed data. Your php install must have the Zlib extension compiled in to support this.');
2880 $this->setError('The server sent compressed data. Your php install must have the Zlib extension compiled in to support this.');
2882 } else {
2883 $this->debug('Unsupported Content-Encoding ' . $this->incoming_headers['content-encoding']);
2884 $this->setError('Unsupported Content-Encoding ' . $this->incoming_headers['content-encoding']);
2886 } else {
2887 $this->debug('No Content-Encoding header');
2890 if(strlen($data) == 0){
2891 $this->debug('no data after headers!');
2892 $this->setError('no data present after HTTP headers');
2893 return false;
2896 return $data;
2899 function setContentType($type, $charset = false) {
2900 $this->outgoing_headers['Content-Type'] = $type . ($charset ? '; charset=' . $charset : '');
2901 $this->debug('set Content-Type: ' . $this->outgoing_headers['Content-Type']);
2904 function usePersistentConnection(){
2905 if (isset($this->outgoing_headers['Accept-Encoding'])) {
2906 return false;
2908 $this->protocol_version = '1.1';
2909 $this->persistentConnection = true;
2910 $this->outgoing_headers['Connection'] = 'Keep-Alive';
2911 $this->debug('set Connection: ' . $this->outgoing_headers['Connection']);
2912 return true;
2916 * parse an incoming Cookie into it's parts
2918 * @param string $cookie_str content of cookie
2919 * @return array with data of that cookie
2920 * @access private
2923 * TODO: allow a Set-Cookie string to be parsed into multiple cookies
2925 function parseCookie($cookie_str) {
2926 $cookie_str = str_replace('; ', ';', $cookie_str) . ';';
2927 $data = split(';', $cookie_str);
2928 $value_str = $data[0];
2930 $cookie_param = 'domain=';
2931 $start = strpos($cookie_str, $cookie_param);
2932 if ($start > 0) {
2933 $domain = substr($cookie_str, $start + strlen($cookie_param));
2934 $domain = substr($domain, 0, strpos($domain, ';'));
2935 } else {
2936 $domain = '';
2939 $cookie_param = 'expires=';
2940 $start = strpos($cookie_str, $cookie_param);
2941 if ($start > 0) {
2942 $expires = substr($cookie_str, $start + strlen($cookie_param));
2943 $expires = substr($expires, 0, strpos($expires, ';'));
2944 } else {
2945 $expires = '';
2948 $cookie_param = 'path=';
2949 $start = strpos($cookie_str, $cookie_param);
2950 if ( $start > 0 ) {
2951 $path = substr($cookie_str, $start + strlen($cookie_param));
2952 $path = substr($path, 0, strpos($path, ';'));
2953 } else {
2954 $path = '/';
2957 $cookie_param = ';secure;';
2958 if (strpos($cookie_str, $cookie_param) !== FALSE) {
2959 $secure = true;
2960 } else {
2961 $secure = false;
2964 $sep_pos = strpos($value_str, '=');
2966 if ($sep_pos) {
2967 $name = substr($value_str, 0, $sep_pos);
2968 $value = substr($value_str, $sep_pos + 1);
2969 $cookie= array( 'name' => $name,
2970 'value' => $value,
2971 'domain' => $domain,
2972 'path' => $path,
2973 'expires' => $expires,
2974 'secure' => $secure
2976 return $cookie;
2978 return false;
2982 * sort out cookies for the current request
2984 * @param array $cookies array with all cookies
2985 * @param boolean $secure is the send-content secure or not?
2986 * @return string for Cookie-HTTP-Header
2987 * @access private
2989 function getCookiesForRequest($cookies, $secure=false) {
2990 $cookie_str = '';
2991 if ((! is_null($cookies)) && (is_array($cookies))) {
2992 foreach ($cookies as $cookie) {
2993 if (! is_array($cookie)) {
2994 continue;
2996 $this->debug("check cookie for validity: ".$cookie['name'].'='.$cookie['value']);
2997 if ((isset($cookie['expires'])) && (! empty($cookie['expires']))) {
2998 if (strtotime($cookie['expires']) <= time()) {
2999 $this->debug('cookie has expired');
3000 continue;
3003 if ((isset($cookie['domain'])) && (! empty($cookie['domain']))) {
3004 $domain = preg_quote($cookie['domain']);
3005 if (! preg_match("'.*$domain$'i", $this->host)) {
3006 $this->debug('cookie has different domain');
3007 continue;
3010 if ((isset($cookie['path'])) && (! empty($cookie['path']))) {
3011 $path = preg_quote($cookie['path']);
3012 if (! preg_match("'^$path.*'i", $this->path)) {
3013 $this->debug('cookie is for a different path');
3014 continue;
3017 if ((! $secure) && (isset($cookie['secure'])) && ($cookie['secure'])) {
3018 $this->debug('cookie is secure, transport is not');
3019 continue;
3021 $cookie_str .= $cookie['name'] . '=' . $cookie['value'] . '; ';
3022 $this->debug('add cookie to Cookie-String: ' . $cookie['name'] . '=' . $cookie['value']);
3025 return $cookie_str;
3029 ?><?php
3035 * soap_server allows the user to create a SOAP server
3036 * that is capable of receiving messages and returning responses
3038 * NOTE: WSDL functionality is experimental
3040 * @author Dietrich Ayala <dietrich@ganx4.com>
3041 * @version $Id$
3042 * @access public
3044 class soap_server extends nusoap_base {
3046 * HTTP headers of request
3047 * @var array
3048 * @access private
3050 var $headers = array();
3052 * HTTP request
3053 * @var string
3054 * @access private
3056 var $request = '';
3058 * SOAP headers from request (incomplete namespace resolution; special characters not escaped) (text)
3059 * @var string
3060 * @access public
3062 var $requestHeaders = '';
3064 * SOAP body request portion (incomplete namespace resolution; special characters not escaped) (text)
3065 * @var string
3066 * @access public
3068 var $document = '';
3070 * SOAP payload for request (text)
3071 * @var string
3072 * @access public
3074 var $requestSOAP = '';
3076 * requested method namespace URI
3077 * @var string
3078 * @access private
3080 var $methodURI = '';
3082 * name of method requested
3083 * @var string
3084 * @access private
3086 var $methodname = '';
3088 * method parameters from request
3089 * @var array
3090 * @access private
3092 var $methodparams = array();
3094 * SOAP Action from request
3095 * @var string
3096 * @access private
3098 var $SOAPAction = '';
3100 * character set encoding of incoming (request) messages
3101 * @var string
3102 * @access public
3104 var $xml_encoding = '';
3106 * toggles whether the parser decodes element content w/ utf8_decode()
3107 * @var boolean
3108 * @access public
3110 var $decode_utf8 = true;
3113 * HTTP headers of response
3114 * @var array
3115 * @access public
3117 var $outgoing_headers = array();
3119 * HTTP response
3120 * @var string
3121 * @access private
3123 var $response = '';
3125 * SOAP headers for response (text)
3126 * @var string
3127 * @access public
3129 var $responseHeaders = '';
3131 * SOAP payload for response (text)
3132 * @var string
3133 * @access private
3135 var $responseSOAP = '';
3137 * method return value to place in response
3138 * @var mixed
3139 * @access private
3141 var $methodreturn = false;
3143 * whether $methodreturn is a string of literal XML
3144 * @var boolean
3145 * @access public
3147 var $methodreturnisliteralxml = false;
3149 * SOAP fault for response (or false)
3150 * @var mixed
3151 * @access private
3153 var $fault = false;
3155 * text indication of result (for debugging)
3156 * @var string
3157 * @access private
3159 var $result = 'successful';
3162 * assoc array of operations => opData; operations are added by the register()
3163 * method or by parsing an external WSDL definition
3164 * @var array
3165 * @access private
3167 var $operations = array();
3169 * wsdl instance (if one)
3170 * @var mixed
3171 * @access private
3173 var $wsdl = false;
3175 * URL for WSDL (if one)
3176 * @var mixed
3177 * @access private
3179 var $externalWSDLURL = false;
3181 * whether to append debug to response as XML comment
3182 * @var boolean
3183 * @access public
3185 var $debug_flag = false;
3189 * constructor
3190 * the optional parameter is a path to a WSDL file that you'd like to bind the server instance to.
3192 * @param mixed $wsdl file path or URL (string), or wsdl instance (object)
3193 * @access public
3195 function soap_server($wsdl=false){
3196 parent::nusoap_base();
3197 // turn on debugging?
3198 global $debug;
3199 global $HTTP_SERVER_VARS;
3201 if (isset($_SERVER)) {
3202 $this->debug("_SERVER is defined:");
3203 $this->appendDebug($this->varDump($_SERVER));
3204 } elseif (isset($HTTP_SERVER_VARS)) {
3205 $this->debug("HTTP_SERVER_VARS is defined:");
3206 $this->appendDebug($this->varDump($HTTP_SERVER_VARS));
3207 } else {
3208 $this->debug("Neither _SERVER nor HTTP_SERVER_VARS is defined.");
3211 if (isset($debug)) {
3212 $this->debug("In soap_server, set debug_flag=$debug based on global flag");
3213 $this->debug_flag = $debug;
3214 } elseif (isset($_SERVER['QUERY_STRING'])) {
3215 $qs = explode('&', $_SERVER['QUERY_STRING']);
3216 foreach ($qs as $v) {
3217 if (substr($v, 0, 6) == 'debug=') {
3218 $this->debug("In soap_server, set debug_flag=" . substr($v, 6) . " based on query string #1");
3219 $this->debug_flag = substr($v, 6);
3222 } elseif (isset($HTTP_SERVER_VARS['QUERY_STRING'])) {
3223 $qs = explode('&', $HTTP_SERVER_VARS['QUERY_STRING']);
3224 foreach ($qs as $v) {
3225 if (substr($v, 0, 6) == 'debug=') {
3226 $this->debug("In soap_server, set debug_flag=" . substr($v, 6) . " based on query string #2");
3227 $this->debug_flag = substr($v, 6);
3232 // wsdl
3233 if($wsdl){
3234 $this->debug("In soap_server, WSDL is specified");
3235 if (is_object($wsdl) && (get_class($wsdl) == 'wsdl')) {
3236 $this->wsdl = $wsdl;
3237 $this->externalWSDLURL = $this->wsdl->wsdl;
3238 $this->debug('Use existing wsdl instance from ' . $this->externalWSDLURL);
3239 } else {
3240 $this->debug('Create wsdl from ' . $wsdl);
3241 $this->wsdl = new wsdl($wsdl);
3242 $this->externalWSDLURL = $wsdl;
3244 $this->appendDebug($this->wsdl->getDebug());
3245 $this->wsdl->clearDebug();
3246 if($err = $this->wsdl->getError()){
3247 die('WSDL ERROR: '.$err);
3253 * processes request and returns response
3255 * @param string $data usually is the value of $HTTP_RAW_POST_DATA
3256 * @access public
3258 function service($data){
3259 global $HTTP_SERVER_VARS;
3261 if (isset($_SERVER['QUERY_STRING'])) {
3262 $qs = $_SERVER['QUERY_STRING'];
3263 } elseif (isset($HTTP_SERVER_VARS['QUERY_STRING'])) {
3264 $qs = $HTTP_SERVER_VARS['QUERY_STRING'];
3265 } else {
3266 $qs = '';
3268 $this->debug("In service, query string=$qs");
3270 if (ereg('wsdl', $qs) ){
3271 $this->debug("In service, this is a request for WSDL");
3272 if($this->externalWSDLURL){
3273 if (strpos($this->externalWSDLURL,"://")!==false) { // assume URL
3274 header('Location: '.$this->externalWSDLURL);
3275 } else { // assume file
3276 header("Content-Type: text/xml\r\n");
3277 $fp = fopen($this->externalWSDLURL, 'r');
3278 fpassthru($fp);
3280 } elseif ($this->wsdl) {
3281 header("Content-Type: text/xml; charset=ISO-8859-1\r\n");
3282 print $this->wsdl->serialize($this->debug_flag);
3283 if ($this->debug_flag) {
3284 $this->debug('wsdl:');
3285 $this->appendDebug($this->varDump($this->wsdl));
3286 print $this->getDebugAsXMLComment();
3288 } else {
3289 header("Content-Type: text/html; charset=ISO-8859-1\r\n");
3290 print "This service does not provide WSDL";
3292 } elseif ($data == '' && $this->wsdl) {
3293 $this->debug("In service, there is no data, so return Web description");
3294 print $this->wsdl->webDescription();
3295 } else {
3296 $this->debug("In service, invoke the request");
3297 $this->parse_request($data);
3298 if (! $this->fault) {
3299 $this->invoke_method();
3301 if (! $this->fault) {
3302 $this->serialize_return();
3304 $this->send_response();
3309 * parses HTTP request headers.
3311 * The following fields are set by this function (when successful)
3313 * headers
3314 * request
3315 * xml_encoding
3316 * SOAPAction
3318 * @access private
3320 function parse_http_headers() {
3321 global $HTTP_SERVER_VARS;
3323 $this->request = '';
3324 $this->SOAPAction = '';
3325 if(function_exists('getallheaders')){
3326 $this->debug("In parse_http_headers, use getallheaders");
3327 $headers = getallheaders();
3328 foreach($headers as $k=>$v){
3329 $k = strtolower($k);
3330 $this->headers[$k] = $v;
3331 $this->request .= "$k: $v\r\n";
3332 $this->debug("$k: $v");
3334 // get SOAPAction header
3335 if(isset($this->headers['soapaction'])){
3336 $this->SOAPAction = str_replace('"','',$this->headers['soapaction']);
3338 // get the character encoding of the incoming request
3339 if(isset($this->headers['content-type']) && strpos($this->headers['content-type'],'=')){
3340 $enc = str_replace('"','',substr(strstr($this->headers["content-type"],'='),1));
3341 if(eregi('^(ISO-8859-1|US-ASCII|UTF-8)$',$enc)){
3342 $this->xml_encoding = strtoupper($enc);
3343 } else {
3344 $this->xml_encoding = 'US-ASCII';
3346 } else {
3347 // should be US-ASCII for HTTP 1.0 or ISO-8859-1 for HTTP 1.1
3348 $this->xml_encoding = 'ISO-8859-1';
3350 } elseif(isset($_SERVER) && is_array($_SERVER)){
3351 $this->debug("In parse_http_headers, use _SERVER");
3352 foreach ($_SERVER as $k => $v) {
3353 if (substr($k, 0, 5) == 'HTTP_') {
3354 $k = str_replace(' ', '-', strtolower(str_replace('_', ' ', substr($k, 5)))); $k = strtolower(substr($k, 5));
3355 } else {
3356 $k = str_replace(' ', '-', strtolower(str_replace('_', ' ', $k))); $k = strtolower($k);
3358 if ($k == 'soapaction') {
3359 // get SOAPAction header
3360 $k = 'SOAPAction';
3361 $v = str_replace('"', '', $v);
3362 $v = str_replace('\\', '', $v);
3363 $this->SOAPAction = $v;
3364 } else if ($k == 'content-type') {
3365 // get the character encoding of the incoming request
3366 if (strpos($v, '=')) {
3367 $enc = substr(strstr($v, '='), 1);
3368 $enc = str_replace('"', '', $enc);
3369 $enc = str_replace('\\', '', $enc);
3370 if (eregi('^(ISO-8859-1|US-ASCII|UTF-8)$', $enc)) {
3371 $this->xml_encoding = strtoupper($enc);
3372 } else {
3373 $this->xml_encoding = 'US-ASCII';
3375 } else {
3376 // should be US-ASCII for HTTP 1.0 or ISO-8859-1 for HTTP 1.1
3377 $this->xml_encoding = 'ISO-8859-1';
3380 $this->headers[$k] = $v;
3381 $this->request .= "$k: $v\r\n";
3382 $this->debug("$k: $v");
3384 } elseif (is_array($HTTP_SERVER_VARS)) {
3385 $this->debug("In parse_http_headers, use HTTP_SERVER_VARS");
3386 foreach ($HTTP_SERVER_VARS as $k => $v) {
3387 if (substr($k, 0, 5) == 'HTTP_') {
3388 $k = str_replace(' ', '-', strtolower(str_replace('_', ' ', substr($k, 5)))); $k = strtolower(substr($k, 5));
3389 } else {
3390 $k = str_replace(' ', '-', strtolower(str_replace('_', ' ', $k))); $k = strtolower($k);
3392 if ($k == 'soapaction') {
3393 // get SOAPAction header
3394 $k = 'SOAPAction';
3395 $v = str_replace('"', '', $v);
3396 $v = str_replace('\\', '', $v);
3397 $this->SOAPAction = $v;
3398 } else if ($k == 'content-type') {
3399 // get the character encoding of the incoming request
3400 if (strpos($v, '=')) {
3401 $enc = substr(strstr($v, '='), 1);
3402 $enc = str_replace('"', '', $enc);
3403 $enc = str_replace('\\', '', $enc);
3404 if (eregi('^(ISO-8859-1|US-ASCII|UTF-8)$', $enc)) {
3405 $this->xml_encoding = strtoupper($enc);
3406 } else {
3407 $this->xml_encoding = 'US-ASCII';
3409 } else {
3410 // should be US-ASCII for HTTP 1.0 or ISO-8859-1 for HTTP 1.1
3411 $this->xml_encoding = 'ISO-8859-1';
3414 $this->headers[$k] = $v;
3415 $this->request .= "$k: $v\r\n";
3416 $this->debug("$k: $v");
3418 } else {
3419 $this->debug("In parse_http_headers, HTTP headers not accessible");
3420 $this->setError("HTTP headers not accessible");
3425 * parses a request
3427 * The following fields are set by this function (when successful)
3429 * headers
3430 * request
3431 * xml_encoding
3432 * SOAPAction
3433 * request
3434 * requestSOAP
3435 * methodURI
3436 * methodname
3437 * methodparams
3438 * requestHeaders
3439 * document
3441 * This sets the fault field on error
3443 * @param string $data XML string
3444 * @access private
3446 function parse_request($data='') {
3447 $this->debug('entering parse_request()');
3448 $this->parse_http_headers();
3449 $this->debug('got character encoding: '.$this->xml_encoding);
3450 // uncompress if necessary
3451 if (isset($this->headers['content-encoding']) && $this->headers['content-encoding'] != '') {
3452 $this->debug('got content encoding: ' . $this->headers['content-encoding']);
3453 if ($this->headers['content-encoding'] == 'deflate' || $this->headers['content-encoding'] == 'gzip') {
3454 // if decoding works, use it. else assume data wasn't gzencoded
3455 if (function_exists('gzuncompress')) {
3456 if ($this->headers['content-encoding'] == 'deflate' && $degzdata = @gzuncompress($data)) {
3457 $data = $degzdata;
3458 } elseif ($this->headers['content-encoding'] == 'gzip' && $degzdata = gzinflate(substr($data, 10))) {
3459 $data = $degzdata;
3460 } else {
3461 $this->fault('SOAP-ENV:Client', 'Errors occurred when trying to decode the data');
3462 return;
3464 } else {
3465 $this->fault('SOAP-ENV:Client', 'This Server does not support compressed data');
3466 return;
3470 $this->request .= "\r\n".$data;
3471 $data = $this->parseRequest($this->headers, $data);
3472 $this->requestSOAP = $data;
3473 $this->debug('leaving parse_request');
3477 * invokes a PHP function for the requested SOAP method
3479 * The following fields are set by this function (when successful)
3481 * methodreturn
3483 * Note that the PHP function that is called may also set the following
3484 * fields to affect the response sent to the client
3486 * responseHeaders
3487 * outgoing_headers
3489 * This sets the fault field on error
3491 * @access private
3493 function invoke_method() {
3494 $this->debug('in invoke_method, methodname=' . $this->methodname . ' methodURI=' . $this->methodURI . ' SOAPAction=' . $this->SOAPAction);
3496 if ($this->wsdl) {
3497 if ($this->opData = $this->wsdl->getOperationData($this->methodname)) {
3498 $this->debug('in invoke_method, found WSDL operation=' . $this->methodname);
3499 $this->appendDebug('opData=' . $this->varDump($this->opData));
3500 } elseif ($this->opData = $this->wsdl->getOperationDataForSoapAction($this->SOAPAction)) {
3501 // Note: hopefully this case will only be used for doc/lit, since rpc services should have wrapper element
3502 $this->debug('in invoke_method, found WSDL soapAction=' . $this->SOAPAction . ' for operation=' . $this->opData['name']);
3503 $this->appendDebug('opData=' . $this->varDump($this->opData));
3504 $this->methodname = $this->opData['name'];
3505 } else {
3506 $this->debug('in invoke_method, no WSDL for operation=' . $this->methodname);
3507 $this->fault('SOAP-ENV:Client', "Operation '" . $this->methodname . "' is not defined in the WSDL for this service");
3508 return;
3510 } else {
3511 $this->debug('in invoke_method, no WSDL to validate method');
3514 // if a . is present in $this->methodname, we see if there is a class in scope,
3515 // which could be referred to. We will also distinguish between two deliminators,
3516 // to allow methods to be called a the class or an instance
3517 $class = '';
3518 $method = '';
3519 if (strpos($this->methodname, '..') > 0) {
3520 $delim = '..';
3521 } else if (strpos($this->methodname, '.') > 0) {
3522 $delim = '.';
3523 } else {
3524 $delim = '';
3527 if (strlen($delim) > 0 && substr_count($this->methodname, $delim) == 1 &&
3528 class_exists(substr($this->methodname, 0, strpos($this->methodname, $delim)))) {
3529 // get the class and method name
3530 $class = substr($this->methodname, 0, strpos($this->methodname, $delim));
3531 $method = substr($this->methodname, strpos($this->methodname, $delim) + strlen($delim));
3532 $this->debug("in invoke_method, class=$class method=$method delim=$delim");
3535 // does method exist?
3536 if ($class == '') {
3537 if (!function_exists($this->methodname)) {
3538 $this->debug("in invoke_method, function '$this->methodname' not found!");
3539 $this->result = 'fault: method not found';
3540 $this->fault('SOAP-ENV:Client',"method '$this->methodname' not defined in service");
3541 return;
3543 } else {
3544 $method_to_compare = (substr(phpversion(), 0, 2) == '4.') ? strtolower($method) : $method;
3545 if (!in_array($method_to_compare, get_class_methods($class))) {
3546 $this->debug("in invoke_method, method '$this->methodname' not found in class '$class'!");
3547 $this->result = 'fault: method not found';
3548 $this->fault('SOAP-ENV:Client',"method '$this->methodname' not defined in service");
3549 return;
3553 // evaluate message, getting back parameters
3554 // verify that request parameters match the method's signature
3555 if(! $this->verify_method($this->methodname,$this->methodparams)){
3556 // debug
3557 $this->debug('ERROR: request not verified against method signature');
3558 $this->result = 'fault: request failed validation against method signature';
3559 // return fault
3560 $this->fault('SOAP-ENV:Client',"Operation '$this->methodname' not defined in service.");
3561 return;
3564 // if there are parameters to pass
3565 $this->debug('in invoke_method, params:');
3566 $this->appendDebug($this->varDump($this->methodparams));
3567 $this->debug("in invoke_method, calling '$this->methodname'");
3568 if (!function_exists('call_user_func_array')) {
3569 if ($class == '') {
3570 $this->debug('in invoke_method, calling function using eval()');
3571 $funcCall = "\$this->methodreturn = $this->methodname(";
3572 } else {
3573 if ($delim == '..') {
3574 $this->debug('in invoke_method, calling class method using eval()');
3575 $funcCall = "\$this->methodreturn = ".$class."::".$method."(";
3576 } else {
3577 $this->debug('in invoke_method, calling instance method using eval()');
3578 // generate unique instance name
3579 $instname = "\$inst_".time();
3580 $funcCall = $instname." = new ".$class."(); ";
3581 $funcCall .= "\$this->methodreturn = ".$instname."->".$method."(";
3584 if ($this->methodparams) {
3585 foreach ($this->methodparams as $param) {
3586 if (is_array($param)) {
3587 $this->fault('SOAP-ENV:Client', 'NuSOAP does not handle complexType parameters correctly when using eval; call_user_func_array must be available');
3588 return;
3590 $funcCall .= "\"$param\",";
3592 $funcCall = substr($funcCall, 0, -1);
3594 $funcCall .= ');';
3595 $this->debug('in invoke_method, function call: '.$funcCall);
3596 @eval($funcCall);
3597 } else {
3598 if ($class == '') {
3599 $this->debug('in invoke_method, calling function using call_user_func_array()');
3600 $call_arg = "$this->methodname"; // straight assignment changes $this->methodname to lower case after call_user_func_array()
3601 } elseif ($delim == '..') {
3602 $this->debug('in invoke_method, calling class method using call_user_func_array()');
3603 $call_arg = array ($class, $method);
3604 } else {
3605 $this->debug('in invoke_method, calling instance method using call_user_func_array()');
3606 $instance = new $class ();
3607 $call_arg = array(&$instance, $method);
3609 $this->methodreturn = call_user_func_array($call_arg, array_values($this->methodparams));
3611 $this->debug('in invoke_method, methodreturn:');
3612 $this->appendDebug($this->varDump($this->methodreturn));
3613 $this->debug("in invoke_method, called method $this->methodname, received $this->methodreturn of type ".gettype($this->methodreturn));
3617 * serializes the return value from a PHP function into a full SOAP Envelope
3619 * The following fields are set by this function (when successful)
3621 * responseSOAP
3623 * This sets the fault field on error
3625 * @access private
3627 function serialize_return() {
3628 $this->debug('Entering serialize_return methodname: ' . $this->methodname . ' methodURI: ' . $this->methodURI);
3629 // if fault
3630 if (isset($this->methodreturn) && (get_class($this->methodreturn) == 'soap_fault')) {
3631 $this->debug('got a fault object from method');
3632 $this->fault = $this->methodreturn;
3633 return;
3634 } elseif ($this->methodreturnisliteralxml) {
3635 $return_val = $this->methodreturn;
3636 // returned value(s)
3637 } else {
3638 $this->debug('got a(n) '.gettype($this->methodreturn).' from method');
3639 $this->debug('serializing return value');
3640 if($this->wsdl){
3641 // weak attempt at supporting multiple output params
3642 if(sizeof($this->opData['output']['parts']) > 1){
3643 $opParams = $this->methodreturn;
3644 } else {
3645 // TODO: is this really necessary?
3646 $opParams = array($this->methodreturn);
3648 $return_val = $this->wsdl->serializeRPCParameters($this->methodname,'output',$opParams);
3649 $this->appendDebug($this->wsdl->getDebug());
3650 $this->wsdl->clearDebug();
3651 if($errstr = $this->wsdl->getError()){
3652 $this->debug('got wsdl error: '.$errstr);
3653 $this->fault('SOAP-ENV:Server', 'unable to serialize result');
3654 return;
3656 } else {
3657 if (isset($this->methodreturn)) {
3658 $return_val = $this->serialize_val($this->methodreturn, 'return');
3659 } else {
3660 $return_val = '';
3661 $this->debug('in absence of WSDL, assume void return for backward compatibility');
3665 $this->debug('return value:');
3666 $this->appendDebug($this->varDump($return_val));
3668 $this->debug('serializing response');
3669 if ($this->wsdl) {
3670 $this->debug('have WSDL for serialization: style is ' . $this->opData['style']);
3671 if ($this->opData['style'] == 'rpc') {
3672 $this->debug('style is rpc for serialization: use is ' . $this->opData['output']['use']);
3673 if ($this->opData['output']['use'] == 'literal') {
3674 $payload = '<'.$this->methodname.'Response xmlns="'.$this->methodURI.'">'.$return_val.'</'.$this->methodname."Response>";
3675 } else {
3676 $payload = '<ns1:'.$this->methodname.'Response xmlns:ns1="'.$this->methodURI.'">'.$return_val.'</ns1:'.$this->methodname."Response>";
3678 } else {
3679 $this->debug('style is not rpc for serialization: assume document');
3680 $payload = $return_val;
3682 } else {
3683 $this->debug('do not have WSDL for serialization: assume rpc/encoded');
3684 $payload = '<ns1:'.$this->methodname.'Response xmlns:ns1="'.$this->methodURI.'">'.$return_val.'</ns1:'.$this->methodname."Response>";
3686 $this->result = 'successful';
3687 if($this->wsdl){
3688 //if($this->debug_flag){
3689 $this->appendDebug($this->wsdl->getDebug());
3690 // }
3691 if (isset($opData['output']['encodingStyle'])) {
3692 $encodingStyle = $opData['output']['encodingStyle'];
3693 } else {
3694 $encodingStyle = '';
3696 // Added: In case we use a WSDL, return a serialized env. WITH the usedNamespaces.
3697 $this->responseSOAP = $this->serializeEnvelope($payload,$this->responseHeaders,$this->wsdl->usedNamespaces,$this->opData['style'],$encodingStyle);
3698 } else {
3699 $this->responseSOAP = $this->serializeEnvelope($payload,$this->responseHeaders);
3701 $this->debug("Leaving serialize_return");
3705 * sends an HTTP response
3707 * The following fields are set by this function (when successful)
3709 * outgoing_headers
3710 * response
3712 * @access private
3714 function send_response() {
3715 $this->debug('Enter send_response');
3716 if ($this->fault) {
3717 $payload = $this->fault->serialize();
3718 $this->outgoing_headers[] = "HTTP/1.0 500 Internal Server Error";
3719 $this->outgoing_headers[] = "Status: 500 Internal Server Error";
3720 } else {
3721 $payload = $this->responseSOAP;
3722 // Some combinations of PHP+Web server allow the Status
3723 // to come through as a header. Since OK is the default
3724 // just do nothing.
3725 // $this->outgoing_headers[] = "HTTP/1.0 200 OK";
3726 // $this->outgoing_headers[] = "Status: 200 OK";
3728 // add debug data if in debug mode
3729 if(isset($this->debug_flag) && $this->debug_flag){
3730 $payload .= $this->getDebugAsXMLComment();
3732 $this->outgoing_headers[] = "Server: $this->title Server v$this->version";
3733 ereg('\$Revisio' . 'n: ([^ ]+)', $this->revision, $rev);
3734 $this->outgoing_headers[] = "X-SOAP-Server: $this->title/$this->version (".$rev[1].")";
3735 // Let the Web server decide about this
3736 //$this->outgoing_headers[] = "Connection: Close\r\n";
3737 $payload = $this->getHTTPBody($payload);
3738 $type = $this->getHTTPContentType();
3739 $charset = $this->getHTTPContentTypeCharset();
3740 $this->outgoing_headers[] = "Content-Type: $type" . ($charset ? '; charset=' . $charset : '');
3741 //begin code to compress payload - by John
3742 // NOTE: there is no way to know whether the Web server will also compress
3743 // this data.
3744 if (strlen($payload) > 1024 && isset($this->headers) && isset($this->headers['accept-encoding'])) {
3745 if (strstr($this->headers['accept-encoding'], 'gzip')) {
3746 if (function_exists('gzencode')) {
3747 if (isset($this->debug_flag) && $this->debug_flag) {
3748 $payload .= "<!-- Content being gzipped -->";
3750 $this->outgoing_headers[] = "Content-Encoding: gzip";
3751 $payload = gzencode($payload);
3752 } else {
3753 if (isset($this->debug_flag) && $this->debug_flag) {
3754 $payload .= "<!-- Content will not be gzipped: no gzencode -->";
3757 } elseif (strstr($this->headers['accept-encoding'], 'deflate')) {
3758 // Note: MSIE requires gzdeflate output (no Zlib header and checksum),
3759 // instead of gzcompress output,
3760 // which conflicts with HTTP 1.1 spec (http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.5)
3761 if (function_exists('gzdeflate')) {
3762 if (isset($this->debug_flag) && $this->debug_flag) {
3763 $payload .= "<!-- Content being deflated -->";
3765 $this->outgoing_headers[] = "Content-Encoding: deflate";
3766 $payload = gzdeflate($payload);
3767 } else {
3768 if (isset($this->debug_flag) && $this->debug_flag) {
3769 $payload .= "<!-- Content will not be deflated: no gzcompress -->";
3774 //end code
3775 $this->outgoing_headers[] = "Content-Length: ".strlen($payload);
3776 reset($this->outgoing_headers);
3777 foreach($this->outgoing_headers as $hdr){
3778 header($hdr, false);
3780 print $payload;
3781 $this->response = join("\r\n",$this->outgoing_headers)."\r\n\r\n".$payload;
3785 * takes the value that was created by parsing the request
3786 * and compares to the method's signature, if available.
3788 * @param string $operation The operation to be invoked
3789 * @param array $request The array of parameter values
3790 * @return boolean Whether the operation was found
3791 * @access private
3793 function verify_method($operation,$request){
3794 if(isset($this->wsdl) && is_object($this->wsdl)){
3795 if($this->wsdl->getOperationData($operation)){
3796 return true;
3798 } elseif(isset($this->operations[$operation])){
3799 return true;
3801 return false;
3805 * processes SOAP message received from client
3807 * @param array $headers The HTTP headers
3808 * @param string $data unprocessed request data from client
3809 * @return mixed value of the message, decoded into a PHP type
3810 * @access private
3812 function parseRequest($headers, $data) {
3813 $this->debug('Entering parseRequest() for data of length ' . strlen($data) . ' and type ' . $headers['content-type']);
3814 if (!strstr($headers['content-type'], 'text/xml')) {
3815 $this->setError('Request not of type text/xml');
3816 return false;
3818 if (strpos($headers['content-type'], '=')) {
3819 $enc = str_replace('"', '', substr(strstr($headers["content-type"], '='), 1));
3820 $this->debug('Got response encoding: ' . $enc);
3821 if(eregi('^(ISO-8859-1|US-ASCII|UTF-8)$',$enc)){
3822 $this->xml_encoding = strtoupper($enc);
3823 } else {
3824 $this->xml_encoding = 'US-ASCII';
3826 } else {
3827 // should be US-ASCII for HTTP 1.0 or ISO-8859-1 for HTTP 1.1
3828 $this->xml_encoding = 'ISO-8859-1';
3830 $this->debug('Use encoding: ' . $this->xml_encoding . ' when creating soap_parser');
3831 // parse response, get soap parser obj
3832 $parser = new soap_parser($data,$this->xml_encoding,'',$this->decode_utf8);
3833 // parser debug
3834 $this->debug("parser debug: \n".$parser->getDebug());
3835 // if fault occurred during message parsing
3836 if($err = $parser->getError()){
3837 $this->result = 'fault: error in msg parsing: '.$err;
3838 $this->fault('SOAP-ENV:Client',"error in msg parsing:\n".$err);
3839 // else successfully parsed request into soapval object
3840 } else {
3841 // get/set methodname
3842 $this->methodURI = $parser->root_struct_namespace;
3843 $this->methodname = $parser->root_struct_name;
3844 $this->debug('methodname: '.$this->methodname.' methodURI: '.$this->methodURI);
3845 $this->debug('calling parser->get_response()');
3846 $this->methodparams = $parser->get_response();
3847 // get SOAP headers
3848 $this->requestHeaders = $parser->getHeaders();
3849 // add document for doclit support
3850 $this->document = $parser->document;
3855 * gets the HTTP body for the current response.
3857 * @param string $soapmsg The SOAP payload
3858 * @return string The HTTP body, which includes the SOAP payload
3859 * @access private
3861 function getHTTPBody($soapmsg) {
3862 return $soapmsg;
3866 * gets the HTTP content type for the current response.
3868 * Note: getHTTPBody must be called before this.
3870 * @return string the HTTP content type for the current response.
3871 * @access private
3873 function getHTTPContentType() {
3874 return 'text/xml';
3878 * gets the HTTP content type charset for the current response.
3879 * returns false for non-text content types.
3881 * Note: getHTTPBody must be called before this.
3883 * @return string the HTTP content type charset for the current response.
3884 * @access private
3886 function getHTTPContentTypeCharset() {
3887 return $this->soap_defencoding;
3891 * add a method to the dispatch map (this has been replaced by the register method)
3893 * @param string $methodname
3894 * @param string $in array of input values
3895 * @param string $out array of output values
3896 * @access public
3897 * @deprecated
3899 function add_to_map($methodname,$in,$out){
3900 $this->operations[$methodname] = array('name' => $methodname,'in' => $in,'out' => $out);
3904 * register a service function with the server
3906 * @param string $name the name of the PHP function, class.method or class..method
3907 * @param array $in assoc array of input values: key = param name, value = param type
3908 * @param array $out assoc array of output values: key = param name, value = param type
3909 * @param mixed $namespace the element namespace for the method or false
3910 * @param mixed $soapaction the soapaction for the method or false
3911 * @param mixed $style optional (rpc|document) or false Note: when 'document' is specified, parameter and return wrappers are created for you automatically
3912 * @param mixed $use optional (encoded|literal) or false
3913 * @param string $documentation optional Description to include in WSDL
3914 * @param string $encodingStyle optional (usually 'http://schemas.xmlsoap.org/soap/encoding/' for encoded)
3915 * @access public
3917 function register($name,$in=array(),$out=array(),$namespace=false,$soapaction=false,$style=false,$use=false,$documentation='',$encodingStyle=''){
3918 global $HTTP_SERVER_VARS;
3920 if($this->externalWSDLURL){
3921 die('You cannot bind to an external WSDL file, and register methods outside of it! Please choose either WSDL or no WSDL.');
3923 if (! $name) {
3924 die('You must specify a name when you register an operation');
3926 if (!is_array($in)) {
3927 die('You must provide an array for operation inputs');
3929 if (!is_array($out)) {
3930 die('You must provide an array for operation outputs');
3932 if(false == $namespace) {
3934 if(false == $soapaction) {
3935 if (isset($_SERVER)) {
3936 $SERVER_NAME = $_SERVER['SERVER_NAME'];
3937 $SCRIPT_NAME = isset($_SERVER['PHP_SELF']) ? $_SERVER['PHP_SELF'] : $_SERVER['SCRIPT_NAME'];
3938 } elseif (isset($HTTP_SERVER_VARS)) {
3939 $SERVER_NAME = $HTTP_SERVER_VARS['SERVER_NAME'];
3940 $SCRIPT_NAME = isset($HTTP_SERVER_VARS['PHP_SELF']) ? $HTTP_SERVER_VARS['PHP_SELF'] : $HTTP_SERVER_VARS['SCRIPT_NAME'];
3941 } else {
3942 $this->setError("Neither _SERVER nor HTTP_SERVER_VARS is available");
3944 $soapaction = "http://$SERVER_NAME$SCRIPT_NAME/$name";
3946 if(false == $style) {
3947 $style = "rpc";
3949 if(false == $use) {
3950 $use = "encoded";
3952 if ($use == 'encoded' && $encodingStyle = '') {
3953 $encodingStyle = 'http://schemas.xmlsoap.org/soap/encoding/';
3956 $this->operations[$name] = array(
3957 'name' => $name,
3958 'in' => $in,
3959 'out' => $out,
3960 'namespace' => $namespace,
3961 'soapaction' => $soapaction,
3962 'style' => $style);
3963 if($this->wsdl){
3964 $this->wsdl->addOperation($name,$in,$out,$namespace,$soapaction,$style,$use,$documentation,$encodingStyle);
3966 return true;
3970 * Specify a fault to be returned to the client.
3971 * This also acts as a flag to the server that a fault has occured.
3973 * @param string $faultcode
3974 * @param string $faultstring
3975 * @param string $faultactor
3976 * @param string $faultdetail
3977 * @access public
3979 function fault($faultcode,$faultstring,$faultactor='',$faultdetail=''){
3980 if ($faultdetail == '' && $this->debug_flag) {
3981 $faultdetail = $this->getDebug();
3983 $this->fault = new soap_fault($faultcode,$faultactor,$faultstring,$faultdetail);
3984 $this->fault->soap_defencoding = $this->soap_defencoding;
3988 * Sets up wsdl object.
3989 * Acts as a flag to enable internal WSDL generation
3991 * @param string $serviceName, name of the service
3992 * @param mixed $namespace optional 'tns' service namespace or false
3993 * @param mixed $endpoint optional URL of service endpoint or false
3994 * @param string $style optional (rpc|document) WSDL style (also specified by operation)
3995 * @param string $transport optional SOAP transport
3996 * @param mixed $schemaTargetNamespace optional 'types' targetNamespace for service schema or false
3998 function configureWSDL($serviceName,$namespace = false,$endpoint = false,$style='rpc', $transport = 'http://schemas.xmlsoap.org/soap/http', $schemaTargetNamespace = false)
4000 global $HTTP_SERVER_VARS;
4002 if (isset($_SERVER)) {
4003 $SERVER_NAME = $_SERVER['SERVER_NAME'];
4004 $SERVER_PORT = $_SERVER['SERVER_PORT'];
4005 $SCRIPT_NAME = isset($_SERVER['PHP_SELF']) ? $_SERVER['PHP_SELF'] : $_SERVER['SCRIPT_NAME'];
4006 $HTTPS = isset($_SERVER['HTTPS']) ? $_SERVER['HTTPS'] : (isset($HTTP_SERVER_VARS['HTTPS']) ? $HTTP_SERVER_VARS['HTTPS'] : 'off');
4007 } elseif (isset($HTTP_SERVER_VARS)) {
4008 $SERVER_NAME = $HTTP_SERVER_VARS['SERVER_NAME'];
4009 $SERVER_PORT = $HTTP_SERVER_VARS['SERVER_PORT'];
4010 $SCRIPT_NAME = isset($HTTP_SERVER_VARS['PHP_SELF']) ? $HTTP_SERVER_VARS['PHP_SELF'] : $HTTP_SERVER_VARS['SCRIPT_NAME'];
4011 $HTTPS = isset($HTTP_SERVER_VARS['HTTPS']) ? $HTTP_SERVER_VARS['HTTPS'] : 'off';
4012 } else {
4013 $this->setError("Neither _SERVER nor HTTP_SERVER_VARS is available");
4015 if ($SERVER_PORT == 80) {
4016 $SERVER_PORT = '';
4017 } else {
4018 $SERVER_PORT = ':' . $SERVER_PORT;
4020 if(false == $namespace) {
4021 $namespace = "http://$SERVER_NAME/soap/$serviceName";
4024 if(false == $endpoint) {
4025 if ($HTTPS == '1' || $HTTPS == 'on') {
4026 $SCHEME = 'https';
4027 } else {
4028 $SCHEME = 'http';
4030 $endpoint = "$SCHEME://$SERVER_NAME$SERVER_PORT$SCRIPT_NAME";
4033 if(false == $schemaTargetNamespace) {
4034 $schemaTargetNamespace = $namespace;
4037 $this->wsdl = new wsdl;
4038 $this->wsdl->serviceName = $serviceName;
4039 $this->wsdl->endpoint = $endpoint;
4040 $this->wsdl->namespaces['tns'] = $namespace;
4041 $this->wsdl->namespaces['soap'] = 'http://schemas.xmlsoap.org/wsdl/soap/';
4042 $this->wsdl->namespaces['wsdl'] = 'http://schemas.xmlsoap.org/wsdl/';
4043 if ($schemaTargetNamespace != $namespace) {
4044 $this->wsdl->namespaces['types'] = $schemaTargetNamespace;
4046 $this->wsdl->schemas[$schemaTargetNamespace][0] = new xmlschema('', '', $this->wsdl->namespaces);
4047 $this->wsdl->schemas[$schemaTargetNamespace][0]->schemaTargetNamespace = $schemaTargetNamespace;
4048 $this->wsdl->schemas[$schemaTargetNamespace][0]->imports['http://schemas.xmlsoap.org/soap/encoding/'][0] = array('location' => '', 'loaded' => true);
4049 $this->wsdl->schemas[$schemaTargetNamespace][0]->imports['http://schemas.xmlsoap.org/wsdl/'][0] = array('location' => '', 'loaded' => true);
4050 $this->wsdl->bindings[$serviceName.'Binding'] = array(
4051 'name'=>$serviceName.'Binding',
4052 'style'=>$style,
4053 'transport'=>$transport,
4054 'portType'=>$serviceName.'PortType');
4055 $this->wsdl->ports[$serviceName.'Port'] = array(
4056 'binding'=>$serviceName.'Binding',
4057 'location'=>$endpoint,
4058 'bindingType'=>'http://schemas.xmlsoap.org/wsdl/soap/');
4064 ?><?php
4069 * parses a WSDL file, allows access to it's data, other utility methods
4071 * @author Dietrich Ayala <dietrich@ganx4.com>
4072 * @version $Id$
4073 * @access public
4075 class wsdl extends nusoap_base {
4076 // URL or filename of the root of this WSDL
4077 var $wsdl;
4078 // define internal arrays of bindings, ports, operations, messages, etc.
4079 var $schemas = array();
4080 var $currentSchema;
4081 var $message = array();
4082 var $complexTypes = array();
4083 var $messages = array();
4084 var $currentMessage;
4085 var $currentOperation;
4086 var $portTypes = array();
4087 var $currentPortType;
4088 var $bindings = array();
4089 var $currentBinding;
4090 var $ports = array();
4091 var $currentPort;
4092 var $opData = array();
4093 var $status = '';
4094 var $documentation = false;
4095 var $endpoint = '';
4096 // array of wsdl docs to import
4097 var $import = array();
4098 // parser vars
4099 var $parser;
4100 var $position = 0;
4101 var $depth = 0;
4102 var $depth_array = array();
4103 // for getting wsdl
4104 var $proxyhost = '';
4105 var $proxyport = '';
4106 var $proxyusername = '';
4107 var $proxypassword = '';
4108 var $timeout = 0;
4109 var $response_timeout = 30;
4112 * constructor
4114 * @param string $wsdl WSDL document URL
4115 * @param string $proxyhost
4116 * @param string $proxyport
4117 * @param string $proxyusername
4118 * @param string $proxypassword
4119 * @param integer $timeout set the connection timeout
4120 * @param integer $response_timeout set the response timeout
4121 * @access public
4123 function wsdl($wsdl = '',$proxyhost=false,$proxyport=false,$proxyusername=false,$proxypassword=false,$timeout=0,$response_timeout=30){
4124 parent::nusoap_base();
4125 $this->wsdl = $wsdl;
4126 $this->proxyhost = $proxyhost;
4127 $this->proxyport = $proxyport;
4128 $this->proxyusername = $proxyusername;
4129 $this->proxypassword = $proxypassword;
4130 $this->timeout = $timeout;
4131 $this->response_timeout = $response_timeout;
4133 // parse wsdl file
4134 if ($wsdl != "") {
4135 $this->debug('initial wsdl URL: ' . $wsdl);
4136 $this->parseWSDL($wsdl);
4138 // imports
4139 // TODO: handle imports more properly, grabbing them in-line and nesting them
4140 $imported_urls = array();
4141 $imported = 1;
4142 while ($imported > 0) {
4143 $imported = 0;
4144 // Schema imports
4145 foreach ($this->schemas as $ns => $list) {
4146 foreach ($list as $xs) {
4147 $wsdlparts = parse_url($this->wsdl); // this is bogusly simple!
4148 foreach ($xs->imports as $ns2 => $list2) {
4149 for ($ii = 0; $ii < count($list2); $ii++) {
4150 if (! $list2[$ii]['loaded']) {
4151 $this->schemas[$ns]->imports[$ns2][$ii]['loaded'] = true;
4152 $url = $list2[$ii]['location'];
4153 if ($url != '') {
4154 $urlparts = parse_url($url);
4155 if (!isset($urlparts['host'])) {
4156 $url = $wsdlparts['scheme'] . '://' . $wsdlparts['host'] . (isset($wsdlparts['port']) ? ':' .$wsdlparts['port'] : '') .
4157 substr($wsdlparts['path'],0,strrpos($wsdlparts['path'],'/') + 1) .$urlparts['path'];
4159 if (! in_array($url, $imported_urls)) {
4160 $this->parseWSDL($url);
4161 $imported++;
4162 $imported_urls[] = $url;
4164 } else {
4165 $this->debug("Unexpected scenario: empty URL for unloaded import");
4172 // WSDL imports
4173 $wsdlparts = parse_url($this->wsdl); // this is bogusly simple!
4174 foreach ($this->import as $ns => $list) {
4175 for ($ii = 0; $ii < count($list); $ii++) {
4176 if (! $list[$ii]['loaded']) {
4177 $this->import[$ns][$ii]['loaded'] = true;
4178 $url = $list[$ii]['location'];
4179 if ($url != '') {
4180 $urlparts = parse_url($url);
4181 if (!isset($urlparts['host'])) {
4182 $url = $wsdlparts['scheme'] . '://' . $wsdlparts['host'] . (isset($wsdlparts['port']) ? ':' . $wsdlparts['port'] : '') .
4183 substr($wsdlparts['path'],0,strrpos($wsdlparts['path'],'/') + 1) .$urlparts['path'];
4185 if (! in_array($url, $imported_urls)) {
4186 $this->parseWSDL($url);
4187 $imported++;
4188 $imported_urls[] = $url;
4190 } else {
4191 $this->debug("Unexpected scenario: empty URL for unloaded import");
4197 // add new data to operation data
4198 foreach($this->bindings as $binding => $bindingData) {
4199 if (isset($bindingData['operations']) && is_array($bindingData['operations'])) {
4200 foreach($bindingData['operations'] as $operation => $data) {
4201 $this->debug('post-parse data gathering for ' . $operation);
4202 $this->bindings[$binding]['operations'][$operation]['input'] =
4203 isset($this->bindings[$binding]['operations'][$operation]['input']) ?
4204 array_merge($this->bindings[$binding]['operations'][$operation]['input'], $this->portTypes[ $bindingData['portType'] ][$operation]['input']) :
4205 $this->portTypes[ $bindingData['portType'] ][$operation]['input'];
4206 $this->bindings[$binding]['operations'][$operation]['output'] =
4207 isset($this->bindings[$binding]['operations'][$operation]['output']) ?
4208 array_merge($this->bindings[$binding]['operations'][$operation]['output'], $this->portTypes[ $bindingData['portType'] ][$operation]['output']) :
4209 $this->portTypes[ $bindingData['portType'] ][$operation]['output'];
4210 if(isset($this->messages[ $this->bindings[$binding]['operations'][$operation]['input']['message'] ])){
4211 $this->bindings[$binding]['operations'][$operation]['input']['parts'] = $this->messages[ $this->bindings[$binding]['operations'][$operation]['input']['message'] ];
4213 if(isset($this->messages[ $this->bindings[$binding]['operations'][$operation]['output']['message'] ])){
4214 $this->bindings[$binding]['operations'][$operation]['output']['parts'] = $this->messages[ $this->bindings[$binding]['operations'][$operation]['output']['message'] ];
4216 if (isset($bindingData['style'])) {
4217 $this->bindings[$binding]['operations'][$operation]['style'] = $bindingData['style'];
4219 $this->bindings[$binding]['operations'][$operation]['transport'] = isset($bindingData['transport']) ? $bindingData['transport'] : '';
4220 $this->bindings[$binding]['operations'][$operation]['documentation'] = isset($this->portTypes[ $bindingData['portType'] ][$operation]['documentation']) ? $this->portTypes[ $bindingData['portType'] ][$operation]['documentation'] : '';
4221 $this->bindings[$binding]['operations'][$operation]['endpoint'] = isset($bindingData['endpoint']) ? $bindingData['endpoint'] : '';
4228 * parses the wsdl document
4230 * @param string $wsdl path or URL
4231 * @access private
4233 function parseWSDL($wsdl = '')
4235 if ($wsdl == '') {
4236 $this->debug('no wsdl passed to parseWSDL()!!');
4237 $this->setError('no wsdl passed to parseWSDL()!!');
4238 return false;
4241 // parse $wsdl for url format
4242 $wsdl_props = parse_url($wsdl);
4244 if (isset($wsdl_props['scheme']) && ($wsdl_props['scheme'] == 'http' || $wsdl_props['scheme'] == 'https')) {
4245 $this->debug('getting WSDL http(s) URL ' . $wsdl);
4246 // get wsdl
4247 $tr = new soap_transport_http($wsdl);
4248 $tr->request_method = 'GET';
4249 $tr->useSOAPAction = false;
4250 if($this->proxyhost && $this->proxyport){
4251 $tr->setProxy($this->proxyhost,$this->proxyport,$this->proxyusername,$this->proxypassword);
4253 $tr->setEncoding('gzip, deflate');
4254 $wsdl_string = $tr->send('', $this->timeout, $this->response_timeout);
4255 //$this->debug("WSDL request\n" . $tr->outgoing_payload);
4256 //$this->debug("WSDL response\n" . $tr->incoming_payload);
4257 $this->appendDebug($tr->getDebug());
4258 // catch errors
4259 if($err = $tr->getError() ){
4260 $errstr = 'HTTP ERROR: '.$err;
4261 $this->debug($errstr);
4262 $this->setError($errstr);
4263 unset($tr);
4264 return false;
4266 unset($tr);
4267 $this->debug("got WSDL URL");
4268 } else {
4269 // $wsdl is not http(s), so treat it as a file URL or plain file path
4270 if (isset($wsdl_props['scheme']) && ($wsdl_props['scheme'] == 'file') && isset($wsdl_props['path'])) {
4271 $path = isset($wsdl_props['host']) ? ($wsdl_props['host'] . ':' . $wsdl_props['path']) : $wsdl_props['path'];
4272 } else {
4273 $path = $wsdl;
4275 $this->debug('getting WSDL file ' . $path);
4276 if ($fp = @fopen($path, 'r')) {
4277 $wsdl_string = '';
4278 while ($data = fread($fp, 32768)) {
4279 $wsdl_string .= $data;
4281 fclose($fp);
4282 } else {
4283 $errstr = "Bad path to WSDL file $path";
4284 $this->debug($errstr);
4285 $this->setError($errstr);
4286 return false;
4289 $this->debug('Parse WSDL');
4290 // end new code added
4291 // Create an XML parser.
4292 $this->parser = xml_parser_create();
4293 // Set the options for parsing the XML data.
4294 // xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 1);
4295 xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING, 0);
4296 // Set the object for the parser.
4297 xml_set_object($this->parser, $this);
4298 // Set the element handlers for the parser.
4299 xml_set_element_handler($this->parser, 'start_element', 'end_element');
4300 xml_set_character_data_handler($this->parser, 'character_data');
4301 // Parse the XML file.
4302 if (!xml_parse($this->parser, $wsdl_string, true)) {
4303 // Display an error message.
4304 $errstr = sprintf(
4305 'XML error parsing WSDL from %s on line %d: %s',
4306 $wsdl,
4307 xml_get_current_line_number($this->parser),
4308 xml_error_string(xml_get_error_code($this->parser))
4310 $this->debug($errstr);
4311 $this->debug("XML payload:\n" . $wsdl_string);
4312 $this->setError($errstr);
4313 return false;
4315 // free the parser
4316 xml_parser_free($this->parser);
4317 $this->debug('Parsing WSDL done');
4318 // catch wsdl parse errors
4319 if($this->getError()){
4320 return false;
4322 return true;
4326 * start-element handler
4328 * @param string $parser XML parser object
4329 * @param string $name element name
4330 * @param string $attrs associative array of attributes
4331 * @access private
4333 function start_element($parser, $name, $attrs)
4335 if ($this->status == 'schema') {
4336 $this->currentSchema->schemaStartElement($parser, $name, $attrs);
4337 $this->appendDebug($this->currentSchema->getDebug());
4338 $this->currentSchema->clearDebug();
4339 } elseif (ereg('schema$', $name)) {
4340 $this->debug('Parsing WSDL schema');
4341 // $this->debug("startElement for $name ($attrs[name]). status = $this->status (".$this->getLocalPart($name).")");
4342 $this->status = 'schema';
4343 $this->currentSchema = new xmlschema('', '', $this->namespaces);
4344 $this->currentSchema->schemaStartElement($parser, $name, $attrs);
4345 $this->appendDebug($this->currentSchema->getDebug());
4346 $this->currentSchema->clearDebug();
4347 } else {
4348 // position in the total number of elements, starting from 0
4349 $pos = $this->position++;
4350 $depth = $this->depth++;
4351 // set self as current value for this depth
4352 $this->depth_array[$depth] = $pos;
4353 $this->message[$pos] = array('cdata' => '');
4354 // process attributes
4355 if (count($attrs) > 0) {
4356 // register namespace declarations
4357 foreach($attrs as $k => $v) {
4358 if (ereg("^xmlns", $k)) {
4359 if ($ns_prefix = substr(strrchr($k, ':'), 1)) {
4360 $this->namespaces[$ns_prefix] = $v;
4361 } else {
4362 $this->namespaces['ns' . (count($this->namespaces) + 1)] = $v;
4364 if ($v == 'http://www.w3.org/2001/XMLSchema' || $v == 'http://www.w3.org/1999/XMLSchema' || $v == 'http://www.w3.org/2000/10/XMLSchema') {
4365 $this->XMLSchemaVersion = $v;
4366 $this->namespaces['xsi'] = $v . '-instance';
4370 // expand each attribute prefix to its namespace
4371 foreach($attrs as $k => $v) {
4372 $k = strpos($k, ':') ? $this->expandQname($k) : $k;
4373 if ($k != 'location' && $k != 'soapAction' && $k != 'namespace') {
4374 $v = strpos($v, ':') ? $this->expandQname($v) : $v;
4376 $eAttrs[$k] = $v;
4378 $attrs = $eAttrs;
4379 } else {
4380 $attrs = array();
4382 // get element prefix, namespace and name
4383 if (ereg(':', $name)) {
4384 // get ns prefix
4385 $prefix = substr($name, 0, strpos($name, ':'));
4386 // get ns
4387 $namespace = isset($this->namespaces[$prefix]) ? $this->namespaces[$prefix] : '';
4388 // get unqualified name
4389 $name = substr(strstr($name, ':'), 1);
4391 // process attributes, expanding any prefixes to namespaces
4392 // find status, register data
4393 switch ($this->status) {
4394 case 'message':
4395 if ($name == 'part') {
4396 if (isset($attrs['type'])) {
4397 $this->debug("msg " . $this->currentMessage . ": found part $attrs[name]: " . implode(',', $attrs));
4398 $this->messages[$this->currentMessage][$attrs['name']] = $attrs['type'];
4400 if (isset($attrs['element'])) {
4401 $this->debug("msg " . $this->currentMessage . ": found part $attrs[name]: " . implode(',', $attrs));
4402 $this->messages[$this->currentMessage][$attrs['name']] = $attrs['element'];
4405 break;
4406 case 'portType':
4407 switch ($name) {
4408 case 'operation':
4409 $this->currentPortOperation = $attrs['name'];
4410 $this->debug("portType $this->currentPortType operation: $this->currentPortOperation");
4411 if (isset($attrs['parameterOrder'])) {
4412 $this->portTypes[$this->currentPortType][$attrs['name']]['parameterOrder'] = $attrs['parameterOrder'];
4414 break;
4415 case 'documentation':
4416 $this->documentation = true;
4417 break;
4418 // merge input/output data
4419 default:
4420 $m = isset($attrs['message']) ? $this->getLocalPart($attrs['message']) : '';
4421 $this->portTypes[$this->currentPortType][$this->currentPortOperation][$name]['message'] = $m;
4422 break;
4424 break;
4425 case 'binding':
4426 switch ($name) {
4427 case 'binding':
4428 // get ns prefix
4429 if (isset($attrs['style'])) {
4430 $this->bindings[$this->currentBinding]['prefix'] = $prefix;
4432 $this->bindings[$this->currentBinding] = array_merge($this->bindings[$this->currentBinding], $attrs);
4433 break;
4434 case 'header':
4435 $this->bindings[$this->currentBinding]['operations'][$this->currentOperation][$this->opStatus]['headers'][] = $attrs;
4436 break;
4437 case 'operation':
4438 if (isset($attrs['soapAction'])) {
4439 $this->bindings[$this->currentBinding]['operations'][$this->currentOperation]['soapAction'] = $attrs['soapAction'];
4441 if (isset($attrs['style'])) {
4442 $this->bindings[$this->currentBinding]['operations'][$this->currentOperation]['style'] = $attrs['style'];
4444 if (isset($attrs['name'])) {
4445 $this->currentOperation = $attrs['name'];
4446 $this->debug("current binding operation: $this->currentOperation");
4447 $this->bindings[$this->currentBinding]['operations'][$this->currentOperation]['name'] = $attrs['name'];
4448 $this->bindings[$this->currentBinding]['operations'][$this->currentOperation]['binding'] = $this->currentBinding;
4449 $this->bindings[$this->currentBinding]['operations'][$this->currentOperation]['endpoint'] = isset($this->bindings[$this->currentBinding]['endpoint']) ? $this->bindings[$this->currentBinding]['endpoint'] : '';
4451 break;
4452 case 'input':
4453 $this->opStatus = 'input';
4454 break;
4455 case 'output':
4456 $this->opStatus = 'output';
4457 break;
4458 case 'body':
4459 if (isset($this->bindings[$this->currentBinding]['operations'][$this->currentOperation][$this->opStatus])) {
4460 $this->bindings[$this->currentBinding]['operations'][$this->currentOperation][$this->opStatus] = array_merge($this->bindings[$this->currentBinding]['operations'][$this->currentOperation][$this->opStatus], $attrs);
4461 } else {
4462 $this->bindings[$this->currentBinding]['operations'][$this->currentOperation][$this->opStatus] = $attrs;
4464 break;
4466 break;
4467 case 'service':
4468 switch ($name) {
4469 case 'port':
4470 $this->currentPort = $attrs['name'];
4471 $this->debug('current port: ' . $this->currentPort);
4472 $this->ports[$this->currentPort]['binding'] = $this->getLocalPart($attrs['binding']);
4474 break;
4475 case 'address':
4476 $this->ports[$this->currentPort]['location'] = $attrs['location'];
4477 $this->ports[$this->currentPort]['bindingType'] = $namespace;
4478 $this->bindings[ $this->ports[$this->currentPort]['binding'] ]['bindingType'] = $namespace;
4479 $this->bindings[ $this->ports[$this->currentPort]['binding'] ]['endpoint'] = $attrs['location'];
4480 break;
4482 break;
4484 // set status
4485 switch ($name) {
4486 case 'import':
4487 if (isset($attrs['location'])) {
4488 $this->import[$attrs['namespace']][] = array('location' => $attrs['location'], 'loaded' => false);
4489 $this->debug('parsing import ' . $attrs['namespace']. ' - ' . $attrs['location'] . ' (' . count($this->import[$attrs['namespace']]).')');
4490 } else {
4491 $this->import[$attrs['namespace']][] = array('location' => '', 'loaded' => true);
4492 if (! $this->getPrefixFromNamespace($attrs['namespace'])) {
4493 $this->namespaces['ns'.(count($this->namespaces)+1)] = $attrs['namespace'];
4495 $this->debug('parsing import ' . $attrs['namespace']. ' - [no location] (' . count($this->import[$attrs['namespace']]).')');
4497 break;
4498 //wait for schema
4499 //case 'types':
4500 // $this->status = 'schema';
4501 // break;
4502 case 'message':
4503 $this->status = 'message';
4504 $this->messages[$attrs['name']] = array();
4505 $this->currentMessage = $attrs['name'];
4506 break;
4507 case 'portType':
4508 $this->status = 'portType';
4509 $this->portTypes[$attrs['name']] = array();
4510 $this->currentPortType = $attrs['name'];
4511 break;
4512 case "binding":
4513 if (isset($attrs['name'])) {
4514 // get binding name
4515 if (strpos($attrs['name'], ':')) {
4516 $this->currentBinding = $this->getLocalPart($attrs['name']);
4517 } else {
4518 $this->currentBinding = $attrs['name'];
4520 $this->status = 'binding';
4521 $this->bindings[$this->currentBinding]['portType'] = $this->getLocalPart($attrs['type']);
4522 $this->debug("current binding: $this->currentBinding of portType: " . $attrs['type']);
4524 break;
4525 case 'service':
4526 $this->serviceName = $attrs['name'];
4527 $this->status = 'service';
4528 $this->debug('current service: ' . $this->serviceName);
4529 break;
4530 case 'definitions':
4531 foreach ($attrs as $name => $value) {
4532 $this->wsdl_info[$name] = $value;
4534 break;
4540 * end-element handler
4542 * @param string $parser XML parser object
4543 * @param string $name element name
4544 * @access private
4546 function end_element($parser, $name){
4547 // unset schema status
4548 if (/*ereg('types$', $name) ||*/ ereg('schema$', $name)) {
4549 $this->status = "";
4550 $this->appendDebug($this->currentSchema->getDebug());
4551 $this->currentSchema->clearDebug();
4552 $this->schemas[$this->currentSchema->schemaTargetNamespace][] = $this->currentSchema;
4553 $this->debug('Parsing WSDL schema done');
4555 if ($this->status == 'schema') {
4556 $this->currentSchema->schemaEndElement($parser, $name);
4557 } else {
4558 // bring depth down a notch
4559 $this->depth--;
4561 // end documentation
4562 if ($this->documentation) {
4563 //TODO: track the node to which documentation should be assigned; it can be a part, message, etc.
4564 //$this->portTypes[$this->currentPortType][$this->currentPortOperation]['documentation'] = $this->documentation;
4565 $this->documentation = false;
4570 * element content handler
4572 * @param string $parser XML parser object
4573 * @param string $data element content
4574 * @access private
4576 function character_data($parser, $data)
4578 $pos = isset($this->depth_array[$this->depth]) ? $this->depth_array[$this->depth] : 0;
4579 if (isset($this->message[$pos]['cdata'])) {
4580 $this->message[$pos]['cdata'] .= $data;
4582 if ($this->documentation) {
4583 $this->documentation .= $data;
4587 function getBindingData($binding)
4589 if (is_array($this->bindings[$binding])) {
4590 return $this->bindings[$binding];
4595 * returns an assoc array of operation names => operation data
4597 * @param string $bindingType eg: soap, smtp, dime (only soap is currently supported)
4598 * @return array
4599 * @access public
4601 function getOperations($bindingType = 'soap')
4603 $ops = array();
4604 if ($bindingType == 'soap') {
4605 $bindingType = 'http://schemas.xmlsoap.org/wsdl/soap/';
4607 // loop thru ports
4608 foreach($this->ports as $port => $portData) {
4609 // binding type of port matches parameter
4610 if ($portData['bindingType'] == $bindingType) {
4611 //$this->debug("getOperations for port $port");
4612 //$this->debug("port data: " . $this->varDump($portData));
4613 //$this->debug("bindings: " . $this->varDump($this->bindings[ $portData['binding'] ]));
4614 // merge bindings
4615 if (isset($this->bindings[ $portData['binding'] ]['operations'])) {
4616 $ops = array_merge ($ops, $this->bindings[ $portData['binding'] ]['operations']);
4620 return $ops;
4624 * returns an associative array of data necessary for calling an operation
4626 * @param string $operation , name of operation
4627 * @param string $bindingType , type of binding eg: soap
4628 * @return array
4629 * @access public
4631 function getOperationData($operation, $bindingType = 'soap')
4633 if ($bindingType == 'soap') {
4634 $bindingType = 'http://schemas.xmlsoap.org/wsdl/soap/';
4636 // loop thru ports
4637 foreach($this->ports as $port => $portData) {
4638 // binding type of port matches parameter
4639 if ($portData['bindingType'] == $bindingType) {
4640 // get binding
4641 //foreach($this->bindings[ $portData['binding'] ]['operations'] as $bOperation => $opData) {
4642 foreach(array_keys($this->bindings[ $portData['binding'] ]['operations']) as $bOperation) {
4643 // note that we could/should also check the namespace here
4644 if ($operation == $bOperation) {
4645 $opData = $this->bindings[ $portData['binding'] ]['operations'][$operation];
4646 return $opData;
4654 * returns an associative array of data necessary for calling an operation
4656 * @param string $soapAction soapAction for operation
4657 * @param string $bindingType type of binding eg: soap
4658 * @return array
4659 * @access public
4661 function getOperationDataForSoapAction($soapAction, $bindingType = 'soap') {
4662 if ($bindingType == 'soap') {
4663 $bindingType = 'http://schemas.xmlsoap.org/wsdl/soap/';
4665 // loop thru ports
4666 foreach($this->ports as $port => $portData) {
4667 // binding type of port matches parameter
4668 if ($portData['bindingType'] == $bindingType) {
4669 // loop through operations for the binding
4670 foreach ($this->bindings[ $portData['binding'] ]['operations'] as $bOperation => $opData) {
4671 if ($opData['soapAction'] == $soapAction) {
4672 return $opData;
4680 * returns an array of information about a given type
4681 * returns false if no type exists by the given name
4683 * typeDef = array(
4684 * 'elements' => array(), // refs to elements array
4685 * 'restrictionBase' => '',
4686 * 'phpType' => '',
4687 * 'order' => '(sequence|all)',
4688 * 'attrs' => array() // refs to attributes array
4691 * @param $type string the type
4692 * @param $ns string namespace (not prefix) of the type
4693 * @return mixed
4694 * @access public
4695 * @see xmlschema
4697 function getTypeDef($type, $ns) {
4698 $this->debug("in getTypeDef: type=$type, ns=$ns");
4699 if ((! $ns) && isset($this->namespaces['tns'])) {
4700 $ns = $this->namespaces['tns'];
4701 $this->debug("in getTypeDef: type namespace forced to $ns");
4703 if (isset($this->schemas[$ns])) {
4704 $this->debug("in getTypeDef: have schema for namespace $ns");
4705 for ($i = 0; $i < count($this->schemas[$ns]); $i++) {
4706 $xs = &$this->schemas[$ns][$i];
4707 $t = $xs->getTypeDef($type);
4708 $this->appendDebug($xs->getDebug());
4709 $xs->clearDebug();
4710 if ($t) {
4711 if (!isset($t['phpType'])) {
4712 // get info for type to tack onto the element
4713 $uqType = substr($t['type'], strrpos($t['type'], ':') + 1);
4714 $ns = substr($t['type'], 0, strrpos($t['type'], ':'));
4715 $etype = $this->getTypeDef($uqType, $ns);
4716 if ($etype) {
4717 $this->debug("found type for [element] $type:");
4718 $this->debug($this->varDump($etype));
4719 if (isset($etype['phpType'])) {
4720 $t['phpType'] = $etype['phpType'];
4722 if (isset($etype['elements'])) {
4723 $t['elements'] = $etype['elements'];
4725 if (isset($etype['attrs'])) {
4726 $t['attrs'] = $etype['attrs'];
4730 return $t;
4733 } else {
4734 $this->debug("in getTypeDef: do not have schema for namespace $ns");
4736 return false;
4740 * prints html description of services
4742 * @access private
4744 function webDescription(){
4745 global $HTTP_SERVER_VARS;
4747 if (isset($_SERVER)) {
4748 $PHP_SELF = $_SERVER['PHP_SELF'];
4749 } elseif (isset($HTTP_SERVER_VARS)) {
4750 $PHP_SELF = $HTTP_SERVER_VARS['PHP_SELF'];
4751 } else {
4752 $this->setError("Neither _SERVER nor HTTP_SERVER_VARS is available");
4755 $b = '
4756 <html><head><title>NuSOAP: '.$this->serviceName.'</title>
4757 <style type="text/css">
4758 body { font-family: arial; color: #000000; background-color: #ffffff; margin: 0px 0px 0px 0px; }
4759 p { font-family: arial; color: #000000; margin-top: 0px; margin-bottom: 12px; }
4760 pre { background-color: silver; padding: 5px; font-family: Courier New; font-size: x-small; color: #000000;}
4761 ul { margin-top: 10px; margin-left: 20px; }
4762 li { list-style-type: none; margin-top: 10px; color: #000000; }
4763 .content{
4764 margin-left: 0px; padding-bottom: 2em; }
4765 .nav {
4766 padding-top: 10px; padding-bottom: 10px; padding-left: 15px; font-size: .70em;
4767 margin-top: 10px; margin-left: 0px; color: #000000;
4768 background-color: #ccccff; width: 20%; margin-left: 20px; margin-top: 20px; }
4769 .title {
4770 font-family: arial; font-size: 26px; color: #ffffff;
4771 background-color: #999999; width: 105%; margin-left: 0px;
4772 padding-top: 10px; padding-bottom: 10px; padding-left: 15px;}
4773 .hidden {
4774 position: absolute; visibility: hidden; z-index: 200; left: 250px; top: 100px;
4775 font-family: arial; overflow: hidden; width: 600;
4776 padding: 20px; font-size: 10px; background-color: #999999;
4777 layer-background-color:#FFFFFF; }
4778 a,a:active { color: charcoal; font-weight: bold; }
4779 a:visited { color: #666666; font-weight: bold; }
4780 a:hover { color: cc3300; font-weight: bold; }
4781 </style>
4782 <script type="text/javascript">
4783 //<![CDATA[
4784 // POP-UP CAPTIONS...
4785 function lib_bwcheck(){ //Browsercheck (needed)
4786 this.ver=navigator.appVersion
4787 this.agent=navigator.userAgent
4788 this.dom=document.getElementById?1:0
4789 this.opera5=this.agent.indexOf("Opera 5")>-1
4790 this.ie5=(this.ver.indexOf("MSIE 5")>-1 && this.dom && !this.opera5)?1:0;
4791 this.ie6=(this.ver.indexOf("MSIE 6")>-1 && this.dom && !this.opera5)?1:0;
4792 this.ie4=(document.all && !this.dom && !this.opera5)?1:0;
4793 this.ie=this.ie4||this.ie5||this.ie6
4794 this.mac=this.agent.indexOf("Mac")>-1
4795 this.ns6=(this.dom && parseInt(this.ver) >= 5) ?1:0;
4796 this.ns4=(document.layers && !this.dom)?1:0;
4797 this.bw=(this.ie6 || this.ie5 || this.ie4 || this.ns4 || this.ns6 || this.opera5)
4798 return this
4800 var bw = new lib_bwcheck()
4801 //Makes crossbrowser object.
4802 function makeObj(obj){
4803 this.evnt=bw.dom? document.getElementById(obj):bw.ie4?document.all[obj]:bw.ns4?document.layers[obj]:0;
4804 if(!this.evnt) return false
4805 this.css=bw.dom||bw.ie4?this.evnt.style:bw.ns4?this.evnt:0;
4806 this.wref=bw.dom||bw.ie4?this.evnt:bw.ns4?this.css.document:0;
4807 this.writeIt=b_writeIt;
4808 return this
4810 // A unit of measure that will be added when setting the position of a layer.
4811 //var px = bw.ns4||window.opera?"":"px";
4812 function b_writeIt(text){
4813 if (bw.ns4){this.wref.write(text);this.wref.close()}
4814 else this.wref.innerHTML = text
4816 //Shows the messages
4817 var oDesc;
4818 function popup(divid){
4819 if(oDesc = new makeObj(divid)){
4820 oDesc.css.visibility = "visible"
4823 function popout(){ // Hides message
4824 if(oDesc) oDesc.css.visibility = "hidden"
4826 //]]>
4827 </script>
4828 </head>
4829 <body>
4830 <div class=content>
4831 <br><br>
4832 <div class=title>'.$this->serviceName.'</div>
4833 <div class=nav>
4834 <p>View the <a href="'.$PHP_SELF.'?wsdl">WSDL</a> for the service.
4835 Click on an operation name to view it&apos;s details.</p>
4836 <ul>';
4837 foreach($this->getOperations() as $op => $data){
4838 $b .= "<li><a href='#' onclick=\"popout();popup('$op')\">$op</a></li>";
4839 // create hidden div
4840 $b .= "<div id='$op' class='hidden'>
4841 <a href='#' onclick='popout()'><font color='#ffffff'>Close</font></a><br><br>";
4842 foreach($data as $donnie => $marie){ // loop through opdata
4843 if($donnie == 'input' || $donnie == 'output'){ // show input/output data
4844 $b .= "<font color='white'>".ucfirst($donnie).':</font><br>';
4845 foreach($marie as $captain => $tenille){ // loop through data
4846 if($captain == 'parts'){ // loop thru parts
4847 $b .= "&nbsp;&nbsp;$captain:<br>";
4848 //if(is_array($tenille)){
4849 foreach($tenille as $joanie => $chachi){
4850 $b .= "&nbsp;&nbsp;&nbsp;&nbsp;$joanie: $chachi<br>";
4853 } else {
4854 $b .= "&nbsp;&nbsp;$captain: $tenille<br>";
4857 } else {
4858 $b .= "<font color='white'>".ucfirst($donnie).":</font> $marie<br>";
4861 $b .= '</div>';
4863 $b .= '
4864 <ul>
4865 </div>
4866 </div></body></html>';
4867 return $b;
4871 * serialize the parsed wsdl
4873 * @param mixed $debug whether to put debug=1 in endpoint URL
4874 * @return string serialization of WSDL
4875 * @access public
4877 function serialize($debug = 0)
4879 $xml = '<?xml version="1.0" encoding="ISO-8859-1"?>';
4880 $xml .= "\n<definitions";
4881 foreach($this->namespaces as $k => $v) {
4882 $xml .= " xmlns:$k=\"$v\"";
4884 // 10.9.02 - add poulter fix for wsdl and tns declarations
4885 if (isset($this->namespaces['wsdl'])) {
4886 $xml .= " xmlns=\"" . $this->namespaces['wsdl'] . "\"";
4888 if (isset($this->namespaces['tns'])) {
4889 $xml .= " targetNamespace=\"" . $this->namespaces['tns'] . "\"";
4891 $xml .= '>';
4892 // imports
4893 if (sizeof($this->import) > 0) {
4894 foreach($this->import as $ns => $list) {
4895 foreach ($list as $ii) {
4896 if ($ii['location'] != '') {
4897 $xml .= '<import location="' . $ii['location'] . '" namespace="' . $ns . '" />';
4898 } else {
4899 $xml .= '<import namespace="' . $ns . '" />';
4904 // types
4905 if (count($this->schemas)>=1) {
4906 $xml .= "\n<types>\n";
4907 foreach ($this->schemas as $ns => $list) {
4908 foreach ($list as $xs) {
4909 $xml .= $xs->serializeSchema();
4912 $xml .= '</types>';
4914 // messages
4915 if (count($this->messages) >= 1) {
4916 foreach($this->messages as $msgName => $msgParts) {
4917 $xml .= "\n<message name=\"" . $msgName . '">';
4918 if(is_array($msgParts)){
4919 foreach($msgParts as $partName => $partType) {
4920 // print 'serializing '.$partType.', sv: '.$this->XMLSchemaVersion.'<br>';
4921 if (strpos($partType, ':')) {
4922 $typePrefix = $this->getPrefixFromNamespace($this->getPrefix($partType));
4923 } elseif (isset($this->typemap[$this->namespaces['xsd']][$partType])) {
4924 // print 'checking typemap: '.$this->XMLSchemaVersion.'<br>';
4925 $typePrefix = 'xsd';
4926 } else {
4927 foreach($this->typemap as $ns => $types) {
4928 if (isset($types[$partType])) {
4929 $typePrefix = $this->getPrefixFromNamespace($ns);
4932 if (!isset($typePrefix)) {
4933 die("$partType has no namespace!");
4936 $ns = $this->getNamespaceFromPrefix($typePrefix);
4937 $typeDef = $this->getTypeDef($this->getLocalPart($partType), $ns);
4938 if ($typeDef['typeClass'] == 'element') {
4939 $elementortype = 'element';
4940 } else {
4941 $elementortype = 'type';
4943 $xml .= "\n" . ' <part name="' . $partName . '" ' . $elementortype . '="' . $typePrefix . ':' . $this->getLocalPart($partType) . '" />';
4946 $xml .= '</message>';
4949 // bindings & porttypes
4950 if (count($this->bindings) >= 1) {
4951 $binding_xml = '';
4952 $portType_xml = '';
4953 foreach($this->bindings as $bindingName => $attrs) {
4954 $binding_xml .= "\n<binding name=\"" . $bindingName . '" type="tns:' . $attrs['portType'] . '">';
4955 $binding_xml .= "\n" . ' <soap:binding style="' . $attrs['style'] . '" transport="' . $attrs['transport'] . '"/>';
4956 $portType_xml .= "\n<portType name=\"" . $attrs['portType'] . '">';
4957 foreach($attrs['operations'] as $opName => $opParts) {
4958 $binding_xml .= "\n" . ' <operation name="' . $opName . '">';
4959 $binding_xml .= "\n" . ' <soap:operation soapAction="' . $opParts['soapAction'] . '" style="'. $opParts['style'] . '"/>';
4960 if (isset($opParts['input']['encodingStyle']) && $opParts['input']['encodingStyle'] != '') {
4961 $enc_style = ' encodingStyle="' . $opParts['input']['encodingStyle'] . '"';
4962 } else {
4963 $enc_style = '';
4965 $binding_xml .= "\n" . ' <input><soap:body use="' . $opParts['input']['use'] . '" namespace="' . $opParts['input']['namespace'] . '"' . $enc_style . '/></input>';
4966 if (isset($opParts['output']['encodingStyle']) && $opParts['output']['encodingStyle'] != '') {
4967 $enc_style = ' encodingStyle="' . $opParts['output']['encodingStyle'] . '"';
4968 } else {
4969 $enc_style = '';
4971 $binding_xml .= "\n" . ' <output><soap:body use="' . $opParts['output']['use'] . '" namespace="' . $opParts['output']['namespace'] . '"' . $enc_style . '/></output>';
4972 $binding_xml .= "\n" . ' </operation>';
4973 $portType_xml .= "\n" . ' <operation name="' . $opParts['name'] . '"';
4974 if (isset($opParts['parameterOrder'])) {
4975 $portType_xml .= ' parameterOrder="' . $opParts['parameterOrder'] . '"';
4977 $portType_xml .= '>';
4978 if(isset($opParts['documentation']) && $opParts['documentation'] != '') {
4979 $portType_xml .= "\n" . ' <documentation>' . htmlspecialchars($opParts['documentation']) . '</documentation>';
4981 $portType_xml .= "\n" . ' <input message="tns:' . $opParts['input']['message'] . '"/>';
4982 $portType_xml .= "\n" . ' <output message="tns:' . $opParts['output']['message'] . '"/>';
4983 $portType_xml .= "\n" . ' </operation>';
4985 $portType_xml .= "\n" . '</portType>';
4986 $binding_xml .= "\n" . '</binding>';
4988 $xml .= $portType_xml . $binding_xml;
4990 // services
4991 $xml .= "\n<service name=\"" . $this->serviceName . '">';
4992 if (count($this->ports) >= 1) {
4993 foreach($this->ports as $pName => $attrs) {
4994 $xml .= "\n" . ' <port name="' . $pName . '" binding="tns:' . $attrs['binding'] . '">';
4995 $xml .= "\n" . ' <soap:address location="' . $attrs['location'] . ($debug ? '?debug=1' : '') . '"/>';
4996 $xml .= "\n" . ' </port>';
4999 $xml .= "\n" . '</service>';
5000 return $xml . "\n</definitions>";
5004 * serialize PHP values according to a WSDL message definition
5006 * TODO
5007 * - multi-ref serialization
5008 * - validate PHP values against type definitions, return errors if invalid
5010 * @param string $operation operation name
5011 * @param string $direction (input|output)
5012 * @param mixed $parameters parameter value(s)
5013 * @return mixed parameters serialized as XML or false on error (e.g. operation not found)
5014 * @access public
5016 function serializeRPCParameters($operation, $direction, $parameters)
5018 $this->debug("in serializeRPCParameters: operation=$operation, direction=$direction, XMLSchemaVersion=$this->XMLSchemaVersion");
5019 $this->appendDebug('parameters=' . $this->varDump($parameters));
5021 if ($direction != 'input' && $direction != 'output') {
5022 $this->debug('The value of the \$direction argument needs to be either "input" or "output"');
5023 $this->setError('The value of the \$direction argument needs to be either "input" or "output"');
5024 return false;
5026 if (!$opData = $this->getOperationData($operation)) {
5027 $this->debug('Unable to retrieve WSDL data for operation: ' . $operation);
5028 $this->setError('Unable to retrieve WSDL data for operation: ' . $operation);
5029 return false;
5031 $this->debug('opData:');
5032 $this->appendDebug($this->varDump($opData));
5034 // Get encoding style for output and set to current
5035 $encodingStyle = 'http://schemas.xmlsoap.org/soap/encoding/';
5036 if(($direction == 'input') && isset($opData['output']['encodingStyle']) && ($opData['output']['encodingStyle'] != $encodingStyle)) {
5037 $encodingStyle = $opData['output']['encodingStyle'];
5038 $enc_style = $encodingStyle;
5041 // set input params
5042 $xml = '';
5043 if (isset($opData[$direction]['parts']) && sizeof($opData[$direction]['parts']) > 0) {
5045 $use = $opData[$direction]['use'];
5046 $this->debug('have ' . count($opData[$direction]['parts']) . ' part(s) to serialize');
5047 if (is_array($parameters)) {
5048 $parametersArrayType = $this->isArraySimpleOrStruct($parameters);
5049 $this->debug('have ' . count($parameters) . ' parameter(s) provided as ' . $parametersArrayType . ' to serialize');
5050 foreach($opData[$direction]['parts'] as $name => $type) {
5051 $this->debug('serializing part "'.$name.'" of type "'.$type.'"');
5052 // Track encoding style
5053 if (isset($opData[$direction]['encodingStyle']) && $encodingStyle != $opData[$direction]['encodingStyle']) {
5054 $encodingStyle = $opData[$direction]['encodingStyle'];
5055 $enc_style = $encodingStyle;
5056 } else {
5057 $enc_style = false;
5059 // NOTE: add error handling here
5060 // if serializeType returns false, then catch global error and fault
5061 if ($parametersArrayType == 'arraySimple') {
5062 $p = array_shift($parameters);
5063 $this->debug('calling serializeType w/indexed param');
5064 $xml .= $this->serializeType($name, $type, $p, $use, $enc_style);
5065 } elseif (isset($parameters[$name])) {
5066 $this->debug('calling serializeType w/named param');
5067 $xml .= $this->serializeType($name, $type, $parameters[$name], $use, $enc_style);
5068 } else {
5069 // TODO: only send nillable
5070 $this->debug('calling serializeType w/null param');
5071 $xml .= $this->serializeType($name, $type, null, $use, $enc_style);
5074 } else {
5075 $this->debug('no parameters passed.');
5078 $this->debug("serializeRPCParameters returning: $xml");
5079 return $xml;
5083 * serialize a PHP value according to a WSDL message definition
5085 * TODO
5086 * - multi-ref serialization
5087 * - validate PHP values against type definitions, return errors if invalid
5089 * @param string $ type name
5090 * @param mixed $ param value
5091 * @return mixed new param or false if initial value didn't validate
5092 * @access public
5093 * @deprecated
5095 function serializeParameters($operation, $direction, $parameters)
5097 $this->debug("in serializeParameters: operation=$operation, direction=$direction, XMLSchemaVersion=$this->XMLSchemaVersion");
5098 $this->appendDebug('parameters=' . $this->varDump($parameters));
5100 if ($direction != 'input' && $direction != 'output') {
5101 $this->debug('The value of the \$direction argument needs to be either "input" or "output"');
5102 $this->setError('The value of the \$direction argument needs to be either "input" or "output"');
5103 return false;
5105 if (!$opData = $this->getOperationData($operation)) {
5106 $this->debug('Unable to retrieve WSDL data for operation: ' . $operation);
5107 $this->setError('Unable to retrieve WSDL data for operation: ' . $operation);
5108 return false;
5110 $this->debug('opData:');
5111 $this->appendDebug($this->varDump($opData));
5113 // Get encoding style for output and set to current
5114 $encodingStyle = 'http://schemas.xmlsoap.org/soap/encoding/';
5115 if(($direction == 'input') && isset($opData['output']['encodingStyle']) && ($opData['output']['encodingStyle'] != $encodingStyle)) {
5116 $encodingStyle = $opData['output']['encodingStyle'];
5117 $enc_style = $encodingStyle;
5120 // set input params
5121 $xml = '';
5122 if (isset($opData[$direction]['parts']) && sizeof($opData[$direction]['parts']) > 0) {
5124 $use = $opData[$direction]['use'];
5125 $this->debug("use=$use");
5126 $this->debug('got ' . count($opData[$direction]['parts']) . ' part(s)');
5127 if (is_array($parameters)) {
5128 $parametersArrayType = $this->isArraySimpleOrStruct($parameters);
5129 $this->debug('have ' . $parametersArrayType . ' parameters');
5130 foreach($opData[$direction]['parts'] as $name => $type) {
5131 $this->debug('serializing part "'.$name.'" of type "'.$type.'"');
5132 // Track encoding style
5133 if(isset($opData[$direction]['encodingStyle']) && $encodingStyle != $opData[$direction]['encodingStyle']) {
5134 $encodingStyle = $opData[$direction]['encodingStyle'];
5135 $enc_style = $encodingStyle;
5136 } else {
5137 $enc_style = false;
5139 // NOTE: add error handling here
5140 // if serializeType returns false, then catch global error and fault
5141 if ($parametersArrayType == 'arraySimple') {
5142 $p = array_shift($parameters);
5143 $this->debug('calling serializeType w/indexed param');
5144 $xml .= $this->serializeType($name, $type, $p, $use, $enc_style);
5145 } elseif (isset($parameters[$name])) {
5146 $this->debug('calling serializeType w/named param');
5147 $xml .= $this->serializeType($name, $type, $parameters[$name], $use, $enc_style);
5148 } else {
5149 // TODO: only send nillable
5150 $this->debug('calling serializeType w/null param');
5151 $xml .= $this->serializeType($name, $type, null, $use, $enc_style);
5154 } else {
5155 $this->debug('no parameters passed.');
5158 $this->debug("serializeParameters returning: $xml");
5159 return $xml;
5163 * serializes a PHP value according a given type definition
5165 * @param string $name name of value (part or element)
5166 * @param string $type XML schema type of value (type or element)
5167 * @param mixed $value a native PHP value (parameter value)
5168 * @param string $use use for part (encoded|literal)
5169 * @param string $encodingStyle SOAP encoding style for the value (if different than the enclosing style)
5170 * @param boolean $unqualified a kludge for what should be XML namespace form handling
5171 * @return string value serialized as an XML string
5172 * @access private
5174 function serializeType($name, $type, $value, $use='encoded', $encodingStyle=false, $unqualified=false)
5176 $this->debug("in serializeType: name=$name, type=$type, use=$use, encodingStyle=$encodingStyle, unqualified=" . ($unqualified ? "unqualified" : "qualified"));
5177 $this->appendDebug("value=" . $this->varDump($value));
5178 if($use == 'encoded' && $encodingStyle) {
5179 $encodingStyle = ' SOAP-ENV:encodingStyle="' . $encodingStyle . '"';
5182 // if a soapval has been supplied, let its type override the WSDL
5183 if (is_object($value) && get_class($value) == 'soapval') {
5184 if ($value->type_ns) {
5185 $type = $value->type_ns . ':' . $value->type;
5186 $forceType = true;
5187 $this->debug("in serializeType: soapval overrides type to $type");
5188 } elseif ($value->type) {
5189 $type = $value->type;
5190 $forceType = true;
5191 $this->debug("in serializeType: soapval overrides type to $type");
5192 } else {
5193 $forceType = false;
5194 $this->debug("in serializeType: soapval does not override type");
5196 $attrs = $value->attributes;
5197 $value = $value->value;
5198 $this->debug("in serializeType: soapval overrides value to $value");
5199 if ($attrs) {
5200 if (!is_array($value)) {
5201 $value['!'] = $value;
5203 foreach ($attrs as $n => $v) {
5204 $value['!' . $n] = $v;
5206 $this->debug("in serializeType: soapval provides attributes");
5208 } else {
5209 $forceType = false;
5212 $xml = '';
5213 if (strpos($type, ':')) {
5214 $uqType = substr($type, strrpos($type, ':') + 1);
5215 $ns = substr($type, 0, strrpos($type, ':'));
5216 $this->debug("in serializeType: got a prefixed type: $uqType, $ns");
5217 if ($this->getNamespaceFromPrefix($ns)) {
5218 $ns = $this->getNamespaceFromPrefix($ns);
5219 $this->debug("in serializeType: expanded prefixed type: $uqType, $ns");
5222 if($ns == $this->XMLSchemaVersion || $ns == 'http://schemas.xmlsoap.org/soap/encoding/'){
5223 $this->debug('in serializeType: type namespace indicates XML Schema or SOAP Encoding type');
5224 if ($unqualified && $use == 'literal') {
5225 $elementNS = " xmlns=\"\"";
5226 } else {
5227 $elementNS = '';
5229 if (is_null($value)) {
5230 if ($use == 'literal') {
5231 // TODO: depends on minOccurs
5232 $xml = "<$name$elementNS/>";
5233 } else {
5234 // TODO: depends on nillable, which should be checked before calling this method
5235 $xml = "<$name$elementNS xsi:nil=\"true\" xsi:type=\"" . $this->getPrefixFromNamespace($ns) . ":$uqType\"/>";
5237 $this->debug("in serializeType: returning: $xml");
5238 return $xml;
5240 if ($uqType == 'Array') {
5241 // JBoss/Axis does this sometimes
5242 return $this->serialize_val($value, $name, false, false, false, false, $use);
5244 if ($uqType == 'boolean') {
5245 if ((is_string($value) && $value == 'false') || (! $value)) {
5246 $value = 'false';
5247 } else {
5248 $value = 'true';
5251 if ($uqType == 'string' && gettype($value) == 'string') {
5252 $value = $this->expandEntities($value);
5254 if (($uqType == 'long' || $uqType == 'unsignedLong') && gettype($value) == 'double') {
5255 $value = sprintf("%.0lf", $value);
5257 // it's a scalar
5258 // TODO: what about null/nil values?
5259 // check type isn't a custom type extending xmlschema namespace
5260 if (!$this->getTypeDef($uqType, $ns)) {
5261 if ($use == 'literal') {
5262 if ($forceType) {
5263 $xml = "<$name$elementNS xsi:type=\"" . $this->getPrefixFromNamespace($ns) . ":$uqType\">$value</$name>";
5264 } else {
5265 $xml = "<$name$elementNS>$value</$name>";
5267 } else {
5268 $xml = "<$name$elementNS xsi:type=\"" . $this->getPrefixFromNamespace($ns) . ":$uqType\"$encodingStyle>$value</$name>";
5270 $this->debug("in serializeType: returning: $xml");
5271 return $xml;
5273 $this->debug('custom type extends XML Schema or SOAP Encoding namespace (yuck)');
5274 } else if ($ns == 'http://xml.apache.org/xml-soap') {
5275 $this->debug('in serializeType: appears to be Apache SOAP type');
5276 if ($uqType == 'Map') {
5277 $tt_prefix = $this->getPrefixFromNamespace('http://xml.apache.org/xml-soap');
5278 if (! $tt_prefix) {
5279 $this->debug('in serializeType: Add namespace for Apache SOAP type');
5280 $tt_prefix = 'ns' . rand(1000, 9999);
5281 $this->namespaces[$tt_prefix] = 'http://xml.apache.org/xml-soap';
5282 // force this to be added to usedNamespaces
5283 $tt_prefix = $this->getPrefixFromNamespace('http://xml.apache.org/xml-soap');
5285 $contents = '';
5286 foreach($value as $k => $v) {
5287 $this->debug("serializing map element: key $k, value $v");
5288 $contents .= '<item>';
5289 $contents .= $this->serialize_val($k,'key',false,false,false,false,$use);
5290 $contents .= $this->serialize_val($v,'value',false,false,false,false,$use);
5291 $contents .= '</item>';
5293 if ($use == 'literal') {
5294 if ($forceType) {
5295 $xml = "<$name xsi:type=\"" . $tt_prefix . ":$uqType\">$contents</$name>";
5296 } else {
5297 $xml = "<$name>$contents</$name>";
5299 } else {
5300 $xml = "<$name xsi:type=\"" . $tt_prefix . ":$uqType\"$encodingStyle>$contents</$name>";
5302 $this->debug("in serializeType: returning: $xml");
5303 return $xml;
5305 $this->debug('in serializeType: Apache SOAP type, but only support Map');
5307 } else {
5308 // TODO: should the type be compared to types in XSD, and the namespace
5309 // set to XSD if the type matches?
5310 $this->debug("in serializeType: No namespace for type $type");
5311 $ns = '';
5312 $uqType = $type;
5314 if(!$typeDef = $this->getTypeDef($uqType, $ns)){
5315 $this->setError("$type ($uqType) is not a supported type.");
5316 $this->debug("in serializeType: $type ($uqType) is not a supported type.");
5317 return false;
5318 } else {
5319 $this->debug("in serializeType: found typeDef");
5320 $this->appendDebug('typeDef=' . $this->varDump($typeDef));
5322 $phpType = $typeDef['phpType'];
5323 $this->debug("in serializeType: uqType: $uqType, ns: $ns, phptype: $phpType, arrayType: " . (isset($typeDef['arrayType']) ? $typeDef['arrayType'] : '') );
5324 // if php type == struct, map value to the <all> element names
5325 if ($phpType == 'struct') {
5326 if (isset($typeDef['typeClass']) && $typeDef['typeClass'] == 'element') {
5327 $elementName = $uqType;
5328 if (isset($typeDef['form']) && ($typeDef['form'] == 'qualified')) {
5329 $elementNS = " xmlns=\"$ns\"";
5330 } else {
5331 $elementNS = " xmlns=\"\"";
5333 } else {
5334 $elementName = $name;
5335 if ($unqualified) {
5336 $elementNS = " xmlns=\"\"";
5337 } else {
5338 $elementNS = '';
5341 if (is_null($value)) {
5342 if ($use == 'literal') {
5343 // TODO: depends on minOccurs
5344 $xml = "<$elementName$elementNS/>";
5345 } else {
5346 $xml = "<$elementName$elementNS xsi:nil=\"true\" xsi:type=\"" . $this->getPrefixFromNamespace($ns) . ":$uqType\"/>";
5348 $this->debug("in serializeType: returning: $xml");
5349 return $xml;
5351 if (is_object($value)) {
5352 $value = get_object_vars($value);
5354 if (is_array($value)) {
5355 $elementAttrs = $this->serializeComplexTypeAttributes($typeDef, $value, $ns, $uqType);
5356 if ($use == 'literal') {
5357 if ($forceType) {
5358 $xml = "<$elementName$elementNS$elementAttrs xsi:type=\"" . $this->getPrefixFromNamespace($ns) . ":$uqType\">";
5359 } else {
5360 $xml = "<$elementName$elementNS$elementAttrs>";
5362 } else {
5363 $xml = "<$elementName$elementNS$elementAttrs xsi:type=\"" . $this->getPrefixFromNamespace($ns) . ":$uqType\"$encodingStyle>";
5366 $xml .= $this->serializeComplexTypeElements($typeDef, $value, $ns, $uqType, $use, $encodingStyle);
5367 $xml .= "</$elementName>";
5368 } else {
5369 $this->debug("in serializeType: phpType is struct, but value is not an array");
5370 $this->setError("phpType is struct, but value is not an array: see debug output for details");
5371 $xml = '';
5373 } elseif ($phpType == 'array') {
5374 if (isset($typeDef['form']) && ($typeDef['form'] == 'qualified')) {
5375 $elementNS = " xmlns=\"$ns\"";
5376 } else {
5377 if ($unqualified) {
5378 $elementNS = " xmlns=\"\"";
5379 } else {
5380 $elementNS = '';
5383 if (is_null($value)) {
5384 if ($use == 'literal') {
5385 // TODO: depends on minOccurs
5386 $xml = "<$name$elementNS/>";
5387 } else {
5388 $xml = "<$name$elementNS xsi:nil=\"true\" xsi:type=\"" .
5389 $this->getPrefixFromNamespace('http://schemas.xmlsoap.org/soap/encoding/') .
5390 ":Array\" " .
5391 $this->getPrefixFromNamespace('http://schemas.xmlsoap.org/soap/encoding/') .
5392 ':arrayType="' .
5393 $this->getPrefixFromNamespace($this->getPrefix($typeDef['arrayType'])) .
5394 ':' .
5395 $this->getLocalPart($typeDef['arrayType'])."[0]\"/>";
5397 $this->debug("in serializeType: returning: $xml");
5398 return $xml;
5400 if (isset($typeDef['multidimensional'])) {
5401 $nv = array();
5402 foreach($value as $v) {
5403 $cols = ',' . sizeof($v);
5404 $nv = array_merge($nv, $v);
5406 $value = $nv;
5407 } else {
5408 $cols = '';
5410 if (is_array($value) && sizeof($value) >= 1) {
5411 $rows = sizeof($value);
5412 $contents = '';
5413 foreach($value as $k => $v) {
5414 $this->debug("serializing array element: $k, $v of type: $typeDef[arrayType]");
5415 //if (strpos($typeDef['arrayType'], ':') ) {
5416 if (!in_array($typeDef['arrayType'],$this->typemap['http://www.w3.org/2001/XMLSchema'])) {
5417 $contents .= $this->serializeType('item', $typeDef['arrayType'], $v, $use);
5418 } else {
5419 $contents .= $this->serialize_val($v, 'item', $typeDef['arrayType'], null, $this->XMLSchemaVersion, false, $use);
5422 } else {
5423 $rows = 0;
5424 $contents = null;
5426 // TODO: for now, an empty value will be serialized as a zero element
5427 // array. Revisit this when coding the handling of null/nil values.
5428 if ($use == 'literal') {
5429 $xml = "<$name$elementNS>"
5430 .$contents
5431 ."</$name>";
5432 } else {
5433 $xml = "<$name$elementNS xsi:type=\"".$this->getPrefixFromNamespace('http://schemas.xmlsoap.org/soap/encoding/').':Array" '.
5434 $this->getPrefixFromNamespace('http://schemas.xmlsoap.org/soap/encoding/')
5435 .':arrayType="'
5436 .$this->getPrefixFromNamespace($this->getPrefix($typeDef['arrayType']))
5437 .":".$this->getLocalPart($typeDef['arrayType'])."[$rows$cols]\">"
5438 .$contents
5439 ."</$name>";
5441 } elseif ($phpType == 'scalar') {
5442 if (isset($typeDef['form']) && ($typeDef['form'] == 'qualified')) {
5443 $elementNS = " xmlns=\"$ns\"";
5444 } else {
5445 if ($unqualified) {
5446 $elementNS = " xmlns=\"\"";
5447 } else {
5448 $elementNS = '';
5451 if ($use == 'literal') {
5452 if ($forceType) {
5453 $xml = "<$name$elementNS xsi:type=\"" . $this->getPrefixFromNamespace($ns) . ":$uqType\">$value</$name>";
5454 } else {
5455 $xml = "<$name$elementNS>$value</$name>";
5457 } else {
5458 $xml = "<$name$elementNS xsi:type=\"" . $this->getPrefixFromNamespace($ns) . ":$uqType\"$encodingStyle>$value</$name>";
5461 $this->debug("in serializeType: returning: $xml");
5462 return $xml;
5466 * serializes the attributes for a complexType
5468 * @param array $typeDef our internal representation of an XML schema type (or element)
5469 * @param mixed $value a native PHP value (parameter value)
5470 * @param string $ns the namespace of the type
5471 * @param string $uqType the local part of the type
5472 * @return string value serialized as an XML string
5473 * @access private
5475 function serializeComplexTypeAttributes($typeDef, $value, $ns, $uqType) {
5476 $xml = '';
5477 if (isset($typeDef['attrs']) && is_array($typeDef['attrs'])) {
5478 $this->debug("serialize attributes for XML Schema type $ns:$uqType");
5479 if (is_array($value)) {
5480 $xvalue = $value;
5481 } elseif (is_object($value)) {
5482 $xvalue = get_object_vars($value);
5483 } else {
5484 $this->debug("value is neither an array nor an object for XML Schema type $ns:$uqType");
5485 $xvalue = array();
5487 foreach ($typeDef['attrs'] as $aName => $attrs) {
5488 if (isset($xvalue['!' . $aName])) {
5489 $xname = '!' . $aName;
5490 $this->debug("value provided for attribute $aName with key $xname");
5491 } elseif (isset($xvalue[$aName])) {
5492 $xname = $aName;
5493 $this->debug("value provided for attribute $aName with key $xname");
5494 } elseif (isset($attrs['default'])) {
5495 $xname = '!' . $aName;
5496 $xvalue[$xname] = $attrs['default'];
5497 $this->debug('use default value of ' . $xvalue[$aName] . ' for attribute ' . $aName);
5498 } else {
5499 $xname = '';
5500 $this->debug("no value provided for attribute $aName");
5502 if ($xname) {
5503 $xml .= " $aName=\"" . $this->expandEntities($xvalue[$xname]) . "\"";
5506 } else {
5507 $this->debug("no attributes to serialize for XML Schema type $ns:$uqType");
5509 if (isset($typeDef['extensionBase'])) {
5510 $ns = $this->getPrefix($typeDef['extensionBase']);
5511 $uqType = $this->getLocalPart($typeDef['extensionBase']);
5512 if ($this->getNamespaceFromPrefix($ns)) {
5513 $ns = $this->getNamespaceFromPrefix($ns);
5515 if ($typeDef = $this->getTypeDef($uqType, $ns)) {
5516 $this->debug("serialize attributes for extension base $ns:$uqType");
5517 $xml .= $this->serializeComplexTypeAttributes($typeDef, $value, $ns, $uqType);
5518 } else {
5519 $this->debug("extension base $ns:$uqType is not a supported type");
5522 return $xml;
5526 * serializes the elements for a complexType
5528 * @param array $typeDef our internal representation of an XML schema type (or element)
5529 * @param mixed $value a native PHP value (parameter value)
5530 * @param string $ns the namespace of the type
5531 * @param string $uqType the local part of the type
5532 * @param string $use use for part (encoded|literal)
5533 * @param string $encodingStyle SOAP encoding style for the value (if different than the enclosing style)
5534 * @return string value serialized as an XML string
5535 * @access private
5537 function serializeComplexTypeElements($typeDef, $value, $ns, $uqType, $use='encoded', $encodingStyle=false) {
5538 $xml = '';
5539 if (isset($typeDef['elements']) && is_array($typeDef['elements'])) {
5540 $this->debug("in serializeComplexTypeElements, serialize elements for XML Schema type $ns:$uqType");
5541 if (is_array($value)) {
5542 $xvalue = $value;
5543 } elseif (is_object($value)) {
5544 $xvalue = get_object_vars($value);
5545 } else {
5546 $this->debug("value is neither an array nor an object for XML Schema type $ns:$uqType");
5547 $xvalue = array();
5549 // toggle whether all elements are present - ideally should validate against schema
5550 if (count($typeDef['elements']) != count($xvalue)){
5551 $optionals = true;
5553 foreach ($typeDef['elements'] as $eName => $attrs) {
5554 if (!isset($xvalue[$eName])) {
5555 if (isset($attrs['default'])) {
5556 $xvalue[$eName] = $attrs['default'];
5557 $this->debug('use default value of ' . $xvalue[$eName] . ' for element ' . $eName);
5560 // if user took advantage of a minOccurs=0, then only serialize named parameters
5561 if (isset($optionals)
5562 && (!isset($xvalue[$eName]))
5563 && ( (!isset($attrs['nillable'])) || $attrs['nillable'] != 'true')
5565 if (isset($attrs['minOccurs']) && $attrs['minOccurs'] <> '0') {
5566 $this->debug("apparent error: no value provided for element $eName with minOccurs=" . $attrs['minOccurs']);
5568 // do nothing
5569 $this->debug("no value provided for complexType element $eName and element is not nillable, so serialize nothing");
5570 } else {
5571 // get value
5572 if (isset($xvalue[$eName])) {
5573 $v = $xvalue[$eName];
5574 } else {
5575 $v = null;
5577 if (isset($attrs['form'])) {
5578 $unqualified = ($attrs['form'] == 'unqualified');
5579 } else {
5580 $unqualified = false;
5582 if (isset($attrs['maxOccurs']) && ($attrs['maxOccurs'] == 'unbounded' || $attrs['maxOccurs'] > 1) && isset($v) && is_array($v) && $this->isArraySimpleOrStruct($v) == 'arraySimple') {
5583 $vv = $v;
5584 foreach ($vv as $k => $v) {
5585 if (isset($attrs['type']) || isset($attrs['ref'])) {
5586 // serialize schema-defined type
5587 $xml .= $this->serializeType($eName, isset($attrs['type']) ? $attrs['type'] : $attrs['ref'], $v, $use, $encodingStyle, $unqualified);
5588 } else {
5589 // serialize generic type (can this ever really happen?)
5590 $this->debug("calling serialize_val() for $v, $eName, false, false, false, false, $use");
5591 $xml .= $this->serialize_val($v, $eName, false, false, false, false, $use);
5594 } else {
5595 if (isset($attrs['type']) || isset($attrs['ref'])) {
5596 // serialize schema-defined type
5597 $xml .= $this->serializeType($eName, isset($attrs['type']) ? $attrs['type'] : $attrs['ref'], $v, $use, $encodingStyle, $unqualified);
5598 } else {
5599 // serialize generic type (can this ever really happen?)
5600 $this->debug("calling serialize_val() for $v, $eName, false, false, false, false, $use");
5601 $xml .= $this->serialize_val($v, $eName, false, false, false, false, $use);
5606 } else {
5607 $this->debug("no elements to serialize for XML Schema type $ns:$uqType");
5609 if (isset($typeDef['extensionBase'])) {
5610 $ns = $this->getPrefix($typeDef['extensionBase']);
5611 $uqType = $this->getLocalPart($typeDef['extensionBase']);
5612 if ($this->getNamespaceFromPrefix($ns)) {
5613 $ns = $this->getNamespaceFromPrefix($ns);
5615 if ($typeDef = $this->getTypeDef($uqType, $ns)) {
5616 $this->debug("serialize elements for extension base $ns:$uqType");
5617 $xml .= $this->serializeComplexTypeElements($typeDef, $value, $ns, $uqType, $use, $encodingStyle);
5618 } else {
5619 $this->debug("extension base $ns:$uqType is not a supported type");
5622 return $xml;
5626 * adds an XML Schema complex type to the WSDL types
5628 * @param string name
5629 * @param string typeClass (complexType|simpleType|attribute)
5630 * @param string phpType: currently supported are array and struct (php assoc array)
5631 * @param string compositor (all|sequence|choice)
5632 * @param string restrictionBase namespace:name (http://schemas.xmlsoap.org/soap/encoding/:Array)
5633 * @param array elements = array ( name => array(name=>'',type=>'') )
5634 * @param array attrs = array(array('ref'=>'SOAP-ENC:arrayType','wsdl:arrayType'=>'xsd:string[]'))
5635 * @param string arrayType: namespace:name (xsd:string)
5636 * @see xmlschema
5637 * @access public
5639 function addComplexType($name,$typeClass='complexType',$phpType='array',$compositor='',$restrictionBase='',$elements=array(),$attrs=array(),$arrayType='') {
5640 if (count($elements) > 0) {
5641 foreach($elements as $n => $e){
5642 // expand each element
5643 foreach ($e as $k => $v) {
5644 $k = strpos($k,':') ? $this->expandQname($k) : $k;
5645 $v = strpos($v,':') ? $this->expandQname($v) : $v;
5646 $ee[$k] = $v;
5648 $eElements[$n] = $ee;
5650 $elements = $eElements;
5653 if (count($attrs) > 0) {
5654 foreach($attrs as $n => $a){
5655 // expand each attribute
5656 foreach ($a as $k => $v) {
5657 $k = strpos($k,':') ? $this->expandQname($k) : $k;
5658 $v = strpos($v,':') ? $this->expandQname($v) : $v;
5659 $aa[$k] = $v;
5661 $eAttrs[$n] = $aa;
5663 $attrs = $eAttrs;
5666 $restrictionBase = strpos($restrictionBase,':') ? $this->expandQname($restrictionBase) : $restrictionBase;
5667 $arrayType = strpos($arrayType,':') ? $this->expandQname($arrayType) : $arrayType;
5669 $typens = isset($this->namespaces['types']) ? $this->namespaces['types'] : $this->namespaces['tns'];
5670 $this->schemas[$typens][0]->addComplexType($name,$typeClass,$phpType,$compositor,$restrictionBase,$elements,$attrs,$arrayType);
5674 * adds an XML Schema simple type to the WSDL types
5676 * @param string $name
5677 * @param string $restrictionBase namespace:name (http://schemas.xmlsoap.org/soap/encoding/:Array)
5678 * @param string $typeClass (should always be simpleType)
5679 * @param string $phpType (should always be scalar)
5680 * @param array $enumeration array of values
5681 * @see xmlschema
5682 * @access public
5684 function addSimpleType($name, $restrictionBase='', $typeClass='simpleType', $phpType='scalar', $enumeration=array()) {
5685 $restrictionBase = strpos($restrictionBase,':') ? $this->expandQname($restrictionBase) : $restrictionBase;
5687 $typens = isset($this->namespaces['types']) ? $this->namespaces['types'] : $this->namespaces['tns'];
5688 $this->schemas[$typens][0]->addSimpleType($name, $restrictionBase, $typeClass, $phpType, $enumeration);
5692 * adds an element to the WSDL types
5694 * @param array $attrs attributes that must include name and type
5695 * @see xmlschema
5696 * @access public
5698 function addElement($attrs) {
5699 $typens = isset($this->namespaces['types']) ? $this->namespaces['types'] : $this->namespaces['tns'];
5700 $this->schemas[$typens][0]->addElement($attrs);
5704 * register an operation with the server
5706 * @param string $name operation (method) name
5707 * @param array $in assoc array of input values: key = param name, value = param type
5708 * @param array $out assoc array of output values: key = param name, value = param type
5709 * @param string $namespace optional The namespace for the operation
5710 * @param string $soapaction optional The soapaction for the operation
5711 * @param string $style (rpc|document) optional The style for the operation Note: when 'document' is specified, parameter and return wrappers are created for you automatically
5712 * @param string $use (encoded|literal) optional The use for the parameters (cannot mix right now)
5713 * @param string $documentation optional The description to include in the WSDL
5714 * @param string $encodingStyle optional (usually 'http://schemas.xmlsoap.org/soap/encoding/' for encoded)
5715 * @access public
5717 function addOperation($name, $in = false, $out = false, $namespace = false, $soapaction = false, $style = 'rpc', $use = 'encoded', $documentation = '', $encodingStyle = ''){
5718 if ($use == 'encoded' && $encodingStyle == '') {
5719 $encodingStyle = 'http://schemas.xmlsoap.org/soap/encoding/';
5722 if ($style == 'document') {
5723 $elements = array();
5724 foreach ($in as $n => $t) {
5725 $elements[$n] = array('name' => $n, 'type' => $t);
5727 $this->addComplexType($name . 'RequestType', 'complexType', 'struct', 'all', '', $elements);
5728 $this->addElement(array('name' => $name, 'type' => $name . 'RequestType'));
5729 $in = array('parameters' => 'tns:' . $name);
5731 $elements = array();
5732 foreach ($out as $n => $t) {
5733 $elements[$n] = array('name' => $n, 'type' => $t);
5735 $this->addComplexType($name . 'ResponseType', 'complexType', 'struct', 'all', '', $elements);
5736 $this->addElement(array('name' => $name . 'Response', 'type' => $name . 'ResponseType'));
5737 $out = array('parameters' => 'tns:' . $name . 'Response');
5740 // get binding
5741 $this->bindings[ $this->serviceName . 'Binding' ]['operations'][$name] =
5742 array(
5743 'name' => $name,
5744 'binding' => $this->serviceName . 'Binding',
5745 'endpoint' => $this->endpoint,
5746 'soapAction' => $soapaction,
5747 'style' => $style,
5748 'input' => array(
5749 'use' => $use,
5750 'namespace' => $namespace,
5751 'encodingStyle' => $encodingStyle,
5752 'message' => $name . 'Request',
5753 'parts' => $in),
5754 'output' => array(
5755 'use' => $use,
5756 'namespace' => $namespace,
5757 'encodingStyle' => $encodingStyle,
5758 'message' => $name . 'Response',
5759 'parts' => $out),
5760 'namespace' => $namespace,
5761 'transport' => 'http://schemas.xmlsoap.org/soap/http',
5762 'documentation' => $documentation);
5763 // add portTypes
5764 // add messages
5765 if($in)
5767 foreach($in as $pName => $pType)
5769 if(strpos($pType,':')) {
5770 $pType = $this->getNamespaceFromPrefix($this->getPrefix($pType)).":".$this->getLocalPart($pType);
5772 $this->messages[$name.'Request'][$pName] = $pType;
5774 } else {
5775 $this->messages[$name.'Request']= '0';
5777 if($out)
5779 foreach($out as $pName => $pType)
5781 if(strpos($pType,':')) {
5782 $pType = $this->getNamespaceFromPrefix($this->getPrefix($pType)).":".$this->getLocalPart($pType);
5784 $this->messages[$name.'Response'][$pName] = $pType;
5786 } else {
5787 $this->messages[$name.'Response']= '0';
5789 return true;
5792 ?><?php
5798 * soap_parser class parses SOAP XML messages into native PHP values
5800 * @author Dietrich Ayala <dietrich@ganx4.com>
5801 * @version $Id$
5802 * @access public
5804 class soap_parser extends nusoap_base {
5806 var $xml = '';
5807 var $xml_encoding = '';
5808 var $method = '';
5809 var $root_struct = '';
5810 var $root_struct_name = '';
5811 var $root_struct_namespace = '';
5812 var $root_header = '';
5813 var $document = ''; // incoming SOAP body (text)
5814 // determines where in the message we are (envelope,header,body,method)
5815 var $status = '';
5816 var $position = 0;
5817 var $depth = 0;
5818 var $default_namespace = '';
5819 var $namespaces = array();
5820 var $message = array();
5821 var $parent = '';
5822 var $fault = false;
5823 var $fault_code = '';
5824 var $fault_str = '';
5825 var $fault_detail = '';
5826 var $depth_array = array();
5827 var $debug_flag = true;
5828 var $soapresponse = NULL;
5829 var $responseHeaders = ''; // incoming SOAP headers (text)
5830 var $body_position = 0;
5831 // for multiref parsing:
5832 // array of id => pos
5833 var $ids = array();
5834 // array of id => hrefs => pos
5835 var $multirefs = array();
5836 // toggle for auto-decoding element content
5837 var $decode_utf8 = true;
5840 * constructor that actually does the parsing
5842 * @param string $xml SOAP message
5843 * @param string $encoding character encoding scheme of message
5844 * @param string $method method for which XML is parsed (unused?)
5845 * @param string $decode_utf8 whether to decode UTF-8 to ISO-8859-1
5846 * @access public
5848 function soap_parser($xml,$encoding='UTF-8',$method='',$decode_utf8=true){
5849 parent::nusoap_base();
5850 $this->xml = $xml;
5851 $this->xml_encoding = $encoding;
5852 $this->method = $method;
5853 $this->decode_utf8 = $decode_utf8;
5855 // Check whether content has been read.
5856 if(!empty($xml)){
5857 // Check XML encoding
5858 $pos_xml = strpos($xml, '<?xml');
5859 if ($pos_xml !== FALSE) {
5860 $xml_decl = substr($xml, $pos_xml, strpos($xml, '?>', $pos_xml + 2) - $pos_xml + 1);
5861 if (preg_match("/encoding=[\"']([^\"']*)[\"']/", $xml_decl, $res)) {
5862 $xml_encoding = $res[1];
5863 if (strtoupper($xml_encoding) != $encoding) {
5864 $err = "Charset from HTTP Content-Type '" . $encoding . "' does not match encoding from XML declaration '" . $xml_encoding . "'";
5865 $this->debug($err);
5866 if ($encoding != 'ISO-8859-1' || strtoupper($xml_encoding) != 'UTF-8') {
5867 $this->setError($err);
5868 return;
5870 // when HTTP says ISO-8859-1 (the default) and XML says UTF-8 (the typical), assume the other endpoint is just sloppy and proceed
5871 } else {
5872 $this->debug('Charset from HTTP Content-Type matches encoding from XML declaration');
5874 } else {
5875 $this->debug('No encoding specified in XML declaration');
5877 } else {
5878 $this->debug('No XML declaration');
5880 $this->debug('Entering soap_parser(), length='.strlen($xml).', encoding='.$encoding);
5881 // Create an XML parser - why not xml_parser_create_ns?
5882 $this->parser = xml_parser_create($this->xml_encoding);
5883 // Set the options for parsing the XML data.
5884 //xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 1);
5885 xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING, 0);
5886 xml_parser_set_option($this->parser, XML_OPTION_TARGET_ENCODING, $this->xml_encoding);
5887 // Set the object for the parser.
5888 xml_set_object($this->parser, $this);
5889 // Set the element handlers for the parser.
5890 xml_set_element_handler($this->parser, 'start_element','end_element');
5891 xml_set_character_data_handler($this->parser,'character_data');
5893 // Parse the XML file.
5894 if(!xml_parse($this->parser,$xml,true)){
5895 // Display an error message.
5896 $err = sprintf('XML error parsing SOAP payload on line %d: %s',
5897 xml_get_current_line_number($this->parser),
5898 xml_error_string(xml_get_error_code($this->parser)));
5899 $this->debug($err);
5900 $this->debug("XML payload:\n" . $xml);
5901 $this->setError($err);
5902 } else {
5903 $this->debug('parsed successfully, found root struct: '.$this->root_struct.' of name '.$this->root_struct_name);
5904 // get final value
5905 $this->soapresponse = $this->message[$this->root_struct]['result'];
5906 // get header value: no, because this is documented as XML string
5907 // if($this->root_header != '' && isset($this->message[$this->root_header]['result'])){
5908 // $this->responseHeaders = $this->message[$this->root_header]['result'];
5909 // }
5910 // resolve hrefs/ids
5911 if(sizeof($this->multirefs) > 0){
5912 foreach($this->multirefs as $id => $hrefs){
5913 $this->debug('resolving multirefs for id: '.$id);
5914 $idVal = $this->buildVal($this->ids[$id]);
5915 if (is_array($idVal) && isset($idVal['!id'])) {
5916 unset($idVal['!id']);
5918 foreach($hrefs as $refPos => $ref){
5919 $this->debug('resolving href at pos '.$refPos);
5920 $this->multirefs[$id][$refPos] = $idVal;
5925 xml_parser_free($this->parser);
5926 } else {
5927 $this->debug('xml was empty, didn\'t parse!');
5928 $this->setError('xml was empty, didn\'t parse!');
5933 * start-element handler
5935 * @param resource $parser XML parser object
5936 * @param string $name element name
5937 * @param array $attrs associative array of attributes
5938 * @access private
5940 function start_element($parser, $name, $attrs) {
5941 // position in a total number of elements, starting from 0
5942 // update class level pos
5943 $pos = $this->position++;
5944 // and set mine
5945 $this->message[$pos] = array('pos' => $pos,'children'=>'','cdata'=>'');
5946 // depth = how many levels removed from root?
5947 // set mine as current global depth and increment global depth value
5948 $this->message[$pos]['depth'] = $this->depth++;
5950 // else add self as child to whoever the current parent is
5951 if($pos != 0){
5952 $this->message[$this->parent]['children'] .= '|'.$pos;
5954 // set my parent
5955 $this->message[$pos]['parent'] = $this->parent;
5956 // set self as current parent
5957 $this->parent = $pos;
5958 // set self as current value for this depth
5959 $this->depth_array[$this->depth] = $pos;
5960 // get element prefix
5961 if(strpos($name,':')){
5962 // get ns prefix
5963 $prefix = substr($name,0,strpos($name,':'));
5964 // get unqualified name
5965 $name = substr(strstr($name,':'),1);
5967 // set status
5968 if($name == 'Envelope'){
5969 $this->status = 'envelope';
5970 } elseif($name == 'Header'){
5971 $this->root_header = $pos;
5972 $this->status = 'header';
5973 } elseif($name == 'Body'){
5974 $this->status = 'body';
5975 $this->body_position = $pos;
5976 // set method
5977 } elseif($this->status == 'body' && $pos == ($this->body_position+1)){
5978 $this->status = 'method';
5979 $this->root_struct_name = $name;
5980 $this->root_struct = $pos;
5981 $this->message[$pos]['type'] = 'struct';
5982 $this->debug("found root struct $this->root_struct_name, pos $this->root_struct");
5984 // set my status
5985 $this->message[$pos]['status'] = $this->status;
5986 // set name
5987 $this->message[$pos]['name'] = htmlspecialchars($name);
5988 // set attrs
5989 $this->message[$pos]['attrs'] = $attrs;
5991 // loop through atts, logging ns and type declarations
5992 $attstr = '';
5993 foreach($attrs as $key => $value){
5994 $key_prefix = $this->getPrefix($key);
5995 $key_localpart = $this->getLocalPart($key);
5996 // if ns declarations, add to class level array of valid namespaces
5997 if($key_prefix == 'xmlns'){
5998 if(ereg('^http://www.w3.org/[0-9]{4}/XMLSchema$',$value)){
5999 $this->XMLSchemaVersion = $value;
6000 $this->namespaces['xsd'] = $this->XMLSchemaVersion;
6001 $this->namespaces['xsi'] = $this->XMLSchemaVersion.'-instance';
6003 $this->namespaces[$key_localpart] = $value;
6004 // set method namespace
6005 if($name == $this->root_struct_name){
6006 $this->methodNamespace = $value;
6008 // if it's a type declaration, set type
6009 } elseif($key_localpart == 'type'){
6010 if (isset($this->message[$pos]['type']) && $this->message[$pos]['type'] == 'array') {
6011 // do nothing: already processed arrayType
6012 } else {
6013 $value_prefix = $this->getPrefix($value);
6014 $value_localpart = $this->getLocalPart($value);
6015 $this->message[$pos]['type'] = $value_localpart;
6016 $this->message[$pos]['typePrefix'] = $value_prefix;
6017 if(isset($this->namespaces[$value_prefix])){
6018 $this->message[$pos]['type_namespace'] = $this->namespaces[$value_prefix];
6019 } else if(isset($attrs['xmlns:'.$value_prefix])) {
6020 $this->message[$pos]['type_namespace'] = $attrs['xmlns:'.$value_prefix];
6022 // should do something here with the namespace of specified type?
6024 } elseif($key_localpart == 'arrayType'){
6025 $this->message[$pos]['type'] = 'array';
6026 /* do arrayType ereg here
6027 [1] arrayTypeValue ::= atype asize
6028 [2] atype ::= QName rank*
6029 [3] rank ::= '[' (',')* ']'
6030 [4] asize ::= '[' length~ ']'
6031 [5] length ::= nextDimension* Digit+
6032 [6] nextDimension ::= Digit+ ','
6034 $expr = '([A-Za-z0-9_]+):([A-Za-z]+[A-Za-z0-9_]+)\[([0-9]+),?([0-9]*)\]';
6035 if(ereg($expr,$value,$regs)){
6036 $this->message[$pos]['typePrefix'] = $regs[1];
6037 $this->message[$pos]['arrayTypePrefix'] = $regs[1];
6038 if (isset($this->namespaces[$regs[1]])) {
6039 $this->message[$pos]['arrayTypeNamespace'] = $this->namespaces[$regs[1]];
6040 } else if (isset($attrs['xmlns:'.$regs[1]])) {
6041 $this->message[$pos]['arrayTypeNamespace'] = $attrs['xmlns:'.$regs[1]];
6043 $this->message[$pos]['arrayType'] = $regs[2];
6044 $this->message[$pos]['arraySize'] = $regs[3];
6045 $this->message[$pos]['arrayCols'] = $regs[4];
6047 // specifies nil value (or not)
6048 } elseif ($key_localpart == 'nil'){
6049 $this->message[$pos]['nil'] = ($value == 'true' || $value == '1');
6050 // some other attribute
6051 } elseif ($key != 'href' && $key != 'xmlns' && $key_localpart != 'encodingStyle' && $key_localpart != 'root') {
6052 $this->message[$pos]['xattrs']['!' . $key] = $value;
6055 if ($key == 'xmlns') {
6056 $this->default_namespace = $value;
6058 // log id
6059 if($key == 'id'){
6060 $this->ids[$value] = $pos;
6062 // root
6063 if($key_localpart == 'root' && $value == 1){
6064 $this->status = 'method';
6065 $this->root_struct_name = $name;
6066 $this->root_struct = $pos;
6067 $this->debug("found root struct $this->root_struct_name, pos $pos");
6069 // for doclit
6070 $attstr .= " $key=\"$value\"";
6072 // get namespace - must be done after namespace atts are processed
6073 if(isset($prefix)){
6074 $this->message[$pos]['namespace'] = $this->namespaces[$prefix];
6075 $this->default_namespace = $this->namespaces[$prefix];
6076 } else {
6077 $this->message[$pos]['namespace'] = $this->default_namespace;
6079 if($this->status == 'header'){
6080 if ($this->root_header != $pos) {
6081 $this->responseHeaders .= "<" . (isset($prefix) ? $prefix . ':' : '') . "$name$attstr>";
6083 } elseif($this->root_struct_name != ''){
6084 $this->document .= "<" . (isset($prefix) ? $prefix . ':' : '') . "$name$attstr>";
6089 * end-element handler
6091 * @param resource $parser XML parser object
6092 * @param string $name element name
6093 * @access private
6095 function end_element($parser, $name) {
6096 // position of current element is equal to the last value left in depth_array for my depth
6097 $pos = $this->depth_array[$this->depth--];
6099 // get element prefix
6100 if(strpos($name,':')){
6101 // get ns prefix
6102 $prefix = substr($name,0,strpos($name,':'));
6103 // get unqualified name
6104 $name = substr(strstr($name,':'),1);
6107 // build to native type
6108 if(isset($this->body_position) && $pos > $this->body_position){
6109 // deal w/ multirefs
6110 if(isset($this->message[$pos]['attrs']['href'])){
6111 // get id
6112 $id = substr($this->message[$pos]['attrs']['href'],1);
6113 // add placeholder to href array
6114 $this->multirefs[$id][$pos] = 'placeholder';
6115 // add set a reference to it as the result value
6116 $this->message[$pos]['result'] =& $this->multirefs[$id][$pos];
6117 // build complexType values
6118 } elseif($this->message[$pos]['children'] != ''){
6119 // if result has already been generated (struct/array)
6120 if(!isset($this->message[$pos]['result'])){
6121 $this->message[$pos]['result'] = $this->buildVal($pos);
6123 // build complexType values of attributes and possibly simpleContent
6124 } elseif (isset($this->message[$pos]['xattrs'])) {
6125 if (isset($this->message[$pos]['nil']) && $this->message[$pos]['nil']) {
6126 $this->message[$pos]['xattrs']['!'] = null;
6127 } elseif (isset($this->message[$pos]['cdata']) && trim($this->message[$pos]['cdata']) != '') {
6128 if (isset($this->message[$pos]['type'])) {
6129 $this->message[$pos]['xattrs']['!'] = $this->decodeSimple($this->message[$pos]['cdata'], $this->message[$pos]['type'], isset($this->message[$pos]['type_namespace']) ? $this->message[$pos]['type_namespace'] : '');
6130 } else {
6131 $parent = $this->message[$pos]['parent'];
6132 if (isset($this->message[$parent]['type']) && ($this->message[$parent]['type'] == 'array') && isset($this->message[$parent]['arrayType'])) {
6133 $this->message[$pos]['xattrs']['!'] = $this->decodeSimple($this->message[$pos]['cdata'], $this->message[$parent]['arrayType'], isset($this->message[$parent]['arrayTypeNamespace']) ? $this->message[$parent]['arrayTypeNamespace'] : '');
6134 } else {
6135 $this->message[$pos]['xattrs']['!'] = $this->message[$pos]['cdata'];
6139 $this->message[$pos]['result'] = $this->message[$pos]['xattrs'];
6140 // set value of simpleType (or nil complexType)
6141 } else {
6142 //$this->debug('adding data for scalar value '.$this->message[$pos]['name'].' of value '.$this->message[$pos]['cdata']);
6143 if (isset($this->message[$pos]['nil']) && $this->message[$pos]['nil']) {
6144 $this->message[$pos]['xattrs']['!'] = null;
6145 } elseif (isset($this->message[$pos]['type'])) {
6146 $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'] : '');
6147 } else {
6148 $parent = $this->message[$pos]['parent'];
6149 if (isset($this->message[$parent]['type']) && ($this->message[$parent]['type'] == 'array') && isset($this->message[$parent]['arrayType'])) {
6150 $this->message[$pos]['result'] = $this->decodeSimple($this->message[$pos]['cdata'], $this->message[$parent]['arrayType'], isset($this->message[$parent]['arrayTypeNamespace']) ? $this->message[$parent]['arrayTypeNamespace'] : '');
6151 } else {
6152 $this->message[$pos]['result'] = $this->message[$pos]['cdata'];
6156 /* add value to parent's result, if parent is struct/array
6157 $parent = $this->message[$pos]['parent'];
6158 if($this->message[$parent]['type'] != 'map'){
6159 if(strtolower($this->message[$parent]['type']) == 'array'){
6160 $this->message[$parent]['result'][] = $this->message[$pos]['result'];
6161 } else {
6162 $this->message[$parent]['result'][$this->message[$pos]['name']] = $this->message[$pos]['result'];
6169 // for doclit
6170 if($this->status == 'header'){
6171 if ($this->root_header != $pos) {
6172 $this->responseHeaders .= "</" . (isset($prefix) ? $prefix . ':' : '') . "$name>";
6174 } elseif($pos >= $this->root_struct){
6175 $this->document .= "</" . (isset($prefix) ? $prefix . ':' : '') . "$name>";
6177 // switch status
6178 if($pos == $this->root_struct){
6179 $this->status = 'body';
6180 $this->root_struct_namespace = $this->message[$pos]['namespace'];
6181 } elseif($name == 'Body'){
6182 $this->status = 'envelope';
6183 } elseif($name == 'Header'){
6184 $this->status = 'envelope';
6185 } elseif($name == 'Envelope'){
6188 // set parent back to my parent
6189 $this->parent = $this->message[$pos]['parent'];
6193 * element content handler
6195 * @param resource $parser XML parser object
6196 * @param string $data element content
6197 * @access private
6199 function character_data($parser, $data){
6200 $pos = $this->depth_array[$this->depth];
6201 if ($this->xml_encoding=='UTF-8'){
6202 // TODO: add an option to disable this for folks who want
6203 // raw UTF-8 that, e.g., might not map to iso-8859-1
6204 // TODO: this can also be handled with xml_parser_set_option($this->parser, XML_OPTION_TARGET_ENCODING, "ISO-8859-1");
6205 if($this->decode_utf8){
6206 $data = utf8_decode($data);
6209 $this->message[$pos]['cdata'] .= $data;
6210 // for doclit
6211 if($this->status == 'header'){
6212 $this->responseHeaders .= $data;
6213 } else {
6214 $this->document .= $data;
6219 * get the parsed message
6221 * @return mixed
6222 * @access public
6224 function get_response(){
6225 return $this->soapresponse;
6229 * get the parsed headers
6231 * @return string XML or empty if no headers
6232 * @access public
6234 function getHeaders(){
6235 return $this->responseHeaders;
6239 * decodes simple types into PHP variables
6241 * @param string $value value to decode
6242 * @param string $type XML type to decode
6243 * @param string $typens XML type namespace to decode
6244 * @return mixed PHP value
6245 * @access private
6247 function decodeSimple($value, $type, $typens) {
6248 // TODO: use the namespace!
6249 if ((!isset($type)) || $type == 'string' || $type == 'long' || $type == 'unsignedLong') {
6250 return (string) $value;
6252 if ($type == 'int' || $type == 'integer' || $type == 'short' || $type == 'byte') {
6253 return (int) $value;
6255 if ($type == 'float' || $type == 'double' || $type == 'decimal') {
6256 return (double) $value;
6258 if ($type == 'boolean') {
6259 if (strtolower($value) == 'false' || strtolower($value) == 'f') {
6260 return false;
6262 return (boolean) $value;
6264 if ($type == 'base64' || $type == 'base64Binary') {
6265 $this->debug('Decode base64 value');
6266 return base64_decode($value);
6268 // obscure numeric types
6269 if ($type == 'nonPositiveInteger' || $type == 'negativeInteger'
6270 || $type == 'nonNegativeInteger' || $type == 'positiveInteger'
6271 || $type == 'unsignedInt'
6272 || $type == 'unsignedShort' || $type == 'unsignedByte') {
6273 return (int) $value;
6275 // bogus: parser treats array with no elements as a simple type
6276 if ($type == 'array') {
6277 return array();
6279 // everything else
6280 return (string) $value;
6284 * builds response structures for compound values (arrays/structs)
6285 * and scalars
6287 * @param integer $pos position in node tree
6288 * @return mixed PHP value
6289 * @access private
6291 function buildVal($pos){
6292 if(!isset($this->message[$pos]['type'])){
6293 $this->message[$pos]['type'] = '';
6295 $this->debug('in buildVal() for '.$this->message[$pos]['name']."(pos $pos) of type ".$this->message[$pos]['type']);
6296 // if there are children...
6297 if($this->message[$pos]['children'] != ''){
6298 $this->debug('in buildVal, there are children');
6299 $children = explode('|',$this->message[$pos]['children']);
6300 array_shift($children); // knock off empty
6301 // md array
6302 if(isset($this->message[$pos]['arrayCols']) && $this->message[$pos]['arrayCols'] != ''){
6303 $r=0; // rowcount
6304 $c=0; // colcount
6305 foreach($children as $child_pos){
6306 $this->debug("in buildVal, got an MD array element: $r, $c");
6307 $params[$r][] = $this->message[$child_pos]['result'];
6308 $c++;
6309 if($c == $this->message[$pos]['arrayCols']){
6310 $c = 0;
6311 $r++;
6314 // array
6315 } elseif($this->message[$pos]['type'] == 'array' || $this->message[$pos]['type'] == 'Array'){
6316 $this->debug('in buildVal, adding array '.$this->message[$pos]['name']);
6317 foreach($children as $child_pos){
6318 $params[] = &$this->message[$child_pos]['result'];
6320 // apache Map type: java hashtable
6321 } elseif($this->message[$pos]['type'] == 'Map' && $this->message[$pos]['type_namespace'] == 'http://xml.apache.org/xml-soap'){
6322 $this->debug('in buildVal, Java Map '.$this->message[$pos]['name']);
6323 foreach($children as $child_pos){
6324 $kv = explode("|",$this->message[$child_pos]['children']);
6325 $params[$this->message[$kv[1]]['result']] = &$this->message[$kv[2]]['result'];
6327 // generic compound type
6328 //} elseif($this->message[$pos]['type'] == 'SOAPStruct' || $this->message[$pos]['type'] == 'struct') {
6329 } else {
6330 // Apache Vector type: treat as an array
6331 $this->debug('in buildVal, adding Java Vector or generic compound type '.$this->message[$pos]['name']);
6332 if ($this->message[$pos]['type'] == 'Vector' && $this->message[$pos]['type_namespace'] == 'http://xml.apache.org/xml-soap') {
6333 $notstruct = 1;
6334 } else {
6335 $notstruct = 0;
6338 foreach($children as $child_pos){
6339 if($notstruct){
6340 $params[] = &$this->message[$child_pos]['result'];
6341 } else {
6342 if (isset($params[$this->message[$child_pos]['name']])) {
6343 // de-serialize repeated element name into an array
6344 if ((!is_array($params[$this->message[$child_pos]['name']])) || (!isset($params[$this->message[$child_pos]['name']][0]))) {
6345 $params[$this->message[$child_pos]['name']] = array($params[$this->message[$child_pos]['name']]);
6347 $params[$this->message[$child_pos]['name']][] = &$this->message[$child_pos]['result'];
6348 } else {
6349 $params[$this->message[$child_pos]['name']] = &$this->message[$child_pos]['result'];
6354 if (isset($this->message[$pos]['xattrs'])) {
6355 $this->debug('in buildVal, handling attributes');
6356 foreach ($this->message[$pos]['xattrs'] as $n => $v) {
6357 $params[$n] = $v;
6360 // handle simpleContent
6361 if (isset($this->message[$pos]['cdata']) && trim($this->message[$pos]['cdata']) != '') {
6362 $this->debug('in buildVal, handling simpleContent');
6363 if (isset($this->message[$pos]['type'])) {
6364 $params['!'] = $this->decodeSimple($this->message[$pos]['cdata'], $this->message[$pos]['type'], isset($this->message[$pos]['type_namespace']) ? $this->message[$pos]['type_namespace'] : '');
6365 } else {
6366 $parent = $this->message[$pos]['parent'];
6367 if (isset($this->message[$parent]['type']) && ($this->message[$parent]['type'] == 'array') && isset($this->message[$parent]['arrayType'])) {
6368 $params['!'] = $this->decodeSimple($this->message[$pos]['cdata'], $this->message[$parent]['arrayType'], isset($this->message[$parent]['arrayTypeNamespace']) ? $this->message[$parent]['arrayTypeNamespace'] : '');
6369 } else {
6370 $params['!'] = $this->message[$pos]['cdata'];
6374 $ret = is_array($params) ? $params : array();
6375 $this->debug('in buildVal, return:');
6376 $this->appendDebug($this->varDump($ret));
6377 return $ret;
6378 } else {
6379 $this->debug('in buildVal, no children, building scalar');
6380 $cdata = isset($this->message[$pos]['cdata']) ? $this->message[$pos]['cdata'] : '';
6381 if (isset($this->message[$pos]['type'])) {
6382 $ret = $this->decodeSimple($cdata, $this->message[$pos]['type'], isset($this->message[$pos]['type_namespace']) ? $this->message[$pos]['type_namespace'] : '');
6383 $this->debug("in buildVal, return: $ret");
6384 return $ret;
6386 $parent = $this->message[$pos]['parent'];
6387 if (isset($this->message[$parent]['type']) && ($this->message[$parent]['type'] == 'array') && isset($this->message[$parent]['arrayType'])) {
6388 $ret = $this->decodeSimple($cdata, $this->message[$parent]['arrayType'], isset($this->message[$parent]['arrayTypeNamespace']) ? $this->message[$parent]['arrayTypeNamespace'] : '');
6389 $this->debug("in buildVal, return: $ret");
6390 return $ret;
6392 $ret = $this->message[$pos]['cdata'];
6393 $this->debug("in buildVal, return: $ret");
6394 return $ret;
6401 ?><?php
6407 * soap_client higher level class for easy usage.
6409 * usage:
6411 * // instantiate client with server info
6412 * $soap_client = new soap_client( string path [ ,boolean wsdl] );
6414 * // call method, get results
6415 * echo $soap_client->call( string methodname [ ,array parameters] );
6417 * // bye bye client
6418 * unset($soap_client);
6420 * @author Dietrich Ayala <dietrich@ganx4.com>
6421 * @version $Id$
6422 * @access public
6424 class soap_client extends nusoap_base {
6426 var $username = '';
6427 var $password = '';
6428 var $authtype = '';
6429 var $certRequest = array();
6430 var $requestHeaders = false; // SOAP headers in request (text)
6431 var $responseHeaders = ''; // SOAP headers from response (incomplete namespace resolution) (text)
6432 var $document = ''; // SOAP body response portion (incomplete namespace resolution) (text)
6433 var $endpoint;
6434 var $forceEndpoint = ''; // overrides WSDL endpoint
6435 var $proxyhost = '';
6436 var $proxyport = '';
6437 var $proxyusername = '';
6438 var $proxypassword = '';
6439 var $xml_encoding = ''; // character set encoding of incoming (response) messages
6440 var $http_encoding = false;
6441 var $timeout = 0; // HTTP connection timeout
6442 var $response_timeout = 30; // HTTP response timeout
6443 var $endpointType = ''; // soap|wsdl, empty for WSDL initialization error
6444 var $persistentConnection = false;
6445 var $defaultRpcParams = false; // This is no longer used
6446 var $request = ''; // HTTP request
6447 var $response = ''; // HTTP response
6448 var $responseData = ''; // SOAP payload of response
6449 var $cookies = array(); // Cookies from response or for request
6450 var $decode_utf8 = true; // toggles whether the parser decodes element content w/ utf8_decode()
6451 var $operations = array(); // WSDL operations, empty for WSDL initialization error
6454 * fault related variables
6457 * @var fault
6458 * @access public
6460 var $fault;
6462 * @var faultcode
6463 * @access public
6465 var $faultcode;
6467 * @var faultstring
6468 * @access public
6470 var $faultstring;
6472 * @var faultdetail
6473 * @access public
6475 var $faultdetail;
6478 * constructor
6480 * @param mixed $endpoint SOAP server or WSDL URL (string), or wsdl instance (object)
6481 * @param bool $wsdl optional, set to true if using WSDL
6482 * @param int $portName optional portName in WSDL document
6483 * @param string $proxyhost
6484 * @param string $proxyport
6485 * @param string $proxyusername
6486 * @param string $proxypassword
6487 * @param integer $timeout set the connection timeout
6488 * @param integer $response_timeout set the response timeout
6489 * @access public
6491 function soap_client($endpoint,$wsdl = false,$proxyhost = false,$proxyport = false,$proxyusername = false, $proxypassword = false, $timeout = 0, $response_timeout = 30){
6492 parent::nusoap_base();
6493 $this->endpoint = $endpoint;
6494 $this->proxyhost = $proxyhost;
6495 $this->proxyport = $proxyport;
6496 $this->proxyusername = $proxyusername;
6497 $this->proxypassword = $proxypassword;
6498 $this->timeout = $timeout;
6499 $this->response_timeout = $response_timeout;
6501 // make values
6502 if($wsdl){
6503 if (is_object($endpoint) && (get_class($endpoint) == 'wsdl')) {
6504 $this->wsdl = $endpoint;
6505 $this->endpoint = $this->wsdl->wsdl;
6506 $this->wsdlFile = $this->endpoint;
6507 $this->debug('existing wsdl instance created from ' . $this->endpoint);
6508 } else {
6509 $this->wsdlFile = $this->endpoint;
6511 // instantiate wsdl object and parse wsdl file
6512 $this->debug('instantiating wsdl class with doc: '.$endpoint);
6513 $this->wsdl =& new wsdl($this->wsdlFile,$this->proxyhost,$this->proxyport,$this->proxyusername,$this->proxypassword,$this->timeout,$this->response_timeout);
6515 $this->appendDebug($this->wsdl->getDebug());
6516 $this->wsdl->clearDebug();
6517 // catch errors
6518 if($errstr = $this->wsdl->getError()){
6519 $this->debug('got wsdl error: '.$errstr);
6520 $this->setError('wsdl error: '.$errstr);
6521 } elseif($this->operations = $this->wsdl->getOperations()){
6522 $this->debug( 'got '.count($this->operations).' operations from wsdl '.$this->wsdlFile);
6523 $this->endpointType = 'wsdl';
6524 } else {
6525 $this->debug( 'getOperations returned false');
6526 $this->setError('no operations defined in the WSDL document!');
6528 } else {
6529 $this->debug("instantiate SOAP with endpoint at $endpoint");
6530 $this->endpointType = 'soap';
6535 * calls method, returns PHP native type
6537 * @param string $method SOAP server URL or path
6538 * @param mixed $params An array, associative or simple, of the parameters
6539 * for the method call, or a string that is the XML
6540 * for the call. For rpc style, this call will
6541 * wrap the XML in a tag named after the method, as
6542 * well as the SOAP Envelope and Body. For document
6543 * style, this will only wrap with the Envelope and Body.
6544 * IMPORTANT: when using an array with document style,
6545 * in which case there
6546 * is really one parameter, the root of the fragment
6547 * used in the call, which encloses what programmers
6548 * normally think of parameters. A parameter array
6549 * *must* include the wrapper.
6550 * @param string $namespace optional method namespace (WSDL can override)
6551 * @param string $soapAction optional SOAPAction value (WSDL can override)
6552 * @param mixed $headers optional string of XML with SOAP header content, or array of soapval objects for SOAP headers
6553 * @param boolean $rpcParams optional (no longer used)
6554 * @param string $style optional (rpc|document) the style to use when serializing parameters (WSDL can override)
6555 * @param string $use optional (encoded|literal) the use when serializing parameters (WSDL can override)
6556 * @return mixed response from SOAP call
6557 * @access public
6559 function call($operation,$params=array(),$namespace='http://tempuri.org',$soapAction='',$headers=false,$rpcParams=null,$style='rpc',$use='encoded'){
6560 $this->operation = $operation;
6561 $this->fault = false;
6562 $this->setError('');
6563 $this->request = '';
6564 $this->response = '';
6565 $this->responseData = '';
6566 $this->faultstring = '';
6567 $this->faultcode = '';
6568 $this->opData = array();
6570 $this->debug("call: operation=$operation, namespace=$namespace, soapAction=$soapAction, rpcParams=$rpcParams, style=$style, use=$use, endpointType=$this->endpointType");
6571 $this->appendDebug('params=' . $this->varDump($params));
6572 $this->appendDebug('headers=' . $this->varDump($headers));
6573 if ($headers) {
6574 $this->requestHeaders = $headers;
6576 // serialize parameters
6577 if($this->endpointType == 'wsdl' && $opData = $this->getOperationData($operation)){
6578 // use WSDL for operation
6579 $this->opData = $opData;
6580 $this->debug("found operation");
6581 $this->appendDebug('opData=' . $this->varDump($opData));
6582 if (isset($opData['soapAction'])) {
6583 $soapAction = $opData['soapAction'];
6585 if (! $this->forceEndpoint) {
6586 $this->endpoint = $opData['endpoint'];
6587 } else {
6588 $this->endpoint = $this->forceEndpoint;
6590 $namespace = isset($opData['input']['namespace']) ? $opData['input']['namespace'] : $namespace;
6591 $style = $opData['style'];
6592 $use = $opData['input']['use'];
6593 // add ns to ns array
6594 if($namespace != '' && !isset($this->wsdl->namespaces[$namespace])){
6595 $nsPrefix = 'ns' . rand(1000, 9999);
6596 $this->wsdl->namespaces[$nsPrefix] = $namespace;
6598 $nsPrefix = $this->wsdl->getPrefixFromNamespace($namespace);
6599 // serialize payload
6600 if (is_string($params)) {
6601 $this->debug("serializing param string for WSDL operation $operation");
6602 $payload = $params;
6603 } elseif (is_array($params)) {
6604 $this->debug("serializing param array for WSDL operation $operation");
6605 $payload = $this->wsdl->serializeRPCParameters($operation,'input',$params);
6606 } else {
6607 $this->debug('params must be array or string');
6608 $this->setError('params must be array or string');
6609 return false;
6611 $usedNamespaces = $this->wsdl->usedNamespaces;
6612 if (isset($opData['input']['encodingStyle'])) {
6613 $encodingStyle = $opData['input']['encodingStyle'];
6614 } else {
6615 $encodingStyle = '';
6617 $this->appendDebug($this->wsdl->getDebug());
6618 $this->wsdl->clearDebug();
6619 if ($errstr = $this->wsdl->getError()) {
6620 $this->debug('got wsdl error: '.$errstr);
6621 $this->setError('wsdl error: '.$errstr);
6622 return false;
6624 } elseif($this->endpointType == 'wsdl') {
6625 // operation not in WSDL
6626 $this->appendDebug($this->wsdl->getDebug());
6627 $this->wsdl->clearDebug();
6628 $this->setError( 'operation '.$operation.' not present.');
6629 $this->debug("operation '$operation' not present.");
6630 return false;
6631 } else {
6632 // no WSDL
6633 //$this->namespaces['ns1'] = $namespace;
6634 $nsPrefix = 'ns' . rand(1000, 9999);
6635 // serialize
6636 $payload = '';
6637 if (is_string($params)) {
6638 $this->debug("serializing param string for operation $operation");
6639 $payload = $params;
6640 } elseif (is_array($params)) {
6641 $this->debug("serializing param array for operation $operation");
6642 foreach($params as $k => $v){
6643 $payload .= $this->serialize_val($v,$k,false,false,false,false,$use);
6645 } else {
6646 $this->debug('params must be array or string');
6647 $this->setError('params must be array or string');
6648 return false;
6650 $usedNamespaces = array();
6651 if ($use == 'encoded') {
6652 $encodingStyle = 'http://schemas.xmlsoap.org/soap/encoding/';
6653 } else {
6654 $encodingStyle = '';
6657 // wrap RPC calls with method element
6658 if ($style == 'rpc') {
6659 if ($use == 'literal') {
6660 $this->debug("wrapping RPC request with literal method element");
6661 if ($namespace) {
6662 $payload = "<$operation xmlns=\"$namespace\">" . $payload . "</$operation>";
6663 } else {
6664 $payload = "<$operation>" . $payload . "</$operation>";
6666 } else {
6667 $this->debug("wrapping RPC request with encoded method element");
6668 if ($namespace) {
6669 $payload = "<$nsPrefix:$operation xmlns:$nsPrefix=\"$namespace\">" .
6670 $payload .
6671 "</$nsPrefix:$operation>";
6672 } else {
6673 $payload = "<$operation>" .
6674 $payload .
6675 "</$operation>";
6679 // serialize envelope
6680 $soapmsg = $this->serializeEnvelope($payload,$this->requestHeaders,$usedNamespaces,$style,$use,$encodingStyle);
6681 $this->debug("endpoint=$this->endpoint, soapAction=$soapAction, namespace=$namespace, style=$style, use=$use, encodingStyle=$encodingStyle");
6682 $this->debug('SOAP message length=' . strlen($soapmsg) . ' contents (max 1000 bytes)=' . substr($soapmsg, 0, 1000));
6683 // send
6684 $return = $this->send($this->getHTTPBody($soapmsg),$soapAction,$this->timeout,$this->response_timeout);
6685 if($errstr = $this->getError()){
6686 $this->debug('Error: '.$errstr);
6687 return false;
6688 } else {
6689 $this->return = $return;
6690 $this->debug('sent message successfully and got a(n) '.gettype($return));
6691 $this->appendDebug('return=' . $this->varDump($return));
6693 // fault?
6694 if(is_array($return) && isset($return['faultcode'])){
6695 $this->debug('got fault');
6696 $this->setError($return['faultcode'].': '.$return['faultstring']);
6697 $this->fault = true;
6698 foreach($return as $k => $v){
6699 $this->$k = $v;
6700 $this->debug("$k = $v<br>");
6702 return $return;
6703 } elseif ($style == 'document') {
6704 // NOTE: if the response is defined to have multiple parts (i.e. unwrapped),
6705 // we are only going to return the first part here...sorry about that
6706 return $return;
6707 } else {
6708 // array of return values
6709 if(is_array($return)){
6710 // multiple 'out' parameters, which we return wrapped up
6711 // in the array
6712 if(sizeof($return) > 1){
6713 return $return;
6715 // single 'out' parameter (normally the return value)
6716 $return = array_shift($return);
6717 $this->debug('return shifted value: ');
6718 $this->appendDebug($this->varDump($return));
6719 return $return;
6720 // nothing returned (ie, echoVoid)
6721 } else {
6722 return "";
6729 * get available data pertaining to an operation
6731 * @param string $operation operation name
6732 * @return array array of data pertaining to the operation
6733 * @access public
6735 function getOperationData($operation){
6736 if(isset($this->operations[$operation])){
6737 return $this->operations[$operation];
6739 $this->debug("No data for operation: $operation");
6743 * send the SOAP message
6745 * Note: if the operation has multiple return values
6746 * the return value of this method will be an array
6747 * of those values.
6749 * @param string $msg a SOAPx4 soapmsg object
6750 * @param string $soapaction SOAPAction value
6751 * @param integer $timeout set connection timeout in seconds
6752 * @param integer $response_timeout set response timeout in seconds
6753 * @return mixed native PHP types.
6754 * @access private
6756 function send($msg, $soapaction = '', $timeout=0, $response_timeout=30) {
6757 $this->checkCookies();
6758 // detect transport
6759 switch(true){
6760 // http(s)
6761 case ereg('^http',$this->endpoint):
6762 $this->debug('transporting via HTTP');
6763 if($this->persistentConnection == true && is_object($this->persistentConnection)){
6764 $http =& $this->persistentConnection;
6765 } else {
6766 $http = new soap_transport_http($this->endpoint);
6767 if ($this->persistentConnection) {
6768 $http->usePersistentConnection();
6771 $http->setContentType($this->getHTTPContentType(), $this->getHTTPContentTypeCharset());
6772 $http->setSOAPAction($soapaction);
6773 if($this->proxyhost && $this->proxyport){
6774 $http->setProxy($this->proxyhost,$this->proxyport,$this->proxyusername,$this->proxypassword);
6776 if($this->authtype != '') {
6777 $http->setCredentials($this->username, $this->password, $this->authtype, array(), $this->certRequest);
6779 if($this->http_encoding != ''){
6780 $http->setEncoding($this->http_encoding);
6782 $this->debug('sending message, length='.strlen($msg));
6783 if(ereg('^http:',$this->endpoint)){
6784 //if(strpos($this->endpoint,'http:')){
6785 $this->responseData = $http->send($msg,$timeout,$response_timeout,$this->cookies);
6786 } elseif(ereg('^https',$this->endpoint)){
6787 //} elseif(strpos($this->endpoint,'https:')){
6788 //if(phpversion() == '4.3.0-dev'){
6789 //$response = $http->send($msg,$timeout,$response_timeout);
6790 //$this->request = $http->outgoing_payload;
6791 //$this->response = $http->incoming_payload;
6792 //} else
6793 $this->responseData = $http->sendHTTPS($msg,$timeout,$response_timeout,$this->cookies);
6794 } else {
6795 $this->setError('no http/s in endpoint url');
6797 $this->request = $http->outgoing_payload;
6798 $this->response = $http->incoming_payload;
6799 $this->appendDebug($http->getDebug());
6800 $this->UpdateCookies($http->incoming_cookies);
6802 // save transport object if using persistent connections
6803 if ($this->persistentConnection) {
6804 $http->clearDebug();
6805 if (!is_object($this->persistentConnection)) {
6806 $this->persistentConnection = $http;
6810 if($err = $http->getError()){
6811 $this->setError('HTTP Error: '.$err);
6812 return false;
6813 } elseif($this->getError()){
6814 return false;
6815 } else {
6816 $this->debug('got response, length='. strlen($this->responseData).' type='.$http->incoming_headers['content-type']);
6817 return $this->parseResponse($http->incoming_headers, $this->responseData);
6819 break;
6820 default:
6821 $this->setError('no transport found, or selected transport is not yet supported!');
6822 return false;
6823 break;
6828 * processes SOAP message returned from server
6830 * @param array $headers The HTTP headers
6831 * @param string $data unprocessed response data from server
6832 * @return mixed value of the message, decoded into a PHP type
6833 * @access private
6835 function parseResponse($headers, $data) {
6836 $this->debug('Entering parseResponse() for data of length ' . strlen($data) . ' and type ' . $headers['content-type']);
6837 if (!strstr($headers['content-type'], 'text/xml')) {
6838 $this->setError('Response not of type text/xml');
6839 return false;
6841 if (strpos($headers['content-type'], '=')) {
6842 $enc = str_replace('"', '', substr(strstr($headers["content-type"], '='), 1));
6843 $this->debug('Got response encoding: ' . $enc);
6844 if(eregi('^(ISO-8859-1|US-ASCII|UTF-8)$',$enc)){
6845 $this->xml_encoding = strtoupper($enc);
6846 } else {
6847 $this->xml_encoding = 'US-ASCII';
6849 } else {
6850 // should be US-ASCII for HTTP 1.0 or ISO-8859-1 for HTTP 1.1
6851 $this->xml_encoding = 'ISO-8859-1';
6853 $this->debug('Use encoding: ' . $this->xml_encoding . ' when creating soap_parser');
6854 $parser = new soap_parser($data,$this->xml_encoding,$this->operation,$this->decode_utf8);
6855 // add parser debug data to our debug
6856 $this->appendDebug($parser->getDebug());
6857 // if parse errors
6858 if($errstr = $parser->getError()){
6859 $this->setError( $errstr);
6860 // destroy the parser object
6861 unset($parser);
6862 return false;
6863 } else {
6864 // get SOAP headers
6865 $this->responseHeaders = $parser->getHeaders();
6866 // get decoded message
6867 $return = $parser->get_response();
6868 // add document for doclit support
6869 $this->document = $parser->document;
6870 // destroy the parser object
6871 unset($parser);
6872 // return decode message
6873 return $return;
6878 * sets the SOAP endpoint, which can override WSDL
6880 * @param $endpoint string The endpoint URL to use, or empty string or false to prevent override
6881 * @access public
6883 function setEndpoint($endpoint) {
6884 $this->forceEndpoint = $endpoint;
6888 * set the SOAP headers
6890 * @param $headers mixed String of XML with SOAP header content, or array of soapval objects for SOAP headers
6891 * @access public
6893 function setHeaders($headers){
6894 $this->requestHeaders = $headers;
6898 * get the SOAP response headers (namespace resolution incomplete)
6900 * @return string
6901 * @access public
6903 function getHeaders(){
6904 return $this->responseHeaders;
6908 * set proxy info here
6910 * @param string $proxyhost
6911 * @param string $proxyport
6912 * @param string $proxyusername
6913 * @param string $proxypassword
6914 * @access public
6916 function setHTTPProxy($proxyhost, $proxyport, $proxyusername = '', $proxypassword = '') {
6917 $this->proxyhost = $proxyhost;
6918 $this->proxyport = $proxyport;
6919 $this->proxyusername = $proxyusername;
6920 $this->proxypassword = $proxypassword;
6924 * if authenticating, set user credentials here
6926 * @param string $username
6927 * @param string $password
6928 * @param string $authtype (basic|digest|certificate)
6929 * @param array $certRequest (keys must be cainfofile (optional), sslcertfile, sslkeyfile, passphrase, verifypeer (optional), verifyhost (optional): see corresponding options in cURL docs)
6930 * @access public
6932 function setCredentials($username, $password, $authtype = 'basic', $certRequest = array()) {
6933 $this->username = $username;
6934 $this->password = $password;
6935 $this->authtype = $authtype;
6936 $this->certRequest = $certRequest;
6940 * use HTTP encoding
6942 * @param string $enc
6943 * @access public
6945 function setHTTPEncoding($enc='gzip, deflate'){
6946 $this->http_encoding = $enc;
6950 * use HTTP persistent connections if possible
6952 * @access public
6954 function useHTTPPersistentConnection(){
6955 $this->persistentConnection = true;
6959 * gets the default RPC parameter setting.
6960 * If true, default is that call params are like RPC even for document style.
6961 * Each call() can override this value.
6963 * This is no longer used.
6965 * @return boolean
6966 * @access public
6967 * @deprecated
6969 function getDefaultRpcParams() {
6970 return $this->defaultRpcParams;
6974 * sets the default RPC parameter setting.
6975 * If true, default is that call params are like RPC even for document style
6976 * Each call() can override this value.
6978 * This is no longer used.
6980 * @param boolean $rpcParams
6981 * @access public
6982 * @deprecated
6984 function setDefaultRpcParams($rpcParams) {
6985 $this->defaultRpcParams = $rpcParams;
6989 * dynamically creates an instance of a proxy class,
6990 * allowing user to directly call methods from wsdl
6992 * @return object soap_proxy object
6993 * @access public
6995 function getProxy(){
6996 $r = rand();
6997 $evalStr = $this->_getProxyClassCode($r);
6998 //$this->debug("proxy class: $evalStr";
6999 // eval the class
7000 eval($evalStr);
7001 // instantiate proxy object
7002 eval("\$proxy = new soap_proxy_$r('');");
7003 // transfer current wsdl data to the proxy thereby avoiding parsing the wsdl twice
7004 $proxy->endpointType = 'wsdl';
7005 $proxy->wsdlFile = $this->wsdlFile;
7006 $proxy->wsdl = $this->wsdl;
7007 $proxy->operations = $this->operations;
7008 $proxy->defaultRpcParams = $this->defaultRpcParams;
7009 // transfer other state
7010 $proxy->username = $this->username;
7011 $proxy->password = $this->password;
7012 $proxy->authtype = $this->authtype;
7013 $proxy->proxyhost = $this->proxyhost;
7014 $proxy->proxyport = $this->proxyport;
7015 $proxy->proxyusername = $this->proxyusername;
7016 $proxy->proxypassword = $this->proxypassword;
7017 $proxy->timeout = $this->timeout;
7018 $proxy->response_timeout = $this->response_timeout;
7019 $proxy->http_encoding = $this->http_encoding;
7020 $proxy->persistentConnection = $this->persistentConnection;
7021 $proxy->requestHeaders = $this->requestHeaders;
7022 $proxy->soap_defencoding = $this->soap_defencoding;
7023 $proxy->endpoint = $this->endpoint;
7024 $proxy->forceEndpoint = $this->forceEndpoint;
7025 return $proxy;
7029 * dynamically creates proxy class code
7031 * @return string PHP/NuSOAP code for the proxy class
7032 * @access private
7034 function _getProxyClassCode($r) {
7035 if ($this->endpointType != 'wsdl') {
7036 $evalStr = 'A proxy can only be created for a WSDL client';
7037 $this->setError($evalStr);
7038 return $evalStr;
7040 $evalStr = '';
7041 foreach ($this->operations as $operation => $opData) {
7042 if ($operation != '') {
7043 // create param string and param comment string
7044 if (sizeof($opData['input']['parts']) > 0) {
7045 $paramStr = '';
7046 $paramArrayStr = '';
7047 $paramCommentStr = '';
7048 foreach ($opData['input']['parts'] as $name => $type) {
7049 $paramStr .= "\$$name, ";
7050 $paramArrayStr .= "'$name' => \$$name, ";
7051 $paramCommentStr .= "$type \$$name, ";
7053 $paramStr = substr($paramStr, 0, strlen($paramStr)-2);
7054 $paramArrayStr = substr($paramArrayStr, 0, strlen($paramArrayStr)-2);
7055 $paramCommentStr = substr($paramCommentStr, 0, strlen($paramCommentStr)-2);
7056 } else {
7057 $paramStr = '';
7058 $paramArrayStr = '';
7059 $paramCommentStr = 'void';
7061 $opData['namespace'] = !isset($opData['namespace']) ? 'http://testuri.com' : $opData['namespace'];
7062 $evalStr .= "// $paramCommentStr
7063 function " . str_replace('.', '__', $operation) . "($paramStr) {
7064 \$params = array($paramArrayStr);
7065 return \$this->call('$operation', \$params, '".$opData['namespace']."', '".(isset($opData['soapAction']) ? $opData['soapAction'] : '')."');
7068 unset($paramStr);
7069 unset($paramCommentStr);
7072 $evalStr = 'class soap_proxy_'.$r.' extends soap_client {
7073 '.$evalStr.'
7075 return $evalStr;
7079 * dynamically creates proxy class code
7081 * @return string PHP/NuSOAP code for the proxy class
7082 * @access public
7084 function getProxyClassCode() {
7085 $r = rand();
7086 return $this->_getProxyClassCode($r);
7090 * gets the HTTP body for the current request.
7092 * @param string $soapmsg The SOAP payload
7093 * @return string The HTTP body, which includes the SOAP payload
7094 * @access private
7096 function getHTTPBody($soapmsg) {
7097 return $soapmsg;
7101 * gets the HTTP content type for the current request.
7103 * Note: getHTTPBody must be called before this.
7105 * @return string the HTTP content type for the current request.
7106 * @access private
7108 function getHTTPContentType() {
7109 return 'text/xml';
7113 * gets the HTTP content type charset for the current request.
7114 * returns false for non-text content types.
7116 * Note: getHTTPBody must be called before this.
7118 * @return string the HTTP content type charset for the current request.
7119 * @access private
7121 function getHTTPContentTypeCharset() {
7122 return $this->soap_defencoding;
7126 * whether or not parser should decode utf8 element content
7128 * @return always returns true
7129 * @access public
7131 function decodeUTF8($bool){
7132 $this->decode_utf8 = $bool;
7133 return true;
7137 * adds a new Cookie into $this->cookies array
7139 * @param string $name Cookie Name
7140 * @param string $value Cookie Value
7141 * @return if cookie-set was successful returns true, else false
7142 * @access public
7144 function setCookie($name, $value) {
7145 if (strlen($name) == 0) {
7146 return false;
7148 $this->cookies[] = array('name' => $name, 'value' => $value);
7149 return true;
7153 * gets all Cookies
7155 * @return array with all internal cookies
7156 * @access public
7158 function getCookies() {
7159 return $this->cookies;
7163 * checks all Cookies and delete those which are expired
7165 * @return always return true
7166 * @access private
7168 function checkCookies() {
7169 if (sizeof($this->cookies) == 0) {
7170 return true;
7172 $this->debug('checkCookie: check ' . sizeof($this->cookies) . ' cookies');
7173 $curr_cookies = $this->cookies;
7174 $this->cookies = array();
7175 foreach ($curr_cookies as $cookie) {
7176 if (! is_array($cookie)) {
7177 $this->debug('Remove cookie that is not an array');
7178 continue;
7180 if ((isset($cookie['expires'])) && (! empty($cookie['expires']))) {
7181 if (strtotime($cookie['expires']) > time()) {
7182 $this->cookies[] = $cookie;
7183 } else {
7184 $this->debug('Remove expired cookie ' . $cookie['name']);
7186 } else {
7187 $this->cookies[] = $cookie;
7190 $this->debug('checkCookie: '.sizeof($this->cookies).' cookies left in array');
7191 return true;
7195 * updates the current cookies with a new set
7197 * @param array $cookies new cookies with which to update current ones
7198 * @return always return true
7199 * @access private
7201 function UpdateCookies($cookies) {
7202 if (sizeof($this->cookies) == 0) {
7203 // no existing cookies: take whatever is new
7204 if (sizeof($cookies) > 0) {
7205 $this->debug('Setting new cookie(s)');
7206 $this->cookies = $cookies;
7208 return true;
7210 if (sizeof($cookies) == 0) {
7211 // no new cookies: keep what we've got
7212 return true;
7214 // merge
7215 foreach ($cookies as $newCookie) {
7216 if (!is_array($newCookie)) {
7217 continue;
7219 if ((!isset($newCookie['name'])) || (!isset($newCookie['value']))) {
7220 continue;
7222 $newName = $newCookie['name'];
7224 $found = false;
7225 for ($i = 0; $i < count($this->cookies); $i++) {
7226 $cookie = $this->cookies[$i];
7227 if (!is_array($cookie)) {
7228 continue;
7230 if (!isset($cookie['name'])) {
7231 continue;
7233 if ($newName != $cookie['name']) {
7234 continue;
7236 $newDomain = isset($newCookie['domain']) ? $newCookie['domain'] : 'NODOMAIN';
7237 $domain = isset($cookie['domain']) ? $cookie['domain'] : 'NODOMAIN';
7238 if ($newDomain != $domain) {
7239 continue;
7241 $newPath = isset($newCookie['path']) ? $newCookie['path'] : 'NOPATH';
7242 $path = isset($cookie['path']) ? $cookie['path'] : 'NOPATH';
7243 if ($newPath != $path) {
7244 continue;
7246 $this->cookies[$i] = $newCookie;
7247 $found = true;
7248 $this->debug('Update cookie ' . $newName . '=' . $newCookie['value']);
7249 break;
7251 if (! $found) {
7252 $this->debug('Add cookie ' . $newName . '=' . $newCookie['value']);
7253 $this->cookies[] = $newCookie;
7256 return true;