From 91262899042bf5306b98dfc9af0b4678b0a6cc4e Mon Sep 17 00:00:00 2001 From: Paulo Arromba <17236129+wavvves@users.noreply.github.com> Date: Tue, 28 Jun 2022 13:43:33 +0100 Subject: [PATCH 1/7] Added Cross-sells object and schema for the API Cart response. --- src/StoreApi/SchemaController.php | 1 + src/StoreApi/Schemas/ExtendSchema.php | 2 + src/StoreApi/Schemas/V1/CartSchema.php | 19 +++++ .../Schemas/V1/CrossSellsItemSchema.php | 75 +++++++++++++++++++ 4 files changed, 97 insertions(+) create mode 100644 src/StoreApi/Schemas/V1/CrossSellsItemSchema.php diff --git a/src/StoreApi/SchemaController.php b/src/StoreApi/SchemaController.php index cc0833f7c94..94ba7f7d284 100644 --- a/src/StoreApi/SchemaController.php +++ b/src/StoreApi/SchemaController.php @@ -42,6 +42,7 @@ public function __construct( ExtendSchema $extend ) { Schemas\V1\CartCouponSchema::IDENTIFIER => Schemas\V1\CartCouponSchema::class, Schemas\V1\CartFeeSchema::IDENTIFIER => Schemas\V1\CartFeeSchema::class, Schemas\V1\CartItemSchema::IDENTIFIER => Schemas\V1\CartItemSchema::class, + Schemas\V1\CrossSellsItemSchema::IDENTIFIER => Schemas\V1\CrossSellsItemSchema::class, Schemas\V1\CartSchema::IDENTIFIER => Schemas\V1\CartSchema::class, Schemas\V1\CartExtensionsSchema::IDENTIFIER => Schemas\V1\CartExtensionsSchema::class, Schemas\V1\CheckoutSchema::IDENTIFIER => Schemas\V1\CheckoutSchema::class, diff --git a/src/StoreApi/Schemas/ExtendSchema.php b/src/StoreApi/Schemas/ExtendSchema.php index 413c5563e52..094087e2739 100644 --- a/src/StoreApi/Schemas/ExtendSchema.php +++ b/src/StoreApi/Schemas/ExtendSchema.php @@ -4,6 +4,7 @@ use Automattic\WooCommerce\StoreApi\Schemas\V1\CartItemSchema; use Automattic\WooCommerce\StoreApi\Schemas\V1\CartSchema; use Automattic\WooCommerce\StoreApi\Schemas\V1\CheckoutSchema; +use Automattic\WooCommerce\StoreApi\Schemas\V1\CrossSellsItemSchema; use Automattic\WooCommerce\StoreApi\Schemas\V1\ProductSchema; use Automattic\WooCommerce\StoreApi\Formatters; @@ -25,6 +26,7 @@ final class ExtendSchema { */ private $endpoints = [ CartItemSchema::IDENTIFIER, + CrossSellsItemSchema::IDENTIFIER, CartSchema::IDENTIFIER, CheckoutSchema::IDENTIFIER, ProductSchema::IDENTIFIER, diff --git a/src/StoreApi/Schemas/V1/CartSchema.php b/src/StoreApi/Schemas/V1/CartSchema.php index ff0727a7f22..9e2882a41af 100644 --- a/src/StoreApi/Schemas/V1/CartSchema.php +++ b/src/StoreApi/Schemas/V1/CartSchema.php @@ -40,6 +40,13 @@ class CartSchema extends AbstractSchema { */ public $coupon_schema; + /** + * Cross-sells item schema instance. + * + * @var CrossSellsItemSchema + */ + public $cross_sells_item_schema; + /** * Fee schema instance. * @@ -84,6 +91,7 @@ class CartSchema extends AbstractSchema { public function __construct( ExtendSchema $extend, SchemaController $controller ) { parent::__construct( $extend, $controller ); $this->item_schema = $this->controller->get( CartItemSchema::IDENTIFIER ); + $this->cross_sells_item_schema = $this->controller->get( CrossSellsItemSchema::IDENTIFIER ); $this->coupon_schema = $this->controller->get( CartCouponSchema::IDENTIFIER ); $this->fee_schema = $this->controller->get( CartFeeSchema::IDENTIFIER ); $this->shipping_rate_schema = $this->controller->get( CartShippingRateSchema::IDENTIFIER ); @@ -155,6 +163,16 @@ public function get_properties() { 'context' => [ 'view', 'edit' ], 'readonly' => true, ], + 'cross_sells' => [ + 'description' => __( 'List of cross-sells items related to cart items.', 'woo-gutenberg-products-block' ), + 'type' => 'array', + 'context' => [ 'view', 'edit' ], + 'readonly' => true, + 'items' => [ + 'type' => 'object', + 'properties' => $this->force_schema_readonly( $this->cross_sells_item_schema->get_properties() ), + ], + ], 'needs_payment' => [ 'description' => __( 'True if the cart needs payment. False for carts with only free products and no shipping costs.', 'woo-gutenberg-products-block' ), 'type' => 'boolean', @@ -331,6 +349,7 @@ public function get_item_response( $cart ) { 'items' => $this->get_item_responses_from_schema( $this->item_schema, $cart->get_cart() ), 'items_count' => $cart->get_cart_contents_count(), 'items_weight' => wc_get_weight( $cart->get_cart_contents_weight(), 'g' ), + 'cross_sells' => $this->get_item_responses_from_schema( $this->cross_sells_item_schema, $cart->get_cross_sells() ), 'needs_payment' => $cart->needs_payment(), 'needs_shipping' => $cart->needs_shipping(), 'has_calculated_shipping' => $has_calculated_shipping, diff --git a/src/StoreApi/Schemas/V1/CrossSellsItemSchema.php b/src/StoreApi/Schemas/V1/CrossSellsItemSchema.php new file mode 100644 index 00000000000..1c06fb87d57 --- /dev/null +++ b/src/StoreApi/Schemas/V1/CrossSellsItemSchema.php @@ -0,0 +1,75 @@ + $properties['key'], + 'id' => $properties['id'], + 'name' => $properties['name'], + 'permalink' => $properties['permalink'], + 'prices' => $properties['prices'], + 'images' => $properties['images'], + 'average_rating' => $properties['average_rating'], + self::EXTENDING_KEY => $this->get_extended_schema( self::IDENTIFIER ), + ]; + } + + /** + * Converts a WooCommerce product id into an object suitable for the response. + * + * @param integer $product_id Cross-sell product id. + * + * @return array + * @throws RouteException When unable to find cross-sells product by id. + */ + public function get_item_response( $product_id ) { + $product = wc_get_product( $product_id ); + + if ( ! $product instanceof \WC_Product || 0 === $product->get_id() ) { + throw new RouteException( + 'woocommerce_rest_product_invalid_id', + __( 'Invalid product ID for cross-sells block.', 'woo-gutenberg-products-block' ), + 500 + ); + } + + return [ + 'id' => $product->get_id(), + 'name' => $this->prepare_html_response( $product->get_title() ), + 'permalink' => $product->get_permalink(), + 'prices' => (object) $this->prepare_product_price_response( $product ), + 'average_rating' => (string) $product->get_average_rating(), + 'images' => $this->get_images( $product ), + self::EXTENDING_KEY => $this->get_extended_data( self::IDENTIFIER ), + ]; + } + +} From 81666d3e7f5030c36ee7d6ed2884fb02fadceba6 Mon Sep 17 00:00:00 2001 From: Paulo Arromba <17236129+wavvves@users.noreply.github.com> Date: Tue, 28 Jun 2022 14:40:03 +0100 Subject: [PATCH 2/7] Cross-sells are now WC_Product objects that pass through the visibility filter. --- src/StoreApi/Schemas/V1/CartSchema.php | 5 ++++- src/StoreApi/Schemas/V1/CrossSellsItemSchema.php | 10 ++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/StoreApi/Schemas/V1/CartSchema.php b/src/StoreApi/Schemas/V1/CartSchema.php index 9e2882a41af..05f50ff349c 100644 --- a/src/StoreApi/Schemas/V1/CartSchema.php +++ b/src/StoreApi/Schemas/V1/CartSchema.php @@ -341,6 +341,9 @@ public function get_item_response( $cart ) { // Get shipping packages to return in the response from the cart. $shipping_packages = $has_calculated_shipping ? $controller->get_shipping_packages() : []; + // Get visible cross sells products. + $cross_sells = array_filter( array_map( 'wc_get_product', $cart->get_cross_sells() ), 'wc_products_array_filter_visible' ); + return [ 'coupons' => $this->get_item_responses_from_schema( $this->coupon_schema, $cart->get_applied_coupons() ), 'shipping_rates' => $this->get_item_responses_from_schema( $this->shipping_rate_schema, $shipping_packages ), @@ -349,7 +352,7 @@ public function get_item_response( $cart ) { 'items' => $this->get_item_responses_from_schema( $this->item_schema, $cart->get_cart() ), 'items_count' => $cart->get_cart_contents_count(), 'items_weight' => wc_get_weight( $cart->get_cart_contents_weight(), 'g' ), - 'cross_sells' => $this->get_item_responses_from_schema( $this->cross_sells_item_schema, $cart->get_cross_sells() ), + 'cross_sells' => $this->get_item_responses_from_schema( $this->cross_sells_item_schema, $cross_sells ), 'needs_payment' => $cart->needs_payment(), 'needs_shipping' => $cart->needs_shipping(), 'has_calculated_shipping' => $has_calculated_shipping, diff --git a/src/StoreApi/Schemas/V1/CrossSellsItemSchema.php b/src/StoreApi/Schemas/V1/CrossSellsItemSchema.php index 1c06fb87d57..e80e54cb871 100644 --- a/src/StoreApi/Schemas/V1/CrossSellsItemSchema.php +++ b/src/StoreApi/Schemas/V1/CrossSellsItemSchema.php @@ -31,7 +31,6 @@ public function get_properties() { $properties = parent::get_properties(); return [ - 'key' => $properties['key'], 'id' => $properties['id'], 'name' => $properties['name'], 'permalink' => $properties['permalink'], @@ -43,15 +42,14 @@ public function get_properties() { } /** - * Converts a WooCommerce product id into an object suitable for the response. + * Converts a WooCommerce product into an object suitable for the response. * - * @param integer $product_id Cross-sell product id. + * @param object $product Cross-sell product. * * @return array - * @throws RouteException When unable to find cross-sells product by id. + * @throws RouteException When cross-sells product is invalid. */ - public function get_item_response( $product_id ) { - $product = wc_get_product( $product_id ); + public function get_item_response( $product ) { if ( ! $product instanceof \WC_Product || 0 === $product->get_id() ) { throw new RouteException( From 1066a2548275da7fb6183133ba0e6f8d8770cbb9 Mon Sep 17 00:00:00 2001 From: Paulo Arromba <17236129+wavvves@users.noreply.github.com> Date: Tue, 28 Jun 2022 14:42:41 +0100 Subject: [PATCH 3/7] Removed redundant check. --- src/StoreApi/Schemas/V1/CrossSellsItemSchema.php | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/StoreApi/Schemas/V1/CrossSellsItemSchema.php b/src/StoreApi/Schemas/V1/CrossSellsItemSchema.php index e80e54cb871..8c251145b58 100644 --- a/src/StoreApi/Schemas/V1/CrossSellsItemSchema.php +++ b/src/StoreApi/Schemas/V1/CrossSellsItemSchema.php @@ -47,18 +47,9 @@ public function get_properties() { * @param object $product Cross-sell product. * * @return array - * @throws RouteException When cross-sells product is invalid. */ public function get_item_response( $product ) { - if ( ! $product instanceof \WC_Product || 0 === $product->get_id() ) { - throw new RouteException( - 'woocommerce_rest_product_invalid_id', - __( 'Invalid product ID for cross-sells block.', 'woo-gutenberg-products-block' ), - 500 - ); - } - return [ 'id' => $product->get_id(), 'name' => $this->prepare_html_response( $product->get_title() ), From 6f3f82572124ef538ae95602895e01dedea873dd Mon Sep 17 00:00:00 2001 From: Paulo Arromba <17236129+wavvves@users.noreply.github.com> Date: Tue, 28 Jun 2022 14:43:26 +0100 Subject: [PATCH 4/7] Updated function doc comment. --- src/StoreApi/Schemas/V1/CrossSellsItemSchema.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/StoreApi/Schemas/V1/CrossSellsItemSchema.php b/src/StoreApi/Schemas/V1/CrossSellsItemSchema.php index 8c251145b58..ac76aa761a8 100644 --- a/src/StoreApi/Schemas/V1/CrossSellsItemSchema.php +++ b/src/StoreApi/Schemas/V1/CrossSellsItemSchema.php @@ -44,7 +44,7 @@ public function get_properties() { /** * Converts a WooCommerce product into an object suitable for the response. * - * @param object $product Cross-sell product. + * @param \WC_Product $product Cross-sell product. * * @return array */ From 3c4db88f1e6e5cf7298743980a2135495690b274 Mon Sep 17 00:00:00 2001 From: Paulo Arromba <17236129+wavvves@users.noreply.github.com> Date: Tue, 28 Jun 2022 14:43:45 +0100 Subject: [PATCH 5/7] Cleaned up imports. --- src/StoreApi/Schemas/V1/CrossSellsItemSchema.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/StoreApi/Schemas/V1/CrossSellsItemSchema.php b/src/StoreApi/Schemas/V1/CrossSellsItemSchema.php index ac76aa761a8..b1f1264d572 100644 --- a/src/StoreApi/Schemas/V1/CrossSellsItemSchema.php +++ b/src/StoreApi/Schemas/V1/CrossSellsItemSchema.php @@ -1,8 +1,6 @@ Date: Fri, 8 Jul 2022 00:59:29 +0100 Subject: [PATCH 6/7] Cross-sells item schema (extension of ProductSchema) was removed, and replaced by ProductSchema itself. Cross-sells are direct product representation, and extending this goes out of scope for the task at hand. --- src/StoreApi/SchemaController.php | 2 - src/StoreApi/Schemas/ExtendSchema.php | 2 - src/StoreApi/Schemas/V1/CartSchema.php | 6 +- .../Schemas/V1/CrossSellsItemSchema.php | 62 ------------------- 4 files changed, 3 insertions(+), 69 deletions(-) delete mode 100644 src/StoreApi/Schemas/V1/CrossSellsItemSchema.php diff --git a/src/StoreApi/SchemaController.php b/src/StoreApi/SchemaController.php index 94ba7f7d284..ecaab22799c 100644 --- a/src/StoreApi/SchemaController.php +++ b/src/StoreApi/SchemaController.php @@ -38,11 +38,9 @@ public function __construct( ExtendSchema $extend ) { Schemas\V1\BillingAddressSchema::IDENTIFIER => Schemas\V1\BillingAddressSchema::class, Schemas\V1\ShippingAddressSchema::IDENTIFIER => Schemas\V1\ShippingAddressSchema::class, Schemas\V1\CartShippingRateSchema::IDENTIFIER => Schemas\V1\CartShippingRateSchema::class, - Schemas\V1\CartShippingRateSchema::IDENTIFIER => Schemas\V1\CartShippingRateSchema::class, Schemas\V1\CartCouponSchema::IDENTIFIER => Schemas\V1\CartCouponSchema::class, Schemas\V1\CartFeeSchema::IDENTIFIER => Schemas\V1\CartFeeSchema::class, Schemas\V1\CartItemSchema::IDENTIFIER => Schemas\V1\CartItemSchema::class, - Schemas\V1\CrossSellsItemSchema::IDENTIFIER => Schemas\V1\CrossSellsItemSchema::class, Schemas\V1\CartSchema::IDENTIFIER => Schemas\V1\CartSchema::class, Schemas\V1\CartExtensionsSchema::IDENTIFIER => Schemas\V1\CartExtensionsSchema::class, Schemas\V1\CheckoutSchema::IDENTIFIER => Schemas\V1\CheckoutSchema::class, diff --git a/src/StoreApi/Schemas/ExtendSchema.php b/src/StoreApi/Schemas/ExtendSchema.php index 094087e2739..413c5563e52 100644 --- a/src/StoreApi/Schemas/ExtendSchema.php +++ b/src/StoreApi/Schemas/ExtendSchema.php @@ -4,7 +4,6 @@ use Automattic\WooCommerce\StoreApi\Schemas\V1\CartItemSchema; use Automattic\WooCommerce\StoreApi\Schemas\V1\CartSchema; use Automattic\WooCommerce\StoreApi\Schemas\V1\CheckoutSchema; -use Automattic\WooCommerce\StoreApi\Schemas\V1\CrossSellsItemSchema; use Automattic\WooCommerce\StoreApi\Schemas\V1\ProductSchema; use Automattic\WooCommerce\StoreApi\Formatters; @@ -26,7 +25,6 @@ final class ExtendSchema { */ private $endpoints = [ CartItemSchema::IDENTIFIER, - CrossSellsItemSchema::IDENTIFIER, CartSchema::IDENTIFIER, CheckoutSchema::IDENTIFIER, ProductSchema::IDENTIFIER, diff --git a/src/StoreApi/Schemas/V1/CartSchema.php b/src/StoreApi/Schemas/V1/CartSchema.php index 05f50ff349c..3fbcdf479dd 100644 --- a/src/StoreApi/Schemas/V1/CartSchema.php +++ b/src/StoreApi/Schemas/V1/CartSchema.php @@ -41,9 +41,9 @@ class CartSchema extends AbstractSchema { public $coupon_schema; /** - * Cross-sells item schema instance. + * Product item schema instance representing cross-sell items. * - * @var CrossSellsItemSchema + * @var ProductSchema */ public $cross_sells_item_schema; @@ -91,7 +91,7 @@ class CartSchema extends AbstractSchema { public function __construct( ExtendSchema $extend, SchemaController $controller ) { parent::__construct( $extend, $controller ); $this->item_schema = $this->controller->get( CartItemSchema::IDENTIFIER ); - $this->cross_sells_item_schema = $this->controller->get( CrossSellsItemSchema::IDENTIFIER ); + $this->cross_sells_item_schema = $this->controller->get( ProductSchema::IDENTIFIER ); $this->coupon_schema = $this->controller->get( CartCouponSchema::IDENTIFIER ); $this->fee_schema = $this->controller->get( CartFeeSchema::IDENTIFIER ); $this->shipping_rate_schema = $this->controller->get( CartShippingRateSchema::IDENTIFIER ); diff --git a/src/StoreApi/Schemas/V1/CrossSellsItemSchema.php b/src/StoreApi/Schemas/V1/CrossSellsItemSchema.php deleted file mode 100644 index b1f1264d572..00000000000 --- a/src/StoreApi/Schemas/V1/CrossSellsItemSchema.php +++ /dev/null @@ -1,62 +0,0 @@ - $properties['id'], - 'name' => $properties['name'], - 'permalink' => $properties['permalink'], - 'prices' => $properties['prices'], - 'images' => $properties['images'], - 'average_rating' => $properties['average_rating'], - self::EXTENDING_KEY => $this->get_extended_schema( self::IDENTIFIER ), - ]; - } - - /** - * Converts a WooCommerce product into an object suitable for the response. - * - * @param \WC_Product $product Cross-sell product. - * - * @return array - */ - public function get_item_response( $product ) { - - return [ - 'id' => $product->get_id(), - 'name' => $this->prepare_html_response( $product->get_title() ), - 'permalink' => $product->get_permalink(), - 'prices' => (object) $this->prepare_product_price_response( $product ), - 'average_rating' => (string) $product->get_average_rating(), - 'images' => $this->get_images( $product ), - self::EXTENDING_KEY => $this->get_extended_data( self::IDENTIFIER ), - ]; - } - -} From b7bd802fb87e5d33196255cf1fd70f6a804dea4b Mon Sep 17 00:00:00 2001 From: Paulo Arromba <17236129+wavvves@users.noreply.github.com> Date: Fri, 8 Jul 2022 02:03:20 +0100 Subject: [PATCH 7/7] Unit testing for cart response containing cross-sell products. --- tests/php/StoreApi/Routes/Cart.php | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/tests/php/StoreApi/Routes/Cart.php b/tests/php/StoreApi/Routes/Cart.php index 276ce6be1f0..025fe113d88 100644 --- a/tests/php/StoreApi/Routes/Cart.php +++ b/tests/php/StoreApi/Routes/Cart.php @@ -5,7 +5,6 @@ namespace Automattic\WooCommerce\Blocks\Tests\StoreApi\Routes; -use Automattic\WooCommerce\Blocks\Tests\StoreApi\Routes\ControllerTestCase; use Automattic\WooCommerce\Blocks\Tests\Helpers\FixtureData; use Automattic\WooCommerce\Blocks\Tests\Helpers\ValidateSchema; @@ -40,8 +39,21 @@ public function setUp() { 'weight' => 10, ) ), + + $fixtures->get_simple_product( + array( + 'name' => 'Test Product 3', + 'stock_status' => 'instock', + 'regular_price' => 10, + 'weight' => 10, + ) + ), ); + // Add product #3 as a cross-sell for product #1. + $this->products[0]->set_cross_sell_ids( array( $this->products[2]->get_id() ) ); + $this->products[0]->save(); + $this->coupon = $fixtures->get_coupon( array( 'code' => 'test_coupon', @@ -53,7 +65,7 @@ public function setUp() { wc_empty_cart(); $this->keys = array(); $this->keys[] = wc()->cart->add_to_cart( $this->products[0]->get_id(), 2 ); - $this->keys[] = wc()->cart->add_to_cart( $this->products[1]->get_id(), 1 ); + $this->keys[] = wc()->cart->add_to_cart( $this->products[1]->get_id() ); wc()->cart->apply_coupon( $this->coupon->get_code() ); // Draft order. @@ -78,6 +90,11 @@ public function test_get_item() { 'items' => function( $value ) { return count( $value ) === 2; }, + 'cross_sells' => array( + array( + 'id' => $this->products[2]->get_id(), + ), + ), 'totals' => array( 'currency_code' => 'USD', 'currency_minor_unit' => 2, @@ -475,6 +492,7 @@ public function test_prepare_item() { $this->assertArrayHasKey( 'needs_shipping', $data ); $this->assertArrayHasKey( 'items_weight', $data ); $this->assertArrayHasKey( 'totals', $data ); + $this->assertArrayHasKey( 'cross_sells', $data ); } /** @@ -483,7 +501,6 @@ public function test_prepare_item() { public function test_get_item_schema() { $routes = new \Automattic\WooCommerce\StoreApi\RoutesController( new \Automattic\WooCommerce\StoreApi\SchemaController( $this->mock_extend ) ); $controller = $routes->get( 'cart', 'v1' ); - $schema = $controller->get_item_schema(); $cart = wc()->cart; $response = $controller->prepare_item_for_response( $cart, new \WP_REST_Request() ); $schema = $controller->get_item_schema();