diff --git a/src/BeSimple/SoapClient/Helper.php b/src/BeSimple/SoapClient/Helper.php index 89edeba..f79ab1f 100644 --- a/src/BeSimple/SoapClient/Helper.php +++ b/src/BeSimple/SoapClient/Helper.php @@ -1,369 +1,369 @@ - - * (c) Francis Besset - * - * This source file is subject to the MIT license that is bundled - * with this source code in the file LICENSE. - */ - -namespace BeSimple\SoapClient; - -/** - * Soap helper class with static functions that are used in the client and - * server implementations. It also provides namespace and configuration - * constants. - * - * @author Andreas Schamberger - */ -class Helper -{ - /** - * Attachment type: xsd:base64Binary (native in ext/soap). - */ - const ATTACHMENTS_TYPE_BASE64 = 1; - - /** - * Attachment type: MTOM (SOAP Message Transmission Optimization Mechanism). - */ - const ATTACHMENTS_TYPE_MTOM = 2; - - /** - * Attachment type: SWA (SOAP Messages with Attachments). - */ - const ATTACHMENTS_TYPE_SWA = 4; - - /** - * Web Services Security: SOAP Message Security 1.0 (WS-Security 2004) - */ - const NAME_WSS_SMS = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0'; - - /** - * Web Services Security: SOAP Message Security 1.1 (WS-Security 2004) - */ - const NAME_WSS_SMS_1_1 = 'http://docs.oasis-open.org/wss/oasis-wss-soap-message-security-1.1'; - - /** - * Web Services Security UsernameToken Profile 1.0 - */ - const NAME_WSS_UTP = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0'; - - /** - * Web Services Security X.509 Certificate Token Profile - */ - const NAME_WSS_X509 = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0'; - - /** - * Soap 1.1 namespace. - */ - const NS_SOAP_1_1 = 'http://schemas.xmlsoap.org/soap/envelope/'; - - /** - * Soap 1.1 namespace. - */ - const NS_SOAP_1_2 = 'http://www.w3.org/2003/05/soap-envelope/'; - - /** - * Web Services Addressing 1.0 namespace. - */ - const NS_WSA = 'http://www.w3.org/2005/08/addressing'; - - /** - * Web Services Security Extension namespace. - */ - const NS_WSS = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd'; - - /** - * Web Services Security Utility namespace. - */ - const NS_WSU = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd'; - - /** - * Describing Media Content of Binary Data in XML namespace. - */ - const NS_XMLMIME = 'http://www.w3.org/2004/11/xmlmime'; - - /** - * XML Schema namespace. - */ - const NS_XML_SCHEMA = 'http://www.w3.org/2001/XMLSchema'; - - /** - * XML Schema instance namespace. - */ - const NS_XML_SCHEMA_INSTANCE = 'http://www.w3.org/2001/XMLSchema-instance'; - - /** - * XML-binary Optimized Packaging namespace. - */ - const NS_XOP = 'http://www.w3.org/2004/08/xop/include'; - - /** - * Web Services Addressing 1.0 prefix. - */ - const PFX_WSA = 'wsa'; - - /** - * Web Services Security Extension namespace. - */ - const PFX_WSS = 'wsse'; - - /** - * Web Services Security Utility namespace prefix. - */ - const PFX_WSU = 'wsu'; - - /** - * Describing Media Content of Binary Data in XML namespace prefix. - */ - const PFX_XMLMIME = 'xmlmime'; - - /** - * XML Schema namespace prefix. - */ - const PFX_XML_SCHEMA = 'xsd'; - - /** - * XML Schema instance namespace prefix. - */ - const PFX_XML_SCHEMA_INSTANCE = 'xsi'; - - /** - * XML-binary Optimized Packaging namespace prefix. - */ - const PFX_XOP = 'xop'; - - /** - * Constant for a request. - */ - const REQUEST = 0; - - /** - * Constant for a response. - */ - const RESPONSE = 1; - - /** - * Wheather to format the XML output or not. - * - * @var boolean - */ - public static $formatXmlOutput = false; - - /** - * Contains previously defined error handler string. - * - * @var string - */ - private static $previousErrorHandler = null; - - /** - * User-defined error handler function to convert errors to exceptions. - * - * @param string $errno - * @param string $errstr - * @param string $errfile - * @param string $errline - * @throws ErrorException - */ - public static function exceptionErrorHandler($errno, $errstr, $errfile, $errline) - { - // don't throw exception for errors suppresed with @ - if (error_reporting() != 0) { - throw new \ErrorException($errstr, 0, $errno, $errfile, $errline); - } - } - - /** - * Generate a pseudo-random version 4 UUID. - * - * @see http://de.php.net/manual/en/function.uniqid.php#94959 - * @return string - */ - public static function generateUUID() - { - return sprintf( - '%04x%04x-%04x-%04x-%04x-%04x%04x%04x', - // 32 bits for "time_low" - mt_rand(0, 0xffff), mt_rand(0, 0xffff), - // 16 bits for "time_mid" - mt_rand(0, 0xffff), - // 16 bits for "time_hi_and_version", - // four most significant bits holds version number 4 - mt_rand(0, 0x0fff) | 0x4000, - // 16 bits, 8 bits for "clk_seq_hi_res", - // 8 bits for "clk_seq_low", - // two most significant bits holds zero and one for variant DCE1.1 - mt_rand(0, 0x3fff) | 0x8000, - // 48 bits for "node" - mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff) - ); - } - - /** - * Builds the current URL from the $_SERVER array. - * - * @return string - */ - public static function getCurrentUrl() - { - $url = ''; - if (isset($_SERVER['HTTPS']) && - (strtolower($_SERVER['HTTPS']) === 'on' || $_SERVER['HTTPS'] === '1')) { - $url .= 'https://'; - } else { - $url .= 'http://'; - } - $url .= isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : ''; - if (isset($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] != 80) { - $url .= ":{$_SERVER['SERVER_PORT']}"; - } - $url .= isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : ''; - return $url; - } - - /** - * Get SOAP namespace for the given $version. - * - * @param int $version SOAP_1_1|SOAP_1_2 - * @return string - */ - public static function getSoapNamespace($version) - { - if ($version === SOAP_1_2) { - return self::NS_SOAP_1_2; - } else { - return self::NS_SOAP_1_1; - } - } - - /** - * Get SOAP version from namespace URI. - * - * @param string $namespace NS_SOAP_1_1|NS_SOAP_1_2 - * @return int SOAP_1_1|SOAP_1_2 - */ - public static function getSoapVersionFromNamespace($namespace) - { - if ($namespace === self::NS_SOAP_1_2) { - return SOAP_1_2; - } else { - return SOAP_1_1; - } - } - - /** - * Runs the registered Plugins on the given request $xml. - * - * @param array(\ass\Soap\Plugin) $plugins - * @param int $requestType \ass\Soap\Helper::REQUEST|\ass\Soap\Helper::RESPONSE - * @param string $xml - * @param string $location - * @param string $action - * @param \ass\Soap\WsdlHandler $wsdlHandler - * @return string - */ - public static function runPlugins(array $plugins, $requestType, $xml, $location = null, $action = null, \ass\Soap\WsdlHandler $wsdlHandler = null) - { - if (count($plugins) > 0) { - // instantiate new dom object - $dom = new \DOMDocument('1.0'); - // format the XML if option is set - $dom->formatOutput = self::$formatXmlOutput; - $dom->loadXML($xml); - $params = array( - $dom, - $location, - $action, - $wsdlHandler - ); - if ($requestType == self::REQUEST) { - $callMethod = 'modifyRequest'; - } else { - $callMethod = 'modifyResponse'; - } - // modify dom - foreach($plugins AS $plugin) { - if ($plugin instanceof \ass\Soap\Plugin) { - call_user_func_array(array($plugin, $callMethod), $params); - } - } - // return the modified xml document - return $dom->saveXML(); - // format the XML if option is set - } elseif (self::$formatXmlOutput === true) { - $dom = new \DOMDocument('1.0'); - $dom->formatOutput = true; - $dom->loadXML($xml); - return $dom->saveXML(); - } - return $xml; - } - - /** - * Set custom error handler that converts all php errors to ErrorExceptions - * - * @param boolean $reset - */ - public static function setCustomErrorHandler($reset = false) - { - if ($reset === true && !is_null(self::$previousErrorHandler)) { - set_error_handler(self::$previousErrorHandler); - self::$previousErrorHandler = null; - } else { - self::$previousErrorHandler = set_error_handler('ass\\Soap\\Helper::exceptionErrorHandler'); - } - return self::$previousErrorHandler; - } - - /** - * Build data string to used to bridge ext/soap - * 'SOAP_MIME_ATTACHMENT:cid=...&type=...' - * - * @param string $contentId - * @param string $contentType - * @return string - */ - public static function makeSoapAttachmentDataString($contentId, $contentType) - { - $parameter = array( - 'cid' => $contentId, - 'type' => $contentType, - ); - return 'SOAP-MIME-ATTACHMENT:' . http_build_query($parameter, null, '&'); - } - - /** - * Parse data string used to bridge ext/soap - * 'SOAP_MIME_ATTACHMENT:cid=...&type=...' - * - * @param string $dataString - * @return array(string=>string) - */ - public static function parseSoapAttachmentDataString($dataString) - { - $dataString = substr($dataString, 21); - // get all data - $data = array(); - parse_str($dataString, $data); - return $data; - } - - /** - * Function to set proper http status header. - * Neccessary as there is a difference between mod_php and the cgi SAPIs. - * - * @param string $header - */ - public static function setHttpStatusHeader($header) - { - if ('cgi' == substr(php_sapi_name(), 0, 3)) { - header('Status: ' . $header); - } else { - header($_SERVER['SERVER_PROTOCOL'] . ' ' . $header); - } - } + + * (c) Francis Besset + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace BeSimple\SoapClient; + +/** + * Soap helper class with static functions that are used in the client and + * server implementations. It also provides namespace and configuration + * constants. + * + * @author Andreas Schamberger + */ +class Helper +{ + /** + * Attachment type: xsd:base64Binary (native in ext/soap). + */ + const ATTACHMENTS_TYPE_BASE64 = 1; + + /** + * Attachment type: MTOM (SOAP Message Transmission Optimization Mechanism). + */ + const ATTACHMENTS_TYPE_MTOM = 2; + + /** + * Attachment type: SWA (SOAP Messages with Attachments). + */ + const ATTACHMENTS_TYPE_SWA = 4; + + /** + * Web Services Security: SOAP Message Security 1.0 (WS-Security 2004) + */ + const NAME_WSS_SMS = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0'; + + /** + * Web Services Security: SOAP Message Security 1.1 (WS-Security 2004) + */ + const NAME_WSS_SMS_1_1 = 'http://docs.oasis-open.org/wss/oasis-wss-soap-message-security-1.1'; + + /** + * Web Services Security UsernameToken Profile 1.0 + */ + const NAME_WSS_UTP = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0'; + + /** + * Web Services Security X.509 Certificate Token Profile + */ + const NAME_WSS_X509 = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0'; + + /** + * Soap 1.1 namespace. + */ + const NS_SOAP_1_1 = 'http://schemas.xmlsoap.org/soap/envelope/'; + + /** + * Soap 1.1 namespace. + */ + const NS_SOAP_1_2 = 'http://www.w3.org/2003/05/soap-envelope/'; + + /** + * Web Services Addressing 1.0 namespace. + */ + const NS_WSA = 'http://www.w3.org/2005/08/addressing'; + + /** + * Web Services Security Extension namespace. + */ + const NS_WSS = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd'; + + /** + * Web Services Security Utility namespace. + */ + const NS_WSU = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd'; + + /** + * Describing Media Content of Binary Data in XML namespace. + */ + const NS_XMLMIME = 'http://www.w3.org/2004/11/xmlmime'; + + /** + * XML Schema namespace. + */ + const NS_XML_SCHEMA = 'http://www.w3.org/2001/XMLSchema'; + + /** + * XML Schema instance namespace. + */ + const NS_XML_SCHEMA_INSTANCE = 'http://www.w3.org/2001/XMLSchema-instance'; + + /** + * XML-binary Optimized Packaging namespace. + */ + const NS_XOP = 'http://www.w3.org/2004/08/xop/include'; + + /** + * Web Services Addressing 1.0 prefix. + */ + const PFX_WSA = 'wsa'; + + /** + * Web Services Security Extension namespace. + */ + const PFX_WSS = 'wsse'; + + /** + * Web Services Security Utility namespace prefix. + */ + const PFX_WSU = 'wsu'; + + /** + * Describing Media Content of Binary Data in XML namespace prefix. + */ + const PFX_XMLMIME = 'xmlmime'; + + /** + * XML Schema namespace prefix. + */ + const PFX_XML_SCHEMA = 'xsd'; + + /** + * XML Schema instance namespace prefix. + */ + const PFX_XML_SCHEMA_INSTANCE = 'xsi'; + + /** + * XML-binary Optimized Packaging namespace prefix. + */ + const PFX_XOP = 'xop'; + + /** + * Constant for a request. + */ + const REQUEST = 0; + + /** + * Constant for a response. + */ + const RESPONSE = 1; + + /** + * Wheather to format the XML output or not. + * + * @var boolean + */ + public static $formatXmlOutput = false; + + /** + * Contains previously defined error handler string. + * + * @var string + */ + private static $previousErrorHandler = null; + + /** + * User-defined error handler function to convert errors to exceptions. + * + * @param string $errno + * @param string $errstr + * @param string $errfile + * @param string $errline + * @throws ErrorException + */ + public static function exceptionErrorHandler($errno, $errstr, $errfile, $errline) + { + // don't throw exception for errors suppresed with @ + if (error_reporting() != 0) { + throw new \ErrorException($errstr, 0, $errno, $errfile, $errline); + } + } + + /** + * Generate a pseudo-random version 4 UUID. + * + * @see http://de.php.net/manual/en/function.uniqid.php#94959 + * @return string + */ + public static function generateUUID() + { + return sprintf( + '%04x%04x-%04x-%04x-%04x-%04x%04x%04x', + // 32 bits for "time_low" + mt_rand(0, 0xffff), mt_rand(0, 0xffff), + // 16 bits for "time_mid" + mt_rand(0, 0xffff), + // 16 bits for "time_hi_and_version", + // four most significant bits holds version number 4 + mt_rand(0, 0x0fff) | 0x4000, + // 16 bits, 8 bits for "clk_seq_hi_res", + // 8 bits for "clk_seq_low", + // two most significant bits holds zero and one for variant DCE1.1 + mt_rand(0, 0x3fff) | 0x8000, + // 48 bits for "node" + mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff) + ); + } + + /** + * Builds the current URL from the $_SERVER array. + * + * @return string + */ + public static function getCurrentUrl() + { + $url = ''; + if (isset($_SERVER['HTTPS']) && + (strtolower($_SERVER['HTTPS']) === 'on' || $_SERVER['HTTPS'] === '1')) { + $url .= 'https://'; + } else { + $url .= 'http://'; + } + $url .= isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : ''; + if (isset($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] != 80) { + $url .= ":{$_SERVER['SERVER_PORT']}"; + } + $url .= isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : ''; + return $url; + } + + /** + * Get SOAP namespace for the given $version. + * + * @param int $version SOAP_1_1|SOAP_1_2 + * @return string + */ + public static function getSoapNamespace($version) + { + if ($version === SOAP_1_2) { + return self::NS_SOAP_1_2; + } else { + return self::NS_SOAP_1_1; + } + } + + /** + * Get SOAP version from namespace URI. + * + * @param string $namespace NS_SOAP_1_1|NS_SOAP_1_2 + * @return int SOAP_1_1|SOAP_1_2 + */ + public static function getSoapVersionFromNamespace($namespace) + { + if ($namespace === self::NS_SOAP_1_2) { + return SOAP_1_2; + } else { + return SOAP_1_1; + } + } + + /** + * Runs the registered Plugins on the given request $xml. + * + * @param array(\ass\Soap\Plugin) $plugins + * @param int $requestType \ass\Soap\Helper::REQUEST|\ass\Soap\Helper::RESPONSE + * @param string $xml + * @param string $location + * @param string $action + * @param \ass\Soap\WsdlHandler $wsdlHandler + * @return string + */ + public static function runPlugins(array $plugins, $requestType, $xml, $location = null, $action = null, \ass\Soap\WsdlHandler $wsdlHandler = null) + { + if (count($plugins) > 0) { + // instantiate new dom object + $dom = new \DOMDocument('1.0'); + // format the XML if option is set + $dom->formatOutput = self::$formatXmlOutput; + $dom->loadXML($xml); + $params = array( + $dom, + $location, + $action, + $wsdlHandler + ); + if ($requestType == self::REQUEST) { + $callMethod = 'modifyRequest'; + } else { + $callMethod = 'modifyResponse'; + } + // modify dom + foreach($plugins AS $plugin) { + if ($plugin instanceof \ass\Soap\Plugin) { + call_user_func_array(array($plugin, $callMethod), $params); + } + } + // return the modified xml document + return $dom->saveXML(); + // format the XML if option is set + } elseif (self::$formatXmlOutput === true) { + $dom = new \DOMDocument('1.0'); + $dom->formatOutput = true; + $dom->loadXML($xml); + return $dom->saveXML(); + } + return $xml; + } + + /** + * Set custom error handler that converts all php errors to ErrorExceptions + * + * @param boolean $reset + */ + public static function setCustomErrorHandler($reset = false) + { + if ($reset === true && !is_null(self::$previousErrorHandler)) { + set_error_handler(self::$previousErrorHandler); + self::$previousErrorHandler = null; + } else { + self::$previousErrorHandler = set_error_handler('ass\\Soap\\Helper::exceptionErrorHandler'); + } + return self::$previousErrorHandler; + } + + /** + * Build data string to used to bridge ext/soap + * 'SOAP_MIME_ATTACHMENT:cid=...&type=...' + * + * @param string $contentId + * @param string $contentType + * @return string + */ + public static function makeSoapAttachmentDataString($contentId, $contentType) + { + $parameter = array( + 'cid' => $contentId, + 'type' => $contentType, + ); + return 'SOAP-MIME-ATTACHMENT:' . http_build_query($parameter, null, '&'); + } + + /** + * Parse data string used to bridge ext/soap + * 'SOAP_MIME_ATTACHMENT:cid=...&type=...' + * + * @param string $dataString + * @return array(string=>string) + */ + public static function parseSoapAttachmentDataString($dataString) + { + $dataString = substr($dataString, 21); + // get all data + $data = array(); + parse_str($dataString, $data); + return $data; + } + + /** + * Function to set proper http status header. + * Neccessary as there is a difference between mod_php and the cgi SAPIs. + * + * @param string $header + */ + public static function setHttpStatusHeader($header) + { + if ('cgi' == substr(php_sapi_name(), 0, 3)) { + header('Status: ' . $header); + } else { + header($_SERVER['SERVER_PROTOCOL'] . ' ' . $header); + } + } } \ No newline at end of file diff --git a/src/BeSimple/SoapClient/SoapClient.php b/src/BeSimple/SoapClient/SoapClient.php index 65b584d..86444da 100644 --- a/src/BeSimple/SoapClient/SoapClient.php +++ b/src/BeSimple/SoapClient/SoapClient.php @@ -22,31 +22,31 @@ namespace BeSimple\SoapClient; */ class SoapClient extends \SoapClient { - /** - * Last request headers. - * - * @var string + /** + * Last request headers. + * + * @var string */ private $lastRequestHeaders = ''; - - /** - * Last request. - * - * @var string + + /** + * Last request. + * + * @var string */ private $lastRequest = ''; - - /** - * Last response headers. - * - * @var string + + /** + * Last response headers. + * + * @var string */ private $lastResponseHeaders = ''; - - /** - * Last response. - * - * @var string + + /** + * Last response. + * + * @var string */ private $lastResponse = ''; @@ -65,7 +65,7 @@ class SoapClient extends \SoapClient private $wsdlFile = null; /** - * Extended constructor that saves the options as the parent class' + * Extended constructor that saves the options as the parent class' * property is private. * * @param string $wsdl @@ -99,16 +99,16 @@ class SoapClient extends \SoapClient } } // store local copy as ext/soap's property is private - $this->options = $options; + $this->options = $options; // disable obsolete trace option for native SoapClient as we need to do our own tracing anyways $options['trace'] = false; - // disable WSDL caching as we handle WSDL caching for remote URLs ourself + // disable WSDL caching as we handle WSDL caching for remote URLs ourself $options['cache_wsdl'] = WSDL_CACHE_NONE; // load WSDL and run parent constructor // can't be loaded later as we need it already in the parent constructor $this->wsdlFile = $this->loadWsdl($wsdl); parent::__construct($this->wsdlFile, $options); - } + } /** * Perform HTTP request with cURL. @@ -186,7 +186,7 @@ class SoapClient extends \SoapClient // destruct curl object unset($curl); return $response; - } + } /** * Custom request method to be able to modify the SOAP messages. diff --git a/src/BeSimple/SoapClient/WsdlDownloader.php b/src/BeSimple/SoapClient/WsdlDownloader.php index 55c7d90..6406e2b 100644 --- a/src/BeSimple/SoapClient/WsdlDownloader.php +++ b/src/BeSimple/SoapClient/WsdlDownloader.php @@ -1,227 +1,227 @@ - - * (c) Francis Besset - * - * This source file is subject to the MIT license that is bundled - * with this source code in the file LICENSE. - */ - -namespace BeSimple\SoapClient; - -/** - * Downloads WSDL files with cURL. Uses all SoapClient options for - * authentication. Uses the WSDL_CACHE_* constants and the 'soap.wsdl_*' - * ini settings. Does only file caching as SoapClient only supports a file - * name parameter. The class also resolves remote XML schema includes. - * - * @author Andreas Schamberger - */ -class WsdlDownloader -{ - /** - * Cache enabled. - * - * @var bool - */ - private $cacheEnabled; - - /** - * Cache dir. - * - * @var string - */ - private $cacheDir; - - /** - * Cache TTL. - * - * @var int - */ - private $cacheTtl; - - /** - * Options array - * - * @var array(string=>mixed) - */ - private $options = array(); - - /** - * Constructor. - * - * @param array $options - */ - public function __construct(array $options = array()) - { - // get current WSDL caching config - $this->cacheEnabled = (bool)ini_get('soap.wsdl_cache_enabled'); - if ($this->cacheEnabled === true - && isset($options['cache_wsdl']) - && $options['cache_wsdl'] === WSDL_CACHE_NONE) { - $this->cacheEnabled = false; - } - $this->cacheDir = ini_get('soap.wsdl_cache_dir'); - if (!is_dir($this->cacheDir)) { - $this->cacheDir = sys_get_temp_dir(); - } - $this->cacheDir = rtrim($this->cacheDir, '/\\'); - $this->cacheTtl = ini_get('soap.wsdl_cache_ttl'); - $this->options = $options; - if (!isset($this->options['resolve_xsd_includes'])) { - $this->options['resolve_xsd_includes'] = true; - } - } - - /** - * Download given WSDL file and return name of cache file. - * - * @param string $wsdl - * @return string - */ - public function download($wsdl) - { - // download and cache remote WSDL files or local ones where we want to - // resolve remote XSD includes - $isRemoteFile = $this->isRemoteFile($wsdl); - if ($isRemoteFile === true || $this->options['resolve_xsd_includes'] === true) { - $cacheFile = $this->cacheDir . DIRECTORY_SEPARATOR . 'wsdl_' . md5($wsdl) . '.cache'; - if ($this->cacheEnabled === false - || !file_exists($cacheFile) - || (filemtime($cacheFile) + $this->cacheTtl) < time()) { - if ($isRemoteFile === true) { - // new curl object for request - $curl = new Curl($this->options); - // execute request - $responseSuccessfull = $curl->exec($wsdl); - // get content - if ($responseSuccessfull === true) { - $response = $curl->getResponseBody(); - if ($this->options['resolve_xsd_includes'] === true) { - $this->resolveXsdIncludes($response, $cacheFile, $wsdl); - } else { - file_put_contents($cacheFile, $response); - } - } else { - throw new \ErrorException("SOAP-ERROR: Parsing WSDL: Couldn't load from '" . $wsdl ."'"); - } - } elseif (file_exists($wsdl)) { - $response = file_get_contents($wsdl); - $this->resolveXsdIncludes($response, $cacheFile); - } else { - throw new \ErrorException("SOAP-ERROR: Parsing WSDL: Couldn't load from '" . $wsdl ."'"); - } - } - return $cacheFile; - } elseif (file_exists($wsdl)) { - return realpath($wsdl); - } else { - throw new \ErrorException("SOAP-ERROR: Parsing WSDL: Couldn't load from '" . $wsdl ."'"); - } - } - - /** - * Do we have a remote file? - * - * @param string $file - * @return boolean - */ - private function isRemoteFile($file) - { - $isRemoteFile = false; - // @parse_url to suppress E_WARNING for invalid urls - if (($url = @parse_url($file)) !== false) { - if (isset($url['scheme']) && substr($url['scheme'], 0, 4) == 'http') { - $isRemoteFile = true; - } - } - return $isRemoteFile; - } - - /** - * Resolves remote XSD includes within the WSDL files. - * - * @param string $xml - * @param string $cacheFile - * @param unknown_type $parentIsRemote - * @return string - */ - private function resolveXsdIncludes($xml, $cacheFile, $parentFile = null) - { - $doc = new \DOMDocument(); - $doc->loadXML($xml); - $xpath = new \DOMXPath($doc); - $xpath->registerNamespace(Helper::PFX_XML_SCHEMA, Helper::NS_XML_SCHEMA); - $query = './/' . Helper::PFX_XML_SCHEMA . ':include'; - $nodes = $xpath->query($query); - if ($nodes->length > 0) { - foreach ($nodes as $node) { - $schemaLocation = $node->getAttribute('schemaLocation'); - if ($this->isRemoteFile($schemaLocation)) { - $schemaLocation = $this->download($schemaLocation); - $node->setAttribute('schemaLocation', $schemaLocation); - } elseif (!is_null($parentFile)) { - $schemaLocation = $this->resolveRelativePathInUrl($parentFile, $schemaLocation); - $schemaLocation = $this->download($schemaLocation); - $node->setAttribute('schemaLocation', $schemaLocation); - } - } - } - $doc->save($cacheFile); - } - - /** - * Resolves the relative path to base into an absolute. - * - * @param string $base - * @param string $relative - * @return string - */ - private function resolveRelativePathInUrl($base, $relative) - { - $urlParts = parse_url($base); - // combine base path with relative path - if (isset($urlParts['path']) && strpos($relative, '/') === 0) { - // $relative is absolute path from domain (starts with /) - $path = $relative; - } elseif (isset($urlParts['path']) && strrpos($urlParts['path'], '/') === (strlen($urlParts['path']) )) { - // base path is directory - $path = $urlParts['path'] . $relative; - } elseif (isset($urlParts['path'])) { - // strip filename from base path - $path = substr($urlParts['path'], 0, strrpos($urlParts['path'], '/')) . '/' . $relative; - } else { - // no base path - $path = '/' . $relative; - } - // foo/./bar ==> foo/bar - $path = preg_replace('~/\./~', '/', $path); - // remove double slashes - $path = preg_replace('~/+~', '/', $path); - // split path by '/' - $parts = explode('/', $path); - // resolve /../ - foreach ($parts as $key => $part) { - if ($part == "..") { - $keyToDelete = $key-1; - while ($keyToDelete > 0) { - if (isset($parts[$keyToDelete])) { - unset($parts[$keyToDelete]); - break; - } else { - $keyToDelete--; - } - } - unset($parts[$key]); - } - } - $hostname = $urlParts['scheme'] . '://' . $urlParts['host']; - if (isset($urlParts['port'])) { - $hostname .= ':' . $urlParts['port']; - } - return $hostname . implode('/', $parts); - } + + * (c) Francis Besset + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace BeSimple\SoapClient; + +/** + * Downloads WSDL files with cURL. Uses all SoapClient options for + * authentication. Uses the WSDL_CACHE_* constants and the 'soap.wsdl_*' + * ini settings. Does only file caching as SoapClient only supports a file + * name parameter. The class also resolves remote XML schema includes. + * + * @author Andreas Schamberger + */ +class WsdlDownloader +{ + /** + * Cache enabled. + * + * @var bool + */ + private $cacheEnabled; + + /** + * Cache dir. + * + * @var string + */ + private $cacheDir; + + /** + * Cache TTL. + * + * @var int + */ + private $cacheTtl; + + /** + * Options array + * + * @var array(string=>mixed) + */ + private $options = array(); + + /** + * Constructor. + * + * @param array $options + */ + public function __construct(array $options = array()) + { + // get current WSDL caching config + $this->cacheEnabled = (bool)ini_get('soap.wsdl_cache_enabled'); + if ($this->cacheEnabled === true + && isset($options['cache_wsdl']) + && $options['cache_wsdl'] === WSDL_CACHE_NONE) { + $this->cacheEnabled = false; + } + $this->cacheDir = ini_get('soap.wsdl_cache_dir'); + if (!is_dir($this->cacheDir)) { + $this->cacheDir = sys_get_temp_dir(); + } + $this->cacheDir = rtrim($this->cacheDir, '/\\'); + $this->cacheTtl = ini_get('soap.wsdl_cache_ttl'); + $this->options = $options; + if (!isset($this->options['resolve_xsd_includes'])) { + $this->options['resolve_xsd_includes'] = true; + } + } + + /** + * Download given WSDL file and return name of cache file. + * + * @param string $wsdl + * @return string + */ + public function download($wsdl) + { + // download and cache remote WSDL files or local ones where we want to + // resolve remote XSD includes + $isRemoteFile = $this->isRemoteFile($wsdl); + if ($isRemoteFile === true || $this->options['resolve_xsd_includes'] === true) { + $cacheFile = $this->cacheDir . DIRECTORY_SEPARATOR . 'wsdl_' . md5($wsdl) . '.cache'; + if ($this->cacheEnabled === false + || !file_exists($cacheFile) + || (filemtime($cacheFile) + $this->cacheTtl) < time()) { + if ($isRemoteFile === true) { + // new curl object for request + $curl = new Curl($this->options); + // execute request + $responseSuccessfull = $curl->exec($wsdl); + // get content + if ($responseSuccessfull === true) { + $response = $curl->getResponseBody(); + if ($this->options['resolve_xsd_includes'] === true) { + $this->resolveXsdIncludes($response, $cacheFile, $wsdl); + } else { + file_put_contents($cacheFile, $response); + } + } else { + throw new \ErrorException("SOAP-ERROR: Parsing WSDL: Couldn't load from '" . $wsdl ."'"); + } + } elseif (file_exists($wsdl)) { + $response = file_get_contents($wsdl); + $this->resolveXsdIncludes($response, $cacheFile); + } else { + throw new \ErrorException("SOAP-ERROR: Parsing WSDL: Couldn't load from '" . $wsdl ."'"); + } + } + return $cacheFile; + } elseif (file_exists($wsdl)) { + return realpath($wsdl); + } else { + throw new \ErrorException("SOAP-ERROR: Parsing WSDL: Couldn't load from '" . $wsdl ."'"); + } + } + + /** + * Do we have a remote file? + * + * @param string $file + * @return boolean + */ + private function isRemoteFile($file) + { + $isRemoteFile = false; + // @parse_url to suppress E_WARNING for invalid urls + if (($url = @parse_url($file)) !== false) { + if (isset($url['scheme']) && substr($url['scheme'], 0, 4) == 'http') { + $isRemoteFile = true; + } + } + return $isRemoteFile; + } + + /** + * Resolves remote XSD includes within the WSDL files. + * + * @param string $xml + * @param string $cacheFile + * @param unknown_type $parentIsRemote + * @return string + */ + private function resolveXsdIncludes($xml, $cacheFile, $parentFile = null) + { + $doc = new \DOMDocument(); + $doc->loadXML($xml); + $xpath = new \DOMXPath($doc); + $xpath->registerNamespace(Helper::PFX_XML_SCHEMA, Helper::NS_XML_SCHEMA); + $query = './/' . Helper::PFX_XML_SCHEMA . ':include'; + $nodes = $xpath->query($query); + if ($nodes->length > 0) { + foreach ($nodes as $node) { + $schemaLocation = $node->getAttribute('schemaLocation'); + if ($this->isRemoteFile($schemaLocation)) { + $schemaLocation = $this->download($schemaLocation); + $node->setAttribute('schemaLocation', $schemaLocation); + } elseif (!is_null($parentFile)) { + $schemaLocation = $this->resolveRelativePathInUrl($parentFile, $schemaLocation); + $schemaLocation = $this->download($schemaLocation); + $node->setAttribute('schemaLocation', $schemaLocation); + } + } + } + $doc->save($cacheFile); + } + + /** + * Resolves the relative path to base into an absolute. + * + * @param string $base + * @param string $relative + * @return string + */ + private function resolveRelativePathInUrl($base, $relative) + { + $urlParts = parse_url($base); + // combine base path with relative path + if (isset($urlParts['path']) && strpos($relative, '/') === 0) { + // $relative is absolute path from domain (starts with /) + $path = $relative; + } elseif (isset($urlParts['path']) && strrpos($urlParts['path'], '/') === (strlen($urlParts['path']) )) { + // base path is directory + $path = $urlParts['path'] . $relative; + } elseif (isset($urlParts['path'])) { + // strip filename from base path + $path = substr($urlParts['path'], 0, strrpos($urlParts['path'], '/')) . '/' . $relative; + } else { + // no base path + $path = '/' . $relative; + } + // foo/./bar ==> foo/bar + $path = preg_replace('~/\./~', '/', $path); + // remove double slashes + $path = preg_replace('~/+~', '/', $path); + // split path by '/' + $parts = explode('/', $path); + // resolve /../ + foreach ($parts as $key => $part) { + if ($part == "..") { + $keyToDelete = $key-1; + while ($keyToDelete > 0) { + if (isset($parts[$keyToDelete])) { + unset($parts[$keyToDelete]); + break; + } else { + $keyToDelete--; + } + } + unset($parts[$key]); + } + } + $hostname = $urlParts['scheme'] . '://' . $urlParts['host']; + if (isset($urlParts['port'])) { + $hostname .= ':' . $urlParts['port']; + } + return $hostname . implode('/', $parts); + } } \ No newline at end of file