diff --git a/.github/workflows/ci_static-checks.yaml b/.github/workflows/ci_static-checks.yaml
index ae4cc1ff79..0e67e16dc4 100644
--- a/.github/workflows/ci_static-checks.yaml
+++ b/.github/workflows/ci_static-checks.yaml
@@ -103,6 +103,7 @@ jobs:
- name: "Check for security vulnerabilities"
run: symfony security:check
+ continue-on-error: true
- name: "Validate Twig templates"
run: bin/console lint:twig src
diff --git a/CONFLICTS.md b/CONFLICTS.md
index ad952188fe..1a61604305 100644
--- a/CONFLICTS.md
+++ b/CONFLICTS.md
@@ -27,3 +27,7 @@ references related issues.
- https://github.com/stof/StofDoctrineExtensionsBundle/issues/455
- https://github.com/doctrine-extensions/DoctrineExtensions/issues/2600
+
+- `doctrine/doctrine-bundle:2.11.0`:
+
+ This version uses the [readonly property](https://github.com/doctrine/DoctrineBundle/blob/2.11.0/Repository/ServiceEntityRepositoryProxy.php#L34), which is available from PHP 8.1 while the package also supports PHP 7.4 and 8.0, leading to compatibility issues.
diff --git a/UPGRADE-2.0.md b/UPGRADE-2.0.md
index a763c6424b..46d3993a5c 100644
--- a/UPGRADE-2.0.md
+++ b/UPGRADE-2.0.md
@@ -3,7 +3,7 @@
## Codebase
* Doctrine MongoDB and PHPCR is not longer supported in ResourceBundle and GridBundle:
-
+
* The following classes were removed:
* `Sylius\Bundle\GridBundle\Doctrine\PHPCRODM\DataSource`
@@ -26,13 +26,13 @@
* `Sylius\Bundle\ResourceBundle\EventListener\ODMTranslatableListener`
* The following services were removed:
-
+
* `sylius.event_subscriber.odm_mapped_super_class`
* `sylius.event_subscriber.odm_repository_class`
* `sylius.grid_driver.doctrine.phpcrodm`
-
+
* The following parameters were removed:
-
+
* `sylius.mongodb_odm.repository.class`
* `sylius.phpcr_odm.repository.class`
@@ -40,28 +40,33 @@
* `sylius.mailer.templates`
-* Added the `Sylius\Component\Order\Context\ResettableCartContextInterface` that extends `Sylius\Component\Order\Context\CartContextInterface` and `Symfony\Contracts\Service\ResetInterface`.
+* Added the `Sylius\Component\Order\Context\ResettableCartContextInterface` that
+ extends `Sylius\Component\Order\Context\CartContextInterface` and `Symfony\Contracts\Service\ResetInterface`.
-* The name of the default `LiipImagineBundle`'s resolver and loader were changed from **default** to **sylius_image** ([reference](https://github.com/Sylius/Sylius/pull/12543)).
- To change the default resolver and/or loader for `LiipImagineBundle`, configure `cache` and/or `data_loader` parameters under the `liip_imagine` key.
+* The name of the default `LiipImagineBundle`'s resolver and loader were changed from **default** to **sylius_image
+ ** ([reference](https://github.com/Sylius/Sylius/pull/12543)).
+ To change the default resolver and/or loader for `LiipImagineBundle`, configure `cache` and/or `data_loader`
+ parameters under the `liip_imagine` key.
-* The `sylius/calendar` package has been replaced with `symfony/clock` package. All usages of the `Sylius\Calendar\Provider\DateTimeProviderInterface` class
- have been replaced with `Symfony\Component\Clock\ClockInterface` class.
+* The `sylius/calendar` package has been replaced with `symfony/clock` package. All usages of
+ the `Sylius\Calendar\Provider\DateTimeProviderInterface` class
+ have been replaced with `Symfony\Component\Clock\ClockInterface` class.
- * The following classes were changed:
+ * The following classes were changed:
- * `Sylius\Bundle\CoreBundle\CatalogPromotion\Announcer\CatalogPromotionAnnouncer`
- * `Sylius\Bundle\CoreBundle\MessageHandler\Admin\Account\RequestResetPasswordEmailHandler`
- * `Sylius\Bundle\CoreBundle\PriceHistory\Logger\PriceChangeLogger`
- * `Sylius\Bundle\CoreBundle\PriceHistory\Remover\ChannelPricingLogEntriesRemover`
- * `Sylius\Bundle\ShippingBundle\Assigner\ShippingDateAssigner`
- * `Sylius\Bundle\PromotionBundle\Criteria\DateRange`
- * `Sylius\Bundle\ApiBundle\Applicator\ArchivingShippingMethodApplicator`
- * `Sylius\Bundle\ApiBundle\CommandHandler\Account\RequestResetPasswordTokenHandler`
- * `Sylius\Bundle\ApiBundle\CommandHandler\Account\VerifyCustomerAccountHandler`
- * `Sylius\Component\Taxation\Checker\TaxRateDateEligibilityChecker`
+ * `Sylius\Bundle\CoreBundle\CatalogPromotion\Announcer\CatalogPromotionAnnouncer`
+ * `Sylius\Bundle\CoreBundle\MessageHandler\Admin\Account\RequestResetPasswordEmailHandler`
+ * `Sylius\Bundle\CoreBundle\PriceHistory\Logger\PriceChangeLogger`
+ * `Sylius\Bundle\CoreBundle\PriceHistory\Remover\ChannelPricingLogEntriesRemover`
+ * `Sylius\Bundle\ShippingBundle\Assigner\ShippingDateAssigner`
+ * `Sylius\Bundle\PromotionBundle\Criteria\DateRange`
+ * `Sylius\Bundle\ApiBundle\Applicator\ArchivingShippingMethodApplicator`
+ * `Sylius\Bundle\ApiBundle\CommandHandler\Account\RequestResetPasswordTokenHandler`
+ * `Sylius\Bundle\ApiBundle\CommandHandler\Account\VerifyCustomerAccountHandler`
+ * `Sylius\Component\Taxation\Checker\TaxRateDateEligibilityChecker`
* The parameter order of `Sylius\Bundle\CoreBundle\Form\Type\Checkout\AddressType::__construct` has been changed:
+
```php
public function __construct(
+ private readonly AddressComparatorInterface $addressComparator,
@@ -73,7 +78,9 @@
* The `\Serializable` interface has been removed from the `Sylius\Component\User\Model\UserInterface`.
-* The parameter order of the `Sylius\Component\Core\OrderProcessing\OrderPaymentProcessor::__construct` has been changed:
+* The parameter order of the `Sylius\Component\Core\OrderProcessing\OrderPaymentProcessor::__construct` has been
+ changed:
+
```php
public function __construct(
private OrderPaymentProviderInterface $orderPaymentProvider,
@@ -112,7 +119,67 @@
): void;
```
+* The `swiftmailer/swiftmailer` dependency has been removed. Use `symfony/mailer` instead.
+
+* The following repository classes and interfaces were added, if you have custom repositories,
+ you need to update them to extend the new ones:
+
+ Addressing:
+
+ * `Sylius\Bundle\AddressingBundle\Doctrine\ORM\AddressRepository`
+ extends `Sylius\Bundle\ResourceBundle\Doctrine\ORM\EntityRepository`
+ implements `Sylius\Component\Addressing\Repository\AddressRepositoryInterface`
+ * `Sylius\Bundle\AddressingBundle\Doctrine\ORM\CountryRepository`
+ extends `Sylius\Bundle\ResourceBundle\Doctrine\ORM\EntityRepository`
+ implements `Sylius\Component\Addressing\Repository\CountryRepositoryInterface`
+ * `Sylius\Bundle\AddressingBundle\Doctrine\ORM\ProvinceRepository`
+ extends `Sylius\Bundle\ResourceBundle\Doctrine\ORM\EntityRepository`
+ implements `Sylius\Component\Addressing\Repository\ProvinceRepositoryInterface`
+ * `Sylius\Bundle\AddressingBundle\Doctrine\ORM\ZoneMemberRepository`
+ extends `Sylius\Bundle\ResourceBundle\Doctrine\ORM\EntityRepository`
+ implements `Sylius\Component\Addressing\Repository\ZoneMemberRepositoryInterface`
+
+ Attribute:
+
+ * `Sylius\Bundle\AttributeBundle\Doctrine\ORM\AttributeRepository`
+ extends `Sylius\Bundle\ResourceBundle\Doctrine\ORM\EntityRepository`
+ implements `Sylius\Component\Attribute\Repository\AttributeRepositoryInterface`
+ * `Sylius\Bundle\AttributeBundle\Doctrine\ORM\AttributeTranslationRepository`
+ extends `Sylius\Bundle\ResourceBundle\Doctrine\ORM\EntityRepository`
+ implements `Sylius\Component\Attribute\Repository\AttributeTranslationRepositoryInterface`
+ * `Sylius\Bundle\AttributeBundle\Doctrine\ORM\AttributeValueRepository`
+ extends `Sylius\Bundle\ResourceBundle\Doctrine\ORM\EntityRepository`
+ implements `Sylius\Component\Attribute\Repository\AttributeValueRepositoryInterface`
+
+ Currency:
+
+ * `Sylius\Bundle\CurrencyBundle\Doctrine\ORM\CurrencyRepository`
+ extends `Sylius\Bundle\ResourceBundle\Doctrine\ORM\EntityRepository`
+ implements `Sylius\Component\Currency\Repository\CurrencyRepositoryInterface`
+
+* The following repository classes and interfaces namespaces were changed, if you have custom repositories,
+ you need to update them to extend the new ones:
+
+ Addressing:
+
+ * `Sylius\Bundle\CoreBundle\Doctrine\ORM\AddressRepository` extended class changed from
+ `Sylius\Bundle\ResourceBundle\Doctrine\ORM\EntityRepository` to
+ `Sylius\Bundle\AddressingBundle\Doctrine\ORM\AddressRepository`
+ * `Sylius\Component\Core\Repository\AddressRepositoryInterface` implemented interface changed from
+ `Sylius\Component\Resource\Repository\RepositoryInterface` to
+ `Sylius\Component\Addressing\Repository\AddressRepositoryInterface`
+
+ Attribute:
+
+ * `Sylius\Bundle\CoreBundle\Doctrine\ORM\AttributeRepository` extended class changed from
+ `Sylius\Bundle\ResourceBundle\Doctrine\ORM\EntityRepository` to
+ `Sylius\Bundle\AttributeBundle\Doctrine\ORM\AttributeRepository`
+ * `Sylius\Component\Product\Repository\ProductAttributeValueRepositoryInterface` extended interface changed from
+ `Sylius\Component\Resource\Repository\RepositoryInterface` to
+ `Sylius\Component\Attribute\Repository\AttributeValueRepositoryInterface`
+
## Frontend
-* `use_webpack` option was removed from the `sylius_ui` configuration, and the Webpack has become the only module bundler provided by Sylius.
+* `use_webpack` option was removed from the `sylius_ui` configuration, and the Webpack has become the only module
+ bundler provided by Sylius.
* `use_webpack` twig global variable was removed. Webpack is always used now, and there is no need to check for it.
diff --git a/composer-require-checker.json b/composer-require-checker.json
index eb600991d2..2640a0c24c 100644
--- a/composer-require-checker.json
+++ b/composer-require-checker.json
@@ -3,7 +3,6 @@
"null", "true", "false",
"static", "self", "parent",
"array", "string", "int", "float", "bool", "iterable", "callable", "void", "object", "mixed", "never",
- "Swift_Message",
"ApiPlatform\\Core\\Api\\FilterInterface",
"ApiPlatform\\Core\\Api\\IriConverterInterface",
"ApiPlatform\\Core\\Api\\OperationType",
@@ -82,11 +81,12 @@
"Payum\\Core\\Security\\TokenInterface",
"Payum\\Core\\Security\\Util\\Random",
"Payum\\Core\\Storage\\AbstractStorage",
+ "Payum\\Paypal\\ExpressCheckout\\Nvp\\PaypalExpressCheckoutGatewayFactory",
+ "Payum\\Stripe\\StripeCheckoutGatewayFactory",
"PHPUnit\\Framework\\ExpectationFailedException",
"Psr\\Container\\ContainerInterface",
"Psr\\Http\\Message\\RequestFactoryInterface",
"Psr\\Http\\Message\\StreamFactoryInterface",
- "Stripe\\Stripe",
"Symfony\\Component\\Security\\Core\\Encoder\\EncoderFactoryInterface"
],
"php-core-extensions" : [
diff --git a/composer.json b/composer.json
index 6e43c7c9a8..7ecc27933d 100644
--- a/composer.json
+++ b/composer.json
@@ -62,9 +62,7 @@
"liip/imagine-bundle": "^2.10",
"nyholm/psr7": "^1.6",
"pagerfanta/pagerfanta": "^3.0",
- "payum/core": "^1.7.3",
"payum/offline": "^1.7.3",
- "payum/paypal-express-checkout-nvp": "^1.7.3",
"payum/payum-bundle": "^2.5",
"php-http/httplug": "^2.4",
"php-http/message-factory": "^1.0",
@@ -186,6 +184,7 @@
"sylius/user-bundle": "self.version"
},
"conflict": {
+ "doctrine/doctrine-bundle": "2.11.0",
"doctrine/orm": ">= 2.16.0",
"stof/doctrine-extensions-bundle": "1.8.0"
},
@@ -211,6 +210,8 @@
"matthiasnoback/symfony-dependency-injection-test": "^4.2",
"mikey179/vfsstream": "^1.6",
"mockery/mockery": "^1.4",
+ "payum/paypal-express-checkout-nvp": "^1.7.3",
+ "payum/stripe": "^1.7.3",
"phparkitect/phparkitect": "^0.2.9",
"phpspec/phpspec": "^7.2",
"phpspec/prophecy-phpunit": "^2.0",
@@ -223,7 +224,6 @@
"psalm/plugin-mockery": "1.1.0",
"psr/event-dispatcher": "^1.0",
"rector/rector": "^0.18.0",
- "stripe/stripe-php": "^12.0",
"sylius-labs/coding-standard": "^4.2",
"symfony/browser-kit": "^6.3.2",
"symfony/debug-bundle": "^6.3.2",
diff --git a/disabledTests/Api/Admin/ChannelsTest.php b/disabledTests/Api/Admin/ChannelsTest.php
index d9bad69d40..7591bea486 100644
--- a/disabledTests/Api/Admin/ChannelsTest.php
+++ b/disabledTests/Api/Admin/ChannelsTest.php
@@ -44,6 +44,61 @@ final class ChannelsTest extends JsonApiTestCase
);
}
+ /** @test */
+ public function it_deletes_a_channel(): void
+ {
+ $this->loadFixturesFromFiles(['authentication/api_administrator.yaml', 'channel.yaml']);
+ $header = array_merge($this->logInAdminUser('api@example.com'), self::CONTENT_TYPE_HEADER);
+
+ $this->client->request(
+ method: 'GET',
+ uri: '/api/v2/admin/channels/MOBILE',
+ server: $header,
+ );
+
+ $this->assertResponseCode($this->client->getResponse(), Response::HTTP_OK);
+
+ $this->client->request(
+ method: 'DELETE',
+ uri: '/api/v2/admin/channels/MOBILE',
+ server: $header,
+ );
+
+ $this->assertResponseCode($this->client->getResponse(), Response::HTTP_NO_CONTENT);
+
+ $this->client->request(
+ method: 'GET',
+ uri: '/api/v2/admin/channels/MOBILE',
+ server: $header,
+ );
+
+ $this->assertResponseCode($this->client->getResponse(), Response::HTTP_NOT_FOUND);
+ }
+
+ /** @test */
+ public function it_prevents_deleting_the_only_channel(): void
+ {
+ $this->loadFixturesFromFiles(['authentication/api_administrator.yaml', 'channel.yaml']);
+ $header = array_merge($this->logInAdminUser('api@example.com'), self::CONTENT_TYPE_HEADER);
+
+ $this->client->request(
+ method: 'DELETE',
+ uri: '/api/v2/admin/channels/MOBILE',
+ server: $header,
+ );
+ $this->client->request(
+ method: 'DELETE',
+ uri: '/api/v2/admin/channels/WEB',
+ server: $header,
+ );
+
+ $this->assertResponse(
+ $this->client->getResponse(),
+ 'admin/channel/delete_channel_that_cannot_be_deleted',
+ Response::HTTP_UNPROCESSABLE_ENTITY
+ );
+ }
+
/** @test */
public function it_gets_channels(): void
{
@@ -119,7 +174,15 @@ final class ChannelsTest extends JsonApiTestCase
uri: '/api/v2/admin/channels/' . $channel->getCode(),
server: $header,
content: json_encode([
+ 'defaultLocale' => '/api/v2/admin/locales/en_US',
+ 'locales' => ['/api/v2/admin/locales/en_US'],
'shippingAddressInCheckoutRequired' => true,
+ 'taxCalculationStrategy' => 'order_items_based',
+ 'accountVerificationRequired' => false,
+ 'name' => 'Web Store',
+ 'description' => 'different description',
+ 'hostname' => 'updated-hostname.com',
+ 'color' => 'blue',
], JSON_THROW_ON_ERROR),
);
@@ -129,4 +192,48 @@ final class ChannelsTest extends JsonApiTestCase
Response::HTTP_OK,
);
}
+
+ /** @test */
+ public function it_updates_a_shop_billing_data(): void
+ {
+ $fixtures = $this->loadFixturesFromFiles(['authentication/api_administrator.yaml', 'channel.yaml']);
+ $header = array_merge($this->logInAdminUser('api@example.com'), self::CONTENT_TYPE_HEADER);
+
+ /** @var ChannelInterface $channel */
+ $channel = $fixtures['channel_web'];
+
+ $this->client->request(
+ method: 'PUT',
+ uri: sprintf('/api/v2/admin/channels/%s', $channel->getCode()),
+ server: $header,
+ content: json_encode([
+ 'shopBillingData' => [
+ '@id' => sprintf('/api/v2/admin/shop-billing-datas/%s', $channel->getShopBillingData()->getId()),
+ 'company' => 'DifferentCompany',
+ 'taxId' => '123',
+ 'countryCode' => 'DE',
+ 'street' => 'Different Street',
+ 'city' => 'different City',
+ 'postcode' => '12-124'
+ ]
+ ], JSON_THROW_ON_ERROR),
+ );
+
+ $this->assertResponseCode(
+ $this->client->getResponse(),
+ Response::HTTP_OK,
+ );
+
+ $this->client->request(
+ method: 'GET',
+ uri: sprintf('/api/v2/admin/channels/%s/shop-billing-data', $channel->getCode()),
+ server: $header,
+ );
+
+ $this->assertResponse(
+ $this->client->getResponse(),
+ 'admin/put_shop_billing_data_response',
+ Response::HTTP_OK,
+ );
+ }
}
diff --git a/disabledTests/Api/Admin/ProductAssociationTypesTest.php b/disabledTests/Api/Admin/ProductAssociationTypesTest.php
index 6265563200..f43ac45af1 100644
--- a/disabledTests/Api/Admin/ProductAssociationTypesTest.php
+++ b/disabledTests/Api/Admin/ProductAssociationTypesTest.php
@@ -38,7 +38,7 @@ final class ProductAssociationTypesTest extends JsonApiTestCase
$this->assertResponse(
$this->client->getResponse(),
- 'admin/get_product_association_type_response',
+ 'admin/product_association_type/get_product_association_type_response',
Response::HTTP_OK,
);
}
@@ -70,7 +70,7 @@ final class ProductAssociationTypesTest extends JsonApiTestCase
$this->assertResponse(
$this->client->getResponse(),
- 'admin/get_product_association_type_collection_response',
+ 'admin/product_association_type/get_product_association_type_collection_response',
Response::HTTP_OK,
);
}
@@ -97,11 +97,31 @@ final class ProductAssociationTypesTest extends JsonApiTestCase
$this->assertResponse(
$this->client->getResponse(),
- 'admin/post_product_association_type_response',
+ 'admin/product_association_type/post_product_association_type_response',
Response::HTTP_CREATED,
);
}
+ /** @test */
+ public function it_does_not_create_product_association_type_without_required_data(): void
+ {
+ $this->loadFixturesFromFiles(['product/product_with_many_locales.yaml', 'authentication/api_administrator.yaml']);
+ $header = array_merge($this->logInAdminUser('api@example.com'), self::CONTENT_TYPE_HEADER);
+
+ $this->client->request(
+ method: 'POST',
+ uri: '/api/v2/admin/product-association-types',
+ server: $header,
+ content: '{}',
+ );
+
+ $this->assertResponse(
+ $this->client->getResponse(),
+ 'admin/product_association_type/post_product_association_type_without_required_data_response',
+ Response::HTTP_UNPROCESSABLE_ENTITY,
+ );
+ }
+
/** @test */
public function it_updates_product_association_type(): void
{
@@ -116,17 +136,22 @@ final class ProductAssociationTypesTest extends JsonApiTestCase
server: $header,
content: json_encode([
'code' => 'TEST',
- 'translations' => ['en_US' => [
- 'name' => 'test',
- 'description' => 'test description',
- 'locale' => 'de_DE'
- ]]
+ 'translations' => [
+ 'en_US' => [
+ '@id' => sprintf('/api/v2/admin/product-association-type-translations/%s', $associationType->getTranslation('en_US')->getId()),
+ 'name' => 'Similar products',
+ ],
+ 'de_DE' => [
+ 'name' => 'test',
+ 'description' => 'test description'
+ ],
+ ]
], JSON_THROW_ON_ERROR),
);
$this->assertResponse(
$this->client->getResponse(),
- 'admin/put_product_association_type_response',
+ 'admin/product_association_type/put_product_association_type_response',
Response::HTTP_OK,
);
}
diff --git a/disabledTests/Api/Admin/ProductAttributesTest.php b/disabledTests/Api/Admin/ProductAttributesTest.php
index 5dd1cfdd90..640d9d7319 100644
--- a/disabledTests/Api/Admin/ProductAttributesTest.php
+++ b/disabledTests/Api/Admin/ProductAttributesTest.php
@@ -394,13 +394,7 @@ final class ProductAttributesTest extends JsonApiTestCase
method: 'POST',
uri: '/api/v2/admin/product-attributes',
server: $header,
- content: json_encode([
- 'translations' => [
- 'en_US' => [
- 'locale' => 'en_US',
- ],
- ],
- ], JSON_THROW_ON_ERROR),
+ content: '{}',
);
$this->assertResponse(
@@ -423,6 +417,12 @@ final class ProductAttributesTest extends JsonApiTestCase
content: json_encode([
'code' => 'test',
'type' => 'foobar',
+ 'translations' => [
+ 'en_US' => [
+ 'locale' => 'en_US',
+ 'name' => 'Test',
+ ],
+ ],
], JSON_THROW_ON_ERROR),
);
diff --git a/disabledTests/Api/Admin/ProductVariantsTest.php b/disabledTests/Api/Admin/ProductVariantsTest.php
index beacad759d..c070703e67 100644
--- a/disabledTests/Api/Admin/ProductVariantsTest.php
+++ b/disabledTests/Api/Admin/ProductVariantsTest.php
@@ -141,7 +141,7 @@ final class ProductVariantsTest extends JsonApiTestCase
}
/** @test */
- public function it_creates_a_product_variant_enabled_by_default(): void
+ public function it_creates_a_product_variant_enabled_by_default_with_translation_in_default_locale(): void
{
$this->loadFixturesFromFiles([
'authentication/api_administrator.yaml',
@@ -168,7 +168,7 @@ final class ProductVariantsTest extends JsonApiTestCase
$this->assertResponse(
$this->client->getResponse(),
- 'admin/product_variant/post_product_variant_enabled_by_default_response',
+ 'admin/product_variant/post_product_variant_enabled_by_default_with_translation_in_default_locale_response',
Response::HTTP_CREATED,
);
}
@@ -325,13 +325,11 @@ final class ProductVariantsTest extends JsonApiTestCase
'minimumPrice' => 500,
]],
'translations' => [
- 'pl_PL' => [
- '@id' => sprintf('/api/v2/admin/product-variant-translations/%s', $productVariant->getTranslation('pl_PL')->getId()),
- 'locale' => 'pl_PL',
- 'name' => 'Czerwony kubek',
+ 'en_US' => [
+ '@id' => sprintf('/api/v2/admin/product-variant-translations/%s', $productVariant->getTranslation('en_US')->getId()),
+ 'name' => 'Red mug',
],
'de_DE' => [
- 'locale' => 'de_DE',
'name' => 'Rote Tasse',
],
],
@@ -396,6 +394,41 @@ final class ProductVariantsTest extends JsonApiTestCase
$this->assertResponseCode($this->client->getResponse(), Response::HTTP_UNPROCESSABLE_ENTITY);
}
+ /** @test */
+ public function it_does_not_allow_to_update_product_variant_without_translation_in_default_locale(): void
+ {
+ $fixtures = $this->loadFixturesFromFiles([
+ 'authentication/api_administrator.yaml',
+ 'channel.yaml',
+ 'tax_category.yaml',
+ 'shipping_category.yaml',
+ 'product/product_variant.yaml',
+ ]);
+ $header = array_merge($this->logInAdminUser('api@example.com'), self::CONTENT_TYPE_HEADER);
+
+ /** @var ProductVariantInterface $productVariant */
+ $productVariant = $fixtures['product_variant'];
+
+ $this->client->request(
+ method: 'PUT',
+ uri: sprintf('/api/v2/admin/product-variants/%s', $productVariant->getCode()),
+ server: $header,
+ content: json_encode([
+ 'translations' => [
+ 'de_DE' => [
+ 'name' => 'Tasse',
+ ],
+ ],
+ ], JSON_THROW_ON_ERROR),
+ );
+
+ $this->assertResponse(
+ $this->client->getResponse(),
+ 'admin/product_variant/put_product_variant_without_translation_in_default_locale_response',
+ Response::HTTP_UNPROCESSABLE_ENTITY,
+ );
+ }
+
/** @test */
public function it_deletes_the_product_variant(): void
{
diff --git a/disabledTests/Api/Admin/ProductsTest.php b/disabledTests/Api/Admin/ProductsTest.php
index 4e8305b1a3..bd47f2610f 100644
--- a/disabledTests/Api/Admin/ProductsTest.php
+++ b/disabledTests/Api/Admin/ProductsTest.php
@@ -99,7 +99,6 @@ final class ProductsTest extends JsonApiTestCase
]],
'translations' => [
'en_US' => [
- 'locale' => 'en_US',
'slug' => 'mug',
'name' => 'Mug',
'description' => 'This is a mug',
@@ -108,7 +107,6 @@ final class ProductsTest extends JsonApiTestCase
'metaDescription' => 'Mug description',
],
'pl_PL' => [
- 'locale' => 'pl_PL',
'slug' => 'kubek',
'name' => 'Kubek',
'description' => 'To jest kubek',
@@ -127,6 +125,58 @@ final class ProductsTest extends JsonApiTestCase
);
}
+ /** @test */
+ public function it_does_not_create_a_product_without_required_data(): void
+ {
+ $this->loadFixturesFromFile('authentication/api_administrator.yaml');
+ $header = array_merge($this->logInAdminUser('api@example.com'), self::CONTENT_TYPE_HEADER);
+
+ $this->client->request(
+ method: 'POST',
+ uri: '/api/v2/admin/products',
+ server: $header,
+ content: '{}',
+ );
+
+ $this->assertResponse(
+ $this->client->getResponse(),
+ 'admin/product/post_product_without_required_data_response',
+ Response::HTTP_UNPROCESSABLE_ENTITY,
+ );
+ }
+
+ /** @test */
+ public function it_does_not_create_a_product_with_invalid_translation_locale(): void
+ {
+ $this->loadFixturesFromFile('authentication/api_administrator.yaml');
+ $header = array_merge($this->logInAdminUser('api@example.com'), self::CONTENT_TYPE_HEADER);
+
+ $this->client->request(
+ method: 'POST',
+ uri: '/api/v2/admin/products',
+ server: $header,
+ content: json_encode([
+ 'code' => 'MUG',
+ 'translations' => [
+ 'en_US' => [
+ 'slug' => 'mug',
+ 'name' => 'Mug',
+ ],
+ 'a' => [
+ 'slug' => 'kubek',
+ 'name' => 'Kubek',
+ ],
+ ],
+ ], JSON_THROW_ON_ERROR),
+ );
+
+ $this->assertResponse(
+ $this->client->getResponse(),
+ 'admin/product/post_product_with_invalid_translation_locale',
+ Response::HTTP_UNPROCESSABLE_ENTITY,
+ );
+ }
+
/** @test */
public function it_updates_the_existing_product(): void
{
@@ -178,7 +228,6 @@ final class ProductsTest extends JsonApiTestCase
'translations' => [
'en_US' => [
'@id' => sprintf('/api/v2/admin/product-translations/%s', $product->getTranslation('en_US')->getId()),
- 'locale' => 'en_US',
'slug' => 'caps/cap',
'name' => 'Cap',
'description' => 'This is a cap',
@@ -188,7 +237,6 @@ final class ProductsTest extends JsonApiTestCase
],
'pl_PL' => [
'@id' => sprintf('/api/v2/admin/product-translations/%s', $product->getTranslation('pl_PL')->getId()),
- 'locale' => 'pl_PL',
'slug' => 'czapki/czapka',
'name' => 'Czapka',
'description' => 'To jest czapka',
diff --git a/disabledTests/Api/Admin/TaxonsTest.php b/disabledTests/Api/Admin/TaxonsTest.php
index efc49842cd..fb5bb0c57c 100644
--- a/disabledTests/Api/Admin/TaxonsTest.php
+++ b/disabledTests/Api/Admin/TaxonsTest.php
@@ -99,13 +99,7 @@ final class TaxonsTest extends JsonApiTestCase
method: 'POST',
uri: '/api/v2/admin/taxons',
server: $header,
- content: json_encode([
- 'translations' => [
- 'en_US' => [
- 'locale' => 'en_US',
- ]
- ]
- ], JSON_THROW_ON_ERROR),
+ content: '{}',
);
$this->assertResponse(
diff --git a/features/channel/managing_channels/adding_channel.feature b/features/channel/managing_channels/adding_channel.feature
index aa48daf64c..32265b9a97 100644
--- a/features/channel/managing_channels/adding_channel.feature
+++ b/features/channel/managing_channels/adding_channel.feature
@@ -10,7 +10,7 @@ Feature: Adding a new channel
And the store operates in "United States" and "Poland"
And I am logged in as an administrator
- @ui @api
+ @api @ui
Scenario: Adding a new channel
When I want to create a new channel
And I specify its code as "MOBILE"
@@ -23,7 +23,7 @@ Feature: Adding a new channel
Then I should be notified that it has been successfully created
And the channel "Mobile channel" should appear in the registry
- @ui @api
+ @api @ui
Scenario: Adding a new channel with additional fields
When I want to create a new channel
And I specify its code as "MOBILE"
diff --git a/features/channel/managing_channels/adding_channel_with_shop_billing_data.feature b/features/channel/managing_channels/adding_channel_with_shop_billing_data.feature
index dd3e351a7f..cb405a8382 100644
--- a/features/channel/managing_channels/adding_channel_with_shop_billing_data.feature
+++ b/features/channel/managing_channels/adding_channel_with_shop_billing_data.feature
@@ -10,7 +10,7 @@ Feature: Adding a new channel with shop billing data
And the store operates in "United States"
And I am logged in as an administrator
- @ui @api
+ @api @ui
Scenario: Adding a new channel with shop billing data
When I want to create a new channel
And I specify its code as "MOBILE"
diff --git a/features/channel/managing_channels/browsing_channels.feature b/features/channel/managing_channels/browsing_channels.feature
index f95b116ae0..070993551b 100644
--- a/features/channel/managing_channels/browsing_channels.feature
+++ b/features/channel/managing_channels/browsing_channels.feature
@@ -9,7 +9,7 @@ Feature: Browsing channels
And the store operates on another channel named "Mobile Channel"
And I am logged in as an administrator
- @ui @api
+ @api @ui
Scenario: Browsing defined channels
When I want to browse channels
Then I should see 2 channels in the list
diff --git a/features/channel/managing_channels/channel_unique_code_validation.feature b/features/channel/managing_channels/channel_unique_code_validation.feature
index f8b6e62cff..47d6543623 100644
--- a/features/channel/managing_channels/channel_unique_code_validation.feature
+++ b/features/channel/managing_channels/channel_unique_code_validation.feature
@@ -8,7 +8,7 @@ Feature: Channel unique code validation
Given the store operates on a channel identified by "WEB" code
And I am logged in as an administrator
- @ui
+ @api @ui
Scenario: Trying to add channel with taken code
When I want to create a new channel
And I specify its code as "WEB"
diff --git a/features/channel/managing_channels/channel_validation.feature b/features/channel/managing_channels/channel_validation.feature
index fe45e18b4b..766a3a4a2c 100644
--- a/features/channel/managing_channels/channel_validation.feature
+++ b/features/channel/managing_channels/channel_validation.feature
@@ -7,7 +7,7 @@ Feature: Channel validation
Background:
Given I am logged in as an administrator
- @ui
+ @api @ui
Scenario: Trying to add a new channel without specifying its code
When I want to create a new channel
And I name it "Mobile channel"
@@ -16,7 +16,7 @@ Feature: Channel validation
Then I should be notified that code is required
And channel with name "Mobile channel" should not be added
- @ui
+ @api @ui
Scenario: Trying to add a new channel without specifying its name
When I want to create a new channel
And I specify its code as "MOBILE"
@@ -25,7 +25,7 @@ Feature: Channel validation
Then I should be notified that name is required
And channel with code "MOBILE" should not be added
- @ui
+ @api @ui
Scenario: Trying to add a new channel without base currency
When I want to create a new channel
And I specify its code as "MOBILE"
@@ -34,7 +34,7 @@ Feature: Channel validation
Then I should be notified that base currency is required
And channel with code "MOBILE" should not be added
- @ui
+ @api @ui
Scenario: Trying to add a new channel without default locale
When I want to create a new channel
And I specify its code as "MOBILE"
@@ -43,7 +43,7 @@ Feature: Channel validation
Then I should be notified that default locale is required
And channel with code "MOBILE" should not be added
- @ui
+ @api @ui
Scenario: Trying to remove name from existing channel
Given the store operates on a channel named "Web Channel"
When I want to modify this channel
diff --git a/features/channel/managing_channels/deleting_channel.feature b/features/channel/managing_channels/deleting_channel.feature
index 372e70c0eb..fb2976984b 100644
--- a/features/channel/managing_channels/deleting_channel.feature
+++ b/features/channel/managing_channels/deleting_channel.feature
@@ -9,7 +9,7 @@ Feature: Deleting a channel
And the store operates on another channel named "Mobile Store"
And I am logged in as an administrator
- @ui
+ @api @ui
Scenario: Deleted channel should disappear from the registry
When I delete channel "Web Store"
Then I should be notified that it has been successfully deleted
diff --git a/features/channel/managing_channels/editing_channel.feature b/features/channel/managing_channels/editing_channel.feature
index 4a011b1f75..42e7fa0046 100644
--- a/features/channel/managing_channels/editing_channel.feature
+++ b/features/channel/managing_channels/editing_channel.feature
@@ -8,20 +8,12 @@ Feature: Editing channel
Given the store operates on a channel named "Web Channel"
And I am logged in as an administrator
- @todo
- Scenario: Trying to change channel code
+ @api @ui
+ Scenario: Being unable to change the code of an existing channel
When I want to modify a channel "Web Channel"
- And I change its code to "MOBILE"
- And I save my changes
- Then I should be notified that code cannot be changed
- And channel "Web Channel" should still have code "MOBILE"
+ Then I should not be able to edit its code
- @ui
- Scenario: Seeing disabled code field when editing channel
- When I want to modify a channel "Web Channel"
- Then the code field should be disabled
-
- @ui
+ @api @ui
Scenario: Renaming the channel
When I want to modify a channel "Web Channel"
And I rename it to "Website store"
@@ -29,7 +21,7 @@ Feature: Editing channel
Then I should be notified that it has been successfully edited
And this channel name should be "Website store"
- @ui
- Scenario: Seeing disabled base currency field during channel edition
+ @api @ui
+ Scenario: Being unable to change base currency of an existing channel
When I want to modify a channel "Web Channel"
- Then the base currency field should be disabled
+ Then I should not be able to edit its base currency
diff --git a/features/channel/managing_channels/editing_menu_taxon_on_channel.feature b/features/channel/managing_channels/editing_menu_taxon_on_channel.feature
index bb32cbf8b5..e0440d57a1 100644
--- a/features/channel/managing_channels/editing_menu_taxon_on_channel.feature
+++ b/features/channel/managing_channels/editing_menu_taxon_on_channel.feature
@@ -11,7 +11,7 @@ Feature: Editing menu taxon on channel
And channel "Web Store" has menu taxon "Clothes"
And I am logged in as an administrator
- @ui @javascript
+ @api @ui @javascript
Scenario: Editing menu taxon on the channel
When I want to modify a channel "Web Store"
And I change its menu taxon to "Guns"
diff --git a/features/channel/managing_channels/editing_shop_billing_data_on_channel.feature b/features/channel/managing_channels/editing_shop_billing_data_on_channel.feature
index 801ae49a6e..d1bb24601a 100644
--- a/features/channel/managing_channels/editing_shop_billing_data_on_channel.feature
+++ b/features/channel/managing_channels/editing_shop_billing_data_on_channel.feature
@@ -10,14 +10,20 @@ Feature: Editing shop billing data on channel
And channel "Web Store" billing data is "Ragnarok", "Pacific Coast Hwy", "90806" "Los Angeles", "United States" with "1100110011" tax ID
And I am logged in as an administrator
- @ui
+ @api @ui
Scenario: Editing shop billing data on channel
When I want to modify a channel "Web Store"
- And I specify company as "Götterdämmerung"
- And I specify tax ID as "666777"
- And I specify shop billing address as "Valhalla", "123" "Asgard", "United States"
+ And I specify shop billing data for this channel as "Götterdämmerung", "Valhalla", "123", "Asgard", "666777" tax ID and "United States" country
And I save my changes
Then I should be notified that it has been successfully edited
And this channel company should be "Götterdämmerung"
And this channel tax ID should be "666777"
- And this channel shop billing address should be "Valhalla", "123" "Asgard", "United States"
+ And this channel shop billing address should be "Valhalla", "123" "Asgard" and "United States" country
+
+ @api @no-ui
+ Scenario: Editing shop billing data with wrong country code
+ When I want to modify a channel "Web Store"
+ And I specify new country code for this channel as "ZZ"
+ And I save my changes
+ Then I should be notified that it is not a valid country
+ And this channel shop billing address should still be "Pacific Coast Hwy", "90806" "Los Angeles" and "United States" country
diff --git a/features/channel/managing_channels/not_being_able_to_add_disabled_channel_when_no_other_exist.feature b/features/channel/managing_channels/not_being_able_to_add_disabled_channel_when_no_other_exist.feature
index c9d19fd580..31f2cb450e 100644
--- a/features/channel/managing_channels/not_being_able_to_add_disabled_channel_when_no_other_exist.feature
+++ b/features/channel/managing_channels/not_being_able_to_add_disabled_channel_when_no_other_exist.feature
@@ -9,8 +9,8 @@ Feature: Not being able to add a disabled channel when no other exist
And the store has locale "English (United States)"
And I am logged in as an administrator
- @ui
- Scenario: Adding a new disabled channel should result
+ @api @ui
+ Scenario: Trying to add a new disabled channel when no other exist
When I want to create a new channel
And I specify its code as "MOBILE"
And I name it "Mobile channel"
diff --git a/features/channel/managing_channels/not_being_able_to_delete_last_available_channel.feature b/features/channel/managing_channels/not_being_able_to_delete_last_available_channel.feature
index e30a65d27c..f46c66cb40 100644
--- a/features/channel/managing_channels/not_being_able_to_delete_last_available_channel.feature
+++ b/features/channel/managing_channels/not_being_able_to_delete_last_available_channel.feature
@@ -8,8 +8,8 @@ Feature: Not being able to delete a last available channel
Given the store operates on a channel named "Web Store"
And I am logged in as an administrator
- @ui
- Scenario: Prevented from deleting only channel
+ @api @ui
+ Scenario: Preventing from deleting only channel
When I delete channel "Web Store"
Then I should be notified that it cannot be deleted
And this channel should still be in the registry
diff --git a/features/channel/managing_channels/not_being_able_to_disable_last_available_channel.feature b/features/channel/managing_channels/not_being_able_to_disable_last_available_channel.feature
index bb95ad1290..b7e302c4b4 100644
--- a/features/channel/managing_channels/not_being_able_to_disable_last_available_channel.feature
+++ b/features/channel/managing_channels/not_being_able_to_disable_last_available_channel.feature
@@ -8,7 +8,7 @@ Feature: Toggling a channel
Given the store operates on a channel named "Web Channel"
And I am logged in as an administrator
- @ui
+ @api @ui
Scenario: Disabling the last available channel
Given the channel "Web Channel" is enabled
When I want to modify this channel
diff --git a/features/channel/managing_channels/selecting_currencies_available_for_channel.feature b/features/channel/managing_channels/selecting_currencies_available_for_channel.feature
index b478e47268..339b63a728 100644
--- a/features/channel/managing_channels/selecting_currencies_available_for_channel.feature
+++ b/features/channel/managing_channels/selecting_currencies_available_for_channel.feature
@@ -7,20 +7,24 @@ Feature: Selecting available currencies for a channel
Background:
Given the store has currency "Euro"
And the store has locale "English (United States)"
+ And the store operates in "United States"
And I am logged in as an administrator
- @ui
+ @api @ui
Scenario: Adding a new channel with currencies
When I want to create a new channel
And I specify its code as MOBILE
+ And I choose "Euro" as the base currency
And I name it "Mobile store"
And I allow for paying in "Euro"
+ And I make it available in "English (United States)"
And I choose "English (United States)" as a default locale
+ And I select the "Order items based" as tax calculation strategy
And I add it
Then I should be notified that it has been successfully created
And paying in Euro should be possible for the "Mobile store" channel
- @ui
+ @api @ui
Scenario: Adding currencies to an existing channel
Given the store operates on a channel named "Web store"
When I want to modify this channel
diff --git a/features/channel/managing_channels/selecting_default_tax_zone_for_channel.feature b/features/channel/managing_channels/selecting_default_tax_zone_for_channel.feature
index 891dcd70ae..bc3e2ef5db 100644
--- a/features/channel/managing_channels/selecting_default_tax_zone_for_channel.feature
+++ b/features/channel/managing_channels/selecting_default_tax_zone_for_channel.feature
@@ -8,19 +8,21 @@ Feature: Selecting default tax zone for a channel
Given the store operates on a single channel in "United States"
And I am logged in as an administrator
- @ui
+ @api @ui
Scenario: Adding a new channel with default tax zone
When I want to create a new channel
And I specify its code as "MOBILE"
And I name it "Mobile store"
And I select the "United States" as default tax zone
And I choose "USD" as the base currency
+ And I make it available in "English (United States)"
And I choose "English (United States)" as a default locale
+ And I select the "Order items based" as tax calculation strategy
And I add it
Then I should be notified that it has been successfully created
And the default tax zone for the "Mobile store" channel should be "United States"
- @ui
+ @api @ui
Scenario: Selecting default tax zone for existing channel
Given the store operates on a channel named "Web store"
When I want to modify this channel
@@ -29,7 +31,7 @@ Feature: Selecting default tax zone for a channel
Then I should be notified that it has been successfully edited
And the default tax zone for the "Web store" channel should be "United States"
- @ui
+ @api @ui
Scenario: Removing existing channel default tax zone
Given the store operates on a channel named "Web store"
And its default tax zone is zone "US"
diff --git a/features/channel/managing_channels/selecting_locales_available_for_channel.feature b/features/channel/managing_channels/selecting_locales_available_for_channel.feature
index 25afdd3ff9..1c17675e3a 100644
--- a/features/channel/managing_channels/selecting_locales_available_for_channel.feature
+++ b/features/channel/managing_channels/selecting_locales_available_for_channel.feature
@@ -9,7 +9,7 @@ Feature: Selecting available locales for a channel
And the store has locale "English (United States)"
And I am logged in as an administrator
- @ui
+ @api @ui
Scenario: Adding a new channel with locales
When I want to create a new channel
And I specify its code as "MOBILE"
@@ -17,11 +17,12 @@ Feature: Selecting available locales for a channel
And I make it available in "English (United States)"
And I choose "Euro" as the base currency
And I choose "English (United States)" as a default locale
+ And I select the "Order items based" as tax calculation strategy
And I add it
Then I should be notified that it has been successfully created
And the channel "Mobile Channel" should be available in "English (United States)"
- @ui
+ @api @ui
Scenario: Adding locales to an existing channel
Given the store operates on a channel named "Web Channel"
When I want to modify this channel
@@ -30,23 +31,23 @@ Feature: Selecting available locales for a channel
Then I should be notified that it has been successfully edited
And the channel "Web Channel" should be available in "English (United States)"
- @ui
+ @api @ui
Scenario: Being unable to disable locale used as the default one for a channel
Given the store operates on a channel named "Web"
And this channel allows to shop using "English (United States)" and "Polish (Poland)" locales
And this channel uses the "English (United States)" locale as default
- And I am modifying a channel "Web"
- When I make it available only in "Polish (Poland)"
+ When I want to modify this channel
+ And I make it available only in "Polish (Poland)"
And I try to save my changes
Then I should be notified that the default locale has to be enabled
- @ui
+ @api @ui
Scenario: Being unable to set disabled locale as a default one for a channel
Given the store has locale "Polish (Poland)"
And the store operates on a channel named "Web"
And this channel allows to shop using the "English (United States)" locale
And this channel uses the "English (United States)" locale as default
- And I am modifying a channel "Web"
- When I choose "Polish (Poland)" as a default locale
+ When I want to modify this channel
+ And I choose "Polish (Poland)" as a default locale
And I try to save my changes
Then I should be notified that the default locale has to be enabled
diff --git a/features/channel/managing_channels/selecting_tax_calculation_strategy_for_channel.feature b/features/channel/managing_channels/selecting_tax_calculation_strategy_for_channel.feature
index ff8f6bac6f..8b2d560df7 100644
--- a/features/channel/managing_channels/selecting_tax_calculation_strategy_for_channel.feature
+++ b/features/channel/managing_channels/selecting_tax_calculation_strategy_for_channel.feature
@@ -20,19 +20,20 @@ Feature: Selecting tax calculation strategy for a channel
Then I should be notified that it has been successfully created
And the tax calculation strategy for the "Mobile store" channel should be "Order items based"
- @ui
+ @api @ui
Scenario: Adding a new channel with tax calculation strategy
When I want to create a new channel
And I specify its code as "MOBILE"
And I select the "Order item units based" as tax calculation strategy
And I name it "Mobile store"
And I choose "Euro" as the base currency
+ And I make it available in "English (United States)"
And I choose "English (United States)" as a default locale
And I add it
Then I should be notified that it has been successfully created
And the tax calculation strategy for the "Mobile store" channel should be "Order item units based"
- @ui
+ @api @ui
Scenario: Changing tax calculation strategy of existing channel
Given the store operates on a channel named "Web store"
When I want to modify this channel
diff --git a/features/channel/managing_channels/toggling_channel.feature b/features/channel/managing_channels/toggling_channel.feature
index 41875817f1..92d88bc4d0 100644
--- a/features/channel/managing_channels/toggling_channel.feature
+++ b/features/channel/managing_channels/toggling_channel.feature
@@ -9,7 +9,7 @@ Feature: Toggling a channel
And the store operates on another channel named "Mobile Channel"
And I am logged in as an administrator
- @ui
+ @api @ui
Scenario: Disabling the channel
Given the channel "Web Channel" is enabled
When I want to modify this channel
@@ -18,7 +18,7 @@ Feature: Toggling a channel
Then I should be notified that it has been successfully edited
And this channel should be disabled
- @ui
+ @api @ui
Scenario: Enabling the channel
Given the channel "Web Channel" is disabled
When I want to modify this channel
diff --git a/features/product/managing_product_attributes/seeing_correct_select_attribute_values_in_different_locale_than_default_one.feature b/features/product/managing_product_attributes/seeing_correct_select_attribute_values_in_different_locale_than_default_one.feature
index 8e0eee07a3..9362431da5 100644
--- a/features/product/managing_product_attributes/seeing_correct_select_attribute_values_in_different_locale_than_default_one.feature
+++ b/features/product/managing_product_attributes/seeing_correct_select_attribute_values_in_different_locale_than_default_one.feature
@@ -8,7 +8,7 @@ Feature: Seeing correct select attribute values in different locale than default
Given the store is available in "French (France)"
And I am logged in as an administrator
- @ui @javascript @api
+ @ui @javascript @no-api
Scenario: Seeing correct attribute values in different locale than default one
When I want to create a new select product attribute
And I specify its code as "mug_material"
diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon
index 8823b12446..41bfed5818 100644
--- a/phpstan-baseline.neon
+++ b/phpstan-baseline.neon
@@ -1820,6 +1820,11 @@ parameters:
count: 1
path: src/Sylius/Bundle/ApiBundle/Serializer/ShippingMethodNormalizer.php
+ -
+ message: "#^Method Sylius\\\\Bundle\\\\ApiBundle\\\\Serializer\\\\TranslatableDenormalizer\\:\\:hasDefaultTranslation\\(\\) has parameter \\$translations with no value type specified in iterable type array\\.$#"
+ count: 1
+ path: src/Sylius/Bundle/ApiBundle/Serializer/TranslatableDenormalizer.php
+
-
message: "#^Method Sylius\\\\Bundle\\\\ApiBundle\\\\Serializer\\\\ZoneDenormalizer\\:\\:getZoneMemberByCode\\(\\) has parameter \\$zoneMembers with generic interface Doctrine\\\\Common\\\\Collections\\\\Selectable but does not specify its types\\: TKey, T$#"
count: 1
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index f7c4b7da20..9390adf949 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -24,5 +24,7 @@
+
+
diff --git a/src/Sylius/Behat/Client/ApiClientInterface.php b/src/Sylius/Behat/Client/ApiClientInterface.php
index 318fc077c9..7ae5bf4fbe 100644
--- a/src/Sylius/Behat/Client/ApiClientInterface.php
+++ b/src/Sylius/Behat/Client/ApiClientInterface.php
@@ -62,7 +62,9 @@ interface ApiClientInterface
public function addFile(string $key, UploadedFile $file): void;
- public function addRequestData(string $key, string|int|bool|array $value): void;
+ public function addRequestData(string $key, null|string|int|bool|array $value): void;
+
+ public function replaceRequestData(string $key, null|string|int|bool|array $value): void;
public function setSubResourceData(string $key, array $data): void;
diff --git a/src/Sylius/Behat/Client/ApiPlatformClient.php b/src/Sylius/Behat/Client/ApiPlatformClient.php
index d6b116cb74..efac250742 100644
--- a/src/Sylius/Behat/Client/ApiPlatformClient.php
+++ b/src/Sylius/Behat/Client/ApiPlatformClient.php
@@ -200,12 +200,18 @@ final class ApiPlatformClient implements ApiClientInterface
$this->request->updateFiles([$key => $file]);
}
- /** @param string|int|bool|array $value */
- public function addRequestData(string $key, $value): void
+ public function addRequestData(string $key, null|string|int|bool|array $value): void
{
$this->request->updateContent([$key => $value]);
}
+ public function replaceRequestData(string $key, null|string|int|bool|array $value): void
+ {
+ $requestContent = $this->request->getContent();
+
+ $this->request->setContent(array_replace($requestContent, [$key => $value]));
+ }
+
public function updateRequestData(array $data): void
{
$this->request->updateContent($data);
diff --git a/src/Sylius/Behat/Context/Api/Admin/ManagingChannelsBillingDataContext.php b/src/Sylius/Behat/Context/Api/Admin/ManagingChannelsBillingDataContext.php
new file mode 100644
index 0000000000..02ff55114c
--- /dev/null
+++ b/src/Sylius/Behat/Context/Api/Admin/ManagingChannelsBillingDataContext.php
@@ -0,0 +1,83 @@
+getShopBillingDataFromChannel($channel);
+
+ Assert::same($shopBillingData->getCompany(), $company);
+ }
+
+ /**
+ * @Then /^(this channel) tax ID should be "([^"]+)"$/
+ */
+ public function thisChanneTaxIdShouldBe(ChannelInterface $channel, string $taxId): void
+ {
+ $shopBillingData = $this->getShopBillingDataFromChannel($channel);
+
+ Assert::same($shopBillingData->getTaxId(), $taxId);
+ }
+
+ /**
+ * @Then /^(this channel) shop billing address should be "([^"]+)", "([^"]+)" "([^"]+)" and ("([^"]+)" country)$/
+ * @Then /^(this channel) shop billing address should still be "([^"]+)", "([^"]+)" "([^"]+)" and ("([^"]+)" country)$/
+ */
+ public function thisChannelShopBillingAddressShouldBe(
+ ChannelInterface $channel,
+ string $street,
+ string $postcode,
+ string $city,
+ CountryInterface $country,
+ ): void {
+ $shopBillingData = $this->getShopBillingDataFromChannel($channel);
+
+ Assert::same($shopBillingData->getStreet(), $street);
+ Assert::same($shopBillingData->getPostcode(), $postcode);
+ Assert::same($shopBillingData->getCity(), $city);
+ Assert::same($shopBillingData->getCountryCode(), $country->getCode());
+ }
+
+ private function getShopBillingDataFromChannel(ChannelInterface $channel): ShopBillingDataInterface
+ {
+ $this->client->show(Resources::CHANNELS, $channel->getCode());
+
+ /** @var ShopBillingDataInterface $shopBillingData */
+ $shopBillingData = $this->iriConverter->getResourceFromIri($this->responseChecker->getValue($this->client->getLastResponse(), 'shopBillingData'));
+
+ return $shopBillingData;
+ }
+}
diff --git a/src/Sylius/Behat/Context/Api/Admin/ManagingChannelsContext.php b/src/Sylius/Behat/Context/Api/Admin/ManagingChannelsContext.php
index b1dee20e63..8546c610cf 100644
--- a/src/Sylius/Behat/Context/Api/Admin/ManagingChannelsContext.php
+++ b/src/Sylius/Behat/Context/Api/Admin/ManagingChannelsContext.php
@@ -20,6 +20,7 @@ use Sylius\Behat\Client\ResponseCheckerInterface;
use Sylius\Behat\Context\Api\Resources;
use Sylius\Behat\Service\Converter\SectionAwareIriConverterInterface;
use Sylius\Component\Addressing\Model\CountryInterface;
+use Sylius\Component\Addressing\Model\ZoneInterface;
use Sylius\Component\Core\Formatter\StringInflector;
use Sylius\Component\Core\Model\ChannelInterface;
use Sylius\Component\Core\Model\TaxonInterface;
@@ -47,6 +48,14 @@ final class ManagingChannelsContext implements Context
$this->client->buildCreateRequest(Resources::CHANNELS);
}
+ /**
+ * @When I delete channel :channel
+ */
+ public function iDeleteChannel(ChannelInterface $channel): void
+ {
+ $this->client->delete(Resources::CHANNELS, $channel->getCode());
+ }
+
/**
* @When I want to modify a channel :channel
*/
@@ -55,23 +64,78 @@ final class ManagingChannelsContext implements Context
$this->client->buildUpdateRequest(Resources::CHANNELS, $channel->getCode());
}
+ /**
+ * @When I rename it to :name
+ * @When I do not name it
+ * @When I remove its name
+ */
+ public function iRenameIt(string $name = ''): void
+ {
+ $this->client->addRequestData('name', $name);
+ }
+
+ /**
+ * @When /^I (enable|disable) it$/
+ */
+ public function iDisableIt(string $toggleAction): void
+ {
+ $this->client->addRequestData('enabled', $toggleAction === 'enable');
+ }
+
+ /**
+ * @When I change its menu taxon to :taxon
+ */
+ public function iChangeItsMenuTaxonTo(TaxonInterface $taxon): void
+ {
+ $this->client->addRequestData('menuTaxon', $this->sectionAwareIriConverter->getIriFromResourceInSection($taxon, 'admin'));
+ }
+
/**
* @When I specify its :field as :value
* @When I :field it :value
* @When I set its :field as :value
* @When I define its :field as :value
+ * @When I do not specify its :field
*/
- public function iSpecifyItsAs(string $field, string $value): void
+ public function iSpecifyItsAs(string $field, string $value = ''): void
{
$this->client->addRequestData($field, $value);
}
/**
* @When I choose :currency as the base currency
+ * @When I do not choose base currency
*/
- public function iChooseAsTheBaseCurrency(CurrencyInterface $currency): void
+ public function iChooseAsTheBaseCurrency(?CurrencyInterface $currency = null): void
{
- $this->client->addRequestData('baseCurrency', $this->sectionAwareIriConverter->getIriFromResourceInSection($currency, 'admin'));
+ $this->client->addRequestData(
+ 'baseCurrency',
+ null === $currency ? $currency : $this->sectionAwareIriConverter->getIriFromResourceInSection($currency, 'admin'),
+ );
+ }
+
+ /**
+ * @When I allow for paying in :currency
+ */
+ public function iAllowToPayingForThisChannel(CurrencyInterface $currency): void
+ {
+ $this->client->addRequestData('currencies', [$this->iriConverter->getIriFromResource($currency)]);
+ }
+
+ /**
+ * @When I select the :zone as default tax zone
+ */
+ public function iSelectDefaultTaxZone(ZoneInterface $zone): void
+ {
+ $this->client->addRequestData('defaultTaxZone', $this->iriConverter->getIriFromResource($zone));
+ }
+
+ /**
+ * @When I remove its default tax zone
+ */
+ public function iRemoveItsDefaultTaxZone(): void
+ {
+ $this->client->addRequestData('defaultTaxZone', null);
}
/**
@@ -79,15 +143,27 @@ final class ManagingChannelsContext implements Context
*/
public function iMakeItAvailableInLocale(LocaleInterface $locale): void
{
- $this->client->addRequestData('locales', [$this->iriConverter->getIriFromResource($locale)]);
+ $this->client->addRequestData('locales', [$this->sectionAwareIriConverter->getIriFromResourceInSection($locale, 'admin')]);
+ }
+
+ /**
+ * @When I make it available only in :locale
+ */
+ public function iMakeItAvailableOnlyInLocale(LocaleInterface $locale): void
+ {
+ $this->client->replaceRequestData('locales', [$this->sectionAwareIriConverter->getIriFromResourceInSection($locale, 'admin')]);
}
/**
* @When I choose :locale as a default locale
+ * @When I do not choose default locale
*/
- public function iChooseAsADefaultLocale(LocaleInterface $locale): void
+ public function iChooseAsADefaultLocale(?LocaleInterface $locale = null): void
{
- $this->client->addRequestData('defaultLocale', $this->sectionAwareIriConverter->getIriFromResourceInSection($locale, 'admin'));
+ $this->client->addRequestData(
+ 'defaultLocale',
+ null === $locale ? $locale : $this->sectionAwareIriConverter->getIriFromResourceInSection($locale, 'admin'),
+ );
}
/**
@@ -165,6 +241,44 @@ final class ManagingChannelsContext implements Context
$this->shopBillingData['taxId'] = $taxId;
}
+ /**
+ * @When /^I specify shop billing data for (this channel) as "([^"]+)", "([^"]+)", "([^"]+)", "([^"]+)", "([^"]+)" tax ID and ("([^"]+)" country)$/
+ */
+ public function iSpecifyShopBillingDataAs(
+ ChannelInterface $channel,
+ string $company,
+ string $street,
+ string $postcode,
+ string $city,
+ string $taxId,
+ CountryInterface $country,
+ ): void {
+ $shopBillingDataId = $this->iriConverter->getIriFromResource($channel->getShopBillingData());
+
+ $this->client->addRequestData(
+ 'shopBillingData',
+ [
+ '@id' => $shopBillingDataId,
+ 'company' => $company,
+ 'street' => $street,
+ 'postcode' => $postcode,
+ 'city' => $city,
+ 'countryCode' => $country->getCode(),
+ 'taxId' => $taxId,
+ ],
+ );
+ }
+
+ /**
+ * @When /^I specify new country code for (this channel) as "([^"]+)"$/
+ */
+ public function iSpecifyNewCountryCodeForThisChannelAs(ChannelInterface $channel, string $code): void
+ {
+ $shopBillingDataId = $this->iriConverter->getIriFromResource($channel->getShopBillingData());
+
+ $this->client->addRequestData('shopBillingData', ['@id' => $shopBillingDataId, 'countryCode' => $code]);
+ }
+
/**
* @When I specify shop billing address as :street, :postcode :city, :country
*/
@@ -180,6 +294,15 @@ final class ManagingChannelsContext implements Context
$this->shopBillingData['countryCode'] = $country->getCode();
}
+ /**
+ * @Then I save it
+ */
+ public function iSaveIt(): void
+ {
+ $this->iAddIt();
+ $this->client->update();
+ }
+
/**
* @When I select the :taxCalculationStrategy as tax calculation strategy
*/
@@ -280,6 +403,74 @@ final class ManagingChannelsContext implements Context
);
}
+ /**
+ * @Then I should not be able to edit its code
+ */
+ public function iShouldNotBeAbleToEditItsCode(): void
+ {
+ $this->client->updateRequestData(['code' => 'NEW_CODE']);
+
+ Assert::false($this->responseChecker->hasValue($this->client->update(), 'code', 'NEW_CODE'));
+ }
+
+ /**
+ * @Then the base currency field should be disabled
+ * @Then I should not be able to edit its base currency
+ */
+ public function theBaseCurrencyFieldShouldBeDisabled(): void
+ {
+ $this->client->updateRequestData(['baseCurrency' => 'PLN']);
+
+ Assert::false($this->responseChecker->hasValue($this->client->update(), 'baseCurrency', 'PLN'));
+ }
+
+ /**
+ * @Then /^(this channel) name should be "([^"]*)"$/
+ */
+ public function thisChannelNameShouldBe(ChannelInterface $channel, string $name): void
+ {
+ Assert::true(
+ $this->responseChecker->hasValue($this->client->show(Resources::CHANNELS, $channel->getCode()), 'name', $name),
+ sprintf('Its Channel does not have name %s.', $name),
+ );
+ }
+
+ /**
+ * @Then the :channel channel should no longer exist in the registry
+ */
+ public function theChannelShouldNoLongerExistInTheRegistry(string $name): void
+ {
+ Assert::false(
+ $this->responseChecker->hasItemWithValue($this->client->index(Resources::CHANNELS), 'name', $name),
+ sprintf('Channel with name %s exists', $name),
+ );
+ }
+
+ /**
+ * @Then I should be notified that it has been successfully deleted
+ */
+ public function iShouldBeNotifiedThatChannelHasBeenDeleted(): void
+ {
+ Assert::true($this->responseChecker->isDeletionSuccessful($this->client->getLastResponse()));
+ }
+
+ /**
+ * @Then /^(this channel) menu (taxon should be "([^"]+)")$/
+ */
+ public function thisChannelMenuTaxonShouldBe(ChannelInterface $channel, TaxonInterface $taxon): void
+ {
+ Assert::true(
+ $this->responseChecker->hasValue(
+ $this->client->show(
+ Resources::CHANNELS,
+ $channel->getCode(),
+ ),
+ 'menuTaxon',
+ $this->sectionAwareIriConverter->getIriFromResourceInSection($taxon, 'admin'),
+ ),
+ );
+ }
+
/**
* @Then I should see :count channels in the list
*/
@@ -299,4 +490,225 @@ final class ManagingChannelsContext implements Context
$type === 'shipping',
));
}
+
+ /**
+ * @Then I should be notified that it cannot be deleted
+ */
+ public function iShouldBeNotifiedThatItCannotBeDeleted(): void
+ {
+ Assert::contains(
+ $this->responseChecker->getError($this->client->getLastResponse()),
+ 'The channel cannot be deleted. At least one enabled channel is required.',
+ );
+ }
+
+ /**
+ * @Then I should be notified that at least one channel has to be defined
+ */
+ public function iShouldBeNotifiedThatAtLeastOneChannelHasToBeDefined(): void
+ {
+ Assert::contains(
+ $this->responseChecker->getError($this->client->getLastResponse()),
+ 'Must have at least one enabled entity',
+ );
+ }
+
+ /**
+ * @Then channel with name :channel should still be enabled
+ * @Then /^(this channel) should be enabled$/
+ */
+ public function channelWithNameShouldStillBeEnabled(ChannelInterface $channel): void
+ {
+ Assert::true(
+ $this->responseChecker->hasValue($this->client->show(Resources::CHANNELS, $channel->getCode()), 'enabled', true),
+ sprintf('Channel with name %s does not exists', $channel->getName()),
+ );
+ }
+
+ /**
+ * @Then this channel should still be named :channel
+ */
+ public function thisChannelShouldStillBeNamed(ChannelInterface $channel): void
+ {
+ Assert::true(
+ $this->responseChecker->hasValue($this->client->show(Resources::CHANNELS, $channel->getCode()), 'name', $channel->getName()),
+ sprintf('Channel with name %s does not exists', $channel->getName()),
+ );
+ }
+
+ /**
+ * @Then paying in :currency should be possible for the :channel channel
+ */
+ public function payingInCurrencyShouldBePossibleForTheChannel(CurrencyInterface $currency, ChannelInterface $channel): void
+ {
+ $currencies = $this->responseChecker->getValue(
+ $this->client->show(Resources::CHANNELS, $channel->getCode()),
+ 'currencies',
+ );
+
+ Assert::true(in_array($this->sectionAwareIriConverter->getIriFromResourceInSection($currency, 'admin'), $currencies));
+ }
+
+ /**
+ * @Then channel :channel should not have default tax zone
+ */
+ public function channelShouldNotHaveDefaultTaxZone(ChannelInterface $channel): void
+ {
+ Assert::same(
+ $this->responseChecker->getValue($this->client->show(Resources::CHANNELS, $channel->getCode()), 'defaultTaxZone'),
+ null,
+ sprintf('Channel %s has default tax zone', $channel->getName()),
+ );
+ }
+
+ /**
+ * @Then the default tax zone for the :channel channel should be :zone
+ */
+ public function theDefaultTaxZoneForTheChannelShouldBe(ChannelInterface $channel, ZoneInterface $zone): void
+ {
+ Assert::same(
+ $this->responseChecker->getValue($this->client->show(Resources::CHANNELS, $channel->getCode()), 'defaultTaxZone'),
+ $this->sectionAwareIriConverter->getIriFromResourceInSection($zone, 'admin'),
+ sprintf('Channel %s does not have %s default tax zone', $channel->getName(), $zone),
+ );
+ }
+
+ /**
+ * @Then the channel :channel should be available in :locale
+ */
+ public function theChannelShouldBeAvailableIn(ChannelInterface $channel, LocaleInterface $locale): void
+ {
+ $locales = $this->responseChecker->getValue(
+ $this->client->show(Resources::CHANNELS, $channel->getCode()),
+ 'locales',
+ );
+
+ Assert::true(in_array($this->sectionAwareIriConverter->getIriFromResourceInSection($locale, 'admin'), $locales));
+ }
+
+ /**
+ * @Then I should be notified that the default locale has to be enabled
+ */
+ public function iShouldBeNotifiedThatTheDefaultLocaleHasToBeEnabled(): void
+ {
+ Assert::contains(
+ $this->responseChecker->getError($this->client->getLastResponse()),
+ 'defaultLocale: Default locale has to be enabled.',
+ );
+ }
+
+ /**
+ * @Then /^(this channel) should still be in the registry$/
+ */
+ public function thisChannelShouldStillBeInTheRegistry(ChannelInterface $channel): void
+ {
+ Assert::true(
+ $this->responseChecker->hasItemWithValue($this->client->index(Resources::CHANNELS), 'code', $channel->getCode()),
+ sprintf('Channel with code %s does not exists', $channel->getCode()),
+ );
+ }
+
+ /**
+ * @Then the tax calculation strategy for the :channel channel should be :taxCalculationStrategy
+ */
+ public function theTaxCalculationStrategyForTheChannelShouldBe(
+ ChannelInterface $channel,
+ string $taxCalculationStrategy,
+ ): void {
+ Assert::same(
+ $this->responseChecker->getValue($this->client->show(Resources::CHANNELS, $channel->getCode()), 'taxCalculationStrategy'),
+ StringInflector::nameToLowercaseCode($taxCalculationStrategy),
+ sprintf('Channel %s does not have %s tax calculation strategy', $channel->getName(), $taxCalculationStrategy),
+ );
+ }
+
+ /**
+ * @Then /^(this channel) should be disabled$/
+ */
+ public function thisChannelShouldBeDisabled(ChannelInterface $channel): void
+ {
+ Assert::same(
+ $this->responseChecker->getValue($this->client->show(Resources::CHANNELS, $channel->getCode()), 'enabled'),
+ false,
+ sprintf('Channel %s is enabled', $channel->getName()),
+ );
+ }
+
+ /**
+ * @Then I should be notified that :element is required
+ */
+ public function iShouldBeNotifiedThatIsRequired(string $element): void
+ {
+ Assert::contains(
+ $this->responseChecker->getError($this->client->getLastResponse()),
+ sprintf('%s: Please enter channel %s.', $element, $element),
+ );
+ }
+
+ /**
+ * @Then I should be notified that base currency is required
+ */
+ public function iShouldBeNotifiedThatBaseCurrencyIsRequired(): void
+ {
+ Assert::contains(
+ $this->responseChecker->getError($this->client->getLastResponse()),
+ 'Expected IRI or nested document for attribute "baseCurrency", "NULL" given.',
+ );
+ }
+
+ /**
+ * @Then I should be notified that default locale is required
+ */
+ public function iShouldBeNotifiedThatDefaultLocaleIsRequired(): void
+ {
+ Assert::contains(
+ $this->responseChecker->getError($this->client->getLastResponse()),
+ 'Expected IRI or nested document for attribute "defaultLocale", "NULL" given.',
+ );
+ }
+
+ /**
+ * @Then channel with :element :value should not be added
+ */
+ public function channelWithShouldNotBeAdded(string $element, string $value): void
+ {
+ Assert::false(
+ $this->responseChecker->hasItemWithValue($this->client->index(Resources::CHANNELS), $element, $value),
+ sprintf('Channel with %s: %s exists', $element, $value),
+ );
+ }
+
+ /**
+ * @Then I should be notified that channel with this code already exists
+ */
+ public function iShouldBeNotifiedThatChannelWithThisCodeAlreadyExists(): void
+ {
+ Assert::contains(
+ $this->responseChecker->getError($this->client->getLastResponse()),
+ 'code: Channel code has to be unique.',
+ );
+ }
+
+ /**
+ * @Then there should still be only one channel with :element :value
+ */
+ public function thereShouldStillBeOnlyOneChannelWithCode(string $element, string $value): void
+ {
+ Assert::same(
+ count($this->responseChecker->getCollectionItemsWithValue($this->client->index(Resources::CHANNELS), $element, $value)),
+ 1,
+ sprintf('There is more than one channel with %s: %s', $element, $value),
+ );
+ }
+
+ /**
+ * @Then I should be notified that it is not a valid country
+ */
+ public function iShouldBeNotifiedThatItIsNotAValidCountryCode(): void
+ {
+ Assert::contains(
+ $this->responseChecker->getError($this->client->getLastResponse()),
+ 'countryCode: This value is not a valid country.',
+ );
+ }
}
diff --git a/src/Sylius/Behat/Context/Api/Admin/ManagingProductsContext.php b/src/Sylius/Behat/Context/Api/Admin/ManagingProductsContext.php
index e910784eda..4a5b7ad03f 100644
--- a/src/Sylius/Behat/Context/Api/Admin/ManagingProductsContext.php
+++ b/src/Sylius/Behat/Context/Api/Admin/ManagingProductsContext.php
@@ -276,7 +276,7 @@ final class ManagingProductsContext implements Context
public function iSetItsNonTranslatableAttributeTo(ProductAttributeInterface $attribute, string $value): void
{
$this->client->addSubResourceData(
- 'attributes',
+ 'attributes',
[
'attribute' => $this->iriConverter->getIriFromResource($attribute),
'value' => $this->getAttributeValueInProperType($attribute, $value),
@@ -290,7 +290,7 @@ final class ManagingProductsContext implements Context
public function iSetTheInvalidIntegerValueOfTheNonTranslatableAttributeTo(ProductAttributeInterface $attribute, int $value): void
{
$this->client->addSubResourceData(
- 'attributes',
+ 'attributes',
[
'attribute' => $this->iriConverter->getIriFromResource($attribute),
'value' => $value,
@@ -304,7 +304,7 @@ final class ManagingProductsContext implements Context
public function iSetTheInvalidStringValueOfTheNonTranslatableAttributeTo(ProductAttributeInterface $attribute, string $value): void
{
$this->client->addSubResourceData(
- 'attributes',
+ 'attributes',
[
'attribute' => $this->iriConverter->getIriFromResource($attribute),
'value' => $value,
@@ -320,7 +320,7 @@ final class ManagingProductsContext implements Context
public function iSetItsAttributeTo(
ProductAttributeInterface $attribute,
?string $value = null,
- string $localeCode = 'en_US'
+ string $localeCode = 'en_US',
): void {
$this->client->addSubResourceData(
'attributes',
@@ -363,7 +363,7 @@ final class ManagingProductsContext implements Context
public function iSelectValueInForTheAttribute(
string $value,
string $localeCode,
- ProductAttributeInterface $attribute
+ ProductAttributeInterface $attribute,
): void {
$this->client->addSubResourceData(
'attributes',
@@ -380,7 +380,7 @@ final class ManagingProductsContext implements Context
*/
public function iSelectValueForTheAttribute(
string $value,
- ProductAttributeInterface $attribute
+ ProductAttributeInterface $attribute,
): void {
$this->client->addSubResourceData(
'attributes',
@@ -678,7 +678,7 @@ final class ManagingProductsContext implements Context
public function nonTranslatableAttributeOfProductShouldBe(
ProductAttributeInterface $attribute,
ProductInterface $product,
- string $value
+ string $value,
): void {
$this->client->show(Resources::PRODUCTS, $product->getCode());
@@ -702,7 +702,7 @@ final class ManagingProductsContext implements Context
ProductAttributeInterface $attribute,
ProductInterface $product,
string $value,
- string $localeCode = 'en_US'
+ string $localeCode = 'en_US',
): void {
$this->client->show(Resources::PRODUCTS, $product->getCode());
@@ -718,7 +718,7 @@ final class ManagingProductsContext implements Context
foreach ($attributes as $attributeValue) {
if ($attributeValue['attribute'] === $this->sectionAwareIriConverter->getIriFromResourceInSection($attribute, 'admin')) {
throw new \InvalidArgumentException(
- sprintf('Product %s have attribute %s', $product->getName(), $attribute->getName())
+ sprintf('Product %s have attribute %s', $product->getName(), $attribute->getName()),
);
}
}
@@ -780,7 +780,7 @@ final class ManagingProductsContext implements Context
public function iShouldBeNotifiedThatTheAttributeInShouldBeLongerThan(
string $attributeName,
string $localeCode,
- int $number
+ int $number,
): void {
Assert::contains(
$this->responseChecker->getError($this->client->getLastResponse()),
@@ -861,7 +861,7 @@ final class ManagingProductsContext implements Context
private function getAttributeValueInProperType(
ProductAttributeInterface $productAttribute,
- string $value
+ string $value,
): string|bool|float|int {
switch ($productAttribute->getStorageType()) {
case AttributeValueInterface::STORAGE_BOOLEAN:
@@ -887,7 +887,7 @@ final class ManagingProductsContext implements Context
}
throw new \InvalidArgumentException(
- sprintf('Value "%s" not found in attribute "%s"', $value, $attribute->getName())
+ sprintf('Value "%s" not found in attribute "%s"', $value, $attribute->getName()),
);
}
@@ -908,7 +908,7 @@ final class ManagingProductsContext implements Context
}
throw new \InvalidArgumentException(
- sprintf('The given product does not have attribute %s', $attribute->getName())
+ sprintf('The given product does not have attribute %s', $attribute->getName()),
);
}
diff --git a/src/Sylius/Behat/Context/Ui/Admin/ManagingChannelsBillingDataContext.php b/src/Sylius/Behat/Context/Ui/Admin/ManagingChannelsBillingDataContext.php
index 302623d43a..42eb04b045 100644
--- a/src/Sylius/Behat/Context/Ui/Admin/ManagingChannelsBillingDataContext.php
+++ b/src/Sylius/Behat/Context/Ui/Admin/ManagingChannelsBillingDataContext.php
@@ -24,6 +24,22 @@ final class ManagingChannelsBillingDataContext implements Context
{
}
+ /**
+ * @When I specify shop billing data for this channel as :company, :street, :postcode, :city, :taxId tax ID and :country country
+ */
+ public function iSpecifyNewShopBillingDataForChannelAs(
+ string $company,
+ string $street,
+ string $postcode,
+ string $city,
+ string $taxId,
+ CountryInterface $country,
+ ): void {
+ $this->shopBillingDataElement->specifyCompany($company);
+ $this->shopBillingDataElement->specifyTaxId($taxId);
+ $this->shopBillingDataElement->specifyBillingAddress($street, $postcode, $city, $country->getCode());
+ }
+
/**
* @When I specify company as :company
*/
@@ -69,6 +85,7 @@ final class ManagingChannelsBillingDataContext implements Context
}
/**
+ * @Then this channel shop billing address should be :street, :postcode :city and :country country
* @Then this channel shop billing address should be :street, :postcode :city, :country
*/
public function thisChannelShopBillingAddressShouldBe(
diff --git a/src/Sylius/Behat/Context/Ui/Admin/ManagingChannelsContext.php b/src/Sylius/Behat/Context/Ui/Admin/ManagingChannelsContext.php
index a44d18bb00..d1d43bd43e 100644
--- a/src/Sylius/Behat/Context/Ui/Admin/ManagingChannelsContext.php
+++ b/src/Sylius/Behat/Context/Ui/Admin/ManagingChannelsContext.php
@@ -292,6 +292,7 @@ final class ManagingChannelsContext implements Context
* @Given I am modifying a channel :channel
* @When I want to modify a channel :channel
* @When /^I want to modify (this channel)$/
+ * @When I want to modify a billing data of channel :channel
*/
public function iWantToModifyChannel(ChannelInterface $channel): void
{
@@ -315,6 +316,7 @@ final class ManagingChannelsContext implements Context
/**
* @When I save my changes
* @When I try to save my changes
+ * @When I save it
*/
public function iSaveMyChanges(): void
{
@@ -332,7 +334,7 @@ final class ManagingChannelsContext implements Context
/**
* @Then there should still be only one channel with :element :value
*/
- public function thereShouldStillBeOnlyOneChannelWithCode(string $element, string $value)
+ public function thereShouldStillBeOnlyOneChannelWithCode(string $element, string $value): void
{
$this->iWantToBrowseChannels();
@@ -375,6 +377,7 @@ final class ManagingChannelsContext implements Context
/**
* @Then the code field should be disabled
+ * @Then I should not be able to edit its code
*/
public function theCodeFieldShouldBeDisabled(): void
{
@@ -461,7 +464,7 @@ final class ManagingChannelsContext implements Context
/**
* @Then paying in :currencyCode should be possible for the :channel channel
*/
- public function payingInEuroShouldBePossibleForTheChannel(string $currencyCode, ChannelInterface $channel): void
+ public function payingInCurrencyShouldBePossibleForTheChannel(string $currencyCode, ChannelInterface $channel): void
{
$this->updatePage->open(['id' => $channel->getId()]);
@@ -607,6 +610,7 @@ final class ManagingChannelsContext implements Context
/**
* @Then the base currency field should be disabled
+ * @Then I should not be able to edit its base currency
*/
public function theBaseCurrencyFieldShouldBeDisabled(): void
{
diff --git a/src/Sylius/Behat/Resources/config/services/contexts/api/admin.xml b/src/Sylius/Behat/Resources/config/services/contexts/api/admin.xml
index 1d0db51420..ef996dd029 100644
--- a/src/Sylius/Behat/Resources/config/services/contexts/api/admin.xml
+++ b/src/Sylius/Behat/Resources/config/services/contexts/api/admin.xml
@@ -64,6 +64,12 @@
+
+
+
+
+
+
diff --git a/src/Sylius/Behat/Resources/config/suites/api/channel/managing_channels.yml b/src/Sylius/Behat/Resources/config/suites/api/channel/managing_channels.yml
index 924a4300d3..1f68d15ceb 100644
--- a/src/Sylius/Behat/Resources/config/suites/api/channel/managing_channels.yml
+++ b/src/Sylius/Behat/Resources/config/suites/api/channel/managing_channels.yml
@@ -29,6 +29,7 @@ default:
- sylius.behat.context.api.admin.managing_channels
- sylius.behat.context.api.admin.response
- sylius.behat.context.api.admin.save
+ - Sylius\Behat\Context\Api\Admin\ManagingChannelsBillingDataContext
- Sylius\Behat\Context\Api\Admin\ManagingChannelPriceHistoryConfigsContext
filters:
diff --git a/src/Sylius/Bundle/AddressingBundle/DependencyInjection/Configuration.php b/src/Sylius/Bundle/AddressingBundle/DependencyInjection/Configuration.php
index fed7c017d7..3851b89526 100644
--- a/src/Sylius/Bundle/AddressingBundle/DependencyInjection/Configuration.php
+++ b/src/Sylius/Bundle/AddressingBundle/DependencyInjection/Configuration.php
@@ -14,6 +14,10 @@ declare(strict_types=1);
namespace Sylius\Bundle\AddressingBundle\DependencyInjection;
use Sylius\Bundle\AddressingBundle\Controller\ProvinceController;
+use Sylius\Bundle\AddressingBundle\Doctrine\ORM\AddressRepository;
+use Sylius\Bundle\AddressingBundle\Doctrine\ORM\CountryRepository;
+use Sylius\Bundle\AddressingBundle\Doctrine\ORM\ProvinceRepository;
+use Sylius\Bundle\AddressingBundle\Doctrine\ORM\ZoneMemberRepository;
use Sylius\Bundle\AddressingBundle\Form\Type\AddressType;
use Sylius\Bundle\AddressingBundle\Form\Type\CountryType;
use Sylius\Bundle\AddressingBundle\Form\Type\ProvinceType;
@@ -77,7 +81,7 @@ final class Configuration implements ConfigurationInterface
->scalarNode('model')->defaultValue(Address::class)->cannotBeEmpty()->end()
->scalarNode('interface')->defaultValue(AddressInterface::class)->cannotBeEmpty()->end()
->scalarNode('controller')->defaultValue(ResourceController::class)->cannotBeEmpty()->end()
- ->scalarNode('repository')->cannotBeEmpty()->end()
+ ->scalarNode('repository')->defaultValue(AddressRepository::class)->cannotBeEmpty()->end()
->scalarNode('factory')->defaultValue(Factory::class)->end()
->scalarNode('form')->defaultValue(AddressType::class)->cannotBeEmpty()->end()
->end()
@@ -109,7 +113,7 @@ final class Configuration implements ConfigurationInterface
->scalarNode('model')->defaultValue(Country::class)->cannotBeEmpty()->end()
->scalarNode('interface')->defaultValue(CountryInterface::class)->cannotBeEmpty()->end()
->scalarNode('controller')->defaultValue(ResourceController::class)->cannotBeEmpty()->end()
- ->scalarNode('repository')->cannotBeEmpty()->end()
+ ->scalarNode('repository')->defaultValue(CountryRepository::class)->cannotBeEmpty()->end()
->scalarNode('factory')->defaultValue(Factory::class)->end()
->scalarNode('form')->defaultValue(CountryType::class)->cannotBeEmpty()->end()
->end()
@@ -126,7 +130,7 @@ final class Configuration implements ConfigurationInterface
->scalarNode('model')->defaultValue(Province::class)->cannotBeEmpty()->end()
->scalarNode('interface')->defaultValue(ProvinceInterface::class)->cannotBeEmpty()->end()
->scalarNode('controller')->defaultValue(ProvinceController::class)->cannotBeEmpty()->end()
- ->scalarNode('repository')->cannotBeEmpty()->end()
+ ->scalarNode('repository')->defaultValue(ProvinceRepository::class)->cannotBeEmpty()->end()
->scalarNode('factory')->defaultValue(Factory::class)->end()
->scalarNode('form')->defaultValue(ProvinceType::class)->cannotBeEmpty()->end()
->end()
@@ -160,7 +164,7 @@ final class Configuration implements ConfigurationInterface
->scalarNode('model')->defaultValue(ZoneMember::class)->cannotBeEmpty()->end()
->scalarNode('interface')->defaultValue(ZoneMemberInterface::class)->cannotBeEmpty()->end()
->scalarNode('controller')->defaultValue(ResourceController::class)->cannotBeEmpty()->end()
- ->scalarNode('repository')->cannotBeEmpty()->end()
+ ->scalarNode('repository')->defaultValue(ZoneMemberRepository::class)->cannotBeEmpty()->end()
->scalarNode('factory')->defaultValue(Factory::class)->end()
->scalarNode('form')->defaultValue(ZoneMemberType::class)->cannotBeEmpty()->end()
->end()
diff --git a/src/Sylius/Bundle/AddressingBundle/Doctrine/ORM/AddressRepository.php b/src/Sylius/Bundle/AddressingBundle/Doctrine/ORM/AddressRepository.php
new file mode 100644
index 0000000000..28ec20afd9
--- /dev/null
+++ b/src/Sylius/Bundle/AddressingBundle/Doctrine/ORM/AddressRepository.php
@@ -0,0 +1,25 @@
+
+ */
+class AddressRepository extends EntityRepository implements AddressRepositoryInterface
+{
+}
diff --git a/src/Sylius/Bundle/AddressingBundle/Doctrine/ORM/CountryRepository.php b/src/Sylius/Bundle/AddressingBundle/Doctrine/ORM/CountryRepository.php
new file mode 100644
index 0000000000..0586511909
--- /dev/null
+++ b/src/Sylius/Bundle/AddressingBundle/Doctrine/ORM/CountryRepository.php
@@ -0,0 +1,25 @@
+
+ */
+class CountryRepository extends EntityRepository implements CountryRepositoryInterface
+{
+}
diff --git a/src/Sylius/Bundle/AddressingBundle/Doctrine/ORM/ProvinceRepository.php b/src/Sylius/Bundle/AddressingBundle/Doctrine/ORM/ProvinceRepository.php
new file mode 100644
index 0000000000..0aa9ef408e
--- /dev/null
+++ b/src/Sylius/Bundle/AddressingBundle/Doctrine/ORM/ProvinceRepository.php
@@ -0,0 +1,25 @@
+
+ */
+class ProvinceRepository extends EntityRepository implements ProvinceRepositoryInterface
+{
+}
diff --git a/src/Sylius/Bundle/AddressingBundle/Doctrine/ORM/ZoneMemberRepository.php b/src/Sylius/Bundle/AddressingBundle/Doctrine/ORM/ZoneMemberRepository.php
new file mode 100644
index 0000000000..5632ed0477
--- /dev/null
+++ b/src/Sylius/Bundle/AddressingBundle/Doctrine/ORM/ZoneMemberRepository.php
@@ -0,0 +1,25 @@
+
+ */
+class ZoneMemberRepository extends EntityRepository implements ZoneMemberRepositoryInterface
+{
+}
diff --git a/src/Sylius/Bundle/AdminBundle/Action/Account/RenderResetPasswordPageAction.php b/src/Sylius/Bundle/AdminBundle/Action/Account/RenderResetPasswordPageAction.php
index cef6f5aedd..f144e9a4cd 100644
--- a/src/Sylius/Bundle/AdminBundle/Action/Account/RenderResetPasswordPageAction.php
+++ b/src/Sylius/Bundle/AdminBundle/Action/Account/RenderResetPasswordPageAction.php
@@ -72,7 +72,7 @@ final readonly class RenderResetPasswordPageAction
->add('error', 'sylius.admin.password_reset.token_expired')
;
- $attributes = $request->attributes->get('_sylius');
+ $attributes = $request->attributes->get('_sylius', []);
$redirect = $attributes['redirect'] ?? 'sylius_admin_login';
if (is_array($redirect)) {
diff --git a/src/Sylius/Bundle/AdminBundle/Action/Account/RequestPasswordResetAction.php b/src/Sylius/Bundle/AdminBundle/Action/Account/RequestPasswordResetAction.php
index 50aa89368e..acaf627876 100644
--- a/src/Sylius/Bundle/AdminBundle/Action/Account/RequestPasswordResetAction.php
+++ b/src/Sylius/Bundle/AdminBundle/Action/Account/RequestPasswordResetAction.php
@@ -56,7 +56,7 @@ final readonly class RequestPasswordResetAction
->add('success', 'sylius.admin.request_reset_password.success')
;
- $options = $request->attributes->get('_sylius');
+ $options = $request->attributes->get('_sylius', []);
$redirectRoute = $options['redirect'] ?? 'sylius_admin_login';
if (is_array($redirectRoute)) {
diff --git a/src/Sylius/Bundle/AdminBundle/Action/Account/ResetPasswordAction.php b/src/Sylius/Bundle/AdminBundle/Action/Account/ResetPasswordAction.php
index db1e8df216..61421f555a 100644
--- a/src/Sylius/Bundle/AdminBundle/Action/Account/ResetPasswordAction.php
+++ b/src/Sylius/Bundle/AdminBundle/Action/Account/ResetPasswordAction.php
@@ -52,7 +52,7 @@ final class ResetPasswordAction
->add('success', 'sylius.admin.password_reset.success')
;
- $attributes = $request->attributes->get('_sylius');
+ $attributes = $request->attributes->get('_sylius', []);
$redirect = $attributes['redirect'] ?? 'sylius_admin_login';
if (is_array($redirect)) {
diff --git a/src/Sylius/Bundle/AdminBundle/Action/RemoveAvatarAction.php b/src/Sylius/Bundle/AdminBundle/Action/RemoveAvatarAction.php
index 8f65e90820..99a39f710b 100644
--- a/src/Sylius/Bundle/AdminBundle/Action/RemoveAvatarAction.php
+++ b/src/Sylius/Bundle/AdminBundle/Action/RemoveAvatarAction.php
@@ -34,9 +34,11 @@ final class RemoveAvatarAction
public function __invoke(Request $request): Response
{
- $userId = $request->attributes->get('id');
+ $userId = $request->attributes->get('id', '');
- if (!$this->csrfTokenManager->isTokenValid(new CsrfToken($userId, (string) $request->query->get('_csrf_token')))) {
+ if (!$this->csrfTokenManager->isTokenValid(
+ new CsrfToken($userId, (string) $request->query->get('_csrf_token', '')),
+ )) {
throw new HttpException(Response::HTTP_FORBIDDEN, 'Invalid csrf token.');
}
diff --git a/src/Sylius/Bundle/AdminBundle/Action/ResendOrderConfirmationEmailAction.php b/src/Sylius/Bundle/AdminBundle/Action/ResendOrderConfirmationEmailAction.php
index f231abfda9..02441a1d4f 100644
--- a/src/Sylius/Bundle/AdminBundle/Action/ResendOrderConfirmationEmailAction.php
+++ b/src/Sylius/Bundle/AdminBundle/Action/ResendOrderConfirmationEmailAction.php
@@ -38,9 +38,11 @@ final readonly class ResendOrderConfirmationEmailAction
public function __invoke(Request $request): Response
{
- $orderId = $request->attributes->get('id');
+ $orderId = $request->attributes->get('id', '');
- if (!$this->csrfTokenManager->isTokenValid(new CsrfToken($orderId, (string) $request->query->get('_csrf_token')))) {
+ if (!$this->csrfTokenManager->isTokenValid(
+ new CsrfToken($orderId, (string) $request->query->get('_csrf_token', '')),
+ )) {
throw new HttpException(Response::HTTP_FORBIDDEN, 'Invalid csrf token.');
}
diff --git a/src/Sylius/Bundle/AdminBundle/Action/ResendShipmentConfirmationEmailAction.php b/src/Sylius/Bundle/AdminBundle/Action/ResendShipmentConfirmationEmailAction.php
index d8034defab..6a6eebfc42 100644
--- a/src/Sylius/Bundle/AdminBundle/Action/ResendShipmentConfirmationEmailAction.php
+++ b/src/Sylius/Bundle/AdminBundle/Action/ResendShipmentConfirmationEmailAction.php
@@ -38,9 +38,11 @@ final readonly class ResendShipmentConfirmationEmailAction
public function __invoke(Request $request): Response
{
- $shipmentId = $request->attributes->get('id');
+ $shipmentId = $request->attributes->get('id', '');
- if (!$this->csrfTokenManager->isTokenValid(new CsrfToken($shipmentId, (string) $request->query->get('_csrf_token')))) {
+ if (!$this->csrfTokenManager->isTokenValid(
+ new CsrfToken($shipmentId, (string) $request->query->get('_csrf_token', '')),
+ )) {
throw new HttpException(Response::HTTP_FORBIDDEN, 'Invalid csrf token.');
}
diff --git a/src/Sylius/Bundle/AdminBundle/Controller/RemoveCatalogPromotionAction.php b/src/Sylius/Bundle/AdminBundle/Controller/RemoveCatalogPromotionAction.php
index 20bb2c5f58..6f48ac30e6 100644
--- a/src/Sylius/Bundle/AdminBundle/Controller/RemoveCatalogPromotionAction.php
+++ b/src/Sylius/Bundle/AdminBundle/Controller/RemoveCatalogPromotionAction.php
@@ -31,8 +31,13 @@ final class RemoveCatalogPromotionAction
public function __invoke(Request $request): Response
{
+ $catalogPromotionCode = $request->attributes->get('code');
+ if (null === $catalogPromotionCode) {
+ throw new NotFoundHttpException('The catalog promotion has not been found');
+ }
+
try {
- $this->catalogPromotionRemovalProcessor->removeCatalogPromotion($request->attributes->get('code'));
+ $this->catalogPromotionRemovalProcessor->removeCatalogPromotion($catalogPromotionCode);
/** @var Session $session */
$session = $request->getSession();
diff --git a/src/Sylius/Bundle/AdminBundle/EventListener/AdminFilterSubscriber.php b/src/Sylius/Bundle/AdminBundle/EventListener/AdminFilterSubscriber.php
index dfb114f286..32305a7e53 100644
--- a/src/Sylius/Bundle/AdminBundle/EventListener/AdminFilterSubscriber.php
+++ b/src/Sylius/Bundle/AdminBundle/EventListener/AdminFilterSubscriber.php
@@ -44,17 +44,12 @@ final class AdminFilterSubscriber implements EventSubscriberInterface
}
$requestAttributes = $eventRequest->attributes;
- $originalRoute = $requestAttributes->get('_route');
- if (!$this->isIndexResourceRoute($originalRoute)) {
- return;
- }
-
- if (!$this->isAdminSection($requestAttributes->get('_sylius', []))) {
- return;
- }
-
- if (null === $requestAttributes->get('_controller')) {
+ if (
+ null === $requestAttributes->get('_controller') ||
+ !$this->isIndexResourceRoute($requestAttributes->get('_route', '')) ||
+ !$this->isAdminSection($requestAttributes->get('_sylius', []))
+ ) {
return;
}
@@ -80,6 +75,6 @@ final class AdminFilterSubscriber implements EventSubscriberInterface
private function isAdminSection(array $syliusParameters): bool
{
- return array_key_exists('section', $syliusParameters) && 'admin' === $syliusParameters['section'];
+ return isset($syliusParameters['section']) && 'admin' === $syliusParameters['section'];
}
}
diff --git a/src/Sylius/Bundle/AdminBundle/EventListener/ResourceDeleteSubscriber.php b/src/Sylius/Bundle/AdminBundle/EventListener/ResourceDeleteSubscriber.php
index 024dceaab2..83128a778f 100644
--- a/src/Sylius/Bundle/AdminBundle/EventListener/ResourceDeleteSubscriber.php
+++ b/src/Sylius/Bundle/AdminBundle/EventListener/ResourceDeleteSubscriber.php
@@ -58,7 +58,7 @@ final readonly class ResourceDeleteSubscriber implements EventSubscriberInterfac
$eventRequest = $event->getRequest();
$requestAttributes = $eventRequest->attributes;
- $originalRoute = $requestAttributes->get('_route');
+ $originalRoute = $requestAttributes->get('_route', '');
if (!$this->isMethodDelete($eventRequest) ||
!$this->isSyliusRoute($originalRoute) ||
diff --git a/src/Sylius/Bundle/AdminBundle/Tests/MessageHandler/Admin/SendResetPasswordEmailHandlerTest.php b/src/Sylius/Bundle/AdminBundle/Tests/MessageHandler/Admin/SendResetPasswordEmailHandlerTest.php
index cca655a0a9..fedcc1aaa4 100644
--- a/src/Sylius/Bundle/AdminBundle/Tests/MessageHandler/Admin/SendResetPasswordEmailHandlerTest.php
+++ b/src/Sylius/Bundle/AdminBundle/Tests/MessageHandler/Admin/SendResetPasswordEmailHandlerTest.php
@@ -16,24 +16,16 @@ namespace Sylius\Bundle\AdminBundle\Tests\MessageHandler\Admin;
use Sylius\Bundle\CoreBundle\Message\Admin\Account\SendResetPasswordEmail;
use Sylius\Bundle\CoreBundle\MessageHandler\Admin\Account\SendResetPasswordEmailHandler;
use Sylius\Component\Core\Model\AdminUser;
-use Sylius\Component\Core\Test\SwiftmailerAssertionTrait;
use Sylius\Component\Mailer\Sender\SenderInterface;
use Sylius\Component\User\Repository\UserRepositoryInterface;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
-use Symfony\Component\Filesystem\Filesystem;
use Symfony\Contracts\Translation\TranslatorInterface;
final class SendResetPasswordEmailHandlerTest extends KernelTestCase
{
- use SwiftmailerAssertionTrait;
-
/** @test */
public function it_sends_password_reset_token_email(): void
{
- if (self::isItSwiftmailerTestEnv()) {
- $this->markTestSkipped('Test is relevant only for the environment without swiftmailer');
- }
-
$container = self::bootKernel()->getContainer();
/** @var TranslatorInterface $translator */
@@ -67,57 +59,4 @@ final class SendResetPasswordEmailHandlerTest extends KernelTestCase
$translator->trans('sylius.email.admin_password_reset.to_reset_your_password', [], null, 'en_US'),
);
}
-
- /** @test */
- public function it_sends_password_reset_token_email_with_swiftmailer(): void
- {
- if (!self::isItSwiftmailerTestEnv()) {
- $this->markTestSkipped('Test is relevant only for the environment with swiftmailer');
- }
-
- $container = self::bootKernel()->getContainer();
-
- self::setSpoolDirectory($container->getParameter('kernel.cache_dir') . '/spool');
-
- /** @var Filesystem $filesystem */
- $filesystem = $container->get('test.filesystem.public');
-
- $filesystem->remove(self::getSpoolDirectory());
-
- /** @var TranslatorInterface $translator */
- $translator = $container->get('translator');
-
- /** @var SenderInterface $emailSender */
- $emailSender = $container->get('sylius.email_sender');
-
- $adminUser = new AdminUser();
- $adminUser->setEmail('sylius@example.com');
- $adminUser->setPasswordResetToken('my_reset_token');
-
- $adminUserRepository = $this->createMock(UserRepositoryInterface::class);
- $adminUserRepository
- ->method('findOneByEmail')
- ->with('sylius@example.com')
- ->willReturn($adminUser)
- ;
-
- $resetPasswordEmailHandler = new SendResetPasswordEmailHandler($adminUserRepository, $emailSender);
- $resetPasswordEmailHandler(new SendResetPasswordEmail(
- 'sylius@example.com',
- 'en_US',
- ));
-
- self::assertSpooledMessagesCountWithRecipient(1, 'sylius@example.com');
- self::assertSpooledMessageWithContentHasRecipient(
- $translator->trans('sylius.email.admin_password_reset.to_reset_your_password', [], null, 'en_US'),
- 'sylius@example.com',
- );
- }
-
- private static function isItSwiftmailerTestEnv(): bool
- {
- $env = self::getContainer()->getParameter('kernel.environment');
-
- return $env === 'test_with_swiftmailer';
- }
}
diff --git a/src/Sylius/Bundle/AdminBundle/spec/Action/Account/RequestPasswordResetActionSpec.php b/src/Sylius/Bundle/AdminBundle/spec/Action/Account/RequestPasswordResetActionSpec.php
index ec1aa68bd8..2016713938 100644
--- a/src/Sylius/Bundle/AdminBundle/spec/Action/Account/RequestPasswordResetActionSpec.php
+++ b/src/Sylius/Bundle/AdminBundle/spec/Action/Account/RequestPasswordResetActionSpec.php
@@ -81,7 +81,7 @@ final class RequestPasswordResetActionSpec extends ObjectBehavior
$flashBag->add('success', 'sylius.admin.request_reset_password.success')->shouldBeCalled();
- $attributesBag->get('_sylius')->shouldBeCalled()->willReturn([
+ $attributesBag->get('_sylius', [])->shouldBeCalled()->willReturn([
'redirect' => 'my_custom_route',
]);
$request->attributes = $attributesBag->getWrappedObject();
@@ -120,7 +120,7 @@ final class RequestPasswordResetActionSpec extends ObjectBehavior
$parameters = [
'my_parameter' => 'my_value',
];
- $attributesBag->get('_sylius')->shouldBeCalled()->willReturn([
+ $attributesBag->get('_sylius', [])->shouldBeCalled()->willReturn([
'redirect' => [
'route' => $route,
'params' => $parameters,
@@ -158,7 +158,7 @@ final class RequestPasswordResetActionSpec extends ObjectBehavior
$flashBag->add('success', 'sylius.admin.request_reset_password.success')->shouldBeCalled();
- $attributesBag->get('_sylius')->shouldBeCalled()->willReturn(null);
+ $attributesBag->get('_sylius', [])->shouldBeCalled()->willReturn([]);
$request->attributes = $attributesBag->getWrappedObject();
$router->generate('sylius_admin_login')->shouldBeCalled()->willReturn('/login');
diff --git a/src/Sylius/Bundle/AdminBundle/spec/EventListener/AdminFilterSubscriberSpec.php b/src/Sylius/Bundle/AdminBundle/spec/EventListener/AdminFilterSubscriberSpec.php
index 5d8266fae7..e151760f8a 100644
--- a/src/Sylius/Bundle/AdminBundle/spec/EventListener/AdminFilterSubscriberSpec.php
+++ b/src/Sylius/Bundle/AdminBundle/spec/EventListener/AdminFilterSubscriberSpec.php
@@ -43,7 +43,7 @@ final class AdminFilterSubscriberSpec extends ObjectBehavior
$event->isMainRequest()->willReturn(true);
$request->getRequestFormat()->willReturn('html');
- $attributes->get('_route')->willReturn('sylius_admin_product_index');
+ $attributes->get('_route', '')->willReturn('sylius_admin_product_index');
$attributes->get('_sylius', [])->willReturn(['section' => 'admin']);
$attributes->get('_controller')->willReturn('Sylius\Bundle\AdminBundle\Controller\ProductController::indexAction');
$request->attributes = $attributes;
@@ -82,7 +82,7 @@ final class AdminFilterSubscriberSpec extends ObjectBehavior
$event->isMainRequest()->willReturn(true);
$request->getRequestFormat()->willReturn('json');
- $attributes->get('_route')->willReturn('sylius_admin_product_index');
+ $attributes->get('_route', '')->willReturn('sylius_admin_product_index');
$attributes->get('_sylius', [])->willReturn(['section' => 'admin']);
$attributes->get('_controller')->willReturn('Sylius\Bundle\AdminBundle\Controller\ProductController::indexAction');
$request->attributes = $attributes;
@@ -109,7 +109,7 @@ final class AdminFilterSubscriberSpec extends ObjectBehavior
$event->isMainRequest()->willReturn(true);
$request->getRequestFormat()->willReturn('json');
- $attributes->get('_route')->willReturn('sylius_admin_product_index');
+ $attributes->get('_route', '')->willReturn('sylius_admin_product_index');
$attributes->get('_sylius', [])->willReturn(['section' => 'shop']);
$attributes->get('_controller')->willReturn('Sylius\Bundle\AdminBundle\Controller\ProductController::indexAction');
$request->attributes = $attributes;
@@ -136,7 +136,7 @@ final class AdminFilterSubscriberSpec extends ObjectBehavior
$event->isMainRequest()->willReturn(true);
$request->getRequestFormat()->willReturn('json');
- $attributes->get('_route')->willReturn('sylius_admin_product_index');
+ $attributes->get('_route', '')->willReturn('sylius_admin_product_index');
$attributes->get('_sylius', [])->willReturn(['section' => 'shop']);
$attributes->get('_controller')->willReturn(null);
$request->attributes = $attributes;
@@ -153,6 +153,33 @@ final class AdminFilterSubscriberSpec extends ObjectBehavior
$this->onKernelRequest($event);
}
+ function it_does_not_add_filter_to_filter_storage_if_route_is_missing(
+ RequestEvent $event,
+ Request $request,
+ ParameterBag $attributes,
+ ParameterBag $query,
+ FilterStorageInterface $filterStorage,
+ ): void {
+ $event->isMainRequest()->willReturn(true);
+ $request->getRequestFormat()->willReturn('json');
+
+ $attributes->get('_route', '')->willReturn('');
+ $attributes->get('_sylius', [])->willReturn(['section' => 'shop']);
+ $attributes->get('_controller')->willReturn('Sylius\Bundle\AdminBundle\Controller\ProductController::indexAction');
+ $request->attributes = $attributes;
+
+ $query->all()->willReturn(['filter' => 'foo']);
+ $request->query = $query;
+
+ $filterStorage->all()->willReturn([]);
+
+ $event->getRequest()->willReturn($request);
+
+ $filterStorage->set(Argument::any())->shouldNotBeCalled();
+
+ $this->onKernelRequest($event);
+ }
+
function it_does_not_add_filter_to_filter_storage_if_it_is_not_a_index_resource_route(
RequestEvent $event,
Request $request,
@@ -163,7 +190,7 @@ final class AdminFilterSubscriberSpec extends ObjectBehavior
$event->isMainRequest()->willReturn(true);
$request->getRequestFormat()->willReturn('json');
- $attributes->get('_route')->willReturn('sylius_admin_product_update');
+ $attributes->get('_route', '')->willReturn('sylius_admin_product_update');
$attributes->get('_sylius', [])->willReturn(['section' => 'shop']);
$attributes->get('_controller')->willReturn('Sylius\Bundle\AdminBundle\Controller\ProductController::indexAction');
$request->attributes = $attributes;
diff --git a/src/Sylius/Bundle/AdminBundle/test/config/bundles.php b/src/Sylius/Bundle/AdminBundle/test/config/bundles.php
index b5af30ce91..4f3ef19851 100644
--- a/src/Sylius/Bundle/AdminBundle/test/config/bundles.php
+++ b/src/Sylius/Bundle/AdminBundle/test/config/bundles.php
@@ -14,7 +14,6 @@ declare(strict_types=1);
return [
Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
- Symfony\Bundle\SwiftmailerBundle\SwiftmailerBundle::class => ['test_with_swiftmailer' => true],
Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true],
Symfony\WebpackEncoreBundle\WebpackEncoreBundle::class => ['all' => true],
Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
diff --git a/src/Sylius/Bundle/AdminBundle/test/config/packages/test_with_swiftmailer/filesystem.yaml b/src/Sylius/Bundle/AdminBundle/test/config/packages/test_with_swiftmailer/filesystem.yaml
deleted file mode 100644
index 1828fd69a2..0000000000
--- a/src/Sylius/Bundle/AdminBundle/test/config/packages/test_with_swiftmailer/filesystem.yaml
+++ /dev/null
@@ -1,4 +0,0 @@
-services:
- test.filesystem.public:
- alias: filesystem
- public: true
diff --git a/src/Sylius/Bundle/AdminBundle/test/config/packages/test_with_swiftmailer/swiftmailer.yaml b/src/Sylius/Bundle/AdminBundle/test/config/packages/test_with_swiftmailer/swiftmailer.yaml
deleted file mode 100644
index c438f4b256..0000000000
--- a/src/Sylius/Bundle/AdminBundle/test/config/packages/test_with_swiftmailer/swiftmailer.yaml
+++ /dev/null
@@ -1,6 +0,0 @@
-swiftmailer:
- disable_delivery: true
- logging: true
- spool:
- type: file
- path: "%kernel.cache_dir%/spool"
diff --git a/src/Sylius/Bundle/AdminBundle/test/config/packages/test_with_swiftmailer/sylius_mailer.yaml b/src/Sylius/Bundle/AdminBundle/test/config/packages/test_with_swiftmailer/sylius_mailer.yaml
deleted file mode 100644
index e2310f20bf..0000000000
--- a/src/Sylius/Bundle/AdminBundle/test/config/packages/test_with_swiftmailer/sylius_mailer.yaml
+++ /dev/null
@@ -1,2 +0,0 @@
-sylius_mailer:
- sender_adapter: sylius.email_sender.adapter.swiftmailer
diff --git a/src/Sylius/Bundle/ApiBundle/Controller/DeleteOrderItemAction.php b/src/Sylius/Bundle/ApiBundle/Controller/DeleteOrderItemAction.php
index 3e872f3e6c..f02d72d503 100644
--- a/src/Sylius/Bundle/ApiBundle/Controller/DeleteOrderItemAction.php
+++ b/src/Sylius/Bundle/ApiBundle/Controller/DeleteOrderItemAction.php
@@ -33,9 +33,11 @@ final readonly class DeleteOrderItemAction
{
$orderItemId = $request->attributes->get('itemId');
$tokenValue = $request->attributes->get('tokenValue');
+ if (null === $orderItemId || null === $tokenValue) {
+ throw new OrderItemNotFoundException();
+ }
$orderItem = $this->orderItemRepository->findOneByIdAndCartTokenValue($orderItemId, $tokenValue);
-
if ($orderItem === null) {
throw new OrderItemNotFoundException();
}
diff --git a/src/Sylius/Bundle/ApiBundle/Controller/Payment/GetPaymentConfiguration.php b/src/Sylius/Bundle/ApiBundle/Controller/Payment/GetPaymentConfiguration.php
index 0d74eea608..559ad546be 100644
--- a/src/Sylius/Bundle/ApiBundle/Controller/Payment/GetPaymentConfiguration.php
+++ b/src/Sylius/Bundle/ApiBundle/Controller/Payment/GetPaymentConfiguration.php
@@ -31,11 +31,14 @@ final readonly class GetPaymentConfiguration
public function __invoke(Request $request): JsonResponse
{
+ $paymentId = $request->attributes->get('paymentId');
+ $tokenValue = $request->attributes->get('tokenValue');
+ if (null === $paymentId || null === $tokenValue) {
+ throw new PaymentNotFoundException();
+ }
+
/** @var PaymentInterface|null $payment */
- $payment = $this->paymentRepository->findOneByOrderToken(
- $request->attributes->get('paymentId'),
- $request->attributes->get('tokenValue'),
- );
+ $payment = $this->paymentRepository->findOneByOrderToken($paymentId, $tokenValue);
if ($payment === null) {
throw new PaymentNotFoundException();
diff --git a/src/Sylius/Bundle/ApiBundle/Controller/RemoveCatalogPromotionAction.php b/src/Sylius/Bundle/ApiBundle/Controller/RemoveCatalogPromotionAction.php
index bbc97b1f65..93f8ee45f5 100644
--- a/src/Sylius/Bundle/ApiBundle/Controller/RemoveCatalogPromotionAction.php
+++ b/src/Sylius/Bundle/ApiBundle/Controller/RemoveCatalogPromotionAction.php
@@ -30,8 +30,13 @@ final readonly class RemoveCatalogPromotionAction
public function __invoke(Request $request): Response
{
+ $catalogPromotionCode = $request->attributes->get('code');
+ if (null === $catalogPromotionCode) {
+ return new JsonResponse(status: Response::HTTP_NOT_FOUND);
+ }
+
try {
- $this->catalogPromotionRemovalProcessor->removeCatalogPromotion($request->attributes->get('code'));
+ $this->catalogPromotionRemovalProcessor->removeCatalogPromotion($catalogPromotionCode);
return new Response(status: Response::HTTP_ACCEPTED);
} catch (CatalogPromotionNotFoundException) {
diff --git a/src/Sylius/Bundle/ApiBundle/DataPersister/ChannelDataPersister.php b/src/Sylius/Bundle/ApiBundle/DataPersister/ChannelDataPersister.php
new file mode 100644
index 0000000000..ee070bf845
--- /dev/null
+++ b/src/Sylius/Bundle/ApiBundle/DataPersister/ChannelDataPersister.php
@@ -0,0 +1,56 @@
+ $context
+ */
+ public function supports($data, array $context = []): bool
+ {
+ return $data instanceof ChannelInterface;
+ }
+
+ /**
+ * @param array $context
+ */
+ public function persist($data, array $context = [])
+ {
+ return $this->decoratedDataPersister->persist($data, $context);
+ }
+
+ /**
+ * @param array $context
+ */
+ public function remove($data, array $context = []): void
+ {
+ if (!$this->channelDeletionChecker->isDeletable($data)) {
+ throw new ChannelCannotBeRemoved('The channel cannot be deleted. At least one enabled channel is required.');
+ }
+
+ $this->decoratedDataPersister->remove($data, $context);
+ }
+}
diff --git a/src/Sylius/Bundle/ApiBundle/DataPersister/TranslatableDataPersister.php b/src/Sylius/Bundle/ApiBundle/DataPersister/TranslatableDataPersister.php
new file mode 100644
index 0000000000..6111461ac3
--- /dev/null
+++ b/src/Sylius/Bundle/ApiBundle/DataPersister/TranslatableDataPersister.php
@@ -0,0 +1,67 @@
+ $context
+ */
+ public function supports($data, array $context = []): bool
+ {
+ return $data instanceof TranslatableInterface;
+ }
+
+ /**
+ * @param TranslatableInterface $data
+ * @param array $context
+ */
+ public function persist($data, array $context = []): object
+ {
+ $defaultLocaleCode = $this->localeProvider->getDefaultLocaleCode();
+
+ if (!$data->getTranslations()->containsKey($defaultLocaleCode)) {
+ throw new TranslationInDefaultLocaleCannotBeRemoved(
+ sprintf('Translation in the default locale "%s" cannot be removed.', $defaultLocaleCode),
+ );
+ }
+
+ return $data;
+ }
+
+ /**
+ * @param TranslatableInterface $data
+ * @param array $context
+ */
+ public function remove($data, array $context = []): mixed
+ {
+ return $data;
+ }
+
+ /** @param array $context */
+ public function resumable(array $context = []): bool
+ {
+ return true;
+ }
+}
diff --git a/src/Sylius/Bundle/ApiBundle/Exception/ChannelCannotBeRemoved.php b/src/Sylius/Bundle/ApiBundle/Exception/ChannelCannotBeRemoved.php
new file mode 100644
index 0000000000..46827bdc1c
--- /dev/null
+++ b/src/Sylius/Bundle/ApiBundle/Exception/ChannelCannotBeRemoved.php
@@ -0,0 +1,26 @@
+
sylius
+
+
+ DELETE
+ /admin/channels/{code}
+
diff --git a/src/Sylius/Bundle/ApiBundle/Resources/config/legacy_api_resources/Product.xml b/src/Sylius/Bundle/ApiBundle/Resources/config/legacy_api_resources/Product.xml
index 84a63872ea..7a8812d169 100644
--- a/src/Sylius/Bundle/ApiBundle/Resources/config/legacy_api_resources/Product.xml
+++ b/src/Sylius/Bundle/ApiBundle/Resources/config/legacy_api_resources/Product.xml
@@ -146,7 +146,6 @@
string
string
- string
diff --git a/src/Sylius/Bundle/ApiBundle/Resources/config/legacy_api_resources/ProductAssociationType.xml b/src/Sylius/Bundle/ApiBundle/Resources/config/legacy_api_resources/ProductAssociationType.xml
index 36fded64b4..566e9db8e6 100644
--- a/src/Sylius/Bundle/ApiBundle/Resources/config/legacy_api_resources/ProductAssociationType.xml
+++ b/src/Sylius/Bundle/ApiBundle/Resources/config/legacy_api_resources/ProductAssociationType.xml
@@ -87,7 +87,6 @@
string
string
- string
diff --git a/src/Sylius/Bundle/ApiBundle/Resources/config/legacy_api_resources/ProductOption.xml b/src/Sylius/Bundle/ApiBundle/Resources/config/legacy_api_resources/ProductOption.xml
index 916115da52..a2e953c1ff 100644
--- a/src/Sylius/Bundle/ApiBundle/Resources/config/legacy_api_resources/ProductOption.xml
+++ b/src/Sylius/Bundle/ApiBundle/Resources/config/legacy_api_resources/ProductOption.xml
@@ -95,7 +95,6 @@
string
- string
diff --git a/src/Sylius/Bundle/ApiBundle/Resources/config/legacy_api_resources/ProductVariant.xml b/src/Sylius/Bundle/ApiBundle/Resources/config/legacy_api_resources/ProductVariant.xml
index 43e9ebc768..2617661fa6 100644
--- a/src/Sylius/Bundle/ApiBundle/Resources/config/legacy_api_resources/ProductVariant.xml
+++ b/src/Sylius/Bundle/ApiBundle/Resources/config/legacy_api_resources/ProductVariant.xml
@@ -99,7 +99,6 @@
string
- string
diff --git a/src/Sylius/Bundle/ApiBundle/Resources/config/legacy_api_resources/ShippingMethod.xml b/src/Sylius/Bundle/ApiBundle/Resources/config/legacy_api_resources/ShippingMethod.xml
index 4b48374b6d..d482e8dc62 100644
--- a/src/Sylius/Bundle/ApiBundle/Resources/config/legacy_api_resources/ShippingMethod.xml
+++ b/src/Sylius/Bundle/ApiBundle/Resources/config/legacy_api_resources/ShippingMethod.xml
@@ -140,7 +140,6 @@
string
string
- string
diff --git a/src/Sylius/Bundle/ApiBundle/Resources/config/legacy_api_resources/Taxon.xml b/src/Sylius/Bundle/ApiBundle/Resources/config/legacy_api_resources/Taxon.xml
index 0993184258..aa26ccb3e1 100644
--- a/src/Sylius/Bundle/ApiBundle/Resources/config/legacy_api_resources/Taxon.xml
+++ b/src/Sylius/Bundle/ApiBundle/Resources/config/legacy_api_resources/Taxon.xml
@@ -85,7 +85,6 @@
string
string
- string
diff --git a/src/Sylius/Bundle/ApiBundle/Resources/config/serialization/Channel.xml b/src/Sylius/Bundle/ApiBundle/Resources/config/serialization/Channel.xml
index 41e7be26a2..9e076c196e 100644
--- a/src/Sylius/Bundle/ApiBundle/Resources/config/serialization/Channel.xml
+++ b/src/Sylius/Bundle/ApiBundle/Resources/config/serialization/Channel.xml
@@ -24,23 +24,28 @@
admin:channel:read
admin:channel:create
+ admin:channel:update
shop:channel:read
admin:channel:read
admin:channel:create
+ admin:channel:update
admin:channel:read
admin:channel:create
+ admin:channel:update
admin:channel:read
admin:channel:create
+ admin:channel:update
admin:channel:read
admin:channel:create
+ admin:channel:update
admin:channel:read
@@ -50,53 +55,65 @@
admin:channel:read
admin:channel:create
+ admin:channel:update
shop:channel:read
admin:channel:read
admin:channel:create
+ admin:channel:update
admin:channel:read
admin:channel:create
+ admin:channel:update
admin:channel:read
admin:channel:create
+ admin:channel:update
shop:channel:read
admin:channel:read
admin:channel:create
+ admin:channel:update
shop:channel:read
admin:channel:read
admin:channel:create
+ admin:channel:update
admin:channel:read
admin:channel:create
+ admin:channel:update
admin:channel:read
admin:channel:create
+ admin:channel:update
admin:channel:read
admin:channel:create
+ admin:channel:update
admin:channel:read
admin:channel:create
+ admin:channel:update
admin:channel:read
admin:channel:create
+ admin:channel:update
admin:channel:read
admin:channel:create
+ admin:channel:update
admin:channel:read
@@ -106,14 +123,17 @@
admin:channel:read
admin:channel:create
+ admin:channel:update
admin:channel:read
admin:channel:create
+ admin:channel:update
admin:channel:read
admin:channel:create
+ admin:channel:update
diff --git a/src/Sylius/Bundle/ApiBundle/Resources/config/serialization/ShopBillingData.xml b/src/Sylius/Bundle/ApiBundle/Resources/config/serialization/ShopBillingData.xml
index 06ab5ff099..05c504e196 100644
--- a/src/Sylius/Bundle/ApiBundle/Resources/config/serialization/ShopBillingData.xml
+++ b/src/Sylius/Bundle/ApiBundle/Resources/config/serialization/ShopBillingData.xml
@@ -21,26 +21,32 @@
admin:channel:create
+ admin:channel:update
admin:shop_billing_data:read
admin:channel:create
+ admin:channel:update
admin:shop_billing_data:read
admin:channel:create
+ admin:channel:update
admin:shop_billing_data:read
admin:channel:create
+ admin:channel:update
admin:shop_billing_data:read
admin:channel:create
+ admin:channel:update
admin:shop_billing_data:read
admin:channel:create
+ admin:channel:update
admin:shop_billing_data:read
diff --git a/src/Sylius/Bundle/ApiBundle/Resources/config/services/data_persisters.xml b/src/Sylius/Bundle/ApiBundle/Resources/config/services/data_persisters.xml
index 24bfa7a41e..fa7d1b4531 100644
--- a/src/Sylius/Bundle/ApiBundle/Resources/config/services/data_persisters.xml
+++ b/src/Sylius/Bundle/ApiBundle/Resources/config/services/data_persisters.xml
@@ -31,6 +31,12 @@
+
+
+
+
+
+
@@ -74,6 +80,11 @@
+
+
+
+
+
diff --git a/src/Sylius/Bundle/ApiBundle/Resources/config/services/serializers.xml b/src/Sylius/Bundle/ApiBundle/Resources/config/services/serializers.xml
index 11144f55b8..18fb273da3 100644
--- a/src/Sylius/Bundle/ApiBundle/Resources/config/services/serializers.xml
+++ b/src/Sylius/Bundle/ApiBundle/Resources/config/services/serializers.xml
@@ -105,6 +105,11 @@
+
+
+
+
+
Y-m-d H:i:s
@@ -122,12 +127,17 @@
+
+
+
+
+
diff --git a/src/Sylius/Bundle/ApiBundle/Resources/config/validation/Channel.xml b/src/Sylius/Bundle/ApiBundle/Resources/config/validation/Channel.xml
new file mode 100644
index 0000000000..eae9231cdb
--- /dev/null
+++ b/src/Sylius/Bundle/ApiBundle/Resources/config/validation/Channel.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Sylius/Bundle/ApiBundle/Resources/config/validation/ShopBillingData.xml b/src/Sylius/Bundle/ApiBundle/Resources/config/validation/ShopBillingData.xml
new file mode 100644
index 0000000000..0a77587ba1
--- /dev/null
+++ b/src/Sylius/Bundle/ApiBundle/Resources/config/validation/ShopBillingData.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Sylius/Bundle/ApiBundle/Serializer/ChannelDenormalizer.php b/src/Sylius/Bundle/ApiBundle/Serializer/ChannelDenormalizer.php
index 503a789ee5..424f1e9704 100644
--- a/src/Sylius/Bundle/ApiBundle/Serializer/ChannelDenormalizer.php
+++ b/src/Sylius/Bundle/ApiBundle/Serializer/ChannelDenormalizer.php
@@ -15,6 +15,7 @@ namespace Sylius\Bundle\ApiBundle\Serializer;
use Sylius\Component\Core\Model\ChannelInterface;
use Sylius\Component\Core\Model\ChannelPriceHistoryConfigInterface;
+use Sylius\Component\Core\Model\ShopBillingDataInterface;
use Sylius\Component\Resource\Factory\FactoryInterface;
use Symfony\Component\Serializer\Normalizer\ContextAwareDenormalizerInterface;
use Symfony\Component\Serializer\Normalizer\DenormalizerAwareInterface;
@@ -28,8 +29,14 @@ final class ChannelDenormalizer implements ContextAwareDenormalizerInterface, De
private const ALREADY_CALLED = 'sylius_channel_denormalizer_already_called';
- public function __construct(private FactoryInterface $channelPriceHistoryConfigFactory)
- {
+ /**
+ * @param FactoryInterface $channelPriceHistoryConfigFactory
+ * @param FactoryInterface $shopBillingDataFactory
+ */
+ public function __construct(
+ private FactoryInterface $channelPriceHistoryConfigFactory,
+ private FactoryInterface $shopBillingDataFactory,
+ ) {
}
public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool
@@ -54,6 +61,12 @@ final class ChannelDenormalizer implements ContextAwareDenormalizerInterface, De
$channel->setChannelPriceHistoryConfig($channelPriceHistoryConfig);
}
+ if (null === $channel->getShopBillingData()) {
+ /** @var ShopBillingDataInterface $shopBillingData */
+ $shopBillingData = $this->shopBillingDataFactory->createNew();
+ $channel->setShopBillingData($shopBillingData);
+ }
+
return $channel;
}
}
diff --git a/src/Sylius/Bundle/ApiBundle/Serializer/TranslatableDenormalizer.php b/src/Sylius/Bundle/ApiBundle/Serializer/TranslatableDenormalizer.php
new file mode 100644
index 0000000000..5bde6a9038
--- /dev/null
+++ b/src/Sylius/Bundle/ApiBundle/Serializer/TranslatableDenormalizer.php
@@ -0,0 +1,71 @@
+localeProvider->getDefaultLocaleCode();
+
+ if (!$this->hasDefaultTranslation($data['translations'] ?? [], $defaultLocaleCode)) {
+ $data['translations'][$defaultLocaleCode] = [
+ 'locale' => $defaultLocaleCode,
+ ];
+ }
+
+ return $this->denormalizer->denormalize($data, $type, $format, $context);
+ }
+
+ public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool
+ {
+ return
+ Request::METHOD_POST === ($context[ContextKeys::HTTP_REQUEST_METHOD_TYPE] ?? null) &&
+ !isset($context[self::getAlreadyCalledKey($type)]) &&
+ is_a($type, TranslatableInterface::class, true)
+ ;
+ }
+
+ private static function getAlreadyCalledKey(string $class): string
+ {
+ return sprintf(self::ALREADY_CALLED, $class);
+ }
+
+ private function hasDefaultTranslation(array $translations, string $defaultLocale): bool
+ {
+ return
+ isset($translations[$defaultLocale]['locale']) &&
+ $defaultLocale === $translations[$defaultLocale]['locale']
+ ;
+ }
+}
diff --git a/src/Sylius/Bundle/ApiBundle/Serializer/TranslatableLocaleKeyDenormalizer.php b/src/Sylius/Bundle/ApiBundle/Serializer/TranslatableLocaleKeyDenormalizer.php
new file mode 100644
index 0000000000..b9fbae3dac
--- /dev/null
+++ b/src/Sylius/Bundle/ApiBundle/Serializer/TranslatableLocaleKeyDenormalizer.php
@@ -0,0 +1,52 @@
+ }> $data
+ */
+ public function denormalize(mixed $data, string $type, string $format = null, array $context = [])
+ {
+ $context[self::ALREADY_CALLED] = true;
+
+ if (array_key_exists('translations', $data)) {
+ foreach ($data['translations'] as $key => &$translation) {
+ $translation['locale'] = $key;
+ }
+ }
+
+ return $this->denormalizer->denormalize($data, $type, $format, $context);
+ }
+}
diff --git a/src/Sylius/Bundle/ApiBundle/Tests/Application/.env.test_with_swiftmailer b/src/Sylius/Bundle/ApiBundle/Tests/Application/.env.test_with_swiftmailer
deleted file mode 100644
index 26a5ec11b1..0000000000
--- a/src/Sylius/Bundle/ApiBundle/Tests/Application/.env.test_with_swiftmailer
+++ /dev/null
@@ -1 +0,0 @@
-APP_ENV=test_with_swiftmailer
diff --git a/src/Sylius/Bundle/ApiBundle/Tests/Application/config/bundles.php b/src/Sylius/Bundle/ApiBundle/Tests/Application/config/bundles.php
index e13a5b1da0..6aa2bdbb12 100644
--- a/src/Sylius/Bundle/ApiBundle/Tests/Application/config/bundles.php
+++ b/src/Sylius/Bundle/ApiBundle/Tests/Application/config/bundles.php
@@ -13,7 +13,6 @@ declare(strict_types=1);
return [
Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
- Symfony\Bundle\SwiftmailerBundle\SwiftmailerBundle::class => ['test_with_swiftmailer' => true],
Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true],
Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
@@ -54,9 +53,9 @@ return [
Sylius\Bundle\PayumBundle\SyliusPayumBundle::class => ['all' => true],
ApiPlatform\Symfony\Bundle\ApiPlatformBundle::class => ['all' => true],
Sylius\Bundle\ApiBundle\SyliusApiBundle::class => ['all' => true],
- Symfony\Bundle\DebugBundle\DebugBundle::class => ['dev' => true, 'test' => true, 'test_cached' => true, 'test_with_swiftmailer' => true],
- Nelmio\Alice\Bridge\Symfony\NelmioAliceBundle::class => ['dev' => true, 'test' => true, 'test_cached' => true, 'test_with_swiftmailer' => true],
- Fidry\AliceDataFixtures\Bridge\Symfony\FidryAliceDataFixturesBundle::class => ['dev' => true, 'test' => true, 'test_cached' => true, 'test_with_swiftmailer' => true],
+ Symfony\Bundle\DebugBundle\DebugBundle::class => ['dev' => true, 'test' => true, 'test_cached' => true],
+ Nelmio\Alice\Bridge\Symfony\NelmioAliceBundle::class => ['dev' => true, 'test' => true, 'test_cached' => true],
+ Fidry\AliceDataFixtures\Bridge\Symfony\FidryAliceDataFixturesBundle::class => ['dev' => true, 'test' => true, 'test_cached' => true],
Lexik\Bundle\JWTAuthenticationBundle\LexikJWTAuthenticationBundle::class => ['all' => true],
SyliusLabs\DoctrineMigrationsExtraBundle\SyliusLabsDoctrineMigrationsExtraBundle::class => ['all' => true],
SyliusLabs\Polyfill\Symfony\Security\Bundle\SyliusLabsPolyfillSymfonySecurityBundle::class => ['all' => true],
diff --git a/src/Sylius/Bundle/ApiBundle/Tests/Application/config/packages/test_with_swiftmailer/filesystem.yaml b/src/Sylius/Bundle/ApiBundle/Tests/Application/config/packages/test_with_swiftmailer/filesystem.yaml
deleted file mode 100644
index 1828fd69a2..0000000000
--- a/src/Sylius/Bundle/ApiBundle/Tests/Application/config/packages/test_with_swiftmailer/filesystem.yaml
+++ /dev/null
@@ -1,4 +0,0 @@
-services:
- test.filesystem.public:
- alias: filesystem
- public: true
diff --git a/src/Sylius/Bundle/ApiBundle/Tests/Application/config/packages/test_with_swiftmailer/swiftmailer.yaml b/src/Sylius/Bundle/ApiBundle/Tests/Application/config/packages/test_with_swiftmailer/swiftmailer.yaml
deleted file mode 100644
index c438f4b256..0000000000
--- a/src/Sylius/Bundle/ApiBundle/Tests/Application/config/packages/test_with_swiftmailer/swiftmailer.yaml
+++ /dev/null
@@ -1,6 +0,0 @@
-swiftmailer:
- disable_delivery: true
- logging: true
- spool:
- type: file
- path: "%kernel.cache_dir%/spool"
diff --git a/src/Sylius/Bundle/ApiBundle/Tests/Application/config/packages/test_with_swiftmailer/sylius_mailer.yaml b/src/Sylius/Bundle/ApiBundle/Tests/Application/config/packages/test_with_swiftmailer/sylius_mailer.yaml
deleted file mode 100644
index e2310f20bf..0000000000
--- a/src/Sylius/Bundle/ApiBundle/Tests/Application/config/packages/test_with_swiftmailer/sylius_mailer.yaml
+++ /dev/null
@@ -1,2 +0,0 @@
-sylius_mailer:
- sender_adapter: sylius.email_sender.adapter.swiftmailer
diff --git a/src/Sylius/Bundle/ApiBundle/Tests/CommandHandler/SendAccountRegistrationEmailHandlerTest.php b/src/Sylius/Bundle/ApiBundle/Tests/CommandHandler/SendAccountRegistrationEmailHandlerTest.php
index cf1e1472f3..6117d0268f 100644
--- a/src/Sylius/Bundle/ApiBundle/Tests/CommandHandler/SendAccountRegistrationEmailHandlerTest.php
+++ b/src/Sylius/Bundle/ApiBundle/Tests/CommandHandler/SendAccountRegistrationEmailHandlerTest.php
@@ -19,25 +19,18 @@ use Sylius\Bundle\ApiBundle\Command\Account\SendAccountRegistrationEmail;
use Sylius\Bundle\ApiBundle\CommandHandler\Account\SendAccountRegistrationEmailHandler;
use Sylius\Component\Channel\Repository\ChannelRepositoryInterface;
use Sylius\Component\Core\Model\ChannelInterface;
-use Sylius\Component\Core\Test\SwiftmailerAssertionTrait;
use Sylius\Component\User\Model\UserInterface;
use Sylius\Component\User\Repository\UserRepositoryInterface;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
-use Symfony\Component\Filesystem\Filesystem;
use Symfony\Contracts\Translation\TranslatorInterface;
final class SendAccountRegistrationEmailHandlerTest extends KernelTestCase
{
use ProphecyTrait;
- use SwiftmailerAssertionTrait;
/** @test */
public function it_sends_account_registration_email(): void
{
- if ($this->isItSwiftmailerTestEnv()) {
- $this->markTestSkipped('Test is relevant only for the environment without swiftmailer');
- }
-
$container = self::getContainer();
/** @var TranslatorInterface $translator */
@@ -82,71 +75,4 @@ final class SendAccountRegistrationEmailHandlerTest extends KernelTestCase
self::assertEmailAddressContains($email, 'To', 'user@example.com');
self::assertEmailHtmlBodyContains($email, $translator->trans('sylius.email.user_registration.start_shopping', [], null, 'en_US'));
}
-
- /** @test */
- public function it_sends_account_registration_email_with_swiftmailer(): void
- {
- if (!$this->isItSwiftmailerTestEnv()) {
- $this->markTestSkipped('Test is relevant only for the environment with swiftmailer');
- }
-
- $container = self::getContainer();
-
- self::setSpoolDirectory($container->getParameter('kernel.cache_dir') . '/spool');
-
- /** @var Filesystem $filesystem */
- $filesystem = $container->get('test.filesystem.public');
-
- /** @var TranslatorInterface $translator */
- $translator = $container->get('translator');
-
- $filesystem->remove(self::getSpoolDirectory());
-
- $emailSender = $container->get('sylius.email_sender');
-
- /** @var ChannelRepositoryInterface|ObjectProphecy $channelRepository */
- $channelRepository = $this->prophesize(ChannelRepositoryInterface::class);
- /** @var UserRepositoryInterface|ObjectProphecy $userRepository */
- $userRepository = $this->prophesize(UserRepositoryInterface::class);
- /** @var ChannelInterface|ObjectProphecy $channel */
- $channel = $this->prophesize(ChannelInterface::class);
- /** @var UserInterface|ObjectProphecy $user */
- $user = $this->prophesize(UserInterface::class);
-
- $channel->isAccountVerificationRequired()->willReturn(false);
- $channel->getHostname()->willReturn('example.com');
-
- $user->getUsername()->willReturn('username');
- $user->getEmailVerificationToken()->willReturn('token');
-
- $channelRepository->findOneByCode('CHANNEL_CODE')->willReturn($channel->reveal());
- $userRepository->findOneByEmail('user@example.com')->willReturn($user->reveal());
-
- $sendAccountRegistrationEmailHandler = new SendAccountRegistrationEmailHandler(
- $userRepository->reveal(),
- $channelRepository->reveal(),
- $emailSender,
- );
-
- $sendAccountRegistrationEmailHandler(
- new SendAccountRegistrationEmail(
- 'user@example.com',
- 'en_US',
- 'CHANNEL_CODE',
- ),
- );
-
- self::assertSpooledMessagesCountWithRecipient(1, 'user@example.com');
- self::assertSpooledMessageWithContentHasRecipient(
- $translator->trans('sylius.email.user_registration.start_shopping', [], null, 'en_US'),
- 'user@example.com',
- );
- }
-
- private function isItSwiftmailerTestEnv(): bool
- {
- $env = self::getContainer()->getParameter('kernel.environment');
-
- return $env === 'test_with_swiftmailer';
- }
}
diff --git a/src/Sylius/Bundle/ApiBundle/Tests/CommandHandler/SendAccountVerificationEmailHandlerTest.php b/src/Sylius/Bundle/ApiBundle/Tests/CommandHandler/SendAccountVerificationEmailHandlerTest.php
index c191f32f1c..1b599a7a0e 100644
--- a/src/Sylius/Bundle/ApiBundle/Tests/CommandHandler/SendAccountVerificationEmailHandlerTest.php
+++ b/src/Sylius/Bundle/ApiBundle/Tests/CommandHandler/SendAccountVerificationEmailHandlerTest.php
@@ -33,10 +33,6 @@ final class SendAccountVerificationEmailHandlerTest extends KernelTestCase
/** @test */
public function it_sends_account_verification_token_email(): void
{
- if ($this->isItSwiftmailerTestEnv()) {
- $this->markTestSkipped('Test is relevant only for the environment without swiftmailer');
- }
-
$container = self::getContainer();
/** @var TranslatorInterface $translator */
@@ -81,11 +77,4 @@ final class SendAccountVerificationEmailHandlerTest extends KernelTestCase
$translator->trans('sylius.email.verification_token.message', [], null, 'en_US'),
);
}
-
- private function isItSwiftmailerTestEnv(): bool
- {
- $env = self::getContainer()->getParameter('kernel.environment');
-
- return $env === 'test_with_swiftmailer';
- }
}
diff --git a/src/Sylius/Bundle/ApiBundle/Tests/CommandHandler/SendContactRequestHandlerTest.php b/src/Sylius/Bundle/ApiBundle/Tests/CommandHandler/SendContactRequestHandlerTest.php
index f63e347593..8f71931a63 100644
--- a/src/Sylius/Bundle/ApiBundle/Tests/CommandHandler/SendContactRequestHandlerTest.php
+++ b/src/Sylius/Bundle/ApiBundle/Tests/CommandHandler/SendContactRequestHandlerTest.php
@@ -31,10 +31,6 @@ final class SendContactRequestHandlerTest extends KernelTestCase
/** @test */
public function it_sends_contact_request(): void
{
- if ($this->isItSwiftmailerTestEnv()) {
- $this->markTestSkipped('Test is relevant only for the environment without swiftmailer');
- }
-
$container = self::getContainer();
/** @var TranslatorInterface $translator */
@@ -68,11 +64,4 @@ final class SendContactRequestHandlerTest extends KernelTestCase
self::assertEmailAddressContains($email, 'To', 'shop@example.com');
self::assertEmailHtmlBodyContains($email, $translator->trans('sylius.email.contact_request.content', [], null, 'en_US'));
}
-
- private function isItSwiftmailerTestEnv(): bool
- {
- $env = self::getContainer()->getParameter('kernel.environment');
-
- return $env === 'test_with_swiftmailer';
- }
}
diff --git a/src/Sylius/Bundle/ApiBundle/Tests/CommandHandler/SendOrderConfirmationEmailHandlerTest.php b/src/Sylius/Bundle/ApiBundle/Tests/CommandHandler/SendOrderConfirmationEmailHandlerTest.php
index 224454c019..cd391103e7 100644
--- a/src/Sylius/Bundle/ApiBundle/Tests/CommandHandler/SendOrderConfirmationEmailHandlerTest.php
+++ b/src/Sylius/Bundle/ApiBundle/Tests/CommandHandler/SendOrderConfirmationEmailHandlerTest.php
@@ -32,10 +32,6 @@ final class SendOrderConfirmationEmailHandlerTest extends KernelTestCase
/** @test */
public function it_sends_order_confirmation_email(): void
{
- if ($this->isItSwiftmailerTestEnv()) {
- $this->markTestSkipped('Test is relevant only for the environment without swiftmailer');
- }
-
$container = self::bootKernel()->getContainer();
$emailSender = $container->get('sylius.email_sender');
@@ -70,11 +66,4 @@ final class SendOrderConfirmationEmailHandlerTest extends KernelTestCase
$this->assertEmailAddressContains($email, 'To', 'johnny.bravo@email.com');
$this->assertEmailHtmlBodyContains($email, '#000001');
}
-
- private function isItSwiftmailerTestEnv(): bool
- {
- $env = $this->getContainer()->getParameter('kernel.environment');
-
- return $env === 'test_with_swiftmailer';
- }
}
diff --git a/src/Sylius/Bundle/ApiBundle/Tests/CommandHandler/SendResetPasswordEmailHandlerTest.php b/src/Sylius/Bundle/ApiBundle/Tests/CommandHandler/SendResetPasswordEmailHandlerTest.php
index 5cc8c19925..94245e9d20 100644
--- a/src/Sylius/Bundle/ApiBundle/Tests/CommandHandler/SendResetPasswordEmailHandlerTest.php
+++ b/src/Sylius/Bundle/ApiBundle/Tests/CommandHandler/SendResetPasswordEmailHandlerTest.php
@@ -33,10 +33,6 @@ final class SendResetPasswordEmailHandlerTest extends KernelTestCase
/** @test */
public function it_sends_password_reset_token_email(): void
{
- if ($this->isItSwiftmailerTestEnv()) {
- $this->markTestSkipped('Test is relevant only for the environment without swiftmailer');
- }
-
$container = self::getContainer();
/** @var TranslatorInterface $translator */
@@ -79,11 +75,4 @@ final class SendResetPasswordEmailHandlerTest extends KernelTestCase
$translator->trans('sylius.email.password_reset.to_reset_your_password_token', [], null, 'en_US'),
);
}
-
- private function isItSwiftmailerTestEnv(): bool
- {
- $env = self::getContainer()->getParameter('kernel.environment');
-
- return $env === 'test_with_swiftmailer';
- }
}
diff --git a/src/Sylius/Bundle/ApiBundle/Tests/MessageHandler/Admin/SendResetPasswordEmailHandlerTest.php b/src/Sylius/Bundle/ApiBundle/Tests/MessageHandler/Admin/SendResetPasswordEmailHandlerTest.php
index be39846a29..bf2279f62a 100644
--- a/src/Sylius/Bundle/ApiBundle/Tests/MessageHandler/Admin/SendResetPasswordEmailHandlerTest.php
+++ b/src/Sylius/Bundle/ApiBundle/Tests/MessageHandler/Admin/SendResetPasswordEmailHandlerTest.php
@@ -29,10 +29,6 @@ final class SendResetPasswordEmailHandlerTest extends KernelTestCase
/** @test */
public function it_sends_password_reset_token_email(): void
{
- if ($this->isItSwiftmailerTestEnv()) {
- $this->markTestSkipped('Test is relevant only for the environment without swiftmailer');
- }
-
$container = self::getContainer();
/** @var TranslatorInterface $translator */
@@ -66,11 +62,4 @@ final class SendResetPasswordEmailHandlerTest extends KernelTestCase
$translator->trans('sylius.email.admin_password_reset.to_reset_your_password_token', [], null, 'en_US'),
);
}
-
- private function isItSwiftmailerTestEnv(): bool
- {
- $env = self::getContainer()->getParameter('kernel.environment');
-
- return $env === 'test_with_swiftmailer';
- }
}
diff --git a/src/Sylius/Bundle/ApiBundle/disabledSpec/DataPersister/ChannelDataPersisterSpec.php b/src/Sylius/Bundle/ApiBundle/disabledSpec/DataPersister/ChannelDataPersisterSpec.php
new file mode 100644
index 0000000000..e0750d5f78
--- /dev/null
+++ b/src/Sylius/Bundle/ApiBundle/disabledSpec/DataPersister/ChannelDataPersisterSpec.php
@@ -0,0 +1,68 @@
+beConstructedWith($decoratedDataPersister, $channelDeletionChecker);
+ }
+
+ function it_supports_only_channel_entity(ChannelInterface $channel, \stdClass $object): void
+ {
+ $this->supports($channel)->shouldReturn(true);
+ $this->supports($object)->shouldReturn(false);
+ }
+
+ function it_persists_channel(
+ ContextAwareDataPersisterInterface $decoratedDataPersister,
+ ChannelInterface $channel,
+ ): void {
+ $decoratedDataPersister->persist($channel, [])->willReturn($channel);
+
+ $this->persist($channel, [])->shouldReturn($channel);
+ }
+
+ function it_throws_an_exception_if_channel_is_not_deletable(
+ ChannelDeletionCheckerInterface $channelDeletionChecker,
+ ChannelInterface $channel,
+ ): void {
+ $channelDeletionChecker->isDeletable($channel)->willReturn(false);
+
+ $this
+ ->shouldThrow(ChannelCannotBeRemoved::class)
+ ->during('remove', [$channel])
+ ;
+ }
+
+ function it_removes_channel(
+ ContextAwareDataPersisterInterface $decoratedDataPersister,
+ ChannelDeletionCheckerInterface $channelDeletionChecker,
+ ChannelInterface $channel,
+ ): void {
+ $channelDeletionChecker->isDeletable($channel)->willReturn(true);
+ $decoratedDataPersister->remove($channel, [])->willReturn(null);
+
+ $this->remove($channel, [])->shouldReturn(null);
+ }
+}
diff --git a/src/Sylius/Bundle/ApiBundle/disabledSpec/DataPersister/TranslatableDataPersisterSpec.php b/src/Sylius/Bundle/ApiBundle/disabledSpec/DataPersister/TranslatableDataPersisterSpec.php
new file mode 100644
index 0000000000..2c903d3b4d
--- /dev/null
+++ b/src/Sylius/Bundle/ApiBundle/disabledSpec/DataPersister/TranslatableDataPersisterSpec.php
@@ -0,0 +1,78 @@
+beConstructedWith($localeProvider);
+ }
+
+ function it_is_a_context_aware_data_persister(): void
+ {
+ $this->shouldImplement(ContextAwareDataPersisterInterface::class);
+ }
+
+ function it_is_a_resumable_data_persister(): void
+ {
+ $this->shouldImplement(ResumableDataPersisterInterface::class);
+ }
+
+ function it_supports_only_translatable(TranslatableInterface $translatable): void
+ {
+ $this->supports(new \stdClass())->shouldReturn(false);
+ $this->supports($translatable)->shouldReturn(true);
+ }
+
+ function it_does_nothing_if_there_is_a_translation_in_default_locale(
+ TranslationLocaleProviderInterface $localeProvider,
+ TranslatableInterface $translatable,
+ TranslatableInterface $translation,
+ ): void {
+ $localeProvider->getDefaultLocaleCode()->willReturn('en_US');
+ $translatable->getTranslations()->willReturn(new ArrayCollection(['en_US' => $translation]));
+
+ $this->persist($translatable)->shouldReturn($translatable);
+ }
+
+ function it_throws_an_exception_if_there_is_no_translation_in_default_locale(
+ TranslationLocaleProviderInterface $localeProvider,
+ TranslatableInterface $translatable,
+ TranslatableInterface $translation,
+ ): void {
+ $localeProvider->getDefaultLocaleCode()->willReturn('en_US');
+ $translatable->getTranslations()->willReturn(new ArrayCollection(['de_DE' => $translation]));
+
+ $this->shouldThrow(TranslationInDefaultLocaleCannotBeRemoved::class)->during('persist', [$translatable]);
+ }
+
+ function it_does_nothing_during_removing_object(TranslatableInterface $translatable): void
+ {
+ $this->remove($translatable)->shouldReturn($translatable);
+ }
+
+ function it_is_resumable(): void
+ {
+ $this->resumable()->shouldReturn(true);
+ }
+}
diff --git a/src/Sylius/Bundle/ApiBundle/disabledSpec/Serializer/ChannelDenormalizerSpec.php b/src/Sylius/Bundle/ApiBundle/disabledSpec/Serializer/ChannelDenormalizerSpec.php
index 79f1e9b7af..64770e448e 100644
--- a/src/Sylius/Bundle/ApiBundle/disabledSpec/Serializer/ChannelDenormalizerSpec.php
+++ b/src/Sylius/Bundle/ApiBundle/disabledSpec/Serializer/ChannelDenormalizerSpec.php
@@ -17,6 +17,7 @@ use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Sylius\Component\Core\Model\ChannelInterface;
use Sylius\Component\Core\Model\ChannelPriceHistoryConfigInterface;
+use Sylius\Component\Core\Model\ShopBillingDataInterface;
use Sylius\Component\Resource\Factory\FactoryInterface;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
@@ -24,9 +25,9 @@ final class ChannelDenormalizerSpec extends ObjectBehavior
{
private const ALREADY_CALLED = 'sylius_channel_denormalizer_already_called';
- function let(FactoryInterface $configFactory): void
+ function let(FactoryInterface $configFactory, FactoryInterface $shopBillingDataFactory): void
{
- $this->beConstructedWith($configFactory);
+ $this->beConstructedWith($configFactory, $shopBillingDataFactory);
}
function it_does_not_support_denormalization_when_the_denormalizer_has_already_been_called(): void
@@ -54,15 +55,17 @@ final class ChannelDenormalizerSpec extends ObjectBehavior
$this->shouldThrow(\InvalidArgumentException::class)->during('denormalize', [[], 'string']);
}
- function it_returns_channel_as_is_when_channel_price_history_config_has_already_been_set(
+ function it_returns_channel_as_is_when_shop_billing_data_and_channel_price_history_config_has_already_been_set(
DenormalizerInterface $denormalizer,
FactoryInterface $configFactory,
+ ShopBillingDataInterface $shopBillingData,
ChannelPriceHistoryConfigInterface $config,
ChannelInterface $channel,
): void {
$this->setDenormalizer($denormalizer);
$channel->getChannelPriceHistoryConfig()->willReturn($config);
+ $channel->getShopBillingData()->willReturn($shopBillingData);
$channel->setChannelPriceHistoryConfig(Argument::any())->shouldNotBeCalled();
$configFactory->createNew()->shouldNotBeCalled();
@@ -75,12 +78,14 @@ final class ChannelDenormalizerSpec extends ObjectBehavior
function it_adds_a_new_channel_price_history_config_when_channel_has_none(
DenormalizerInterface $denormalizer,
FactoryInterface $configFactory,
+ ShopBillingDataInterface $shopBillingData,
ChannelPriceHistoryConfigInterface $config,
ChannelInterface $channel,
): void {
$this->setDenormalizer($denormalizer);
$channel->getChannelPriceHistoryConfig()->willReturn(null);
+ $channel->getShopBillingData()->willReturn($shopBillingData);
$configFactory->createNew()->willReturn($config);
$channel->setChannelPriceHistoryConfig($config)->shouldBeCalled();
@@ -89,4 +94,24 @@ final class ChannelDenormalizerSpec extends ObjectBehavior
$this->denormalize([], ChannelInterface::class)->shouldReturn($channel);
}
+
+ function it_adds_a_new_shop_billing_data_when_channel_has_none(
+ DenormalizerInterface $denormalizer,
+ FactoryInterface $shopBillingDataFactory,
+ ShopBillingDataInterface $shopBillingData,
+ ChannelPriceHistoryConfigInterface $config,
+ ChannelInterface $channel,
+ ): void {
+ $this->setDenormalizer($denormalizer);
+
+ $channel->getChannelPriceHistoryConfig()->willReturn($config);
+ $channel->getShopBillingData()->willReturn(null);
+
+ $shopBillingDataFactory->createNew()->willReturn($shopBillingData);
+ $channel->setShopBillingData($shopBillingData)->shouldBeCalled();
+
+ $denormalizer->denormalize([], ChannelInterface::class, null, [self::ALREADY_CALLED => true])->willReturn($channel);
+
+ $this->denormalize([], ChannelInterface::class)->shouldReturn($channel);
+ }
}
diff --git a/src/Sylius/Bundle/ApiBundle/disabledSpec/Serializer/ProductAttributeValueDenormalizerSpec.php b/src/Sylius/Bundle/ApiBundle/disabledSpec/Serializer/ProductAttributeValueDenormalizerSpec.php
index 8407e4a0be..21bd22e96e 100644
--- a/src/Sylius/Bundle/ApiBundle/disabledSpec/Serializer/ProductAttributeValueDenormalizerSpec.php
+++ b/src/Sylius/Bundle/ApiBundle/disabledSpec/Serializer/ProductAttributeValueDenormalizerSpec.php
@@ -88,7 +88,7 @@ final class ProductAttributeValueDenormalizerSpec extends ObjectBehavior
['attribute' => '/attributes/material', 'value' => 'ceramic'],
ProductAttributeValueInterface::class,
null,
- [self::ALREADY_CALLED => true]
+ [self::ALREADY_CALLED => true],
)
->shouldBeCalled()
;
diff --git a/src/Sylius/Bundle/ApiBundle/spec/Serializer/TranslatableDenormalizerSpec.php b/src/Sylius/Bundle/ApiBundle/spec/Serializer/TranslatableDenormalizerSpec.php
new file mode 100644
index 0000000000..6da72ab875
--- /dev/null
+++ b/src/Sylius/Bundle/ApiBundle/spec/Serializer/TranslatableDenormalizerSpec.php
@@ -0,0 +1,186 @@
+beConstructedWith($localeProvider);
+
+ $this->setDenormalizer($denormalizer);
+ }
+
+ function it_only_supports_translatable_resource(): void
+ {
+ $this->supportsDenormalization([], TranslatableInterface::class, null, [
+ ContextKeys::HTTP_REQUEST_METHOD_TYPE => 'PUT',
+ ])->shouldReturn(false);
+
+ $this->supportsDenormalization([], TranslatableInterface::class, null, [
+ 'sylius_translatable_denormalizer_already_called_for_Sylius\Component\Resource\Model\TranslatableInterface' => true,
+ ContextKeys::HTTP_REQUEST_METHOD_TYPE => 'POST',
+ ])->shouldReturn(false);
+
+ $this->supportsDenormalization([], \stdClass::class, null, [
+ ContextKeys::HTTP_REQUEST_METHOD_TYPE => 'POST',
+ ])->shouldReturn(false);
+ }
+
+ function it_does_nothing_when_data_contains_a_translation_in_default_locale(
+ DenormalizerInterface $denormalizer,
+ TranslationLocaleProviderInterface $localeProvider,
+ ): void {
+ $data = ['translations' => ['en' => ['locale' => 'en']]];
+
+ $localeProvider->getDefaultLocaleCode()->willReturn('en');
+
+ $denormalizer->denormalize($data, TranslatableInterface::class, null, [
+ 'sylius_translatable_denormalizer_already_called_for_Sylius\Component\Resource\Model\TranslatableInterface' => true,
+ ContextKeys::HTTP_REQUEST_METHOD_TYPE => 'POST',
+ ])->shouldBeCalled()->willReturn($data);
+
+ $this
+ ->denormalize($data, TranslatableInterface::class, null, [ContextKeys::HTTP_REQUEST_METHOD_TYPE => 'POST'])
+ ->shouldReturn($data)
+ ;
+ }
+
+ function it_adds_default_translation_when_no_translations_passed_in_data(
+ DenormalizerInterface $denormalizer,
+ TranslationLocaleProviderInterface $localeProvider,
+ ): void {
+ $localeProvider->getDefaultLocaleCode()->willReturn('en');
+
+ $updatedData = ['translations' => ['en' => ['locale' => 'en']]];
+
+ $denormalizer->denormalize($updatedData, TranslatableInterface::class, null, [
+ 'sylius_translatable_denormalizer_already_called_for_Sylius\Component\Resource\Model\TranslatableInterface' => true,
+ ContextKeys::HTTP_REQUEST_METHOD_TYPE => 'POST',
+ ])->shouldBeCalled()->willReturn($updatedData);
+
+ $this
+ ->denormalize([], TranslatableInterface::class, null, [ContextKeys::HTTP_REQUEST_METHOD_TYPE => 'POST'])
+ ->shouldReturn($updatedData)
+ ;
+ }
+
+ function it_adds_default_translation_when_no_translation_passed_for_default_locale_in_data(
+ DenormalizerInterface $denormalizer,
+ TranslationLocaleProviderInterface $localeProvider,
+ ): void {
+ $localeProvider->getDefaultLocaleCode()->willReturn('en');
+
+ $originalData = ['translations' => ['en' => []]];
+ $updatedData = ['translations' => ['en' => ['locale' => 'en']]];
+
+ $denormalizer->denormalize($updatedData, TranslatableInterface::class, null, [
+ 'sylius_translatable_denormalizer_already_called_for_Sylius\Component\Resource\Model\TranslatableInterface' => true,
+ ContextKeys::HTTP_REQUEST_METHOD_TYPE => 'POST',
+ ])->shouldBeCalled()->willReturn($updatedData);
+
+ $this
+ ->denormalize($originalData, TranslatableInterface::class, null, [ContextKeys::HTTP_REQUEST_METHOD_TYPE => 'POST'])
+ ->shouldReturn($updatedData)
+ ;
+ }
+
+ function it_adds_default_translation_when_passed_default_translation_has_empty_locale(
+ DenormalizerInterface $denormalizer,
+ TranslationLocaleProviderInterface $localeProvider,
+ ): void {
+ $localeProvider->getDefaultLocaleCode()->willReturn('en');
+
+ $originalData = ['translations' => ['en' => ['locale' => '']]];
+ $updatedData = ['translations' => ['en' => ['locale' => 'en']]];
+
+ $denormalizer->denormalize($updatedData, TranslatableInterface::class, null, [
+ 'sylius_translatable_denormalizer_already_called_for_Sylius\Component\Resource\Model\TranslatableInterface' => true,
+ ContextKeys::HTTP_REQUEST_METHOD_TYPE => 'POST',
+ ])->shouldBeCalled()->willReturn($updatedData);
+
+ $this
+ ->denormalize($originalData, TranslatableInterface::class, null, [ContextKeys::HTTP_REQUEST_METHOD_TYPE => 'POST'])
+ ->shouldReturn($updatedData)
+ ;
+ }
+
+ function it_adds_default_translation_when_passed_default_translation_has_null_locale(
+ DenormalizerInterface $denormalizer,
+ TranslationLocaleProviderInterface $localeProvider,
+ ): void {
+ $localeProvider->getDefaultLocaleCode()->willReturn('en');
+
+ $originalData = ['translations' => ['en' => ['locale' => null]]];
+ $updatedData = ['translations' => ['en' => ['locale' => 'en']]];
+
+ $denormalizer->denormalize($updatedData, TranslatableInterface::class, null, [
+ 'sylius_translatable_denormalizer_already_called_for_Sylius\Component\Resource\Model\TranslatableInterface' => true,
+ ContextKeys::HTTP_REQUEST_METHOD_TYPE => 'POST',
+ ])->shouldBeCalled()->willReturn($updatedData);
+
+ $this
+ ->denormalize($originalData, TranslatableInterface::class, null, [ContextKeys::HTTP_REQUEST_METHOD_TYPE => 'POST'])
+ ->shouldReturn($updatedData)
+ ;
+ }
+
+ function it_adds_default_translation_when_passed_default_translation_has_mismatched_locale(
+ DenormalizerInterface $denormalizer,
+ TranslationLocaleProviderInterface $localeProvider,
+ ): void {
+ $localeProvider->getDefaultLocaleCode()->willReturn('en');
+
+ $originalData = ['translations' => ['en' => ['locale' => 'fr']]];
+ $updatedData = ['translations' => ['en' => ['locale' => 'en']]];
+
+ $denormalizer->denormalize($updatedData, TranslatableInterface::class, null, [
+ 'sylius_translatable_denormalizer_already_called_for_Sylius\Component\Resource\Model\TranslatableInterface' => true,
+ ContextKeys::HTTP_REQUEST_METHOD_TYPE => 'POST',
+ ])->shouldBeCalled()->willReturn($updatedData);
+
+ $this
+ ->denormalize($originalData, TranslatableInterface::class, null, [ContextKeys::HTTP_REQUEST_METHOD_TYPE => 'POST'])
+ ->shouldReturn($updatedData)
+ ;
+ }
+
+ function it_adds_default_translation_when_no_translation_in_default_locale_passed_in_data(
+ DenormalizerInterface $denormalizer,
+ TranslationLocaleProviderInterface $localeProvider,
+ ): void {
+ $localeProvider->getDefaultLocaleCode()->willReturn('en');
+
+ $originalData = ['translations' => ['pl' => ['locale' => 'pl']]];
+ $updatedData = ['translations' => ['en' => ['locale' => 'en'], 'pl' => ['locale' => 'pl']]];
+
+ $denormalizer->denormalize($updatedData, TranslatableInterface::class, null, [
+ 'sylius_translatable_denormalizer_already_called_for_Sylius\Component\Resource\Model\TranslatableInterface' => true,
+ ContextKeys::HTTP_REQUEST_METHOD_TYPE => 'POST',
+ ])->shouldBeCalled()->willReturn($updatedData);
+
+ $this
+ ->denormalize($originalData, TranslatableInterface::class, null, [ContextKeys::HTTP_REQUEST_METHOD_TYPE => 'POST'])
+ ->shouldReturn($updatedData)
+ ;
+ }
+}
diff --git a/src/Sylius/Bundle/ApiBundle/spec/Serializer/TranslatableLocaleKeyDenormalizerSpec.php b/src/Sylius/Bundle/ApiBundle/spec/Serializer/TranslatableLocaleKeyDenormalizerSpec.php
new file mode 100644
index 0000000000..f41f132e7d
--- /dev/null
+++ b/src/Sylius/Bundle/ApiBundle/spec/Serializer/TranslatableLocaleKeyDenormalizerSpec.php
@@ -0,0 +1,79 @@
+supportsDenormalization([], TranslatableInterface::class, context: [self::ALREADY_CALLED => true])
+ ->shouldReturn(false)
+ ;
+ }
+
+ function it_does_not_support_denormalization_when_data_is_not_an_array(): void
+ {
+ $this->supportsDenormalization('string', TranslatableInterface::class)->shouldReturn(false);
+ }
+
+ function it_does_not_support_denormalization_when_type_is_not_a_translatable(): void
+ {
+ $this->supportsDenormalization([], 'string')->shouldReturn(false);
+ }
+
+ function it_does_nothing_if_there_is_no_translation_key(
+ DenormalizerInterface $denormalizer,
+ ): void {
+ $this->setDenormalizer($denormalizer);
+
+ $this->denormalize([], TranslatableInterface::class);
+
+ $denormalizer->denormalize([], TranslatableInterface::class, null, [self::ALREADY_CALLED => true])->shouldHaveBeenCalledOnce();
+ }
+
+ function it_changes_keys_of_translations_to_locale(
+ DenormalizerInterface $denormalizer,
+ ): void {
+ $this->setDenormalizer($denormalizer);
+
+ $this->denormalize(
+ [
+ 'translations' => [
+ 'en_US' => ['locale' => 'en_US'],
+ 'de_DE' => ['locale' => 'locale'],
+ ],
+ ],
+ TranslatableInterface::class,
+ );
+
+ $denormalizer->denormalize(
+ [
+ 'translations' => [
+ 'en_US' => ['locale' => 'en_US'],
+ 'de_DE' => ['locale' => 'de_DE'],
+ ],
+ ],
+ TranslatableInterface::class,
+ null,
+ [self::ALREADY_CALLED => true],
+ )->shouldHaveBeenCalledOnce();
+ }
+}
diff --git a/src/Sylius/Bundle/ApiBundle/spec/Validator/Constraints/ShopUserResetPasswordTokenExistsValidatorSpec.php b/src/Sylius/Bundle/ApiBundle/spec/Validator/Constraints/ShopUserResetPasswordTokenExistsValidatorSpec.php
index 6f42fbe533..fc15e08e74 100644
--- a/src/Sylius/Bundle/ApiBundle/spec/Validator/Constraints/ShopUserResetPasswordTokenExistsValidatorSpec.php
+++ b/src/Sylius/Bundle/ApiBundle/spec/Validator/Constraints/ShopUserResetPasswordTokenExistsValidatorSpec.php
@@ -41,7 +41,7 @@ final class ShopUserResetPasswordTokenExistsValidatorSpec extends ObjectBehavior
;
}
- function it_throws_an_exception_if_constraint_is_not_a_resetPasswordTokenExists_constraint(): void
+ function it_throws_an_exception_if_constraint_is_not_a_reset_password_token_exists_constraint(): void
{
$this
->shouldThrow(\InvalidArgumentException::class)
@@ -50,7 +50,7 @@ final class ShopUserResetPasswordTokenExistsValidatorSpec extends ObjectBehavior
;
}
- function it_does_not_add_violation_if_user_exist(
+ function it_does_not_add_violation_if_user_exists(
UserRepositoryInterface $userRepository,
ExecutionContextInterface $executionContext,
UserInterface $user,
diff --git a/src/Sylius/Bundle/ApiBundle/spec/Validator/Constraints/ShopUserResetPasswordTokenNotExpiredValidatorSpec.php b/src/Sylius/Bundle/ApiBundle/spec/Validator/Constraints/ShopUserResetPasswordTokenNotExpiredValidatorSpec.php
index 67c109fe2b..1b104f7123 100644
--- a/src/Sylius/Bundle/ApiBundle/spec/Validator/Constraints/ShopUserResetPasswordTokenNotExpiredValidatorSpec.php
+++ b/src/Sylius/Bundle/ApiBundle/spec/Validator/Constraints/ShopUserResetPasswordTokenNotExpiredValidatorSpec.php
@@ -42,7 +42,7 @@ final class ShopUserResetPasswordTokenNotExpiredValidatorSpec extends ObjectBeha
;
}
- function it_throws_an_exception_if_constraint_is_not_a_resetPasswordTokenExpires_constraint(): void
+ function it_throws_an_exception_if_constraint_is_not_a_reset_password_token_not_expired_constraint(): void
{
$this
->shouldThrow(\InvalidArgumentException::class)
diff --git a/src/Sylius/Bundle/AttributeBundle/DependencyInjection/Configuration.php b/src/Sylius/Bundle/AttributeBundle/DependencyInjection/Configuration.php
index b3726e4458..9ca1b94928 100644
--- a/src/Sylius/Bundle/AttributeBundle/DependencyInjection/Configuration.php
+++ b/src/Sylius/Bundle/AttributeBundle/DependencyInjection/Configuration.php
@@ -13,6 +13,9 @@ declare(strict_types=1);
namespace Sylius\Bundle\AttributeBundle\DependencyInjection;
+use Sylius\Bundle\AttributeBundle\Doctrine\ORM\AttributeRepository;
+use Sylius\Bundle\AttributeBundle\Doctrine\ORM\AttributeTranslationRepository;
+use Sylius\Bundle\AttributeBundle\Doctrine\ORM\AttributeValueRepository;
use Sylius\Bundle\ResourceBundle\Controller\ResourceController;
use Sylius\Bundle\ResourceBundle\SyliusResourceBundle;
use Sylius\Component\Attribute\Model\Attribute;
@@ -64,7 +67,7 @@ final class Configuration implements ConfigurationInterface
->scalarNode('model')->defaultValue(Attribute::class)->cannotBeEmpty()->end()
->scalarNode('interface')->defaultValue(AttributeInterface::class)->cannotBeEmpty()->end()
->scalarNode('controller')->cannotBeEmpty()->end()
- ->scalarNode('repository')->cannotBeEmpty()->end()
+ ->scalarNode('repository')->defaultValue(AttributeRepository::class)->cannotBeEmpty()->end()
->scalarNode('factory')->defaultValue(TranslatableFactory::class)->end()
->scalarNode('form')->cannotBeEmpty()->end()
->end()
@@ -79,7 +82,7 @@ final class Configuration implements ConfigurationInterface
->scalarNode('model')->defaultValue(AttributeTranslation::class)->cannotBeEmpty()->end()
->scalarNode('interface')->defaultValue(AttributeTranslationInterface::class)->cannotBeEmpty()->end()
->scalarNode('controller')->defaultValue(ResourceController::class)->cannotBeEmpty()->end()
- ->scalarNode('repository')->cannotBeEmpty()->end()
+ ->scalarNode('repository')->defaultValue(AttributeTranslationRepository::class)->cannotBeEmpty()->end()
->scalarNode('factory')->defaultValue(Factory::class)->end()
->scalarNode('form')->cannotBeEmpty()->end()
->end()
@@ -99,7 +102,7 @@ final class Configuration implements ConfigurationInterface
->scalarNode('model')->isRequired()->cannotBeEmpty()->end()
->scalarNode('interface')->isRequired()->cannotBeEmpty()->end()
->scalarNode('controller')->defaultValue(ResourceController::class)->cannotBeEmpty()->end()
- ->scalarNode('repository')->cannotBeEmpty()->end()
+ ->scalarNode('repository')->defaultValue(AttributeValueRepository::class)->cannotBeEmpty()->end()
->scalarNode('factory')->defaultValue(Factory::class)->end()
->scalarNode('form')->cannotBeEmpty()->end()
->end()
diff --git a/src/Sylius/Bundle/AttributeBundle/Doctrine/ORM/AttributeRepository.php b/src/Sylius/Bundle/AttributeBundle/Doctrine/ORM/AttributeRepository.php
new file mode 100644
index 0000000000..777acea5f9
--- /dev/null
+++ b/src/Sylius/Bundle/AttributeBundle/Doctrine/ORM/AttributeRepository.php
@@ -0,0 +1,25 @@
+
+ */
+class AttributeRepository extends EntityRepository implements AttributeRepositoryInterface
+{
+}
diff --git a/src/Sylius/Bundle/AttributeBundle/Doctrine/ORM/AttributeTranslationRepository.php b/src/Sylius/Bundle/AttributeBundle/Doctrine/ORM/AttributeTranslationRepository.php
new file mode 100644
index 0000000000..2e5a784daa
--- /dev/null
+++ b/src/Sylius/Bundle/AttributeBundle/Doctrine/ORM/AttributeTranslationRepository.php
@@ -0,0 +1,25 @@
+
+ */
+class AttributeTranslationRepository extends EntityRepository implements AttributeTranslationRepositoryInterface
+{
+}
diff --git a/src/Sylius/Bundle/AttributeBundle/Doctrine/ORM/AttributeValueRepository.php b/src/Sylius/Bundle/AttributeBundle/Doctrine/ORM/AttributeValueRepository.php
new file mode 100644
index 0000000000..9107746446
--- /dev/null
+++ b/src/Sylius/Bundle/AttributeBundle/Doctrine/ORM/AttributeValueRepository.php
@@ -0,0 +1,25 @@
+
+ */
+class AttributeValueRepository extends EntityRepository implements AttributeValueRepositoryInterface
+{
+}
diff --git a/src/Sylius/Bundle/AttributeBundle/Resources/config/validation/AttributeTranslation.xml b/src/Sylius/Bundle/AttributeBundle/Resources/config/validation/AttributeTranslation.xml
index 6587628f32..0bdb915ea8 100644
--- a/src/Sylius/Bundle/AttributeBundle/Resources/config/validation/AttributeTranslation.xml
+++ b/src/Sylius/Bundle/AttributeBundle/Resources/config/validation/AttributeTranslation.xml
@@ -26,5 +26,15 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Sylius/Bundle/AttributeBundle/Resources/translations/validators.en.yml b/src/Sylius/Bundle/AttributeBundle/Resources/translations/validators.en.yml
index 2ffe830ae3..045b0fdd62 100644
--- a/src/Sylius/Bundle/AttributeBundle/Resources/translations/validators.en.yml
+++ b/src/Sylius/Bundle/AttributeBundle/Resources/translations/validators.en.yml
@@ -24,3 +24,7 @@ sylius:
not_blank: Please select attribute.
value:
not_blank: Please enter attribute value.
+ translation:
+ locale:
+ not_blank: Please enter the locale.
+ invalid: This value is not a valid locale.
diff --git a/src/Sylius/Bundle/ChannelBundle/Doctrine/ORM/ChannelRepository.php b/src/Sylius/Bundle/ChannelBundle/Doctrine/ORM/ChannelRepository.php
index ecb5f7493f..6b27728119 100644
--- a/src/Sylius/Bundle/ChannelBundle/Doctrine/ORM/ChannelRepository.php
+++ b/src/Sylius/Bundle/ChannelBundle/Doctrine/ORM/ChannelRepository.php
@@ -56,6 +56,17 @@ class ChannelRepository extends EntityRepository implements ChannelRepositoryInt
;
}
+ /**
+ * @return ChannelInterface[]
+ */
+ public function findEnabled(): iterable
+ {
+ /** @var ChannelInterface[] $enabledChannels */
+ $enabledChannels = $this->findBy(['enabled' => true]);
+
+ return $enabledChannels;
+ }
+
public function countAll(): int
{
return $this->count([]);
diff --git a/src/Sylius/Bundle/ChannelBundle/Resources/config/services.xml b/src/Sylius/Bundle/ChannelBundle/Resources/config/services.xml
index 5c114a2502..cc0fdf70d8 100644
--- a/src/Sylius/Bundle/ChannelBundle/Resources/config/services.xml
+++ b/src/Sylius/Bundle/ChannelBundle/Resources/config/services.xml
@@ -69,5 +69,10 @@
false
+
+
+
+
+
diff --git a/src/Sylius/Bundle/CoreBundle/Checkout/CheckoutRedirectListener.php b/src/Sylius/Bundle/CoreBundle/Checkout/CheckoutRedirectListener.php
index b944adb4e3..afbf13f2e0 100644
--- a/src/Sylius/Bundle/CoreBundle/Checkout/CheckoutRedirectListener.php
+++ b/src/Sylius/Bundle/CoreBundle/Checkout/CheckoutRedirectListener.php
@@ -32,13 +32,19 @@ final class CheckoutRedirectListener
public function handleCheckoutRedirect(ResourceControllerEvent $resourceControllerEvent): void
{
$request = $this->requestStack->getCurrentRequest();
- if (!$this->requestMatcher->matches($request) || isset($request->attributes->get('_sylius')['redirect'])) {
+ if (
+ null === $request ||
+ !$this->requestMatcher->matches($request) ||
+ isset($request->attributes->get('_sylius', [])['redirect'])
+ ) {
return;
}
$order = $resourceControllerEvent->getSubject();
Assert::isInstanceOf($order, OrderInterface::class);
- $resourceControllerEvent->setResponse(new RedirectResponse($this->checkoutStateUrlGenerator->generateForOrderCheckoutState($order)));
+ $resourceControllerEvent->setResponse(
+ new RedirectResponse($this->checkoutStateUrlGenerator->generateForOrderCheckoutState($order)),
+ );
}
}
diff --git a/src/Sylius/Bundle/CoreBundle/Doctrine/ORM/AddressRepository.php b/src/Sylius/Bundle/CoreBundle/Doctrine/ORM/AddressRepository.php
index bcca8d8b37..590ec222cb 100644
--- a/src/Sylius/Bundle/CoreBundle/Doctrine/ORM/AddressRepository.php
+++ b/src/Sylius/Bundle/CoreBundle/Doctrine/ORM/AddressRepository.php
@@ -13,17 +13,12 @@ declare(strict_types=1);
namespace Sylius\Bundle\CoreBundle\Doctrine\ORM;
-use Sylius\Bundle\ResourceBundle\Doctrine\ORM\EntityRepository;
+use Sylius\Bundle\AddressingBundle\Doctrine\ORM\AddressRepository as BaseAddressRepository;
use Sylius\Component\Core\Model\AddressInterface;
use Sylius\Component\Core\Model\CustomerInterface;
use Sylius\Component\Core\Repository\AddressRepositoryInterface;
-/**
- * @template T of AddressInterface
- *
- * @implements AddressRepositoryInterface
- */
-class AddressRepository extends EntityRepository implements AddressRepositoryInterface
+class AddressRepository extends BaseAddressRepository implements AddressRepositoryInterface
{
public function findByCustomer(CustomerInterface $customer): array
{
diff --git a/src/Sylius/Bundle/CoreBundle/Doctrine/ORM/AttributeRepository.php b/src/Sylius/Bundle/CoreBundle/Doctrine/ORM/AttributeRepository.php
index cc3c53917a..83b4ecde9d 100644
--- a/src/Sylius/Bundle/CoreBundle/Doctrine/ORM/AttributeRepository.php
+++ b/src/Sylius/Bundle/CoreBundle/Doctrine/ORM/AttributeRepository.php
@@ -15,14 +15,14 @@ namespace Sylius\Bundle\CoreBundle\Doctrine\ORM;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\ClassMetadata;
-use Sylius\Bundle\ResourceBundle\Doctrine\ORM\EntityRepository;
+use Sylius\Bundle\AttributeBundle\Doctrine\ORM\AttributeRepository as BaseAttributeRepository;
use Sylius\Component\Attribute\Model\AttributeInterface;
use SyliusLabs\AssociationHydrator\AssociationHydrator;
/**
* @template T of AttributeInterface
*/
-class AttributeRepository extends EntityRepository
+class AttributeRepository extends BaseAttributeRepository
{
protected AssociationHydrator $associationHydrator;
diff --git a/src/Sylius/Bundle/CoreBundle/EventListener/ChannelDeletionListener.php b/src/Sylius/Bundle/CoreBundle/EventListener/ChannelDeletionListener.php
index 742375b63c..9966020af5 100644
--- a/src/Sylius/Bundle/CoreBundle/EventListener/ChannelDeletionListener.php
+++ b/src/Sylius/Bundle/CoreBundle/EventListener/ChannelDeletionListener.php
@@ -14,13 +14,13 @@ declare(strict_types=1);
namespace Sylius\Bundle\CoreBundle\EventListener;
use Sylius\Bundle\ResourceBundle\Event\ResourceControllerEvent;
+use Sylius\Component\Channel\Checker\ChannelDeletionCheckerInterface;
use Sylius\Component\Channel\Model\ChannelInterface;
-use Sylius\Component\Channel\Repository\ChannelRepositoryInterface;
use Sylius\Component\Resource\Exception\UnexpectedTypeException;
final class ChannelDeletionListener
{
- public function __construct(private ChannelRepositoryInterface $channelRepository)
+ public function __construct(private ChannelDeletionCheckerInterface $channelDeletionChecker)
{
}
@@ -38,9 +38,7 @@ final class ChannelDeletionListener
);
}
- $results = $this->channelRepository->findBy(['enabled' => true]);
-
- if (!$results || (count($results) === 1 && current($results) === $channel)) {
+ if (!$this->channelDeletionChecker->isDeletable($channel)) {
$event->stop('sylius.channel.delete_error');
}
}
diff --git a/src/Sylius/Bundle/CoreBundle/EventListener/Workflow/Order/AssignOrderNumberListener.php b/src/Sylius/Bundle/CoreBundle/EventListener/Workflow/Order/AssignOrderNumberListener.php
new file mode 100644
index 0000000000..b462751b25
--- /dev/null
+++ b/src/Sylius/Bundle/CoreBundle/EventListener/Workflow/Order/AssignOrderNumberListener.php
@@ -0,0 +1,35 @@
+getSubject();
+ Assert::isInstanceOf($order, OrderInterface::class);
+
+ $this->orderNumberAssigner->assignNumber($order);
+ }
+}
diff --git a/src/Sylius/Bundle/CoreBundle/EventListener/Workflow/Order/AssignOrderTokenListener.php b/src/Sylius/Bundle/CoreBundle/EventListener/Workflow/Order/AssignOrderTokenListener.php
new file mode 100644
index 0000000000..33216edd71
--- /dev/null
+++ b/src/Sylius/Bundle/CoreBundle/EventListener/Workflow/Order/AssignOrderTokenListener.php
@@ -0,0 +1,35 @@
+getSubject();
+ Assert::isInstanceOf($order, OrderInterface::class);
+
+ $this->orderTokenAssigner->assignTokenValueIfNotSet($order);
+ }
+}
diff --git a/src/Sylius/Bundle/CoreBundle/EventListener/Workflow/Order/CancelOrderPaymentListener.php b/src/Sylius/Bundle/CoreBundle/EventListener/Workflow/Order/CancelOrderPaymentListener.php
new file mode 100644
index 0000000000..2fbb8e153c
--- /dev/null
+++ b/src/Sylius/Bundle/CoreBundle/EventListener/Workflow/Order/CancelOrderPaymentListener.php
@@ -0,0 +1,36 @@
+getSubject();
+ Assert::isInstanceOf($order, OrderInterface::class);
+
+ $this->compositeOrderStateMachine->apply($order, OrderPaymentTransitions::GRAPH, OrderPaymentTransitions::TRANSITION_CANCEL);
+ }
+}
diff --git a/src/Sylius/Bundle/CoreBundle/EventListener/Workflow/Order/CancelOrderShippingListener.php b/src/Sylius/Bundle/CoreBundle/EventListener/Workflow/Order/CancelOrderShippingListener.php
new file mode 100644
index 0000000000..309bc30406
--- /dev/null
+++ b/src/Sylius/Bundle/CoreBundle/EventListener/Workflow/Order/CancelOrderShippingListener.php
@@ -0,0 +1,36 @@
+getSubject();
+ Assert::isInstanceOf($order, OrderInterface::class);
+
+ $this->compositeOrderStateMachine->apply($order, OrderShippingTransitions::GRAPH, OrderShippingTransitions::TRANSITION_CANCEL);
+ }
+}
diff --git a/src/Sylius/Bundle/CoreBundle/EventListener/Workflow/Order/CancelPaymentListener.php b/src/Sylius/Bundle/CoreBundle/EventListener/Workflow/Order/CancelPaymentListener.php
new file mode 100644
index 0000000000..316b7c7037
--- /dev/null
+++ b/src/Sylius/Bundle/CoreBundle/EventListener/Workflow/Order/CancelPaymentListener.php
@@ -0,0 +1,40 @@
+getSubject();
+ Assert::isInstanceOf($order, OrderInterface::class);
+
+ $payments = $order->getPayments();
+
+ foreach ($payments as $payment) {
+ $this->compositeOrderStateMachine->apply($payment, PaymentTransitions::GRAPH, PaymentTransitions::TRANSITION_CANCEL);
+ }
+ }
+}
diff --git a/src/Sylius/Bundle/CoreBundle/EventListener/Workflow/Order/CancelShipmentListener.php b/src/Sylius/Bundle/CoreBundle/EventListener/Workflow/Order/CancelShipmentListener.php
new file mode 100644
index 0000000000..6950c8a56e
--- /dev/null
+++ b/src/Sylius/Bundle/CoreBundle/EventListener/Workflow/Order/CancelShipmentListener.php
@@ -0,0 +1,40 @@
+getSubject();
+ Assert::isInstanceOf($order, OrderInterface::class);
+
+ $shipments = $order->getShipments();
+
+ foreach ($shipments as $shipment) {
+ $this->compositeOrderStateMachine->apply($shipment, ShipmentTransitions::GRAPH, ShipmentTransitions::TRANSITION_CANCEL);
+ }
+ }
+}
diff --git a/src/Sylius/Bundle/CoreBundle/EventListener/Workflow/Order/CreatePaymentListener.php b/src/Sylius/Bundle/CoreBundle/EventListener/Workflow/Order/CreatePaymentListener.php
new file mode 100644
index 0000000000..636ba1b411
--- /dev/null
+++ b/src/Sylius/Bundle/CoreBundle/EventListener/Workflow/Order/CreatePaymentListener.php
@@ -0,0 +1,40 @@
+getSubject();
+ Assert::isInstanceOf($order, OrderInterface::class);
+
+ $payments = $order->getPayments();
+
+ foreach ($payments as $payment) {
+ $this->compositeStateMachine->apply($payment, PaymentTransitions::GRAPH, PaymentTransitions::TRANSITION_CREATE);
+ }
+ }
+}
diff --git a/src/Sylius/Bundle/CoreBundle/EventListener/Workflow/Order/CreateShipmentListener.php b/src/Sylius/Bundle/CoreBundle/EventListener/Workflow/Order/CreateShipmentListener.php
new file mode 100644
index 0000000000..1ffb293459
--- /dev/null
+++ b/src/Sylius/Bundle/CoreBundle/EventListener/Workflow/Order/CreateShipmentListener.php
@@ -0,0 +1,40 @@
+getSubject();
+ Assert::isInstanceOf($order, OrderInterface::class);
+
+ $shipments = $order->getShipments();
+
+ foreach ($shipments as $shipment) {
+ $this->compositeStateMachine->apply($shipment, ShipmentTransitions::GRAPH, ShipmentTransitions::TRANSITION_CREATE);
+ }
+ }
+}
diff --git a/src/Sylius/Bundle/CoreBundle/EventListener/Workflow/Order/DecrementPromotionUsagesListener.php b/src/Sylius/Bundle/CoreBundle/EventListener/Workflow/Order/DecrementPromotionUsagesListener.php
new file mode 100644
index 0000000000..2c945743b4
--- /dev/null
+++ b/src/Sylius/Bundle/CoreBundle/EventListener/Workflow/Order/DecrementPromotionUsagesListener.php
@@ -0,0 +1,35 @@
+getSubject();
+ Assert::isInstanceOf($order, OrderInterface::class);
+
+ $this->orderPromotionsUsageModifier->decrement($order);
+ }
+}
diff --git a/src/Sylius/Bundle/CoreBundle/EventListener/Workflow/Order/GiveBackInventoryListener.php b/src/Sylius/Bundle/CoreBundle/EventListener/Workflow/Order/GiveBackInventoryListener.php
new file mode 100644
index 0000000000..96899a21a0
--- /dev/null
+++ b/src/Sylius/Bundle/CoreBundle/EventListener/Workflow/Order/GiveBackInventoryListener.php
@@ -0,0 +1,35 @@
+getSubject();
+ Assert::isInstanceOf($order, OrderInterface::class);
+
+ $this->orderInventoryOperator->cancel($order);
+ }
+}
diff --git a/src/Sylius/Bundle/CoreBundle/EventListener/Workflow/Order/HoldInventoryListener.php b/src/Sylius/Bundle/CoreBundle/EventListener/Workflow/Order/HoldInventoryListener.php
new file mode 100644
index 0000000000..025b28c725
--- /dev/null
+++ b/src/Sylius/Bundle/CoreBundle/EventListener/Workflow/Order/HoldInventoryListener.php
@@ -0,0 +1,35 @@
+getSubject();
+ Assert::isInstanceOf($order, OrderInterface::class);
+
+ $this->orderInventoryOperator->hold($order);
+ }
+}
diff --git a/src/Sylius/Bundle/CoreBundle/EventListener/Workflow/Order/IncrementPromotionUsagesListener.php b/src/Sylius/Bundle/CoreBundle/EventListener/Workflow/Order/IncrementPromotionUsagesListener.php
new file mode 100644
index 0000000000..0cf3c6af33
--- /dev/null
+++ b/src/Sylius/Bundle/CoreBundle/EventListener/Workflow/Order/IncrementPromotionUsagesListener.php
@@ -0,0 +1,35 @@
+getSubject();
+ Assert::isInstanceOf($order, OrderInterface::class);
+
+ $this->orderPromotionsUsageModifier->increment($order);
+ }
+}
diff --git a/src/Sylius/Bundle/CoreBundle/EventListener/Workflow/Order/RequestOrderPaymentListener.php b/src/Sylius/Bundle/CoreBundle/EventListener/Workflow/Order/RequestOrderPaymentListener.php
new file mode 100644
index 0000000000..db44b11978
--- /dev/null
+++ b/src/Sylius/Bundle/CoreBundle/EventListener/Workflow/Order/RequestOrderPaymentListener.php
@@ -0,0 +1,36 @@
+getSubject();
+ Assert::isInstanceOf($order, OrderInterface::class);
+
+ $this->compositeStateMachine->apply($order, OrderPaymentTransitions::GRAPH, OrderPaymentTransitions::TRANSITION_REQUEST_PAYMENT);
+ }
+}
diff --git a/src/Sylius/Bundle/CoreBundle/EventListener/Workflow/Order/RequestOrderShippingListener.php b/src/Sylius/Bundle/CoreBundle/EventListener/Workflow/Order/RequestOrderShippingListener.php
new file mode 100644
index 0000000000..6f2939dc81
--- /dev/null
+++ b/src/Sylius/Bundle/CoreBundle/EventListener/Workflow/Order/RequestOrderShippingListener.php
@@ -0,0 +1,36 @@
+getSubject();
+ Assert::isInstanceOf($order, OrderInterface::class);
+
+ $this->compositeStateMachine->apply($order, OrderShippingTransitions::GRAPH, OrderShippingTransitions::TRANSITION_REQUEST_SHIPPING);
+ }
+}
diff --git a/src/Sylius/Bundle/CoreBundle/EventListener/Workflow/Order/SaveCustomerAddressesListener.php b/src/Sylius/Bundle/CoreBundle/EventListener/Workflow/Order/SaveCustomerAddressesListener.php
new file mode 100644
index 0000000000..6843f98612
--- /dev/null
+++ b/src/Sylius/Bundle/CoreBundle/EventListener/Workflow/Order/SaveCustomerAddressesListener.php
@@ -0,0 +1,35 @@
+getSubject();
+ Assert::isInstanceOf($order, OrderInterface::class);
+
+ $this->orderAddressesSaver->saveAddresses($order);
+ }
+}
diff --git a/src/Sylius/Bundle/CoreBundle/EventListener/Workflow/Order/SetImmutableNamesListener.php b/src/Sylius/Bundle/CoreBundle/EventListener/Workflow/Order/SetImmutableNamesListener.php
new file mode 100644
index 0000000000..5d94c2122e
--- /dev/null
+++ b/src/Sylius/Bundle/CoreBundle/EventListener/Workflow/Order/SetImmutableNamesListener.php
@@ -0,0 +1,35 @@
+getSubject();
+ Assert::isInstanceOf($order, OrderInterface::class);
+
+ $this->orderItemNamesSetter->__invoke($order);
+ }
+}
diff --git a/src/Sylius/Bundle/CoreBundle/Migrations/Version20231103004216.php b/src/Sylius/Bundle/CoreBundle/Migrations/Version20231103004216.php
new file mode 100644
index 0000000000..4be36ec16a
--- /dev/null
+++ b/src/Sylius/Bundle/CoreBundle/Migrations/Version20231103004216.php
@@ -0,0 +1,41 @@
+addSql('CREATE UNIQUE INDEX UNIQ_88D5CC4D6B7BA4B6 ON sylius_admin_user (password_reset_token)');
+ $this->addSql('CREATE UNIQUE INDEX UNIQ_88D5CC4DC4995C67 ON sylius_admin_user (email_verification_token)');
+ $this->addSql('CREATE UNIQUE INDEX UNIQ_7C2B74806B7BA4B6 ON sylius_shop_user (password_reset_token)');
+ $this->addSql('CREATE UNIQUE INDEX UNIQ_7C2B7480C4995C67 ON sylius_shop_user (email_verification_token)');
+ }
+
+ public function down(Schema $schema): void
+ {
+ $this->addSql('DROP INDEX UNIQ_88D5CC4D6B7BA4B6 ON sylius_admin_user');
+ $this->addSql('DROP INDEX UNIQ_88D5CC4DC4995C67 ON sylius_admin_user');
+ $this->addSql('DROP INDEX UNIQ_7C2B74806B7BA4B6 ON sylius_shop_user');
+ $this->addSql('DROP INDEX UNIQ_7C2B7480C4995C67 ON sylius_shop_user');
+ }
+}
diff --git a/src/Sylius/Bundle/CoreBundle/Resources/config/app/workflow/sylius_order.yaml b/src/Sylius/Bundle/CoreBundle/Resources/config/app/workflow/sylius_order.yaml
new file mode 100644
index 0000000000..04e43688ac
--- /dev/null
+++ b/src/Sylius/Bundle/CoreBundle/Resources/config/app/workflow/sylius_order.yaml
@@ -0,0 +1,25 @@
+framework:
+ workflows:
+ !php/const Sylius\Component\Order\OrderTransitions::GRAPH:
+ type: state_machine
+ marking_store:
+ type: method
+ property: state
+ supports:
+ - Sylius\Component\Core\Model\OrderInterface
+ initial_marking: !php/const Sylius\Component\Order\Model\OrderInterface::STATE_CART
+ places:
+ - !php/const Sylius\Component\Order\Model\OrderInterface::STATE_CART
+ - !php/const Sylius\Component\Order\Model\OrderInterface::STATE_NEW
+ - !php/const Sylius\Component\Order\Model\OrderInterface::STATE_CANCELLED
+ - !php/const Sylius\Component\Order\Model\OrderInterface::STATE_FULFILLED
+ transitions:
+ !php/const Sylius\Component\Order\OrderTransitions::TRANSITION_CREATE:
+ from: !php/const Sylius\Component\Order\Model\OrderInterface::STATE_CART
+ to: !php/const Sylius\Component\Order\Model\OrderInterface::STATE_NEW
+ !php/const Sylius\Component\Order\OrderTransitions::TRANSITION_CANCEL:
+ from: !php/const Sylius\Component\Order\Model\OrderInterface::STATE_NEW
+ to: !php/const Sylius\Component\Order\Model\OrderInterface::STATE_CANCELLED
+ !php/const Sylius\Component\Order\OrderTransitions::TRANSITION_FULFILL:
+ from: !php/const Sylius\Component\Order\Model\OrderInterface::STATE_NEW
+ to: !php/const Sylius\Component\Order\Model\OrderInterface::STATE_FULFILLED
diff --git a/src/Sylius/Bundle/CoreBundle/Resources/config/services/listeners.xml b/src/Sylius/Bundle/CoreBundle/Resources/config/services/listeners.xml
index 34d58f5f24..281e5cae54 100644
--- a/src/Sylius/Bundle/CoreBundle/Resources/config/services/listeners.xml
+++ b/src/Sylius/Bundle/CoreBundle/Resources/config/services/listeners.xml
@@ -20,7 +20,7 @@
-
+
diff --git a/src/Sylius/Bundle/CoreBundle/Resources/config/services/listeners/workflow/order.xml b/src/Sylius/Bundle/CoreBundle/Resources/config/services/listeners/workflow/order.xml
new file mode 100644
index 0000000000..080b174b22
--- /dev/null
+++ b/src/Sylius/Bundle/CoreBundle/Resources/config/services/listeners/workflow/order.xml
@@ -0,0 +1,96 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Sylius/Bundle/CoreBundle/Resources/config/services/listeners/workflow/order_checkout.xml b/src/Sylius/Bundle/CoreBundle/Resources/config/services/listeners/workflow/order_checkout.xml
index d8b309bc1c..794a7f5ba3 100644
--- a/src/Sylius/Bundle/CoreBundle/Resources/config/services/listeners/workflow/order_checkout.xml
+++ b/src/Sylius/Bundle/CoreBundle/Resources/config/services/listeners/workflow/order_checkout.xml
@@ -13,8 +13,6 @@
-
-
diff --git a/src/Sylius/Bundle/CoreBundle/Tests/Functional/StateMachine/OrderWorkflowTest.php b/src/Sylius/Bundle/CoreBundle/Tests/Functional/StateMachine/OrderWorkflowTest.php
new file mode 100644
index 0000000000..af27fb5639
--- /dev/null
+++ b/src/Sylius/Bundle/CoreBundle/Tests/Functional/StateMachine/OrderWorkflowTest.php
@@ -0,0 +1,80 @@
+createMock(RepositoryInterface::class);
+ $sequenceRepository
+ ->method('findOneBy')
+ ->willReturn(null)
+ ;
+
+ self::getContainer()->set('sylius.repository.order_sequence', $sequenceRepository);
+
+ $channel = $this->createMock(ChannelInterface::class);
+ $customer = $this->createMock(Customer::class);
+
+ $order = new Order();
+ $order->setChannel($channel);
+ $order->setCustomer($customer);
+
+ $this->order = $order;
+ }
+
+ /**
+ * @test
+ *
+ * @dataProvider availableTransitionsForOrder
+ */
+ public function it_applies_all_available_transitions_for_order(
+ string $initialState,
+ string $transition,
+ string $expectedState,
+ ): void {
+ $stateMachine = $this->getStateMachine();
+ $order = $this->order;
+
+ $stateMachine->apply($order, OrderTransitions::GRAPH, $initialState);
+ $stateMachine->apply($order, OrderTransitions::GRAPH, $transition);
+
+ $this->assertSame($expectedState, $order->getState());
+ }
+
+ public function availableTransitionsForOrder(): iterable
+ {
+ yield [OrderTransitions::TRANSITION_CREATE, OrderTransitions::TRANSITION_CANCEL, OrderInterface::STATE_CANCELLED];
+ yield [OrderTransitions::TRANSITION_CREATE, OrderTransitions::TRANSITION_FULFILL, OrderInterface::STATE_FULFILLED];
+ }
+
+ private function getStateMachine(): StateMachineInterface
+ {
+ return self::getContainer()->get('sylius.state_machine.adapter.symfony_workflow');
+ }
+}
diff --git a/src/Sylius/Bundle/CoreBundle/Tests/Mailer/OrderEmailManagerTest.php b/src/Sylius/Bundle/CoreBundle/Tests/Mailer/OrderEmailManagerTest.php
index af9d469c87..70378b7a91 100644
--- a/src/Sylius/Bundle/CoreBundle/Tests/Mailer/OrderEmailManagerTest.php
+++ b/src/Sylius/Bundle/CoreBundle/Tests/Mailer/OrderEmailManagerTest.php
@@ -19,15 +19,12 @@ use Sylius\Bundle\CoreBundle\Mailer\OrderEmailManagerInterface;
use Sylius\Component\Core\Model\ChannelInterface;
use Sylius\Component\Core\Model\CustomerInterface;
use Sylius\Component\Core\Model\OrderInterface;
-use Sylius\Component\Core\Test\SwiftmailerAssertionTrait;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
-use Symfony\Component\Filesystem\Filesystem;
use Symfony\Contracts\Translation\TranslatorInterface;
final class OrderEmailManagerTest extends KernelTestCase
{
use ProphecyTrait;
- use SwiftmailerAssertionTrait;
private const RECIPIENT_EMAIL = 'test@example.com';
@@ -36,12 +33,8 @@ final class OrderEmailManagerTest extends KernelTestCase
private const ORDER_NUMBER = '#000001';
/** @test */
- public function it_sends_order_confirmation_email_with_symfony_mailer_if_swift_mailer_is_not_present(): void
+ public function it_sends_order_confirmation_email(): void
{
- if ($this->isItSwiftmailerTestEnv()) {
- $this->markTestSkipped('This test should be executed only outside of test_with_swiftmailer environment');
- }
-
$container = self::getContainer();
/** @var TranslatorInterface $translator */
@@ -78,59 +71,4 @@ final class OrderEmailManagerTest extends KernelTestCase
preg_replace('/\s+/', ' ', strip_tags($email->getHtmlBody())),
);
}
-
- /** @test */
- public function it_sends_order_confirmation_email_with_swift_mailer_by_default_if_is_present(): void
- {
- if (!$this->isItSwiftmailerTestEnv()) {
- $this->markTestSkipped('This test should be executed only in test_with_swiftmailer environment');
- }
-
- $container = self::getContainer();
-
- self::setSpoolDirectory($container->getParameter('kernel.cache_dir') . '/spool');
-
- /** @var Filesystem $filesystem */
- $filesystem = $container->get('filesystem');
-
- /** @var TranslatorInterface $translator */
- $translator = $container->get('translator');
-
- $filesystem->remove(self::getSpoolDirectory());
-
- $orderEmailManager = static::$kernel->getContainer()->get('sylius.mailer.order_email_manager');
- /** @var OrderInterface|ObjectProphecy $order */
- $order = $this->prophesize(OrderInterface::class);
- /** @var CustomerInterface|ObjectProphecy $customer */
- $customer = $this->prophesize(CustomerInterface::class);
- $customer->getEmail()->willReturn(self::RECIPIENT_EMAIL);
- /** @var ChannelInterface|ObjectProphecy $channel */
- $channel = $this->prophesize(ChannelInterface::class);
-
- $order->getCustomer()->willReturn($customer->reveal());
- $order->getChannel()->willReturn($channel->reveal());
- $order->getLocaleCode()->willReturn(self::LOCALE_CODE);
- $order->getNumber()->willReturn(self::ORDER_NUMBER);
- $order->getTokenValue()->willReturn('ASFAFA4654AF');
-
- $orderEmailManager->sendConfirmationEmail($order->reveal());
-
- self::assertSpooledMessagesCountWithRecipient(1, self::RECIPIENT_EMAIL);
- self::assertSpooledMessageWithContentHasRecipient(
- sprintf(
- '%s %s %s',
- $translator->trans('sylius.email.order_confirmation.your_order_number', [], null, self::LOCALE_CODE),
- self::ORDER_NUMBER,
- $translator->trans('sylius.email.order_confirmation.has_been_successfully_placed', [], null, self::LOCALE_CODE),
- ),
- self::RECIPIENT_EMAIL,
- );
- }
-
- private function isItSwiftmailerTestEnv(): bool
- {
- $env = self::getContainer()->getParameter('kernel.environment');
-
- return $env === 'test_with_swiftmailer';
- }
}
diff --git a/src/Sylius/Bundle/CoreBundle/spec/EventListener/Workflow/Order/AssignOrderNumberListenerSpec.php b/src/Sylius/Bundle/CoreBundle/spec/EventListener/Workflow/Order/AssignOrderNumberListenerSpec.php
new file mode 100644
index 0000000000..4ba629d86d
--- /dev/null
+++ b/src/Sylius/Bundle/CoreBundle/spec/EventListener/Workflow/Order/AssignOrderNumberListenerSpec.php
@@ -0,0 +1,46 @@
+beConstructedWith($orderNumberAssigner);
+ }
+
+ function it_throws_an_exception_on_non_supported_subject(\stdClass $subject): void
+ {
+ $this
+ ->shouldThrow(\InvalidArgumentException::class)
+ ->during('__invoke', [new TransitionEvent($subject->getWrappedObject(), new Marking())])
+ ;
+ }
+
+ function it_assigns_order_number(OrderNumberAssignerInterface $orderNumberAssigner): void
+ {
+ $order = new Order();
+ $event = new TransitionEvent($order, new Marking());
+
+ $orderNumberAssigner->assignNumber($order)->shouldBeCalled();
+
+ $this($event);
+ }
+}
diff --git a/src/Sylius/Bundle/CoreBundle/spec/EventListener/Workflow/Order/AssignOrderTokenListenerSpec.php b/src/Sylius/Bundle/CoreBundle/spec/EventListener/Workflow/Order/AssignOrderTokenListenerSpec.php
new file mode 100644
index 0000000000..2877134759
--- /dev/null
+++ b/src/Sylius/Bundle/CoreBundle/spec/EventListener/Workflow/Order/AssignOrderTokenListenerSpec.php
@@ -0,0 +1,46 @@
+beConstructedWith($orderTokenAssigner);
+ }
+
+ function it_throws_an_exception_on_non_supported_subject(\stdClass $subject): void
+ {
+ $this
+ ->shouldThrow(\InvalidArgumentException::class)
+ ->during('__invoke', [new TransitionEvent($subject->getWrappedObject(), new Marking())])
+ ;
+ }
+
+ function it_assigns_order_token(OrderTokenAssignerInterface $orderTokenAssigner): void
+ {
+ $order = new Order();
+ $event = new TransitionEvent($order, new Marking());
+
+ $orderTokenAssigner->assignTokenValueIfNotSet($order)->shouldBeCalled();
+
+ $this($event);
+ }
+}
diff --git a/src/Sylius/Bundle/CoreBundle/spec/EventListener/Workflow/Order/CancelOrderPaymentListenerSpec.php b/src/Sylius/Bundle/CoreBundle/spec/EventListener/Workflow/Order/CancelOrderPaymentListenerSpec.php
new file mode 100644
index 0000000000..048efc8d0b
--- /dev/null
+++ b/src/Sylius/Bundle/CoreBundle/spec/EventListener/Workflow/Order/CancelOrderPaymentListenerSpec.php
@@ -0,0 +1,50 @@
+beConstructedWith($compositeStateMachine);
+ }
+
+ function it_throws_an_exception_on_non_supported_subject(\stdClass $callback): void
+ {
+ $this
+ ->shouldThrow(\InvalidArgumentException::class)
+ ->during('__invoke', [new CompletedEvent($callback->getWrappedObject(), new Marking())]);
+ }
+
+ function it_cancels_order_payment(
+ StateMachineInterface $compositeStateMachine,
+ OrderInterface $order,
+ ): void {
+ $event = new CompletedEvent($order->getWrappedObject(), new Marking());
+
+ $this($event);
+
+ $compositeStateMachine
+ ->apply($order, OrderPaymentTransitions::GRAPH, OrderPaymentTransitions::TRANSITION_CANCEL)
+ ->shouldBeCalled()
+ ;
+ }
+}
diff --git a/src/Sylius/Bundle/CoreBundle/spec/EventListener/Workflow/Order/CancelOrderShippingListenerSpec.php b/src/Sylius/Bundle/CoreBundle/spec/EventListener/Workflow/Order/CancelOrderShippingListenerSpec.php
new file mode 100644
index 0000000000..c903deed1a
--- /dev/null
+++ b/src/Sylius/Bundle/CoreBundle/spec/EventListener/Workflow/Order/CancelOrderShippingListenerSpec.php
@@ -0,0 +1,50 @@
+beConstructedWith($compositeStateMachine);
+ }
+
+ function it_throws_an_exception_on_non_supported_subject(\stdClass $callback): void
+ {
+ $this
+ ->shouldThrow(\InvalidArgumentException::class)
+ ->during('__invoke', [new CompletedEvent($callback->getWrappedObject(), new Marking())]);
+ }
+
+ function it_cancels_order_shipping(
+ StateMachineInterface $compositeStateMachine,
+ OrderInterface $order,
+ ): void {
+ $event = new CompletedEvent($order->getWrappedObject(), new Marking());
+
+ $this($event);
+
+ $compositeStateMachine
+ ->apply($order, OrderShippingTransitions::GRAPH, OrderShippingTransitions::TRANSITION_CANCEL)
+ ->shouldBeCalled()
+ ;
+ }
+}
diff --git a/src/Sylius/Bundle/CoreBundle/spec/EventListener/Workflow/Order/CancelPaymentListenerSpec.php b/src/Sylius/Bundle/CoreBundle/spec/EventListener/Workflow/Order/CancelPaymentListenerSpec.php
new file mode 100644
index 0000000000..b6b37b9e9d
--- /dev/null
+++ b/src/Sylius/Bundle/CoreBundle/spec/EventListener/Workflow/Order/CancelPaymentListenerSpec.php
@@ -0,0 +1,60 @@
+beConstructedWith($compositeStateMachine);
+ }
+
+ function it_throws_an_exception_on_non_supported_subject(\stdClass $callback): void
+ {
+ $this
+ ->shouldThrow(\InvalidArgumentException::class)
+ ->during('__invoke', [new CompletedEvent($callback->getWrappedObject(), new Marking())]);
+ }
+
+ function it_cancels_payments(
+ StateMachineInterface $compositeStateMachine,
+ OrderInterface $order,
+ PaymentInterface $payment1,
+ PaymentInterface $payment2,
+ ): void {
+ $event = new CompletedEvent($order->getWrappedObject(), new Marking());
+ $order->getPayments()->willReturn(new ArrayCollection([$payment1->getWrappedObject(), $payment2->getWrappedObject()]));
+
+ $this($event);
+
+ $compositeStateMachine
+ ->apply($payment1, PaymentTransitions::GRAPH, PaymentTransitions::TRANSITION_CANCEL)
+ ->shouldHaveBeenCalledOnce()
+ ;
+
+ $compositeStateMachine
+ ->apply($payment2, PaymentTransitions::GRAPH, PaymentTransitions::TRANSITION_CANCEL)
+ ->shouldHaveBeenCalledOnce()
+ ;
+ }
+}
diff --git a/src/Sylius/Bundle/CoreBundle/spec/EventListener/Workflow/Order/CancelShipmentListenerSpec.php b/src/Sylius/Bundle/CoreBundle/spec/EventListener/Workflow/Order/CancelShipmentListenerSpec.php
new file mode 100644
index 0000000000..4d5a86d741
--- /dev/null
+++ b/src/Sylius/Bundle/CoreBundle/spec/EventListener/Workflow/Order/CancelShipmentListenerSpec.php
@@ -0,0 +1,61 @@
+beConstructedWith($compositeStateMachine);
+ }
+
+ function it_throws_an_exception_on_non_supported_subject(\stdClass $callback): void
+ {
+ $this
+ ->shouldThrow(\InvalidArgumentException::class)
+ ->during('__invoke', [new CompletedEvent($callback->getWrappedObject(), new Marking())])
+ ;
+ }
+
+ function it_cancels_shipments(
+ StateMachineInterface $compositeStateMachine,
+ OrderInterface $order,
+ ShipmentInterface $shipment1,
+ ShipmentInterface $shipment2,
+ ): void {
+ $event = new CompletedEvent($order->getWrappedObject(), new Marking());
+ $order->getShipments()->willReturn(new ArrayCollection([$shipment1->getWrappedObject(), $shipment2->getWrappedObject()]));
+
+ $this($event);
+
+ $compositeStateMachine
+ ->apply($shipment1, ShipmentTransitions::GRAPH, ShipmentTransitions::TRANSITION_CANCEL)
+ ->shouldHaveBeenCalledOnce()
+ ;
+
+ $compositeStateMachine
+ ->apply($shipment2, ShipmentTransitions::GRAPH, ShipmentTransitions::TRANSITION_CANCEL)
+ ->shouldHaveBeenCalledOnce()
+ ;
+ }
+}
diff --git a/src/Sylius/Bundle/CoreBundle/spec/EventListener/Workflow/Order/DecrementPromotionUsagesListenerSpec.php b/src/Sylius/Bundle/CoreBundle/spec/EventListener/Workflow/Order/DecrementPromotionUsagesListenerSpec.php
new file mode 100644
index 0000000000..362baf14db
--- /dev/null
+++ b/src/Sylius/Bundle/CoreBundle/spec/EventListener/Workflow/Order/DecrementPromotionUsagesListenerSpec.php
@@ -0,0 +1,46 @@
+beConstructedWith($orderPromotionsUsageModifier);
+ }
+
+ function it_throws_an_exception_on_non_supported_subject(\stdClass $callback): void
+ {
+ $this
+ ->shouldThrow(\InvalidArgumentException::class)
+ ->during('__invoke', [new CompletedEvent($callback->getWrappedObject(), new Marking())]);
+ }
+
+ function it_decrements_promotion_usages(
+ OrderPromotionsUsageModifierInterface $orderPromotionsUsageModifier,
+ OrderInterface $order,
+ ): void {
+ $event = new CompletedEvent($order->getWrappedObject(), new Marking());
+
+ $this($event);
+
+ $orderPromotionsUsageModifier->decrement($order)->shouldBeCalled();
+ }
+}
diff --git a/src/Sylius/Bundle/CoreBundle/spec/EventListener/Workflow/Order/GiveBackInventoryListenerSpec.php b/src/Sylius/Bundle/CoreBundle/spec/EventListener/Workflow/Order/GiveBackInventoryListenerSpec.php
new file mode 100644
index 0000000000..fac019f2f5
--- /dev/null
+++ b/src/Sylius/Bundle/CoreBundle/spec/EventListener/Workflow/Order/GiveBackInventoryListenerSpec.php
@@ -0,0 +1,46 @@
+beConstructedWith($orderInventoryOperator);
+ }
+
+ function it_throws_an_exception_on_non_supported_subject(\stdClass $callback): void
+ {
+ $this
+ ->shouldThrow(\InvalidArgumentException::class)
+ ->during('__invoke', [new CompletedEvent($callback->getWrappedObject(), new Marking())]);
+ }
+
+ function it_cancels_order(
+ OrderInventoryOperatorInterface $orderInventoryOperator,
+ OrderInterface $order,
+ ): void {
+ $event = new CompletedEvent($order->getWrappedObject(), new Marking());
+
+ $this($event);
+
+ $orderInventoryOperator->cancel($order)->shouldBeCalled();
+ }
+}
diff --git a/src/Sylius/Bundle/CoreBundle/spec/EventListener/Workflow/Order/HoldInventoryListenerSpec.php b/src/Sylius/Bundle/CoreBundle/spec/EventListener/Workflow/Order/HoldInventoryListenerSpec.php
new file mode 100644
index 0000000000..51cec5be30
--- /dev/null
+++ b/src/Sylius/Bundle/CoreBundle/spec/EventListener/Workflow/Order/HoldInventoryListenerSpec.php
@@ -0,0 +1,46 @@
+beConstructedWith($orderInventoryOperator);
+ }
+
+ function it_throws_an_exception_on_non_supported_subject(\stdClass $callback): void
+ {
+ $this
+ ->shouldThrow(\InvalidArgumentException::class)
+ ->during('__invoke', [new CompletedEvent($callback->getWrappedObject(), new Marking())]);
+ }
+
+ function it_holds_order_inventory(
+ OrderInventoryOperatorInterface $orderInventoryOperator,
+ OrderInterface $order,
+ ): void {
+ $event = new CompletedEvent($order->getWrappedObject(), new Marking());
+
+ $this($event);
+
+ $orderInventoryOperator->hold($order)->shouldBeCalled();
+ }
+}
diff --git a/src/Sylius/Bundle/CoreBundle/spec/EventListener/Workflow/Order/IncrementPromotionUsagesListenerSpec.php b/src/Sylius/Bundle/CoreBundle/spec/EventListener/Workflow/Order/IncrementPromotionUsagesListenerSpec.php
new file mode 100644
index 0000000000..dccd821bd8
--- /dev/null
+++ b/src/Sylius/Bundle/CoreBundle/spec/EventListener/Workflow/Order/IncrementPromotionUsagesListenerSpec.php
@@ -0,0 +1,46 @@
+beConstructedWith($orderPromotionsUsageModifier);
+ }
+
+ function it_throws_an_exception_on_non_supported_subject(\stdClass $callback): void
+ {
+ $this
+ ->shouldThrow(\InvalidArgumentException::class)
+ ->during('__invoke', [new CompletedEvent($callback->getWrappedObject(), new Marking())]);
+ }
+
+ function it_increments_promotion_usages(
+ OrderPromotionsUsageModifierInterface $orderPromotionsUsageModifier,
+ OrderInterface $order,
+ ): void {
+ $event = new CompletedEvent($order->getWrappedObject(), new Marking());
+
+ $this($event);
+
+ $orderPromotionsUsageModifier->increment($order)->shouldBeCalled();
+ }
+}
diff --git a/src/Sylius/Bundle/CoreBundle/spec/EventListener/Workflow/Order/RequestOrderPaymentListenerSpec.php b/src/Sylius/Bundle/CoreBundle/spec/EventListener/Workflow/Order/RequestOrderPaymentListenerSpec.php
new file mode 100644
index 0000000000..0b0d460801
--- /dev/null
+++ b/src/Sylius/Bundle/CoreBundle/spec/EventListener/Workflow/Order/RequestOrderPaymentListenerSpec.php
@@ -0,0 +1,51 @@
+beConstructedWith($compositeStateMachine);
+ }
+
+ function it_throws_an_exception_on_non_supported_subject(\stdClass $callback): void
+ {
+ $this
+ ->shouldThrow(\InvalidArgumentException::class)
+ ->during('__invoke', [new CompletedEvent($callback->getWrappedObject(), new Marking())])
+ ;
+ }
+
+ function it_requests_order_payment(
+ StateMachineInterface $compositeStateMachine,
+ OrderInterface $order,
+ ): void {
+ $event = new CompletedEvent($order->getWrappedObject(), new Marking());
+
+ $this($event);
+
+ $compositeStateMachine
+ ->apply($order, OrderPaymentTransitions::GRAPH, OrderPaymentTransitions::TRANSITION_REQUEST_PAYMENT)
+ ->shouldBeCalled()
+ ;
+ }
+}
diff --git a/src/Sylius/Bundle/CoreBundle/spec/EventListener/Workflow/Order/RequestOrderShippingListenerSpec.php b/src/Sylius/Bundle/CoreBundle/spec/EventListener/Workflow/Order/RequestOrderShippingListenerSpec.php
new file mode 100644
index 0000000000..9dbfeb69be
--- /dev/null
+++ b/src/Sylius/Bundle/CoreBundle/spec/EventListener/Workflow/Order/RequestOrderShippingListenerSpec.php
@@ -0,0 +1,51 @@
+beConstructedWith($compositeStateMachine);
+ }
+
+ function it_throws_an_exception_on_non_supported_subject(\stdClass $callback): void
+ {
+ $this
+ ->shouldThrow(\InvalidArgumentException::class)
+ ->during('__invoke', [new CompletedEvent($callback->getWrappedObject(), new Marking())])
+ ;
+ }
+
+ function it_requests_order_shipping(
+ StateMachineInterface $compositeStateMachine,
+ OrderInterface $order,
+ ): void {
+ $event = new CompletedEvent($order->getWrappedObject(), new Marking());
+
+ $this($event);
+
+ $compositeStateMachine
+ ->apply($order, OrderShippingTransitions::GRAPH, OrderShippingTransitions::TRANSITION_REQUEST_SHIPPING)
+ ->shouldBeCalled()
+ ;
+ }
+}
diff --git a/src/Sylius/Bundle/CoreBundle/spec/EventListener/Workflow/Order/SaveCustomerAddressesListenerSpec.php b/src/Sylius/Bundle/CoreBundle/spec/EventListener/Workflow/Order/SaveCustomerAddressesListenerSpec.php
new file mode 100644
index 0000000000..6ddb5976f4
--- /dev/null
+++ b/src/Sylius/Bundle/CoreBundle/spec/EventListener/Workflow/Order/SaveCustomerAddressesListenerSpec.php
@@ -0,0 +1,47 @@
+beConstructedWith($orderAddressesSaver);
+ }
+
+ function it_throws_an_exception_on_non_supported_subject(\stdClass $callback): void
+ {
+ $this
+ ->shouldThrow(\InvalidArgumentException::class)
+ ->during('__invoke', [new CompletedEvent($callback->getWrappedObject(), new Marking())])
+ ;
+ }
+
+ function it_saves_addresses(
+ OrderAddressesSaverInterface $orderAddressesSaver,
+ OrderInterface $order,
+ ): void {
+ $event = new CompletedEvent($order->getWrappedObject(), new Marking());
+
+ $this($event);
+
+ $orderAddressesSaver->saveAddresses($order)->shouldBeCalled();
+ }
+}
diff --git a/src/Sylius/Bundle/CoreBundle/spec/EventListener/Workflow/Order/SetImmutableNamesListenerSpec.php b/src/Sylius/Bundle/CoreBundle/spec/EventListener/Workflow/Order/SetImmutableNamesListenerSpec.php
new file mode 100644
index 0000000000..1b5631986c
--- /dev/null
+++ b/src/Sylius/Bundle/CoreBundle/spec/EventListener/Workflow/Order/SetImmutableNamesListenerSpec.php
@@ -0,0 +1,47 @@
+beConstructedWith($orderItemNamesSetter);
+ }
+
+ function it_throws_an_exception_on_non_supported_subject(\stdClass $callback): void
+ {
+ $this
+ ->shouldThrow(\InvalidArgumentException::class)
+ ->during('__invoke', [new CompletedEvent($callback->getWrappedObject(), new Marking())])
+ ;
+ }
+
+ function it_sets_immutable_names(
+ OrderItemNamesSetterInterface $orderItemNamesSetter,
+ OrderInterface $order,
+ ): void {
+ $event = new CompletedEvent($order->getWrappedObject(), new Marking());
+
+ $this($event);
+
+ $orderItemNamesSetter->__invoke($order)->shouldBeCalled();
+ }
+}
diff --git a/src/Sylius/Bundle/CoreBundle/test/config/bundles.php b/src/Sylius/Bundle/CoreBundle/test/config/bundles.php
index e18063af37..4b6e93e595 100644
--- a/src/Sylius/Bundle/CoreBundle/test/config/bundles.php
+++ b/src/Sylius/Bundle/CoreBundle/test/config/bundles.php
@@ -14,7 +14,6 @@ declare(strict_types=1);
return [
Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
- Symfony\Bundle\SwiftmailerBundle\SwiftmailerBundle::class => ['test_with_swiftmailer' => true],
Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true],
Symfony\WebpackEncoreBundle\WebpackEncoreBundle::class => ['all' => true],
Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
diff --git a/src/Sylius/Bundle/CoreBundle/test/config/packages/test_with_swiftmailer/filesystem.yaml b/src/Sylius/Bundle/CoreBundle/test/config/packages/test_with_swiftmailer/filesystem.yaml
deleted file mode 100644
index 1828fd69a2..0000000000
--- a/src/Sylius/Bundle/CoreBundle/test/config/packages/test_with_swiftmailer/filesystem.yaml
+++ /dev/null
@@ -1,4 +0,0 @@
-services:
- test.filesystem.public:
- alias: filesystem
- public: true
diff --git a/src/Sylius/Bundle/CoreBundle/test/config/packages/test_with_swiftmailer/swiftmailer.yaml b/src/Sylius/Bundle/CoreBundle/test/config/packages/test_with_swiftmailer/swiftmailer.yaml
deleted file mode 100644
index c438f4b256..0000000000
--- a/src/Sylius/Bundle/CoreBundle/test/config/packages/test_with_swiftmailer/swiftmailer.yaml
+++ /dev/null
@@ -1,6 +0,0 @@
-swiftmailer:
- disable_delivery: true
- logging: true
- spool:
- type: file
- path: "%kernel.cache_dir%/spool"
diff --git a/src/Sylius/Bundle/CoreBundle/test/config/packages/test_with_swiftmailer/sylius_mailer.yaml b/src/Sylius/Bundle/CoreBundle/test/config/packages/test_with_swiftmailer/sylius_mailer.yaml
deleted file mode 100644
index e2310f20bf..0000000000
--- a/src/Sylius/Bundle/CoreBundle/test/config/packages/test_with_swiftmailer/sylius_mailer.yaml
+++ /dev/null
@@ -1,2 +0,0 @@
-sylius_mailer:
- sender_adapter: sylius.email_sender.adapter.swiftmailer
diff --git a/src/Sylius/Bundle/CurrencyBundle/DependencyInjection/Configuration.php b/src/Sylius/Bundle/CurrencyBundle/DependencyInjection/Configuration.php
index ca60fd271a..85acde4ec8 100644
--- a/src/Sylius/Bundle/CurrencyBundle/DependencyInjection/Configuration.php
+++ b/src/Sylius/Bundle/CurrencyBundle/DependencyInjection/Configuration.php
@@ -13,6 +13,8 @@ declare(strict_types=1);
namespace Sylius\Bundle\CurrencyBundle\DependencyInjection;
+use Sylius\Bundle\CurrencyBundle\Doctrine\ORM\CurrencyRepository;
+use Sylius\Bundle\CurrencyBundle\Doctrine\ORM\ExchangeRateRepository;
use Sylius\Bundle\CurrencyBundle\Form\Type\CurrencyType;
use Sylius\Bundle\CurrencyBundle\Form\Type\ExchangeRateType;
use Sylius\Bundle\ResourceBundle\Controller\ResourceController;
@@ -62,7 +64,7 @@ final class Configuration implements ConfigurationInterface
->scalarNode('model')->defaultValue(Currency::class)->cannotBeEmpty()->end()
->scalarNode('interface')->defaultValue(CurrencyInterface::class)->cannotBeEmpty()->end()
->scalarNode('controller')->defaultValue(ResourceController::class)->cannotBeEmpty()->end()
- ->scalarNode('repository')->cannotBeEmpty()->end()
+ ->scalarNode('repository')->defaultValue(CurrencyRepository::class)->cannotBeEmpty()->end()
->scalarNode('factory')->defaultValue(Factory::class)->cannotBeEmpty()->end()
->scalarNode('form')->defaultValue(CurrencyType::class)->cannotBeEmpty()->end()
->end()
@@ -79,7 +81,7 @@ final class Configuration implements ConfigurationInterface
->scalarNode('model')->defaultValue(ExchangeRate::class)->cannotBeEmpty()->end()
->scalarNode('interface')->defaultValue(ExchangeRateInterface::class)->cannotBeEmpty()->end()
->scalarNode('controller')->defaultValue(ResourceController::class)->cannotBeEmpty()->end()
- ->scalarNode('repository')->cannotBeEmpty()->end()
+ ->scalarNode('repository')->defaultValue(ExchangeRateRepository::class)->cannotBeEmpty()->end()
->scalarNode('factory')->defaultValue(Factory::class)->cannotBeEmpty()->end()
->scalarNode('form')->defaultValue(ExchangeRateType::class)->cannotBeEmpty()->end()
->end()
diff --git a/src/Sylius/Bundle/CurrencyBundle/Doctrine/ORM/CurrencyRepository.php b/src/Sylius/Bundle/CurrencyBundle/Doctrine/ORM/CurrencyRepository.php
new file mode 100644
index 0000000000..90f21fe644
--- /dev/null
+++ b/src/Sylius/Bundle/CurrencyBundle/Doctrine/ORM/CurrencyRepository.php
@@ -0,0 +1,25 @@
+
+ */
+class CurrencyRepository extends EntityRepository implements CurrencyRepositoryInterface
+{
+}
diff --git a/src/Sylius/Bundle/OrderBundle/Resources/config/workflow/state_machine.yaml b/src/Sylius/Bundle/OrderBundle/Resources/config/workflow/state_machine.yaml
new file mode 100644
index 0000000000..5a88ef99d3
--- /dev/null
+++ b/src/Sylius/Bundle/OrderBundle/Resources/config/workflow/state_machine.yaml
@@ -0,0 +1,25 @@
+framework:
+ workflows:
+ !php/const Sylius\Component\Order\OrderTransitions::GRAPH:
+ type: state_machine
+ marking_store:
+ type: method
+ property: state
+ supports:
+ - Sylius\Component\Order\Model\OrderInterface
+ initial_marking: !php/const Sylius\Component\Order\Model\OrderInterface::STATE_CART
+ places:
+ - !php/const Sylius\Component\Order\Model\OrderInterface::STATE_CART
+ - !php/const Sylius\Component\Order\Model\OrderInterface::STATE_NEW
+ - !php/const Sylius\Component\Order\Model\OrderInterface::STATE_CANCELLED
+ - !php/const Sylius\Component\Order\Model\OrderInterface::STATE_FULFILLED
+ transitions:
+ !php/const Sylius\Component\Order\OrderTransitions::TRANSITION_CREATE:
+ from: !php/const Sylius\Component\Order\Model\OrderInterface::STATE_CART
+ to: !php/const Sylius\Component\Order\Model\OrderInterface::STATE_NEW
+ !php/const Sylius\Component\Order\OrderTransitions::TRANSITION_CANCEL:
+ from: !php/const Sylius\Component\Order\Model\OrderInterface::STATE_NEW
+ to: !php/const Sylius\Component\Order\Model\OrderInterface::STATE_CANCELLED
+ !php/const Sylius\Component\Order\OrderTransitions::TRANSITION_FULFILL:
+ from: !php/const Sylius\Component\Order\Model\OrderInterface::STATE_NEW
+ to: !php/const Sylius\Component\Order\Model\OrderInterface::STATE_FULFILLED
diff --git a/src/Sylius/Bundle/PayumBundle/DependencyInjection/Compiler/UnregisterPaypalGatewayTypePass.php b/src/Sylius/Bundle/PayumBundle/DependencyInjection/Compiler/UnregisterPaypalGatewayTypePass.php
new file mode 100644
index 0000000000..ca0b76b679
--- /dev/null
+++ b/src/Sylius/Bundle/PayumBundle/DependencyInjection/Compiler/UnregisterPaypalGatewayTypePass.php
@@ -0,0 +1,35 @@
+removeDefinition(self::PAYPAL_GATEWAY_TYPE_SERVICE_ID);
+ $container->removeDefinition(self::PAYPAL_CONVERT_ACTION_SERVICE_ID);
+ }
+}
diff --git a/src/Sylius/Bundle/PayumBundle/DependencyInjection/Compiler/UnregisterStripeGatewayTypePass.php b/src/Sylius/Bundle/PayumBundle/DependencyInjection/Compiler/UnregisterStripeGatewayTypePass.php
index bb886e9fae..201ff3ed7e 100644
--- a/src/Sylius/Bundle/PayumBundle/DependencyInjection/Compiler/UnregisterStripeGatewayTypePass.php
+++ b/src/Sylius/Bundle/PayumBundle/DependencyInjection/Compiler/UnregisterStripeGatewayTypePass.php
@@ -13,7 +13,7 @@ declare(strict_types=1);
namespace Sylius\Bundle\PayumBundle\DependencyInjection\Compiler;
-use Stripe\Stripe;
+use Payum\Stripe\StripeCheckoutGatewayFactory;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
@@ -21,9 +21,9 @@ final class UnregisterStripeGatewayTypePass implements CompilerPassInterface
{
private const STRIPE_GATEWAY_TYPE_SERVICE_ID = 'sylius.form.type.gateway_configuration.stripe';
- public function process(ContainerBuilder $container)
+ public function process(ContainerBuilder $container): void
{
- if (class_exists(Stripe::class)) {
+ if (class_exists(StripeCheckoutGatewayFactory::class)) {
return;
}
diff --git a/src/Sylius/Bundle/PayumBundle/SyliusPayumBundle.php b/src/Sylius/Bundle/PayumBundle/SyliusPayumBundle.php
index 23a2bfe697..eb0601154c 100644
--- a/src/Sylius/Bundle/PayumBundle/SyliusPayumBundle.php
+++ b/src/Sylius/Bundle/PayumBundle/SyliusPayumBundle.php
@@ -15,6 +15,7 @@ namespace Sylius\Bundle\PayumBundle;
use Sylius\Bundle\PayumBundle\DependencyInjection\Compiler\InjectContainerIntoControllersPass;
use Sylius\Bundle\PayumBundle\DependencyInjection\Compiler\RegisterGatewayConfigTypePass;
+use Sylius\Bundle\PayumBundle\DependencyInjection\Compiler\UnregisterPaypalGatewayTypePass;
use Sylius\Bundle\PayumBundle\DependencyInjection\Compiler\UnregisterStripeGatewayTypePass;
use Sylius\Bundle\PayumBundle\DependencyInjection\Compiler\UseTweakedDoctrineStoragePass;
use Sylius\Bundle\ResourceBundle\AbstractResourceBundle;
@@ -37,6 +38,7 @@ final class SyliusPayumBundle extends AbstractResourceBundle
$container->addCompilerPass(new InjectContainerIntoControllersPass());
$container->addCompilerPass(new RegisterGatewayConfigTypePass());
$container->addCompilerPass(new UseTweakedDoctrineStoragePass());
+ $container->addCompilerPass(new UnregisterPaypalGatewayTypePass(), priority: 128);
$container->addCompilerPass(new UnregisterStripeGatewayTypePass(), priority: 128);
}
}
diff --git a/src/Sylius/Bundle/PayumBundle/composer.json b/src/Sylius/Bundle/PayumBundle/composer.json
index 49c729faa9..a793b70dfa 100644
--- a/src/Sylius/Bundle/PayumBundle/composer.json
+++ b/src/Sylius/Bundle/PayumBundle/composer.json
@@ -30,7 +30,6 @@
"require": {
"php": "^8.2",
"payum/offline": "^1.7.3",
- "payum/paypal-express-checkout-nvp": "^1.7.3",
"payum/payum-bundle": "^2.5",
"sylius/core": "^2.0",
"sylius/currency": "^2.0",
@@ -43,6 +42,10 @@
"phpspec/phpspec": "^7.2",
"symfony/dependency-injection": "^6.3.4"
},
+ "suggest": {
+ "payum/paypal-express-checkout-nvp": "To use PayPal Express Chekout NVP",
+ "payum/stripe": "To use Stripe Checkout"
+ },
"config": {
"allow-plugins": {
"symfony/flex": true
diff --git a/src/Sylius/Bundle/ProductBundle/Resources/config/validation/ProductAssociationTypeTranslation.xml b/src/Sylius/Bundle/ProductBundle/Resources/config/validation/ProductAssociationTypeTranslation.xml
index 26a3620b74..5c10b2a462 100644
--- a/src/Sylius/Bundle/ProductBundle/Resources/config/validation/ProductAssociationTypeTranslation.xml
+++ b/src/Sylius/Bundle/ProductBundle/Resources/config/validation/ProductAssociationTypeTranslation.xml
@@ -26,5 +26,15 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Sylius/Bundle/ProductBundle/Resources/config/validation/ProductOptionTranslation.xml b/src/Sylius/Bundle/ProductBundle/Resources/config/validation/ProductOptionTranslation.xml
index 737c9b2df9..c0cdc43c64 100644
--- a/src/Sylius/Bundle/ProductBundle/Resources/config/validation/ProductOptionTranslation.xml
+++ b/src/Sylius/Bundle/ProductBundle/Resources/config/validation/ProductOptionTranslation.xml
@@ -26,5 +26,15 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Sylius/Bundle/ProductBundle/Resources/config/validation/ProductOptionValueTranslation.xml b/src/Sylius/Bundle/ProductBundle/Resources/config/validation/ProductOptionValueTranslation.xml
index 9a78fa6a91..68b4261962 100644
--- a/src/Sylius/Bundle/ProductBundle/Resources/config/validation/ProductOptionValueTranslation.xml
+++ b/src/Sylius/Bundle/ProductBundle/Resources/config/validation/ProductOptionValueTranslation.xml
@@ -19,5 +19,15 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Sylius/Bundle/ProductBundle/Resources/config/validation/ProductTranslation.xml b/src/Sylius/Bundle/ProductBundle/Resources/config/validation/ProductTranslation.xml
index c3b92078e2..9161372dd5 100644
--- a/src/Sylius/Bundle/ProductBundle/Resources/config/validation/ProductTranslation.xml
+++ b/src/Sylius/Bundle/ProductBundle/Resources/config/validation/ProductTranslation.xml
@@ -60,5 +60,15 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Sylius/Bundle/ProductBundle/Resources/config/validation/ProductVariant.xml b/src/Sylius/Bundle/ProductBundle/Resources/config/validation/ProductVariant.xml
index 6dd5bc6d16..611b45f244 100644
--- a/src/Sylius/Bundle/ProductBundle/Resources/config/validation/ProductVariant.xml
+++ b/src/Sylius/Bundle/ProductBundle/Resources/config/validation/ProductVariant.xml
@@ -37,5 +37,8 @@
+
+
+
diff --git a/src/Sylius/Bundle/ProductBundle/Resources/config/validation/ProductVariantTranslation.xml b/src/Sylius/Bundle/ProductBundle/Resources/config/validation/ProductVariantTranslation.xml
new file mode 100644
index 0000000000..661ed82f66
--- /dev/null
+++ b/src/Sylius/Bundle/ProductBundle/Resources/config/validation/ProductVariantTranslation.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Sylius/Bundle/ProductBundle/Resources/translations/validators.en.yml b/src/Sylius/Bundle/ProductBundle/Resources/translations/validators.en.yml
index d13341caa5..243426ff08 100644
--- a/src/Sylius/Bundle/ProductBundle/Resources/translations/validators.en.yml
+++ b/src/Sylius/Bundle/ProductBundle/Resources/translations/validators.en.yml
@@ -56,3 +56,7 @@ sylius:
not_blank: Please enter association type code.
regex: Association type code can only be comprised of letters, numbers, dashes and underscores.
unique: The association type with given code already exists.
+ translation:
+ locale:
+ not_blank: Please enter the locale.
+ invalid: This value is not a valid locale.
diff --git a/src/Sylius/Bundle/PromotionBundle/Resources/config/validation/CatalogPromotion.xml b/src/Sylius/Bundle/PromotionBundle/Resources/config/validation/CatalogPromotion.xml
index 37674a788c..4249c16f82 100644
--- a/src/Sylius/Bundle/PromotionBundle/Resources/config/validation/CatalogPromotion.xml
+++ b/src/Sylius/Bundle/PromotionBundle/Resources/config/validation/CatalogPromotion.xml
@@ -62,5 +62,8 @@
+
+
+
diff --git a/src/Sylius/Bundle/PromotionBundle/Resources/config/validation/CatalogPromotionTranslation.xml b/src/Sylius/Bundle/PromotionBundle/Resources/config/validation/CatalogPromotionTranslation.xml
new file mode 100644
index 0000000000..92a3a618c3
--- /dev/null
+++ b/src/Sylius/Bundle/PromotionBundle/Resources/config/validation/CatalogPromotionTranslation.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Sylius/Bundle/PromotionBundle/Resources/config/validation/PromotionTranslation.xml b/src/Sylius/Bundle/PromotionBundle/Resources/config/validation/PromotionTranslation.xml
index cba8384e52..8a5e374fa8 100644
--- a/src/Sylius/Bundle/PromotionBundle/Resources/config/validation/PromotionTranslation.xml
+++ b/src/Sylius/Bundle/PromotionBundle/Resources/config/validation/PromotionTranslation.xml
@@ -19,5 +19,15 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Sylius/Bundle/PromotionBundle/Resources/translations/validators.en.yml b/src/Sylius/Bundle/PromotionBundle/Resources/translations/validators.en.yml
index 5fdb11feac..543d8f1f0e 100644
--- a/src/Sylius/Bundle/PromotionBundle/Resources/translations/validators.en.yml
+++ b/src/Sylius/Bundle/PromotionBundle/Resources/translations/validators.en.yml
@@ -61,3 +61,7 @@ sylius:
possible_generation_amount: Invalid coupons code length or coupons amount. It is not possible to generate %expectedAmount% unique coupons with code length %codeLength%. The possible amount to generate is %possibleAmount%.
usage_limit:
min: Usage limit of generated coupons must be at least {{ limit }}.
+ translation:
+ locale:
+ not_blank: Please enter the locale.
+ invalid: This value is not a valid locale.
diff --git a/src/Sylius/Bundle/ShippingBundle/Resources/config/validation/ShippingMethodTranslation.xml b/src/Sylius/Bundle/ShippingBundle/Resources/config/validation/ShippingMethodTranslation.xml
index 3998e30849..ee8118fc29 100644
--- a/src/Sylius/Bundle/ShippingBundle/Resources/config/validation/ShippingMethodTranslation.xml
+++ b/src/Sylius/Bundle/ShippingBundle/Resources/config/validation/ShippingMethodTranslation.xml
@@ -26,5 +26,15 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Sylius/Bundle/ShippingBundle/Resources/translations/validators.en.yml b/src/Sylius/Bundle/ShippingBundle/Resources/translations/validators.en.yml
index 9da6ebf270..f745c087c0 100644
--- a/src/Sylius/Bundle/ShippingBundle/Resources/translations/validators.en.yml
+++ b/src/Sylius/Bundle/ShippingBundle/Resources/translations/validators.en.yml
@@ -30,3 +30,7 @@ sylius:
shipment:
shipping_method:
not_blank: 'Please select shipping method.'
+ translation:
+ locale:
+ not_blank: 'Please enter the locale.'
+ invalid: 'This value is not a valid locale.'
diff --git a/src/Sylius/Bundle/ShopBundle/Controller/ContactController.php b/src/Sylius/Bundle/ShopBundle/Controller/ContactController.php
index 50cfca1f07..42a768e381 100644
--- a/src/Sylius/Bundle/ShopBundle/Controller/ContactController.php
+++ b/src/Sylius/Bundle/ShopBundle/Controller/ContactController.php
@@ -97,7 +97,7 @@ final class ContactController
private function getSyliusAttribute(Request $request, string $attributeName, ?string $default): ?string
{
- $attributes = $request->attributes->get('_sylius');
+ $attributes = $request->attributes->get('_sylius', []);
return $attributes[$attributeName] ?? $default;
}
diff --git a/src/Sylius/Bundle/ShopBundle/Resources/private/scss/theme.scss b/src/Sylius/Bundle/ShopBundle/Resources/private/scss/theme.scss
index 53ca8b4292..51eec721f1 100644
--- a/src/Sylius/Bundle/ShopBundle/Resources/private/scss/theme.scss
+++ b/src/Sylius/Bundle/ShopBundle/Resources/private/scss/theme.scss
@@ -738,13 +738,15 @@ header .ui.three.column.stackable.grid > .column:nth-child(3) .button span {
}
[data-route="sylius_shop_cart_summary"] .ui.two.column.stackable.grid,
-[data-route="sylius_shop_cart_save"] .ui.two.column.stackable.grid {
+[data-route="sylius_shop_cart_save"] .ui.two.column.stackable.grid,
+[data-route="sylius_shop_cart_checkout"] .ui.two.column.stackable.grid {
margin-top: 20px;
margin-bottom: 10px;
}
[data-route="sylius_shop_cart_summary"] .ui.segment,
-[data-route="sylius_shop_cart_save"] .ui.segment {
+[data-route="sylius_shop_cart_save"] .ui.segment,
+[data-route="sylius_shop_cart_checkout"] .ui.segment {
border: 0;
box-shadow: none;
padding: 0;
@@ -752,6 +754,7 @@ header .ui.three.column.stackable.grid > .column:nth-child(3) .button span {
[data-route="sylius_shop_cart_summary"] .column:first-child .table tbody td,
[data-route="sylius_shop_cart_save"] .column:first-child .table tbody td,
+[data-route="sylius_shop_cart_checkout"] .column:first-child .table tbody td,
[data-route="sylius_shop_checkout_complete"] form table tbody td,
[data-route="sylius_shop_account_order_show"] table tbody td {
border: 0 !important;
@@ -761,6 +764,7 @@ header .ui.three.column.stackable.grid > .column:nth-child(3) .button span {
[data-route="sylius_shop_cart_summary"] .column:first-child .table thead th,
[data-route="sylius_shop_cart_save"] .column:first-child .table thead th,
+[data-route="sylius_shop_cart_checkout"] .column:first-child .table thead th,
[data-route="sylius_shop_checkout_complete"] form table thead th,
[data-route="sylius_shop_account_order_show"] table thead th {
border: none !important;
@@ -774,6 +778,8 @@ header .ui.three.column.stackable.grid > .column:nth-child(3) .button span {
[data-route="sylius_shop_cart_summary"] .column:first-child .table td:nth-child(2),
[data-route="sylius_shop_cart_save"] .column:first-child .table th:nth-child(2),
[data-route="sylius_shop_cart_save"] .column:first-child .table td:nth-child(2),
+[data-route="sylius_shop_cart_checkout"] .column:first-child .table th:nth-child(2),
+[data-route="sylius_shop_cart_checkout"] .column:first-child .table td:nth-child(2),
[data-route="sylius_shop_checkout_complete"] form table th:nth-child(2),
[data-route="sylius_shop_checkout_complete"] form table td:nth-child(2),
[data-route="sylius_shop_account_order_show"] table th:nth-child(2),
@@ -784,6 +790,7 @@ header .ui.three.column.stackable.grid > .column:nth-child(3) .button span {
[data-route="sylius_shop_cart_summary"] .column:first-child .table th:nth-child(3),
[data-route="sylius_shop_cart_save"] .column:first-child .table th:nth-child(3),
+[data-route="sylius_shop_cart_checkout"] .column:first-child .table th:nth-child(3),
[data-route="sylius_shop_checkout_complete"] form table th:nth-child(3),
[data-route="sylius_shop_account_order_show"] table th:nth-child(3) {
text-align: left !important;
@@ -792,6 +799,7 @@ header .ui.three.column.stackable.grid > .column:nth-child(3) .button span {
[data-route="sylius_shop_cart_summary"] .column:first-child .table th:nth-child(4),
[data-route="sylius_shop_cart_save"] .column:first-child .table th:nth-child(4),
+[data-route="sylius_shop_cart_checkout"] .column:first-child .table th:nth-child(4),
[data-route="sylius_shop_checkout_complete"] form table th:nth-child(4),
[data-route="sylius_shop_account_order_show"] table th:nth-child(4) {
text-align: right !important;
@@ -799,6 +807,7 @@ header .ui.three.column.stackable.grid > .column:nth-child(3) .button span {
[data-route="sylius_shop_cart_summary"] .column:first-child .table .sylius-product-name,
[data-route="sylius_shop_cart_save"] .column:first-child .table .sylius-product-name,
+[data-route="sylius_shop_cart_checkout"] .column:first-child .table .sylius-product-name,
[data-route="sylius_shop_checkout_complete"] form table .sylius-product-name,
[data-route="sylius_shop_account_order_show"] table .sylius-product-name {
font-size: 0.9em !important;
@@ -808,6 +817,7 @@ header .ui.three.column.stackable.grid > .column:nth-child(3) .button span {
[data-route="sylius_shop_cart_summary"] .column:first-child .table .image,
[data-route="sylius_shop_cart_save"] .column:first-child .table .image,
+[data-route="sylius_shop_cart_checkout"] .column:first-child .table .image,
[data-route="sylius_shop_checkout_complete"] form table .image,
[data-route="sylius_shop_account_order_show"] table .image {
width: 100px !important;
@@ -817,29 +827,34 @@ header .ui.three.column.stackable.grid > .column:nth-child(3) .button span {
[data-route="sylius_shop_cart_summary"] .column:first-child .table .sub.header,
[data-route="sylius_shop_cart_save"] .column:first-child .table .sub.header,
+[data-route="sylius_shop_cart_checkout"] .column:first-child .table .sub.header,
[data-route="sylius_shop_checkout_complete"] form table .sub.header,
[data-route="sylius_shop_account_order_show"] table .sub.header {
color: #adadad !important;
}
[data-route="sylius_shop_cart_summary"] .column:first-child .table .sylius-cart-remove-button,
-[data-route="sylius_shop_cart_save"] .column:first-child .table .sylius-cart-remove-button {
+[data-route="sylius_shop_cart_save"] .column:first-child .table .sylius-cart-remove-button,
+[data-route="sylius_shop_cart_checkout"] .column:first-child .table .sylius-cart-remove-button {
background: transparent;
border: 1px solid #eee;
}
[data-route="sylius_shop_cart_summary"] .column:first-child .table .sylius-total,
-[data-route="sylius_shop_cart_save"] .column:first-child .table .sylius-total {
+[data-route="sylius_shop_cart_save"] .column:first-child .table .sylius-total,
+[data-route="sylius_shop_cart_checkout"] .column:first-child .table .sylius-total {
font-weight: 700;
}
[data-route="sylius_shop_cart_summary"] .five.column:last-child,
-[data-route="sylius_shop_cart_save"] .five.column:last-child {
+[data-route="sylius_shop_cart_save"] .five.column:last-child,
+[data-route="sylius_shop_cart_checkout"] .five.column:last-child {
padding-left: 50px !important;
}
[data-route="sylius_shop_cart_summary"] .column:last-child .ui.dividing.header,
-[data-route="sylius_shop_cart_save"] .column:last-child .ui.dividing.header {
+[data-route="sylius_shop_cart_save"] .column:last-child .ui.dividing.header,
+[data-route="sylius_shop_cart_checkout"] .column:last-child .ui.dividing.header {
border: 0;
font-size: 1em;
color: #999;
@@ -850,12 +865,14 @@ header .ui.three.column.stackable.grid > .column:nth-child(3) .button span {
}
[data-route="sylius_shop_cart_summary"] .column:last-child .table td,
-[data-route="sylius_shop_cart_save"] .column:last-child .table td {
+[data-route="sylius_shop_cart_save"] .column:last-child .table td,
+[data-route="sylius_shop_cart_checkout"] .column:last-child .table td {
padding: 16px 0;
}
[data-route="sylius_shop_cart_summary"] .column:last-child .table .header td,
-[data-route="sylius_shop_cart_save"] .column:last-child .table .header td {
+[data-route="sylius_shop_cart_save"] .column:last-child .table .header td,
+[data-route="sylius_shop_cart_checkout"] .column:last-child .table .header td {
padding-top: 30px !important;
}
diff --git a/src/Sylius/Bundle/TaxonomyBundle/Resources/config/validation/TaxonTranslation.xml b/src/Sylius/Bundle/TaxonomyBundle/Resources/config/validation/TaxonTranslation.xml
index e6ca4276c6..125d16e398 100644
--- a/src/Sylius/Bundle/TaxonomyBundle/Resources/config/validation/TaxonTranslation.xml
+++ b/src/Sylius/Bundle/TaxonomyBundle/Resources/config/validation/TaxonTranslation.xml
@@ -30,6 +30,16 @@
+
+
+
+
+
+
+
+
+
+