Facebook for WooCommerce - Version 2.6.0

Version Description

  • 2021-06-10 =
    • Fix Add cron heartbeat and use to offload feed generation from init / admin_init (performance) #1953
    • Fix Clean up background sync options (performance) #1962
    • Dev Add tracker props to understand usage of feed-based sync and other FB business config options #1972
    • Dev Configure release tooling to auto-update version numbers in code #1982
    • Dev Refactor code responsible for validating whether a product should be synced to FB into one place #19333
Download this release

Release Info

Developer automattic
Plugin Icon Facebook for WooCommerce
Version 2.6.0
Comparing to
See all releases

Code changes from version 2.5.1 to 2.6.0

assets/build/admin/google-product-category-fields.asset.php CHANGED
@@ -1 +1 @@
1
- <?php return array('dependencies' => array('wp-polyfill'), 'version' => '00c66c0ea3ceb2d3751483c954e5a649');
1
+ <?php return array('dependencies' => array('wp-polyfill'), 'version' => '626147500c00fb1fe6128015d31a1369');
assets/build/admin/google-product-category-fields.js CHANGED
@@ -1 +1 @@
1
- !function(e){var t={};function o(a){if(t[a])return t[a].exports;var n=t[a]={i:a,l:!1,exports:{}};return e[a].call(n.exports,n,n.exports,o),n.l=!0,n.exports}o.m=e,o.c=t,o.d=function(e,t,a){o.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:a})},o.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},o.t=function(e,t){if(1&t&&(e=o(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var a=Object.create(null);if(o.r(a),Object.defineProperty(a,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var n in e)o.d(a,n,function(t){return e[t]}.bind(null,n));return a},o.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return o.d(t,"a",t),t},o.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},o.p="",o(o.s=3)}([function(e,t){e.exports=function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}},function(e,t){function o(e,t){for(var o=0;o<t.length;o++){var a=t[o];a.enumerable=a.enumerable||!1,a.configurable=!0,"value"in a&&(a.writable=!0),Object.defineProperty(e,a.key,a)}}e.exports=function(e,t,a){return t&&o(e.prototype,t),a&&o(e,a),e}},,function(e,t,o){"use strict";o.r(t);var a=o(0),n=o.n(a),r=o(1),c=o.n(r);jQuery(document).ready((function(e){window.WC_Facebook_Google_Product_Category_Fields=function(){function t(o,a){var r=this;n()(this,t),this.categories=o,this.input_id=a;var c=e("#"+this.input_id);e('<div id="wc-facebook-google-product-category-fields"></div>').insertBefore(c).on("change","select.wc-facebook-google-product-category-select",(function(t){r.onChange(e(t.target))})),this.addInitialSelects(c.val());var i=this.globalsHolder().enhanced_attribute_optional_selector;void 0!==i&&e("#"+i).on("change",(function(){e(".wc-facebook-enhanced-catalog-attribute-optional-row").toggleClass("hidden",!e(this).prop("checked"))}))}return c()(t,[{key:"globalsHolder",value:function(){return"undefined"!=typeof facebook_for_woocommerce_product_categories?facebook_for_woocommerce_product_categories:"undefined"!=typeof facebook_for_woocommerce_settings_sync?facebook_for_woocommerce_settings_sync:facebook_for_woocommerce_products_admin}},{key:"getPageType",value:function(){return"undefined"!=typeof facebook_for_woocommerce_product_categories?0===e("input[name=tag_ID]").length?this.globalsHolder().enhanced_attribute_page_type_add_category:this.globalsHolder().enhanced_attribute_page_type_edit_category:this.globalsHolder().enhanced_attribute_page_type_edit_product}},{key:"addInitialSelects",value:function(e){var t=this;if(e){this.getSelectedCategoryIds(e).forEach((function(e){t.addSelect(t.getOptions(e[1]),e[0])}));var o=this.getOptions(e);Object.keys(o).length&&this.addSelect(o)}else this.addSelect(this.getOptions()),this.addSelect({})}},{key:"requestAttributesIfValid",value:function(){if("true"===e("#wc_facebook_can_show_enhanced_catalog_attributes_id").val()&&(e(".wc-facebook-enhanced-catalog-attribute-row").remove(),this.isValid())){var t="#"+this.input_id,o=e(t).parents("div.form-field"),a=this.globalsHolder().enhanced_attribute_optional_selector;this.getPageType()===this.globalsHolder().enhanced_attribute_page_type_edit_category?o=e(t).parents("tr.form-field"):this.getPageType()===this.globalsHolder().enhanced_attribute_page_type_edit_product&&(o=e(t).parents("p.form-field")),e.get(this.globalsHolder().ajax_url,{action:"wc_facebook_enhanced_catalog_attributes",security:"",selected_category:e(t).val(),tag_id:parseInt(e("input[name=tag_ID]").val(),10),taxonomy:e("input[name=taxonomy]").val(),item_id:parseInt(e("input[name=post_ID]").val(),10),page_type:this.getPageType()},(function(t){var n=e(t);e("#"+a,n).on("change",(function(){e(".wc-facebook-enhanced-catalog-attribute-optional-row").toggleClass("hidden",!e(this).prop("checked"))})),n.insertAfter(o),e(document.body).trigger("init_tooltips")}))}}},{key:"onChange",value:function(t){t.hasClass("locked")&&t.closest(".wc-facebook-google-product-category-field").nextAll().remove();var o=t.val();if(o){var a=this.getOptions(o);Object.keys(a).length&&this.addSelect(a)}else(o=t.closest("#wc-facebook-google-product-category-fields").find(".wc-facebook-google-product-category-select").not(t).last().val())||this.addSelect({});e("#"+this.input_id).val(o),this.requestAttributesIfValid()}},{key:"isValid",value:function(){return e(".wc-facebook-google-product-category-select").filter((function(t,o){return""!==e(o).val()})).length>=2}},{key:"addSelect",value:function(t,o){var a=e("#wc-facebook-google-product-category-fields"),n=a.find(".wc-facebook-google-product-category-select"),r=e('<select class="wc-enhanced-select wc-facebook-google-product-category-select"></select>');n.addClass("locked"),a.append(e('<div class="wc-facebook-google-product-category-field" style="margin-bottom: 16px">').append(r)),r.attr("data-placeholder",this.getSelectPlaceholder(n,t)).append(e('<option value=""></option>')),Object.keys(t).forEach((function(o){r.append(e('<option value="'+o+'">'+t[o]+"</option>"))})),r.val(o).select2({allowClear:!0})}},{key:"getSelectPlaceholder",value:function(e,t){return 0===e.length?facebook_for_woocommerce_google_product_category.i18n.top_level_dropdown_placeholder:1===e.length&&0===Object.keys(t).length?facebook_for_woocommerce_google_product_category.i18n.second_level_empty_dropdown_placeholder:facebook_for_woocommerce_google_product_category.i18n.general_dropdown_placeholder}},{key:"getOptions",value:function(e){return void 0===e||""===e?this.getTopLevelOptions():void 0===this.categories[e]||void 0===this.categories[e].options?[]:this.categories[e].options}},{key:"getTopLevelOptions",value:function(){var e=this,t={};return Object.keys(this.categories).forEach((function(o){e.categories[o].parent||(t[o]=e.categories[o].label)})),t}},{key:"getSelectedCategoryIds",value:function(e){var t=[];do{void 0!==this.categories[e]&&(t.push([e,this.categories[e].parent]),e=this.categories[e].parent)}while(""!==e);return t.reverse()}}]),t}()}))}]);
1
+ !function(e){var t={};function o(a){if(t[a])return t[a].exports;var r=t[a]={i:a,l:!1,exports:{}};return e[a].call(r.exports,r,r.exports,o),r.l=!0,r.exports}o.m=e,o.c=t,o.d=function(e,t,a){o.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:a})},o.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},o.t=function(e,t){if(1&t&&(e=o(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var a=Object.create(null);if(o.r(a),Object.defineProperty(a,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var r in e)o.d(a,r,function(t){return e[t]}.bind(null,r));return a},o.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return o.d(t,"a",t),t},o.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},o.p="",o(o.s=3)}([function(e,t){e.exports=function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")},e.exports.default=e.exports,e.exports.__esModule=!0},function(e,t){function o(e,t){for(var o=0;o<t.length;o++){var a=t[o];a.enumerable=a.enumerable||!1,a.configurable=!0,"value"in a&&(a.writable=!0),Object.defineProperty(e,a.key,a)}}e.exports=function(e,t,a){return t&&o(e.prototype,t),a&&o(e,a),e},e.exports.default=e.exports,e.exports.__esModule=!0},,function(e,t,o){"use strict";o.r(t);var a=o(0),r=o.n(a),n=o(1),c=o.n(n);jQuery(document).ready((function(e){window.WC_Facebook_Google_Product_Category_Fields=function(){function t(o,a){var n=this;r()(this,t),this.categories=o,this.input_id=a;var c=e("#"+this.input_id);e('<div id="wc-facebook-google-product-category-fields"></div>').insertBefore(c).on("change","select.wc-facebook-google-product-category-select",(function(t){n.onChange(e(t.target))})),this.addInitialSelects(c.val());var i=this.globalsHolder().enhanced_attribute_optional_selector;void 0!==i&&e("#"+i).on("change",(function(){e(".wc-facebook-enhanced-catalog-attribute-optional-row").toggleClass("hidden",!e(this).prop("checked"))}))}return c()(t,[{key:"globalsHolder",value:function(){return"undefined"!=typeof facebook_for_woocommerce_product_categories?facebook_for_woocommerce_product_categories:"undefined"!=typeof facebook_for_woocommerce_settings_sync?facebook_for_woocommerce_settings_sync:facebook_for_woocommerce_products_admin}},{key:"getPageType",value:function(){return"undefined"!=typeof facebook_for_woocommerce_product_categories?0===e("input[name=tag_ID]").length?this.globalsHolder().enhanced_attribute_page_type_add_category:this.globalsHolder().enhanced_attribute_page_type_edit_category:this.globalsHolder().enhanced_attribute_page_type_edit_product}},{key:"addInitialSelects",value:function(e){var t=this;if(e){this.getSelectedCategoryIds(e).forEach((function(e){t.addSelect(t.getOptions(e[1]),e[0])}));var o=this.getOptions(e);Object.keys(o).length&&this.addSelect(o)}else this.addSelect(this.getOptions()),this.addSelect({})}},{key:"requestAttributesIfValid",value:function(){if("true"===e("#wc_facebook_can_show_enhanced_catalog_attributes_id").val()&&(e(".wc-facebook-enhanced-catalog-attribute-row").remove(),this.isValid())){var t="#"+this.input_id,o=e(t).parents("div.form-field"),a=this.globalsHolder().enhanced_attribute_optional_selector;this.getPageType()===this.globalsHolder().enhanced_attribute_page_type_edit_category?o=e(t).parents("tr.form-field"):this.getPageType()===this.globalsHolder().enhanced_attribute_page_type_edit_product&&(o=e(t).parents("p.form-field")),e.get(this.globalsHolder().ajax_url,{action:"wc_facebook_enhanced_catalog_attributes",security:"",selected_category:e(t).val(),tag_id:parseInt(e("input[name=tag_ID]").val(),10),taxonomy:e("input[name=taxonomy]").val(),item_id:parseInt(e("input[name=post_ID]").val(),10),page_type:this.getPageType()},(function(t){var r=e(t);e("#"+a,r).on("change",(function(){e(".wc-facebook-enhanced-catalog-attribute-optional-row").toggleClass("hidden",!e(this).prop("checked"))})),r.insertAfter(o),e(document.body).trigger("init_tooltips")}))}}},{key:"onChange",value:function(t){t.hasClass("locked")&&t.closest(".wc-facebook-google-product-category-field").nextAll().remove();var o=t.val();if(o){var a=this.getOptions(o);Object.keys(a).length&&this.addSelect(a)}else(o=t.closest("#wc-facebook-google-product-category-fields").find(".wc-facebook-google-product-category-select").not(t).last().val())||this.addSelect({});e("#"+this.input_id).val(o),this.requestAttributesIfValid()}},{key:"isValid",value:function(){return e(".wc-facebook-google-product-category-select").filter((function(t,o){return""!==e(o).val()})).length>=2}},{key:"addSelect",value:function(t,o){var a=e("#wc-facebook-google-product-category-fields"),r=a.find(".wc-facebook-google-product-category-select"),n=e('<select class="wc-enhanced-select wc-facebook-google-product-category-select"></select>');r.addClass("locked"),a.append(e('<div class="wc-facebook-google-product-category-field" style="margin-bottom: 16px">').append(n)),n.attr("data-placeholder",this.getSelectPlaceholder(r,t)).append(e('<option value=""></option>')),Object.keys(t).forEach((function(o){n.append(e('<option value="'+o+'">'+t[o]+"</option>"))})),n.val(o).select2({allowClear:!0})}},{key:"getSelectPlaceholder",value:function(e,t){return 0===e.length?facebook_for_woocommerce_google_product_category.i18n.top_level_dropdown_placeholder:1===e.length&&0===Object.keys(t).length?facebook_for_woocommerce_google_product_category.i18n.second_level_empty_dropdown_placeholder:facebook_for_woocommerce_google_product_category.i18n.general_dropdown_placeholder}},{key:"getOptions",value:function(e){return void 0===e||""===e?this.getTopLevelOptions():void 0===this.categories[e]||void 0===this.categories[e].options?[]:this.categories[e].options}},{key:"getTopLevelOptions",value:function(){var e=this,t={};return Object.keys(this.categories).forEach((function(o){e.categories[o].parent||(t[o]=e.categories[o].label)})),t}},{key:"getSelectedCategoryIds",value:function(e){var t=[];do{void 0!==this.categories[e]&&(t.push([e,this.categories[e].parent]),e=this.categories[e].parent)}while(""!==e);return t.reverse()}}]),t}()}))}]);
changelog.txt CHANGED
@@ -1,6 +1,13 @@
1
  *** Facebook for WooCommerce Changelog ***
2
 
3
- 2021-05-28 - version 2.5.1
 
 
 
 
 
 
 
4
  * Fix - Reinstate reset and delete functions in Facebook metabox on Edit product admin screen #1980
5
 
6
  2021-05-19 - version 2.5.0
1
  *** Facebook for WooCommerce Changelog ***
2
 
3
+ 2021-06-10 - version 2.6.0
4
+ * Fix – Add cron heartbeat and use to offload feed generation from init / admin_init (performance) #1953
5
+ * Fix – Clean up background sync options (performance) #1962
6
+ * Dev – Add tracker props to understand usage of feed-based sync and other FB business config options #1972
7
+ * Dev – Configure release tooling to auto-update version numbers in code #1982
8
+ * Dev – Refactor code responsible for validating whether a product should be synced to FB into one place #19333
9
+
10
+ 2021-05-28 - version 2.5.1
11
  * Fix - Reinstate reset and delete functions in Facebook metabox on Edit product admin screen #1980
12
 
13
  2021-05-19 - version 2.5.0
class-wc-facebookcommerce.php CHANGED
@@ -13,6 +13,8 @@ use SkyVerge\WooCommerce\Facebook\Lifecycle;
13
  use SkyVerge\WooCommerce\Facebook\Utilities\Background_Handle_Virtual_Products_Variations;
14
  use SkyVerge\WooCommerce\Facebook\Utilities\Background_Remove_Duplicate_Visibility_Meta;
15
  use SkyVerge\WooCommerce\PluginFramework\v5_10_0 as Framework;
 
 
16
 
17
  if ( ! class_exists( 'WC_Facebookcommerce' ) ) :
18
 
@@ -94,6 +96,9 @@ if ( ! class_exists( 'WC_Facebookcommerce' ) ) :
94
  /** @var \SkyVerge\WooCommerce\Facebook\Jobs\JobRegistry */
95
  public $job_registry;
96
 
 
 
 
97
  /**
98
  * Constructs the plugin.
99
  *
@@ -142,10 +147,14 @@ if ( ! class_exists( 'WC_Facebookcommerce' ) ) :
142
  require_once __DIR__ . '/facebook-commerce-messenger-chat.php';
143
  require_once __DIR__ . '/includes/Exceptions/ConnectWCAPIException.php';
144
 
 
 
 
145
  $this->product_feed = new \SkyVerge\WooCommerce\Facebook\Products\Feed();
146
  $this->products_stock_handler = new \SkyVerge\WooCommerce\Facebook\Products\Stock();
147
  $this->products_sync_handler = new \SkyVerge\WooCommerce\Facebook\Products\Sync();
148
  $this->sync_background_handler = new \SkyVerge\WooCommerce\Facebook\Products\Sync\Background();
 
149
  $this->product_sets_sync_handler = new \SkyVerge\WooCommerce\Facebook\ProductSets\Sync();
150
  $this->commerce_handler = new \SkyVerge\WooCommerce\Facebook\Commerce();
151
  $this->fb_categories = new \SkyVerge\WooCommerce\Facebook\Products\FBCategories();
@@ -909,6 +918,18 @@ if ( ! class_exists( 'WC_Facebookcommerce' ) ) :
909
  return $this->commerce_handler;
910
  }
911
 
 
 
 
 
 
 
 
 
 
 
 
 
912
  /**
913
  * Gets the debug profiling logger instance.
914
  *
@@ -924,6 +945,17 @@ if ( ! class_exists( 'WC_Facebookcommerce' ) ) :
924
  return $instance;
925
  }
926
 
 
 
 
 
 
 
 
 
 
 
 
927
  /**
928
  * Gets the settings page URL.
929
  *
13
  use SkyVerge\WooCommerce\Facebook\Utilities\Background_Handle_Virtual_Products_Variations;
14
  use SkyVerge\WooCommerce\Facebook\Utilities\Background_Remove_Duplicate_Visibility_Meta;
15
  use SkyVerge\WooCommerce\PluginFramework\v5_10_0 as Framework;
16
+ use SkyVerge\WooCommerce\Facebook\ProductSync\ProductValidator as ProductSyncValidator;
17
+ use SkyVerge\WooCommerce\Facebook\Utilities\Heartbeat;
18
 
19
  if ( ! class_exists( 'WC_Facebookcommerce' ) ) :
20
 
96
  /** @var \SkyVerge\WooCommerce\Facebook\Jobs\JobRegistry */
97
  public $job_registry;
98
 
99
+ /** @var Heartbeat */
100
+ public $heartbeat;
101
+
102
  /**
103
  * Constructs the plugin.
104
  *
147
  require_once __DIR__ . '/facebook-commerce-messenger-chat.php';
148
  require_once __DIR__ . '/includes/Exceptions/ConnectWCAPIException.php';
149
 
150
+ $this->heartbeat = new Heartbeat( WC()->queue() );
151
+ $this->heartbeat->init();
152
+
153
  $this->product_feed = new \SkyVerge\WooCommerce\Facebook\Products\Feed();
154
  $this->products_stock_handler = new \SkyVerge\WooCommerce\Facebook\Products\Stock();
155
  $this->products_sync_handler = new \SkyVerge\WooCommerce\Facebook\Products\Sync();
156
  $this->sync_background_handler = new \SkyVerge\WooCommerce\Facebook\Products\Sync\Background();
157
+ $this->configuration_detection = new \SkyVerge\WooCommerce\Facebook\Feed\FeedConfigurationDetection();
158
  $this->product_sets_sync_handler = new \SkyVerge\WooCommerce\Facebook\ProductSets\Sync();
159
  $this->commerce_handler = new \SkyVerge\WooCommerce\Facebook\Commerce();
160
  $this->fb_categories = new \SkyVerge\WooCommerce\Facebook\Products\FBCategories();
918
  return $this->commerce_handler;
919
  }
920
 
921
+ /**
922
+ * Gets tracker instance.
923
+ *
924
+ * @since 2.6.0
925
+ *
926
+ * @return \SkyVerge\WooCommerce\Facebook\Utilities\Tracker
927
+ */
928
+ public function get_tracker() {
929
+
930
+ return $this->tracker;
931
+ }
932
+
933
  /**
934
  * Gets the debug profiling logger instance.
935
  *
945
  return $instance;
946
  }
947
 
948
+ /**
949
+ * Get the product sync validator class.
950
+ *
951
+ * @param WC_Product $product A product object to be validated.
952
+ *
953
+ * @return ProductSyncValidator
954
+ */
955
+ public function get_product_sync_validator( WC_Product $product ) {
956
+ return new ProductSyncValidator( $this->get_integration(), $product );
957
+ }
958
+
959
  /**
960
  * Gets the settings page URL.
961
  *
facebook-commerce-events-tracker.php CHANGED
@@ -1036,10 +1036,8 @@ if ( ! class_exists( 'WC_Facebookcommerce_EventsTracker' ) ) :
1036
  } catch ( Framework\SV_WC_API_Exception $exception ) {
1037
 
1038
  $success = false;
1039
-
1040
- if ( facebook_for_woocommerce()->get_integration()->is_debug_mode_enabled() ) {
1041
- facebook_for_woocommerce()->log( 'Could not send Pixel event: ' . $exception->getMessage() );
1042
- }
1043
  }
1044
 
1045
  return $success;
1036
  } catch ( Framework\SV_WC_API_Exception $exception ) {
1037
 
1038
  $success = false;
1039
+
1040
+ facebook_for_woocommerce()->log( 'Could not send Pixel event: ' . $exception->getMessage() );
 
 
1041
  }
1042
 
1043
  return $success;
facebook-commerce.php CHANGED
@@ -137,6 +137,8 @@ class WC_Facebookcommerce_Integration extends WC_Integration {
137
  /** @var array the page name and url */
138
  private $page;
139
 
 
 
140
 
141
  /** Legacy properties *********************************************************************************************/
142
 
@@ -685,6 +687,16 @@ class WC_Facebookcommerce_Integration extends WC_Integration {
685
  <?php
686
  }
687
 
 
 
 
 
 
 
 
 
 
 
688
 
689
  /**
690
  * Gets a list of Product Item IDs indexed by the ID of the variation.
@@ -1192,10 +1204,6 @@ class WC_Facebookcommerce_Integration extends WC_Integration {
1192
 
1193
  $product = wc_get_product( $product_id );
1194
 
1195
- if ( ! $this->product_should_be_synced( $product ) ) {
1196
- return;
1197
- }
1198
-
1199
  if ( $product->is_type( 'variable' ) ) {
1200
  $this->on_variable_product_publish( $product_id );
1201
  } else {
@@ -1343,25 +1351,19 @@ class WC_Facebookcommerce_Integration extends WC_Integration {
1343
  /**
1344
  * Determines whether the product with the given ID should be synced.
1345
  *
 
 
1346
  * @since 2.0.0
1347
  *
1348
  * @param \WC_Product|false $product product object
1349
  */
1350
  public function product_should_be_synced( $product ) {
1351
-
1352
- $should_be_synced = $this->is_product_sync_enabled();
1353
-
1354
- // can't sync if we don't have a valid product object
1355
- if ( $should_be_synced && ! $product instanceof \WC_Product ) {
1356
- $should_be_synced = false;
1357
- }
1358
-
1359
- // make sure the given product is enabled for sync
1360
- if ( $should_be_synced && ! Products::product_should_be_synced( $product ) ) {
1361
- $should_be_synced = false;
1362
  }
1363
-
1364
- return $should_be_synced;
1365
  }
1366
 
1367
 
137
  /** @var array the page name and url */
138
  private $page;
139
 
140
+ /** @var WC_Facebookcommerce_Graph_API API handling class. */
141
+ private $fbgraph;
142
 
143
  /** Legacy properties *********************************************************************************************/
144
 
687
  <?php
688
  }
689
 
690
+ /**
691
+ * Returns graph API client object.
692
+ *
693
+ * @since 2.6.0
694
+ *
695
+ * @return WC_Facebookcommerce_Graph_API
696
+ */
697
+ public function get_graph_api() {
698
+ return $this->fbgraph;
699
+ }
700
 
701
  /**
702
  * Gets a list of Product Item IDs indexed by the ID of the variation.
1204
 
1205
  $product = wc_get_product( $product_id );
1206
 
 
 
 
 
1207
  if ( $product->is_type( 'variable' ) ) {
1208
  $this->on_variable_product_publish( $product_id );
1209
  } else {
1351
  /**
1352
  * Determines whether the product with the given ID should be synced.
1353
  *
1354
+ * @deprecated use \SkyVerge\WooCommerce\Facebook\ProductSync\ProductValidator::validate instead
1355
+ *
1356
  * @since 2.0.0
1357
  *
1358
  * @param \WC_Product|false $product product object
1359
  */
1360
  public function product_should_be_synced( $product ) {
1361
+ try {
1362
+ facebook_for_woocommerce()->get_product_sync_validator( $product )->validate();
1363
+ return true;
1364
+ } catch ( \Exception $e ) {
1365
+ return false;
 
 
 
 
 
 
1366
  }
 
 
1367
  }
1368
 
1369
 
facebook-for-woocommerce.php CHANGED
@@ -10,10 +10,10 @@
10
  * Description: Grow your business on Facebook! Use this official plugin to help sell more of your products using Facebook. After completing the setup, you'll be ready to create ads that promote your products and you can also create a shop section on your Page where customers can browse your products on Facebook.
11
  * Author: Facebook
12
  * Author URI: https://www.facebook.com/
13
- * Version: 2.5.1
14
  * Text Domain: facebook-for-woocommerce
15
  * WC requires at least: 3.5.0
16
- * WC tested up to: 5.2.2
17
  * Requires PHP: 7.0
18
  *
19
  * @package FacebookCommerce
@@ -31,7 +31,7 @@ class WC_Facebook_Loader {
31
  /**
32
  * @var string the plugin version. This must be in the main plugin file to be automatically bumped by Woorelease.
33
  */
34
- const PLUGIN_VERSION = '2.5.1'; // WRCS: DEFINED_VERSION.
35
 
36
  // Minimum PHP version required by this plugin.
37
  const MINIMUM_PHP_VERSION = '7.0.0';
10
  * Description: Grow your business on Facebook! Use this official plugin to help sell more of your products using Facebook. After completing the setup, you'll be ready to create ads that promote your products and you can also create a shop section on your Page where customers can browse your products on Facebook.
11
  * Author: Facebook
12
  * Author URI: https://www.facebook.com/
13
+ * Version: 2.6.0
14
  * Text Domain: facebook-for-woocommerce
15
  * WC requires at least: 3.5.0
16
+ * WC tested up to: 5.7.0
17
  * Requires PHP: 7.0
18
  *
19
  * @package FacebookCommerce
31
  /**
32
  * @var string the plugin version. This must be in the main plugin file to be automatically bumped by Woorelease.
33
  */
34
+ const PLUGIN_VERSION = '2.6.0'; // WRCS: DEFINED_VERSION.
35
 
36
  // Minimum PHP version required by this plugin.
37
  const MINIMUM_PHP_VERSION = '7.0.0';
i18n/languages/facebook-for-woocommerce.pot CHANGED
@@ -2,10 +2,10 @@
2
  # This file is distributed under the same license as the Facebook for WooCommerce package.
3
  msgid ""
4
  msgstr ""
5
- "Project-Id-Version: Facebook for WooCommerce 2.5.1\n"
6
  "Report-Msgid-Bugs-To: "
7
  "https://woocommerce.com/my-account/marketplace-ticket-form/\n"
8
- "POT-Creation-Date: 2021-05-28 02:58:40+00:00\n"
9
  "MIME-Version: 1.0\n"
10
  "Content-Type: text/plain; charset=utf-8\n"
11
  "Content-Transfer-Encoding: 8bit\n"
@@ -13,7 +13,7 @@ msgstr ""
13
  "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14
  "Language-Team: LANGUAGE <LL@li.org>\n"
15
 
16
- #: class-wc-facebookcommerce.php:252
17
  #. translators: Placeholders %1$s - opening strong HTML tag, %2$s - closing
18
  #. strong HTML tag, %3$s - opening link HTML tag, %4$s - closing link HTML tag
19
  msgid ""
@@ -21,7 +21,7 @@ msgid ""
21
  "Facebook for WooCommerce connection. Please %3$sclick here%4$s to reconnect!"
22
  msgstr ""
23
 
24
- #: class-wc-facebookcommerce.php:273
25
  #. translators: Placeholders %1$s - opening link HTML tag, %2$s - closing link
26
  #. HTML tag
27
  msgid ""
@@ -29,7 +29,7 @@ msgid ""
29
  "under %1$sWooCommerce > Facebook%2$s."
30
  msgstr ""
31
 
32
- #: class-wc-facebookcommerce.php:293
33
  #. translators: Placeholders %1$s - opening strong HTML tag, %2$s - closing
34
  #. strong HTML tag, %3$s - opening link HTML tag, %4$s - closing link HTML tag
35
  msgid ""
@@ -37,7 +37,7 @@ msgid ""
37
  "configuration, %3$scomplete the setup steps%4$s."
38
  msgstr ""
39
 
40
- #: class-wc-facebookcommerce.php:325
41
  #. translators: Placeholders: %1$s - <strong> tag, %2$s - </strong> tag, %3$s -
42
  #. <a> tag, %4$s - </a> tag
43
  msgid ""
@@ -46,7 +46,7 @@ msgid ""
46
  "Connection%4$s area."
47
  msgstr ""
48
 
49
- #: class-wc-facebookcommerce.php:348
50
  #. translators: Placeholders: %1$s - <strong> tag, %2$s - </strong> tag, %3$s -
51
  #. <a> tag, %4$s - </a> tag
52
  msgid ""
@@ -55,51 +55,51 @@ msgid ""
55
  "products."
56
  msgstr ""
57
 
58
- #: class-wc-facebookcommerce.php:374
59
  #. translators: Placeholders: %1$s - opening <a> HTML link tag, %2$s - closing
60
  #. </a> HTML link tag
61
  msgid "Heads up! The Facebook menu is now located under the %1$sMarketing%2$s menu."
62
  msgstr ""
63
 
64
- #: class-wc-facebookcommerce.php:446
65
  msgid "FB Product Sets"
66
  msgstr ""
67
 
68
- #: class-wc-facebookcommerce.php:447
69
  msgid "FB Product Set"
70
  msgstr ""
71
 
72
- #: class-wc-facebookcommerce.php:455
73
  #. translators: Edit item label
74
  msgid "Edit %s"
75
  msgstr ""
76
 
77
- #: class-wc-facebookcommerce.php:457
78
  #. translators: Add new label
79
  msgid "Add new %s"
80
  msgstr ""
81
 
82
- #: class-wc-facebookcommerce.php:460
83
  #. translators: No items found text
84
  msgid "No %s found."
85
  msgstr ""
86
 
87
- #: class-wc-facebookcommerce.php:462
88
  #. translators: Search label
89
  msgid "Search %s."
90
  msgstr ""
91
 
92
- #: class-wc-facebookcommerce.php:464
93
  #. translators: Text label
94
  msgid "Separate %s with commas"
95
  msgstr ""
96
 
97
- #: class-wc-facebookcommerce.php:466
98
  #. translators: Text label
99
  msgid "Choose from the most used %s"
100
  msgstr ""
101
 
102
- #: class-wc-facebookcommerce.php:582
103
  msgid "Cannot create the API instance because the access token is missing."
104
  msgstr ""
105
 
@@ -107,69 +107,69 @@ msgstr ""
107
  msgid "Facebook for WooCommerce"
108
  msgstr ""
109
 
110
- #: facebook-commerce.php:228
111
  msgid "Facebook Commerce and Dynamic Ads (Pixel) Extension"
112
  msgstr ""
113
 
114
- #: facebook-commerce.php:640
115
  msgid "Facebook ID:"
116
  msgstr ""
117
 
118
- #: facebook-commerce.php:648
119
  msgid "Variant IDs:"
120
  msgstr ""
121
 
122
- #: facebook-commerce.php:666
123
  msgid "Reset Facebook metadata"
124
  msgstr ""
125
 
126
- #: facebook-commerce.php:671
127
  msgid "Delete product(s) on Facebook"
128
  msgstr ""
129
 
130
- #: facebook-commerce.php:679
131
  msgid "This product is not yet synced to Facebook."
132
  msgstr ""
133
 
134
- #: facebook-commerce.php:1491
135
  msgid "Nothing to update for product group for %1$s"
136
  msgstr ""
137
 
138
- #: facebook-commerce.php:1885
139
  #. translators: Placeholders %1$s - original error message from Facebook API
140
  msgid "There was an issue connecting to the Facebook API: %1$s"
141
  msgstr ""
142
 
143
- #: facebook-commerce.php:2221
144
  msgid "Your connection has expired."
145
  msgstr ""
146
 
147
- #: facebook-commerce.php:2221
148
  msgid ""
149
  "Please click Manage connection > Advanced Options > Update Token to refresh "
150
  "your connection to Facebook."
151
  msgstr ""
152
 
153
- #: facebook-commerce.php:2228
154
  #. translators: Placeholders %s - error message
155
  msgid "There was an error trying to sync the products to Facebook. %s"
156
  msgstr ""
157
 
158
- #: facebook-commerce.php:2251 facebook-commerce.php:2424
159
  msgid "Product sync is disabled."
160
  msgstr ""
161
 
162
- #: facebook-commerce.php:2258 facebook-commerce.php:2431
163
  msgid "The plugin is not configured or the Catalog ID is missing."
164
  msgstr ""
165
 
166
- #: facebook-commerce.php:2281
167
  msgid ""
168
  "A product sync is in progress. Please wait until the sync finishes before "
169
  "starting a new one."
170
  msgstr ""
171
 
172
- #: facebook-commerce.php:2293 facebook-commerce.php:2445
173
  msgid ""
174
  "We've detected that your Facebook Product Catalog is no longer valid. This "
175
  "may happen if it was deleted, but could also be a temporary error. If the "
@@ -177,33 +177,33 @@ msgid ""
177
  "and setup the plugin again."
178
  msgstr ""
179
 
180
- #: facebook-commerce.php:2469
181
  msgid "We couldn't create the feed or upload the product information."
182
  msgstr ""
183
 
184
- #: facebook-commerce.php:2937
185
  msgid "Hi! We're here to answer any questions you may have."
186
  msgstr ""
187
 
188
- #: facebook-commerce.php:3304
189
  msgid "Facebook for WooCommerce error:"
190
  msgstr ""
191
 
192
- #: facebook-commerce.php:3386
193
  msgid ""
194
  "There was an error trying to retrieve information about the Facebook page: "
195
  "%s"
196
  msgstr ""
197
 
198
- #: facebook-commerce.php:3437
199
  msgid "Get started with Messenger Customer Chat"
200
  msgstr ""
201
 
202
- #: facebook-commerce.php:3438
203
  msgid "Get started with Instagram Shopping"
204
  msgstr ""
205
 
206
- #: facebook-commerce.php:3673
207
  #. translators: Placeholders %1$s - original error message from Facebook API
208
  msgid "There was an issue connecting to the Facebook API: %s"
209
  msgstr ""
@@ -240,11 +240,11 @@ msgstr ""
240
  msgid "Order not found"
241
  msgstr ""
242
 
243
- #: includes/AJAX.php:337 includes/AJAX.php:408
244
  msgid "Go to Settings"
245
  msgstr ""
246
 
247
- #: includes/AJAX.php:342 includes/AJAX.php:413 includes/AJAX.php:479
248
  #: includes/Admin/Product_Categories.php:118
249
  #: includes/Admin/Settings_Screens/Product_Sync.php:157 includes/Admin.php:391
250
  msgid "Cancel"
@@ -259,7 +259,7 @@ msgid ""
259
  "or click Cancel and update the product's category / tag assignments."
260
  msgstr ""
261
 
262
- #: includes/AJAX.php:420
263
  msgid ""
264
  "One or more of the selected products belongs to a category or tag that is "
265
  "excluded from the Facebook catalog sync. To sync these products to "
@@ -267,11 +267,11 @@ msgid ""
267
  "settings."
268
  msgstr ""
269
 
270
- #: includes/AJAX.php:473
271
  msgid "Exclude Products"
272
  msgstr ""
273
 
274
- #: includes/AJAX.php:488
275
  #. translators: Placeholder %s - <br/> tags
276
  msgid ""
277
  "The categories and/or tags that you have selected to exclude from sync "
@@ -980,10 +980,42 @@ msgstr ""
980
  msgid "Page %s not authorized."
981
  msgstr ""
982
 
983
- #: includes/Handlers/Connection.php:1385
984
  msgid "You do not have permission to finish App Store login."
985
  msgstr ""
986
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
987
  #: includes/Products/Sync/Background.php:68
988
  msgid "Job data key \"%s\" not set"
989
  msgstr ""
@@ -1014,19 +1046,19 @@ msgstr ""
1014
  msgid "Dismiss"
1015
  msgstr ""
1016
 
1017
- #: includes/fbproductfeed.php:465
1018
  msgid "Could not create product catalog feed directory"
1019
  msgstr ""
1020
 
1021
- #: includes/fbproductfeed.php:530
1022
  msgid "Could not open the product catalog temporary feed file for writing"
1023
  msgstr ""
1024
 
1025
- #: includes/fbproductfeed.php:537
1026
  msgid "Could not open the product catalog feed file for writing"
1027
  msgstr ""
1028
 
1029
- #: includes/fbproductfeed.php:580
1030
  msgid "Could not rename the product catalog feed file"
1031
  msgstr ""
1032
 
2
  # This file is distributed under the same license as the Facebook for WooCommerce package.
3
  msgid ""
4
  msgstr ""
5
+ "Project-Id-Version: Facebook for WooCommerce 2.6.0\n"
6
  "Report-Msgid-Bugs-To: "
7
  "https://woocommerce.com/my-account/marketplace-ticket-form/\n"
8
+ "POT-Creation-Date: 2021-06-10 18:28:50+00:00\n"
9
  "MIME-Version: 1.0\n"
10
  "Content-Type: text/plain; charset=utf-8\n"
11
  "Content-Transfer-Encoding: 8bit\n"
13
  "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14
  "Language-Team: LANGUAGE <LL@li.org>\n"
15
 
16
+ #: class-wc-facebookcommerce.php:261
17
  #. translators: Placeholders %1$s - opening strong HTML tag, %2$s - closing
18
  #. strong HTML tag, %3$s - opening link HTML tag, %4$s - closing link HTML tag
19
  msgid ""
21
  "Facebook for WooCommerce connection. Please %3$sclick here%4$s to reconnect!"
22
  msgstr ""
23
 
24
+ #: class-wc-facebookcommerce.php:282
25
  #. translators: Placeholders %1$s - opening link HTML tag, %2$s - closing link
26
  #. HTML tag
27
  msgid ""
29
  "under %1$sWooCommerce > Facebook%2$s."
30
  msgstr ""
31
 
32
+ #: class-wc-facebookcommerce.php:302
33
  #. translators: Placeholders %1$s - opening strong HTML tag, %2$s - closing
34
  #. strong HTML tag, %3$s - opening link HTML tag, %4$s - closing link HTML tag
35
  msgid ""
37
  "configuration, %3$scomplete the setup steps%4$s."
38
  msgstr ""
39
 
40
+ #: class-wc-facebookcommerce.php:334
41
  #. translators: Placeholders: %1$s - <strong> tag, %2$s - </strong> tag, %3$s -
42
  #. <a> tag, %4$s - </a> tag
43
  msgid ""
46
  "Connection%4$s area."
47
  msgstr ""
48
 
49
+ #: class-wc-facebookcommerce.php:357
50
  #. translators: Placeholders: %1$s - <strong> tag, %2$s - </strong> tag, %3$s -
51
  #. <a> tag, %4$s - </a> tag
52
  msgid ""
55
  "products."
56
  msgstr ""
57
 
58
+ #: class-wc-facebookcommerce.php:383
59
  #. translators: Placeholders: %1$s - opening <a> HTML link tag, %2$s - closing
60
  #. </a> HTML link tag
61
  msgid "Heads up! The Facebook menu is now located under the %1$sMarketing%2$s menu."
62
  msgstr ""
63
 
64
+ #: class-wc-facebookcommerce.php:455
65
  msgid "FB Product Sets"
66
  msgstr ""
67
 
68
+ #: class-wc-facebookcommerce.php:456
69
  msgid "FB Product Set"
70
  msgstr ""
71
 
72
+ #: class-wc-facebookcommerce.php:464
73
  #. translators: Edit item label
74
  msgid "Edit %s"
75
  msgstr ""
76
 
77
+ #: class-wc-facebookcommerce.php:466
78
  #. translators: Add new label
79
  msgid "Add new %s"
80
  msgstr ""
81
 
82
+ #: class-wc-facebookcommerce.php:469
83
  #. translators: No items found text
84
  msgid "No %s found."
85
  msgstr ""
86
 
87
+ #: class-wc-facebookcommerce.php:471
88
  #. translators: Search label
89
  msgid "Search %s."
90
  msgstr ""
91
 
92
+ #: class-wc-facebookcommerce.php:473
93
  #. translators: Text label
94
  msgid "Separate %s with commas"
95
  msgstr ""
96
 
97
+ #: class-wc-facebookcommerce.php:475
98
  #. translators: Text label
99
  msgid "Choose from the most used %s"
100
  msgstr ""
101
 
102
+ #: class-wc-facebookcommerce.php:591
103
  msgid "Cannot create the API instance because the access token is missing."
104
  msgstr ""
105
 
107
  msgid "Facebook for WooCommerce"
108
  msgstr ""
109
 
110
+ #: facebook-commerce.php:230
111
  msgid "Facebook Commerce and Dynamic Ads (Pixel) Extension"
112
  msgstr ""
113
 
114
+ #: facebook-commerce.php:642
115
  msgid "Facebook ID:"
116
  msgstr ""
117
 
118
+ #: facebook-commerce.php:650
119
  msgid "Variant IDs:"
120
  msgstr ""
121
 
122
+ #: facebook-commerce.php:668
123
  msgid "Reset Facebook metadata"
124
  msgstr ""
125
 
126
+ #: facebook-commerce.php:673
127
  msgid "Delete product(s) on Facebook"
128
  msgstr ""
129
 
130
+ #: facebook-commerce.php:681
131
  msgid "This product is not yet synced to Facebook."
132
  msgstr ""
133
 
134
+ #: facebook-commerce.php:1493
135
  msgid "Nothing to update for product group for %1$s"
136
  msgstr ""
137
 
138
+ #: facebook-commerce.php:1887
139
  #. translators: Placeholders %1$s - original error message from Facebook API
140
  msgid "There was an issue connecting to the Facebook API: %1$s"
141
  msgstr ""
142
 
143
+ #: facebook-commerce.php:2223
144
  msgid "Your connection has expired."
145
  msgstr ""
146
 
147
+ #: facebook-commerce.php:2223
148
  msgid ""
149
  "Please click Manage connection > Advanced Options > Update Token to refresh "
150
  "your connection to Facebook."
151
  msgstr ""
152
 
153
+ #: facebook-commerce.php:2230
154
  #. translators: Placeholders %s - error message
155
  msgid "There was an error trying to sync the products to Facebook. %s"
156
  msgstr ""
157
 
158
+ #: facebook-commerce.php:2253 facebook-commerce.php:2426
159
  msgid "Product sync is disabled."
160
  msgstr ""
161
 
162
+ #: facebook-commerce.php:2260 facebook-commerce.php:2433
163
  msgid "The plugin is not configured or the Catalog ID is missing."
164
  msgstr ""
165
 
166
+ #: facebook-commerce.php:2283
167
  msgid ""
168
  "A product sync is in progress. Please wait until the sync finishes before "
169
  "starting a new one."
170
  msgstr ""
171
 
172
+ #: facebook-commerce.php:2295 facebook-commerce.php:2447
173
  msgid ""
174
  "We've detected that your Facebook Product Catalog is no longer valid. This "
175
  "may happen if it was deleted, but could also be a temporary error. If the "
177
  "and setup the plugin again."
178
  msgstr ""
179
 
180
+ #: facebook-commerce.php:2471
181
  msgid "We couldn't create the feed or upload the product information."
182
  msgstr ""
183
 
184
+ #: facebook-commerce.php:2939
185
  msgid "Hi! We're here to answer any questions you may have."
186
  msgstr ""
187
 
188
+ #: facebook-commerce.php:3306
189
  msgid "Facebook for WooCommerce error:"
190
  msgstr ""
191
 
192
+ #: facebook-commerce.php:3388
193
  msgid ""
194
  "There was an error trying to retrieve information about the Facebook page: "
195
  "%s"
196
  msgstr ""
197
 
198
+ #: facebook-commerce.php:3439
199
  msgid "Get started with Messenger Customer Chat"
200
  msgstr ""
201
 
202
+ #: facebook-commerce.php:3440
203
  msgid "Get started with Instagram Shopping"
204
  msgstr ""
205
 
206
+ #: facebook-commerce.php:3675
207
  #. translators: Placeholders %1$s - original error message from Facebook API
208
  msgid "There was an issue connecting to the Facebook API: %s"
209
  msgstr ""
240
  msgid "Order not found"
241
  msgstr ""
242
 
243
+ #: includes/AJAX.php:337 includes/AJAX.php:405
244
  msgid "Go to Settings"
245
  msgstr ""
246
 
247
+ #: includes/AJAX.php:342 includes/AJAX.php:410 includes/AJAX.php:476
248
  #: includes/Admin/Product_Categories.php:118
249
  #: includes/Admin/Settings_Screens/Product_Sync.php:157 includes/Admin.php:391
250
  msgid "Cancel"
259
  "or click Cancel and update the product's category / tag assignments."
260
  msgstr ""
261
 
262
+ #: includes/AJAX.php:417
263
  msgid ""
264
  "One or more of the selected products belongs to a category or tag that is "
265
  "excluded from the Facebook catalog sync. To sync these products to "
267
  "settings."
268
  msgstr ""
269
 
270
+ #: includes/AJAX.php:470
271
  msgid "Exclude Products"
272
  msgstr ""
273
 
274
+ #: includes/AJAX.php:485
275
  #. translators: Placeholder %s - <br/> tags
276
  msgid ""
277
  "The categories and/or tags that you have selected to exclude from sync "
980
  msgid "Page %s not authorized."
981
  msgstr ""
982
 
983
+ #: includes/Handlers/Connection.php:1379
984
  msgid "You do not have permission to finish App Store login."
985
  msgstr ""
986
 
987
+ #: includes/ProductSync/ProductValidator.php:148
988
+ msgid "Product sync is globally disabled."
989
+ msgstr ""
990
+
991
+ #: includes/ProductSync/ProductValidator.php:161
992
+ msgid "Product is not published."
993
+ msgstr ""
994
+
995
+ #: includes/ProductSync/ProductValidator.php:172
996
+ msgid "Product must be in stock."
997
+ msgstr ""
998
+
999
+ #: includes/ProductSync/ProductValidator.php:187
1000
+ msgid "Product is hidden from catalog and search."
1001
+ msgstr ""
1002
+
1003
+ #: includes/ProductSync/ProductValidator.php:202
1004
+ msgid "Product excluded because of categories."
1005
+ msgstr ""
1006
+
1007
+ #: includes/ProductSync/ProductValidator.php:209
1008
+ msgid "Product excluded because of tags."
1009
+ msgstr ""
1010
+
1011
+ #: includes/ProductSync/ProductValidator.php:220
1012
+ msgid "Sync disabled in product field."
1013
+ msgstr ""
1014
+
1015
+ #: includes/ProductSync/ProductValidator.php:255
1016
+ msgid "If product is not simple, variable or variation it must have a price."
1017
+ msgstr ""
1018
+
1019
  #: includes/Products/Sync/Background.php:68
1020
  msgid "Job data key \"%s\" not set"
1021
  msgstr ""
1046
  msgid "Dismiss"
1047
  msgstr ""
1048
 
1049
+ #: includes/fbproductfeed.php:469
1050
  msgid "Could not create product catalog feed directory"
1051
  msgstr ""
1052
 
1053
+ #: includes/fbproductfeed.php:534
1054
  msgid "Could not open the product catalog temporary feed file for writing"
1055
  msgstr ""
1056
 
1057
+ #: includes/fbproductfeed.php:541
1058
  msgid "Could not open the product catalog feed file for writing"
1059
  msgstr ""
1060
 
1061
+ #: includes/fbproductfeed.php:584
1062
  msgid "Could not rename the product catalog feed file"
1063
  msgstr ""
1064
 
includes/AJAX.php CHANGED
@@ -384,12 +384,9 @@ class AJAX {
384
  $has_excluded_term = false;
385
 
386
  foreach ( $product_ids as $product_id ) {
387
-
388
  $product = wc_get_product( $product_id );
389
 
390
- // product belongs to at least one excluded term: break the loop
391
- if ( $product instanceof \WC_Product && Products::is_sync_excluded_for_product_terms( $product ) ) {
392
-
393
  $has_excluded_term = true;
394
  break;
395
  }
384
  $has_excluded_term = false;
385
 
386
  foreach ( $product_ids as $product_id ) {
 
387
  $product = wc_get_product( $product_id );
388
 
389
+ if ( $product instanceof \WC_Product && ! facebook_for_woocommerce()->get_product_sync_validator( $product )->passes_product_terms_check() ) {
 
 
390
  $has_excluded_term = true;
391
  break;
392
  }
includes/API/FBE/Configuration/Read/Response.php CHANGED
@@ -40,5 +40,40 @@ class Response extends API\Response {
40
  return $configuration;
41
  }
42
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
 
44
  }
40
  return $configuration;
41
  }
42
 
43
+ /**
44
+ * Is Instagram Shopping enabled?
45
+ *
46
+ * @since 2.6.0
47
+ *
48
+ * @return boolean
49
+ */
50
+ public function is_ig_shopping_enabled() {
51
+
52
+ $ig_shopping_enabled = false;
53
+
54
+ if ( ! empty( $this->response_data->ig_shopping ) && is_object( $this->response_data->ig_shopping ) ) {
55
+ $ig_shopping_enabled = ! ! $this->response_data->ig_shopping->enabled;
56
+ }
57
+
58
+ return $ig_shopping_enabled;
59
+ }
60
+
61
+ /**
62
+ * Is Instagram CTA enabled?
63
+ *
64
+ * @since 2.6.0
65
+ *
66
+ * @return boolean
67
+ */
68
+ public function is_ig_cta_enabled() {
69
+
70
+ $ig_cta_enabled = false;
71
+
72
+ if ( ! empty( $this->response_data->ig_cta ) && is_object( $this->response_data->ig_cta ) ) {
73
+ $ig_cta_enabled = ! ! $this->response_data->ig_cta->enabled;
74
+ }
75
+
76
+ return $ig_cta_enabled;
77
+ }
78
 
79
  }
includes/Feed/FeedConfigurationDetection.php ADDED
@@ -0,0 +1,210 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace SkyVerge\WooCommerce\Facebook\Feed;
4
+
5
+ if ( ! defined( 'ABSPATH' ) ) {
6
+ exit;
7
+ }
8
+
9
+ use Error;
10
+ use SkyVerge\WooCommerce\Facebook\Utilities\Heartbeat;
11
+ use SkyVerge\WooCommerce\Facebook\Products\Feed;
12
+
13
+ /**
14
+ * A class responsible detecting feed configuration.
15
+ */
16
+ class FeedConfigurationDetection {
17
+
18
+ /**
19
+ * Constructor.
20
+ */
21
+ public function __construct() {
22
+ add_action( Heartbeat::DAILY, array( $this, 'track_data_source_feed_tracker_info' ) );
23
+ }
24
+
25
+ /**
26
+ * Store config settings for feed-based sync for WooCommerce Tracker.
27
+ *
28
+ * Gets various settings related to the feed, and data about recent uploads.
29
+ * This is formatted into an array of keys/values, and saved to a transient for inclusion in tracker snapshot.
30
+ * Note this does not send the data to tracker - this happens later (see Tracker class).
31
+ *
32
+ * @since 2.6.0
33
+ * @return void
34
+ */
35
+ public function track_data_source_feed_tracker_info() {
36
+ try {
37
+ $info = $this->get_data_source_feed_tracker_info();
38
+ facebook_for_woocommerce()->get_tracker()->track_facebook_feed_config( $info );
39
+ } catch ( \Error $error ) {
40
+ facebook_for_woocommerce()->log( 'Unable to detect valid feed configuration: ' . $error->getMessage() );
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Get config settings for feed-based sync for WooCommerce Tracker.
46
+ *
47
+ * @throws Error Catalog id missing.
48
+ * @return Array Key-value array of various configuration settings.
49
+ */
50
+ private function get_data_source_feed_tracker_info() {
51
+ $integration = facebook_for_woocommerce()->get_integration();
52
+ $graph_api = $integration->get_graph_api();
53
+ $integration_feed_id = $integration->get_feed_id();
54
+ $catalog_id = $integration->get_product_catalog_id();
55
+
56
+ $info = array();
57
+ $info['site-feed-id'] = $integration_feed_id;
58
+
59
+ // No catalog id. Most probably means that we don't have a valid connection.
60
+ if ( '' === $catalog_id ) {
61
+ throw new Error( 'No catalog ID' );
62
+ }
63
+
64
+ // Get all feeds configured for the catalog.
65
+ $feed_nodes = $this->get_feed_nodes_for_catalog( $catalog_id, $graph_api );
66
+
67
+ $info['feed-count'] = count( $feed_nodes );
68
+
69
+ // Check if the catalog has any feed configured.
70
+ if ( empty( $feed_nodes ) ) {
71
+ throw new Error( 'No feed nodes for catalog' );
72
+ }
73
+
74
+ /*
75
+ * We will only track settings for one feed config (for now at least).
76
+ * So we need to determine which is the most relevant feed.
77
+ * If there is only one, we use that.
78
+ * If one has the same ID as $integration_feed_id, we use that.
79
+ * Otherwise we pick the one that was most recently updated.
80
+ */
81
+ $active_feed_metadata = null;
82
+ foreach ( $feed_nodes as $feed ) {
83
+ $metadata = $this->get_feed_metadata( $feed['id'], $graph_api );
84
+
85
+ if ( $feed['id'] === $integration_feed_id ) {
86
+ $active_feed_metadata = $metadata;
87
+ break;
88
+ }
89
+
90
+ if ( ! array_key_exists( 'latest_upload', $metadata ) || ! array_key_exists( 'start_time', $metadata['latest_upload'] ) ) {
91
+ continue;
92
+ }
93
+ $metadata['latest_upload_time'] = strtotime( $metadata['latest_upload']['start_time'] );
94
+ if ( ! $active_feed_metadata ||
95
+ ( $metadata['latest_upload_time'] > $active_feed_metadata['latest_upload_time'] ) ) {
96
+ $active_feed_metadata = $metadata;
97
+ }
98
+ }
99
+
100
+ $active_feed['created-time'] = gmdate( 'Y-m-d H:i:s', strtotime( $active_feed_metadata['created_time'] ) );
101
+ $active_feed['product-count'] = $active_feed_metadata['product_count'];
102
+
103
+ /*
104
+ * Upload schedule settings can be in two keys:
105
+ * `schedule` => full replace of catalog with items in feed (including delete).
106
+ * `update_schedule` => append any new or updated products to catalog.
107
+ * These may both be configured; we will track settings for each individually (i.e. both).
108
+ * https://developers.facebook.com/docs/marketing-api/reference/product-feed/
109
+ */
110
+ if ( array_key_exists( 'schedule', $active_feed_metadata ) ) {
111
+ $active_feed['schedule']['interval'] = $active_feed_metadata['schedule']['interval'];
112
+ $active_feed['schedule']['interval-count'] = $active_feed_metadata['schedule']['interval_count'];
113
+ }
114
+ if ( array_key_exists( 'update_schedule', $active_feed_metadata ) ) {
115
+ $active_feed['update-schedule']['interval'] = $active_feed_metadata['update_schedule']['interval'];
116
+ $active_feed['update-schedule']['interval-count'] = $active_feed_metadata['update_schedule']['interval_count'];
117
+ }
118
+
119
+ $info['active-feed'] = $active_feed;
120
+
121
+ $latest_upload = $active_feed_metadata['latest_upload'];
122
+ if ( array_key_exists( 'latest_upload', $active_feed_metadata ) ) {
123
+ $upload = array();
124
+
125
+ if ( array_key_exists( 'end_time', $latest_upload ) ) {
126
+ $upload['end-time'] = gmdate( 'Y-m-d H:i:s', strtotime( $latest_upload['end_time'] ) );
127
+ }
128
+
129
+ // Get more detailed metadata about the most recent feed upload.
130
+ $upload_metadata = $this->get_feed_upload_metadata( $latest_upload['id'], $graph_api );
131
+
132
+ $upload['error-count'] = $upload_metadata['error_count'];
133
+ $upload['warning-count'] = $upload_metadata['warning_count'];
134
+ $upload['num-detected-items'] = $upload_metadata['num_detected_items'];
135
+ $upload['num-persisted-items'] = $upload_metadata['num_persisted_items'];
136
+
137
+ // True if the feed upload url (Facebook side) matches the feed endpoint URL and secret.
138
+ // If it doesn't match, it's likely it's unused.
139
+ $upload['url-matches-site-endpoint'] = wc_bool_to_string(
140
+ Feed::get_feed_data_url() === $upload_metadata['url']
141
+ );
142
+
143
+ $info['active-feed']['latest-upload'] = $upload;
144
+ }
145
+
146
+ return $info;
147
+ }
148
+
149
+ /**
150
+ * Given catalog id this function fetches all feed configurations defined for this catalog.
151
+ *
152
+ * @throws Error Feed configurations fetch was not successful.
153
+ * @param String $catalog_id Facebook Catalog ID.
154
+ * @param WC_Facebookcommerce_Graph_API $graph_api Facebook Graph handler instance.
155
+ *
156
+ * @return Array Array of feed configurations.
157
+ */
158
+ private function get_feed_nodes_for_catalog( $catalog_id, $graph_api ) {
159
+ // Read all the feed configurations specified for the catalog.
160
+ $response = $graph_api->read_feeds( $catalog_id );
161
+ $code = (int) wp_remote_retrieve_response_code( $response );
162
+ if ( 200 !== $code ) {
163
+ throw new Error( 'Reading catalog feeds error', $code );
164
+ }
165
+
166
+ $response_body = wp_remote_retrieve_body( $response );
167
+
168
+ $body = json_decode( $response_body, true );
169
+ return $body['data'];
170
+ }
171
+
172
+ /**
173
+ * Given feed id fetch this feed configuration metadata.
174
+ *
175
+ * @throws Error Feed metadata fetch was not successful.
176
+ * @param String $feed_id Facebook Feed ID.
177
+ * @param WC_Facebookcommerce_Graph_API $graph_api Facebook Graph handler instance.
178
+ *
179
+ * @return Array Array of feed configurations.
180
+ */
181
+ private function get_feed_metadata( $feed_id, $graph_api ) {
182
+ $response = $graph_api->read_feed_metadata( $feed_id );
183
+ $code = (int) wp_remote_retrieve_response_code( $response );
184
+ if ( 200 !== $code ) {
185
+ throw new Error( 'Error reading feed metadata', $code );
186
+ }
187
+ $response_body = wp_remote_retrieve_body( $response );
188
+ return json_decode( $response_body, true );
189
+ }
190
+
191
+ /**
192
+ * Given upload id fetch this upload execution metadata.
193
+ *
194
+ * @throws Error Upload metadata fetch was not successful.
195
+ * @param String $upload_id Facebook Feed upload ID.
196
+ * @param WC_Facebookcommerce_Graph_API $graph_api Facebook Graph handler instance.
197
+ *
198
+ * @return Array Array of feed configurations.
199
+ */
200
+ private function get_feed_upload_metadata( $upload_id, $graph_api ) {
201
+ $response = $graph_api->read_upload_metadata( $upload_id );
202
+ $code = (int) wp_remote_retrieve_response_code( $response );
203
+ if ( 200 !== $code ) {
204
+ throw new Error( 'Error reading feed upload metadata', $code );
205
+ }
206
+ $response_body = wp_remote_retrieve_body( $response );
207
+ return json_decode( $response_body, true );
208
+ }
209
+
210
+ }
includes/Handlers/Connection.php CHANGED
@@ -143,6 +143,10 @@ class Connection {
143
  try {
144
 
145
  $response = $this->get_plugin()->get_api()->get_business_configuration( $this->get_external_business_id() );
 
 
 
 
146
 
147
  // update the messenger settings
148
  if ( $messenger_configuration = $response->get_messenger_configuration() ) {
@@ -163,10 +167,8 @@ class Connection {
163
  }
164
  }
165
  } catch ( SV_WC_API_Exception $exception ) {
166
-
167
- if ( $this->get_plugin()->get_integration()->is_debug_mode_enabled() ) {
168
- $this->get_plugin()->log( 'Could not refresh business configuration. ' . $exception->getMessage() );
169
- }
170
  }
171
 
172
  set_transient( 'wc_facebook_business_configuration_refresh', time(), HOUR_IN_SECONDS );
@@ -196,9 +198,7 @@ class Connection {
196
 
197
  } catch ( SV_WC_API_Exception $exception ) {
198
 
199
- if ( $this->get_plugin()->get_integration()->is_debug_mode_enabled() ) {
200
- $this->get_plugin()->log( 'Could not refresh installation data. ' . $exception->getMessage() );
201
- }
202
  }
203
 
204
  set_transient( 'wc_facebook_connection_refresh', time(), DAY_IN_SECONDS );
@@ -1185,19 +1185,17 @@ class Connection {
1185
  // Reject other objects other than subscribed object
1186
  if ( empty( $data ) || ! isset( $data->object ) || self::WEBHOOK_SUBSCRIBED_OBJECT !== $data->object ) {
1187
 
1188
- if ( $this->get_plugin()->get_integration()->is_debug_mode_enabled() ) {
1189
- $this->get_plugin()->log( 'Wrong (or empty) WebHook Event received' );
1190
- $this->get_plugin()->log( print_r( $data, true ) ); //phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r
1191
- }
1192
-
1193
  return;
1194
  }
1195
 
1196
  $log_data = array();
1197
- if ( $this->get_plugin()->get_integration()->is_debug_mode_enabled() ) {
1198
- $this->get_plugin()->log( 'WebHook User Event received' );
1199
- $this->get_plugin()->log( print_r( $data, true ) ); //phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r
1200
- }
1201
 
1202
  $entry = (array) $data->entry[0];
1203
  if ( empty( $entry ) ) {
@@ -1309,16 +1307,12 @@ class Connection {
1309
 
1310
  } catch ( \Exception $e ) {
1311
 
1312
- if ( $this->get_plugin()->get_integration()->is_debug_mode_enabled() ) {
1313
- $this->get_plugin()->log( 'Could not request Page Token: ' . $e->getMessage() );
1314
- }
1315
  }
1316
  }//end if
1317
 
1318
- if ( $this->get_plugin()->get_integration()->is_debug_mode_enabled() ) {
1319
- $this->get_plugin()->log( 'WebHook User event saved data' );
1320
- $this->get_plugin()->log( print_r( $log_data, true ) ); //phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r
1321
- }
1322
  }
1323
 
1324
 
143
  try {
144
 
145
  $response = $this->get_plugin()->get_api()->get_business_configuration( $this->get_external_business_id() );
146
+ facebook_for_woocommerce()->get_tracker()->track_facebook_business_config(
147
+ $response->is_ig_shopping_enabled(),
148
+ $response->is_ig_cta_enabled()
149
+ );
150
 
151
  // update the messenger settings
152
  if ( $messenger_configuration = $response->get_messenger_configuration() ) {
167
  }
168
  }
169
  } catch ( SV_WC_API_Exception $exception ) {
170
+
171
+ $this->get_plugin()->log( 'Could not refresh business configuration. ' . $exception->getMessage() );
 
 
172
  }
173
 
174
  set_transient( 'wc_facebook_business_configuration_refresh', time(), HOUR_IN_SECONDS );
198
 
199
  } catch ( SV_WC_API_Exception $exception ) {
200
 
201
+ $this->get_plugin()->log( 'Could not refresh installation data. ' . $exception->getMessage() );
 
 
202
  }
203
 
204
  set_transient( 'wc_facebook_connection_refresh', time(), DAY_IN_SECONDS );
1185
  // Reject other objects other than subscribed object
1186
  if ( empty( $data ) || ! isset( $data->object ) || self::WEBHOOK_SUBSCRIBED_OBJECT !== $data->object ) {
1187
 
1188
+ $this->get_plugin()->log( 'Wrong (or empty) WebHook Event received' );
1189
+ $this->get_plugin()->log( print_r( $data, true ) ); //phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r
1190
+
 
 
1191
  return;
1192
  }
1193
 
1194
  $log_data = array();
1195
+
1196
+ $this->get_plugin()->log( 'WebHook User Event received' );
1197
+ $this->get_plugin()->log( print_r( $data, true ) ); //phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r
1198
+
1199
 
1200
  $entry = (array) $data->entry[0];
1201
  if ( empty( $entry ) ) {
1307
 
1308
  } catch ( \Exception $e ) {
1309
 
1310
+ $this->get_plugin()->log( 'Could not request Page Token: ' . $e->getMessage() );
 
 
1311
  }
1312
  }//end if
1313
 
1314
+ $this->get_plugin()->log( 'WebHook User event saved data' );
1315
+ $this->get_plugin()->log( print_r( $log_data, true ) ); //phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r
 
 
1316
  }
1317
 
1318
 
includes/Jobs/CleanupSkyvergeFrameworkJobOptions.php ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace SkyVerge\WooCommerce\Facebook\Jobs;
4
+
5
+ use SkyVerge\WooCommerce\Facebook\Utilities\Heartbeat;
6
+
7
+ defined( 'ABSPATH' ) || exit;
8
+
9
+ /**
10
+ * Class CleanupSkyvergeFrameworkJobOptions
11
+ *
12
+ * Responsible for cleaning up old completed background sync jobs from SkyVerge background job system.
13
+ * Each job is represented by a row in wp_options table, and these can accumulate over time.
14
+ *
15
+ * Note - this is closely coupled to the SkyVerge background job system, and is essentially a patch to improve it.
16
+ *
17
+ * @see SV_WP_Background_Job_Handler
18
+ *
19
+ * @since 2.6.0
20
+ */
21
+ class CleanupSkyvergeFrameworkJobOptions {
22
+
23
+ /**
24
+ * Add hooks.
25
+ */
26
+ public function init() {
27
+ // Register our cleanup routine to run regularly.
28
+ add_action( Heartbeat::DAILY, array( $this, 'clean_up_old_completed_options' ) );
29
+ }
30
+
31
+ /**
32
+ * Delete old completed product sync job rows from options table.
33
+ *
34
+ * Logic and database query are adapted from SV_WP_Background_Job_Handler::get_jobs().
35
+ *
36
+ * @see SV_WP_Background_Job_Handler
37
+ * @see Products\Sync\Background
38
+ */
39
+ public function clean_up_old_completed_options() {
40
+ global $wpdb;
41
+
42
+ /**
43
+ * Query notes:
44
+ * - Matching product sync job only (Products\Sync\Background class).
45
+ * - Matching "completed" status by sniffing json option value.
46
+ * - Order by lowest id, to delete older rows first.
47
+ * - Limit number of rows (periodic task will eventually remove all).
48
+ * Using `get_results` so we can limit number of items; `delete` doesn't allow this.
49
+ */
50
+ $wpdb->query(
51
+ "DELETE
52
+ FROM {$wpdb->options}
53
+ WHERE option_name LIKE 'wc_facebook_background_product_sync_job_%'
54
+ AND option_value LIKE '%\"status\":\"completed\"%'
55
+ ORDER BY option_id ASC
56
+ LIMIT 250"
57
+ );
58
+ }
59
+
60
+ }
includes/Jobs/JobRegistry.php CHANGED
@@ -18,6 +18,11 @@ class JobRegistry {
18
  */
19
  public $generate_product_feed_job;
20
 
 
 
 
 
 
21
  /**
22
  * Instantiate and init all jobs for the plugin.
23
  */
@@ -26,6 +31,9 @@ class JobRegistry {
26
 
27
  $this->generate_product_feed_job = new GenerateProductFeed( $action_scheduler_proxy );
28
  $this->generate_product_feed_job->init();
 
 
 
29
  }
30
 
31
  }
18
  */
19
  public $generate_product_feed_job;
20
 
21
+ /**
22
+ * @var CleanupSkyvergeFrameworkJobOptions
23
+ */
24
+ public $cleanup_skyverge_job_options;
25
+
26
  /**
27
  * Instantiate and init all jobs for the plugin.
28
  */
31
 
32
  $this->generate_product_feed_job = new GenerateProductFeed( $action_scheduler_proxy );
33
  $this->generate_product_feed_job->init();
34
+
35
+ $this->cleanup_skyverge_job_options = new CleanupSkyvergeFrameworkJobOptions();
36
+ $this->cleanup_skyverge_job_options->init();
37
  }
38
 
39
  }
includes/ProductSync/ProductExcludedException.php ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace SkyVerge\WooCommerce\Facebook\ProductSync;
4
+
5
+ use Exception;
6
+
7
+ /**
8
+ * Class ProductExcludedException
9
+ *
10
+ * Exception for when a product is excluded from Facebook product sync.
11
+ */
12
+ class ProductExcludedException extends Exception {}
includes/ProductSync/ProductValidator.php ADDED
@@ -0,0 +1,259 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace SkyVerge\WooCommerce\Facebook\ProductSync;
4
+
5
+ use SkyVerge\WooCommerce\Facebook\Products;
6
+ use WC_Product;
7
+ use WC_Facebookcommerce_Integration;
8
+
9
+ /**
10
+ * Class ProductValidator
11
+ *
12
+ * This class is responsible for validating whether a product should be synced to Facebook.
13
+ *
14
+ * @since 2.5.0
15
+ */
16
+ class ProductValidator {
17
+
18
+ /**
19
+ * The meta key used to flag whether a product should be synced in Facebook
20
+ *
21
+ * @var string
22
+ */
23
+ const SYNC_ENABLED_META_KEY = '_wc_facebook_sync_enabled';
24
+
25
+ /**
26
+ * The FB integration instance.
27
+ *
28
+ * @var WC_Facebookcommerce_Integration
29
+ */
30
+ protected $integration;
31
+
32
+ /**
33
+ * The product object to validate.
34
+ *
35
+ * @var WC_Product
36
+ */
37
+ protected $product;
38
+
39
+ /**
40
+ * The product parent object if the product has a parent.
41
+ *
42
+ * @var WC_Product
43
+ */
44
+ protected $product_parent;
45
+
46
+ /**
47
+ * ProductValidator constructor.
48
+ *
49
+ * @param WC_Facebookcommerce_Integration $integration The FB integration instance.
50
+ * @param WC_Product $product The product to validate. Accepts both variations and variable products.
51
+ */
52
+ public function __construct( WC_Facebookcommerce_Integration $integration, WC_Product $product ) {
53
+ $this->product = $product;
54
+
55
+ if ( $product->get_parent_id() ) {
56
+ $parent_product = wc_get_product( $product->get_parent_id() );
57
+ if ( $parent_product instanceof WC_Product ) {
58
+ $this->product_parent = $parent_product;
59
+ }
60
+ }
61
+
62
+ $this->integration = $integration;
63
+ }
64
+
65
+ /**
66
+ * Validate whether the product should be synced to Facebook.
67
+ *
68
+ * @throws ProductExcludedException If product should not be synced.
69
+ */
70
+ public function validate() {
71
+ $this->validate_sync_enabled_globally();
72
+ $this->validate_product_status();
73
+ $this->validate_product_stock_status();
74
+ $this->validate_product_sync_field();
75
+ $this->validate_product_price();
76
+ $this->validate_product_visibility();
77
+ $this->validate_product_terms();
78
+ }
79
+
80
+ /**
81
+ * Validate whether the product should be synced to Facebook but skip the status check for backwards compatibility.
82
+ *
83
+ * @internal Do not use this as it will likely be removed.
84
+ *
85
+ * @throws ProductExcludedException If product should not be synced.
86
+ */
87
+ public function validate_but_skip_status_check() {
88
+ $this->validate_sync_enabled_globally();
89
+ $this->validate_product_stock_status();
90
+ $this->validate_product_sync_field();
91
+ $this->validate_product_price();
92
+ $this->validate_product_visibility();
93
+ $this->validate_product_terms();
94
+ }
95
+
96
+ /**
97
+ * Validate whether the product should be synced to Facebook.
98
+ *
99
+ * @return bool
100
+ */
101
+ public function passes_all_checks(): bool {
102
+ try {
103
+ $this->validate();
104
+ } catch ( ProductExcludedException $e ) {
105
+ return false;
106
+ }
107
+
108
+ return true;
109
+ }
110
+
111
+ /**
112
+ * Check if the product's terms (categories and tags) allow it to sync.
113
+ *
114
+ * @return bool
115
+ */
116
+ public function passes_product_terms_check(): bool {
117
+ try {
118
+ $this->validate_product_terms();
119
+ } catch ( ProductExcludedException $e ) {
120
+ return false;
121
+ }
122
+
123
+ return true;
124
+ }
125
+
126
+ /**
127
+ * Check if the product's product sync meta field allows it to sync.
128
+ *
129
+ * @return bool
130
+ */
131
+ public function passes_product_sync_field_check(): bool {
132
+ try {
133
+ $this->validate_product_sync_field();
134
+ } catch ( ProductExcludedException $e ) {
135
+ return false;
136
+ }
137
+
138
+ return true;
139
+ }
140
+
141
+ /**
142
+ * Check whether product sync is globally disabled.
143
+ *
144
+ * @throws ProductExcludedException If product should not be synced.
145
+ */
146
+ protected function validate_sync_enabled_globally() {
147
+ if ( ! $this->integration->is_product_sync_enabled() ) {
148
+ throw new ProductExcludedException( __( 'Product sync is globally disabled.', 'facebook-for-woocommerce' ) );
149
+ }
150
+ }
151
+
152
+ /**
153
+ * Check whether the product's status excludes it from sync.
154
+ *
155
+ * @throws ProductExcludedException If product should not be synced.
156
+ */
157
+ protected function validate_product_status() {
158
+ $product = $this->product_parent ? $this->product_parent : $this->product;
159
+
160
+ if ( 'publish' !== $product->get_status() ) {
161
+ throw new ProductExcludedException( __( 'Product is not published.', 'facebook-for-woocommerce' ) );
162
+ }
163
+ }
164
+
165
+ /**
166
+ * Check whether the product should be excluded due to being out of stock.
167
+ *
168
+ * @throws ProductExcludedException If product should not be synced.
169
+ */
170
+ protected function validate_product_stock_status() {
171
+ if ( 'yes' === get_option( 'woocommerce_hide_out_of_stock_items' ) && ! $this->product->is_in_stock() ) {
172
+ throw new ProductExcludedException( __( 'Product must be in stock.', 'facebook-for-woocommerce' ) );
173
+ }
174
+ }
175
+
176
+ /**
177
+ * Check whether the product's visibility excludes it from sync.
178
+ *
179
+ * Products are excluded if they are hidden from the store catalog or from search results.
180
+ *
181
+ * @throws ProductExcludedException If product should not be synced.
182
+ */
183
+ protected function validate_product_visibility() {
184
+ $product = $this->product_parent ? $this->product_parent : $this->product;
185
+
186
+ if ( 'visible' !== $product->get_catalog_visibility() ) {
187
+ throw new ProductExcludedException( __( 'Product is hidden from catalog and search.', 'facebook-for-woocommerce' ) );
188
+ }
189
+ }
190
+
191
+ /**
192
+ * Check whether the product's categories or tags (terms) exclude it from sync.
193
+ *
194
+ * @throws ProductExcludedException If product should not be synced.
195
+ */
196
+ protected function validate_product_terms() {
197
+ $product = $this->product_parent ? $this->product_parent : $this->product;
198
+
199
+ $excluded_categories = $this->integration->get_excluded_product_category_ids();
200
+ if ( $excluded_categories ) {
201
+ if ( ! empty( array_intersect( $product->get_category_ids(), $excluded_categories ) ) ) {
202
+ throw new ProductExcludedException( __( 'Product excluded because of categories.', 'facebook-for-woocommerce' ) );
203
+ }
204
+ }
205
+
206
+ $excluded_tags = $this->integration->get_excluded_product_tag_ids();
207
+ if ( $excluded_tags ) {
208
+ if ( ! empty( array_intersect( $product->get_tag_ids(), $excluded_tags ) ) ) {
209
+ throw new ProductExcludedException( __( 'Product excluded because of tags.', 'facebook-for-woocommerce' ) );
210
+ }
211
+ }
212
+ }
213
+
214
+ /**
215
+ * Validate if the product is excluded from at the "product level" (product meta value).
216
+ *
217
+ * @throws ProductExcludedException If product should not be synced.
218
+ */
219
+ protected function validate_product_sync_field() {
220
+ $invalid_exception = new ProductExcludedException( __( 'Sync disabled in product field.', 'facebook-for-woocommerce' ) );
221
+
222
+ if ( $this->product->is_type( 'variable' ) ) {
223
+ foreach ( $this->product->get_children() as $child_id ) {
224
+ $child_product = wc_get_product( $child_id );
225
+ if ( $child_product && 'no' !== $child_product->get_meta( self::SYNC_ENABLED_META_KEY ) ) {
226
+ // At least one product is "sync-enabled" so bail before exception.
227
+ return;
228
+ }
229
+ }
230
+
231
+ // Variable product has no variations with sync enabled so it shouldn't be synced.
232
+ throw $invalid_exception;
233
+ } else {
234
+ if ( 'no' === $this->product->get_meta( self::SYNC_ENABLED_META_KEY ) ) {
235
+ throw $invalid_exception;
236
+ }
237
+ }
238
+ }
239
+
240
+ /**
241
+ * "allow simple or variable products (and their variations) with zero or empty price - exclude other product types with zero or empty price"
242
+ * unsure why but that's what we're doing
243
+ *
244
+ * @throws ProductExcludedException If product should not be synced.
245
+ */
246
+ protected function validate_product_price() {
247
+ $primary_product = $this->product_parent ? $this->product_parent : $this->product;
248
+
249
+ // Variable and simple products are allowed to have no price.
250
+ if ( in_array( $primary_product->get_type(), array( 'simple', 'variable' ), true ) ) {
251
+ return;
252
+ }
253
+
254
+ if ( ! Products::get_product_price( $this->product ) ) {
255
+ throw new ProductExcludedException( __( 'If product is not simple, variable or variation it must have a price.', 'facebook-for-woocommerce' ) );
256
+ }
257
+ }
258
+
259
+ }
includes/Products.php CHANGED
@@ -64,10 +64,6 @@ class Products {
64
  /** @var string the meta key used to store the name of the pattern attribute for a product */
65
  const PATTERN_ATTRIBUTE_META_KEY = '_wc_facebook_pattern_attribute';
66
 
67
-
68
- /** @var array memoized array of sync enabled status for products */
69
- private static $products_sync_enabled = array();
70
-
71
  /** @var array memoized array of visibility status for products */
72
  private static $products_visibility = array();
73
 
@@ -81,9 +77,6 @@ class Products {
81
  * @param bool $enabled whether sync should be enabled for $products
82
  */
83
  private static function set_sync_for_products( array $products, $enabled ) {
84
-
85
- self::$products_sync_enabled = array();
86
-
87
  $enabled = wc_bool_to_string( $enabled );
88
 
89
  foreach ( $products as $product ) {
@@ -193,7 +186,7 @@ class Products {
193
  /**
194
  * Determines whether the given product should be synced.
195
  *
196
- * @see Products::published_product_should_be_synced()
197
  *
198
  * @since 1.10.0
199
  *
@@ -201,8 +194,12 @@ class Products {
201
  * @return bool
202
  */
203
  public static function product_should_be_synced( \WC_Product $product ) {
204
-
205
- return 'publish' === $product->get_status() && self::published_product_should_be_synced( $product );
 
 
 
 
206
  }
207
 
208
 
@@ -211,8 +208,7 @@ class Products {
211
  *
212
  * If a product is enabled for sync, but belongs to an excluded term, it will return as excluded from sync:
213
  *
214
- * @see Products::is_sync_enabled_for_product()
215
- * @see Products::is_sync_excluded_for_product_terms()
216
  *
217
  * @since 2.0.0-dev.1
218
  *
@@ -220,32 +216,12 @@ class Products {
220
  * @return bool
221
  */
222
  public static function published_product_should_be_synced( \WC_Product $product ) {
223
-
224
- $should_sync = self::is_sync_enabled_for_product( $product );
225
-
226
- // define the product to check terms on
227
- if ( $should_sync ) {
228
- $terms_product = $product->is_type( 'variation' ) ? wc_get_product( $product->get_parent_id() ) : $product;
229
- } else {
230
- $terms_product = null;
231
- }
232
-
233
- // allow simple or variable products (and their variations) with zero or empty price - exclude other product types with zero or empty price
234
- if ( $should_sync && ( ! $terms_product || ( ! self::get_product_price( $product ) && ! in_array( $terms_product->get_type(), array( 'simple', 'variable' ) ) ) ) ) {
235
- $should_sync = false;
236
- }
237
-
238
- // exclude products that are excluded from the store catalog or from search results
239
- if ( $should_sync && ( ! $terms_product || has_term( array( 'exclude-from-catalog', 'exclude-from-search' ), 'product_visibility', $terms_product->get_id() ) ) ) {
240
- $should_sync = false;
241
- }
242
-
243
- // exclude products that belong to one of the excluded terms
244
- if ( $should_sync && ( ! $terms_product || self::is_sync_excluded_for_product_terms( $terms_product ) ) ) {
245
- $should_sync = false;
246
  }
247
-
248
- return $should_sync;
249
  }
250
 
251
 
@@ -271,39 +247,15 @@ class Products {
271
  * If the product is not explicitly set to disable sync, it'll be considered enabled.
272
  * This applies to products that may not have the meta value set.
273
  *
 
 
274
  * @since 1.10.0
275
  *
276
  * @param \WC_Product $product product object
277
  * @return bool
278
  */
279
  public static function is_sync_enabled_for_product( \WC_Product $product ) {
280
-
281
- if ( ! isset( self::$products_sync_enabled[ $product->get_id() ] ) ) {
282
-
283
- if ( $product->is_type( 'variable' ) ) {
284
-
285
- // assume variable products are not synced until a synced child is found
286
- $enabled = false;
287
-
288
- foreach ( $product->get_children() as $child_id ) {
289
-
290
- $child_product = wc_get_product( $child_id );
291
-
292
- if ( $child_product && self::is_sync_enabled_for_product( $child_product ) ) {
293
-
294
- $enabled = true;
295
- break;
296
- }
297
- }
298
- } else {
299
-
300
- $enabled = 'no' !== $product->get_meta( self::SYNC_ENABLED_META_KEY );
301
- }
302
-
303
- self::$products_sync_enabled[ $product->get_id() ] = $enabled;
304
- }//end if
305
-
306
- return self::$products_sync_enabled[ $product->get_id() ];
307
  }
308
 
309
 
@@ -312,26 +264,13 @@ class Products {
312
  *
313
  * @since 1.10.0
314
  *
 
 
315
  * @param \WC_Product $product product object
316
  * @return bool if true, product should be excluded from sync, if false, product can be included in sync (unless manually excluded by individual product meta)
317
  */
318
  public static function is_sync_excluded_for_product_terms( \WC_Product $product ) {
319
-
320
- if ( $integration = facebook_for_woocommerce()->get_integration() ) {
321
- $excluded_categories = $integration->get_excluded_product_category_ids();
322
- $excluded_tags = $integration->get_excluded_product_tag_ids();
323
- } else {
324
- $excluded_categories = $excluded_tags = array();
325
- }
326
-
327
- $categories = $product->get_category_ids();
328
- $tags = $product->get_tag_ids();
329
-
330
- // returns true if no terms on the product, or no terms excluded, or if the product does not contain any of the excluded terms
331
- $matches = ( ! $categories || ! $excluded_categories || ! array_intersect( $categories, $excluded_categories ) )
332
- && ( ! $tags || ! $excluded_tags || ! array_intersect( $tags, $excluded_tags ) );
333
-
334
- return ! $matches;
335
  }
336
 
337
 
64
  /** @var string the meta key used to store the name of the pattern attribute for a product */
65
  const PATTERN_ATTRIBUTE_META_KEY = '_wc_facebook_pattern_attribute';
66
 
 
 
 
 
67
  /** @var array memoized array of visibility status for products */
68
  private static $products_visibility = array();
69
 
77
  * @param bool $enabled whether sync should be enabled for $products
78
  */
79
  private static function set_sync_for_products( array $products, $enabled ) {
 
 
 
80
  $enabled = wc_bool_to_string( $enabled );
81
 
82
  foreach ( $products as $product ) {
186
  /**
187
  * Determines whether the given product should be synced.
188
  *
189
+ * @deprecated use \SkyVerge\WooCommerce\Facebook\ProductSync\ProductValidator::validate() instead
190
  *
191
  * @since 1.10.0
192
  *
194
  * @return bool
195
  */
196
  public static function product_should_be_synced( \WC_Product $product ) {
197
+ try {
198
+ facebook_for_woocommerce()->get_product_sync_validator( $product )->validate();
199
+ return true;
200
+ } catch ( \Exception $e ) {
201
+ return false;
202
+ }
203
  }
204
 
205
 
208
  *
209
  * If a product is enabled for sync, but belongs to an excluded term, it will return as excluded from sync:
210
  *
211
+ * @deprecated use \SkyVerge\WooCommerce\Facebook\ProductSync\ProductValidator::validate() instead
 
212
  *
213
  * @since 2.0.0-dev.1
214
  *
216
  * @return bool
217
  */
218
  public static function published_product_should_be_synced( \WC_Product $product ) {
219
+ try {
220
+ facebook_for_woocommerce()->get_product_sync_validator( $product )->validate_but_skip_status_check();
221
+ return true;
222
+ } catch ( \Exception $e ) {
223
+ return false;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
224
  }
 
 
225
  }
226
 
227
 
247
  * If the product is not explicitly set to disable sync, it'll be considered enabled.
248
  * This applies to products that may not have the meta value set.
249
  *
250
+ * @deprecated use \SkyVerge\WooCommerce\Facebook\ProductSync\ProductValidator::passes_product_sync_field_check() instead
251
+ *
252
  * @since 1.10.0
253
  *
254
  * @param \WC_Product $product product object
255
  * @return bool
256
  */
257
  public static function is_sync_enabled_for_product( \WC_Product $product ) {
258
+ return facebook_for_woocommerce()->get_product_sync_validator( $product )->passes_product_sync_field_check();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
259
  }
260
 
261
 
264
  *
265
  * @since 1.10.0
266
  *
267
+ * @deprecated use \SkyVerge\WooCommerce\Facebook\ProductSync\ProductValidator::passes_product_terms_check() instead
268
+ *
269
  * @param \WC_Product $product product object
270
  * @return bool if true, product should be excluded from sync, if false, product can be included in sync (unless manually excluded by individual product meta)
271
  */
272
  public static function is_sync_excluded_for_product_terms( \WC_Product $product ) {
273
+ return ! facebook_for_woocommerce()->get_product_sync_validator( $product )->passes_product_terms_check();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
274
  }
275
 
276
 
includes/Products/Feed.php CHANGED
@@ -12,6 +12,7 @@ namespace SkyVerge\WooCommerce\Facebook\Products;
12
 
13
  defined( 'ABSPATH' ) or exit;
14
 
 
15
  use SkyVerge\WooCommerce\PluginFramework\v5_10_0 as Framework;
16
 
17
  /**
@@ -54,7 +55,7 @@ class Feed {
54
  private function add_hooks() {
55
 
56
  // schedule the recurring feed generation
57
- add_action( 'admin_init', array( $this, 'schedule_feed_generation' ) );
58
 
59
  // regenerate the product feed
60
  add_action( self::GENERATE_FEED_ACTION, array( $this, 'regenerate_feed' ) );
@@ -74,6 +75,7 @@ class Feed {
74
  public function handle_feed_data_request() {
75
 
76
  \WC_Facebookcommerce_Utils::log( 'Facebook is requesting the product feed.' );
 
77
 
78
  $feed_handler = new \WC_Facebook_Product_Feed();
79
  $file_path = $feed_handler->get_file_path();
12
 
13
  defined( 'ABSPATH' ) or exit;
14
 
15
+ use SkyVerge\WooCommerce\Facebook\Utilities\Heartbeat;
16
  use SkyVerge\WooCommerce\PluginFramework\v5_10_0 as Framework;
17
 
18
  /**
55
  private function add_hooks() {
56
 
57
  // schedule the recurring feed generation
58
+ add_action( Heartbeat::HOURLY, array( $this, 'schedule_feed_generation' ) );
59
 
60
  // regenerate the product feed
61
  add_action( self::GENERATE_FEED_ACTION, array( $this, 'regenerate_feed' ) );
75
  public function handle_feed_data_request() {
76
 
77
  \WC_Facebookcommerce_Utils::log( 'Facebook is requesting the product feed.' );
78
+ facebook_for_woocommerce()->get_tracker()->track_feed_file_requested();
79
 
80
  $feed_handler = new \WC_Facebook_Product_Feed();
81
  $file_path = $feed_handler->get_file_path();
includes/Utilities/Heartbeat.php ADDED
@@ -0,0 +1,95 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace SkyVerge\WooCommerce\Facebook\Utilities;
4
+
5
+ use WC_Queue_Interface;
6
+
7
+ defined( 'ABSPATH' ) || exit;
8
+
9
+ /**
10
+ * Class Heartbeat
11
+ *
12
+ * Responsible for scheduling cron heartbeat hooks. Currently there is a single hourly heartbeat:
13
+ * - `facebook_for_woocommerce_heartbeat_hourly`
14
+ *
15
+ * @since 2.6.0
16
+ */
17
+ class Heartbeat {
18
+
19
+ /**
20
+ * Hook name for hourly heartbeat.
21
+ */
22
+ const HOURLY = 'facebook_for_woocommerce_hourly_heartbeat';
23
+
24
+ /**
25
+ * Hook name for daily heartbeat.
26
+ */
27
+ const DAILY = 'facebook_for_woocommerce_daily_heartbeat';
28
+
29
+ /**
30
+ * @var string
31
+ */
32
+ protected $hourly_cron_name = 'facebook_for_woocommerce_hourly_heartbeat_cron';
33
+
34
+ /**
35
+ * @var string
36
+ */
37
+ protected $daily_cron_name = 'facebook_for_woocommerce_daily_heartbeat_cron';
38
+
39
+ /**
40
+ * @var WC_Queue_Interface
41
+ */
42
+ protected $queue;
43
+
44
+ /**
45
+ * Heartbeat constructor.
46
+ *
47
+ * @param WC_Queue_Interface $queue WC Action Scheduler proxy.
48
+ */
49
+ public function __construct( WC_Queue_Interface $queue ) {
50
+ $this->queue = $queue;
51
+ }
52
+
53
+ /**
54
+ * Add hooks.
55
+ */
56
+ public function init() {
57
+ add_action( 'init', array( $this, 'schedule_cron_events' ) );
58
+ add_action( $this->hourly_cron_name, array( $this, 'schedule_hourly_action' ) );
59
+ add_action( $this->daily_cron_name, array( $this, 'schedule_daily_action' ) );
60
+ }
61
+
62
+ /**
63
+ * Schedule heartbeat cron events.
64
+ *
65
+ * WP Cron events are stored in an auto-loaded option so the performance impact is much lower than checking and
66
+ * scheduling an Action Scheduler action.
67
+ */
68
+ public function schedule_cron_events() {
69
+ if ( ! wp_next_scheduled( $this->hourly_cron_name ) ) {
70
+ wp_schedule_event( time(), 'hourly', $this->hourly_cron_name );
71
+ }
72
+ if ( ! wp_next_scheduled( $this->daily_cron_name ) ) {
73
+ wp_schedule_event( time(), 'daily', $this->daily_cron_name );
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Schedule the hourly heartbeat action to run immediately.
79
+ *
80
+ * Scheduling an action frees up WP Cron to process more jobs in the current request. Action Scheduler has greater
81
+ * throughput so running our checks there is better.
82
+ */
83
+ public function schedule_hourly_action() {
84
+ $this->queue->add( self::HOURLY );
85
+ }
86
+
87
+ /**
88
+ * Schedule the daily heartbeat action to run immediately.
89
+ */
90
+ public function schedule_daily_action() {
91
+ $this->queue->add( self::DAILY );
92
+ }
93
+
94
+
95
+ }
includes/Utilities/Tracker.php CHANGED
@@ -10,7 +10,7 @@
10
 
11
  namespace SkyVerge\WooCommerce\Facebook\Utilities;
12
 
13
- defined( 'ABSPATH' ) or exit;
14
 
15
  /**
16
  * Class for adding diagnostic info to WooCommerce Tracker snapshot.
@@ -21,6 +21,41 @@ defined( 'ABSPATH' ) or exit;
21
  */
22
  class Tracker {
23
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  /**
25
  * Constructor.
26
  *
@@ -45,8 +80,11 @@ class Tracker {
45
  $data['extensions'] = array();
46
  }
47
 
48
- // Is the site connected?
49
- // @since 2.3.4
 
 
 
50
  $connection_is_happy = false;
51
  $connection_handler = facebook_for_woocommerce()->get_connection_handler();
52
  if ( $connection_handler ) {
@@ -54,13 +92,101 @@ class Tracker {
54
  }
55
  $data['extensions']['facebook-for-woocommerce']['is-connected'] = wc_bool_to_string( $connection_is_happy );
56
 
57
- // What features are enabled on this site?
58
- // @since 2.4.0
 
 
 
59
  $product_sync_enabled = facebook_for_woocommerce()->get_integration()->is_product_sync_enabled();
60
  $data['extensions']['facebook-for-woocommerce']['product-sync-enabled'] = wc_bool_to_string( $product_sync_enabled );
61
  $messenger_enabled = facebook_for_woocommerce()->get_integration()->is_messenger_enabled();
62
  $data['extensions']['facebook-for-woocommerce']['messenger-enabled'] = wc_bool_to_string( $messenger_enabled );
63
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
  return $data;
65
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
  }
10
 
11
  namespace SkyVerge\WooCommerce\Facebook\Utilities;
12
 
13
+ defined( 'ABSPATH' ) || exit;
14
 
15
  /**
16
  * Class for adding diagnostic info to WooCommerce Tracker snapshot.
21
  */
22
  class Tracker {
23
 
24
+ /**
25
+ * Life time for transients used for temporary caching of values we want to add to tracker snapshot.
26
+ *
27
+ * @var string
28
+ */
29
+ const TRANSIENT_WCTRACKER_LIFE_TIME = 2 * WEEK_IN_SECONDS;
30
+
31
+ /**
32
+ * Transient key name; how long it took to generate the most recent feed file, or zero if it failed.
33
+ *
34
+ * @var string
35
+ */
36
+ const TRANSIENT_WCTRACKER_FEED_GENERATION_TIME = 'facebook_for_woocommerce_wctracker_feed_generation_time';
37
+
38
+ /**
39
+ * Transient key name; true if feed has been requested by Facebook.
40
+ *
41
+ * @var string
42
+ */
43
+ const TRANSIENT_WCTRACKER_FEED_REQUESTED = 'facebook_for_woocommerce_wctracker_feed_requested';
44
+
45
+ /**
46
+ * Transient key name; stores various FBE business settings.
47
+ *
48
+ * @var string
49
+ */
50
+ const TRANSIENT_WCTRACKER_FBE_BUSINESS_CONFIG = 'facebook_for_woocommerce_wctracker_fbe_business_config';
51
+
52
+ /**
53
+ * Transient key name; stores feed (data source) settings for catalog sync.
54
+ *
55
+ * @var string
56
+ */
57
+ const TRANSIENT_WCTRACKER_FB_FEED_CONFIG = 'facebook_for_woocommerce_wctracker_fb_feed_config';
58
+
59
  /**
60
  * Constructor.
61
  *
80
  $data['extensions'] = array();
81
  }
82
 
83
+ /**
84
+ * Is the site connected?
85
+ *
86
+ * @since 2.3.4
87
+ */
88
  $connection_is_happy = false;
89
  $connection_handler = facebook_for_woocommerce()->get_connection_handler();
90
  if ( $connection_handler ) {
92
  }
93
  $data['extensions']['facebook-for-woocommerce']['is-connected'] = wc_bool_to_string( $connection_is_happy );
94
 
95
+ /**
96
+ * What features are enabled on this site?
97
+ *
98
+ * @since 2.3.4
99
+ */
100
  $product_sync_enabled = facebook_for_woocommerce()->get_integration()->is_product_sync_enabled();
101
  $data['extensions']['facebook-for-woocommerce']['product-sync-enabled'] = wc_bool_to_string( $product_sync_enabled );
102
  $messenger_enabled = facebook_for_woocommerce()->get_integration()->is_messenger_enabled();
103
  $data['extensions']['facebook-for-woocommerce']['messenger-enabled'] = wc_bool_to_string( $messenger_enabled );
104
 
105
+ /**
106
+ * How long did the last feed generation take (or did it fail - 0)?
107
+ *
108
+ * @since 2.6.0
109
+ */
110
+ $feed_generation_time = get_transient( self::TRANSIENT_WCTRACKER_FEED_GENERATION_TIME );
111
+ $data['extensions']['facebook-for-woocommerce']['feed-generation-time'] = floatval( $feed_generation_time );
112
+
113
+ /**
114
+ * Has the feed file been requested since the last snapshot?
115
+ *
116
+ * @since 2.6.0
117
+ */
118
+ $feed_file_requested = get_transient( self::TRANSIENT_WCTRACKER_FEED_REQUESTED );
119
+ $data['extensions']['facebook-for-woocommerce']['feed-file-requested'] = wc_bool_to_string( $feed_file_requested );
120
+ // Manually delete the transient. This prop tracks if feed has been requested _since last snapshot_.
121
+ delete_transient( self::TRANSIENT_WCTRACKER_FEED_REQUESTED );
122
+
123
+ /**
124
+ * Miscellaneous Facebook config settings.
125
+ *
126
+ * @since 2.6.0
127
+ */
128
+ $config = get_transient( self::TRANSIENT_WCTRACKER_FBE_BUSINESS_CONFIG );
129
+ $data['extensions']['facebook-for-woocommerce']['instagram-shopping-enabled'] = wc_bool_to_string( $config ?: $config->ig_shopping_enabled );
130
+ $data['extensions']['facebook-for-woocommerce']['instagram-cta-enabled'] = wc_bool_to_string( $config ?: $config->ig_cta_enabled );
131
+
132
+ /**
133
+ * Feed pull / upload settings configured in Facebook UI.
134
+ *
135
+ * @since 2.6.0
136
+ */
137
+ $data['extensions']['facebook-for-woocommerce']['product-feed-config'] = get_transient( self::TRANSIENT_WCTRACKER_FB_FEED_CONFIG );
138
+
139
  return $data;
140
  }
141
+
142
+ /**
143
+ * Update transient with feed file generation time (in seconds).
144
+ *
145
+ * Note this is used to clear the transient (set to -1) to track feed generation failure.
146
+ *
147
+ * @since 2.6.0
148
+ */
149
+ public function track_feed_file_generation_time( $time_in_seconds ) {
150
+ set_transient( self::TRANSIENT_WCTRACKER_FEED_GENERATION_TIME, $time_in_seconds, self::TRANSIENT_WCTRACKER_LIFE_TIME );
151
+ }
152
+
153
+ /**
154
+ * Store the fact that the feed has been requested by Facebook in a transient.
155
+ * This will later be added to next tracker snapshot.
156
+ *
157
+ * @since 2.6.0
158
+ */
159
+ public function track_feed_file_requested() {
160
+ set_transient( self::TRANSIENT_WCTRACKER_FEED_REQUESTED, true, self::TRANSIENT_WCTRACKER_LIFE_TIME );
161
+ }
162
+
163
+ /**
164
+ * Store some Facebook config settings for tracking.
165
+ *
166
+ * @param bool $ig_shopping_enabled True if Instagram Shopping is configured.
167
+ * @param bool $ig_cta_enabled True if `ig_cta` config option is enabled.
168
+ * @since 2.6.0
169
+ */
170
+ public function track_facebook_business_config(
171
+ bool $ig_shopping_enabled,
172
+ bool $ig_cta_enabled
173
+ ) {
174
+ $transient = array(
175
+ 'ig_shopping_enabled' => $ig_shopping_enabled,
176
+ 'ig_cta_enabled' => $ig_cta_enabled,
177
+ );
178
+ set_transient( self::TRANSIENT_WCTRACKER_FBE_BUSINESS_CONFIG, $transient, self::TRANSIENT_WCTRACKER_LIFE_TIME );
179
+ }
180
+
181
+ /**
182
+ * Store Facebook feed config for tracking.
183
+ *
184
+ * @param array $feed_settings Key-value array of settings to add to tracker snapshot.
185
+ * @since 2.6.0
186
+ */
187
+ public function track_facebook_feed_config(
188
+ array $feed_settings
189
+ ) {
190
+ set_transient( self::TRANSIENT_WCTRACKER_FB_FEED_CONFIG, $feed_settings, self::TRANSIENT_WCTRACKER_LIFE_TIME );
191
+ }
192
  }
includes/fbgraph.php CHANGED
@@ -473,11 +473,80 @@ if ( ! class_exists( 'WC_Facebookcommerce_Graph_API' ) ) :
473
 
474
  public function create_feed( $facebook_catalog_id, $data ) {
475
  $url = $this->build_url( $facebook_catalog_id, '/product_feeds' );
 
476
  // success API call will return {id: <product feed id>}
477
  // failure API will return {error: <error message>}
478
  return self::_post( $url, $data );
479
  }
480
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
481
  public function get_upload_status( $facebook_upload_id ) {
482
  $url = $this->build_url( $facebook_upload_id, '/?fields=end_time' );
483
  // success API call will return
473
 
474
  public function create_feed( $facebook_catalog_id, $data ) {
475
  $url = $this->build_url( $facebook_catalog_id, '/product_feeds' );
476
+ $url = $this->get_feed_endpoint_url( $facebook_catalog_id );
477
  // success API call will return {id: <product feed id>}
478
  // failure API will return {error: <error message>}
479
  return self::_post( $url, $data );
480
  }
481
 
482
+ /**
483
+ * Get all feed configurations for a given catalog id.
484
+ *
485
+ * @see https://developers.facebook.com/docs/marketing-api/reference/product-feed/
486
+ * @since 2.6.0
487
+ *
488
+ * @param String $facebook_catalog_id Facebook Catalog Id.
489
+ * @return Array Facebook feeds configurations.
490
+ */
491
+ public function read_feeds( $facebook_catalog_id ) {
492
+ $url = $this->get_feed_endpoint_url( $facebook_catalog_id );
493
+ return $this->_get( $url );
494
+ }
495
+
496
+ /**
497
+ * Get general info about a feed (data source) configured in Facebook Business.
498
+ *
499
+ * @see https://developers.facebook.com/docs/marketing-api/reference/product-feed/
500
+ * @since 2.6.0
501
+ *
502
+ * @param String $feed_id Feed Id.
503
+ * @return Array Facebook feeds configurations.
504
+ */
505
+ public function read_feed_information( $feed_id ) {
506
+ $url = $this->build_url( $feed_id, '/?fields=id,name,schedule,update_schedule,uploads' );
507
+ return $this->_get( $url );
508
+ }
509
+
510
+ /**
511
+ * Get metadata about a feed (data source) configured in Facebook Business.
512
+ *
513
+ * @see https://developers.facebook.com/docs/marketing-api/reference/product-feed/
514
+ * @since 2.6.0
515
+ *
516
+ * @param String $feed_id Facebook Catalog Id.
517
+ * @return Array Facebook feed metadata.
518
+ */
519
+ public function read_feed_metadata( $feed_id ) {
520
+ $url = $this->build_url( $feed_id, '/?fields=created_time,latest_upload,product_count,schedule,update_schedule' );
521
+ return $this->_get( $url );
522
+ }
523
+
524
+ /**
525
+ * Get metadata about a recent feed upload.
526
+ *
527
+ * @see https://developers.facebook.com/docs/marketing-api/reference/product-feed-upload/
528
+ * @since 2.6.0
529
+ *
530
+ * @param String $upload_id Feed Upload Id.
531
+ * @return Array Feed upload metadata.
532
+ */
533
+ public function read_upload_metadata( $upload_id ) {
534
+ $url = $this->build_url( $upload_id, '/?fields=error_count,warning_count,num_detected_items,num_persisted_items,url' );
535
+ return $this->_get( $url );
536
+ }
537
+
538
+ /**
539
+ * Create product_feeds graph edge url.
540
+ *
541
+ * @since 2.6.0
542
+ *
543
+ * @param String $facebook_catalog_id Facebook Catalog Id.
544
+ * @return String Graph edge url.
545
+ */
546
+ public function get_feed_endpoint_url( $facebook_catalog_id ) {
547
+ return $this->build_url( $facebook_catalog_id, '/product_feeds' );
548
+ }
549
+
550
  public function get_upload_status( $facebook_upload_id ) {
551
  $url = $this->build_url( $facebook_upload_id, '/?fields=end_time' );
552
  // success API call will return
includes/fbproductfeed.php CHANGED
@@ -101,6 +101,7 @@ if ( ! class_exists( 'WC_Facebook_Product_Feed' ) ) :
101
  $this->generate_productfeed_file();
102
 
103
  $generation_time = microtime( true ) - $start_time;
 
104
 
105
  $this->set_feed_generation_time_with_decay( $generation_time );
106
 
@@ -109,6 +110,9 @@ if ( ! class_exists( 'WC_Facebook_Product_Feed' ) ) :
109
  } catch ( \Exception $exception ) {
110
 
111
  \WC_Facebookcommerce_Utils::log( $exception->getMessage() );
 
 
 
112
  }
113
 
114
  $profiling_logger->stop( 'generate_feed' );
101
  $this->generate_productfeed_file();
102
 
103
  $generation_time = microtime( true ) - $start_time;
104
+ facebook_for_woocommerce()->get_tracker()->track_feed_file_generation_time( $generation_time );
105
 
106
  $this->set_feed_generation_time_with_decay( $generation_time );
107
 
110
  } catch ( \Exception $exception ) {
111
 
112
  \WC_Facebookcommerce_Utils::log( $exception->getMessage() );
113
+ // Feed generation failed - clear the generation time to track that there's an issue.
114
+ facebook_for_woocommerce()->get_tracker()->track_feed_file_generation_time( -1 );
115
+
116
  }
117
 
118
  $profiling_logger->stop( 'generate_feed' );
readme.txt CHANGED
@@ -2,8 +2,8 @@
2
  Contributors: facebook, automattic, woothemes
3
  Tags: facebook, shop, catalog, advertise, pixel, product
4
  Requires at least: 4.4
5
- Tested up to: 5.6
6
- Stable tag: 2.5.1
7
  Requires PHP: 5.6 or greater
8
  MySQL: 5.6 or greater
9
  License: GPLv2 or later
@@ -39,7 +39,14 @@ When opening a bug on GitHub, please give us as many details as possible.
39
 
40
  == Changelog ==
41
 
42
- = 2.5.1 - 2021-05-28 =
 
 
 
 
 
 
 
43
  * Fix - Reinstate reset and delete functions in Facebook metabox on Edit product admin screen
44
 
45
  = 2.5.0 - 2021-05-19 =
2
  Contributors: facebook, automattic, woothemes
3
  Tags: facebook, shop, catalog, advertise, pixel, product
4
  Requires at least: 4.4
5
+ Tested up to: 5.7
6
+ Stable tag: 2.6.0
7
  Requires PHP: 5.6 or greater
8
  MySQL: 5.6 or greater
9
  License: GPLv2 or later
39
 
40
  == Changelog ==
41
 
42
+ = 2.6.0 - 2021-06-10 =
43
+ * Fix – Add cron heartbeat and use to offload feed generation from init / admin_init (performance) #1953
44
+ * Fix – Clean up background sync options (performance) #1962
45
+ * Dev – Add tracker props to understand usage of feed-based sync and other FB business config options #1972
46
+ * Dev – Configure release tooling to auto-update version numbers in code #1982
47
+ * Dev – Refactor code responsible for validating whether a product should be synced to FB into one place #19333
48
+
49
+ = 2.5.1 - 2021-05-28 =
50
  * Fix - Reinstate reset and delete functions in Facebook metabox on Edit product admin screen
51
 
52
  = 2.5.0 - 2021-05-19 =
vendor/autoload.php CHANGED
@@ -4,4 +4,4 @@
4
 
5
  require_once __DIR__ . '/composer/autoload_real.php';
6
 
7
- return ComposerAutoloaderInitf67523c78e13409ff91c41e965ab86c6::getLoader();
4
 
5
  require_once __DIR__ . '/composer/autoload_real.php';
6
 
7
+ return ComposerAutoloaderInitf259fca39ad059a7a2ae6e49ea65d16c::getLoader();
vendor/composer/autoload_real.php CHANGED
@@ -2,7 +2,7 @@
2
 
3
  // autoload_real.php @generated by Composer
4
 
5
- class ComposerAutoloaderInitf67523c78e13409ff91c41e965ab86c6
6
  {
7
  private static $loader;
8
 
@@ -22,15 +22,15 @@ class ComposerAutoloaderInitf67523c78e13409ff91c41e965ab86c6
22
  return self::$loader;
23
  }
24
 
25
- spl_autoload_register(array('ComposerAutoloaderInitf67523c78e13409ff91c41e965ab86c6', 'loadClassLoader'), true, true);
26
  self::$loader = $loader = new \Composer\Autoload\ClassLoader();
27
- spl_autoload_unregister(array('ComposerAutoloaderInitf67523c78e13409ff91c41e965ab86c6', 'loadClassLoader'));
28
 
29
  $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
30
  if ($useStaticLoader) {
31
  require_once __DIR__ . '/autoload_static.php';
32
 
33
- call_user_func(\Composer\Autoload\ComposerStaticInitf67523c78e13409ff91c41e965ab86c6::getInitializer($loader));
34
  } else {
35
  $map = require __DIR__ . '/autoload_namespaces.php';
36
  foreach ($map as $namespace => $path) {
2
 
3
  // autoload_real.php @generated by Composer
4
 
5
+ class ComposerAutoloaderInitf259fca39ad059a7a2ae6e49ea65d16c
6
  {
7
  private static $loader;
8
 
22
  return self::$loader;
23
  }
24
 
25
+ spl_autoload_register(array('ComposerAutoloaderInitf259fca39ad059a7a2ae6e49ea65d16c', 'loadClassLoader'), true, true);
26
  self::$loader = $loader = new \Composer\Autoload\ClassLoader();
27
+ spl_autoload_unregister(array('ComposerAutoloaderInitf259fca39ad059a7a2ae6e49ea65d16c', 'loadClassLoader'));
28
 
29
  $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
30
  if ($useStaticLoader) {
31
  require_once __DIR__ . '/autoload_static.php';
32
 
33
+ call_user_func(\Composer\Autoload\ComposerStaticInitf259fca39ad059a7a2ae6e49ea65d16c::getInitializer($loader));
34
  } else {
35
  $map = require __DIR__ . '/autoload_namespaces.php';
36
  foreach ($map as $namespace => $path) {
vendor/composer/autoload_static.php CHANGED
@@ -4,7 +4,7 @@
4
 
5
  namespace Composer\Autoload;
6
 
7
- class ComposerStaticInitf67523c78e13409ff91c41e965ab86c6
8
  {
9
  public static $prefixLengthsPsr4 = array (
10
  'S' =>
@@ -39,8 +39,8 @@ class ComposerStaticInitf67523c78e13409ff91c41e965ab86c6
39
  public static function getInitializer(ClassLoader $loader)
40
  {
41
  return \Closure::bind(function () use ($loader) {
42
- $loader->prefixLengthsPsr4 = ComposerStaticInitf67523c78e13409ff91c41e965ab86c6::$prefixLengthsPsr4;
43
- $loader->prefixDirsPsr4 = ComposerStaticInitf67523c78e13409ff91c41e965ab86c6::$prefixDirsPsr4;
44
 
45
  }, null, ClassLoader::class);
46
  }
4
 
5
  namespace Composer\Autoload;
6
 
7
+ class ComposerStaticInitf259fca39ad059a7a2ae6e49ea65d16c
8
  {
9
  public static $prefixLengthsPsr4 = array (
10
  'S' =>
39
  public static function getInitializer(ClassLoader $loader)
40
  {
41
  return \Closure::bind(function () use ($loader) {
42
+ $loader->prefixLengthsPsr4 = ComposerStaticInitf259fca39ad059a7a2ae6e49ea65d16c::$prefixLengthsPsr4;
43
+ $loader->prefixDirsPsr4 = ComposerStaticInitf259fca39ad059a7a2ae6e49ea65d16c::$prefixDirsPsr4;
44
 
45
  }, null, ClassLoader::class);
46
  }