diff --git a/spec/Validator/InStockWishlistValidatorSpec.php b/spec/Validator/InStockWishlistValidatorSpec.php new file mode 100644 index 00000000..d3f529ce --- /dev/null +++ b/spec/Validator/InStockWishlistValidatorSpec.php @@ -0,0 +1,176 @@ +beConstructedWith($availabilityChecker, $requestStack, $router); + } + + public function it_is_initializable(): void + { + $this->shouldHaveType(InStockWishlistValidator::class); + } + + public function it_should_throw_exception_if_value_is_not_instance_of_command_interface( + Constraint $constraint, + stdClass $value, + ): void { + $this->shouldThrow(\Exception::class)->during( + 'validate', + [$value, $constraint], + ); + } + + public function it_should_throw_exception_if_constraint_is_not_instance_of_command_interface( + Constraint $constraint, + AddToCartCommandInterface $value, + ): void { + $this->shouldThrow(\Exception::class)->during( + 'validate', + [$value, $constraint], + ); + } + + public function it_should_throw_exception_if_request_is_null( + RequestStack $requestStack, + AddToCartCommandInterface $value, + ): void { + $constraint = new CartItemAvailability(); + + $requestStack->getCurrentRequest()->willReturn(null); + + $this->shouldThrow(\Exception::class)->during( + 'validate', + [$value, $constraint], + ); + } + + public function it_should_do_nothing_if_the_request_is_not_add_to_cart( + RequestStack $requestStack, + AddToCartCommandInterface $value, + Request $request, + Router $router, + ): void { + $constraint = new CartItemAvailability(); + $exampleArray = [ + '_route' => 'bitbag_sylius_wishlist_plugin_shop_locale_wishlist_remove_selected_products', + '_controller' => 'bitbag_sylius_wishlist_plugin.controller.action.remove_selected_products_from_wishlist', + '_locale' => 'en_US', + 'wishlistId' => '4', + ]; + + $requestStack->getCurrentRequest()->willReturn($request); + $request->getPathInfo()->willReturn('/en_US/wishlist/4/products/delete'); + $router->match('/en_US/wishlist/4/products/delete')->willReturn($exampleArray); + + $this->validate($value, $constraint)->shouldReturn(null); + } + + public function it_should_do_nothing_if_current_stock_is_not_empty( + AvailabilityCheckerInterface $availabilityChecker, + RequestStack $requestStack, + AddToCartCommandInterface $value, + Request $request, + Router $router, + OrderItemInterface $cartItem, + ExecutionContextInterface $context, + ProductVariantInterface $variant, + OrderInterface $order, + ): void { + $constraint = new CartItemAvailability(); + $exampleArray = ['_route' => 'bitbag_sylius_wishlist_plugin_shop_locale_wishlist_add_selected_products', + '_controller' => 'bitbag_sylius_wishlist_plugin.controller.action.add_selected_products_to_cart', + '_locale' => 'en_US', + 'wishlistId' => '4', + ]; + + $requestStack->getCurrentRequest()->willReturn($request); + $request->getPathInfo()->willReturn('/en_US/wishlist/4/products/add'); + $router->match('/en_US/wishlist/4/products/add')->willReturn($exampleArray); + $value->getCartItem()->willReturn($cartItem); + $cartItem->getVariant()->willReturn($variant); + $cartItem->getQuantity()->willReturn(5); + $value->getCart()->willReturn($order); + $itemsCollection = new ArrayCollection([$cartItem->getWrappedObject()]); + $order->getItems()->willReturn($itemsCollection); + $cartItem->equals(Argument::any())->willReturn(false); + $availabilityChecker->isStockSufficient($variant, 5)->willReturn(true); + + $context->addViolation(Argument::any(), Argument::any())->shouldNotBeCalled(); + + $this->validate($value, $constraint)->shouldReturn(null); + } + + public function it_should_do_build_violation_if_current_stock_is_empty_and_user_added_to_cart_item_from_wishlist( + AvailabilityCheckerInterface $availabilityChecker, + RequestStack $requestStack, + AddToCartCommandInterface $value, + Request $request, + Router $router, + OrderItemInterface $cartItem, + OrderInterface $order, + ProductVariantInterface $variant, + ExecutionContextInterface $context, + ): void { + $constraint = new CartItemAvailability(); + $constraint->message = 'sylius.cart_item.not_available'; + $exampleArray = ['_route' => 'bitbag_sylius_wishlist_plugin_shop_locale_wishlist_add_selected_products', + '_controller' => 'bitbag_sylius_wishlist_plugin.controller.action.add_selected_products_to_cart', + '_locale' => 'en_US', + 'wishlistId' => '4', + ]; + + $requestStack->getCurrentRequest()->willReturn($request); + $request->getPathInfo()->willReturn('/en_US/wishlist/4/products/add'); + $router->match('/en_US/wishlist/4/products/add')->willReturn($exampleArray); + $value->getCartItem()->willReturn($cartItem); + $cartItem->getVariant()->willReturn($variant); + $cartItem->getQuantity()->willReturn(5); + $value->getCart()->willReturn($order); + $itemsCollection = new ArrayCollection([$cartItem->getWrappedObject()]); + $order->getItems()->willReturn($itemsCollection); + $cartItem->equals(Argument::any())->willReturn(false); + $availabilityChecker->isStockSufficient($variant, 5)->willReturn(false); + $variant->getInventoryName()->willReturn('Red T-shirt'); + + $context->addViolation( + $constraint->message, + ['%itemName%' => 'Red T-shirt'], + )->shouldBeCalled(); + + $this->initialize($context); + $this->validate($value, $constraint); + } +} diff --git a/src/Resources/config/services/validator.xml b/src/Resources/config/services/validator.xml new file mode 100644 index 00000000..ea329c06 --- /dev/null +++ b/src/Resources/config/services/validator.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + diff --git a/src/Resources/config/validation/AddToCartCommand.xml b/src/Resources/config/validation/AddToCartCommand.xml new file mode 100644 index 00000000..7369f259 --- /dev/null +++ b/src/Resources/config/validation/AddToCartCommand.xml @@ -0,0 +1,12 @@ + + + + + + + + + + diff --git a/src/Validator/Constraints/InStockWishlistConstraint.php b/src/Validator/Constraints/InStockWishlistConstraint.php new file mode 100644 index 00000000..3e57c7b7 --- /dev/null +++ b/src/Validator/Constraints/InStockWishlistConstraint.php @@ -0,0 +1,31 @@ +requestStack->getCurrentRequest(); + + Assert::notNull($request); + + try { + $route = $this->router->match($request->getPathInfo()); + } catch (ResourceNotFoundException $exception) { + throw new AccessDeniedHttpException('Access denied'); + } + + if (array_key_exists('_route', $route) && + InStockWishlistConstraint::ADD_PRODUCTS_ROUTE !== $route['_route']) { + return; + } + + /** @var OrderItemInterface $cartItem */ + $cartItem = $value->getCartItem(); + + /** @var StockableInterface $variant */ + $variant = $cartItem->getVariant(); + + $isStockSufficient = $this->availabilityChecker->isStockSufficient( + $variant, + $cartItem->getQuantity() + $this->getExistingCartItemQuantityFromCart($value->getCart(), $cartItem), + ); + + if (!$isStockSufficient) { + $this->context->addViolation( + $constraint->message, + ['%itemName%' => $variant->getInventoryName()], + ); + } + } + + private function getExistingCartItemQuantityFromCart(OrderInterface $cart, OrderItemInterface $cartItem): int + { + foreach ($cart->getItems() as $existingCartItem) { + if ($existingCartItem->equals($cartItem)) { + return $existingCartItem->getQuantity(); + } + } + + return 0; + } +}