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

Video in catalogs #2651

Closed
wants to merge 9 commits into from
48 changes: 47 additions & 1 deletion 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 @@ -1181,7 +1182,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 @@ -1471,6 +1472,51 @@ 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 );
$product['item_group_id'] = $product['retailer_id'];
$product_data = WC_Facebookcommerce_Utils::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,
]);

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
2 changes: 1 addition & 1 deletion includes/API.php
Original file line number Diff line number Diff line change
Expand Up @@ -589,7 +589,7 @@ public function update_plugin_version_configuration( string $external_business_i
* @return API\Response|API\ProductCatalog\ItemsBatch\Create\Response
* @throws ApiException
*/
public function send_item_updates( string $facebook_product_catalog_id, array $requests ) {
public function send_item_updates( string $facebook_product_catalog_id, array $requests ): API\ProductCatalog\ItemsBatch\Create\Response {
$request = new API\ProductCatalog\ItemsBatch\Create\Request( $facebook_product_catalog_id, $requests );
$this->set_response_handler( API\ProductCatalog\ItemsBatch\Create\Response::class );
return $this->perform_request( $request );
Expand Down
100 changes: 5 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,9 @@ 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;
// For some reason isset( $response->handles ) is false while isset( $response_handles is true
$handles = ( isset( $response_handles ) && is_array( $response_handles ) ) ? $response_handles : [];
return $handles;
}
}
33 changes: 33 additions & 0 deletions includes/fbproduct.php
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,29 @@ public function get_all_image_urls() {
}


public function get_all_video_urls() {

$video_urls = array();

$attached_videos = get_attached_media('video', $this->id);
if (empty($attached_videos)) {
return $video_urls;
}
foreach ($attached_videos as $video) {
$url = $video->guid;
array_push(
$video_urls,
array(
'url' => $url,
)
);
}

return $video_urls;

}


/**
* Gets the list of additional image URLs for the product from the complete list of image URLs.
*
Expand Down Expand Up @@ -591,6 +614,8 @@ public function prepare_product( $retailer_id = null, $type_to_prepare_for = sel
}
$image_urls = $this->get_all_image_urls();

$video_urls = $this->get_all_video_urls();

// Replace WordPress sanitization's ampersand with a real ampersand.
$product_url = str_replace(
'&amp%3B',
Expand Down Expand Up @@ -633,6 +658,9 @@ public function prepare_product( $retailer_id = null, $type_to_prepare_for = sel
);
$product_data = $this->add_sale_price( $product_data, true );
$gpc_field_name = 'google_product_category';
if ( ! empty( $video_urls ) ) {
$product_data['video'] = $video_urls;
}
} else {
$product_data = array(
'name' => WC_Facebookcommerce_Utils::clean_string( $this->get_title() ),
Expand All @@ -659,6 +687,11 @@ public function prepare_product( $retailer_id = null, $type_to_prepare_for = sel
'availability' => $this->is_in_stock() ? 'in stock' : 'out of stock',
'visibility' => Products::is_product_visible( $this->woo_product ) ? \WC_Facebookcommerce_Integration::FB_SHOP_PRODUCT_VISIBLE : \WC_Facebookcommerce_Integration::FB_SHOP_PRODUCT_HIDDEN,
);

if ( self::PRODUCT_PREP_TYPE_NORMAL !== $type_to_prepare_for && ! empty( $video_urls ) ) {
$product_data['video'] = $video_urls;
}

$product_data = $this->add_sale_price( $product_data );
$gpc_field_name = 'category';
}//end if
Expand Down
93 changes: 93 additions & 0 deletions includes/fbutils.php
Original file line number Diff line number Diff line change
Expand Up @@ -633,6 +633,99 @@ 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 2.0.0
*
* @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 2.0.0
*
* @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 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_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;
6 changes: 3 additions & 3 deletions tests/Unit/WCFacebookCommerceIntegrationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -823,9 +823,9 @@ public function test_on_product_publish_simple_product() {
$facebook_product_data['additional_image_urls'] = '';

$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->anything() )
->willReturn( new API\ProductCatalog\ItemsBatch\Create\Response( '{"success":true}' ) );

$this->integration->on_product_publish( $product->get_id() );
}
Expand Down
Loading