diff --git a/UPGRADE-API-1.11.md b/UPGRADE-API-1.11.md
index 39d115b5a16..7dff0b545fe 100644
--- a/UPGRADE-API-1.11.md
+++ b/UPGRADE-API-1.11.md
@@ -1,5 +1,27 @@
# UPGRADE FROM `v1.10.X` TO `v1.11.0`
+1. The product images should have a proper prefix (`/media/image/`) added to the path, so the images could be resolved.
+ This is now done out of the box and response of `Product Image` resource is now:
+
+ ```diff
+ {
+ "@context": "/api/v2/contexts/ProductImage",
+ "@id": "/api/v2/shop/product-images/123",
+ "@type": "ProductImage",
+ "id": "123",
+ "type": "thumbnail",
+ - "path": "uo/product.jpg",
+ + "path": "/media/image/uo/product.jpg"
+ }
+ ```
+
+ To change the prefix you need to set parameter in ``app/config/packages/_sylius.yaml``:
+
+ ```yaml
+ sylius_api:
+ product_image_prefix: 'media/image'
+ ```
+
1. `Sylius\Bundle\ApiBundle\Doctrine\Filters\ExchangeRateFilter` and `Sylius\Bundle\ApiBundle\Doctrine\Filters\TranslationOrderNameAndLocaleFilter` has been moved to `Sylius\Bundle\ApiBundle\Doctrine\Filter\ExchangeRateFilter` and `Sylius\Bundle\ApiBundle\Doctrine\Filter\TranslationOrderNameAndLocaleFilter` respectively.
1. `Sylius\Bundle\ApiBundle\View\CartShippingMethodInterface` and `Sylius\Bundle\ApiBundle\View\CartShippingMethod` have been removed.
diff --git a/src/Sylius/Behat/Context/Api/Admin/ManagingProductsContext.php b/src/Sylius/Behat/Context/Api/Admin/ManagingProductsContext.php
index 4c9679b3c60..f944dbaa73f 100644
--- a/src/Sylius/Behat/Context/Api/Admin/ManagingProductsContext.php
+++ b/src/Sylius/Behat/Context/Api/Admin/ManagingProductsContext.php
@@ -563,7 +563,7 @@ private function hasProductImage(Response $response, ProductInterface $product):
return
isset($productFromResponse['images'][0]) &&
- $productFromResponse['images'][0]['path'] === $product->getImages()->first()->getPath()
+ str_contains($productFromResponse['images'][0]['path'], $product->getImages()->first()->getPath())
;
}
diff --git a/src/Sylius/Bundle/ApiBundle/DependencyInjection/Configuration.php b/src/Sylius/Bundle/ApiBundle/DependencyInjection/Configuration.php
index a6a824c604b..9c4632f8759 100644
--- a/src/Sylius/Bundle/ApiBundle/DependencyInjection/Configuration.php
+++ b/src/Sylius/Bundle/ApiBundle/DependencyInjection/Configuration.php
@@ -33,6 +33,11 @@ public function getConfigTreeBuilder(): TreeBuilder
->defaultFalse()
->end()
->end()
+ ->children()
+ ->variableNode('product_image_prefix')
+ ->defaultValue('media/image')
+ ->end()
+ ->end()
;
return $treeBuilder;
diff --git a/src/Sylius/Bundle/ApiBundle/DependencyInjection/SyliusApiExtension.php b/src/Sylius/Bundle/ApiBundle/DependencyInjection/SyliusApiExtension.php
index 77c4c3f221d..2aebd76bbfc 100644
--- a/src/Sylius/Bundle/ApiBundle/DependencyInjection/SyliusApiExtension.php
+++ b/src/Sylius/Bundle/ApiBundle/DependencyInjection/SyliusApiExtension.php
@@ -27,6 +27,7 @@ public function load(array $configs, ContainerBuilder $container): void
$loader = new XmlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config'));
$container->setParameter('sylius_api.enabled', $config['enabled']);
+ $container->setParameter('sylius_api.product_image_prefix', $config['product_image_prefix']);
$loader->load('services.xml');
}
diff --git a/src/Sylius/Bundle/ApiBundle/Resources/config/services/serializers.xml b/src/Sylius/Bundle/ApiBundle/Resources/config/services/serializers.xml
index b09b0cb5223..d28b17cd716 100644
--- a/src/Sylius/Bundle/ApiBundle/Resources/config/services/serializers.xml
+++ b/src/Sylius/Bundle/ApiBundle/Resources/config/services/serializers.xml
@@ -36,6 +36,11 @@
+
+ %sylius_api.product_image_prefix%
+
+
+
diff --git a/src/Sylius/Bundle/ApiBundle/Serializer/ProductImageNormalizer.php b/src/Sylius/Bundle/ApiBundle/Serializer/ProductImageNormalizer.php
new file mode 100644
index 00000000000..63d9e75e11c
--- /dev/null
+++ b/src/Sylius/Bundle/ApiBundle/Serializer/ProductImageNormalizer.php
@@ -0,0 +1,72 @@
+prefix = $this->validatePrefix($prefix);
+ }
+
+ public function normalize($object, $format = null, array $context = [])
+ {
+ Assert::isInstanceOf($object, ProductImageInterface::class);
+ Assert::keyNotExists($context, self::ALREADY_CALLED);
+
+ $context[self::ALREADY_CALLED] = true;
+
+ $data = $this->normalizer->normalize($object, $format, $context);
+
+ $data['path'] = $this->prefix . $data['path'];
+
+ return $data;
+ }
+
+ public function supportsNormalization($data, $format = null, $context = []): bool
+ {
+ if (isset($context[self::ALREADY_CALLED])) {
+ return false;
+ }
+
+ return $data instanceof ProductImageInterface;
+ }
+
+ private function validatePrefix(string $prefix): string
+ {
+ if (\DIRECTORY_SEPARATOR !== substr($prefix, 0, 1)) {
+ $prefix = \DIRECTORY_SEPARATOR . $prefix;
+ }
+
+ if (\DIRECTORY_SEPARATOR === substr($prefix, -1)) {
+ return $prefix;
+ }
+
+ return $prefix . \DIRECTORY_SEPARATOR;
+ }
+}
diff --git a/src/Sylius/Bundle/ApiBundle/spec/Serializer/ProductImageNormalizerSpec.php b/src/Sylius/Bundle/ApiBundle/spec/Serializer/ProductImageNormalizerSpec.php
new file mode 100644
index 00000000000..718f5c23e65
--- /dev/null
+++ b/src/Sylius/Bundle/ApiBundle/spec/Serializer/ProductImageNormalizerSpec.php
@@ -0,0 +1,50 @@
+beConstructedWith('prefix', '/prefix', '/prefix/', 'prefix/');
+ }
+
+ function it_implements_context_aware_normalizer_interface(): void
+ {
+ $this->shouldImplement(ContextAwareNormalizerInterface::class);
+ }
+
+ function it_supports_only_product_image_interface(ProductImageInterface $productImage, OrderInterface $order): void
+ {
+ $this->supportsNormalization($productImage)->shouldReturn(true);
+ $this->supportsNormalization($order)->shouldReturn(false);
+ }
+
+ function it_serializes_product_image_with_proper_prefix(
+ NormalizerInterface $normalizer,
+ ProductImageInterface $productImage
+ ): void {
+ $this->setNormalizer($normalizer);
+
+ $normalizer->normalize($productImage, null, ['product_image_normalizer_already_called' => true])->willReturn(['path' => 'some_path']);
+
+ $this->normalize($productImage, null, [])->shouldReturn(['path' => '/prefix/some_path']);
+ }
+}
diff --git a/tests/Api/DataFixtures/ORM/product_image.yaml b/tests/Api/DataFixtures/ORM/product_image.yaml
index 373553eb318..09d9523035c 100644
--- a/tests/Api/DataFixtures/ORM/product_image.yaml
+++ b/tests/Api/DataFixtures/ORM/product_image.yaml
@@ -8,4 +8,4 @@ Sylius\Component\Core\Model\Product:
Sylius\Component\Core\Model\ProductImage:
product_thumbnail:
type: "thumbnail"
- path: "/uo/product.jpg"
+ path: "uo/product.jpg"
diff --git a/tests/Api/Responses/Expected/admin/get_product_image_response.json b/tests/Api/Responses/Expected/admin/get_product_image_response.json
index b7b91d8cb29..426a45c6c94 100644
--- a/tests/Api/Responses/Expected/admin/get_product_image_response.json
+++ b/tests/Api/Responses/Expected/admin/get_product_image_response.json
@@ -4,5 +4,5 @@
"@type": "ProductImage",
"id": "@integer@",
"type": "thumbnail",
- "path": "\/uo\/product.jpg"
+ "path": "\/media\/image\/uo\/product.jpg"
}
diff --git a/tests/Api/Responses/Expected/admin/get_product_images_response.json b/tests/Api/Responses/Expected/admin/get_product_images_response.json
index 2a5a6081975..d81cf67f482 100644
--- a/tests/Api/Responses/Expected/admin/get_product_images_response.json
+++ b/tests/Api/Responses/Expected/admin/get_product_images_response.json
@@ -8,7 +8,7 @@
"@type": "ProductImage",
"id": "@integer@",
"type": "thumbnail",
- "path": "\/uo\/product.jpg"
+ "path": "\/media\/image\/uo\/product.jpg"
}
],
"hydra:totalItems": 1
diff --git a/tests/Api/Responses/Expected/shop/get_product_image_response.json b/tests/Api/Responses/Expected/shop/get_product_image_response.json
index 3156370acaa..7da3858e6c4 100644
--- a/tests/Api/Responses/Expected/shop/get_product_image_response.json
+++ b/tests/Api/Responses/Expected/shop/get_product_image_response.json
@@ -4,5 +4,5 @@
"@type": "ProductImage",
"id": "@integer@",
"type": "thumbnail",
- "path": "\/uo\/product.jpg"
+ "path": "\/media\/image\/uo\/product.jpg"
}