call( string methodname [ ,array parameters] ); * * // bye bye client * unset($soapclient); * * @author Dietrich Ayala * @version $Id: class.soapclient.php 46 2007-06-19 07:29:11Z tsigo $ * @access public */ class soapclient extends nusoap_base { var $username = ''; var $password = ''; var $authtype = ''; var $certRequest = array(); var $requestHeaders = false; // SOAP headers in request (text) var $responseHeaders = ''; // SOAP headers from response (incomplete namespace resolution) (text) var $document = ''; // SOAP body response portion (incomplete namespace resolution) (text) var $endpoint; var $proxyhost = ''; var $proxyport = ''; var $proxyusername = ''; var $proxypassword = ''; var $xml_encoding = ''; // character set encoding of incoming (response) messages var $http_encoding = false; var $timeout = 0; // HTTP connection timeout var $response_timeout = 30; // HTTP response timeout var $endpointType = ''; // soap|wsdl, empty for WSDL initialization error var $persistentConnection = false; var $defaultRpcParams = false; // This is no longer used var $request = ''; // HTTP request var $response = ''; // HTTP response var $responseData = ''; // SOAP payload of response var $cookies = array(); // Cookies from response or for request var $decode_utf8 = true; // toggles whether the parser decodes element content w/ utf8_decode() var $operations = array(); // WSDL operations, empty for WSDL initialization error /** * fault related variables * * @var fault * @var faultcode * @var faultstring * @var faultdetail * @access public */ var $fault, $faultcode, $faultstring, $faultdetail; /** * constructor * * @param mixed $endpoint SOAP server or WSDL URL (string), or wsdl instance (object) * @param bool $wsdl optional, set to true if using WSDL * @param int $portName optional portName in WSDL document * @param string $proxyhost * @param string $proxyport * @param string $proxyusername * @param string $proxypassword * @param integer $timeout set the connection timeout * @param integer $response_timeout set the response timeout * @access public */ function soapclient($endpoint,$wsdl = false,$proxyhost = false,$proxyport = false,$proxyusername = false, $proxypassword = false, $timeout = 0, $response_timeout = 30){ $this->endpoint = $endpoint; $this->proxyhost = $proxyhost; $this->proxyport = $proxyport; $this->proxyusername = $proxyusername; $this->proxypassword = $proxypassword; $this->timeout = $timeout; $this->response_timeout = $response_timeout; // make values if($wsdl){ if (is_object($endpoint) && is_a($endpoint, 'wsdl')) { $this->wsdl = $endpoint; $this->endpoint = $this->wsdl->wsdl; $this->wsdlFile = $this->endpoint; $this->debug('existing wsdl instance created from ' . $this->endpoint); } else { $this->wsdlFile = $this->endpoint; // instantiate wsdl object and parse wsdl file $this->debug('instantiating wsdl class with doc: '.$endpoint); $this->wsdl =& new wsdl($this->wsdlFile,$this->proxyhost,$this->proxyport,$this->proxyusername,$this->proxypassword,$this->timeout,$this->response_timeout); } $this->appendDebug($this->wsdl->getDebug()); $this->wsdl->clearDebug(); // catch errors if($errstr = $this->wsdl->getError()){ $this->debug('got wsdl error: '.$errstr); $this->setError('wsdl error: '.$errstr); } elseif($this->operations = $this->wsdl->getOperations()){ $this->debug( 'got '.count($this->operations).' operations from wsdl '.$this->wsdlFile); $this->endpointType = 'wsdl'; } else { $this->debug( 'getOperations returned false'); $this->setError('no operations defined in the WSDL document!'); } } else { $this->endpointType = 'soap'; } } /** * calls method, returns PHP native type * * @param string $method SOAP server URL or path * @param array $params An array, associative or simple, of the parameters * for the method call, or a string that is the XML * for the call. For rpc style, this call will * wrap the XML in a tag named after the method, as * well as the SOAP Envelope and Body. For document * style, this will only wrap with the Envelope and Body. * IMPORTANT: when using an array with document style, * in which case there * is really one parameter, the root of the fragment * used in the call, which encloses what programmers * normally think of parameters. A parameter array * *must* include the wrapper. * @param string $namespace optional method namespace (WSDL can override) * @param string $soapAction optional SOAPAction value (WSDL can override) * @param boolean $headers optional array of soapval objects for headers * @param boolean $rpcParams optional (no longer used) * @param string $style optional (rpc|document) the style to use when serializing parameters (WSDL can override) * @param string $use optional (encoded|literal) the use when serializing parameters (WSDL can override) * @return mixed * @access public */ function call($operation,$params=array(),$namespace='http://tempuri.org',$soapAction='',$headers=false,$rpcParams=null,$style='rpc',$use='encoded'){ $this->operation = $operation; $this->fault = false; $this->setError(''); $this->request = ''; $this->response = ''; $this->responseData = ''; $this->faultstring = ''; $this->faultcode = ''; $this->opData = array(); $this->debug("call: $operation, $params, $namespace, $soapAction, $headers, $style, $use; endpointType: $this->endpointType"); if ($headers) { $this->requestHeaders = $headers; } // serialize parameters if($this->endpointType == 'wsdl' && $opData = $this->getOperationData($operation)){ // use WSDL for operation $this->opData = $opData; $this->debug("opData:"); $this->appendDebug($this->varDump($opData)); if (isset($opData['soapAction'])) { $soapAction = $opData['soapAction']; } $this->endpoint = $opData['endpoint']; $namespace = isset($opData['input']['namespace']) ? $opData['input']['namespace'] : $namespace; $style = $opData['style']; $use = $opData['input']['use']; // add ns to ns array if($namespace != '' && !isset($this->wsdl->namespaces[$namespace])){ $nsPrefix = 'ns' . rand(1000, 9999); $this->wsdl->namespaces[$nsPrefix] = $namespace; } $nsPrefix = $this->wsdl->getPrefixFromNamespace($namespace); // serialize payload if (is_string($params)) { $this->debug("serializing param string for WSDL operation $operation"); $payload = $params; } elseif (is_array($params)) { $this->debug("serializing param array for WSDL operation $operation"); $payload = $this->wsdl->serializeRPCParameters($operation,'input',$params); } else { $this->debug('params must be array or string'); $this->setError('params must be array or string'); return false; } $usedNamespaces = $this->wsdl->usedNamespaces; // Partial fix for multiple encoding styles in the same function call $encodingStyle = 'http://schemas.xmlsoap.org/soap/encoding/'; if (isset($opData['output']['encodingStyle']) && $encodingStyle != $opData['output']['encodingStyle']) { $methodEncodingStyle = ' SOAP-ENV:encodingStyle="' . $opData['output']['encodingStyle'] . '"'; } else { $methodEncodingStyle = ''; } $this->appendDebug($this->wsdl->getDebug()); $this->wsdl->clearDebug(); if ($errstr = $this->wsdl->getError()) { $this->debug('got wsdl error: '.$errstr); $this->setError('wsdl error: '.$errstr); return false; } } elseif($this->endpointType == 'wsdl') { // operation not in WSDL $this->appendDebug($this->wsdl->getDebug()); $this->wsdl->clearDebug(); $this->setError( 'operation '.$operation.' not present.'); $this->debug("operation '$operation' not present."); return false; } else { // no WSDL //$this->namespaces['ns1'] = $namespace; $nsPrefix = 'ns' . rand(1000, 9999); // serialize $payload = ''; if (is_string($params)) { $this->debug("serializing param string for operation $operation"); $payload = $params; } elseif (is_array($params)) { $this->debug("serializing param array for operation $operation"); foreach($params as $k => $v){ $payload .= $this->serialize_val($v,$k,false,false,false,false,$use); } } else { $this->debug('params must be array or string'); $this->setError('params must be array or string'); return false; } $usedNamespaces = array(); $methodEncodingStyle = ''; } // wrap RPC calls with method element if ($style == 'rpc') { if ($use == 'literal') { $this->debug("wrapping RPC request with literal method element"); if ($namespace) { $payload = "<$operation xmlns=\"$namespace\">" . $payload . ""; } else { $payload = "<$operation>" . $payload . ""; } } else { $this->debug("wrapping RPC request with encoded method element"); if ($namespace) { $payload = "<$nsPrefix:$operation$methodEncodingStyle xmlns:$nsPrefix=\"$namespace\">" . $payload . ""; } else { $payload = "<$operation$methodEncodingStyle>" . $payload . ""; } } } // serialize envelope $soapmsg = $this->serializeEnvelope($payload,$this->requestHeaders,$usedNamespaces,$style,$use); $this->debug("endpoint: $this->endpoint, soapAction: $soapAction, namespace: $namespace, style: $style, use: $use"); $this->debug('SOAP message length: ' . strlen($soapmsg) . ' contents: ' . substr($soapmsg, 0, 1000)); // send $return = $this->send($this->getHTTPBody($soapmsg),$soapAction,$this->timeout,$this->response_timeout); if($errstr = $this->getError()){ $this->debug('Error: '.$errstr); return false; } else { $this->return = $return; $this->debug('sent message successfully and got a(n) '.gettype($return).' back: '); $this->appendDebug($this->varDump($return)); // fault? if(is_array($return) && isset($return['faultcode'])){ $this->debug('got fault'); $this->setError($return['faultcode'].': '.$return['faultstring']); $this->fault = true; foreach($return as $k => $v){ $this->$k = $v; $this->debug("$k = $v
"); } return $return; } elseif ($style == 'document') { // NOTE: if the response is defined to have multiple parts (i.e. unwrapped), // we are only going to return the first part here...sorry about that return $return; } else { // array of return values if(is_array($return)){ // multiple 'out' parameters, which we return wrapped up // in the array if(sizeof($return) > 1){ return $return; } // single 'out' parameter (normally the return value) $return = array_shift($return); $this->debug('return shifted value: '); $this->appendDebug($this->varDump($return)); return $return; // nothing returned (ie, echoVoid) } else { return ""; } } } } /** * get available data pertaining to an operation * * @param string $operation operation name * @return array array of data pertaining to the operation * @access public */ function getOperationData($operation){ if(isset($this->operations[$operation])){ return $this->operations[$operation]; } $this->debug("No data for operation: $operation"); } /** * send the SOAP message * * Note: if the operation has multiple return values * the return value of this method will be an array * of those values. * * @param string $msg a SOAPx4 soapmsg object * @param string $soapaction SOAPAction value * @param integer $timeout set connection timeout in seconds * @param integer $response_timeout set response timeout in seconds * @return mixed native PHP types. * @access private */ function send($msg, $soapaction = '', $timeout=0, $response_timeout=30) { $this->checkCookies(); // detect transport switch(true){ // http(s) case ereg('^http',$this->endpoint): $this->debug('transporting via HTTP'); if($this->persistentConnection == true && is_object($this->persistentConnection)){ $http =& $this->persistentConnection; } else { $http = new soap_transport_http($this->endpoint); if ($this->persistentConnection) { $http->usePersistentConnection(); } } $http->setContentType($this->getHTTPContentType(), $this->getHTTPContentTypeCharset()); $http->setSOAPAction($soapaction); if($this->proxyhost && $this->proxyport){ $http->setProxy($this->proxyhost,$this->proxyport,$this->proxyusername,$this->proxypassword); } if($this->authtype != '') { $http->setCredentials($this->username, $this->password, $this->authtype, array(), $this->certRequest); } if($this->http_encoding != ''){ $http->setEncoding($this->http_encoding); } $this->debug('sending message, length: '.strlen($msg)); if(ereg('^http:',$this->endpoint)){ //if(strpos($this->endpoint,'http:')){ $this->responseData = $http->send($msg,$timeout,$response_timeout,$this->cookies); } elseif(ereg('^https',$this->endpoint)){ //} elseif(strpos($this->endpoint,'https:')){ //if(phpversion() == '4.3.0-dev'){ //$response = $http->send($msg,$timeout,$response_timeout); //$this->request = $http->outgoing_payload; //$this->response = $http->incoming_payload; //} else $this->responseData = $http->sendHTTPS($msg,$timeout,$response_timeout,$this->cookies); } else { $this->setError('no http/s in endpoint url'); } $this->request = $http->outgoing_payload; $this->response = $http->incoming_payload; $this->appendDebug($http->getDebug()); $this->UpdateCookies($http->incoming_cookies); // save transport object if using persistent connections if ($this->persistentConnection) { $http->clearDebug(); if (!is_object($this->persistentConnection)) { $this->persistentConnection = $http; } } if($err = $http->getError()){ $this->setError('HTTP Error: '.$err); return false; } elseif($this->getError()){ return false; } else { $this->debug('got response, length: '. strlen($this->responseData).' type: '.$http->incoming_headers['content-type']); return $this->parseResponse($http->incoming_headers, $this->responseData); } break; default: $this->setError('no transport found, or selected transport is not yet supported!'); return false; break; } } /** * processes SOAP message returned from server * * @param array $headers The HTTP headers * @param string $data unprocessed response data from server * @return mixed value of the message, decoded into a PHP type * @access protected */ function parseResponse($headers, $data) { $this->debug('Entering parseResponse() for data of length ' . strlen($data) . ' and type ' . $headers['content-type']); if (!strstr($headers['content-type'], 'text/xml')) { $this->setError('Response not of type text/xml'); return false; } if (strpos($headers['content-type'], '=')) { $enc = str_replace('"', '', substr(strstr($headers["content-type"], '='), 1)); $this->debug('Got response encoding: ' . $enc); if(eregi('^(ISO-8859-1|US-ASCII|UTF-8)$',$enc)){ $this->xml_encoding = strtoupper($enc); } else { $this->xml_encoding = 'US-ASCII'; } } else { // should be US-ASCII for HTTP 1.0 or ISO-8859-1 for HTTP 1.1 $this->xml_encoding = 'ISO-8859-1'; } $this->debug('Use encoding: ' . $this->xml_encoding . ' when creating soap_parser'); $parser = new soap_parser($data,$this->xml_encoding,$this->operation,$this->decode_utf8); // add parser debug data to our debug $this->appendDebug($parser->getDebug()); // if parse errors if($errstr = $parser->getError()){ $this->setError( $errstr); // destroy the parser object unset($parser); return false; } else { // get SOAP headers $this->responseHeaders = $parser->getHeaders(); // get decoded message $return = $parser->get_response(); // add document for doclit support $this->document = $parser->document; // destroy the parser object unset($parser); // return decode message return $return; } } /** * set the SOAP headers * * @param $headers string XML * @access public */ function setHeaders($headers){ $this->requestHeaders = $headers; } /** * get the response headers * * @return string * @access public */ function getHeaders(){ return $this->responseHeaders; } /** * set proxy info here * * @param string $proxyhost * @param string $proxyport * @param string $proxyusername * @param string $proxypassword * @access public */ function setHTTPProxy($proxyhost, $proxyport, $proxyusername = '', $proxypassword = '') { $this->proxyhost = $proxyhost; $this->proxyport = $proxyport; $this->proxyusername = $proxyusername; $this->proxypassword = $proxypassword; } /** * if authenticating, set user credentials here * * @param string $username * @param string $password * @param string $authtype (basic|digest|certificate) * @param array $certRequest (keys must be cainfofile, sslcertfile, sslkeyfile, passphrase: see corresponding options in cURL docs) * @access public */ function setCredentials($username, $password, $authtype = 'basic', $certRequest = array()) { $this->username = $username; $this->password = $password; $this->authtype = $authtype; $this->certRequest = $certRequest; } /** * use HTTP encoding * * @param string $enc * @access public */ function setHTTPEncoding($enc='gzip, deflate'){ $this->http_encoding = $enc; } /** * use HTTP persistent connections if possible * * @access public */ function useHTTPPersistentConnection(){ $this->persistentConnection = true; } /** * gets the default RPC parameter setting. * If true, default is that call params are like RPC even for document style. * Each call() can override this value. * * This is no longer used. * * @access public * @deprecated */ function getDefaultRpcParams() { return $this->defaultRpcParams; } /** * sets the default RPC parameter setting. * If true, default is that call params are like RPC even for document style * Each call() can override this value. * * This is no longer used. * * @param boolean $rpcParams * @access public * @deprecated */ function setDefaultRpcParams($rpcParams) { $this->defaultRpcParams = $rpcParams; } /** * dynamically creates proxy class, allowing user to directly call methods from wsdl * * @return object soap_proxy object * @access public */ function getProxy(){ if ($this->endpointType != 'wsdl') { $this->setError('The getProxy method only works for a WSDL client'); return null; } $evalStr = ''; foreach($this->operations as $operation => $opData){ if($operation != ''){ // create param string $paramStr = ''; if(sizeof($opData['input']['parts']) > 0){ foreach($opData['input']['parts'] as $name => $type){ $paramStr .= "\$$name,"; } $paramStr = substr($paramStr,0,strlen($paramStr)-1); } $opData['namespace'] = !isset($opData['namespace']) ? 'http://testuri.com' : $opData['namespace']; $evalStr .= "function " . str_replace('.', '__', $operation) . " ($paramStr) { // load params into array \$params = array($paramStr); return \$this->call('$operation',\$params,'".$opData['namespace']."','".(isset($opData['soapAction']) ? $opData['soapAction'] : '')."'); }"; unset($paramStr); } } $r = rand(); $evalStr = 'class soap_proxy_'.$r.' extends soapclient { '.$evalStr.' }'; //print "proxy class:
$evalStr
"; // eval the class eval($evalStr); // instantiate proxy object eval("\$proxy = new soap_proxy_$r('');"); // transfer current wsdl data to the proxy thereby avoiding parsing the wsdl twice $proxy->endpointType = 'wsdl'; $proxy->wsdlFile = $this->wsdlFile; $proxy->wsdl = $this->wsdl; $proxy->operations = $this->operations; $proxy->defaultRpcParams = $this->defaultRpcParams; // transfer other state $proxy->username = $this->username; $proxy->password = $this->password; $proxy->authtype = $this->authtype; $proxy->proxyhost = $this->proxyhost; $proxy->proxyport = $this->proxyport; $proxy->proxyusername = $this->proxyusername; $proxy->proxypassword = $this->proxypassword; $proxy->timeout = $this->timeout; $proxy->response_timeout = $this->response_timeout; $proxy->http_encoding = $this->http_encoding; $proxy->persistentConnection = $this->persistentConnection; $proxy->requestHeaders = $this->requestHeaders; $proxy->soap_defencoding = $this->soap_defencoding; return $proxy; } /** * gets the HTTP body for the current request. * * @param string $soapmsg The SOAP payload * @return string The HTTP body, which includes the SOAP payload * @access protected */ function getHTTPBody($soapmsg) { return $soapmsg; } /** * gets the HTTP content type for the current request. * * Note: getHTTPBody must be called before this. * * @return string the HTTP content type for the current request. * @access protected */ function getHTTPContentType() { return 'text/xml'; } /** * gets the HTTP content type charset for the current request. * returns false for non-text content types. * * Note: getHTTPBody must be called before this. * * @return string the HTTP content type charset for the current request. * @access protected */ function getHTTPContentTypeCharset() { return $this->soap_defencoding; } /* * whether or not parser should decode utf8 element content * * @return always returns true * @access public */ function decodeUTF8($bool){ $this->decode_utf8 = $bool; return true; } /** * adds a new Cookie into $this->cookies array * * @param string $name Cookie Name * @param string $value Cookie Value * @return if cookie-set was successful returns true, else false * @access public */ function setCookie($name, $value) { if (strlen($name) == 0) { return false; } $this->cookies[] = array('name' => $name, 'value' => $value); return true; } /** * gets all Cookies * * @return array with all internal cookies * @access public */ function getCookies() { return $this->cookies; } /** * checks all Cookies and delete those which are expired * * @return always return true * @access public */ function checkCookies() { if (sizeof($this->cookies) == 0) { return true; } $this->debug('checkCookie: check ' . sizeof($this->cookies) . ' cookies'); $curr_cookies = $this->cookies; $this->cookies = array(); foreach ($curr_cookies as $cookie) { if (! is_array($cookie)) { $this->debug('Remove cookie that is not an array'); continue; } if ((isset($cookie['expires'])) && (! empty($cookie['expires']))) { if (strtotime($cookie['expires']) > time()) { $this->cookies[] = $cookie; } else { $this->debug('Remove expired cookie ' . $cookie['name']); } } else { $this->cookies[] = $cookie; } } $this->debug('checkCookie: '.sizeof($this->cookies).' cookies left in array'); return true; } /** * updates the current cookies with a new set * * @param array $cookies new cookies with which to update current ones * @return always return true * @access public */ function UpdateCookies($cookies) { if (sizeof($this->cookies) == 0) { // no existing cookies: take whatever is new if (sizeof($cookies) > 0) { $this->debug('Setting new cookie(s)'); $this->cookies = $cookies; } return true; } if (sizeof($cookies) == 0) { // no new cookies: keep what we've got return true; } // merge foreach ($cookies as $newCookie) { if (!is_array($newCookie)) { continue; } if ((!isset($newCookie['name'])) || (!isset($newCookie['value']))) { continue; } $newName = $newCookie['name']; $found = false; for ($i = 0; $i < count($this->cookies); $i++) { $cookie = $this->cookies[$i]; if (!is_array($cookie)) { continue; } if (!isset($cookie['name'])) { continue; } if ($newName != $cookie['name']) { continue; } $newDomain = isset($newCookie['domain']) ? $newCookie['domain'] : 'NODOMAIN'; $domain = isset($cookie['domain']) ? $cookie['domain'] : 'NODOMAIN'; if ($newDomain != $domain) { continue; } $newPath = isset($newCookie['path']) ? $newCookie['path'] : 'NOPATH'; $path = isset($cookie['path']) ? $cookie['path'] : 'NOPATH'; if ($newPath != $path) { continue; } $this->cookies[$i] = $newCookie; $found = true; $this->debug('Update cookie ' . $newName . '=' . $newCookie['value']); break; } if (! $found) { $this->debug('Add cookie ' . $newName . '=' . $newCookie['value']); $this->cookies[] = $newCookie; } } return true; } } ?>