From b8c26af50a7655f30ce9cb8e45a2f4ec92fb9681 Mon Sep 17 00:00:00 2001 From: Rahul Raina Date: Thu, 14 Dec 2023 13:51:38 +0800 Subject: [PATCH 1/5] Add support for syncing(creating/updating) single products using Catalog Batch API --- facebook-commerce.php | 110 ++++++++++++++- includes/Products/Sync/Background.php | 99 +------------ includes/fbutils.php | 122 ++++++++++++++++ .../WCFacebookCommerceIntegrationTest.php | 130 +++++++++++------- 4 files changed, 310 insertions(+), 151 deletions(-) diff --git a/facebook-commerce.php b/facebook-commerce.php index 53d2cef57..f95b1cd41 100644 --- a/facebook-commerce.php +++ b/facebook-commerce.php @@ -16,6 +16,7 @@ use WooCommerce\Facebook\Framework\Plugin\Exception as PluginException; use WooCommerce\Facebook\Products; use WooCommerce\Facebook\Products\Feed; +use WooCommerce\Facebook\Products\Sync; defined( 'ABSPATH' ) || exit; @@ -314,6 +315,8 @@ public function __construct( WC_Facebookcommerce $facebook_for_woocommerce ) { add_action( 'add_meta_boxes', 'WooCommerce\Facebook\Admin\Product_Sync_Meta_Box::register', 10, 1 ); + add_action( 'add_meta_boxes', [ $this, 'display_batch_api_completed' ], 10, 2 ); + add_action( 'wp_ajax_ajax_fb_toggle_visibility', array( $this, 'ajax_fb_toggle_visibility' ) @@ -1175,7 +1178,7 @@ public function on_simple_product_publish( $wp_id, $woo_product = null, &$parent if ( $fb_product_item_id ) { $woo_product->fb_visibility = Products::is_product_visible( $woo_product->woo_product ); - $this->update_product_item( $woo_product, $fb_product_item_id ); + $this->update_product_item_batch_api( $woo_product, $fb_product_item_id ); return $fb_product_item_id; } else { // Check if this is a new product item for an existing product group @@ -1232,8 +1235,7 @@ public function create_product_simple( WC_Facebook_Product $woo_product, string } if ( $fb_product_group_id ) { - $fb_product_item_id = $this->create_product_item( $woo_product, $retailer_id, $fb_product_group_id ); - return $fb_product_item_id; + return $this->create_product_item_batch_api( $woo_product, $retailer_id, $fb_product_group_id ); } return ''; } @@ -1339,6 +1341,35 @@ public function update_product_group( WC_Facebook_Product $woo_product ) { } } + /** + * Creates a product item using the facebook Catalog Batch API. This replaces existing functionality, + * which is currently using facebook Product Item API implemented by `WC_Facebookcommerce_Integration::create_product_item` + * + * @since x.x.x + * @param WC_Facebook_Product $woo_product + * @param string $retailer_id + **/ + public function create_product_item_batch_api( $woo_product, $retailer_id, $product_group_id ): string { + try { + $product_data = $woo_product->prepare_product( $retailer_id, \WC_Facebook_Product::PRODUCT_PREP_TYPE_ITEMS_BATCH ); + $requests = WC_Facebookcommerce_Utils::prepare_product_requests_items_batch( $product_data ); + $facebook_catalog_id = $this->get_product_catalog_id(); + $response = $this->facebook_for_woocommerce->get_api()->send_item_updates( $facebook_catalog_id, $requests ); + + if ( $response->handles ) { + return ''; + } else { + $this->display_error_message( + 'Updated product on Facebook has failed.' + ); + } + } catch ( ApiException $e ) { + $message = sprintf( 'There was an error trying to create a product item: %s', $e->getMessage() ); + WC_Facebookcommerce_Utils::log( $message ); + } + return ''; + } + public function create_product_item( $woo_product, $retailer_id, $product_group_id ): string { try { $product_data = $woo_product->prepare_product( $retailer_id ); @@ -1465,6 +1496,37 @@ private function get_product_variation_attributes( array $variation ): array { return $final_attributes; } + /** + * Update existing product using batch API. + * + * @param WC_Facebook_Product $woo_product + * @param string $fb_product_item_id + * @return void + */ + public function update_product_item_batch_api( WC_Facebook_Product $woo_product, string $fb_product_item_id ): void { + $product = $woo_product->prepare_product( null, \WC_Facebook_Product::PRODUCT_PREP_TYPE_ITEMS_BATCH ); + $requests = WC_Facebookcommerce_Utils::prepare_product_requests_items_batch( $product ); + + try { + $facebook_catalog_id = $this->get_product_catalog_id(); + $response = $this->facebook_for_woocommerce->get_api()->send_item_updates( $facebook_catalog_id, $requests ); + if ( $response->handles ) { + $this->display_success_message( + 'Updated product ' . $fb_product_item_id . ' on Facebook.' + ); + } else { + $this->display_error_message( + 'Updated product ' . $fb_product_item_id . ' on Facebook has failed.' + ); + } + } catch ( ApiException $e ) { + $message = sprintf( 'There was an error trying to update a product item: %s', $e->getMessage() ); + WC_Facebookcommerce_Utils::log( $message ); + } + } + /** * Update existing product. * @@ -1550,6 +1612,48 @@ public function delete_product_set_item( string $fb_product_set_id ) { } } + /** + * Displays Batch API completed message on simple_product_publish. + * This is called by the hook `add_meta_boxes` because that is sufficient time + * to retrieve product_item_id for the product item created via batch API. + * + * Some sanity checks are added before displaying the message after publish + * - product_item_id : if exists, means product was created else not and don't display + * - should_sync: Don't display if the product is not supposed to be synced. + * + * @param string $post_type Wordpress Post type + * @param WP_Post $post Wordpress Post + * @return void + */ + public function display_batch_api_completed( string $post_type, WP_Post $post ) { + $fb_product = new \WC_Facebook_Product( $post->ID ); + $fb_product_item_id = null; + $should_sync = true; + $no_sync_reason = ''; + + if ( $fb_product->woo_product instanceof \WC_Product ) { + try { + facebook_for_woocommerce()->get_product_sync_validator( $fb_product->woo_product )->validate(); + } catch ( \Exception $e ) { + $should_sync = false; + $no_sync_reason = $e->getMessage(); + } + } + if( $should_sync ) { + if ( $fb_product->woo_product->is_type( 'variable' ) ) { + $fb_product_item_id = $this->get_product_fbid( self::FB_PRODUCT_GROUP_ID, $post->ID, $fb_product->woo_product ); + } else { + $fb_product_item_id = $this->get_product_fbid( self::FB_PRODUCT_ITEM_ID, $post->ID, $fb_product->woo_product ); + } + } + if ( $fb_product_item_id ) { + $this->display_success_message( + 'Created product ' . $fb_product_item_id . ' on Facebook.' + ); + } + } + /** * Checks the feed upload status (FBE v1.0). diff --git a/includes/Products/Sync/Background.php b/includes/Products/Sync/Background.php index 0858fe524..ba8464e98 100644 --- a/includes/Products/Sync/Background.php +++ b/includes/Products/Sync/Background.php @@ -185,9 +185,9 @@ private function process_item_update( $prefixed_product_id ) { if ( ! Products::product_should_be_deleted( $product ) && Products::product_should_be_synced( $product ) ) { if ( $product->is_type( 'variation' ) ) { - $product_data = $this->prepare_product_variation_data( $product ); + $product_data = \WC_Facebookcommerce_Utils::prepare_product_variation_data_items_batch( $product ); } else { - $product_data = $this->prepare_product_data( $product ); + $product_data = \WC_Facebookcommerce_Utils::prepare_product_data_items_batch( $product ); } // extract the retailer_id @@ -217,98 +217,6 @@ private function process_item_update( $prefixed_product_id ) { return $request; } - /** - * Prepares the data for a product variation to be included in a sync request. - * - * @since 2.0.0 - * - * @param \WC_Product $product product object - * @return array - * @throws PluginException In case no product found. - */ - private function prepare_product_variation_data( $product ) { - $parent_product = wc_get_product( $product->get_parent_id() ); - - if ( ! $parent_product instanceof \WC_Product ) { - throw new PluginException( "No parent product found with ID equal to {$product->get_parent_id()}." ); - } - - $fb_parent_product = new \WC_Facebook_Product( $parent_product->get_id() ); - $fb_product = new \WC_Facebook_Product( $product->get_id(), $fb_parent_product ); - - $data = $fb_product->prepare_product( null, \WC_Facebook_Product::PRODUCT_PREP_TYPE_ITEMS_BATCH ); - - // product variations use the parent product's retailer ID as the retailer product group ID - // $data['retailer_product_group_id'] = \WC_Facebookcommerce_Utils::get_fb_retailer_id( $parent_product ); - $data['item_group_id'] = \WC_Facebookcommerce_Utils::get_fb_retailer_id( $parent_product ); - - return $this->normalize_product_data( $data ); - } - - /** - * Normalizes product data to be included in a sync request. /items_batch - * rather than /batch this time. - * - * @since 2.0.0 - * - * @param array $data product data. - * @return array - */ - private function normalize_product_data( $data ) { - // Allowed values are 'refurbished', 'used', and 'new', but the plugin has always used the latter. - $data['condition'] = 'new'; - // Attributes other than size, color, pattern, or gender need to be included in the additional_variant_attributes field. - if ( isset( $data['custom_data'] ) && is_array( $data['custom_data'] ) ) { - $attributes = []; - foreach ( $data['custom_data'] as $key => $val ) { - - /** - * Filter: facebook_for_woocommerce_variant_attribute_comma_replacement - * - * The Facebook API expects a comma-separated list of attributes in `additional_variant_attribute` field. - * https://developers.facebook.com/docs/marketing-api/catalog/reference/ - * This means that WooCommerce product attributes included in this field should avoid the comma (`,`) character. - * Facebook for WooCommerce replaces any `,` with a space by default. - * This filter allows a site to provide a different replacement string. - * - * @since 2.5.0 - * - * @param string $replacement The default replacement string (`,`). - * @param string $value Attribute value. - * @return string Return the desired replacement string. - */ - $attribute_value = str_replace( - ',', - apply_filters( 'facebook_for_woocommerce_variant_attribute_comma_replacement', ' ', $val ), - $val - ); - /** Force replacing , and : characters if those were not cleaned up by filters */ - $attributes[] = str_replace( [ ',', ':' ], ' ', $key ) . ':' . str_replace( [ ',', ':' ], ' ', $attribute_value ); - } - - $data['additional_variant_attribute'] = implode( ',', $attributes ); - unset( $data['custom_data'] ); - } - - return $data; - } - - /** - * Prepares the product data to be included in a sync request. - * - * @since 2.0.0 - * - * @param \WC_Product $product product object - * @return array - */ - private function prepare_product_data( $product ) { - $fb_product = new \WC_Facebook_Product( $product->get_id() ); - $data = $fb_product->prepare_product( null, \WC_Facebook_Product::PRODUCT_PREP_TYPE_ITEMS_BATCH ); - // products that are not variations use their retailer retailer ID as the retailer product group ID - $data['item_group_id'] = $data['retailer_id']; - return $this->normalize_product_data( $data ); - } - /** * Processes a DELETE sync request for the given product. * @@ -342,7 +250,8 @@ private function process_item_delete( $prefixed_retailer_id ) { private function send_item_updates( array $requests ): array { $facebook_catalog_id = facebook_for_woocommerce()->get_integration()->get_product_catalog_id(); $response = facebook_for_woocommerce()->get_api()->send_item_updates( $facebook_catalog_id, $requests ); - $handles = ( isset( $response->handles ) && is_array( $response->handles ) ) ? $response->handles : []; + $response_handles = $response->handles; + $handles = ( isset( $response_handles ) && is_array( $response_handles ) ) ? $response_handles : array(); return $handles; } } diff --git a/includes/fbutils.php b/includes/fbutils.php index 64f81c57e..3116e0949 100644 --- a/includes/fbutils.php +++ b/includes/fbutils.php @@ -14,6 +14,7 @@ use WooCommerce\Facebook\Events\AAMSettings; use WooCommerce\Facebook\Events\Normalizer; use WooCommerce\Facebook\Framework\Api\Exception as ApiException; +use WooCommerce\Facebook\Products\Sync; if ( ! class_exists( 'WC_Facebookcommerce_Utils' ) ) : @@ -622,6 +623,127 @@ public static function get_cached_best_tip() { ); return $cached_best_tip; } + + /** + * Normalizes product data to be included in a sync request. /items_batch + * rather than /batch this time. + * + * @since x.x.x + * + * @param array $data product data. + * @return array + */ + public static function normalize_product_data_for_items_batch( $data ) { + // Allowed values are 'refurbished', 'used', and 'new', but the plugin has always used the latter. + $data['condition'] = 'new'; + // Attributes other than size, color, pattern, or gender need to be included in the additional_variant_attributes field. + if ( isset( $data['custom_data'] ) && is_array( $data['custom_data'] ) ) { + $attributes = []; + foreach ( $data['custom_data'] as $key => $val ) { + + /** + * Filter: facebook_for_woocommerce_variant_attribute_comma_replacement + * + * The Facebook API expects a comma-separated list of attributes in `additional_variant_attribute` field. + * https://developers.facebook.com/docs/marketing-api/catalog/reference/ + * This means that WooCommerce product attributes included in this field should avoid the comma (`,`) character. + * Facebook for WooCommerce replaces any `,` with a space by default. + * This filter allows a site to provide a different replacement string. + * + * @since 2.5.0 + * + * @param string $replacement The default replacement string (`,`). + * @param string $value Attribute value. + * @return string Return the desired replacement string. + */ + $attribute_value = str_replace( + ',', + apply_filters( 'facebook_for_woocommerce_variant_attribute_comma_replacement', ' ', $val ), + $val + ); + /** Force replacing , and : characters if those were not cleaned up by filters */ + $attributes[] = str_replace( [ ',', ':' ], ' ', $key ) . ':' . str_replace( [ ',', ':' ], ' ', $attribute_value ); + } + + $data['additional_variant_attribute'] = implode( ',', $attributes ); + unset( $data['custom_data'] ); + } + + return $data; + } + + /** + * Prepares the product data to be included in a sync request. + * + * @since x.x.x + * + * @param \WC_Product $product product object + * @return array + */ + public static function prepare_product_data_items_batch( $product ) { + $fb_product = new \WC_Facebook_Product( $product->get_id() ); + $data = $fb_product->prepare_product( null, \WC_Facebook_Product::PRODUCT_PREP_TYPE_ITEMS_BATCH ); + // products that are not variations use their retailer retailer ID as the retailer product group ID + $data['item_group_id'] = $data['retailer_id']; + return self::normalize_product_data_for_items_batch( $data ); + } + + /** + * Prepares the requests array to be included in a batch api request. + * + * @since x.x.x + * + * @param array $product Array + * @return array + */ + public static function prepare_product_requests_items_batch( $product ) { + $product['item_group_id'] = $product['retailer_id']; + $product_data = self::normalize_product_data_for_items_batch( $product ); + + // extract the retailer_id + $retailer_id = $product_data['retailer_id']; + + // NB: Changing this to get items_batch to work + // retailer_id cannot be included in the data object + unset( $product_data['retailer_id'] ); + $product_data['id'] = $retailer_id; + + $requests = array([ + 'method' => Sync::ACTION_UPDATE, + 'data' => $product_data, + ]); + + return $requests; + } + + /** + * Prepares the data for a product variation to be included in a sync request. + * + * @since x.x.x + * + * @param \WC_Product $product product object + * @return array + * @throws PluginException In case no product found. + */ + public static function prepare_product_variation_data_items_batch( $product ) { + $parent_product = wc_get_product( $product->get_parent_id() ); + + if ( ! $parent_product instanceof \WC_Product ) { + throw new PluginException( "No parent product found with ID equal to {$product->get_parent_id()}." ); + } + + $fb_parent_product = new \WC_Facebook_Product( $parent_product->get_id() ); + $fb_product = new \WC_Facebook_Product( $product->get_id(), $fb_parent_product ); + + $data = $fb_product->prepare_product( null, \WC_Facebook_Product::PRODUCT_PREP_TYPE_ITEMS_BATCH ); + + // product variations use the parent product's retailer ID as the retailer product group ID + // $data['retailer_product_group_id'] = \WC_Facebookcommerce_Utils::get_fb_retailer_id( $parent_product ); + $data['item_group_id'] = \WC_Facebookcommerce_Utils::get_fb_retailer_id( $parent_product ); + + return self::normalize_product_data_for_items_batch( $data ); + } + } endif; diff --git a/tests/Unit/WCFacebookCommerceIntegrationTest.php b/tests/Unit/WCFacebookCommerceIntegrationTest.php index 112fad037..cabb9837e 100644 --- a/tests/Unit/WCFacebookCommerceIntegrationTest.php +++ b/tests/Unit/WCFacebookCommerceIntegrationTest.php @@ -520,18 +520,23 @@ public function test_on_product_save_existing_simple_product_sync_enabled_update $product_to_update->set_meta_data( Products::VISIBILITY_META_KEY, true ); - $facebook_product = new WC_Facebook_Product( $product_to_update->get_id() ); - $facebook_product_data = $facebook_product->prepare_product(); - $facebook_product_data['additional_image_urls'] = ''; + $facebook_product = new WC_Facebook_Product( $product_to_update->get_id() ); + $facebook_product_data = $facebook_product->prepare_product(null, \WC_Facebook_Product::PRODUCT_PREP_TYPE_ITEMS_BATCH ); + $this->integration->product_catalog_id = '123123123123123123'; /* Data coming from _POST data. */ - $facebook_product_data['description'] = 'Facebook product description.'; - $facebook_product_data['price'] = 19900; - $facebook_product_data['category'] = 1718; + $facebook_product_data['description'] = 'Facebook product description.'; + $facebook_product_data['price'] = '199 USD'; + $facebook_product_data['google_product_category'] = 1718; + + $requests = WC_Facebookcommerce_Utils::prepare_product_requests_items_batch($facebook_product_data); $this->api->expects( $this->once() ) - ->method( 'update_product_item' ) - ->with( 'facebook-product-item-id', $facebook_product_data ) - ->willReturn( new API\ProductCatalog\Products\Update\Response( '{"success":true}' ) ); + ->method( 'send_item_updates' ) + ->with( + $this->integration->get_product_catalog_id(), + $requests + ) + ->willReturn( new API\ProductCatalog\ItemsBatch\Create\Response( '{"handles":"abcxyz"}' ) ); $this->integration->on_product_save( $product_to_update->get_id() ); @@ -721,9 +726,11 @@ public function test_fb_change_product_published_status_for_simple_product() { ->method( 'is_connected' ) ->willReturn( true ); - $product = WC_Helper_Product::create_simple_product(); - $facebook_product = new WC_Facebook_Product( $product ); - $product_data = $facebook_product->prepare_product(); + $product = WC_Helper_Product::create_simple_product(); + $facebook_product = new WC_Facebook_Product( $product ); + $product_data = $facebook_product->prepare_product(null, \WC_Facebook_Product::PRODUCT_PREP_TYPE_ITEMS_BATCH ); + $requests = WC_Facebookcommerce_Utils::prepare_product_requests_items_batch($product_data); + $this->integration->product_catalog_id = '123123123123123123'; if ( empty( $product_data['additional_image_urls'] ) ) { $product_data['additional_image_urls'] = ''; } @@ -737,9 +744,12 @@ public function test_fb_change_product_published_status_for_simple_product() { ->willReturn( $product_validator ); $this->api->expects( $this->once() ) - ->method( 'update_product_item' ) - ->with( 'facebook-product-id', $product_data ) - ->willReturn( new API\ProductCatalog\Products\Update\Response( '{"success":true}' ) ); + ->method( 'send_item_updates' ) + ->with( + $this->integration->get_product_catalog_id(), + $requests + ) + ->willReturn( new API\ProductCatalog\ItemsBatch\Create\Response( '{"handles":"abcxyz"}' ) ); /* Statuses involved into logic: publish, trash */ $new_status = 'publish'; @@ -818,14 +828,19 @@ public function test_on_product_publish_simple_product() { ->with( $product ) ->willReturn( $validator ); + $this->integration->product_catalog_id = '123123123123123123'; $facebook_product = new WC_Facebook_Product( $product->get_id() ); - $facebook_product_data = $facebook_product->prepare_product(); - $facebook_product_data['additional_image_urls'] = ''; + $facebook_product_data = $facebook_product->prepare_product(null, \WC_Facebook_Product::PRODUCT_PREP_TYPE_ITEMS_BATCH ); + + $requests = WC_Facebookcommerce_Utils::prepare_product_requests_items_batch($facebook_product_data); $this->api->expects( $this->once() ) - ->method( 'update_product_item' ) - ->with( 'facebook-product-item-id', $facebook_product_data ) - ->willReturn( new API\ProductCatalog\Products\Update\Response( '{"success":true}' ) ); + ->method( 'send_item_updates' ) + ->with( + $this->integration->get_product_catalog_id(), + $requests + ) + ->willReturn( new API\ProductCatalog\ItemsBatch\Create\Response( '{"handles":"abcxyz"}' ) ); $this->integration->on_product_publish( $product->get_id() ); } @@ -1043,12 +1058,18 @@ public function test_on_simple_product_publish_existing_product_updates_product( $facebook_product->woo_product->set_stock_status( 'instock' ); add_post_meta( $product->get_id(), WC_Facebookcommerce_Integration::FB_PRODUCT_ITEM_ID, 'facebook-simple-product-item-id' ); - $facebook_product_data = $facebook_product->prepare_product(); - $facebook_product_data['additional_image_urls'] = ''; + $this->integration->product_catalog_id = '123123123123123123'; + $facebook_product_data = $facebook_product->prepare_product(null, \WC_Facebook_Product::PRODUCT_PREP_TYPE_ITEMS_BATCH ); + + $requests = WC_Facebookcommerce_Utils::prepare_product_requests_items_batch($facebook_product_data); + $this->api->expects( $this->once() ) - ->method( 'update_product_item' ) - ->with( 'facebook-simple-product-item-id', $facebook_product_data ) - ->willReturn( new API\ProductCatalog\Products\Update\Response( '{"success":true}' ) ); + ->method( 'send_item_updates' ) + ->with( + $this->integration->get_product_catalog_id(), + $requests + ) + ->willReturn( new API\ProductCatalog\ItemsBatch\Create\Response( '{"handles":"abcxyz"}' ) ); $facebook_product_item_id = $this->integration->on_simple_product_publish( $product->get_id(), $facebook_product ); @@ -1063,8 +1084,10 @@ public function test_on_simple_product_publish_existing_product_updates_product( public function test_on_simple_product_publish_existing_product_creates_product() { add_option( WC_Facebookcommerce_Integration::OPTION_PRODUCT_CATALOG_ID, '1234567891011121314' ); - $product = WC_Helper_Product::create_simple_product(); - $facebook_product = new WC_Facebook_Product( $product->get_id() ); + $product = WC_Helper_Product::create_simple_product(); + $facebook_product = new WC_Facebook_Product( $product->get_id() ); + $facebook_product_data = $facebook_product->prepare_product(null, \WC_Facebook_Product::PRODUCT_PREP_TYPE_ITEMS_BATCH ); + $requests = WC_Facebookcommerce_Utils::prepare_product_requests_items_batch($facebook_product_data); /* Product should be synced with all its variations. So seven calls expected. */ $validator = $this->createMock( ProductValidator::class ); @@ -1087,16 +1110,15 @@ public function test_on_simple_product_publish_existing_product_creates_product( ) ->willReturn( new API\ProductCatalog\ProductGroups\Create\Response( '{"id":"facebook-simple-product-group-item-id"}' ) ); $this->api->expects( $this->once() ) - ->method( 'create_product_item' ) + ->method( 'send_item_updates' ) ->with( - 'facebook-simple-product-group-item-id', - $facebook_product->prepare_product( WC_Facebookcommerce_Utils::get_fb_retailer_id( $facebook_product ) ) + $this->integration->get_product_catalog_id(), + $requests ) - ->willReturn( new API\ProductCatalog\Products\Create\Response( '{"id":"facebook-simple-product-group-item-id"}' ) ); + ->willReturn( new API\ProductCatalog\ItemsBatch\Create\Response( '{"handles":"abcxyz"}' ) ); - $facebook_product_item_id = $this->integration->on_simple_product_publish( $product->get_id(), $facebook_product ); + $this->integration->on_simple_product_publish( $product->get_id(), $facebook_product ); - $this->assertEquals( 'facebook-simple-product-group-item-id', $facebook_product_item_id ); $this->assertEquals( 'facebook-simple-product-group-item-id', get_post_meta( $facebook_product->get_id(), WC_Facebookcommerce_Integration::FB_PRODUCT_GROUP_ID, true ) @@ -1154,9 +1176,11 @@ public function test_product_should_be_synced_calls_facebook_api_with_exception( public function test_create_product_simple_creates_product_group_before_creating_product_item() { add_option( WC_Facebookcommerce_Integration::OPTION_PRODUCT_CATALOG_ID, '123456789101112' ); - $product = WC_Helper_Product::create_simple_product(); - $facebook_product = new WC_Facebook_Product( $product->get_id() ); - $retailer_id = WC_Facebookcommerce_Utils::get_fb_retailer_id( $facebook_product ); + $product = WC_Helper_Product::create_simple_product(); + $facebook_product = new WC_Facebook_Product( $product->get_id() ); + $facebook_product_data = $facebook_product->prepare_product(null, \WC_Facebook_Product::PRODUCT_PREP_TYPE_ITEMS_BATCH ); + $requests = WC_Facebookcommerce_Utils::prepare_product_requests_items_batch($facebook_product_data); + $retailer_id = WC_Facebookcommerce_Utils::get_fb_retailer_id( $facebook_product ); $data = [ 'retailer_id' => $retailer_id, @@ -1166,17 +1190,17 @@ public function test_create_product_simple_creates_product_group_before_creating ->with( '123456789101112', $data ) ->willReturn( new API\ProductCatalog\ProductGroups\Create\Response( '{"id":"facebook-simple-product-group-id"}' ) ); - $data = $facebook_product->prepare_product( $retailer_id ); $this->api->expects( $this->once() ) - ->method( 'create_product_item' ) - ->with( 'facebook-simple-product-group-id', $data ) - ->willReturn( new API\ProductCatalog\Products\Create\Response( '{"id":"facebook-simple-product-item-id"}' ) ); + ->method( 'send_item_updates' ) + ->with( + $this->integration->get_product_catalog_id(), + $requests + ) + ->willReturn( new API\ProductCatalog\ItemsBatch\Create\Response( '{"handles":"abcxyz"}' ) ); - $facebook_product_item_id = $this->integration->create_product_simple( $facebook_product ); + $this->integration->create_product_simple( $facebook_product ); $this->assertEquals( 'facebook-simple-product-group-id', get_post_meta( $facebook_product->get_id(), WC_Facebookcommerce_Integration::FB_PRODUCT_GROUP_ID, true ) ); - $this->assertEquals( 'facebook-simple-product-item-id', get_post_meta( $facebook_product->get_id(), WC_Facebookcommerce_Integration::FB_PRODUCT_ITEM_ID, true ) ); - $this->assertEquals( 'facebook-simple-product-item-id', $facebook_product_item_id ); } /** @@ -1187,20 +1211,20 @@ public function test_create_product_simple_creates_product_group_before_creating public function test_create_product_simple_creates_product_with_provided_product_group_id() { add_option( WC_Facebookcommerce_Integration::OPTION_PRODUCT_CATALOG_ID, '123456789101112' ); - $product = WC_Helper_Product::create_simple_product(); - $facebook_product = new WC_Facebook_Product( $product->get_id() ); - $retailer_id = WC_Facebookcommerce_Utils::get_fb_retailer_id( $facebook_product ); + $product = WC_Helper_Product::create_simple_product(); + $facebook_product = new WC_Facebook_Product( $product->get_id() ); + $facebook_product_data = $facebook_product->prepare_product(null, \WC_Facebook_Product::PRODUCT_PREP_TYPE_ITEMS_BATCH ); + $requests = WC_Facebookcommerce_Utils::prepare_product_requests_items_batch($facebook_product_data); - $data = $facebook_product->prepare_product( $retailer_id ); $this->api->expects( $this->once() ) - ->method( 'create_product_item' ) - ->with( 'facebook-simple-product-group-id', $data ) - ->willReturn( new API\ProductCatalog\Products\Create\Response( '{"id":"facebook-simple-product-item-id"}' ) ); + ->method( 'send_item_updates' ) + ->with( + $this->integration->get_product_catalog_id(), + $requests + ) + ->willReturn( new API\ProductCatalog\ItemsBatch\Create\Response( '{"handles":"abcxyz"}' ) ); $facebook_product_item_id = $this->integration->create_product_simple( $facebook_product, 'facebook-simple-product-group-id' ); - - $this->assertEquals( 'facebook-simple-product-item-id', get_post_meta( $facebook_product->get_id(), WC_Facebookcommerce_Integration::FB_PRODUCT_ITEM_ID, true ) ); - $this->assertEquals( 'facebook-simple-product-item-id', $facebook_product_item_id ); } /** From 19d2232e34264f2d1aad4658f9872b92d386224c Mon Sep 17 00:00:00 2001 From: Rahul Raina Date: Wed, 20 Dec 2023 19:35:50 +0800 Subject: [PATCH 2/5] Apply suggestions from code review Code format Co-authored-by: Kader Ibrahim S --- facebook-commerce.php | 2 +- includes/fbutils.php | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/facebook-commerce.php b/facebook-commerce.php index f95b1cd41..6289cfd1d 100644 --- a/facebook-commerce.php +++ b/facebook-commerce.php @@ -1504,7 +1504,7 @@ private function get_product_variation_attributes( array $variation ): array { * @return void */ public function update_product_item_batch_api( WC_Facebook_Product $woo_product, string $fb_product_item_id ): void { - $product = $woo_product->prepare_product( null, \WC_Facebook_Product::PRODUCT_PREP_TYPE_ITEMS_BATCH ); + $product = $woo_product->prepare_product( null, \WC_Facebook_Product::PRODUCT_PREP_TYPE_ITEMS_BATCH ); $requests = WC_Facebookcommerce_Utils::prepare_product_requests_items_batch( $product ); try { diff --git a/includes/fbutils.php b/includes/fbutils.php index 3116e0949..adc34b34b 100644 --- a/includes/fbutils.php +++ b/includes/fbutils.php @@ -698,7 +698,7 @@ public static function prepare_product_data_items_batch( $product ) { */ public static function prepare_product_requests_items_batch( $product ) { $product['item_group_id'] = $product['retailer_id']; - $product_data = self::normalize_product_data_for_items_batch( $product ); + $product_data = self::normalize_product_data_for_items_batch( $product ); // extract the retailer_id $retailer_id = $product_data['retailer_id']; @@ -708,10 +708,10 @@ public static function prepare_product_requests_items_batch( $product ) { unset( $product_data['retailer_id'] ); $product_data['id'] = $retailer_id; - $requests = array([ + $requests = array( [ 'method' => Sync::ACTION_UPDATE, 'data' => $product_data, - ]); + ] ); return $requests; } From f93922fabf61e261b3bf52273a166e805adc2764 Mon Sep 17 00:00:00 2001 From: Rahul Raina Date: Wed, 20 Dec 2023 20:10:19 +0800 Subject: [PATCH 3/5] Remove outdated docbloc --- facebook-commerce.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/facebook-commerce.php b/facebook-commerce.php index 6289cfd1d..bde685859 100644 --- a/facebook-commerce.php +++ b/facebook-commerce.php @@ -1225,7 +1225,7 @@ public function product_should_be_synced( WC_Product $product ): bool { * * @param WC_Facebook_Product $woo_product * @param string|null $fb_product_group_id - * @return string facebook product item id + * @return string */ public function create_product_simple( WC_Facebook_Product $woo_product, string $fb_product_group_id = null ): string { $retailer_id = WC_Facebookcommerce_Utils::get_fb_retailer_id( $woo_product ); From 0d6295e8908f32dda57a958d85b8c63f204f85a9 Mon Sep 17 00:00:00 2001 From: Kader Ibrahim S Date: Wed, 3 Jan 2024 14:24:46 +0530 Subject: [PATCH 4/5] Product version bump update --- facebook-commerce.php | 2 +- facebook-for-woocommerce.php | 4 ++-- includes/fbutils.php | 8 ++++---- package-lock.json | 2 +- package.json | 2 +- readme.txt | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/facebook-commerce.php b/facebook-commerce.php index bde685859..12308e69d 100644 --- a/facebook-commerce.php +++ b/facebook-commerce.php @@ -1345,7 +1345,7 @@ public function update_product_group( WC_Facebook_Product $woo_product ) { * Creates a product item using the facebook Catalog Batch API. This replaces existing functionality, * which is currently using facebook Product Item API implemented by `WC_Facebookcommerce_Integration::create_product_item` * - * @since x.x.x + * @since 3.1.7 * @param WC_Facebook_Product $woo_product * @param string $retailer_id **/ diff --git a/facebook-for-woocommerce.php b/facebook-for-woocommerce.php index 563e5fb3e..dfbc3c730 100644 --- a/facebook-for-woocommerce.php +++ b/facebook-for-woocommerce.php @@ -11,7 +11,7 @@ * Description: Grow your business on Facebook! Use this official plugin to help sell more of your products using Facebook. After completing the setup, you'll be ready to create ads that promote your products and you can also create a shop section on your Page where customers can browse your products on Facebook. * Author: Facebook * Author URI: https://www.facebook.com/ - * Version: 3.1.6 + * Version: 3.1.7 * Requires at least: 5.6 * Text Domain: facebook-for-woocommerce * Tested up to: 6.4 @@ -44,7 +44,7 @@ class WC_Facebook_Loader { /** * @var string the plugin version. This must be in the main plugin file to be automatically bumped by Woorelease. */ - const PLUGIN_VERSION = '3.1.6'; // WRCS: DEFINED_VERSION. + const PLUGIN_VERSION = '3.1.7'; // WRCS: DEFINED_VERSION. // Minimum PHP version required by this plugin. const MINIMUM_PHP_VERSION = '7.4.0'; diff --git a/includes/fbutils.php b/includes/fbutils.php index de685cf28..58c920741 100644 --- a/includes/fbutils.php +++ b/includes/fbutils.php @@ -723,7 +723,7 @@ public static function get_cached_best_tip() { * Normalizes product data to be included in a sync request. /items_batch * rather than /batch this time. * - * @since x.x.x + * @since 3.1.7 * * @param array $data product data. * @return array @@ -770,7 +770,7 @@ public static function normalize_product_data_for_items_batch( $data ) { /** * Prepares the product data to be included in a sync request. * - * @since x.x.x + * @since 3.1.7 * * @param \WC_Product $product product object * @return array @@ -786,7 +786,7 @@ public static function prepare_product_data_items_batch( $product ) { /** * Prepares the requests array to be included in a batch api request. * - * @since x.x.x + * @since 3.1.7 * * @param array $product Array * @return array @@ -814,7 +814,7 @@ public static function prepare_product_requests_items_batch( $product ) { /** * Prepares the data for a product variation to be included in a sync request. * - * @since x.x.x + * @since 3.1.7 * * @param \WC_Product $product product object * @return array diff --git a/package-lock.json b/package-lock.json index cf5aa29f1..3e4eabdd3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "facebook-for-woocommerce", - "version": "3.1.6", + "version": "3.1.7", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 77783a700..e1ca0be4e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "facebook-for-woocommerce", - "version": "3.1.6", + "version": "3.1.7", "author": "Facebook", "homepage": "https://woo.com/products/facebook/", "license": "GPL-2.0", diff --git a/readme.txt b/readme.txt index ee2c63ce4..feded2ee0 100644 --- a/readme.txt +++ b/readme.txt @@ -3,7 +3,7 @@ Contributors: facebook, automattic, woothemes Tags: facebook, shop, catalog, advertise, pixel, product Requires at least: 4.4 Tested up to: 6.4 -Stable tag: 3.1.6 +Stable tag: 3.1.7 Requires PHP: 5.6 or greater MySQL: 5.6 or greater License: GPLv2 or later From 5d3ba78d641415624585f3b6432775ad97764865 Mon Sep 17 00:00:00 2001 From: Kader Ibrahim S Date: Wed, 3 Jan 2024 14:24:54 +0530 Subject: [PATCH 5/5] Changelog update --- changelog.txt | 3 +++ readme.txt | 3 +++ 2 files changed, 6 insertions(+) diff --git a/changelog.txt b/changelog.txt index 8c9bfeb80..7874f2b73 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,8 @@ *** Facebook for WooCommerce Changelog *** += 3.1.7 - 2024-01-03 = +* Add - Create/Update products sync to facebook with Batch API. + = 3.1.6 - 2023-12-27 = * Fix - Facebook Pixel events missing on redirect to cart. diff --git a/readme.txt b/readme.txt index feded2ee0..4b604c8af 100644 --- a/readme.txt +++ b/readme.txt @@ -40,6 +40,9 @@ When opening a bug on GitHub, please give us as many details as possible. == Changelog == += 3.1.7 - 2024-01-03 = +* Add - Create/Update products sync to facebook with Batch API. + = 3.1.6 - 2023-12-27 = * Fix - Facebook Pixel events missing on redirect to cart.