diff --git a/src/class-actions.php b/src/class-actions.php index a5e73c0e..0b72aa23 100644 --- a/src/class-actions.php +++ b/src/class-actions.php @@ -60,7 +60,7 @@ use WPGraphQL\Extensions\WooCommerce\Mutation\Customer_Register; use WPGraphQL\Extensions\WooCommerce\Mutation\Customer_Update; use WPGraphQL\Extensions\WooCommerce\Mutation\Cart_Add_Item; use WPGraphQL\Extensions\WooCommerce\Mutation\Cart_Update_Item_Quantity; -use WPGraphQL\Extensions\WooCommerce\Mutation\Cart_Remove_Item; +use WPGraphQL\Extensions\WooCommerce\Mutation\Cart_Remove_Items; use WPGraphQL\Extensions\WooCommerce\Mutation\Cart_Restore_Item; use WPGraphQL\Extensions\WooCommerce\Mutation\Cart_Empty; use WPGraphQL\Extensions\WooCommerce\Mutation\Cart_Apply_Coupon; @@ -147,7 +147,7 @@ class Actions { Customer_Update::register_mutation(); Cart_Add_Item::register_mutation(); Cart_Update_Item_Quantity::register_mutation(); - Cart_Remove_Item::register_mutation(); + Cart_Remove_Items::register_mutation(); Cart_Restore_Item::register_mutation(); Cart_Empty::register_mutation(); Cart_Apply_Coupon::register_mutation(); diff --git a/src/data/mutation/class-cart-mutation.php b/src/data/mutation/class-cart-mutation.php index dbd33c5e..ef27dc8e 100644 --- a/src/data/mutation/class-cart-mutation.php +++ b/src/data/mutation/class-cart-mutation.php @@ -8,16 +8,18 @@ namespace WPGraphQL\Extensions\WooCommerce\Data\Mutation; +use GraphQL\Error\UserError; + /** * Class - Cart_Mutation */ class Cart_Mutation { /** - * Return array of data to be when defining a cart item. + * Returns a cart item. * - * @param array $input input data describing cart item. + * @param array $input Input data describing cart item. * @param AppContext $context AppContext instance. - * @param ResolveInfo $info query info. + * @param ResolveInfo $info Query info. * * @return array */ @@ -33,6 +35,37 @@ class Cart_Mutation { return apply_filters( 'woocommerce_new_cart_item_data', $cart_item_args, $input, $context, $info ); } + /** + * Returns an array of cart items. + * + * @param array $input Input data describing cart items. + * @param AppContext $context AppContext instance. + * @param ResolveInfo $info Query info. + * @param string $mutation Mutation type. + * + * @return array + * @throws UserError Cart item not found message. + */ + public static function retrieve_cart_items( $input, $context, $info, $mutation = '' ) { + if ( ! empty( $input['all'] ) && $input['all'] ) { + $items = array_values( \WC()->cart->get_cart() ); + } + + if ( ! empty( $input['keys'] ) && ! isset( $items ) ) { + $items = array(); + foreach ( $input['keys'] as $key ) { + $item = \WC()->cart->get_cart_item( $key ); + if ( empty( $item ) ) { + /* translators: Cart item not found message */ + throw new UserError( sprintf( __( 'No cart item found with the key: %s', 'wp-graphql-woocommerce' ), $key ) ); + } + $items[] = $item; + } + } + + return apply_filters( 'retrieve_cart_items', $items, $input, $context, $info, $mutation ); + } + /** * Return array of data to be when defining a cart fee. * diff --git a/src/mutation/class-cart-add-item.php b/src/mutation/class-cart-add-item.php index 0ad3c02a..55711283 100644 --- a/src/mutation/class-cart-add-item.php +++ b/src/mutation/class-cart-add-item.php @@ -99,11 +99,8 @@ class Cart_Add_Item { // Prepare args for "add_to_cart" from input data. $cart_item_args = Cart_Mutation::prepare_cart_item( $input, $context, $info ); - // Get WC_Cart instance. - $cart = WC()->cart; - // Add item to cart and get item key. - $cart_item_key = $cart->add_to_cart( ...$cart_item_args ); + $cart_item_key = \WC()->cart->add_to_cart( ...$cart_item_args ); // Return payload. return array( 'key' => $cart_item_key ); diff --git a/src/mutation/class-cart-remove-item.php b/src/mutation/class-cart-remove-item.php deleted file mode 100644 index 4d121aeb..00000000 --- a/src/mutation/class-cart-remove-item.php +++ /dev/null @@ -1,96 +0,0 @@ - self::get_input_fields(), - 'outputFields' => self::get_output_fields(), - 'mutateAndGetPayload' => self::mutate_and_get_payload(), - ) - ); - } - - /** - * Defines the mutation input field configuration - * - * @return array - */ - public static function get_input_fields() { - $input_fields = array( - 'key' => array( - 'type' => array( 'non_null' => 'ID' ), - 'description' => __( 'Cart item key of the item being removed', 'wp-graphql-woocommerce' ), - ), - ); - - return $input_fields; - } - - /** - * Defines the mutation output field configuration - * - * @return array - */ - public static function get_output_fields() { - return array( - 'cartItem' => array( - 'type' => 'CartItem', - 'resolve' => function ( $item ) { - return $item; - }, - ), - ); - } - - /** - * Defines the mutation data modification closure. - * - * @return callable - */ - public static function mutate_and_get_payload() { - return function( $input, AppContext $context, ResolveInfo $info ) { - // Retrieve product database ID if relay ID provided. - if ( empty( $input['key'] ) ) { - throw new UserError( __( 'No cart item key provided', 'wp-graphql-woocommerce' ) ); - } - - // Get WC_Cart instance. - $cart = WC()->cart; - - // Get cart item for payload. - $cart_item = $cart->get_cart_item( $input['key'] ); - if ( empty( $cart_item ) ) { - /* translators: Cart item not found message */ - throw new UserError( sprintf( __( 'No cart item found with the key: %s', 'wp-graphql-woocommerce' ), $input['key'] ) ); - } - - // Remove item from cart. - $cart->remove_cart_item( $input['key'] ); - - // Return payload. - return $cart_item; - }; - } -} diff --git a/src/mutation/class-cart-remove-items.php b/src/mutation/class-cart-remove-items.php new file mode 100644 index 00000000..8f2c8383 --- /dev/null +++ b/src/mutation/class-cart-remove-items.php @@ -0,0 +1,100 @@ + self::get_input_fields(), + 'outputFields' => self::get_output_fields(), + 'mutateAndGetPayload' => self::mutate_and_get_payload(), + ) + ); + } + + /** + * Defines the mutation input field configuration + * + * @return array + */ + public static function get_input_fields() { + $input_fields = array( + 'keys' => array( + 'type' => array( 'list_of' => 'ID' ), + 'description' => __( 'Item keys of the items being removed', 'wp-graphql-woocommerce' ), + ), + 'all' => array( + 'type' => 'Boolean', + 'description' => __( 'Remove all cart items', 'wp-graphql-woocommerce' ), + ), + ); + + return $input_fields; + } + + /** + * Defines the mutation output field configuration + * + * @return array + */ + public static function get_output_fields() { + return array( + 'cartItems' => array( + 'type' => array( 'list_of' => 'CartItem' ), + 'resolve' => function ( $payload ) { + return $payload['items']; + }, + ), + ); + } + + /** + * Defines the mutation data modification closure. + * + * @return callable + */ + public static function mutate_and_get_payload() { + return function( $input, AppContext $context, ResolveInfo $info ) { + if ( \WC()->cart->is_empty() ) { + throw new UserError( __( 'No items in cart to remove.', 'wp-graphql-woocommerce' ) ); + } + + if ( empty( $input['keys'] ) && empty( $input['all'] ) ) { + throw new UserError( __( 'No cart item keys provided', 'wp-graphql-woocommerce' ) ); + } + + $cart_items = Cart_Mutation::retrieve_cart_items( $input, $context, $info, 'remove' ); + foreach ( $cart_items as $item ) { + $success = \WC()->cart->remove_cart_item( $item['key'] ); + if ( false === $success ) { + /* translators: Cart item removal failure message */ + throw new UserError( sprintf( __( 'Failed to remove item %s from cart.', 'wp-graphql-woocommerce' ), $item['key'] ) ); + } + } + + // Return payload. + return array( 'items' => $cart_items ); + }; + } +} diff --git a/tests/wpunit/CartMutationsTest.php b/tests/wpunit/CartMutationsTest.php index df51c0cd..9573d7cf 100644 --- a/tests/wpunit/CartMutationsTest.php +++ b/tests/wpunit/CartMutationsTest.php @@ -24,7 +24,7 @@ class CartMutationsTest extends \Codeception\TestCase\WPTestCase { } private function addToCart( $input ) { - $mutation = ' + $mutation = ' mutation addToCart( $input: AddToCartInput! ) { addToCart( input: $input ) { clientMutationId @@ -46,12 +46,45 @@ class CartMutationsTest extends \Codeception\TestCase\WPTestCase { } '; - $variables = array( 'input' => $input ); - $actual = graphql( + $actual = graphql( array( 'query' => $mutation, 'operation_name' => 'addToCart', - 'variables' => $variables, + 'variables' => array( 'input' => $input ), + ) + ); + + return $actual; + } + + private function removeItemsFromCart( $input ) { + $mutation = ' + mutation removeItemsFromCart( $input: RemoveItemsFromCartInput! ) { + removeItemsFromCart( input: $input ) { + clientMutationId + cartItems { + key + product { + id + } + variation { + id + } + quantity + subtotal + subtotalTax + total + tax + } + } + } + '; + + $actual = graphql( + array( + 'query' => $mutation, + 'operation_name' => 'removeItemsFromCart', + 'variables' => array( 'input' => $input ), ) ); @@ -216,46 +249,28 @@ class CartMutationsTest extends \Codeception\TestCase\WPTestCase { $this->assertEqualSets( $expected, $actual ); } - public function testRemoveItemFromCartMutation() { + public function testRemoveItemsFromCartMutation() { $ids = $this->variation->create( $this->product->create_variable() ); - $cart = WC()->cart; - $cart_item = $cart->get_cart_item( - $cart->add_to_cart( $ids['product'], 2, $ids['variations'][0] ) - ); - - $mutation = ' - mutation removeItemFromCart( $input: RemoveItemFromCartInput! ) { - removeItemFromCart( input: $input ) { - clientMutationId - cartItem { - key - product { - id - } - variation { - id - } - quantity - subtotal - subtotalTax - total - tax - } - } - } - '; - - $variables = array( - 'input' => array( - 'clientMutationId' => 'someId', - 'key' => $cart_item['key'], - ), - ); - $actual = graphql( + $addToCart = $this->addToCart( array( - 'query' => $mutation, - 'operation_name' => 'removeItemFromCart', - 'variables' => $variables, + 'clientMutationId' => 'someId', + 'productId' => $ids['product'], + 'quantity' => 2, + 'variationId' => $ids['variations'][0], + ) + ); + + // Retrieve cart item key. + $this->assertArrayHasKey('data', $addToCart ); + $this->assertArrayHasKey('addToCart', $addToCart['data'] ); + $this->assertArrayHasKey('cartItem', $addToCart['data']['addToCart'] ); + $cartItem = $addToCart['data']['addToCart']['cartItem']; + $key = $cartItem['key']; + + $actual = $this->removeItemsFromCart( + array( + 'clientMutationId' => 'someId', + 'keys' => array( $key ), ) ); @@ -264,28 +279,136 @@ class CartMutationsTest extends \Codeception\TestCase\WPTestCase { $expected = array( 'data' => array( - 'removeItemFromCart' => array( + 'removeItemsFromCart' => array( 'clientMutationId' => 'someId', - 'cartItem' => array( - 'key' => $cart_item['key'], - 'product' => array( - 'id' => $this->product->to_relay_id( $cart_item['product_id'] ), - ), - 'variation' => array( - 'id' => $this->variation->to_relay_id( $cart_item['variation_id'] ), - ), - 'quantity' => $cart_item['quantity'], - 'subtotal' => floatval( $cart_item['line_subtotal'] ), - 'subtotalTax' => floatval( $cart_item['line_subtotal_tax'] ), - 'total' => floatval( $cart_item['line_total'] ), - 'tax' => floatval( $cart_item['line_tax'] ), - ), + 'cartItems' => array( $cartItem ), ), ), ); $this->assertEqualSets( $expected, $actual ); - $this->assertEmpty( \WC()->cart->get_cart_item( $cart_item['key'] ) ); + $this->assertEmpty( \WC()->cart->get_cart_item( $key ) ); + } + + public function testRemoveItemsFromCartMutationWithMultipleItems() { + // Create products + $ids = $this->variation->create( $this->product->create_variable() ); + + // Add item 1. + $addToCart = $this->addToCart( + array( + 'clientMutationId' => 'someId', + 'productId' => $ids['product'], + 'quantity' => 2, + 'variationId' => $ids['variations'][0], + ) + ); + + $this->assertArrayHasKey('data', $addToCart ); + $this->assertArrayHasKey('addToCart', $addToCart['data'] ); + $this->assertArrayHasKey('cartItem', $addToCart['data']['addToCart'] ); + $cartItem1 = $addToCart['data']['addToCart']['cartItem']; + $key1 = $cartItem1['key']; + + // Add item 2. + $addToCart = $this->addToCart( + array( + 'clientMutationId' => 'someId', + 'productId' => $ids['product'], + 'quantity' => 3, + 'variationId' => $ids['variations'][1], + ) + ); + + // Retrieve cart item key. + $this->assertArrayHasKey('data', $addToCart ); + $this->assertArrayHasKey('addToCart', $addToCart['data'] ); + $this->assertArrayHasKey('cartItem', $addToCart['data']['addToCart'] ); + $cartItem2 = $addToCart['data']['addToCart']['cartItem']; + $key2 = $cartItem2['key']; + + $actual = $this->removeItemsFromCart( + array( + 'clientMutationId' => 'someId', + 'keys' => array( $key1, $key2 ), + ) + ); + + // use --debug flag to view. + codecept_debug( $actual ); + + $expected = array( + 'data' => array( + 'removeItemsFromCart' => array( + 'clientMutationId' => 'someId', + 'cartItems' => array( $cartItem1, $cartItem2 ), + ), + ), + ); + + $this->assertEqualSets( $expected, $actual ); + $this->assertEmpty( \WC()->cart->get_cart_item( $key1 ) ); + $this->assertEmpty( \WC()->cart->get_cart_item( $key2 ) ); + } + + public function testRemoveItemsFromCartMutationUsingAllField() { + // Create products + $ids = $this->variation->create( $this->product->create_variable() ); + + // Add item 1. + $addToCart = $this->addToCart( + array( + 'clientMutationId' => 'someId', + 'productId' => $ids['product'], + 'quantity' => 2, + 'variationId' => $ids['variations'][0], + ) + ); + + $this->assertArrayHasKey('data', $addToCart ); + $this->assertArrayHasKey('addToCart', $addToCart['data'] ); + $this->assertArrayHasKey('cartItem', $addToCart['data']['addToCart'] ); + $cartItem1 = $addToCart['data']['addToCart']['cartItem']; + $key1 = $cartItem1['key']; + + // Add item 2. + $addToCart = $this->addToCart( + array( + 'clientMutationId' => 'someId', + 'productId' => $ids['product'], + 'quantity' => 3, + 'variationId' => $ids['variations'][1], + ) + ); + + // Retrieve cart item key. + $this->assertArrayHasKey('data', $addToCart ); + $this->assertArrayHasKey('addToCart', $addToCart['data'] ); + $this->assertArrayHasKey('cartItem', $addToCart['data']['addToCart'] ); + $cartItem2 = $addToCart['data']['addToCart']['cartItem']; + $key2 = $cartItem2['key']; + + $actual = $this->removeItemsFromCart( + array( + 'clientMutationId' => 'someId', + 'all' => true + ) + ); + + // use --debug flag to view. + codecept_debug( $actual ); + + $expected = array( + 'data' => array( + 'removeItemsFromCart' => array( + 'clientMutationId' => 'someId', + 'cartItems' => array( $cartItem1, $cartItem2 ), + ), + ), + ); + + $this->assertEqualSets( $expected, $actual ); + $this->assertTrue( \WC()->cart->is_empty() ); } public function testRestoreCartItemMutation() { diff --git a/vendor/autoload.php b/vendor/autoload.php index a848e664..52321b87 100644 --- a/vendor/autoload.php +++ b/vendor/autoload.php @@ -4,4 +4,4 @@ require_once __DIR__ . '/composer/autoload_real.php'; -return ComposerAutoloaderInit6435da3701bc6fd8e2fee24595606a11::getLoader(); +return ComposerAutoloaderInitc0b9c25b0e0e6597e0a2526e78def682::getLoader(); diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php index e00f0548..fad6160a 100644 --- a/vendor/composer/autoload_classmap.php +++ b/vendor/composer/autoload_classmap.php @@ -57,7 +57,7 @@ return array( 'WPGraphQL\\Extensions\\WooCommerce\\Mutation\\Cart_Apply_Coupon' => $baseDir . '/src/mutation/class-cart-apply-coupon.php', 'WPGraphQL\\Extensions\\WooCommerce\\Mutation\\Cart_Empty' => $baseDir . '/src/mutation/class-cart-empty.php', 'WPGraphQL\\Extensions\\WooCommerce\\Mutation\\Cart_Remove_Coupon' => $baseDir . '/src/mutation/class-cart-remove-coupon.php', - 'WPGraphQL\\Extensions\\WooCommerce\\Mutation\\Cart_Remove_Item' => $baseDir . '/src/mutation/class-cart-remove-item.php', + 'WPGraphQL\\Extensions\\WooCommerce\\Mutation\\Cart_Remove_Items' => $baseDir . '/src/mutation/class-cart-remove-items.php', 'WPGraphQL\\Extensions\\WooCommerce\\Mutation\\Cart_Restore_Item' => $baseDir . '/src/mutation/class-cart-restore-item.php', 'WPGraphQL\\Extensions\\WooCommerce\\Mutation\\Cart_Update_Item_Quantity' => $baseDir . '/src/mutation/class-cart-update-item-quantity.php', 'WPGraphQL\\Extensions\\WooCommerce\\Mutation\\Customer_Register' => $baseDir . '/src/mutation/class-customer-register.php', diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php index f2b532ed..1f7bfce5 100644 --- a/vendor/composer/autoload_real.php +++ b/vendor/composer/autoload_real.php @@ -2,7 +2,7 @@ // autoload_real.php @generated by Composer -class ComposerAutoloaderInit6435da3701bc6fd8e2fee24595606a11 +class ComposerAutoloaderInitc0b9c25b0e0e6597e0a2526e78def682 { private static $loader; @@ -19,15 +19,15 @@ class ComposerAutoloaderInit6435da3701bc6fd8e2fee24595606a11 return self::$loader; } - spl_autoload_register(array('ComposerAutoloaderInit6435da3701bc6fd8e2fee24595606a11', 'loadClassLoader'), true, true); + spl_autoload_register(array('ComposerAutoloaderInitc0b9c25b0e0e6597e0a2526e78def682', 'loadClassLoader'), true, true); self::$loader = $loader = new \Composer\Autoload\ClassLoader(); - spl_autoload_unregister(array('ComposerAutoloaderInit6435da3701bc6fd8e2fee24595606a11', 'loadClassLoader')); + spl_autoload_unregister(array('ComposerAutoloaderInitc0b9c25b0e0e6597e0a2526e78def682', 'loadClassLoader')); $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); if ($useStaticLoader) { require_once __DIR__ . '/autoload_static.php'; - call_user_func(\Composer\Autoload\ComposerStaticInit6435da3701bc6fd8e2fee24595606a11::getInitializer($loader)); + call_user_func(\Composer\Autoload\ComposerStaticInitc0b9c25b0e0e6597e0a2526e78def682::getInitializer($loader)); } else { $map = require __DIR__ . '/autoload_namespaces.php'; foreach ($map as $namespace => $path) { diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php index 7ffdc9da..a1ee1497 100644 --- a/vendor/composer/autoload_static.php +++ b/vendor/composer/autoload_static.php @@ -4,7 +4,7 @@ namespace Composer\Autoload; -class ComposerStaticInit6435da3701bc6fd8e2fee24595606a11 +class ComposerStaticInitc0b9c25b0e0e6597e0a2526e78def682 { public static $prefixLengthsPsr4 = array ( 'W' => @@ -72,7 +72,7 @@ class ComposerStaticInit6435da3701bc6fd8e2fee24595606a11 'WPGraphQL\\Extensions\\WooCommerce\\Mutation\\Cart_Apply_Coupon' => __DIR__ . '/../..' . '/src/mutation/class-cart-apply-coupon.php', 'WPGraphQL\\Extensions\\WooCommerce\\Mutation\\Cart_Empty' => __DIR__ . '/../..' . '/src/mutation/class-cart-empty.php', 'WPGraphQL\\Extensions\\WooCommerce\\Mutation\\Cart_Remove_Coupon' => __DIR__ . '/../..' . '/src/mutation/class-cart-remove-coupon.php', - 'WPGraphQL\\Extensions\\WooCommerce\\Mutation\\Cart_Remove_Item' => __DIR__ . '/../..' . '/src/mutation/class-cart-remove-item.php', + 'WPGraphQL\\Extensions\\WooCommerce\\Mutation\\Cart_Remove_Items' => __DIR__ . '/../..' . '/src/mutation/class-cart-remove-items.php', 'WPGraphQL\\Extensions\\WooCommerce\\Mutation\\Cart_Restore_Item' => __DIR__ . '/../..' . '/src/mutation/class-cart-restore-item.php', 'WPGraphQL\\Extensions\\WooCommerce\\Mutation\\Cart_Update_Item_Quantity' => __DIR__ . '/../..' . '/src/mutation/class-cart-update-item-quantity.php', 'WPGraphQL\\Extensions\\WooCommerce\\Mutation\\Customer_Register' => __DIR__ . '/../..' . '/src/mutation/class-customer-register.php', @@ -115,9 +115,9 @@ class ComposerStaticInit6435da3701bc6fd8e2fee24595606a11 public static function getInitializer(ClassLoader $loader) { return \Closure::bind(function () use ($loader) { - $loader->prefixLengthsPsr4 = ComposerStaticInit6435da3701bc6fd8e2fee24595606a11::$prefixLengthsPsr4; - $loader->prefixDirsPsr4 = ComposerStaticInit6435da3701bc6fd8e2fee24595606a11::$prefixDirsPsr4; - $loader->classMap = ComposerStaticInit6435da3701bc6fd8e2fee24595606a11::$classMap; + $loader->prefixLengthsPsr4 = ComposerStaticInitc0b9c25b0e0e6597e0a2526e78def682::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInitc0b9c25b0e0e6597e0a2526e78def682::$prefixDirsPsr4; + $loader->classMap = ComposerStaticInitc0b9c25b0e0e6597e0a2526e78def682::$classMap; }, null, ClassLoader::class); }