"variable_facebook_sync_mode$index",
- 'name' => "variable_facebook_sync_mode[$index]",
- 'label' => __( 'Facebook Sync', 'facebook-for-woocommerce' ),
- 'options' => array(
- self::SYNC_MODE_SYNC_AND_SHOW => __( 'Sync and show in catalog', 'facebook-for-woocommerce' ),
- self::SYNC_MODE_SYNC_AND_HIDE => __( 'Sync and hide in catalog', 'facebook-for-woocommerce' ),
- self::SYNC_MODE_SYNC_DISABLED => __( 'Do not sync', 'facebook-for-woocommerce' ),
- ),
- 'value' => $sync_mode,
- 'desc_tip' => true,
- 'description' => __( 'Choose whether to sync this product to Facebook and, if synced, whether it should be visible in the catalog.', 'facebook-for-woocommerce' ),
- 'class' => 'js-variable-fb-sync-toggle',
- 'wrapper_class' => 'form-row form-row-full',
- )
- );
-
woocommerce_wp_textarea_input(
array(
'id' => sprintf( 'variable_%s%s', \WC_Facebookcommerce_Integration::FB_PRODUCT_DESCRIPTION, $index ),
@@ -1642,7 +1620,8 @@ public function save_product_variation_edit_fields( $variation_id, $index ) {
if ( ! $variation instanceof \WC_Product_Variation ) {
return;
}
- $sync_mode = isset( $_POST['variable_facebook_sync_mode'][ $index ] ) ? wc_clean( wp_unslash( $_POST['variable_facebook_sync_mode'][ $index ] ) ) : self::SYNC_MODE_SYNC_DISABLED;
+ // phpcs:disable WordPress.Security.NonceVerification.Missing
+ $sync_mode = isset( $_POST['wc_facebook_sync_mode'] ) ? wc_clean( wp_unslash( $_POST['wc_facebook_sync_mode'] ) ) : self::SYNC_MODE_SYNC_DISABLED;
$sync_enabled = self::SYNC_MODE_SYNC_DISABLED !== $sync_mode;
if ( self::SYNC_MODE_SYNC_AND_SHOW === $sync_mode && $variation->is_virtual() ) {
// force to Sync and hide
diff --git a/includes/Admin/Settings_Screens/Product_Sync.php b/includes/Admin/Settings_Screens/Product_Sync.php
index 9efcc65ef..4bf899958 100644
--- a/includes/Admin/Settings_Screens/Product_Sync.php
+++ b/includes/Admin/Settings_Screens/Product_Sync.php
@@ -33,7 +33,6 @@ class Product_Sync extends Abstract_Settings_Screen {
/** @var string the get sync status action */
const ACTION_GET_SYNC_STATUS = 'wc_facebook_get_sync_status';
-
/**
* Connection constructor.
*/
diff --git a/includes/ExternalVersionUpdate/Update.php b/includes/ExternalVersionUpdate/Update.php
index a10398d37..c2d6e7aa5 100644
--- a/includes/ExternalVersionUpdate/Update.php
+++ b/includes/ExternalVersionUpdate/Update.php
@@ -82,7 +82,8 @@ public function send_new_version_to_facebook_server() {
// Send the request to the Meta server with the latest plugin version.
try {
$external_business_id = $plugin->get_connection_handler()->get_external_business_id();
- $response = $plugin->get_api()->update_plugin_version_configuration( $external_business_id, WC_Facebookcommerce_Utils::PLUGIN_VERSION );
+ $is_woo_all_product_opted_out = $plugin->get_plugin_render_handler()->is_master_sync_on() === false;
+ $response = $plugin->get_api()->update_plugin_version_configuration( $external_business_id, $is_woo_all_product_opted_out, WC_Facebookcommerce_Utils::PLUGIN_VERSION );
if ( $response->has_api_error() ) {
// If the request fails, we should retry it in the next heartbeat.
return false;
diff --git a/includes/Handlers/PluginRender.php b/includes/Handlers/PluginRender.php
new file mode 100644
index 000000000..594c3c7f6
--- /dev/null
+++ b/includes/Handlers/PluginRender.php
@@ -0,0 +1,201 @@
+plugin = $plugin;
+ $this->should_show_banners();
+ $this->add_hooks();
+ }
+
+ public function enqueue_assets() {
+ wp_enqueue_script( 'wc-backbone-modal', null, array( 'backbone' ) );
+ wp_enqueue_script(
+ 'facebook-for-woocommerce-modal',
+ facebook_for_woocommerce()->get_asset_build_dir_url() . '/admin/modal.js',
+ array( 'jquery', 'wc-backbone-modal', 'jquery-blockui' ),
+ \WC_Facebookcommerce::PLUGIN_VERSION
+ );
+ wp_enqueue_script(
+ 'facebook-for-woocommerce-plugin-update',
+ facebook_for_woocommerce()->get_asset_build_dir_url() . '/admin/plugin-rendering.js',
+ array( 'jquery', 'wc-backbone-modal', 'jquery-blockui', 'jquery-tiptip', 'facebook-for-woocommerce-modal', 'wc-enhanced-select' ),
+ \WC_Facebookcommerce::PLUGIN_VERSION,
+ );
+ wp_localize_script(
+ 'facebook-for-woocommerce-plugin-update',
+ 'facebook_for_woocommerce_plugin_update',
+ array(
+ 'ajax_url' => admin_url( 'admin-ajax.php' ),
+ 'set_excluded_terms_prompt_nonce' => wp_create_nonce( 'set-excluded-terms-prompt' ),
+ 'opt_out_of_sync' => wp_create_nonce( self::ACTION_OPT_OUT_OF_SYNC ),
+ 'banner_close' => wp_create_nonce( self::ACTION_CLOSE_BANNER ),
+ 'sync_back_in' => wp_create_nonce( self::ACTION_SYNC_BACK_IN ),
+ 'sync_in_progress' => Sync::is_sync_in_progress(),
+ 'opt_out_confirmation_message' => self::get_opt_out_modal_message(),
+ 'opt_out_confirmation_buttons' => self::get_opt_out_modal_buttons(),
+ )
+ );
+ }
+
+ private static function add_hooks() {
+ add_action( 'admin_enqueue_scripts', [ __CLASS__, 'enqueue_assets' ] );
+ add_action( 'wp_ajax_wc_facebook_opt_out_of_sync', [ __CLASS__, 'opt_out_of_sync_clicked' ] );
+ add_action( 'wp_ajax_nopriv_wc_facebook_opt_out_of_sync', [ __CLASS__,'opt_out_of_sync_clicked' ] );
+ add_action( 'wp_ajax_wc_banner_close_action', [ __CLASS__, 'reset_upcoming_version_banners' ] );
+ add_action( 'wp_ajax_nopriv_wc_banner_close_action', [ __CLASS__,'reset_upcoming_version_banners' ] );
+ }
+
+ public function should_show_banners() {
+ $current_version = $this->plugin->get_version();
+ /**
+ * Case when current version is less or equal to latest
+ * but latest is below 3.4.12
+ * Should show the opt in/ opt out banner
+ */
+ if ( version_compare( $current_version, self::ALL_PRODUCTS_PLUGIN_VERSION, '<' ) ) {
+ if ( get_transient( 'upcoming_woo_all_products_banner_hide' ) ) {
+ return;
+ }
+ add_action( 'admin_notices', [ __CLASS__, 'upcoming_woo_all_products_banner' ], 0, 1 );
+ }
+ }
+
+ public static function get_opt_out_time() {
+ $option_value = get_option( self::MASTER_SYNC_OPT_OUT_TIME );
+ if ( ! $option_value ) {
+ return '';
+ }
+ return $option_value;
+ }
+
+ public static function is_master_sync_on() {
+ $option_value = self::get_opt_out_time();
+ return '' === $option_value;
+ }
+
+ public function upcoming_woo_all_products_banner() {
+ $screen = get_current_screen();
+
+ if ( isset( $screen->id ) && 'marketing_page_wc-facebook' === $screen->id ) {
+ echo '
+ ';
+
+ echo '
+
You’ve opted out of automatic syncing on the next plugin update
+
+ Products that are not synced will not be available for your customers to discover on your ads and shops. To manually add products, learn how to sync products to your Meta catalog
+
+
';
+ }
+ }
+
+ public function opt_out_of_sync_clicked() {
+ $latest_date = gmdate( 'Y-m-d H:i:s' );
+ update_option( self::MASTER_SYNC_OPT_OUT_TIME, $latest_date );
+ wp_send_json_success( 'Opted out successfully' );
+ }
+
+ /**
+ * Banner for initmation of WooAllProducts version will show up
+ * after a week
+ */
+ public function reset_upcoming_version_banners() {
+ set_transient( 'upcoming_woo_all_products_banner_hide', true, 7 * DAY_IN_SECONDS );
+ }
+
+
+ private function get_opted_out_successfully_banner_class() {
+ $hidden = ! self::is_master_sync_on();
+ $opt_in_banner_class = 'notice notice-success is-dismissible';
+
+ if ( $hidden ) {
+ $opt_in_banner_class = 'notice notice-success is-dismissible';
+ } else {
+ $opt_in_banner_class = 'notice notice-success is-dismissible hidden';
+ }
+ return $opt_in_banner_class;
+ }
+
+ private function get_opt_out_banner_class() {
+ $hidden = ! self::is_master_sync_on();
+ $opt_out_banner_class = 'notice notice-info is-dismissible';
+
+ if ( $hidden ) {
+ $opt_out_banner_class = 'notice notice-info is-dismissible hidden';
+ } else {
+ $opt_out_banner_class = 'notice notice-info is-dismissible';
+ }
+ return $opt_out_banner_class;
+ }
+
+ private function get_opt_out_modal_message() {
+ return '
+
Opt out of automatic product sync?
+
+ If you opt out, we will not be syncing your products to your Meta catalog even after you update your Facebook for WooCommerce plugin.
+
+
+
+ However, we strongly recommend syncing all products to help drive sales and optimize ad performance. Products that aren’t synced will not be available for your customers to discover and buy in your ads and shops.
+
+
+
+ If you change your mind later, you can easily un-sync your products by going to WooCommerce > Products.
+
+ ';
+ }
+
+ private function get_opt_out_modal_buttons() {
+ return '
+
+ Opt out
+
+ ';
+ }
+}
diff --git a/includes/Jobs/DeleteProductsFromFBCatalog.php b/includes/Jobs/DeleteProductsFromFBCatalog.php
index f27f474a2..b2db9660f 100644
--- a/includes/Jobs/DeleteProductsFromFBCatalog.php
+++ b/includes/Jobs/DeleteProductsFromFBCatalog.php
@@ -75,11 +75,7 @@ protected function process_items( array $items, array $args ) {
foreach ( $items as $product_id ) {
$product = wc_get_product( $product_id );
// check if variable product
- if ( $product->is_type( 'variable' ) ) {
- $integration->delete_product_group( $product_id );
- } else {
- $integration->delete_product_item( $product_id );
- }
+ $integration->delete_product_item( $product_id );
// Reset product.
$integration->reset_single_product( $product_id );
diff --git a/includes/ProductSets/ProductSetSync.php b/includes/ProductSets/ProductSetSync.php
index 091ca6fc0..db97de18d 100644
--- a/includes/ProductSets/ProductSetSync.php
+++ b/includes/ProductSets/ProductSetSync.php
@@ -14,6 +14,7 @@
use WooCommerce\Facebook\RolloutSwitches;
use WooCommerce\Facebook\Utilities\Heartbeat;
+use WC_Facebookcommerce_Utils;
/**
* The product set sync handler.
@@ -103,6 +104,12 @@ public function on_delete_wc_product_category_callback( $term_id, $tt_id, $delet
*/
public function sync_all_product_sets() {
try {
+ $flag_name = '_wc_facebook_for_woocommerce_product_sets_sync_flag';
+ if ( 'yes' === get_transient( $flag_name ) ) {
+ return;
+ }
+ set_transient( $flag_name, 'yes', DAY_IN_SECONDS - 1 );
+
if ( ! $this->is_sync_enabled() ) {
return;
}
@@ -171,7 +178,7 @@ protected function build_fb_product_set_data( $wc_category ) {
$fb_product_set_metadata['cover_image_url'] = $wc_category_thumbnail_url;
}
if ( ! empty( $wc_category_description ) ) {
- $fb_product_set_metadata['description'] = $wc_category_description;
+ $fb_product_set_metadata['description'] = WC_Facebookcommerce_Utils::clean_string( $wc_category_description );
}
if ( ! empty( $wc_category_url ) ) {
$fb_product_set_metadata['external_url'] = $wc_category_url;
diff --git a/includes/ProductSync/ProductValidator.php b/includes/ProductSync/ProductValidator.php
index 06655fa9b..7c1a929f5 100644
--- a/includes/ProductSync/ProductValidator.php
+++ b/includes/ProductSync/ProductValidator.php
@@ -118,8 +118,8 @@ public function __get( $key ) {
*/
public function validate() {
$this->validate_sync_enabled_globally();
- $this->validate_product_status();
$this->validate_product_sync_field();
+ $this->validate_product_status();
$this->validate_product_visibility();
$this->validate_product_terms();
}
@@ -321,7 +321,10 @@ protected function validate_product_sync_field() {
if ( ! apply_filters( 'wc_facebook_should_sync_product', true, $this->product ) ) {
throw new ProductExcludedException( __( 'Product excluded by wc_facebook_should_sync_product filter.', 'facebook-for-woocommerce' ) );
}
-
+ /**
+ * The variable check will be used when we have create update of a product
+ * Either from Product details page or bulk editor
+ */
if ( $this->product->is_type( 'variable' ) ) {
foreach ( $this->product->get_children() as $child_id ) {
$child_product = wc_get_product( $child_id );
@@ -331,6 +334,39 @@ protected function validate_product_sync_field() {
}
}
+ // Variable product has no variations with sync enabled so it shouldn't be synced.
+ throw $invalid_exception;
+ } elseif ( $this->product->get_type() === 'variation' ) {
+ /**
+ * This check will run for background jobs like sync all and feeds
+ */
+ $parent_sync = $this->product_parent->get_meta( self::SYNC_ENABLED_META_KEY ) || null;
+
+ if ( 'yes' === $parent_sync ) {
+ return;
+ } elseif ( 'no' === $parent_sync ) {
+ throw $invalid_exception;
+ } else {
+ $variation_sync = false;
+ foreach ( $this->product_parent->get_children() as $child_id ) {
+ $child_product = wc_get_product( $child_id );
+ if ( $child_product && 'no' !== $child_product->get_meta( self::SYNC_ENABLED_META_KEY ) ) {
+ // At least one product is "sync-enabled" so bail before exception.
+ $variation_sync = true;
+ break;
+ }
+ }
+
+ /**
+ * Updating parent level sync for UI issues and
+ * Future variation checks for sync
+ */
+ update_post_meta( $this->product_parent->get_id(), self::SYNC_ENABLED_META_KEY, $variation_sync ? 'yes' : 'no' );
+ if ( $variation_sync ) {
+ return;
+ }
+ }
+
// Variable product has no variations with sync enabled so it shouldn't be synced.
throw $invalid_exception;
} elseif ( 'no' === $this->product->get_meta( self::SYNC_ENABLED_META_KEY ) ) {
diff --git a/includes/Products.php b/includes/Products.php
index 30ed52085..74607d2ef 100644
--- a/includes/Products.php
+++ b/includes/Products.php
@@ -85,11 +85,12 @@ private static function set_sync_for_products( array $products, $enabled ) {
$product_variation->save_meta_data();
}
}
- } else {
- $product->update_meta_data( self::SYNC_ENABLED_META_KEY, $enabled );
- $product->save_meta_data();
}
+ // Adding sync mode settings to simple product as well as main product for variants.
+ $product->update_meta_data( self::SYNC_ENABLED_META_KEY, $enabled );
+ $product->save_meta_data();
+
// Remove excluded product from FB.
if ( "no" === $enabled ) {
facebook_for_woocommerce()->get_integration()->delete_fb_product( $product );
@@ -186,7 +187,6 @@ public static function product_should_be_synced( \WC_Product $product ) {
}
}
-
/**
* Determines whether the given product should be synced assuming the product is published.
*
@@ -272,6 +272,18 @@ public static function set_product_visibility( \WC_Product $product, $visibility
if ( ! is_bool( $visibility ) ) {
return false;
}
+
+ // Updating visibility for the all variable products
+ if ( $product->is_type( 'variable' ) ) {
+ foreach ( $product->get_children() as $variation ) {
+ $product_variation = wc_get_product( $variation );
+ if ( $product_variation instanceof \WC_Product ) {
+ $product_variation->update_meta_data( self::VISIBILITY_META_KEY, wc_bool_to_string($visibility));
+ $product_variation->save_meta_data();
+ }
+ }
+ }
+
$product->update_meta_data( self::VISIBILITY_META_KEY, wc_bool_to_string( $visibility ) );
$product->save_meta_data();
self::$products_visibility[ $product->get_id() ] = $visibility;
diff --git a/includes/RolloutSwitches.php b/includes/RolloutSwitches.php
index 897930881..310b9ed67 100644
--- a/includes/RolloutSwitches.php
+++ b/includes/RolloutSwitches.php
@@ -44,7 +44,6 @@ public function init() {
return;
}
- // This is to avoid calling the API multiple times
$flag_name = '_wc_facebook_for_woocommerce_rollout_switch_flag';
if ( 'yes' === get_transient( $flag_name ) ) {
return;
diff --git a/includes/fbproduct.php b/includes/fbproduct.php
index 9abc0088a..08a421629 100644
--- a/includes/fbproduct.php
+++ b/includes/fbproduct.php
@@ -1676,6 +1676,65 @@ public function prepare_product( $retailer_id = null, $type_to_prepare_for = sel
$product_data[ 'material' ] = Helper::str_truncate( $this->get_fb_material(), 100 );
// $product_data[ 'woo_product_type' ] = $this->get_type();
// $product_data[ 'unmapped_attributes' ] = $this->get_unmapped_attributes();
+ $product_data[ 'disabled_capabilities' ] = $this->get_disabled_capabilities();
+
+ if($this->get_type() === "variation"){
+ $parent_id = $this->woo_product->get_parent_id();
+ $parent_product = wc_get_product( $parent_id );
+
+ if( $parent_product ){
+ $parent_product_visibility = $parent_product->get_meta( Products::VISIBILITY_META_KEY );
+ $current_variation_product_visibility = Products::is_product_visible( $this->woo_product );
+
+ /**
+ * If parent's visibility is already marked we know we should assign it to the child/variation as well
+ */
+ if($parent_product_visibility === "yes"){
+ $product_data["is_woo_all_products_sync"] = !$current_variation_product_visibility;
+ $product_data[ 'visibility' ] = \WC_Facebookcommerce_Integration::FB_SHOP_PRODUCT_VISIBLE;
+ }
+ else if ($parent_product_visibility === "no"){
+ $product_data[ 'visibility' ] = \WC_Facebookcommerce_Integration::FB_SHOP_PRODUCT_HIDDEN;
+ }
+ else{
+ /**
+ * If the visibility is empty,
+ * We then check for the variation's visibility.
+ * If even a single one is marked yes, we bail it out as published.
+ * If all marked no we honor the visibility as hidden.
+ */
+ $variations = $parent_product->get_children();
+ $variation_visibility = false;
+
+ foreach ($variations as $variation_id) {
+ $variation = wc_get_product($variation_id);
+
+ if ($variation) {
+ $variation_visibility = $variation_visibility || Products::is_product_visible($variation);
+ }
+
+ if ($variation_visibility) break;
+ }
+
+ /**
+ * Tagging those products who were previously having visibility hidden
+ * But now have visibility published
+ */
+ if($variation_visibility){
+ $product_data["is_woo_all_products_sync"] = !$current_variation_product_visibility;
+ }
+
+ $product_data[ 'visibility' ] = $variation_visibility ? \WC_Facebookcommerce_Integration::FB_SHOP_PRODUCT_VISIBLE : \WC_Facebookcommerce_Integration::FB_SHOP_PRODUCT_HIDDEN;
+ /**
+ * Since this function will be called again for other variations as well for the same parent product.
+ * We can now assign the visibility marker to the parent product
+ * That way it won't come to this block next time
+ */
+
+ update_post_meta($parent_id,Products::VISIBILITY_META_KEY, $variation_visibility ? "yes" : "no");
+ }
+ }
+ }
if ( self::PRODUCT_PREP_TYPE_ITEMS_BATCH === $type_to_prepare_for ) {
$product_data['title'] = Helper::str_truncate( WC_Facebookcommerce_Utils::clean_string( $this->get_title() ), self::MAX_TITLE_LENGTH );
diff --git a/includes/fbproductfeed.php b/includes/fbproductfeed.php
index f40ea5318..d7eb512cd 100644
--- a/includes/fbproductfeed.php
+++ b/includes/fbproductfeed.php
@@ -385,8 +385,8 @@ public function get_product_feed_header_row() {
'brand,price,availability,item_group_id,checkout_url,' .
'additional_image_link,sale_price_effective_date,sale_price,condition,' .
'visibility,gender,color,size,pattern,google_product_category,default_product,'.
- 'variant,gtin,quantity_to_sell_on_facebook,rich_text_description,external_update_time,'.
- 'external_variant_id'. PHP_EOL;
+ 'variant,gtin,quantity_to_sell_on_facebook,rich_text_description,internal_label,external_update_time,'.
+ 'external_variant_id, is_woo_all_products_sync'. PHP_EOL ;
}
@@ -507,6 +507,9 @@ private function prepare_product_for_feed( $woo_product, &$attribute_variants )
);
}
+ // Setting up Woo All Products sync flag
+ $is_woo_all_products_sync = $product_data['is_woo_all_products_sync'] || false;
+
return $product_data['retailer_id'] . ',' .
static::format_string_for_feed( static::get_value_from_product_data( $product_data, 'name' ) ) . ',' .
static::format_string_for_feed( static::get_value_from_product_data( $product_data, 'description' ) ) . ',' .
@@ -537,7 +540,8 @@ private function prepare_product_for_feed( $woo_product, &$attribute_variants )
static::format_string_for_feed( static::get_value_from_product_data( $product_data, 'quantity_to_sell_on_facebook' )) . ',' .
static::format_string_for_feed( static::get_value_from_product_data( $product_data, 'rich_text_description' ) ) . ',' .
static::get_value_from_product_data( $product_data, 'external_update_time' ) . ',' .
- static::get_value_from_product_data( $product_data, 'external_variant_id' ) . PHP_EOL;
+ static::get_value_from_product_data( $product_data, 'external_variant_id' ) . ',' .
+ static::format_string_for_feed($is_woo_all_products_sync). PHP_EOL ;
}
private static function format_additional_image_url( $product_image_urls ) {
diff --git a/readme.txt b/readme.txt
index 596116b93..8967a2411 100644
--- a/readme.txt
+++ b/readme.txt
@@ -40,8 +40,18 @@ When opening a bug on GitHub, please give us as many details as possible.
== Changelog ==
-= 3.4.10 - 2025-05-22 =
-* Fix - Disabled the RollOut switch
-* Fix - Removed the Global Admin Notice
+= 3.4.11 - 2025-06-02 =
+* Tweak - Removing Variant Level Sync by @SayanPandey in #2931
+* Tweak - Removed concept of Product Group Deletion from the plugin by @vinkmeta in #3062
+* Tweak - Tagging woo all products using a flag by @SayanPandey in #3165
+* Add - A simple column for Woo All Products sync in feed file by @SayanPandey in #3197
+* Tweak - Preparation for migrating Batch API to Graph API by @vinkmeta in #3203
+* Tweak Block Product Group Creation for Simple Products by @vinkmeta in #3204
+* Fix - Removed html tags from product set description by @mshymon in #3230
+* Fix - Fix for the rollout Switches by @vinkmeta in #3236
+* Add - Opt out sync experience. by @SayanPandey in #3220
+* Fix - Added a transient flag to avoid flooding of product set api requests by @vinkmeta in #3245
+* Fix - Additional check for the opt-out banner by @SayanPandey in #3259
+* Fix - Bump up GraphAPI version to 21 by @vahidkay-meta in #3219
[See changelog for all versions](https://raw.githubusercontent.com/facebook/facebook-for-woocommerce/refs/heads/releases/changelog.txt).
diff --git a/tests/Unit/ApiTest.php b/tests/Unit/ApiTest.php
index 0dc92d1fb..8a5f09c92 100644
--- a/tests/Unit/ApiTest.php
+++ b/tests/Unit/ApiTest.php
@@ -362,33 +362,6 @@ public function test_update_product_group_preforms_update_product_group_request(
$this->assertTrue( $response->success );
}
- /**
- * Tests delete product group prepares a request to Facebook.
- *
- * @return void
- * @throws ApiException In case of network request error.
- */
- public function test_delete_product_group_deletes_product_group_request() {
- $facebook_product_group_id = '5427299404026432';
-
- $response = function( $result, $parsed_args, $url ) use ( $facebook_product_group_id ) {
- $this->assertEquals( 'DELETE', $parsed_args['method'] );
- $this->assertEquals( "{$this->endpoint}{$this->version}/{$facebook_product_group_id}?deletion_method=delete_items", $url );
- return [
- 'body' => '{"success":true}',
- 'response' => [
- 'code' => 200,
- 'message' => 'OK',
- ],
- ];
- };
- $this->add_filter_with_safe_teardown( 'pre_http_request', $response, 10, 3 );
-
- $response = $this->api->delete_product_group( $facebook_product_group_id );
-
- $this->assertTrue( $response->success );
- }
-
/**
* Tests get product group products prepares a request to Facebook.
*
diff --git a/tests/Unit/ExternalVersionUpdate/UpdateTest.php b/tests/Unit/ExternalVersionUpdate/UpdateTest.php
index 67a90c037..315b925b1 100644
--- a/tests/Unit/ExternalVersionUpdate/UpdateTest.php
+++ b/tests/Unit/ExternalVersionUpdate/UpdateTest.php
@@ -80,6 +80,7 @@ public function test_should_update_version() {
*/
public function test_maybe_update_external_plugin_version() {
$plugin = facebook_for_woocommerce();
+ $plugin->init_admin();
/**
* Set the $plugin->connection_handler and $plugin->api access to true. This will allow us
@@ -89,6 +90,10 @@ public function test_maybe_update_external_plugin_version() {
$prop_connection_handler = $plugin_ref_obj->getProperty( 'connection_handler' );
$prop_connection_handler->setAccessible( true );
+ // Set up plugin render properties
+ $prop_plugin_render_handler = $plugin_ref_obj->getProperty( 'plugin_render_handler' );
+ $prop_plugin_render_handler->setAccessible( true );
+
$prop_api = $plugin_ref_obj->getProperty( 'api' );
$prop_api->setAccessible( true );
@@ -101,6 +106,14 @@ public function test_maybe_update_external_plugin_version() {
$mock_connection_handler->expects( $this->any() )->method( 'is_connected' )->willReturn( true );
$prop_connection_handler->setValue( $plugin, $mock_connection_handler );
+ // Mock render handler
+ $mock_plugin_render_handler = $this->getMockBuilder( Connection::class )
+ ->disableOriginalConstructor()
+ ->setMethods( array( 'is_master_sync_on' ) )
+ ->getMock();
+ $mock_plugin_render_handler->expects( $this->any() )->method( 'is_master_sync_on' )->willReturn( true );
+ $prop_plugin_render_handler->setValue($plugin,$mock_plugin_render_handler);
+
// Create the mock api object that will return an array, meaning a successful response.
$mock_api = $this->getMockBuilder( API::class )->disableOriginalConstructor()->setMethods( array( 'do_remote_request' ) )->getMock();
$mock_api->expects( $this->any() )->method( 'do_remote_request' )->willReturn(
@@ -122,6 +135,7 @@ public function test_maybe_update_external_plugin_version() {
'external_client' => array(
'version_id' => WC_Facebookcommerce_Utils::PLUGIN_VERSION,
'is_multisite' => false,
+ 'is_woo_all_products_opted_out' => false
),
),
);
diff --git a/tests/Unit/ProductSets/ProductSetSyncTest.php b/tests/Unit/ProductSets/ProductSetSyncTest.php
new file mode 100644
index 000000000..6bdc05872
--- /dev/null
+++ b/tests/Unit/ProductSets/ProductSetSyncTest.php
@@ -0,0 +1,202 @@
+createWPCategory();
+
+ $product_set_sync = $this->getMockBuilder( ProductSetSyncTestable::class )
+ ->setMethods(['is_sync_enabled', 'get_fb_product_set_id','create_fb_product_set'])
+ ->getMock();
+
+ $product_set_sync->expects( $this->once() )
+ ->method( 'is_sync_enabled' )
+ ->willReturn(true);
+ $product_set_sync->expects( $this->once() )
+ ->method( 'get_fb_product_set_id' )
+ ->with($wc_category)
+ ->willReturn(null);
+ $product_set_sync->expects( $this->once() )
+ ->method( 'create_fb_product_set' );
+
+ $product_set_sync->on_create_or_update_product_wc_category_callback(
+ $wc_category->term_id,
+ $wc_category->term_taxonomy_id,
+ array()
+ );
+ }
+
+ public function testUpdate() {
+ $wc_category = $this->createWPCategory();
+
+ $product_set_sync = $this->getMockBuilder( ProductSetSyncTestable::class )
+ ->setMethods(['is_sync_enabled', 'get_fb_product_set_id','update_fb_product_set'])
+ ->getMock();
+
+ $product_set_sync->expects( $this->once() )
+ ->method( 'is_sync_enabled' )
+ ->willReturn(true);
+ $product_set_sync->expects( $this->once() )
+ ->method( 'get_fb_product_set_id' )
+ ->with($wc_category)
+ ->willReturn(self::FB_PRODUCT_SET_ID);
+ $product_set_sync->expects( $this->once() )
+ ->method( 'update_fb_product_set' )
+ ->with($wc_category, self::FB_PRODUCT_SET_ID);
+
+ $product_set_sync->on_create_or_update_product_wc_category_callback(
+ $wc_category->term_id,
+ $wc_category->term_taxonomy_id,
+ array()
+ );
+ }
+
+ public function testDelete() {
+ $wc_category = $this->createWPCategory();
+
+ $product_set_sync = $this->getMockBuilder( ProductSetSyncTestable::class )
+ ->setMethods(['is_sync_enabled', 'get_fb_product_set_id','delete_fb_product_set'])
+ ->getMock();
+
+ $product_set_sync->expects( $this->once() )
+ ->method( 'is_sync_enabled' )
+ ->willReturn(true);
+ $product_set_sync->expects( $this->once() )
+ ->method( 'get_fb_product_set_id' )
+ ->with($wc_category)
+ ->willReturn(self::FB_PRODUCT_SET_ID);
+ $product_set_sync->expects( $this->once() )
+ ->method( 'delete_fb_product_set' )
+ ->with(self::FB_PRODUCT_SET_ID);
+
+ $product_set_sync->on_delete_wc_product_category_callback(
+ $wc_category->term_id,
+ $wc_category->term_taxonomy_id,
+ $wc_category,
+ array()
+ );
+ }
+
+ public function testSyncDisabled() {
+ $wc_category = $this->createWPCategory();
+
+ $product_set_sync = $this->getMockBuilder( ProductSetSyncTestable::class )
+ ->setMethods(['is_sync_enabled', 'get_fb_product_set_id','create_fb_product_set'])
+ ->getMock();
+
+ $product_set_sync->expects( $this->once() )
+ ->method( 'is_sync_enabled' )
+ ->willReturn(false);
+ $product_set_sync->expects( $this->never() )
+ ->method( 'get_fb_product_set_id' );
+ $product_set_sync->expects( $this->never() )
+ ->method( 'create_fb_product_set' );
+
+ $product_set_sync->on_create_or_update_product_wc_category_callback(
+ $wc_category->term_id,
+ $wc_category->term_taxonomy_id,
+ array()
+ );
+ }
+
+ public function testSyncAllProductSets() {
+ $this->createWPCategory( self::WC_CATEGORY_NAME_1 );
+ $this->createWPCategory( self::WC_CATEGORY_NAME_2 );
+
+ $product_set_sync = $this->getMockBuilder( ProductSetSyncTestable::class )
+ ->setMethods(['is_sync_enabled', 'get_fb_product_set_id','create_fb_product_set'])
+ ->getMock();
+
+ $product_set_sync->expects( $this->exactly(1) )
+ ->method( 'is_sync_enabled' )
+ ->willReturn(true);
+ $product_set_sync->expects( $this->atLeast(2) )
+ ->method( 'get_fb_product_set_id' )
+ ->willReturn(null);
+ $product_set_sync->expects( $this->atLeast(2) )
+ ->method( 'create_fb_product_set' );
+
+ $product_set_sync->sync_all_product_sets();
+ }
+
+ public function testProductSetData() {
+ $wc_category = $this->createWPCategory();
+
+ $product_set_sync = $this->getMockBuilder( ProductSetSyncTestable::class )
+ ->setMethods(['is_sync_enabled', 'get_fb_product_set_id','create_fb_product_set'])
+ ->getMock();
+
+ $data = $product_set_sync->build_fb_product_set_data( $wc_category );
+ $this->assertEquals( self::WC_CATEGORY_NAME_1, $data['name'] );
+ $this->assertEquals( $wc_category->term_taxonomy_id, $data['retailer_id'] );
+ $this->assertEquals('{"and":[{"product_type":{"i_contains":"Test Category 1"}}]}', $data['filter'] );
+ $this->assertEquals( '{"description":"This is a test category","external_url":"http:\/\/example.org\/?product_cat=test-category"}', $data['metadata'] );
+ }
+
+ /* ------------------ Utils Methods ------------------ */
+
+ private function createWPCategory( $name = self::WC_CATEGORY_NAME_1 ) {
+ $wc_category = wp_insert_term(
+ $name,
+ 'product_cat', // taxonomy
+ array(
+ 'description' => 'This is a test category',
+ 'slug' => 'test-category',
+ )
+ );
+
+ return get_term( $wc_category['term_id'], ProductSetSync::WC_PRODUCT_CATEGORY_TAXONOMY );
+ }
+}
+
+/**
+ * A test-specific subclass of ProductSetSync to expose private methods for mocking.
+ */
+class ProductSetSyncTestable extends ProductSetSync {
+
+ public function is_sync_enabled() {
+ return parent::is_sync_enabled();
+ }
+
+ public function get_fb_product_set_id( $wc_category ) {
+ return parent::get_fb_product_set_id( $wc_category );
+ }
+
+ public function create_fb_product_set( $wc_category ) {
+ return parent::create_fb_product_set( $wc_category );
+ }
+
+ public function update_fb_product_set( $wc_category, $fb_product_set_id ) {
+ return parent::update_fb_product_set( $wc_category, $fb_product_set_id );
+ }
+
+ public function delete_fb_product_set( $fb_product_set_id ) {
+ return parent::delete_fb_product_set( $fb_product_set_id );
+ }
+
+ public function build_fb_product_set_data( $wc_category ) {
+ return parent::build_fb_product_set_data( $wc_category );
+ }
+}
diff --git a/tests/Unit/WCFacebookCommerceIntegrationTest.php b/tests/Unit/WCFacebookCommerceIntegrationTest.php
index 5e443a589..21f603837 100644
--- a/tests/Unit/WCFacebookCommerceIntegrationTest.php
+++ b/tests/Unit/WCFacebookCommerceIntegrationTest.php
@@ -685,9 +685,6 @@ public function test_on_product_delete_simple_product() {
$this->api->expects( $this->once() )
->method( 'delete_product_item' )
->with( 'facebook-product-id' );
- $this->api->expects( $this->once() )
- ->method( 'delete_product_group' )
- ->with( 'facebook-product-group-id' );
$this->integration->on_product_delete( $product_to_delete->get_id() );
@@ -1084,55 +1081,6 @@ public function test_on_simple_product_publish_existing_product_updates_product(
$this->assertEquals( 'facebook-simple-product-item-id', $facebook_product_item_id );
}
- /**
- * Tests on simple product publish update callback/hook creates new product.
- *
- * @return void
- */
- 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() );
- $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 );
- $validator->expects( $this->once() )
- ->method( 'validate' );
- $this->facebook_for_woocommerce->expects( $this->once() )
- ->method( 'get_product_sync_validator' )
- ->with( $facebook_product->woo_product )
- ->willReturn( $validator );
-
- update_option( 'woocommerce_hide_out_of_stock_items', 'yes' );
- $facebook_product->woo_product->set_stock_status( 'instock' );
- add_post_meta( $product->get_id(), WC_Facebookcommerce_Integration::FB_PRODUCT_ITEM_ID, '' );
-
- $this->api->expects( $this->once() )
- ->method( 'create_product_group' )
- ->with(
- '1234567891011121314',
- [ 'retailer_id' => WC_Facebookcommerce_Utils::get_fb_retailer_id( $facebook_product ) ]
- )
- ->willReturn( new API\ProductCatalog\ProductGroups\Create\Response( '{"id":"facebook-simple-product-group-item-id"}' ) );
- $this->api->expects( $this->once() )
- ->method( 'send_item_updates' )
- ->with(
- $this->integration->get_product_catalog_id(),
- $requests
- )
- ->willReturn( new API\ProductCatalog\ItemsBatch\Create\Response( '{"handles":"abcxyz"}' ) );
-
- $this->integration->on_simple_product_publish( $product->get_id(), $facebook_product );
-
- $this->assertEquals(
- 'facebook-simple-product-group-item-id',
- get_post_meta( $facebook_product->get_id(), WC_Facebookcommerce_Integration::FB_PRODUCT_GROUP_ID, true )
- );
- }
-
/**
* Tests product should be synced calls to return success.
*
@@ -1176,41 +1124,6 @@ public function test_product_should_be_synced_calls_facebook_api_with_exception(
$this->assertFalse( $output );
}
- /**
- * Tests create simple product creates product group and the product itself.
- *
- * @return void
- */
- 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() );
- $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,
- ];
- $this->api->expects( $this->once() )
- ->method( 'create_product_group' )
- ->with( '123456789101112', $data )
- ->willReturn( new API\ProductCatalog\ProductGroups\Create\Response( '{"id":"facebook-simple-product-group-id"}' ) );
-
- $this->api->expects( $this->once() )
- ->method( 'send_item_updates' )
- ->with(
- $this->integration->get_product_catalog_id(),
- $requests
- )
- ->willReturn( new API\ProductCatalog\ItemsBatch\Create\Response( '{"handles":"abcxyz"}' ) );
-
- $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 ) );
- }
-
/**
* Tests create simple product with provided product group id.
*
@@ -1235,57 +1148,6 @@ public function test_create_product_simple_creates_product_with_provided_product
$facebook_product_item_id = $this->integration->create_product_simple( $facebook_product, 'facebook-simple-product-group-id' );
}
- /**
- * Tests create simple product fails to create product group and returns empty product item id.
- *
- * @return void
- */
- public function test_create_product_simple_with_failed_create_product_group_returns_empty_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 );
- $data = [
- 'retailer_id' => $retailer_id,
- ];
- $this->api->expects( $this->once() )
- ->method( 'create_product_group' )
- ->with( '123456789101112', $data )
- ->willReturn( new API\ProductCatalog\ProductGroups\Create\Response( '{"error":{"message":"Unsupported post request. Object with ID \'4964146013695812\' does not exist, cannot be loaded due to missing permissions, or does not support this operation. Please read the Graph API documentation at https:\/\/developers.facebook.com\/docs\/graph-api","type":"GraphMethodException","code":100,"error_subcode":33,"fbtrace_id":"AtmMkt0H2dwNBhdRfcYqzVY"}}' ) );
-
- $facebook_product_item_id = $this->integration->create_product_simple( $facebook_product );
-
- $this->assertEquals( '', get_post_meta( $facebook_product->get_id(), WC_Facebookcommerce_Integration::FB_PRODUCT_GROUP_ID, true ) );
- $this->assertEquals( '', get_post_meta( $facebook_product->get_id(), WC_Facebookcommerce_Integration::FB_PRODUCT_ITEM_ID, true ) );
- $this->assertEquals( '', $facebook_product_item_id );
- }
-
- /**
- * Tests create product group fpr product w/o variants.
- *
- * @return void
- */
- public function test_create_product_group_creates_group_no_variants() {
- 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 = 'product-retailer-id';
- $data = [
- 'retailer_id' => $retailer_id,
- ];
- $this->api->expects( $this->once() )
- ->method( 'create_product_group' )
- ->with( '123456789101112', $data )
- ->willReturn( new API\ProductCatalog\ProductGroups\Create\Response( '{"id":"facebook-product-group-id"}' ) );
-
- $facebook_product_group_id = $this->integration->create_product_group( $facebook_product, $retailer_id );
-
- $this->assertEquals( 'facebook-product-group-id', $facebook_product_group_id );
- $this->assertEquals( 'facebook-product-group-id', get_post_meta( $facebook_product->get_id(), WC_Facebookcommerce_Integration::FB_PRODUCT_GROUP_ID, true ) );
- }
-
/**
* Tests create product group for product with variants.
*
@@ -1320,11 +1182,13 @@ public function test_create_product_group_creates_group_with_variants() {
public function test_create_product_group_fails_to_create_group() {
add_option( WC_Facebookcommerce_Integration::OPTION_PRODUCT_CATALOG_ID, '123456789101112' );
- $product = WC_Helper_Product::create_simple_product();
+ $product = WC_Helper_Product::create_variation_product();
$facebook_product = new WC_Facebook_Product( $product->get_id() );
+ add_post_meta( $facebook_product->get_id(), WC_Facebookcommerce_Integration::FB_PRODUCT_GROUP_ID, 'facebook-product-group-id' );
$retailer_id = 'product-retailer-id';
$data = [
'retailer_id' => $retailer_id,
+ 'variants' => $facebook_product->prepare_variants_for_group(),
];
$this->api->expects( $this->once() )
->method( 'create_product_group' )
diff --git a/webpack.config.js b/webpack.config.js
index 3949fc7e7..44cccfbc6 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -20,6 +20,7 @@ const jQueryUIAdminFileNames = [
'whatsapp-consent-remove',
'whatsapp-disconnect',
'whatsapp-events',
+ 'plugin-rendering'
];
const jQueryUIAdminFileEntries = {};