Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create/Update products sync to facebook with Batch API #2672

Merged
merged 3 commits into from
Jan 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 );
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we replacing all calls to update_product_item with update_product_item_batch_api? In that case, we still have update_product_item call being made here: https://github.com/woocommerce/facebook-for-woocommerce/blob/b8c26af50a7655f30ce9cb8e45a2f4ec92fb9681/facebook-commerce.php#L2996

Can you please verify if this needs to be replaced?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ibndawood - thank you for pointing this out. I am of the opinion that lets leave this function as it is, as this is merely changing the visibility of the product. Even though we could possible migrate to the new API, but that prepares an entire product again with a new Woo Product class and makes the code a bit more verbose IMO, because the current way of preparing a product for sending batch updates sends the entire product, instead of just say the visibility in this case.

Let me know what you think ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand. We can proceed without changing this function.

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 );
rahulraina7 marked this conversation as resolved.
Show resolved Hide resolved
}
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 ) {
rahulraina7 marked this conversation as resolved.
Show resolved Hide resolved
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 {
rahulraina7 marked this conversation as resolved.
Show resolved Hide resolved
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 ) {
rahulraina7 marked this conversation as resolved.
Show resolved Hide resolved
$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();
rahulraina7 marked this conversation as resolved.
Show resolved Hide resolved
return $handles;
}
}
Loading