From ebf9a393da394fa057a2a7e2ec84c0cd22c50592 Mon Sep 17 00:00:00 2001 From: Marvin Muxfeld Date: Mon, 8 Jul 2024 15:34:51 +0200 Subject: [PATCH 1/6] PISHPS-303: extended LineItemDataExtractor. It now also sanitizes query parameters --- .../MollieApi/LineItemDataExtractor.php | 53 ++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/src/Service/MollieApi/LineItemDataExtractor.php b/src/Service/MollieApi/LineItemDataExtractor.php index bd37aa31c..c3f297a11 100644 --- a/src/Service/MollieApi/LineItemDataExtractor.php +++ b/src/Service/MollieApi/LineItemDataExtractor.php @@ -46,6 +46,7 @@ public function extractExtraData(OrderLineItemEntity $lineItem): LineItemExtraDa private function encodePathAndQuery(string $fullUrl):string { + $fullUrl .= '&width=1920&height={height}'; $urlParts = parse_url($fullUrl); $scheme = isset($urlParts['scheme']) ? $urlParts['scheme'] . '://' : ''; @@ -70,11 +71,61 @@ private function encodePathAndQuery(string $fullUrl):string $path = implode('/', $pathParts); } - $query = isset($urlParts['query']) ? '?' . $urlParts['query'] : ''; + $query = ''; + if (isset($urlParts['query'])) { + $urlParts['query'] = $this->sanitizeQuery(explode('&', $urlParts['query'])); + $query = '?' . implode('&', $urlParts['query']); + } $fragment = isset($urlParts['fragment']) ? '#' . $urlParts['fragment'] : ''; return trim($scheme.$user.$pass.$host.$port.$path.$query.$fragment); } + + /** + * Sanitizes an array of query strings by URL encoding their components. + * + * This method takes an array of query strings, where each string is expected to be in the format + * 'key=value'. It applies the sanitizeQueryPart method to each query string to ensure the keys + * and values are URL encoded, making them safe for use in URLs. + * + * @param array $query An array of query strings to be sanitized. + * @return array The sanitized array with URL encoded query strings. + */ + private function sanitizeQuery(array $query): array + { + // Use array_map to apply the sanitizeQueryPart method to each element of the $query array + return array_map([$this, 'sanitizeQueryPart'], $query); + } + + /** + * Sanitizes a single query string part by URL encoding its key and value. + * + * This method takes a query string part, expected to be in the format 'key=value', splits it into + * its key and value components, URL encodes each component, and then recombines them into a single + * query string part. + * + * @param string $queryPart A single query string part to be sanitized. + * @return string The sanitized query string part with URL encoded components. + */ + private function sanitizeQueryPart(string $queryPart): string + { + // If the query part does not contain an '=', return it as is + if (strpos($queryPart, '=') === false) { + return$queryPart; + } + + // Split the query part into key and value based on the '=' delimiter + [$key, $value] = explode('=', $queryPart); + + // URL encode the key (first element of the split array) + $key = rawurlencode($key); + + // URL encode the value (second element of the split array) + $value = rawurlencode($value); + + // Join the key and value back into a single string with '=' and return it + return sprintf('%s=%s', $key, $value); + } } From d16c21dd7b883bf928548464132e287537da3a3b Mon Sep 17 00:00:00 2001 From: Marvin Muxfeld Date: Tue, 9 Jul 2024 08:34:30 +0200 Subject: [PATCH 2/6] PISHPS-303: removed redundant comments --- src/Service/MollieApi/LineItemDataExtractor.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/Service/MollieApi/LineItemDataExtractor.php b/src/Service/MollieApi/LineItemDataExtractor.php index c3f297a11..f7cf89a98 100644 --- a/src/Service/MollieApi/LineItemDataExtractor.php +++ b/src/Service/MollieApi/LineItemDataExtractor.php @@ -111,7 +111,6 @@ private function sanitizeQuery(array $query): array */ private function sanitizeQueryPart(string $queryPart): string { - // If the query part does not contain an '=', return it as is if (strpos($queryPart, '=') === false) { return$queryPart; } @@ -119,13 +118,9 @@ private function sanitizeQueryPart(string $queryPart): string // Split the query part into key and value based on the '=' delimiter [$key, $value] = explode('=', $queryPart); - // URL encode the key (first element of the split array) $key = rawurlencode($key); - - // URL encode the value (second element of the split array) $value = rawurlencode($value); - // Join the key and value back into a single string with '=' and return it return sprintf('%s=%s', $key, $value); } } From 81edacc177a57d7bf74ca127714b653373f8300d Mon Sep 17 00:00:00 2001 From: Marvin Muxfeld Date: Tue, 9 Jul 2024 11:11:45 +0200 Subject: [PATCH 3/6] PISHPS-303: removed debug line --- src/Service/MollieApi/LineItemDataExtractor.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Service/MollieApi/LineItemDataExtractor.php b/src/Service/MollieApi/LineItemDataExtractor.php index f7cf89a98..f59c8fc2b 100644 --- a/src/Service/MollieApi/LineItemDataExtractor.php +++ b/src/Service/MollieApi/LineItemDataExtractor.php @@ -46,7 +46,6 @@ public function extractExtraData(OrderLineItemEntity $lineItem): LineItemExtraDa private function encodePathAndQuery(string $fullUrl):string { - $fullUrl .= '&width=1920&height={height}'; $urlParts = parse_url($fullUrl); $scheme = isset($urlParts['scheme']) ? $urlParts['scheme'] . '://' : ''; From 987faadf37ca7cbe89c9d4e78dbc6c5aae02f972 Mon Sep 17 00:00:00 2001 From: Marvin Muxfeld Date: Tue, 9 Jul 2024 14:01:14 +0200 Subject: [PATCH 4/6] PISHPS-303: fixed phpstan error --- src/Service/MollieApi/LineItemDataExtractor.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Service/MollieApi/LineItemDataExtractor.php b/src/Service/MollieApi/LineItemDataExtractor.php index f59c8fc2b..1bea058e9 100644 --- a/src/Service/MollieApi/LineItemDataExtractor.php +++ b/src/Service/MollieApi/LineItemDataExtractor.php @@ -89,8 +89,8 @@ private function encodePathAndQuery(string $fullUrl):string * 'key=value'. It applies the sanitizeQueryPart method to each query string to ensure the keys * and values are URL encoded, making them safe for use in URLs. * - * @param array $query An array of query strings to be sanitized. - * @return array The sanitized array with URL encoded query strings. + * @param string[] $query An array of query strings to be sanitized. + * @return string[] The sanitized array with URL encoded query strings. */ private function sanitizeQuery(array $query): array { From 519834b109c95f86dd5f4dc568970000494c1a5f Mon Sep 17 00:00:00 2001 From: Marvin Muxfeld Date: Fri, 26 Jul 2024 13:22:16 +0200 Subject: [PATCH 5/6] PISHPS-303: added missing space --- src/Service/MollieApi/LineItemDataExtractor.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Service/MollieApi/LineItemDataExtractor.php b/src/Service/MollieApi/LineItemDataExtractor.php index 1bea058e9..2ae07eb1a 100644 --- a/src/Service/MollieApi/LineItemDataExtractor.php +++ b/src/Service/MollieApi/LineItemDataExtractor.php @@ -111,7 +111,7 @@ private function sanitizeQuery(array $query): array private function sanitizeQueryPart(string $queryPart): string { if (strpos($queryPart, '=') === false) { - return$queryPart; + return $queryPart; } // Split the query part into key and value based on the '=' delimiter From e8d64fb5aa4d803631672b8f6c4e09bfed1b16b2 Mon Sep 17 00:00:00 2001 From: Marvin Muxfeld Date: Thu, 1 Aug 2024 10:48:15 +0200 Subject: [PATCH 6/6] PISHPS-329: added UrlParsingService to separate tracking code from tracking url when not entered correctly --- src/Resources/config/services.xml | 6 +- src/Resources/config/services/services.xml | 4 +- .../MollieApi/LineItemDataExtractor.php | 91 ++-------- src/Service/TrackingInfoStructFactory.php | 24 ++- src/Service/UrlParsingService.php | 131 +++++++++++++++ src/Subscriber/OrderDeliverySubscriber.php | 2 +- .../ShipmentManager/ShipmentManagerTest.php | 3 +- .../Builder/AbstractMollieOrderBuilder.php | 3 +- .../Builder/MollieLineItemBuilderTest.php | 3 +- .../MollieApi/LineItemDataExtractorTest.php | 11 +- .../Service/TrackingInfoStructFactoryTest.php | 31 +--- .../PHPUnit/Service/UrlParsingServiceTest.php | 159 ++++++++++++++++++ .../Utils/Traits/PaymentBuilderTrait.php | 3 +- 13 files changed, 343 insertions(+), 128 deletions(-) create mode 100644 src/Service/UrlParsingService.php create mode 100644 tests/PHPUnit/Service/UrlParsingServiceTest.php diff --git a/src/Resources/config/services.xml b/src/Resources/config/services.xml index 7f61d88d6..5f0063151 100644 --- a/src/Resources/config/services.xml +++ b/src/Resources/config/services.xml @@ -176,7 +176,11 @@ %env(default::APP_URL)% - + + + + + diff --git a/src/Resources/config/services/services.xml b/src/Resources/config/services/services.xml index 935d2dfdc..0ba19a43b 100644 --- a/src/Resources/config/services/services.xml +++ b/src/Resources/config/services/services.xml @@ -113,7 +113,9 @@ - + + + diff --git a/src/Service/MollieApi/LineItemDataExtractor.php b/src/Service/MollieApi/LineItemDataExtractor.php index 2ae07eb1a..24191d344 100644 --- a/src/Service/MollieApi/LineItemDataExtractor.php +++ b/src/Service/MollieApi/LineItemDataExtractor.php @@ -2,6 +2,7 @@ namespace Kiener\MolliePayments\Service\MollieApi; +use Kiener\MolliePayments\Service\UrlParsingService; use Kiener\MolliePayments\Struct\LineItemExtraData; use Shopware\Core\Checkout\Order\Aggregate\OrderLineItem\OrderLineItemEntity; use Shopware\Core\Content\Media\MediaEntity; @@ -13,6 +14,15 @@ class LineItemDataExtractor { + /** + * @var UrlParsingService + */ + private $urlParsingService; + public function __construct(UrlParsingService $urlParsingService) + { + $this->urlParsingService = $urlParsingService; + } + public function extractExtraData(OrderLineItemEntity $lineItem): LineItemExtraData { $product = $lineItem->getProduct(); @@ -30,7 +40,7 @@ public function extractExtraData(OrderLineItemEntity $lineItem): LineItemExtraDa && $medias->first()->getMedia() instanceof MediaEntity ) { $url = $medias->first()->getMedia()->getUrl(); - $url = $this->encodePathAndQuery($url); + $url = $this->urlParsingService->encodePathAndQuery($url); $extraData->setImageUrl($url); } @@ -43,83 +53,4 @@ public function extractExtraData(OrderLineItemEntity $lineItem): LineItemExtraDa return $extraData; } - - private function encodePathAndQuery(string $fullUrl):string - { - $urlParts = parse_url($fullUrl); - - $scheme = isset($urlParts['scheme']) ? $urlParts['scheme'] . '://' : ''; - - $host = isset($urlParts['host']) ? $urlParts['host'] : ''; - - $port = isset($urlParts['port']) ? ':' . $urlParts['port'] : ''; - - $user = isset($urlParts['user']) ? $urlParts['user'] : ''; - - $pass = isset($urlParts['pass']) ? ':' . $urlParts['pass'] : ''; - - $pass = ($user || $pass) ? "$pass@" : ''; - - $path = isset($urlParts['path']) ? $urlParts['path'] : ''; - - if (mb_strlen($path) > 0) { - $pathParts = explode('/', $path); - array_walk($pathParts, function (&$pathPart) { - $pathPart = rawurlencode($pathPart); - }); - $path = implode('/', $pathParts); - } - - $query = ''; - if (isset($urlParts['query'])) { - $urlParts['query'] = $this->sanitizeQuery(explode('&', $urlParts['query'])); - $query = '?' . implode('&', $urlParts['query']); - } - - - $fragment = isset($urlParts['fragment']) ? '#' . $urlParts['fragment'] : ''; - - return trim($scheme.$user.$pass.$host.$port.$path.$query.$fragment); - } - - /** - * Sanitizes an array of query strings by URL encoding their components. - * - * This method takes an array of query strings, where each string is expected to be in the format - * 'key=value'. It applies the sanitizeQueryPart method to each query string to ensure the keys - * and values are URL encoded, making them safe for use in URLs. - * - * @param string[] $query An array of query strings to be sanitized. - * @return string[] The sanitized array with URL encoded query strings. - */ - private function sanitizeQuery(array $query): array - { - // Use array_map to apply the sanitizeQueryPart method to each element of the $query array - return array_map([$this, 'sanitizeQueryPart'], $query); - } - - /** - * Sanitizes a single query string part by URL encoding its key and value. - * - * This method takes a query string part, expected to be in the format 'key=value', splits it into - * its key and value components, URL encodes each component, and then recombines them into a single - * query string part. - * - * @param string $queryPart A single query string part to be sanitized. - * @return string The sanitized query string part with URL encoded components. - */ - private function sanitizeQueryPart(string $queryPart): string - { - if (strpos($queryPart, '=') === false) { - return $queryPart; - } - - // Split the query part into key and value based on the '=' delimiter - [$key, $value] = explode('=', $queryPart); - - $key = rawurlencode($key); - $value = rawurlencode($value); - - return sprintf('%s=%s', $key, $value); - } } diff --git a/src/Service/TrackingInfoStructFactory.php b/src/Service/TrackingInfoStructFactory.php index 5e4d1f908..b0a5b5a6a 100644 --- a/src/Service/TrackingInfoStructFactory.php +++ b/src/Service/TrackingInfoStructFactory.php @@ -13,6 +13,16 @@ class TrackingInfoStructFactory { use StringTrait; + /** + * @var UrlParsingService + */ + private $urlParsingService; + + public function __construct(UrlParsingService $urlParsingService) + { + $this->urlParsingService = $urlParsingService; + } + /** * Mollie throws an error with length >= 100 @@ -91,6 +101,11 @@ private function createInfoStruct(string $trackingCarrier, string $trackingCode, throw new \InvalidArgumentException('Missing Argument for Tracking Code!'); } + // determine if the provided tracking code is actually a tracking URL + if (empty($trackingUrl) === true || $this->urlParsingService->isUrl($trackingCode)) { + [$trackingCode, $trackingUrl] = $this->urlParsingService->parseTrackingCodeFromUrl($trackingCode); + } + # we just have to completely remove those codes, so that no tracking happens, but a shipping works. # still, if we find multiple codes (because separators exist), then we use the first one only if (mb_strlen($trackingCode) > self::MAX_TRACKING_CODE_LENGTH) { @@ -114,13 +129,8 @@ private function createInfoStruct(string $trackingCarrier, string $trackingCode, $trackingUrl = trim(sprintf($trackingUrl, $trackingCode)); - if (filter_var($trackingUrl, FILTER_VALIDATE_URL) === false) { - $trackingUrl = ''; - } - - # following characters are not allowed in the tracking URL {,},<,>,# - if (preg_match_all('/[{}<>#]/m', $trackingUrl)) { - $trackingUrl = ''; + if ($this->urlParsingService->isUrl($trackingUrl) === false) { + return new ShipmentTrackingInfoStruct($trackingCarrier, $trackingCode, ''); } return new ShipmentTrackingInfoStruct($trackingCarrier, $trackingCode, $trackingUrl); diff --git a/src/Service/UrlParsingService.php b/src/Service/UrlParsingService.php new file mode 100644 index 000000000..2bbeec37d --- /dev/null +++ b/src/Service/UrlParsingService.php @@ -0,0 +1,131 @@ + 0) { + $pathParts = explode('/', $path); + array_walk($pathParts, function (&$pathPart) { + $pathPart = rawurlencode($pathPart); + }); + $path = implode('/', $pathParts); + } + + $query = ''; + if (isset($urlParts['query'])) { + $urlParts['query'] = $this->sanitizeQuery(explode('&', $urlParts['query'])); + $query = '?' . implode('&', $urlParts['query']); + } + + + $fragment = isset($urlParts['fragment']) ? '#' . rawurlencode($urlParts['fragment']) : ''; + + return trim($scheme.$user.$pass.$host.$port.$path.$query.$fragment); + } + + /** + * Sanitizes an array of query strings by URL encoding their components. + * + * This method takes an array of query strings, where each string is expected to be in the format + * 'key=value'. It applies the sanitizeQueryPart method to each query string to ensure the keys + * and values are URL encoded, making them safe for use in URLs. + * + * @param string[] $query An array of query strings to be sanitized. + * @return string[] The sanitized array with URL encoded query strings. + */ + public function sanitizeQuery(array $query): array + { + // Use array_map to apply the sanitizeQueryPart method to each element of the $query array + return array_map([$this, 'sanitizeQueryPart'], $query); + } + + /** + * Sanitizes a single query string part by URL encoding its key and value. + * + * This method takes a query string part, expected to be in the format 'key=value', splits it into + * its key and value components, URL encodes each component, and then recombines them into a single + * query string part. + * + * @param string $queryPart A single query string part to be sanitized. + * @return string The sanitized query string part with URL encoded components. + */ + public function sanitizeQueryPart(string $queryPart): string + { + if (strpos($queryPart, '=') === false) { + return $queryPart; + } + + // Split the query part into key and value based on the '=' delimiter + [$key, $value] = explode('=', $queryPart); + + $key = rawurlencode($key); + $value = rawurlencode($value); + + return sprintf('%s=%s', $key, $value); + } +} diff --git a/src/Subscriber/OrderDeliverySubscriber.php b/src/Subscriber/OrderDeliverySubscriber.php index c7d3b7a8f..dd915e1d7 100644 --- a/src/Subscriber/OrderDeliverySubscriber.php +++ b/src/Subscriber/OrderDeliverySubscriber.php @@ -119,7 +119,7 @@ public function onOrderDeliveryChanged(StateMachineStateChangeEvent $event): voi $this->mollieShipment->shipOrderRest($order, null, $event->getContext()); } catch (\Throwable $ex) { - $this->logger->error('Failed to transfer delivery state to mollie: '.$ex->getMessage()); + $this->logger->error('Failed to transfer delivery state to mollie: '.$ex->getMessage(), ['exception' => $ex]); return; } } diff --git a/tests/PHPUnit/Components/ShipmentManager/ShipmentManagerTest.php b/tests/PHPUnit/Components/ShipmentManager/ShipmentManagerTest.php index d14ce1de2..952af4d0e 100644 --- a/tests/PHPUnit/Components/ShipmentManager/ShipmentManagerTest.php +++ b/tests/PHPUnit/Components/ShipmentManager/ShipmentManagerTest.php @@ -14,6 +14,7 @@ use Kiener\MolliePayments\Service\OrderService; use Kiener\MolliePayments\Service\TrackingInfoStructFactory; use Kiener\MolliePayments\Service\Transition\DeliveryTransitionService; +use Kiener\MolliePayments\Service\UrlParsingService; use MolliePayments\Tests\Fakes\FakeShipment; use MolliePayments\Tests\Traits\OrderTrait; use PHPUnit\Framework\TestCase; @@ -65,7 +66,7 @@ public function setUp(): void $orderService, $deliveryExtractor, new OrderItemsExtractor(), - new TrackingInfoStructFactory() + new TrackingInfoStructFactory(new UrlParsingService()) ); $this->context = $this->getMockBuilder(Context::class)->disableOriginalConstructor()->getMock(); diff --git a/tests/PHPUnit/Service/MollieApi/Builder/AbstractMollieOrderBuilder.php b/tests/PHPUnit/Service/MollieApi/Builder/AbstractMollieOrderBuilder.php index 423ebecc0..fec432dc0 100644 --- a/tests/PHPUnit/Service/MollieApi/Builder/AbstractMollieOrderBuilder.php +++ b/tests/PHPUnit/Service/MollieApi/Builder/AbstractMollieOrderBuilder.php @@ -23,6 +23,7 @@ use Kiener\MolliePayments\Service\Router\RoutingDetector; use Kiener\MolliePayments\Service\SettingsService; use Kiener\MolliePayments\Service\Transition\TransactionTransitionServiceInterface; +use Kiener\MolliePayments\Service\UrlParsingService; use Kiener\MolliePayments\Setting\MollieSettingStruct; use Kiener\MolliePayments\Validator\IsOrderLineItemValid; use MolliePayments\Tests\Fakes\FakeCompatibilityGateway; @@ -180,7 +181,7 @@ public function setUp(): void new MollieLineItemBuilder( new IsOrderLineItemValid(), new PriceCalculator(), - new LineItemDataExtractor(), + new LineItemDataExtractor(new UrlParsingService()), new FakeCompatibilityGateway(), new RoundingDifferenceFixer(), new MollieLineItemHydrator(new MollieOrderPriceBuilder()), diff --git a/tests/PHPUnit/Service/MollieApi/Builder/MollieLineItemBuilderTest.php b/tests/PHPUnit/Service/MollieApi/Builder/MollieLineItemBuilderTest.php index 97f586acf..08db54f81 100644 --- a/tests/PHPUnit/Service/MollieApi/Builder/MollieLineItemBuilderTest.php +++ b/tests/PHPUnit/Service/MollieApi/Builder/MollieLineItemBuilderTest.php @@ -9,6 +9,7 @@ use Kiener\MolliePayments\Service\MollieApi\Fixer\RoundingDifferenceFixer; use Kiener\MolliePayments\Service\MollieApi\LineItemDataExtractor; use Kiener\MolliePayments\Service\MollieApi\PriceCalculator; +use Kiener\MolliePayments\Service\UrlParsingService; use Kiener\MolliePayments\Setting\MollieSettingStruct; use Kiener\MolliePayments\Validator\IsOrderLineItemValid; use Mollie\Api\Types\OrderLineType; @@ -38,7 +39,7 @@ public function setUp(): void $this->builder = new MollieLineItemBuilder( (new IsOrderLineItemValid()), (new PriceCalculator()), - (new LineItemDataExtractor()), + (new LineItemDataExtractor(new UrlParsingService())), new FakeCompatibilityGateway(), new RoundingDifferenceFixer(), new MollieLineItemHydrator(new MollieOrderPriceBuilder()), diff --git a/tests/PHPUnit/Service/MollieApi/LineItemDataExtractorTest.php b/tests/PHPUnit/Service/MollieApi/LineItemDataExtractorTest.php index 2974f936a..272d8df14 100644 --- a/tests/PHPUnit/Service/MollieApi/LineItemDataExtractorTest.php +++ b/tests/PHPUnit/Service/MollieApi/LineItemDataExtractorTest.php @@ -3,6 +3,7 @@ namespace MolliePayments\Tests\Service\MollieApi; use Kiener\MolliePayments\Service\MollieApi\LineItemDataExtractor; +use Kiener\MolliePayments\Service\UrlParsingService; use PHPUnit\Framework\TestCase; use Shopware\Core\Checkout\Order\Aggregate\OrderLineItem\OrderLineItemEntity; use Shopware\Core\Content\Media\MediaEntity; @@ -17,7 +18,7 @@ class LineItemDataExtractorTest extends TestCase { public function testWithMissingProduct(): void { - $extractor = new LineItemDataExtractor(); + $extractor = new LineItemDataExtractor(new UrlParsingService()); $lineItemId = Uuid::randomHex(); $lineItem = new OrderLineItemEntity(); $lineItem->setId($lineItemId); @@ -31,7 +32,7 @@ public function testWithMissingProduct(): void public function testNoMediaNoSeo(): void { $expected = 'foo'; - $extractor = new LineItemDataExtractor(); + $extractor = new LineItemDataExtractor(new UrlParsingService()); $lineItem = new OrderLineItemEntity(); $product = new ProductEntity(); $product->setProductNumber($expected); @@ -47,7 +48,7 @@ public function testMediaExtraction(): void { $expectedImageUrl = 'https://bar.baz'; $expectedProductNumber = 'foo'; - $extractor = new LineItemDataExtractor(); + $extractor = new LineItemDataExtractor(new UrlParsingService()); $lineItem = new OrderLineItemEntity(); $product = new ProductEntity(); $product->setProductNumber($expectedProductNumber); @@ -71,7 +72,7 @@ public function testSeoUrlExtraction(): void { $expectedSeoUrl = 'https://bar.foo'; $expectedProductNumber = 'foo'; - $extractor = new LineItemDataExtractor(); + $extractor = new LineItemDataExtractor(new UrlParsingService()); $lineItem = new OrderLineItemEntity(); $product = new ProductEntity(); $product->setProductNumber($expectedProductNumber); @@ -93,7 +94,7 @@ public function testCompleteExtraction(): void $expectedImageUrl = 'https://bar.baz'; $expectedSeoUrl = 'https://bar.foo'; $expectedProductNumber = 'foo'; - $extractor = new LineItemDataExtractor(); + $extractor = new LineItemDataExtractor(new UrlParsingService()); $lineItem = new OrderLineItemEntity(); $product = new ProductEntity(); $product->setProductNumber($expectedProductNumber); diff --git a/tests/PHPUnit/Service/TrackingInfoStructFactoryTest.php b/tests/PHPUnit/Service/TrackingInfoStructFactoryTest.php index 15598acdc..df5e64643 100644 --- a/tests/PHPUnit/Service/TrackingInfoStructFactoryTest.php +++ b/tests/PHPUnit/Service/TrackingInfoStructFactoryTest.php @@ -5,6 +5,7 @@ use Kiener\MolliePayments\Components\ShipmentManager\Exceptions\NoDeliveriesFoundExceptions; use Kiener\MolliePayments\Service\TrackingInfoStructFactory; +use Kiener\MolliePayments\Service\UrlParsingService; use PHPUnit\Framework\TestCase; use Shopware\Core\Checkout\Order\Aggregate\OrderDelivery\OrderDeliveryCollection; use Shopware\Core\Checkout\Order\Aggregate\OrderDelivery\OrderDeliveryEntity; @@ -23,7 +24,7 @@ class TrackingInfoStructFactoryTest extends TestCase public function setUp(): void { - $this->factory = new TrackingInfoStructFactory(); + $this->factory = new TrackingInfoStructFactory(new UrlParsingService()); } @@ -160,34 +161,6 @@ public function testCommaSeparatorHasHigherPriority(): void } - /** - * @return array - */ - public function invalidCodes(): array - { - return [ - ['some{code'], - ['some}code'], - ['somecode'], - ['some#code'], - ['some#<>{},' . str_repeat('1', 200)], - [str_repeat('1', 200)], - ]; - } - - /** - * @dataProvider invalidCodes - * @param string $invalidCode - * @return void - */ - public function testUrlEmptyOnInvalidCodes(string $invalidCode): void - { - $trackingInfoStruct = $this->factory->create('test', $invalidCode, 'https://foo.bar/%s'); - - $this->assertSame('', $trackingInfoStruct->getUrl()); - } - /** * @return array diff --git a/tests/PHPUnit/Service/UrlParsingServiceTest.php b/tests/PHPUnit/Service/UrlParsingServiceTest.php new file mode 100644 index 000000000..52ed45b61 --- /dev/null +++ b/tests/PHPUnit/Service/UrlParsingServiceTest.php @@ -0,0 +1,159 @@ +service = new UrlParsingService(); + } + + /** + * @dataProvider urlProvider + */ + public function testIsUrl(string $url, bool $expected) + { + $this->assertEquals($expected, $this->service->isUrl($url)); + } + + public function urlProvider(): array + { + return [ + ['https://www.example.com', true], + ['http://example.com', true], + ['not a url', false], + ['example.com', false], + ]; + } + + /** + * @dataProvider queryParameterProvider + */ + public function testParseTrackingCodeQueryParameter(string $input, array $expected) + { + $this->assertEquals($expected, $this->service->parseTrackingCodeFromUrl($input)); + } + + public function queryParameterProvider(): array + { + return [ + ['https://www.example.com/product?code=12345', ['12345', 'https://www.example.com/product?code=12345']], + ['https://www.example.com/product?shipment=abc123', ['abc123', 'https://www.example.com/product?shipment=abc123']], + ['https://www.example.com/product?track=track123', ['track123', 'https://www.example.com/product?track=track123']], + ['https://www.example.com/product?tracking=track456', ['track456', 'https://www.example.com/product?tracking=track456']], + ]; + } + + /** + * @dataProvider pathProvider + */ + public function testParseTrackingCodePath(string $input, array $expected) + { + $this->assertEquals($expected, $this->service->parseTrackingCodeFromUrl($input)); + } + + public function pathProvider(): array + { + return [ + ['https://www.example.com/code/12345/product', ['12345', 'https://www.example.com/code/12345/product']], + ['https://www.example.com/shipment/abc123/product', ['abc123', 'https://www.example.com/shipment/abc123/product']], + ['https://www.example.com/track/track123/product', ['track123', 'https://www.example.com/track/track123/product']], + ['https://www.example.com/tracking/track456/product', ['track456', 'https://www.example.com/tracking/track456/product']], + ]; + } + + /** + * @dataProvider hashProvider + */ + public function testParseTrackingCodeHash(string $input, array $expected) + { + $this->assertEquals($expected, $this->service->parseTrackingCodeFromUrl($input)); + } + + public function hashProvider(): array + { + return [ + ['https://www.example.com/product#code=12345', ['12345', 'https://www.example.com/product#code=12345']], + ['https://www.example.com/product#shipment=abc123', ['abc123', 'https://www.example.com/product#shipment=abc123']], + ['https://www.example.com/product#track=track123', ['track123', 'https://www.example.com/product#track=track123']], + ['https://www.example.com/product#tracking=track456', ['track456', 'https://www.example.com/product#tracking=track456']], + ]; + } + + /** + * @dataProvider notFoundProvider + */ + public function testParseTrackingCodeNotFound(string $input, array $expected) + { + $this->assertEquals($expected, $this->service->parseTrackingCodeFromUrl($input)); + } + + public function notFoundProvider(): array + { + return [ + ['https://www.example.com/product', ['', 'https://www.example.com/product']], + ['https://www.example.com/code/product', ['', 'https://www.example.com/code/product']], + ]; + } + + /** + * @dataProvider encodePathAndQueryProvider + */ + public function testEncodePathAndQuery(string $input, string $expected) + { + $this->assertEquals($expected, $this->service->encodePathAndQuery($input)); + } + + public function encodePathAndQueryProvider(): array + { + return [ + ['https://www.example.com/path/to/resource', 'https://www.example.com/path/to/resource'], + ['https://www.example.com/path/to/{resource}', 'https://www.example.com/path/to/%7Bresource%7D'], + ['https://www.example.com/path with spaces/to/resource', 'https://www.example.com/path%20with%20spaces/to/resource'], + ['https://www.example.com/path/to/resource?query=123&test={test}', 'https://www.example.com/path/to/resource?query=123&test=%7Btest%7D'], + ]; + } + + /** + * @dataProvider sanitizeQueryProvider + */ + public function testSanitizeQuery(array $input, array $expected) + { + $this->assertEquals($expected, $this->service->sanitizeQuery($input)); + } + + public function sanitizeQueryProvider(): array + { + return [ + [['key=value'], ['key=value']], + [['key with spaces=value with spaces'], ['key%20with%20spaces=value%20with%20spaces']], + [['key={value}'], ['key=%7Bvalue%7D']], + [['key1=value1', 'key2=value2'], ['key1=value1', 'key2=value2']], + ]; + } + + /** + * @dataProvider sanitizeQueryPartProvider + */ + public function testSanitizeQueryPart(string $input, string $expected) + { + $this->assertEquals($expected, $this->service->sanitizeQueryPart($input)); + } + + public function sanitizeQueryPartProvider(): array + { + return [ + ['key=value', 'key=value'], + ['key with spaces=value with spaces', 'key%20with%20spaces=value%20with%20spaces'], + ['key={value}', 'key=%7Bvalue%7D'], + ['key', 'key'], // No '=' in the input, should return as is + ]; + } +} diff --git a/tests/PHPUnit/Utils/Traits/PaymentBuilderTrait.php b/tests/PHPUnit/Utils/Traits/PaymentBuilderTrait.php index 4c65cd4d4..5796fbbe9 100644 --- a/tests/PHPUnit/Utils/Traits/PaymentBuilderTrait.php +++ b/tests/PHPUnit/Utils/Traits/PaymentBuilderTrait.php @@ -9,6 +9,7 @@ use Kiener\MolliePayments\Service\MollieApi\Fixer\RoundingDifferenceFixer; use Kiener\MolliePayments\Service\MollieApi\LineItemDataExtractor; use Kiener\MolliePayments\Service\MollieApi\PriceCalculator; +use Kiener\MolliePayments\Service\UrlParsingService; use Kiener\MolliePayments\Validator\IsOrderLineItemValid; use MolliePayments\Tests\Fakes\FakeCompatibilityGateway; use Shopware\Core\Checkout\Cart\LineItem\LineItem; @@ -81,7 +82,7 @@ public function getExpectedLineItems(string $taxStatus, ?OrderLineItemCollection $mollieLineItemBuilder = new MollieLineItemBuilder( new IsOrderLineItemValid(), new PriceCalculator(), - new LineItemDataExtractor(), + new LineItemDataExtractor(new UrlParsingService()), new FakeCompatibilityGateway(), new RoundingDifferenceFixer(), new MollieLineItemHydrator(new MollieOrderPriceBuilder()),