diff --git a/facebook-commerce.php b/facebook-commerce.php index b83c9d278..280b803e8 100644 --- a/facebook-commerce.php +++ b/facebook-commerce.php @@ -16,6 +16,7 @@ use WooCommerce\Facebook\Products; use WooCommerce\Facebook\Products\Feed; use WooCommerce\Facebook\Framework\Logger; +use WooCommerce\Facebook\RolloutSwitches; defined( 'ABSPATH' ) || exit; @@ -75,6 +76,9 @@ class WC_Facebookcommerce_Integration extends WC_Integration { /** @var string the "access token" setting ID */ const SETTING_ACCESS_TOKEN = 'access_token'; + /** @var string the "enable product sync" setting ID */ + const SETTING_ENABLE_PRODUCT_SYNC = 'wc_facebook_enable_product_sync'; + /** @var string the excluded product category IDs setting ID */ const SETTING_EXCLUDED_PRODUCT_CATEGORY_IDS = 'wc_facebook_excluded_product_category_ids'; @@ -2485,6 +2489,19 @@ public function is_configured() { return $this->get_facebook_page_id() && $this->facebook_for_woocommerce->get_connection_handler()->is_connected(); } + /** + * Determines if viewing the plugin settings in the admin. + * + * @since 3.5.3 + * + * @return bool + */ + public function is_woo_all_products_enabled() { + return $this->facebook_for_woocommerce->get_rollout_switches()->is_switch_enabled( + RolloutSwitches::SWITCH_WOO_ALL_PRODUCTS_SYNC_ENABLED + ); + } + /** * Determines whether advanced matching is enabled. * @@ -2503,6 +2520,33 @@ public function is_advanced_matching_enabled() { return (bool) apply_filters( 'wc_facebook_is_advanced_matching_enabled', true, $this ); } + /** + * Determines whether product sync is enabled. + * + * @return bool + * @since 1.10.0 + */ + public function is_product_sync_enabled() { + /** + * If all products switch is enabled + * There is no check for global sync + */ + + if ( $this->is_woo_all_products_enabled() ) { + return true; + } + + /** + * Filters whether product sync is enabled. + * + * @param bool $is_enabled whether product sync is enabled + * @param \WC_Facebookcommerce_Integration $integration the integration instance + * + * @since 1.10.0 + */ + return (bool) apply_filters( 'wc_facebook_is_product_sync_enabled', 'yes' === get_option( self::SETTING_ENABLE_PRODUCT_SYNC, 'yes' ), $this ); + } + /** * Return true if (legacy) feed generation is enabled. * diff --git a/includes/Admin/Settings_Screens/Product_Sync.php b/includes/Admin/Settings_Screens/Product_Sync.php index e4f68b040..2d62cab7a 100644 --- a/includes/Admin/Settings_Screens/Product_Sync.php +++ b/includes/Admin/Settings_Screens/Product_Sync.php @@ -269,6 +269,15 @@ public function get_settings(): array { 'type' => 'product_sync_title', 'title' => __( 'Product sync', 'facebook-for-woocommerce' ), ), + array( + 'id' => \WC_Facebookcommerce_Integration::SETTING_ENABLE_PRODUCT_SYNC, + 'title' => __( 'Enable product sync', 'facebook-for-woocommerce' ), + 'type' => 'checkbox', + 'label' => ' ', + 'default' => 'yes', + 'desc_tip' => __( 'Enable product syncing with Facebook.', 'facebook-for-woocommerce' ), + ), + array( 'id' => \WC_Facebookcommerce_Integration::SETTING_EXCLUDED_PRODUCT_CATEGORY_IDS, 'title' => __( 'Exclude categories from sync', 'facebook-for-woocommerce' ), diff --git a/includes/Lifecycle.php b/includes/Lifecycle.php index 895ce977b..b2da960e6 100644 --- a/includes/Lifecycle.php +++ b/includes/Lifecycle.php @@ -144,6 +144,14 @@ private function migrate_1_9_settings() { } } + $is_woo_all_products_sync_enbaled = facebook_for_woocommerce()->get_integration()->is_woo_all_products_enabled(); + + // migrate settings from standalone options + if ( ! $is_woo_all_products_sync_enbaled && ! isset( $new_settings[ \WC_Facebookcommerce_Integration::SETTING_ENABLE_PRODUCT_SYNC ] ) ) { + $product_sync_enabled = empty( get_option( 'fb_disable_sync_on_dev_environment', 0 ) ); + $new_settings[ \WC_Facebookcommerce_Integration::SETTING_ENABLE_PRODUCT_SYNC ] = $product_sync_enabled ? 'yes' : 'no'; + } + if ( ! isset( $new_settings[ \WC_Facebookcommerce_Integration::SETTING_SCHEDULED_RESYNC_OFFSET ] ) ) { $autosync_time = get_option( 'woocommerce_fb_autosync_time' ); $parsed_time = ! empty( $autosync_time ) ? strtotime( $autosync_time ) : false; @@ -208,6 +216,7 @@ protected function upgrade_to_2_0_0() { $settings_map = array( 'facebook_pixel_id' => \WC_Facebookcommerce_Integration::SETTING_FACEBOOK_PIXEL_ID, 'facebook_page_id' => \WC_Facebookcommerce_Integration::SETTING_FACEBOOK_PAGE_ID, + 'enable_product_sync' => \WC_Facebookcommerce_Integration::SETTING_ENABLE_PRODUCT_SYNC, 'excluded_product_category_ids' => \WC_Facebookcommerce_Integration::SETTING_EXCLUDED_PRODUCT_CATEGORY_IDS, 'excluded_product_tag_ids' => \WC_Facebookcommerce_Integration::SETTING_EXCLUDED_PRODUCT_TAG_IDS, 'enable_messenger' => self::SETTING_ENABLE_MESSENGER, diff --git a/includes/ProductSync/ProductValidator.php b/includes/ProductSync/ProductValidator.php index 413dba5c1..afe3e0c0a 100644 --- a/includes/ProductSync/ProductValidator.php +++ b/includes/ProductSync/ProductValidator.php @@ -109,6 +109,7 @@ public function __get( $key ) { * @throws ProductExcludedException If product should not be synced. */ public function validate() { + $this->validate_sync_enabled_globally(); $this->validate_product_sync_field(); $this->validate_product_status(); $this->validate_product_visibility(); @@ -122,6 +123,7 @@ public function validate() { * @throws ProductExcludedException If product should not be synced. */ public function validate_but_skip_status_check() { + $this->validate_sync_enabled_globally(); $this->validate_product_sync_field(); $this->validate_product_visibility(); } @@ -133,6 +135,7 @@ public function validate_but_skip_status_check() { * @throws ProductExcludedException|ProductInvalidException If product should not be synced. */ public function validate_but_skip_sync_field() { + $this->validate_sync_enabled_globally(); $this->validate_product_visibility(); } @@ -187,6 +190,21 @@ public function passes_all_checks_except_sync_field(): bool { return true; } + /** + * Check whether product sync is globally disabled. + * + * @throws ProductExcludedException If product should not be synced. + */ + protected function validate_sync_enabled_globally() { + if ( $this->integration->is_woo_all_products_enabled() ) { + return true; + } + + if ( ! $this->integration->is_product_sync_enabled() ) { + throw new ProductExcludedException( __( 'Product sync is globally disabled.', 'facebook-for-woocommerce' ) ); + } + } + /** * Check whether the product's status excludes it from sync. * diff --git a/includes/Products/Feed.php b/includes/Products/Feed.php index dc2d33407..f407444e9 100644 --- a/includes/Products/Feed.php +++ b/includes/Products/Feed.php @@ -193,9 +193,11 @@ public function schedule_feed_generation() { set_transient( $flag_name, 'yes', HOUR_IN_SECONDS ); $integration = facebook_for_woocommerce()->get_integration(); $configured_ok = $integration && $integration->is_configured(); + // Only schedule feed job if store has not opted out of product sync. + $store_allows_sync = ( $configured_ok && $integration->is_product_sync_enabled() ) || $integration->is_woo_all_products_enabled(); // Only schedule if has not opted out of feed generation (e.g. large stores). $store_allows_feed = $configured_ok && $integration->is_legacy_feed_file_generation_enabled(); - if ( ! $store_allows_feed || ! $configured_ok ) { + if ( ! $store_allows_sync || ! $store_allows_feed ) { as_unschedule_all_actions( self::GENERATE_FEED_ACTION ); $message = ''; @@ -203,6 +205,8 @@ public function schedule_feed_generation() { $message = 'Integration not configured.'; } elseif ( ! $store_allows_feed ) { $message = 'Store does not allow feed.'; + } elseif ( ! $store_allows_sync ) { + $message = 'Store does not allow sync.'; } Logger::log( sprintf( 'Product feed scheduling failed: %s', $message ), diff --git a/includes/RolloutSwitches.php b/includes/RolloutSwitches.php index 5d7caa396..c3456bffa 100644 --- a/includes/RolloutSwitches.php +++ b/includes/RolloutSwitches.php @@ -24,15 +24,17 @@ class RolloutSwitches { /** @var \WC_Facebookcommerce commerce handler */ private \WC_Facebookcommerce $plugin; - public const SWITCH_ROLLOUT_FEATURES = 'rollout_enabled'; - public const WHATSAPP_UTILITY_MESSAGING = 'whatsapp_utility_messages_enabled'; - public const SWITCH_PRODUCT_SETS_SYNC_ENABLED = 'product_sets_sync_enabled'; - private const SETTINGS_KEY = 'wc_facebook_for_woocommerce_rollout_switches'; + public const SWITCH_ROLLOUT_FEATURES = 'rollout_enabled'; + public const WHATSAPP_UTILITY_MESSAGING = 'whatsapp_utility_messages_enabled'; + public const SWITCH_PRODUCT_SETS_SYNC_ENABLED = 'product_sets_sync_enabled'; + public const SWITCH_WOO_ALL_PRODUCTS_SYNC_ENABLED = 'woo_all_products_sync_enabled'; + private const SETTINGS_KEY = 'wc_facebook_for_woocommerce_rollout_switches'; private const ACTIVE_SWITCHES = array( self::SWITCH_ROLLOUT_FEATURES, self::WHATSAPP_UTILITY_MESSAGING, self::SWITCH_PRODUCT_SETS_SYNC_ENABLED, + self::SWITCH_WOO_ALL_PRODUCTS_SYNC_ENABLED, ); public function __construct( \WC_Facebookcommerce $plugin ) { @@ -82,13 +84,13 @@ public function init() { Logger::log( $e->getMessage(), array( - 'flow_name' => 'rollout_switches', - 'flow_step' => 'init', + 'flow_name' => 'rollout_switches', + 'flow_step' => 'init', ), array( - 'should_send_log_to_meta' => true, + 'should_send_log_to_meta' => true, 'should_save_log_in_woocommerce' => true, - 'woocommerce_log_level' => \WC_Log_Levels::ERROR, + 'woocommerce_log_level' => \WC_Log_Levels::ERROR, ) ); } diff --git a/includes/Utilities/Tracker.php b/includes/Utilities/Tracker.php index bb9c10657..0384d7d60 100644 --- a/includes/Utilities/Tracker.php +++ b/includes/Utilities/Tracker.php @@ -118,7 +118,14 @@ public function add_tracker_data( array $data = [] ) { * * @since 3.5.3 */ - $data['extensions']['facebook-for-woocommerce']['product-sync-enabled'] = true; + + $is_woo_all_products_enabled = facebook_for_woocommerce()->get_integration()->is_woo_all_products_enabled(); + if ( ! $is_woo_all_products_enabled ) { + $product_sync_enabled = facebook_for_woocommerce()->get_integration()->is_product_sync_enabled(); + $data['extensions']['facebook-for-woocommerce']['product-sync-enabled'] = wc_bool_to_string( $product_sync_enabled ); + } else { + $data['extensions']['facebook-for-woocommerce']['product-sync-enabled'] = wc_bool_to_string( true ); + } /** * How long did the last feed generation take (or did it fail - 0)? This counts just the time when the batches have been generated. diff --git a/tests/Unit/WCFacebookCommerceIntegrationTest.php b/tests/Unit/WCFacebookCommerceIntegrationTest.php index 83f4bd4e2..adca55878 100644 --- a/tests/Unit/WCFacebookCommerceIntegrationTest.php +++ b/tests/Unit/WCFacebookCommerceIntegrationTest.php @@ -560,6 +560,8 @@ public function test_on_product_save_existing_simple_product_sync_disabled_updat // The mock below is hit otherwise it would generate a random Mock_Response and throw error $integration_mock = $this->createMock(WC_Facebookcommerce_Integration::class); $integration_mock->method('delete_product_item'); + $integration_mock->method('is_woo_all_products_enabled') + ->willReturn(false); $this->integration = $integration_mock; $_POST['wc_facebook_sync_mode'] = Admin::SYNC_MODE_SYNC_DISABLED;