From 80bd331123bfd24b87f5926afe7f90599b14e27c Mon Sep 17 00:00:00 2001 From: Steffen Mahler Date: Fri, 29 May 2020 13:28:54 +0200 Subject: [PATCH] add function to remove inactive products from cart --- packages/Webkul/Checkout/src/Cart.php | 52 +++++- .../src/Listeners/CustomerEventsHandler.php | 14 ++ tests/unit/Checkout/Cart/CartCest.php | 151 ++++++++++++++++++ 3 files changed, 213 insertions(+), 4 deletions(-) create mode 100644 tests/unit/Checkout/Cart/CartCest.php diff --git a/packages/Webkul/Checkout/src/Cart.php b/packages/Webkul/Checkout/src/Cart.php index 635764533..dbe62c262 100755 --- a/packages/Webkul/Checkout/src/Cart.php +++ b/packages/Webkul/Checkout/src/Cart.php @@ -499,7 +499,7 @@ class Cart $this->saveAddressesWhenRequested($data, $billingAddressData, $shippingAddressData); $this->linkAddresses($cart, $billingAddressData, $shippingAddressData); - + $this->assignCustomerFields($cart); $cart->save(); @@ -765,7 +765,7 @@ class Cart return true; } - if (! $this->isItemsHaveSufficientQuantity()) { + if (! $this->checkCartItems()) { return true; } @@ -773,13 +773,19 @@ class Cart } /** - * Checks if all cart items have sufficient quantity. + * Checks all cart items for: + * - product is active (if not, cart item will be removed) + * - product has sufficient quantity * * @return bool */ - public function isItemsHaveSufficientQuantity(): bool + public function checkCartItems(): bool { foreach ($this->getCart()->items as $item) { + if ($this->removeInactiveItem($item)) { + continue; + } + if (! $this->isItemHaveQuantity($item)) { return false; } @@ -788,6 +794,22 @@ class Cart return true; } + /** + * Remove cart items, whose product is inactive + */ + public function removeInactiveItems() + { + if (! $cart = $this->getCart()) { + return; + } + + foreach ($cart->items as $item) { + if ($this->removeInactiveItem($item)) { + continue; + } + } + } + /** * Checks if all cart items have sufficient quantity. * @@ -1121,6 +1143,28 @@ class Cart return $attributes; } + /** + * Remove item from cart, whose product is inactive + * and returns true, if so. + */ + private function removeInactiveItem($item): bool + { + if (! $item) { + return false; + } + + if (! $this->getCart()) { + return false; + } + + if ($item->product && $item->product->status === 0) { + $this->removeItem($item->id); + return true; + } + + return false; + } + /** * @param array $data * @param array $billingAddress diff --git a/packages/Webkul/Checkout/src/Listeners/CustomerEventsHandler.php b/packages/Webkul/Checkout/src/Listeners/CustomerEventsHandler.php index a72116f26..ae0def7d6 100755 --- a/packages/Webkul/Checkout/src/Listeners/CustomerEventsHandler.php +++ b/packages/Webkul/Checkout/src/Listeners/CustomerEventsHandler.php @@ -19,6 +19,14 @@ class CustomerEventsHandler { Cart::mergeCart(); } + /** + * Handle cart changes + */ + public function onCartChanged() + { + Cart::removeInactiveItems(); + } + /** * Register the listeners for the subscriber. * @@ -28,5 +36,11 @@ class CustomerEventsHandler { public function subscribe($events) { $events->listen('customer.after.login', 'Webkul\Checkout\Listeners\CustomerEventsHandler@onCustomerLogin'); + + $events->listen('checkout.cart.add.before', 'Webkul\Checkout\Listeners\CustomerEventsHandler@onCartChanged'); + $events->listen('checkout.cart.item.add.before', 'Webkul\Checkout\Listeners\CustomerEventsHandler@onCartChanged'); + + $events->listen('checkout.cart.update.before', 'Webkul\Checkout\Listeners\CustomerEventsHandler@onCartChanged'); + $events->listen('checkout.cart.item.update.before', 'Webkul\Checkout\Listeners\CustomerEventsHandler@onCartChanged'); } } \ No newline at end of file diff --git a/tests/unit/Checkout/Cart/CartCest.php b/tests/unit/Checkout/Cart/CartCest.php new file mode 100644 index 000000000..e2ae38cdc --- /dev/null +++ b/tests/unit/Checkout/Cart/CartCest.php @@ -0,0 +1,151 @@ +faker = Factory::create(); + + $this->sessionToken = $this->faker->uuid; + session(['_token' => $this->sessionToken]); + + $this->simpleProduct1 = $I->haveProduct(Laravel5Helper::SIMPLE_PRODUCT); + cart()->addProduct($this->simpleProduct1->id, [ + '_token' => session('_token'), + 'product_id' => $this->simpleProduct1->id, + 'quantity' => 1, + ]); + + $this->simpleProduct2 = $I->haveProduct(Laravel5Helper::SIMPLE_PRODUCT); + cart()->addProduct($this->simpleProduct2->id, [ + '_token' => session('_token'), + 'product_id' => $this->simpleProduct2->id, + 'quantity' => 1, + ]); + + $this->virtualProduct1 = $I->haveProduct(Laravel5Helper::VIRTUAL_PRODUCT); + cart()->addProduct($this->virtualProduct1->id, [ + '_token' => session('_token'), + 'product_id' => $this->virtualProduct1->id, + 'quantity' => 1, + ]); + + $this->virtualProduct2 = $I->haveProduct(Laravel5Helper::VIRTUAL_PRODUCT); + cart()->addProduct($this->virtualProduct2->id, [ + '_token' => session('_token'), + 'product_id' => $this->virtualProduct2->id, + 'quantity' => 1, + ]); + + $this->downloadableProduct1 = $I->haveProduct(Laravel5Helper::DOWNLOADABLE_PRODUCT); + + $this->downloadableProduct2 = $I->haveProduct(Laravel5Helper::DOWNLOADABLE_PRODUCT); + } + + public function testCartWithInactiveProducts(UnitTester $I) + { + $I->comment('sP1, sP2, vP1 and vP2 in cart'); + $I->assertEquals(4, count(cart()->getCart()->items)); + + $I->comment('deactivate sP2'); + DB::table('product_attribute_values') + ->where([ + 'product_id' => $this->simpleProduct2->id, + 'attribute_id' => 8 // status + ]) + ->update(['boolean_value' => 0]); + + Event::dispatch('catalog.product.update.after', $this->simpleProduct2->refresh()); + + $I->assertFalse(cart()->hasError()); + $I->comment('sP2 is inactive'); + $I->assertEquals(3, count(cart()->getCart()->items)); + + $I->comment('add dP2 to cart'); + cart()->addProduct($this->downloadableProduct2->id, [ + '_token' => session('_token'), + 'product_id' => $this->downloadableProduct2->id, + 'quantity' => 1, + 'links' => $this->downloadableProduct2->downloadable_links->pluck('id')->all(), + ]); + + $I->assertFalse(cart()->hasError()); + $I->assertEquals(4, count(cart()->getCart()->items)); + + $I->comment('deactivate dP2'); + DB::table('product_attribute_values') + ->where([ + 'product_id' => $this->downloadableProduct2->id, + 'attribute_id' => 8 // status + ]) + ->update(['boolean_value' => 0]); + + Event::dispatch('catalog.product.update.after', $this->downloadableProduct2->refresh()); + + $I->comment('we still have 4 products in cart as we didn`t changed cart itself'); + $I->assertEquals(4, count(cart()->getCart()->items)); + + $I->comment('add dP1 to cart, dP2 should be removed now'); + cart()->addProduct($this->downloadableProduct1->id, [ + '_token' => session('_token'), + 'product_id' => $this->downloadableProduct1->id, + 'quantity' => 1, + 'links' => $this->downloadableProduct1->downloadable_links->pluck('id')->all(), + ]); + + $I->assertEquals(4, count(cart()->getCart()->items)); + + $I->comment('deactivate vP2'); + DB::table('product_attribute_values') + ->where([ + 'product_id' => $this->virtualProduct2->id, + 'attribute_id' => 8 // status + ]) + ->update(['boolean_value' => 0]); + + Event::dispatch('catalog.product.update.after', $this->virtualProduct2->refresh()); + + $I->comment('change quantity of vP1, vP2 should be removed now'); + $cartItemId = $this->getCartItemIdFromProduct($this->virtualProduct1->id); + cart()->updateItems([ + 'qty' => [ + $cartItemId => 5 + ], + ]); + $I->assertEquals(3, count(cart()->getCart()->items)); + + $I->assertEquals(5, cart()->getCart()->items()->find($cartItemId)->quantity); + } + + /** + * @param int $productId + * + * @return int|null + */ + private function getCartItemIdFromProduct(int $productId): ?int + { + foreach(cart()->getCart()->items as $item) { + if ($item->product_id === $productId) { + return $item->id; + } + } + + return null; + } +} \ No newline at end of file