\n---SENDING---\n" . htmlentities($op) . "\n---END---\n"; // let the client see this now in case http times out... flush(); } if($timeout>0) { $fp=@fsockopen($connectserver, $connectport, $this->errno, $this->errstr, $timeout); } else { $fp=@fsockopen($connectserver, $connectport, $this->errno, $this->errstr); } if($fp) { if($timeout>0 && function_exists('stream_set_timeout')) { stream_set_timeout($fp, $timeout); } } else { $this->errstr='Connect error: '.$this->errstr; $r=new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['http_error'], $this->errstr . ' (' . $this->errno . ')'); return $r; } if(!fputs($fp, $op, strlen($op))) { fclose($fp); $this->errstr='Write error'; $r=new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['http_error'], $this->errstr); return $r; } else { // reset errno and errstr on successful socket connection $this->errstr = ''; } // G. Giunta 2005/10/24: close socket before parsing. // should yield slightly better execution times, and make easier recursive calls (e.g. to follow http redirects) $ipd=''; do { // shall we check for $data === FALSE? // as per the manual, it signals an error $ipd.=fread($fp, 32768); } while(!feof($fp)); fclose($fp); $r =& $msg->parseResponse($ipd, false, $this->return_type); return $r; } /** * @access private */ function &sendPayloadHTTPS($msg, $server, $port, $timeout=0, $username='', $password='', $authtype=1, $cert='',$certpass='', $cacert='', $cacertdir='', $proxyhost='', $proxyport=0, $proxyusername='', $proxypassword='', $proxyauthtype=1, $keepalive=false, $key='', $keypass='') { $r =& $this->sendPayloadCURL($msg, $server, $port, $timeout, $username, $password, $authtype, $cert, $certpass, $cacert, $cacertdir, $proxyhost, $proxyport, $proxyusername, $proxypassword, $proxyauthtype, 'https', $keepalive, $key, $keypass); return $r; } /** * Contributed by Justin Miller
\n---SENDING---\n" . htmlentities($payload) . "\n---END---\n"; // let the client see this now in case http times out... flush(); } if(!$keepalive || !$this->xmlrpc_curl_handle) { $curl = curl_init($method . '://' . $server . ':' . $port . $this->path); if($keepalive) { $this->xmlrpc_curl_handle = $curl; } } else { $curl = $this->xmlrpc_curl_handle; } // results into variable curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); if($this->debug) { curl_setopt($curl, CURLOPT_VERBOSE, 1); } curl_setopt($curl, CURLOPT_USERAGENT, $this->user_agent); // required for XMLRPC: post the data curl_setopt($curl, CURLOPT_POST, 1); // the data curl_setopt($curl, CURLOPT_POSTFIELDS, $payload); // return the header too curl_setopt($curl, CURLOPT_HEADER, 1); // NB: if we set an empty string, CURL will add http header indicating // ALL methods it is supporting. This is possibly a better option than // letting the user tell what curl can / cannot do... if(is_array($this->accepted_compression) && count($this->accepted_compression)) { //curl_setopt($curl, CURLOPT_ENCODING, implode(',', $this->accepted_compression)); // empty string means 'any supported by CURL' (shall we catch errors in case CURLOPT_SSLKEY undefined ?) if (count($this->accepted_compression) == 1) { curl_setopt($curl, CURLOPT_ENCODING, $this->accepted_compression[0]); } else curl_setopt($curl, CURLOPT_ENCODING, ''); } // extra headers $headers = array('Content-Type: ' . $msg->content_type , 'Accept-Charset: ' . implode(',', $this->accepted_charset_encodings)); // if no keepalive is wanted, let the server know it in advance if(!$keepalive) { $headers[] = 'Connection: close'; } // request compression header if($encoding_hdr) { $headers[] = $encoding_hdr; } curl_setopt($curl, CURLOPT_HTTPHEADER, $headers); // timeout is borked if($timeout) { curl_setopt($curl, CURLOPT_TIMEOUT, $timeout == 1 ? 1 : $timeout - 1); } if($username && $password) { curl_setopt($curl, CURLOPT_USERPWD, $username.':'.$password); if (defined('CURLOPT_HTTPAUTH')) { curl_setopt($curl, CURLOPT_HTTPAUTH, $authtype); } else if ($authtype != 1) { error_log('XML-RPC: '.__METHOD__.': warning. Only Basic auth is supported by the current PHP/curl install'); } } if($method == 'https') { // set cert file if($cert) { curl_setopt($curl, CURLOPT_SSLCERT, $cert); } // set cert password if($certpass) { curl_setopt($curl, CURLOPT_SSLCERTPASSWD, $certpass); } // whether to verify remote host's cert curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, $this->verifypeer); // set ca certificates file/dir if($cacert) { curl_setopt($curl, CURLOPT_CAINFO, $cacert); } if($cacertdir) { curl_setopt($curl, CURLOPT_CAPATH, $cacertdir); } // set key file (shall we catch errors in case CURLOPT_SSLKEY undefined ?) if($key) { curl_setopt($curl, CURLOPT_SSLKEY, $key); } // set key password (shall we catch errors in case CURLOPT_SSLKEY undefined ?) if($keypass) { curl_setopt($curl, CURLOPT_SSLKEYPASSWD, $keypass); } // Upgrade transparently to more stringent check for versions of php which do not support otherwise. // Doing it in constructor would be cleaner; doing it here saves us a couple of function calls if($this->verifyhost == 1 && $info = curl_version() && version_compare($info['version'], '7.28.1') >= 0) { $this->verifyhost = 2; } // whether to verify cert's common name (CN); 0 for no, 1 to verify that it exists, and 2 to verify that it matches the hostname used curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, $this->verifyhost); } // proxy info if($proxyhost) { if($proxyport == 0) { $proxyport = 8080; // NB: even for HTTPS, local connection is on port 8080 } curl_setopt($curl, CURLOPT_PROXY, $proxyhost.':'.$proxyport); //curl_setopt($curl, CURLOPT_PROXYPORT,$proxyport); if($proxyusername) { curl_setopt($curl, CURLOPT_PROXYUSERPWD, $proxyusername.':'.$proxypassword); if (defined('CURLOPT_PROXYAUTH')) { curl_setopt($curl, CURLOPT_PROXYAUTH, $proxyauthtype); } else if ($proxyauthtype != 1) { error_log('XML-RPC: '.__METHOD__.': warning. Only Basic auth to proxy is supported by the current PHP/curl install'); } } } // NB: should we build cookie http headers by hand rather than let CURL do it? // the following code does not honour 'expires', 'path' and 'domain' cookie attributes // set to client obj the the user... if (count($this->cookies)) { $cookieheader = ''; foreach ($this->cookies as $name => $cookie) { $cookieheader .= $name . '=' . $cookie['value'] . '; '; } curl_setopt($curl, CURLOPT_COOKIE, substr($cookieheader, 0, -2)); } foreach ($this->extracurlopts as $opt => $val) { curl_setopt($curl, $opt, $val); } $result = curl_exec($curl); if ($this->debug > 1) { print "
\n---CURL INFO---\n"; foreach(curl_getinfo($curl) as $name => $val) { if (is_array($val)) { $val = implode("\n", $val); } print $name . ': ' . htmlentities($val) . "\n"; } print "---END---\n"; } if(!$result) /// @todo we should use a better check here - what if we get back '' or '0'? { $this->errstr='no response'; $resp=new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['curl_fail'], $GLOBALS['xmlrpcstr']['curl_fail']. ': '. curl_error($curl)); curl_close($curl); if($keepalive) { $this->xmlrpc_curl_handle = null; } } else { if(!$keepalive) { curl_close($curl); } $resp =& $msg->parseResponse($result, true, $this->return_type); // if we got back a 302, we can not reuse the curl handle for later calls if($resp->faultCode() == $GLOBALS['xmlrpcerr']['http_error'] && $keepalive) { curl_close($curl); $this->xmlrpc_curl_handle = null; } } return $resp; } /** * Send an array of request messages and return an array of responses. * Unless $this->no_multicall has been set to true, it will try first * to use one single xmlrpc call to server method system.multicall, and * revert to sending many successive calls in case of failure. * This failure is also stored in $this->no_multicall for subsequent calls. * Unfortunately, there is no server error code universally used to denote * the fact that multicall is unsupported, so there is no way to reliably * distinguish between that and a temporary failure. * If you are sure that server supports multicall and do not want to * fallback to using many single calls, set the fourth parameter to FALSE. * * NB: trying to shoehorn extra functionality into existing syntax has resulted * in pretty much convoluted code... * * @param array $msgs an array of xmlrpcmsg objects * @param integer $timeout connection timeout (in seconds) * @param string $method the http protocol variant to be used * @param boolean fallback When true, upon receiving an error during multicall, multiple single calls will be attempted * @return array * @access public */ function multicall($msgs, $timeout=0, $method='', $fallback=true) { if ($method == '') { $method = $this->method; } if(!$this->no_multicall) { $results = $this->_try_multicall($msgs, $timeout, $method); if(is_array($results)) { // System.multicall succeeded return $results; } else { // either system.multicall is unsupported by server, // or call failed for some other reason. if ($fallback) { // Don't try it next time... $this->no_multicall = true; } else { if (is_a($results, 'xmlrpcresp')) { $result = $results; } else { $result = new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['multicall_error'], $GLOBALS['xmlrpcstr']['multicall_error']); } } } } else { // override fallback, in case careless user tries to do two // opposite things at the same time $fallback = true; } $results = array(); if ($fallback) { // system.multicall is (probably) unsupported by server: // emulate multicall via multiple requests foreach($msgs as $msg) { $results[] =& $this->send($msg, $timeout, $method); } } else { // user does NOT want to fallback on many single calls: // since we should always return an array of responses, // return an array with the same error repeated n times foreach($msgs as $msg) { $results[] = $result; } } return $results; } /** * Attempt to boxcar $msgs via system.multicall. * Returns either an array of xmlrpcreponses, an xmlrpc error response * or false (when received response does not respect valid multicall syntax) * @access private */ function _try_multicall($msgs, $timeout, $method) { // Construct multicall message $calls = array(); foreach($msgs as $msg) { $call['methodName'] = new xmlrpcval($msg->method(),'string'); $numParams = $msg->getNumParams(); $params = array(); for($i = 0; $i < $numParams; $i++) { $params[$i] = $msg->getParam($i); } $call['params'] = new xmlrpcval($params, 'array'); $calls[] = new xmlrpcval($call, 'struct'); } $multicall = new xmlrpcmsg('system.multicall'); $multicall->addParam(new xmlrpcval($calls, 'array')); // Attempt RPC call $result =& $this->send($multicall, $timeout, $method); if($result->faultCode() != 0) { // call to system.multicall failed return $result; } // Unpack responses. $rets = $result->value(); if ($this->return_type == 'xml') { return $rets; } else if ($this->return_type == 'phpvals') { ///@todo test this code branch... $rets = $result->value(); if(!is_array($rets)) { return false; // bad return type from system.multicall } $numRets = count($rets); if($numRets != count($msgs)) { return false; // wrong number of return values. } $response = array(); for($i = 0; $i < $numRets; $i++) { $val = $rets[$i]; if (!is_array($val)) { return false; } switch(count($val)) { case 1: if(!isset($val[0])) { return false; // Bad value } // Normal return value $response[$i] = new xmlrpcresp($val[0], 0, '', 'phpvals'); break; case 2: /// @todo remove usage of @: it is apparently quite slow $code = @$val['faultCode']; if(!is_int($code)) { return false; } $str = @$val['faultString']; if(!is_string($str)) { return false; } $response[$i] = new xmlrpcresp(0, $code, $str); break; default: return false; } } return $response; } else // return type == 'xmlrpcvals' { $rets = $result->value(); if($rets->kindOf() != 'array') { return false; // bad return type from system.multicall } $numRets = $rets->arraysize(); if($numRets != count($msgs)) { return false; // wrong number of return values. } $response = array(); for($i = 0; $i < $numRets; $i++) { $val = $rets->arraymem($i); switch($val->kindOf()) { case 'array': if($val->arraysize() != 1) { return false; // Bad value } // Normal return value $response[$i] = new xmlrpcresp($val->arraymem(0)); break; case 'struct': $code = $val->structmem('faultCode'); if($code->kindOf() != 'scalar' || $code->scalartyp() != 'int') { return false; } $str = $val->structmem('faultString'); if($str->kindOf() != 'scalar' || $str->scalartyp() != 'string') { return false; } $response[$i] = new xmlrpcresp(0, $code->scalarval(), $str->scalarval()); break; default: return false; } } return $response; } } } // end class xmlrpc_client class xmlrpcresp { var $val = 0; var $valtyp; var $errno = 0; var $errstr = ''; var $payload; var $hdrs = array(); var $_cookies = array(); var $content_type = 'text/xml'; var $raw_data = ''; /** * @param mixed $val either an xmlrpcval obj, a php value or the xml serialization of an xmlrpcval (a string) * @param integer $fcode set it to anything but 0 to create an error response * @param string $fstr the error string, in case of an error response * @param string $valtyp either 'xmlrpcvals', 'phpvals' or 'xml' * * @todo add check that $val / $fcode / $fstr is of correct type??? * NB: as of now we do not do it, since it might be either an xmlrpcval or a plain * php val, or a complete xml chunk, depending on usage of xmlrpc_client::send() inside which creator is called... */ function __construct($val, $fcode = 0, $fstr = '', $valtyp='') { if($fcode != 0) { // error response $this->errno = $fcode; $this->errstr = $fstr; //$this->errstr = htmlspecialchars($fstr); // XXX: encoding probably shouldn't be done here; fix later. } else { // successful response $this->val = $val; if ($valtyp == '') { // user did not declare type of response value: try to guess it if (is_object($this->val) && is_a($this->val, 'xmlrpcval')) { $this->valtyp = 'xmlrpcvals'; } else if (is_string($this->val)) { $this->valtyp = 'xml'; } else { $this->valtyp = 'phpvals'; } } else { // user declares type of resp value: believe him $this->valtyp = $valtyp; } } } /** * @deprecated */ function xmlrpcresp($val, $fcode = 0, $fstr = '', $valtyp='') { self::__construct($val, $fcode, $fstr, $valtyp); } /** * Returns the error code of the response. * @return integer the error code of this response (0 for not-error responses) * @access public */ function faultCode() { return $this->errno; } /** * Returns the error code of the response. * @return string the error string of this response ('' for not-error responses) * @access public */ function faultString() { return $this->errstr; } /** * Returns the value received by the server. * @return mixed the xmlrpcval object returned by the server. Might be an xml string or php value if the response has been created by specially configured xmlrpc_client objects * @access public */ function value() { return $this->val; } /** * Returns an array with the cookies received from the server. * Array has the form: $cookiename => array ('value' => $val, $attr1 => $val1, $attr2 = $val2, ...) * with attributes being e.g. 'expires', 'path', domain'. * NB: cookies sent as 'expired' by the server (i.e. with an expiry date in the past) * are still present in the array. It is up to the user-defined code to decide * how to use the received cookies, and whether they have to be sent back with the next * request to the server (using xmlrpc_client::setCookie) or not * @return array array of cookies received from the server * @access public */ function cookies() { return $this->_cookies; } /** * Returns xml representation of the response. XML prologue not included * @param string $charset_encoding the charset to be used for serialization. if null, US-ASCII is assumed * @return string the xml representation of the response * @access public */ function serialize($charset_encoding='') { if ($charset_encoding != '') $this->content_type = 'text/xml; charset=' . $charset_encoding; else $this->content_type = 'text/xml'; if ($GLOBALS['xmlrpc_null_apache_encoding']) { $result = "
'; foreach($GLOBALS['_xh']['headers'] as $header => $value) { print htmlentities("HEADER: $header: $value\n"); } foreach($GLOBALS['_xh']['cookies'] as $header => $value) { print htmlentities("COOKIE: $header={$value['value']}\n"); } print "\n"; } // if CURL was used for the call, http headers have been processed, // and dechunking + reinflating have been carried out if(!$headers_processed) { // Decode chunked encoding sent by http 1.1 servers if(isset($GLOBALS['_xh']['headers']['transfer-encoding']) && $GLOBALS['_xh']['headers']['transfer-encoding'] == 'chunked') { if(!$data = decode_chunked($data)) { error_log('XML-RPC: '.__METHOD__.': errors occurred when trying to rebuild the chunked data received from server'); $r = new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['dechunk_fail'], $GLOBALS['xmlrpcstr']['dechunk_fail']); return $r; } } // Decode gzip-compressed stuff // code shamelessly inspired from nusoap library by Dietrich Ayala if(isset($GLOBALS['_xh']['headers']['content-encoding'])) { $GLOBALS['_xh']['headers']['content-encoding'] = str_replace('x-', '', $GLOBALS['_xh']['headers']['content-encoding']); if($GLOBALS['_xh']['headers']['content-encoding'] == 'deflate' || $GLOBALS['_xh']['headers']['content-encoding'] == 'gzip') { // if decoding works, use it. else assume data wasn't gzencoded if(function_exists('gzinflate')) { if($GLOBALS['_xh']['headers']['content-encoding'] == 'deflate' && $degzdata = @gzuncompress($data)) { $data = $degzdata; if($this->debug) print "
---INFLATED RESPONSE---[".strlen($data)." chars]---\n" . htmlentities($data) . "\n---END---"; } elseif($GLOBALS['_xh']['headers']['content-encoding'] == 'gzip' && $degzdata = @gzinflate(substr($data, 10))) { $data = $degzdata; if($this->debug) print "
---INFLATED RESPONSE---[".strlen($data)." chars]---\n" . htmlentities($data) . "\n---END---"; } else { error_log('XML-RPC: '.__METHOD__.': errors occurred when trying to decode the deflated data received from server'); $r = new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['decompress_fail'], $GLOBALS['xmlrpcstr']['decompress_fail']); return $r; } } else { error_log('XML-RPC: '.__METHOD__.': the server sent deflated data. Your php install must have the Zlib extension compiled in to support this.'); $r = new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['cannot_decompress'], $GLOBALS['xmlrpcstr']['cannot_decompress']); return $r; } } } } // end of 'if needed, de-chunk, re-inflate response' // real stupid hack to avoid PHP complaining about returning NULL by ref $r = null; $r =& $r; return $r; } /** * Parse the xmlrpc response contained in the string $data and return an xmlrpcresp object. * @param string $data the xmlrpc response, eventually including http headers * @param bool $headers_processed when true prevents parsing HTTP headers for interpretation of content-encoding and consequent decoding * @param string $return_type decides return type, i.e. content of response->value(). Either 'xmlrpcvals', 'xml' or 'phpvals' * @return xmlrpcresp * @access public */ function &parseResponse($data='', $headers_processed=false, $return_type='xmlrpcvals') { if($this->debug) { //by maHo, replaced htmlspecialchars with htmlentities print "
---GOT---\n" . htmlentities($data) . "\n---END---\n"; } if($data == '') { error_log('XML-RPC: '.__METHOD__.': no response received from server.'); $r = new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['no_data'], $GLOBALS['xmlrpcstr']['no_data']); return $r; } $GLOBALS['_xh']=array(); $raw_data = $data; // parse the HTTP headers of the response, if present, and separate them from data if(substr($data, 0, 4) == 'HTTP') { $r =& $this->parseResponseHeaders($data, $headers_processed); if ($r) { // failed processing of HTTP response headers // save into response obj the full payload received, for debugging $r->raw_data = $data; return $r; } } else { $GLOBALS['_xh']['headers'] = array(); $GLOBALS['_xh']['cookies'] = array(); } if($this->debug) { $start = strpos($data, '', $start); $comments = substr($data, $start, $end-$start); print "
---SERVER DEBUG INFO (DECODED) ---\n\t".htmlentities(str_replace("\n", "\n\t", base64_decode($comments)))."\n---END---\n"; } } // be tolerant of extra whitespace in response body $data = trim($data); /// @todo return an error msg if $data=='' ? // be tolerant of junk after methodResponse (e.g. javascript ads automatically inserted by free hosts) // idea from Luca Mariano
---PARSED---\n"; // somehow htmlentities chokes on var_export, and some full html string... //print htmlentitites(var_export($GLOBALS['_xh']['value'], true)); print htmlspecialchars(var_export($GLOBALS['_xh']['value'], true)); print "\n---END---"; } // note that using =& will raise an error if $GLOBALS['_xh']['st'] does not generate an object. $v =& $GLOBALS['_xh']['value']; if($GLOBALS['_xh']['isf']) { /// @todo we should test here if server sent an int and a string, /// and/or coerce them into such... if ($return_type == 'xmlrpcvals') { $errno_v = $v->structmem('faultCode'); $errstr_v = $v->structmem('faultString'); $errno = $errno_v->scalarval(); $errstr = $errstr_v->scalarval(); } else { $errno = $v['faultCode']; $errstr = $v['faultString']; } if($errno == 0) { // FAULT returned, errno needs to reflect that $errno = -1; } $r = new xmlrpcresp(0, $errno, $errstr); } else { $r=new xmlrpcresp($v, 0, '', $return_type); } } $r->hdrs = $GLOBALS['_xh']['headers']; $r->_cookies = $GLOBALS['_xh']['cookies']; $r->raw_data = $raw_data; return $r; } } class xmlrpcval { var $me=array(); var $mytype=0; var $_php_class=null; /** * @param mixed $val * @param string $type any valid xmlrpc type name (lowercase). If null, 'string' is assumed */ function __construct($val=-1, $type='') { /// @todo: optimization creep - do not call addXX, do it all inline. /// downside: booleans will not be coerced anymore if($val!==-1 || $type!='') { // optimization creep: inlined all work done by constructor switch($type) { case '': $this->mytype=1; $this->me['string']=$val; break; case 'i4': case 'int': case 'double': case 'string': case 'boolean': case 'dateTime.iso8601': case 'base64': case 'null': $this->mytype=1; $this->me[$type]=$val; break; case 'array': $this->mytype=2; $this->me['array']=$val; break; case 'struct': $this->mytype=3; $this->me['struct']=$val; break; default: error_log("XML-RPC: ".__METHOD__.": not a known type ($type)"); } /*if($type=='') { $type='string'; } if($GLOBALS['xmlrpcTypes'][$type]==1) { $this->addScalar($val,$type); } elseif($GLOBALS['xmlrpcTypes'][$type]==2) { $this->addArray($val); } elseif($GLOBALS['xmlrpcTypes'][$type]==3) { $this->addStruct($val); }*/ } } /** * @deprecated */ function xmlrpcval($val=-1, $type='') { self::__construct($val, $type); } /** * Add a single php value to an (unitialized) xmlrpcval * @param mixed $val * @param string $type * @return int 1 or 0 on failure */ function addScalar($val, $type='string') { $typeof=@$GLOBALS['xmlrpcTypes'][$type]; if($typeof!=1) { error_log("XML-RPC: ".__METHOD__.": not a scalar type ($type)"); return 0; } // coerce booleans into correct values // NB: we should either do it for datetimes, integers and doubles, too, // or just plain remove this check, implemented on booleans only... if($type==$GLOBALS['xmlrpcBoolean']) { if(strcasecmp($val,'true')==0 || $val==1 || ($val==true && strcasecmp($val,'false'))) { $val=true; } else { $val=false; } } switch($this->mytype) { case 1: error_log('XML-RPC: '.__METHOD__.': scalar xmlrpcval can have only one value'); return 0; case 3: error_log('XML-RPC: '.__METHOD__.': cannot add anonymous scalar to struct xmlrpcval'); return 0; case 2: // we're adding a scalar value to an array here //$ar=$this->me['array']; //$ar[]=new xmlrpcval($val, $type); //$this->me['array']=$ar; // Faster (?) avoid all the costly array-copy-by-val done here... $this->me['array'][]=new xmlrpcval($val, $type); return 1; default: // a scalar, so set the value and remember we're scalar $this->me[$type]=$val; $this->mytype=$typeof; return 1; } } /** * Add an array of xmlrpcval objects to an xmlrpcval * @param array $vals * @return int 1 or 0 on failure * @access public * * @todo add some checking for $vals to be an array of xmlrpcvals? */ function addArray($vals) { if($this->mytype==0) { $this->mytype=$GLOBALS['xmlrpcTypes']['array']; $this->me['array']=$vals; return 1; } elseif($this->mytype==2) { // we're adding to an array here $this->me['array'] = array_merge($this->me['array'], $vals); return 1; } else { error_log('XML-RPC: '.__METHOD__.': already initialized as a [' . $this->kindOf() . ']'); return 0; } } /** * Add an array of named xmlrpcval objects to an xmlrpcval * @param array $vals * @return int 1 or 0 on failure * @access public * * @todo add some checking for $vals to be an array? */ function addStruct($vals) { if($this->mytype==0) { $this->mytype=$GLOBALS['xmlrpcTypes']['struct']; $this->me['struct']=$vals; return 1; } elseif($this->mytype==3) { // we're adding to a struct here $this->me['struct'] = array_merge($this->me['struct'], $vals); return 1; } else { error_log('XML-RPC: '.__METHOD__.': already initialized as a [' . $this->kindOf() . ']'); return 0; } } // poor man's version of print_r ??? // DEPRECATED! function dump($ar) { foreach($ar as $key => $val) { echo "$key => $val