diff --git a/src/BeSimple/SoapClient/MimeFilter.php b/src/BeSimple/SoapClient/MimeFilter.php index 3549ed1..f5c4616 100644 --- a/src/BeSimple/SoapClient/MimeFilter.php +++ b/src/BeSimple/SoapClient/MimeFilter.php @@ -28,11 +28,29 @@ use BeSimple\SoapCommon\SoapResponseFilter; */ class MimeFilter implements SoapRequestFilter, SoapResponseFilter { + /** + * Attachment type. + * + * @var int Helper::ATTACHMENTS_TYPE_SWA | Helper::ATTACHMENTS_TYPE_MTOM + */ + protected $attachmentType = Helper::ATTACHMENTS_TYPE_SWA; + + /** + * Constructor. + * + * @param int $attachmentType Helper::ATTACHMENTS_TYPE_SWA | Helper::ATTACHMENTS_TYPE_MTOM + */ + public function __construct($attachmentType) + { + $this->attachmentType = $attachmentType; + } + /** * Reset all properties to default values. */ public function resetFilter() { + $this->attachmentType = Helper::ATTACHMENTS_TYPE_SWA; } /** @@ -44,8 +62,8 @@ class MimeFilter implements SoapRequestFilter, SoapResponseFilter */ public function filterRequest(CommonSoapRequest $request) { - // TODO get from request object - $attachmentsToSend = array(); + // get attachments from request object + $attachmentsToSend = $request->getAttachments(); // build mime message if we have attachments if (count($attachmentsToSend) > 0) { @@ -53,8 +71,7 @@ class MimeFilter implements SoapRequestFilter, SoapResponseFilter $soapPart = new MimePart($request->getContent(), 'text/xml', 'utf-8', MimePart::ENCODING_EIGHT_BIT); $soapVersion = $request->getVersion(); // change content type headers for MTOM with SOAP 1.1 - // TODO attachment type option!!!!!!!!!1 - if ($soapVersion == SOAP_1_1 && $this->options['features_mime'] & Helper::ATTACHMENTS_TYPE_MTOM) { + if ($soapVersion == SOAP_1_1 && $this->attachmentType & Helper::ATTACHMENTS_TYPE_MTOM) { $multipart->setHeader('Content-Type', 'type', 'application/xop+xml'); $multipart->setHeader('Content-Type', 'start-info', 'text/xml'); $soapPart->setHeader('Content-Type', 'application/xop+xml'); @@ -69,8 +86,13 @@ class MimeFilter implements SoapRequestFilter, SoapResponseFilter foreach ($attachmentsToSend as $cid => $attachment) { $multipart->addPart($attachment, false); } - $request = $multipart->getMimeMessage(); + $request->setContent($multipart->getMimeMessage()); + + // TODO $headers = $multipart->getHeadersForHttp(); + list($name, $contentType) = explode(': ', $headers[0]); + + $request->setContentType($contentType); } } @@ -83,6 +105,7 @@ class MimeFilter implements SoapRequestFilter, SoapResponseFilter */ public function filterResponse(CommonSoapResponse $response) { + // array to store attachments $attachmentsRecieved = array(); // check content type if it is a multipart mime message @@ -90,12 +113,15 @@ class MimeFilter implements SoapRequestFilter, SoapResponseFilter if (false !== stripos($responseContentType, 'multipart/related')) { // parse mime message $headers = array( - 'Content-Type' => $responseContentType, + 'Content-Type' => trim($responseContentType), ); $multipart = MimeParser::parseMimeMessage($response->getContent(), $headers); // get soap payload and update SoapResponse object $soapPart = $multipart->getPart(); - $response->setContent($soapPart->getContent()); + // convert href -> myhref for external references as PHP throws exception in this case + // http://svn.php.net/viewvc/php/php-src/branches/PHP_5_4/ext/soap/php_encoding.c?view=markup#l3436 + $content = preg_replace('/href=(?!#)/', 'myhref=', $soapPart->getContent()); + $response->setContent($content); $response->setContentType($soapPart->getHeader('Content-Type')); // store attachments $attachments = $multipart->getParts(false); @@ -104,9 +130,9 @@ class MimeFilter implements SoapRequestFilter, SoapResponseFilter } } - // add attachment to request object + // add attachments to response object if (count($attachmentsRecieved) > 0) { - // TODO add to response object + $response->setAttachments($attachmentsRecieved); } } } \ No newline at end of file diff --git a/src/BeSimple/SoapClient/MtomTypeConverter.php b/src/BeSimple/SoapClient/MtomTypeConverter.php new file mode 100644 index 0000000..ab74f83 --- /dev/null +++ b/src/BeSimple/SoapClient/MtomTypeConverter.php @@ -0,0 +1,93 @@ + + * (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; + +use BeSimple\SoapCommon\Helper; +use BeSimple\SoapCommon\Mime\Part as MimePart; +use BeSimple\SoapCommon\SoapKernel; +use BeSimple\SoapCommon\SoapRequest as CommonSoapRequest; +use BeSimple\SoapCommon\SoapResponse as CommonSoapResponse; +use BeSimple\SoapCommon\Converter\TypeConverterInterface; + +/** + * MTOM type converter. + * + * @author Andreas Schamberger + */ +class MtomTypeConverter +{ + /** + * {@inheritDoc} + */ + public function getTypeNamespace() + { + return 'http://www.w3.org/2001/XMLSchema'; + } + + /** + * {@inheritDoc} + */ + public function getTypeName() + { + return 'base64Binary'; + } + + /** + * {@inheritDoc} + */ + public function convertXmlToPhp($data, $soapKernel) + { + $doc = new \DOMDocument(); + $doc->loadXML($data); + + $includes = $doc->getElementsByTagNameNS(Helper::NS_XOP, 'Include'); + $include = $includes->item(0); + + $ref = $include->getAttribute('myhref'); + + if ('cid:' === substr($ref, 0, 4)) { + $contentId = urldecode(substr($ref, 4)); + + if (null !== ($part = $soapKernel->getAttachment($contentId))) { + + return $part->getContent(); + } else { + + return null; + } + } + + return $data; + } + + /** + * {@inheritDoc} + */ + public function convertPhpToXml($data, $soapKernel) + { + $part = new MimePart($data); + $contentId = trim($part->getHeader('Content-ID'), '<>'); + + $soapKernel->addAttachment($part); + + $doc = new \DOMDocument(); + $node = $doc->createElement($this->getTypeName()); + + // add xop:Include element + $xinclude = $doc->createElementNS(Helper::NS_XOP, Helper::PFX_XOP . ':Include'); + $xinclude->setAttribute('href', 'cid:' . $contentId); + $node->appendChild($xinclude); + + return $doc->saveXML(); + } +} diff --git a/src/BeSimple/SoapClient/SoapClient.php b/src/BeSimple/SoapClient/SoapClient.php index 5c6f2a2..23b529a 100644 --- a/src/BeSimple/SoapClient/SoapClient.php +++ b/src/BeSimple/SoapClient/SoapClient.php @@ -12,6 +12,7 @@ namespace BeSimple\SoapClient; +use BeSimple\SoapCommon\Helper; use BeSimple\SoapCommon\SoapKernel; /** @@ -24,6 +25,13 @@ use BeSimple\SoapCommon\SoapKernel; */ class SoapClient extends \SoapClient { + /** + * SOAP attachment type. + * + * @var int + */ + protected $attachmentType = Helper::ATTACHMENTS_TYPE_BASE64; + /** * Soap version. * @@ -96,10 +104,16 @@ class SoapClient extends \SoapClient if (isset($options['soap_version'])) { $this->soapVersion = $options['soap_version']; } + // attachment handling + if (isset($options['attachment_type'])) { + $this->attachmentType = $options['attachment_type']; + } $this->curl = new Curl($options); $wsdlFile = $this->loadWsdl($wsdl, $options); // TODO $wsdlHandler = new WsdlHandler($wsdlFile, $this->soapVersion); $this->soapKernel = new SoapKernel(); + // set up type converter and mime filter + $this->configureMime($options); // we want the exceptions option to be set $options['exceptions'] = true; // disable obsolete trace option for native SoapClient as we need to do our own tracing anyways @@ -244,6 +258,43 @@ class SoapClient extends \SoapClient return $this->lastResponse; } + /** + * Configure filter and type converter for SwA/MTOM. + * + * @param array &$options SOAP constructor options array. + * + * @return void + */ + private function configureMime(array &$options) + { + if (Helper::ATTACHMENTS_TYPE_BASE64 !== $this->attachmentType) { + // register mime filter in SoapKernel + $mimeFilter = new MimeFilter($this->attachmentType); + $this->soapKernel->registerFilter($mimeFilter); + // configure type converter + if (Helper::ATTACHMENTS_TYPE_SWA === $this->attachmentType) { + $converter = new SwaTypeConverter(); + } elseif (Helper::ATTACHMENTS_TYPE_MTOM === $this->attachmentType) { + $converter = new MtomTypeConverter(); + } + // configure typemap + if (!isset($options['typemap'])) { + $options['typemap'] = array(); + } + $soapKernel = $this->soapKernel; + $options['typemap'][] = array( + 'type_name' => $converter->getTypeName(), + 'type_ns' => $converter->getTypeNamespace(), + 'from_xml' => function($input) use ($converter, $soapKernel) { + return $converter->convertXmlToPhp($input, $soapKernel); + }, + 'to_xml' => function($input) use ($converter, $soapKernel) { + return $converter->convertPhpToXml($input, $soapKernel); + }, + ); + } + } + /** * Get SoapKernel instance. * diff --git a/src/BeSimple/SoapClient/SwaTypeConverter.php b/src/BeSimple/SoapClient/SwaTypeConverter.php new file mode 100644 index 0000000..a7f50ea --- /dev/null +++ b/src/BeSimple/SoapClient/SwaTypeConverter.php @@ -0,0 +1,82 @@ + + * (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; + +use BeSimple\SoapCommon\Helper; +use BeSimple\SoapCommon\Mime\Part as MimePart; +use BeSimple\SoapCommon\SoapKernel; +use BeSimple\SoapCommon\SoapRequest as CommonSoapRequest; +use BeSimple\SoapCommon\SoapResponse as CommonSoapResponse; +use BeSimple\SoapCommon\Converter\TypeConverterInterface; + +/** + * SwA type converter. + * + * @author Andreas Schamberger + */ +class SwaTypeConverter +{ + /** + * {@inheritDoc} + */ + public function getTypeNamespace() + { + return 'http://www.w3.org/2001/XMLSchema'; + } + + /** + * {@inheritDoc} + */ + public function getTypeName() + { + return 'base64Binary'; + } + + /** + * {@inheritDoc} + */ + public function convertXmlToPhp($data, $soapKernel) + { + $doc = new \DOMDocument(); + $doc->loadXML($data); + + $ref = $doc->documentElement->getAttribute('myhref'); + + if ('cid:' === substr($ref, 0, 4)) { + $contentId = urldecode(substr($ref, 4)); + + if (null !== ($part = $soapKernel->getAttachment($contentId))) { + + return $part->getContent(); + } else { + + return null; + } + } + + return $data; + } + + /** + * {@inheritDoc} + */ + public function convertPhpToXml($data, $soapKernel) + { + $part = new MimePart($data); + $contentId = trim($part->getHeader('Content-ID'), '<>'); + + $soapKernel->addAttachment($part); + + return sprintf('<%s href="%s"/>', $this->getTypeName(), $contentId); + } +}