Skip to content

Commit

Permalink
Merge pull request #2672 from woocommerce/product_batch_api_sync
Browse files Browse the repository at this point in the history
Create/Update products sync to facebook with Batch API
  • Loading branch information
rahulraina7 authored Jan 2, 2024
2 parents d4994f6 + f93922f commit 18d93b5
Show file tree
Hide file tree
Showing 4 changed files with 311 additions and 152 deletions.
112 changes: 108 additions & 4 deletions facebook-commerce.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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' )
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -1222,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 );
Expand All @@ -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 '';
}
Expand Down Expand Up @@ -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 );
Expand Down Expand Up @@ -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 <a href="https://facebook.com/' . $fb_product_item_id .
'" target="_blank">' . $fb_product_item_id . '</a> on Facebook.'
);
} else {
$this->display_error_message(
'Updated product <a href="https://facebook.com/' . $fb_product_item_id .
'" target="_blank">' . $fb_product_item_id . '</a> 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.
*
Expand Down Expand Up @@ -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 <a href="https://facebook.com/' . $fb_product_item_id .
'" target="_blank">' . $fb_product_item_id . '</a> on Facebook.'
);
}
}


/**
* Checks the feed upload status (FBE v1.0).
Expand Down
99 changes: 4 additions & 95 deletions includes/Products/Sync/Background.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
*
Expand Down Expand Up @@ -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;
}
}
Loading

0 comments on commit 18d93b5

Please sign in to comment.