2011-12-17 20:36:56 +01:00
< ? php
2011-10-16 19:49:24 +02:00
/*
* This file is part of the BeSimpleSoapClient .
*
* ( c ) Christian Kerl < christian - kerl @ web . de >
* ( c ) Francis Besset < francis . besset @ gmail . com >
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE .
*/
namespace BeSimple\SoapClient ;
2013-07-24 23:18:41 +02:00
use BeSimple\SoapCommon\Cache ;
2011-12-11 21:20:35 +01:00
use BeSimple\SoapCommon\Helper ;
2011-11-02 12:48:51 +01:00
2011-10-16 19:49:24 +02:00
/**
2011-11-02 12:48:51 +01:00
* Downloads WSDL files with cURL . 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 .
2011-10-16 19:49:24 +02:00
*
2011-10-22 11:28:15 +02:00
* @ author Andreas Schamberger < mail @ andreass . net >
2011-10-16 19:49:24 +02:00
*/
class WsdlDownloader
{
2018-04-06 10:58:45 +02:00
/**
* Cache enabled .
*
* @ var bool
*/
protected $cacheEnabled ;
/**
* Cache dir .
*
* @ var string
*/
protected $cacheDir ;
/**
* Cache TTL .
*
* @ var int
*/
protected $cacheTtl ;
/**
* cURL instance for downloads .
*
* @ var unknown_type
*/
protected $curl ;
/**
* Resolve WSDl / XSD includes .
*
2019-09-24 08:23:19 -04:00
* @ var bool
2018-04-06 10:58:45 +02:00
*/
protected $resolveRemoteIncludes = true ;
/**
* Constructor .
*
* @ param \BeSimple\SoapClient\Curl $curl Curl instance
2019-09-24 08:23:19 -04:00
* @ param bool $resolveRemoteIncludes WSDL / XSD include enabled ?
* @ param bool $cacheWsdl Cache constant
2018-04-06 10:58:45 +02:00
*/
public function __construct ( Curl $curl , $resolveRemoteIncludes = true , $cacheWsdl = Cache :: TYPE_DISK )
2017-07-18 18:52:52 +02:00
{
2019-09-24 08:23:19 -04:00
$this -> curl = $curl ;
$this -> resolveRemoteIncludes = ( bool ) $resolveRemoteIncludes ;
2018-04-06 10:58:45 +02:00
// get current WSDL caching config
$this -> cacheEnabled = $cacheWsdl === Cache :: TYPE_NONE ? Cache :: DISABLED : Cache :: ENABLED == Cache :: isEnabled ();
$this -> cacheDir = Cache :: getDirectory ();
$this -> cacheTtl = Cache :: getLifetime ();
2017-07-18 18:52:52 +02:00
}
2011-10-16 19:49:24 +02:00
/**
2018-04-06 10:58:45 +02:00
* Download given WSDL file and return name of cache file .
*
* @ param string $wsdl WSDL file URL / path
*
2017-02-03 15:22:37 +01:00
* @ return string
2011-10-16 19:49:24 +02:00
*/
2018-04-06 10:58:45 +02:00
public function download ( $wsdl )
2011-10-16 19:49:24 +02:00
{
2018-04-06 10:58:45 +02:00
// download and cache remote WSDL files or local ones where we want to
// resolve remote XSD includes
$isRemoteFile = $this -> isRemoteFile ( $wsdl );
if ( $isRemoteFile || $this -> resolveRemoteIncludes ) {
$cacheFilePath = $this -> cacheDir . DIRECTORY_SEPARATOR . 'wsdl_' . md5 ( $wsdl ) . '.cache' ;
2019-09-24 08:54:47 -04:00
if ( file_exists ( $cacheFilePath )) {
clearstatcache ();
$xml = \XMLReader :: open ( $cacheFilePath );
$xml -> setParserProperty ( \XMLReader :: VALIDATE , true );
if ( ! filesize ( $cacheFilePath ) || ! $xml -> isValid ()) {
unlink ( $cacheFilePath );
throw new \Exception ( 'There is something wrong with the WSDL file formatting. The file has been deleted from the cache and will be downloaded again on the next request.' );
}
}
2018-04-06 10:58:45 +02:00
if ( ! $this -> cacheEnabled || ! file_exists ( $cacheFilePath ) || ( filemtime ( $cacheFilePath ) + $this -> cacheTtl ) < time ()) {
if ( $isRemoteFile ) {
// execute request
$responseSuccessfull = $this -> curl -> exec ( $wsdl );
// get content
if ( $responseSuccessfull ) {
$response = $this -> curl -> getResponseBody ();
2019-10-10 15:16:10 -04:00
libxml_use_internal_errors ( true );
$doc = simplexml_load_string ( $response );
if ( ! $doc ) {
$errors = libxml_get_errors ();
dump ( $errors );
if ( count ( $errors )) {
throw new \Exception ( 'There is something wrong with the WSDL file formatting. The file can\'t be downloaded to be cached.' );
}
libxml_clear_errors ();
}
2018-04-06 10:58:45 +02:00
if ( $this -> resolveRemoteIncludes ) {
$this -> resolveRemoteIncludes ( $response , $cacheFilePath , $wsdl );
} else {
file_put_contents ( $cacheFilePath , $response );
}
} else {
2019-09-24 08:23:19 -04:00
throw new \ErrorException ( " SOAP-ERROR: Parsing WSDL: Couldn't load from ' " . $wsdl . " ' " );
2018-04-06 10:58:45 +02:00
}
} elseif ( file_exists ( $wsdl )) {
$response = file_get_contents ( $wsdl );
$this -> resolveRemoteIncludes ( $response , $cacheFilePath );
} else {
2019-09-24 08:23:19 -04:00
throw new \ErrorException ( " SOAP-ERROR: Parsing WSDL: Couldn't load from ' " . $wsdl . " ' " );
2017-02-03 15:22:37 +01:00
}
}
2018-04-06 10:58:45 +02:00
return $cacheFilePath ;
} elseif ( file_exists ( $wsdl )) {
return realpath ( $wsdl );
}
2019-09-24 08:23:19 -04:00
throw new \ErrorException ( " SOAP-ERROR: Parsing WSDL: Couldn't load from ' " . $wsdl . " ' " );
2018-04-06 10:58:45 +02:00
}
/**
* Do we have a remote file ?
*
* @ param string $file File URL / path
*
2019-09-24 08:23:19 -04:00
* @ return bool
2018-04-06 10:58:45 +02:00
*/
private function isRemoteFile ( $file )
{
// @parse_url to suppress E_WARNING for invalid urls
if ( false !== $url = @ parse_url ( $file )) {
if ( isset ( $url [ 'scheme' ]) && 'http' === substr ( $url [ 'scheme' ], 0 , 4 )) {
return true ;
2017-02-03 15:22:37 +01:00
}
2018-04-06 10:58:45 +02:00
}
return false ;
}
/**
* Resolves remote WSDL / XSD includes within the WSDL files .
*
2019-09-24 08:23:19 -04:00
* @ param string $xml XML file
* @ param string $cacheFilePath Cache file name
* @ param bool $parentFilePath Parent file name
2018-04-06 10:58:45 +02:00
*/
private function resolveRemoteIncludes ( $xml , $cacheFilePath , $parentFilePath = null )
{
$doc = new \DOMDocument ();
$doc -> loadXML ( $xml );
2017-02-03 15:22:37 +01:00
2018-04-06 10:58:45 +02:00
$xpath = new \DOMXPath ( $doc );
$xpath -> registerNamespace ( Helper :: PFX_XML_SCHEMA , Helper :: NS_XML_SCHEMA );
$xpath -> registerNamespace ( Helper :: PFX_WSDL , Helper :: NS_WSDL );
2013-07-24 23:18:41 +02:00
2018-04-06 10:58:45 +02:00
// WSDL include/import
$query = './/' . Helper :: PFX_WSDL . ':include | .//' . Helper :: PFX_WSDL . ':import' ;
$nodes = $xpath -> query ( $query );
if ( $nodes -> length > 0 ) {
foreach ( $nodes as $node ) {
$location = $node -> getAttribute ( 'location' );
if ( $this -> isRemoteFile ( $location )) {
$location = $this -> download ( $location );
$node -> setAttribute ( 'location' , $location );
} elseif ( null !== $parentFilePath ) {
$location = $this -> resolveRelativePathInUrl ( $parentFilePath , $location );
$location = $this -> download ( $location );
$node -> setAttribute ( 'location' , $location );
}
}
2017-02-03 15:22:37 +01:00
}
2018-04-06 10:58:45 +02:00
// XML schema include/import
$query = './/' . Helper :: PFX_XML_SCHEMA . ':include | .//' . Helper :: PFX_XML_SCHEMA . ':import' ;
$nodes = $xpath -> query ( $query );
if ( $nodes -> length > 0 ) {
foreach ( $nodes as $node ) {
if ( $node -> hasAttribute ( 'schemaLocation' )) {
$schemaLocation = $node -> getAttribute ( 'schemaLocation' );
if ( $this -> isRemoteFile ( $schemaLocation )) {
$schemaLocation = $this -> download ( $schemaLocation );
$node -> setAttribute ( 'schemaLocation' , $schemaLocation );
} elseif ( null !== $parentFilePath ) {
$schemaLocation = $this -> resolveRelativePathInUrl ( $parentFilePath , $schemaLocation );
$schemaLocation = $this -> download ( $schemaLocation );
$node -> setAttribute ( 'schemaLocation' , $schemaLocation );
}
}
}
2011-10-16 19:49:24 +02:00
}
2017-07-18 18:52:52 +02:00
2018-04-06 10:58:45 +02:00
$doc -> save ( $cacheFilePath );
2017-02-03 15:22:37 +01:00
}
2013-07-24 23:18:41 +02:00
2018-04-06 10:58:45 +02:00
/**
* Resolves the relative path to base into an absolute .
*
* @ param string $base Base path
* @ param string $relative Relative path
*
* @ return string
*/
private function resolveRelativePathInUrl ( $base , $relative )
2017-02-03 15:22:37 +01:00
{
2018-04-06 10:58:45 +02:00
$urlParts = parse_url ( $base );
// combine base path with relative path
2019-09-24 08:23:19 -04:00
if ( isset ( $urlParts [ 'path' ]) && '/' === $relative [ 0 ]) {
2018-04-06 10:58:45 +02:00
// $relative is absolute path from domain (starts with /)
$path = $relative ;
2019-09-24 08:23:19 -04:00
} elseif ( isset ( $urlParts [ 'path' ]) && strrpos ( $urlParts [ 'path' ], '/' ) === ( strlen ( $urlParts [ 'path' ]))) {
2018-04-06 10:58:45 +02:00
// 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
// remove double slashes
$path = preg_replace ( array ( '#/\./#' , '#/+#' ), '/' , $path );
// split path by '/'
$parts = explode ( '/' , $path );
// resolve /../
foreach ( $parts as $key => $part ) {
if ( '..' === $part ) {
$keyToDelete = $key - 1 ;
2017-02-03 15:22:37 +01:00
2018-04-06 10:58:45 +02:00
while ( $keyToDelete > 0 ) {
if ( isset ( $parts [ $keyToDelete ])) {
unset ( $parts [ $keyToDelete ]);
break ;
}
2019-09-24 08:23:19 -04:00
-- $keyToDelete ;
2018-04-06 10:58:45 +02:00
}
unset ( $parts [ $key ]);
}
}
2017-02-03 15:22:37 +01:00
2018-04-06 10:58:45 +02:00
$hostname = $urlParts [ 'scheme' ] . '://' . $urlParts [ 'host' ];
if ( isset ( $urlParts [ 'port' ])) {
$hostname .= ':' . $urlParts [ 'port' ];
2017-02-03 15:22:37 +01:00
}
2017-06-07 15:50:04 +02:00
2018-04-06 10:58:45 +02:00
return $hostname . implode ( '/' , $parts );
2011-10-16 19:49:24 +02:00
}
2013-07-24 23:18:41 +02:00
}