Facebook for WooCommerce - Version 1.9.15

Version Description

  • 2019-06-27 =
  • CSRF handling for Ajax calls like ajax_woo_infobanner_post_click, ajax_woo_infobanner_post_xout, ajax_fb_toggle_visibility
  • use phpcs to adhere to WP coding standards
  • Minor UI changes on the iFrame
Download this release

Release Info

Developer facebook4woocommerce
Plugin Icon Facebook for WooCommerce
Version 1.9.15
Comparing to
See all releases

Code changes from version 1.9.14 to 1.9.15

assets/css/facebook.css CHANGED
@@ -42,16 +42,16 @@
42
  }
43
 
44
  #fbsetup .loader {
45
- border: 4px solid #f3f3f3; /* Light grey */
46
- border-top: 4px solid #3498db; /* Blue */
47
- border-radius: 50%;
48
- width: 20px;
49
- height: 20px;
50
- animation: spin 2s linear infinite;
51
  }
52
  @keyframes spin {
53
- 0% { transform: rotate(0deg); }
54
- 100% { transform: rotate(360deg); }
55
  }
56
 
57
  #fbsetup .btn {
@@ -199,7 +199,7 @@
199
  .tooltip {
200
  position: relative;
201
  border-bottom: 1px dotted black;
202
- /* container: */
203
  border: 1px solid;
204
  border-radius: 2px;
205
  /* Launch Test: */
42
  }
43
 
44
  #fbsetup .loader {
45
+ border: 4px solid #f3f3f3; /* Light grey */
46
+ border-top: 4px solid #3498db; /* Blue */
47
+ border-radius: 50%;
48
+ width: 20px;
49
+ height: 20px;
50
+ animation: spin 2s linear infinite;
51
  }
52
  @keyframes spin {
53
+ 0% { transform: rotate(0deg); }
54
+ 100% { transform: rotate(360deg); }
55
  }
56
 
57
  #fbsetup .btn {
199
  .tooltip {
200
  position: relative;
201
  border-bottom: 1px dotted black;
202
+ /* container: */
203
  border: 1px solid;
204
  border-radius: 2px;
205
  /* Launch Test: */
assets/js/facebook-infobanner.js CHANGED
@@ -11,32 +11,45 @@
11
  * Takes optional payload for POST and optional callback.
12
  */
13
  function ajax(action, payload = null, callback = null, failcallback = null) {
14
- var data = {
15
- 'action': action,
16
- };
17
- if (payload){
18
- for (var attrname in payload) { data[attrname] = payload[attrname]; }
19
- }
20
 
21
- // Since Wordpress 2.8 ajaxurl is always defined in admin header and
22
- // points to admin-ajax.php
23
- jQuery.post(ajaxurl, data, function(response) {
24
- if(callback) {
25
- callback(response);
26
- }
27
- }).fail(function(errorResponse){
28
- if(failcallback) {
29
- failcallback(errorResponse);
30
- }
31
- });
 
 
 
 
 
 
32
  }
33
 
34
  function fb_woo_infobanner_post_click(){
35
- console.log("Woo infobanner post tip click!");
36
- return ajax('ajax_woo_infobanner_post_click');
 
 
 
 
 
37
  }
38
 
39
  function fb_woo_infobanner_post_xout() {
40
- console.log("Woo infobanner post tip xout!");
41
- return ajax('ajax_woo_infobanner_post_xout');
 
 
 
 
 
42
  }
11
  * Takes optional payload for POST and optional callback.
12
  */
13
  function ajax(action, payload = null, callback = null, failcallback = null) {
14
+ var data = Object.assign( {}, {
15
+ 'action': action,
16
+ }, payload );
 
 
 
17
 
18
+ // Since Wordpress 2.8 ajaxurl is always defined in admin header and
19
+ // points to admin-ajax.php
20
+ jQuery.post(
21
+ ajaxurl,
22
+ data,
23
+ function(response) {
24
+ if (callback) {
25
+ callback( response );
26
+ }
27
+ }
28
+ ).fail(
29
+ function(errorResponse){
30
+ if (failcallback) {
31
+ failcallback( errorResponse );
32
+ }
33
+ }
34
+ );
35
  }
36
 
37
  function fb_woo_infobanner_post_click(){
38
+ console.log( "Woo infobanner post tip click!" );
39
+ return ajax(
40
+ 'ajax_woo_infobanner_post_click',
41
+ {
42
+ "_ajax_nonce": wc_facebook_infobanner_jsx.nonce
43
+ },
44
+ );
45
  }
46
 
47
  function fb_woo_infobanner_post_xout() {
48
+ console.log( "Woo infobanner post tip xout!" );
49
+ return ajax(
50
+ 'ajax_woo_infobanner_post_xout',
51
+ {
52
+ "_ajax_nonce": wc_facebook_infobanner_jsx.nonce
53
+ },
54
+ );
55
  }
assets/js/facebook-metabox.js CHANGED
@@ -12,56 +12,69 @@
12
  * Takes optional payload for POST and optional callback.
13
  */
14
  function ajax(action, payload = null, cb = null, failcb = null) {
15
- var data = {
16
- 'action': action,
17
- };
18
- if (payload){
19
- for (var attrname in payload) { data[attrname] = payload[attrname]; }
20
- }
21
 
22
- // Since Wordpress 2.8 ajaxurl is always defined in admin header and
23
- // points to admin-ajax.php
24
- jQuery.post(ajaxurl, data, function(response) {
25
- if(cb) {
26
- cb(response);
27
- }
28
- }).fail(function(errorResponse){
29
- if(failcb) {
30
- failcb(errorResponse);
31
- }
32
- });
 
 
 
 
 
 
33
  }
34
 
35
  function fb_reset_product(wp_id) {
36
- if(confirm('Resetting Facebook metadata will not remove this product from your shop. ' +
37
- 'If you have duplicated another product and are trying to publish a new Facebook product, ' +
38
- 'click OK to proceed. ' +
39
- 'Otherwise, Facebook metadata will be restored when this product is updated again.')) {
40
- var metadata = document.querySelector('#fb_metadata');
41
- if(metadata) {
42
- metadata.innerHTML =
43
- "<b>This product is not yet synced to Facebook.</b>";
44
- }
45
- return ajax(
46
- 'ajax_reset_single_fb_product',
47
- {'wp_id': wp_id}
48
- );
49
- }
 
 
 
 
 
50
  }
51
 
52
  function fb_delete_product(wp_id) {
53
- if(confirm('Are you sure you want to delete this product on Facebook? If you only want to "hide" the product, '+
54
- 'uncheck the "Visible" checkbox and hit "Update". If you delete a product on Facebook and hit "Update" after, ' +
55
- 'this product will be recreated. To permanently remove this product from Facebook, hit "OK" and close the window.'+
56
- 'This will not delete the product from WooCommerce.')) {
57
- var metadata = document.querySelector('#fb_metadata');
58
- if(metadata) {
59
- metadata.innerHTML =
60
- "<b>This product is not yet synced to Facebook.</b>";
61
- }
62
- return ajax(
63
- 'ajax_delete_fb_product',
64
- {'wp_id': wp_id}
65
- );
66
- }
 
 
 
 
 
67
  }
12
  * Takes optional payload for POST and optional callback.
13
  */
14
  function ajax(action, payload = null, cb = null, failcb = null) {
15
+ var data = Object.assign( {}, {
16
+ 'action': action,
17
+ }, payload);
 
 
 
18
 
19
+ // Since Wordpress 2.8 ajaxurl is always defined in admin header and
20
+ // points to admin-ajax.php
21
+ jQuery.post(
22
+ ajaxurl,
23
+ data,
24
+ function(response) {
25
+ if (cb) {
26
+ cb( response );
27
+ }
28
+ }
29
+ ).fail(
30
+ function(errorResponse){
31
+ if (failcb) {
32
+ failcb( errorResponse );
33
+ }
34
+ }
35
+ );
36
  }
37
 
38
  function fb_reset_product(wp_id) {
39
+ if (confirm(
40
+ 'Resetting Facebook metadata will not remove this product from your shop. ' +
41
+ 'If you have duplicated another product and are trying to publish a new Facebook product, ' +
42
+ 'click OK to proceed. ' +
43
+ 'Otherwise, Facebook metadata will be restored when this product is updated again.'
44
+ )) {
45
+ var metadata = document.querySelector( '#fb_metadata' );
46
+ if (metadata) {
47
+ metadata.innerHTML =
48
+ "<b>This product is not yet synced to Facebook.</b>";
49
+ }
50
+ return ajax(
51
+ 'ajax_reset_single_fb_product',
52
+ {
53
+ 'wp_id': wp_id,
54
+ "_ajax_nonce": wc_facebook_metabox_jsx.nonce
55
+ }
56
+ );
57
+ }
58
  }
59
 
60
  function fb_delete_product(wp_id) {
61
+ if (confirm(
62
+ 'Are you sure you want to delete this product on Facebook? If you only want to "hide" the product, ' +
63
+ 'uncheck the "Visible" checkbox and hit "Update". If you delete a product on Facebook and hit "Update" after, ' +
64
+ 'this product will be recreated. To permanently remove this product from Facebook, hit "OK" and close the window.' +
65
+ 'This will not delete the product from WooCommerce.'
66
+ )) {
67
+ var metadata = document.querySelector( '#fb_metadata' );
68
+ if (metadata) {
69
+ metadata.innerHTML =
70
+ "<b>This product is not yet synced to Facebook.</b>";
71
+ }
72
+ return ajax(
73
+ 'ajax_delete_fb_product',
74
+ {
75
+ 'wp_id': wp_id,
76
+ "_ajax_nonce": wc_facebook_metabox_jsx.nonce,
77
+ }
78
+ );
79
+ }
80
  }
assets/js/facebook-products.js CHANGED
@@ -12,58 +12,73 @@
12
  * Takes optional payload for POST and optional callback.
13
  */
14
  function ajax(action, payload = null, cb = null, failcb = null) {
15
- var data = {
16
- 'action': action,
17
- };
18
- if (payload){
19
- for (var attrname in payload) { data[attrname] = payload[attrname]; }
20
- }
21
 
22
- // Since Wordpress 2.8 ajaxurl is always defined in admin header and
23
- // points to admin-ajax.php
24
- jQuery.post(ajaxurl, data, function(response) {
25
- if(cb) {
26
- cb(response);
27
- }
28
- }).fail(function(errorResponse){
29
- if(failcb) {
30
- failcb(errorResponse);
31
- }
32
- });
 
 
 
 
 
 
33
  }
34
 
35
  function fb_toggle_visibility(wp_id, published) {
36
- var buttonId = document.querySelector("#viz_" + wp_id);
37
- var tooltip = document.querySelector("#tip_" + wp_id);
38
 
39
- if(published){
40
- tooltip.setAttribute('data-tip',
41
- 'Product is synced and published (visible) on Facebook.'
42
- );
43
- buttonId.setAttribute('onclick','fb_toggle_visibility('+wp_id+', false)');
44
- buttonId.innerHTML = 'Hide';
45
- buttonId.setAttribute('class', 'button');
46
- } else {
47
- tooltip.setAttribute('data-tip',
48
- 'Product is synced but not marked as published (visible) on Facebook.'
49
- );
50
- buttonId.setAttribute('onclick','fb_toggle_visibility('+wp_id+', true)');
51
- buttonId.innerHTML = 'Show';
52
- buttonId.setAttribute('class', 'button button-primary button-large');
53
- }
 
 
54
 
55
- //Reset tooltip
56
- jQuery(function($) {
57
- $('.tips').tipTip({
58
- 'attribute': 'data-tip',
59
- 'fadeIn': 50,
60
- 'fadeOut': 50,
61
- 'delay': 200
62
- });
63
- });
 
 
 
 
64
 
65
- return ajax(
66
- 'ajax_fb_toggle_visibility',
67
- {'wp_id': wp_id, 'published': published}
68
- );
 
 
 
 
69
  }
12
  * Takes optional payload for POST and optional callback.
13
  */
14
  function ajax(action, payload = null, cb = null, failcb = null) {
15
+ var data = Object.assign( {},
16
+ {
17
+ 'action': action,
18
+ }, payload
19
+ );
 
20
 
21
+ // Since Wordpress 2.8 ajaxurl is always defined in admin header and
22
+ // points to admin-ajax.php
23
+ jQuery.post(
24
+ ajaxurl,
25
+ data,
26
+ function(response) {
27
+ if (cb) {
28
+ cb( response );
29
+ }
30
+ }
31
+ ).fail(
32
+ function(errorResponse){
33
+ if (failcb) {
34
+ failcb( errorResponse );
35
+ }
36
+ }
37
+ );
38
  }
39
 
40
  function fb_toggle_visibility(wp_id, published) {
41
+ var buttonId = document.querySelector( "#viz_" + wp_id );
42
+ var tooltip = document.querySelector( "#tip_" + wp_id );
43
 
44
+ if (published) {
45
+ tooltip.setAttribute(
46
+ 'data-tip',
47
+ 'Product is synced and published (visible) on Facebook.'
48
+ );
49
+ buttonId.setAttribute( 'onclick','fb_toggle_visibility(' + wp_id + ', false)' );
50
+ buttonId.innerHTML = 'Hide';
51
+ buttonId.setAttribute( 'class', 'button' );
52
+ } else {
53
+ tooltip.setAttribute(
54
+ 'data-tip',
55
+ 'Product is synced but not marked as published (visible) on Facebook.'
56
+ );
57
+ buttonId.setAttribute( 'onclick','fb_toggle_visibility(' + wp_id + ', true)' );
58
+ buttonId.innerHTML = 'Show';
59
+ buttonId.setAttribute( 'class', 'button button-primary button-large' );
60
+ }
61
 
62
+ // Reset tooltip
63
+ jQuery(
64
+ function($) {
65
+ $( '.tips' ).tipTip(
66
+ {
67
+ 'attribute': 'data-tip',
68
+ 'fadeIn': 50,
69
+ 'fadeOut': 50,
70
+ 'delay': 200
71
+ }
72
+ );
73
+ }
74
+ );
75
 
76
+ return ajax(
77
+ 'ajax_fb_toggle_visibility',
78
+ {
79
+ 'wp_id': wp_id,
80
+ 'published': published,
81
+ "_ajax_nonce": wc_facebook_product_jsx.nonce
82
+ }
83
+ );
84
  }
assets/js/facebook-settings.js CHANGED
@@ -8,200 +8,230 @@
8
  */
9
 
10
  var fb_sync_no_response_count = 0;
11
- var fb_show_advanced_options = false;
12
 
13
  function toggleAdvancedOptions() {
14
- var opts = document.getElementById("fbAdvancedOptions");
15
- if (!fb_show_advanced_options) {
16
- opts.style.display = "block";
17
- document.getElementById('fbAdvancedOptionsText').innerHTML = 'Hide Advanced Settings';
18
- } else {
19
- opts.style.display = "none";
20
- document.getElementById('fbAdvancedOptionsText').innerHTML = 'Show Advanced Settings';
21
- }
22
- fb_show_advanced_options = !fb_show_advanced_options;
23
  }
24
 
25
  function openPopup() {
26
- var width = 1153;
27
- var height = 808;
28
- var topPos = screen.height / 2 - height / 2;
29
- var leftPos = screen.width / 2 - width / 2;
30
- window.originParam = window.location.protocol + '//' + window.location.host;
31
- var popupUrl;
32
- if(window.facebookAdsToolboxConfig.popupOrigin.includes('staticxx')) {
33
- window.facebookAdsToolboxConfig.popupOrigin = 'https://www.facebook.com/';
34
- }
35
- window.facebookAdsToolboxConfig.popupOrigin = prepend_protocol(
36
- window.facebookAdsToolboxConfig.popupOrigin
37
- );
38
- popupUrl = window.facebookAdsToolboxConfig.popupOrigin;
39
-
40
- var path = '/ads/dia';
41
- var page = window.open(popupUrl + '/login.php?display=popup&next=' + encodeURIComponent(popupUrl + path + '?origin=' + window.originParam + ' &merchant_settings_id=' + window.facebookAdsToolboxConfig.diaSettingId), 'DiaWizard', ['toolbar=no', 'location=no', 'directories=no', 'status=no', 'menubar=no', 'scrollbars=no', 'resizable=no', 'copyhistory=no', 'width=' + width, 'height=' + height, 'top=' + topPos, 'left=' + leftPos].join(','));
42
-
43
- return function (type, params) {
44
- page.postMessage({
45
- type: type,
46
- params: params
47
- }, window.facebookAdsToolboxConfig.popupOrigin);
48
- };
 
 
 
49
  }
50
 
51
  function prepend_protocol(url) {
52
- // Preprend https if the url begis with //www.
53
- if (url.indexOf('//www.') === 0) {
54
- url = 'https:' + url;
55
- }
56
- return url;
57
  }
58
 
59
  function get_product_catalog_id_box() {
60
- return document.querySelector('#woocommerce_facebookcommerce_fb_product_catalog_id') || null;
61
  }
62
  function get_pixel_id_box() {
63
- return document.querySelector('#woocommerce_facebookcommerce_fb_pixel_id') || null;
64
  }
65
  function get_pixel_use_pii_id_box() {
66
- return document.querySelector('#woocommerce_facebookcommerce_fb_pixel_use_pii') || null;
67
  }
68
  function get_api_key_box() {
69
- return document.querySelector('#woocommerce_facebookcommerce_fb_api_key') || null;
70
  }
71
  function get_page_id_box() {
72
- return document.querySelector('#woocommerce_facebookcommerce_fb_page_id') || null;
73
  }
74
  function get_ems_id_box() {
75
- return document.querySelector('#woocommerce_facebookcommerce_fb_external_merchant_settings_id') || null;
76
  }
77
 
78
  /*
79
  * Ajax helper function.
80
  * Takes optional payload for POST and optional callback.
81
  */
82
- function ajax(action, payload = null, callback = null, failcallback = null) {
83
- var data = Object.assign( {}, {
84
- 'action': action,
85
- }, payload);
86
-
87
- // Since Wordpress 2.8 ajaxurl is always defined in admin header and
88
- // points to admin-ajax.php
89
- jQuery.post(ajaxurl, data, function(response) {
90
- if(callback) {
91
- callback(response);
92
- }
93
- }).fail(function(errorResponse){
94
- if(failcallback) {
95
- failcallback(errorResponse);
96
- }
97
- });
98
- }
99
-
100
- var settings = {'facebook_for_woocommerce' : 1};
 
 
 
 
 
 
 
 
 
 
101
  var pixel_settings = {'facebook_for_woocommerce' : 1};
102
 
103
  function facebookConfig() {
104
- window.sendToFacebook = openPopup();
105
- window.diaConfig = { 'clientSetup': window.facebookAdsToolboxConfig };
106
  }
107
 
108
  function fb_flush(){
109
- console.log("Removing all FBIDs from all products!");
110
- return ajax('ajax_reset_all_fb_products');
 
 
 
 
 
 
 
111
  }
112
 
113
  function sync_confirm(verbose = null) {
114
- var msg = '';
115
- switch (verbose) {
116
- case 'fb_force_resync':
117
- msg = 'Your products will now be resynced with Facebook, ' +
118
- 'this may take some time.';
119
- break;
120
- case 'fb_test_product_sync':
121
- msg = 'Launch Test?';
122
- break;
123
- default:
124
- msg = 'Facebook for WooCommerce automatically syncs your products on ' +
125
- 'create/update. Are you sure you want to force product resync? ' +
126
- 'This will query all published products and may take some time. ' +
127
- 'You only need to do this if your products are out of sync ' +
128
- 'or some of your products did not sync.';
129
- }
130
- if(confirm(msg)) {
131
- sync_all_products(
132
- window.facebookAdsToolboxConfig.feed.hasClientSideFeedUpload,
133
- verbose == 'fb_test_product_sync'
134
- );
135
- window.fb_sync_start_time = new Date().getTime();
136
- }
137
  }
138
 
139
  // Launch the confirm dialog immediately if the param is in the URL.
140
- if (window.location.href.includes("fb_force_resync")) {
141
- window.onload = function() { sync_confirm("fb_force_resync"); };
142
- } else if (window.location.href.includes("fb_test_product_sync")) {
143
- // Test products sync by feed.
144
- window.is_test = true;
145
- window.onload = function() { sync_confirm("fb_test_product_sync"); };
146
  }
147
 
148
  function sync_all_products($using_feed = false, $is_test = false) {
149
- if (get_product_catalog_id_box() && !get_product_catalog_id_box().value){
150
- return;
151
- }
152
- if (get_api_key_box() && !get_api_key_box().value){
153
- return;
154
- }
155
- console.log('Syncing all products!');
156
- window.fb_connected = true;
157
- sync_in_progress();
158
- if ($using_feed) {
159
- window.facebookAdsToolboxConfig.feed.hasClientSideFeedUpload = true;
160
- window.feed_upload = true;
161
- ping_feed_status_queue();
162
- return $is_test ? ajax('ajax_test_sync_products_using_feed')
163
- : ajax('ajax_sync_all_fb_products_using_feed');
164
- } else {
165
- return ajax('ajax_sync_all_fb_products');
166
- }
 
 
 
 
 
 
 
 
 
 
167
  }
168
 
169
  // Reset all state
170
  function delete_all_settings(callback = null, failcallback = null) {
171
- if (get_product_catalog_id_box()) {
172
- get_product_catalog_id_box().value = '';
173
- }
174
- if(get_pixel_id_box()) {
175
- get_pixel_id_box().value = '';
176
- }
177
- if(get_pixel_use_pii_id_box()) {
178
- get_pixel_use_pii_id_box().checked = false;
179
- }
180
- if(get_api_key_box()) {
181
- get_api_key_box().value = '';
182
- }
183
- if(get_page_id_box()) {
184
- get_page_id_box().value = '';
185
- }
186
- if(get_ems_id_box()) {
187
- get_ems_id_box().value = '';
188
- }
189
-
190
- window.facebookAdsToolboxConfig.pixel.pixelId = '';
191
- window.facebookAdsToolboxConfig.diaSettingId = '';
192
-
193
- reset_buttons();
194
- window.fb_connected = false;
195
-
196
- console.log('Deleting all settings and removing all FBIDs!');
197
- return ajax(
198
- 'ajax_delete_fb_settings',
199
- {
200
- "_ajax_nonce": wc_facebook_settings_jsx.nonce,
201
- },
202
- callback,
203
- failcallback
204
- );
205
  }
206
 
207
  // save_settings and save_settings_and_sync should only be called once
@@ -211,690 +241,744 @@ function delete_all_settings(callback = null, failcallback = null) {
211
  // It's also called again if the pixel id is ever changed or pixel pii is
212
  // enabled or disabled.
213
  function save_settings(callback = null, failcallback = null, localsettings = null){
214
- if (!localsettings) {
215
- localsettings = settings;
216
- }
217
- ajax('ajax_save_fb_settings', localsettings,
218
- function(response){
219
- if(callback) {
220
- callback(response);
221
- }
222
- },
223
- function(errorResponse){
224
- if(failcallback) {
225
- failcallback(errorResponse);
226
- }
227
- }
228
- );
 
 
 
229
  }
230
 
231
  // save_settings wrapper for plugins as we do not need to:
232
- // 1. sync products again after plugin is configured
233
- // 2. check api_key, which is from facebook and is only necessary
234
- // for following sync products
235
  function save_settings_for_plugin(callback, failcallback) {
236
- save_settings(
237
- function(response){
238
- if (response && response.includes('settings_saved')){
239
- console.log(response);
240
- callback(response);
241
- } else {
242
- console.log('Fail response on save_settings_and_sync');
243
- failcallback(response);
244
- }
245
- },
246
- function(errorResponse){
247
- console.log('Ajax error while saving settings:' + JSON.stringify(errorResponse));
248
- failcallback(errorResponse);
249
- });
 
250
  }
251
 
252
  // see comments in save_settings function above
253
  function save_settings_and_sync(message) {
254
- if ('api_key' in settings){
255
- save_settings(
256
- function(response){
257
- if (response && response.includes('settings_saved')){
258
- console.log(response);
259
- //Final acks
260
- window.sendToFacebook('ack set pixel', message.params);
261
- window.sendToFacebook('ack set page access token', message.params);
262
- window.sendToFacebook('ack set merchant settings', message.params);
263
- sync_all_products(true);
264
- } else {
265
- window.sendToFacebook('fail save_settings', response);
266
- console.log('Fail response on save_settings_and_sync');
267
- }
268
- },
269
- function(errorResponse){
270
- console.log('Ajax error while saving settings:' + JSON.stringify(errorResponse));
271
- window.sendToFacebook('fail save_settings_ajax', JSON.stringify(errorResponse));
272
- }
273
- );
274
- }
275
- }
276
-
277
- //Reset buttons to brand new setup state
278
  function reset_buttons(){
279
- if(document.querySelector('#settings')){
280
- document.querySelector('#settings').style.display = 'none';
281
- }
282
- if(document.querySelector('#cta_button')){
283
- var cta_element = document.querySelector('#cta_button');
284
- cta_element.innerHTML = 'Get Started';
285
- cta_element.style['font-size'] = '13px';
286
- cta_element.style.width = '80px';
287
- cta_element.href = '#';
288
- cta_element.onclick= function() { facebookConfig(); };
289
- }
290
- if(document.querySelector('#learnmore_button')){
291
- document.querySelector('#learnmore_button').style.display = 'none';
292
- }
293
- if(document.querySelector('#setup_h1')) {
294
- document.querySelector('#setup_h1').innerHTML =
295
- 'Grow your business on Facebook';
296
- }
297
- if(document.querySelector('#setup_l1')){
298
- document.querySelector('#setup_l1').innerHTML =
299
- 'Easily install a tracking pixel';
300
- }
301
- if(document.querySelector('#setup_l2')){
302
- document.querySelector('#setup_l2').innerHTML =
303
- 'Upload your products and create a shop';
304
- }
305
- if(document.querySelector('#setup_l3')){
306
- document.querySelector('#setup_l3').innerHTML =
307
- 'Create dynamic ads with your products and pixel';
308
- }
309
- }
310
-
311
- //Remove reset/settings buttons during product sync
312
  function sync_in_progress(){
313
- if(document.querySelector('#settings')){
314
- document.querySelector('#settings').style.display = '';
315
- }
316
- if(document.querySelector('#connection_status')){
317
- document.querySelector('#connection_status').style.display = '';
318
- }
319
- if(document.querySelector('#sync_complete')){
320
- document.querySelector('#sync_complete').style.display = 'none';
321
- }
322
- //Get rid of all the buttons
323
- if(document.querySelector('#setting_button')){
324
- document.querySelector('#setting_button').style['pointer-events'] = 'none';
325
- }
326
- if(document.querySelector('#resync_products')) {
327
- document.querySelector('#resync_products').style['pointer-events'] = 'none';
328
- }
329
- if(document.querySelector('#test_product_sync')) {
330
- document.querySelector('#test_product_sync').style.display = 'none';
331
- }
332
- //Set a product sync status
333
- if(document.querySelector('#sync_progress')){
334
- document.querySelector('#sync_progress').innerHTML =
335
- 'Syncing... Keep this browser open <br/>' +
336
- 'Until sync is complete<br/>' +
337
- '<div class="loader"></div>';
338
- }
339
  }
340
 
341
  function sync_not_in_progress(){
342
- // Reset to pre-setup state.
343
- if(document.querySelector('#cta_button')){
344
- var cta_element = document.querySelector('#cta_button');
345
- cta_element.innerHTML = 'Create Ad';
346
- cta_element.style['font-size'] = '12px';
347
- cta_element.style.width = '60px';
348
- if (window.facebookAdsToolboxConfig.diaSettingId) {
349
- cta_element.onclick= function() {
350
- window.open('https://www.facebook.com/ads/dia/redirect/?settings_id=' +
351
- window.facebookAdsToolboxConfig.diaSettingId + '&version=2' +
352
- '&entry_point=admin_panel');
353
- };
354
- } else {
355
- cta_element.style['pointer-events'] = 'none';
356
- }
357
- }
358
- if(document.querySelector('#learnmore_button')){
359
- var learnmore_element = document.querySelector('#learnmore_button');
360
- if (window.facebookAdsToolboxConfig.diaSettingId) {
361
- learnmore_element.style.display = '';
362
- }
363
- }
364
- if(document.querySelector('#setup_h1')) {
365
- document.querySelector('#setup_h1').innerHTML =
366
- 'Reach the right people and sell more products';
367
- }
368
- if(document.querySelector('#setup_l1')){
369
- document.querySelector('#setup_l1').innerHTML =
370
- 'Create an ad in a few steps';
371
- }
372
- if(document.querySelector('#setup_l2')){
373
- document.querySelector('#setup_l2').innerHTML =
374
- 'Use built-in best practice for online sales';
375
- }
376
- if(document.querySelector('#setup_l3')){
377
- document.querySelector('#setup_l3').innerHTML =
378
- 'Get reporting on sales and revenue';
379
- }
380
- if(document.querySelector('#settings')){
381
- document.querySelector('#settings').style.display = '';
382
- }
383
- // Enable buttons.
384
- if(document.querySelector('#setting_button')){
385
- document.querySelector('#setting_button').style['pointer-events'] = 'auto';
386
- }
387
- if(document.querySelector('#resync_products')) {
388
- document.querySelector('#resync_products').style ['pointer-events'] = 'auto';
389
- }
390
- // Remove sync progress.
391
- if(document.querySelector('#sync_progress')){
392
- document.querySelector('#sync_progress').innerHTML = '';
393
- }
 
 
394
  }
395
 
396
  function not_connected(){
397
- if(document.querySelector('#connection_status')){
398
- document.querySelector('#connection_status').style.display = 'none';
399
- }
400
-
401
- if(document.querySelector('#setting_button')){
402
- document.querySelector('#setting_button').style['pointer-events'] = 'auto';
403
- }
404
- if(document.querySelector('#resync_products')) {
405
- document.querySelector('#resync_products').style['pointer-events'] = 'none';
406
- }
407
- if(document.querySelector('#sync_complete')) {
408
- document.querySelector('#sync_complete').style.display = 'none';
409
- }
410
- if(document.querySelector('#sync_progress')){
411
- document.querySelector('#sync_progress').innerHTML = '';
412
- }
413
  }
414
 
415
  function addAnEventListener(obj,evt,func) {
416
- if ('addEventListener' in obj){
417
- obj.addEventListener(evt,func, false);
418
- } else if ('attachEvent' in obj){//IE
419
- obj.attachEvent('on'+evt,func);
420
- }
421
  }
422
 
423
  function setMerchantSettings(message) {
424
- if (!message.params.setting_id) {
425
- console.error('Facebook Extension Error: got no setting_id', message.params);
426
- window.sendToFacebook('fail set merchant settings', message.params);
427
- return;
428
- }
429
- if(get_ems_id_box()){
430
- get_ems_id_box().value = message.params.setting_id;
431
- }
432
 
433
- settings.external_merchant_settings_id = message.params.setting_id;
434
 
435
- //Immediately set in case button is clicked again
436
- window.facebookAdsToolboxConfig.diaSettingId = message.params.setting_id;
437
- //Ack merchant settings happens after settings are saved
438
  }
439
 
440
  function setCatalog(message) {
441
- if (!message.params.catalog_id) {
442
- console.error('Facebook Extension Error: got no catalog_id', message.params);
443
- window.sendToFacebook('fail set catalog', message.params);
444
- return;
445
- }
446
- if(get_api_key_box()){
447
- get_product_catalog_id_box().value = message.params.catalog_id;
448
- }
449
 
450
- settings.product_catalog_id = message.params.catalog_id;
451
 
452
- window.sendToFacebook('ack set catalog', message.params);
453
  }
454
 
455
 
456
  function setPixel(message) {
457
- if (!message.params.pixel_id) {
458
- console.error('Facebook Ads Extension Error: got no pixel_id', message.params);
459
- window.sendToFacebook('fail set pixel', message.params);
460
- return;
461
- }
462
- if(get_pixel_id_box()){
463
- get_pixel_id_box().value = message.params.pixel_id;
464
- }
465
-
466
- settings.pixel_id = message.params.pixel_id;
467
- pixel_settings.pixel_id = settings.pixel_id;
468
- if (message.params.pixel_use_pii !== undefined) {
469
- if(get_pixel_use_pii_id_box()){
470
- //!! will explicitly convert truthy/falsy values to a boolean
471
- get_pixel_use_pii_id_box().checked = !!message.params.pixel_use_pii;
472
- }
473
- settings.pixel_use_pii = message.params.pixel_use_pii;
474
- pixel_settings.pixel_use_pii = settings.pixel_use_pii;
475
- }
476
-
477
- // We need this to support changing the pixel id after setup.
478
- save_settings(
479
- function(response){
480
- if (response && response.includes('settings_saved')){
481
- window.sendToFacebook('ack set pixel', message.params);
482
- } //may not get settings_saved if we try to save pixel before an API key
483
- },
484
- function(errorResponse){
485
- console.log(errorResponse);
486
- window.sendToFacebook('fail set pixel', errorResponse);
487
- },
488
- pixel_settings
489
- );
490
  }
491
 
492
  function genFeed(message) {
493
- //no-op
494
  }
495
 
496
  function setAccessTokenAndPageId(message) {
497
- if (!message.params.page_token) {
498
- console.error('Facebook Ads Extension Error: got no page_token',
499
- message.params);
500
- window.sendToFacebook('fail set page access token', message.params);
501
- return;
502
- }
503
- /*
504
- Set page_token here
505
- */
506
-
507
- if(get_api_key_box()){
508
- get_api_key_box().value = message.params.page_token;
509
- }
510
-
511
- if(get_page_id_box()){
512
- get_page_id_box().value = message.params.page_id;
513
- }
514
-
515
- settings.api_key = message.params.page_token;
516
- settings.page_id = message.params.page_id;
517
- //Ack token in "save_settings_and_sync" for final ack
518
-
519
- window.facebookAdsToolboxConfig.tokenExpired = false;
520
- if(document.querySelector('#token_text')){
521
- document.querySelector('#token_text').innerHTML =
522
- `<strong>Your API key has been updated.<br />
523
- Please refresh the page.</strong>`;
524
- }
 
 
525
  }
526
 
527
  function setMsgerChatSetup(data) {
528
- if (data.hasOwnProperty('is_messenger_chat_plugin_enabled')) {
529
- settings.is_messenger_chat_plugin_enabled =
530
- data.is_messenger_chat_plugin_enabled;
531
- }
532
- if (data.hasOwnProperty('facebook_jssdk_version')) {
533
- settings.facebook_jssdk_version =
534
- data.facebook_jssdk_version;
535
- }
536
- if (data.hasOwnProperty('page_id')) {
537
- settings.fb_page_id = data.page_id;
538
- }
539
-
540
- if (data.hasOwnProperty('customization')) {
541
- var customization = data.customization;
542
-
543
- if (customization.hasOwnProperty('greetingTextCode')) {
544
- settings.msger_chat_customization_greeting_text_code =
545
- customization.greetingTextCode;
546
- }
547
- if (customization.hasOwnProperty('locale')) {
548
- settings.msger_chat_customization_locale =
549
- customization.locale;
550
- }
551
- if (customization.hasOwnProperty('themeColorCode')) {
552
- settings.msger_chat_customization_theme_color_code =
553
- customization.themeColorCode;
554
- }
555
- }
556
  }
557
 
558
  function iFrameListener(event) {
559
- // Fix for web.facebook.com
560
- const origin = event.origin || event.originalEvent.origin;
561
- if (origin != window.facebookAdsToolboxConfig.popupOrigin &&
562
- urlFromSameDomain(origin, window.facebookAdsToolboxConfig.popupOrigin)) {
563
- window.facebookAdsToolboxConfig.popupOrigin = origin;
564
- }
565
-
566
- switch (event.data.type) {
567
- case 'reset':
568
- delete_all_settings(function(res){
569
- if(res && event.data.params) {
570
- if(res === 'Settings Deleted'){
571
- window.sendToFacebook('ack reset', event.data.params);
572
- }else{
573
- console.log(res);
574
- alert(res);
575
- }
576
- }else {
577
- console.log("Got no response from delete_all_settings");
578
- }
579
- },function(err){
580
- console.error(err);
581
- });
582
- break;
583
- case 'get dia settings':
584
- window.sendToFacebook('dia settings', window.diaConfig);
585
- break;
586
- case 'set merchant settings':
587
- setMerchantSettings(event.data);
588
- break;
589
- case 'set catalog':
590
- setCatalog(event.data);
591
- break;
592
- case 'set pixel':
593
- setPixel(event.data);
594
- break;
595
- case 'gen feed':
596
- genFeed();
597
- break;
598
- case 'set page access token':
599
- //Should be last message received
600
- setAccessTokenAndPageId(event.data);
601
- save_settings_and_sync(event.data);
602
- break;
603
- case 'set msger chat':
604
- setMsgerChatSetup(event.data.params);
605
- save_settings_for_plugin(
606
- function(response) {
607
- window.sendToFacebook('ack msger chat', event.data);
608
- },
609
- function(response) {
610
- window.sendToFacebook('fail ack msger chat', event.data);
611
- });
612
- break;
613
- }
614
- }
615
-
616
- addAnEventListener(window,'message',iFrameListener);
 
 
 
 
617
 
618
  function urlFromSameDomain(url1, url2) {
619
- if (!url1.startsWith('http') || !url2.startsWith('http')) {
620
- return false;
621
- }
622
- var u1 = parseURL(url1);
623
- var u2 = parseURL(url2);
624
- var u1host = u1.host.replace(/^\w+\./, 'www.');
625
- var u2host = u2.host.replace(/^\w+\./, 'www.');
626
- return u1.protocol === u2.protocol && u1host === u2host;
627
  }
628
 
629
  function parseURL(url) {
630
- var parser = document.createElement('a');
631
- parser.href = url;
632
- return parser;
633
  }
634
 
635
  // Only do pings for supporting older (pre 1.8) setups.
636
  window.fb_pings =
637
  (window.facebookAdsToolboxConfig.feed.hasClientSideFeedUpload) ?
638
  null :
639
- setInterval(function(){
640
- console.log("Pinging queue...");
641
- check_queues();
642
- }, 10000);
 
 
 
643
 
644
  function ping_feed_status_queue(count = 0) {
645
- window.fb_feed_pings = setInterval(function() {
646
- console.log('Pinging feed uploading queue...');
647
- check_feed_upload_queue(count);
648
- }, 30000*(1 << count));
649
- }
650
-
651
- function product_sync_complete(sync_progress_element){
652
- sync_not_in_progress();
653
- if(document.querySelector('#sync_complete')){
654
- document.querySelector('#sync_complete').style.display = '';
655
- }
656
- if(sync_progress_element) {
657
- sync_progress_element.innerHTML = '';
658
- }
659
- clearInterval(window.fb_pings);
660
- }
661
-
662
- function check_queues(){
663
- ajax('ajax_fb_background_check_queue',
664
- {"request_time": new Date().getTime()}, function(response){
665
- if (window.feed_upload) {
666
- clearInterval(window.fb_pings);
667
- return;
668
- }
669
- var sync_progress_element = document.querySelector('#sync_progress');
670
- var res = parse_response_check_connection(response);
671
- if (!res) {
672
- if (fb_sync_no_response_count++ > 5) {
673
- clearInterval(window.fb_pings);
674
- }
675
- return;
676
- }
677
- fb_sync_no_response_count = 0;
678
-
679
- if(res){
680
- if(!res.background){
681
- console.log("No background sync found, disabling pings");
682
- clearInterval(window.fb_pings);
683
- }
684
-
685
- var processing = !!res.processing; //explicit boolean conversion
686
- var remaining = res.remaining;
687
- if(processing) {
688
- if(sync_progress_element) {
689
- sync_progress_element.innerHTML =
690
- '<strong>Progress:</strong> ' + remaining + ' item' +
691
- (remaining > 1 ? 's' : '') + ' remaining.';
692
- }
693
- if(remaining === 0){
694
- product_sync_complete(sync_progress_element);
695
- }
696
- } else {
697
- //Not processing, none remaining. Either long complete, or just completed
698
- if(window.fb_sync_start_time && res.request_time){
699
- var request_time = new Date(parseInt(res.request_time));
700
- if(window.fb_sync_start_time > request_time){
701
- //Old ping, do nothing.
702
- console.log("OLD PING");
703
- return;
704
- }
705
- }
706
-
707
- if(remaining === 0){
708
- product_sync_complete(sync_progress_element);
709
- }
710
- }
711
- }
712
- });
 
 
 
 
 
 
 
 
 
713
  }
714
 
715
  function parse_response_check_connection(res) {
716
- if (res) {
717
- console.log(res);
718
- var response = res.substring(res.indexOf("{")); //Trim leading extra chars (rnrnr)
719
- response = JSON.parse(response);
720
- if(!response.connected && !window.fb_connected){
721
- not_connected();
722
- return null;
723
- }
724
- return response;
725
- }
726
- return null;
727
  }
728
 
729
  function check_feed_upload_queue(check_num) {
730
- ajax('ajax_check_feed_upload_status', null, function(response) {
731
- var sync_progress_element = document.querySelector('#sync_progress');
732
- var res = parse_response_check_connection(response);
733
- clearInterval(window.fb_feed_pings);
734
- if (res) {
735
- var status = res.status;
736
- switch (status) {
737
- case 'complete':
738
- window.feed_upload = false;
739
- if (window.is_test) {
740
- display_test_result();
741
- } else {
742
- product_sync_complete(sync_progress_element);
743
- }
744
- break;
745
- case 'in progress':
746
- if (sync_progress_element) {
747
- sync_progress_element.innerHTML =
748
- 'Syncing... Keep this browser open <br/>' +
749
- 'Until sync is complete<br/>';
750
- }
751
- ping_feed_status_queue(check_num+1);
752
- break;
753
- default:
754
- sync_progress_element.innerHTML =
755
- '<strong>Something wrong when uploading, please try again.</strong>';
756
- window.feed_upload = false;
757
- if (window.is_test) {
758
- display_test_result();
759
- }
760
- }
761
- }
762
- });
 
 
 
 
 
 
763
  }
764
 
765
  function display_test_result() {
766
- ajax('ajax_display_test_result', null, function(response) {
767
- var sync_complete_element = document.querySelector('#sync_complete');
768
- var sync_progress_element = document.querySelector('#sync_progress');
769
- var res = parse_response_check_connection(response);
770
- if (res) {
771
- var status = res.pass;
772
- switch (status) {
773
- case 'true':
774
- sync_not_in_progress();
775
- if(sync_complete_element) {
776
- sync_complete_element.style.display = '';
777
- sync_complete_element.innerHTML =
778
- '<strong>Status: </strong>Test Pass.';
779
- }
780
- if(sync_progress_element) {
781
- sync_progress_element.innerHTML = '';
782
- }
783
- window.is_test = false;
784
- break;
785
- case 'in progress':
786
- if (sync_progress_element) {
787
- sync_progress_element.innerHTML =
788
- '<strong>Integration test in progress...</strong>';
789
- }
790
- ping_feed_status_queue();
791
- break;
792
- default:
793
- window.debug_info = res.debug_info + '<br/>' + res.stack_trace;
794
- if(sync_complete_element) {
795
- sync_complete_element.style.display = '';
796
- sync_complete_element.innerHTML =
797
- '<strong>Status: </strong>Test Fail.';
798
- }
799
- if (sync_progress_element) {
800
- sync_progress_element.innerHTML = '';
801
- }
802
- if(document.querySelector('#debug_info')) {
803
- document.querySelector('#debug_info').style.display = '';
804
- }
805
- window.is_test = false;
806
- }
807
- }
808
- });
 
 
 
 
 
 
809
  }
810
 
811
  function show_debug_info() {
812
- var stack_trace_element = document.querySelector('#stack_trace');
813
- if(stack_trace_element) {
814
- stack_trace_element.innerHTML = window.debug_info;
815
- }
816
- if(document.querySelector('#debug_info')) {
817
- document.querySelector('#debug_info').style.display = 'none';
818
- }
819
- window.debug_info = '';
820
  }
821
 
822
  function fbe_init_nux_messages() {
823
- var jQuery = window.jQuery;
824
- jQuery(function() {
825
- jQuery.each(jQuery('.nux-message'), function(_index, nux_msg) {
826
- var nux_msg_elem = jQuery(nux_msg);
827
- var targetid = nux_msg_elem.data('target');
828
- var target_elem = jQuery('#' + targetid);
829
- var t_pos = target_elem.position();
830
- var t_half_height = target_elem.height() / 2;
831
- var t_width = target_elem.outerWidth();
832
- nux_msg_elem.css({
833
- 'top': '' + Math.ceil(t_pos.top + t_half_height) + 'px',
834
- 'left': '' + Math.ceil(t_pos.left + t_width) + 'px',
835
- 'display': 'block'
836
- });
837
- jQuery('.nux-message-close-btn', nux_msg_elem).click(function() {
838
- jQuery(nux_msg).hide();
839
- });
840
- });
841
- });
 
 
 
 
 
 
 
 
 
842
  }
843
 
844
  function saveAutoSyncSchedule() {
845
- var isChecked = document.getElementsByClassName('autosyncCheck')[0].checked;
846
- var timebox = document.getElementsByClassName('autosyncTime')[0];
847
- var button = document.getElementsByClassName('autosyncSaveButton')[0];
848
- var saved = document.getElementsByClassName('autosyncSavedNotice')[0];
849
-
850
- if (!isChecked) {
851
- timebox.setAttribute('disabled', true);
852
- } else {
853
- timebox.removeAttribute('disabled');
854
- saved.style.transition = '';
855
- saved.style.opacity = 1;
856
- // Fade out the small 'Saved' after 3 seconds.
857
- setTimeout(function() {
858
- saved.style.opacity = 0;
859
- saved.style.transition = 'opacity 5s';}
860
- ,3000);
861
- }
862
-
863
- ajax('ajax_schedule_force_resync', {"enabled": isChecked ? 1 : 0, "time": timebox.value});
 
 
 
 
 
 
 
 
 
864
  }
865
 
866
  function onSetDisableSyncOnDevEnvironment() {
867
- var isChecked = document.getElementsByClassName('disableOnDevEnvironment')[0].checked;
868
- ajax(
869
- 'ajax_update_fb_option',
870
- {
871
- "option": "fb_disable_sync_on_dev_environment",
872
- "option_value": isChecked ? 1 : 0,
873
- "_ajax_nonce": wc_facebook_settings_jsx.nonce,
874
- },
875
- null,
876
- function onSetDisableSyncOnDevEnvironmentFailCallback(error) {
877
- document.getElementsByClassName(
878
- 'onSetDisableSyncOnDevEnvironment'
879
- )[0].checked = !isChecked;
880
- console.log('Failed to disable sync on dev environment');
881
- }
882
- );
883
  }
884
 
885
  function syncShortDescription() {
886
- var isChecked = document.getElementsByClassName('syncShortDescription')[0].checked;
887
- ajax(
888
- 'ajax_update_fb_option',
889
- {
890
- "option": "fb_sync_short_description",
891
- "option_value": isChecked ? 1 : 0,
892
- "_ajax_nonce": wc_facebook_settings_jsx.nonce,
893
- },
894
- null,
895
- function syncShortDescriptionFailCallback(error) {
896
- document.getElementsByClassName('syncShortDescription')[0].checked = ! isChecked;
897
- console.log('Failed to sync Short Description');
898
- }
899
- );
900
  }
8
  */
9
 
10
  var fb_sync_no_response_count = 0;
11
+ var fb_show_advanced_options = false;
12
 
13
  function toggleAdvancedOptions() {
14
+ var opts = document.getElementById( "fbAdvancedOptions" );
15
+ if ( ! fb_show_advanced_options) {
16
+ opts.style.display = "block";
17
+ document.getElementById( 'fbAdvancedOptionsText' ).innerHTML = 'Hide Advanced Settings';
18
+ } else {
19
+ opts.style.display = "none";
20
+ document.getElementById( 'fbAdvancedOptionsText' ).innerHTML = 'Show Advanced Settings';
21
+ }
22
+ fb_show_advanced_options = ! fb_show_advanced_options;
23
  }
24
 
25
  function openPopup() {
26
+ var width = 1153;
27
+ var height = 808;
28
+ var topPos = screen.height / 2 - height / 2;
29
+ var leftPos = screen.width / 2 - width / 2;
30
+ window.originParam = window.location.protocol + '//' + window.location.host;
31
+ var popupUrl;
32
+ if (window.facebookAdsToolboxConfig.popupOrigin.includes( 'staticxx' )) {
33
+ window.facebookAdsToolboxConfig.popupOrigin = 'https://www.facebook.com/';
34
+ }
35
+ window.facebookAdsToolboxConfig.popupOrigin = prepend_protocol(
36
+ window.facebookAdsToolboxConfig.popupOrigin
37
+ );
38
+ popupUrl = window.facebookAdsToolboxConfig.popupOrigin;
39
+
40
+ var path = '/ads/dia';
41
+ var page = window.open( popupUrl + '/login.php?display=popup&next=' + encodeURIComponent( popupUrl + path + '?origin=' + window.originParam + ' &merchant_settings_id=' + window.facebookAdsToolboxConfig.diaSettingId ), 'DiaWizard', ['toolbar=no', 'location=no', 'directories=no', 'status=no', 'menubar=no', 'scrollbars=no', 'resizable=no', 'copyhistory=no', 'width=' + width, 'height=' + height, 'top=' + topPos, 'left=' + leftPos].join( ',' ) );
42
+
43
+ return function (type, params) {
44
+ page.postMessage(
45
+ {
46
+ type: type,
47
+ params: params
48
+ },
49
+ window.facebookAdsToolboxConfig.popupOrigin
50
+ );
51
+ };
52
  }
53
 
54
  function prepend_protocol(url) {
55
+ // Preprend https if the url begis with //www.
56
+ if (url.indexOf( '//www.' ) === 0) {
57
+ url = 'https:' + url;
58
+ }
59
+ return url;
60
  }
61
 
62
  function get_product_catalog_id_box() {
63
+ return document.querySelector( '#woocommerce_facebookcommerce_fb_product_catalog_id' ) || null;
64
  }
65
  function get_pixel_id_box() {
66
+ return document.querySelector( '#woocommerce_facebookcommerce_fb_pixel_id' ) || null;
67
  }
68
  function get_pixel_use_pii_id_box() {
69
+ return document.querySelector( '#woocommerce_facebookcommerce_fb_pixel_use_pii' ) || null;
70
  }
71
  function get_api_key_box() {
72
+ return document.querySelector( '#woocommerce_facebookcommerce_fb_api_key' ) || null;
73
  }
74
  function get_page_id_box() {
75
+ return document.querySelector( '#woocommerce_facebookcommerce_fb_page_id' ) || null;
76
  }
77
  function get_ems_id_box() {
78
+ return document.querySelector( '#woocommerce_facebookcommerce_fb_external_merchant_settings_id' ) || null;
79
  }
80
 
81
  /*
82
  * Ajax helper function.
83
  * Takes optional payload for POST and optional callback.
84
  */
85
+ function ajax(action, payload = null, callback = null, failcallback = null) {
86
+ var data = Object.assign(
87
+ {},
88
+ {
89
+ 'action': action,
90
+ },
91
+ payload
92
+ );
93
+
94
+ // Since Wordpress 2.8 ajaxurl is always defined in admin header and
95
+ // points to admin-ajax.php
96
+ jQuery.post(
97
+ ajaxurl,
98
+ data,
99
+ function(response) {
100
+ if (callback) {
101
+ callback( response );
102
+ }
103
+ }
104
+ ).fail(
105
+ function(errorResponse){
106
+ if (failcallback) {
107
+ failcallback( errorResponse );
108
+ }
109
+ }
110
+ );
111
+ }
112
+
113
+ var settings = {'facebook_for_woocommerce' : 1};
114
  var pixel_settings = {'facebook_for_woocommerce' : 1};
115
 
116
  function facebookConfig() {
117
+ window.sendToFacebook = openPopup();
118
+ window.diaConfig = { 'clientSetup': window.facebookAdsToolboxConfig };
119
  }
120
 
121
  function fb_flush(){
122
+ console.log( "Removing all FBIDs from all products!" );
123
+ return ajax(
124
+ 'ajax_reset_all_fb_products',
125
+ {"_ajax_nonce": wc_facebook_settings_jsx.nonce},
126
+ null,
127
+ function fb_flushFailCallback(error) {
128
+ console.log('Failed to reset all FB products');
129
+ }
130
+ );
131
  }
132
 
133
  function sync_confirm(verbose = null) {
134
+ var msg = '';
135
+ switch (verbose) {
136
+ case 'fb_force_resync':
137
+ msg = 'Your products will now be resynced with Facebook, ' +
138
+ 'this may take some time.';
139
+ break;
140
+ case 'fb_test_product_sync':
141
+ msg = 'Launch Test?';
142
+ break;
143
+ default:
144
+ msg = 'Facebook for WooCommerce automatically syncs your products on ' +
145
+ 'create/update. Are you sure you want to force product resync? ' +
146
+ 'This will query all published products and may take some time. ' +
147
+ 'You only need to do this if your products are out of sync ' +
148
+ 'or some of your products did not sync.';
149
+ }
150
+ if (confirm( msg )) {
151
+ sync_all_products(
152
+ window.facebookAdsToolboxConfig.feed.hasClientSideFeedUpload,
153
+ verbose == 'fb_test_product_sync'
154
+ );
155
+ window.fb_sync_start_time = new Date().getTime();
156
+ }
157
  }
158
 
159
  // Launch the confirm dialog immediately if the param is in the URL.
160
+ if (window.location.href.includes( "fb_force_resync" )) {
161
+ window.onload = function() { sync_confirm( "fb_force_resync" ); };
162
+ } else if (window.location.href.includes( "fb_test_product_sync" )) {
163
+ // Test products sync by feed.
164
+ window.is_test = true;
165
+ window.onload = function() { sync_confirm( "fb_test_product_sync" ); };
166
  }
167
 
168
  function sync_all_products($using_feed = false, $is_test = false) {
169
+ if (get_product_catalog_id_box() && ! get_product_catalog_id_box().value) {
170
+ return;
171
+ }
172
+ if (get_api_key_box() && ! get_api_key_box().value) {
173
+ return;
174
+ }
175
+ console.log( 'Syncing all products!' );
176
+ window.fb_connected = true;
177
+ sync_in_progress();
178
+ if ($using_feed) {
179
+ window.facebookAdsToolboxConfig.feed.hasClientSideFeedUpload = true;
180
+ window.feed_upload = true;
181
+ ping_feed_status_queue();
182
+ return $is_test ? ajax( 'ajax_test_sync_products_using_feed' )
183
+ : ajax(
184
+ 'ajax_sync_all_fb_products_using_feed',
185
+ {
186
+ "_ajax_nonce": wc_facebook_settings_jsx.nonce,
187
+ },
188
+ );
189
+ } else {
190
+ return ajax(
191
+ 'ajax_sync_all_fb_products',
192
+ {
193
+ "_ajax_nonce": wc_facebook_settings_jsx.nonce,
194
+ }
195
+ );
196
+ }
197
  }
198
 
199
  // Reset all state
200
  function delete_all_settings(callback = null, failcallback = null) {
201
+ if (get_product_catalog_id_box()) {
202
+ get_product_catalog_id_box().value = '';
203
+ }
204
+ if (get_pixel_id_box()) {
205
+ get_pixel_id_box().value = '';
206
+ }
207
+ if (get_pixel_use_pii_id_box()) {
208
+ get_pixel_use_pii_id_box().checked = false;
209
+ }
210
+ if (get_api_key_box()) {
211
+ get_api_key_box().value = '';
212
+ }
213
+ if (get_page_id_box()) {
214
+ get_page_id_box().value = '';
215
+ }
216
+ if (get_ems_id_box()) {
217
+ get_ems_id_box().value = '';
218
+ }
219
+
220
+ window.facebookAdsToolboxConfig.pixel.pixelId = '';
221
+ window.facebookAdsToolboxConfig.diaSettingId = '';
222
+
223
+ reset_buttons();
224
+ window.fb_connected = false;
225
+
226
+ console.log( 'Deleting all settings and removing all FBIDs!' );
227
+ return ajax(
228
+ 'ajax_delete_fb_settings',
229
+ {
230
+ "_ajax_nonce": wc_facebook_settings_jsx.nonce,
231
+ },
232
+ callback,
233
+ failcallback
234
+ );
235
  }
236
 
237
  // save_settings and save_settings_and_sync should only be called once
241
  // It's also called again if the pixel id is ever changed or pixel pii is
242
  // enabled or disabled.
243
  function save_settings(callback = null, failcallback = null, localsettings = null){
244
+ if ( ! localsettings) {
245
+ localsettings = settings;
246
+ }
247
+ localsettings["_ajax_nonce"] = wc_facebook_settings_jsx.nonce;
248
+ ajax(
249
+ 'ajax_save_fb_settings',
250
+ localsettings,
251
+ function(response){
252
+ if (callback) {
253
+ callback( response );
254
+ }
255
+ },
256
+ function(errorResponse){
257
+ if (failcallback) {
258
+ failcallback( errorResponse );
259
+ }
260
+ }
261
+ );
262
  }
263
 
264
  // save_settings wrapper for plugins as we do not need to:
265
+ // 1. sync products again after plugin is configured
266
+ // 2. check api_key, which is from facebook and is only necessary
267
+ // for following sync products
268
  function save_settings_for_plugin(callback, failcallback) {
269
+ save_settings(
270
+ function(response){
271
+ if (response && response.includes( 'settings_saved' )) {
272
+ console.log( response );
273
+ callback( response );
274
+ } else {
275
+ console.log( 'Fail response on save_settings_and_sync' );
276
+ failcallback( response );
277
+ }
278
+ },
279
+ function(errorResponse){
280
+ console.log( 'Ajax error while saving settings:' + JSON.stringify( errorResponse ) );
281
+ failcallback( errorResponse );
282
+ }
283
+ );
284
  }
285
 
286
  // see comments in save_settings function above
287
  function save_settings_and_sync(message) {
288
+ if ('api_key' in settings) {
289
+ save_settings(
290
+ function(response){
291
+ if (response && response.includes( 'settings_saved' )) {
292
+ console.log( response );
293
+ // Final acks
294
+ window.sendToFacebook( 'ack set pixel', message.params );
295
+ window.sendToFacebook( 'ack set page access token', message.params );
296
+ window.sendToFacebook( 'ack set merchant settings', message.params );
297
+ sync_all_products( true );
298
+ } else {
299
+ window.sendToFacebook( 'fail save_settings', response );
300
+ console.log( 'Fail response on save_settings_and_sync' );
301
+ }
302
+ },
303
+ function(errorResponse){
304
+ console.log( 'Ajax error while saving settings:' + JSON.stringify( errorResponse ) );
305
+ window.sendToFacebook( 'fail save_settings_ajax', JSON.stringify( errorResponse ) );
306
+ }
307
+ );
308
+ }
309
+ }
310
+
311
+ // Reset buttons to brand new setup state
312
  function reset_buttons(){
313
+ if (document.querySelector( '#settings' )) {
314
+ document.querySelector( '#settings' ).style.display = 'none';
315
+ }
316
+ if (document.querySelector( '#cta_button' )) {
317
+ var cta_element = document.querySelector( '#cta_button' );
318
+ cta_element.innerHTML = 'Get Started';
319
+ cta_element.style['font-size'] = '13px';
320
+ cta_element.style.width = '80px';
321
+ cta_element.href = '#';
322
+ cta_element.onclick = function() { facebookConfig(); };
323
+ }
324
+ if (document.querySelector( '#learnmore_button' )) {
325
+ document.querySelector( '#learnmore_button' ).style.display = 'none';
326
+ }
327
+ if (document.querySelector( '#setup_h1' )) {
328
+ document.querySelector( '#setup_h1' ).innerHTML =
329
+ 'Grow your business on Facebook';
330
+ }
331
+ if (document.querySelector( '#setup_l1' )) {
332
+ document.querySelector( '#setup_l1' ).innerHTML =
333
+ 'Easily install a tracking pixel';
334
+ }
335
+ if (document.querySelector( '#setup_l2' )) {
336
+ document.querySelector( '#setup_l2' ).innerHTML =
337
+ 'Upload your products and create a shop';
338
+ }
339
+ if (document.querySelector( '#setup_l3' )) {
340
+ document.querySelector( '#setup_l3' ).innerHTML =
341
+ 'Create dynamic ads with your products and pixel';
342
+ }
343
+ }
344
+
345
+ // Remove reset/settings buttons during product sync
346
  function sync_in_progress(){
347
+ if (document.querySelector( '#settings' )) {
348
+ document.querySelector( '#settings' ).style.display = '';
349
+ }
350
+ if (document.querySelector( '#connection_status' )) {
351
+ document.querySelector( '#connection_status' ).style.display = '';
352
+ }
353
+ if (document.querySelector( '#sync_complete' )) {
354
+ document.querySelector( '#sync_complete' ).style.display = 'none';
355
+ }
356
+ // Get rid of all the buttons
357
+ if (document.querySelector( '#setting_button' )) {
358
+ document.querySelector( '#setting_button' ).style['pointer-events'] = 'none';
359
+ }
360
+ if (document.querySelector( '#resync_products' )) {
361
+ document.querySelector( '#resync_products' ).style['pointer-events'] = 'none';
362
+ }
363
+ if (document.querySelector( '#test_product_sync' )) {
364
+ document.querySelector( '#test_product_sync' ).style.display = 'none';
365
+ }
366
+ // Set a product sync status
367
+ if (document.querySelector( '#sync_progress' )) {
368
+ document.querySelector( '#sync_progress' ).innerHTML =
369
+ 'Syncing... Keep this browser open <br/>' +
370
+ 'Until sync is complete<br/>' +
371
+ '<div class="loader"></div>';
372
+ }
373
  }
374
 
375
  function sync_not_in_progress(){
376
+ // Reset to pre-setup state.
377
+ if (document.querySelector( '#cta_button' )) {
378
+ var cta_element = document.querySelector( '#cta_button' );
379
+ cta_element.innerHTML = 'Create Ad';
380
+ cta_element.style['font-size'] = '12px';
381
+ cta_element.style.width = '60px';
382
+ if (window.facebookAdsToolboxConfig.diaSettingId) {
383
+ cta_element.onclick = function() {
384
+ window.open(
385
+ 'https://www.facebook.com/ads/dia/redirect/?settings_id=' +
386
+ window.facebookAdsToolboxConfig.diaSettingId + '&version=2' +
387
+ '&entry_point=admin_panel'
388
+ );
389
+ };
390
+ } else {
391
+ cta_element.style['pointer-events'] = 'none';
392
+ }
393
+ }
394
+ if (document.querySelector( '#learnmore_button' )) {
395
+ var learnmore_element = document.querySelector( '#learnmore_button' );
396
+ if (window.facebookAdsToolboxConfig.diaSettingId) {
397
+ learnmore_element.style.display = '';
398
+ }
399
+ }
400
+ if (document.querySelector( '#setup_h1' )) {
401
+ document.querySelector( '#setup_h1' ).innerHTML =
402
+ 'Reach the right people and sell more products';
403
+ }
404
+ if (document.querySelector( '#setup_l1' )) {
405
+ document.querySelector( '#setup_l1' ).innerHTML =
406
+ 'Create an ad in a few steps';
407
+ }
408
+ if (document.querySelector( '#setup_l2' )) {
409
+ document.querySelector( '#setup_l2' ).innerHTML =
410
+ 'Use built-in best practice for online sales';
411
+ }
412
+ if (document.querySelector( '#setup_l3' )) {
413
+ document.querySelector( '#setup_l3' ).innerHTML =
414
+ 'Get reporting on sales and revenue';
415
+ }
416
+ if (document.querySelector( '#settings' )) {
417
+ document.querySelector( '#settings' ).style.display = '';
418
+ }
419
+ // Enable buttons.
420
+ if (document.querySelector( '#setting_button' )) {
421
+ document.querySelector( '#setting_button' ).style['pointer-events'] = 'auto';
422
+ }
423
+ if (document.querySelector( '#resync_products' )) {
424
+ document.querySelector( '#resync_products' ).style ['pointer-events'] = 'auto';
425
+ }
426
+ // Remove sync progress.
427
+ if (document.querySelector( '#sync_progress' )) {
428
+ document.querySelector( '#sync_progress' ).innerHTML = '';
429
+ }
430
  }
431
 
432
  function not_connected(){
433
+ if (document.querySelector( '#connection_status' )) {
434
+ document.querySelector( '#connection_status' ).style.display = 'none';
435
+ }
436
+
437
+ if (document.querySelector( '#setting_button' )) {
438
+ document.querySelector( '#setting_button' ).style['pointer-events'] = 'auto';
439
+ }
440
+ if (document.querySelector( '#resync_products' )) {
441
+ document.querySelector( '#resync_products' ).style['pointer-events'] = 'none';
442
+ }
443
+ if (document.querySelector( '#sync_complete' )) {
444
+ document.querySelector( '#sync_complete' ).style.display = 'none';
445
+ }
446
+ if (document.querySelector( '#sync_progress' )) {
447
+ document.querySelector( '#sync_progress' ).innerHTML = '';
448
+ }
449
  }
450
 
451
  function addAnEventListener(obj,evt,func) {
452
+ if ('addEventListener' in obj) {
453
+ obj.addEventListener( evt,func, false );
454
+ } else if ('attachEvent' in obj) {// IE
455
+ obj.attachEvent( 'on' + evt,func );
456
+ }
457
  }
458
 
459
  function setMerchantSettings(message) {
460
+ if ( ! message.params.setting_id) {
461
+ console.error( 'Facebook Extension Error: got no setting_id', message.params );
462
+ window.sendToFacebook( 'fail set merchant settings', message.params );
463
+ return;
464
+ }
465
+ if (get_ems_id_box()) {
466
+ get_ems_id_box().value = message.params.setting_id;
467
+ }
468
 
469
+ settings.external_merchant_settings_id = message.params.setting_id;
470
 
471
+ // Immediately set in case button is clicked again
472
+ window.facebookAdsToolboxConfig.diaSettingId = message.params.setting_id;
473
+ // Ack merchant settings happens after settings are saved
474
  }
475
 
476
  function setCatalog(message) {
477
+ if ( ! message.params.catalog_id) {
478
+ console.error( 'Facebook Extension Error: got no catalog_id', message.params );
479
+ window.sendToFacebook( 'fail set catalog', message.params );
480
+ return;
481
+ }
482
+ if (get_api_key_box()) {
483
+ get_product_catalog_id_box().value = message.params.catalog_id;
484
+ }
485
 
486
+ settings.product_catalog_id = message.params.catalog_id;
487
 
488
+ window.sendToFacebook( 'ack set catalog', message.params );
489
  }
490
 
491
 
492
  function setPixel(message) {
493
+ if ( ! message.params.pixel_id) {
494
+ console.error( 'Facebook Ads Extension Error: got no pixel_id', message.params );
495
+ window.sendToFacebook( 'fail set pixel', message.params );
496
+ return;
497
+ }
498
+ if (get_pixel_id_box()) {
499
+ get_pixel_id_box().value = message.params.pixel_id;
500
+ }
501
+
502
+ settings.pixel_id = message.params.pixel_id;
503
+ pixel_settings.pixel_id = settings.pixel_id;
504
+ if (message.params.pixel_use_pii !== undefined) {
505
+ if (get_pixel_use_pii_id_box()) {
506
+ // !! will explicitly convert truthy/falsy values to a boolean
507
+ get_pixel_use_pii_id_box().checked = ! ! message.params.pixel_use_pii;
508
+ }
509
+ settings.pixel_use_pii = message.params.pixel_use_pii;
510
+ pixel_settings.pixel_use_pii = settings.pixel_use_pii;
511
+ }
512
+
513
+ // We need this to support changing the pixel id after setup.
514
+ save_settings(
515
+ function(response){
516
+ if (response && response.includes( 'settings_saved' )) {
517
+ window.sendToFacebook( 'ack set pixel', message.params );
518
+ } //may not get settings_saved if we try to save pixel before an API key
519
+ },
520
+ function(errorResponse){
521
+ console.log( errorResponse );
522
+ window.sendToFacebook( 'fail set pixel', errorResponse );
523
+ },
524
+ pixel_settings
525
+ );
526
  }
527
 
528
  function genFeed(message) {
529
+ // no-op
530
  }
531
 
532
  function setAccessTokenAndPageId(message) {
533
+ if ( ! message.params.page_token) {
534
+ console.error(
535
+ 'Facebook Ads Extension Error: got no page_token',
536
+ message.params
537
+ );
538
+ window.sendToFacebook( 'fail set page access token', message.params );
539
+ return;
540
+ }
541
+ /*
542
+ Set page_token here
543
+ */
544
+
545
+ if (get_api_key_box()) {
546
+ get_api_key_box().value = message.params.page_token;
547
+ }
548
+
549
+ if (get_page_id_box()) {
550
+ get_page_id_box().value = message.params.page_id;
551
+ }
552
+
553
+ settings.api_key = message.params.page_token;
554
+ settings.page_id = message.params.page_id;
555
+ // Ack token in "save_settings_and_sync" for final ack
556
+
557
+ window.facebookAdsToolboxConfig.tokenExpired = false;
558
+ if (document.querySelector( '#token_text' )) {
559
+ document.querySelector( '#token_text' ).innerHTML =
560
+ ` < strong > Your API key has been updated.<br / >
561
+ Please refresh the page.< / strong > `;
562
+ }
563
  }
564
 
565
  function setMsgerChatSetup(data) {
566
+ if (data.hasOwnProperty( 'is_messenger_chat_plugin_enabled' )) {
567
+ settings.is_messenger_chat_plugin_enabled =
568
+ data.is_messenger_chat_plugin_enabled;
569
+ }
570
+ if (data.hasOwnProperty( 'facebook_jssdk_version' )) {
571
+ settings.facebook_jssdk_version =
572
+ data.facebook_jssdk_version;
573
+ }
574
+ if (data.hasOwnProperty( 'page_id' )) {
575
+ settings.fb_page_id = data.page_id;
576
+ }
577
+
578
+ if (data.hasOwnProperty( 'customization' )) {
579
+ var customization = data.customization;
580
+
581
+ if (customization.hasOwnProperty( 'greetingTextCode' )) {
582
+ settings.msger_chat_customization_greeting_text_code =
583
+ customization.greetingTextCode;
584
+ }
585
+ if (customization.hasOwnProperty( 'locale' )) {
586
+ settings.msger_chat_customization_locale =
587
+ customization.locale;
588
+ }
589
+ if (customization.hasOwnProperty( 'themeColorCode' )) {
590
+ settings.msger_chat_customization_theme_color_code =
591
+ customization.themeColorCode;
592
+ }
593
+ }
594
  }
595
 
596
  function iFrameListener(event) {
597
+ // Fix for web.facebook.com
598
+ const origin = event.origin || event.originalEvent.origin;
599
+ if (origin != window.facebookAdsToolboxConfig.popupOrigin &&
600
+ urlFromSameDomain( origin, window.facebookAdsToolboxConfig.popupOrigin )) {
601
+ window.facebookAdsToolboxConfig.popupOrigin = origin;
602
+ }
603
+
604
+ switch (event.data.type) {
605
+ case 'reset':
606
+ delete_all_settings(
607
+ function(res){
608
+ if (res && event.data.params) {
609
+ if (res === 'Settings Deleted') {
610
+ window.sendToFacebook( 'ack reset', event.data.params );
611
+ } else {
612
+ console.log( res );
613
+ alert( res );
614
+ }
615
+ } else {
616
+ console.log( "Got no response from delete_all_settings" );
617
+ }
618
+ },
619
+ function(err){
620
+ console.error( err );
621
+ }
622
+ );
623
+ break;
624
+ case 'get dia settings':
625
+ window.sendToFacebook( 'dia settings', window.diaConfig );
626
+ break;
627
+ case 'set merchant settings':
628
+ setMerchantSettings( event.data );
629
+ break;
630
+ case 'set catalog':
631
+ setCatalog( event.data );
632
+ break;
633
+ case 'set pixel':
634
+ setPixel( event.data );
635
+ break;
636
+ case 'gen feed':
637
+ genFeed();
638
+ break;
639
+ case 'set page access token':
640
+ // Should be last message received
641
+ setAccessTokenAndPageId( event.data );
642
+ save_settings_and_sync( event.data );
643
+ break;
644
+ case 'set msger chat':
645
+ setMsgerChatSetup( event.data.params );
646
+ save_settings_for_plugin(
647
+ function(response) {
648
+ window.sendToFacebook( 'ack msger chat', event.data );
649
+ },
650
+ function(response) {
651
+ window.sendToFacebook( 'fail ack msger chat', event.data );
652
+ }
653
+ );
654
+ break;
655
+ }
656
+ }
657
+
658
+ addAnEventListener( window,'message',iFrameListener );
659
 
660
  function urlFromSameDomain(url1, url2) {
661
+ if ( ! url1.startsWith( 'http' ) || ! url2.startsWith( 'http' )) {
662
+ return false;
663
+ }
664
+ var u1 = parseURL( url1 );
665
+ var u2 = parseURL( url2 );
666
+ var u1host = u1.host.replace( /^\w+\./, 'www.' );
667
+ var u2host = u2.host.replace( /^\w+\./, 'www.' );
668
+ return u1.protocol === u2.protocol && u1host === u2host;
669
  }
670
 
671
  function parseURL(url) {
672
+ var parser = document.createElement( 'a' );
673
+ parser.href = url;
674
+ return parser;
675
  }
676
 
677
  // Only do pings for supporting older (pre 1.8) setups.
678
  window.fb_pings =
679
  (window.facebookAdsToolboxConfig.feed.hasClientSideFeedUpload) ?
680
  null :
681
+ setInterval(
682
+ function(){
683
+ console.log( "Pinging queue..." );
684
+ check_queues();
685
+ },
686
+ 10000
687
+ );
688
 
689
  function ping_feed_status_queue(count = 0) {
690
+ window.fb_feed_pings = setInterval(
691
+ function() {
692
+ console.log( 'Pinging feed uploading queue...' );
693
+ check_feed_upload_queue( count );
694
+ },
695
+ 30000 * (1 << count)
696
+ );
697
+ }
698
+
699
+ function product_sync_complete(sync_progress_element) {
700
+ sync_not_in_progress();
701
+ if (document.querySelector( '#sync_complete' )) {
702
+ document.querySelector( '#sync_complete' ).style.display = '';
703
+ }
704
+ if (sync_progress_element) {
705
+ sync_progress_element.innerHTML = '';
706
+ }
707
+ clearInterval( window.fb_pings );
708
+ }
709
+
710
+ function check_queues() {
711
+ ajax(
712
+ 'ajax_fb_background_check_queue',
713
+ {
714
+ "request_time": new Date().getTime(),
715
+ "_ajax_nonce": wc_facebook_settings_jsx.nonce,
716
+ },
717
+ function( response ) {
718
+ if ( window.feed_upload ) {
719
+ clearInterval( window.fb_pings );
720
+ return;
721
+ }
722
+ var sync_progress_element = document.querySelector( '#sync_progress' );
723
+ var res = parse_response_check_connection( response );
724
+ if ( !res ) {
725
+ if ( fb_sync_no_response_count++ > 5 ) {
726
+ clearInterval( window.fb_pings );
727
+ }
728
+ return;
729
+ }
730
+ fb_sync_no_response_count = 0;
731
+
732
+ if ( res ) {
733
+ if ( !res.background ) {
734
+ console.log( "No background sync found, disabling pings" );
735
+ clearInterval( window.fb_pings );
736
+ }
737
+
738
+ var processing = !!res.processing; // explicit boolean conversion
739
+ var remaining = res.remaining;
740
+ if ( processing ) {
741
+ if ( sync_progress_element ) {
742
+ sync_progress_element.innerHTML =
743
+ '<strong>Progress:</strong> ' + remaining + ' item' +
744
+ ( remaining > 1 ? 's' : '' ) + ' remaining.';
745
+ }
746
+ if ( remaining === 0 ) {
747
+ product_sync_complete( sync_progress_element );
748
+ }
749
+ } else {
750
+ // Not processing, none remaining. Either long complete, or just completed
751
+ if ( window.fb_sync_start_time && res.request_time ) {
752
+ var request_time = new Date( parseInt( res.request_time ) );
753
+ if ( window.fb_sync_start_time > request_time ) {
754
+ // Old ping, do nothing.
755
+ console.log( "OLD PING" );
756
+ return;
757
+ }
758
+ }
759
+
760
+ if ( remaining === 0 ) {
761
+ product_sync_complete( sync_progress_element );
762
+ }
763
+ }
764
+ }
765
+ }
766
+ );
767
  }
768
 
769
  function parse_response_check_connection(res) {
770
+ if (res) {
771
+ console.log( res );
772
+ var response = res.substring( res.indexOf( "{" ) ); // Trim leading extra chars (rnrnr)
773
+ response = JSON.parse( response );
774
+ if ( ! response.connected && ! window.fb_connected) {
775
+ not_connected();
776
+ return null;
777
+ }
778
+ return response;
779
+ }
780
+ return null;
781
  }
782
 
783
  function check_feed_upload_queue(check_num) {
784
+ ajax(
785
+ 'ajax_check_feed_upload_status',
786
+ {
787
+ "_ajax_nonce": wc_facebook_settings_jsx.nonce,
788
+ },
789
+ function(response) {
790
+ var sync_progress_element = document.querySelector( '#sync_progress' );
791
+ var res = parse_response_check_connection( response );
792
+ clearInterval( window.fb_feed_pings );
793
+ if (res) {
794
+ var status = res.status;
795
+ switch (status) {
796
+ case 'complete':
797
+ window.feed_upload = false;
798
+ if (window.is_test) {
799
+ display_test_result();
800
+ } else {
801
+ product_sync_complete( sync_progress_element );
802
+ }
803
+ break;
804
+ case 'in progress':
805
+ if (sync_progress_element) {
806
+ sync_progress_element.innerHTML =
807
+ 'Syncing... Keep this browser open <br/>' +
808
+ 'Until sync is complete<br/>';
809
+ }
810
+ ping_feed_status_queue( check_num + 1 );
811
+ break;
812
+ default:
813
+ sync_progress_element.innerHTML =
814
+ '<strong>Something wrong when uploading, please try again.</strong>';
815
+ window.feed_upload = false;
816
+ if (window.is_test) {
817
+ display_test_result();
818
+ }
819
+ }
820
+ }
821
+ }
822
+ );
823
  }
824
 
825
  function display_test_result() {
826
+ ajax(
827
+ 'ajax_display_test_result',
828
+ {
829
+ "_ajax_nonce": wc_facebook_settings_jsx.nonce
830
+ },
831
+ function(response) {
832
+ var sync_complete_element = document.querySelector( '#sync_complete' );
833
+ var sync_progress_element = document.querySelector( '#sync_progress' );
834
+ var res = parse_response_check_connection( response );
835
+ if (res) {
836
+ var status = res.pass;
837
+ switch (status) {
838
+ case 'true':
839
+ sync_not_in_progress();
840
+ if (sync_complete_element) {
841
+ sync_complete_element.style.display = '';
842
+ sync_complete_element.innerHTML =
843
+ '<strong>Status: </strong>Test Pass.';
844
+ }
845
+ if (sync_progress_element) {
846
+ sync_progress_element.innerHTML = '';
847
+ }
848
+ window.is_test = false;
849
+ break;
850
+ case 'in progress':
851
+ if (sync_progress_element) {
852
+ sync_progress_element.innerHTML =
853
+ '<strong>Integration test in progress...</strong>';
854
+ }
855
+ ping_feed_status_queue();
856
+ break;
857
+ default:
858
+ window.debug_info = res.debug_info + '<br/>' + res.stack_trace;
859
+ if (sync_complete_element) {
860
+ sync_complete_element.style.display = '';
861
+ sync_complete_element.innerHTML =
862
+ '<strong>Status: </strong>Test Fail.';
863
+ }
864
+ if (sync_progress_element) {
865
+ sync_progress_element.innerHTML = '';
866
+ }
867
+ if (document.querySelector( '#debug_info' )) {
868
+ document.querySelector( '#debug_info' ).style.display = '';
869
+ }
870
+ window.is_test = false;
871
+ }
872
+ }
873
+ }
874
+ );
875
  }
876
 
877
  function show_debug_info() {
878
+ var stack_trace_element = document.querySelector( '#stack_trace' );
879
+ if (stack_trace_element) {
880
+ stack_trace_element.innerHTML = window.debug_info;
881
+ }
882
+ if (document.querySelector( '#debug_info' )) {
883
+ document.querySelector( '#debug_info' ).style.display = 'none';
884
+ }
885
+ window.debug_info = '';
886
  }
887
 
888
  function fbe_init_nux_messages() {
889
+ var jQuery = window.jQuery;
890
+ jQuery(
891
+ function() {
892
+ jQuery.each(
893
+ jQuery( '.nux-message' ),
894
+ function(_index, nux_msg) {
895
+ var nux_msg_elem = jQuery( nux_msg );
896
+ var targetid = nux_msg_elem.data( 'target' );
897
+ var target_elem = jQuery( '#' + targetid );
898
+ var t_pos = target_elem.position();
899
+ var t_half_height = target_elem.height() / 2;
900
+ var t_width = target_elem.outerWidth();
901
+ nux_msg_elem.css(
902
+ {
903
+ 'top': '' + Math.ceil( t_pos.top + t_half_height ) + 'px',
904
+ 'left': '' + Math.ceil( t_pos.left + t_width ) + 'px',
905
+ 'display': 'block'
906
+ }
907
+ );
908
+ jQuery( '.nux-message-close-btn', nux_msg_elem ).click(
909
+ function() {
910
+ jQuery( nux_msg ).hide();
911
+ }
912
+ );
913
+ }
914
+ );
915
+ }
916
+ );
917
  }
918
 
919
  function saveAutoSyncSchedule() {
920
+ var isChecked = document.getElementsByClassName( 'autosyncCheck' )[0].checked;
921
+ var timebox = document.getElementsByClassName( 'autosyncTime' )[0];
922
+ var button = document.getElementsByClassName( 'autosyncSaveButton' )[0];
923
+ var saved = document.getElementsByClassName( 'autosyncSavedNotice' )[0];
924
+
925
+ if ( ! isChecked) {
926
+ timebox.setAttribute( 'disabled', true );
927
+ } else {
928
+ timebox.removeAttribute( 'disabled' );
929
+ saved.style.transition = '';
930
+ saved.style.opacity = 1;
931
+ // Fade out the small 'Saved' after 3 seconds.
932
+ setTimeout(
933
+ function() {
934
+ saved.style.opacity = 0;
935
+ saved.style.transition = 'opacity 5s';}
936
+ ,
937
+ 3000
938
+ );
939
+ }
940
+
941
+ ajax( 'ajax_schedule_force_resync',
942
+ {
943
+ "enabled": isChecked ? 1 : 0,
944
+ "time" : timebox.value,
945
+ "_ajax_nonce": wc_facebook_settings_jsx.nonce,
946
+ }
947
+ );
948
  }
949
 
950
  function onSetDisableSyncOnDevEnvironment() {
951
+ var isChecked = document.getElementsByClassName( 'disableOnDevEnvironment' )[0].checked;
952
+ ajax(
953
+ 'ajax_update_fb_option',
954
+ {
955
+ "option": "fb_disable_sync_on_dev_environment",
956
+ "option_value": isChecked ? 1 : 0,
957
+ "_ajax_nonce": wc_facebook_settings_jsx.nonce,
958
+ },
959
+ null,
960
+ function onSetDisableSyncOnDevEnvironmentFailCallback(error) {
961
+ document.getElementsByClassName(
962
+ 'onSetDisableSyncOnDevEnvironment'
963
+ )[0].checked = ! isChecked;
964
+ console.log( 'Failed to disable sync on dev environment' );
965
+ }
966
+ );
967
  }
968
 
969
  function syncShortDescription() {
970
+ var isChecked = document.getElementsByClassName( 'syncShortDescription' )[0].checked;
971
+ ajax(
972
+ 'ajax_update_fb_option',
973
+ {
974
+ "option": "fb_sync_short_description",
975
+ "option_value": isChecked ? 1 : 0,
976
+ "_ajax_nonce": wc_facebook_settings_jsx.nonce,
977
+ },
978
+ null,
979
+ function syncShortDescriptionFailCallback(error) {
980
+ document.getElementsByClassName( 'syncShortDescription' )[0].checked = ! isChecked;
981
+ console.log( 'Failed to sync Short Description' );
982
+ }
983
+ );
984
  }
changelog.txt CHANGED
@@ -1,4 +1,9 @@
1
  *** Facebook for WooCommerce Changelog ***
 
 
 
 
 
2
  2019-06-20 - Version 1.9.14
3
  * Revisit CSRF security issue
4
  * Remove rest controller which is not used
1
  *** Facebook for WooCommerce Changelog ***
2
+ 2019-06-27 - Version 1.9.15
3
+ * CSRF handling for Ajax calls like ajax_woo_infobanner_post_click, ajax_woo_infobanner_post_xout, ajax_fb_toggle_visibility
4
+ * use phpcs to adhere to WP coding standards
5
+ * Minor UI changes on the iFrame
6
+
7
  2019-06-20 - Version 1.9.14
8
  * Revisit CSRF security issue
9
  * Remove rest controller which is not used
facebook-commerce-events-tracker.php CHANGED
@@ -8,379 +8,426 @@
8
  * @package FacebookCommerce
9
  */
10
 
11
- if (!class_exists('WC_Facebookcommerce_EventsTracker')) :
12
-
13
- if (!class_exists('WC_Facebookcommerce_Utils')) {
14
- include_once 'includes/fbutils.php';
15
- }
16
-
17
- if (!class_exists('WC_Facebookcommerce_Pixel')) {
18
- include_once 'facebook-commerce-pixel-event.php';
19
- }
20
-
21
- class WC_Facebookcommerce_EventsTracker {
22
- private $pixel;
23
- private static $isEnabled = true;
24
- const FB_PRIORITY_HIGH = 2;
25
- const FB_PRIORITY_LOW = 11;
26
-
27
- public function __construct($user_info) {
28
- $this->pixel = new WC_Facebookcommerce_Pixel($user_info);
29
-
30
- add_action('wp_head', array($this, 'apply_filters'));
31
-
32
- // Pixel Tracking Hooks
33
- add_action('wp_head',
34
- array($this, 'inject_base_pixel'));
35
- add_action('wp_footer',
36
- array($this, 'inject_base_pixel_noscript'));
37
- add_action('woocommerce_after_single_product',
38
- array($this, 'inject_view_content_event'), self::FB_PRIORITY_HIGH);
39
- add_action('woocommerce_after_shop_loop',
40
- array($this, 'inject_view_category_event'));
41
- add_action('pre_get_posts',
42
- array($this, 'inject_search_event'));
43
- add_action('woocommerce_after_cart',
44
- array($this, 'inject_add_to_cart_redirect_event'));
45
- add_action('woocommerce_add_to_cart',
46
- array($this, 'inject_add_to_cart_event'), self::FB_PRIORITY_HIGH);
47
- add_action('wc_ajax_fb_inject_add_to_cart_event',
48
- array($this, 'inject_ajax_add_to_cart_event' ), self::FB_PRIORITY_HIGH);
49
- add_action('woocommerce_after_checkout_form',
50
- array($this, 'inject_initiate_checkout_event'));
51
- add_action('woocommerce_thankyou',
52
- array($this, 'inject_gateway_purchase_event'), self::FB_PRIORITY_HIGH);
53
- add_action('woocommerce_payment_complete',
54
- array($this, 'inject_purchase_event'), self::FB_PRIORITY_HIGH);
55
- add_action('wpcf7_contact_form',
56
- array($this, 'inject_lead_event_hook'), self::FB_PRIORITY_LOW);
57
-
58
- }
59
-
60
- public function apply_filters() {
61
- self::$isEnabled = apply_filters(
62
- "facebook_for_woocommerce_integration_pixel_enabled",
63
- self::$isEnabled);
64
- }
65
-
66
- /**
67
- * Base pixel code to be injected on page head. Because of this, it's better
68
- * to echo the return value than using
69
- * WC_Facebookcommerce_Utils::wc_enqueue_js() in this case
70
- */
71
- public function inject_base_pixel() {
72
- if (self::$isEnabled) {
73
- echo $this->pixel->pixel_base_code();
74
- }
75
- }
76
-
77
- /**
78
- * Base pixel noscript to be injected on page body. This is to avoid W3
79
- * validation error.
80
- */
81
- public function inject_base_pixel_noscript() {
82
- if (self::$isEnabled) {
83
- echo $this->pixel->pixel_base_code_noscript();
84
- }
85
- }
86
-
87
- /**
88
- * Triggers ViewCategory for product category listings
89
- */
90
- public function inject_view_category_event() {
91
- global $wp_query;
92
- if (!self::$isEnabled) {
93
- return;
94
- }
95
-
96
- $products = array_values(array_map(function($item) {
97
- return wc_get_product($item->ID);
98
- },
99
- $wp_query->posts));
100
-
101
- // if any product is a variant, fire the pixel with
102
- // content_type: product_group
103
- $content_type = 'product';
104
- $product_ids = array();
105
- foreach ($products as $product) {
106
- if (!$product) {
107
- continue;
108
- }
109
- $product_ids = array_merge(
110
- $product_ids,
111
- WC_Facebookcommerce_Utils::get_fb_content_ids($product));
112
- if (WC_Facebookcommerce_Utils::is_variable_type($product->get_type())) {
113
- $content_type = 'product_group';
114
- }
115
- }
116
-
117
- $categories =
118
- WC_Facebookcommerce_Utils::get_product_categories(get_the_ID());
119
-
120
- $this->pixel->inject_event(
121
- 'ViewCategory',
122
- array(
123
- 'content_name' => $categories['name'],
124
- 'content_category' => $categories['categories'],
125
- 'content_ids' => json_encode(array_slice($product_ids, 0, 10)),
126
- 'content_type' => $content_type
127
- ),
128
- 'trackCustom');
129
- }
130
-
131
- /**
132
- * Triggers Search for result pages (deduped)
133
- */
134
- public function inject_search_event() {
135
- if (!self::$isEnabled) {
136
- return;
137
- }
138
-
139
- if (!is_admin() && is_search() && get_search_query() !== '') {
140
- if ($this->pixel->check_last_event('Search')) {
141
- return;
142
- }
143
-
144
- if (WC_Facebookcommerce_Utils::isWoocommerceIntegration()) {
145
- $this->actually_inject_search_event();
146
- } else {
147
- add_action('wp_head', array($this, 'actually_inject_search_event'), 11);
148
- }
149
- }
150
- }
151
-
152
- /**
153
- * Triggers Search for result pages
154
- */
155
- public function actually_inject_search_event() {
156
- if (!self::$isEnabled) {
157
- return;
158
- }
159
-
160
- $this->pixel->inject_event(
161
- 'Search',
162
- array(
163
- 'search_string' => get_search_query()
164
- ));
165
- }
166
-
167
- /**
168
- * Helper function to iterate through a cart and gather all content ids
169
- */
170
- private function get_content_ids_from_cart($cart) {
171
- $product_ids = array();
172
- foreach ($cart as $item) {
173
- $product_ids = array_merge(
174
- $product_ids,
175
- WC_Facebookcommerce_Utils::get_fb_content_ids($item['data']));
176
- }
177
- return $product_ids;
178
- }
179
-
180
- /**
181
- * Triggers ViewContent product pages
182
- */
183
- public function inject_view_content_event() {
184
- if (!self::$isEnabled) {
185
- return;
186
- }
187
- global $post;
188
- $product = wc_get_product($post->ID);
189
- $content_type = 'product_group';
190
- if (!$product) {
191
- return;
192
- }
193
-
194
- // if product is a variant, fire the pixel with content_type: product_group
195
- if (WC_Facebookcommerce_Utils::is_variation_type($product->get_type())) {
196
- $content_type = 'product';
197
- }
198
-
199
- $content_ids = WC_Facebookcommerce_Utils::get_fb_content_ids($product);
200
- $this->pixel->inject_event(
201
- 'ViewContent',
202
- array(
203
- 'content_name' => $product->get_title(),
204
- 'content_ids' => json_encode($content_ids),
205
- 'content_type' => $content_type,
206
- 'value' => $product->get_price(),
207
- 'currency' => get_woocommerce_currency()
208
- ));
209
- }
210
-
211
- /**
212
- * Triggers AddToCart for cart page and add_to_cart button clicks
213
- */
214
- public function inject_add_to_cart_event() {
215
- if (!self::$isEnabled) {
216
- return;
217
- }
218
-
219
- $product_ids = $this->get_content_ids_from_cart(WC()->cart->get_cart());
220
-
221
- $this->pixel->inject_event(
222
- 'AddToCart',
223
- array(
224
- 'content_ids' => json_encode($product_ids),
225
- 'content_type' => 'product',
226
- 'value' => WC()->cart->total,
227
- 'currency' => get_woocommerce_currency()
228
- ));
229
- }
230
-
231
- /**
232
- * Triggered by add_to_cart jquery trigger
233
- */
234
- public function inject_ajax_add_to_cart_event() {
235
- if (!self::$isEnabled) {
236
- return;
237
- }
238
-
239
- ob_start();
240
-
241
- echo '<script>';
242
-
243
- $product_ids = $this->get_content_ids_from_cart(WC()->cart->get_cart());
244
-
245
- echo $this->pixel->build_event(
246
- 'AddToCart',
247
- array(
248
- 'content_ids' => json_encode($product_ids),
249
- 'content_type' => 'product',
250
- 'value' => WC()->cart->total,
251
- 'currency' => get_woocommerce_currency()
252
- ));
253
- echo '</script>';
254
-
255
- $pixel = ob_get_clean();
256
-
257
- wp_send_json($pixel);
258
- }
259
-
260
- /**
261
- * Trigger AddToCart for cart page and woocommerce_after_cart hook.
262
- * When set 'redirect to cart', ajax call for button click and
263
- * woocommerce_add_to_cart will be skipped.
264
- */
265
- public function inject_add_to_cart_redirect_event() {
266
- if (!self::$isEnabled) {
267
- return;
268
- }
269
- $redirect_checked = get_option('woocommerce_cart_redirect_after_add', 'no');
270
- if ($redirect_checked == 'yes') {
271
- $this->inject_add_to_cart_event();
272
- }
273
- }
274
-
275
- /**
276
- * Triggers InitiateCheckout for checkout page
277
- */
278
- public function inject_initiate_checkout_event() {
279
- if (!self::$isEnabled ||
280
- $this->pixel->check_last_event('InitiateCheckout')) {
281
- return;
282
- }
283
-
284
- $product_ids = $this->get_content_ids_from_cart(WC()->cart->get_cart());
285
-
286
- $this->pixel->inject_event(
287
- 'InitiateCheckout',
288
- array(
289
- 'num_items' => WC()->cart->get_cart_contents_count(),
290
- 'content_ids' => json_encode($product_ids),
291
- 'content_type' => 'product',
292
- 'value' => WC()->cart->total,
293
- 'currency' => get_woocommerce_currency()
294
- ));
295
- }
296
-
297
- /**
298
- * Triggers Purchase for payment transaction complete and for the thank you
299
- * page in cases of delayed payment.
300
- */
301
- public function inject_purchase_event($order_id) {
302
- if (!self::$isEnabled ||
303
- $this->pixel->check_last_event('Purchase')) {
304
- return;
305
- }
306
-
307
- $this->inject_subscribe_event($order_id);
308
-
309
- $order = new WC_Order($order_id);
310
- $content_type = 'product';
311
- $product_ids = array();
312
- foreach ($order->get_items() as $item) {
313
- $product = wc_get_product($item['product_id']);
314
- $product_ids = array_merge(
315
- $product_ids,
316
- WC_Facebookcommerce_Utils::get_fb_content_ids($product));
317
- if (WC_Facebookcommerce_Utils::is_variable_type($product->get_type())) {
318
- $content_type = 'product_group';
319
- }
320
- }
321
-
322
- $this->pixel->inject_event(
323
- 'Purchase',
324
- array(
325
- 'content_ids' => json_encode($product_ids),
326
- 'content_type' => $content_type,
327
- 'value' => $order->get_total(),
328
- 'currency' => get_woocommerce_currency()
329
- ));
330
- }
331
-
332
- /**
333
- * Triggers Subscribe for payment transaction complete of purchase with
334
- * subscription.
335
- */
336
- public function inject_subscribe_event($order_id) {
337
- if (!function_exists("wcs_get_subscriptions_for_order")) {
338
- return;
339
- }
340
-
341
- $subscription_ids = wcs_get_subscriptions_for_order($order_id);
342
- foreach ($subscription_ids as $subscription_id) {
343
- $subscription = new WC_Subscription($subscription_id);
344
- $this->pixel->inject_event(
345
- 'Subscribe',
346
- array(
347
- 'sign_up_fee' => $subscription->get_sign_up_fee(),
348
- 'value' => $subscription->get_total(),
349
- 'currency' => get_woocommerce_currency()
350
- ));
351
- }
352
- }
353
-
354
- /**
355
- * Triggers Purchase for thank you page for COD, BACS CHEQUE payment
356
- * which won't invoke woocommerce_payment_complete.
357
- */
358
- public function inject_gateway_purchase_event($order_id) {
359
- if (!self::$isEnabled ||
360
- $this->pixel->check_last_event('Purchase')) {
361
- return;
362
- }
363
-
364
- $order = new WC_Order($order_id);
365
- $payment = $order->get_payment_method();
366
- $this->inject_purchase_event($order_id);
367
- $this->inject_subscribe_event($order_id);
368
- }
369
-
370
- /** Contact Form 7 Support **/
371
- public function inject_lead_event_hook() {
372
- add_action('wp_footer', array($this, 'inject_lead_event'), 11);
373
- }
374
-
375
- public function inject_lead_event() {
376
- if (!is_admin()) {
377
- $this->pixel->inject_conditional_event(
378
- 'Lead',
379
- array(),
380
- 'wpcf7submit',
381
- '{ em: event.detail.inputs.filter(ele => ele.name.includes("email"))[0].value }');
382
- }
383
- }
384
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
385
 
386
  endif;
8
  * @package FacebookCommerce
9
  */
10
 
11
+ if ( ! class_exists( 'WC_Facebookcommerce_EventsTracker' ) ) :
12
+
13
+ if ( ! class_exists( 'WC_Facebookcommerce_Utils' ) ) {
14
+ include_once 'includes/fbutils.php';
15
+ }
16
+
17
+ if ( ! class_exists( 'WC_Facebookcommerce_Pixel' ) ) {
18
+ include_once 'facebook-commerce-pixel-event.php';
19
+ }
20
+
21
+ class WC_Facebookcommerce_EventsTracker {
22
+ private $pixel;
23
+ private static $isEnabled = true;
24
+ const FB_PRIORITY_HIGH = 2;
25
+ const FB_PRIORITY_LOW = 11;
26
+
27
+ public function __construct( $user_info ) {
28
+ $this->pixel = new WC_Facebookcommerce_Pixel( $user_info );
29
+
30
+ add_action( 'wp_head', array( $this, 'apply_filters' ) );
31
+
32
+ // Pixel Tracking Hooks
33
+ add_action(
34
+ 'wp_head',
35
+ array( $this, 'inject_base_pixel' )
36
+ );
37
+ add_action(
38
+ 'wp_footer',
39
+ array( $this, 'inject_base_pixel_noscript' )
40
+ );
41
+ add_action(
42
+ 'woocommerce_after_single_product',
43
+ array( $this, 'inject_view_content_event' ),
44
+ self::FB_PRIORITY_HIGH
45
+ );
46
+ add_action(
47
+ 'woocommerce_after_shop_loop',
48
+ array( $this, 'inject_view_category_event' )
49
+ );
50
+ add_action(
51
+ 'pre_get_posts',
52
+ array( $this, 'inject_search_event' )
53
+ );
54
+ add_action(
55
+ 'woocommerce_after_cart',
56
+ array( $this, 'inject_add_to_cart_redirect_event' )
57
+ );
58
+ add_action(
59
+ 'woocommerce_add_to_cart',
60
+ array( $this, 'inject_add_to_cart_event' ),
61
+ self::FB_PRIORITY_HIGH
62
+ );
63
+ add_action(
64
+ 'wc_ajax_fb_inject_add_to_cart_event',
65
+ array( $this, 'inject_ajax_add_to_cart_event' ),
66
+ self::FB_PRIORITY_HIGH
67
+ );
68
+ add_action(
69
+ 'woocommerce_after_checkout_form',
70
+ array( $this, 'inject_initiate_checkout_event' )
71
+ );
72
+ add_action(
73
+ 'woocommerce_thankyou',
74
+ array( $this, 'inject_gateway_purchase_event' ),
75
+ self::FB_PRIORITY_HIGH
76
+ );
77
+ add_action(
78
+ 'woocommerce_payment_complete',
79
+ array( $this, 'inject_purchase_event' ),
80
+ self::FB_PRIORITY_HIGH
81
+ );
82
+ add_action(
83
+ 'wpcf7_contact_form',
84
+ array( $this, 'inject_lead_event_hook' ),
85
+ self::FB_PRIORITY_LOW
86
+ );
87
+
88
+ }
89
+
90
+ public function apply_filters() {
91
+ self::$isEnabled = apply_filters(
92
+ 'facebook_for_woocommerce_integration_pixel_enabled',
93
+ self::$isEnabled
94
+ );
95
+ }
96
+
97
+ /**
98
+ * Base pixel code to be injected on page head. Because of this, it's better
99
+ * to echo the return value than using
100
+ * WC_Facebookcommerce_Utils::wc_enqueue_js() in this case
101
+ */
102
+ public function inject_base_pixel() {
103
+ if ( self::$isEnabled ) {
104
+ echo $this->pixel->pixel_base_code();
105
+ }
106
+ }
107
+
108
+ /**
109
+ * Base pixel noscript to be injected on page body. This is to avoid W3
110
+ * validation error.
111
+ */
112
+ public function inject_base_pixel_noscript() {
113
+ if ( self::$isEnabled ) {
114
+ echo $this->pixel->pixel_base_code_noscript();
115
+ }
116
+ }
117
+
118
+ /**
119
+ * Triggers ViewCategory for product category listings
120
+ */
121
+ public function inject_view_category_event() {
122
+ global $wp_query;
123
+ if ( ! self::$isEnabled ) {
124
+ return;
125
+ }
126
+
127
+ $products = array_values(
128
+ array_map(
129
+ function( $item ) {
130
+ return wc_get_product( $item->ID );
131
+ },
132
+ $wp_query->posts
133
+ )
134
+ );
135
+
136
+ // if any product is a variant, fire the pixel with
137
+ // content_type: product_group
138
+ $content_type = 'product';
139
+ $product_ids = array();
140
+ foreach ( $products as $product ) {
141
+ if ( ! $product ) {
142
+ continue;
143
+ }
144
+ $product_ids = array_merge(
145
+ $product_ids,
146
+ WC_Facebookcommerce_Utils::get_fb_content_ids( $product )
147
+ );
148
+ if ( WC_Facebookcommerce_Utils::is_variable_type( $product->get_type() ) ) {
149
+ $content_type = 'product_group';
150
+ }
151
+ }
152
+
153
+ $categories =
154
+ WC_Facebookcommerce_Utils::get_product_categories( get_the_ID() );
155
+
156
+ $this->pixel->inject_event(
157
+ 'ViewCategory',
158
+ array(
159
+ 'content_name' => $categories['name'],
160
+ 'content_category' => $categories['categories'],
161
+ 'content_ids' => json_encode( array_slice( $product_ids, 0, 10 ) ),
162
+ 'content_type' => $content_type,
163
+ ),
164
+ 'trackCustom'
165
+ );
166
+ }
167
+
168
+ /**
169
+ * Triggers Search for result pages (deduped)
170
+ */
171
+ public function inject_search_event() {
172
+ if ( ! self::$isEnabled ) {
173
+ return;
174
+ }
175
+
176
+ if ( ! is_admin() && is_search() && get_search_query() !== '' ) {
177
+ if ( $this->pixel->check_last_event( 'Search' ) ) {
178
+ return;
179
+ }
180
+
181
+ if ( WC_Facebookcommerce_Utils::isWoocommerceIntegration() ) {
182
+ $this->actually_inject_search_event();
183
+ } else {
184
+ add_action( 'wp_head', array( $this, 'actually_inject_search_event' ), 11 );
185
+ }
186
+ }
187
+ }
188
+
189
+ /**
190
+ * Triggers Search for result pages
191
+ */
192
+ public function actually_inject_search_event() {
193
+ if ( ! self::$isEnabled ) {
194
+ return;
195
+ }
196
+
197
+ $this->pixel->inject_event(
198
+ 'Search',
199
+ array(
200
+ 'search_string' => get_search_query(),
201
+ )
202
+ );
203
+ }
204
+
205
+ /**
206
+ * Helper function to iterate through a cart and gather all content ids
207
+ */
208
+ private function get_content_ids_from_cart( $cart ) {
209
+ $product_ids = array();
210
+ foreach ( $cart as $item ) {
211
+ $product_ids = array_merge(
212
+ $product_ids,
213
+ WC_Facebookcommerce_Utils::get_fb_content_ids( $item['data'] )
214
+ );
215
+ }
216
+ return $product_ids;
217
+ }
218
+
219
+ /**
220
+ * Triggers ViewContent product pages
221
+ */
222
+ public function inject_view_content_event() {
223
+ if ( ! self::$isEnabled ) {
224
+ return;
225
+ }
226
+ global $post;
227
+ $product = wc_get_product( $post->ID );
228
+ $content_type = 'product_group';
229
+ if ( ! $product ) {
230
+ return;
231
+ }
232
+
233
+ // if product is a variant, fire the pixel with content_type: product_group
234
+ if ( WC_Facebookcommerce_Utils::is_variation_type( $product->get_type() ) ) {
235
+ $content_type = 'product';
236
+ }
237
+
238
+ $content_ids = WC_Facebookcommerce_Utils::get_fb_content_ids( $product );
239
+ $this->pixel->inject_event(
240
+ 'ViewContent',
241
+ array(
242
+ 'content_name' => $product->get_title(),
243
+ 'content_ids' => json_encode( $content_ids ),
244
+ 'content_type' => $content_type,
245
+ 'value' => $product->get_price(),
246
+ 'currency' => get_woocommerce_currency(),
247
+ )
248
+ );
249
+ }
250
+
251
+ /**
252
+ * Triggers AddToCart for cart page and add_to_cart button clicks
253
+ */
254
+ public function inject_add_to_cart_event() {
255
+ if ( ! self::$isEnabled ) {
256
+ return;
257
+ }
258
+
259
+ $product_ids = $this->get_content_ids_from_cart( WC()->cart->get_cart() );
260
+
261
+ $this->pixel->inject_event(
262
+ 'AddToCart',
263
+ array(
264
+ 'content_ids' => json_encode( $product_ids ),
265
+ 'content_type' => 'product',
266
+ 'value' => WC()->cart->total,
267
+ 'currency' => get_woocommerce_currency(),
268
+ )
269
+ );
270
+ }
271
+
272
+ /**
273
+ * Triggered by add_to_cart jquery trigger
274
+ */
275
+ public function inject_ajax_add_to_cart_event() {
276
+ if ( ! self::$isEnabled ) {
277
+ return;
278
+ }
279
+
280
+ ob_start();
281
+
282
+ echo '<script>';
283
+
284
+ $product_ids = $this->get_content_ids_from_cart( WC()->cart->get_cart() );
285
+
286
+ echo $this->pixel->build_event(
287
+ 'AddToCart',
288
+ array(
289
+ 'content_ids' => json_encode( $product_ids ),
290
+ 'content_type' => 'product',
291
+ 'value' => WC()->cart->total,
292
+ 'currency' => get_woocommerce_currency(),
293
+ )
294
+ );
295
+ echo '</script>';
296
+
297
+ $pixel = ob_get_clean();
298
+
299
+ wp_send_json( $pixel );
300
+ }
301
+
302
+ /**
303
+ * Trigger AddToCart for cart page and woocommerce_after_cart hook.
304
+ * When set 'redirect to cart', ajax call for button click and
305
+ * woocommerce_add_to_cart will be skipped.
306
+ */
307
+ public function inject_add_to_cart_redirect_event() {
308
+ if ( ! self::$isEnabled ) {
309
+ return;
310
+ }
311
+ $redirect_checked = get_option( 'woocommerce_cart_redirect_after_add', 'no' );
312
+ if ( $redirect_checked == 'yes' ) {
313
+ $this->inject_add_to_cart_event();
314
+ }
315
+ }
316
+
317
+ /**
318
+ * Triggers InitiateCheckout for checkout page
319
+ */
320
+ public function inject_initiate_checkout_event() {
321
+ if ( ! self::$isEnabled ||
322
+ $this->pixel->check_last_event( 'InitiateCheckout' ) ) {
323
+ return;
324
+ }
325
+
326
+ $product_ids = $this->get_content_ids_from_cart( WC()->cart->get_cart() );
327
+
328
+ $this->pixel->inject_event(
329
+ 'InitiateCheckout',
330
+ array(
331
+ 'num_items' => WC()->cart->get_cart_contents_count(),
332
+ 'content_ids' => json_encode( $product_ids ),
333
+ 'content_type' => 'product',
334
+ 'value' => WC()->cart->total,
335
+ 'currency' => get_woocommerce_currency(),
336
+ )
337
+ );
338
+ }
339
+
340
+ /**
341
+ * Triggers Purchase for payment transaction complete and for the thank you
342
+ * page in cases of delayed payment.
343
+ */
344
+ public function inject_purchase_event( $order_id ) {
345
+ if ( ! self::$isEnabled ||
346
+ $this->pixel->check_last_event( 'Purchase' ) ) {
347
+ return;
348
+ }
349
+
350
+ $this->inject_subscribe_event( $order_id );
351
+
352
+ $order = new WC_Order( $order_id );
353
+ $content_type = 'product';
354
+ $product_ids = array();
355
+ foreach ( $order->get_items() as $item ) {
356
+ $product = wc_get_product( $item['product_id'] );
357
+ $product_ids = array_merge(
358
+ $product_ids,
359
+ WC_Facebookcommerce_Utils::get_fb_content_ids( $product )
360
+ );
361
+ if ( WC_Facebookcommerce_Utils::is_variable_type( $product->get_type() ) ) {
362
+ $content_type = 'product_group';
363
+ }
364
+ }
365
+
366
+ $this->pixel->inject_event(
367
+ 'Purchase',
368
+ array(
369
+ 'content_ids' => json_encode( $product_ids ),
370
+ 'content_type' => $content_type,
371
+ 'value' => $order->get_total(),
372
+ 'currency' => get_woocommerce_currency(),
373
+ )
374
+ );
375
+ }
376
+
377
+ /**
378
+ * Triggers Subscribe for payment transaction complete of purchase with
379
+ * subscription.
380
+ */
381
+ public function inject_subscribe_event( $order_id ) {
382
+ if ( ! function_exists( 'wcs_get_subscriptions_for_order' ) ) {
383
+ return;
384
+ }
385
+
386
+ $subscription_ids = wcs_get_subscriptions_for_order( $order_id );
387
+ foreach ( $subscription_ids as $subscription_id ) {
388
+ $subscription = new WC_Subscription( $subscription_id );
389
+ $this->pixel->inject_event(
390
+ 'Subscribe',
391
+ array(
392
+ 'sign_up_fee' => $subscription->get_sign_up_fee(),
393
+ 'value' => $subscription->get_total(),
394
+ 'currency' => get_woocommerce_currency(),
395
+ )
396
+ );
397
+ }
398
+ }
399
+
400
+ /**
401
+ * Triggers Purchase for thank you page for COD, BACS CHEQUE payment
402
+ * which won't invoke woocommerce_payment_complete.
403
+ */
404
+ public function inject_gateway_purchase_event( $order_id ) {
405
+ if ( ! self::$isEnabled ||
406
+ $this->pixel->check_last_event( 'Purchase' ) ) {
407
+ return;
408
+ }
409
+
410
+ $order = new WC_Order( $order_id );
411
+ $payment = $order->get_payment_method();
412
+ $this->inject_purchase_event( $order_id );
413
+ $this->inject_subscribe_event( $order_id );
414
+ }
415
+
416
+ /** Contact Form 7 Support **/
417
+ public function inject_lead_event_hook() {
418
+ add_action( 'wp_footer', array( $this, 'inject_lead_event' ), 11 );
419
+ }
420
+
421
+ public function inject_lead_event() {
422
+ if ( ! is_admin() ) {
423
+ $this->pixel->inject_conditional_event(
424
+ 'Lead',
425
+ array(),
426
+ 'wpcf7submit',
427
+ '{ em: event.detail.inputs.filter(ele => ele.name.includes("email"))[0].value }'
428
+ );
429
+ }
430
+ }
431
+ }
432
 
433
  endif;
facebook-commerce-messenger-chat.php CHANGED
@@ -8,45 +8,46 @@
8
  * @package FacebookCommerce
9
  */
10
 
11
- if (!class_exists('WC_Facebookcommerce_MessengerChat')) :
12
 
13
- if (!class_exists('WC_Facebookcommerce_Utils')) {
14
- include_once 'includes/fbutils.php';
15
- }
16
 
17
- class WC_Facebookcommerce_MessengerChat {
18
 
19
- public function __construct($settings) {
20
- $this->enabled = isset($settings['is_messenger_chat_plugin_enabled'])
21
- ? $settings['is_messenger_chat_plugin_enabled']
22
- : 'no';
23
 
24
- $this->page_id = isset($settings['fb_page_id'])
25
- ? $settings['fb_page_id']
26
- : '';
27
 
28
- $this->jssdk_version = isset($settings['facebook_jssdk_version'])
29
- ? $settings['facebook_jssdk_version']
30
- : '';
31
 
32
- $this->greeting_text_code = isset($settings['msger_chat_customization_greeting_text_code'])
33
- ? $settings['msger_chat_customization_greeting_text_code']
34
- : null;
35
 
36
- $this->locale = isset($settings['msger_chat_customization_locale'])
37
- ? $settings['msger_chat_customization_locale']
38
- : null;
39
 
40
- $this->theme_color_code = isset($settings['msger_chat_customization_theme_color_code'])
41
- ? $settings['msger_chat_customization_theme_color_code']
42
- : null;
43
 
44
- add_action('wp_footer', array($this, 'inject_messenger_chat_plugin'));
45
- }
46
 
47
- public function inject_messenger_chat_plugin() {
48
- if ($this->enabled === 'yes') {
49
- echo sprintf("<div
 
50
  attribution=\"fbe_woocommerce\"
51
  class=\"fb-customerchat\"
52
  page_id=\"%s\"
@@ -73,15 +74,16 @@ class WC_Facebookcommerce_MessengerChat {
73
  }(document, 'script', 'facebook-jssdk'));
74
  </script>
75
  <div></div>",
76
- $this->page_id,
77
- $this->theme_color_code ? sprintf('theme_color="%s"', $this->theme_color_code) : '',
78
- $this->greeting_text_code ? sprintf('logged_in_greeting="%s"', $this->greeting_text_code) : '',
79
- $this->greeting_text_code ? sprintf('logged_out_greeting="%s"', $this->greeting_text_code) : '',
80
- $this->jssdk_version,
81
- $this->locale ? $this->locale : 'en_US');
82
- }
83
- }
 
84
 
85
- }
86
 
87
  endif;
8
  * @package FacebookCommerce
9
  */
10
 
11
+ if ( ! class_exists( 'WC_Facebookcommerce_MessengerChat' ) ) :
12
 
13
+ if ( ! class_exists( 'WC_Facebookcommerce_Utils' ) ) {
14
+ include_once 'includes/fbutils.php';
15
+ }
16
 
17
+ class WC_Facebookcommerce_MessengerChat {
18
 
19
+ public function __construct( $settings ) {
20
+ $this->enabled = isset( $settings['is_messenger_chat_plugin_enabled'] )
21
+ ? $settings['is_messenger_chat_plugin_enabled']
22
+ : 'no';
23
 
24
+ $this->page_id = isset( $settings['fb_page_id'] )
25
+ ? $settings['fb_page_id']
26
+ : '';
27
 
28
+ $this->jssdk_version = isset( $settings['facebook_jssdk_version'] )
29
+ ? $settings['facebook_jssdk_version']
30
+ : '';
31
 
32
+ $this->greeting_text_code = isset( $settings['msger_chat_customization_greeting_text_code'] )
33
+ ? $settings['msger_chat_customization_greeting_text_code']
34
+ : null;
35
 
36
+ $this->locale = isset( $settings['msger_chat_customization_locale'] )
37
+ ? $settings['msger_chat_customization_locale']
38
+ : null;
39
 
40
+ $this->theme_color_code = isset( $settings['msger_chat_customization_theme_color_code'] )
41
+ ? $settings['msger_chat_customization_theme_color_code']
42
+ : null;
43
 
44
+ add_action( 'wp_footer', array( $this, 'inject_messenger_chat_plugin' ) );
45
+ }
46
 
47
+ public function inject_messenger_chat_plugin() {
48
+ if ( $this->enabled === 'yes' ) {
49
+ echo sprintf(
50
+ "<div
51
  attribution=\"fbe_woocommerce\"
52
  class=\"fb-customerchat\"
53
  page_id=\"%s\"
74
  }(document, 'script', 'facebook-jssdk'));
75
  </script>
76
  <div></div>",
77
+ $this->page_id,
78
+ $this->theme_color_code ? sprintf( 'theme_color="%s"', $this->theme_color_code ) : '',
79
+ $this->greeting_text_code ? sprintf( 'logged_in_greeting="%s"', $this->greeting_text_code ) : '',
80
+ $this->greeting_text_code ? sprintf( 'logged_out_greeting="%s"', $this->greeting_text_code ) : '',
81
+ $this->jssdk_version,
82
+ $this->locale ? $this->locale : 'en_US'
83
+ );
84
+ }
85
+ }
86
 
87
+ }
88
 
89
  endif;
facebook-commerce-pixel-event.php CHANGED
@@ -8,22 +8,22 @@
8
  * @package FacebookCommerce
9
  */
10
 
11
- if (!class_exists('WC_Facebookcommerce_Pixel')) :
12
 
13
 
14
- class WC_Facebookcommerce_Pixel {
15
- const SETTINGS_KEY = 'facebook_config';
16
- const PIXEL_ID_KEY = 'pixel_id';
17
- const USE_PII_KEY = 'use_pii';
18
 
19
- const PIXEL_RENDER = 'pixel_render';
20
- const NO_SCRIPT_RENDER = 'no_script_render';
21
 
22
- private $user_info;
23
- private $last_event;
24
- static $render_cache = array();
25
 
26
- static $default_pixel_basecode = "
27
  <script type='text/javascript'>
28
  !function(f,b,e,v,n,t,s){if(f.fbq)return;n=f.fbq=function(){n.callMethod?
29
  n.callMethod.apply(n,arguments):n.queue.push(arguments)};if(!f._fbq)f._fbq=n;
@@ -33,61 +33,62 @@ document,'script','https://connect.facebook.net/en_US/fbevents.js');
33
  </script>
34
  ";
35
 
36
- public function __construct($user_info=array()) {
37
- $this->user_info = $user_info;
38
- $this->last_event = '';
39
- }
40
-
41
- public static function initialize() {
42
- if (!is_admin()) {
43
- return;
44
- }
45
-
46
- // Initialize PixelID in storage - this will only need to happen when the
47
- // user is an admin
48
- $pixel_id = self::get_pixel_id();
49
- if (!WC_Facebookcommerce_Utils::is_valid_id($pixel_id) &&
50
- class_exists('WC_Facebookcommerce_WarmConfig')) {
51
- $fb_warm_pixel_id = WC_Facebookcommerce_WarmConfig::$fb_warm_pixel_id;
52
-
53
- if (WC_Facebookcommerce_Utils::is_valid_id($fb_warm_pixel_id) &&
54
- (int)$fb_warm_pixel_id == $fb_warm_pixel_id) {
55
- $fb_warm_pixel_id = (string)$fb_warm_pixel_id;
56
- self::set_pixel_id($fb_warm_pixel_id);
57
- }
58
- }
59
-
60
- $is_advanced_matching_enabled = self::get_use_pii_key();
61
- if ($is_advanced_matching_enabled == null &&
62
- class_exists('WC_Facebookcommerce_WarmConfig')) {
63
- $fb_warm_is_advanced_matching_enabled =
64
- WC_Facebookcommerce_WarmConfig::$fb_warm_is_advanced_matching_enabled;
65
- if (is_bool($fb_warm_is_advanced_matching_enabled)) {
66
- self::set_use_pii_key($fb_warm_is_advanced_matching_enabled ? 1 : 0);
67
- }
68
- }
69
- }
70
-
71
- /**
72
- * Returns FB pixel code script part
73
- */
74
- public function pixel_base_code() {
75
- $pixel_id = self::get_pixel_id();
76
- if (
77
- (
78
- isset(self::$render_cache[self::PIXEL_RENDER]) &&
79
- self::$render_cache[self::PIXEL_RENDER] === true
80
- ) ||
81
- !isset($pixel_id) ||
82
- $pixel_id === 0
83
- ) {
84
- return;
85
- }
86
-
87
- self::$render_cache[self::PIXEL_RENDER] = true;
88
- $params = self::add_version_info();
89
-
90
- return sprintf("
 
91
  <!-- %s Facebook Integration Begin -->
92
  %s
93
  <script>
@@ -109,55 +110,59 @@ document.addEventListener('DOMContentLoaded', function() {
109
  <!-- DO NOT MODIFY -->
110
  <!-- %s Facebook Integration end -->
111
  ",
112
- WC_Facebookcommerce_Utils::getIntegrationName(),
113
- self::get_basecode(),
114
- $this->pixel_init_code(),
115
- json_encode($params, JSON_PRETTY_PRINT | JSON_FORCE_OBJECT),
116
- WC_Facebookcommerce_Utils::getIntegrationName());
117
- }
118
-
119
- /**
120
- * Prevent double-fires by checking the last event
121
- */
122
- public function check_last_event($event_name) {
123
- return $event_name === $this->last_event;
124
- }
125
-
126
- /**
127
- * Preferred method to inject events in a page, normally you should use this
128
- * instead of WC_Facebookcommerce_Pixel::build_event()
129
- */
130
- public function inject_event($event_name, $params, $method='track') {
131
- $code = self::build_event($event_name, $params, $method);
132
- $this->last_event = $event_name;
133
-
134
- if (WC_Facebookcommerce_Utils::isWoocommerceIntegration()) {
135
- WC_Facebookcommerce_Utils::wc_enqueue_js($code);
136
- } else {
137
- printf("
 
 
138
  <!-- Facebook Pixel Event Code -->
139
  <script>
140
  %s
141
  </script>
142
  <!-- End Facebook Pixel Event Code -->
143
- ",
144
- $code);
145
- }
146
- }
147
-
148
- public function inject_conditional_event(
149
- $event_name, $params, $listener, $jsonified_pii = '') {
150
- $code = self::build_event($event_name, $params, 'track');
151
- $this->last_event = $event_name;
152
-
153
- // Prepends fbq(...) with pii information to the injected code.
154
- if ($jsonified_pii && get_option(self::SETTINGS_KEY)[self::USE_PII_KEY]) {
155
- $this->user_info = '%s';
156
- $code =
157
- sprintf($this->pixel_init_code(), '" || '.$jsonified_pii.' || "').$code;
158
- }
159
-
160
- printf("
 
 
161
  <!-- Facebook Pixel Event Code -->
162
  <script>
163
  document.addEventListener('%s', function (event) {
@@ -166,158 +171,171 @@ document.addEventListener('%s', function (event) {
166
  </script>
167
  <!-- End Facebook Pixel Event Code -->
168
  ",
169
- $listener,
170
- $code);
171
- }
172
-
173
- /**
174
- * Returns FB pixel code noscript part to avoid W3 validation error
175
- */
176
- public function pixel_base_code_noscript() {
177
- $pixel_id = self::get_pixel_id();
178
- if (
179
- (
180
- isset(self::$render_cache[self::NO_SCRIPT_RENDER]) &&
181
- self::$render_cache[self::NO_SCRIPT_RENDER] === true
182
- ) ||
183
- !isset($pixel_id) ||
184
- $pixel_id === 0
185
- ) {
186
- return;
187
- }
188
-
189
- self::$render_cache[self::NO_SCRIPT_RENDER] = true;
190
-
191
- return sprintf("
 
 
192
  <!-- Facebook Pixel Code -->
193
  <noscript>
194
- <img height=\"1\" width=\"1\" style=\"display:none\" alt=\"fbpx\"
195
- src=\"https://www.facebook.com/tr?id=%s&ev=PageView&noscript=1\"/>
196
  </noscript>
197
  <!-- DO NOT MODIFY -->
198
  <!-- End Facebook Pixel Code -->
199
- ",
200
- esc_js($pixel_id));
201
- }
202
-
203
- /**
204
- * You probably should use WC_Facebookcommerce_Pixel::inject_event() but
205
- * this method is available if you need to modify the JS code somehow
206
- */
207
- public static function build_event($event_name, $params, $method='track') {
208
- $params = self::add_version_info($params);
209
- return sprintf(
210
- "/* %s Facebook Integration Event Tracking */\n".
211
- "fbq('%s', '%s', %s);",
212
- WC_Facebookcommerce_Utils::getIntegrationName(),
213
- $method,
214
- $event_name,
215
- json_encode($params, JSON_PRETTY_PRINT | JSON_FORCE_OBJECT));
216
- }
217
-
218
- public static function get_pixel_id() {
219
- $fb_options = self::get_options();
220
- if (!$fb_options) {
221
- return '';
222
- }
223
- return isset($fb_options[self::PIXEL_ID_KEY]) ?
224
- $fb_options[self::PIXEL_ID_KEY] : '';
225
- }
226
-
227
- public static function set_pixel_id($pixel_id) {
228
- $fb_options = self::get_options();
229
-
230
- if (isset($fb_options[self::PIXEL_ID_KEY])
231
- && $fb_options[self::PIXEL_ID_KEY] == $pixel_id) {
232
- return;
233
- }
234
-
235
- $fb_options[self::PIXEL_ID_KEY] = $pixel_id;
236
- update_option(self::SETTINGS_KEY, $fb_options);
237
- }
238
-
239
- public static function get_use_pii_key() {
240
- $fb_options = self::get_options();
241
- if (!$fb_options) {
242
- return null;
243
- }
244
- return isset($fb_options[self::USE_PII_KEY]) ?
245
- $fb_options[self::USE_PII_KEY] : null;
246
- }
247
-
248
- public static function set_use_pii_key($use_pii) {
249
- $fb_options = self::get_options();
250
-
251
- if (isset($fb_options[self::USE_PII_KEY])
252
- && $fb_options[self::USE_PII_KEY] == $use_pii) {
253
- return;
254
- }
255
-
256
- $fb_options[self::USE_PII_KEY] = $use_pii;
257
- update_option(self::SETTINGS_KEY, $fb_options);
258
- }
259
-
260
- public static function get_basecode() {
261
- return self::$default_pixel_basecode;
262
- }
263
-
264
- private static function get_version_info() {
265
- global $wp_version;
266
-
267
- if (WC_Facebookcommerce_Utils::isWoocommerceIntegration()) {
268
- return array(
269
- 'source' => 'woocommerce',
270
- 'version' => WC()->version,
271
- 'pluginVersion' => WC_Facebookcommerce_Utils::PLUGIN_VERSION
272
- );
273
- }
274
-
275
- return array(
276
- 'source' => 'wordpress',
277
- 'version' => $wp_version,
278
- 'pluginVersion' => WC_Facebookcommerce_Utils::PLUGIN_VERSION
279
- );
280
- }
281
-
282
- public static function get_options() {
283
- return get_option(self::SETTINGS_KEY, array(
284
- self::PIXEL_ID_KEY => '0',
285
- self::USE_PII_KEY => 0,
286
- ));
287
- }
288
-
289
- /**
290
- * Returns an array with version_info for pixel fires. Parameters provided by
291
- * users should not be overwritten by this function
292
- */
293
- private static function add_version_info($params=array()) {
294
- // if any parameter is passed in the pixel, do not overwrite it
295
- return array_replace(self::get_version_info(), $params);
296
- }
297
-
298
- /**
299
- * Init code might contain additional information to help matching website
300
- * users with facebook users. Information is hashed in JS side using SHA256
301
- * before sending to Facebook.
302
- */
303
- private function pixel_init_code() {
304
- $version_info = self::get_version_info();
305
- $agent_string = sprintf(
306
- '%s-%s-%s',
307
- $version_info['source'],
308
- $version_info['version'],
309
- $version_info['pluginVersion']);
310
-
311
- $params = array(
312
- 'agent' => $agent_string);
313
-
314
- return apply_filters('facebook_woocommerce_pixel_init', sprintf(
315
- "fbq('init', '%s', %s, %s);\n",
316
- esc_js(self::get_pixel_id()),
317
- json_encode($this->user_info, JSON_PRETTY_PRINT | JSON_FORCE_OBJECT),
318
- json_encode($params, JSON_PRETTY_PRINT | JSON_FORCE_OBJECT)));
319
- }
320
-
321
- }
 
 
 
 
 
 
 
 
 
 
 
322
 
323
  endif;
8
  * @package FacebookCommerce
9
  */
10
 
11
+ if ( ! class_exists( 'WC_Facebookcommerce_Pixel' ) ) :
12
 
13
 
14
+ class WC_Facebookcommerce_Pixel {
15
+ const SETTINGS_KEY = 'facebook_config';
16
+ const PIXEL_ID_KEY = 'pixel_id';
17
+ const USE_PII_KEY = 'use_pii';
18
 
19
+ const PIXEL_RENDER = 'pixel_render';
20
+ const NO_SCRIPT_RENDER = 'no_script_render';
21
 
22
+ private $user_info;
23
+ private $last_event;
24
+ static $render_cache = array();
25
 
26
+ static $default_pixel_basecode = "
27
  <script type='text/javascript'>
28
  !function(f,b,e,v,n,t,s){if(f.fbq)return;n=f.fbq=function(){n.callMethod?
29
  n.callMethod.apply(n,arguments):n.queue.push(arguments)};if(!f._fbq)f._fbq=n;
33
  </script>
34
  ";
35
 
36
+ public function __construct( $user_info = array() ) {
37
+ $this->user_info = $user_info;
38
+ $this->last_event = '';
39
+ }
40
+
41
+ public static function initialize() {
42
+ if ( ! is_admin() ) {
43
+ return;
44
+ }
45
+
46
+ // Initialize PixelID in storage - this will only need to happen when the
47
+ // user is an admin
48
+ $pixel_id = self::get_pixel_id();
49
+ if ( ! WC_Facebookcommerce_Utils::is_valid_id( $pixel_id ) &&
50
+ class_exists( 'WC_Facebookcommerce_WarmConfig' ) ) {
51
+ $fb_warm_pixel_id = WC_Facebookcommerce_WarmConfig::$fb_warm_pixel_id;
52
+
53
+ if ( WC_Facebookcommerce_Utils::is_valid_id( $fb_warm_pixel_id ) &&
54
+ (int) $fb_warm_pixel_id == $fb_warm_pixel_id ) {
55
+ $fb_warm_pixel_id = (string) $fb_warm_pixel_id;
56
+ self::set_pixel_id( $fb_warm_pixel_id );
57
+ }
58
+ }
59
+
60
+ $is_advanced_matching_enabled = self::get_use_pii_key();
61
+ if ( $is_advanced_matching_enabled == null &&
62
+ class_exists( 'WC_Facebookcommerce_WarmConfig' ) ) {
63
+ $fb_warm_is_advanced_matching_enabled =
64
+ WC_Facebookcommerce_WarmConfig::$fb_warm_is_advanced_matching_enabled;
65
+ if ( is_bool( $fb_warm_is_advanced_matching_enabled ) ) {
66
+ self::set_use_pii_key( $fb_warm_is_advanced_matching_enabled ? 1 : 0 );
67
+ }
68
+ }
69
+ }
70
+
71
+ /**
72
+ * Returns FB pixel code script part
73
+ */
74
+ public function pixel_base_code() {
75
+ $pixel_id = self::get_pixel_id();
76
+ if (
77
+ (
78
+ isset( self::$render_cache[ self::PIXEL_RENDER ] ) &&
79
+ self::$render_cache[ self::PIXEL_RENDER ] === true
80
+ ) ||
81
+ ! isset( $pixel_id ) ||
82
+ $pixel_id === 0
83
+ ) {
84
+ return;
85
+ }
86
+
87
+ self::$render_cache[ self::PIXEL_RENDER ] = true;
88
+ $params = self::add_version_info();
89
+
90
+ return sprintf(
91
+ "
92
  <!-- %s Facebook Integration Begin -->
93
  %s
94
  <script>
110
  <!-- DO NOT MODIFY -->
111
  <!-- %s Facebook Integration end -->
112
  ",
113
+ WC_Facebookcommerce_Utils::getIntegrationName(),
114
+ self::get_basecode(),
115
+ $this->pixel_init_code(),
116
+ json_encode( $params, JSON_PRETTY_PRINT | JSON_FORCE_OBJECT ),
117
+ WC_Facebookcommerce_Utils::getIntegrationName()
118
+ );
119
+ }
120
+
121
+ /**
122
+ * Prevent double-fires by checking the last event
123
+ */
124
+ public function check_last_event( $event_name ) {
125
+ return $event_name === $this->last_event;
126
+ }
127
+
128
+ /**
129
+ * Preferred method to inject events in a page, normally you should use this
130
+ * instead of WC_Facebookcommerce_Pixel::build_event()
131
+ */
132
+ public function inject_event( $event_name, $params, $method = 'track' ) {
133
+ $code = self::build_event( $event_name, $params, $method );
134
+ $this->last_event = $event_name;
135
+
136
+ if ( WC_Facebookcommerce_Utils::isWoocommerceIntegration() ) {
137
+ WC_Facebookcommerce_Utils::wc_enqueue_js( $code );
138
+ } else {
139
+ printf(
140
+ '
141
  <!-- Facebook Pixel Event Code -->
142
  <script>
143
  %s
144
  </script>
145
  <!-- End Facebook Pixel Event Code -->
146
+ ',
147
+ $code
148
+ );
149
+ }
150
+ }
151
+
152
+ public function inject_conditional_event(
153
+ $event_name, $params, $listener, $jsonified_pii = '' ) {
154
+ $code = self::build_event( $event_name, $params, 'track' );
155
+ $this->last_event = $event_name;
156
+
157
+ // Prepends fbq(...) with pii information to the injected code.
158
+ if ( $jsonified_pii && get_option( self::SETTINGS_KEY )[ self::USE_PII_KEY ] ) {
159
+ $this->user_info = '%s';
160
+ $code =
161
+ sprintf( $this->pixel_init_code(), '" || ' . $jsonified_pii . ' || "' ) . $code;
162
+ }
163
+
164
+ printf(
165
+ "
166
  <!-- Facebook Pixel Event Code -->
167
  <script>
168
  document.addEventListener('%s', function (event) {
171
  </script>
172
  <!-- End Facebook Pixel Event Code -->
173
  ",
174
+ $listener,
175
+ $code
176
+ );
177
+ }
178
+
179
+ /**
180
+ * Returns FB pixel code noscript part to avoid W3 validation error
181
+ */
182
+ public function pixel_base_code_noscript() {
183
+ $pixel_id = self::get_pixel_id();
184
+ if (
185
+ (
186
+ isset( self::$render_cache[ self::NO_SCRIPT_RENDER ] ) &&
187
+ self::$render_cache[ self::NO_SCRIPT_RENDER ] === true
188
+ ) ||
189
+ ! isset( $pixel_id ) ||
190
+ $pixel_id === 0
191
+ ) {
192
+ return;
193
+ }
194
+
195
+ self::$render_cache[ self::NO_SCRIPT_RENDER ] = true;
196
+
197
+ return sprintf(
198
+ '
199
  <!-- Facebook Pixel Code -->
200
  <noscript>
201
+ <img height="1" width="1" style="display:none" alt="fbpx"
202
+ src="https://www.facebook.com/tr?id=%s&ev=PageView&noscript=1"/>
203
  </noscript>
204
  <!-- DO NOT MODIFY -->
205
  <!-- End Facebook Pixel Code -->
206
+ ',
207
+ esc_js( $pixel_id )
208
+ );
209
+ }
210
+
211
+ /**
212
+ * You probably should use WC_Facebookcommerce_Pixel::inject_event() but
213
+ * this method is available if you need to modify the JS code somehow
214
+ */
215
+ public static function build_event( $event_name, $params, $method = 'track' ) {
216
+ $params = self::add_version_info( $params );
217
+ return sprintf(
218
+ "/* %s Facebook Integration Event Tracking */\n" .
219
+ "fbq('%s', '%s', %s);",
220
+ WC_Facebookcommerce_Utils::getIntegrationName(),
221
+ $method,
222
+ $event_name,
223
+ json_encode( $params, JSON_PRETTY_PRINT | JSON_FORCE_OBJECT )
224
+ );
225
+ }
226
+
227
+ public static function get_pixel_id() {
228
+ $fb_options = self::get_options();
229
+ if ( ! $fb_options ) {
230
+ return '';
231
+ }
232
+ return isset( $fb_options[ self::PIXEL_ID_KEY ] ) ?
233
+ $fb_options[ self::PIXEL_ID_KEY ] : '';
234
+ }
235
+
236
+ public static function set_pixel_id( $pixel_id ) {
237
+ $fb_options = self::get_options();
238
+
239
+ if ( isset( $fb_options[ self::PIXEL_ID_KEY ] )
240
+ && $fb_options[ self::PIXEL_ID_KEY ] == $pixel_id ) {
241
+ return;
242
+ }
243
+
244
+ $fb_options[ self::PIXEL_ID_KEY ] = $pixel_id;
245
+ update_option( self::SETTINGS_KEY, $fb_options );
246
+ }
247
+
248
+ public static function get_use_pii_key() {
249
+ $fb_options = self::get_options();
250
+ if ( ! $fb_options ) {
251
+ return null;
252
+ }
253
+ return isset( $fb_options[ self::USE_PII_KEY ] ) ?
254
+ $fb_options[ self::USE_PII_KEY ] : null;
255
+ }
256
+
257
+ public static function set_use_pii_key( $use_pii ) {
258
+ $fb_options = self::get_options();
259
+
260
+ if ( isset( $fb_options[ self::USE_PII_KEY ] )
261
+ && $fb_options[ self::USE_PII_KEY ] == $use_pii ) {
262
+ return;
263
+ }
264
+
265
+ $fb_options[ self::USE_PII_KEY ] = $use_pii;
266
+ update_option( self::SETTINGS_KEY, $fb_options );
267
+ }
268
+
269
+ public static function get_basecode() {
270
+ return self::$default_pixel_basecode;
271
+ }
272
+
273
+ private static function get_version_info() {
274
+ global $wp_version;
275
+
276
+ if ( WC_Facebookcommerce_Utils::isWoocommerceIntegration() ) {
277
+ return array(
278
+ 'source' => 'woocommerce',
279
+ 'version' => WC()->version,
280
+ 'pluginVersion' => WC_Facebookcommerce_Utils::PLUGIN_VERSION,
281
+ );
282
+ }
283
+
284
+ return array(
285
+ 'source' => 'wordpress',
286
+ 'version' => $wp_version,
287
+ 'pluginVersion' => WC_Facebookcommerce_Utils::PLUGIN_VERSION,
288
+ );
289
+ }
290
+
291
+ public static function get_options() {
292
+ return get_option(
293
+ self::SETTINGS_KEY,
294
+ array(
295
+ self::PIXEL_ID_KEY => '0',
296
+ self::USE_PII_KEY => 0,
297
+ )
298
+ );
299
+ }
300
+
301
+ /**
302
+ * Returns an array with version_info for pixel fires. Parameters provided by
303
+ * users should not be overwritten by this function
304
+ */
305
+ private static function add_version_info( $params = array() ) {
306
+ // if any parameter is passed in the pixel, do not overwrite it
307
+ return array_replace( self::get_version_info(), $params );
308
+ }
309
+
310
+ /**
311
+ * Init code might contain additional information to help matching website
312
+ * users with facebook users. Information is hashed in JS side using SHA256
313
+ * before sending to Facebook.
314
+ */
315
+ private function pixel_init_code() {
316
+ $version_info = self::get_version_info();
317
+ $agent_string = sprintf(
318
+ '%s-%s-%s',
319
+ $version_info['source'],
320
+ $version_info['version'],
321
+ $version_info['pluginVersion']
322
+ );
323
+
324
+ $params = array(
325
+ 'agent' => $agent_string,
326
+ );
327
+
328
+ return apply_filters(
329
+ 'facebook_woocommerce_pixel_init',
330
+ sprintf(
331
+ "fbq('init', '%s', %s, %s);\n",
332
+ esc_js( self::get_pixel_id() ),
333
+ json_encode( $this->user_info, JSON_PRETTY_PRINT | JSON_FORCE_OBJECT ),
334
+ json_encode( $params, JSON_PRETTY_PRINT | JSON_FORCE_OBJECT )
335
+ )
336
+ );
337
+ }
338
+
339
+ }
340
 
341
  endif;
facebook-commerce.php CHANGED
@@ -8,2536 +8,2928 @@
8
  * @package FacebookCommerce
9
  */
10
 
11
- if (!defined('ABSPATH')) exit; // Exit if accessed directly
 
 
12
 
13
- include_once('facebook-config-warmer.php');
14
- include_once('includes/fbproduct.php');
15
- include_once 'facebook-commerce-pixel-event.php';
16
 
17
  class WC_Facebookcommerce_Integration extends WC_Integration {
18
 
19
- const FB_PRODUCT_GROUP_ID = 'fb_product_group_id';
20
- const FB_PRODUCT_ITEM_ID = 'fb_product_item_id';
21
- const FB_PRODUCT_DESCRIPTION = 'fb_product_description';
22
-
23
- const FB_VISIBILITY = 'fb_visibility';
24
-
25
- const FB_CART_URL = 'fb_cart_url';
26
-
27
- const FB_MESSAGE_DISPLAY_TIME = 180;
28
-
29
- // Number of days to query tip.
30
- const FB_TIP_QUERY = 1;
31
-
32
- const FB_VARIANT_IMAGE = 'fb_image';
33
-
34
- const FB_ADMIN_MESSAGE_PREPEND = '<b>Facebook for WooCommerce</b><br/>';
35
-
36
- const FB_SYNC_IN_PROGRESS = 'fb_sync_in_progress';
37
- const FB_SYNC_REMAINING = 'fb_sync_remaining';
38
- const FB_SYNC_TIMEOUT = 30;
39
- const FB_PRIORITY_MID = 9;
40
-
41
- private $test_mode = false;
42
-
43
- public function init_settings() {
44
- parent::init_settings();
45
- }
46
-
47
- public function init_pixel() {
48
- WC_Facebookcommerce_Pixel::initialize();
49
-
50
- // Migrate WC customer pixel_id from WC settings to WP options.
51
- // This is part of a larger effort to consolidate all the FB-specific
52
- // settings for all plugin integrations.
53
- if (is_admin()) {
54
- $pixel_id = WC_Facebookcommerce_Pixel::get_pixel_id();
55
- $settings_pixel_id = isset($this->settings['fb_pixel_id']) ?
56
- (string)$this->settings['fb_pixel_id'] : null;
57
- if (
58
- WC_Facebookcommerce_Utils::is_valid_id($settings_pixel_id) &&
59
- (!WC_Facebookcommerce_Utils::is_valid_id($pixel_id) ||
60
- $pixel_id != $settings_pixel_id
61
- )
62
- ) {
63
- WC_Facebookcommerce_Pixel::set_pixel_id($settings_pixel_id);
64
- }
65
- }
66
- }
67
-
68
- /**
69
- * Init and hook in the integration.
70
- *
71
- * @access public
72
- * @return void
73
- */
74
- public function __construct() {
75
- if (!class_exists('WC_Facebookcommerce_EventsTracker')) {
76
- include_once 'facebook-commerce-events-tracker.php';
77
- }
78
-
79
- $this->id = 'facebookcommerce';
80
- $this->method_title = __(
81
- 'Facebook for WooCommerce',
82
- 'facebook-for-commerce');
83
- $this->method_description = __(
84
- 'Facebook Commerce and Dynamic Ads (Pixel) Extension',
85
- 'facebook-for-commerce');
86
-
87
- // Load the settings.
88
- $this->init_settings();
89
-
90
- $this->page_id = isset($this->settings['fb_page_id'])
91
- ? $this->settings['fb_page_id']
92
- : '';
93
-
94
- $this->api_key = isset($this->settings['fb_api_key'])
95
- ? $this->settings['fb_api_key']
96
- : '';
97
-
98
- $pixel_id = WC_Facebookcommerce_Pixel::get_pixel_id();
99
- if (!$pixel_id) {
100
- $pixel_id = isset($this->settings['fb_pixel_id']) ?
101
- $this->settings['fb_pixel_id'] : '';
102
- }
103
- $this->pixel_id = isset($pixel_id)
104
- ? $pixel_id
105
- : '';
106
-
107
- $this->pixel_install_time = isset($this->settings['pixel_install_time'])
108
- ? $this->settings['pixel_install_time']
109
- : '';
110
-
111
- $this->use_pii = isset($this->settings['fb_pixel_use_pii'])
112
- && $this->settings['fb_pixel_use_pii'] === 'yes'
113
- ? true
114
- : false;
115
-
116
- $this->product_catalog_id = isset($this->settings['fb_product_catalog_id'])
117
- ? $this->settings['fb_product_catalog_id']
118
- : '';
119
-
120
- $this->external_merchant_settings_id =
121
- isset($this->settings['fb_external_merchant_settings_id'])
122
- ? $this->settings['fb_external_merchant_settings_id']
123
- : '';
124
-
125
- if (!class_exists('WC_Facebookcommerce_Utils')) {
126
- include_once 'includes/fbutils.php';
127
- }
128
-
129
- WC_Facebookcommerce_Utils::$ems = $this->external_merchant_settings_id;
130
-
131
- if (!class_exists('WC_Facebookcommerce_Graph_API')) {
132
- include_once 'includes/fbgraph.php';
133
- $this->fbgraph = new WC_Facebookcommerce_Graph_API($this->api_key);
134
- }
135
-
136
- WC_Facebookcommerce_Utils::$fbgraph = $this->fbgraph;
137
- $this->feed_id = isset($this->settings['fb_feed_id'])
138
- ? $this->settings['fb_feed_id']
139
- : '';
140
-
141
- // Hooks
142
- if (is_admin()) {
143
- $this->init_pixel();
144
- $this->init_form_fields();
145
- // Display an info banner for eligible pixel and user.
146
- if ($this->external_merchant_settings_id
147
- && $this->pixel_id
148
- && $this->pixel_install_time) {
149
- $should_query_tip =
150
- WC_Facebookcommerce_Utils::check_time_cap(
151
- get_option('fb_info_banner_last_query_time', ''),
152
- self::FB_TIP_QUERY);
153
- $last_tip_info = WC_Facebookcommerce_Utils::get_cached_best_tip();
154
-
155
- if ($should_query_tip || $last_tip_info) {
156
- if (!class_exists('WC_Facebookcommerce_Info_Banner')) {
157
- include_once 'includes/fbinfobanner.php';
158
- }
159
- WC_Facebookcommerce_Info_Banner::get_instance(
160
- $this->external_merchant_settings_id,
161
- $this->fbgraph,
162
- $should_query_tip);
163
- }
164
- }
165
-
166
- if (!class_exists('WC_Facebook_Integration_Test')) {
167
- include_once 'includes/test/facebook-integration-test.php';
168
- }
169
- $integration_test = WC_Facebook_Integration_Test::get_instance($this);
170
- $integration_test::$fbgraph = $this->fbgraph;
171
-
172
- if (!$this->pixel_install_time && $this->pixel_id) {
173
- $this->pixel_install_time = current_time('mysql');
174
- $this->settings['pixel_install_time'] = $this->pixel_install_time;
175
- update_option(
176
- $this->get_option_key(),
177
- apply_filters(
178
- 'woocommerce_settings_api_sanitized_fields_' . $this->id,
179
- $this->settings));
180
- }
181
- add_action('admin_notices', array( $this, 'checks' ));
182
- add_action('woocommerce_update_options_integration_facebookcommerce',
183
- array($this, 'process_admin_options'));
184
- add_action('admin_enqueue_scripts', array( $this, 'load_assets'));
185
-
186
- add_action('wp_ajax_ajax_save_fb_settings',
187
- array($this, 'ajax_save_fb_settings'), self::FB_PRIORITY_MID);
188
-
189
- add_action('wp_ajax_ajax_delete_fb_settings',
190
- array($this, 'ajax_delete_fb_settings'), self::FB_PRIORITY_MID);
191
-
192
- add_action('wp_ajax_ajax_sync_all_fb_products',
193
- array($this, 'ajax_sync_all_fb_products'), self::FB_PRIORITY_MID);
194
-
195
- add_action('wp_ajax_ajax_sync_all_fb_products_using_feed',
196
- array($this, 'ajax_sync_all_fb_products_using_feed'),
197
- self::FB_PRIORITY_MID);
198
-
199
- add_action('wp_ajax_ajax_check_feed_upload_status',
200
- array($this, 'ajax_check_feed_upload_status'),
201
- self::FB_PRIORITY_MID);
202
-
203
- add_action('wp_ajax_ajax_reset_all_fb_products',
204
- array($this, 'ajax_reset_all_fb_products'),
205
- self::FB_PRIORITY_MID);
206
- add_action('wp_ajax_ajax_display_test_result',
207
- array($this, 'ajax_display_test_result'));
208
-
209
- add_action('wp_ajax_ajax_schedule_force_resync',
210
- array($this, 'ajax_schedule_force_resync'), self::FB_PRIORITY_MID);
211
-
212
- add_action('wp_ajax_ajax_update_fb_option',
213
- array($this, 'ajax_update_fb_option'), self::FB_PRIORITY_MID);
214
-
215
- // Only load product processing hooks if we have completed setup.
216
- if ($this->api_key && $this->product_catalog_id) {
217
- add_action(
218
- 'woocommerce_process_product_meta_simple',
219
- array($this, 'on_simple_product_publish'),
220
- 10, // Action priority
221
- 1 // Args passed to on_product_publish (should be 'id')
222
- );
223
-
224
- add_action(
225
- 'woocommerce_process_product_meta_variable',
226
- array($this, 'on_variable_product_publish'),
227
- 10, // Action priority
228
- 1 // Args passed to on_product_publish (should be 'id')
229
- );
230
-
231
- add_action(
232
- 'woocommerce_process_product_meta_booking',
233
- array($this, 'on_simple_product_publish'),
234
- 10, // Action priority
235
- 1 // Args passed to on_product_publish (should be 'id')
236
- );
237
-
238
- add_action(
239
- 'woocommerce_process_product_meta_external',
240
- array($this, 'on_simple_product_publish'),
241
- 10, // Action priority
242
- 1 // Args passed to on_product_publish (should be 'id')
243
- );
244
-
245
- add_action(
246
- 'woocommerce_process_product_meta_subscription',
247
- array($this, 'on_product_publish'),
248
- 10, // Action priority
249
- 1 // Args passed to on_product_publish (should be 'id')
250
- );
251
-
252
- add_action(
253
- 'woocommerce_process_product_meta_variable-subscription',
254
- array($this, 'on_product_publish'),
255
- 10, // Action priority
256
- 1 // Args passed to on_product_publish (should be 'id')
257
- );
258
-
259
- add_action('woocommerce_process_product_meta_bundle',
260
- array($this, 'on_product_publish'),
261
- 10, // Action priority
262
- 1 // Args passed to on_product_publish (should be 'id')
263
- );
264
-
265
- add_action(
266
- 'woocommerce_product_quick_edit_save',
267
- array($this, 'on_quick_and_bulk_edit_save'),
268
- 10, // Action priority
269
- 1 // Args passed to on_quick_and_bulk_edit_save ('product')
270
- );
271
-
272
- add_action(
273
- 'woocommerce_product_bulk_edit_save',
274
- array($this, 'on_quick_and_bulk_edit_save'),
275
- 10, // Action priority
276
- 1 // Args passed to on_quick_and_bulk_edit_save ('product')
277
- );
278
-
279
- add_action(
280
- 'before_delete_post',
281
- array($this, 'on_product_delete'),
282
- 10,
283
- 1);
284
-
285
- add_action('add_meta_boxes', array($this, 'fb_product_metabox'), 10, 1);
286
-
287
- add_filter('manage_product_posts_columns',
288
- array($this, 'fb_product_columns'));
289
- add_action('manage_product_posts_custom_column',
290
- array( $this, 'fb_render_product_columns' ), 2);
291
- add_action('transition_post_status',
292
- array($this, 'fb_change_product_published_status'), 10, 3);
293
-
294
- // Product data tab
295
- add_filter('woocommerce_product_data_tabs',
296
- array($this, 'fb_new_product_tab'));
297
-
298
- add_action('woocommerce_product_data_panels',
299
- array($this, 'fb_new_product_tab_content' ));
300
-
301
- add_action('wp_ajax_ajax_fb_toggle_visibility',
302
- array($this, 'ajax_toggle_visibility'));
303
-
304
- add_action('wp_ajax_ajax_reset_single_fb_product',
305
- array($this, 'ajax_reset_single_fb_product'));
306
-
307
- add_action('wp_ajax_ajax_delete_fb_product',
308
- array($this, 'ajax_delete_fb_product'));
309
-
310
- add_filter('woocommerce_duplicate_product_exclude_meta',
311
- array($this, 'fb_duplicate_product_reset_meta'));
312
-
313
- add_action('pmxi_after_xml_import',
314
- array($this, 'wp_all_import_compat'));
315
-
316
- add_action('wp_ajax_wpmelon_adv_bulk_edit',
317
- array($this, 'ajax_woo_adv_bulk_edit_compat'), self::FB_PRIORITY_MID);
318
-
319
- // Used to remove the 'you need to resync' message.
320
- if (isset($_GET['remove_sticky'])) {
321
- $this->remove_sticky_message();
322
- }
323
-
324
- if (defined('ICL_LANGUAGE_CODE')) {
325
- include_once('includes/fbwpml.php');
326
- new WC_Facebook_WPML_Injector();
327
- }
328
-
329
- }
330
- $this->load_background_sync_process();
331
- }
332
- // Must be outside of admin for cron to schedule correctly.
333
- add_action('sync_all_fb_products_using_feed',
334
- array($this, 'sync_all_fb_products_using_feed'),
335
- self::FB_PRIORITY_MID);
336
-
337
- if ($this->pixel_id) {
338
- $user_info = WC_Facebookcommerce_Utils::get_user_info($this->use_pii);
339
- $this->events_tracker = new WC_Facebookcommerce_EventsTracker($user_info);
340
- }
341
-
342
- if (isset($this->settings['is_messenger_chat_plugin_enabled']) &&
343
- $this->settings['is_messenger_chat_plugin_enabled'] === 'yes') {
344
- if (!class_exists('WC_Facebookcommerce_MessengerChat')) {
345
- include_once 'facebook-commerce-messenger-chat.php';
346
- }
347
- $this->messenger_chat = new WC_Facebookcommerce_MessengerChat($this->settings);
348
- }
349
- }
350
-
351
- public function load_background_sync_process() {
352
- // Attempt to load background processing (Woo 3.x.x only)
353
- include_once('includes/fbbackground.php');
354
- if (class_exists('WC_Facebookcommerce_Background_Process')) {
355
- if (!isset($this->background_processor)) {
356
- $this->background_processor =
357
- new WC_Facebookcommerce_Background_Process($this);
358
- }
359
- }
360
- add_action('wp_ajax_ajax_fb_background_check_queue',
361
- array($this, 'ajax_fb_background_check_queue'));
362
- }
363
-
364
- public function ajax_fb_background_check_queue() {
365
- WC_Facebookcommerce_Utils::check_woo_ajax_permissions('background check queue', true);
366
- $request_time = null;
367
- if (isset($_POST['request_time'])) {
368
- $request_time = esc_js(sanitize_text_field($_POST['request_time']));
369
- }
370
- if ($this->settings['fb_api_key']) {
371
- if (isset($this->background_processor)) {
372
- $is_processing = $this->background_processor->handle_cron_healthcheck();
373
- $remaining = $this->background_processor->get_item_count();
374
- $response = array(
375
- 'connected' => true,
376
- 'background' => true,
377
- 'processing' => $is_processing,
378
- 'remaining' => $remaining,
379
- 'request_time' => $request_time,
380
- );
381
- } else {
382
- $response = array(
383
- 'connected' => true,
384
- 'background' => false,
385
- );
386
- }
387
- } else {
388
- $response = array(
389
- 'connected' => false,
390
- 'background' => false,
391
- );
392
- }
393
- printf(json_encode($response));
394
- wp_die();
395
- }
396
-
397
- public function fb_new_product_tab($tabs) {
398
- $tabs['fb_commerce_tab'] = array(
399
- 'label' => __('Facebook', 'facebook-for-woocommerce'),
400
- 'target' => 'facebook_options',
401
- 'class' => array( 'show_if_simple', 'show_if_variable' ),
402
- );
403
- return $tabs;
404
- }
405
-
406
- public function fb_new_product_tab_content() {
407
- global $post;
408
- $woo_product = new WC_Facebook_Product($post->ID);
409
- $description = get_post_meta(
410
- $post->ID,
411
- self::FB_PRODUCT_DESCRIPTION,
412
- true);
413
-
414
- $price = get_post_meta(
415
- $post->ID,
416
- WC_Facebook_Product::FB_PRODUCT_PRICE,
417
- true);
418
-
419
- $image = get_post_meta(
420
- $post->ID,
421
- WC_Facebook_Product::FB_PRODUCT_IMAGE,
422
- true);
423
-
424
- $image_setting = null;
425
- if (WC_Facebookcommerce_Utils::is_variable_type($woo_product->get_type())) {
426
- $image_setting = $woo_product->get_use_parent_image();
427
- }
428
-
429
- // 'id' attribute needs to match the 'target' parameter set above
430
- ?><div id='facebook_options' class='panel woocommerce_options_panel'><?php
431
- ?><div class='options_group'><?php
432
- woocommerce_wp_textarea_input(
433
- array(
434
- 'id' => self::FB_PRODUCT_DESCRIPTION,
435
- 'label' => __('Facebook Description', 'facebook-for-woocommerce'),
436
- 'desc_tip' => 'true',
437
- 'description' => __(
438
- 'Custom (plain-text only) description for product on Facebook. '.
439
- 'If blank, product description will be used. ' .
440
- 'If product description is blank, shortname will be used.',
441
- 'facebook-for-woocommerce'),
442
- 'cols' => 40,
443
- 'rows' => 20,
444
- 'value' => $description,
445
- ));
446
- woocommerce_wp_textarea_input(
447
- array(
448
- 'id' => WC_Facebook_Product::FB_PRODUCT_IMAGE,
449
- 'label' => __('Facebook Product Image', 'facebook-for-woocommerce'),
450
- 'desc_tip' => 'true',
451
- 'description' => __(
452
- 'Image URL for product on Facebook. Must be an absolute URL '.
453
- 'e.g. https://...'.
454
- 'This can be used to override the primary image that will be '.
455
- 'used on Facebook for this product. If blank, the primary '.
456
- 'product image in Woo will be used as the primary image on FB.',
457
- 'facebook-for-woocommerce'),
458
- 'cols' => 40,
459
- 'rows' => 10,
460
- 'value' => $image,
461
- ));
462
- woocommerce_wp_text_input(
463
- array(
464
- 'id' => WC_Facebook_Product::FB_PRODUCT_PRICE,
465
- 'label' => __('Facebook Price (' .
466
- get_woocommerce_currency_symbol() . ')', 'facebook-for-woocommerce'),
467
- 'desc_tip' => 'true',
468
- 'description' => __(
469
- 'Custom price for product on Facebook. '.
470
- 'Please enter in monetary decimal (.) format without thousand '.
471
- 'separators and currency symbols. '.
472
- 'If blank, product price will be used. ',
473
- 'facebook-for-woocommerce'),
474
- 'cols' => 40,
475
- 'rows' => 60,
476
- 'value' => $price,
477
- ));
478
- if ($image_setting !== null) {
479
- woocommerce_wp_checkbox(array(
480
- 'id' => self::FB_VARIANT_IMAGE,
481
- 'label' => __('Use Parent Image', 'facebook-for-woocommerce'),
482
- 'required' => false,
483
- 'desc_tip' => 'true',
484
- 'description' => __(
485
- ' By default, the primary image uploaded to Facebook is the image'.
486
- ' specified in each variant, if provided. '.
487
- ' However, if you enable this setting, the '.
488
- ' image of the parent will be used as the primary image'.
489
- ' for this product and all its variants instead.'),
490
- 'value' => $image_setting ? 'yes' : 'no',
491
- ));
492
- }
493
- ?>
494
- </div>
495
- </div><?php
496
- }
497
-
498
- public function fb_product_columns($existing_columns) {
499
- if (empty($existing_columns) && ! is_array($existing_columns)) {
500
- $existing_columns = array();
501
- }
502
-
503
- $columns = array();
504
- $columns['fb'] = __('FB Shop', 'facebook-for-woocommerce');
505
-
506
- // Verify that cart URL hasn't changed. We do it here because this page
507
- // is most likely to be visited (so it's a handy place to make the check)
508
- $cart_url = get_option(self::FB_CART_URL);
509
- if (!empty($cart_url) && (wc_get_cart_url() !== $cart_url)) {
510
- $this->display_warning_message('One or more of your products is using a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
511
  checkout URL that may be different than your shop checkout URL.
512
  <a href="' . WOOCOMMERCE_FACEBOOK_PLUGIN_SETTINGS_URL . '">
513
- Re-sync your products to update checkout URLs on Facebook.</a>');
514
- }
515
-
516
- return array_merge($columns, $existing_columns);
517
- }
518
-
519
- public function fb_render_product_columns($column) {
520
- global $post, $the_product;
521
-
522
- wp_enqueue_script('wc_facebook_jsx', plugins_url(
523
- '/assets/js/facebook-products.js?ts=' . time(), __FILE__));
524
-
525
- if (empty($the_product) || $the_product->get_id() != $post->ID) {
526
- $the_product = new WC_Facebook_Product($post);
527
- }
528
-
529
- if ($column === 'fb') {
530
- $fb_product_group_id = $this->get_product_fbid(
531
- self::FB_PRODUCT_GROUP_ID,
532
- $post->ID,
533
- $the_product);
534
- if (!$fb_product_group_id) {
535
- printf('<span>Not Synced</span>');
536
- } else {
537
- $viz_value = get_post_meta($post->ID, self::FB_VISIBILITY, true);
538
- $data_tip = $viz_value === '' ?
539
- 'Product is synced but not marked as published (visible)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
540
  on Facebook.' :
541
- 'Product is synced and published (visible) on Facebook.';
542
 
543
- printf('<span class="tips" id="tip_%1$s" data-tip="%2$s">',
544
- $post->ID,
545
- $data_tip);
 
 
546
 
547
- if ($viz_value === '') {
548
- printf(
549
- '<a id="viz_%1$s" class="button button-primary button-large"
550
  href="javascript:;" onclick="fb_toggle_visibility(%1$s, true)">Show</a>',
551
- $post->ID);
552
- } else {
553
- printf(
554
- '<a id="viz_%1$s" class="button" href="javascript:;"
 
555
  onclick="fb_toggle_visibility(%1$s, false)">Hide</a>',
556
- $post->ID);
557
- }
558
- }
559
- }
560
- }
561
-
562
- public function fb_product_metabox() {
563
- wp_enqueue_script('wc_facebook_jsx', plugins_url(
564
- '/assets/js/facebook-metabox.js?ts=' . time(), __FILE__));
565
-
566
- add_meta_box(
567
- 'facebook_metabox', // Meta box ID
568
- 'Facebook', // Meta box Title
569
- array($this, 'fb_product_meta_box_html'), // Callback
570
- 'product', // Screen to which to add the meta box
571
- 'side' // Context
572
  );
573
- }
574
-
575
- public function fb_product_meta_box_html() {
576
- global $post;
577
- $woo_product = new WC_Facebook_Product($post->ID);
578
- $fb_product_group_id = $this->get_product_fbid(
579
- self::FB_PRODUCT_GROUP_ID,
580
- $post->ID,
581
- $woo_product);
582
- printf('<span id="fb_metadata">');
583
- if ($fb_product_group_id) {
584
- printf('Facebook ID: <a href="https://facebook.com/'.
585
- $fb_product_group_id . '" target="_blank">' .
586
- $fb_product_group_id . '</a><p/>');
587
- if (WC_Facebookcommerce_Utils::is_variable_type($woo_product->get_type())) {
588
- printf('<p>Variant IDs:<br/>');
589
- $children = $woo_product->get_children();
590
- foreach ($children as $child_id) {
591
- $fb_product_item_id = $this->get_product_fbid(
592
- self::FB_PRODUCT_ITEM_ID,
593
- $child_id);
594
- printf($child_id .' : <a href="https://facebook.com/'.
595
- $fb_product_item_id . '" target="_blank">' .
596
- $fb_product_item_id . '</a><br/>');
597
- }
598
- printf('</p>');
599
- }
600
-
601
- $checkbox_value = get_post_meta($post->ID, self::FB_VISIBILITY, true);
602
-
603
- printf('Visible: <input name="%1$s" type="checkbox" value="1" %2$s/>',
604
- self::FB_VISIBILITY,
605
- $checkbox_value === '' ? '' : 'checked');
606
- printf('<p/><input name="is_product_page" type="hidden" value="1"');
607
-
608
- printf(
609
- '<p/><a href="#" onclick="fb_reset_product(%1$s)">
610
- Reset Facebook metadata</a>',
611
- $post->ID);
612
-
613
- printf(
614
- '<p/><a href="#" onclick="fb_delete_product(%1$s)">
615
- Delete product(s) on Facebook</a>',
616
- $post->ID);
617
- } else {
618
- printf("<b>This product is not yet synced to Facebook.</b>");
619
- }
620
- printf('</span>');
621
- }
622
-
623
- private function get_product_count() {
624
- $args = array(
625
- 'post_type' => 'product',
626
- 'post_status' => 'publish',
627
- 'posts_per_page' => -1,
628
- 'fields' => 'ids'
629
- );
630
- $products = new WP_Query($args);
631
-
632
- wp_reset_postdata();
633
-
634
- echo $products->found_posts;
635
- }
636
-
637
- /**
638
- * Load DIA specific JS Data
639
- */
640
- public function load_assets() {
641
- $screen = get_current_screen();
642
-
643
- // load banner assets
644
- wp_enqueue_script('wc_facebook_infobanner_jsx', plugins_url(
645
- '/assets/js/facebook-infobanner.js?ts=' . time(), __FILE__));
646
-
647
- wp_enqueue_style('wc_facebook_infobanner_css', plugins_url(
648
- '/assets/css/facebook-infobanner.css', __FILE__));
649
-
650
- if (strpos($screen->id , "page_wc-settings") == 0) {
651
- return;
652
- }
653
-
654
- if (empty($_GET['tab'])) {
655
- return;
656
- }
657
-
658
- if ('integration' !== $_GET['tab']) {
659
- return;
660
- }
661
-
662
- ?>
663
- <script>
664
- window.facebookAdsToolboxConfig = {
665
- hasGzipSupport:
666
- '<?php echo extension_loaded('zlib') ? 'true' : 'false' ?>'
667
- ,enabledPlugins: ['MESSENGER_CHAT','INSTAGRAM_SHOP', 'PAGE_SHOP']
668
- ,enableSubscription:
669
- '<?php echo class_exists('WC_Subscriptions') ? 'true' : 'false' ?>'
670
- ,popupOrigin: '<?php echo isset($_GET['url']) ? esc_js($_GET['url']) :
671
- 'https://www.facebook.com/' ?>'
672
- ,feedWasDisabled: 'true'
673
- ,platform: 'WooCommerce'
674
- ,pixel: {
675
- pixelId: '<?php echo $this->pixel_id ?: '' ?>'
676
- ,advanced_matching_supported: true
677
- }
678
- ,diaSettingId: '<?php echo $this->external_merchant_settings_id ?: '' ?>'
679
- ,store: {
680
- baseUrl: window.location.protocol + '//' + window.location.host
681
- ,baseCurrency:
682
- '<?php echo esc_js(
683
- WC_Admin_Settings::get_option('woocommerce_currency'))?>'
684
- ,timezoneId: '<?php echo date('Z') ?>'
685
- ,storeName:
686
- '<?php echo esc_js(WC_Facebookcommerce_Utils::get_store_name()); ?>'
687
- ,version: '<?php echo WC()->version ?>'
688
- ,php_version: '<?php echo PHP_VERSION ?>'
689
- ,plugin_version:
690
- '<?php echo WC_Facebookcommerce_Utils::PLUGIN_VERSION ?>'
691
- }
692
- ,feed: {
693
- totalVisibleProducts: '<?php echo $this->get_product_count() ?>'
694
- ,hasClientSideFeedUpload: '<?php echo !!$this->feed_id ?>'
695
- }
696
- ,feedPrepared: {
697
- feedUrl: ''
698
- ,feedPingUrl: ''
699
- ,samples: <?php echo $this->get_sample_product_feed()?>
700
- }
701
- ,tokenExpired: '<?php echo $this->settings['fb_api_key'] &&
702
- !$this->get_page_name()?>'
703
- };
704
- </script>
705
- <?php
706
- $ajax_data = array(
707
- 'nonce' => wp_create_nonce( 'wc_facebook_settings_jsx' ),
708
- );
709
- wp_enqueue_script('wc_facebook_settings_jsx', plugins_url(
710
- '/assets/js/facebook-settings.js?ts=' . time(), __FILE__));
711
- wp_localize_script(
712
- 'wc_facebook_settings_jsx',
713
- 'wc_facebook_settings_jsx',
714
  $ajax_data
715
  );
716
- wp_enqueue_style('wc_facebook_css', plugins_url(
717
- '/assets/css/facebook.css', __FILE__));
718
- }
719
-
720
- function on_product_delete($wp_id) {
721
- $woo_product = new WC_Facebook_Product($wp_id);
722
- if (!$woo_product->exists()) {
723
- // This happens when the wp_id is not a product or it's already
724
- // been deleted.
725
- return;
726
- }
727
- $fb_product_group_id = $this->get_product_fbid(
728
- self::FB_PRODUCT_GROUP_ID,
729
- $wp_id,
730
- $woo_product);
731
- $fb_product_item_id = $this->get_product_fbid(
732
- self::FB_PRODUCT_ITEM_ID,
733
- $wp_id,
734
- $woo_product);
735
- if (! ($fb_product_group_id || $fb_product_item_id ) ) {
736
- return; // No synced product, no-op.
737
- }
738
- $products = array($wp_id);
739
- if (WC_Facebookcommerce_Utils::is_variable_type($woo_product->get_type())) {
740
- $children = $woo_product->get_children();
741
- $products = array_merge($products, $children);
742
- }
743
- foreach ($products as $item_id) {
744
- $this->delete_product_item($item_id);
745
- }
746
- if ($fb_product_group_id) {
747
- $pg_result = $this->fbgraph->delete_product_group($fb_product_group_id);
748
- WC_Facebookcommerce_Utils::log($pg_result);
749
- }
750
- }
751
-
752
- /**
753
- * Update FB visibility for trashing and restore.
754
- */
755
- function fb_change_product_published_status($new_status, $old_status, $post) {
756
- global $post;
757
- $visibility = $new_status == 'publish' ? 'published' : 'staging';
758
-
759
- // change from publish status -> unpublish status, e.g. trash, draft, etc.
760
- // change from trash status -> publish status
761
- // no need to update for change from trash <-> unpublish status
762
- if (($old_status == 'publish' && $new_status != 'publish') ||
763
- ($old_status == 'trash' && $new_status == 'publish')) {
764
- $this->update_fb_visibility($post->ID, $visibility);
765
- }
766
- }
767
-
768
- /**
769
- * Generic function for use with any product publishing.
770
- * Will determine product type (simple or variable) and delegate to
771
- * appropriate handler.
772
- */
773
- function on_product_publish($wp_id) {
774
- if (get_post_status($wp_id) != 'publish') {
775
- return;
776
- }
777
-
778
- $woo_product = new WC_Facebook_Product($wp_id);
779
- $product_type = $woo_product->get_type();
780
- if (WC_Facebookcommerce_Utils::is_variable_type($woo_product->get_type())) {
781
- $this->on_variable_product_publish($wp_id, $woo_product);
782
- } else {
783
- $this->on_simple_product_publish($wp_id, $woo_product);
784
- }
785
- }
786
-
787
- /**
788
- * If the user has opt-in to remove products that are out of stock,
789
- * this function will delete the product from FB Page as well.
790
- */
791
- function delete_on_out_of_stock($wp_id, $woo_product) {
792
- if (get_option('woocommerce_hide_out_of_stock_items') === 'yes' &&
793
- !$woo_product->is_in_stock()) {
794
- $this->delete_product_item($wp_id);
795
- return true;
796
- }
797
- return false;
798
- }
799
-
800
- function on_variable_product_publish($wp_id, $woo_product = null) {
801
- if (get_option('fb_disable_sync_on_dev_environment', false)) {
802
- return;
803
- }
804
-
805
- if (get_post_status($wp_id) != 'publish') {
806
- return;
807
- }
808
- // Check if product group has been published to FB. If not, it's new.
809
- // If yes, loop through variants and see if product items are published.
810
- if (!$woo_product) {
811
- $woo_product = new WC_Facebook_Product($wp_id);
812
- }
813
-
814
- if ($this->delete_on_out_of_stock($wp_id, $woo_product)) {
815
- return;
816
- }
817
-
818
- if (isset($_POST[self::FB_PRODUCT_DESCRIPTION])) {
819
- $woo_product->set_description($_POST[self::FB_PRODUCT_DESCRIPTION]);
820
- }
821
- if (isset($_POST[WC_Facebook_Product::FB_PRODUCT_PRICE])) {
822
- $woo_product->set_price($_POST[WC_Facebook_Product::FB_PRODUCT_PRICE]);
823
- }
824
- if (isset($_POST[WC_Facebook_Product::FB_PRODUCT_IMAGE])) {
825
- $woo_product->set_product_image($_POST[WC_Facebook_Product::FB_PRODUCT_IMAGE]);
826
- }
827
-
828
- $woo_product->set_use_parent_image(
829
- (isset($_POST[self::FB_VARIANT_IMAGE])) ?
830
- $_POST[self::FB_VARIANT_IMAGE] :
831
- null);
832
- $fb_product_group_id = $this->get_product_fbid(
833
- self::FB_PRODUCT_GROUP_ID,
834
- $wp_id,
835
- $woo_product);
836
-
837
- if ($fb_product_group_id) {
838
- $woo_product->update_visibility(
839
- isset($_POST['is_product_page']),
840
- isset($_POST[self::FB_VISIBILITY]));
841
- $this->update_product_group($woo_product);
842
- $child_products = $woo_product->get_children();
843
- $variation_id = $woo_product->find_matching_product_variation();
844
- // check if item_id is default variation. If yes, update in the end.
845
- // If default variation value is to update, delete old fb_product_item_id
846
- // and create new one in order to make it order correctly.
847
- foreach ($child_products as $item_id) {
848
- $fb_product_item_id =
849
- $this->on_simple_product_publish($item_id, null, $woo_product);
850
- if ($item_id == $variation_id && $fb_product_item_id) {
851
- $this->set_default_variant($fb_product_group_id, $fb_product_item_id);
852
- }
853
- }
854
- } else {
855
- $this->create_product_variable($woo_product);
856
- }
857
- }
858
-
859
- function on_simple_product_publish(
860
- $wp_id,
861
- $woo_product = null,
862
- &$parent_product = null) {
863
- if (get_option('fb_disable_sync_on_dev_environment', false)) {
864
- return;
865
- }
866
-
867
- if (get_post_status($wp_id) != 'publish') {
868
- return;
869
- }
870
-
871
- if (!$woo_product) {
872
- $woo_product = new WC_Facebook_Product($wp_id, $parent_product);
873
- }
874
-
875
- if ($this->delete_on_out_of_stock($wp_id, $woo_product)) {
876
- return;
877
- }
878
-
879
- if (isset($_POST[self::FB_PRODUCT_DESCRIPTION])) {
880
- $woo_product->set_description($_POST[self::FB_PRODUCT_DESCRIPTION]);
881
- }
882
-
883
- if (isset($_POST[WC_Facebook_Product::FB_PRODUCT_PRICE])) {
884
- $woo_product->set_price($_POST[WC_Facebook_Product::FB_PRODUCT_PRICE]);
885
- }
886
-
887
- if (isset($_POST[WC_Facebook_Product::FB_PRODUCT_IMAGE])) {
888
- $woo_product->set_product_image($_POST[WC_Facebook_Product::FB_PRODUCT_IMAGE]);
889
- }
890
-
891
- // Check if this product has already been published to FB.
892
- // If not, it's new!
893
- $fb_product_item_id = $this->get_product_fbid(
894
- self::FB_PRODUCT_ITEM_ID,
895
- $wp_id,
896
- $woo_product);
897
-
898
- if ($fb_product_item_id) {
899
- $woo_product->update_visibility(
900
- isset($_POST['is_product_page']),
901
- isset($_POST[self::FB_VISIBILITY]));
902
- $this->update_product_item($woo_product, $fb_product_item_id);
903
- return $fb_product_item_id;
904
- } else {
905
- // Check if this is a new product item for an existing product group
906
- if ($woo_product->get_parent_id()) {
907
- $fb_product_group_id = $this->get_product_fbid(
908
- self::FB_PRODUCT_GROUP_ID,
909
- $woo_product->get_parent_id(),
910
- $woo_product);
911
-
912
- // New variant added
913
- if ($fb_product_group_id) {
914
- return
915
- $this->create_product_simple($woo_product, $fb_product_group_id);
916
- } else {
917
- WC_Facebookcommerce_Utils::fblog(
918
- "Wrong! simple_product_publish called without group ID for
919
- a variable product!", array(), true);
920
- }
921
- } else {
922
- return $this->create_product_simple($woo_product); // new product
923
- }
924
- }
925
- }
926
-
927
- function create_product_variable($woo_product) {
928
- $retailer_id = WC_Facebookcommerce_Utils::get_fb_retailer_id($woo_product);
929
-
930
- $fb_product_group_id = $this->create_product_group(
931
- $woo_product,
932
- $retailer_id,
933
- true);
934
-
935
- if ($fb_product_group_id) {
936
- $child_products = $woo_product->get_children();
937
- $variation_id = $woo_product->find_matching_product_variation();
938
- foreach ($child_products as $item_id) {
939
- $child_product = new WC_Facebook_Product($item_id, $woo_product);
940
- $retailer_id =
941
- WC_Facebookcommerce_Utils::get_fb_retailer_id($child_product);
942
- $fb_product_item_id = $this->create_product_item(
943
- $child_product,
944
- $retailer_id,
945
- $fb_product_group_id);
946
- if ($item_id == $variation_id && $fb_product_item_id) {
947
- $this->set_default_variant($fb_product_group_id, $fb_product_item_id);
948
- }
949
- }
950
- }
951
- }
952
-
953
- /**
954
- * Create product group and product, store fb-specific info
955
- **/
956
- function create_product_simple($woo_product, $fb_product_group_id = null) {
957
- $retailer_id = WC_Facebookcommerce_Utils::get_fb_retailer_id($woo_product);
958
-
959
- if (!$fb_product_group_id) {
960
- $fb_product_group_id = $this->create_product_group(
961
- $woo_product,
962
- $retailer_id);
963
- }
964
-
965
- if ($fb_product_group_id) {
966
- $fb_product_item_id = $this->create_product_item(
967
- $woo_product,
968
- $retailer_id,
969
- $fb_product_group_id);
970
- return $fb_product_item_id;
971
- }
972
- }
973
-
974
- function create_product_group($woo_product, $retailer_id, $variants = false) {
975
-
976
- $product_group_data = array(
977
- 'retailer_id' => $retailer_id,
978
- );
979
 
980
- // Default visibility on create = published
981
- $woo_product->fb_visibility = true;
982
- update_post_meta($woo_product->get_id(), self::FB_VISIBILITY, true);
983
-
984
- if ($variants) {
985
- $product_group_data['variants'] =
986
- $woo_product->prepare_variants_for_group();
987
- }
988
-
989
- $create_product_group_result = $this->check_api_result(
990
- $this->fbgraph->create_product_group(
991
- $this->product_catalog_id,
992
- $product_group_data),
993
- $product_group_data,
994
- $woo_product->get_id());
995
-
996
- // New variant added
997
- if ($create_product_group_result) {
998
- $decode_result = WC_Facebookcommerce_Utils::decode_json($create_product_group_result['body']);
999
- $fb_product_group_id = $decode_result->id;
1000
- // update_post_meta is actually more of a create_or_update
1001
- update_post_meta(
1002
- $woo_product->get_id(),
1003
- self::FB_PRODUCT_GROUP_ID,
1004
- $fb_product_group_id);
1005
-
1006
- $this->display_success_message(
1007
- 'Created product group <a href="https://facebook.com/'.
1008
- $fb_product_group_id . '" target="_blank">' .
1009
- $fb_product_group_id . '</a> on Facebook.');
1010
-
1011
- return $fb_product_group_id;
1012
- }
1013
- }
1014
-
1015
- function create_product_item($woo_product, $retailer_id, $product_group_id) {
1016
- // Default visibility on create = published
1017
- $woo_product->fb_visibility = true;
1018
- $product_data = $woo_product->prepare_product($retailer_id);
1019
- if (!$product_data['price']) {
1020
- return 0;
1021
- }
1022
-
1023
- update_post_meta($woo_product->get_id(), self::FB_VISIBILITY, true);
1024
-
1025
- $product_result = $this->check_api_result(
1026
- $this->fbgraph->create_product_item(
1027
- $product_group_id,
1028
- $product_data),
1029
- $product_data,
1030
- $woo_product->get_id());
1031
-
1032
- if ($product_result) {
1033
- $decode_result = WC_Facebookcommerce_Utils::decode_json($product_result['body']);
1034
- $fb_product_item_id = $decode_result->id;
1035
-
1036
- update_post_meta($woo_product->get_id(),
1037
- self::FB_PRODUCT_ITEM_ID, $fb_product_item_id);
1038
-
1039
- $this->display_success_message(
1040
- 'Created product item <a href="https://facebook.com/'.
1041
- $fb_product_item_id . '" target="_blank">' .
1042
- $fb_product_item_id . '</a> on Facebook.');
1043
-
1044
- return $fb_product_item_id;
1045
- }
1046
- }
1047
-
1048
-
1049
- /**
1050
- * Update existing product group (variant data only)
1051
- **/
1052
- function update_product_group($woo_product) {
1053
- $fb_product_group_id = $this->get_product_fbid(
1054
- self::FB_PRODUCT_GROUP_ID,
1055
- $woo_product->get_id(),
1056
- $woo_product);
1057
-
1058
- if (!$fb_product_group_id) {
1059
- return;
1060
- }
1061
-
1062
- $variants = $woo_product->prepare_variants_for_group();
1063
-
1064
- if (!$variants) {
1065
- WC_Facebookcommerce_Utils::log(
1066
- sprintf(__('Nothing to update for product group for %1$s',
1067
- 'facebook-for-woocommerce'),
1068
- $fb_product_group_id));
1069
- return;
1070
- }
1071
-
1072
- $product_group_data = array(
1073
- 'variants' => $variants
1074
- );
1075
 
1076
- $result = $this->check_api_result(
1077
- $this->fbgraph->update_product_group(
1078
- $fb_product_group_id,
1079
- $product_group_data));
1080
-
1081
- if ($result) {
1082
- $this->display_success_message(
1083
- 'Updated product group <a href="https://facebook.com/'.
1084
- $fb_product_group_id .'" target="_blank">' . $fb_product_group_id .
1085
- '</a> on Facebook.');
1086
- }
1087
- }
1088
-
1089
- /**
1090
- * Update existing product
1091
- **/
1092
- function update_product_item($woo_product, $fb_product_item_id) {
1093
- $product_data = $woo_product->prepare_product();
1094
-
1095
- $result = $this->check_api_result(
1096
- $this->fbgraph->update_product_item(
1097
- $fb_product_item_id,
1098
- $product_data));
1099
-
1100
- if ($result) {
1101
- $this->display_success_message(
1102
- 'Updated product <a href="https://facebook.com/'. $fb_product_item_id .
1103
- '" target="_blank">' . $fb_product_item_id . '</a> on Facebook.');
1104
- }
1105
- }
1106
-
1107
- /**
1108
- * Save settings via AJAX (to preserve window context for onboarding)
1109
- **/
1110
- function ajax_save_fb_settings() {
1111
- WC_Facebookcommerce_Utils::check_woo_ajax_permissions('save settings', true);
1112
-
1113
- if (isset($_REQUEST)) {
1114
- if (!isset($_REQUEST['facebook_for_woocommerce'])) {
1115
- // This is not a request from our plugin,
1116
- // some other handler or plugin probably
1117
- // wants to handle it and wp_die() after.
1118
- return;
1119
- }
1120
-
1121
- if (isset($_REQUEST['api_key']) && ctype_alnum($_REQUEST['api_key'])) {
1122
- $this->settings['fb_api_key'] = $_REQUEST['api_key'];
1123
- }
1124
- if (isset($_REQUEST['product_catalog_id']) &&
1125
- ctype_digit($_REQUEST['product_catalog_id'])) {
1126
-
1127
- if ($this->product_catalog_id != '' &&
1128
- $this->product_catalog_id != $_REQUEST['product_catalog_id']) {
1129
- $this->reset_all_products();
1130
- }
1131
- $this->settings['fb_product_catalog_id'] =
1132
- $_REQUEST['product_catalog_id'];
1133
- }
1134
- if (isset($_REQUEST['pixel_id']) && ctype_digit($_REQUEST['pixel_id'])) {
1135
- // To prevent race conditions with pixel-only settings,
1136
- // only save a pixel if we already have an API key.
1137
- if ($this->settings['fb_api_key']) {
1138
- $this->settings['fb_pixel_id'] = $_REQUEST['pixel_id'];
1139
- if ($this->pixel_id != $_REQUEST['pixel_id']) {
1140
- $this->settings['pixel_install_time'] = current_time('mysql');
1141
- }
1142
- } else {
1143
- WC_Facebookcommerce_Utils::log(
1144
- "Got pixel-only settings, doing nothing");
1145
- echo "Not saving pixel-only settings";
1146
- wp_die();
1147
- }
1148
- }
1149
- if (isset($_REQUEST['pixel_use_pii'])) {
1150
- $this->settings['fb_pixel_use_pii'] =
1151
- ($_REQUEST['pixel_use_pii'] === 'true' ||
1152
- $_REQUEST['pixel_use_pii'] === true) ? 'yes' : 'no';
1153
- }
1154
- if (isset($_REQUEST['page_id']) &&
1155
- ctype_digit($_REQUEST['page_id'])) {
1156
- $this->settings['fb_page_id'] = $_REQUEST['page_id'];
1157
- }
1158
- if (isset($_REQUEST['external_merchant_settings_id']) &&
1159
- ctype_digit($_REQUEST['external_merchant_settings_id'])) {
1160
- $this->settings['fb_external_merchant_settings_id'] =
1161
- $_REQUEST['external_merchant_settings_id'];
1162
- }
1163
- if (isset($_REQUEST['is_messenger_chat_plugin_enabled'])) {
1164
- $this->settings['is_messenger_chat_plugin_enabled'] =
1165
- ($_REQUEST['is_messenger_chat_plugin_enabled'] === 'true' ||
1166
- $_REQUEST['is_messenger_chat_plugin_enabled'] === true) ? 'yes' : 'no';
1167
- }
1168
- if (isset($_REQUEST['facebook_jssdk_version'])) {
1169
- $this->settings['facebook_jssdk_version'] =
1170
- sanitize_text_field($_REQUEST['facebook_jssdk_version']);
1171
- }
1172
- if (isset($_REQUEST['msger_chat_customization_greeting_text_code'])
1173
- && ctype_digit($_REQUEST['msger_chat_customization_greeting_text_code'])) {
1174
- $this->settings['msger_chat_customization_greeting_text_code'] =
1175
- $_REQUEST['msger_chat_customization_greeting_text_code'];
1176
- }
1177
- if (isset($_REQUEST['msger_chat_customization_locale'])) {
1178
- $this->settings['msger_chat_customization_locale'] =
1179
- sanitize_text_field($_REQUEST['msger_chat_customization_locale']);
1180
- }
1181
- if (isset($_REQUEST['msger_chat_customization_theme_color_code']) &&
1182
- ctype_digit($_REQUEST['msger_chat_customization_theme_color_code'])) {
1183
- $this->settings['msger_chat_customization_theme_color_code'] =
1184
- $_REQUEST['msger_chat_customization_theme_color_code'];
1185
- }
1186
-
1187
- update_option(
1188
- $this->get_option_key(),
1189
- apply_filters(
1190
- 'woocommerce_settings_api_sanitized_fields_' . $this->id,
1191
- $this->settings));
1192
-
1193
- WC_Facebookcommerce_Utils::log("Settings saved!");
1194
- echo "settings_saved";
1195
- } else {
1196
- echo "No Request";
1197
- }
1198
-
1199
- wp_die();
1200
- }
1201
-
1202
- /**
1203
- * Delete all settings via AJAX
1204
- **/
1205
- function ajax_delete_fb_settings() {
1206
- check_ajax_referer( 'wc_facebook_settings_jsx' );
1207
- if (!WC_Facebookcommerce_Utils::check_woo_ajax_permissions('delete settings', false)) {
1208
- return;
1209
- }
1210
-
1211
- // Do not allow reset in the middle of product sync
1212
- $currently_syncing = get_transient(self::FB_SYNC_IN_PROGRESS);
1213
- if ($currently_syncing) {
1214
- wp_send_json('A Facebook product sync is currently in progress.
1215
- Deleting settings during product sync may cause errors.');
1216
- return;
1217
- }
1218
-
1219
- if (isset($_REQUEST)) {
1220
- $ems = $this->settings['fb_external_merchant_settings_id'];
1221
- if ($ems) {
1222
- WC_Facebookcommerce_Utils::fblog(
1223
- "Deleted all settings!",
1224
- array(),
1225
- false,
1226
- $ems);
1227
- }
1228
-
1229
- $this->init_settings();
1230
- $this->settings['fb_api_key'] = '';
1231
- $this->settings['fb_product_catalog_id'] = '';
1232
-
1233
- $this->settings['fb_pixel_id'] = '';
1234
- $this->settings['fb_pixel_use_pii'] = 'no';
1235
-
1236
- $this->settings['fb_page_id'] = '';
1237
- $this->settings['fb_external_merchant_settings_id'] = '';
1238
- $this->settings['pixel_install_time'] = '';
1239
- $this->settings['fb_feed_id'] = '';
1240
- $this->settings['fb_upload_id'] = '';
1241
- $this->settings['upload_end_time'] = '';
1242
-
1243
- WC_Facebookcommerce_Pixel::set_pixel_id(0);
1244
-
1245
- update_option(
1246
- $this->get_option_key(),
1247
- apply_filters(
1248
- 'woocommerce_settings_api_sanitized_fields_' . $this->id,
1249
- $this->settings));
1250
-
1251
- // Clean up old messages
1252
- delete_transient('facebook_plugin_api_error');
1253
- delete_transient('facebook_plugin_api_success');
1254
- delete_transient('facebook_plugin_api_warning');
1255
- delete_transient('facebook_plugin_api_info');
1256
- delete_transient('facebook_plugin_api_sticky');
1257
-
1258
- $this->reset_all_products();
1259
-
1260
- WC_Facebookcommerce_Utils::log("Settings deleted");
1261
- echo "Settings Deleted";
1262
-
1263
- }
1264
-
1265
- wp_die();
1266
- }
1267
-
1268
- /**
1269
- * Check Feed Upload Status
1270
- **/
1271
- function ajax_check_feed_upload_status() {
1272
- WC_Facebookcommerce_Utils::check_woo_ajax_permissions('check feed upload status', true);
1273
- if ($this->settings['fb_api_key']) {
1274
- $response = array(
1275
- 'connected' => true,
1276
- 'status' => 'in progress',
1277
- );
1278
- if ($this->settings['fb_upload_id']) {
1279
- if (!isset($this->fbproductfeed)) {
1280
- if (!class_exists('WC_Facebook_Product_Feed')) {
1281
- include_once 'includes/fbproductfeed.php';
1282
- }
1283
- $this->fbproductfeed = new WC_Facebook_Product_Feed(
1284
- $this->product_catalog_id, $this->fbgraph);
1285
- }
1286
- $status = $this->fbproductfeed->is_upload_complete($this->settings);
1287
-
1288
- $response['status'] = $status;
1289
- } else {
1290
- $response = array(
1291
- 'connected' => true,
1292
- 'status' => 'error',
1293
- );
1294
- }
1295
- if ($response['status'] == 'complete') {
1296
- update_option(
1297
- $this->get_option_key(),
1298
- apply_filters(
1299
- 'woocommerce_settings_api_sanitized_fields_' . $this->id,
1300
- $this->settings));
1301
- }
1302
- } else {
1303
- $response = array(
1304
- 'connected' => false,
1305
- );
1306
- }
1307
- printf(json_encode($response));
1308
- wp_die();
1309
- }
1310
-
1311
- /**
1312
- * Display custom success message (sugar)
1313
- **/
1314
- function display_success_message($msg) {
1315
- $msg = self::FB_ADMIN_MESSAGE_PREPEND . $msg;
1316
- set_transient('facebook_plugin_api_success', $msg,
1317
- self::FB_MESSAGE_DISPLAY_TIME);
1318
- }
1319
-
1320
- /**
1321
- * Display custom warning message (sugar)
1322
- **/
1323
- function display_warning_message($msg) {
1324
- $msg = self::FB_ADMIN_MESSAGE_PREPEND . $msg;
1325
- set_transient('facebook_plugin_api_warning', $msg,
1326
- self::FB_MESSAGE_DISPLAY_TIME);
1327
- }
1328
-
1329
- /**
1330
- * Display custom info message (sugar)
1331
- **/
1332
- function display_info_message($msg) {
1333
- $msg = self::FB_ADMIN_MESSAGE_PREPEND . $msg;
1334
- set_transient('facebook_plugin_api_info', $msg,
1335
- self::FB_MESSAGE_DISPLAY_TIME);
1336
- }
1337
-
1338
- /**
1339
- * Display custom "sticky" info message.
1340
- * Call remove_sticky_message or wait for time out.
1341
- **/
1342
- function display_sticky_message($msg) {
1343
- $msg = self::FB_ADMIN_MESSAGE_PREPEND . $msg;
1344
- set_transient('facebook_plugin_api_sticky', $msg,
1345
- self::FB_MESSAGE_DISPLAY_TIME);
1346
- }
1347
-
1348
- /**
1349
- * Remove custom "sticky" info message
1350
- **/
1351
- function remove_sticky_message() {
1352
- delete_transient('facebook_plugin_api_sticky');
1353
- }
1354
-
1355
- function remove_resync_message() {
1356
- $msg = get_transient('facebook_plugin_api_sticky');
1357
- if ($msg && strpos($msg, 'Sync') !== false) {
1358
- delete_transient('facebook_plugin_resync_sticky');
1359
- }
1360
- }
1361
-
1362
- /**
1363
- * Display custom error message (sugar)
1364
- **/
1365
- function display_error_message($msg) {
1366
- $msg = self::FB_ADMIN_MESSAGE_PREPEND . $msg;
1367
- WC_Facebookcommerce_Utils::log($msg);
1368
- set_transient('facebook_plugin_api_error', $msg,
1369
- self::FB_MESSAGE_DISPLAY_TIME);
1370
- }
1371
-
1372
- /**
1373
- * Display error message from API result (sugar)
1374
- **/
1375
- function display_error_message_from_result($result) {
1376
- $msg = json_decode($result['body'])->error->message;
1377
- $this->display_error_message($msg);
1378
- }
1379
-
1380
- /**
1381
- * Deal with FB API responses, display error if FB API returns error
1382
- *
1383
- * @return result if response is 200, null otherwise
1384
- **/
1385
- function check_api_result($result, $logdata = null, $wpid = null) {
1386
- if (is_wp_error($result)) {
1387
- WC_Facebookcommerce_Utils::log($result->get_error_message());
1388
- $this->display_error_message(
1389
- "There was an issue connecting to the Facebook API: ".
1390
- $result->get_error_message());
1391
- return;
1392
- }
1393
- if ($result['response']['code'] != '200') {
1394
- // Catch 10800 fb error code ("Duplicate retailer ID") and capture FBID
1395
- // if possible, otherwise let user know we found dupe SKUs
1396
- $body = WC_Facebookcommerce_Utils::decode_json($result['body']);
1397
- if ($body && $body->error->code == '10800') {
1398
- $error_data = $body->error->error_data; // error_data may contain FBIDs
1399
- if ($error_data && $wpid) {
1400
- $existing_id = $this->get_existing_fbid($error_data, $wpid);
1401
- if ($existing_id) {
1402
- // Add "existing_id" ID to result
1403
- $body->id = $existing_id;
1404
- $result['body'] = json_encode($body);
1405
- return $result;
1406
- }
1407
- }
1408
- } else {
1409
- $this->display_error_message_from_result($result);
1410
- }
1411
-
1412
- WC_Facebookcommerce_Utils::log($result);
1413
- $data = array(
1414
- 'result' => $result,
1415
- 'data' => $logdata,
1416
- );
1417
- WC_Facebookcommerce_Utils::fblog(
1418
- 'Non-200 error code from FB',
1419
- $data,
1420
- true);
1421
- return null;
1422
- }
1423
- return $result;
1424
- }
1425
-
1426
- function ajax_woo_adv_bulk_edit_compat($import_id) {
1427
- if (!WC_Facebookcommerce_Utils::check_woo_ajax_permissions('adv bulk edit', false)) {
1428
- return;
1429
- }
1430
- $type = isset($_POST["type"]) ? $_POST["type"] : "";
1431
- if (strpos($type, 'product') !== false && strpos($type, 'load') === false) {
1432
- $this->display_out_of_sync_message("advanced bulk edit");
1433
- }
1434
- }
1435
-
1436
- function wp_all_import_compat($import_id) {
1437
- $import = new PMXI_Import_Record();
1438
- $import->getById($import_id);
1439
- if (!$import->isEmpty() && in_array($import->options['custom_type'], array('product', 'product_variation'))) {
1440
- $this->display_out_of_sync_message("import");
1441
- }
1442
- }
1443
-
1444
- function display_out_of_sync_message($action_name) {
1445
- $this->display_sticky_message(
1446
- sprintf(
1447
- 'Products may be out of Sync with Facebook due to your recent '.$action_name.'.'.
1448
- ' <a href="%s&fb_force_resync=true&remove_sticky=true">Re-Sync them with FB.</a>',
1449
- WOOCOMMERCE_FACEBOOK_PLUGIN_SETTINGS_URL));
1450
- }
1451
-
1452
- /**
1453
- * If we get a product group ID or product item ID back for a dupe retailer
1454
- * id error, update existing ID.
1455
- *
1456
- * @return null
1457
- **/
1458
- function get_existing_fbid($error_data, $wpid) {
1459
- if (isset($error_data->product_group_id)) {
1460
- update_post_meta(
1461
- $wpid,
1462
- self::FB_PRODUCT_GROUP_ID,
1463
- (string)$error_data->product_group_id);
1464
- return $error_data->product_group_id;
1465
- }
1466
- else if (isset($error_data->product_item_id)) {
1467
- update_post_meta(
1468
- $wpid,
1469
- self::FB_PRODUCT_ITEM_ID,
1470
- (string)$error_data->product_item_id);
1471
- return $error_data->product_item_id;
1472
- } else {
1473
- return;
1474
- }
1475
- }
1476
-
1477
- /**
1478
- * Check for api key and any other API errors
1479
- **/
1480
- function checks() {
1481
- // Check required fields
1482
-
1483
- if (!$this->api_key || !$this->product_catalog_id) {
1484
- echo $this->get_message_html(sprintf(__('%1$sFacebook for WooCommerce
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1485
  is almost ready.%2$s To complete your configuration, %3$scomplete the
1486
  setup steps%4$s.',
1487
- 'facebook-for-woocommerce'), '<strong>', '</strong>',
1488
-
1489
- '<a href="' . esc_url(WOOCOMMERCE_FACEBOOK_PLUGIN_SETTINGS_URL) . '">',
1490
- '</a>'), 'info');
1491
- }
1492
-
1493
- // WooCommerce 2.x upgrade nag
1494
- if ($this->api_key && (!isset($this->background_processor))) {
1495
- echo $this->get_message_html(sprintf(__(
1496
- 'Facebook product sync may not work correctly in WooCommerce version
 
 
 
 
 
 
 
1497
  %1$s. Please upgrade to WooCommerce 3.',
1498
- 'facebook-for-woocommerce'), WC()->version), 'warning');
1499
- }
1500
-
1501
- $this->maybe_display_facebook_api_messages();
1502
- }
1503
-
1504
- function get_sample_product_feed() {
1505
- ob_start();
1506
-
1507
- // Get up to 12 published posts that are products
1508
- $args = array(
1509
- 'post_type' => 'product',
1510
- 'post_status' => 'publish',
1511
- 'posts_per_page' => 12,
1512
- 'fields' => 'ids'
1513
- );
1514
-
1515
- $post_ids = get_posts($args);
1516
- $items = array();
1517
-
1518
- foreach ($post_ids as $post_id) {
1519
-
1520
- $woo_product = new WC_Facebook_Product($post_id);
1521
- $product_data = $woo_product->prepare_product();
1522
-
1523
- $feed_item = array(
1524
- 'title' => strip_tags($product_data['name']),
1525
- 'availability' => $woo_product->is_in_stock() ? 'in stock' :
1526
- 'out of stock',
1527
- 'description' => strip_tags($product_data['description']),
1528
- 'id' => $product_data['retailer_id'],
1529
- 'image_link' => $product_data['image_url'],
1530
- 'brand' => strip_tags(WC_Facebookcommerce_Utils::get_store_name()),
1531
- 'link' => $product_data['url'],
1532
- 'price' => $product_data['price'] . ' ' . get_woocommerce_currency(),
1533
- );
1534
-
1535
- array_push($items, $feed_item);
1536
- }
1537
- // https://codex.wordpress.org/Function_Reference/wp_reset_postdata
1538
- wp_reset_postdata();
1539
- ob_end_clean();
1540
- return json_encode(array($items));
1541
- }
1542
-
1543
- /**
1544
- * Loop through array of WPIDs to remove metadata.
1545
- **/
1546
- function delete_post_meta_loop($products) {
1547
- foreach ($products as $product_id) {
1548
- delete_post_meta($product_id, self::FB_PRODUCT_GROUP_ID);
1549
- delete_post_meta($product_id, self::FB_PRODUCT_ITEM_ID);
1550
- delete_post_meta($product_id, self::FB_VISIBILITY);
1551
- }
1552
- }
1553
-
1554
- /**
1555
- * Remove FBIDs from all products when resetting store.
1556
- **/
1557
- function reset_all_products() {
1558
- if (!is_admin()) {
1559
- WC_Facebookcommerce_Utils::log("Not resetting any FBIDs from products,
1560
- must call reset from admin context.");
1561
- return false;
1562
- }
1563
-
1564
- $test_instance = WC_Facebook_Integration_Test::get_instance($this);
1565
- $this->test_mode = $test_instance::$test_mode;
1566
-
1567
- // Include draft products (omit 'post_status' => 'publish')
1568
- WC_Facebookcommerce_Utils::log("Removing FBIDs from all products");
1569
-
1570
- $post_ids = get_posts(array(
1571
- 'post_type' => 'product',
1572
- 'posts_per_page' => -1,
1573
- 'fields' => 'ids'
1574
- ));
1575
-
1576
- $children = array();
1577
- foreach ($post_ids as $post_id) {
1578
- $children = array_merge(get_posts(array(
1579
- 'post_type' => 'product_variation',
1580
- 'posts_per_page' => -1,
1581
- 'post_parent' => $post_id,
1582
- 'fields' => 'ids'
1583
- )), $children);
1584
- }
1585
- $post_ids = array_merge($post_ids, $children);
1586
- $this->delete_post_meta_loop($post_ids);
1587
-
1588
- WC_Facebookcommerce_Utils::log("Product FBIDs deleted");
1589
- return true;
1590
- }
1591
-
1592
- /**
1593
- * Remove FBIDs from a single WC product
1594
- **/
1595
- function reset_single_product($wp_id) {
1596
- $woo_product = new WC_Facebook_Product($wp_id);
1597
- $products = array($woo_product->get_id());
1598
- if (WC_Facebookcommerce_Utils::is_variable_type($woo_product->get_type())) {
1599
- $products = array_merge($products, $woo_product->get_children());
1600
- }
1601
-
1602
- $this->delete_post_meta_loop($products);
1603
-
1604
- WC_Facebookcommerce_Utils::log("Deleted FB Metadata for product " . $wp_id);
1605
- }
1606
-
1607
- function ajax_reset_all_fb_products() {
1608
- WC_Facebookcommerce_Utils::check_woo_ajax_permissions('reset products', true);
1609
- $this->reset_all_products();
1610
- wp_reset_postdata();
1611
- wp_die();
1612
- }
1613
-
1614
- function ajax_reset_single_fb_product() {
1615
- WC_Facebookcommerce_Utils::check_woo_ajax_permissions('reset single product', true);
1616
- if (!isset($_POST['wp_id'])) {
1617
- wp_die();
1618
- }
1619
-
1620
- $wp_id = sanitize_text_field($_POST['wp_id']);
1621
- $woo_product = new WC_Facebook_Product($wp_id);
1622
- if ($woo_product) {
1623
- $this->reset_single_product($wp_id);
1624
- }
1625
-
1626
- wp_reset_postdata();
1627
- wp_die();
1628
- }
1629
-
1630
- function ajax_delete_fb_product() {
1631
- WC_Facebookcommerce_Utils::check_woo_ajax_permissions('delete single product', true);
1632
- if (!isset($_POST['wp_id'])) {
1633
- wp_die();
1634
- }
1635
-
1636
- $wp_id = sanitize_text_field($_POST['wp_id']);
1637
- $this->on_product_delete($wp_id);
1638
- $this->reset_single_product($wp_id);
1639
- wp_reset_postdata();
1640
- wp_die();
1641
- }
1642
-
1643
- /**
1644
- * Special function to run all visible products through on_product_publish
1645
- **/
1646
- function ajax_sync_all_fb_products() {
1647
- WC_Facebookcommerce_Utils::check_woo_ajax_permissions('syncall products', true);
1648
- if (get_option('fb_disable_sync_on_dev_environment', false)) {
1649
- WC_Facebookcommerce_Utils::log(
1650
- 'Sync to FB Page is not allowed in Dev Environment');
1651
- wp_die();
1652
- return;
1653
- }
1654
-
1655
- if (!$this->api_key || !$this->product_catalog_id) {
1656
- WC_Facebookcommerce_Utils::log("No API key or catalog ID: " .
1657
- $this->api_key . ' and ' . $this->product_catalog_id);
1658
- wp_die();
1659
- return;
1660
- }
1661
- $this->remove_resync_message();
1662
-
1663
- $currently_syncing = get_transient(self::FB_SYNC_IN_PROGRESS);
1664
-
1665
- if (isset($this->background_processor)) {
1666
- if ($this->background_processor->is_updating()) {
1667
- $this->background_processor->handle_cron_healthcheck();
1668
- $currently_syncing = 1;
1669
- }
1670
- }
1671
-
1672
- if ($currently_syncing) {
1673
- WC_Facebookcommerce_Utils::log('Not syncing, sync in progress');
1674
- WC_Facebookcommerce_Utils::fblog(
1675
- 'Tried to sync during an in-progress sync!', array(), true);
1676
- $this->display_warning_message('A product sync is in progress.
1677
- Please wait until the sync finishes before starting a new one.');
1678
- wp_die();
1679
- return;
1680
- }
1681
-
1682
- $is_valid_product_catalog =
1683
- $this->fbgraph->validate_product_catalog($this->product_catalog_id);
1684
-
1685
- if (!$is_valid_product_catalog) {
1686
- WC_Facebookcommerce_Utils::log('Not syncing, invalid product catalog!');
1687
- WC_Facebookcommerce_Utils::fblog(
1688
- 'Tried to sync with an invalid product catalog!', array(), true);
1689
- $this->display_warning_message('We\'ve detected that your
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1690
  Facebook Product Catalog is no longer valid. This may happen if it was
1691
  deleted, or this may be a transient error.
1692
  If this error persists please remove your settings via
1693
  "Advanced Options > Advanced Settings > Remove"
1694
- and try setup again');
1695
- wp_die();
1696
- return;
1697
- }
1698
-
1699
- // Cache the cart URL to display a warning in case it changes later
1700
- $cart_url = get_option(self::FB_CART_URL);
1701
- if ($cart_url != wc_get_cart_url()) {
1702
- update_option(self::FB_CART_URL, wc_get_cart_url());
1703
- }
1704
-
1705
- $sanitized_settings = $this->settings;
1706
- unset($sanitized_settings['fb_api_key']);
1707
-
1708
- // Get all published posts. First unsynced then already-synced.
1709
- $post_ids_new = WC_Facebookcommerce_Utils::get_wp_posts(
1710
- self::FB_PRODUCT_GROUP_ID, 'NOT EXISTS');
1711
- $post_ids_old = WC_Facebookcommerce_Utils::get_wp_posts(
1712
- self::FB_PRODUCT_GROUP_ID, 'EXISTS');
1713
-
1714
- $total_new = count($post_ids_new);
1715
- $total_old = count($post_ids_old);
1716
- $post_ids = array_merge($post_ids_new, $post_ids_old);
1717
- $total = count($post_ids);
1718
-
1719
- WC_Facebookcommerce_Utils::fblog(
1720
- 'Attempting to sync ' . $total . ' ( ' .
1721
- $total_new . ' new) products with settings: ',
1722
- $sanitized_settings,
1723
- false);
1724
-
1725
- // Check for background processing (Woo 3.x.x)
1726
- if (isset($this->background_processor)) {
1727
- $starting_message = sprintf(
1728
- 'Starting background sync to Facebook: %d products...',
1729
- $total);
1730
-
1731
- set_transient(
1732
- self::FB_SYNC_IN_PROGRESS,
1733
- true,
1734
- self::FB_SYNC_TIMEOUT);
1735
-
1736
- set_transient(
1737
- self::FB_SYNC_REMAINING,
1738
- (int)$total);
1739
-
1740
- $this->display_info_message($starting_message);
1741
- WC_Facebookcommerce_Utils::log($starting_message);
1742
-
1743
- foreach ($post_ids as $post_id) {
1744
- WC_Facebookcommerce_Utils::log("Pushing post to queue: " . $post_id);
1745
- $this->background_processor->push_to_queue($post_id);
1746
- }
1747
-
1748
- $this->background_processor->save()->dispatch();
1749
- // reset FB_SYNC_REMAINING to avoid race condition
1750
- set_transient(
1751
- self::FB_SYNC_REMAINING,
1752
- (int)$total);
1753
- // handle_cron_healthcheck must be called
1754
- // https://github.com/A5hleyRich/wp-background-processing/issues/34
1755
- $this->background_processor->handle_cron_healthcheck();
1756
- } else {
1757
- // Oldschool sync for WooCommerce 2.x
1758
- $count = ($total_old === $total) ? 0 : $total_old;
1759
- foreach ($post_ids as $post_id) {
1760
- // Repeatedly overwrite sync total while in actual sync loop
1761
- set_transient(
1762
- self::FB_SYNC_IN_PROGRESS,
1763
- true,
1764
- self::FB_SYNC_TIMEOUT);
1765
-
1766
- $this->display_sticky_message(
1767
- sprintf(
1768
- 'Syncing products to Facebook: %d out of %d...',
1769
- // Display different # when resuming to avoid confusion.
1770
- min($count, $total),
1771
- $total),
1772
- true);
1773
-
1774
- $this->on_product_publish($post_id);
1775
- $count++;
1776
- }
1777
- WC_Facebookcommerce_Utils::log('Synced ' . $count . ' products');
1778
- $this->remove_sticky_message();
1779
- $this->display_info_message('Facebook product sync complete!');
1780
- delete_transient(self::FB_SYNC_IN_PROGRESS);
1781
- WC_Facebookcommerce_Utils::fblog(
1782
- 'Product sync complete. Total products synced: ' . $count);
1783
- }
1784
-
1785
- // https://codex.wordpress.org/Function_Reference/wp_reset_postdata
1786
- wp_reset_postdata();
1787
-
1788
- // This is important, for some reason.
1789
- // See https://codex.wordpress.org/AJAX_in_Plugins
1790
- wp_die();
1791
- }
1792
-
1793
- /**
1794
- * Special function to run all visible products by uploading feed.
1795
- **/
1796
- function ajax_sync_all_fb_products_using_feed() {
1797
- WC_Facebookcommerce_Utils::check_woo_ajax_permissions(
1798
- 'syncall products using feed', !$this->test_mode);
1799
- return $this->sync_all_fb_products_using_feed();
1800
- }
1801
-
1802
- // Separate entry point that bypasses permission check for use in cron.
1803
- function sync_all_fb_products_using_feed() {
1804
- if (get_option('fb_disable_sync_on_dev_environment', false)) {
1805
- WC_Facebookcommerce_Utils::log(
1806
- 'Sync to FB Page is not allowed in Dev Environment');
1807
- $this->fb_wp_die();
1808
- return false;
1809
- }
1810
-
1811
- if (!$this->api_key || !$this->product_catalog_id) {
1812
- self::log("No API key or catalog ID: " . $this->api_key .
1813
- ' and ' . $this->product_catalog_id);
1814
- $this->fb_wp_die();
1815
- return false;
1816
- }
1817
- $this->remove_resync_message();
1818
- $is_valid_product_catalog =
1819
- $this->fbgraph->validate_product_catalog($this->product_catalog_id);
1820
-
1821
- if (!$is_valid_product_catalog) {
1822
- WC_Facebookcommerce_Utils::log('Not syncing, invalid product catalog!');
1823
- WC_Facebookcommerce_Utils::fblog(
1824
- 'Tried to sync with an invalid product catalog!', array(), true);
1825
- $this->display_warning_message('We\'ve detected that your
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1826
  Facebook Product Catalog is no longer valid. This may happen if it was
1827
  deleted, or this may be a transient error.
1828
  If this error persists please remove your settings via
1829
  "Advanced Options > Advanced Settings > Remove"
1830
- and try setup again');
1831
- $this->fb_wp_die();
1832
- return false;
1833
- }
1834
-
1835
- // Cache the cart URL to display a warning in case it changes later
1836
- $cart_url = get_option(self::FB_CART_URL);
1837
- if ($cart_url != wc_get_cart_url()) {
1838
- update_option(self::FB_CART_URL, wc_get_cart_url());
1839
- }
1840
-
1841
- if (!class_exists('WC_Facebook_Product_Feed')) {
1842
- include_once 'includes/fbproductfeed.php';
1843
- }
1844
- if ($this->test_mode) {
1845
- $this->fbproductfeed = new WC_Facebook_Product_Feed_Test_Mock(
1846
- $this->product_catalog_id, $this->fbgraph, $this->feed_id);
1847
- } else {
1848
- $this->fbproductfeed = new WC_Facebook_Product_Feed(
1849
- $this->product_catalog_id, $this->fbgraph, $this->feed_id);
1850
- }
1851
-
1852
- $upload_success = $this->fbproductfeed->sync_all_products_using_feed();
1853
- if ($upload_success) {
1854
- $this->settings['fb_feed_id'] = $this->fbproductfeed->feed_id;
1855
- $this->settings['fb_upload_id'] = $this->fbproductfeed->upload_id;
1856
- update_option($this->get_option_key(),
1857
- apply_filters('woocommerce_settings_api_sanitized_fields_' .
1858
- $this->id, $this->settings));
1859
- wp_reset_postdata();
1860
- $this->fb_wp_die();
1861
- return true;
1862
- }
1863
- WC_Facebookcommerce_Utils::fblog(
1864
- 'Sync all products using feed, curl failed', array(), true);
1865
- return false;
1866
- }
1867
-
1868
- /**
1869
- * Toggles product visibility via AJAX (checks current viz and flips it)
1870
- **/
1871
- function ajax_toggle_visibility() {
1872
- WC_Facebookcommerce_Utils::check_woo_ajax_permissions('toggle visibility', true);
1873
- if (!isset($_POST['wp_id']) || ! isset($_POST['published'])) {
1874
- wp_die();
1875
- }
1876
-
1877
- $wp_id = sanitize_text_field($_POST['wp_id']);
1878
- $published = ($_POST['published']) === 'true' ? true : false;
1879
-
1880
- $woo_product = new WC_Facebook_Product($wp_id);
1881
- $products = WC_Facebookcommerce_Utils::get_product_array($woo_product);
1882
-
1883
- // Loop through product items and flip visibility
1884
- foreach ($products as $item_id) {
1885
- $fb_product_item_id = $this->get_product_fbid(
1886
- self::FB_PRODUCT_ITEM_ID,
1887
- $item_id);
1888
- $data = array(
1889
- 'visibility' => $published ? 'published' : 'staging'
1890
- );
1891
-
1892
- $result = $this->check_api_result(
1893
- $this->fbgraph->update_product_item(
1894
- $fb_product_item_id,
1895
- $data));
1896
-
1897
- if ($result) {
1898
- update_post_meta($item_id, self::FB_VISIBILITY, $published);
1899
- update_post_meta($wp_id, self::FB_VISIBILITY, $published);
1900
- }
1901
- }
1902
- wp_die();
1903
- }
1904
-
1905
- /**
1906
- * Initialize Settings Form Fields
1907
- *
1908
- * @access public
1909
- * @return void
1910
- */
1911
- function init_form_fields() {
1912
- $this->form_fields = array(
1913
- 'fb_settings_heading' => array(
1914
- 'title' => __('Debug Mode', 'facebook-for-woocommerce'),
1915
- 'type' => 'title',
1916
- 'description' => '',
1917
- 'default' => ''
1918
- ),
1919
- 'fb_page_id' => array(
1920
- 'title' => __('Facebook Page ID', 'facebook-for-woocommerce'),
1921
- 'type' => 'text',
1922
- 'description' => __('The unique identifier for your Facebook page.',
1923
- 'facebook-for-woocommerce'),
1924
- 'default' => '',
1925
- ),
1926
- 'fb_product_catalog_id' => array(
1927
- 'title' => __('Product Catalog ID', 'facebook-for-woocommerce'),
1928
- 'type' => 'text',
1929
- 'description' => __('The unique identifier for your product catalog,
1930
- on Facebook.', 'facebook-for-woocommerce'),
1931
- 'default' => ''
1932
- ),
1933
- 'fb_pixel_id' => array(
1934
- 'title' => __('Pixel ID', 'facebook-for-woocommerce'),
1935
- 'type' => 'text',
1936
- 'description' => __('The unique identifier for your Facebook pixel',
1937
- 'facebook-for-woocommerce'),
1938
- 'default' => ''
1939
- ),
1940
- 'fb_pixel_use_pii' => array(
1941
- 'title' => __('Use Advanced Matching on pixel?',
1942
- 'facebook-for-woocommerce'),
1943
- 'type' => 'checkbox',
1944
- 'description' => __('Enabling Advanced Matching
1945
- improves audience building.', 'facebook-for-woocommerce'),
1946
- 'default' => 'yes'
1947
- ),
1948
- 'fb_external_merchant_settings_id' => array(
1949
- 'title' => __('External Merchant Settings ID',
1950
- 'facebook-for-woocommerce'),
1951
- 'type' => 'text',
1952
- 'description' => __('The unique identifier for your external merchant
1953
- settings, on Facebook.', 'facebook-for-woocommerce'),
1954
- 'default' => ''
1955
- ),
1956
- 'fb_api_key' => array(
1957
- 'title' => __('API Key', 'facebook-for-woocommerce'),
1958
- 'type' => 'text',
1959
- 'description' => sprintf(__('A non-expiring Page Token with
1960
- %1$smanage_pages%2$s permissions.', 'facebook-for-woocommerce'),
1961
- '<code>', '</code>'),
1962
- 'default' => ''
1963
- ),
1964
- );
1965
-
1966
- if (!class_exists('WC_Facebookcommerce_EventsTracker')) {
1967
- include_once 'includes/fbutils.php';
1968
- }
1969
- } // End init_form_fields()
1970
-
1971
-
1972
- /**
1973
- * Get message
1974
- * @return string Error
1975
- */
1976
- private function get_message_html($message, $type = 'error') {
1977
- ob_start();
1978
-
1979
- ?>
1980
- <div class="notice is-dismissible notice-<?php echo $type ?>">
1981
- <p><?php echo $message ?></p>
1982
- </div>
1983
- <?php
1984
- return ob_get_clean();
1985
- }
1986
-
1987
- /**
1988
- * Display relevant messages to user from transients, clear once displayed
1989
- *
1990
- * @param void
1991
- */
1992
- public function maybe_display_facebook_api_messages() {
1993
- $error_msg = get_transient('facebook_plugin_api_error');
1994
-
1995
- if ($error_msg) {
1996
- echo $this->get_message_html(sprintf(__('Facebook extension error: %s ',
1997
- 'facebook-for-woocommerce'), $error_msg));
1998
- delete_transient('facebook_plugin_api_error');
1999
-
2000
- WC_Facebookcommerce_Utils::fblog(
2001
- $error_msg,
2002
- array(),
2003
- true);
2004
- }
2005
-
2006
- $warning_msg = get_transient('facebook_plugin_api_warning');
2007
-
2008
- if ($warning_msg) {
2009
- echo $this->get_message_html(__($warning_msg, 'facebook-for-woocommerce'),
2010
- 'warning');
2011
- delete_transient('facebook_plugin_api_warning');
2012
- }
2013
-
2014
- $success_msg = get_transient('facebook_plugin_api_success');
2015
-
2016
- if ($success_msg) {
2017
- echo $this->get_message_html(__($success_msg, 'facebook-for-woocommerce'),
2018
- 'success');
2019
- delete_transient('facebook_plugin_api_success');
2020
- }
2021
-
2022
- $info_msg = get_transient('facebook_plugin_api_info');
2023
-
2024
- if ($info_msg) {
2025
- echo $this->get_message_html(__($info_msg, 'facebook-for-woocommerce'),
2026
- 'info');
2027
- delete_transient('facebook_plugin_api_info');
2028
- }
2029
-
2030
- $sticky_msg = get_transient('facebook_plugin_api_sticky');
2031
-
2032
- if ($sticky_msg) {
2033
- echo $this->get_message_html(__($sticky_msg, 'facebook-for-woocommerce'),
2034
- 'info');
2035
- // Transient must be deleted elsewhere, or wait for timeout
2036
- }
2037
-
2038
- }
2039
-
2040
- function get_page_name() {
2041
- $page_name = '';
2042
- if (!empty($this->settings['fb_page_id']) &&
2043
- !empty($this->settings['fb_api_key']) ) {
2044
-
2045
- $page_name = $this->fbgraph->get_page_name($this->settings['fb_page_id'],
2046
- $this->settings['fb_api_key']);
2047
- }
2048
- return $page_name;
2049
- }
2050
-
2051
- function get_nux_message_ifexist() {
2052
- $nux_type_to_elemid_map = array(
2053
- 'messenger_chat' => 'connect_button',
2054
- 'instagram_shopping' => 'connect_button'
2055
- );
2056
- $nux_type_to_message_map = array(
2057
- 'messenger_chat' => __('Get started with Messenger Customer Chat'),
2058
- 'instagram_shopping' => __('Get started with Instagram Shopping')
2059
- );
2060
- if (isset($_GET['nux'])) {
2061
- return sprintf('<div class="nux-message" style="display: none;" data-target="%s">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2062
  <div class="nux-message-text">%s</div>
2063
  <div class="nux-message-arrow"></div>
2064
  <i class="nux-message-close-btn">x</i>
2065
  </div>
2066
  <script>(function() { fbe_init_nux_messages(); })();</script>',
2067
- $nux_type_to_elemid_map[sanitize_text_field($_GET['nux'])],
2068
- $nux_type_to_message_map[sanitize_text_field($_GET['nux'])]);
2069
- } else {
2070
- return '';
2071
- }
2072
- }
2073
-
2074
- /**
2075
- * Admin Panel Options
2076
- */
2077
- function admin_options() {
2078
- $domain = 'facebook-for-woocommerce';
2079
- $cta_button_text = __('Get Started', $domain);
2080
- $page_name = $this->get_page_name();
2081
-
2082
- $can_manage = current_user_can('manage_woocommerce');
2083
- $pre_setup = empty($this->settings['fb_page_id']) ||
2084
- empty($this->settings['fb_api_key']);
2085
- $apikey_invalid = !$pre_setup && $this->settings['fb_api_key'] && !$page_name;
2086
-
2087
- $redirect_uri = '';
2088
- $remove_http_active = is_plugin_active('remove-http/remove-http.php');
2089
- $https_will_be_stripped = $remove_http_active &&
2090
- !get_option('factmaven_rhttp')['external'];
2091
- if ($https_will_be_stripped) {
2092
- $this->display_sticky_message(__('You\'re using Remove HTTP which has
 
 
 
2093
  incompatibilities with our extension. Please disable it, or select the
2094
- "Ignore external links" option on the Remove HTTP settings page.'));
2095
- }
2096
-
2097
- if (!$pre_setup) {
2098
- $cta_button_text = __('Create Ad', $domain);
2099
- $redirect_uri = 'https://www.facebook.com/ads/dia/redirect/?settings_id='
2100
- . $this->external_merchant_settings_id . '&version=2' .
2101
- '&entry_point=admin_panel';
2102
- }
2103
- $currently_syncing = get_transient(self::FB_SYNC_IN_PROGRESS);
2104
- $connected = ($page_name != '');
2105
- $hide_test = ($connected && $currently_syncing) || !defined('WP_DEBUG') ||
2106
- WP_DEBUG !== true;
2107
- $nux_message = $this->get_nux_message_ifexist();
2108
- ?>
2109
- <h2><?php _e('Facebook', $domain); ?></h2>
2110
- <p><?php _e('Control how WooCommerce integrates with your Facebook store.',
2111
- $domain);?>
2112
- </p>
2113
- <hr/>
2114
-
2115
- <div id="fbsetup">
2116
- <div class="wrapper">
2117
- <header>
2118
- <div class="help-center">
2119
- <a href="https://www.facebook.com/business/help/900699293402826" target="_blank">Help Center <i class="help-center-icon"></i></a>
2120
- </div>
2121
- </header>
2122
- <div class="content">
2123
- <h1 id="setup_h1">
2124
- <?php
2125
- $pre_setup
2126
- ? _e('Grow your business on Facebook', $domain)
2127
- : _e('Reach The Right People and Sell More Online', $domain);
2128
- ?>
2129
- </h1>
2130
- <h2>
2131
- <?php _e('Use this WooCommerce and Facebook integration to:',
2132
- $domain); ?>
2133
- </h2>
2134
- <ul>
2135
- <li id="setup_l1">
2136
- <?php
2137
- $pre_setup
2138
- ? _e('Easily install a tracking pixel', $domain)
2139
- : _e('Create an ad in a few steps', $domain);
2140
- ?>
2141
- </li>
2142
- <li id="setup_l2">
2143
- <?php
2144
- $pre_setup
2145
- ? _e('Upload your products and create a shop', $domain)
2146
- : _e('Use built-in best practices for online sales', $domain);
2147
- ?>
2148
- </li>
2149
- <li id="setup_l3">
2150
- <?php
2151
- $pre_setup
2152
- ? _e('Create dynamic ads with your products and pixel', $domain)
2153
- : _e('Get reporting on sales and revenue', $domain);
2154
- ?>
2155
- </li>
2156
- </ul>
2157
- <span
2158
- <?php
2159
- if ($pre_setup) {
2160
- if (!$can_manage) {
2161
- echo ' style="pointer-events: none;"';
2162
- }
2163
- echo '><a href="#" class="btn pre-setup" onclick="facebookConfig()"
2164
- id="cta_button">' . esc_html($cta_button_text) . '</a></span>';
2165
- } else {
2166
- if (!$can_manage || $apikey_invalid ||
2167
- !isset($this->external_merchant_settings_id)) {
2168
- echo ' style="pointer-events: none;"';
2169
- }
2170
- echo (
2171
- '><a href='.$redirect_uri.' class="btn" id="cta_button">' .
2172
- esc_html($cta_button_text) . '</a>' .
2173
- '<a href="https://www.facebook.com/business/m/drive-more-online-sales"
2174
- class="btn grey" id="learnmore_button">' . __("Learn More") .
2175
- '</a></span>'
2176
- );
2177
- }
2178
- ?>
2179
- <hr />
2180
- <div class="settings-container">
2181
- <div id="plugins" class="settings-section"
2182
- <?php echo ($pre_setup && $can_manage) ? ' style="display:none;"' : ''; ?>
2183
- >
2184
- <h1><?php echo __('Add Ways for People to Shop'); ?></h1>
2185
- <h2><?php echo __('Connect your business with features such as Messenger and more.'); ?></h2>
2186
- <a href="#" class="btn small" onclick="facebookConfig()" id="connect_button">
2187
- <?php echo __('Add Features'); ?>
2188
- </a>
2189
- </div>
2190
- <div id="settings" class="settings-section"
2191
- <?php
2192
- if ($pre_setup && $can_manage) {
2193
- echo ' style="display:none;"';
2194
- }
2195
- echo '><h1>' . esc_html__('Settings', $domain) . '</h1>';
2196
- if ($apikey_invalid) {
2197
- // API key is set, but no page name.
2198
- echo '<h2 id="token_text" style="color:red;">' .
2199
- __('Your API key is no longer valid. Please click "Settings >
2200
- Advanced Options > Update Token".', $domain) . '</h2>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2201
 
2202
  <span><a href="#" class="btn small" onclick="facebookConfig()"
2203
- id="setting_button">' . __('Settings', $domain) . '</a>
2204
  </span>';
2205
- } else {
2206
- if (!$can_manage) {
2207
- echo '<h2 style="color:red;">' . __('You must have
2208
- "manage_woocommerce" permissions to use this plugin.', $domain) .
2209
- '</h2>';
2210
- } else {
2211
- echo '<h2><span id="connection_status"';
2212
- if (!$connected) {
2213
- echo ' style="display: none;"';
2214
- }
2215
- echo '>';
2216
- echo __('Your WooCommerce store is connected to ', $domain) .
2217
- (($page_name != '')
2218
- ? sprintf(
2219
- __('the Facebook page <a target="_blank" href="https://www.facebook.com/%1$s">%2$s</a></span>', $domain),
2220
- $this->settings['fb_page_id'],
2221
- esc_html($page_name))
2222
- : sprintf(
2223
- __('<a target="_blank" href="https://www.facebook.com/%1$s">your Facebook page</a></span>', $domain),
2224
- $this->settings['fb_page_id'])
2225
- ) .
2226
- '.<span id="sync_complete" style="margin-left: 5px;';
2227
- if (!$connected || $currently_syncing) {
2228
- echo ' display: none;';
2229
- }
2230
- echo '">' . __('Status', $domain) . ': '
2231
- . __('Products are synced to Facebook.', $domain) . '</span>'.
2232
- sprintf(__('<span><a href="#" onclick="show_debug_info()"
 
 
 
 
 
 
 
2233
  id="debug_info" style="display:none;" > More Info </a></span>',
2234
- $domain)) . '</span></h2>
 
 
2235
  <span><a href="#" class="btn small" onclick="facebookConfig()"
2236
  id="setting_button"';
2237
 
2238
- if ($currently_syncing) {
2239
- echo ' style="pointer-events: none;" ';
2240
- }
2241
- echo '>' . __('Manage Settings', $domain) . '</a></span>
2242
 
2243
  <span><a href="#" class="btn small" onclick="sync_confirm()"
2244
  id="resync_products"';
2245
 
2246
- if ($connected && $currently_syncing) {
2247
- echo ' style="pointer-events: none;" ';
2248
- }
2249
- echo '>' . __('Sync Products', $domain) . '</a></span>
2250
 
2251
  <p id="sync_progress">';
2252
- if ($connected && $currently_syncing) {
2253
- echo '<hr/>';
2254
- echo __('Syncing... Keep this browser open', $domain);
2255
- echo '<br/>';
2256
- echo __('Until sync is complete', $domain);
2257
- }
2258
- echo '</p>';
2259
- }
2260
- } ?>
2261
- </div>
2262
- <hr />
2263
- </div>
2264
- <?php echo $nux_message; ?>
2265
-
2266
- <div>
2267
- <div id='fbAdvancedOptionsText' onclick="toggleAdvancedOptions();">
2268
- Show Advanced Settings
2269
- </div>
2270
- <div id='fbAdvancedOptions'>
2271
- <div class='autosync' title="This experimental feature will call force resync at the specified time using wordpress cron scheduling.">
2272
- <input type="checkbox"
2273
- onclick="saveAutoSyncSchedule()"
2274
- class="autosyncCheck"
2275
- <?php echo get_option('woocommerce_fb_autosync_time', false) ? 'checked' : 'unchecked'; ?>>
2276
- Automatically Force Resync of Products At
2277
-
2278
- <input
2279
- type="time"
2280
- value="<?php echo get_option('woocommerce_fb_autosync_time', '23:00'); ?>"
2281
- class="autosyncTime"
2282
- onfocusout="saveAutoSyncSchedule()"
2283
- <?php echo get_option('woocommerce_fb_autosync_time', 0) ? '' : 'disabled'; ?> />
2284
- Every Day.
2285
- <span class="autosyncSavedNotice" disabled> Saved </span>
2286
- </div>
2287
- <div title="This option is meant for development and testing environments.">
2288
- <input type="checkbox"
2289
- onclick="onSetDisableSyncOnDevEnvironment()"
2290
- class="disableOnDevEnvironment"
2291
- <?php echo get_option('fb_disable_sync_on_dev_environment', false)
2292
- ? 'checked'
2293
- : 'unchecked'; ?> />
2294
- Disable Product Sync with FB
2295
- </div>
2296
- <div class='shortdescr' title="This experimental feature will import short description instead of description for all products.">
2297
- <input type="checkbox"
2298
- onclick="syncShortDescription()"
2299
- class="syncShortDescription"
2300
- <?php echo get_option('fb_sync_short_description', false)
2301
- ? 'checked'
2302
- : 'unchecked'; ?> />
2303
- Sync Short Description Instead of Description
2304
- </div>
2305
- </div>
2306
- </div>
2307
- </div>
2308
- </div>
2309
- <div <?php echo ($hide_test) ? ' style="display:none;" ' : ''; ?> >
2310
- <p class="tooltip" id="test_product_sync">
2311
- <?php
2312
- // WP_DEBUG mode: button to launch test
2313
- echo sprintf(__('<a href="%s&fb_test_product_sync=true"', $domain),
2314
- WOOCOMMERCE_FACEBOOK_PLUGIN_SETTINGS_URL);
2315
- echo '>' . esc_html__('Launch Test', $domain);
2316
- ?>
2317
- <span class='tooltiptext'>
2318
- <?php
2319
- _e('This button will run an integration test suite verifying the
 
 
 
 
 
 
 
 
 
 
2320
  extension. Note that this will reset your products and resync them
2321
  to Facebook. Not recommended to use unless you are changing the
2322
- extension code and want to test your changes.', $domain);
2323
- ?>
2324
- </span>
2325
- <?php
2326
- echo '</a>';
2327
- ?>
2328
- </p>
2329
- <p id="stack_trace"></p>
2330
- </div>
2331
- <br/><hr/><br/>
2332
- <?php
2333
-
2334
- $GLOBALS['hide_save_button'] = true;
2335
- if (defined('WP_DEBUG') && true === WP_DEBUG) {
2336
- $GLOBALS['hide_save_button'] = false;
2337
- ?>
2338
- <table class="form-table">
2339
- <?php $this->generate_settings_html(); ?>
2340
- </table><!--/.form-table-->
2341
- <?php
2342
- }
2343
- }
2344
-
2345
- function delete_product_item($wp_id) {
2346
- $fb_product_item_id = $this->get_product_fbid(
2347
- self::FB_PRODUCT_ITEM_ID,
2348
- $wp_id);
2349
- if ($fb_product_item_id) {
2350
- $pi_result =
2351
- $this->fbgraph->delete_product_item($fb_product_item_id);
2352
- WC_Facebookcommerce_Utils::log($pi_result);
2353
- }
2354
- }
2355
-
2356
- function fb_duplicate_product_reset_meta($to_delete) {
2357
- array_push($to_delete, self::FB_PRODUCT_ITEM_ID);
2358
- array_push($to_delete, self::FB_PRODUCT_GROUP_ID);
2359
- return $to_delete;
2360
- }
2361
-
2362
- /**
2363
- * Helper function to update FB visibility.
2364
- */
2365
- function update_fb_visibility($wp_id, $visibility) {
2366
- $woo_product = new WC_Facebook_Product($wp_id);
2367
- if (!$woo_product->exists()) {
2368
- // This function can be called for non-woo products.
2369
- return;
2370
- }
2371
-
2372
- $products = WC_Facebookcommerce_Utils::get_product_array($woo_product);
2373
- foreach ($products as $item_id) {
2374
- $fb_product_item_id = $this->get_product_fbid(
2375
- self::FB_PRODUCT_ITEM_ID,
2376
- $item_id);
2377
-
2378
- if (!$fb_product_item_id) {
2379
- WC_Facebookcommerce_Utils::fblog(
2380
- $fb_product_item_id." doesn't exist but underwent a visibility transform.",
2381
- array(),
2382
- true);
2383
- continue;
2384
- }
2385
- $result = $this->check_api_result(
2386
- $this->fbgraph->update_product_item(
2387
- $fb_product_item_id,
2388
- array('visibility' => $visibility)));
2389
- if ($result) {
2390
- update_post_meta($item_id, self::FB_VISIBILITY, $visibility);
2391
- update_post_meta($wp_id, self::FB_VISIBILITY, $visibility);
2392
- }
2393
- }
2394
- }
2395
-
2396
- function on_quick_and_bulk_edit_save($product) {
2397
- $wp_id = $product->get_id();
2398
- $visibility = get_post_status($wp_id) == 'publish'
2399
- ? 'published'
2400
- : 'staging';
2401
- // case 1: new status is 'publish' regardless of old status, sync to FB
2402
- if ($visibility == 'published') {
2403
- $this->on_product_publish($wp_id);
2404
- } else {
2405
- // case 2: product never publish to FB, new status is not publish
2406
- // case 3: product new status is not publish and published before
2407
- $this->update_fb_visibility($wp_id, $visibility);
2408
- }
2409
- }
2410
-
2411
- private function get_product_fbid($fbid_type, $wp_id, $woo_product = null) {
2412
- $fb_id = WC_Facebookcommerce_Utils::get_fbid_post_meta(
2413
- $wp_id,
2414
- $fbid_type);
2415
- if ($fb_id) {
2416
- return $fb_id;
2417
- }
2418
- if (!isset($this->settings['upload_end_time'])) {
2419
- return null;
2420
- }
2421
- if (!$woo_product) {
2422
- $woo_product = new WC_Facebook_Product($wp_id);
2423
- }
2424
- $products = WC_Facebookcommerce_Utils::get_product_array($woo_product);
2425
- $woo_product = new WC_Facebook_Product(current($products));
2426
- // This is a generalized function used elsewhere
2427
- // Cannot call is_hidden for VC_Product_Variable Object
2428
- if ($woo_product->is_hidden()) {
2429
- return null;
2430
- }
2431
- $fb_retailer_id =
2432
- WC_Facebookcommerce_Utils::get_fb_retailer_id($woo_product);
2433
-
2434
- $product_fbid_result = $this->fbgraph->get_facebook_id(
2435
- $this->product_catalog_id,
2436
- $fb_retailer_id);
2437
- if (is_wp_error($product_fbid_result)) {
2438
- WC_Facebookcommerce_Utils::log($product_fbid_result->get_error_message());
2439
- $this->display_error_message(
2440
- "There was an issue connecting to the Facebook API: ".
2441
- $product_fbid_result->get_error_message());
2442
- return;
2443
- }
2444
-
2445
- if ($product_fbid_result && isset($product_fbid_result['body'])) {
2446
- $body = WC_Facebookcommerce_Utils::decode_json($product_fbid_result['body']);
2447
- if ($body && $body->id) {
2448
- if ($fbid_type == self::FB_PRODUCT_GROUP_ID) {
2449
- $fb_id = $body->product_group->id;
2450
- } else {
2451
- $fb_id = $body->id;
2452
- }
2453
- update_post_meta(
2454
- $wp_id,
2455
- $fbid_type,
2456
- $fb_id);
2457
- update_post_meta($wp_id, self::FB_VISIBILITY, true);
2458
- return $fb_id;
2459
- }
2460
- }
2461
- return;
2462
- }
2463
-
2464
- private function set_default_variant($product_group_id, $product_item_id) {
2465
- $result = $this->check_api_result(
2466
- $this->fbgraph->set_default_variant(
2467
- $product_group_id,
2468
- array('default_product_id' => $product_item_id)));
2469
- if (!$result) {
2470
- WC_Facebookcommerce_Utils::fblog(
2471
- 'Fail to set default product item',
2472
- array(),
2473
- true);
2474
- }
2475
- }
2476
-
2477
- private function fb_wp_die() {
2478
- if (!$this->test_mode) {
2479
- wp_die();
2480
- }
2481
- }
2482
-
2483
- /**
2484
- * Display test result.
2485
- **/
2486
- function ajax_display_test_result() {
2487
- WC_Facebookcommerce_Utils::check_woo_ajax_permissions('test result', true);
2488
- $response = array(
2489
- 'pass' => 'true',
2490
- );
2491
- $test_pass = get_option('fb_test_pass', null);
2492
- if (!isset($test_pass)) {
2493
- $response['pass'] = 'in progress';
2494
- } else if ($test_pass == 0) {
2495
- $response['pass'] = 'false';
2496
- $response['debug_info'] = get_transient('facebook_plugin_test_fail');
2497
- $response['stack_trace'] =
2498
- get_transient('facebook_plugin_test_stack_trace');
2499
- $response['stack_trace'] =
2500
- preg_replace("/\n/", '<br>', $response['stack_trace']);
2501
- delete_transient('facebook_plugin_test_fail');
2502
- delete_transient('facebook_plugin_test_stack_trace');
2503
- }
2504
- delete_option('fb_test_pass');
2505
- printf(json_encode($response));
2506
- wp_die();
2507
- }
2508
-
2509
- /**
2510
- * Schedule Force Resync
2511
- */
2512
- function ajax_schedule_force_resync() {
2513
- WC_Facebookcommerce_Utils::check_woo_ajax_permissions('resync schedule', true);
2514
- if (isset($_POST) && isset($_POST['enabled'])) {
2515
- if (isset($_POST['time']) && $_POST['enabled']) { // Enabled
2516
- $time = sanitize_text_field($_POST['time']);
2517
- wp_clear_scheduled_hook('sync_all_fb_products_using_feed');
2518
- wp_schedule_event(
2519
- strtotime($time),
2520
- 'daily',
2521
- 'sync_all_fb_products_using_feed');
2522
- WC_Facebookcommerce_Utils::fblog('Scheduled autosync for '.$time);
2523
- update_option('woocommerce_fb_autosync_time', $time);
2524
- } else if (!$_POST['enabled']) { // Disabled
2525
- wp_clear_scheduled_hook('sync_all_fb_products_using_feed');
2526
- WC_Facebookcommerce_Utils::fblog('Autosync disabled');
2527
- delete_option('woocommerce_fb_autosync_time');
2528
- }
2529
- } else {
2530
- WC_Facebookcommerce_Utils::fblog('Autosync AJAX Problem', $_POST, true);
2531
- }
2532
- wp_die();
2533
- }
2534
-
2535
- function ajax_update_fb_option() {
2536
- check_ajax_referer( 'wc_facebook_settings_jsx' );
2537
- WC_Facebookcommerce_Utils::check_woo_ajax_permissions('update fb options', true);
2538
- if (isset($_POST) && stripos($_POST['option'], 'fb_') === 0) {
2539
- update_option(sanitize_text_field($_POST['option']), sanitize_text_field($_POST['option_value']));
2540
- }
2541
- wp_die();
2542
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2543
  }
8
  * @package FacebookCommerce
9
  */
10
 
11
+ if ( ! defined( 'ABSPATH' ) ) {
12
+ exit; // Exit if accessed directly
13
+ }
14
 
15
+ require_once 'facebook-config-warmer.php';
16
+ require_once 'includes/fbproduct.php';
17
+ require_once 'facebook-commerce-pixel-event.php';
18
 
19
  class WC_Facebookcommerce_Integration extends WC_Integration {
20
 
21
+ const FB_PRODUCT_GROUP_ID = 'fb_product_group_id';
22
+ const FB_PRODUCT_ITEM_ID = 'fb_product_item_id';
23
+ const FB_PRODUCT_DESCRIPTION = 'fb_product_description';
24
+
25
+ const FB_VISIBILITY = 'fb_visibility';
26
+
27
+ const FB_CART_URL = 'fb_cart_url';
28
+
29
+ const FB_MESSAGE_DISPLAY_TIME = 180;
30
+
31
+ // Number of days to query tip.
32
+ const FB_TIP_QUERY = 1;
33
+
34
+ const FB_VARIANT_IMAGE = 'fb_image';
35
+
36
+ const FB_ADMIN_MESSAGE_PREPEND = '<b>Facebook for WooCommerce</b><br/>';
37
+
38
+ const FB_SYNC_IN_PROGRESS = 'fb_sync_in_progress';
39
+ const FB_SYNC_REMAINING = 'fb_sync_remaining';
40
+ const FB_SYNC_TIMEOUT = 30;
41
+ const FB_PRIORITY_MID = 9;
42
+
43
+ private $test_mode = false;
44
+
45
+ public function init_settings() {
46
+ parent::init_settings();
47
+ }
48
+
49
+ public function init_pixel() {
50
+ WC_Facebookcommerce_Pixel::initialize();
51
+
52
+ // Migrate WC customer pixel_id from WC settings to WP options.
53
+ // This is part of a larger effort to consolidate all the FB-specific
54
+ // settings for all plugin integrations.
55
+ if ( is_admin() ) {
56
+ $pixel_id = WC_Facebookcommerce_Pixel::get_pixel_id();
57
+ $settings_pixel_id = isset( $this->settings['fb_pixel_id'] ) ?
58
+ (string) $this->settings['fb_pixel_id'] : null;
59
+ if (
60
+ WC_Facebookcommerce_Utils::is_valid_id( $settings_pixel_id ) &&
61
+ ( ! WC_Facebookcommerce_Utils::is_valid_id( $pixel_id ) ||
62
+ $pixel_id != $settings_pixel_id
63
+ )
64
+ ) {
65
+ WC_Facebookcommerce_Pixel::set_pixel_id( $settings_pixel_id );
66
+ }
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Init and hook in the integration.
72
+ *
73
+ * @access public
74
+ * @return void
75
+ */
76
+ public function __construct() {
77
+ if ( ! class_exists( 'WC_Facebookcommerce_EventsTracker' ) ) {
78
+ include_once 'facebook-commerce-events-tracker.php';
79
+ }
80
+
81
+ $this->id = 'facebookcommerce';
82
+ $this->method_title = __(
83
+ 'Facebook for WooCommerce',
84
+ 'facebook-for-commerce'
85
+ );
86
+ $this->method_description = __(
87
+ 'Facebook Commerce and Dynamic Ads (Pixel) Extension',
88
+ 'facebook-for-commerce'
89
+ );
90
+
91
+ // Load the settings.
92
+ $this->init_settings();
93
+
94
+ $this->page_id = isset( $this->settings['fb_page_id'] )
95
+ ? $this->settings['fb_page_id']
96
+ : '';
97
+
98
+ $this->api_key = isset( $this->settings['fb_api_key'] )
99
+ ? $this->settings['fb_api_key']
100
+ : '';
101
+
102
+ $pixel_id = WC_Facebookcommerce_Pixel::get_pixel_id();
103
+ if ( ! $pixel_id ) {
104
+ $pixel_id = isset( $this->settings['fb_pixel_id'] ) ?
105
+ $this->settings['fb_pixel_id'] : '';
106
+ }
107
+ $this->pixel_id = isset( $pixel_id )
108
+ ? $pixel_id
109
+ : '';
110
+
111
+ $this->pixel_install_time = isset( $this->settings['pixel_install_time'] )
112
+ ? $this->settings['pixel_install_time']
113
+ : '';
114
+
115
+ $this->use_pii = isset( $this->settings['fb_pixel_use_pii'] )
116
+ && $this->settings['fb_pixel_use_pii'] === 'yes'
117
+ ? true
118
+ : false;
119
+
120
+ $this->product_catalog_id = isset( $this->settings['fb_product_catalog_id'] )
121
+ ? $this->settings['fb_product_catalog_id']
122
+ : '';
123
+
124
+ $this->external_merchant_settings_id =
125
+ isset( $this->settings['fb_external_merchant_settings_id'] )
126
+ ? $this->settings['fb_external_merchant_settings_id']
127
+ : '';
128
+
129
+ if ( ! class_exists( 'WC_Facebookcommerce_Utils' ) ) {
130
+ include_once 'includes/fbutils.php';
131
+ }
132
+
133
+ WC_Facebookcommerce_Utils::$ems = $this->external_merchant_settings_id;
134
+
135
+ if ( ! class_exists( 'WC_Facebookcommerce_Graph_API' ) ) {
136
+ include_once 'includes/fbgraph.php';
137
+ $this->fbgraph = new WC_Facebookcommerce_Graph_API( $this->api_key );
138
+ }
139
+
140
+ WC_Facebookcommerce_Utils::$fbgraph = $this->fbgraph;
141
+ $this->feed_id = isset( $this->settings['fb_feed_id'] )
142
+ ? $this->settings['fb_feed_id']
143
+ : '';
144
+
145
+ // Hooks
146
+ if ( is_admin() ) {
147
+ $this->init_pixel();
148
+ $this->init_form_fields();
149
+ // Display an info banner for eligible pixel and user.
150
+ if ( $this->external_merchant_settings_id
151
+ && $this->pixel_id
152
+ && $this->pixel_install_time ) {
153
+ $should_query_tip =
154
+ WC_Facebookcommerce_Utils::check_time_cap(
155
+ get_option( 'fb_info_banner_last_query_time', '' ),
156
+ self::FB_TIP_QUERY
157
+ );
158
+ $last_tip_info = WC_Facebookcommerce_Utils::get_cached_best_tip();
159
+
160
+ if ( $should_query_tip || $last_tip_info ) {
161
+ if ( ! class_exists( 'WC_Facebookcommerce_Info_Banner' ) ) {
162
+ include_once 'includes/fbinfobanner.php';
163
+ }
164
+ WC_Facebookcommerce_Info_Banner::get_instance(
165
+ $this->external_merchant_settings_id,
166
+ $this->fbgraph,
167
+ $should_query_tip
168
+ );
169
+ }
170
+ }
171
+
172
+ if ( ! class_exists( 'WC_Facebook_Integration_Test' ) ) {
173
+ include_once 'includes/test/facebook-integration-test.php';
174
+ }
175
+ $integration_test = WC_Facebook_Integration_Test::get_instance( $this );
176
+ $integration_test::$fbgraph = $this->fbgraph;
177
+
178
+ if ( ! $this->pixel_install_time && $this->pixel_id ) {
179
+ $this->pixel_install_time = current_time( 'mysql' );
180
+ $this->settings['pixel_install_time'] = $this->pixel_install_time;
181
+ update_option(
182
+ $this->get_option_key(),
183
+ apply_filters(
184
+ 'woocommerce_settings_api_sanitized_fields_' . $this->id,
185
+ $this->settings
186
+ )
187
+ );
188
+ }
189
+ add_action( 'admin_notices', array( $this, 'checks' ) );
190
+ add_action(
191
+ 'woocommerce_update_options_integration_facebookcommerce',
192
+ array( $this, 'process_admin_options' )
193
+ );
194
+ add_action( 'admin_enqueue_scripts', array( $this, 'load_assets' ) );
195
+
196
+ add_action(
197
+ 'wp_ajax_ajax_save_fb_settings',
198
+ array( $this, 'ajax_save_fb_settings' ),
199
+ self::FB_PRIORITY_MID
200
+ );
201
+
202
+ add_action(
203
+ 'wp_ajax_ajax_delete_fb_settings',
204
+ array( $this, 'ajax_delete_fb_settings' ),
205
+ self::FB_PRIORITY_MID
206
+ );
207
+
208
+ add_action(
209
+ 'wp_ajax_ajax_sync_all_fb_products',
210
+ array( $this, 'ajax_sync_all_fb_products' ),
211
+ self::FB_PRIORITY_MID
212
+ );
213
+
214
+ add_action(
215
+ 'wp_ajax_ajax_sync_all_fb_products_using_feed',
216
+ array( $this, 'ajax_sync_all_fb_products_using_feed' ),
217
+ self::FB_PRIORITY_MID
218
+ );
219
+
220
+ add_action(
221
+ 'wp_ajax_ajax_check_feed_upload_status',
222
+ array( $this, 'ajax_check_feed_upload_status' ),
223
+ self::FB_PRIORITY_MID
224
+ );
225
+
226
+ add_action(
227
+ 'wp_ajax_ajax_reset_all_fb_products',
228
+ array( $this, 'ajax_reset_all_fb_products' ),
229
+ self::FB_PRIORITY_MID
230
+ );
231
+ add_action(
232
+ 'wp_ajax_ajax_display_test_result',
233
+ array( $this, 'ajax_display_test_result' )
234
+ );
235
+
236
+ add_action(
237
+ 'wp_ajax_ajax_schedule_force_resync',
238
+ array( $this, 'ajax_schedule_force_resync' ),
239
+ self::FB_PRIORITY_MID
240
+ );
241
+
242
+ add_action(
243
+ 'wp_ajax_ajax_update_fb_option',
244
+ array( $this, 'ajax_update_fb_option' ),
245
+ self::FB_PRIORITY_MID
246
+ );
247
+
248
+ // Only load product processing hooks if we have completed setup.
249
+ if ( $this->api_key && $this->product_catalog_id ) {
250
+ add_action(
251
+ 'woocommerce_process_product_meta_simple',
252
+ array( $this, 'on_simple_product_publish' ),
253
+ 10, // Action priority
254
+ 1 // Args passed to on_product_publish (should be 'id')
255
+ );
256
+
257
+ add_action(
258
+ 'woocommerce_process_product_meta_variable',
259
+ array( $this, 'on_variable_product_publish' ),
260
+ 10, // Action priority
261
+ 1 // Args passed to on_product_publish (should be 'id')
262
+ );
263
+
264
+ add_action(
265
+ 'woocommerce_process_product_meta_booking',
266
+ array( $this, 'on_simple_product_publish' ),
267
+ 10, // Action priority
268
+ 1 // Args passed to on_product_publish (should be 'id')
269
+ );
270
+
271
+ add_action(
272
+ 'woocommerce_process_product_meta_external',
273
+ array( $this, 'on_simple_product_publish' ),
274
+ 10, // Action priority
275
+ 1 // Args passed to on_product_publish (should be 'id')
276
+ );
277
+
278
+ add_action(
279
+ 'woocommerce_process_product_meta_subscription',
280
+ array( $this, 'on_product_publish' ),
281
+ 10, // Action priority
282
+ 1 // Args passed to on_product_publish (should be 'id')
283
+ );
284
+
285
+ add_action(
286
+ 'woocommerce_process_product_meta_variable-subscription',
287
+ array( $this, 'on_product_publish' ),
288
+ 10, // Action priority
289
+ 1 // Args passed to on_product_publish (should be 'id')
290
+ );
291
+
292
+ add_action(
293
+ 'woocommerce_process_product_meta_bundle',
294
+ array( $this, 'on_product_publish' ),
295
+ 10, // Action priority
296
+ 1 // Args passed to on_product_publish (should be 'id')
297
+ );
298
+
299
+ add_action(
300
+ 'woocommerce_product_quick_edit_save',
301
+ array( $this, 'on_quick_and_bulk_edit_save' ),
302
+ 10, // Action priority
303
+ 1 // Args passed to on_quick_and_bulk_edit_save ('product')
304
+ );
305
+
306
+ add_action(
307
+ 'woocommerce_product_bulk_edit_save',
308
+ array( $this, 'on_quick_and_bulk_edit_save' ),
309
+ 10, // Action priority
310
+ 1 // Args passed to on_quick_and_bulk_edit_save ('product')
311
+ );
312
+
313
+ add_action(
314
+ 'before_delete_post',
315
+ array( $this, 'on_product_delete' ),
316
+ 10,
317
+ 1
318
+ );
319
+
320
+ add_action( 'add_meta_boxes', array( $this, 'fb_product_metabox' ), 10, 1 );
321
+
322
+ add_filter(
323
+ 'manage_product_posts_columns',
324
+ array( $this, 'fb_product_columns' )
325
+ );
326
+ add_action(
327
+ 'manage_product_posts_custom_column',
328
+ array( $this, 'fb_render_product_columns' ),
329
+ 2
330
+ );
331
+ add_action(
332
+ 'transition_post_status',
333
+ array( $this, 'fb_change_product_published_status' ),
334
+ 10,
335
+ 3
336
+ );
337
+
338
+ // Product data tab
339
+ add_filter(
340
+ 'woocommerce_product_data_tabs',
341
+ array( $this, 'fb_new_product_tab' )
342
+ );
343
+
344
+ add_action(
345
+ 'woocommerce_product_data_panels',
346
+ array( $this, 'fb_new_product_tab_content' )
347
+ );
348
+
349
+ add_action(
350
+ 'wp_ajax_ajax_fb_toggle_visibility',
351
+ array( $this, 'ajax_fb_toggle_visibility' )
352
+ );
353
+
354
+ add_action(
355
+ 'wp_ajax_ajax_reset_single_fb_product',
356
+ array( $this, 'ajax_reset_single_fb_product' )
357
+ );
358
+
359
+ add_action(
360
+ 'wp_ajax_ajax_delete_fb_product',
361
+ array( $this, 'ajax_delete_fb_product' )
362
+ );
363
+
364
+ add_filter(
365
+ 'woocommerce_duplicate_product_exclude_meta',
366
+ array( $this, 'fb_duplicate_product_reset_meta' )
367
+ );
368
+
369
+ add_action(
370
+ 'pmxi_after_xml_import',
371
+ array( $this, 'wp_all_import_compat' )
372
+ );
373
+
374
+ add_action(
375
+ 'wp_ajax_wpmelon_adv_bulk_edit',
376
+ array( $this, 'ajax_woo_adv_bulk_edit_compat' ),
377
+ self::FB_PRIORITY_MID
378
+ );
379
+
380
+ // Used to remove the 'you need to resync' message.
381
+ if ( isset( $_GET['remove_sticky'] ) ) {
382
+ $this->remove_sticky_message();
383
+ }
384
+
385
+ if ( defined( 'ICL_LANGUAGE_CODE' ) ) {
386
+ include_once 'includes/fbwpml.php';
387
+ new WC_Facebook_WPML_Injector();
388
+ }
389
+ }
390
+ $this->load_background_sync_process();
391
+ }
392
+ // Must be outside of admin for cron to schedule correctly.
393
+ add_action(
394
+ 'sync_all_fb_products_using_feed',
395
+ array( $this, 'sync_all_fb_products_using_feed' ),
396
+ self::FB_PRIORITY_MID
397
+ );
398
+
399
+ if ( $this->pixel_id ) {
400
+ $user_info = WC_Facebookcommerce_Utils::get_user_info( $this->use_pii );
401
+ $this->events_tracker = new WC_Facebookcommerce_EventsTracker( $user_info );
402
+ }
403
+
404
+ if ( isset( $this->settings['is_messenger_chat_plugin_enabled'] ) &&
405
+ $this->settings['is_messenger_chat_plugin_enabled'] === 'yes' ) {
406
+ if ( ! class_exists( 'WC_Facebookcommerce_MessengerChat' ) ) {
407
+ include_once 'facebook-commerce-messenger-chat.php';
408
+ }
409
+ $this->messenger_chat = new WC_Facebookcommerce_MessengerChat( $this->settings );
410
+ }
411
+ }
412
+
413
+ public function load_background_sync_process() {
414
+ // Attempt to load background processing (Woo 3.x.x only)
415
+ include_once 'includes/fbbackground.php';
416
+ if ( class_exists( 'WC_Facebookcommerce_Background_Process' ) ) {
417
+ if ( ! isset( $this->background_processor ) ) {
418
+ $this->background_processor =
419
+ new WC_Facebookcommerce_Background_Process( $this );
420
+ }
421
+ }
422
+ add_action(
423
+ 'wp_ajax_ajax_fb_background_check_queue',
424
+ array( $this, 'ajax_fb_background_check_queue' )
425
+ );
426
+ }
427
+
428
+ public function ajax_fb_background_check_queue() {
429
+ WC_Facebookcommerce_Utils::check_woo_ajax_permissions( 'background check queue', true );
430
+ check_ajax_referer( 'wc_facebook_settings_jsx' );
431
+ $request_time = null;
432
+ if ( isset( $_POST['request_time'] ) ) {
433
+ $request_time = esc_js( sanitize_text_field( $_POST['request_time'] ) );
434
+ }
435
+ if ( $this->settings['fb_api_key'] ) {
436
+ if ( isset( $this->background_processor ) ) {
437
+ $is_processing = $this->background_processor->handle_cron_healthcheck();
438
+ $remaining = $this->background_processor->get_item_count();
439
+ $response = array(
440
+ 'connected' => true,
441
+ 'background' => true,
442
+ 'processing' => $is_processing,
443
+ 'remaining' => $remaining,
444
+ 'request_time' => $request_time,
445
+ );
446
+ } else {
447
+ $response = array(
448
+ 'connected' => true,
449
+ 'background' => false,
450
+ );
451
+ }
452
+ } else {
453
+ $response = array(
454
+ 'connected' => false,
455
+ 'background' => false,
456
+ );
457
+ }
458
+ printf( json_encode( $response ) );
459
+ wp_die();
460
+ }
461
+
462
+ public function fb_new_product_tab( $tabs ) {
463
+ $tabs['fb_commerce_tab'] = array(
464
+ 'label' => __( 'Facebook', 'facebook-for-woocommerce' ),
465
+ 'target' => 'facebook_options',
466
+ 'class' => array( 'show_if_simple', 'show_if_variable' ),
467
+ );
468
+ return $tabs;
469
+ }
470
+
471
+ public function fb_new_product_tab_content() {
472
+ global $post;
473
+ $woo_product = new WC_Facebook_Product( $post->ID );
474
+ $description = get_post_meta(
475
+ $post->ID,
476
+ self::FB_PRODUCT_DESCRIPTION,
477
+ true
478
+ );
479
+
480
+ $price = get_post_meta(
481
+ $post->ID,
482
+ WC_Facebook_Product::FB_PRODUCT_PRICE,
483
+ true
484
+ );
485
+
486
+ $image = get_post_meta(
487
+ $post->ID,
488
+ WC_Facebook_Product::FB_PRODUCT_IMAGE,
489
+ true
490
+ );
491
+
492
+ $image_setting = null;
493
+ if ( WC_Facebookcommerce_Utils::is_variable_type( $woo_product->get_type() ) ) {
494
+ $image_setting = $woo_product->get_use_parent_image();
495
+ }
496
+
497
+ // 'id' attribute needs to match the 'target' parameter set above
498
+ ?><div id='facebook_options' class='panel woocommerce_options_panel'><div class='options_group'>
499
+ <?php
500
+ woocommerce_wp_textarea_input(
501
+ array(
502
+ 'id' => self::FB_PRODUCT_DESCRIPTION,
503
+ 'label' => __( 'Facebook Description', 'facebook-for-woocommerce' ),
504
+ 'desc_tip' => 'true',
505
+ 'description' => __(
506
+ 'Custom (plain-text only) description for product on Facebook. ' .
507
+ 'If blank, product description will be used. ' .
508
+ 'If product description is blank, shortname will be used.',
509
+ 'facebook-for-woocommerce'
510
+ ),
511
+ 'cols' => 40,
512
+ 'rows' => 20,
513
+ 'value' => $description,
514
+ )
515
+ );
516
+ woocommerce_wp_textarea_input(
517
+ array(
518
+ 'id' => WC_Facebook_Product::FB_PRODUCT_IMAGE,
519
+ 'label' => __( 'Facebook Product Image', 'facebook-for-woocommerce' ),
520
+ 'desc_tip' => 'true',
521
+ 'description' => __(
522
+ 'Image URL for product on Facebook. Must be an absolute URL ' .
523
+ 'e.g. https://...' .
524
+ 'This can be used to override the primary image that will be ' .
525
+ 'used on Facebook for this product. If blank, the primary ' .
526
+ 'product image in Woo will be used as the primary image on FB.',
527
+ 'facebook-for-woocommerce'
528
+ ),
529
+ 'cols' => 40,
530
+ 'rows' => 10,
531
+ 'value' => $image,
532
+ )
533
+ );
534
+ woocommerce_wp_text_input(
535
+ array(
536
+ 'id' => WC_Facebook_Product::FB_PRODUCT_PRICE,
537
+ 'label' => __(
538
+ 'Facebook Price (' .
539
+ get_woocommerce_currency_symbol() . ')',
540
+ 'facebook-for-woocommerce'
541
+ ),
542
+ 'desc_tip' => 'true',
543
+ 'description' => __(
544
+ 'Custom price for product on Facebook. ' .
545
+ 'Please enter in monetary decimal (.) format without thousand ' .
546
+ 'separators and currency symbols. ' .
547
+ 'If blank, product price will be used. ',
548
+ 'facebook-for-woocommerce'
549
+ ),
550
+ 'cols' => 40,
551
+ 'rows' => 60,
552
+ 'value' => $price,
553
+ )
554
+ );
555
+ if ( $image_setting !== null ) {
556
+ woocommerce_wp_checkbox(
557
+ array(
558
+ 'id' => self::FB_VARIANT_IMAGE,
559
+ 'label' => __( 'Use Parent Image', 'facebook-for-woocommerce' ),
560
+ 'required' => false,
561
+ 'desc_tip' => 'true',
562
+ 'description' => __(
563
+ ' By default, the primary image uploaded to Facebook is the image' .
564
+ ' specified in each variant, if provided. ' .
565
+ ' However, if you enable this setting, the ' .
566
+ ' image of the parent will be used as the primary image' .
567
+ ' for this product and all its variants instead.'
568
+ ),
569
+ 'value' => $image_setting ? 'yes' : 'no',
570
+ )
571
+ );
572
+ }
573
+ ?>
574
+ </div>
575
+ </div>
576
+ <?php
577
+ }
578
+
579
+ public function fb_product_columns( $existing_columns ) {
580
+ if ( empty( $existing_columns ) && ! is_array( $existing_columns ) ) {
581
+ $existing_columns = array();
582
+ }
583
+
584
+ $columns = array();
585
+ $columns['fb'] = __( 'FB Shop', 'facebook-for-woocommerce' );
586
+
587
+ // Verify that cart URL hasn't changed. We do it here because this page
588
+ // is most likely to be visited (so it's a handy place to make the check)
589
+ $cart_url = get_option( self::FB_CART_URL );
590
+ if ( ! empty( $cart_url ) && ( wc_get_cart_url() !== $cart_url ) ) {
591
+ $this->display_warning_message(
592
+ 'One or more of your products is using a
593
  checkout URL that may be different than your shop checkout URL.
594
  <a href="' . WOOCOMMERCE_FACEBOOK_PLUGIN_SETTINGS_URL . '">
595
+ Re-sync your products to update checkout URLs on Facebook.</a>'
596
+ );
597
+ }
598
+
599
+ return array_merge( $columns, $existing_columns );
600
+ }
601
+
602
+ public function fb_render_product_columns( $column ) {
603
+ global $post, $the_product;
604
+ $ajax_data = array(
605
+ 'nonce' => wp_create_nonce( 'wc_facebook_product_jsx' ),
606
+ );
607
+ wp_enqueue_script(
608
+ 'wc_facebook_product_jsx',
609
+ plugins_url(
610
+ '/assets/js/facebook-products.js?ts=' . time(),
611
+ __FILE__
612
+ )
613
+ );
614
+ wp_localize_script(
615
+ 'wc_facebook_product_jsx',
616
+ 'wc_facebook_product_jsx',
617
+ $ajax_data
618
+ );
619
+
620
+ if ( empty( $the_product ) || $the_product->get_id() != $post->ID ) {
621
+ $the_product = new WC_Facebook_Product( $post );
622
+ }
623
+
624
+ if ( $column === 'fb' ) {
625
+ $fb_product_group_id = $this->get_product_fbid(
626
+ self::FB_PRODUCT_GROUP_ID,
627
+ $post->ID,
628
+ $the_product
629
+ );
630
+ if ( ! $fb_product_group_id ) {
631
+ printf( '<span>Not Synced</span>' );
632
+ } else {
633
+ $viz_value = get_post_meta( $post->ID, self::FB_VISIBILITY, true );
634
+ $data_tip = $viz_value === '' ?
635
+ 'Product is synced but not marked as published (visible)
636
  on Facebook.' :
637
+ 'Product is synced and published (visible) on Facebook.';
638
 
639
+ printf(
640
+ '<span class="tips" id="tip_%1$s" data-tip="%2$s">',
641
+ $post->ID,
642
+ $data_tip
643
+ );
644
 
645
+ if ( $viz_value === '' ) {
646
+ printf(
647
+ '<a id="viz_%1$s" class="button button-primary button-large"
648
  href="javascript:;" onclick="fb_toggle_visibility(%1$s, true)">Show</a>',
649
+ $post->ID
650
+ );
651
+ } else {
652
+ printf(
653
+ '<a id="viz_%1$s" class="button" href="javascript:;"
654
  onclick="fb_toggle_visibility(%1$s, false)">Hide</a>',
655
+ $post->ID
656
+ );
657
+ }
658
+ }
659
+ }
660
+ }
661
+
662
+ public function fb_product_metabox() {
663
+ $ajax_data = array(
664
+ 'nonce' => wp_create_nonce( 'wc_facebook_metabox_jsx' ),
 
 
 
 
 
 
665
  );
666
+ wp_enqueue_script(
667
+ 'wc_facebook_metabox_jsx',
668
+ plugins_url(
669
+ '/assets/js/facebook-metabox.js?ts=' . time(),
670
+ __FILE__
671
+ )
672
+ );
673
+ wp_localize_script(
674
+ 'wc_facebook_metabox_jsx',
675
+ 'wc_facebook_metabox_jsx',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
676
  $ajax_data
677
  );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
678
 
679
+ add_meta_box(
680
+ 'facebook_metabox', // Meta box ID
681
+ 'Facebook', // Meta box Title
682
+ array( $this, 'fb_product_meta_box_html' ), // Callback
683
+ 'product', // Screen to which to add the meta box
684
+ 'side' // Context
685
+ );
686
+ }
687
+
688
+ public function fb_product_meta_box_html() {
689
+ global $post;
690
+ $woo_product = new WC_Facebook_Product( $post->ID );
691
+ $fb_product_group_id = $this->get_product_fbid(
692
+ self::FB_PRODUCT_GROUP_ID,
693
+ $post->ID,
694
+ $woo_product
695
+ );
696
+ printf( '<span id="fb_metadata">' );
697
+ if ( $fb_product_group_id ) {
698
+ printf(
699
+ 'Facebook ID: <a href="https://facebook.com/' .
700
+ $fb_product_group_id . '" target="_blank">' .
701
+ $fb_product_group_id . '</a><p/>'
702
+ );
703
+ if ( WC_Facebookcommerce_Utils::is_variable_type( $woo_product->get_type() ) ) {
704
+ printf( '<p>Variant IDs:<br/>' );
705
+ $children = $woo_product->get_children();
706
+ foreach ( $children as $child_id ) {
707
+ $fb_product_item_id = $this->get_product_fbid(
708
+ self::FB_PRODUCT_ITEM_ID,
709
+ $child_id
710
+ );
711
+ printf(
712
+ $child_id . ' : <a href="https://facebook.com/' .
713
+ $fb_product_item_id . '" target="_blank">' .
714
+ $fb_product_item_id . '</a><br/>'
715
+ );
716
+ }
717
+ printf( '</p>' );
718
+ }
719
+
720
+ $checkbox_value = get_post_meta( $post->ID, self::FB_VISIBILITY, true );
721
+
722
+ printf(
723
+ 'Visible: <input name="%1$s" type="checkbox" value="1" %2$s/>',
724
+ self::FB_VISIBILITY,
725
+ $checkbox_value === '' ? '' : 'checked'
726
+ );
727
+ printf( '<p/><input name="is_product_page" type="hidden" value="1"' );
728
+
729
+ printf(
730
+ '<p/><a href="#" onclick="fb_reset_product(%1$s)">
731
+ Reset Facebook metadata</a>',
732
+ $post->ID
733
+ );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
734
 
735
+ printf(
736
+ '<p/><a href="#" onclick="fb_delete_product(%1$s)">
737
+ Delete product(s) on Facebook</a>',
738
+ $post->ID
739
+ );
740
+ } else {
741
+ printf( '<b>This product is not yet synced to Facebook.</b>' );
742
+ }
743
+ printf( '</span>' );
744
+ }
745
+
746
+ private function get_product_count() {
747
+ $args = array(
748
+ 'post_type' => 'product',
749
+ 'post_status' => 'publish',
750
+ 'posts_per_page' => -1,
751
+ 'fields' => 'ids',
752
+ );
753
+ $products = new WP_Query( $args );
754
+
755
+ wp_reset_postdata();
756
+
757
+ echo $products->found_posts;
758
+ }
759
+
760
+ /**
761
+ * Load DIA specific JS Data
762
+ */
763
+ public function load_assets() {
764
+ $screen = get_current_screen();
765
+ $ajax_data = array(
766
+ 'nonce' => wp_create_nonce( 'wc_facebook_infobanner_jsx' ),
767
+ );
768
+ // load banner assets
769
+ wp_enqueue_script(
770
+ 'wc_facebook_infobanner_jsx',
771
+ plugins_url(
772
+ '/assets/js/facebook-infobanner.js?ts=' . time(),
773
+ __FILE__
774
+ )
775
+ );
776
+ wp_localize_script(
777
+ 'wc_facebook_infobanner_jsx',
778
+ 'wc_facebook_infobanner_jsx',
779
+ $ajax_data
780
+ );
781
+
782
+ wp_enqueue_style(
783
+ 'wc_facebook_infobanner_css',
784
+ plugins_url(
785
+ '/assets/css/facebook-infobanner.css',
786
+ __FILE__
787
+ )
788
+ );
789
+
790
+ if ( strpos( $screen->id, 'page_wc-settings' ) == 0 ) {
791
+ return;
792
+ }
793
+
794
+ if ( empty( $_GET['tab'] ) ) {
795
+ return;
796
+ }
797
+
798
+ if ( 'integration' !== $_GET['tab'] ) {
799
+ return;
800
+ }
801
+
802
+ ?>
803
+ <script>
804
+ window.facebookAdsToolboxConfig = {
805
+ hasGzipSupport:
806
+ '<?php echo extension_loaded( 'zlib' ) ? 'true' : 'false'; ?>'
807
+ ,enabledPlugins: ['MESSENGER_CHAT','INSTAGRAM_SHOP', 'PAGE_SHOP']
808
+ ,enableSubscription: '<?php echo class_exists( 'WC_Subscriptions' ) ? 'true' : 'false'; ?>'
809
+ ,popupOrigin: '<?php echo isset( $_GET['url'] ) ? esc_js( $_GET['url'] ) : 'https://www.facebook.com/'; ?>'
810
+ ,feedWasDisabled: 'true'
811
+ ,platform: 'WooCommerce'
812
+ ,pixel: {
813
+ pixelId: '<?php echo $this->pixel_id ?: ''; ?>'
814
+ ,advanced_matching_supported: true
815
+ }
816
+ ,diaSettingId: '<?php echo $this->external_merchant_settings_id ?: ''; ?>'
817
+ ,store: {
818
+ baseUrl: window.location.protocol + '//' + window.location.host
819
+ ,baseCurrency:'<?php echo esc_js( WC_Admin_Settings::get_option( 'woocommerce_currency' ) ); ?>'
820
+ ,timezoneId: '<?php echo date( 'Z' ); ?>'
821
+ ,storeName: '<?php echo esc_js( WC_Facebookcommerce_Utils::get_store_name() ); ?>'
822
+ ,version: '<?php echo WC()->version; ?>'
823
+ ,php_version: '<?php echo PHP_VERSION; ?>'
824
+ ,plugin_version: '<?php echo WC_Facebookcommerce_Utils::PLUGIN_VERSION; ?>'
825
+ }
826
+ ,feed: {
827
+ totalVisibleProducts: '<?php echo $this->get_product_count(); ?>'
828
+ ,hasClientSideFeedUpload: '<?php echo ! ! $this->feed_id; ?>'
829
+ }
830
+ ,feedPrepared: {
831
+ feedUrl: ''
832
+ ,feedPingUrl: ''
833
+ ,samples: <?php echo $this->get_sample_product_feed(); ?>
834
+ }
835
+ ,tokenExpired: '<?php echo $this->settings['fb_api_key'] && ! $this->get_page_name(); ?>'
836
+ };
837
+ </script>
838
+ <?php
839
+ $ajax_data = array(
840
+ 'nonce' => wp_create_nonce( 'wc_facebook_settings_jsx' ),
841
+ );
842
+ wp_enqueue_script(
843
+ 'wc_facebook_settings_jsx',
844
+ plugins_url(
845
+ '/assets/js/facebook-settings.js?ts=' . time(),
846
+ __FILE__
847
+ )
848
+ );
849
+ wp_localize_script(
850
+ 'wc_facebook_settings_jsx',
851
+ 'wc_facebook_settings_jsx',
852
+ $ajax_data
853
+ );
854
+ wp_enqueue_style(
855
+ 'wc_facebook_css',
856
+ plugins_url(
857
+ '/assets/css/facebook.css',
858
+ __FILE__
859
+ )
860
+ );
861
+ }
862
+
863
+ function on_product_delete( $wp_id ) {
864
+ $woo_product = new WC_Facebook_Product( $wp_id );
865
+ if ( ! $woo_product->exists() ) {
866
+ // This happens when the wp_id is not a product or it's already
867
+ // been deleted.
868
+ return;
869
+ }
870
+ $fb_product_group_id = $this->get_product_fbid(
871
+ self::FB_PRODUCT_GROUP_ID,
872
+ $wp_id,
873
+ $woo_product
874
+ );
875
+ $fb_product_item_id = $this->get_product_fbid(
876
+ self::FB_PRODUCT_ITEM_ID,
877
+ $wp_id,
878
+ $woo_product
879
+ );
880
+ if ( ! ( $fb_product_group_id || $fb_product_item_id ) ) {
881
+ return; // No synced product, no-op.
882
+ }
883
+ $products = array( $wp_id );
884
+ if ( WC_Facebookcommerce_Utils::is_variable_type( $woo_product->get_type() ) ) {
885
+ $children = $woo_product->get_children();
886
+ $products = array_merge( $products, $children );
887
+ }
888
+ foreach ( $products as $item_id ) {
889
+ $this->delete_product_item( $item_id );
890
+ }
891
+ if ( $fb_product_group_id ) {
892
+ $pg_result = $this->fbgraph->delete_product_group( $fb_product_group_id );
893
+ WC_Facebookcommerce_Utils::log( $pg_result );
894
+ }
895
+ }
896
+
897
+ /**
898
+ * Update FB visibility for trashing and restore.
899
+ */
900
+ function fb_change_product_published_status( $new_status, $old_status, $post ) {
901
+ global $post;
902
+ $visibility = $new_status == 'publish' ? 'published' : 'staging';
903
+
904
+ // change from publish status -> unpublish status, e.g. trash, draft, etc.
905
+ // change from trash status -> publish status
906
+ // no need to update for change from trash <-> unpublish status
907
+ if ( ( $old_status == 'publish' && $new_status != 'publish' ) ||
908
+ ( $old_status == 'trash' && $new_status == 'publish' ) ) {
909
+ $this->update_fb_visibility( $post->ID, $visibility );
910
+ }
911
+ }
912
+
913
+ /**
914
+ * Generic function for use with any product publishing.
915
+ * Will determine product type (simple or variable) and delegate to
916
+ * appropriate handler.
917
+ */
918
+ function on_product_publish( $wp_id ) {
919
+ if ( get_post_status( $wp_id ) != 'publish' ) {
920
+ return;
921
+ }
922
+
923
+ $woo_product = new WC_Facebook_Product( $wp_id );
924
+ $product_type = $woo_product->get_type();
925
+ if ( WC_Facebookcommerce_Utils::is_variable_type( $woo_product->get_type() ) ) {
926
+ $this->on_variable_product_publish( $wp_id, $woo_product );
927
+ } else {
928
+ $this->on_simple_product_publish( $wp_id, $woo_product );
929
+ }
930
+ }
931
+
932
+ /**
933
+ * If the user has opt-in to remove products that are out of stock,
934
+ * this function will delete the product from FB Page as well.
935
+ */
936
+ function delete_on_out_of_stock( $wp_id, $woo_product ) {
937
+ if ( get_option( 'woocommerce_hide_out_of_stock_items' ) === 'yes' &&
938
+ ! $woo_product->is_in_stock() ) {
939
+ $this->delete_product_item( $wp_id );
940
+ return true;
941
+ }
942
+ return false;
943
+ }
944
+
945
+ function on_variable_product_publish( $wp_id, $woo_product = null ) {
946
+ if ( get_option( 'fb_disable_sync_on_dev_environment', false ) ) {
947
+ return;
948
+ }
949
+
950
+ if ( get_post_status( $wp_id ) != 'publish' ) {
951
+ return;
952
+ }
953
+ // Check if product group has been published to FB. If not, it's new.
954
+ // If yes, loop through variants and see if product items are published.
955
+ if ( ! $woo_product ) {
956
+ $woo_product = new WC_Facebook_Product( $wp_id );
957
+ }
958
+
959
+ if ( $this->delete_on_out_of_stock( $wp_id, $woo_product ) ) {
960
+ return;
961
+ }
962
+
963
+ if ( isset( $_POST[ self::FB_PRODUCT_DESCRIPTION ] ) ) {
964
+ $woo_product->set_description( $_POST[ self::FB_PRODUCT_DESCRIPTION ] );
965
+ }
966
+ if ( isset( $_POST[ WC_Facebook_Product::FB_PRODUCT_PRICE ] ) ) {
967
+ $woo_product->set_price( $_POST[ WC_Facebook_Product::FB_PRODUCT_PRICE ] );
968
+ }
969
+ if ( isset( $_POST[ WC_Facebook_Product::FB_PRODUCT_IMAGE ] ) ) {
970
+ $woo_product->set_product_image( $_POST[ WC_Facebook_Product::FB_PRODUCT_IMAGE ] );
971
+ }
972
+
973
+ $woo_product->set_use_parent_image(
974
+ ( isset( $_POST[ self::FB_VARIANT_IMAGE ] ) ) ?
975
+ $_POST[ self::FB_VARIANT_IMAGE ] :
976
+ null
977
+ );
978
+ $fb_product_group_id = $this->get_product_fbid(
979
+ self::FB_PRODUCT_GROUP_ID,
980
+ $wp_id,
981
+ $woo_product
982
+ );
983
+
984
+ if ( $fb_product_group_id ) {
985
+ $woo_product->update_visibility(
986
+ isset( $_POST['is_product_page'] ),
987
+ isset( $_POST[ self::FB_VISIBILITY ] )
988
+ );
989
+ $this->update_product_group( $woo_product );
990
+ $child_products = $woo_product->get_children();
991
+ $variation_id = $woo_product->find_matching_product_variation();
992
+ // check if item_id is default variation. If yes, update in the end.
993
+ // If default variation value is to update, delete old fb_product_item_id
994
+ // and create new one in order to make it order correctly.
995
+ foreach ( $child_products as $item_id ) {
996
+ $fb_product_item_id =
997
+ $this->on_simple_product_publish( $item_id, null, $woo_product );
998
+ if ( $item_id == $variation_id && $fb_product_item_id ) {
999
+ $this->set_default_variant( $fb_product_group_id, $fb_product_item_id );
1000
+ }
1001
+ }
1002
+ } else {
1003
+ $this->create_product_variable( $woo_product );
1004
+ }
1005
+ }
1006
+
1007
+ function on_simple_product_publish(
1008
+ $wp_id,
1009
+ $woo_product = null,
1010
+ &$parent_product = null ) {
1011
+ if ( get_option( 'fb_disable_sync_on_dev_environment', false ) ) {
1012
+ return;
1013
+ }
1014
+
1015
+ if ( get_post_status( $wp_id ) != 'publish' ) {
1016
+ return;
1017
+ }
1018
+
1019
+ if ( ! $woo_product ) {
1020
+ $woo_product = new WC_Facebook_Product( $wp_id, $parent_product );
1021
+ }
1022
+
1023
+ if ( $this->delete_on_out_of_stock( $wp_id, $woo_product ) ) {
1024
+ return;
1025
+ }
1026
+
1027
+ if ( isset( $_POST[ self::FB_PRODUCT_DESCRIPTION ] ) ) {
1028
+ $woo_product->set_description( $_POST[ self::FB_PRODUCT_DESCRIPTION ] );
1029
+ }
1030
+
1031
+ if ( isset( $_POST[ WC_Facebook_Product::FB_PRODUCT_PRICE ] ) ) {
1032
+ $woo_product->set_price( $_POST[ WC_Facebook_Product::FB_PRODUCT_PRICE ] );
1033
+ }
1034
+
1035
+ if ( isset( $_POST[ WC_Facebook_Product::FB_PRODUCT_IMAGE ] ) ) {
1036
+ $woo_product->set_product_image( $_POST[ WC_Facebook_Product::FB_PRODUCT_IMAGE ] );
1037
+ }
1038
+
1039
+ // Check if this product has already been published to FB.
1040
+ // If not, it's new!
1041
+ $fb_product_item_id = $this->get_product_fbid(
1042
+ self::FB_PRODUCT_ITEM_ID,
1043
+ $wp_id,
1044
+ $woo_product
1045
+ );
1046
+
1047
+ if ( $fb_product_item_id ) {
1048
+ $woo_product->update_visibility(
1049
+ isset( $_POST['is_product_page'] ),
1050
+ isset( $_POST[ self::FB_VISIBILITY ] )
1051
+ );
1052
+ $this->update_product_item( $woo_product, $fb_product_item_id );
1053
+ return $fb_product_item_id;
1054
+ } else {
1055
+ // Check if this is a new product item for an existing product group
1056
+ if ( $woo_product->get_parent_id() ) {
1057
+ $fb_product_group_id = $this->get_product_fbid(
1058
+ self::FB_PRODUCT_GROUP_ID,
1059
+ $woo_product->get_parent_id(),
1060
+ $woo_product
1061
+ );
1062
+
1063
+ // New variant added
1064
+ if ( $fb_product_group_id ) {
1065
+ return $this->create_product_simple( $woo_product, $fb_product_group_id );
1066
+ } else {
1067
+ WC_Facebookcommerce_Utils::fblog(
1068
+ 'Wrong! simple_product_publish called without group ID for
1069
+ a variable product!',
1070
+ array(),
1071
+ true
1072
+ );
1073
+ }
1074
+ } else {
1075
+ return $this->create_product_simple( $woo_product ); // new product
1076
+ }
1077
+ }
1078
+ }
1079
+
1080
+ function create_product_variable( $woo_product ) {
1081
+ $retailer_id = WC_Facebookcommerce_Utils::get_fb_retailer_id( $woo_product );
1082
+
1083
+ $fb_product_group_id = $this->create_product_group(
1084
+ $woo_product,
1085
+ $retailer_id,
1086
+ true
1087
+ );
1088
+
1089
+ if ( $fb_product_group_id ) {
1090
+ $child_products = $woo_product->get_children();
1091
+ $variation_id = $woo_product->find_matching_product_variation();
1092
+ foreach ( $child_products as $item_id ) {
1093
+ $child_product = new WC_Facebook_Product( $item_id, $woo_product );
1094
+ $retailer_id =
1095
+ WC_Facebookcommerce_Utils::get_fb_retailer_id( $child_product );
1096
+ $fb_product_item_id = $this->create_product_item(
1097
+ $child_product,
1098
+ $retailer_id,
1099
+ $fb_product_group_id
1100
+ );
1101
+ if ( $item_id == $variation_id && $fb_product_item_id ) {
1102
+ $this->set_default_variant( $fb_product_group_id, $fb_product_item_id );
1103
+ }
1104
+ }
1105
+ }
1106
+ }
1107
+
1108
+ /**
1109
+ * Create product group and product, store fb-specific info
1110
+ **/
1111
+ function create_product_simple( $woo_product, $fb_product_group_id = null ) {
1112
+ $retailer_id = WC_Facebookcommerce_Utils::get_fb_retailer_id( $woo_product );
1113
+
1114
+ if ( ! $fb_product_group_id ) {
1115
+ $fb_product_group_id = $this->create_product_group(
1116
+ $woo_product,
1117
+ $retailer_id
1118
+ );
1119
+ }
1120
+
1121
+ if ( $fb_product_group_id ) {
1122
+ $fb_product_item_id = $this->create_product_item(
1123
+ $woo_product,
1124
+ $retailer_id,
1125
+ $fb_product_group_id
1126
+ );
1127
+ return $fb_product_item_id;
1128
+ }
1129
+ }
1130
+
1131
+ function create_product_group( $woo_product, $retailer_id, $variants = false ) {
1132
+
1133
+ $product_group_data = array(
1134
+ 'retailer_id' => $retailer_id,
1135
+ );
1136
+
1137
+ // Default visibility on create = published
1138
+ $woo_product->fb_visibility = true;
1139
+ update_post_meta( $woo_product->get_id(), self::FB_VISIBILITY, true );
1140
+
1141
+ if ( $variants ) {
1142
+ $product_group_data['variants'] =
1143
+ $woo_product->prepare_variants_for_group();
1144
+ }
1145
+
1146
+ $create_product_group_result = $this->check_api_result(
1147
+ $this->fbgraph->create_product_group(
1148
+ $this->product_catalog_id,
1149
+ $product_group_data
1150
+ ),
1151
+ $product_group_data,
1152
+ $woo_product->get_id()
1153
+ );
1154
+
1155
+ // New variant added
1156
+ if ( $create_product_group_result ) {
1157
+ $decode_result = WC_Facebookcommerce_Utils::decode_json( $create_product_group_result['body'] );
1158
+ $fb_product_group_id = $decode_result->id;
1159
+ // update_post_meta is actually more of a create_or_update
1160
+ update_post_meta(
1161
+ $woo_product->get_id(),
1162
+ self::FB_PRODUCT_GROUP_ID,
1163
+ $fb_product_group_id
1164
+ );
1165
+
1166
+ $this->display_success_message(
1167
+ 'Created product group <a href="https://facebook.com/' .
1168
+ $fb_product_group_id . '" target="_blank">' .
1169
+ $fb_product_group_id . '</a> on Facebook.'
1170
+ );
1171
+
1172
+ return $fb_product_group_id;
1173
+ }
1174
+ }
1175
+
1176
+ function create_product_item( $woo_product, $retailer_id, $product_group_id ) {
1177
+ // Default visibility on create = published
1178
+ $woo_product->fb_visibility = true;
1179
+ $product_data = $woo_product->prepare_product( $retailer_id );
1180
+ if ( ! $product_data['price'] ) {
1181
+ return 0;
1182
+ }
1183
+
1184
+ update_post_meta( $woo_product->get_id(), self::FB_VISIBILITY, true );
1185
+
1186
+ $product_result = $this->check_api_result(
1187
+ $this->fbgraph->create_product_item(
1188
+ $product_group_id,
1189
+ $product_data
1190
+ ),
1191
+ $product_data,
1192
+ $woo_product->get_id()
1193
+ );
1194
+
1195
+ if ( $product_result ) {
1196
+ $decode_result = WC_Facebookcommerce_Utils::decode_json( $product_result['body'] );
1197
+ $fb_product_item_id = $decode_result->id;
1198
+
1199
+ update_post_meta(
1200
+ $woo_product->get_id(),
1201
+ self::FB_PRODUCT_ITEM_ID,
1202
+ $fb_product_item_id
1203
+ );
1204
+
1205
+ $this->display_success_message(
1206
+ 'Created product item <a href="https://facebook.com/' .
1207
+ $fb_product_item_id . '" target="_blank">' .
1208
+ $fb_product_item_id . '</a> on Facebook.'
1209
+ );
1210
+
1211
+ return $fb_product_item_id;
1212
+ }
1213
+ }
1214
+
1215
+
1216
+ /**
1217
+ * Update existing product group (variant data only)
1218
+ **/
1219
+ function update_product_group( $woo_product ) {
1220
+ $fb_product_group_id = $this->get_product_fbid(
1221
+ self::FB_PRODUCT_GROUP_ID,
1222
+ $woo_product->get_id(),
1223
+ $woo_product
1224
+ );
1225
+
1226
+ if ( ! $fb_product_group_id ) {
1227
+ return;
1228
+ }
1229
+
1230
+ $variants = $woo_product->prepare_variants_for_group();
1231
+
1232
+ if ( ! $variants ) {
1233
+ WC_Facebookcommerce_Utils::log(
1234
+ sprintf(
1235
+ __(
1236
+ 'Nothing to update for product group for %1$s',
1237
+ 'facebook-for-woocommerce'
1238
+ ),
1239
+ $fb_product_group_id
1240
+ )
1241
+ );
1242
+ return;
1243
+ }
1244
+
1245
+ $product_group_data = array(
1246
+ 'variants' => $variants,
1247
+ );
1248
+
1249
+ $result = $this->check_api_result(
1250
+ $this->fbgraph->update_product_group(
1251
+ $fb_product_group_id,
1252
+ $product_group_data
1253
+ )
1254
+ );
1255
+
1256
+ if ( $result ) {
1257
+ $this->display_success_message(
1258
+ 'Updated product group <a href="https://facebook.com/' .
1259
+ $fb_product_group_id . '" target="_blank">' . $fb_product_group_id .
1260
+ '</a> on Facebook.'
1261
+ );
1262
+ }
1263
+ }
1264
+
1265
+ /**
1266
+ * Update existing product
1267
+ **/
1268
+ function update_product_item( $woo_product, $fb_product_item_id ) {
1269
+ $product_data = $woo_product->prepare_product();
1270
+
1271
+ $result = $this->check_api_result(
1272
+ $this->fbgraph->update_product_item(
1273
+ $fb_product_item_id,
1274
+ $product_data
1275
+ )
1276
+ );
1277
+
1278
+ if ( $result ) {
1279
+ $this->display_success_message(
1280
+ 'Updated product <a href="https://facebook.com/' . $fb_product_item_id .
1281
+ '" target="_blank">' . $fb_product_item_id . '</a> on Facebook.'
1282
+ );
1283
+ }
1284
+ }
1285
+
1286
+ /**
1287
+ * Save settings via AJAX (to preserve window context for onboarding)
1288
+ **/
1289
+ function ajax_save_fb_settings() {
1290
+ WC_Facebookcommerce_Utils::check_woo_ajax_permissions( 'save settings', true );
1291
+ check_ajax_referer( 'wc_facebook_settings_jsx' );
1292
+
1293
+ if ( isset( $_REQUEST ) ) {
1294
+ if ( ! isset( $_REQUEST['facebook_for_woocommerce'] ) ) {
1295
+ // This is not a request from our plugin,
1296
+ // some other handler or plugin probably
1297
+ // wants to handle it and wp_die() after.
1298
+ return;
1299
+ }
1300
+
1301
+ if ( isset( $_REQUEST['api_key'] ) && ctype_alnum( $_REQUEST['api_key'] ) ) {
1302
+ $this->settings['fb_api_key'] = $_REQUEST['api_key'];
1303
+ }
1304
+ if ( isset( $_REQUEST['product_catalog_id'] ) &&
1305
+ ctype_digit( $_REQUEST['product_catalog_id'] ) ) {
1306
+
1307
+ if ( $this->product_catalog_id != '' &&
1308
+ $this->product_catalog_id != $_REQUEST['product_catalog_id'] ) {
1309
+ $this->reset_all_products();
1310
+ }
1311
+ $this->settings['fb_product_catalog_id'] =
1312
+ $_REQUEST['product_catalog_id'];
1313
+ }
1314
+ if ( isset( $_REQUEST['pixel_id'] ) && ctype_digit( $_REQUEST['pixel_id'] ) ) {
1315
+ // To prevent race conditions with pixel-only settings,
1316
+ // only save a pixel if we already have an API key.
1317
+ if ( $this->settings['fb_api_key'] ) {
1318
+ $this->settings['fb_pixel_id'] = $_REQUEST['pixel_id'];
1319
+ if ( $this->pixel_id != $_REQUEST['pixel_id'] ) {
1320
+ $this->settings['pixel_install_time'] = current_time( 'mysql' );
1321
+ }
1322
+ } else {
1323
+ WC_Facebookcommerce_Utils::log(
1324
+ 'Got pixel-only settings, doing nothing'
1325
+ );
1326
+ echo 'Not saving pixel-only settings';
1327
+ wp_die();
1328
+ }
1329
+ }
1330
+ if ( isset( $_REQUEST['pixel_use_pii'] ) ) {
1331
+ $this->settings['fb_pixel_use_pii'] =
1332
+ ( $_REQUEST['pixel_use_pii'] === 'true' ||
1333
+ $_REQUEST['pixel_use_pii'] === true ) ? 'yes' : 'no';
1334
+ }
1335
+ if ( isset( $_REQUEST['page_id'] ) &&
1336
+ ctype_digit( $_REQUEST['page_id'] ) ) {
1337
+ $this->settings['fb_page_id'] = $_REQUEST['page_id'];
1338
+ }
1339
+ if ( isset( $_REQUEST['external_merchant_settings_id'] ) &&
1340
+ ctype_digit( $_REQUEST['external_merchant_settings_id'] ) ) {
1341
+ $this->settings['fb_external_merchant_settings_id'] =
1342
+ $_REQUEST['external_merchant_settings_id'];
1343
+ }
1344
+ if ( isset( $_REQUEST['is_messenger_chat_plugin_enabled'] ) ) {
1345
+ $this->settings['is_messenger_chat_plugin_enabled'] =
1346
+ ( $_REQUEST['is_messenger_chat_plugin_enabled'] === 'true' ||
1347
+ $_REQUEST['is_messenger_chat_plugin_enabled'] === true ) ? 'yes' : 'no';
1348
+ }
1349
+ if ( isset( $_REQUEST['facebook_jssdk_version'] ) ) {
1350
+ $this->settings['facebook_jssdk_version'] =
1351
+ sanitize_text_field( $_REQUEST['facebook_jssdk_version'] );
1352
+ }
1353
+ if ( isset( $_REQUEST['msger_chat_customization_greeting_text_code'] )
1354
+ && ctype_digit( $_REQUEST['msger_chat_customization_greeting_text_code'] ) ) {
1355
+ $this->settings['msger_chat_customization_greeting_text_code'] =
1356
+ $_REQUEST['msger_chat_customization_greeting_text_code'];
1357
+ }
1358
+ if ( isset( $_REQUEST['msger_chat_customization_locale'] ) ) {
1359
+ $this->settings['msger_chat_customization_locale'] =
1360
+ sanitize_text_field( $_REQUEST['msger_chat_customization_locale'] );
1361
+ }
1362
+ if ( isset( $_REQUEST['msger_chat_customization_theme_color_code'] ) &&
1363
+ ctype_digit( $_REQUEST['msger_chat_customization_theme_color_code'] ) ) {
1364
+ $this->settings['msger_chat_customization_theme_color_code'] =
1365
+ $_REQUEST['msger_chat_customization_theme_color_code'];
1366
+ }
1367
+
1368
+ update_option(
1369
+ $this->get_option_key(),
1370
+ apply_filters(
1371
+ 'woocommerce_settings_api_sanitized_fields_' . $this->id,
1372
+ $this->settings
1373
+ )
1374
+ );
1375
+
1376
+ WC_Facebookcommerce_Utils::log( 'Settings saved!' );
1377
+ echo 'settings_saved';
1378
+ } else {
1379
+ echo 'No Request';
1380
+ }
1381
+
1382
+ wp_die();
1383
+ }
1384
+
1385
+ /**
1386
+ * Delete all settings via AJAX
1387
+ **/
1388
+ function ajax_delete_fb_settings() {
1389
+ check_ajax_referer( 'wc_facebook_settings_jsx' );
1390
+ if ( ! WC_Facebookcommerce_Utils::check_woo_ajax_permissions( 'delete settings', false ) ) {
1391
+ return;
1392
+ }
1393
+
1394
+ // Do not allow reset in the middle of product sync
1395
+ $currently_syncing = get_transient( self::FB_SYNC_IN_PROGRESS );
1396
+ if ( $currently_syncing ) {
1397
+ wp_send_json(
1398
+ 'A Facebook product sync is currently in progress.
1399
+ Deleting settings during product sync may cause errors.'
1400
+ );
1401
+ return;
1402
+ }
1403
+
1404
+ if ( isset( $_REQUEST ) ) {
1405
+ $ems = $this->settings['fb_external_merchant_settings_id'];
1406
+ if ( $ems ) {
1407
+ WC_Facebookcommerce_Utils::fblog(
1408
+ 'Deleted all settings!',
1409
+ array(),
1410
+ false,
1411
+ $ems
1412
+ );
1413
+ }
1414
+
1415
+ $this->init_settings();
1416
+ $this->settings['fb_api_key'] = '';
1417
+ $this->settings['fb_product_catalog_id'] = '';
1418
+
1419
+ $this->settings['fb_pixel_id'] = '';
1420
+ $this->settings['fb_pixel_use_pii'] = 'no';
1421
+
1422
+ $this->settings['fb_page_id'] = '';
1423
+ $this->settings['fb_external_merchant_settings_id'] = '';
1424
+ $this->settings['pixel_install_time'] = '';
1425
+ $this->settings['fb_feed_id'] = '';
1426
+ $this->settings['fb_upload_id'] = '';
1427
+ $this->settings['upload_end_time'] = '';
1428
+
1429
+ WC_Facebookcommerce_Pixel::set_pixel_id( 0 );
1430
+
1431
+ update_option(
1432
+ $this->get_option_key(),
1433
+ apply_filters(
1434
+ 'woocommerce_settings_api_sanitized_fields_' . $this->id,
1435
+ $this->settings
1436
+ )
1437
+ );
1438
+
1439
+ // Clean up old messages
1440
+ delete_transient( 'facebook_plugin_api_error' );
1441
+ delete_transient( 'facebook_plugin_api_success' );
1442
+ delete_transient( 'facebook_plugin_api_warning' );
1443
+ delete_transient( 'facebook_plugin_api_info' );
1444
+ delete_transient( 'facebook_plugin_api_sticky' );
1445
+
1446
+ $this->reset_all_products();
1447
+
1448
+ WC_Facebookcommerce_Utils::log( 'Settings deleted' );
1449
+ echo 'Settings Deleted';
1450
+
1451
+ }
1452
+
1453
+ wp_die();
1454
+ }
1455
+
1456
+ /**
1457
+ * Check Feed Upload Status
1458
+ **/
1459
+ function ajax_check_feed_upload_status() {
1460
+ WC_Facebookcommerce_Utils::check_woo_ajax_permissions( 'check feed upload status', true );
1461
+ check_ajax_referer( 'wc_facebook_settings_jsx' );
1462
+ if ( $this->settings['fb_api_key'] ) {
1463
+ $response = array(
1464
+ 'connected' => true,
1465
+ 'status' => 'in progress',
1466
+ );
1467
+ if ( $this->settings['fb_upload_id'] ) {
1468
+ if ( ! isset( $this->fbproductfeed ) ) {
1469
+ if ( ! class_exists( 'WC_Facebook_Product_Feed' ) ) {
1470
+ include_once 'includes/fbproductfeed.php';
1471
+ }
1472
+ $this->fbproductfeed = new WC_Facebook_Product_Feed(
1473
+ $this->product_catalog_id,
1474
+ $this->fbgraph
1475
+ );
1476
+ }
1477
+ $status = $this->fbproductfeed->is_upload_complete( $this->settings );
1478
+
1479
+ $response['status'] = $status;
1480
+ } else {
1481
+ $response = array(
1482
+ 'connected' => true,
1483
+ 'status' => 'error',
1484
+ );
1485
+ }
1486
+ if ( $response['status'] == 'complete' ) {
1487
+ update_option(
1488
+ $this->get_option_key(),
1489
+ apply_filters(
1490
+ 'woocommerce_settings_api_sanitized_fields_' . $this->id,
1491
+ $this->settings
1492
+ )
1493
+ );
1494
+ }
1495
+ } else {
1496
+ $response = array(
1497
+ 'connected' => false,
1498
+ );
1499
+ }
1500
+ printf( json_encode( $response ) );
1501
+ wp_die();
1502
+ }
1503
+
1504
+ /**
1505
+ * Display custom success message (sugar)
1506
+ **/
1507
+ function display_success_message( $msg ) {
1508
+ $msg = self::FB_ADMIN_MESSAGE_PREPEND . $msg;
1509
+ set_transient(
1510
+ 'facebook_plugin_api_success',
1511
+ $msg,
1512
+ self::FB_MESSAGE_DISPLAY_TIME
1513
+ );
1514
+ }
1515
+
1516
+ /**
1517
+ * Display custom warning message (sugar)
1518
+ **/
1519
+ function display_warning_message( $msg ) {
1520
+ $msg = self::FB_ADMIN_MESSAGE_PREPEND . $msg;
1521
+ set_transient(
1522
+ 'facebook_plugin_api_warning',
1523
+ $msg,
1524
+ self::FB_MESSAGE_DISPLAY_TIME
1525
+ );
1526
+ }
1527
+
1528
+ /**
1529
+ * Display custom info message (sugar)
1530
+ **/
1531
+ function display_info_message( $msg ) {
1532
+ $msg = self::FB_ADMIN_MESSAGE_PREPEND . $msg;
1533
+ set_transient(
1534
+ 'facebook_plugin_api_info',
1535
+ $msg,
1536
+ self::FB_MESSAGE_DISPLAY_TIME
1537
+ );
1538
+ }
1539
+
1540
+ /**
1541
+ * Display custom "sticky" info message.
1542
+ * Call remove_sticky_message or wait for time out.
1543
+ **/
1544
+ function display_sticky_message( $msg ) {
1545
+ $msg = self::FB_ADMIN_MESSAGE_PREPEND . $msg;
1546
+ set_transient(
1547
+ 'facebook_plugin_api_sticky',
1548
+ $msg,
1549
+ self::FB_MESSAGE_DISPLAY_TIME
1550
+ );
1551
+ }
1552
+
1553
+ /**
1554
+ * Remove custom "sticky" info message
1555
+ **/
1556
+ function remove_sticky_message() {
1557
+ delete_transient( 'facebook_plugin_api_sticky' );
1558
+ }
1559
+
1560
+ function remove_resync_message() {
1561
+ $msg = get_transient( 'facebook_plugin_api_sticky' );
1562
+ if ( $msg && strpos( $msg, 'Sync' ) !== false ) {
1563
+ delete_transient( 'facebook_plugin_resync_sticky' );
1564
+ }
1565
+ }
1566
+
1567
+ /**
1568
+ * Display custom error message (sugar)
1569
+ **/
1570
+ function display_error_message( $msg ) {
1571
+ $msg = self::FB_ADMIN_MESSAGE_PREPEND . $msg;
1572
+ WC_Facebookcommerce_Utils::log( $msg );
1573
+ set_transient(
1574
+ 'facebook_plugin_api_error',
1575
+ $msg,
1576
+ self::FB_MESSAGE_DISPLAY_TIME
1577
+ );
1578
+ }
1579
+
1580
+ /**
1581
+ * Display error message from API result (sugar)
1582
+ **/
1583
+ function display_error_message_from_result( $result ) {
1584
+ $msg = json_decode( $result['body'] )->error->message;
1585
+ $this->display_error_message( $msg );
1586
+ }
1587
+
1588
+ /**
1589
+ * Deal with FB API responses, display error if FB API returns error
1590
+ *
1591
+ * @return result if response is 200, null otherwise
1592
+ **/
1593
+ function check_api_result( $result, $logdata = null, $wpid = null ) {
1594
+ if ( is_wp_error( $result ) ) {
1595
+ WC_Facebookcommerce_Utils::log( $result->get_error_message() );
1596
+ $this->display_error_message(
1597
+ 'There was an issue connecting to the Facebook API: ' .
1598
+ $result->get_error_message()
1599
+ );
1600
+ return;
1601
+ }
1602
+ if ( $result['response']['code'] != '200' ) {
1603
+ // Catch 10800 fb error code ("Duplicate retailer ID") and capture FBID
1604
+ // if possible, otherwise let user know we found dupe SKUs
1605
+ $body = WC_Facebookcommerce_Utils::decode_json( $result['body'] );
1606
+ if ( $body && $body->error->code == '10800' ) {
1607
+ $error_data = $body->error->error_data; // error_data may contain FBIDs
1608
+ if ( $error_data && $wpid ) {
1609
+ $existing_id = $this->get_existing_fbid( $error_data, $wpid );
1610
+ if ( $existing_id ) {
1611
+ // Add "existing_id" ID to result
1612
+ $body->id = $existing_id;
1613
+ $result['body'] = json_encode( $body );
1614
+ return $result;
1615
+ }
1616
+ }
1617
+ } else {
1618
+ $this->display_error_message_from_result( $result );
1619
+ }
1620
+
1621
+ WC_Facebookcommerce_Utils::log( $result );
1622
+ $data = array(
1623
+ 'result' => $result,
1624
+ 'data' => $logdata,
1625
+ );
1626
+ WC_Facebookcommerce_Utils::fblog(
1627
+ 'Non-200 error code from FB',
1628
+ $data,
1629
+ true
1630
+ );
1631
+ return null;
1632
+ }
1633
+ return $result;
1634
+ }
1635
+
1636
+ function ajax_woo_adv_bulk_edit_compat( $import_id ) {
1637
+ if ( ! WC_Facebookcommerce_Utils::check_woo_ajax_permissions( 'adv bulk edit', false ) ) {
1638
+ return;
1639
+ }
1640
+ $type = isset( $_POST['type'] ) ? $_POST['type'] : '';
1641
+ if ( strpos( $type, 'product' ) !== false && strpos( $type, 'load' ) === false ) {
1642
+ $this->display_out_of_sync_message( 'advanced bulk edit' );
1643
+ }
1644
+ }
1645
+
1646
+ function wp_all_import_compat( $import_id ) {
1647
+ $import = new PMXI_Import_Record();
1648
+ $import->getById( $import_id );
1649
+ if ( ! $import->isEmpty() && in_array( $import->options['custom_type'], array( 'product', 'product_variation' ) ) ) {
1650
+ $this->display_out_of_sync_message( 'import' );
1651
+ }
1652
+ }
1653
+
1654
+ function display_out_of_sync_message( $action_name ) {
1655
+ $this->display_sticky_message(
1656
+ sprintf(
1657
+ 'Products may be out of Sync with Facebook due to your recent ' . $action_name . '.' .
1658
+ ' <a href="%s&fb_force_resync=true&remove_sticky=true">Re-Sync them with FB.</a>',
1659
+ WOOCOMMERCE_FACEBOOK_PLUGIN_SETTINGS_URL
1660
+ )
1661
+ );
1662
+ }
1663
+
1664
+ /**
1665
+ * If we get a product group ID or product item ID back for a dupe retailer
1666
+ * id error, update existing ID.
1667
+ *
1668
+ * @return null
1669
+ **/
1670
+ function get_existing_fbid( $error_data, $wpid ) {
1671
+ if ( isset( $error_data->product_group_id ) ) {
1672
+ update_post_meta(
1673
+ $wpid,
1674
+ self::FB_PRODUCT_GROUP_ID,
1675
+ (string) $error_data->product_group_id
1676
+ );
1677
+ return $error_data->product_group_id;
1678
+ } elseif ( isset( $error_data->product_item_id ) ) {
1679
+ update_post_meta(
1680
+ $wpid,
1681
+ self::FB_PRODUCT_ITEM_ID,
1682
+ (string) $error_data->product_item_id
1683
+ );
1684
+ return $error_data->product_item_id;
1685
+ } else {
1686
+ return;
1687
+ }
1688
+ }
1689
+
1690
+ /**
1691
+ * Check for api key and any other API errors
1692
+ **/
1693
+ function checks() {
1694
+ // Check required fields
1695
+
1696
+ if ( ! $this->api_key || ! $this->product_catalog_id ) {
1697
+ echo $this->get_message_html(
1698
+ sprintf(
1699
+ __(
1700
+ '%1$sFacebook for WooCommerce
1701
  is almost ready.%2$s To complete your configuration, %3$scomplete the
1702
  setup steps%4$s.',
1703
+ 'facebook-for-woocommerce'
1704
+ ),
1705
+ '<strong>',
1706
+ '</strong>',
1707
+ '<a href="' . esc_url( WOOCOMMERCE_FACEBOOK_PLUGIN_SETTINGS_URL ) . '">',
1708
+ '</a>'
1709
+ ),
1710
+ 'info'
1711
+ );
1712
+ }
1713
+
1714
+ // WooCommerce 2.x upgrade nag
1715
+ if ( $this->api_key && ( ! isset( $this->background_processor ) ) ) {
1716
+ echo $this->get_message_html(
1717
+ sprintf(
1718
+ __(
1719
+ 'Facebook product sync may not work correctly in WooCommerce version
1720
  %1$s. Please upgrade to WooCommerce 3.',
1721
+ 'facebook-for-woocommerce'
1722
+ ),
1723
+ WC()->version
1724
+ ),
1725
+ 'warning'
1726
+ );
1727
+ }
1728
+
1729
+ $this->maybe_display_facebook_api_messages();
1730
+ }
1731
+
1732
+ function get_sample_product_feed() {
1733
+ ob_start();
1734
+
1735
+ // Get up to 12 published posts that are products
1736
+ $args = array(
1737
+ 'post_type' => 'product',
1738
+ 'post_status' => 'publish',
1739
+ 'posts_per_page' => 12,
1740
+ 'fields' => 'ids',
1741
+ );
1742
+
1743
+ $post_ids = get_posts( $args );
1744
+ $items = array();
1745
+
1746
+ foreach ( $post_ids as $post_id ) {
1747
+
1748
+ $woo_product = new WC_Facebook_Product( $post_id );
1749
+ $product_data = $woo_product->prepare_product();
1750
+
1751
+ $feed_item = array(
1752
+ 'title' => strip_tags( $product_data['name'] ),
1753
+ 'availability' => $woo_product->is_in_stock() ? 'in stock' :
1754
+ 'out of stock',
1755
+ 'description' => strip_tags( $product_data['description'] ),
1756
+ 'id' => $product_data['retailer_id'],
1757
+ 'image_link' => $product_data['image_url'],
1758
+ 'brand' => strip_tags( WC_Facebookcommerce_Utils::get_store_name() ),
1759
+ 'link' => $product_data['url'],
1760
+ 'price' => $product_data['price'] . ' ' . get_woocommerce_currency(),
1761
+ );
1762
+
1763
+ array_push( $items, $feed_item );
1764
+ }
1765
+ // https://codex.wordpress.org/Function_Reference/wp_reset_postdata
1766
+ wp_reset_postdata();
1767
+ ob_end_clean();
1768
+ return json_encode( array( $items ) );
1769
+ }
1770
+
1771
+ /**
1772
+ * Loop through array of WPIDs to remove metadata.
1773
+ **/
1774
+ function delete_post_meta_loop( $products ) {
1775
+ foreach ( $products as $product_id ) {
1776
+ delete_post_meta( $product_id, self::FB_PRODUCT_GROUP_ID );
1777
+ delete_post_meta( $product_id, self::FB_PRODUCT_ITEM_ID );
1778
+ delete_post_meta( $product_id, self::FB_VISIBILITY );
1779
+ }
1780
+ }
1781
+
1782
+ /**
1783
+ * Remove FBIDs from all products when resetting store.
1784
+ **/
1785
+ function reset_all_products() {
1786
+ if ( ! is_admin() ) {
1787
+ WC_Facebookcommerce_Utils::log(
1788
+ 'Not resetting any FBIDs from products,
1789
+ must call reset from admin context.'
1790
+ );
1791
+ return false;
1792
+ }
1793
+
1794
+ $test_instance = WC_Facebook_Integration_Test::get_instance( $this );
1795
+ $this->test_mode = $test_instance::$test_mode;
1796
+
1797
+ // Include draft products (omit 'post_status' => 'publish')
1798
+ WC_Facebookcommerce_Utils::log( 'Removing FBIDs from all products' );
1799
+
1800
+ $post_ids = get_posts(
1801
+ array(
1802
+ 'post_type' => 'product',
1803
+ 'posts_per_page' => -1,
1804
+ 'fields' => 'ids',
1805
+ )
1806
+ );
1807
+
1808
+ $children = array();
1809
+ foreach ( $post_ids as $post_id ) {
1810
+ $children = array_merge(
1811
+ get_posts(
1812
+ array(
1813
+ 'post_type' => 'product_variation',
1814
+ 'posts_per_page' => -1,
1815
+ 'post_parent' => $post_id,
1816
+ 'fields' => 'ids',
1817
+ )
1818
+ ),
1819
+ $children
1820
+ );
1821
+ }
1822
+ $post_ids = array_merge( $post_ids, $children );
1823
+ $this->delete_post_meta_loop( $post_ids );
1824
+
1825
+ WC_Facebookcommerce_Utils::log( 'Product FBIDs deleted' );
1826
+ return true;
1827
+ }
1828
+
1829
+ /**
1830
+ * Remove FBIDs from a single WC product
1831
+ **/
1832
+ function reset_single_product( $wp_id ) {
1833
+ $woo_product = new WC_Facebook_Product( $wp_id );
1834
+ $products = array( $woo_product->get_id() );
1835
+ if ( WC_Facebookcommerce_Utils::is_variable_type( $woo_product->get_type() ) ) {
1836
+ $products = array_merge( $products, $woo_product->get_children() );
1837
+ }
1838
+
1839
+ $this->delete_post_meta_loop( $products );
1840
+
1841
+ WC_Facebookcommerce_Utils::log( 'Deleted FB Metadata for product ' . $wp_id );
1842
+ }
1843
+
1844
+ function ajax_reset_all_fb_products() {
1845
+ WC_Facebookcommerce_Utils::check_woo_ajax_permissions( 'reset products', true );
1846
+ check_ajax_referer( 'wc_facebook_settings_jsx' );
1847
+ $this->reset_all_products();
1848
+ wp_reset_postdata();
1849
+ wp_die();
1850
+ }
1851
+
1852
+ function ajax_reset_single_fb_product() {
1853
+ WC_Facebookcommerce_Utils::check_woo_ajax_permissions( 'reset single product', true );
1854
+ check_ajax_referer( 'wc_facebook_metabox_jsx' );
1855
+ if ( ! isset( $_POST['wp_id'] ) ) {
1856
+ wp_die();
1857
+ }
1858
+
1859
+ $wp_id = sanitize_text_field( $_POST['wp_id'] );
1860
+ $woo_product = new WC_Facebook_Product( $wp_id );
1861
+ if ( $woo_product ) {
1862
+ $this->reset_single_product( $wp_id );
1863
+ }
1864
+
1865
+ wp_reset_postdata();
1866
+ wp_die();
1867
+ }
1868
+
1869
+ function ajax_delete_fb_product() {
1870
+ WC_Facebookcommerce_Utils::check_woo_ajax_permissions( 'delete single product', true );
1871
+ check_ajax_referer( 'wc_facebook_metabox_jsx' );
1872
+ if ( ! isset( $_POST['wp_id'] ) ) {
1873
+ wp_die();
1874
+ }
1875
+
1876
+ $wp_id = sanitize_text_field( $_POST['wp_id'] );
1877
+ $this->on_product_delete( $wp_id );
1878
+ $this->reset_single_product( $wp_id );
1879
+ wp_reset_postdata();
1880
+ wp_die();
1881
+ }
1882
+
1883
+ /**
1884
+ * Special function to run all visible products through on_product_publish
1885
+ **/
1886
+ function ajax_sync_all_fb_products() {
1887
+ WC_Facebookcommerce_Utils::check_woo_ajax_permissions( 'syncall products', true );
1888
+ check_ajax_referer( 'wc_facebook_settings_jsx' );
1889
+ if ( get_option( 'fb_disable_sync_on_dev_environment', false ) ) {
1890
+ WC_Facebookcommerce_Utils::log(
1891
+ 'Sync to FB Page is not allowed in Dev Environment'
1892
+ );
1893
+ wp_die();
1894
+ return;
1895
+ }
1896
+
1897
+ if ( ! $this->api_key || ! $this->product_catalog_id ) {
1898
+ WC_Facebookcommerce_Utils::log(
1899
+ 'No API key or catalog ID: ' .
1900
+ $this->api_key . ' and ' . $this->product_catalog_id
1901
+ );
1902
+ wp_die();
1903
+ return;
1904
+ }
1905
+ $this->remove_resync_message();
1906
+
1907
+ $currently_syncing = get_transient( self::FB_SYNC_IN_PROGRESS );
1908
+
1909
+ if ( isset( $this->background_processor ) ) {
1910
+ if ( $this->background_processor->is_updating() ) {
1911
+ $this->background_processor->handle_cron_healthcheck();
1912
+ $currently_syncing = 1;
1913
+ }
1914
+ }
1915
+
1916
+ if ( $currently_syncing ) {
1917
+ WC_Facebookcommerce_Utils::log( 'Not syncing, sync in progress' );
1918
+ WC_Facebookcommerce_Utils::fblog(
1919
+ 'Tried to sync during an in-progress sync!',
1920
+ array(),
1921
+ true
1922
+ );
1923
+ $this->display_warning_message(
1924
+ 'A product sync is in progress.
1925
+ Please wait until the sync finishes before starting a new one.'
1926
+ );
1927
+ wp_die();
1928
+ return;
1929
+ }
1930
+
1931
+ $is_valid_product_catalog =
1932
+ $this->fbgraph->validate_product_catalog( $this->product_catalog_id );
1933
+
1934
+ if ( ! $is_valid_product_catalog ) {
1935
+ WC_Facebookcommerce_Utils::log( 'Not syncing, invalid product catalog!' );
1936
+ WC_Facebookcommerce_Utils::fblog(
1937
+ 'Tried to sync with an invalid product catalog!',
1938
+ array(),
1939
+ true
1940
+ );
1941
+ $this->display_warning_message(
1942
+ 'We\'ve detected that your
1943
  Facebook Product Catalog is no longer valid. This may happen if it was
1944
  deleted, or this may be a transient error.
1945
  If this error persists please remove your settings via
1946
  "Advanced Options > Advanced Settings > Remove"
1947
+ and try setup again'
1948
+ );
1949
+ wp_die();
1950
+ return;
1951
+ }
1952
+
1953
+ // Cache the cart URL to display a warning in case it changes later
1954
+ $cart_url = get_option( self::FB_CART_URL );
1955
+ if ( $cart_url != wc_get_cart_url() ) {
1956
+ update_option( self::FB_CART_URL, wc_get_cart_url() );
1957
+ }
1958
+
1959
+ $sanitized_settings = $this->settings;
1960
+ unset( $sanitized_settings['fb_api_key'] );
1961
+
1962
+ // Get all published posts. First unsynced then already-synced.
1963
+ $post_ids_new = WC_Facebookcommerce_Utils::get_wp_posts(
1964
+ self::FB_PRODUCT_GROUP_ID,
1965
+ 'NOT EXISTS'
1966
+ );
1967
+ $post_ids_old = WC_Facebookcommerce_Utils::get_wp_posts(
1968
+ self::FB_PRODUCT_GROUP_ID,
1969
+ 'EXISTS'
1970
+ );
1971
+
1972
+ $total_new = count( $post_ids_new );
1973
+ $total_old = count( $post_ids_old );
1974
+ $post_ids = array_merge( $post_ids_new, $post_ids_old );
1975
+ $total = count( $post_ids );
1976
+
1977
+ WC_Facebookcommerce_Utils::fblog(
1978
+ 'Attempting to sync ' . $total . ' ( ' .
1979
+ $total_new . ' new) products with settings: ',
1980
+ $sanitized_settings,
1981
+ false
1982
+ );
1983
+
1984
+ // Check for background processing (Woo 3.x.x)
1985
+ if ( isset( $this->background_processor ) ) {
1986
+ $starting_message = sprintf(
1987
+ 'Starting background sync to Facebook: %d products...',
1988
+ $total
1989
+ );
1990
+
1991
+ set_transient(
1992
+ self::FB_SYNC_IN_PROGRESS,
1993
+ true,
1994
+ self::FB_SYNC_TIMEOUT
1995
+ );
1996
+
1997
+ set_transient(
1998
+ self::FB_SYNC_REMAINING,
1999
+ (int) $total
2000
+ );
2001
+
2002
+ $this->display_info_message( $starting_message );
2003
+ WC_Facebookcommerce_Utils::log( $starting_message );
2004
+
2005
+ foreach ( $post_ids as $post_id ) {
2006
+ WC_Facebookcommerce_Utils::log( 'Pushing post to queue: ' . $post_id );
2007
+ $this->background_processor->push_to_queue( $post_id );
2008
+ }
2009
+
2010
+ $this->background_processor->save()->dispatch();
2011
+ // reset FB_SYNC_REMAINING to avoid race condition
2012
+ set_transient(
2013
+ self::FB_SYNC_REMAINING,
2014
+ (int) $total
2015
+ );
2016
+ // handle_cron_healthcheck must be called
2017
+ // https://github.com/A5hleyRich/wp-background-processing/issues/34
2018
+ $this->background_processor->handle_cron_healthcheck();
2019
+ } else {
2020
+ // Oldschool sync for WooCommerce 2.x
2021
+ $count = ( $total_old === $total ) ? 0 : $total_old;
2022
+ foreach ( $post_ids as $post_id ) {
2023
+ // Repeatedly overwrite sync total while in actual sync loop
2024
+ set_transient(
2025
+ self::FB_SYNC_IN_PROGRESS,
2026
+ true,
2027
+ self::FB_SYNC_TIMEOUT
2028
+ );
2029
+
2030
+ $this->display_sticky_message(
2031
+ sprintf(
2032
+ 'Syncing products to Facebook: %d out of %d...',
2033
+ // Display different # when resuming to avoid confusion.
2034
+ min( $count, $total ),
2035
+ $total
2036
+ ),
2037
+ true
2038
+ );
2039
+
2040
+ $this->on_product_publish( $post_id );
2041
+ $count++;
2042
+ }
2043
+ WC_Facebookcommerce_Utils::log( 'Synced ' . $count . ' products' );
2044
+ $this->remove_sticky_message();
2045
+ $this->display_info_message( 'Facebook product sync complete!' );
2046
+ delete_transient( self::FB_SYNC_IN_PROGRESS );
2047
+ WC_Facebookcommerce_Utils::fblog(
2048
+ 'Product sync complete. Total products synced: ' . $count
2049
+ );
2050
+ }
2051
+
2052
+ // https://codex.wordpress.org/Function_Reference/wp_reset_postdata
2053
+ wp_reset_postdata();
2054
+
2055
+ // This is important, for some reason.
2056
+ // See https://codex.wordpress.org/AJAX_in_Plugins
2057
+ wp_die();
2058
+ }
2059
+
2060
+ /**
2061
+ * Special function to run all visible products by uploading feed.
2062
+ **/
2063
+ function ajax_sync_all_fb_products_using_feed() {
2064
+ WC_Facebookcommerce_Utils::check_woo_ajax_permissions(
2065
+ 'syncall products using feed',
2066
+ ! $this->test_mode
2067
+ );
2068
+ check_ajax_referer( 'wc_facebook_settings_jsx' );
2069
+ return $this->sync_all_fb_products_using_feed();
2070
+ }
2071
+
2072
+ // Separate entry point that bypasses permission check for use in cron.
2073
+ function sync_all_fb_products_using_feed() {
2074
+ if ( get_option( 'fb_disable_sync_on_dev_environment', false ) ) {
2075
+ WC_Facebookcommerce_Utils::log(
2076
+ 'Sync to FB Page is not allowed in Dev Environment'
2077
+ );
2078
+ $this->fb_wp_die();
2079
+ return false;
2080
+ }
2081
+
2082
+ if ( ! $this->api_key || ! $this->product_catalog_id ) {
2083
+ self::log(
2084
+ 'No API key or catalog ID: ' . $this->api_key .
2085
+ ' and ' . $this->product_catalog_id
2086
+ );
2087
+ $this->fb_wp_die();
2088
+ return false;
2089
+ }
2090
+ $this->remove_resync_message();
2091
+ $is_valid_product_catalog =
2092
+ $this->fbgraph->validate_product_catalog( $this->product_catalog_id );
2093
+
2094
+ if ( ! $is_valid_product_catalog ) {
2095
+ WC_Facebookcommerce_Utils::log( 'Not syncing, invalid product catalog!' );
2096
+ WC_Facebookcommerce_Utils::fblog(
2097
+ 'Tried to sync with an invalid product catalog!',
2098
+ array(),
2099
+ true
2100
+ );
2101
+ $this->display_warning_message(
2102
+ 'We\'ve detected that your
2103
  Facebook Product Catalog is no longer valid. This may happen if it was
2104
  deleted, or this may be a transient error.
2105
  If this error persists please remove your settings via
2106
  "Advanced Options > Advanced Settings > Remove"
2107
+ and try setup again'
2108
+ );
2109
+ $this->fb_wp_die();
2110
+ return false;
2111
+ }
2112
+
2113
+ // Cache the cart URL to display a warning in case it changes later
2114
+ $cart_url = get_option( self::FB_CART_URL );
2115
+ if ( $cart_url != wc_get_cart_url() ) {
2116
+ update_option( self::FB_CART_URL, wc_get_cart_url() );
2117
+ }
2118
+
2119
+ if ( ! class_exists( 'WC_Facebook_Product_Feed' ) ) {
2120
+ include_once 'includes/fbproductfeed.php';
2121
+ }
2122
+ if ( $this->test_mode ) {
2123
+ $this->fbproductfeed = new WC_Facebook_Product_Feed_Test_Mock(
2124
+ $this->product_catalog_id,
2125
+ $this->fbgraph,
2126
+ $this->feed_id
2127
+ );
2128
+ } else {
2129
+ $this->fbproductfeed = new WC_Facebook_Product_Feed(
2130
+ $this->product_catalog_id,
2131
+ $this->fbgraph,
2132
+ $this->feed_id
2133
+ );
2134
+ }
2135
+
2136
+ $upload_success = $this->fbproductfeed->sync_all_products_using_feed();
2137
+ if ( $upload_success ) {
2138
+ $this->settings['fb_feed_id'] = $this->fbproductfeed->feed_id;
2139
+ $this->settings['fb_upload_id'] = $this->fbproductfeed->upload_id;
2140
+ update_option(
2141
+ $this->get_option_key(),
2142
+ apply_filters(
2143
+ 'woocommerce_settings_api_sanitized_fields_' .
2144
+ $this->id,
2145
+ $this->settings
2146
+ )
2147
+ );
2148
+ wp_reset_postdata();
2149
+ $this->fb_wp_die();
2150
+ return true;
2151
+ }
2152
+ WC_Facebookcommerce_Utils::fblog(
2153
+ 'Sync all products using feed, curl failed',
2154
+ array(),
2155
+ true
2156
+ );
2157
+ return false;
2158
+ }
2159
+
2160
+ /**
2161
+ * Toggles product visibility via AJAX (checks current viz and flips it)
2162
+ **/
2163
+ function ajax_fb_toggle_visibility() {
2164
+ WC_Facebookcommerce_Utils::check_woo_ajax_permissions( 'toggle visibility', true );
2165
+ check_ajax_referer( 'wc_facebook_product_jsx' );
2166
+ if ( ! isset( $_POST['wp_id'] ) || ! isset( $_POST['published'] ) ) {
2167
+ wp_die();
2168
+ }
2169
+
2170
+ $wp_id = sanitize_text_field( $_POST['wp_id'] );
2171
+ $published = ( $_POST['published'] ) === 'true' ? true : false;
2172
+
2173
+ $woo_product = new WC_Facebook_Product( $wp_id );
2174
+ $products = WC_Facebookcommerce_Utils::get_product_array( $woo_product );
2175
+
2176
+ // Loop through product items and flip visibility
2177
+ foreach ( $products as $item_id ) {
2178
+ $fb_product_item_id = $this->get_product_fbid(
2179
+ self::FB_PRODUCT_ITEM_ID,
2180
+ $item_id
2181
+ );
2182
+ $data = array(
2183
+ 'visibility' => $published ? 'published' : 'staging',
2184
+ );
2185
+
2186
+ $result = $this->check_api_result(
2187
+ $this->fbgraph->update_product_item(
2188
+ $fb_product_item_id,
2189
+ $data
2190
+ )
2191
+ );
2192
+
2193
+ if ( $result ) {
2194
+ update_post_meta( $item_id, self::FB_VISIBILITY, $published );
2195
+ update_post_meta( $wp_id, self::FB_VISIBILITY, $published );
2196
+ }
2197
+ }
2198
+ wp_die();
2199
+ }
2200
+
2201
+ /**
2202
+ * Initialize Settings Form Fields
2203
+ *
2204
+ * @access public
2205
+ * @return void
2206
+ */
2207
+ function init_form_fields() {
2208
+ $this->form_fields = array(
2209
+ 'fb_settings_heading' => array(
2210
+ 'title' => __( 'Debug Mode', 'facebook-for-woocommerce' ),
2211
+ 'type' => 'title',
2212
+ 'description' => '',
2213
+ 'default' => '',
2214
+ ),
2215
+ 'fb_page_id' => array(
2216
+ 'title' => __( 'Facebook Page ID', 'facebook-for-woocommerce' ),
2217
+ 'type' => 'text',
2218
+ 'description' => __(
2219
+ 'The unique identifier for your Facebook page.',
2220
+ 'facebook-for-woocommerce'
2221
+ ),
2222
+ 'default' => '',
2223
+ ),
2224
+ 'fb_product_catalog_id' => array(
2225
+ 'title' => __( 'Product Catalog ID', 'facebook-for-woocommerce' ),
2226
+ 'type' => 'text',
2227
+ 'description' => __(
2228
+ 'The unique identifier for your product catalog,
2229
+ on Facebook.',
2230
+ 'facebook-for-woocommerce'
2231
+ ),
2232
+ 'default' => '',
2233
+ ),
2234
+ 'fb_pixel_id' => array(
2235
+ 'title' => __( 'Pixel ID', 'facebook-for-woocommerce' ),
2236
+ 'type' => 'text',
2237
+ 'description' => __(
2238
+ 'The unique identifier for your Facebook pixel',
2239
+ 'facebook-for-woocommerce'
2240
+ ),
2241
+ 'default' => '',
2242
+ ),
2243
+ 'fb_pixel_use_pii' => array(
2244
+ 'title' => __(
2245
+ 'Use Advanced Matching on pixel?',
2246
+ 'facebook-for-woocommerce'
2247
+ ),
2248
+ 'type' => 'checkbox',
2249
+ 'description' => __(
2250
+ 'Enabling Advanced Matching
2251
+ improves audience building.',
2252
+ 'facebook-for-woocommerce'
2253
+ ),
2254
+ 'default' => 'yes',
2255
+ ),
2256
+ 'fb_external_merchant_settings_id' => array(
2257
+ 'title' => __(
2258
+ 'External Merchant Settings ID',
2259
+ 'facebook-for-woocommerce'
2260
+ ),
2261
+ 'type' => 'text',
2262
+ 'description' => __(
2263
+ 'The unique identifier for your external merchant
2264
+ settings, on Facebook.',
2265
+ 'facebook-for-woocommerce'
2266
+ ),
2267
+ 'default' => '',
2268
+ ),
2269
+ 'fb_api_key' => array(
2270
+ 'title' => __( 'API Key', 'facebook-for-woocommerce' ),
2271
+ 'type' => 'text',
2272
+ 'description' => sprintf(
2273
+ __(
2274
+ 'A non-expiring Page Token with
2275
+ %1$smanage_pages%2$s permissions.',
2276
+ 'facebook-for-woocommerce'
2277
+ ),
2278
+ '<code>',
2279
+ '</code>'
2280
+ ),
2281
+ 'default' => '',
2282
+ ),
2283
+ );
2284
+
2285
+ if ( ! class_exists( 'WC_Facebookcommerce_EventsTracker' ) ) {
2286
+ include_once 'includes/fbutils.php';
2287
+ }
2288
+ } // End init_form_fields()
2289
+
2290
+
2291
+ /**
2292
+ * Get message
2293
+ *
2294
+ * @return string Error
2295
+ */
2296
+ private function get_message_html( $message, $type = 'error' ) {
2297
+ ob_start();
2298
+
2299
+ ?>
2300
+ <div class="notice is-dismissible notice-<?php echo $type; ?>">
2301
+ <p><?php echo $message; ?></p>
2302
+ </div>
2303
+ <?php
2304
+ return ob_get_clean();
2305
+ }
2306
+
2307
+ /**
2308
+ * Display relevant messages to user from transients, clear once displayed
2309
+ *
2310
+ * @param void
2311
+ */
2312
+ public function maybe_display_facebook_api_messages() {
2313
+ $error_msg = get_transient( 'facebook_plugin_api_error' );
2314
+
2315
+ if ( $error_msg ) {
2316
+ echo $this->get_message_html(
2317
+ sprintf(
2318
+ __(
2319
+ 'Facebook extension error: %s ',
2320
+ 'facebook-for-woocommerce'
2321
+ ),
2322
+ $error_msg
2323
+ )
2324
+ );
2325
+ delete_transient( 'facebook_plugin_api_error' );
2326
+
2327
+ WC_Facebookcommerce_Utils::fblog(
2328
+ $error_msg,
2329
+ array(),
2330
+ true
2331
+ );
2332
+ }
2333
+
2334
+ $warning_msg = get_transient( 'facebook_plugin_api_warning' );
2335
+
2336
+ if ( $warning_msg ) {
2337
+ echo $this->get_message_html(
2338
+ __( $warning_msg, 'facebook-for-woocommerce' ),
2339
+ 'warning'
2340
+ );
2341
+ delete_transient( 'facebook_plugin_api_warning' );
2342
+ }
2343
+
2344
+ $success_msg = get_transient( 'facebook_plugin_api_success' );
2345
+
2346
+ if ( $success_msg ) {
2347
+ echo $this->get_message_html(
2348
+ __( $success_msg, 'facebook-for-woocommerce' ),
2349
+ 'success'
2350
+ );
2351
+ delete_transient( 'facebook_plugin_api_success' );
2352
+ }
2353
+
2354
+ $info_msg = get_transient( 'facebook_plugin_api_info' );
2355
+
2356
+ if ( $info_msg ) {
2357
+ echo $this->get_message_html(
2358
+ __( $info_msg, 'facebook-for-woocommerce' ),
2359
+ 'info'
2360
+ );
2361
+ delete_transient( 'facebook_plugin_api_info' );
2362
+ }
2363
+
2364
+ $sticky_msg = get_transient( 'facebook_plugin_api_sticky' );
2365
+
2366
+ if ( $sticky_msg ) {
2367
+ echo $this->get_message_html(
2368
+ __( $sticky_msg, 'facebook-for-woocommerce' ),
2369
+ 'info'
2370
+ );
2371
+ // Transient must be deleted elsewhere, or wait for timeout
2372
+ }
2373
+
2374
+ }
2375
+
2376
+ function get_page_name() {
2377
+ $page_name = '';
2378
+ if ( ! empty( $this->settings['fb_page_id'] ) &&
2379
+ ! empty( $this->settings['fb_api_key'] ) ) {
2380
+
2381
+ $page_name = $this->fbgraph->get_page_name(
2382
+ $this->settings['fb_page_id'],
2383
+ $this->settings['fb_api_key']
2384
+ );
2385
+ }
2386
+ return $page_name;
2387
+ }
2388
+
2389
+ function get_nux_message_ifexist() {
2390
+ $nux_type_to_elemid_map = array(
2391
+ 'messenger_chat' => 'connect_button',
2392
+ 'instagram_shopping' => 'connect_button',
2393
+ );
2394
+ $nux_type_to_message_map = array(
2395
+ 'messenger_chat' => __( 'Get started with Messenger Customer Chat' ),
2396
+ 'instagram_shopping' => __( 'Get started with Instagram Shopping' ),
2397
+ );
2398
+ if ( isset( $_GET['nux'] ) ) {
2399
+ return sprintf(
2400
+ '<div class="nux-message" style="display: none;" data-target="%s">
2401
  <div class="nux-message-text">%s</div>
2402
  <div class="nux-message-arrow"></div>
2403
  <i class="nux-message-close-btn">x</i>
2404
  </div>
2405
  <script>(function() { fbe_init_nux_messages(); })();</script>',
2406
+ $nux_type_to_elemid_map[ sanitize_text_field( $_GET['nux'] ) ],
2407
+ $nux_type_to_message_map[ sanitize_text_field( $_GET['nux'] ) ]
2408
+ );
2409
+ } else {
2410
+ return '';
2411
+ }
2412
+ }
2413
+
2414
+ /**
2415
+ * Admin Panel Options
2416
+ */
2417
+ function admin_options() {
2418
+ $domain = 'facebook-for-woocommerce';
2419
+ $cta_button_text = __( 'Get Started', $domain );
2420
+ $page_name = $this->get_page_name();
2421
+
2422
+ $can_manage = current_user_can( 'manage_woocommerce' );
2423
+ $pre_setup = empty( $this->settings['fb_page_id'] ) ||
2424
+ empty( $this->settings['fb_api_key'] );
2425
+ $apikey_invalid = ! $pre_setup && $this->settings['fb_api_key'] && ! $page_name;
2426
+
2427
+ $redirect_uri = '';
2428
+ $remove_http_active = is_plugin_active( 'remove-http/remove-http.php' );
2429
+ $https_will_be_stripped = $remove_http_active &&
2430
+ ! get_option( 'factmaven_rhttp' )['external'];
2431
+ if ( $https_will_be_stripped ) {
2432
+ $this->display_sticky_message(
2433
+ __(
2434
+ 'You\'re using Remove HTTP which has
2435
  incompatibilities with our extension. Please disable it, or select the
2436
+ "Ignore external links" option on the Remove HTTP settings page.'
2437
+ )
2438
+ );
2439
+ }
2440
+
2441
+ if ( ! $pre_setup ) {
2442
+ $cta_button_text = __( 'Create Ad', $domain );
2443
+ $redirect_uri = 'https://www.facebook.com/ads/dia/redirect/?settings_id='
2444
+ . $this->external_merchant_settings_id . '&version=2' .
2445
+ '&entry_point=admin_panel';
2446
+ }
2447
+ $currently_syncing = get_transient( self::FB_SYNC_IN_PROGRESS );
2448
+ $connected = ( $page_name != '' );
2449
+ $hide_test = ( $connected && $currently_syncing ) || ! defined( 'WP_DEBUG' ) ||
2450
+ WP_DEBUG !== true;
2451
+ $nux_message = $this->get_nux_message_ifexist();
2452
+ ?>
2453
+ <h2><?php _e( 'Facebook', $domain ); ?></h2>
2454
+ <p>
2455
+ <?php
2456
+ _e(
2457
+ 'Control how WooCommerce integrates with your Facebook store.',
2458
+ $domain
2459
+ );
2460
+ ?>
2461
+ </p>
2462
+ <hr/>
2463
+
2464
+ <div id="fbsetup">
2465
+ <div class="wrapper">
2466
+ <header>
2467
+ <div class="help-center">
2468
+ <a href="https://www.facebook.com/business/help/900699293402826" target="_blank">Help Center <i class="help-center-icon"></i></a>
2469
+ </div>
2470
+ </header>
2471
+ <div class="content">
2472
+ <h1 id="setup_h1">
2473
+ <?php
2474
+ $pre_setup
2475
+ ? _e( 'Grow your business on Facebook', $domain )
2476
+ : _e( 'Reach The Right People and Sell More Online', $domain );
2477
+ ?>
2478
+ </h1>
2479
+ <h2>
2480
+ <?php
2481
+ _e(
2482
+ 'Use this WooCommerce and Facebook integration to:',
2483
+ $domain
2484
+ );
2485
+ ?>
2486
+ </h2>
2487
+ <ul>
2488
+ <li id="setup_l1">
2489
+ <?php
2490
+ $pre_setup
2491
+ ? _e( 'Easily install a tracking pixel', $domain )
2492
+ : _e( 'Create an ad in a few steps', $domain );
2493
+ ?>
2494
+ </li>
2495
+ <li id="setup_l2">
2496
+ <?php
2497
+ $pre_setup
2498
+ ? _e( 'Upload your products and create a shop', $domain )
2499
+ : _e( 'Use built-in best practices for online sales', $domain );
2500
+ ?>
2501
+ </li>
2502
+ <li id="setup_l3">
2503
+ <?php
2504
+ $pre_setup
2505
+ ? _e( 'Create dynamic ads with your products and pixel', $domain )
2506
+ : _e( 'Get reporting on sales and revenue', $domain );
2507
+ ?>
2508
+ </li>
2509
+ </ul>
2510
+ <span
2511
+ <?php
2512
+ if ( $pre_setup ) {
2513
+ if ( ! $can_manage ) {
2514
+ echo ' style="pointer-events: none;"';
2515
+ }
2516
+ echo '><a href="#" class="btn pre-setup" onclick="facebookConfig()"
2517
+ id="cta_button">' . esc_html( $cta_button_text ) . '</a></span>';
2518
+ } else {
2519
+ if ( ! $can_manage || $apikey_invalid ||
2520
+ ! isset( $this->external_merchant_settings_id ) ) {
2521
+ echo ' style="pointer-events: none;"';
2522
+ }
2523
+ echo (
2524
+ '><a href=' . $redirect_uri . ' class="btn" id="cta_button">' .
2525
+ esc_html( $cta_button_text ) . '</a>' .
2526
+ '<a href="https://www.facebook.com/business/m/drive-more-online-sales"
2527
+ class="btn grey" id="learnmore_button">' . __( 'Learn More' ) .
2528
+ '</a></span>'
2529
+ );
2530
+ }
2531
+ ?>
2532
+ <hr />
2533
+ <div class="settings-container">
2534
+ <div id="plugins" class="settings-section"
2535
+ <?php echo ( $pre_setup && $can_manage ) ? ' style="display:none;"' : ''; ?>
2536
+ >
2537
+ <h1><?php echo __( 'Add Ways for People to Shop' ); ?></h1>
2538
+ <h2><?php echo __( 'Connect your business with features such as Messenger and more.' ); ?></h2>
2539
+ <a href="#" class="btn small" onclick="facebookConfig()" id="connect_button">
2540
+ <?php echo __( 'Add Features' ); ?>
2541
+ </a>
2542
+ </div>
2543
+ <div id="settings" class="settings-section"
2544
+ <?php
2545
+ if ( $pre_setup && $can_manage ) {
2546
+ echo ' style="display:none;"';
2547
+ }
2548
+ echo '><h1>' . esc_html__( 'Settings', $domain ) . '</h1>';
2549
+ if ( $apikey_invalid ) {
2550
+ // API key is set, but no page name.
2551
+ echo '<h2 id="token_text" style="color:red;">' .
2552
+ __(
2553
+ 'Your API key is no longer valid. Please click "Settings >
2554
+ Advanced Options > Update Token".',
2555
+ $domain
2556
+ ) . '</h2>
2557
 
2558
  <span><a href="#" class="btn small" onclick="facebookConfig()"
2559
+ id="setting_button">' . __( 'Settings', $domain ) . '</a>
2560
  </span>';
2561
+ } else {
2562
+ if ( ! $can_manage ) {
2563
+ echo '<h2 style="color:red;">' . __(
2564
+ 'You must have
2565
+ "manage_woocommerce" permissions to use this plugin.',
2566
+ $domain
2567
+ ) .
2568
+ '</h2>';
2569
+ } else {
2570
+ echo '<h2><span id="connection_status"';
2571
+ if ( ! $connected ) {
2572
+ echo ' style="display: none;"';
2573
+ }
2574
+ echo '>';
2575
+ echo __( 'Your WooCommerce store is connected to ', $domain ) .
2576
+ ( ( $page_name != '' )
2577
+ ? sprintf(
2578
+ __( 'the Facebook page <a target="_blank" href="https://www.facebook.com/%1$s">%2$s</a></span>', $domain ),
2579
+ $this->settings['fb_page_id'],
2580
+ esc_html( $page_name )
2581
+ )
2582
+ : sprintf(
2583
+ __( '<a target="_blank" href="https://www.facebook.com/%1$s">your Facebook page</a></span>', $domain ),
2584
+ $this->settings['fb_page_id']
2585
+ )
2586
+ ) .
2587
+ '.<span id="sync_complete" style="margin-left: 5px;';
2588
+ if ( ! $connected || $currently_syncing ) {
2589
+ echo ' display: none;';
2590
+ }
2591
+ echo '">' . __( 'Status', $domain ) . ': '
2592
+ . __( 'Products are synced to Facebook.', $domain ) . '</span>' .
2593
+ sprintf(
2594
+ __(
2595
+ '<span><a href="#" onclick="show_debug_info()"
2596
  id="debug_info" style="display:none;" > More Info </a></span>',
2597
+ $domain
2598
+ )
2599
+ ) . '</span></h2>
2600
  <span><a href="#" class="btn small" onclick="facebookConfig()"
2601
  id="setting_button"';
2602
 
2603
+ if ( $currently_syncing ) {
2604
+ echo ' style="pointer-events: none;" ';
2605
+ }
2606
+ echo '>' . __( 'Manage Settings', $domain ) . '</a></span>
2607
 
2608
  <span><a href="#" class="btn small" onclick="sync_confirm()"
2609
  id="resync_products"';
2610
 
2611
+ if ( $connected && $currently_syncing ) {
2612
+ echo ' style="pointer-events: none;" ';
2613
+ }
2614
+ echo '>' . __( 'Sync Products', $domain ) . '</a></span>
2615
 
2616
  <p id="sync_progress">';
2617
+ if ( $connected && $currently_syncing ) {
2618
+ echo '<hr/>';
2619
+ echo __( 'Syncing... Keep this browser open', $domain );
2620
+ echo '<br/>';
2621
+ echo __( 'Until sync is complete', $domain );
2622
+ }
2623
+ echo '</p>';
2624
+ }
2625
+ }
2626
+ ?>
2627
+ </div>
2628
+ <hr />
2629
+ </div>
2630
+ <?php echo $nux_message; ?>
2631
+
2632
+ <div>
2633
+ <div id='fbAdvancedOptionsText' onclick="toggleAdvancedOptions();">
2634
+ Show Advanced Settings
2635
+ </div>
2636
+ <div id='fbAdvancedOptions'>
2637
+ <div class='autosync' title="This experimental feature will call force resync at the specified time using WordPress cron scheduling.">
2638
+ <input type="checkbox"
2639
+ onclick="saveAutoSyncSchedule()"
2640
+ class="autosyncCheck"
2641
+ <?php echo get_option( 'woocommerce_fb_autosync_time', false ) ? 'checked' : 'unchecked'; ?>>
2642
+ Automatically Force Resync of Products At
2643
+
2644
+ <input
2645
+ type="time"
2646
+ value="<?php echo get_option( 'woocommerce_fb_autosync_time', '23:00' ); ?>"
2647
+ class="autosyncTime"
2648
+ onfocusout="saveAutoSyncSchedule()"
2649
+ <?php echo get_option( 'woocommerce_fb_autosync_time', 0 ) ? '' : 'disabled'; ?> />
2650
+ Every Day.
2651
+ <span class="autosyncSavedNotice" disabled> Saved </span>
2652
+ </div>
2653
+ <div title="This option is meant for development and testing environments.">
2654
+ <input type="checkbox"
2655
+ onclick="onSetDisableSyncOnDevEnvironment()"
2656
+ class="disableOnDevEnvironment"
2657
+ <?php
2658
+ echo get_option( 'fb_disable_sync_on_dev_environment', false )
2659
+ ? 'checked'
2660
+ : 'unchecked';
2661
+ ?>
2662
+ />
2663
+ Disable Product Sync with FB
2664
+ </div>
2665
+ <div class='shortdescr' title="This experimental feature will import short description instead of description for all products.">
2666
+ <input type="checkbox"
2667
+ onclick="syncShortDescription()"
2668
+ class="syncShortDescription"
2669
+ <?php
2670
+ echo get_option( 'fb_sync_short_description', false )
2671
+ ? 'checked'
2672
+ : 'unchecked';
2673
+ ?>
2674
+ />
2675
+ Sync Short Description Instead of Description
2676
+ </div>
2677
+ </div>
2678
+ </div>
2679
+ </div>
2680
+ </div>
2681
+ <div <?php echo ( $hide_test ) ? ' style="display:none;" ' : ''; ?> >
2682
+ <p class="tooltip" id="test_product_sync">
2683
+ <?php
2684
+ // WP_DEBUG mode: button to launch test
2685
+ echo sprintf(
2686
+ __( '<a href="%s&fb_test_product_sync=true"', $domain ),
2687
+ WOOCOMMERCE_FACEBOOK_PLUGIN_SETTINGS_URL
2688
+ );
2689
+ echo '>' . esc_html__( 'Launch Test', $domain );
2690
+ ?>
2691
+ <span class='tooltiptext'>
2692
+ <?php
2693
+ _e(
2694
+ 'This button will run an integration test suite verifying the
2695
  extension. Note that this will reset your products and resync them
2696
  to Facebook. Not recommended to use unless you are changing the
2697
+ extension code and want to test your changes.',
2698
+ $domain
2699
+ );
2700
+ ?>
2701
+ </span>
2702
+ <?php
2703
+ echo '</a>';
2704
+ ?>
2705
+ </p>
2706
+ <p id="stack_trace"></p>
2707
+ </div>
2708
+ <br/><hr/><br/>
2709
+ <?php
2710
+
2711
+ $GLOBALS['hide_save_button'] = true;
2712
+ if ( defined( 'WP_DEBUG' ) && true === WP_DEBUG ) {
2713
+ $GLOBALS['hide_save_button'] = false;
2714
+ ?>
2715
+ <table class="form-table">
2716
+ <?php $this->generate_settings_html(); ?>
2717
+ </table><!--/.form-table-->
2718
+ <?php
2719
+ }
2720
+ }
2721
+
2722
+ function delete_product_item( $wp_id ) {
2723
+ $fb_product_item_id = $this->get_product_fbid(
2724
+ self::FB_PRODUCT_ITEM_ID,
2725
+ $wp_id
2726
+ );
2727
+ if ( $fb_product_item_id ) {
2728
+ $pi_result =
2729
+ $this->fbgraph->delete_product_item( $fb_product_item_id );
2730
+ WC_Facebookcommerce_Utils::log( $pi_result );
2731
+ }
2732
+ }
2733
+
2734
+ function fb_duplicate_product_reset_meta( $to_delete ) {
2735
+ array_push( $to_delete, self::FB_PRODUCT_ITEM_ID );
2736
+ array_push( $to_delete, self::FB_PRODUCT_GROUP_ID );
2737
+ return $to_delete;
2738
+ }
2739
+
2740
+ /**
2741
+ * Helper function to update FB visibility.
2742
+ */
2743
+ function update_fb_visibility( $wp_id, $visibility ) {
2744
+ $woo_product = new WC_Facebook_Product( $wp_id );
2745
+ if ( ! $woo_product->exists() ) {
2746
+ // This function can be called for non-woo products.
2747
+ return;
2748
+ }
2749
+
2750
+ $products = WC_Facebookcommerce_Utils::get_product_array( $woo_product );
2751
+ foreach ( $products as $item_id ) {
2752
+ $fb_product_item_id = $this->get_product_fbid(
2753
+ self::FB_PRODUCT_ITEM_ID,
2754
+ $item_id
2755
+ );
2756
+
2757
+ if ( ! $fb_product_item_id ) {
2758
+ WC_Facebookcommerce_Utils::fblog(
2759
+ $fb_product_item_id . " doesn't exist but underwent a visibility transform.",
2760
+ array(),
2761
+ true
2762
+ );
2763
+ continue;
2764
+ }
2765
+ $result = $this->check_api_result(
2766
+ $this->fbgraph->update_product_item(
2767
+ $fb_product_item_id,
2768
+ array( 'visibility' => $visibility )
2769
+ )
2770
+ );
2771
+ if ( $result ) {
2772
+ update_post_meta( $item_id, self::FB_VISIBILITY, $visibility );
2773
+ update_post_meta( $wp_id, self::FB_VISIBILITY, $visibility );
2774
+ }
2775
+ }
2776
+ }
2777
+
2778
+ function on_quick_and_bulk_edit_save( $product ) {
2779
+ $wp_id = $product->get_id();
2780
+ $visibility = get_post_status( $wp_id ) == 'publish'
2781
+ ? 'published'
2782
+ : 'staging';
2783
+ // case 1: new status is 'publish' regardless of old status, sync to FB
2784
+ if ( $visibility == 'published' ) {
2785
+ $this->on_product_publish( $wp_id );
2786
+ } else {
2787
+ // case 2: product never publish to FB, new status is not publish
2788
+ // case 3: product new status is not publish and published before
2789
+ $this->update_fb_visibility( $wp_id, $visibility );
2790
+ }
2791
+ }
2792
+
2793
+ private function get_product_fbid( $fbid_type, $wp_id, $woo_product = null ) {
2794
+ $fb_id = WC_Facebookcommerce_Utils::get_fbid_post_meta(
2795
+ $wp_id,
2796
+ $fbid_type
2797
+ );
2798
+ if ( $fb_id ) {
2799
+ return $fb_id;
2800
+ }
2801
+ if ( ! isset( $this->settings['upload_end_time'] ) ) {
2802
+ return null;
2803
+ }
2804
+ if ( ! $woo_product ) {
2805
+ $woo_product = new WC_Facebook_Product( $wp_id );
2806
+ }
2807
+ $products = WC_Facebookcommerce_Utils::get_product_array( $woo_product );
2808
+ $woo_product = new WC_Facebook_Product( current( $products ) );
2809
+ // This is a generalized function used elsewhere
2810
+ // Cannot call is_hidden for VC_Product_Variable Object
2811
+ if ( $woo_product->is_hidden() ) {
2812
+ return null;
2813
+ }
2814
+ $fb_retailer_id =
2815
+ WC_Facebookcommerce_Utils::get_fb_retailer_id( $woo_product );
2816
+
2817
+ $product_fbid_result = $this->fbgraph->get_facebook_id(
2818
+ $this->product_catalog_id,
2819
+ $fb_retailer_id
2820
+ );
2821
+ if ( is_wp_error( $product_fbid_result ) ) {
2822
+ WC_Facebookcommerce_Utils::log( $product_fbid_result->get_error_message() );
2823
+ $this->display_error_message(
2824
+ 'There was an issue connecting to the Facebook API: ' .
2825
+ $product_fbid_result->get_error_message()
2826
+ );
2827
+ return;
2828
+ }
2829
+
2830
+ if ( $product_fbid_result && isset( $product_fbid_result['body'] ) ) {
2831
+ $body = WC_Facebookcommerce_Utils::decode_json( $product_fbid_result['body'] );
2832
+ if ( $body && $body->id ) {
2833
+ if ( $fbid_type == self::FB_PRODUCT_GROUP_ID ) {
2834
+ $fb_id = $body->product_group->id;
2835
+ } else {
2836
+ $fb_id = $body->id;
2837
+ }
2838
+ update_post_meta(
2839
+ $wp_id,
2840
+ $fbid_type,
2841
+ $fb_id
2842
+ );
2843
+ update_post_meta( $wp_id, self::FB_VISIBILITY, true );
2844
+ return $fb_id;
2845
+ }
2846
+ }
2847
+ return;
2848
+ }
2849
+
2850
+ private function set_default_variant( $product_group_id, $product_item_id ) {
2851
+ $result = $this->check_api_result(
2852
+ $this->fbgraph->set_default_variant(
2853
+ $product_group_id,
2854
+ array( 'default_product_id' => $product_item_id )
2855
+ )
2856
+ );
2857
+ if ( ! $result ) {
2858
+ WC_Facebookcommerce_Utils::fblog(
2859
+ 'Fail to set default product item',
2860
+ array(),
2861
+ true
2862
+ );
2863
+ }
2864
+ }
2865
+
2866
+ private function fb_wp_die() {
2867
+ if ( ! $this->test_mode ) {
2868
+ wp_die();
2869
+ }
2870
+ }
2871
+
2872
+ /**
2873
+ * Display test result.
2874
+ **/
2875
+ function ajax_display_test_result() {
2876
+ WC_Facebookcommerce_Utils::check_woo_ajax_permissions( 'test result', true );
2877
+ check_ajax_referer( 'wc_facebook_settings_jsx' );
2878
+ $response = array(
2879
+ 'pass' => 'true',
2880
+ );
2881
+ $test_pass = get_option( 'fb_test_pass', null );
2882
+ if ( ! isset( $test_pass ) ) {
2883
+ $response['pass'] = 'in progress';
2884
+ } elseif ( $test_pass == 0 ) {
2885
+ $response['pass'] = 'false';
2886
+ $response['debug_info'] = get_transient( 'facebook_plugin_test_fail' );
2887
+ $response['stack_trace'] =
2888
+ get_transient( 'facebook_plugin_test_stack_trace' );
2889
+ $response['stack_trace'] =
2890
+ preg_replace( "/\n/", '<br>', $response['stack_trace'] );
2891
+ delete_transient( 'facebook_plugin_test_fail' );
2892
+ delete_transient( 'facebook_plugin_test_stack_trace' );
2893
+ }
2894
+ delete_option( 'fb_test_pass' );
2895
+ printf( json_encode( $response ) );
2896
+ wp_die();
2897
+ }
2898
+
2899
+ /**
2900
+ * Schedule Force Resync
2901
+ */
2902
+ function ajax_schedule_force_resync() {
2903
+ WC_Facebookcommerce_Utils::check_woo_ajax_permissions( 'resync schedule', true );
2904
+ check_ajax_referer( 'wc_facebook_settings_jsx' );
2905
+ if ( isset( $_POST ) && isset( $_POST['enabled'] ) ) {
2906
+ if ( isset( $_POST['time'] ) && $_POST['enabled'] ) { // Enabled
2907
+ $time = sanitize_text_field( $_POST['time'] );
2908
+ wp_clear_scheduled_hook( 'sync_all_fb_products_using_feed' );
2909
+ wp_schedule_event(
2910
+ strtotime( $time ),
2911
+ 'daily',
2912
+ 'sync_all_fb_products_using_feed'
2913
+ );
2914
+ WC_Facebookcommerce_Utils::fblog( 'Scheduled autosync for ' . $time );
2915
+ update_option( 'woocommerce_fb_autosync_time', $time );
2916
+ } elseif ( ! $_POST['enabled'] ) { // Disabled
2917
+ wp_clear_scheduled_hook( 'sync_all_fb_products_using_feed' );
2918
+ WC_Facebookcommerce_Utils::fblog( 'Autosync disabled' );
2919
+ delete_option( 'woocommerce_fb_autosync_time' );
2920
+ }
2921
+ } else {
2922
+ WC_Facebookcommerce_Utils::fblog( 'Autosync AJAX Problem', $_POST, true );
2923
+ }
2924
+ wp_die();
2925
+ }
2926
+
2927
+ function ajax_update_fb_option() {
2928
+ check_ajax_referer( 'wc_facebook_settings_jsx' );
2929
+ WC_Facebookcommerce_Utils::check_woo_ajax_permissions( 'update fb options', true );
2930
+ if ( isset( $_POST ) && stripos( $_POST['option'], 'fb_' ) === 0 ) {
2931
+ update_option( sanitize_text_field( $_POST['option'] ), sanitize_text_field( $_POST['option_value'] ) );
2932
+ }
2933
+ wp_die();
2934
+ }
2935
  }
facebook-config-warmer.php CHANGED
@@ -8,14 +8,16 @@
8
  * @package FacebookCommerce
9
  */
10
 
11
- if (!defined('ABSPATH')) exit; // Exit if accessed directly
 
 
12
 
13
 
14
- if (!class_exists('WC_Facebookcommerce_WarmConfig')) :
15
 
16
- class WC_Facebookcommerce_WarmConfig {
17
- static $fb_warm_pixel_id = null;
18
- static $fb_warm_is_advanced_matching_enabled = null;
19
- }
20
 
21
  endif;
8
  * @package FacebookCommerce
9
  */
10
 
11
+ if ( ! defined( 'ABSPATH' ) ) {
12
+ exit; // Exit if accessed directly
13
+ }
14
 
15
 
16
+ if ( ! class_exists( 'WC_Facebookcommerce_WarmConfig' ) ) :
17
 
18
+ class WC_Facebookcommerce_WarmConfig {
19
+ static $fb_warm_pixel_id = null;
20
+ static $fb_warm_is_advanced_matching_enabled = null;
21
+ }
22
 
23
  endif;
facebook-for-woocommerce.php CHANGED
@@ -10,7 +10,7 @@
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: 1.9.14
14
  * Woo: 2127297:0ea4fe4c2d7ca6338f8a322fb3e4e187
15
  * Text Domain: facebook-for-woocommerce
16
  * WC requires at least: 3.0.0
@@ -20,86 +20,97 @@
20
  */
21
 
22
 
23
- if (!class_exists('WC_Facebookcommerce')) :
24
- include_once 'includes/fbutils.php';
25
 
26
- class WC_Facebookcommerce {
27
 
28
- // Change it above as well
29
- const PLUGIN_VERSION = WC_Facebookcommerce_Utils::PLUGIN_VERSION;
30
 
31
- /**
32
- * Construct the plugin.
33
- */
34
- public function __construct() {
35
- add_action('plugins_loaded', array( $this, 'init'));
36
- }
37
 
38
- /**
39
- * Initialize the plugin.
40
- */
41
- public function init() {
42
- if (is_admin()) {
43
- add_filter('plugin_action_links_'.plugin_basename(__FILE__),
44
- array($this, 'add_settings_link'));
45
- }
 
 
46
 
47
- if (WC_Facebookcommerce_Utils::isWoocommerceIntegration()) {
48
- if (!defined('WOOCOMMERCE_FACEBOOK_PLUGIN_SETTINGS_URL')) {
49
- define(
50
- 'WOOCOMMERCE_FACEBOOK_PLUGIN_SETTINGS_URL',
51
- get_admin_url()
52
- .'/admin.php?page=wc-settings&tab=integration'
53
- .'&section=facebookcommerce');
54
- }
55
- include_once 'facebook-commerce.php';
 
56
 
57
- // Register WooCommerce integration.
58
- add_filter('woocommerce_integrations', array(
59
- $this,
60
- 'add_woocommerce_integration'
61
- ));
62
- }
63
- }
 
 
 
64
 
65
- public function add_settings_link($links) {
66
- $settings = array(
67
- 'settings' => sprintf(
68
- '<a href="%s">%s</a>',
69
- admin_url('admin.php?page=wc-settings&tab=integration&section=facebookcommerce'),
70
- 'Settings')
71
- );
72
- return array_merge($settings, $links);
73
- }
 
74
 
75
- public function wp_debug_display_error() {
76
- ?>
77
- <div class="error below-h3">
78
- <p>
79
- <?php
80
- printf(__('To use Facebook for WooCommerce,
 
 
81
  please disable WP_DEBUG_DISPLAY in your wp-config.php file.
82
  Contact your server administrator for more assistance.',
83
- 'facebook-for-woocommerce'));
84
- ?>
85
- </p>
86
- </div>
87
- <?php
88
- }
 
 
89
 
90
- /**
91
- * Add a new integration to WooCommerce.
92
- */
93
- public function add_woocommerce_integration($integrations) {
94
- $integrations[] = 'WC_Facebookcommerce_Integration';
95
- return $integrations;
96
- }
97
 
98
- public function add_wordpress_integration() {
99
- new WP_Facebook_Integration();
100
- }
101
- }
102
 
103
- $WC_Facebookcommerce = new WC_Facebookcommerce(__FILE__);
104
 
105
  endif;
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: 1.9.15
14
  * Woo: 2127297:0ea4fe4c2d7ca6338f8a322fb3e4e187
15
  * Text Domain: facebook-for-woocommerce
16
  * WC requires at least: 3.0.0
20
  */
21
 
22
 
23
+ if ( ! class_exists( 'WC_Facebookcommerce' ) ) :
24
+ include_once 'includes/fbutils.php';
25
 
26
+ class WC_Facebookcommerce {
27
 
28
+ // Change it above as well
29
+ const PLUGIN_VERSION = WC_Facebookcommerce_Utils::PLUGIN_VERSION;
30
 
31
+ /**
32
+ * Construct the plugin.
33
+ */
34
+ public function __construct() {
35
+ add_action( 'plugins_loaded', array( $this, 'init' ) );
36
+ }
37
 
38
+ /**
39
+ * Initialize the plugin.
40
+ */
41
+ public function init() {
42
+ if ( is_admin() ) {
43
+ add_filter(
44
+ 'plugin_action_links_' . plugin_basename( __FILE__ ),
45
+ array( $this, 'add_settings_link' )
46
+ );
47
+ }
48
 
49
+ if ( WC_Facebookcommerce_Utils::isWoocommerceIntegration() ) {
50
+ if ( ! defined( 'WOOCOMMERCE_FACEBOOK_PLUGIN_SETTINGS_URL' ) ) {
51
+ define(
52
+ 'WOOCOMMERCE_FACEBOOK_PLUGIN_SETTINGS_URL',
53
+ get_admin_url()
54
+ . '/admin.php?page=wc-settings&tab=integration'
55
+ . '&section=facebookcommerce'
56
+ );
57
+ }
58
+ include_once 'facebook-commerce.php';
59
 
60
+ // Register WooCommerce integration.
61
+ add_filter(
62
+ 'woocommerce_integrations',
63
+ array(
64
+ $this,
65
+ 'add_woocommerce_integration',
66
+ )
67
+ );
68
+ }
69
+ }
70
 
71
+ public function add_settings_link( $links ) {
72
+ $settings = array(
73
+ 'settings' => sprintf(
74
+ '<a href="%s">%s</a>',
75
+ admin_url( 'admin.php?page=wc-settings&tab=integration&section=facebookcommerce' ),
76
+ 'Settings'
77
+ ),
78
+ );
79
+ return array_merge( $settings, $links );
80
+ }
81
 
82
+ public function wp_debug_display_error() {
83
+ ?>
84
+ <div class="error below-h3">
85
+ <p>
86
+ <?php
87
+ printf(
88
+ __(
89
+ 'To use Facebook for WooCommerce,
90
  please disable WP_DEBUG_DISPLAY in your wp-config.php file.
91
  Contact your server administrator for more assistance.',
92
+ 'facebook-for-woocommerce'
93
+ )
94
+ );
95
+ ?>
96
+ </p>
97
+ </div>
98
+ <?php
99
+ }
100
 
101
+ /**
102
+ * Add a new integration to WooCommerce.
103
+ */
104
+ public function add_woocommerce_integration( $integrations ) {
105
+ $integrations[] = 'WC_Facebookcommerce_Integration';
106
+ return $integrations;
107
+ }
108
 
109
+ public function add_wordpress_integration() {
110
+ new WP_Facebook_Integration();
111
+ }
112
+ }
113
 
114
+ $WC_Facebookcommerce = new WC_Facebookcommerce( __FILE__ );
115
 
116
  endif;
includes/fbasync.php CHANGED
@@ -8,35 +8,34 @@
8
  * @package FacebookCommerce
9
  */
10
 
11
- if (!defined('ABSPATH')) {
12
- exit;
13
  }
14
 
15
- if (!class_exists('WP_Async_Request', false) ) {
16
- // Do not attempt to create this class without WP_Async_Request
17
- return;
18
  }
19
 
20
- if (!class_exists('WC_Facebookcommerce_Async_Request')) :
21
 
22
- /**
23
- * FB Graph API async request
24
- *
25
- */
26
- class WC_Facebookcommerce_Async_Request extends WP_Async_Request {
27
 
28
- protected $action = 'wc_facebook_async_request';
29
 
30
- /**
31
- * Handle
32
- *
33
- * Override this method to perform any actions required
34
- * during the async request.
35
- */
36
- protected function handle() {
37
- // Actions to perform
38
- }
39
 
40
- }
41
 
42
  endif;
8
  * @package FacebookCommerce
9
  */
10
 
11
+ if ( ! defined( 'ABSPATH' ) ) {
12
+ exit;
13
  }
14
 
15
+ if ( ! class_exists( 'WP_Async_Request', false ) ) {
16
+ // Do not attempt to create this class without WP_Async_Request
17
+ return;
18
  }
19
 
20
+ if ( ! class_exists( 'WC_Facebookcommerce_Async_Request' ) ) :
21
 
22
+ /**
23
+ * FB Graph API async request
24
+ */
25
+ class WC_Facebookcommerce_Async_Request extends WP_Async_Request {
 
26
 
27
+ protected $action = 'wc_facebook_async_request';
28
 
29
+ /**
30
+ * Handle
31
+ *
32
+ * Override this method to perform any actions required
33
+ * during the async request.
34
+ */
35
+ protected function handle() {
36
+ // Actions to perform
37
+ }
38
 
39
+ }
40
 
41
  endif;
includes/fbbackground.php CHANGED
@@ -8,145 +8,156 @@
8
  * @package FacebookCommerce
9
  */
10
 
11
- if (! defined('ABSPATH')) {
12
- exit;
13
  }
14
 
15
- if (!class_exists('WP_Background_Process', false) ) {
16
- // Do not attempt to create this class without WP_Background_Process
17
- return;
18
  }
19
 
20
- if (! class_exists('WC_Facebookcommerce_Background_Process')) :
21
-
22
- class WC_Facebookcommerce_Background_Process extends WP_Background_Process {
23
-
24
- public function __construct($commerce) {
25
- $this->commerce = $commerce; // Full WC_Facebookcommerce_Integration obj
26
- }
27
-
28
- /**
29
- * @var string
30
- */
31
- protected $action = 'fb_commerce_background_process';
32
-
33
- public function dispatch() {
34
- $commerce = $this->commerce;
35
- $dispatched = parent::dispatch();
36
-
37
- if (is_wp_error($dispatched)) {
38
- WC_Facebookcommerce_Utils::log(
39
- sprintf('Unable to dispatch FB Background processor: %s',
40
- $dispatched->get_error_message()),
41
- array( 'source' => 'wc_facebook_background_process' ));
42
- }
43
- }
44
-
45
- public function get_item_count() {
46
- $commerce = $this->commerce;
47
- return (int)get_transient($commerce::FB_SYNC_REMAINING);
48
- }
49
-
50
- /**
51
- * Handle cron healthcheck
52
- *
53
- * Restart the background process if not already running
54
- * and data exists in the queue.
55
- */
56
- public function handle_cron_healthcheck() {
57
- $commerce = $this->commerce;
58
- if ($this->is_process_running()) {
59
- // Background process already running, no-op
60
- return true; // Return "is running? status"
61
- }
62
-
63
- if ($this->is_queue_empty()) {
64
- // No data to process.
65
- $this->clear_scheduled_event();
66
- delete_transient($commerce::FB_SYNC_REMAINING);
67
- return;
68
- }
69
-
70
- $this->handle();
71
- return true;
72
-
73
- }
74
-
75
- /**
76
- * Schedule fallback event.
77
- */
78
- protected function schedule_event() {
79
- if (!wp_next_scheduled($this->cron_hook_identifier)) {
80
- wp_schedule_event(time() + 10, $this->cron_interval_identifier,
81
- $this->cron_hook_identifier);
82
- }
83
- }
84
-
85
- /**
86
- * Is the processor updating?
87
- * @return boolean
88
- */
89
- public function is_updating() {
90
- return false === $this->is_queue_empty();
91
- }
92
-
93
- /**
94
- * Is the processor running?
95
- * @return boolean
96
- */
97
- public function is_running() {
98
- return $this->is_process_running();
99
- }
100
-
101
- /**
102
- * Process individual product
103
- *
104
- * Returns false to remove the item from the queue
105
- * (would return item if it needed additional processing).
106
- *
107
- * @param mixed $item Queue item to iterate over
108
- *
109
- * @return mixed
110
- */
111
- protected function task($item) {
112
- $commerce = $this->commerce; // PHP5 compatibility for static access
113
- $remaining = $this->get_item_count();
114
- $count_message = sprintf(
115
- 'Background syncing products to Facebook. Products remaining: %1$d',
116
- $remaining);
117
-
118
- $this->commerce->display_sticky_message($count_message, true);
119
-
120
- $this->commerce->on_product_publish($item);
121
- $remaining--;
122
- set_transient(
123
- $commerce::FB_SYNC_IN_PROGRESS,
124
- true,
125
- $commerce::FB_SYNC_TIMEOUT);
126
- set_transient(
127
- $commerce::FB_SYNC_REMAINING,
128
- $remaining);
129
-
130
- return false;
131
- }
132
-
133
- /**
134
- * Complete
135
- *
136
- * Override if applicable, but ensure that the below actions are
137
- * performed, or, call parent::complete().
138
- */
139
- protected function complete() {
140
- $commerce = $this->commerce; // PHP5 compatibility for static access
141
- delete_transient($commerce::FB_SYNC_IN_PROGRESS);
142
- delete_transient($commerce::FB_SYNC_REMAINING);
143
- WC_Facebookcommerce_Utils::log("Background sync complete!");
144
- WC_Facebookcommerce_Utils::fblog("Background sync complete!");
145
- $this->commerce->remove_sticky_message();
146
- $this->commerce->display_info_message('Facebook product sync complete!');
147
- parent::complete();
148
- }
149
-
150
- }
 
 
 
 
 
 
 
 
 
 
 
151
 
152
  endif;
8
  * @package FacebookCommerce
9
  */
10
 
11
+ if ( ! defined( 'ABSPATH' ) ) {
12
+ exit;
13
  }
14
 
15
+ if ( ! class_exists( 'WP_Background_Process', false ) ) {
16
+ // Do not attempt to create this class without WP_Background_Process
17
+ return;
18
  }
19
 
20
+ if ( ! class_exists( 'WC_Facebookcommerce_Background_Process' ) ) :
21
+
22
+ class WC_Facebookcommerce_Background_Process extends WP_Background_Process {
23
+
24
+ public function __construct( $commerce ) {
25
+ $this->commerce = $commerce; // Full WC_Facebookcommerce_Integration obj
26
+ }
27
+
28
+ /**
29
+ * @var string
30
+ */
31
+ protected $action = 'fb_commerce_background_process';
32
+
33
+ public function dispatch() {
34
+ $commerce = $this->commerce;
35
+ $dispatched = parent::dispatch();
36
+
37
+ if ( is_wp_error( $dispatched ) ) {
38
+ WC_Facebookcommerce_Utils::log(
39
+ sprintf(
40
+ 'Unable to dispatch FB Background processor: %s',
41
+ $dispatched->get_error_message()
42
+ ),
43
+ array( 'source' => 'wc_facebook_background_process' )
44
+ );
45
+ }
46
+ }
47
+
48
+ public function get_item_count() {
49
+ $commerce = $this->commerce;
50
+ return (int) get_transient( $commerce::FB_SYNC_REMAINING );
51
+ }
52
+
53
+ /**
54
+ * Handle cron healthcheck
55
+ *
56
+ * Restart the background process if not already running
57
+ * and data exists in the queue.
58
+ */
59
+ public function handle_cron_healthcheck() {
60
+ $commerce = $this->commerce;
61
+ if ( $this->is_process_running() ) {
62
+ // Background process already running, no-op
63
+ return true; // Return "is running? status"
64
+ }
65
+
66
+ if ( $this->is_queue_empty() ) {
67
+ // No data to process.
68
+ $this->clear_scheduled_event();
69
+ delete_transient( $commerce::FB_SYNC_REMAINING );
70
+ return;
71
+ }
72
+
73
+ $this->handle();
74
+ return true;
75
+
76
+ }
77
+
78
+ /**
79
+ * Schedule fallback event.
80
+ */
81
+ protected function schedule_event() {
82
+ if ( ! wp_next_scheduled( $this->cron_hook_identifier ) ) {
83
+ wp_schedule_event(
84
+ time() + 10,
85
+ $this->cron_interval_identifier,
86
+ $this->cron_hook_identifier
87
+ );
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Is the processor updating?
93
+ *
94
+ * @return boolean
95
+ */
96
+ public function is_updating() {
97
+ return false === $this->is_queue_empty();
98
+ }
99
+
100
+ /**
101
+ * Is the processor running?
102
+ *
103
+ * @return boolean
104
+ */
105
+ public function is_running() {
106
+ return $this->is_process_running();
107
+ }
108
+
109
+ /**
110
+ * Process individual product
111
+ *
112
+ * Returns false to remove the item from the queue
113
+ * (would return item if it needed additional processing).
114
+ *
115
+ * @param mixed $item Queue item to iterate over
116
+ *
117
+ * @return mixed
118
+ */
119
+ protected function task( $item ) {
120
+ $commerce = $this->commerce; // PHP5 compatibility for static access
121
+ $remaining = $this->get_item_count();
122
+ $count_message = sprintf(
123
+ 'Background syncing products to Facebook. Products remaining: %1$d',
124
+ $remaining
125
+ );
126
+
127
+ $this->commerce->display_sticky_message( $count_message, true );
128
+
129
+ $this->commerce->on_product_publish( $item );
130
+ $remaining--;
131
+ set_transient(
132
+ $commerce::FB_SYNC_IN_PROGRESS,
133
+ true,
134
+ $commerce::FB_SYNC_TIMEOUT
135
+ );
136
+ set_transient(
137
+ $commerce::FB_SYNC_REMAINING,
138
+ $remaining
139
+ );
140
+
141
+ return false;
142
+ }
143
+
144
+ /**
145
+ * Complete
146
+ *
147
+ * Override if applicable, but ensure that the below actions are
148
+ * performed, or, call parent::complete().
149
+ */
150
+ protected function complete() {
151
+ $commerce = $this->commerce; // PHP5 compatibility for static access
152
+ delete_transient( $commerce::FB_SYNC_IN_PROGRESS );
153
+ delete_transient( $commerce::FB_SYNC_REMAINING );
154
+ WC_Facebookcommerce_Utils::log( 'Background sync complete!' );
155
+ WC_Facebookcommerce_Utils::fblog( 'Background sync complete!' );
156
+ $this->commerce->remove_sticky_message();
157
+ $this->commerce->display_info_message( 'Facebook product sync complete!' );
158
+ parent::complete();
159
+ }
160
+
161
+ }
162
 
163
  endif;
includes/fbgraph.php CHANGED
@@ -8,291 +8,305 @@
8
  * @package FacebookCommerce
9
  */
10
 
11
- if (!defined('ABSPATH')) {
12
- exit;
13
  }
14
 
15
- if (!class_exists('WC_Facebookcommerce_Graph_API')) :
16
-
17
- if (!class_exists('WC_Facebookcommerce_Async_Request')) {
18
- include_once 'fbasync.php';
19
- }
20
-
21
- /**
22
- * FB Graph API helper functions
23
- *
24
- */
25
- class WC_Facebookcommerce_Graph_API {
26
- const GRAPH_API_URL = 'https://graph.facebook.com/v2.9/';
27
- const CURL_TIMEOUT = 500;
28
-
29
- /**
30
- * Cache the api_key
31
- */
32
- var $api_key;
33
-
34
- /**
35
- * Init
36
- */
37
- public function __construct($api_key) {
38
- $this->api_key = $api_key;
39
- }
40
-
41
- public function _get($url, $api_key = '') {
42
- $api_key = $api_key ?: $this->api_key;
43
- return wp_remote_get($url, array(
44
- 'headers' => array(
45
- 'Authorization' => 'Bearer ' . $api_key,
46
- ),
47
- 'timeout' => self::CURL_TIMEOUT,
48
- ));
49
- }
50
-
51
- public function _post($url, $data, $api_key = '') {
52
- if (class_exists('WC_Facebookcommerce_Async_Request')) {
53
- return self::_post_async($url, $data);
54
- } else {
55
- return self::_post_sync($url, $data);
56
- }
57
- }
58
-
59
- public function _post_sync($url, $data, $api_key = '') {
60
- $api_key = $api_key ?: $this->api_key;
61
- return wp_remote_post($url, array(
62
- 'body' => $data,
63
- 'headers' => array(
64
- 'Authorization' => 'Bearer ' . $api_key,
65
- ),
66
- 'timeout' => self::CURL_TIMEOUT,
67
- ));
68
- }
69
-
70
- public function _post_async($url, $data, $api_key = '') {
71
- if (!class_exists('WC_Facebookcommerce_Async_Request')) {
72
- return;
73
- }
74
-
75
- $api_key = $api_key ?: $this->api_key;
76
- $fbasync = new WC_Facebookcommerce_Async_Request();
77
-
78
- $fbasync->query_url = $url;
79
- $fbasync->query_args = array();
80
- $fbasync->post_args = array(
81
- 'body' => $data,
82
- 'headers' => array(
83
- 'Authorization' => 'Bearer ' . $api_key,
84
- ),
85
- 'timeout' => self::CURL_TIMEOUT,
86
- );
87
-
88
- return $fbasync->dispatch();
89
- }
90
-
91
- public function _delete($url, $api_key = '') {
92
- $api_key = $api_key ?: $this->api_key;
93
-
94
- return wp_remote_request($url, array(
95
- 'headers' => array(
96
- 'Authorization' => 'Bearer ' . $api_key,
97
- ),
98
- 'timeout' => self::CURL_TIMEOUT,
99
- 'method' => 'DELETE',
100
- ));
101
- }
102
-
103
- // GET https://graph.facebook.com/vX.X/{page-id}/?fields=name
104
- public function get_page_name($page_id, $api_key = '') {
105
- $api_key = $api_key ?: $this->api_key;
106
- $url = $this->build_url($page_id, '/?fields=name');
107
- $response = self::_get($url, $api_key);
108
- if (is_wp_error($response)) {
109
- WC_Facebookcommerce_Utils::log($response->get_error_message());
110
- return;
111
- }
112
- if ($response['response']['code'] != '200') {
113
- return;
114
- }
115
-
116
- $response_body = wp_remote_retrieve_body($response);
117
-
118
- return json_decode($response_body)->name;
119
- }
120
-
121
- public function validate_product_catalog($product_catalog_id) {
122
- $url = $this->build_url($product_catalog_id);
123
- $response = self::_get($url);
124
- if (is_wp_error($response)) {
125
- WC_Facebookcommerce_Utils::log($response->get_error_message());
126
- return;
127
- }
128
- return $response['response']['code'] == '200';
129
- }
130
-
131
- // POST https://graph.facebook.com/vX.X/{product-catalog-id}/product_groups
132
- public function create_product_group($product_catalog_id, $data) {
133
- $url = $this->build_url($product_catalog_id, '/product_groups');
134
- return self::_post($url, $data);
135
- }
136
-
137
- // POST https://graph.facebook.com/vX.X/{product-group-id}/products
138
- public function create_product_item($product_group_id, $data) {
139
- $url = $this->build_url($product_group_id, '/products');
140
- return self::_post($url, $data);
141
- }
142
-
143
- public function update_product_group($product_catalog_id, $data) {
144
- $url = $this->build_url($product_catalog_id);
145
- return self::_post($url, $data);
146
- }
147
-
148
- public function update_product_item($product_id, $data) {
149
- $url = $this->build_url($product_id);
150
- return self::_post($url, $data);
151
- }
152
-
153
- public function delete_product_item($product_item_id) {
154
- $product_item_url = $this->build_url($product_item_id);
155
- return self::_delete($product_item_url);
156
- }
157
-
158
- public function delete_product_group($product_group_id) {
159
- $product_group_url = $this->build_url($product_group_id);
160
- return self::_delete($product_group_url);
161
- }
162
-
163
- public function log($ems_id, $message, $error) {
164
- $log_url = $this->build_url($ems_id, '/log_events');
165
-
166
- $data = array(
167
- 'message'=> $message,
168
- 'error' => $error
169
- );
170
-
171
- self::_post($log_url, $data);
172
- }
173
-
174
- public function log_tip_event($tip_id, $channel_id, $event) {
175
- $tip_event_log_url = $this->build_url('', '/log_tip_events');
176
-
177
- $data = array(
178
- 'tip_id' => $tip_id,
179
- 'channel_id' => $channel_id,
180
- 'event' => $event
181
- );
182
-
183
- self::_post($tip_event_log_url, $data);
184
- }
185
-
186
- public function create_upload($facebook_feed_id, $path_to_feed_file) {
187
- $url = $this->build_url(
188
- $facebook_feed_id,
189
- '/uploads?access_token=' . $this->api_key);
190
- $data = array(
191
- 'file' => new CurlFile($path_to_feed_file, 'text/csv')
192
- );
193
- $curl = curl_init();
194
- curl_setopt_array(
195
- $curl,
196
- array(
197
- CURLOPT_URL => $url,
198
- CURLOPT_POST => 1,
199
- CURLOPT_POSTFIELDS => $data,
200
- CURLOPT_RETURNTRANSFER => 1));
201
- $response = curl_exec($curl);
202
- if (curl_errno($curl)) {
203
- WC_Facebookcommerce_Utils::fblog($response);
204
- return null;
205
- }
206
- return WC_Facebookcommerce_Utils::decode_json($response, true);
207
- }
208
-
209
- public function create_feed($facebook_catalog_id, $data) {
210
- $url = $this->build_url($facebook_catalog_id, '/product_feeds');
211
- // success API call will return {id: <product feed id>}
212
- // failure API will return {error: <error message>}
213
- return self::_post($url, $data);
214
- }
215
-
216
- public function get_upload_status($facebook_upload_id) {
217
- $url = $this->build_url($facebook_upload_id, '/?fields=end_time');
218
- // success API call will return
219
- // {id: <upload id>, end_time: <time when upload completes>}
220
- // failure API will return {error: <error message>}
221
- return self::_get($url);
222
- }
223
-
224
- // success API call will return a JSON of tip info
225
- public function get_tip_info($external_merchant_settings_id) {
226
- $url = $this->build_url($external_merchant_settings_id, '/?fields=connect_woo');
227
- $response = self::_get($url, $this->api_key);
228
- $data = array(
229
- 'response' => $response,
230
- );
231
- if (is_wp_error($response)) {
232
- $data['error_type'] = 'is_wp_error';
233
- WC_Facebookcommerce_Utils::fblog(
234
- 'Failed to get AYMT tip info via API.',
235
- $data,
236
- true);
237
- return;
238
- }
239
- if ($response['response']['code'] != '200') {
240
- $data['error_type'] = 'Non-200 error code from FB';
241
- WC_Facebookcommerce_Utils::fblog(
242
- 'Failed to get AYMT tip info via API.',
243
- $data,
244
- true);
245
- return;
246
- }
247
-
248
- $response_body = wp_remote_retrieve_body($response);
249
- $connect_woo =
250
- WC_Facebookcommerce_Utils::decode_json($response_body)->connect_woo;
251
- if (!isset($connect_woo)) {
252
- $data['error_type'] = 'Response body not set';
253
- WC_Facebookcommerce_Utils::fblog(
254
- "Failed to get AYMT tip info via API.",
255
- $data,
256
- true);
257
- }
258
- return $connect_woo;
259
- }
260
-
261
- public function get_facebook_id($facebook_catalog_id, $product_id) {
262
- $param = 'catalog:' . (string)$facebook_catalog_id . ':' .
263
- base64_encode($product_id) . '/?fields=id,product_group{id}';
264
- $url = $this->build_url('', $param);
265
- // success API call will return
266
- // {id: <fb product id>, product_group{id} <fb product group id>}
267
- // failure API will return {error: <error message>}
268
- return self::_get($url);
269
- }
270
-
271
- public function check_product_info($facebook_catalog_id, $product_id, $pr_v) {
272
- $param = 'catalog:' . (string)$facebook_catalog_id . ':' .
273
- base64_encode($product_id) . '/?fields=id,name,description,price,' .
274
- 'sale_price,sale_price_start_date,sale_price_end_date,image_url,' .
275
- 'visibility';
276
- if ($pr_v) {
277
- $param = $param . ',additional_variant_attributes{value}';
278
- }
279
- $url = $this->build_url('', $param);
280
- // success API call will return
281
- // {id: <fb product id>, name,description,price,sale_price,sale_price_start_date
282
- // sale_price_end_date
283
- // failure API will return {error: <error message>}
284
- return self::_get($url);
285
- }
286
-
287
- public function set_default_variant($product_group_id, $data) {
288
- $url = $this->build_url($product_group_id);
289
- return self::_post($url, $data);
290
- }
291
-
292
- private function build_url($field_id, $param ='') {
293
- return self::GRAPH_API_URL . (string)$field_id . $param;
294
- }
295
-
296
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
297
 
298
  endif;
8
  * @package FacebookCommerce
9
  */
10
 
11
+ if ( ! defined( 'ABSPATH' ) ) {
12
+ exit;
13
  }
14
 
15
+ if ( ! class_exists( 'WC_Facebookcommerce_Graph_API' ) ) :
16
+
17
+ if ( ! class_exists( 'WC_Facebookcommerce_Async_Request' ) ) {
18
+ include_once 'fbasync.php';
19
+ }
20
+
21
+ /**
22
+ * FB Graph API helper functions
23
+ */
24
+ class WC_Facebookcommerce_Graph_API {
25
+ const GRAPH_API_URL = 'https://graph.facebook.com/v2.9/';
26
+ const CURL_TIMEOUT = 500;
27
+
28
+ /**
29
+ * Cache the api_key
30
+ */
31
+ var $api_key;
32
+
33
+ /**
34
+ * Init
35
+ */
36
+ public function __construct( $api_key ) {
37
+ $this->api_key = $api_key;
38
+ }
39
+
40
+ public function _get( $url, $api_key = '' ) {
41
+ $api_key = $api_key ?: $this->api_key;
42
+ return wp_remote_get(
43
+ $url,
44
+ array(
45
+ 'headers' => array(
46
+ 'Authorization' => 'Bearer ' . $api_key,
47
+ ),
48
+ 'timeout' => self::CURL_TIMEOUT,
49
+ )
50
+ );
51
+ }
52
+
53
+ public function _post( $url, $data, $api_key = '' ) {
54
+ if ( class_exists( 'WC_Facebookcommerce_Async_Request' ) ) {
55
+ return self::_post_async( $url, $data );
56
+ } else {
57
+ return self::_post_sync( $url, $data );
58
+ }
59
+ }
60
+
61
+ public function _post_sync( $url, $data, $api_key = '' ) {
62
+ $api_key = $api_key ?: $this->api_key;
63
+ return wp_remote_post(
64
+ $url,
65
+ array(
66
+ 'body' => $data,
67
+ 'headers' => array(
68
+ 'Authorization' => 'Bearer ' . $api_key,
69
+ ),
70
+ 'timeout' => self::CURL_TIMEOUT,
71
+ )
72
+ );
73
+ }
74
+
75
+ public function _post_async( $url, $data, $api_key = '' ) {
76
+ if ( ! class_exists( 'WC_Facebookcommerce_Async_Request' ) ) {
77
+ return;
78
+ }
79
+
80
+ $api_key = $api_key ?: $this->api_key;
81
+ $fbasync = new WC_Facebookcommerce_Async_Request();
82
+
83
+ $fbasync->query_url = $url;
84
+ $fbasync->query_args = array();
85
+ $fbasync->post_args = array(
86
+ 'body' => $data,
87
+ 'headers' => array(
88
+ 'Authorization' => 'Bearer ' . $api_key,
89
+ ),
90
+ 'timeout' => self::CURL_TIMEOUT,
91
+ );
92
+
93
+ return $fbasync->dispatch();
94
+ }
95
+
96
+ public function _delete( $url, $api_key = '' ) {
97
+ $api_key = $api_key ?: $this->api_key;
98
+
99
+ return wp_remote_request(
100
+ $url,
101
+ array(
102
+ 'headers' => array(
103
+ 'Authorization' => 'Bearer ' . $api_key,
104
+ ),
105
+ 'timeout' => self::CURL_TIMEOUT,
106
+ 'method' => 'DELETE',
107
+ )
108
+ );
109
+ }
110
+
111
+ // GET https://graph.facebook.com/vX.X/{page-id}/?fields=name
112
+ public function get_page_name( $page_id, $api_key = '' ) {
113
+ $api_key = $api_key ?: $this->api_key;
114
+ $url = $this->build_url( $page_id, '/?fields=name' );
115
+ $response = self::_get( $url, $api_key );
116
+ if ( is_wp_error( $response ) ) {
117
+ WC_Facebookcommerce_Utils::log( $response->get_error_message() );
118
+ return;
119
+ }
120
+ if ( $response['response']['code'] != '200' ) {
121
+ return;
122
+ }
123
+
124
+ $response_body = wp_remote_retrieve_body( $response );
125
+
126
+ return json_decode( $response_body )->name;
127
+ }
128
+
129
+ public function validate_product_catalog( $product_catalog_id ) {
130
+ $url = $this->build_url( $product_catalog_id );
131
+ $response = self::_get( $url );
132
+ if ( is_wp_error( $response ) ) {
133
+ WC_Facebookcommerce_Utils::log( $response->get_error_message() );
134
+ return;
135
+ }
136
+ return $response['response']['code'] == '200';
137
+ }
138
+
139
+ // POST https://graph.facebook.com/vX.X/{product-catalog-id}/product_groups
140
+ public function create_product_group( $product_catalog_id, $data ) {
141
+ $url = $this->build_url( $product_catalog_id, '/product_groups' );
142
+ return self::_post( $url, $data );
143
+ }
144
+
145
+ // POST https://graph.facebook.com/vX.X/{product-group-id}/products
146
+ public function create_product_item( $product_group_id, $data ) {
147
+ $url = $this->build_url( $product_group_id, '/products' );
148
+ return self::_post( $url, $data );
149
+ }
150
+
151
+ public function update_product_group( $product_catalog_id, $data ) {
152
+ $url = $this->build_url( $product_catalog_id );
153
+ return self::_post( $url, $data );
154
+ }
155
+
156
+ public function update_product_item( $product_id, $data ) {
157
+ $url = $this->build_url( $product_id );
158
+ return self::_post( $url, $data );
159
+ }
160
+
161
+ public function delete_product_item( $product_item_id ) {
162
+ $product_item_url = $this->build_url( $product_item_id );
163
+ return self::_delete( $product_item_url );
164
+ }
165
+
166
+ public function delete_product_group( $product_group_id ) {
167
+ $product_group_url = $this->build_url( $product_group_id );
168
+ return self::_delete( $product_group_url );
169
+ }
170
+
171
+ public function log( $ems_id, $message, $error ) {
172
+ $log_url = $this->build_url( $ems_id, '/log_events' );
173
+
174
+ $data = array(
175
+ 'message' => $message,
176
+ 'error' => $error,
177
+ );
178
+
179
+ self::_post( $log_url, $data );
180
+ }
181
+
182
+ public function log_tip_event( $tip_id, $channel_id, $event ) {
183
+ $tip_event_log_url = $this->build_url( '', '/log_tip_events' );
184
+
185
+ $data = array(
186
+ 'tip_id' => $tip_id,
187
+ 'channel_id' => $channel_id,
188
+ 'event' => $event,
189
+ );
190
+
191
+ self::_post( $tip_event_log_url, $data );
192
+ }
193
+
194
+ public function create_upload( $facebook_feed_id, $path_to_feed_file ) {
195
+ $url = $this->build_url(
196
+ $facebook_feed_id,
197
+ '/uploads?access_token=' . $this->api_key
198
+ );
199
+ $data = array(
200
+ 'file' => new CurlFile( $path_to_feed_file, 'text/csv' ),
201
+ );
202
+ $curl = curl_init();
203
+ curl_setopt_array(
204
+ $curl,
205
+ array(
206
+ CURLOPT_URL => $url,
207
+ CURLOPT_POST => 1,
208
+ CURLOPT_POSTFIELDS => $data,
209
+ CURLOPT_RETURNTRANSFER => 1,
210
+ )
211
+ );
212
+ $response = curl_exec( $curl );
213
+ if ( curl_errno( $curl ) ) {
214
+ WC_Facebookcommerce_Utils::fblog( $response );
215
+ return null;
216
+ }
217
+ return WC_Facebookcommerce_Utils::decode_json( $response, true );
218
+ }
219
+
220
+ public function create_feed( $facebook_catalog_id, $data ) {
221
+ $url = $this->build_url( $facebook_catalog_id, '/product_feeds' );
222
+ // success API call will return {id: <product feed id>}
223
+ // failure API will return {error: <error message>}
224
+ return self::_post( $url, $data );
225
+ }
226
+
227
+ public function get_upload_status( $facebook_upload_id ) {
228
+ $url = $this->build_url( $facebook_upload_id, '/?fields=end_time' );
229
+ // success API call will return
230
+ // {id: <upload id>, end_time: <time when upload completes>}
231
+ // failure API will return {error: <error message>}
232
+ return self::_get( $url );
233
+ }
234
+
235
+ // success API call will return a JSON of tip info
236
+ public function get_tip_info( $external_merchant_settings_id ) {
237
+ $url = $this->build_url( $external_merchant_settings_id, '/?fields=connect_woo' );
238
+ $response = self::_get( $url, $this->api_key );
239
+ $data = array(
240
+ 'response' => $response,
241
+ );
242
+ if ( is_wp_error( $response ) ) {
243
+ $data['error_type'] = 'is_wp_error';
244
+ WC_Facebookcommerce_Utils::fblog(
245
+ 'Failed to get AYMT tip info via API.',
246
+ $data,
247
+ true
248
+ );
249
+ return;
250
+ }
251
+ if ( $response['response']['code'] != '200' ) {
252
+ $data['error_type'] = 'Non-200 error code from FB';
253
+ WC_Facebookcommerce_Utils::fblog(
254
+ 'Failed to get AYMT tip info via API.',
255
+ $data,
256
+ true
257
+ );
258
+ return;
259
+ }
260
+
261
+ $response_body = wp_remote_retrieve_body( $response );
262
+ $connect_woo =
263
+ WC_Facebookcommerce_Utils::decode_json( $response_body )->connect_woo;
264
+ if ( ! isset( $connect_woo ) ) {
265
+ $data['error_type'] = 'Response body not set';
266
+ WC_Facebookcommerce_Utils::fblog(
267
+ 'Failed to get AYMT tip info via API.',
268
+ $data,
269
+ true
270
+ );
271
+ }
272
+ return $connect_woo;
273
+ }
274
+
275
+ public function get_facebook_id( $facebook_catalog_id, $product_id ) {
276
+ $param = 'catalog:' . (string) $facebook_catalog_id . ':' .
277
+ base64_encode( $product_id ) . '/?fields=id,product_group{id}';
278
+ $url = $this->build_url( '', $param );
279
+ // success API call will return
280
+ // {id: <fb product id>, product_group{id} <fb product group id>}
281
+ // failure API will return {error: <error message>}
282
+ return self::_get( $url );
283
+ }
284
+
285
+ public function check_product_info( $facebook_catalog_id, $product_id, $pr_v ) {
286
+ $param = 'catalog:' . (string) $facebook_catalog_id . ':' .
287
+ base64_encode( $product_id ) . '/?fields=id,name,description,price,' .
288
+ 'sale_price,sale_price_start_date,sale_price_end_date,image_url,' .
289
+ 'visibility';
290
+ if ( $pr_v ) {
291
+ $param = $param . ',additional_variant_attributes{value}';
292
+ }
293
+ $url = $this->build_url( '', $param );
294
+ // success API call will return
295
+ // {id: <fb product id>, name,description,price,sale_price,sale_price_start_date
296
+ // sale_price_end_date
297
+ // failure API will return {error: <error message>}
298
+ return self::_get( $url );
299
+ }
300
+
301
+ public function set_default_variant( $product_group_id, $data ) {
302
+ $url = $this->build_url( $product_group_id );
303
+ return self::_post( $url, $data );
304
+ }
305
+
306
+ private function build_url( $field_id, $param = '' ) {
307
+ return self::GRAPH_API_URL . (string) $field_id . $param;
308
+ }
309
+
310
+ }
311
 
312
  endif;
includes/fbinfobanner.php CHANGED
@@ -8,234 +8,257 @@
8
  * @package FacebookCommerce
9
  */
10
 
11
- if (! defined('ABSPATH')) {
12
- exit;
13
  }
14
 
15
- if (!class_exists('WC_Facebookcommerce_Utils')) {
16
- include_once 'includes/fbutils.php';
17
  }
18
 
19
- if (! class_exists('WC_Facebookcommerce_Info_Banner')) :
20
 
21
- /**
22
- * FB Info Banner class
23
- */
24
- class WC_Facebookcommerce_Info_Banner {
25
-
26
- const FB_NO_TIP_EXISTS = 'No Tip Exist!';
27
- const DEFAULT_TIP_IMG_URL_PREFIX = 'https://www.facebook.com';
28
- const CHANNEL_ID = 2087541767986590;
29
-
30
- /** @var object Class Instance */
31
- private static $instance;
32
-
33
- /** @var string If the banner has been dismissed */
34
- private $external_merchant_settings_id;
35
- private $fbgraph;
36
- private $should_query_tip;
37
-
38
- /**
39
- * Get the class instance
40
- */
41
- public static function get_instance(
42
- $external_merchant_settings_id,
43
- $fbgraph,
44
- $should_query_tip = false) {
45
- return null === self::$instance
46
- ? (self::$instance = new self(
47
- $external_merchant_settings_id,
48
- $fbgraph,
49
- $should_query_tip))
50
- : self::$instance;
51
- }
52
-
53
- /**
54
- * Constructor
55
- */
56
- public function __construct(
57
- $external_merchant_settings_id,
58
- $fbgraph,
59
- $should_query_tip = false) {
60
- $this->should_query_tip = $should_query_tip;
61
- $this->external_merchant_settings_id = $external_merchant_settings_id;
62
- $this->fbgraph = $fbgraph;
63
- add_action('wp_ajax_ajax_woo_infobanner_post_click', array($this, 'ajax_woo_infobanner_post_click'));
64
- add_action('wp_ajax_ajax_woo_infobanner_post_xout', array($this, 'ajax_woo_infobanner_post_xout'));
65
- add_action('admin_notices', array($this, 'banner'));
66
- add_action('admin_init', array($this, 'dismiss_banner'));
67
- }
68
-
69
- /**
70
- * Post click event when hit primary button.
71
- */
72
- function ajax_woo_infobanner_post_click() {
73
- WC_Facebookcommerce_Utils::check_woo_ajax_permissions(
74
- 'post tip click event',
75
- true);
76
- $tip_info = WC_Facebookcommerce_Utils::get_cached_best_tip();
77
- $tip_id = isset($tip_info->tip_id)
78
- ? $tip_info->tip_id
79
- : null;
80
- if ($tip_id == null) {
81
- WC_Facebookcommerce_Utils::fblog(
82
- 'Do not have tip id when click, sth went wrong',
83
- array('tip_info' => $tip_info),
84
- true);
85
- } else {
86
- WC_Facebookcommerce_Utils::tip_events_log(
87
- $tip_id,
88
- self::CHANNEL_ID,
89
- 'click');
90
- }
91
- }
92
-
93
- /**
94
- * Post xout event when hit dismiss button.
95
- */
96
- function ajax_woo_infobanner_post_xout() {
97
- WC_Facebookcommerce_Utils::check_woo_ajax_permissions(
98
- 'post tip xout event',
99
- true);
100
- $tip_info = WC_Facebookcommerce_Utils::get_cached_best_tip();
101
- $tip_id = isset($tip_info->tip_id)
102
- ? $tip_info->tip_id
103
- : null;
104
- // Delete cached tip if xout.
105
- update_option('fb_info_banner_last_best_tip', '');
106
- if ($tip_id == null) {
107
- WC_Facebookcommerce_Utils::fblog(
108
- 'Do not have tip id when xout, sth went wrong',
109
- array('tip_info' => $tip_info),
110
- true);
111
- } else {
112
- WC_Facebookcommerce_Utils::tip_events_log(
113
- $tip_id,
114
- self::CHANNEL_ID,
115
- 'xout');
116
- }
117
- }
118
-
119
- /**
120
- * Display a info banner on Woocommerce pages.
121
- */
122
- public function banner() {
123
- $screen = get_current_screen();
124
- if (!in_array($screen->base, array('woocommerce_page_wc-reports',
125
- 'woocommerce_page_wc-settings', 'woocommerce_page_wc-status')) ||
126
- $screen->is_network || $screen->action) {
127
- return;
128
- }
129
-
130
- $tip_info = null;
131
- if (!$this->should_query_tip) {
132
- // If last query is less than 1 day, either has last best tip or default
133
- // tip pass time cap.
134
- $tip_info = WC_Facebookcommerce_Utils::get_cached_best_tip();
135
- } else {
136
- $tip_info = $this->fbgraph->get_tip_info(
137
- $this->external_merchant_settings_id);
138
- update_option('fb_info_banner_last_query_time', current_time('mysql'));
139
- }
140
-
141
- // Not render if no cached best tip, or no best tip returned from FB.
142
- if (!$tip_info || ($tip_info === self::FB_NO_TIP_EXISTS)) {
143
- // Delete cached tip if should query and get no tip.
144
- delete_option('fb_info_banner_last_best_tip');
145
- return;
146
- } else {
147
- // Get tip creatives via API
148
- if (is_string($tip_info)) {
149
- $tip_info = WC_Facebookcommerce_Utils::decode_json($tip_info);
150
- }
151
- $tip_title = isset($tip_info->tip_title->__html)
152
- ? $tip_info->tip_title->__html
153
- : null;
154
-
155
- $tip_body = isset($tip_info->tip_body->__html)
156
- ? $tip_info->tip_body->__html
157
- : null;
158
-
159
- $tip_action_link = isset($tip_info->tip_action_link)
160
- ? $tip_info->tip_action_link
161
- : null;
162
-
163
- $tip_action = isset($tip_info->tip_action->__html)
164
- ? $tip_info->tip_action->__html
165
- : null;
166
-
167
- $tip_img_url = isset($tip_info->tip_img_url)
168
- ? self::DEFAULT_TIP_IMG_URL_PREFIX . $tip_info->tip_img_url
169
- : null;
170
-
171
- if ($tip_title == null || $tip_body == null || $tip_action_link == null
172
- || $tip_action == null || $tip_action == null) {
173
- WC_Facebookcommerce_Utils::fblog(
174
- 'Unexpected response from FB for tip info.',
175
- array('tip_info' => $tip_info),
176
- true);
177
- return;
178
- }
179
- update_option('fb_info_banner_last_best_tip',
180
- is_object($tip_info) || is_array($tip_info)
181
- ? json_encode($tip_info) : $tip_info);
182
- }
183
-
184
- $dismiss_url = $this->dismiss_url();
185
- echo '<div class="updated fade"><div id="fbinfobanner"><div><img src="'. $tip_img_url .
186
- '" class="iconDetails"></div><p class = "tipTitle">' .
187
- __('<strong>' . $tip_title . '</strong>', 'facebook-for-woocommerce') . "\n";
188
- echo '<p class = "tipContent">'.
189
- __($tip_body, 'facebook-for-woocommerce') . '</p>';
190
- echo '<p class = "tipButton"><a href="' . $tip_action_link . '" class = "btn" onclick="fb_woo_infobanner_post_click(); return true;" title="' .
191
- __('Click and redirect.', 'facebook-for-woocommerce').
192
- '"> ' . __($tip_action, 'facebook-for-woocommerce') . '</a>' .
193
- '<a href="' . esc_url($dismiss_url). '" class = "btn dismiss grey" onclick="fb_woo_infobanner_post_xout(); return true;" title="' .
194
- __('Dismiss this notice.', 'facebook-for-woocommerce').
195
- '"> ' . __('Dismiss', 'facebook-for-woocommerce') . '</a></p></div></div>';
196
- }
197
-
198
- /**
199
- * Returns the url that the user clicks to remove the info banner
200
- * @return (string)
201
- */
202
- private function dismiss_url() {
203
- $url = admin_url('admin.php');
204
-
205
- $url = add_query_arg(array(
206
- 'page' => 'wc-settings',
207
- 'tab' => 'integration',
208
- 'wc-notice' => 'dismiss-fb-info-banner',
209
- ), $url);
210
-
211
- return wp_nonce_url($url, 'woocommerce_info_banner_dismiss');
212
- }
213
-
214
- /**
215
- * Handles the dismiss action so that the banner can be permanently hidden
216
- * during time threshold
217
- */
218
- public function dismiss_banner() {
219
- if (!isset($_GET['wc-notice'])) {
220
- return;
221
- }
222
-
223
- if ('dismiss-fb-info-banner' !== $_GET['wc-notice']) {
224
- return;
225
- }
226
-
227
- if (!check_admin_referer('woocommerce_info_banner_dismiss')) {
228
- return;
229
- }
230
-
231
- // Delete cached tip if xout.
232
- delete_option('fb_info_banner_last_best_tip');
233
- if (wp_get_referer()) {
234
- wp_safe_redirect(wp_get_referer());
235
- } else {
236
- wp_safe_redirect(admin_url('admin.php?page=wc-settings&tab=integration'));
237
- }
238
- }
239
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
240
 
241
  endif;
8
  * @package FacebookCommerce
9
  */
10
 
11
+ if ( ! defined( 'ABSPATH' ) ) {
12
+ exit;
13
  }
14
 
15
+ if ( ! class_exists( 'WC_Facebookcommerce_Utils' ) ) {
16
+ include_once 'includes/fbutils.php';
17
  }
18
 
19
+ if ( ! class_exists( 'WC_Facebookcommerce_Info_Banner' ) ) :
20
 
21
+ /**
22
+ * FB Info Banner class
23
+ */
24
+ class WC_Facebookcommerce_Info_Banner {
25
+
26
+ const FB_NO_TIP_EXISTS = 'No Tip Exist!';
27
+ const DEFAULT_TIP_IMG_URL_PREFIX = 'https://www.facebook.com';
28
+ const CHANNEL_ID = 2087541767986590;
29
+
30
+ /** @var object Class Instance */
31
+ private static $instance;
32
+
33
+ /** @var string If the banner has been dismissed */
34
+ private $external_merchant_settings_id;
35
+ private $fbgraph;
36
+ private $should_query_tip;
37
+
38
+ /**
39
+ * Get the class instance
40
+ */
41
+ public static function get_instance(
42
+ $external_merchant_settings_id,
43
+ $fbgraph,
44
+ $should_query_tip = false ) {
45
+ return null === self::$instance
46
+ ? ( self::$instance = new self(
47
+ $external_merchant_settings_id,
48
+ $fbgraph,
49
+ $should_query_tip
50
+ ) )
51
+ : self::$instance;
52
+ }
53
+
54
+ /**
55
+ * Constructor
56
+ */
57
+ public function __construct(
58
+ $external_merchant_settings_id,
59
+ $fbgraph,
60
+ $should_query_tip = false ) {
61
+ $this->should_query_tip = $should_query_tip;
62
+ $this->external_merchant_settings_id = $external_merchant_settings_id;
63
+ $this->fbgraph = $fbgraph;
64
+ add_action( 'wp_ajax_ajax_woo_infobanner_post_click', array( $this, 'ajax_woo_infobanner_post_click' ) );
65
+ add_action( 'wp_ajax_ajax_woo_infobanner_post_xout', array( $this, 'ajax_woo_infobanner_post_xout' ) );
66
+ add_action( 'admin_notices', array( $this, 'banner' ) );
67
+ add_action( 'admin_init', array( $this, 'dismiss_banner' ) );
68
+ }
69
+
70
+ /**
71
+ * Post click event when hit primary button.
72
+ */
73
+ function ajax_woo_infobanner_post_click() {
74
+ WC_Facebookcommerce_Utils::check_woo_ajax_permissions(
75
+ 'post tip click event',
76
+ true
77
+ );
78
+ check_ajax_referer( 'wc_facebook_infobanner_jsx' );
79
+ $tip_info = WC_Facebookcommerce_Utils::get_cached_best_tip();
80
+ $tip_id = isset( $tip_info->tip_id )
81
+ ? $tip_info->tip_id
82
+ : null;
83
+ if ( $tip_id == null ) {
84
+ WC_Facebookcommerce_Utils::fblog(
85
+ 'Do not have tip id when click, sth went wrong',
86
+ array( 'tip_info' => $tip_info ),
87
+ true
88
+ );
89
+ } else {
90
+ WC_Facebookcommerce_Utils::tip_events_log(
91
+ $tip_id,
92
+ self::CHANNEL_ID,
93
+ 'click'
94
+ );
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Post xout event when hit dismiss button.
100
+ */
101
+ function ajax_woo_infobanner_post_xout() {
102
+ WC_Facebookcommerce_Utils::check_woo_ajax_permissions(
103
+ 'post tip xout event',
104
+ true
105
+ );
106
+ check_ajax_referer( 'wc_facebook_infobanner_jsx' );
107
+ $tip_info = WC_Facebookcommerce_Utils::get_cached_best_tip();
108
+ $tip_id = isset( $tip_info->tip_id )
109
+ ? $tip_info->tip_id
110
+ : null;
111
+ // Delete cached tip if xout.
112
+ update_option( 'fb_info_banner_last_best_tip', '' );
113
+ if ( $tip_id == null ) {
114
+ WC_Facebookcommerce_Utils::fblog(
115
+ 'Do not have tip id when xout, sth went wrong',
116
+ array( 'tip_info' => $tip_info ),
117
+ true
118
+ );
119
+ } else {
120
+ WC_Facebookcommerce_Utils::tip_events_log(
121
+ $tip_id,
122
+ self::CHANNEL_ID,
123
+ 'xout'
124
+ );
125
+ }
126
+ }
127
+
128
+ /**
129
+ * Display a info banner on Woocommerce pages.
130
+ */
131
+ public function banner() {
132
+ $screen = get_current_screen();
133
+ if ( ! in_array(
134
+ $screen->base,
135
+ array(
136
+ 'woocommerce_page_wc-reports',
137
+ 'woocommerce_page_wc-settings',
138
+ 'woocommerce_page_wc-status',
139
+ )
140
+ ) ||
141
+ $screen->is_network || $screen->action ) {
142
+ return;
143
+ }
144
+
145
+ $tip_info = null;
146
+ if ( ! $this->should_query_tip ) {
147
+ // If last query is less than 1 day, either has last best tip or default
148
+ // tip pass time cap.
149
+ $tip_info = WC_Facebookcommerce_Utils::get_cached_best_tip();
150
+ } else {
151
+ $tip_info = $this->fbgraph->get_tip_info(
152
+ $this->external_merchant_settings_id
153
+ );
154
+ update_option( 'fb_info_banner_last_query_time', current_time( 'mysql' ) );
155
+ }
156
+
157
+ // Not render if no cached best tip, or no best tip returned from FB.
158
+ if ( ! $tip_info || ( $tip_info === self::FB_NO_TIP_EXISTS ) ) {
159
+ // Delete cached tip if should query and get no tip.
160
+ delete_option( 'fb_info_banner_last_best_tip' );
161
+ return;
162
+ } else {
163
+ // Get tip creatives via API
164
+ if ( is_string( $tip_info ) ) {
165
+ $tip_info = WC_Facebookcommerce_Utils::decode_json( $tip_info );
166
+ }
167
+ $tip_title = isset( $tip_info->tip_title->__html )
168
+ ? $tip_info->tip_title->__html
169
+ : null;
170
+
171
+ $tip_body = isset( $tip_info->tip_body->__html )
172
+ ? $tip_info->tip_body->__html
173
+ : null;
174
+
175
+ $tip_action_link = isset( $tip_info->tip_action_link )
176
+ ? $tip_info->tip_action_link
177
+ : null;
178
+
179
+ $tip_action = isset( $tip_info->tip_action->__html )
180
+ ? $tip_info->tip_action->__html
181
+ : null;
182
+
183
+ $tip_img_url = isset( $tip_info->tip_img_url )
184
+ ? self::DEFAULT_TIP_IMG_URL_PREFIX . $tip_info->tip_img_url
185
+ : null;
186
+
187
+ if ( $tip_title == null || $tip_body == null || $tip_action_link == null
188
+ || $tip_action == null || $tip_action == null ) {
189
+ WC_Facebookcommerce_Utils::fblog(
190
+ 'Unexpected response from FB for tip info.',
191
+ array( 'tip_info' => $tip_info ),
192
+ true
193
+ );
194
+ return;
195
+ }
196
+ update_option(
197
+ 'fb_info_banner_last_best_tip',
198
+ is_object( $tip_info ) || is_array( $tip_info )
199
+ ? json_encode( $tip_info ) : $tip_info
200
+ );
201
+ }
202
+
203
+ $dismiss_url = $this->dismiss_url();
204
+ echo '<div class="updated fade"><div id="fbinfobanner"><div><img src="' . $tip_img_url .
205
+ '" class="iconDetails"></div><p class = "tipTitle">' .
206
+ __( '<strong>' . $tip_title . '</strong>', 'facebook-for-woocommerce' ) . "\n";
207
+ echo '<p class = "tipContent">' .
208
+ __( $tip_body, 'facebook-for-woocommerce' ) . '</p>';
209
+ echo '<p class = "tipButton"><a href="' . $tip_action_link . '" class = "btn" onclick="fb_woo_infobanner_post_click(); return true;" title="' .
210
+ __( 'Click and redirect.', 'facebook-for-woocommerce' ) .
211
+ '"> ' . __( $tip_action, 'facebook-for-woocommerce' ) . '</a>' .
212
+ '<a href="' . esc_url( $dismiss_url ) . '" class = "btn dismiss grey" onclick="fb_woo_infobanner_post_xout(); return true;" title="' .
213
+ __( 'Dismiss this notice.', 'facebook-for-woocommerce' ) .
214
+ '"> ' . __( 'Dismiss', 'facebook-for-woocommerce' ) . '</a></p></div></div>';
215
+ }
216
+
217
+ /**
218
+ * Returns the url that the user clicks to remove the info banner
219
+ *
220
+ * @return (string)
221
+ */
222
+ private function dismiss_url() {
223
+ $url = admin_url( 'admin.php' );
224
+
225
+ $url = add_query_arg(
226
+ array(
227
+ 'page' => 'wc-settings',
228
+ 'tab' => 'integration',
229
+ 'wc-notice' => 'dismiss-fb-info-banner',
230
+ ),
231
+ $url
232
+ );
233
+
234
+ return wp_nonce_url( $url, 'woocommerce_info_banner_dismiss' );
235
+ }
236
+
237
+ /**
238
+ * Handles the dismiss action so that the banner can be permanently hidden
239
+ * during time threshold
240
+ */
241
+ public function dismiss_banner() {
242
+ if ( ! isset( $_GET['wc-notice'] ) ) {
243
+ return;
244
+ }
245
+
246
+ if ( 'dismiss-fb-info-banner' !== $_GET['wc-notice'] ) {
247
+ return;
248
+ }
249
+
250
+ if ( ! check_admin_referer( 'woocommerce_info_banner_dismiss' ) ) {
251
+ return;
252
+ }
253
+
254
+ // Delete cached tip if xout.
255
+ delete_option( 'fb_info_banner_last_best_tip' );
256
+ if ( wp_get_referer() ) {
257
+ wp_safe_redirect( wp_get_referer() );
258
+ } else {
259
+ wp_safe_redirect( admin_url( 'admin.php?page=wc-settings&tab=integration' ) );
260
+ }
261
+ }
262
+ }
263
 
264
  endif;
includes/fbproduct.php CHANGED
@@ -8,745 +8,795 @@
8
  * @package FacebookCommerce
9
  */
10
 
11
- if (! defined('ABSPATH')) {
12
- exit;
13
  }
14
 
15
- if (!class_exists('WC_Facebookcommerce_Utils')) {
16
- include_once 'includes/fbutils.php';
17
  }
18
 
19
- if (! class_exists('WC_Facebook_Product')) :
20
-
21
- /**
22
- * Custom FB Product proxy class
23
- */
24
- class WC_Facebook_Product {
25
-
26
- // Should match facebook-commerce.php while we migrate that code over
27
- // to this object.
28
- const FB_PRODUCT_DESCRIPTION = 'fb_product_description';
29
- const FB_PRODUCT_PRICE = 'fb_product_price';
30
- const FB_PRODUCT_IMAGE = 'fb_product_image';
31
- const FB_VARIANT_IMAGE = 'fb_image';
32
- const FB_VISIBILITY = 'fb_visibility';
33
-
34
- const MIN_DATE_1 = '1970-01-29';
35
- const MIN_DATE_2 = '1970-01-30';
36
- const MAX_DATE = '2038-01-17';
37
- const MAX_TIME = 'T23:59+00:00';
38
- const MIN_TIME = 'T00:00+00:00';
39
-
40
- static $use_checkout_url = array('simple' => 1, 'variable' => 1, 'variation' => 1);
41
-
42
- public function __construct(
43
- $wpid, $parent_product = null) {
44
- $this->id = $wpid;
45
- $this->fb_description = '';
46
- $this->fb_visibility = get_post_meta($wpid, self::FB_VISIBILITY, true);
47
- $this->woo_product = wc_get_product($wpid);
48
- $this->gallery_urls = null;
49
- $this->fb_use_parent_image = null;
50
- $this->fb_price = 0;
51
- $this->main_description = '';
52
- $this->sync_short_description = get_option('fb_sync_short_description', false);
53
-
54
- // Variable products should use some data from the parent_product
55
- // For performance reasons, that data shouldn't be regenerated every time.
56
- if ($parent_product) {
57
- $this->gallery_urls = $parent_product->get_gallery_urls();
58
- $this->fb_use_parent_image = $parent_product->get_use_parent_image();
59
- $this->main_description = WC_Facebookcommerce_Utils::clean_string(
60
- $parent_product->get_description());
61
- }
62
- }
63
-
64
- public function exists() {
65
- return ($this->woo_product !== null && $this->woo_product !== false);
66
- }
67
-
68
- // Fall back to calling method on $woo_product
69
- public function __call($function, $args) {
70
- if ($this->woo_product) {
71
- return call_user_func_array(array($this->woo_product, $function), $args);
72
- } else {
73
- $e = new Exception();
74
- $backtrace = var_export($e->getTraceAsString(), true);
75
- WC_Facebookcommerce_Utils::fblog(
76
- "Calling $function on Null Woo Object. Trace:\n".$backtrace,
77
- array(),
78
- true);
79
- return null;
80
- }
81
- }
82
-
83
- public function get_gallery_urls() {
84
- if ($this->gallery_urls === null) {
85
- if (is_callable(array($this, 'get_gallery_image_ids'))) {
86
- $image_ids = $this->get_gallery_image_ids();
87
- } else {
88
- $image_ids = $this->get_gallery_attachment_ids();
89
- }
90
- $gallery_urls = array();
91
- foreach ($image_ids as $image_id) {
92
- $image_url = wp_get_attachment_url($image_id);
93
- if (!empty($image_url)) {
94
- array_push($gallery_urls,
95
- WC_Facebookcommerce_Utils::make_url($image_url));
96
- }
97
- }
98
- $this->gallery_urls = array_filter($gallery_urls);
99
- }
100
-
101
- return $this->gallery_urls;
102
- }
103
-
104
- public function get_post_data() {
105
- if (is_callable('get_post')) {
106
- return get_post($this->id);
107
- } else {
108
- return $this->get_post_data();
109
- }
110
- }
111
-
112
- public function get_fb_price() {
113
- // Cache the price in this object in case of multiple calls.
114
- if ($this->fb_price) {
115
- return $this->fb_price;
116
- }
117
-
118
- $price = get_post_meta(
119
- $this->id,
120
- self::FB_PRODUCT_PRICE,
121
- true);
122
-
123
- if (is_numeric($price)) {
124
- return intval(round($price * 100));
125
- }
126
-
127
- // If product is composite product, we rely on their pricing.
128
- if (class_exists('WC_Product_Composite')
129
- && $this->woo_product->get_type() === 'composite') {
130
- $price = get_option('woocommerce_tax_display_shop') === 'incl'
131
- ? $this->woo_product->get_composite_price_including_tax()
132
- : $this->woo_product->get_composite_price();
133
- $this->fb_price = intval(round($price * 100));
134
- return $this->fb_price;
135
- }
136
-
137
- // Get regular price: regular price doesn't include sales
138
- $regular_price = floatval($this->get_regular_price());
139
-
140
- // If it's a bookable product, the normal price is null/0.
141
- if (!$regular_price &&
142
- class_exists('WC_Product_Booking') &&
143
- is_wc_booking_product($this)) {
144
- $product = new WC_Product_Booking($this->woo_product);
145
- $regular_price = $product->get_display_cost();
146
- }
147
-
148
- // Get regular price plus tax, if it's set to display and taxable
149
- // whether price includes tax is based on 'woocommerce_tax_display_shop'
150
- $price = $this->get_price_plus_tax($regular_price);
151
- $this->fb_price = intval(round($price * 100));
152
- return $this->fb_price;
153
- }
154
-
155
- public function get_all_image_urls() {
156
- $image_urls = array();
157
- $parent_image_id = $this->get_parent_image_id();
158
- $image_url = wp_get_attachment_url(
159
- ($parent_image_id) ?: $this->woo_product->get_image_id());
160
-
161
- if ($image_url) {
162
- $image_url = WC_Facebookcommerce_Utils::make_url($image_url);
163
- array_push($image_urls, $image_url);
164
- }
165
-
166
- // For variable products, add the variation specific image.
167
- if ($parent_image_id) {
168
- $image_url2 = wp_get_attachment_url($this->woo_product->get_image_id());
169
- $image_url2 = WC_Facebookcommerce_Utils::make_url($image_url2);
170
- if ($image_url != $image_url2) {
171
- // A Checkbox toggles which image is primary.
172
- // Default to variant specific image as primary.
173
- if ($this->fb_use_parent_image) {
174
- array_push($image_urls, $image_url2);
175
- } else {
176
- array_unshift($image_urls, $image_url2);
177
- }
178
- }
179
- }
180
-
181
- $gallery_urls = $this->get_gallery_urls();
182
- $image_urls = array_merge($image_urls, $gallery_urls);
183
- $image_urls = array_filter($image_urls);
184
-
185
- // If there are no images, create a placeholder image.
186
- if (empty($image_urls)) {
187
- $name = urlencode(strip_tags($this->woo_product->get_title()));
188
- $image_url = 'https://placeholdit.imgix.net/~text?txtsize=33&txt='
189
- . $name . '&w=530&h=530'; // TODO: BETTER PLACEHOLDER
190
- return array($image_url);
191
- }
192
-
193
- $image_override = get_post_meta($this->id, self::FB_PRODUCT_IMAGE, true);
194
- if ($image_override) {
195
- array_unshift($image_urls, $image_override);
196
- $image_urls = array_unique($image_urls);
197
- }
198
-
199
- return $image_urls;
200
- }
201
-
202
- // Returns the parent image id for variable products only.
203
- public function get_parent_image_id() {
204
- if (WC_Facebookcommerce_Utils::is_variation_type($this->woo_product->get_type())) {
205
- $parent_data = $this->get_parent_data();
206
- return $parent_data['image_id'];
207
- }
208
- return null;
209
- }
210
-
211
- public function set_description($description) {
212
- $description = stripslashes(
213
- WC_Facebookcommerce_Utils::clean_string($description));
214
- $this->fb_description = $description;
215
- update_post_meta(
216
- $this->id,
217
- self::FB_PRODUCT_DESCRIPTION,
218
- $description);
219
- }
220
-
221
- public function set_product_image($image) {
222
- if ($image !== null && strlen($image) !== 0) {
223
- $image = WC_Facebookcommerce_Utils::clean_string($image);
224
- $image = WC_Facebookcommerce_Utils::make_url($image);
225
- update_post_meta(
226
- $this->id,
227
- self::FB_PRODUCT_IMAGE,
228
- $image);
229
- }
230
- }
231
-
232
- public function set_price($price) {
233
- if (is_numeric($price)) {
234
- $this->fb_price = intval(round($price * 100));
235
- update_post_meta(
236
- $this->id,
237
- self::FB_PRODUCT_PRICE,
238
- $price);
239
- } else {
240
- delete_post_meta(
241
- $this->id,
242
- self::FB_PRODUCT_PRICE);
243
- }
244
- }
245
-
246
- public function get_use_parent_image() {
247
- if ($this->fb_use_parent_image === null) {
248
- $variant_image_setting =
249
- get_post_meta($this->id, self::FB_VARIANT_IMAGE, true);
250
- $this->fb_use_parent_image = ($variant_image_setting) ? true : false;
251
- }
252
- return $this->fb_use_parent_image;
253
- }
254
-
255
- public function set_use_parent_image($setting) {
256
- $this->fb_use_parent_image = ($setting == 'yes');
257
- update_post_meta(
258
- $this->id,
259
- self::FB_VARIANT_IMAGE,
260
- $this->fb_use_parent_image);
261
- }
262
-
263
- public function get_fb_description() {
264
- if ($this->fb_description) {
265
- return $this->fb_description;
266
- }
267
-
268
- $description = get_post_meta(
269
- $this->id,
270
- self::FB_PRODUCT_DESCRIPTION,
271
- true);
272
-
273
- if ($description) {
274
- return $description;
275
- }
276
-
277
- if (WC_Facebookcommerce_Utils::is_variation_type($this->woo_product->get_type())) {
278
- $description = WC_Facebookcommerce_Utils::clean_string(
279
- $this->get_description());
280
- if ($description) {
281
- return $description;
282
- }
283
- if ($this->main_description) {
284
- return $this->main_description;
285
- }
286
- }
287
-
288
- $post = $this->get_post_data();
289
-
290
- $post_content = WC_Facebookcommerce_Utils::clean_string(
291
- $post->post_content);
292
- $post_excerpt = WC_Facebookcommerce_Utils::clean_string(
293
- $post->post_excerpt);
294
- $post_title = WC_Facebookcommerce_Utils::clean_string(
295
- $post->post_title);
296
-
297
- // Sanitize description
298
- if ($post_content) {
299
- $description = $post_content;
300
- }
301
- if ($this->sync_short_description || ($description == '' && $post_excerpt)) {
302
- $description = $post_excerpt;
303
- }
304
- if ($description == '') {
305
- $description = $post_title;
306
- }
307
-
308
- return $description;
309
- }
310
-
311
- public function add_sale_price($product_data) {
312
- // initialize sale date and sale_price
313
- $product_data['sale_price_start_date'] = self::MIN_DATE_1 . self::MIN_TIME;
314
- $product_data['sale_price_end_date'] = self::MIN_DATE_2 . self::MAX_TIME;
315
- $product_data['sale_price'] = $product_data['price'];
316
-
317
- $sale_price = $this->woo_product->get_sale_price();
318
- // check if sale exist
319
- if (!is_numeric($sale_price)) {
320
- return $product_data;
321
- }
322
- $sale_price =
323
- intval(round($this->get_price_plus_tax($sale_price) * 100));
324
-
325
- $sale_start =
326
- ($date = get_post_meta($this->id, '_sale_price_dates_from', true))
327
- ? date_i18n('Y-m-d', $date) . self::MIN_TIME
328
- : self::MIN_DATE_1 . self::MIN_TIME;
329
-
330
- $sale_end =
331
- ($date = get_post_meta($this->id, '_sale_price_dates_to', true))
332
- ? date_i18n('Y-m-d', $date) . self::MAX_TIME
333
- : self::MAX_DATE . self::MAX_TIME;
334
-
335
- // check if sale is expired and sale time range is valid
336
- $product_data['sale_price_start_date'] = $sale_start;
337
- $product_data['sale_price_end_date'] = $sale_end;
338
- $product_data['sale_price'] = $sale_price;
339
- return $product_data;
340
- }
341
-
342
- public function is_hidden() {
343
- $wpid = $this->id;
344
- if (WC_Facebookcommerce_Utils::is_variation_type($this->get_type())) {
345
- $wpid = $this->get_parent_id();
346
- }
347
- $hidden_from_catalog = has_term(
348
- 'exclude-from-catalog',
349
- 'product_visibility',
350
- $wpid);
351
- $hidden_from_search = has_term(
352
- 'exclude-from-search',
353
- 'product_visibility',
354
- $wpid);
355
- // fb_visibility === '': after initial sync by feed
356
- // fb_visibility === false: set hidden on FB metadata
357
- // Explicitly check whether flip 'hide' before.
358
- return ($hidden_from_catalog && $hidden_from_search) ||
359
- $this->fb_visibility === false || !$this->get_fb_price();
360
- }
361
-
362
- public function get_price_plus_tax($price) {
363
- $woo_product = $this->woo_product;
364
- // // wc_get_price_including_tax exist for Woo > 2.7
365
- if (function_exists('wc_get_price_including_tax')) {
366
- $args = array( 'qty' => 1, 'price' => $price);
367
- return get_option('woocommerce_tax_display_shop') === 'incl'
368
- ? wc_get_price_including_tax($woo_product, $args)
369
- : wc_get_price_excluding_tax($woo_product, $args);
370
- } else {
371
- return get_option('woocommerce_tax_display_shop') === 'incl'
372
- ? $woo_product->get_price_including_tax(1, $price)
373
- : $woo_product->get_price_excluding_tax(1, $price);
374
- }
375
- }
376
-
377
- public function get_grouped_product_option_names($key, $option_values) {
378
- // Convert all slug_names in $option_values into the visible names that
379
- // advertisers have set to be the display names for a given attribute value
380
- $terms = get_the_terms($this->id, $key);
381
- return array_map(
382
- function ($slug_name) use ($terms) {
383
- foreach ($terms as $term) {
384
- if ($term->slug === $slug_name) {
385
- return $term->name;
386
- }
387
- }
388
- return $slug_name;
389
- },
390
- $option_values);
391
- }
392
-
393
- public function get_variant_option_name($label, $default_value) {
394
- // For the given label, get the Visible name rather than the slug
395
- $meta = get_post_meta($this->id, $label, true);
396
- $attribute_name = str_replace('attribute_', '', $label);
397
- $term = get_term_by('slug', $meta, $attribute_name);
398
- return $term && $term->name ? $term->name : $default_value;
399
- }
400
-
401
- public function update_visibility($is_product_page, $visible_box_checked) {
402
- $visibility = get_post_meta($this->id, self::FB_VISIBILITY, true);
403
- if ($visibility && !$is_product_page) {
404
- // If the product was previously set to visible, keep it as visible
405
- // (unless we're on the product page)
406
- $this->fb_visibility = $visibility;
407
- } else {
408
- // If the product is not visible OR we're on the product page,
409
- // then update the visibility as needed.
410
- $this->fb_visibility = $visible_box_checked ? true : false;
411
- update_post_meta($this->id, self::FB_VISIBILITY, $this->fb_visibility);
412
- }
413
- }
414
-
415
- // wrapper function to find item_id for default variation
416
- function find_matching_product_variation() {
417
- if (is_callable(array($this, 'get_default_attributes'))) {
418
- $default_attributes = $this->get_default_attributes();
419
- } else {
420
- $default_attributes = $this->get_variation_default_attributes();
421
- }
422
-
423
- if (!$default_attributes) {
424
- return;
425
- }
426
- foreach ($default_attributes as $key => $value) {
427
- if (strncmp($key, 'attribute_', strlen('attribute_')) === 0) {
428
- continue;
429
- }
430
- unset($default_attributes[$key]);
431
- $default_attributes[sprintf('attribute_%s', $key)] = $value;
432
- }
433
- if (class_exists('WC_Data_Store')) {
434
- // for >= woo 3.0.0
435
- $data_store = WC_Data_Store::load('product');
436
- return
437
- $data_store->find_matching_product_variation(
438
- $this,
439
- $default_attributes);
440
- } else {
441
- return $this->get_matching_variation($default_attributes);
442
- }
443
- }
444
-
445
- /**
446
- * Assemble product payload for POST
447
- **/
448
- function prepare_product(
449
- $retailer_id = null,
450
- $prepare_for_product_feed = false) {
451
- if (!$retailer_id) {
452
- $retailer_id =
453
- WC_Facebookcommerce_Utils::get_fb_retailer_id($this);
454
- }
455
- $image_urls = $this->get_all_image_urls();
456
-
457
- // Replace Wordpress sanitization's ampersand with a real ampersand.
458
- $product_url = str_replace(
459
- '&amp%3B',
460
- '&',
461
- html_entity_decode($this->get_permalink()));
462
-
463
- // Use product_url for external/bundle product setting.
464
- $product_type = $this->get_type();
465
- if (!$product_type || !isset(self::$use_checkout_url[$product_type])) {
466
- $checkout_url = $product_url;
467
- } else if (wc_get_cart_url()) {
468
- $char = '?';
469
- // Some merchant cart pages are actually a querystring
470
- if (strpos(wc_get_cart_url(), '?') !== false) {
471
- $char = '&';
472
- }
473
-
474
- $checkout_url = WC_Facebookcommerce_Utils::make_url(
475
- wc_get_cart_url() . $char);
476
-
477
- if (WC_Facebookcommerce_Utils::is_variation_type($this->get_type())) {
478
- $query_data = array(
479
- 'add-to-cart' => $this->get_parent_id(),
480
- 'variation_id' => $this->get_id()
481
- );
482
-
483
- $query_data = array_merge(
484
- $query_data,
485
- $this->get_variation_attributes());
486
-
487
- } else {
488
- $query_data = array(
489
- 'add-to-cart' => $this->get_id()
490
- );
491
- }
492
-
493
- $checkout_url = $checkout_url . http_build_query($query_data);
494
-
495
- } else {
496
- $checkout_url = null;
497
- }
498
-
499
- $id = $this->get_id();
500
- if (WC_Facebookcommerce_Utils::is_variation_type($this->get_type())) {
501
- $id = $this->get_parent_id();
502
- }
503
- $categories =
504
- WC_Facebookcommerce_Utils::get_product_categories($id);
505
- $brand = get_the_term_list($id, 'product_brand', '', ', ');
506
- $brand = is_wp_error($brand) || !$brand
507
- ? WC_Facebookcommerce_Utils::get_store_name()
508
- : WC_Facebookcommerce_Utils::clean_string($brand);
509
-
510
- $product_data = array(
511
- 'name' => WC_Facebookcommerce_Utils::clean_string(
512
- $this->get_title()),
513
- 'description' => $this->get_fb_description(),
514
- 'image_url' => $image_urls[0], // The array can't be empty.
515
- 'additional_image_urls' => array_filter($image_urls),
516
- 'url'=> $product_url,
517
- 'category' => $categories['categories'],
518
- 'brand' => $brand,
519
- 'retailer_id' => $retailer_id,
520
- 'price' => $this->get_fb_price(),
521
- 'currency' => get_woocommerce_currency(),
522
- 'availability' => $this->is_in_stock() ? 'in stock' :
523
- 'out of stock',
524
- 'visibility' => !$this->is_hidden()
525
- ? 'published'
526
- : 'staging'
527
- );
528
-
529
- // Only use checkout URLs if they exist.
530
- if ($checkout_url) {
531
- $product_data['checkout_url'] = $checkout_url;
532
- }
533
-
534
- $product_data = $this->add_sale_price($product_data);
535
-
536
- // IF using WPML, set the product to staging unless it is in the
537
- // default language. WPML >= 3.2 Supported.
538
- if (defined('ICL_LANGUAGE_CODE')) {
539
- if (class_exists('WC_Facebook_WPML_Injector') && WC_Facebook_WPML_Injector::should_hide($id)) {
540
- $product_data['visibility'] = 'staging';
541
- }
542
- }
543
-
544
- // Exclude variations that are "virtual" products from export to Facebook &&
545
- // No Visibility Option for Variations
546
- if (true === $this->get_virtual()) {
547
- $product_data['visibility'] = 'staging';
548
- }
549
-
550
- if (!$prepare_for_product_feed) {
551
- $this->prepare_variants_for_item($product_data);
552
- } else if (
553
- WC_Facebookcommerce_Utils::is_all_caps($product_data['description'])
554
- ) {
555
- $product_data['description'] =
556
- mb_strtolower($product_data['description']);
557
- }
558
-
559
- /**
560
- * Filters the generated product data.
561
- *
562
- * @param int $id Woocommerce product id
563
- * @param array $product_data An array of product data
564
- */
565
- return apply_filters(
566
- "facebook_for_woocommerce_integration_prepare_product",
567
- $product_data,
568
- $id);
569
- }
570
-
571
-
572
- /**
573
- * Modify Woo variant/taxonomies to be FB compatible
574
- **/
575
- public function prepare_variants_for_item(&$product_data) {
576
- if (!WC_Facebookcommerce_Utils::is_variation_type(
577
- $this->get_type())) {
578
- return;
579
- }
580
-
581
- $attributes = $this->get_variation_attributes();
582
- if (!$attributes) {
583
- return;
584
- }
585
-
586
- $variant_names = array_keys($attributes);
587
- $variant_array = array();
588
-
589
- // Loop through variants (size, color, etc) if they exist
590
- // For each product field type, pull the single variant
591
- foreach ($variant_names as $orig_name) {
592
- // Retrieve label name for attribute
593
- $label = wc_attribute_label($orig_name, $this);
594
-
595
- // Clean up variant name (e.g. pa_color should be color)
596
- // Replace "custom_data:foo" with just "foo" so we can use the key
597
- // Product item API expects "custom_data" instead of "custom_data:foo"
598
- $new_name = str_replace(
599
- 'custom_data:',
600
- '',
601
- WC_Facebookcommerce_Utils::sanitize_variant_name($orig_name));
602
-
603
- // Sometimes WC returns an array, sometimes it's an assoc array, depending
604
- // on what type of taxonomy it's using. array_values will guarantee we
605
- // only get a flat array of values.
606
- $options = $this->get_variant_option_name(
607
- $label,
608
- $attributes[$orig_name]);
609
- if (isset($options)) {
610
- if (is_array($options)) {
611
- $option_values = array_values($options);
612
- } else {
613
- $option_values = array($options);
614
- // If this attribute has value 'any', options will be empty strings
615
- // Redirect to product page to select variants.
616
- // Reset checkout url since checkout_url (build from query data will
617
- // be invalid in this case.
618
- if (count($option_values) === 1 && empty($option_values[0])) {
619
- $option_values[0] = 'any';
620
- $product_data['checkout_url'] = $product_data['url'];
621
- }
622
- }
623
- if ($new_name === WC_Facebookcommerce_Utils::FB_VARIANT_GENDER) {
624
- // If we can't validate the gender, this will be null.
625
- $product_data[$new_name] =
626
- WC_Facebookcommerce_Utils::validateGender($option_values[0]);
627
- }
628
-
629
- switch ($new_name) {
630
- case WC_Facebookcommerce_Utils::FB_VARIANT_SIZE:
631
- case WC_Facebookcommerce_Utils::FB_VARIANT_COLOR:
632
- case WC_Facebookcommerce_Utils::FB_VARIANT_PATTERN:
633
- array_push($variant_array, array(
634
- 'product_field' => $new_name,
635
- 'label' => $label,
636
- 'options' => $option_values,
637
- ));
638
- $product_data[$new_name] = $option_values[0];
639
- break;
640
- case WC_Facebookcommerce_Utils::FB_VARIANT_GENDER:
641
- // If we can't validate the GENDER field, we'll fall through to the
642
- // default case and set the gender into custom data.
643
- if ($product_data[$new_name]) {
644
- array_push($variant_array, array(
645
- 'product_field' => $new_name,
646
- 'label' => $label,
647
- 'options' => $option_values,
648
- ));
649
- break;
650
- }
651
-
652
- default:
653
- // This is for any custom_data.
654
- if (!isset($product_data['custom_data'])) {
655
- $product_data['custom_data'] = array();
656
- }
657
- $product_data['custom_data'][$new_name]
658
- = urldecode($option_values[0]);
659
- break;
660
- }
661
- } else {
662
- WC_Facebookcommerce_Utils::log(
663
- $this->get_id() . ": No options for " . $orig_name);
664
- continue;
665
- }
666
- }
667
- return $variant_array;
668
- }
669
-
670
- /**
671
- * Modify Woo variant/taxonomies for variable products to be FB compatible
672
- **/
673
- public function prepare_variants_for_group($feed_data = false) {
674
- if (!WC_Facebookcommerce_Utils::is_variable_type(
675
- $this->get_type())) {
676
- WC_Facebookcommerce_Utils::fblog(
677
- "prepare_variants_for_group called on non-variable product");
678
- return;
679
- }
680
-
681
- $variation_attributes = $this->get_variation_attributes();
682
- if (!$variation_attributes) {
683
- return;
684
- }
685
- $final_variants = array();
686
-
687
- $attrs = array_keys($this->get_attributes());
688
- foreach ($attrs as $name) {
689
- $label = wc_attribute_label($name, $this);
690
-
691
- if (taxonomy_is_product_attribute($name)) {
692
- $key = $name;
693
- } else {
694
- // variation_attributes keys are labels for custom attrs for some reason
695
- $key = $label;
696
- }
697
-
698
- if (!$key) {
699
- WC_Facebookcommerce_Utils::fblog(
700
- "Critical error: can't get attribute name or label!");
701
- return;
702
- }
703
-
704
- if (isset($variation_attributes[$key])) {
705
- // Array of the options (e.g. small, medium, large)
706
- $option_values = $variation_attributes[$key];
707
- } else {
708
- WC_Facebookcommerce_Utils::log(
709
- $this->get_id() . ": No options for " . $name);
710
- continue; // Skip variations without valid attribute options
711
- }
712
-
713
- // If this is a wc_product_variable, check default attrib.
714
- // If it's being used, show it as the first option on Facebook.
715
- $first_option = $this->get_variation_default_attribute($key);
716
- if ($first_option) {
717
- $idx = array_search($first_option, $option_values);
718
- unset($option_values[$idx]);
719
- array_unshift($option_values, $first_option);
720
- }
721
-
722
- if (
723
- function_exists('taxonomy_is_product_attribute') &&
724
- taxonomy_is_product_attribute($name)
725
- ) {
726
- $option_values = $this->get_grouped_product_option_names(
727
- $key,
728
- $option_values);
729
- }
730
-
731
- // https://developers.facebook.com/docs/marketing-api/reference/product-variant/
732
- // For API approach, product_field need to start with 'custom_data:'
733
- // Clean up variant name (e.g. pa_color should be color)
734
- $name = WC_Facebookcommerce_Utils::sanitize_variant_name($name);
735
-
736
- // For feed uploading, product field should remove prefix 'custom_data:'
737
- if ($feed_data) {
738
- $name = str_replace('custom_data:', '', $name);
739
- }
740
- array_push($final_variants, array(
741
- 'product_field' => $name,
742
- 'label' => $label,
743
- 'options' => $option_values,
744
- ));
745
- }
746
-
747
- return $final_variants;
748
-
749
- }
750
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
751
 
752
  endif;
8
  * @package FacebookCommerce
9
  */
10
 
11
+ if ( ! defined( 'ABSPATH' ) ) {
12
+ exit;
13
  }
14
 
15
+ if ( ! class_exists( 'WC_Facebookcommerce_Utils' ) ) {
16
+ include_once 'includes/fbutils.php';
17
  }
18
 
19
+ if ( ! class_exists( 'WC_Facebook_Product' ) ) :
20
+
21
+ /**
22
+ * Custom FB Product proxy class
23
+ */
24
+ class WC_Facebook_Product {
25
+
26
+ // Should match facebook-commerce.php while we migrate that code over
27
+ // to this object.
28
+ const FB_PRODUCT_DESCRIPTION = 'fb_product_description';
29
+ const FB_PRODUCT_PRICE = 'fb_product_price';
30
+ const FB_PRODUCT_IMAGE = 'fb_product_image';
31
+ const FB_VARIANT_IMAGE = 'fb_image';
32
+ const FB_VISIBILITY = 'fb_visibility';
33
+
34
+ const MIN_DATE_1 = '1970-01-29';
35
+ const MIN_DATE_2 = '1970-01-30';
36
+ const MAX_DATE = '2038-01-17';
37
+ const MAX_TIME = 'T23:59+00:00';
38
+ const MIN_TIME = 'T00:00+00:00';
39
+
40
+ static $use_checkout_url = array(
41
+ 'simple' => 1,
42
+ 'variable' => 1,
43
+ 'variation' => 1,
44
+ );
45
+
46
+ public function __construct(
47
+ $wpid, $parent_product = null ) {
48
+ $this->id = $wpid;
49
+ $this->fb_description = '';
50
+ $this->fb_visibility = get_post_meta( $wpid, self::FB_VISIBILITY, true );
51
+ $this->woo_product = wc_get_product( $wpid );
52
+ $this->gallery_urls = null;
53
+ $this->fb_use_parent_image = null;
54
+ $this->fb_price = 0;
55
+ $this->main_description = '';
56
+ $this->sync_short_description = get_option( 'fb_sync_short_description', false );
57
+
58
+ // Variable products should use some data from the parent_product
59
+ // For performance reasons, that data shouldn't be regenerated every time.
60
+ if ( $parent_product ) {
61
+ $this->gallery_urls = $parent_product->get_gallery_urls();
62
+ $this->fb_use_parent_image = $parent_product->get_use_parent_image();
63
+ $this->main_description = WC_Facebookcommerce_Utils::clean_string(
64
+ $parent_product->get_description()
65
+ );
66
+ }
67
+ }
68
+
69
+ public function exists() {
70
+ return ( $this->woo_product !== null && $this->woo_product !== false );
71
+ }
72
+
73
+ // Fall back to calling method on $woo_product
74
+ public function __call( $function, $args ) {
75
+ if ( $this->woo_product ) {
76
+ return call_user_func_array( array( $this->woo_product, $function ), $args );
77
+ } else {
78
+ $e = new Exception();
79
+ $backtrace = var_export( $e->getTraceAsString(), true );
80
+ WC_Facebookcommerce_Utils::fblog(
81
+ "Calling $function on Null Woo Object. Trace:\n" . $backtrace,
82
+ array(),
83
+ true
84
+ );
85
+ return null;
86
+ }
87
+ }
88
+
89
+ public function get_gallery_urls() {
90
+ if ( $this->gallery_urls === null ) {
91
+ if ( is_callable( array( $this, 'get_gallery_image_ids' ) ) ) {
92
+ $image_ids = $this->get_gallery_image_ids();
93
+ } else {
94
+ $image_ids = $this->get_gallery_attachment_ids();
95
+ }
96
+ $gallery_urls = array();
97
+ foreach ( $image_ids as $image_id ) {
98
+ $image_url = wp_get_attachment_url( $image_id );
99
+ if ( ! empty( $image_url ) ) {
100
+ array_push(
101
+ $gallery_urls,
102
+ WC_Facebookcommerce_Utils::make_url( $image_url )
103
+ );
104
+ }
105
+ }
106
+ $this->gallery_urls = array_filter( $gallery_urls );
107
+ }
108
+
109
+ return $this->gallery_urls;
110
+ }
111
+
112
+ public function get_post_data() {
113
+ if ( is_callable( 'get_post' ) ) {
114
+ return get_post( $this->id );
115
+ } else {
116
+ return $this->get_post_data();
117
+ }
118
+ }
119
+
120
+ public function get_fb_price() {
121
+ // Cache the price in this object in case of multiple calls.
122
+ if ( $this->fb_price ) {
123
+ return $this->fb_price;
124
+ }
125
+
126
+ $price = get_post_meta(
127
+ $this->id,
128
+ self::FB_PRODUCT_PRICE,
129
+ true
130
+ );
131
+
132
+ if ( is_numeric( $price ) ) {
133
+ return intval( round( $price * 100 ) );
134
+ }
135
+
136
+ // If product is composite product, we rely on their pricing.
137
+ if ( class_exists( 'WC_Product_Composite' )
138
+ && $this->woo_product->get_type() === 'composite' ) {
139
+ $price = get_option( 'woocommerce_tax_display_shop' ) === 'incl'
140
+ ? $this->woo_product->get_composite_price_including_tax()
141
+ : $this->woo_product->get_composite_price();
142
+ $this->fb_price = intval( round( $price * 100 ) );
143
+ return $this->fb_price;
144
+ }
145
+
146
+ // Get regular price: regular price doesn't include sales
147
+ $regular_price = floatval( $this->get_regular_price() );
148
+
149
+ // If it's a bookable product, the normal price is null/0.
150
+ if ( ! $regular_price &&
151
+ class_exists( 'WC_Product_Booking' ) &&
152
+ is_wc_booking_product( $this ) ) {
153
+ $product = new WC_Product_Booking( $this->woo_product );
154
+ $regular_price = $product->get_display_cost();
155
+ }
156
+
157
+ // Get regular price plus tax, if it's set to display and taxable
158
+ // whether price includes tax is based on 'woocommerce_tax_display_shop'
159
+ $price = $this->get_price_plus_tax( $regular_price );
160
+ $this->fb_price = intval( round( $price * 100 ) );
161
+ return $this->fb_price;
162
+ }
163
+
164
+ public function get_all_image_urls() {
165
+ $image_urls = array();
166
+ $parent_image_id = $this->get_parent_image_id();
167
+ $image_url = wp_get_attachment_url(
168
+ ( $parent_image_id ) ?: $this->woo_product->get_image_id()
169
+ );
170
+
171
+ if ( $image_url ) {
172
+ $image_url = WC_Facebookcommerce_Utils::make_url( $image_url );
173
+ array_push( $image_urls, $image_url );
174
+ }
175
+
176
+ // For variable products, add the variation specific image.
177
+ if ( $parent_image_id ) {
178
+ $image_url2 = wp_get_attachment_url( $this->woo_product->get_image_id() );
179
+ $image_url2 = WC_Facebookcommerce_Utils::make_url( $image_url2 );
180
+ if ( $image_url != $image_url2 ) {
181
+ // A Checkbox toggles which image is primary.
182
+ // Default to variant specific image as primary.
183
+ if ( $this->fb_use_parent_image ) {
184
+ array_push( $image_urls, $image_url2 );
185
+ } else {
186
+ array_unshift( $image_urls, $image_url2 );
187
+ }
188
+ }
189
+ }
190
+
191
+ $gallery_urls = $this->get_gallery_urls();
192
+ $image_urls = array_merge( $image_urls, $gallery_urls );
193
+ $image_urls = array_filter( $image_urls );
194
+
195
+ // If there are no images, create a placeholder image.
196
+ if ( empty( $image_urls ) ) {
197
+ $name = urlencode( strip_tags( $this->woo_product->get_title() ) );
198
+ $image_url = 'https://placeholdit.imgix.net/~text?txtsize=33&txt='
199
+ . $name . '&w=530&h=530'; // TODO: BETTER PLACEHOLDER
200
+ return array( $image_url );
201
+ }
202
+
203
+ $image_override = get_post_meta( $this->id, self::FB_PRODUCT_IMAGE, true );
204
+ if ( $image_override ) {
205
+ array_unshift( $image_urls, $image_override );
206
+ $image_urls = array_unique( $image_urls );
207
+ }
208
+
209
+ return $image_urls;
210
+ }
211
+
212
+ // Returns the parent image id for variable products only.
213
+ public function get_parent_image_id() {
214
+ if ( WC_Facebookcommerce_Utils::is_variation_type( $this->woo_product->get_type() ) ) {
215
+ $parent_data = $this->get_parent_data();
216
+ return $parent_data['image_id'];
217
+ }
218
+ return null;
219
+ }
220
+
221
+ public function set_description( $description ) {
222
+ $description = stripslashes(
223
+ WC_Facebookcommerce_Utils::clean_string( $description )
224
+ );
225
+ $this->fb_description = $description;
226
+ update_post_meta(
227
+ $this->id,
228
+ self::FB_PRODUCT_DESCRIPTION,
229
+ $description
230
+ );
231
+ }
232
+
233
+ public function set_product_image( $image ) {
234
+ if ( $image !== null && strlen( $image ) !== 0 ) {
235
+ $image = WC_Facebookcommerce_Utils::clean_string( $image );
236
+ $image = WC_Facebookcommerce_Utils::make_url( $image );
237
+ update_post_meta(
238
+ $this->id,
239
+ self::FB_PRODUCT_IMAGE,
240
+ $image
241
+ );
242
+ }
243
+ }
244
+
245
+ public function set_price( $price ) {
246
+ if ( is_numeric( $price ) ) {
247
+ $this->fb_price = intval( round( $price * 100 ) );
248
+ update_post_meta(
249
+ $this->id,
250
+ self::FB_PRODUCT_PRICE,
251
+ $price
252
+ );
253
+ } else {
254
+ delete_post_meta(
255
+ $this->id,
256
+ self::FB_PRODUCT_PRICE
257
+ );
258
+ }
259
+ }
260
+
261
+ public function get_use_parent_image() {
262
+ if ( $this->fb_use_parent_image === null ) {
263
+ $variant_image_setting =
264
+ get_post_meta( $this->id, self::FB_VARIANT_IMAGE, true );
265
+ $this->fb_use_parent_image = ( $variant_image_setting ) ? true : false;
266
+ }
267
+ return $this->fb_use_parent_image;
268
+ }
269
+
270
+ public function set_use_parent_image( $setting ) {
271
+ $this->fb_use_parent_image = ( $setting == 'yes' );
272
+ update_post_meta(
273
+ $this->id,
274
+ self::FB_VARIANT_IMAGE,
275
+ $this->fb_use_parent_image
276
+ );
277
+ }
278
+
279
+ public function get_fb_description() {
280
+ if ( $this->fb_description ) {
281
+ return $this->fb_description;
282
+ }
283
+
284
+ $description = get_post_meta(
285
+ $this->id,
286
+ self::FB_PRODUCT_DESCRIPTION,
287
+ true
288
+ );
289
+
290
+ if ( $description ) {
291
+ return $description;
292
+ }
293
+
294
+ if ( WC_Facebookcommerce_Utils::is_variation_type( $this->woo_product->get_type() ) ) {
295
+ $description = WC_Facebookcommerce_Utils::clean_string(
296
+ $this->get_description()
297
+ );
298
+ if ( $description ) {
299
+ return $description;
300
+ }
301
+ if ( $this->main_description ) {
302
+ return $this->main_description;
303
+ }
304
+ }
305
+
306
+ $post = $this->get_post_data();
307
+
308
+ $post_content = WC_Facebookcommerce_Utils::clean_string(
309
+ $post->post_content
310
+ );
311
+ $post_excerpt = WC_Facebookcommerce_Utils::clean_string(
312
+ $post->post_excerpt
313
+ );
314
+ $post_title = WC_Facebookcommerce_Utils::clean_string(
315
+ $post->post_title
316
+ );
317
+
318
+ // Sanitize description
319
+ if ( $post_content ) {
320
+ $description = $post_content;
321
+ }
322
+ if ( $this->sync_short_description || ( $description == '' && $post_excerpt ) ) {
323
+ $description = $post_excerpt;
324
+ }
325
+ if ( $description == '' ) {
326
+ $description = $post_title;
327
+ }
328
+
329
+ return $description;
330
+ }
331
+
332
+ public function add_sale_price( $product_data ) {
333
+ // initialize sale date and sale_price
334
+ $product_data['sale_price_start_date'] = self::MIN_DATE_1 . self::MIN_TIME;
335
+ $product_data['sale_price_end_date'] = self::MIN_DATE_2 . self::MAX_TIME;
336
+ $product_data['sale_price'] = $product_data['price'];
337
+
338
+ $sale_price = $this->woo_product->get_sale_price();
339
+ // check if sale exist
340
+ if ( ! is_numeric( $sale_price ) ) {
341
+ return $product_data;
342
+ }
343
+ $sale_price =
344
+ intval( round( $this->get_price_plus_tax( $sale_price ) * 100 ) );
345
+
346
+ $sale_start =
347
+ ( $date = get_post_meta( $this->id, '_sale_price_dates_from', true ) )
348
+ ? date_i18n( 'Y-m-d', $date ) . self::MIN_TIME
349
+ : self::MIN_DATE_1 . self::MIN_TIME;
350
+
351
+ $sale_end =
352
+ ( $date = get_post_meta( $this->id, '_sale_price_dates_to', true ) )
353
+ ? date_i18n( 'Y-m-d', $date ) . self::MAX_TIME
354
+ : self::MAX_DATE . self::MAX_TIME;
355
+
356
+ // check if sale is expired and sale time range is valid
357
+ $product_data['sale_price_start_date'] = $sale_start;
358
+ $product_data['sale_price_end_date'] = $sale_end;
359
+ $product_data['sale_price'] = $sale_price;
360
+ return $product_data;
361
+ }
362
+
363
+ public function is_hidden() {
364
+ $wpid = $this->id;
365
+ if ( WC_Facebookcommerce_Utils::is_variation_type( $this->get_type() ) ) {
366
+ $wpid = $this->get_parent_id();
367
+ }
368
+ $hidden_from_catalog = has_term(
369
+ 'exclude-from-catalog',
370
+ 'product_visibility',
371
+ $wpid
372
+ );
373
+ $hidden_from_search = has_term(
374
+ 'exclude-from-search',
375
+ 'product_visibility',
376
+ $wpid
377
+ );
378
+ // fb_visibility === '': after initial sync by feed
379
+ // fb_visibility === false: set hidden on FB metadata
380
+ // Explicitly check whether flip 'hide' before.
381
+ return ( $hidden_from_catalog && $hidden_from_search ) ||
382
+ $this->fb_visibility === false || ! $this->get_fb_price();
383
+ }
384
+
385
+ public function get_price_plus_tax( $price ) {
386
+ $woo_product = $this->woo_product;
387
+ // // wc_get_price_including_tax exist for Woo > 2.7
388
+ if ( function_exists( 'wc_get_price_including_tax' ) ) {
389
+ $args = array(
390
+ 'qty' => 1,
391
+ 'price' => $price,
392
+ );
393
+ return get_option( 'woocommerce_tax_display_shop' ) === 'incl'
394
+ ? wc_get_price_including_tax( $woo_product, $args )
395
+ : wc_get_price_excluding_tax( $woo_product, $args );
396
+ } else {
397
+ return get_option( 'woocommerce_tax_display_shop' ) === 'incl'
398
+ ? $woo_product->get_price_including_tax( 1, $price )
399
+ : $woo_product->get_price_excluding_tax( 1, $price );
400
+ }
401
+ }
402
+
403
+ public function get_grouped_product_option_names( $key, $option_values ) {
404
+ // Convert all slug_names in $option_values into the visible names that
405
+ // advertisers have set to be the display names for a given attribute value
406
+ $terms = get_the_terms( $this->id, $key );
407
+ return array_map(
408
+ function ( $slug_name ) use ( $terms ) {
409
+ foreach ( $terms as $term ) {
410
+ if ( $term->slug === $slug_name ) {
411
+ return $term->name;
412
+ }
413
+ }
414
+ return $slug_name;
415
+ },
416
+ $option_values
417
+ );
418
+ }
419
+
420
+ public function get_variant_option_name( $label, $default_value ) {
421
+ // For the given label, get the Visible name rather than the slug
422
+ $meta = get_post_meta( $this->id, $label, true );
423
+ $attribute_name = str_replace( 'attribute_', '', $label );
424
+ $term = get_term_by( 'slug', $meta, $attribute_name );
425
+ return $term && $term->name ? $term->name : $default_value;
426
+ }
427
+
428
+ public function update_visibility( $is_product_page, $visible_box_checked ) {
429
+ $visibility = get_post_meta( $this->id, self::FB_VISIBILITY, true );
430
+ if ( $visibility && ! $is_product_page ) {
431
+ // If the product was previously set to visible, keep it as visible
432
+ // (unless we're on the product page)
433
+ $this->fb_visibility = $visibility;
434
+ } else {
435
+ // If the product is not visible OR we're on the product page,
436
+ // then update the visibility as needed.
437
+ $this->fb_visibility = $visible_box_checked ? true : false;
438
+ update_post_meta( $this->id, self::FB_VISIBILITY, $this->fb_visibility );
439
+ }
440
+ }
441
+
442
+ // wrapper function to find item_id for default variation
443
+ function find_matching_product_variation() {
444
+ if ( is_callable( array( $this, 'get_default_attributes' ) ) ) {
445
+ $default_attributes = $this->get_default_attributes();
446
+ } else {
447
+ $default_attributes = $this->get_variation_default_attributes();
448
+ }
449
+
450
+ if ( ! $default_attributes ) {
451
+ return;
452
+ }
453
+ foreach ( $default_attributes as $key => $value ) {
454
+ if ( strncmp( $key, 'attribute_', strlen( 'attribute_' ) ) === 0 ) {
455
+ continue;
456
+ }
457
+ unset( $default_attributes[ $key ] );
458
+ $default_attributes[ sprintf( 'attribute_%s', $key ) ] = $value;
459
+ }
460
+ if ( class_exists( 'WC_Data_Store' ) ) {
461
+ // for >= woo 3.0.0
462
+ $data_store = WC_Data_Store::load( 'product' );
463
+ return $data_store->find_matching_product_variation(
464
+ $this,
465
+ $default_attributes
466
+ );
467
+ } else {
468
+ return $this->get_matching_variation( $default_attributes );
469
+ }
470
+ }
471
+
472
+ /**
473
+ * Assemble product payload for POST
474
+ **/
475
+ function prepare_product(
476
+ $retailer_id = null,
477
+ $prepare_for_product_feed = false ) {
478
+ if ( ! $retailer_id ) {
479
+ $retailer_id =
480
+ WC_Facebookcommerce_Utils::get_fb_retailer_id( $this );
481
+ }
482
+ $image_urls = $this->get_all_image_urls();
483
+
484
+ // Replace WordPress sanitization's ampersand with a real ampersand.
485
+ $product_url = str_replace(
486
+ '&amp%3B',
487
+ '&',
488
+ html_entity_decode( $this->get_permalink() )
489
+ );
490
+
491
+ // Use product_url for external/bundle product setting.
492
+ $product_type = $this->get_type();
493
+ if ( ! $product_type || ! isset( self::$use_checkout_url[ $product_type ] ) ) {
494
+ $checkout_url = $product_url;
495
+ } elseif ( wc_get_cart_url() ) {
496
+ $char = '?';
497
+ // Some merchant cart pages are actually a querystring
498
+ if ( strpos( wc_get_cart_url(), '?' ) !== false ) {
499
+ $char = '&';
500
+ }
501
+
502
+ $checkout_url = WC_Facebookcommerce_Utils::make_url(
503
+ wc_get_cart_url() . $char
504
+ );
505
+
506
+ if ( WC_Facebookcommerce_Utils::is_variation_type( $this->get_type() ) ) {
507
+ $query_data = array(
508
+ 'add-to-cart' => $this->get_parent_id(),
509
+ 'variation_id' => $this->get_id(),
510
+ );
511
+
512
+ $query_data = array_merge(
513
+ $query_data,
514
+ $this->get_variation_attributes()
515
+ );
516
+
517
+ } else {
518
+ $query_data = array(
519
+ 'add-to-cart' => $this->get_id(),
520
+ );
521
+ }
522
+
523
+ $checkout_url = $checkout_url . http_build_query( $query_data );
524
+
525
+ } else {
526
+ $checkout_url = null;
527
+ }
528
+
529
+ $id = $this->get_id();
530
+ if ( WC_Facebookcommerce_Utils::is_variation_type( $this->get_type() ) ) {
531
+ $id = $this->get_parent_id();
532
+ }
533
+ $categories =
534
+ WC_Facebookcommerce_Utils::get_product_categories( $id );
535
+ $brand = get_the_term_list( $id, 'product_brand', '', ', ' );
536
+ $brand = is_wp_error( $brand ) || ! $brand
537
+ ? WC_Facebookcommerce_Utils::get_store_name()
538
+ : WC_Facebookcommerce_Utils::clean_string( $brand );
539
+
540
+ $product_data = array(
541
+ 'name' => WC_Facebookcommerce_Utils::clean_string(
542
+ $this->get_title()
543
+ ),
544
+ 'description' => $this->get_fb_description(),
545
+ 'image_url' => $image_urls[0], // The array can't be empty.
546
+ 'additional_image_urls' => array_filter( $image_urls ),
547
+ 'url' => $product_url,
548
+ 'category' => $categories['categories'],
549
+ 'brand' => $brand,
550
+ 'retailer_id' => $retailer_id,
551
+ 'price' => $this->get_fb_price(),
552
+ 'currency' => get_woocommerce_currency(),
553
+ 'availability' => $this->is_in_stock() ? 'in stock' :
554
+ 'out of stock',
555
+ 'visibility' => ! $this->is_hidden()
556
+ ? 'published'
557
+ : 'staging',
558
+ );
559
+
560
+ // Only use checkout URLs if they exist.
561
+ if ( $checkout_url ) {
562
+ $product_data['checkout_url'] = $checkout_url;
563
+ }
564
+
565
+ $product_data = $this->add_sale_price( $product_data );
566
+
567
+ // IF using WPML, set the product to staging unless it is in the
568
+ // default language. WPML >= 3.2 Supported.
569
+ if ( defined( 'ICL_LANGUAGE_CODE' ) ) {
570
+ if ( class_exists( 'WC_Facebook_WPML_Injector' ) && WC_Facebook_WPML_Injector::should_hide( $id ) ) {
571
+ $product_data['visibility'] = 'staging';
572
+ }
573
+ }
574
+
575
+ // Exclude variations that are "virtual" products from export to Facebook &&
576
+ // No Visibility Option for Variations
577
+ if ( true === $this->get_virtual() ) {
578
+ $product_data['visibility'] = 'staging';
579
+ }
580
+
581
+ if ( ! $prepare_for_product_feed ) {
582
+ $this->prepare_variants_for_item( $product_data );
583
+ } elseif (
584
+ WC_Facebookcommerce_Utils::is_all_caps( $product_data['description'] )
585
+ ) {
586
+ $product_data['description'] =
587
+ mb_strtolower( $product_data['description'] );
588
+ }
589
+
590
+ /**
591
+ * Filters the generated product data.
592
+ *
593
+ * @param int $id Woocommerce product id
594
+ * @param array $product_data An array of product data
595
+ */
596
+ return apply_filters(
597
+ 'facebook_for_woocommerce_integration_prepare_product',
598
+ $product_data,
599
+ $id
600
+ );
601
+ }
602
+
603
+
604
+ /**
605
+ * Modify Woo variant/taxonomies to be FB compatible
606
+ **/
607
+ public function prepare_variants_for_item( &$product_data ) {
608
+ if ( ! WC_Facebookcommerce_Utils::is_variation_type(
609
+ $this->get_type()
610
+ ) ) {
611
+ return;
612
+ }
613
+
614
+ $attributes = $this->get_variation_attributes();
615
+ if ( ! $attributes ) {
616
+ return;
617
+ }
618
+
619
+ $variant_names = array_keys( $attributes );
620
+ $variant_array = array();
621
+
622
+ // Loop through variants (size, color, etc) if they exist
623
+ // For each product field type, pull the single variant
624
+ foreach ( $variant_names as $orig_name ) {
625
+ // Retrieve label name for attribute
626
+ $label = wc_attribute_label( $orig_name, $this );
627
+
628
+ // Clean up variant name (e.g. pa_color should be color)
629
+ // Replace "custom_data:foo" with just "foo" so we can use the key
630
+ // Product item API expects "custom_data" instead of "custom_data:foo"
631
+ $new_name = str_replace(
632
+ 'custom_data:',
633
+ '',
634
+ WC_Facebookcommerce_Utils::sanitize_variant_name( $orig_name )
635
+ );
636
+
637
+ // Sometimes WC returns an array, sometimes it's an assoc array, depending
638
+ // on what type of taxonomy it's using. array_values will guarantee we
639
+ // only get a flat array of values.
640
+ $options = $this->get_variant_option_name(
641
+ $label,
642
+ $attributes[ $orig_name ]
643
+ );
644
+ if ( isset( $options ) ) {
645
+ if ( is_array( $options ) ) {
646
+ $option_values = array_values( $options );
647
+ } else {
648
+ $option_values = array( $options );
649
+ // If this attribute has value 'any', options will be empty strings
650
+ // Redirect to product page to select variants.
651
+ // Reset checkout url since checkout_url (build from query data will
652
+ // be invalid in this case.
653
+ if ( count( $option_values ) === 1 && empty( $option_values[0] ) ) {
654
+ $option_values[0] = 'any';
655
+ $product_data['checkout_url'] = $product_data['url'];
656
+ }
657
+ }
658
+ if ( $new_name === WC_Facebookcommerce_Utils::FB_VARIANT_GENDER ) {
659
+ // If we can't validate the gender, this will be null.
660
+ $product_data[ $new_name ] =
661
+ WC_Facebookcommerce_Utils::validateGender( $option_values[0] );
662
+ }
663
+
664
+ switch ( $new_name ) {
665
+ case WC_Facebookcommerce_Utils::FB_VARIANT_SIZE:
666
+ case WC_Facebookcommerce_Utils::FB_VARIANT_COLOR:
667
+ case WC_Facebookcommerce_Utils::FB_VARIANT_PATTERN:
668
+ array_push(
669
+ $variant_array,
670
+ array(
671
+ 'product_field' => $new_name,
672
+ 'label' => $label,
673
+ 'options' => $option_values,
674
+ )
675
+ );
676
+ $product_data[ $new_name ] = $option_values[0];
677
+ break;
678
+ case WC_Facebookcommerce_Utils::FB_VARIANT_GENDER:
679
+ // If we can't validate the GENDER field, we'll fall through to the
680
+ // default case and set the gender into custom data.
681
+ if ( $product_data[ $new_name ] ) {
682
+ array_push(
683
+ $variant_array,
684
+ array(
685
+ 'product_field' => $new_name,
686
+ 'label' => $label,
687
+ 'options' => $option_values,
688
+ )
689
+ );
690
+ break;
691
+ }
692
+
693
+ default:
694
+ // This is for any custom_data.
695
+ if ( ! isset( $product_data['custom_data'] ) ) {
696
+ $product_data['custom_data'] = array();
697
+ }
698
+ $product_data['custom_data'][ $new_name ]
699
+ = urldecode( $option_values[0] );
700
+ break;
701
+ }
702
+ } else {
703
+ WC_Facebookcommerce_Utils::log(
704
+ $this->get_id() . ': No options for ' . $orig_name
705
+ );
706
+ continue;
707
+ }
708
+ }
709
+ return $variant_array;
710
+ }
711
+
712
+ /**
713
+ * Modify Woo variant/taxonomies for variable products to be FB compatible
714
+ **/
715
+ public function prepare_variants_for_group( $feed_data = false ) {
716
+ if ( ! WC_Facebookcommerce_Utils::is_variable_type(
717
+ $this->get_type()
718
+ ) ) {
719
+ WC_Facebookcommerce_Utils::fblog(
720
+ 'prepare_variants_for_group called on non-variable product'
721
+ );
722
+ return;
723
+ }
724
+
725
+ $variation_attributes = $this->get_variation_attributes();
726
+ if ( ! $variation_attributes ) {
727
+ return;
728
+ }
729
+ $final_variants = array();
730
+
731
+ $attrs = array_keys( $this->get_attributes() );
732
+ foreach ( $attrs as $name ) {
733
+ $label = wc_attribute_label( $name, $this );
734
+
735
+ if ( taxonomy_is_product_attribute( $name ) ) {
736
+ $key = $name;
737
+ } else {
738
+ // variation_attributes keys are labels for custom attrs for some reason
739
+ $key = $label;
740
+ }
741
+
742
+ if ( ! $key ) {
743
+ WC_Facebookcommerce_Utils::fblog(
744
+ "Critical error: can't get attribute name or label!"
745
+ );
746
+ return;
747
+ }
748
+
749
+ if ( isset( $variation_attributes[ $key ] ) ) {
750
+ // Array of the options (e.g. small, medium, large)
751
+ $option_values = $variation_attributes[ $key ];
752
+ } else {
753
+ WC_Facebookcommerce_Utils::log(
754
+ $this->get_id() . ': No options for ' . $name
755
+ );
756
+ continue; // Skip variations without valid attribute options
757
+ }
758
+
759
+ // If this is a wc_product_variable, check default attrib.
760
+ // If it's being used, show it as the first option on Facebook.
761
+ $first_option = $this->get_variation_default_attribute( $key );
762
+ if ( $first_option ) {
763
+ $idx = array_search( $first_option, $option_values );
764
+ unset( $option_values[ $idx ] );
765
+ array_unshift( $option_values, $first_option );
766
+ }
767
+
768
+ if (
769
+ function_exists( 'taxonomy_is_product_attribute' ) &&
770
+ taxonomy_is_product_attribute( $name )
771
+ ) {
772
+ $option_values = $this->get_grouped_product_option_names(
773
+ $key,
774
+ $option_values
775
+ );
776
+ }
777
+
778
+ // https://developers.facebook.com/docs/marketing-api/reference/product-variant/
779
+ // For API approach, product_field need to start with 'custom_data:'
780
+ // Clean up variant name (e.g. pa_color should be color)
781
+ $name = WC_Facebookcommerce_Utils::sanitize_variant_name( $name );
782
+
783
+ // For feed uploading, product field should remove prefix 'custom_data:'
784
+ if ( $feed_data ) {
785
+ $name = str_replace( 'custom_data:', '', $name );
786
+ }
787
+ array_push(
788
+ $final_variants,
789
+ array(
790
+ 'product_field' => $name,
791
+ 'label' => $label,
792
+ 'options' => $option_values,
793
+ )
794
+ );
795
+ }
796
+
797
+ return $final_variants;
798
+
799
+ }
800
+ }
801
 
802
  endif;
includes/fbproductfeed.php CHANGED
@@ -8,360 +8,393 @@
8
  * @package FacebookCommerce
9
  */
10
 
11
- if (! defined('ABSPATH')) {
12
- exit;
13
  }
14
 
15
- if (! class_exists('WC_Facebook_Product_Feed')) :
16
-
17
- /**
18
- * Initial Sync by Facebook feed class
19
- */
20
- class WC_Facebook_Product_Feed {
21
-
22
- const FACEBOOK_CATALOG_FEED_FILENAME = 'fae_product_catalog.csv';
23
- const FB_ADDITIONAL_IMAGES_FOR_FEED = 5;
24
- const FEED_NAME = 'Initial product sync from WooCommerce. DO NOT DELETE.';
25
- const FB_PRODUCT_GROUP_ID = 'fb_product_group_id';
26
- const FB_VISIBILITY = 'fb_visibility';
27
-
28
- private $has_default_product_count = 0;
29
- private $no_default_product_count = 0;
30
-
31
- public function __construct(
32
- $facebook_catalog_id,
33
- $fbgraph,
34
- $feed_id = null) {
35
- $this->facebook_catalog_id = $facebook_catalog_id;
36
- $this->fbgraph = $fbgraph;
37
- $this->feed_id = $feed_id;
38
- }
39
-
40
- public function sync_all_products_using_feed() {
41
- $start_time = microtime(true);
42
- $this->log_feed_progress('Sync all products using feed');
43
-
44
- if (!is_writable(dirname(__FILE__))) {
45
- $this->log_feed_progress(
46
- 'Failure - Sync all products using feed, folder is not writable');
47
- return false;
48
- }
49
-
50
- if (!$this->generate_productfeed_file()) {
51
- $this->log_feed_progress(
52
- 'Failure - Sync all products using feed, feed file not generated');
53
- return false;
54
- }
55
- $this->log_feed_progress('Sync all products using feed, feed file generated');
56
-
57
- if (!$this->feed_id) {
58
- $this->feed_id = $this->create_feed();
59
- if (!$this->feed_id) {
60
- $this->log_feed_progress(
61
- 'Failure - Sync all products using feed, facebook feed not created');
62
- return false;
63
- }
64
- $this->log_feed_progress(
65
- 'Sync all products using feed, facebook feed created');
66
- } else {
67
- $this->log_feed_progress(
68
- 'Sync all products using feed, facebook feed already exists.');
69
- }
70
-
71
-
72
- $this->upload_id = $this->create_upload($this->feed_id);
73
- if (!$this->upload_id) {
74
- $this->log_feed_progress(
75
- 'Failure - Sync all products using feed, facebook upload not created');
76
- return false;
77
- }
78
- $this->log_feed_progress(
79
- 'Sync all products using feed, facebook upload created');
80
-
81
- unlink(dirname(__FILE__) .
82
- DIRECTORY_SEPARATOR . (self::FACEBOOK_CATALOG_FEED_FILENAME));
83
-
84
- $total_product_count =
85
- $this->has_default_product_count + $this->no_default_product_count;
86
- $default_product_percentage =
87
- ($total_product_count == 0 || $this->has_default_product_count == 0)
88
- ? 0
89
- : $this->has_default_product_count / $total_product_count * 100;
90
- $time_spent = microtime(true) - $start_time;
91
- $data = array();
92
- // Only log performance if this store has products in order to get average
93
- // performance.
94
- if ($total_product_count != 0) {
95
- $data = array(
96
- 'sync_time' => $time_spent,
97
- 'total' => $total_product_count,
98
- 'default_product_percentage' => $default_product_percentage,
99
- );
100
- }
101
- $this->log_feed_progress('Complete - Sync all products using feed.', $data);
102
- return true;
103
- }
104
-
105
- public function generate_productfeed_file() {
106
- $this->log_feed_progress('Generating product feed file');
107
- $post_ids = $this->get_product_wpid();
108
- $all_parent_product = array_map(function($post_id) {
109
- if (get_post_type($post_id) == 'product_variation') {
110
- return wp_get_post_parent_id($post_id);
111
- }
112
- }, $post_ids);
113
- $all_parent_product = array_filter(array_unique($all_parent_product));
114
- $product_ids = array_diff($post_ids, $all_parent_product);
115
- return $this->write_product_feed_file($product_ids);
116
- }
117
-
118
- public function write_product_feed_file($wp_ids) {
119
- try {
120
- $feed_file =
121
- fopen(dirname(__FILE__) . DIRECTORY_SEPARATOR .
122
- (self::FACEBOOK_CATALOG_FEED_FILENAME), "w");
123
- fwrite($feed_file, $this->get_product_feed_header_row());
124
-
125
- $product_group_attribute_variants = array();
126
- foreach ($wp_ids as $wp_id) {
127
- $woo_product = new WC_Facebook_Product($wp_id);
128
- if ($woo_product->is_hidden()) {
129
- continue;
130
- }
131
- if (get_option('woocommerce_hide_out_of_stock_items') === 'yes' &&
132
- !$woo_product->is_in_stock()) {
133
- continue;
134
- }
135
- $product_data_as_feed_row = $this->prepare_product_for_feed(
136
- $woo_product, $product_group_attribute_variants);
137
- fwrite($feed_file, $product_data_as_feed_row);
138
- }
139
- fclose($feed_file);
140
- wp_reset_postdata();
141
- return true;
142
- } catch (Exception $e) {
143
- WC_Facebookcommerce_Utils::log(json_encode($e->getMessage()));
144
- return false;
145
- }
146
- }
147
-
148
- public function get_product_feed_header_row() {
149
- return 'id,title,description,image_link,link,product_type,' .
150
- 'brand,price,availability,item_group_id,checkout_url,' .
151
- 'additional_image_link,sale_price_effective_date,sale_price,condition,' .
152
- 'visibility,default_product,variant' . PHP_EOL;
153
- }
154
-
155
- /**
156
- * Assemble product payload in feed upload for initial sync.
157
- **/
158
- private function prepare_product_for_feed(
159
- $woo_product,
160
- &$attribute_variants) {
161
- $product_data = $woo_product->prepare_product(null, true);
162
- $item_group_id = $product_data['retailer_id'];
163
- // prepare variant column for variable products
164
- $product_data['variant'] = '';
165
- if (
166
- WC_Facebookcommerce_Utils::is_variation_type($woo_product->get_type())
167
- ) {
168
- $parent_id = $woo_product->get_parent_id();
169
-
170
- if (!isset($attribute_variants[$parent_id])) {
171
- $parent_product = new WC_Facebook_Product($parent_id);
172
-
173
- $gallery_urls = array_filter($parent_product->get_gallery_urls());
174
- $variation_id = $parent_product->find_matching_product_variation();
175
- $variants_for_group = $parent_product->prepare_variants_for_group(true);
176
- $parent_attribute_values = array();
177
- $parent_attribute_values['gallery_urls'] = $gallery_urls;
178
- $parent_attribute_values['default_variant_id'] = $variation_id;
179
- $parent_attribute_values['item_group_id'] =
180
- WC_Facebookcommerce_Utils::get_fb_retailer_id($parent_product);
181
- foreach ($variants_for_group as $variant) {
182
- $parent_attribute_values[$variant['product_field']] =
183
- $variant['options'];
184
- }
185
- // cache product group variants
186
- $attribute_variants[$parent_id] = $parent_attribute_values;
187
- }
188
- $parent_attribute_values = $attribute_variants[$parent_id];
189
- $variants_for_item =
190
- $woo_product->prepare_variants_for_item($product_data);
191
- $variant_feed_column = array();
192
- foreach ($variants_for_item as $variant_array) {
193
- static::format_variant_for_feed(
194
- $variant_array['product_field'],
195
- $variant_array['options'][0],
196
- $parent_attribute_values,
197
- $variant_feed_column);
198
- }
199
- if (isset($product_data['custom_data'])) {
200
- foreach ($product_data['custom_data'] as $product_field => $value) {
201
- static::format_variant_for_feed(
202
- $product_field,
203
- $value,
204
- $parent_attribute_values,
205
- $variant_feed_column);
206
- }
207
- }
208
- if (!empty($variant_feed_column)) {
209
- $product_data['variant'] =
210
- "\"" . implode(',', $variant_feed_column) . "\"";
211
- }
212
- if (isset($parent_attribute_values['gallery_urls'])) {
213
- $product_data['additional_image_urls'] =
214
- array_merge($product_data['additional_image_urls'],
215
- $parent_attribute_values['gallery_urls']);
216
- }
217
- if (isset($parent_attribute_values['item_group_id'])) {
218
- $item_group_id = $parent_attribute_values['item_group_id'];
219
- }
220
-
221
- $product_data['default_product'] =
222
- $parent_attribute_values['default_variant_id'] == $woo_product->id
223
- ? 'default'
224
- :'';
225
-
226
- // If this group has default variant value, log this product item
227
- if (isset($parent_attribute_values['default_variant_id']) &&
228
- !empty($parent_attribute_values['default_variant_id'])) {
229
- $this->has_default_product_count++;
230
- } else {
231
- $this->no_default_product_count++;
232
- }
233
- }
234
-
235
- // log simple product
236
- if (!isset($product_data['default_product'])) {
237
- $this->no_default_product_count++;
238
- $product_data['default_product'];
239
- }
240
-
241
- return
242
- $product_data['retailer_id'] . ',' .
243
- static::format_string_for_feed($product_data['name']) . ',' .
244
- static::format_string_for_feed($product_data['description']) . ',' .
245
- $product_data['image_url'] . ',' .
246
- $product_data['url'] . ',' .
247
- static::format_string_for_feed($product_data['category']) . ',' .
248
- static::format_string_for_feed($product_data['brand']) . ',' .
249
- static::format_price_for_feed(
250
- $product_data['price'], $product_data['currency']) . ',' .
251
- $product_data['availability'] . ',' .
252
- $item_group_id . ',' .
253
- $product_data['checkout_url'] . ',' .
254
- static::format_additional_image_url(
255
- $product_data['additional_image_urls']) . ',' .
256
- $product_data['sale_price_start_date'] . '/' .
257
- $product_data['sale_price_end_date']. ',' .
258
- static::format_price_for_feed(
259
- $product_data['sale_price'], $product_data['currency']) . ',' .
260
- 'new' . ',' .
261
- $product_data['visibility'] . ',' .
262
- $product_data['default_product'] . ',' .
263
- $product_data['variant'] . PHP_EOL;
264
- }
265
-
266
- private function create_feed() {
267
- $result = $this->fbgraph->create_feed(
268
- $this->facebook_catalog_id, array('name' => self::FEED_NAME));
269
- if (is_wp_error($result) || !isset($result['body'])) {
270
- $this->log_feed_progress(json_encode($result));
271
- return null;
272
- }
273
- $decode_result = WC_Facebookcommerce_Utils::decode_json($result['body']);
274
- $feed_id = $decode_result->id;
275
- if (!$feed_id) {
276
- $this->log_feed_progress(
277
- 'Response from creating feed not return feed id!');
278
- return null;
279
- }
280
- return $feed_id;
281
- }
282
-
283
- private function create_upload($facebook_feed_id) {
284
- $result = $this->fbgraph->create_upload(
285
- $facebook_feed_id, dirname(__FILE__) . DIRECTORY_SEPARATOR .
286
- (self::FACEBOOK_CATALOG_FEED_FILENAME));
287
- if (is_null($result) || !isset($result['id']) || !$result['id']) {
288
- $this->log_feed_progress(json_encode($result));
289
- return null;
290
- }
291
- $upload_id = $result['id'];
292
- return $upload_id;
293
- }
294
-
295
- private static function format_additional_image_url($product_image_urls) {
296
- // returns the top 10 additional image urls separated by a comma
297
- // according to feed api rules
298
- $product_image_urls = array_slice(
299
- $product_image_urls,
300
- 0,
301
- self::FB_ADDITIONAL_IMAGES_FOR_FEED);
302
- if ($product_image_urls) {
303
- return "\"" . implode(',', $product_image_urls) . "\"";
304
- } else {
305
- return '';
306
- }
307
- }
308
-
309
- private static function format_string_for_feed($text) {
310
- if ((bool)$text) {
311
- return "\"" . str_replace('"', "'", $text) . "\"";
312
- } else {
313
- return '';
314
- }
315
- }
316
-
317
- private static function format_price_for_feed($value, $currency) {
318
- return (string)(round($value / (float) 100, 2)) . $currency;
319
- }
320
-
321
- private static function format_variant_for_feed(
322
- $product_field,
323
- $value,
324
- $parent_attribute_values,
325
- &$variant_feed_column) {
326
- if (!array_key_exists($product_field, $parent_attribute_values)) {
327
- return;
328
- }
329
- array_push($variant_feed_column,
330
- $product_field . ':' .
331
- implode('/', $parent_attribute_values[$product_field]) . ':' .
332
- $value);
333
- }
334
-
335
- public function is_upload_complete(&$settings) {
336
- $result = $this->fbgraph->get_upload_status($settings['fb_upload_id']);
337
- if (is_wp_error($result) || !isset($result['body'])) {
338
- $this->log_feed_progress(json_encode($result));
339
- return 'error';
340
- }
341
- $decode_result = WC_Facebookcommerce_Utils::decode_json($result['body'], true);
342
- $end_time = $decode_result['end_time'];
343
- if (isset($end_time)) {
344
- $settings['upload_end_time'] = $end_time;
345
- return 'complete';
346
- } else {
347
- return 'in progress';
348
- }
349
- }
350
-
351
- // Log progress in local log file and FB.
352
- public function log_feed_progress($msg, $object = array()) {
353
- WC_Facebookcommerce_Utils::fblog($msg, $object);
354
- $msg = empty($object) ? $msg : $msg . json_encode($object);
355
- WC_Facebookcommerce_Utils::log($msg);
356
- }
357
-
358
- public function get_product_wpid() {
359
- $post_ids = WC_Facebookcommerce_Utils::get_wp_posts(
360
- null,
361
- null,
362
- array('product', 'product_variation'));
363
- return $post_ids;
364
- }
365
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
366
 
367
  endif;
8
  * @package FacebookCommerce
9
  */
10
 
11
+ if ( ! defined( 'ABSPATH' ) ) {
12
+ exit;
13
  }
14
 
15
+ if ( ! class_exists( 'WC_Facebook_Product_Feed' ) ) :
16
+
17
+ /**
18
+ * Initial Sync by Facebook feed class
19
+ */
20
+ class WC_Facebook_Product_Feed {
21
+
22
+ const FACEBOOK_CATALOG_FEED_FILENAME = 'fae_product_catalog.csv';
23
+ const FB_ADDITIONAL_IMAGES_FOR_FEED = 5;
24
+ const FEED_NAME = 'Initial product sync from WooCommerce. DO NOT DELETE.';
25
+ const FB_PRODUCT_GROUP_ID = 'fb_product_group_id';
26
+ const FB_VISIBILITY = 'fb_visibility';
27
+
28
+ private $has_default_product_count = 0;
29
+ private $no_default_product_count = 0;
30
+
31
+ public function __construct(
32
+ $facebook_catalog_id,
33
+ $fbgraph,
34
+ $feed_id = null ) {
35
+ $this->facebook_catalog_id = $facebook_catalog_id;
36
+ $this->fbgraph = $fbgraph;
37
+ $this->feed_id = $feed_id;
38
+ }
39
+
40
+ public function sync_all_products_using_feed() {
41
+ $start_time = microtime( true );
42
+ $this->log_feed_progress( 'Sync all products using feed' );
43
+
44
+ if ( ! is_writable( dirname( __FILE__ ) ) ) {
45
+ $this->log_feed_progress(
46
+ 'Failure - Sync all products using feed, folder is not writable'
47
+ );
48
+ return false;
49
+ }
50
+
51
+ if ( ! $this->generate_productfeed_file() ) {
52
+ $this->log_feed_progress(
53
+ 'Failure - Sync all products using feed, feed file not generated'
54
+ );
55
+ return false;
56
+ }
57
+ $this->log_feed_progress( 'Sync all products using feed, feed file generated' );
58
+
59
+ if ( ! $this->feed_id ) {
60
+ $this->feed_id = $this->create_feed();
61
+ if ( ! $this->feed_id ) {
62
+ $this->log_feed_progress(
63
+ 'Failure - Sync all products using feed, facebook feed not created'
64
+ );
65
+ return false;
66
+ }
67
+ $this->log_feed_progress(
68
+ 'Sync all products using feed, facebook feed created'
69
+ );
70
+ } else {
71
+ $this->log_feed_progress(
72
+ 'Sync all products using feed, facebook feed already exists.'
73
+ );
74
+ }
75
+
76
+ $this->upload_id = $this->create_upload( $this->feed_id );
77
+ if ( ! $this->upload_id ) {
78
+ $this->log_feed_progress(
79
+ 'Failure - Sync all products using feed, facebook upload not created'
80
+ );
81
+ return false;
82
+ }
83
+ $this->log_feed_progress(
84
+ 'Sync all products using feed, facebook upload created'
85
+ );
86
+
87
+ unlink(
88
+ dirname( __FILE__ ) .
89
+ DIRECTORY_SEPARATOR . ( self::FACEBOOK_CATALOG_FEED_FILENAME )
90
+ );
91
+
92
+ $total_product_count =
93
+ $this->has_default_product_count + $this->no_default_product_count;
94
+ $default_product_percentage =
95
+ ( $total_product_count == 0 || $this->has_default_product_count == 0 )
96
+ ? 0
97
+ : $this->has_default_product_count / $total_product_count * 100;
98
+ $time_spent = microtime( true ) - $start_time;
99
+ $data = array();
100
+ // Only log performance if this store has products in order to get average
101
+ // performance.
102
+ if ( $total_product_count != 0 ) {
103
+ $data = array(
104
+ 'sync_time' => $time_spent,
105
+ 'total' => $total_product_count,
106
+ 'default_product_percentage' => $default_product_percentage,
107
+ );
108
+ }
109
+ $this->log_feed_progress( 'Complete - Sync all products using feed.', $data );
110
+ return true;
111
+ }
112
+
113
+ public function generate_productfeed_file() {
114
+ $this->log_feed_progress( 'Generating product feed file' );
115
+ $post_ids = $this->get_product_wpid();
116
+ $all_parent_product = array_map(
117
+ function( $post_id ) {
118
+ if ( get_post_type( $post_id ) == 'product_variation' ) {
119
+ return wp_get_post_parent_id( $post_id );
120
+ }
121
+ },
122
+ $post_ids
123
+ );
124
+ $all_parent_product = array_filter( array_unique( $all_parent_product ) );
125
+ $product_ids = array_diff( $post_ids, $all_parent_product );
126
+ return $this->write_product_feed_file( $product_ids );
127
+ }
128
+
129
+ public function write_product_feed_file( $wp_ids ) {
130
+ try {
131
+ $feed_file =
132
+ fopen(
133
+ dirname( __FILE__ ) . DIRECTORY_SEPARATOR .
134
+ ( self::FACEBOOK_CATALOG_FEED_FILENAME ),
135
+ 'w'
136
+ );
137
+ fwrite( $feed_file, $this->get_product_feed_header_row() );
138
+
139
+ $product_group_attribute_variants = array();
140
+ foreach ( $wp_ids as $wp_id ) {
141
+ $woo_product = new WC_Facebook_Product( $wp_id );
142
+ if ( $woo_product->is_hidden() ) {
143
+ continue;
144
+ }
145
+ if ( get_option( 'woocommerce_hide_out_of_stock_items' ) === 'yes' &&
146
+ ! $woo_product->is_in_stock() ) {
147
+ continue;
148
+ }
149
+ $product_data_as_feed_row = $this->prepare_product_for_feed(
150
+ $woo_product,
151
+ $product_group_attribute_variants
152
+ );
153
+ fwrite( $feed_file, $product_data_as_feed_row );
154
+ }
155
+ fclose( $feed_file );
156
+ wp_reset_postdata();
157
+ return true;
158
+ } catch ( Exception $e ) {
159
+ WC_Facebookcommerce_Utils::log( json_encode( $e->getMessage() ) );
160
+ return false;
161
+ }
162
+ }
163
+
164
+ public function get_product_feed_header_row() {
165
+ return 'id,title,description,image_link,link,product_type,' .
166
+ 'brand,price,availability,item_group_id,checkout_url,' .
167
+ 'additional_image_link,sale_price_effective_date,sale_price,condition,' .
168
+ 'visibility,default_product,variant' . PHP_EOL;
169
+ }
170
+
171
+ /**
172
+ * Assemble product payload in feed upload for initial sync.
173
+ **/
174
+ private function prepare_product_for_feed(
175
+ $woo_product,
176
+ &$attribute_variants ) {
177
+ $product_data = $woo_product->prepare_product( null, true );
178
+ $item_group_id = $product_data['retailer_id'];
179
+ // prepare variant column for variable products
180
+ $product_data['variant'] = '';
181
+ if (
182
+ WC_Facebookcommerce_Utils::is_variation_type( $woo_product->get_type() )
183
+ ) {
184
+ $parent_id = $woo_product->get_parent_id();
185
+
186
+ if ( ! isset( $attribute_variants[ $parent_id ] ) ) {
187
+ $parent_product = new WC_Facebook_Product( $parent_id );
188
+
189
+ $gallery_urls = array_filter( $parent_product->get_gallery_urls() );
190
+ $variation_id = $parent_product->find_matching_product_variation();
191
+ $variants_for_group = $parent_product->prepare_variants_for_group( true );
192
+ $parent_attribute_values = array();
193
+ $parent_attribute_values['gallery_urls'] = $gallery_urls;
194
+ $parent_attribute_values['default_variant_id'] = $variation_id;
195
+ $parent_attribute_values['item_group_id'] =
196
+ WC_Facebookcommerce_Utils::get_fb_retailer_id( $parent_product );
197
+ foreach ( $variants_for_group as $variant ) {
198
+ $parent_attribute_values[ $variant['product_field'] ] =
199
+ $variant['options'];
200
+ }
201
+ // cache product group variants
202
+ $attribute_variants[ $parent_id ] = $parent_attribute_values;
203
+ }
204
+ $parent_attribute_values = $attribute_variants[ $parent_id ];
205
+ $variants_for_item =
206
+ $woo_product->prepare_variants_for_item( $product_data );
207
+ $variant_feed_column = array();
208
+ foreach ( $variants_for_item as $variant_array ) {
209
+ static::format_variant_for_feed(
210
+ $variant_array['product_field'],
211
+ $variant_array['options'][0],
212
+ $parent_attribute_values,
213
+ $variant_feed_column
214
+ );
215
+ }
216
+ if ( isset( $product_data['custom_data'] ) ) {
217
+ foreach ( $product_data['custom_data'] as $product_field => $value ) {
218
+ static::format_variant_for_feed(
219
+ $product_field,
220
+ $value,
221
+ $parent_attribute_values,
222
+ $variant_feed_column
223
+ );
224
+ }
225
+ }
226
+ if ( ! empty( $variant_feed_column ) ) {
227
+ $product_data['variant'] =
228
+ '"' . implode( ',', $variant_feed_column ) . '"';
229
+ }
230
+ if ( isset( $parent_attribute_values['gallery_urls'] ) ) {
231
+ $product_data['additional_image_urls'] =
232
+ array_merge(
233
+ $product_data['additional_image_urls'],
234
+ $parent_attribute_values['gallery_urls']
235
+ );
236
+ }
237
+ if ( isset( $parent_attribute_values['item_group_id'] ) ) {
238
+ $item_group_id = $parent_attribute_values['item_group_id'];
239
+ }
240
+
241
+ $product_data['default_product'] =
242
+ $parent_attribute_values['default_variant_id'] == $woo_product->id
243
+ ? 'default'
244
+ : '';
245
+
246
+ // If this group has default variant value, log this product item
247
+ if ( isset( $parent_attribute_values['default_variant_id'] ) &&
248
+ ! empty( $parent_attribute_values['default_variant_id'] ) ) {
249
+ $this->has_default_product_count++;
250
+ } else {
251
+ $this->no_default_product_count++;
252
+ }
253
+ }
254
+
255
+ // log simple product
256
+ if ( ! isset( $product_data['default_product'] ) ) {
257
+ $this->no_default_product_count++;
258
+ $product_data['default_product'];
259
+ }
260
+
261
+ return $product_data['retailer_id'] . ',' .
262
+ static::format_string_for_feed( $product_data['name'] ) . ',' .
263
+ static::format_string_for_feed( $product_data['description'] ) . ',' .
264
+ $product_data['image_url'] . ',' .
265
+ $product_data['url'] . ',' .
266
+ static::format_string_for_feed( $product_data['category'] ) . ',' .
267
+ static::format_string_for_feed( $product_data['brand'] ) . ',' .
268
+ static::format_price_for_feed(
269
+ $product_data['price'],
270
+ $product_data['currency']
271
+ ) . ',' .
272
+ $product_data['availability'] . ',' .
273
+ $item_group_id . ',' .
274
+ $product_data['checkout_url'] . ',' .
275
+ static::format_additional_image_url(
276
+ $product_data['additional_image_urls']
277
+ ) . ',' .
278
+ $product_data['sale_price_start_date'] . '/' .
279
+ $product_data['sale_price_end_date'] . ',' .
280
+ static::format_price_for_feed(
281
+ $product_data['sale_price'],
282
+ $product_data['currency']
283
+ ) . ',' .
284
+ 'new' . ',' .
285
+ $product_data['visibility'] . ',' .
286
+ $product_data['default_product'] . ',' .
287
+ $product_data['variant'] . PHP_EOL;
288
+ }
289
+
290
+ private function create_feed() {
291
+ $result = $this->fbgraph->create_feed(
292
+ $this->facebook_catalog_id,
293
+ array( 'name' => self::FEED_NAME )
294
+ );
295
+ if ( is_wp_error( $result ) || ! isset( $result['body'] ) ) {
296
+ $this->log_feed_progress( json_encode( $result ) );
297
+ return null;
298
+ }
299
+ $decode_result = WC_Facebookcommerce_Utils::decode_json( $result['body'] );
300
+ $feed_id = $decode_result->id;
301
+ if ( ! $feed_id ) {
302
+ $this->log_feed_progress(
303
+ 'Response from creating feed not return feed id!'
304
+ );
305
+ return null;
306
+ }
307
+ return $feed_id;
308
+ }
309
+
310
+ private function create_upload( $facebook_feed_id ) {
311
+ $result = $this->fbgraph->create_upload(
312
+ $facebook_feed_id,
313
+ dirname( __FILE__ ) . DIRECTORY_SEPARATOR .
314
+ ( self::FACEBOOK_CATALOG_FEED_FILENAME )
315
+ );
316
+ if ( is_null( $result ) || ! isset( $result['id'] ) || ! $result['id'] ) {
317
+ $this->log_feed_progress( json_encode( $result ) );
318
+ return null;
319
+ }
320
+ $upload_id = $result['id'];
321
+ return $upload_id;
322
+ }
323
+
324
+ private static function format_additional_image_url( $product_image_urls ) {
325
+ // returns the top 10 additional image urls separated by a comma
326
+ // according to feed api rules
327
+ $product_image_urls = array_slice(
328
+ $product_image_urls,
329
+ 0,
330
+ self::FB_ADDITIONAL_IMAGES_FOR_FEED
331
+ );
332
+ if ( $product_image_urls ) {
333
+ return '"' . implode( ',', $product_image_urls ) . '"';
334
+ } else {
335
+ return '';
336
+ }
337
+ }
338
+
339
+ private static function format_string_for_feed( $text ) {
340
+ if ( (bool) $text ) {
341
+ return '"' . str_replace( '"', "'", $text ) . '"';
342
+ } else {
343
+ return '';
344
+ }
345
+ }
346
+
347
+ private static function format_price_for_feed( $value, $currency ) {
348
+ return (string) ( round( $value / (float) 100, 2 ) ) . $currency;
349
+ }
350
+
351
+ private static function format_variant_for_feed(
352
+ $product_field,
353
+ $value,
354
+ $parent_attribute_values,
355
+ &$variant_feed_column ) {
356
+ if ( ! array_key_exists( $product_field, $parent_attribute_values ) ) {
357
+ return;
358
+ }
359
+ array_push(
360
+ $variant_feed_column,
361
+ $product_field . ':' .
362
+ implode( '/', $parent_attribute_values[ $product_field ] ) . ':' .
363
+ $value
364
+ );
365
+ }
366
+
367
+ public function is_upload_complete( &$settings ) {
368
+ $result = $this->fbgraph->get_upload_status( $settings['fb_upload_id'] );
369
+ if ( is_wp_error( $result ) || ! isset( $result['body'] ) ) {
370
+ $this->log_feed_progress( json_encode( $result ) );
371
+ return 'error';
372
+ }
373
+ $decode_result = WC_Facebookcommerce_Utils::decode_json( $result['body'], true );
374
+ $end_time = $decode_result['end_time'];
375
+ if ( isset( $end_time ) ) {
376
+ $settings['upload_end_time'] = $end_time;
377
+ return 'complete';
378
+ } else {
379
+ return 'in progress';
380
+ }
381
+ }
382
+
383
+ // Log progress in local log file and FB.
384
+ public function log_feed_progress( $msg, $object = array() ) {
385
+ WC_Facebookcommerce_Utils::fblog( $msg, $object );
386
+ $msg = empty( $object ) ? $msg : $msg . json_encode( $object );
387
+ WC_Facebookcommerce_Utils::log( $msg );
388
+ }
389
+
390
+ public function get_product_wpid() {
391
+ $post_ids = WC_Facebookcommerce_Utils::get_wp_posts(
392
+ null,
393
+ null,
394
+ array( 'product', 'product_variation' )
395
+ );
396
+ return $post_ids;
397
+ }
398
+ }
399
 
400
  endif;
includes/fbutils.php CHANGED
@@ -8,478 +8,498 @@
8
  * @package FacebookCommerce
9
  */
10
 
11
- if (!defined('ABSPATH')) {
12
- exit;
13
  }
14
 
15
- if (!class_exists('WC_Facebookcommerce_Utils')) :
16
-
17
- /**
18
- * FB Graph API helper functions
19
- *
20
- */
21
- class WC_Facebookcommerce_Utils {
22
-
23
- const FB_RETAILER_ID_PREFIX = 'wc_post_id_';
24
- const PLUGIN_VERSION = '1.9.14'; // Change it in `facebook-for-*.php` also
25
-
26
- const FB_VARIANT_IMAGE = 'fb_image';
27
- const FB_VARIANT_SIZE = 'size';
28
- const FB_VARIANT_COLOR = 'color';
29
- const FB_VARIANT_COLOUR = 'colour';
30
- const FB_VARIANT_PATTERN = 'pattern';
31
- const FB_VARIANT_GENDER = 'gender';
32
-
33
- public static $ems = null;
34
- public static $fbgraph = null;
35
- public static $store_name = null;
36
-
37
- public static $validGenderArray =
38
- array("male" => 1, "female" => 1, "unisex" => 1);
39
- /**
40
- * WooCommerce 2.1 support for wc_enqueue_js
41
- *
42
- * @since 1.2.1
43
- *
44
- * @access public
45
- * @param string $code
46
- * @return void
47
- */
48
- public static function wc_enqueue_js($code) {
49
- global $wc_queued_js;
50
-
51
- if (function_exists('wc_enqueue_js') && empty($wc_queued_js)) {
52
- wc_enqueue_js($code);
53
- } else {
54
- $wc_queued_js = $code."\n".$wc_queued_js;
55
- }
56
- }
57
-
58
- /**
59
- * Validate URLs, make relative URLs absolute
60
- *
61
- * @access public
62
- * @param string $url
63
- * @return string
64
- */
65
- public static function make_url($url) {
66
- if (
67
- // The first check incorrectly fails for URLs with special chars.
68
- !filter_var($url , FILTER_VALIDATE_URL) &&
69
- substr($url, 0, 4) !== 'http'
70
- ) {
71
- return get_site_url() . $url ;
72
- } else {
73
- return $url;
74
- }
75
- }
76
-
77
- /**
78
- * Product ID for Dynamic Ads on Facebook can be SKU or wc_post_id_123
79
- * This function should be used to get retailer_id based on a WC_Product
80
- * from WooCommerce
81
- *
82
- * @access public
83
- * @param WC_Product $woo_product
84
- * @return string
85
- */
86
- public static function get_fb_retailer_id($woo_product) {
87
- $woo_id = $woo_product->get_id();
88
-
89
- // Call $woo_product->get_id() instead of ->id to account for Variable
90
- // products, which have their own variant_ids.
91
- return $woo_product->get_sku() ? $woo_product->get_sku() . '_' .
92
- $woo_id : self::FB_RETAILER_ID_PREFIX . $woo_id;
93
- }
94
-
95
- /**
96
- * Return categories for products/pixel
97
- *
98
- * @access public
99
- * @param String $id
100
- * @return Array
101
- */
102
- public static function get_product_categories($wpid) {
103
- $category_path = wp_get_post_terms(
104
- $wpid,
105
- 'product_cat',
106
- array('fields' => 'all'));
107
- $content_category = array_values(
108
- array_map(
109
- function($item) {
110
- return $item->name;
111
- },
112
- $category_path));
113
- $content_category_slice = array_slice($content_category, -1);
114
- $categories =
115
- empty($content_category) ? '""' : implode(', ', $content_category);
116
- return array(
117
- 'name' => array_pop($content_category_slice),
118
- 'categories' => $categories
119
- );
120
- }
121
-
122
- /**
123
- * Returns content id to match on for Pixel fires.
124
- *
125
- * @access public
126
- * @param WC_Product $woo_product
127
- * @return array
128
- */
129
- public static function get_fb_content_ids($woo_product) {
130
- return array(self::get_fb_retailer_id($woo_product));
131
- }
132
-
133
- /**
134
- * Clean up strings for FB Graph POSTing.
135
- * This function should will:
136
- * 1. Replace newlines chars/nbsp with a real space
137
- * 2. strip_tags()
138
- * 3. trim()
139
- *
140
- * @access public
141
- * @param String string
142
- * @return string
143
- */
144
- public static function clean_string($string) {
145
- $string = do_shortcode($string);
146
- $string = str_replace(array('&amp%3B', '&amp;'), '&', $string);
147
- $string = str_replace(array("\r", "&nbsp;", "\t"), ' ', $string);
148
- $string = wp_strip_all_tags($string, false); // true == remove line breaks
149
- return $string;
150
- }
151
-
152
- /**
153
- * Returns flat array of woo IDs for variable products, or
154
- * an array with a single woo ID for simple products.
155
- *
156
- * @access public
157
- * @param WC_Product $woo_product
158
- * @return array
159
- */
160
- public static function get_product_array($woo_product) {
161
- $result = array();
162
- if (WC_Facebookcommerce_Utils::is_variable_type($woo_product->get_type())) {
163
- foreach ($woo_product->get_children() as $item_id) {
164
- array_push($result, $item_id);
165
- }
166
- return $result;
167
- } else {
168
- return array($woo_product->get_id());
169
- }
170
- }
171
-
172
- /**
173
- * Returns true if WooCommerce plugin found.
174
- *
175
- * @access public
176
- * @return bool
177
- */
178
- public static function isWoocommerceIntegration() {
179
- return class_exists('WooCommerce');
180
- }
181
-
182
- /**
183
- * Returns integration dependent name.
184
- *
185
- * @access public
186
- * @return string
187
- */
188
- public static function getIntegrationName() {
189
- if (WC_Facebookcommerce_Utils::isWoocommerceIntegration()) {
190
- return 'WooCommerce';
191
- } else {
192
- return 'WordPress';
193
- }
194
- }
195
-
196
- /**
197
- * Returns user info for the current WP user.
198
- *
199
- * @access public
200
- * @param boolean $use_pii
201
- * @return array
202
- */
203
- public static function get_user_info($use_pii) {
204
- $current_user = wp_get_current_user();
205
- if (0 === $current_user->ID || $use_pii === false) {
206
- // User not logged in or admin chose not to send PII.
207
- return array();
208
- } else {
209
- return array_filter(
210
- array(
211
- // Keys documented in
212
- // https://developers.facebook.com/docs/facebook-pixel/pixel-with-ads/
213
- // /conversion-tracking#advanced_match
214
- 'em' => $current_user->user_email,
215
- 'fn' => $current_user->user_firstname,
216
- 'ln' => $current_user->user_lastname
217
- ),
218
- function ($value) { return $value !== null && $value !== ''; });
219
- }
220
- }
221
-
222
- /**
223
- * Utility function for development logging.
224
- */
225
- public static function fblog(
226
- $message,
227
- $object = array(),
228
- $error = false,
229
- $ems = '') {
230
- if ($error) {
231
- $object['plugin_version'] = self::PLUGIN_VERSION;
232
- $object['php_version'] = phpversion();
233
- }
234
- $message = json_encode(array(
235
- 'message' => $message,
236
- 'object' => $object
237
- ));
238
- $ems = $ems ?: self::$ems;
239
- if ($ems) {
240
- self::$fbgraph->log(
241
- $ems,
242
- $message,
243
- $error);
244
- } else {
245
- error_log('external merchant setting is null, something wrong here: ' .
246
- $message);
247
- }
248
- }
249
-
250
- /**
251
- * Utility function for development Tip Events logging.
252
- */
253
- public static function tip_events_log(
254
- $tip_id,
255
- $channel_id,
256
- $event,
257
- $ems = '') {
258
-
259
- $ems = $ems ?: self::$ems;
260
- if ($ems) {
261
- self::$fbgraph->log_tip_event(
262
- $tip_id,
263
- $channel_id,
264
- $event);
265
- } else {
266
- error_log('external merchant setting is null');
267
- }
268
- }
269
-
270
- public static function is_variation_type($type) {
271
- return $type == 'variation' || $type == 'subscription_variation';
272
- }
273
-
274
- public static function is_variable_type($type) {
275
- return $type == 'variable' || $type == 'variable-subscription';
276
- }
277
-
278
- public static function check_woo_ajax_permissions($action_text, $die) {
279
- if (!current_user_can('manage_woocommerce')) {
280
- self::log(
281
- 'Non manage_woocommerce user attempting to'.$action_text.'!',
282
- array(),
283
- true);
284
- if ($die) {
285
- wp_die();
286
- }
287
- return false;
288
- }
289
- return true;
290
- }
291
-
292
- /**
293
- * Returns true if id is a positive non-zero integer
294
- *
295
- * @access public
296
- * @param string $pixel_id
297
- * @return bool
298
- */
299
- public static function is_valid_id($pixel_id) {
300
- return isset($pixel_id) && is_numeric($pixel_id) && (int)$pixel_id > 0;
301
- }
302
-
303
- /**
304
- * Helper function to query posts.
305
- */
306
- public static function get_wp_posts(
307
- $product_group_id = null,
308
- $compare_condition = null,
309
- $post_type = 'product') {
310
- $args = array(
311
- 'fields' => 'ids',
312
- 'meta_query' => array(
313
- (($product_group_id) ?
314
- array(
315
- 'key' => $product_group_id,
316
- 'compare' => $compare_condition,
317
- ) : array()
318
- ),
319
- ),
320
- 'post_status' => 'publish',
321
- 'post_type' => $post_type,
322
- 'posts_per_page' => -1,
323
- );
324
- return get_posts($args);
325
- }
326
-
327
- /**
328
- * Helper log function for debugging
329
- */
330
- public static function log($message) {
331
- if (WP_DEBUG === true) {
332
- if (is_array($message) || is_object($message)) {
333
- error_log(json_encode($message));
334
- }
335
- else {
336
- error_log(sanitize_textarea_field($message));
337
- }
338
- }
339
- }
340
-
341
- // Return store name with sanitized apostrophe
342
- public static function get_store_name() {
343
- if (self::$store_name) {
344
- return self::$store_name;
345
- }
346
- $name = trim(str_replace(
347
- "'",
348
- "\u{2019}",
349
- html_entity_decode(
350
- get_bloginfo('name'),
351
- ENT_QUOTES,
352
- 'UTF-8')));
353
- if ($name) {
354
- self::$store_name = $name;
355
- return $name;
356
- }
357
- // Fallback to site url
358
- $url = get_site_url();
359
- if ($url) {
360
- self::$store_name = parse_url($url, PHP_URL_HOST);
361
- return self::$store_name;
362
- }
363
- // If site url doesn't exist, fall back to http host.
364
- if ($_SERVER['HTTP_HOST']) {
365
- self::$store_name = $_SERVER['HTTP_HOST'];
366
- return self::$store_name;
367
- }
368
-
369
- // If http host doesn't exist, fall back to local host name.
370
- $url = gethostname();
371
- self::$store_name = $url;
372
- return (self::$store_name) ? (self::$store_name) : 'A Store Has No Name';
373
- }
374
-
375
- /*
376
- * Change variant product field name from Woo taxonomy to FB name
377
- */
378
- public static function sanitize_variant_name($name) {
379
- $name = str_replace(array('attribute_', 'pa_'), '', strtolower($name));
380
-
381
- // British spelling
382
- if ($name === self::FB_VARIANT_COLOUR) {
383
- $name = self::FB_VARIANT_COLOR;
384
- }
385
-
386
- switch ($name) {
387
- case self::FB_VARIANT_SIZE:
388
- case self::FB_VARIANT_COLOR:
389
- case self::FB_VARIANT_GENDER:
390
- case self::FB_VARIANT_PATTERN:
391
- break;
392
- default:
393
- $name = 'custom_data:' . strtolower($name);
394
- break;
395
- }
396
-
397
- return $name;
398
- }
399
-
400
- public static function validateGender($gender) {
401
- if ($gender && !isset(self::$validGenderArray[$gender])) {
402
- $first_char = strtolower(substr($gender, 0, 1));
403
- // Men, Man, Boys
404
- if ($first_char === 'm' || $first_char === 'b') {
405
- return "male";
406
- }
407
- // Women, Woman, Female, Ladies
408
- if ($first_char === 'w' || $first_char === 'f' || $first_char === 'l') {
409
- return "female";
410
- }
411
- if ($first_char === 'u') {
412
- return "unisex";
413
- }
414
- if (strlen($gender) >= 3) {
415
- $gender = strtolower(substr($gender, 0, 3));
416
- if ($gender === 'gir' || $gender === 'her') {
417
- return "female";
418
- }
419
- if ($gender === 'him' || $gender === 'his' || $gender == 'guy') {
420
- return "male";
421
- }
422
- }
423
- return null;
424
- }
425
- return $gender;
426
- }
427
-
428
- public static function get_fbid_post_meta($wp_id, $fbid_type) {
429
- return get_post_meta($wp_id, $fbid_type, true);
430
- }
431
-
432
- public static function is_all_caps($value) {
433
- if ($value === null || $value === '') {
434
- return true;
435
- }
436
- if (preg_match('/[^\\p{Common}\\p{Latin}]/u', $value)) {
437
- // Contains non-western characters
438
- // So, it can't be all uppercase
439
- return false;
440
- }
441
- $latin_string = preg_replace('/[^\\p{Latin}]/u', '', $value);
442
- if ($latin_string === '') {
443
- // Symbols only
444
- return true;
445
- }
446
- return strtoupper($latin_string) === $latin_string;
447
- }
448
-
449
- public static function decode_json($json_string, $assoc = false) {
450
- // Plugin requires 5.6.0 but for some user use 5.5.9 JSON_BIGINT_AS_STRING
451
- // will cause 502 issue when redirect.
452
- return version_compare(phpversion(), '5.6.0') >= 0
453
- ? json_decode($json_string, $assoc, 512, JSON_BIGINT_AS_STRING)
454
- : json_decode($json_string, $assoc, 512);
455
- }
456
-
457
- public static function set_test_fail_reason($msg, $trace) {
458
- $reason_msg = get_transient('facebook_plugin_test_fail');
459
- if ($reason_msg) {
460
- $msg = $reason_msg . PHP_EOL . $msg;
461
- }
462
- set_transient('facebook_plugin_test_fail', $msg);
463
- set_transient('facebook_plugin_test_stack_trace', $trace);
464
- }
465
-
466
- /**
467
- * Helper function to check time cap.
468
- */
469
- public static function check_time_cap($from, $date_cap) {
470
- if ($from == null) {
471
- return true;
472
- }
473
- $now = new DateTime(current_time('mysql'));
474
- $diff_in_day = $now->diff(new DateTime($from))->format('%a');
475
- return is_numeric($diff_in_day) && (int)$diff_in_day > $date_cap;
476
- }
477
-
478
- public static function get_cached_best_tip() {
479
- $cached_best_tip = WC_Facebookcommerce_Utils::decode_json(
480
- get_option('fb_info_banner_last_best_tip', ''));
481
- return $cached_best_tip;
482
- }
483
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
484
 
485
  endif;
8
  * @package FacebookCommerce
9
  */
10
 
11
+ if ( ! defined( 'ABSPATH' ) ) {
12
+ exit;
13
  }
14
 
15
+ if ( ! class_exists( 'WC_Facebookcommerce_Utils' ) ) :
16
+
17
+ /**
18
+ * FB Graph API helper functions
19
+ */
20
+ class WC_Facebookcommerce_Utils {
21
+
22
+ const FB_RETAILER_ID_PREFIX = 'wc_post_id_';
23
+ const PLUGIN_VERSION = '1.9.15'; // Change it in `facebook-for-*.php` also
24
+
25
+ const FB_VARIANT_IMAGE = 'fb_image';
26
+ const FB_VARIANT_SIZE = 'size';
27
+ const FB_VARIANT_COLOR = 'color';
28
+ const FB_VARIANT_COLOUR = 'colour';
29
+ const FB_VARIANT_PATTERN = 'pattern';
30
+ const FB_VARIANT_GENDER = 'gender';
31
+
32
+ public static $ems = null;
33
+ public static $fbgraph = null;
34
+ public static $store_name = null;
35
+
36
+ public static $validGenderArray =
37
+ array(
38
+ 'male' => 1,
39
+ 'female' => 1,
40
+ 'unisex' => 1,
41
+ );
42
+ /**
43
+ * WooCommerce 2.1 support for wc_enqueue_js
44
+ *
45
+ * @since 1.2.1
46
+ *
47
+ * @access public
48
+ * @param string $code
49
+ * @return void
50
+ */
51
+ public static function wc_enqueue_js( $code ) {
52
+ global $wc_queued_js;
53
+
54
+ if ( function_exists( 'wc_enqueue_js' ) && empty( $wc_queued_js ) ) {
55
+ wc_enqueue_js( $code );
56
+ } else {
57
+ $wc_queued_js = $code . "\n" . $wc_queued_js;
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Validate URLs, make relative URLs absolute
63
+ *
64
+ * @access public
65
+ * @param string $url
66
+ * @return string
67
+ */
68
+ public static function make_url( $url ) {
69
+ if (
70
+ // The first check incorrectly fails for URLs with special chars.
71
+ ! filter_var( $url, FILTER_VALIDATE_URL ) &&
72
+ substr( $url, 0, 4 ) !== 'http'
73
+ ) {
74
+ return get_site_url() . $url;
75
+ } else {
76
+ return $url;
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Product ID for Dynamic Ads on Facebook can be SKU or wc_post_id_123
82
+ * This function should be used to get retailer_id based on a WC_Product
83
+ * from WooCommerce
84
+ *
85
+ * @access public
86
+ * @param WC_Product $woo_product
87
+ * @return string
88
+ */
89
+ public static function get_fb_retailer_id( $woo_product ) {
90
+ $woo_id = $woo_product->get_id();
91
+
92
+ // Call $woo_product->get_id() instead of ->id to account for Variable
93
+ // products, which have their own variant_ids.
94
+ return $woo_product->get_sku() ? $woo_product->get_sku() . '_' .
95
+ $woo_id : self::FB_RETAILER_ID_PREFIX . $woo_id;
96
+ }
97
+
98
+ /**
99
+ * Return categories for products/pixel
100
+ *
101
+ * @access public
102
+ * @param String $id
103
+ * @return Array
104
+ */
105
+ public static function get_product_categories( $wpid ) {
106
+ $category_path = wp_get_post_terms(
107
+ $wpid,
108
+ 'product_cat',
109
+ array( 'fields' => 'all' )
110
+ );
111
+ $content_category = array_values(
112
+ array_map(
113
+ function( $item ) {
114
+ return $item->name;
115
+ },
116
+ $category_path
117
+ )
118
+ );
119
+ $content_category_slice = array_slice( $content_category, -1 );
120
+ $categories =
121
+ empty( $content_category ) ? '""' : implode( ', ', $content_category );
122
+ return array(
123
+ 'name' => array_pop( $content_category_slice ),
124
+ 'categories' => $categories,
125
+ );
126
+ }
127
+
128
+ /**
129
+ * Returns content id to match on for Pixel fires.
130
+ *
131
+ * @access public
132
+ * @param WC_Product $woo_product
133
+ * @return array
134
+ */
135
+ public static function get_fb_content_ids( $woo_product ) {
136
+ return array( self::get_fb_retailer_id( $woo_product ) );
137
+ }
138
+
139
+ /**
140
+ * Clean up strings for FB Graph POSTing.
141
+ * This function should will:
142
+ * 1. Replace newlines chars/nbsp with a real space
143
+ * 2. strip_tags()
144
+ * 3. trim()
145
+ *
146
+ * @access public
147
+ * @param String string
148
+ * @return string
149
+ */
150
+ public static function clean_string( $string ) {
151
+ $string = do_shortcode( $string );
152
+ $string = str_replace( array( '&amp%3B', '&amp;' ), '&', $string );
153
+ $string = str_replace( array( "\r", '&nbsp;', "\t" ), ' ', $string );
154
+ $string = wp_strip_all_tags( $string, false ); // true == remove line breaks
155
+ return $string;
156
+ }
157
+
158
+ /**
159
+ * Returns flat array of woo IDs for variable products, or
160
+ * an array with a single woo ID for simple products.
161
+ *
162
+ * @access public
163
+ * @param WC_Product $woo_product
164
+ * @return array
165
+ */
166
+ public static function get_product_array( $woo_product ) {
167
+ $result = array();
168
+ if ( self::is_variable_type( $woo_product->get_type() ) ) {
169
+ foreach ( $woo_product->get_children() as $item_id ) {
170
+ array_push( $result, $item_id );
171
+ }
172
+ return $result;
173
+ } else {
174
+ return array( $woo_product->get_id() );
175
+ }
176
+ }
177
+
178
+ /**
179
+ * Returns true if WooCommerce plugin found.
180
+ *
181
+ * @access public
182
+ * @return bool
183
+ */
184
+ public static function isWoocommerceIntegration() {
185
+ return class_exists( 'WooCommerce' );
186
+ }
187
+
188
+ /**
189
+ * Returns integration dependent name.
190
+ *
191
+ * @access public
192
+ * @return string
193
+ */
194
+ public static function getIntegrationName() {
195
+ if ( self::isWoocommerceIntegration() ) {
196
+ return 'WooCommerce';
197
+ } else {
198
+ return 'WordPress';
199
+ }
200
+ }
201
+
202
+ /**
203
+ * Returns user info for the current WP user.
204
+ *
205
+ * @access public
206
+ * @param boolean $use_pii
207
+ * @return array
208
+ */
209
+ public static function get_user_info( $use_pii ) {
210
+ $current_user = wp_get_current_user();
211
+ if ( 0 === $current_user->ID || $use_pii === false ) {
212
+ // User not logged in or admin chose not to send PII.
213
+ return array();
214
+ } else {
215
+ return array_filter(
216
+ array(
217
+ // Keys documented in
218
+ // https://developers.facebook.com/docs/facebook-pixel/pixel-with-ads/
219
+ // /conversion-tracking#advanced_match
220
+ 'em' => $current_user->user_email,
221
+ 'fn' => $current_user->user_firstname,
222
+ 'ln' => $current_user->user_lastname,
223
+ ),
224
+ function ( $value ) {
225
+ return $value !== null && $value !== '';
226
+ }
227
+ );
228
+ }
229
+ }
230
+
231
+ /**
232
+ * Utility function for development logging.
233
+ */
234
+ public static function fblog(
235
+ $message,
236
+ $object = array(),
237
+ $error = false,
238
+ $ems = '' ) {
239
+ if ( $error ) {
240
+ $object['plugin_version'] = self::PLUGIN_VERSION;
241
+ $object['php_version'] = phpversion();
242
+ }
243
+ $message = json_encode(
244
+ array(
245
+ 'message' => $message,
246
+ 'object' => $object,
247
+ )
248
+ );
249
+ $ems = $ems ?: self::$ems;
250
+ if ( $ems ) {
251
+ self::$fbgraph->log(
252
+ $ems,
253
+ $message,
254
+ $error
255
+ );
256
+ } else {
257
+ error_log(
258
+ 'external merchant setting is null, something wrong here: ' .
259
+ $message
260
+ );
261
+ }
262
+ }
263
+
264
+ /**
265
+ * Utility function for development Tip Events logging.
266
+ */
267
+ public static function tip_events_log(
268
+ $tip_id,
269
+ $channel_id,
270
+ $event,
271
+ $ems = '' ) {
272
+
273
+ $ems = $ems ?: self::$ems;
274
+ if ( $ems ) {
275
+ self::$fbgraph->log_tip_event(
276
+ $tip_id,
277
+ $channel_id,
278
+ $event
279
+ );
280
+ } else {
281
+ error_log( 'external merchant setting is null' );
282
+ }
283
+ }
284
+
285
+ public static function is_variation_type( $type ) {
286
+ return $type == 'variation' || $type == 'subscription_variation';
287
+ }
288
+
289
+ public static function is_variable_type( $type ) {
290
+ return $type == 'variable' || $type == 'variable-subscription';
291
+ }
292
+
293
+ public static function check_woo_ajax_permissions( $action_text, $die ) {
294
+ if ( ! current_user_can( 'manage_woocommerce' ) ) {
295
+ self::log(
296
+ 'Non manage_woocommerce user attempting to' . $action_text . '!',
297
+ array(),
298
+ true
299
+ );
300
+ if ( $die ) {
301
+ wp_die();
302
+ }
303
+ return false;
304
+ }
305
+ return true;
306
+ }
307
+
308
+ /**
309
+ * Returns true if id is a positive non-zero integer
310
+ *
311
+ * @access public
312
+ * @param string $pixel_id
313
+ * @return bool
314
+ */
315
+ public static function is_valid_id( $pixel_id ) {
316
+ return isset( $pixel_id ) && is_numeric( $pixel_id ) && (int) $pixel_id > 0;
317
+ }
318
+
319
+ /**
320
+ * Helper function to query posts.
321
+ */
322
+ public static function get_wp_posts(
323
+ $product_group_id = null,
324
+ $compare_condition = null,
325
+ $post_type = 'product' ) {
326
+ $args = array(
327
+ 'fields' => 'ids',
328
+ 'meta_query' => array(
329
+ ( ( $product_group_id ) ?
330
+ array(
331
+ 'key' => $product_group_id,
332
+ 'compare' => $compare_condition,
333
+ ) : array()
334
+ ),
335
+ ),
336
+ 'post_status' => 'publish',
337
+ 'post_type' => $post_type,
338
+ 'posts_per_page' => -1,
339
+ );
340
+ return get_posts( $args );
341
+ }
342
+
343
+ /**
344
+ * Helper log function for debugging
345
+ */
346
+ public static function log( $message ) {
347
+ if ( WP_DEBUG === true ) {
348
+ if ( is_array( $message ) || is_object( $message ) ) {
349
+ error_log( json_encode( $message ) );
350
+ } else {
351
+ error_log( sanitize_textarea_field( $message ) );
352
+ }
353
+ }
354
+ }
355
+
356
+ // Return store name with sanitized apostrophe
357
+ public static function get_store_name() {
358
+ if ( self::$store_name ) {
359
+ return self::$store_name;
360
+ }
361
+ $name = trim(
362
+ str_replace(
363
+ "'",
364
+ "\u{2019}",
365
+ html_entity_decode(
366
+ get_bloginfo( 'name' ),
367
+ ENT_QUOTES,
368
+ 'UTF-8'
369
+ )
370
+ )
371
+ );
372
+ if ( $name ) {
373
+ self::$store_name = $name;
374
+ return $name;
375
+ }
376
+ // Fallback to site url
377
+ $url = get_site_url();
378
+ if ( $url ) {
379
+ self::$store_name = parse_url( $url, PHP_URL_HOST );
380
+ return self::$store_name;
381
+ }
382
+ // If site url doesn't exist, fall back to http host.
383
+ if ( $_SERVER['HTTP_HOST'] ) {
384
+ self::$store_name = $_SERVER['HTTP_HOST'];
385
+ return self::$store_name;
386
+ }
387
+
388
+ // If http host doesn't exist, fall back to local host name.
389
+ $url = gethostname();
390
+ self::$store_name = $url;
391
+ return ( self::$store_name ) ? ( self::$store_name ) : 'A Store Has No Name';
392
+ }
393
+
394
+ /*
395
+ * Change variant product field name from Woo taxonomy to FB name
396
+ */
397
+ public static function sanitize_variant_name( $name ) {
398
+ $name = str_replace( array( 'attribute_', 'pa_' ), '', strtolower( $name ) );
399
+
400
+ // British spelling
401
+ if ( $name === self::FB_VARIANT_COLOUR ) {
402
+ $name = self::FB_VARIANT_COLOR;
403
+ }
404
+
405
+ switch ( $name ) {
406
+ case self::FB_VARIANT_SIZE:
407
+ case self::FB_VARIANT_COLOR:
408
+ case self::FB_VARIANT_GENDER:
409
+ case self::FB_VARIANT_PATTERN:
410
+ break;
411
+ default:
412
+ $name = 'custom_data:' . strtolower( $name );
413
+ break;
414
+ }
415
+
416
+ return $name;
417
+ }
418
+
419
+ public static function validateGender( $gender ) {
420
+ if ( $gender && ! isset( self::$validGenderArray[ $gender ] ) ) {
421
+ $first_char = strtolower( substr( $gender, 0, 1 ) );
422
+ // Men, Man, Boys
423
+ if ( $first_char === 'm' || $first_char === 'b' ) {
424
+ return 'male';
425
+ }
426
+ // Women, Woman, Female, Ladies
427
+ if ( $first_char === 'w' || $first_char === 'f' || $first_char === 'l' ) {
428
+ return 'female';
429
+ }
430
+ if ( $first_char === 'u' ) {
431
+ return 'unisex';
432
+ }
433
+ if ( strlen( $gender ) >= 3 ) {
434
+ $gender = strtolower( substr( $gender, 0, 3 ) );
435
+ if ( $gender === 'gir' || $gender === 'her' ) {
436
+ return 'female';
437
+ }
438
+ if ( $gender === 'him' || $gender === 'his' || $gender == 'guy' ) {
439
+ return 'male';
440
+ }
441
+ }
442
+ return null;
443
+ }
444
+ return $gender;
445
+ }
446
+
447
+ public static function get_fbid_post_meta( $wp_id, $fbid_type ) {
448
+ return get_post_meta( $wp_id, $fbid_type, true );
449
+ }
450
+
451
+ public static function is_all_caps( $value ) {
452
+ if ( $value === null || $value === '' ) {
453
+ return true;
454
+ }
455
+ if ( preg_match( '/[^\\p{Common}\\p{Latin}]/u', $value ) ) {
456
+ // Contains non-western characters
457
+ // So, it can't be all uppercase
458
+ return false;
459
+ }
460
+ $latin_string = preg_replace( '/[^\\p{Latin}]/u', '', $value );
461
+ if ( $latin_string === '' ) {
462
+ // Symbols only
463
+ return true;
464
+ }
465
+ return strtoupper( $latin_string ) === $latin_string;
466
+ }
467
+
468
+ public static function decode_json( $json_string, $assoc = false ) {
469
+ // Plugin requires 5.6.0 but for some user use 5.5.9 JSON_BIGINT_AS_STRING
470
+ // will cause 502 issue when redirect.
471
+ return version_compare( phpversion(), '5.6.0' ) >= 0
472
+ ? json_decode( $json_string, $assoc, 512, JSON_BIGINT_AS_STRING )
473
+ : json_decode( $json_string, $assoc, 512 );
474
+ }
475
+
476
+ public static function set_test_fail_reason( $msg, $trace ) {
477
+ $reason_msg = get_transient( 'facebook_plugin_test_fail' );
478
+ if ( $reason_msg ) {
479
+ $msg = $reason_msg . PHP_EOL . $msg;
480
+ }
481
+ set_transient( 'facebook_plugin_test_fail', $msg );
482
+ set_transient( 'facebook_plugin_test_stack_trace', $trace );
483
+ }
484
+
485
+ /**
486
+ * Helper function to check time cap.
487
+ */
488
+ public static function check_time_cap( $from, $date_cap ) {
489
+ if ( $from == null ) {
490
+ return true;
491
+ }
492
+ $now = new DateTime( current_time( 'mysql' ) );
493
+ $diff_in_day = $now->diff( new DateTime( $from ) )->format( '%a' );
494
+ return is_numeric( $diff_in_day ) && (int) $diff_in_day > $date_cap;
495
+ }
496
+
497
+ public static function get_cached_best_tip() {
498
+ $cached_best_tip = self::decode_json(
499
+ get_option( 'fb_info_banner_last_best_tip', '' )
500
+ );
501
+ return $cached_best_tip;
502
+ }
503
+ }
504
 
505
  endif;
includes/fbwpml.php CHANGED
@@ -8,130 +8,134 @@
8
  * @package FacebookCommerce
9
  */
10
 
11
- if (!defined('ABSPATH')) {
12
- exit;
13
  }
14
 
15
- if (!class_exists('WC_Facebook_WPML_Injector')) :
16
 
17
- class FB_WPML_Language_Status {
18
- const VISIBLE = 1;
19
- const HIDDEN = 2;
20
- const NOT_SYNCED = 0;
21
- }
22
 
23
- class WC_Facebook_WPML_Injector {
24
- public static $settings = null;
25
- public static $default_lang = null;
26
- const OPTION = 'fb_wmpl_language_visibility';
27
 
28
- public function __construct() {
29
- add_action('icl_menu_footer', array($this, 'wpml_support'));
30
- add_action('icl_ajx_custom_call', array($this, 'wpml_ajax_support'), 10, 2);
31
- self::$settings = get_option(self::OPTION);
32
- self::$default_lang = apply_filters('wpml_default_language', null);
33
- }
34
 
35
- public static function should_hide($wp_id) {
36
- $product_lang = apply_filters('wpml_post_language_details', null, $wp_id);
37
- $settings = self::$settings;
38
- if ($product_lang && isset($product_lang['language_code'])) {
39
- $product_lang = $product_lang['language_code'];
40
- }
41
 
42
- // Option doesn't exist : Backwards Compatibility
43
- if (!$settings) {
44
- return ($product_lang && self::$default_lang !== $product_lang);
45
- }
46
- // Hide products from non-active languages.
47
- if (!isset($settings[$product_lang])) {
48
- return true;
49
- }
50
- return $settings[$product_lang] !== FB_WPML_Language_Status::VISIBLE;
51
- }
52
 
53
- public function wpml_ajax_support($call, $REQUEST) {
54
- global $sitepress;
55
- if (isset($REQUEST['icl_ajx_action'])) {
56
- $call = $REQUEST['icl_ajx_action'];
57
- }
58
- if ($call === "icl_fb_woo") {
59
- $active_languages = array_keys($sitepress->get_active_languages());
60
- $settings = array();
61
- foreach ($active_languages as $lang) {
62
- $settings[$lang] = $REQUEST[$lang] === 'on' ?
63
- FB_WPML_Language_Status::VISIBLE : FB_WPML_Language_Status::HIDDEN;
64
- }
65
 
66
- update_option('fb_wmpl_language_visibility', $settings, false);
67
- self::$settings = $settings;
68
- }
69
- }
70
 
71
- public function wpml_support() {
72
- global $sitepress;
73
- if (strpos($_GET['page'], 'languages.php')) {
74
- $active_languages = $sitepress->get_active_languages();
75
- $settings = get_option(self::OPTION);
76
 
77
- // Default setting is only show default lang.
78
- if (!$settings) {
79
- $settings = array_fill_keys(
80
- array_keys($active_languages), FB_WPML_Language_Status::HIDDEN);
81
- $settings[self::$default_lang] = FB_WPML_Language_Status::VISIBLE;
82
- }
83
- $ajax_response = sprintf(
84
- 'Saved. You should now '.
85
- ' <a href="%s&fb_force_resync=true">Re-Sync</a>'.
86
- ' your products with Facebook. ',
87
- WOOCOMMERCE_FACEBOOK_PLUGIN_SETTINGS_URL);
 
 
 
88
 
89
- ?><div id="lang-sec-fb" class="wpml-section wpml-section-languages">
90
- <div class="wpml-section-header">
91
- <h3><?php _e('Facebook Visibility', 'sitepress') ?></h3>
92
- </div>
93
- <div class="wpml-section-content">
94
- WooCommerce Products with languages that are selected
95
- here will be visible to customers who see your Facebook Shop.
96
 
97
- <div class="wpml-section-content-inner">
98
- <form id="icl_fb_woo" name="icl_fb_woo" action="">
99
- <?php
100
- foreach ($settings as $language => $set) {
101
- $is_checked = $set === FB_WPML_Language_Status::VISIBLE ?
102
- 'checked' : '';
103
- $str = '
104
  <p><label>
105
- <input type="checkbox" id="icl_fb_woo_chk" name="'.$language.'" '.$is_checked.'>
106
- '.$active_languages[$language]['native_name'].'
107
  </label></p>
108
  ';
109
- echo $str;
110
- }
111
- ?>
112
- <p class="buttons-wrap">
113
- <span class="icl_ajx_response_fb" id="icl_ajx_response_fb" hidden="true">
114
- <?php echo $ajax_response ?>
115
- </span>
116
- <input class="button button-primary"
117
- name="save"
118
- value="<?php _e('Save', 'sitepress') ?>"
119
- type="submit" />
120
- </p>
121
- </form>
122
- <script type="text/javascript">
123
- addLoadEvent(function(){
124
- jQuery('#icl_fb_woo').submit(iclSaveForm);
125
- jQuery('#icl_fb_woo').submit(function(){
126
- jQuery('#icl_ajx_response_fb').show();
127
- });
128
- });
129
- </script>
130
- </div>
131
- </div>
132
- </div><?php
133
- }
134
- }
135
- }
 
136
 
137
  endif;
8
  * @package FacebookCommerce
9
  */
10
 
11
+ if ( ! defined( 'ABSPATH' ) ) {
12
+ exit;
13
  }
14
 
15
+ if ( ! class_exists( 'WC_Facebook_WPML_Injector' ) ) :
16
 
17
+ class FB_WPML_Language_Status {
18
+ const VISIBLE = 1;
19
+ const HIDDEN = 2;
20
+ const NOT_SYNCED = 0;
21
+ }
22
 
23
+ class WC_Facebook_WPML_Injector {
24
+ public static $settings = null;
25
+ public static $default_lang = null;
26
+ const OPTION = 'fb_wmpl_language_visibility';
27
 
28
+ public function __construct() {
29
+ add_action( 'icl_menu_footer', array( $this, 'wpml_support' ) );
30
+ add_action( 'icl_ajx_custom_call', array( $this, 'wpml_ajax_support' ), 10, 2 );
31
+ self::$settings = get_option( self::OPTION );
32
+ self::$default_lang = apply_filters( 'wpml_default_language', null );
33
+ }
34
 
35
+ public static function should_hide( $wp_id ) {
36
+ $product_lang = apply_filters( 'wpml_post_language_details', null, $wp_id );
37
+ $settings = self::$settings;
38
+ if ( $product_lang && isset( $product_lang['language_code'] ) ) {
39
+ $product_lang = $product_lang['language_code'];
40
+ }
41
 
42
+ // Option doesn't exist : Backwards Compatibility
43
+ if ( ! $settings ) {
44
+ return ( $product_lang && self::$default_lang !== $product_lang );
45
+ }
46
+ // Hide products from non-active languages.
47
+ if ( ! isset( $settings[ $product_lang ] ) ) {
48
+ return true;
49
+ }
50
+ return $settings[ $product_lang ] !== FB_WPML_Language_Status::VISIBLE;
51
+ }
52
 
53
+ public function wpml_ajax_support( $call, $REQUEST ) {
54
+ global $sitepress;
55
+ if ( isset( $REQUEST['icl_ajx_action'] ) ) {
56
+ $call = $REQUEST['icl_ajx_action'];
57
+ }
58
+ if ( $call === 'icl_fb_woo' ) {
59
+ $active_languages = array_keys( $sitepress->get_active_languages() );
60
+ $settings = array();
61
+ foreach ( $active_languages as $lang ) {
62
+ $settings[ $lang ] = $REQUEST[ $lang ] === 'on' ?
63
+ FB_WPML_Language_Status::VISIBLE : FB_WPML_Language_Status::HIDDEN;
64
+ }
65
 
66
+ update_option( 'fb_wmpl_language_visibility', $settings, false );
67
+ self::$settings = $settings;
68
+ }
69
+ }
70
 
71
+ public function wpml_support() {
72
+ global $sitepress;
73
+ if ( strpos( $_GET['page'], 'languages.php' ) ) {
74
+ $active_languages = $sitepress->get_active_languages();
75
+ $settings = get_option( self::OPTION );
76
 
77
+ // Default setting is only show default lang.
78
+ if ( ! $settings ) {
79
+ $settings = array_fill_keys(
80
+ array_keys( $active_languages ),
81
+ FB_WPML_Language_Status::HIDDEN
82
+ );
83
+ $settings[ self::$default_lang ] = FB_WPML_Language_Status::VISIBLE;
84
+ }
85
+ $ajax_response = sprintf(
86
+ 'Saved. You should now ' .
87
+ ' <a href="%s&fb_force_resync=true">Re-Sync</a>' .
88
+ ' your products with Facebook. ',
89
+ WOOCOMMERCE_FACEBOOK_PLUGIN_SETTINGS_URL
90
+ );
91
 
92
+ ?><div id="lang-sec-fb" class="wpml-section wpml-section-languages">
93
+ <div class="wpml-section-header">
94
+ <h3><?php _e( 'Facebook Visibility', 'sitepress' ); ?></h3>
95
+ </div>
96
+ <div class="wpml-section-content">
97
+ WooCommerce Products with languages that are selected
98
+ here will be visible to customers who see your Facebook Shop.
99
 
100
+ <div class="wpml-section-content-inner">
101
+ <form id="icl_fb_woo" name="icl_fb_woo" action="">
102
+ <?php
103
+ foreach ( $settings as $language => $set ) {
104
+ $is_checked = $set === FB_WPML_Language_Status::VISIBLE ?
105
+ 'checked' : '';
106
+ $str = '
107
  <p><label>
108
+ <input type="checkbox" id="icl_fb_woo_chk" name="' . $language . '" ' . $is_checked . '>
109
+ ' . $active_languages[ $language ]['native_name'] . '
110
  </label></p>
111
  ';
112
+ echo $str;
113
+ }
114
+ ?>
115
+ <p class="buttons-wrap">
116
+ <span class="icl_ajx_response_fb" id="icl_ajx_response_fb" hidden="true">
117
+ <?php echo $ajax_response; ?>
118
+ </span>
119
+ <input class="button button-primary"
120
+ name="save"
121
+ value="<?php _e( 'Save', 'sitepress' ); ?>"
122
+ type="submit" />
123
+ </p>
124
+ </form>
125
+ <script type="text/javascript">
126
+ addLoadEvent(function(){
127
+ jQuery('#icl_fb_woo').submit(iclSaveForm);
128
+ jQuery('#icl_fb_woo').submit(function(){
129
+ jQuery('#icl_ajx_response_fb').show();
130
+ });
131
+ });
132
+ </script>
133
+ </div>
134
+ </div>
135
+ </div>
136
+ <?php
137
+ }
138
+ }
139
+ }
140
 
141
  endif;
includes/test/facebook-integration-test.php CHANGED
@@ -14,562 +14,627 @@
14
  * https://codex.wordpress.org/WP_DEBUG
15
  */
16
 
17
- if (!defined('ABSPATH')) {
18
- exit;
19
  }
20
 
21
- include_once(dirname(__DIR__) . '/fbutils.php');
22
- include_once('fbproductfeed-test.php');
23
-
24
- if (!class_exists('WC_Facebook_Integration_Test')) :
25
-
26
- /**
27
- * This tests the upload of test objects into Facebook using the plugin's
28
- * infrastructure and checks to see if the product field have been correctly
29
- * uploaded into FB.
30
- */
31
- class WC_Facebook_Integration_Test {
32
-
33
- const FB_PRODUCT_GROUP_ID = 'fb_product_group_id';
34
- const FB_PRODUCT_ITEM_ID = 'fb_product_item_id';
35
- const MAX_SLEEP_IN_SEC = 90;
36
- const MAX_TIME = 'T23:59+00:00';
37
- const MIN_TIME = 'T00:00+00:00';
38
- /** Class Instance */
39
- private static $instance;
40
-
41
- public static $commerce = null; // Full WC_Facebookcommerce_Integration obj
42
- public static $fbgraph = null;
43
- public static $test_mode = false;
44
-
45
- // simple products' id and variable products' parent_id
46
- public static $wp_post_ids = array();
47
- // FB product item retailer id.
48
- public static $retailer_ids = array();
49
- // product and product_variation post id for test
50
- public $product_post_wpid = null;
51
- public static $test_pass = 1;
52
-
53
- /**
54
- * Get the class instance
55
- */
56
- public static function get_instance($commerce) {
57
- return null === self::$instance
58
- ? (self::$instance = new self($commerce))
59
- : self::$instance;
60
- }
61
-
62
- /**
63
- * Constructor
64
- */
65
- public function __construct($commerce) {
66
-
67
- self::$commerce = $commerce;
68
-
69
- add_action('wp_ajax_ajax_test_sync_products_using_feed',
70
- array($this, 'ajax_test_sync_products_using_feed'));
71
- }
72
-
73
- /**
74
- * Test visible products by uploading feed.
75
- **/
76
- function ajax_test_sync_products_using_feed() {
77
- self::$test_mode = true;
78
- // test ajax reset all products in db
79
- $reset = self::$commerce->reset_all_products();
80
- if ($reset) {
81
- WC_Facebookcommerce_Utils::log('Test - Removing FBIDs from all products');
82
- $this->product_post_wpid = $this->create_data();
83
- if (empty($this->product_post_wpid)) {
84
- self::$test_pass = 0;
85
- WC_Facebookcommerce_Utils::log(
86
- 'Test - Fail to create test product by inserting posts.');
87
- WC_Facebookcommerce_Utils::set_test_fail_reason(
88
- 'Fail to create test products by inserting posts.',
89
- (new Exception)->getTraceAsString());
90
- update_option('fb_test_pass', false);
91
- wp_die();
92
- return;
93
- }
94
- $this->set_product_wpid($this->product_post_wpid);
95
- $upload_success =
96
- self::$commerce->ajax_sync_all_fb_products_using_feed(true);
97
- if ($upload_success) {
98
- // verification Step.
99
- // Wait till FB finish backend creation to prevent race condition.
100
- $time_start = microtime(true);
101
- while ((microtime(true) - $time_start) < self::MAX_SLEEP_IN_SEC) {
102
- $complete = self::$commerce->fbproductfeed->is_upload_complete(
103
- self::$commerce->settings);
104
- if ($complete) {
105
- break;
106
- } else {
107
- $this->sleep_til_upload_complete(10);
108
- }
109
- }
110
- $this->sleep_til_upload_complete(60);
111
- $check_product_create = $this->check_product_create();
112
- if (!$check_product_create) {
113
- self::$test_pass = 0;
114
- } else {
115
- WC_Facebookcommerce_Utils::log(
116
- 'Test - Products create successfully.');
117
- }
118
- // Clean up whatever has been created.
119
- // Test on_product_delete API hook.
120
- $clean_up = $this->clean_up();
121
- if (!$clean_up) {
122
- self::$test_pass = 0;
123
- WC_Facebookcommerce_Utils::log(
124
- 'Test - Fail to delete product from FB');
125
- WC_Facebookcommerce_Utils::set_test_fail_reason(
126
- 'Fail to delete product from FB',
127
- (new Exception)->getTraceAsString());
128
- } else {
129
- WC_Facebookcommerce_Utils::log(
130
- 'Test - Delete product from FB successfully');
131
- }
132
- } else {
133
- self::$test_pass = 0;
134
- WC_Facebookcommerce_Utils::log(
135
- 'Test - Sync all products using feed, curl failed.');
136
- WC_Facebookcommerce_Utils::set_test_fail_reason(
137
- 'Sync all products using feed, curl failed',
138
- (new Exception)->getTraceAsString());
139
- }
140
-
141
- } else {
142
- self::$test_pass = 0;
143
- WC_Facebookcommerce_Utils::log(
144
- 'Test - Fail to remove FBIDs from local DB');
145
- WC_Facebookcommerce_Utils::set_test_fail_reason(
146
- 'Fail to remove FBIDs from local DB',
147
- (new Exception)->getTraceAsString());
148
- }
149
- update_option('fb_test_pass', self::$test_pass);
150
- wp_die();
151
- return;
152
- }
153
-
154
- function check_product_create() {
155
- if (count(self::$retailer_ids) < 3) {
156
- WC_Facebookcommerce_Utils::log('Test - Failed to create 3 product items.');
157
- WC_Facebookcommerce_Utils::set_test_fail_reason(
158
- 'Failed to create 3 product items.',
159
- (new Exception)->getTraceAsString());
160
- return false;
161
- }
162
-
163
- if (count(self::$retailer_ids) > 3) {
164
- WC_Facebookcommerce_Utils::log(
165
- 'Test - Failed to skip invisible products.');
166
- WC_Facebookcommerce_Utils::set_test_fail_reason(
167
- 'Failed to skip invisible products.',
168
- (new Exception)->getTraceAsString());
169
- return false;
170
- }
171
-
172
- // Check 3 products have been created.
173
- for ($i = 0; $i < 3; $i++) {
174
- $product_type = $i == 0? 'Simple' : 'Variable';
175
- $retailer_id = self::$retailer_ids[$i];
176
- $item_fbid =
177
- $this->check_fbid_api(self::FB_PRODUCT_ITEM_ID, $retailer_id);
178
- $group_fbid =
179
- $this->check_fbid_api(self::FB_PRODUCT_GROUP_ID, $retailer_id);
180
- if (!$item_fbid || !$group_fbid) {
181
- WC_Facebookcommerce_Utils::log('Test - ' . $product_type .
182
- ' product failed to create.');
183
- WC_Facebookcommerce_Utils::set_test_fail_reason($product_type .
184
- ' product failed to create.', (new Exception)->getTraceAsString());
185
- return false;
186
- }
187
- }
188
-
189
- // Check product detailed as expected.
190
- $data = array(
191
- 'name' => 'a simple product for test',
192
- 'price' => '20.00',
193
- 'description' => 'This is to test a simple product.',
194
- 'sale_price' => '10.00',
195
- 'sale_price_dates_from' =>
196
- date_i18n('Y-m-d', strtotime('now')) . self::MIN_TIME,
197
- 'sale_price_dates_to' =>
198
- date_i18n('Y-m-d', strtotime('+10 day')) . self::MAX_TIME,
199
- 'visibility' => 'published',
200
- );
201
- $simple_product_result =
202
- $this->check_product_info(self::$retailer_ids[0], false, $data);
203
- if (!$simple_product_result) {
204
- WC_Facebookcommerce_Utils::log('Test - Simple product failed to match ' .
205
- 'product details.');
206
- WC_Facebookcommerce_Utils::set_test_fail_reason('Simple product failed to'
207
- . ' match product details.', (new Exception)->getTraceAsString());
208
- return false;
209
- }
210
-
211
- $data = array(
212
- 'name' => 'a variable product for test',
213
- 'price' => '30.00',
214
- 'description' => 'This is to test a variable product. - Red',
215
- 'additional_variant_attributes' => array('value' => 'Red'),
216
- 'visibility' => 'published',
217
- );
218
- $variable_product_result =
219
- $this->check_product_info(self::$retailer_ids[1], true, $data);
220
- if (!$variable_product_result) {
221
- WC_Facebookcommerce_Utils::log(
222
- 'Test - Variable product failed to match product details.');
223
- WC_Facebookcommerce_Utils::set_test_fail_reason(
224
- 'Variable product failed to match product details.',
225
- (new Exception)->getTraceAsString());
226
- return false;
227
- }
228
- return true;
229
- }
230
-
231
- function check_fbid_api($fbid_type, $fb_retailer_id) {
232
- $product_fbid_result = self::$fbgraph->get_facebook_id(
233
- self::$commerce->product_catalog_id,
234
- $fb_retailer_id,
235
- true);
236
-
237
- if (is_wp_error($product_fbid_result)) {
238
- WC_Facebookcommerce_Utils::log(
239
- 'Test - ' . $product_fbid_result->get_error_message());
240
- WC_Facebookcommerce_Utils::set_test_fail_reason(
241
- 'There was an issue connecting to the Facebook API: '.
242
- $product_fbid_result->get_error_message(),
243
- (new Exception)->getTraceAsString());
244
- return false;
245
- }
246
-
247
- if ($product_fbid_result && isset($product_fbid_result['body'])) {
248
- $body = WC_Facebookcommerce_Utils::decode_json(
249
- $product_fbid_result['body'], true);
250
- if ($body && isset($body['id'])) {
251
- if ($fbid_type == self::FB_PRODUCT_GROUP_ID) {
252
- $fb_id =
253
- isset($body['product_group'])
254
- ? $body['product_group']['id']
255
- : false;
256
- } else {
257
- $fb_id = $body['id'];
258
- }
259
- return $fb_id;
260
- }
261
- }
262
-
263
- return false;
264
- }
265
-
266
- function check_product_info($retailer_id, $has_variant, $data) {
267
- $prod_info_result = self::$fbgraph->check_product_info(
268
- self::$commerce->product_catalog_id,
269
- $retailer_id,
270
- $has_variant);
271
- if (is_wp_error($prod_info_result)) {
272
- WC_Facebookcommerce_Utils::log(
273
- 'Test - ' . $prod_info_result->get_error_message());
274
- WC_Facebookcommerce_Utils::set_test_fail_reason(
275
- 'There was an issue connecting to the Facebook API: '.
276
- $prod_info_result->get_error_message(),
277
- (new Exception)->getTraceAsString());
278
- return false;
279
- }
280
-
281
- $match = true;
282
- if ($prod_info_result && isset($prod_info_result['body'])) {
283
- $body = WC_Facebookcommerce_Utils::decode_json(
284
- $prod_info_result['body'], true);
285
- if (!$body) {
286
- return false;
287
- }
288
- if ($body['name'] != $data['name']) {
289
- WC_Facebookcommerce_Utils::log(
290
- 'Test - ' . $retailer_id . " doesn\'t match name.");
291
- $match = false;
292
- }
293
-
294
- if ($body['description'] != $data['description']) {
295
- WC_Facebookcommerce_Utils::log(
296
- 'Test - ' . $retailer_id . " doesn\'t match description.");
297
- $match = false;
298
- }
299
- // Woo doesn't have API to return currency symbol.
300
- // FB graph API only support to response with a currency symbol price.
301
- // No php built-in function to support cast html number to symbol.
302
- // Compare numeric price only.
303
- $price = floatval(preg_replace('/[^\d\.]+/', '', $body['price']));
304
- if ($price != $data['price']) {
305
- WC_Facebookcommerce_Utils::log(
306
- 'Test - ' . $retailer_id . " doesn\'t match price.");
307
- $match = false;
308
- }
309
- // Check sale price and dates.
310
- if (isset($data['sale_price'])) {
311
- $sale_price = floatval(
312
- preg_replace('/[^\d\.]+/', '', $body['sale_price']));
313
- if ($sale_price != $data['sale_price']) {
314
- WC_Facebookcommerce_Utils::log(
315
- 'Test - ' . $retailer_id . " doesn\'t match sale price.");
316
- $match = false;
317
- }
318
- if ($body['sale_price_start_date'] != $data['sale_price_dates_from']) {
319
- WC_Facebookcommerce_Utils::log(
320
- 'Test - ' . $retailer_id . " doesn\'t match sale price start date");
321
- $match = false;
322
- }
323
- if ($body['sale_price_end_date'] != $data['sale_price_dates_to']) {
324
- WC_Facebookcommerce_Utils::log(
325
- 'Test - ' . $retailer_id . " doesn\'t match sale price end date.");
326
- $match = false;
327
- }
328
- }
329
-
330
- if ($body['visibility'] != $data['visibility']) {
331
- WC_Facebookcommerce_Utils::log(
332
- 'Test - ' . $retailer_id . " doesn\'t match visibility.");
333
- $match = false;
334
- }
335
-
336
- if ($has_variant &&
337
- (!isset($body['additional_variant_attributes']) ||
338
- $body['additional_variant_attributes'][0]['value'] !=
339
- $data['additional_variant_attributes']['value'])) {
340
-
341
- WC_Facebookcommerce_Utils::log(
342
- 'Test - ' . $retailer_id . " doesn\'t match variation.");
343
- $match = false;
344
- }
345
- }
346
- return $match;
347
- }
348
-
349
- // Don't early return to prevent haunting product id.
350
- function clean_up() {
351
- $failure = false;
352
- foreach (self::$wp_post_ids as $post_id) {
353
- $delete_post_result = wp_delete_post($post_id);
354
- // return false or null if failed.
355
- if (!$delete_post_result) {
356
- WC_Facebookcommerce_Utils::log('Test - Fail to delete post ' . $post_id);
357
- WC_Facebookcommerce_Utils::set_test_fail_reason(
358
- 'Fail to delete post ' . $post_id, (new Exception)->getTraceAsString());
359
- $failure = true;
360
- }
361
- }
362
- self::$wp_post_ids = array();
363
-
364
- $this->sleep_til_upload_complete(60);
365
- foreach (self::$retailer_ids as $retailer_id) {
366
- $item_fbid =
367
- $this->check_fbid_api(self::FB_PRODUCT_ITEM_ID, $retailer_id);
368
- $group_fbid =
369
- $this->check_fbid_api(self::FB_PRODUCT_GROUP_ID, $retailer_id);
370
- if ($item_fbid || $group_fbid) {
371
- WC_Facebookcommerce_Utils::log('Test - Failed to delete product ' .
372
- $retailer_id . ' via plugin deletion hook.');
373
- WC_Facebookcommerce_Utils::set_test_fail_reason(
374
- 'Failed to delete product ' . $retailer_id .
375
- ' via plugin deletion hook.',
376
- (new Exception)->getTraceAsString());
377
- $failure = true;
378
- }
379
- }
380
- self::$retailer_ids = array();
381
-
382
- return !$failure;
383
- }
384
-
385
- function create_data() {
386
- $prod_and_variant_wpid = array();
387
- // Gets term object from Accessories in the database.
388
- $term = get_term_by('name', 'Accessories', 'product_cat');
389
- // Accessories should be a default category.
390
- // If not exist, set categories term first.
391
- if (!$term) {
392
- $term = wp_insert_term(
393
- 'Accessories', // the term
394
- 'product_cat', // the taxonomy
395
- array(
396
- 'slug' => 'accessories'
397
- ));
398
- }
399
- $data = array (
400
- 'post_content' => 'This is to test a simple product.',
401
- 'post_title' => 'a simple product for test',
402
- 'post_status' => 'publish',
403
- 'post_type' => 'product',
404
- 'term' => $term,
405
- 'price' => 20,
406
- 'sale_price' => 10,
407
- 'sale_price_dates_from' => strtotime('now'),
408
- 'sale_price_dates_to' => strtotime('+10 day'),
409
- );
410
- $simple_product_result =
411
- $this->create_test_simple_product($data, $prod_and_variant_wpid);
412
-
413
- if (!$simple_product_result) {
414
- return false;
415
- }
416
-
417
- // Test an invisible product - invisible products won't be synced by feed.
418
- $data['visibility'] = false;
419
- $simple_product_result =
420
- $this->create_test_simple_product($data, $prod_and_variant_wpid);
421
-
422
- if (!$simple_product_result) {
423
- return false;
424
- }
425
-
426
- $data['post_content'] = 'This is to test a variable product.';
427
- $data['post_title'] = 'a variable product for test';
428
- $data['price'] = 30;
429
-
430
- // Test variable products.
431
- $variable_product_result =
432
- $this->create_test_variable_product($data, $prod_and_variant_wpid);
433
- if (!$variable_product_result) {
434
- return false;
435
- }
436
- return $prod_and_variant_wpid;
437
- }
438
-
439
- function create_test_simple_product($data, &$prod_and_variant_wpid) {
440
- $post_id = $this->fb_insert_post($data, 'Simple');
441
- if (!$post_id) {
442
- return false;
443
- }
444
- array_push($prod_and_variant_wpid, $post_id);
445
- update_post_meta($post_id, '_regular_price', $data['price']);
446
- update_post_meta($post_id, '_sale_price', $data['sale_price']);
447
- update_post_meta($post_id, '_sale_price_dates_from', $data['sale_price_dates_from']);
448
- update_post_meta($post_id, '_sale_price_dates_to', $data['sale_price_dates_to']);
449
-
450
- wp_set_object_terms($post_id, 'simple', 'product_type');
451
- // Invisible products won't be synced by feed.
452
- if (isset($data['visibility'])) {
453
- $terms = array('exclude-from-catalog', 'exclude-from-search');
454
- wp_set_object_terms($post_id, $terms, 'product_visibility');
455
- } else {
456
- array_push(self::$wp_post_ids, $post_id);
457
- array_push(self::$retailer_ids, 'wc_post_id_' . $post_id);
458
- }
459
-
460
- $product = wc_get_product($post_id);
461
- $product->set_stock_status('instock');
462
- wp_set_object_terms($post_id, $data['term']->term_id, 'product_cat');
463
- return true;
464
- }
465
-
466
- function create_test_variable_product($data, &$prod_and_variant_wpid) {
467
- $post_id = $this->fb_insert_post($data, 'Variable');
468
- if (!$post_id) {
469
- return false;
470
- }
471
-
472
- wp_set_object_terms($post_id, 'variable', 'product_type');
473
- array_push($prod_and_variant_wpid, $post_id);
474
- array_push(self::$wp_post_ids, $post_id);
475
- // Gets term object from Accessories in the database.
476
- $term = get_term_by('name', 'Accessories', 'product_cat');
477
- wp_set_object_terms($post_id, $term->term_id, 'product_cat');
478
-
479
- // Set up attributes.
480
- $avail_attribute_values = array(
481
- 'Red',
482
- 'Blue'
483
- );
484
- wp_set_object_terms($post_id, $avail_attribute_values, 'pa_color');
485
- $thedata = array(
486
- 'pa_color' => array(
487
- 'name' => 'pa_color',
488
- 'value' => '',
489
- 'is_visible' => '1',
490
- 'is_variation' => '1',
491
- 'is_taxonomy' => '1'
492
- )
493
- );
494
- update_post_meta($post_id, '_product_attributes', $thedata);
495
-
496
- // Insert variations.
497
- $variation_data = array(
498
- 'post_content' => 'This is to test a variable product. - Red',
499
- 'post_status' => 'publish',
500
- 'post_type' => 'product_variation',
501
- 'post_parent' => $post_id,
502
- 'price' => 30,
503
- );
504
- $variation_red = $this->fb_insert_post($variation_data, 'Variation');
505
- if (!$variation_red) {
506
- return;
507
- }
508
-
509
- $this->fb_update_variation_meta(
510
- $prod_and_variant_wpid, $variation_red, 'Red', $variation_data);
511
-
512
- $variation_data['post_content'] = 'a variable product for test - Blue';
513
- $variation_blue = $this->fb_insert_post($variation_data, 'Variatoin');
514
- if (!$variation_blue) {
515
- return false;
516
- }
517
- $this->fb_update_variation_meta(
518
- $prod_and_variant_wpid, $variation_blue, 'Blue', $variation_data);
519
- $product = wc_get_product($variation_blue);
520
- $product->set_stock_status('instock');
521
- wp_set_object_terms($variation_blue, 'variation', 'product_type');
522
- return true;
523
- }
524
-
525
- function fb_update_variation_meta(
526
- &$prod_and_variant_wpid,
527
- $variation_id,
528
- $value,
529
- $data) {
530
- array_push($prod_and_variant_wpid, $variation_id);
531
- array_push(self::$retailer_ids, 'wc_post_id_' . $variation_id);
532
-
533
- $attribute_term = get_term_by('name', $value, 'pa_color');
534
-
535
- update_post_meta($variation_id, 'attribute_pa_color', $attribute_term->slug);
536
- update_post_meta($variation_id, '_price', $data['price']);
537
- update_post_meta($variation_id, '_regular_price', $data['price']);
538
- wp_set_object_terms($variation_id, 'variation', 'product_type');
539
- $product = wc_get_product($variation_id);
540
- $product->set_stock_status('instock');
541
- }
542
-
543
- function fb_insert_post($data, $p_type) {
544
- $postarr = array_intersect_key(
545
- $data,
546
- array_flip(array(
547
- 'post_content',
548
- 'post_title',
549
- 'post_status',
550
- 'post_type',
551
- 'post_parent',
552
- )));
553
- $post_id = wp_insert_post($postarr);
554
- if (is_wp_error($post_id)) {
555
- WC_Facebookcommerce_Utils::log('Test - ' . $p_type .
556
- ' product wp_insert_post' . 'failed: ' . json_encode($post_id));
557
- return false;
558
- } else {
559
- return $post_id;
560
- }
561
- }
562
-
563
- /**
564
- * IMPORTANT! Wait for Ents creation and prevent race condition.
565
- **/
566
- function sleep_til_upload_complete($sec) {
567
- sleep($sec);
568
- }
569
-
570
- function set_product_wpid($product_post_wpid) {
571
- WC_Facebook_Product_Feed_Test_Mock::$product_post_wpid = $product_post_wpid;
572
- }
573
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
574
 
575
  endif;
14
  * https://codex.wordpress.org/WP_DEBUG
15
  */
16
 
17
+ if ( ! defined( 'ABSPATH' ) ) {
18
+ exit;
19
  }
20
 
21
+ require_once dirname( __DIR__ ) . '/fbutils.php';
22
+ require_once 'fbproductfeed-test.php';
23
+
24
+ if ( ! class_exists( 'WC_Facebook_Integration_Test' ) ) :
25
+
26
+ /**
27
+ * This tests the upload of test objects into Facebook using the plugin's
28
+ * infrastructure and checks to see if the product field have been correctly
29
+ * uploaded into FB.
30
+ */
31
+ class WC_Facebook_Integration_Test {
32
+
33
+ const FB_PRODUCT_GROUP_ID = 'fb_product_group_id';
34
+ const FB_PRODUCT_ITEM_ID = 'fb_product_item_id';
35
+ const MAX_SLEEP_IN_SEC = 90;
36
+ const MAX_TIME = 'T23:59+00:00';
37
+ const MIN_TIME = 'T00:00+00:00';
38
+ /** Class Instance */
39
+ private static $instance;
40
+
41
+ public static $commerce = null; // Full WC_Facebookcommerce_Integration obj
42
+ public static $fbgraph = null;
43
+ public static $test_mode = false;
44
+
45
+ // simple products' id and variable products' parent_id
46
+ public static $wp_post_ids = array();
47
+ // FB product item retailer id.
48
+ public static $retailer_ids = array();
49
+ // product and product_variation post id for test
50
+ public $product_post_wpid = null;
51
+ public static $test_pass = 1;
52
+
53
+ /**
54
+ * Get the class instance
55
+ */
56
+ public static function get_instance( $commerce ) {
57
+ return null === self::$instance
58
+ ? ( self::$instance = new self( $commerce ) )
59
+ : self::$instance;
60
+ }
61
+
62
+ /**
63
+ * Constructor
64
+ */
65
+ public function __construct( $commerce ) {
66
+
67
+ self::$commerce = $commerce;
68
+
69
+ add_action(
70
+ 'wp_ajax_ajax_test_sync_products_using_feed',
71
+ array( $this, 'ajax_test_sync_products_using_feed' )
72
+ );
73
+ }
74
+
75
+ /**
76
+ * Test visible products by uploading feed.
77
+ **/
78
+ function ajax_test_sync_products_using_feed() {
79
+ self::$test_mode = true;
80
+ // test ajax reset all products in db
81
+ $reset = self::$commerce->reset_all_products();
82
+ if ( $reset ) {
83
+ WC_Facebookcommerce_Utils::log( 'Test - Removing FBIDs from all products' );
84
+ $this->product_post_wpid = $this->create_data();
85
+ if ( empty( $this->product_post_wpid ) ) {
86
+ self::$test_pass = 0;
87
+ WC_Facebookcommerce_Utils::log(
88
+ 'Test - Fail to create test product by inserting posts.'
89
+ );
90
+ WC_Facebookcommerce_Utils::set_test_fail_reason(
91
+ 'Fail to create test products by inserting posts.',
92
+ ( new Exception() )->getTraceAsString()
93
+ );
94
+ update_option( 'fb_test_pass', false );
95
+ wp_die();
96
+ return;
97
+ }
98
+ $this->set_product_wpid( $this->product_post_wpid );
99
+ $upload_success =
100
+ self::$commerce->ajax_sync_all_fb_products_using_feed( true );
101
+ if ( $upload_success ) {
102
+ // verification Step.
103
+ // Wait till FB finish backend creation to prevent race condition.
104
+ $time_start = microtime( true );
105
+ while ( ( microtime( true ) - $time_start ) < self::MAX_SLEEP_IN_SEC ) {
106
+ $complete = self::$commerce->fbproductfeed->is_upload_complete(
107
+ self::$commerce->settings
108
+ );
109
+ if ( $complete ) {
110
+ break;
111
+ } else {
112
+ $this->sleep_til_upload_complete( 10 );
113
+ }
114
+ }
115
+ $this->sleep_til_upload_complete( 60 );
116
+ $check_product_create = $this->check_product_create();
117
+ if ( ! $check_product_create ) {
118
+ self::$test_pass = 0;
119
+ } else {
120
+ WC_Facebookcommerce_Utils::log(
121
+ 'Test - Products create successfully.'
122
+ );
123
+ }
124
+ // Clean up whatever has been created.
125
+ // Test on_product_delete API hook.
126
+ $clean_up = $this->clean_up();
127
+ if ( ! $clean_up ) {
128
+ self::$test_pass = 0;
129
+ WC_Facebookcommerce_Utils::log(
130
+ 'Test - Fail to delete product from FB'
131
+ );
132
+ WC_Facebookcommerce_Utils::set_test_fail_reason(
133
+ 'Fail to delete product from FB',
134
+ ( new Exception() )->getTraceAsString()
135
+ );
136
+ } else {
137
+ WC_Facebookcommerce_Utils::log(
138
+ 'Test - Delete product from FB successfully'
139
+ );
140
+ }
141
+ } else {
142
+ self::$test_pass = 0;
143
+ WC_Facebookcommerce_Utils::log(
144
+ 'Test - Sync all products using feed, curl failed.'
145
+ );
146
+ WC_Facebookcommerce_Utils::set_test_fail_reason(
147
+ 'Sync all products using feed, curl failed',
148
+ ( new Exception() )->getTraceAsString()
149
+ );
150
+ }
151
+ } else {
152
+ self::$test_pass = 0;
153
+ WC_Facebookcommerce_Utils::log(
154
+ 'Test - Fail to remove FBIDs from local DB'
155
+ );
156
+ WC_Facebookcommerce_Utils::set_test_fail_reason(
157
+ 'Fail to remove FBIDs from local DB',
158
+ ( new Exception() )->getTraceAsString()
159
+ );
160
+ }
161
+ update_option( 'fb_test_pass', self::$test_pass );
162
+ wp_die();
163
+ return;
164
+ }
165
+
166
+ function check_product_create() {
167
+ if ( count( self::$retailer_ids ) < 3 ) {
168
+ WC_Facebookcommerce_Utils::log( 'Test - Failed to create 3 product items.' );
169
+ WC_Facebookcommerce_Utils::set_test_fail_reason(
170
+ 'Failed to create 3 product items.',
171
+ ( new Exception() )->getTraceAsString()
172
+ );
173
+ return false;
174
+ }
175
+
176
+ if ( count( self::$retailer_ids ) > 3 ) {
177
+ WC_Facebookcommerce_Utils::log(
178
+ 'Test - Failed to skip invisible products.'
179
+ );
180
+ WC_Facebookcommerce_Utils::set_test_fail_reason(
181
+ 'Failed to skip invisible products.',
182
+ ( new Exception() )->getTraceAsString()
183
+ );
184
+ return false;
185
+ }
186
+
187
+ // Check 3 products have been created.
188
+ for ( $i = 0; $i < 3; $i++ ) {
189
+ $product_type = $i == 0 ? 'Simple' : 'Variable';
190
+ $retailer_id = self::$retailer_ids[ $i ];
191
+ $item_fbid =
192
+ $this->check_fbid_api( self::FB_PRODUCT_ITEM_ID, $retailer_id );
193
+ $group_fbid =
194
+ $this->check_fbid_api( self::FB_PRODUCT_GROUP_ID, $retailer_id );
195
+ if ( ! $item_fbid || ! $group_fbid ) {
196
+ WC_Facebookcommerce_Utils::log(
197
+ 'Test - ' . $product_type .
198
+ ' product failed to create.'
199
+ );
200
+ WC_Facebookcommerce_Utils::set_test_fail_reason(
201
+ $product_type .
202
+ ' product failed to create.',
203
+ ( new Exception() )->getTraceAsString()
204
+ );
205
+ return false;
206
+ }
207
+ }
208
+
209
+ // Check product detailed as expected.
210
+ $data = array(
211
+ 'name' => 'a simple product for test',
212
+ 'price' => '20.00',
213
+ 'description' => 'This is to test a simple product.',
214
+ 'sale_price' => '10.00',
215
+ 'sale_price_dates_from' =>
216
+ date_i18n( 'Y-m-d', strtotime( 'now' ) ) . self::MIN_TIME,
217
+ 'sale_price_dates_to' =>
218
+ date_i18n( 'Y-m-d', strtotime( '+10 day' ) ) . self::MAX_TIME,
219
+ 'visibility' => 'published',
220
+ );
221
+ $simple_product_result =
222
+ $this->check_product_info( self::$retailer_ids[0], false, $data );
223
+ if ( ! $simple_product_result ) {
224
+ WC_Facebookcommerce_Utils::log(
225
+ 'Test - Simple product failed to match ' .
226
+ 'product details.'
227
+ );
228
+ WC_Facebookcommerce_Utils::set_test_fail_reason(
229
+ 'Simple product failed to'
230
+ . ' match product details.',
231
+ ( new Exception() )->getTraceAsString()
232
+ );
233
+ return false;
234
+ }
235
+
236
+ $data = array(
237
+ 'name' => 'a variable product for test',
238
+ 'price' => '30.00',
239
+ 'description' => 'This is to test a variable product. - Red',
240
+ 'additional_variant_attributes' => array( 'value' => 'Red' ),
241
+ 'visibility' => 'published',
242
+ );
243
+ $variable_product_result =
244
+ $this->check_product_info( self::$retailer_ids[1], true, $data );
245
+ if ( ! $variable_product_result ) {
246
+ WC_Facebookcommerce_Utils::log(
247
+ 'Test - Variable product failed to match product details.'
248
+ );
249
+ WC_Facebookcommerce_Utils::set_test_fail_reason(
250
+ 'Variable product failed to match product details.',
251
+ ( new Exception() )->getTraceAsString()
252
+ );
253
+ return false;
254
+ }
255
+ return true;
256
+ }
257
+
258
+ function check_fbid_api( $fbid_type, $fb_retailer_id ) {
259
+ $product_fbid_result = self::$fbgraph->get_facebook_id(
260
+ self::$commerce->product_catalog_id,
261
+ $fb_retailer_id,
262
+ true
263
+ );
264
+
265
+ if ( is_wp_error( $product_fbid_result ) ) {
266
+ WC_Facebookcommerce_Utils::log(
267
+ 'Test - ' . $product_fbid_result->get_error_message()
268
+ );
269
+ WC_Facebookcommerce_Utils::set_test_fail_reason(
270
+ 'There was an issue connecting to the Facebook API: ' .
271
+ $product_fbid_result->get_error_message(),
272
+ ( new Exception() )->getTraceAsString()
273
+ );
274
+ return false;
275
+ }
276
+
277
+ if ( $product_fbid_result && isset( $product_fbid_result['body'] ) ) {
278
+ $body = WC_Facebookcommerce_Utils::decode_json(
279
+ $product_fbid_result['body'],
280
+ true
281
+ );
282
+ if ( $body && isset( $body['id'] ) ) {
283
+ if ( $fbid_type == self::FB_PRODUCT_GROUP_ID ) {
284
+ $fb_id =
285
+ isset( $body['product_group'] )
286
+ ? $body['product_group']['id']
287
+ : false;
288
+ } else {
289
+ $fb_id = $body['id'];
290
+ }
291
+ return $fb_id;
292
+ }
293
+ }
294
+
295
+ return false;
296
+ }
297
+
298
+ function check_product_info( $retailer_id, $has_variant, $data ) {
299
+ $prod_info_result = self::$fbgraph->check_product_info(
300
+ self::$commerce->product_catalog_id,
301
+ $retailer_id,
302
+ $has_variant
303
+ );
304
+ if ( is_wp_error( $prod_info_result ) ) {
305
+ WC_Facebookcommerce_Utils::log(
306
+ 'Test - ' . $prod_info_result->get_error_message()
307
+ );
308
+ WC_Facebookcommerce_Utils::set_test_fail_reason(
309
+ 'There was an issue connecting to the Facebook API: ' .
310
+ $prod_info_result->get_error_message(),
311
+ ( new Exception() )->getTraceAsString()
312
+ );
313
+ return false;
314
+ }
315
+
316
+ $match = true;
317
+ if ( $prod_info_result && isset( $prod_info_result['body'] ) ) {
318
+ $body = WC_Facebookcommerce_Utils::decode_json(
319
+ $prod_info_result['body'],
320
+ true
321
+ );
322
+ if ( ! $body ) {
323
+ return false;
324
+ }
325
+ if ( $body['name'] != $data['name'] ) {
326
+ WC_Facebookcommerce_Utils::log(
327
+ 'Test - ' . $retailer_id . " doesn\'t match name."
328
+ );
329
+ $match = false;
330
+ }
331
+
332
+ if ( $body['description'] != $data['description'] ) {
333
+ WC_Facebookcommerce_Utils::log(
334
+ 'Test - ' . $retailer_id . " doesn\'t match description."
335
+ );
336
+ $match = false;
337
+ }
338
+ // Woo doesn't have API to return currency symbol.
339
+ // FB graph API only support to response with a currency symbol price.
340
+ // No php built-in function to support cast html number to symbol.
341
+ // Compare numeric price only.
342
+ $price = floatval( preg_replace( '/[^\d\.]+/', '', $body['price'] ) );
343
+ if ( $price != $data['price'] ) {
344
+ WC_Facebookcommerce_Utils::log(
345
+ 'Test - ' . $retailer_id . " doesn\'t match price."
346
+ );
347
+ $match = false;
348
+ }
349
+ // Check sale price and dates.
350
+ if ( isset( $data['sale_price'] ) ) {
351
+ $sale_price = floatval(
352
+ preg_replace( '/[^\d\.]+/', '', $body['sale_price'] )
353
+ );
354
+ if ( $sale_price != $data['sale_price'] ) {
355
+ WC_Facebookcommerce_Utils::log(
356
+ 'Test - ' . $retailer_id . " doesn\'t match sale price."
357
+ );
358
+ $match = false;
359
+ }
360
+ if ( $body['sale_price_start_date'] != $data['sale_price_dates_from'] ) {
361
+ WC_Facebookcommerce_Utils::log(
362
+ 'Test - ' . $retailer_id . " doesn\'t match sale price start date"
363
+ );
364
+ $match = false;
365
+ }
366
+ if ( $body['sale_price_end_date'] != $data['sale_price_dates_to'] ) {
367
+ WC_Facebookcommerce_Utils::log(
368
+ 'Test - ' . $retailer_id . " doesn\'t match sale price end date."
369
+ );
370
+ $match = false;
371
+ }
372
+ }
373
+
374
+ if ( $body['visibility'] != $data['visibility'] ) {
375
+ WC_Facebookcommerce_Utils::log(
376
+ 'Test - ' . $retailer_id . " doesn\'t match visibility."
377
+ );
378
+ $match = false;
379
+ }
380
+
381
+ if ( $has_variant &&
382
+ ( ! isset( $body['additional_variant_attributes'] ) ||
383
+ $body['additional_variant_attributes'][0]['value'] !=
384
+ $data['additional_variant_attributes']['value'] ) ) {
385
+
386
+ WC_Facebookcommerce_Utils::log(
387
+ 'Test - ' . $retailer_id . " doesn\'t match variation."
388
+ );
389
+ $match = false;
390
+ }
391
+ }
392
+ return $match;
393
+ }
394
+
395
+ // Don't early return to prevent haunting product id.
396
+ function clean_up() {
397
+ $failure = false;
398
+ foreach ( self::$wp_post_ids as $post_id ) {
399
+ $delete_post_result = wp_delete_post( $post_id );
400
+ // return false or null if failed.
401
+ if ( ! $delete_post_result ) {
402
+ WC_Facebookcommerce_Utils::log( 'Test - Fail to delete post ' . $post_id );
403
+ WC_Facebookcommerce_Utils::set_test_fail_reason(
404
+ 'Fail to delete post ' . $post_id,
405
+ ( new Exception() )->getTraceAsString()
406
+ );
407
+ $failure = true;
408
+ }
409
+ }
410
+ self::$wp_post_ids = array();
411
+
412
+ $this->sleep_til_upload_complete( 60 );
413
+ foreach ( self::$retailer_ids as $retailer_id ) {
414
+ $item_fbid =
415
+ $this->check_fbid_api( self::FB_PRODUCT_ITEM_ID, $retailer_id );
416
+ $group_fbid =
417
+ $this->check_fbid_api( self::FB_PRODUCT_GROUP_ID, $retailer_id );
418
+ if ( $item_fbid || $group_fbid ) {
419
+ WC_Facebookcommerce_Utils::log(
420
+ 'Test - Failed to delete product ' .
421
+ $retailer_id . ' via plugin deletion hook.'
422
+ );
423
+ WC_Facebookcommerce_Utils::set_test_fail_reason(
424
+ 'Failed to delete product ' . $retailer_id .
425
+ ' via plugin deletion hook.',
426
+ ( new Exception() )->getTraceAsString()
427
+ );
428
+ $failure = true;
429
+ }
430
+ }
431
+ self::$retailer_ids = array();
432
+
433
+ return ! $failure;
434
+ }
435
+
436
+ function create_data() {
437
+ $prod_and_variant_wpid = array();
438
+ // Gets term object from Accessories in the database.
439
+ $term = get_term_by( 'name', 'Accessories', 'product_cat' );
440
+ // Accessories should be a default category.
441
+ // If not exist, set categories term first.
442
+ if ( ! $term ) {
443
+ $term = wp_insert_term(
444
+ 'Accessories', // the term
445
+ 'product_cat', // the taxonomy
446
+ array(
447
+ 'slug' => 'accessories',
448
+ )
449
+ );
450
+ }
451
+ $data = array(
452
+ 'post_content' => 'This is to test a simple product.',
453
+ 'post_title' => 'a simple product for test',
454
+ 'post_status' => 'publish',
455
+ 'post_type' => 'product',
456
+ 'term' => $term,
457
+ 'price' => 20,
458
+ 'sale_price' => 10,
459
+ 'sale_price_dates_from' => strtotime( 'now' ),
460
+ 'sale_price_dates_to' => strtotime( '+10 day' ),
461
+ );
462
+ $simple_product_result =
463
+ $this->create_test_simple_product( $data, $prod_and_variant_wpid );
464
+
465
+ if ( ! $simple_product_result ) {
466
+ return false;
467
+ }
468
+
469
+ // Test an invisible product - invisible products won't be synced by feed.
470
+ $data['visibility'] = false;
471
+ $simple_product_result =
472
+ $this->create_test_simple_product( $data, $prod_and_variant_wpid );
473
+
474
+ if ( ! $simple_product_result ) {
475
+ return false;
476
+ }
477
+
478
+ $data['post_content'] = 'This is to test a variable product.';
479
+ $data['post_title'] = 'a variable product for test';
480
+ $data['price'] = 30;
481
+
482
+ // Test variable products.
483
+ $variable_product_result =
484
+ $this->create_test_variable_product( $data, $prod_and_variant_wpid );
485
+ if ( ! $variable_product_result ) {
486
+ return false;
487
+ }
488
+ return $prod_and_variant_wpid;
489
+ }
490
+
491
+ function create_test_simple_product( $data, &$prod_and_variant_wpid ) {
492
+ $post_id = $this->fb_insert_post( $data, 'Simple' );
493
+ if ( ! $post_id ) {
494
+ return false;
495
+ }
496
+ array_push( $prod_and_variant_wpid, $post_id );
497
+ update_post_meta( $post_id, '_regular_price', $data['price'] );
498
+ update_post_meta( $post_id, '_sale_price', $data['sale_price'] );
499
+ update_post_meta( $post_id, '_sale_price_dates_from', $data['sale_price_dates_from'] );
500
+ update_post_meta( $post_id, '_sale_price_dates_to', $data['sale_price_dates_to'] );
501
+
502
+ wp_set_object_terms( $post_id, 'simple', 'product_type' );
503
+ // Invisible products won't be synced by feed.
504
+ if ( isset( $data['visibility'] ) ) {
505
+ $terms = array( 'exclude-from-catalog', 'exclude-from-search' );
506
+ wp_set_object_terms( $post_id, $terms, 'product_visibility' );
507
+ } else {
508
+ array_push( self::$wp_post_ids, $post_id );
509
+ array_push( self::$retailer_ids, 'wc_post_id_' . $post_id );
510
+ }
511
+
512
+ $product = wc_get_product( $post_id );
513
+ $product->set_stock_status( 'instock' );
514
+ wp_set_object_terms( $post_id, $data['term']->term_id, 'product_cat' );
515
+ return true;
516
+ }
517
+
518
+ function create_test_variable_product( $data, &$prod_and_variant_wpid ) {
519
+ $post_id = $this->fb_insert_post( $data, 'Variable' );
520
+ if ( ! $post_id ) {
521
+ return false;
522
+ }
523
+
524
+ wp_set_object_terms( $post_id, 'variable', 'product_type' );
525
+ array_push( $prod_and_variant_wpid, $post_id );
526
+ array_push( self::$wp_post_ids, $post_id );
527
+ // Gets term object from Accessories in the database.
528
+ $term = get_term_by( 'name', 'Accessories', 'product_cat' );
529
+ wp_set_object_terms( $post_id, $term->term_id, 'product_cat' );
530
+
531
+ // Set up attributes.
532
+ $avail_attribute_values = array(
533
+ 'Red',
534
+ 'Blue',
535
+ );
536
+ wp_set_object_terms( $post_id, $avail_attribute_values, 'pa_color' );
537
+ $thedata = array(
538
+ 'pa_color' => array(
539
+ 'name' => 'pa_color',
540
+ 'value' => '',
541
+ 'is_visible' => '1',
542
+ 'is_variation' => '1',
543
+ 'is_taxonomy' => '1',
544
+ ),
545
+ );
546
+ update_post_meta( $post_id, '_product_attributes', $thedata );
547
+
548
+ // Insert variations.
549
+ $variation_data = array(
550
+ 'post_content' => 'This is to test a variable product. - Red',
551
+ 'post_status' => 'publish',
552
+ 'post_type' => 'product_variation',
553
+ 'post_parent' => $post_id,
554
+ 'price' => 30,
555
+ );
556
+ $variation_red = $this->fb_insert_post( $variation_data, 'Variation' );
557
+ if ( ! $variation_red ) {
558
+ return;
559
+ }
560
+
561
+ $this->fb_update_variation_meta(
562
+ $prod_and_variant_wpid,
563
+ $variation_red,
564
+ 'Red',
565
+ $variation_data
566
+ );
567
+
568
+ $variation_data['post_content'] = 'a variable product for test - Blue';
569
+ $variation_blue = $this->fb_insert_post( $variation_data, 'Variatoin' );
570
+ if ( ! $variation_blue ) {
571
+ return false;
572
+ }
573
+ $this->fb_update_variation_meta(
574
+ $prod_and_variant_wpid,
575
+ $variation_blue,
576
+ 'Blue',
577
+ $variation_data
578
+ );
579
+ $product = wc_get_product( $variation_blue );
580
+ $product->set_stock_status( 'instock' );
581
+ wp_set_object_terms( $variation_blue, 'variation', 'product_type' );
582
+ return true;
583
+ }
584
+
585
+ function fb_update_variation_meta(
586
+ &$prod_and_variant_wpid,
587
+ $variation_id,
588
+ $value,
589
+ $data ) {
590
+ array_push( $prod_and_variant_wpid, $variation_id );
591
+ array_push( self::$retailer_ids, 'wc_post_id_' . $variation_id );
592
+
593
+ $attribute_term = get_term_by( 'name', $value, 'pa_color' );
594
+
595
+ update_post_meta( $variation_id, 'attribute_pa_color', $attribute_term->slug );
596
+ update_post_meta( $variation_id, '_price', $data['price'] );
597
+ update_post_meta( $variation_id, '_regular_price', $data['price'] );
598
+ wp_set_object_terms( $variation_id, 'variation', 'product_type' );
599
+ $product = wc_get_product( $variation_id );
600
+ $product->set_stock_status( 'instock' );
601
+ }
602
+
603
+ function fb_insert_post( $data, $p_type ) {
604
+ $postarr = array_intersect_key(
605
+ $data,
606
+ array_flip(
607
+ array(
608
+ 'post_content',
609
+ 'post_title',
610
+ 'post_status',
611
+ 'post_type',
612
+ 'post_parent',
613
+ )
614
+ )
615
+ );
616
+ $post_id = wp_insert_post( $postarr );
617
+ if ( is_wp_error( $post_id ) ) {
618
+ WC_Facebookcommerce_Utils::log(
619
+ 'Test - ' . $p_type .
620
+ ' product wp_insert_post' . 'failed: ' . json_encode( $post_id )
621
+ );
622
+ return false;
623
+ } else {
624
+ return $post_id;
625
+ }
626
+ }
627
+
628
+ /**
629
+ * IMPORTANT! Wait for Ents creation and prevent race condition.
630
+ **/
631
+ function sleep_til_upload_complete( $sec ) {
632
+ sleep( $sec );
633
+ }
634
+
635
+ function set_product_wpid( $product_post_wpid ) {
636
+ WC_Facebook_Product_Feed_Test_Mock::$product_post_wpid = $product_post_wpid;
637
+ }
638
+ }
639
 
640
  endif;
includes/test/fbproductfeed-test.php CHANGED
@@ -8,33 +8,33 @@
8
  * @package FacebookCommerce
9
  */
10
 
11
- if (! defined('ABSPATH')) {
12
- exit;
13
  }
14
 
15
- include_once(dirname(__DIR__) . '/fbproductfeed.php');
16
- include_once(dirname(__DIR__) . '/fbutils.php');
17
 
18
- if (! class_exists('WC_Facebook_Product_Feed_Test')) :
19
- /**
20
- * Mock for Facebook feed class
21
- */
22
- class WC_Facebook_Product_Feed_Test_Mock extends WC_Facebook_Product_Feed {
23
 
24
- public static $product_post_wpid = null;
25
 
26
- // Return test product post id.
27
- // Don't mess up actual products.
28
- public function get_product_wpid() {
29
- return self::$product_post_wpid;
30
- }
31
 
32
- // Log progress in local log file for testing.
33
- // Not to overwhelm DB log to track important signals.
34
- public function log_feed_progress($msg, $object = array()) {
35
- $msg = empty($object) ? $msg : $msg . json_encode($object);
36
- WC_Facebookcommerce_Utils::log('Test - ' . $msg);
37
- }
38
- }
39
 
40
  endif;
8
  * @package FacebookCommerce
9
  */
10
 
11
+ if ( ! defined( 'ABSPATH' ) ) {
12
+ exit;
13
  }
14
 
15
+ require_once dirname( __DIR__ ) . '/fbproductfeed.php';
16
+ require_once dirname( __DIR__ ) . '/fbutils.php';
17
 
18
+ if ( ! class_exists( 'WC_Facebook_Product_Feed_Test' ) ) :
19
+ /**
20
+ * Mock for Facebook feed class
21
+ */
22
+ class WC_Facebook_Product_Feed_Test_Mock extends WC_Facebook_Product_Feed {
23
 
24
+ public static $product_post_wpid = null;
25
 
26
+ // Return test product post id.
27
+ // Don't mess up actual products.
28
+ public function get_product_wpid() {
29
+ return self::$product_post_wpid;
30
+ }
31
 
32
+ // Log progress in local log file for testing.
33
+ // Not to overwhelm DB log to track important signals.
34
+ public function log_feed_progress( $msg, $object = array() ) {
35
+ $msg = empty( $object ) ? $msg : $msg . json_encode( $object );
36
+ WC_Facebookcommerce_Utils::log( 'Test - ' . $msg );
37
+ }
38
+ }
39
 
40
  endif;
readme.txt CHANGED
@@ -2,9 +2,9 @@
2
  Contributors: facebook, automattic, woothemes
3
  Tags: facebook, shop, catalog, advertise, pixel, product
4
  Requires at least: 4.4
5
- Tested up to: 4.9.8
6
- Stable tag: 1.9.14
7
- Requires PHP: 5.6
8
  MySQL: 5.6 or greater
9
  License: GPLv2 or later
10
  License URI: https://www.gnu.org/licenses/gpl-2.0.html
@@ -38,9 +38,15 @@ When opening a bug on GitHub, please give us as many details as possible.
38
  * Current version of Facebook-for-WooCommerce, WooCommerce, Wordpress, PHP
39
 
40
  == Changelog ==
 
 
 
 
 
41
  = 1.9.14 - 2019-06-20 =
42
  * Revisit CSRF security issue
43
  * Remove rest controller which is not used
 
44
 
45
  = 1.9.13 - 2019-06-18 =
46
  * Fix security issue
@@ -58,7 +64,3 @@ When opening a bug on GitHub, please give us as many details as possible.
58
  https://wordpress.org/plugins/facebook-for-woocommerce/#developers
59
  * removing debug flags notice under facebook-for-woocommerce.php so that
60
  developers will be able to debug with debug logs
61
-
62
- = 1.9.10 - 2019-02-11 =
63
- * Add facebook support link, this will help merchants to reach out to facebook customer service.
64
- * Make plugin wordpress compatible by removing woocommerce updater and removing woo_include
2
  Contributors: facebook, automattic, woothemes
3
  Tags: facebook, shop, catalog, advertise, pixel, product
4
  Requires at least: 4.4
5
+ Tested up to: 5.2.2
6
+ Stable tag: 1.9.15
7
+ Requires PHP: 5.6 or greater
8
  MySQL: 5.6 or greater
9
  License: GPLv2 or later
10
  License URI: https://www.gnu.org/licenses/gpl-2.0.html
38
  * Current version of Facebook-for-WooCommerce, WooCommerce, Wordpress, PHP
39
 
40
  == Changelog ==
41
+ = 1.9.15 - 2019-06-27 =
42
+ * CSRF handling for Ajax calls like ajax_woo_infobanner_post_click, ajax_woo_infobanner_post_xout, ajax_fb_toggle_visibility
43
+ * use phpcs to adhere to WP coding standards
44
+ * Minor UI changes on the iFrame
45
+
46
  = 1.9.14 - 2019-06-20 =
47
  * Revisit CSRF security issue
48
  * Remove rest controller which is not used
49
+ * Tested installation in wordpress 5.2.2, WooCommerce 3.64, php 5.6/7.3 with browser Chrome v75/Safari v12.1/Firefox v67.
50
 
51
  = 1.9.13 - 2019-06-18 =
52
  * Fix security issue
64
  https://wordpress.org/plugins/facebook-for-woocommerce/#developers
65
  * removing debug flags notice under facebook-for-woocommerce.php so that
66
  developers will be able to debug with debug logs