Facebook for WooCommerce - Version 1.10.0

Version Description

Download this release

Release Info

Developer SkyVerge
Plugin Icon Facebook for WooCommerce
Version 1.10.0
Comparing to
See all releases

Code changes from version 1.9.15 to 1.10.0

Files changed (74) hide show
  1. assets/css/admin/facebook-for-woocommerce-products-admin.css +37 -0
  2. assets/css/facebook.css +36 -0
  3. assets/js/admin/facebook-for-woocommerce-products-admin.js +264 -0
  4. assets/js/admin/facebook-for-woocommerce-products-admin.min.js +1 -0
  5. assets/js/admin/facebook-for-woocommerce-settings-sync.js +184 -0
  6. assets/js/admin/facebook-for-woocommerce-settings-sync.min.js +1 -0
  7. assets/js/facebook-for-woocommerce-modal.js +44 -0
  8. assets/js/facebook-for-woocommerce-modal.min.js +1 -0
  9. assets/js/facebook-infobanner.min.js +1 -0
  10. assets/js/facebook-metabox.min.js +1 -0
  11. assets/js/facebook-products.js +0 -84
  12. assets/js/facebook-settings.js +132 -175
  13. assets/js/facebook-settings.min.js +1 -0
  14. changelog.txt +425 -415
  15. class-wc-facebookcommerce.php +381 -0
  16. composer.json +0 -16
  17. facebook-commerce-events-tracker.php +274 -129
  18. facebook-commerce-messenger-chat.php +236 -47
  19. facebook-commerce-pixel-event.php +93 -37
  20. facebook-commerce.php +2461 -1192
  21. facebook-for-woocommerce.php +344 -79
  22. i18n/languages/facebook-for-woocommerce.pot +531 -0
  23. includes/AJAX.php +494 -0
  24. includes/Admin.php +878 -0
  25. includes/Lifecycle.php +156 -0
  26. includes/Products.php +254 -0
  27. includes/fbgraph.php +37 -3
  28. includes/fbinfobanner.php +20 -13
  29. includes/fbproduct.php +55 -49
  30. includes/fbproductfeed.php +14 -2
  31. includes/fbutils.php +2 -1
  32. includes/fbwpml.php +71 -53
  33. includes/test/facebook-integration-test.php +4 -3
  34. readme.txt +76 -66
  35. vendor/skyverge/wc-plugin-framework/license.txt +694 -0
  36. vendor/skyverge/wc-plugin-framework/woocommerce/Addresses/Address.php +292 -0
  37. vendor/skyverge/wc-plugin-framework/woocommerce/Addresses/Customer_Address.php +150 -0
  38. vendor/skyverge/wc-plugin-framework/woocommerce/Country_Helper.php +661 -0
  39. vendor/skyverge/wc-plugin-framework/woocommerce/Lifecycle.php +667 -0
  40. vendor/skyverge/wc-plugin-framework/woocommerce/admin/abstract-sv-wc-plugin-admin-setup-wizard.php +1300 -0
  41. vendor/skyverge/wc-plugin-framework/woocommerce/api/abstract-sv-wc-api-json-request.php +135 -0
  42. vendor/skyverge/wc-plugin-framework/woocommerce/api/abstract-sv-wc-api-json-response.php +105 -0
  43. vendor/skyverge/wc-plugin-framework/woocommerce/api/abstract-sv-wc-api-xml-request.php +216 -0
  44. vendor/skyverge/wc-plugin-framework/woocommerce/api/abstract-sv-wc-api-xml-response.php +138 -0
  45. vendor/skyverge/wc-plugin-framework/woocommerce/api/class-sv-wc-api-base.php +836 -0
  46. vendor/skyverge/wc-plugin-framework/woocommerce/api/class-sv-wc-api-exception.php +38 -0
  47. vendor/skyverge/wc-plugin-framework/woocommerce/api/interface-sv-wc-api-request.php +97 -0
  48. vendor/skyverge/wc-plugin-framework/woocommerce/api/interface-sv-wc-api-response.php +59 -0
  49. vendor/skyverge/wc-plugin-framework/woocommerce/assets/css/admin/sv-wc-plugin-admin-setup-wizard.min.css +1 -0
  50. vendor/skyverge/wc-plugin-framework/woocommerce/assets/images/ajax-loader.gif +0 -0
  51. vendor/skyverge/wc-plugin-framework/woocommerce/assets/js/admin/sv-wc-plugin-admin-setup-wizard.min.js +1 -0
  52. vendor/skyverge/wc-plugin-framework/woocommerce/assets/js/admin/sv-wp-admin-job-batch-handler.min.js +1 -0
  53. vendor/skyverge/wc-plugin-framework/woocommerce/changelog.txt +331 -0
  54. vendor/skyverge/wc-plugin-framework/woocommerce/class-sv-wc-admin-notice-handler.php +433 -0
  55. vendor/skyverge/wc-plugin-framework/woocommerce/class-sv-wc-framework-bootstrap.php +407 -0
  56. vendor/skyverge/wc-plugin-framework/woocommerce/class-sv-wc-helper.php +1024 -0
  57. vendor/skyverge/wc-plugin-framework/woocommerce/class-sv-wc-hook-deprecator.php +199 -0
  58. vendor/skyverge/wc-plugin-framework/woocommerce/class-sv-wc-plugin-compatibility.php +483 -0
  59. vendor/skyverge/wc-plugin-framework/woocommerce/class-sv-wc-plugin-dependencies.php +468 -0
  60. vendor/skyverge/wc-plugin-framework/woocommerce/class-sv-wc-plugin-exception.php +38 -0
  61. vendor/skyverge/wc-plugin-framework/woocommerce/class-sv-wc-plugin.php +1457 -0
  62. vendor/skyverge/wc-plugin-framework/woocommerce/class-sv-wp-admin-message-handler.php +438 -0
  63. vendor/skyverge/wc-plugin-framework/woocommerce/compatibility/abstract-sv-wc-data-compatibility.php +169 -0
  64. vendor/skyverge/wc-plugin-framework/woocommerce/compatibility/class-sv-wc-datetime.php +146 -0
  65. vendor/skyverge/wc-plugin-framework/woocommerce/compatibility/class-sv-wc-order-compatibility.php +528 -0
  66. vendor/skyverge/wc-plugin-framework/woocommerce/compatibility/class-sv-wc-product-compatibility.php +242 -0
  67. vendor/skyverge/wc-plugin-framework/woocommerce/i18n/languages/woocommerce-plugin-framework-et.mo +0 -0
  68. vendor/skyverge/wc-plugin-framework/woocommerce/i18n/languages/woocommerce-plugin-framework-et.po +2176 -0
  69. vendor/skyverge/wc-plugin-framework/woocommerce/i18n/languages/woocommerce-plugin-framework.pot +1939 -0
  70. vendor/skyverge/wc-plugin-framework/woocommerce/index.php +29 -0
  71. vendor/skyverge/wc-plugin-framework/woocommerce/rest-api/class-sv-wc-plugin-rest-api.php +157 -0
  72. vendor/skyverge/wc-plugin-framework/woocommerce/utilities/class-sv-wp-async-request.php +192 -0
  73. vendor/skyverge/wc-plugin-framework/woocommerce/utilities/class-sv-wp-background-job-handler.php +1121 -0
  74. vendor/skyverge/wc-plugin-framework/woocommerce/utilities/class-sv-wp-job-batch-handler.php +310 -0
assets/css/admin/facebook-for-woocommerce-products-admin.css ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* added #woocommerce-product-data to increase selector specificity */
2
+ #woocommerce-product-data .fb-product-image-source-field .wc-radios li {
3
+ margin-bottom: 0;
4
+ }
5
+ #woocommerce-product-data .fb-product-image-source-field .wc-radios li:last-child {
6
+ padding-bottom: 0;
7
+ }
8
+
9
+ /* add padding-top for first radio button in variation fields */
10
+ #woocommerce-product-data .woocommerce_variation .fb-product-image-source-field .wc-radios li:first-child {
11
+ padding-top: 10px;
12
+ }
13
+
14
+ .woocommerce_variation .fb-sync-enabled-field .checkbox {
15
+ margin-top: 2px !important;
16
+ margin-right: 5px !important;
17
+ }
18
+
19
+ .woocommerce_variation .fb-product-image-source-field {
20
+ margin: 1em 0;
21
+ }
22
+
23
+ .woocommerce_variation .fb-product-image-source-field legend {
24
+ float: left;
25
+ padding-inline-start: 0;
26
+ padding-inline-end: 0;
27
+ }
28
+
29
+ .woocommerce_variation .fb-product-image-source-field .woocommerce-help-tip {
30
+ float: left;
31
+ margin-left: 4px;
32
+ vertical-align: top;
33
+ }
34
+
35
+ .woocommerce_variation .fb-product-image-source-field .wc-radios {
36
+ clear: both;
37
+ }
assets/css/facebook.css CHANGED
@@ -272,3 +272,39 @@
272
  font-size: 12px;
273
  height: 18px;
274
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
272
  font-size: 12px;
273
  height: 18px;
274
  }
275
+
276
+ #sync_progress {
277
+ font-size: 0.9em;
278
+ font-weight: normal;
279
+ margin-left: 10px;
280
+ }
281
+
282
+ #sync_progress .spinner {
283
+ float: none;
284
+ margin-top: 0;
285
+ }
286
+
287
+ /* hide Save changes button if integration settings are hidden */
288
+ #integration-settings[style="display: none"] + p.submit {
289
+ display: none;
290
+ }
291
+
292
+ #integration-settings .resync-schedule-fieldset {
293
+ display: inline-block;
294
+ vertical-align: middle !important;
295
+ }
296
+
297
+ #integration-settings .resync-schedule-fieldset input {
298
+ vertical-align: middle !important;
299
+ }
300
+
301
+ #integration-settings .resync-schedule-fieldset input[type="number"] {
302
+ max-width: 50px;
303
+ margin: 5px;
304
+ }
305
+
306
+ #integration-settings .resync-schedule-fieldset select {
307
+ max-width: 75px !important;
308
+ vertical-align: middle !important;
309
+ line-height: 28px !important;
310
+ }
assets/js/admin/facebook-for-woocommerce-products-admin.js ADDED
@@ -0,0 +1,264 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
3
+ *
4
+ * This source code is licensed under the license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @package FacebookCommerce
8
+ */
9
+
10
+ jQuery( document ).ready( function( $ ) {
11
+
12
+ const pagenow = window.pagenow.length ? window.pagenow : '',
13
+ typenow = window.typenow.length ? window.typenow : '';
14
+
15
+
16
+ // products list edit screen
17
+ if ( 'edit-product' === pagenow ) {
18
+
19
+
20
+ let visibilityToggles = $( '.facebook-for-woocommerce-product-visibility-toggle' );
21
+
22
+ // init visibility toggles tooltips
23
+ visibilityToggles.tipTip( {
24
+ attribute: 'title',
25
+ edgeOffset: 5,
26
+ fadeIn: 50,
27
+ fadeOut: 50,
28
+ delay: 200
29
+ } );
30
+
31
+ // handle FB Catalog Visibility buttons
32
+ visibilityToggles.on( 'click', function( e ) {
33
+ e.preventDefault();
34
+
35
+ let action = $( this ).data( 'action' ),
36
+ visibility = 'show' === action ? 'yes' : 'no',
37
+ productID = parseInt( $( this ).data( 'product-id' ), 10 );
38
+
39
+ if ( 'show' === action ) {
40
+ $( this ).hide().next( 'button' ).show();
41
+ } else if ( 'hide' === action ) {
42
+ $( this ).hide().prev( 'button' ).show();
43
+ }
44
+
45
+ $.post( facebook_for_woocommerce_products_admin.ajax_url, {
46
+ action: 'facebook_for_woocommerce_set_products_visibility',
47
+ security: facebook_for_woocommerce_products_admin.set_product_visibility_nonce,
48
+ products: [
49
+ {
50
+ product_id: productID,
51
+ visibility: visibility
52
+ }
53
+ ]
54
+ } );
55
+ } );
56
+
57
+
58
+ // handle bulk actions
59
+ let submitProductBulkAction = false;
60
+
61
+ $( 'input#doaction, input#doaction2' ).on( 'click', function( e ) {
62
+
63
+ if ( ! submitProductBulkAction ) {
64
+ e.preventDefault();
65
+ } else {
66
+ return true;
67
+ }
68
+
69
+ let $submitButton = $( this ),
70
+ chosenBulkAction = $submitButton.prev( 'select' ).val();
71
+
72
+ if ( 'facebook_exclude' === chosenBulkAction || 'facebook_include' === chosenBulkAction ) {
73
+
74
+ let products = [];
75
+
76
+ $.each( $( 'input[name="post[]"]:checked' ), function() {
77
+ products.push( parseInt( $( this ).val(), 10 ) );
78
+ } );
79
+
80
+ $.post( facebook_for_woocommerce_products_admin.ajax_url, {
81
+ action: 'facebook_for_woocommerce_set_product_sync_bulk_action_prompt',
82
+ security: facebook_for_woocommerce_products_admin.set_product_sync_bulk_action_prompt_nonce,
83
+ toggle: chosenBulkAction,
84
+ products: products
85
+ }, function( response ) {
86
+
87
+ if ( response && ! response.success ) {
88
+
89
+ // close existing modals
90
+ $( '#wc-backbone-modal-dialog .modal-close' ).trigger( 'click' );
91
+
92
+ // open new modal, populate template with AJAX response data
93
+ new $.WCBackboneModal.View( {
94
+ target: 'facebook-for-woocommerce-modal',
95
+ string: response.data
96
+ } );
97
+
98
+ // exclude from sync: offer to handle product visibility
99
+ $( '.facebook-for-woocommerce-toggle-product-visibility' ).on( 'click', function( e) {
100
+
101
+ blockModal();
102
+
103
+ if ( $( this ).hasClass( 'hide-products' ) ) {
104
+
105
+ $.each( products, function() {
106
+
107
+ let $toggle = $( '#post-' + this ).find( 'td.facebook_catalog_visibility button.facebook-for-woocommerce-product-visibility-hide' );
108
+
109
+ if ( $toggle.is( ':visible' ) ) {
110
+ $toggle.trigger( 'click' );
111
+ }
112
+ } );
113
+ }
114
+
115
+ // submit form after modal prompt action
116
+ submitProductBulkAction = true;
117
+ $submitButton.trigger( 'click' );
118
+ } );
119
+
120
+ } else {
121
+
122
+ // no modal displayed: submit form as normal
123
+ submitProductBulkAction = true;
124
+ $submitButton.trigger( 'click' );
125
+ }
126
+ } );
127
+
128
+ } else {
129
+
130
+ // no modal displayed: submit form as normal
131
+ submitProductBulkAction = true;
132
+ $submitButton.trigger( 'click' );
133
+ }
134
+ } );
135
+ }
136
+
137
+
138
+ // individual product edit screen
139
+ if ( 'product' === pagenow ) {
140
+
141
+ /**
142
+ * Toggles (enables/disables) Facebook setting fields.
143
+ *
144
+ * @since 1.10.0
145
+ *
146
+ * @param {boolean} enabled whether the settings fields should be enabled or not
147
+ * @param {jQuery} $container a common ancestor of all the elements that can be enabled/disabled
148
+ */
149
+ function toggleFacebookSettings( enabled, $container ) {
150
+
151
+ $container.find( '.enable-if-sync-enabled' ).prop( 'disabled', ! enabled );
152
+ }
153
+
154
+
155
+ // toggle Facebook settings fields for simple products
156
+ const syncEnabledCheckbox = $( '#fb_sync_enabled' );
157
+ const facebookSettingsPanel = syncEnabledCheckbox.closest( '.woocommerce_options_panel' );
158
+
159
+ syncEnabledCheckbox.on( 'click', function() {
160
+ toggleFacebookSettings( $( this ).prop( 'checked' ), facebookSettingsPanel );
161
+ } );
162
+
163
+ toggleFacebookSettings( syncEnabledCheckbox.prop( 'checked' ), facebookSettingsPanel );
164
+
165
+ // toggle Facebook settings fields for variations
166
+ $( '.woocommerce_variations' ).on( 'change', '.js-variable-fb-sync-toggle', function() {
167
+ toggleFacebookSettings( $( this ).prop( 'checked' ), $( this ).closest( '.wc-metabox-content' ) );
168
+ } );
169
+
170
+ // show/hide Custom Image URL setting
171
+ $( '#woocommerce-product-data' ).on( 'change', '.js-fb-product-image-source', function() {
172
+
173
+ let $container = $( this ).closest( '.woocommerce_options_panel, .wc-metabox-content' );
174
+ let imageSource = $( this ).val();
175
+
176
+ $container.find( '.product-image-source-field' ).closest( '.form-field' ).hide();
177
+ $container.find( `.show-if-product-image-source-${imageSource}` ).closest( '.form-field' ).show();
178
+ } );
179
+
180
+ $( '.js-fb-product-image-source:checked' ).trigger( 'change' );
181
+
182
+ // trigger settings fields modifiers when variations are loaded
183
+ $( '#woocommerce-product-data' ).on( 'woocommerce_variations_loaded', function() {
184
+ $( '.js-variable-fb-sync-toggle' ).trigger( 'change' );
185
+ $( '.js-fb-product-image-source:checked' ).trigger( 'change' );
186
+ } );
187
+
188
+ let submitProductSave = false;
189
+
190
+ $( 'form#post input[type="submit"]' ).on( 'click', function( e ) {
191
+
192
+ if ( ! submitProductSave ) {
193
+ e.preventDefault();
194
+ } else {
195
+ return true;
196
+ }
197
+
198
+ let $submitButton = $( this ),
199
+ $visibleCheckbox = $( 'input[name="fb_visibility"]' ),
200
+ productID = parseInt( $( 'input#post_ID' ).val(), 10 ),
201
+ productCat = [],
202
+ productTag = $( 'textarea[name="tax_input[product_tag]"]' ).val().split( ',' ),
203
+ syncEnabled = $( 'input#fb_sync_enabled' ).prop( 'checked' );
204
+
205
+ $( '#taxonomy-product_cat input[name="tax_input[product_cat][]"]:checked' ).each( function() {
206
+ productCat.push( parseInt( $( this ).val(), 10 ) );
207
+ } );
208
+
209
+ if ( productID > 0 ) {
210
+
211
+ $.post( facebook_for_woocommerce_products_admin.ajax_url, {
212
+ action: 'facebook_for_woocommerce_set_product_sync_prompt',
213
+ security: facebook_for_woocommerce_products_admin.set_product_sync_prompt_nonce,
214
+ sync_enabled: syncEnabled ? 'enabled' : 'disabled',
215
+ product: productID,
216
+ categories: productCat,
217
+ tags: productTag
218
+ }, function( response ) {
219
+
220
+ // open modal if visibility checkbox is checked or if there are conflicting terms set for sync exclusion
221
+ if ( response && ! response.success && ( syncEnabled || ( ! syncEnabled && $visibleCheckbox.length && $visibleCheckbox.is( ':checked' ) ) ) ) {
222
+
223
+ // close existing modals
224
+ $( '#wc-backbone-modal-dialog .modal-close' ).trigger( 'click' );
225
+
226
+ // open new modal, populate template with AJAX response data
227
+ new $.WCBackboneModal.View( {
228
+ target: 'facebook-for-woocommerce-modal',
229
+ string: response.data
230
+ } );
231
+
232
+ // exclude from sync: offer to handle product visibility
233
+ $( '.facebook-for-woocommerce-toggle-product-visibility' ).on( 'click', function( e) {
234
+
235
+ blockModal();
236
+
237
+ if ( $( this ).hasClass( 'hide-products' ) ) {
238
+ $visibleCheckbox.prop( 'checked', false );
239
+ }
240
+
241
+ // no modal displayed: submit form as normal
242
+ submitProductSave = true;
243
+ $submitButton.trigger( 'click' );
244
+ } );
245
+
246
+ } else {
247
+
248
+ // no modal displayed: submit form as normal
249
+ submitProductSave = true;
250
+ $submitButton.trigger( 'click' );
251
+ }
252
+ } );
253
+
254
+ } else {
255
+
256
+ // no modal displayed: submit form as normal
257
+ submitProductSave = true;
258
+ $submitButton.trigger( 'click' );
259
+ }
260
+
261
+ } );
262
+ }
263
+
264
+ } );
assets/js/admin/facebook-for-woocommerce-products-admin.min.js ADDED
@@ -0,0 +1 @@
 
1
+ "use strict";jQuery(document).ready(function(n){var o=window.pagenow.length?window.pagenow:"";window.typenow.length&&window.typenow;if("edit-product"===o){var e=n(".facebook-for-woocommerce-product-visibility-toggle");e.tipTip({attribute:"title",edgeOffset:5,fadeIn:50,fadeOut:50,delay:200}),e.on("click",function(o){o.preventDefault();var e=n(this).data("action"),c="show"===e?"yes":"no",t=parseInt(n(this).data("product-id"),10);"show"===e?n(this).hide().next("button").show():"hide"===e&&n(this).hide().prev("button").show(),n.post(facebook_for_woocommerce_products_admin.ajax_url,{action:"facebook_for_woocommerce_set_products_visibility",security:facebook_for_woocommerce_products_admin.set_product_visibility_nonce,products:[{product_id:t,visibility:c}]})});var i=!1;n("input#doaction, input#doaction2").on("click",function(o){if(i)return!0;o.preventDefault();var e=n(this),c=e.prev("select").val();if("facebook_exclude"===c||"facebook_include"===c){var t=[];n.each(n('input[name="post[]"]:checked'),function(){t.push(parseInt(n(this).val(),10))}),n.post(facebook_for_woocommerce_products_admin.ajax_url,{action:"facebook_for_woocommerce_set_product_sync_bulk_action_prompt",security:facebook_for_woocommerce_products_admin.set_product_sync_bulk_action_prompt_nonce,toggle:c,products:t},function(o){o&&!o.success?(n("#wc-backbone-modal-dialog .modal-close").trigger("click"),new n.WCBackboneModal.View({target:"facebook-for-woocommerce-modal",string:o.data}),n(".facebook-for-woocommerce-toggle-product-visibility").on("click",function(o){blockModal(),n(this).hasClass("hide-products")&&n.each(t,function(){var o=n("#post-"+this).find("td.facebook_catalog_visibility button.facebook-for-woocommerce-product-visibility-hide");o.is(":visible")&&o.trigger("click")}),i=!0,e.trigger("click")})):(i=!0,e.trigger("click"))})}else i=!0,e.trigger("click")})}if("product"===o){var c=function(o,e){e.find(".enable-if-sync-enabled").prop("disabled",!o)},t=n("#fb_sync_enabled"),r=t.closest(".woocommerce_options_panel");t.on("click",function(){c(n(this).prop("checked"),r)}),c(t.prop("checked"),r),n(".woocommerce_variations").on("change",".js-variable-fb-sync-toggle",function(){c(n(this).prop("checked"),n(this).closest(".wc-metabox-content"))}),n("#woocommerce-product-data").on("change",".js-fb-product-image-source",function(){var o=n(this).closest(".woocommerce_options_panel, .wc-metabox-content"),e=n(this).val();o.find(".product-image-source-field").closest(".form-field").hide(),o.find(".show-if-product-image-source-"+e).closest(".form-field").show()}),n(".js-fb-product-image-source:checked").trigger("change"),n("#woocommerce-product-data").on("woocommerce_variations_loaded",function(){n(".js-variable-fb-sync-toggle").trigger("change"),n(".js-fb-product-image-source:checked").trigger("change")});var s=!1;n('form#post input[type="submit"]').on("click",function(o){if(s)return!0;o.preventDefault();var e=n(this),c=n('input[name="fb_visibility"]'),t=parseInt(n("input#post_ID").val(),10),i=[],r=n('textarea[name="tax_input[product_tag]"]').val().split(","),a=n("input#fb_sync_enabled").prop("checked");n('#taxonomy-product_cat input[name="tax_input[product_cat][]"]:checked').each(function(){i.push(parseInt(n(this).val(),10))}),0<t?n.post(facebook_for_woocommerce_products_admin.ajax_url,{action:"facebook_for_woocommerce_set_product_sync_prompt",security:facebook_for_woocommerce_products_admin.set_product_sync_prompt_nonce,sync_enabled:a?"enabled":"disabled",product:t,categories:i,tags:r},function(o){o&&!o.success&&(a||!a&&c.length&&c.is(":checked"))?(n("#wc-backbone-modal-dialog .modal-close").trigger("click"),new n.WCBackboneModal.View({target:"facebook-for-woocommerce-modal",string:o.data}),n(".facebook-for-woocommerce-toggle-product-visibility").on("click",function(o){blockModal(),n(this).hasClass("hide-products")&&c.prop("checked",!1),s=!0,e.trigger("click")})):(s=!0,e.trigger("click"))}):(s=!0,e.trigger("click"))})}});
assets/js/admin/facebook-for-woocommerce-settings-sync.js ADDED
@@ -0,0 +1,184 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
3
+ *
4
+ * This source code is licensed under the license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @package FacebookCommerce
8
+ */
9
+
10
+ jQuery( document ).ready( function( $ ) {
11
+
12
+
13
+ // run script only on Facebook Settings page
14
+ if ( 'woocommerce_page_wc-settings' === window.pagenow.length ? window.pagenow : '' ) {
15
+ return;
16
+ }
17
+
18
+
19
+ /**
20
+ * Gets any new excluded categories being added.
21
+ *
22
+ * @return {string[]}
23
+ */
24
+ function getExcludedCategoriesAdded() {
25
+
26
+ const newCategoryIDs = $( '#woocommerce_facebookcommerce_fb_sync_exclude_categories' ).val();
27
+ let oldCategoryIDs = [];
28
+
29
+ if ( window.facebookAdsToolboxConfig && window.facebookAdsToolboxConfig.excludedCategoryIDs ) {
30
+ oldCategoryIDs = window.facebookAdsToolboxConfig.excludedCategoryIDs;
31
+ }
32
+
33
+ // return IDs that are in the new value that were not in the saved value
34
+ return $( newCategoryIDs ).not( oldCategoryIDs ).get();
35
+ }
36
+
37
+
38
+ /**
39
+ * Gets any new excluded tags being added.
40
+ *
41
+ * @return {string[]}
42
+ */
43
+ function getExcludedTagsAdded() {
44
+
45
+ const newTagIDs = $( '#woocommerce_facebookcommerce_fb_sync_exclude_tags' ).val();
46
+ let oldTagIDs = [];
47
+
48
+ if ( window.facebookAdsToolboxConfig && window.facebookAdsToolboxConfig.excludedTagIDs ) {
49
+ oldTagIDs = window.facebookAdsToolboxConfig.excludedTagIDs;
50
+ }
51
+
52
+ // return IDs that are in the new value that were not in the saved value
53
+ return $( newTagIDs ).not( oldTagIDs ).get();
54
+ }
55
+
56
+
57
+ /**
58
+ * Toggles availability of input in setting groups.
59
+ *
60
+ * @param {Object[]} $elements group of jQuery elements (fields or buttons) to toggle
61
+ * @param {boolean} enable whether fields in this group should be enabled or not
62
+ */
63
+ function toggleSettingOptions( $elements, enable ) {
64
+
65
+ $( $elements ).each( function() {
66
+
67
+ let $element = $( this );
68
+
69
+ if ( $( this ).hasClass( 'wc-enhanced-select' ) ) {
70
+ $element = $( this ).next( 'span.select2-container' );
71
+ }
72
+
73
+ if ( enable ) {
74
+ $element.css( 'pointer-events', 'all' ).css( 'opacity', '1.0' );
75
+ } else {
76
+ $element.css( 'pointer-events', 'none' ).css( 'opacity', '0.4' );
77
+ }
78
+ } );
79
+ }
80
+
81
+
82
+ // toggle availability of options withing field groups
83
+ $( 'input[type="checkbox"].toggle-fields-group' ).on( 'change', function ( e ) {
84
+ if ( $( this ).hasClass( 'product-sync-field' ) ) {
85
+ toggleSettingOptions( $( '.product-sync-field' ).not( '.toggle-fields-group' ), $( this ).is( ':checked' ) );
86
+ } else if ( $( this ).hasClass( 'messenger-field' ) ) {
87
+ toggleSettingOptions( $( '.messenger-field' ).not( '.toggle-fields-group' ), $( this ).is( ':checked' ) );
88
+ } else if ( $( this ).hasClass( 'resync-schedule-field' ) ) {
89
+ toggleSettingOptions( $( '.resync-schedule-field' ).not( '.toggle-fields-group' ), $( this ).is( ':checked' ) );
90
+ }
91
+ } ).trigger( 'change' );
92
+
93
+
94
+ // adds a leading zero to time picker fields
95
+ $( '#woocommerce_facebookcommerce_scheduled_resync_hours, #woocommerce_facebookcommerce_scheduled_resync_minutes' ).on( 'input change keyup keydown keypress click', function() {
96
+
97
+ let value = $( this ).val();
98
+
99
+ if ( ! isNaN( value ) && 1 === value.length && value < 10 ) {
100
+ $( this ).val( value.padStart( 2, '0' ) );
101
+ }
102
+
103
+ } ).trigger( 'change' );
104
+
105
+
106
+ // adds a character counter on the Messenger greeting textarea
107
+ $( 'textarea#woocommerce_facebookcommerce_messenger_greeting' ).on( 'focus change keyup keydown keypress', function() {
108
+
109
+ const maxChars = parseInt( window.facebookAdsToolboxConfig.messengerGreetingMaxCharacters, 10 );
110
+ let chars = $( this ).val().length,
111
+ $counter = $( 'span.characters-counter' ),
112
+ $warning = $counter.find( 'span' );
113
+
114
+ $counter.html( chars + ' / ' + maxChars + '<br/>' ).append( $warning ).css( 'display', 'block' );
115
+
116
+ if ( chars > maxChars ) {
117
+ $counter.css( 'color', '#DC3232' ).find( 'span' ).show();
118
+ } else {
119
+ $counter.css( 'color', '#999999' ).find( 'span' ).hide();
120
+ }
121
+ } );
122
+
123
+
124
+ let submitSettingsSave = false;
125
+
126
+ $( '.woocommerce-save-button' ).on( 'click', function ( e ) {
127
+
128
+ if ( ! submitSettingsSave ) {
129
+ e.preventDefault();
130
+ } else {
131
+ return true;
132
+ }
133
+
134
+ const $submitButton = $( this ),
135
+ categoriesAdded = getExcludedCategoriesAdded(),
136
+ tagsAdded = getExcludedTagsAdded();
137
+
138
+
139
+ if ( categoriesAdded.length > 0 || tagsAdded.length > 0 ) {
140
+
141
+ $.post( facebook_for_woocommerce_settings_sync.ajax_url, {
142
+ action: 'facebook_for_woocommerce_set_excluded_terms_prompt',
143
+ security: facebook_for_woocommerce_settings_sync.set_excluded_terms_prompt_nonce,
144
+ categories: categoriesAdded,
145
+ tags: tagsAdded,
146
+ }, function ( response ) {
147
+
148
+ if ( response && ! response.success ) {
149
+
150
+ // close existing modals
151
+ $( '#wc-backbone-modal-dialog .modal-close' ).trigger( 'click' );
152
+
153
+ // open new modal, populate template with AJAX response data
154
+ new $.WCBackboneModal.View( {
155
+ target: 'facebook-for-woocommerce-modal',
156
+ string: response.data,
157
+ } );
158
+
159
+ // exclude products: submit form as normal
160
+ $( '#facebook-for-woocommerce-confirm-settings-change' ).on( 'click', function () {
161
+
162
+ blockModal();
163
+
164
+ submitSettingsSave = true;
165
+ $submitButton.trigger( 'click' );
166
+ } );
167
+
168
+ } else {
169
+
170
+ // no modal displayed: submit form as normal
171
+ submitSettingsSave = true;
172
+ $submitButton.trigger( 'click' );
173
+ }
174
+ } );
175
+
176
+ } else {
177
+
178
+ // no terms added: submit form as normal
179
+ submitSettingsSave = true;
180
+ $submitButton.trigger( 'click' );
181
+ }
182
+ } );
183
+
184
+ } );
assets/js/admin/facebook-for-woocommerce-settings-sync.min.js ADDED
@@ -0,0 +1 @@
 
1
+ "use strict";jQuery(document).ready(function(r){if("woocommerce_page_wc-settings"!==window.pagenow.length||!window.pagenow){r('input[type="checkbox"].toggle-fields-group').on("change",function(e){r(this).hasClass("product-sync-field")?o(r(".product-sync-field").not(".toggle-fields-group"),r(this).is(":checked")):r(this).hasClass("messenger-field")?o(r(".messenger-field").not(".toggle-fields-group"),r(this).is(":checked")):r(this).hasClass("resync-schedule-field")&&o(r(".resync-schedule-field").not(".toggle-fields-group"),r(this).is(":checked"))}).trigger("change"),r("#woocommerce_facebookcommerce_scheduled_resync_hours, #woocommerce_facebookcommerce_scheduled_resync_minutes").on("input change keyup keydown keypress click",function(){var e=r(this).val();!isNaN(e)&&1===e.length&&e<10&&r(this).val(e.padStart(2,"0"))}).trigger("change"),r("textarea#woocommerce_facebookcommerce_messenger_greeting").on("focus change keyup keydown keypress",function(){var e=parseInt(window.facebookAdsToolboxConfig.messengerGreetingMaxCharacters,10),o=r(this).val().length,c=r("span.characters-counter"),s=c.find("span");c.html(o+" / "+e+"<br/>").append(s).css("display","block"),e<o?c.css("color","#DC3232").find("span").show():c.css("color","#999999").find("span").hide()});var l=!1;r(".woocommerce-save-button").on("click",function(e){if(l)return!0;e.preventDefault();var o,c,s,n,t=r(this),i=(s=r("#woocommerce_facebookcommerce_fb_sync_exclude_categories").val(),n=[],window.facebookAdsToolboxConfig&&window.facebookAdsToolboxConfig.excludedCategoryIDs&&(n=window.facebookAdsToolboxConfig.excludedCategoryIDs),r(s).not(n).get()),a=(o=r("#woocommerce_facebookcommerce_fb_sync_exclude_tags").val(),c=[],window.facebookAdsToolboxConfig&&window.facebookAdsToolboxConfig.excludedTagIDs&&(c=window.facebookAdsToolboxConfig.excludedTagIDs),r(o).not(c).get());0<i.length||0<a.length?r.post(facebook_for_woocommerce_settings_sync.ajax_url,{action:"facebook_for_woocommerce_set_excluded_terms_prompt",security:facebook_for_woocommerce_settings_sync.set_excluded_terms_prompt_nonce,categories:i,tags:a},function(e){e&&!e.success?(r("#wc-backbone-modal-dialog .modal-close").trigger("click"),new r.WCBackboneModal.View({target:"facebook-for-woocommerce-modal",string:e.data}),r("#facebook-for-woocommerce-confirm-settings-change").on("click",function(){blockModal(),l=!0,t.trigger("click")})):(l=!0,t.trigger("click"))}):(l=!0,t.trigger("click"))})}function o(e,o){r(e).each(function(){var e=r(this);r(this).hasClass("wc-enhanced-select")&&(e=r(this).next("span.select2-container")),o?e.css("pointer-events","all").css("opacity","1.0"):e.css("pointer-events","none").css("opacity","0.4")})}});
assets/js/facebook-for-woocommerce-modal.js ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
3
+ *
4
+ * This source code is licensed under the license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @package FacebookCommerce
8
+ */
9
+
10
+ $ = jQuery;
11
+
12
+ /**
13
+ * Determines if the current modal is blocked.
14
+ *
15
+ * @returns {boolean}
16
+ */
17
+ function isModalBlocked() {
18
+ let $modal = $( '.wc-backbone-modal-content' );
19
+ return $modal.is( '.processing') || $modal.parents( '.processing' ).length;
20
+ }
21
+
22
+
23
+ /**
24
+ * Blocks the current modal.
25
+ */
26
+ function blockModal() {
27
+ if ( ! isModalBlocked() ) {
28
+ return $( '.wc-backbone-modal-content' ).addClass( 'processing' ).block( {
29
+ message: null,
30
+ overlayCSS: {
31
+ background: '#fff',
32
+ opacity: 0.6
33
+ }
34
+ } );
35
+ }
36
+ }
37
+
38
+
39
+ /**
40
+ * Unblocks the current modal.
41
+ */
42
+ function unBlockModal() {
43
+ $( '.wc-backbone-modal-content' ).removeClass( 'processing' ).unblock();
44
+ }
assets/js/facebook-for-woocommerce-modal.min.js ADDED
@@ -0,0 +1 @@
 
1
+ "use strict";function isModalBlocked(){var o=$(".wc-backbone-modal-content");return o.is(".processing")||o.parents(".processing").length}function blockModal(){if(!isModalBlocked())return $(".wc-backbone-modal-content").addClass("processing").block({message:null,overlayCSS:{background:"#fff",opacity:.6}})}function unBlockModal(){$(".wc-backbone-modal-content").removeClass("processing").unblock()}$=jQuery;
assets/js/facebook-infobanner.min.js ADDED
@@ -0,0 +1 @@
 
1
+ "use strict";function ajax(n){var o=1<arguments.length&&void 0!==arguments[1]?arguments[1]:null,a=2<arguments.length&&void 0!==arguments[2]?arguments[2]:null,t=3<arguments.length&&void 0!==arguments[3]?arguments[3]:null,_=Object.assign({},{action:n},o);jQuery.post(ajaxurl,_,function(n){a&&a(n)}).fail(function(n){t&&t(n)})}function fb_woo_infobanner_post_click(){return console.log("Woo infobanner post tip click!"),ajax("ajax_woo_infobanner_post_click",{_ajax_nonce:wc_facebook_infobanner_jsx.nonce})}function fb_woo_infobanner_post_xout(){return console.log("Woo infobanner post tip xout!"),ajax("ajax_woo_infobanner_post_xout",{_ajax_nonce:wc_facebook_infobanner_jsx.nonce})}
assets/js/facebook-metabox.min.js ADDED
@@ -0,0 +1 @@
 
1
+ "use strict";function ajax(e){var o=1<arguments.length&&void 0!==arguments[1]?arguments[1]:null,t=2<arguments.length&&void 0!==arguments[2]?arguments[2]:null,a=3<arguments.length&&void 0!==arguments[3]?arguments[3]:null,n=Object.assign({},{action:e},o);jQuery.post(ajaxurl,n,function(e){t&&t(e)}).fail(function(e){a&&a(e)})}function fb_reset_product(e){if(confirm("Resetting Facebook metadata will not remove this product from your shop. If you have duplicated another product and are trying to publish a new Facebook product, click OK to proceed. Otherwise, Facebook metadata will be restored when this product is updated again.")){var o=document.querySelector("#fb_metadata");return o&&(o.innerHTML="<b>This product is not yet synced to Facebook.</b>"),ajax("ajax_reset_single_fb_product",{wp_id:e,_ajax_nonce:wc_facebook_metabox_jsx.nonce})}}function fb_delete_product(e){if(confirm('Are you sure you want to delete this product on Facebook? If you only want to "hide" the product, uncheck the "Visible" checkbox and hit "Update". If you delete a product on Facebook and hit "Update" after, this product will be recreated. To permanently remove this product from Facebook, hit "OK" and close the window.This will not delete the product from WooCommerce.')){var o=document.querySelector("#fb_metadata");return o&&(o.innerHTML="<b>This product is not yet synced to Facebook.</b>"),ajax("ajax_delete_fb_product",{wp_id:e,_ajax_nonce:wc_facebook_metabox_jsx.nonce})}}
assets/js/facebook-products.js DELETED
@@ -1,84 +0,0 @@
1
- /**
2
- * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
3
- *
4
- * This source code is licensed under the license found in the
5
- * LICENSE file in the root directory of this source tree.
6
- *
7
- * @package FacebookCommerce
8
- */
9
-
10
- /*
11
- * Ajax helper function.
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
@@ -59,25 +59,40 @@ function prepend_protocol(url) {
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.
@@ -142,7 +157,7 @@ function sync_confirm(verbose = null) {
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.';
@@ -166,13 +181,7 @@ if (window.location.href.includes( "fb_force_resync" )) {
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) {
@@ -187,6 +196,9 @@ function sync_all_products($using_feed = false, $is_test = false) {
187
  },
188
  );
189
  } else {
 
 
 
190
  return ajax(
191
  'ajax_sync_all_fb_products',
192
  {
@@ -198,31 +210,24 @@ function sync_all_products($using_feed = false, $is_test = false) {
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',
@@ -308,70 +313,23 @@ function save_settings_and_sync(message) {
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' )) {
@@ -413,39 +371,28 @@ function sync_not_in_progress(){
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) {
@@ -462,9 +409,6 @@ function setMerchantSettings(message) {
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
 
@@ -479,9 +423,6 @@ function setCatalog(message) {
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
 
@@ -538,13 +479,6 @@ function setAccessTokenAndPageId(message) {
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;
@@ -555,10 +489,13 @@ function setAccessTokenAndPageId(message) {
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
 
@@ -636,11 +573,18 @@ function iFrameListener(event) {
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(
@@ -674,19 +618,32 @@ function parseURL(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...' );
@@ -697,16 +654,19 @@ function ping_feed_status_queue(count = 0) {
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',
@@ -739,9 +699,7 @@ function check_queues() {
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 );
@@ -789,7 +747,9 @@ function check_feed_upload_queue(check_num) {
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) {
@@ -803,15 +763,16 @@ function check_feed_upload_queue(check_num) {
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();
@@ -947,24 +908,6 @@ function saveAutoSyncSchedule() {
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;
@@ -982,3 +925,17 @@ function syncShortDescription() {
982
  }
983
  );
984
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  return url;
60
  }
61
 
62
+
63
+ /**
64
+ * Gets the input Element that holds the value for the Pixel ID setting.
65
+ *
66
+ * @returns {(Element|null)}
67
+ */
68
  function get_pixel_id_box() {
69
+
70
+ return document.querySelector( '#woocommerce_facebookcommerce_facebook_pixel_id' );
71
  }
72
+
73
+
74
+ /**
75
+ * Gets the input Element that holds the value for the Use Advanced Matching setting.
76
+ *
77
+ * @returns {(Element|null)}
78
+ */
79
  function get_pixel_use_pii_id_box() {
80
+
81
+ return document.querySelector( '#woocommerce_facebookcommerce_enable_advanced_matching' );
 
 
82
  }
83
+
84
+
85
+ /**
86
+ * Gets the input Element that holds the value for the Facebook page setting.
87
+ *
88
+ * @return {(Element|null)}
89
+ */
90
  function get_page_id_box() {
91
+
92
+ return document.querySelector( '#woocommerce_facebookcommerce_facebook_page_id' );
 
 
93
  }
94
 
95
+
96
  /*
97
  * Ajax helper function.
98
  * Takes optional payload for POST and optional callback.
157
  break;
158
  default:
159
  msg = 'Facebook for WooCommerce automatically syncs your products on ' +
160
+ 'create/update. Are you sure you want to force product resync?\n\n' +
161
  'This will query all published products and may take some time. ' +
162
  'You only need to do this if your products are out of sync ' +
163
  'or some of your products did not sync.';
181
  }
182
 
183
  function sync_all_products($using_feed = false, $is_test = false) {
184
+
 
 
 
 
 
 
185
  window.fb_connected = true;
186
  sync_in_progress();
187
  if ($using_feed) {
196
  },
197
  );
198
  } else {
199
+
200
+ check_background_processor_status();
201
+
202
  return ajax(
203
  'ajax_sync_all_fb_products',
204
  {
210
 
211
  // Reset all state
212
  function delete_all_settings(callback = null, failcallback = null) {
213
+
 
 
214
  if (get_pixel_id_box()) {
215
  get_pixel_id_box().value = '';
216
  }
217
  if (get_pixel_use_pii_id_box()) {
218
  get_pixel_use_pii_id_box().checked = false;
219
  }
220
+
 
 
221
  if (get_page_id_box()) {
222
  get_page_id_box().value = '';
223
  }
 
 
 
224
 
225
  window.facebookAdsToolboxConfig.pixel.pixelId = '';
226
  window.facebookAdsToolboxConfig.diaSettingId = '';
 
 
227
  window.fb_connected = false;
228
 
229
+ not_connected();
230
+
231
  console.log( 'Deleting all settings and removing all FBIDs!' );
232
  return ajax(
233
  'ajax_delete_fb_settings',
313
  }
314
  }
315
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
316
 
317
+ /**
318
+ * Prepares UI for product sync.
319
+ */
320
+ function sync_in_progress() {
321
+
322
+ // temporarily disable Manage connection and Sync products buttons
323
+ jQuery( '#woocommerce-facebook-settings-manage-connection' ).css( 'pointer-events', 'none' );
324
+ jQuery( '#woocommerce-facebook-settings-sync-products' ).css( 'pointer-events', 'none' );
325
+
326
+ // set products sync status
327
+ if ( document.querySelector( '#sync_progress' ) ) {
328
+ document.querySelector( '#sync_progress' ).innerHTML = 'Syncing... Keep this browser open until sync is complete.<span class="spinner is-active"></span>';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
329
  }
330
  }
331
 
332
+
333
  function sync_not_in_progress(){
334
  // Reset to pre-setup state.
335
  if (document.querySelector( '#cta_button' )) {
371
  document.querySelector( '#setup_l3' ).innerHTML =
372
  'Get reporting on sales and revenue';
373
  }
374
+
375
+ // enable Manage connection and Sync products buttons when sync is complete
376
+ jQuery( '#woocommerce-facebook-settings-manage-connection' ).css( 'pointer-events', 'auto' );
377
+ jQuery( '#woocommerce-facebook-settings-sync-products' ).css( 'pointer-events', 'auto' );
378
+
 
 
 
 
 
379
  // Remove sync progress.
380
  if (document.querySelector( '#sync_progress' )) {
381
  document.querySelector( '#sync_progress' ).innerHTML = '';
382
  }
383
  }
384
 
 
 
 
 
385
 
386
+ /**
387
+ * Shows Facebook fancy box if the store is still not connected to Facebook.
388
+ *
389
+ * Also hides the integration settings fields.
390
+ */
391
+ function not_connected() {
392
+
393
+ jQuery( '#fbsetup' ).show();
394
+ jQuery( '#integration-settings' ).hide();
395
+ jQuery( '.woocommerce-save-button' ).hide();
 
 
396
  }
397
 
398
  function addAnEventListener(obj,evt,func) {
409
  window.sendToFacebook( 'fail set merchant settings', message.params );
410
  return;
411
  }
 
 
 
412
 
413
  settings.external_merchant_settings_id = message.params.setting_id;
414
 
423
  window.sendToFacebook( 'fail set catalog', message.params );
424
  return;
425
  }
 
 
 
426
 
427
  settings.product_catalog_id = message.params.catalog_id;
428
 
479
  window.sendToFacebook( 'fail set page access token', message.params );
480
  return;
481
  }
 
 
 
 
 
 
 
482
 
483
  if (get_page_id_box()) {
484
  get_page_id_box().value = message.params.page_id;
489
  // Ack token in "save_settings_and_sync" for final ack
490
 
491
  window.facebookAdsToolboxConfig.tokenExpired = false;
492
+
493
+ if ( document.querySelector( '#connection-message-invalid' ) ) {
494
+ document.querySelector( '#connection-message-invalid' ).style.display = 'none';
495
+ }
496
+
497
+ if ( document.querySelector( '#connection-message-refresh' ) ) {
498
+ document.querySelector( '#connection-message-refresh' ).style.display = 'block';
499
  }
500
  }
501
 
573
  case 'gen feed':
574
  genFeed();
575
  break;
576
+
577
  case 'set page access token':
578
+ // should be last message received
579
  setAccessTokenAndPageId( event.data );
580
  save_settings_and_sync( event.data );
581
+
582
+ // hide Facebook fancy box and show integration settings
583
+ jQuery( '#fbsetup' ).hide();
584
+ jQuery( '#integration-settings' ).show();
585
+ jQuery( '.woocommerce-save-button' ).show();
586
  break;
587
+
588
  case 'set msger chat':
589
  setMsgerChatSetup( event.data.params );
590
  save_settings_for_plugin(
618
  return parser;
619
  }
620
 
621
+
622
+ /**
623
+ * Setups an interval to check the status a product sync being executed in the background.
624
+ *
625
+ * @since 1.10.0
626
+ */
627
+ function check_background_processor_status() {
628
+
629
+ if ( ! window.facebookAdsToolboxConfig.feed.hasClientSideFeedUpload ) {
630
+
631
+ // sanity check to remove any running intervals
632
+ clearInterval( window.fb_pings );
633
+
634
+ window.fb_pings = setInterval( function() {
635
+ console.log( "Pinging queue..." );
636
+ check_queues();
637
+ }, 10000 );
638
+ }
639
+ }
640
+
641
 
642
  function ping_feed_status_queue(count = 0) {
643
+
644
+ // sanity check to remove any running intervals
645
+ clearInterval( window.fb_feed_pings );
646
+
647
  window.fb_feed_pings = setInterval(
648
  function() {
649
  console.log( 'Pinging feed uploading queue...' );
654
  }
655
 
656
  function product_sync_complete(sync_progress_element) {
657
+
658
  sync_not_in_progress();
659
+
 
 
660
  if (sync_progress_element) {
661
  sync_progress_element.innerHTML = '';
662
  }
663
  clearInterval( window.fb_pings );
664
  }
665
 
666
+
667
+ /**
668
+ * Checks the status a product sync being executed in the background.
669
+ */
670
  function check_queues() {
671
  ajax(
672
  'ajax_fb_background_check_queue',
699
  var remaining = res.remaining;
700
  if ( processing ) {
701
  if ( sync_progress_element ) {
702
+ sync_progress_element.innerHTML = '<strong>Progress:</strong> ' + remaining + ' item' + ( remaining > 1 ? 's' : '' ) + ' remaining.<span class="spinner is-active"></span>';
 
 
703
  }
704
  if ( remaining === 0 ) {
705
  product_sync_complete( sync_progress_element );
747
  function(response) {
748
  var sync_progress_element = document.querySelector( '#sync_progress' );
749
  var res = parse_response_check_connection( response );
750
+
751
  clearInterval( window.fb_feed_pings );
752
+
753
  if (res) {
754
  var status = res.status;
755
  switch (status) {
763
  break;
764
  case 'in progress':
765
  if (sync_progress_element) {
766
+ sync_progress_element.innerHTML = 'Syncing... Keep this browser open until sync is complete.<span class="spinner is-active"></span>';
 
 
767
  }
768
  ping_feed_status_queue( check_num + 1 );
769
  break;
770
+
771
  default:
772
+ if ( sync_progress_element ) {
773
+ sync_progress_element.innerHTML = '<strong>Something wrong when uploading, please try again.</strong>';
774
+ }
775
+
776
  window.feed_upload = false;
777
  if (window.is_test) {
778
  display_test_result();
908
  );
909
  }
910
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
911
 
912
  function syncShortDescription() {
913
  var isChecked = document.getElementsByClassName( 'syncShortDescription' )[0].checked;
925
  }
926
  );
927
  }
928
+
929
+
930
+ jQuery( document ).ready( function( $ ) {
931
+
932
+ // check background processor status in case products are being synced in the background when the page loads
933
+ check_background_processor_status();
934
+
935
+ $( '#woocommerce-facebook-settings-sync-products' ).click( function( event ) {
936
+
937
+ event.preventDefault();
938
+
939
+ sync_confirm();
940
+ } );
941
+ } );
assets/js/facebook-settings.min.js ADDED
@@ -0,0 +1 @@
 
1
+ "use strict";var fb_sync_no_response_count=0,fb_show_advanced_options=!1;function toggleAdvancedOptions(){var e=document.getElementById("fbAdvancedOptions");fb_show_advanced_options?(e.style.display="none",document.getElementById("fbAdvancedOptionsText").innerHTML="Show Advanced Settings"):(e.style.display="block",document.getElementById("fbAdvancedOptionsText").innerHTML="Hide Advanced Settings"),fb_show_advanced_options=!fb_show_advanced_options}function openPopup(){var e,o=screen.height/2-404,n=screen.width/2-576.5;window.originParam=window.location.protocol+"//"+window.location.host,window.facebookAdsToolboxConfig.popupOrigin.includes("staticxx")&&(window.facebookAdsToolboxConfig.popupOrigin="https://www.facebook.com/"),window.facebookAdsToolboxConfig.popupOrigin=prepend_protocol(window.facebookAdsToolboxConfig.popupOrigin),e=window.facebookAdsToolboxConfig.popupOrigin;var t=window.open(e+"/login.php?display=popup&next="+encodeURIComponent(e+"/ads/dia?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=1153","height=808","top="+o,"left="+n].join(","));return function(e,o){t.postMessage({type:e,params:o},window.facebookAdsToolboxConfig.popupOrigin)}}function prepend_protocol(e){return 0===e.indexOf("//www.")&&(e="https:"+e),e}function get_pixel_id_box(){return document.querySelector("#woocommerce_facebookcommerce_facebook_pixel_id")}function get_pixel_use_pii_id_box(){return document.querySelector("#woocommerce_facebookcommerce_enable_advanced_matching")}function get_page_id_box(){return document.querySelector("#woocommerce_facebookcommerce_facebook_page_id")}function ajax(e){var o=1<arguments.length&&void 0!==arguments[1]?arguments[1]:null,n=2<arguments.length&&void 0!==arguments[2]?arguments[2]:null,t=3<arguments.length&&void 0!==arguments[3]?arguments[3]:null,s=Object.assign({},{action:e},o);jQuery.post(ajaxurl,s,function(e){n&&n(e)}).fail(function(e){t&&t(e)})}var settings={facebook_for_woocommerce:1},pixel_settings={facebook_for_woocommerce:1};function facebookConfig(){window.sendToFacebook=openPopup(),window.diaConfig={clientSetup:window.facebookAdsToolboxConfig}}function fb_flush(){return console.log("Removing all FBIDs from all products!"),ajax("ajax_reset_all_fb_products",{_ajax_nonce:wc_facebook_settings_jsx.nonce},null,function(){console.log("Failed to reset all FB products")})}function sync_confirm(){var e=0<arguments.length&&void 0!==arguments[0]?arguments[0]:null,o="";switch(e){case"fb_force_resync":o="Your products will now be resynced with Facebook, this may take some time.";break;case"fb_test_product_sync":o="Launch Test?";break;default:o="Facebook for WooCommerce automatically syncs your products on create/update. Are you sure you want to force product resync?\n\nThis will query all published products and may take some time. You only need to do this if your products are out of sync or some of your products did not sync."}confirm(o)&&(sync_all_products(window.facebookAdsToolboxConfig.feed.hasClientSideFeedUpload,"fb_test_product_sync"==e),window.fb_sync_start_time=(new Date).getTime())}function sync_all_products(){var e=0<arguments.length&&void 0!==arguments[0]&&arguments[0],o=1<arguments.length&&void 0!==arguments[1]&&arguments[1];return window.fb_connected=!0,sync_in_progress(),e?(window.facebookAdsToolboxConfig.feed.hasClientSideFeedUpload=!0,window.feed_upload=!0,ping_feed_status_queue(),o?ajax("ajax_test_sync_products_using_feed"):ajax("ajax_sync_all_fb_products_using_feed",{_ajax_nonce:wc_facebook_settings_jsx.nonce})):(check_background_processor_status(),ajax("ajax_sync_all_fb_products",{_ajax_nonce:wc_facebook_settings_jsx.nonce}))}function delete_all_settings(){var e=0<arguments.length&&void 0!==arguments[0]?arguments[0]:null,o=1<arguments.length&&void 0!==arguments[1]?arguments[1]:null;return get_pixel_id_box()&&(get_pixel_id_box().value=""),get_pixel_use_pii_id_box()&&(get_pixel_use_pii_id_box().checked=!1),get_page_id_box()&&(get_page_id_box().value=""),window.facebookAdsToolboxConfig.pixel.pixelId="",window.facebookAdsToolboxConfig.diaSettingId="",window.fb_connected=!1,not_connected(),console.log("Deleting all settings and removing all FBIDs!"),ajax("ajax_delete_fb_settings",{_ajax_nonce:wc_facebook_settings_jsx.nonce},e,o)}function save_settings(){var o=0<arguments.length&&void 0!==arguments[0]?arguments[0]:null,n=1<arguments.length&&void 0!==arguments[1]?arguments[1]:null,e=2<arguments.length&&void 0!==arguments[2]?arguments[2]:null;(e=e||settings)._ajax_nonce=wc_facebook_settings_jsx.nonce,ajax("ajax_save_fb_settings",e,function(e){o&&o(e)},function(e){n&&n(e)})}function save_settings_for_plugin(o,n){save_settings(function(e){e&&e.includes("settings_saved")?(console.log(e),o(e)):(console.log("Fail response on save_settings_and_sync"),n(e))},function(e){console.log("Ajax error while saving settings:"+JSON.stringify(e)),n(e)})}function save_settings_and_sync(o){"api_key"in settings&&save_settings(function(e){e&&e.includes("settings_saved")?(console.log(e),window.sendToFacebook("ack set pixel",o.params),window.sendToFacebook("ack set page access token",o.params),window.sendToFacebook("ack set merchant settings",o.params),sync_all_products(!0)):(window.sendToFacebook("fail save_settings",e),console.log("Fail response on save_settings_and_sync"))},function(e){console.log("Ajax error while saving settings:"+JSON.stringify(e)),window.sendToFacebook("fail save_settings_ajax",JSON.stringify(e))})}function sync_in_progress(){jQuery("#woocommerce-facebook-settings-manage-connection").css("pointer-events","none"),jQuery("#woocommerce-facebook-settings-sync-products").css("pointer-events","none"),document.querySelector("#sync_progress")&&(document.querySelector("#sync_progress").innerHTML='Syncing... Keep this browser open until sync is complete.<span class="spinner is-active"></span>')}function sync_not_in_progress(){if(document.querySelector("#cta_button")){var e=document.querySelector("#cta_button");e.innerHTML="Create Ad",e.style["font-size"]="12px",e.style.width="60px",window.facebookAdsToolboxConfig.diaSettingId?e.onclick=function(){window.open("https://www.facebook.com/ads/dia/redirect/?settings_id="+window.facebookAdsToolboxConfig.diaSettingId+"&version=2&entry_point=admin_panel")}:e.style["pointer-events"]="none"}if(document.querySelector("#learnmore_button")){var o=document.querySelector("#learnmore_button");window.facebookAdsToolboxConfig.diaSettingId&&(o.style.display="")}document.querySelector("#setup_h1")&&(document.querySelector("#setup_h1").innerHTML="Reach the right people and sell more products"),document.querySelector("#setup_l1")&&(document.querySelector("#setup_l1").innerHTML="Create an ad in a few steps"),document.querySelector("#setup_l2")&&(document.querySelector("#setup_l2").innerHTML="Use built-in best practice for online sales"),document.querySelector("#setup_l3")&&(document.querySelector("#setup_l3").innerHTML="Get reporting on sales and revenue"),jQuery("#woocommerce-facebook-settings-manage-connection").css("pointer-events","auto"),jQuery("#woocommerce-facebook-settings-sync-products").css("pointer-events","auto"),document.querySelector("#sync_progress")&&(document.querySelector("#sync_progress").innerHTML="")}function not_connected(){jQuery("#fbsetup").show(),jQuery("#integration-settings").hide(),jQuery(".woocommerce-save-button").hide()}function addAnEventListener(e,o,n){"addEventListener"in e?e.addEventListener(o,n,!1):"attachEvent"in e&&e.attachEvent("on"+o,n)}function setMerchantSettings(e){if(!e.params.setting_id)return console.error("Facebook Extension Error: got no setting_id",e.params),void window.sendToFacebook("fail set merchant settings",e.params);settings.external_merchant_settings_id=e.params.setting_id,window.facebookAdsToolboxConfig.diaSettingId=e.params.setting_id}function setCatalog(e){if(!e.params.catalog_id)return console.error("Facebook Extension Error: got no catalog_id",e.params),void window.sendToFacebook("fail set catalog",e.params);settings.product_catalog_id=e.params.catalog_id,window.sendToFacebook("ack set catalog",e.params)}function setPixel(o){if(!o.params.pixel_id)return console.error("Facebook Ads Extension Error: got no pixel_id",o.params),void window.sendToFacebook("fail set pixel",o.params);get_pixel_id_box()&&(get_pixel_id_box().value=o.params.pixel_id),settings.pixel_id=o.params.pixel_id,pixel_settings.pixel_id=settings.pixel_id,void 0!==o.params.pixel_use_pii&&(get_pixel_use_pii_id_box()&&(get_pixel_use_pii_id_box().checked=!!o.params.pixel_use_pii),settings.pixel_use_pii=o.params.pixel_use_pii,pixel_settings.pixel_use_pii=settings.pixel_use_pii),save_settings(function(e){e&&e.includes("settings_saved")&&window.sendToFacebook("ack set pixel",o.params)},function(e){console.log(e),window.sendToFacebook("fail set pixel",e)},pixel_settings)}function genFeed(e){}function setAccessTokenAndPageId(e){if(!e.params.page_token)return console.error("Facebook Ads Extension Error: got no page_token",e.params),void window.sendToFacebook("fail set page access token",e.params);get_page_id_box()&&(get_page_id_box().value=e.params.page_id),settings.api_key=e.params.page_token,settings.page_id=e.params.page_id,window.facebookAdsToolboxConfig.tokenExpired=!1,document.querySelector("#connection-message-invalid")&&(document.querySelector("#connection-message-invalid").style.display="none"),document.querySelector("#connection-message-refresh")&&(document.querySelector("#connection-message-refresh").style.display="block")}function setMsgerChatSetup(e){if(e.hasOwnProperty("is_messenger_chat_plugin_enabled")&&(settings.is_messenger_chat_plugin_enabled=e.is_messenger_chat_plugin_enabled),e.hasOwnProperty("facebook_jssdk_version")&&(settings.facebook_jssdk_version=e.facebook_jssdk_version),e.hasOwnProperty("page_id")&&(settings.fb_page_id=e.page_id),e.hasOwnProperty("customization")){var o=e.customization;o.hasOwnProperty("greetingTextCode")&&(settings.msger_chat_customization_greeting_text_code=o.greetingTextCode),o.hasOwnProperty("locale")&&(settings.msger_chat_customization_locale=o.locale),o.hasOwnProperty("themeColorCode")&&(settings.msger_chat_customization_theme_color_code=o.themeColorCode)}}function iFrameListener(o){var e=o.origin||o.originalEvent.origin;switch(e!=window.facebookAdsToolboxConfig.popupOrigin&&urlFromSameDomain(e,window.facebookAdsToolboxConfig.popupOrigin)&&(window.facebookAdsToolboxConfig.popupOrigin=e),o.data.type){case"reset":delete_all_settings(function(e){e&&o.data.params?"Settings Deleted"===e?window.sendToFacebook("ack reset",o.data.params):(console.log(e),alert(e)):console.log("Got no response from delete_all_settings")},function(e){console.error(e)});break;case"get dia settings":window.sendToFacebook("dia settings",window.diaConfig);break;case"set merchant settings":setMerchantSettings(o.data);break;case"set catalog":setCatalog(o.data);break;case"set pixel":setPixel(o.data);break;case"gen feed":genFeed();break;case"set page access token":setAccessTokenAndPageId(o.data),save_settings_and_sync(o.data),jQuery("#fbsetup").hide(),jQuery("#integration-settings").show(),jQuery(".woocommerce-save-button").show();break;case"set msger chat":setMsgerChatSetup(o.data.params),save_settings_for_plugin(function(e){window.sendToFacebook("ack msger chat",o.data)},function(e){window.sendToFacebook("fail ack msger chat",o.data)})}}function urlFromSameDomain(e,o){if(!e.startsWith("http")||!o.startsWith("http"))return!1;var n=parseURL(e),t=parseURL(o),s=n.host.replace(/^\w+\./,"www."),i=t.host.replace(/^\w+\./,"www.");return n.protocol===t.protocol&&s===i}function parseURL(e){var o=document.createElement("a");return o.href=e,o}function check_background_processor_status(){window.facebookAdsToolboxConfig.feed.hasClientSideFeedUpload||(clearInterval(window.fb_pings),window.fb_pings=setInterval(function(){console.log("Pinging queue..."),check_queues()},1e4))}function ping_feed_status_queue(){var e=0<arguments.length&&void 0!==arguments[0]?arguments[0]:0;clearInterval(window.fb_feed_pings),window.fb_feed_pings=setInterval(function(){console.log("Pinging feed uploading queue..."),check_feed_upload_queue(e)},3e4*(1<<e))}function product_sync_complete(e){sync_not_in_progress(),e&&(e.innerHTML=""),clearInterval(window.fb_pings)}function check_queues(){ajax("ajax_fb_background_check_queue",{request_time:(new Date).getTime(),_ajax_nonce:wc_facebook_settings_jsx.nonce},function(e){if(window.feed_upload)clearInterval(window.fb_pings);else{var o=document.querySelector("#sync_progress"),n=parse_response_check_connection(e);if(n){if(fb_sync_no_response_count=0,n){n.background||(console.log("No background sync found, disabling pings"),clearInterval(window.fb_pings));var t=!!n.processing,s=n.remaining;if(t)o&&(o.innerHTML="<strong>Progress:</strong> "+s+" item"+(1<s?"s":"")+' remaining.<span class="spinner is-active"></span>'),0===s&&product_sync_complete(o);else{if(window.fb_sync_start_time&&n.request_time){var i=new Date(parseInt(n.request_time));if(window.fb_sync_start_time>i)return void console.log("OLD PING")}0===s&&product_sync_complete(o)}}}else 5<fb_sync_no_response_count++&&clearInterval(window.fb_pings)}})}function parse_response_check_connection(e){if(e){console.log(e);var o=e.substring(e.indexOf("{"));return(o=JSON.parse(o)).connected||window.fb_connected?o:(not_connected(),null)}return null}function check_feed_upload_queue(t){ajax("ajax_check_feed_upload_status",{_ajax_nonce:wc_facebook_settings_jsx.nonce},function(e){var o=document.querySelector("#sync_progress"),n=parse_response_check_connection(e);if(clearInterval(window.fb_feed_pings),n)switch(n.status){case"complete":window.feed_upload=!1,window.is_test?display_test_result():product_sync_complete(o);break;case"in progress":o&&(o.innerHTML='Syncing... Keep this browser open until sync is complete.<span class="spinner is-active"></span>'),ping_feed_status_queue(t+1);break;default:o&&(o.innerHTML="<strong>Something wrong when uploading, please try again.</strong>"),window.feed_upload=!1,window.is_test&&display_test_result()}})}function display_test_result(){ajax("ajax_display_test_result",{_ajax_nonce:wc_facebook_settings_jsx.nonce},function(e){var o=document.querySelector("#sync_complete"),n=document.querySelector("#sync_progress"),t=parse_response_check_connection(e);if(t)switch(t.pass){case"true":sync_not_in_progress(),o&&(o.style.display="",o.innerHTML="<strong>Status: </strong>Test Pass."),n&&(n.innerHTML=""),window.is_test=!1;break;case"in progress":n&&(n.innerHTML="<strong>Integration test in progress...</strong>"),ping_feed_status_queue();break;default:window.debug_info=t.debug_info+"<br/>"+t.stack_trace,o&&(o.style.display="",o.innerHTML="<strong>Status: </strong>Test Fail."),n&&(n.innerHTML=""),document.querySelector("#debug_info")&&(document.querySelector("#debug_info").style.display=""),window.is_test=!1}})}function show_debug_info(){var e=document.querySelector("#stack_trace");e&&(e.innerHTML=window.debug_info),document.querySelector("#debug_info")&&(document.querySelector("#debug_info").style.display="none"),window.debug_info=""}function fbe_init_nux_messages(){var r=window.jQuery;r(function(){r.each(r(".nux-message"),function(e,o){var n=r(o),t=n.data("target"),s=r("#"+t),i=s.position(),a=s.height()/2,c=s.outerWidth();n.css({top:Math.ceil(i.top+a)+"px",left:Math.ceil(i.left+c)+"px",display:"block"}),r(".nux-message-close-btn",n).click(function(){r(o).hide()})})})}function saveAutoSyncSchedule(){var e=document.getElementsByClassName("autosyncCheck")[0].checked,o=document.getElementsByClassName("autosyncTime")[0],n=(document.getElementsByClassName("autosyncSaveButton")[0],document.getElementsByClassName("autosyncSavedNotice")[0]);e?(o.removeAttribute("disabled"),n.style.transition="",n.style.opacity=1,setTimeout(function(){n.style.opacity=0,n.style.transition="opacity 5s"},3e3)):o.setAttribute("disabled",!0),ajax("ajax_schedule_force_resync",{enabled:e?1:0,time:o.value,_ajax_nonce:wc_facebook_settings_jsx.nonce})}function syncShortDescription(){var e=document.getElementsByClassName("syncShortDescription")[0].checked;ajax("ajax_update_fb_option",{option:"fb_sync_short_description",option_value:e?1:0,_ajax_nonce:wc_facebook_settings_jsx.nonce},null,function(){document.getElementsByClassName("syncShortDescription")[0].checked=!e,console.log("Failed to sync Short Description")})}window.location.href.includes("fb_force_resync")?window.onload=function(){sync_confirm("fb_force_resync")}:window.location.href.includes("fb_test_product_sync")&&(window.is_test=!0,window.onload=function(){sync_confirm("fb_test_product_sync")}),addAnEventListener(window,"message",iFrameListener),jQuery(document).ready(function(e){check_background_processor_status(),e("#woocommerce-facebook-settings-sync-products").click(function(e){e.preventDefault(),sync_confirm()})});
changelog.txt CHANGED
@@ -1,415 +1,425 @@
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
10
-
11
- 2019-06-18 - Version 1.9.13
12
- * Fix security issue
13
- * Add more contributors to the plugin
14
-
15
- 2019-05-02 - Version 1.9.12
16
- * Remove dead code which causes exception (Issue 975)
17
-
18
- 2019-02-26 - Version 1.9.11
19
- * changing contributor to facebook from facebook4woocommerce, so that
20
- woo plugin will be shown under https://profiles.wordpress.org/facebook/#content-plugins
21
- * adding changelog in readme.txt so that notifications will be sent for updates and
22
- changelog will be shown under https://wordpress.org/plugins/facebook-for-woocommerce/#developers
23
- * removing debug flags notice under facebook-for-woocommerce.php so that developers will
24
- be able to debug with debug logs
25
-
26
- 2019-02-11 - Version 1.9.10
27
- * Add facebook support link, this will help merchants to reach out to facebook customer service.
28
- * Make plugin wordpress compatible by removing woocommerce updater and removing woo_include
29
-
30
- 2018-12-30 - Version 1.9.9
31
- * Fix issue with missing file in v1.9.8
32
- * Remove misleading content relating to Instagram which is not launched yet.
33
-
34
- 2018-11-30 - Version 1.9.8
35
- * Prevent Show/Hide button auto scroll.
36
- * DIY entry field for FB product Image.
37
- * Initial Support for Advanced Bulk Edit.
38
- * Added Option to Use short description instead of description.
39
- * Added checkbox for disable sync with FB for dev environment.
40
- * Add New Advanced Option : Auto Force Resync on Schedule.
41
- * Don't sync out of stock items to FB depending on Woo setting.
42
- * Add custom price.
43
- * Fix 502 Bad Gateway Error When Redirect.
44
- * Sync composite product with calculating the price based on the price of sub-items.
45
- * Advanced Options Toggle in Configuration Screen.
46
-
47
- 2018-11-01 - Version 1.9.7
48
- * Support messenger chat customization dialog
49
- * Add Copyright header.
50
- * Fix lowercasting product description.
51
- * Fix Connect Woo AYMT Logic Flow and improve Logging.
52
-
53
- 2018-09-21 - Version 1.9.6
54
- * Update plugin description of new design for WooCommerce.
55
- * Remove get_date() in Woocommerce plugin.
56
- * Add External Action-to-Make Channel Support.
57
-
58
- 2018-08-14 - Version 1.9.5
59
- * Fix Subscription Event Injection
60
-
61
- 2018-08-10 - Version 1.9.4
62
- * Support Lead Gen Event - Contact Form 7
63
- * Separate Redirect Entry Point Logging
64
- * Fix undefined variable warnings.
65
- * Add a Settings link to the plugin config page from the WP Plugins page.
66
- * Adding filter to Pixel init for injection of fbq(consent, revoke) (GDPR)
67
-
68
- 2018-08-01 - Version 1.9.3
69
- * Add Edge Cases for Integration Test.
70
- * Fix Undefined PHP Warning.
71
- * Add subscribe event.
72
- * Fix Unable to Change Pixel after Setup.
73
- * Fix Integration Test Confirmation Dialog.
74
-
75
- 2018-07-08 - Version 1.9.2
76
- * Exclude Virtual Variation and Set Staging.
77
- * Add Version Number for Logging.
78
-
79
- 2018-06-22 - version 1.9.1
80
- * Fix Page Name Extra Space .
81
- * Remove Strange Box in Design.
82
-
83
- 2018-06-15 - version 1.9.0
84
- * Fix Performance Issue by Reusing Existing wp_query Object Contents.
85
- * Rename Long Column Which Causes UI Issues for Users in Product Overview.
86
- * Update Admin Notice Content.
87
- * Fix Weird ID Error Message.
88
- * Fix Product ID for Brand Attribute.
89
- * Don't use Checkout URL for Unknown Product Types.
90
- * Add Beta Integration Test for Syncing Products by Uploading Feed.
91
- * Fix Pixel Fired on Related Products.
92
- * Redesign Admin Panel.
93
- * Add Integration Test Entry Point Button.
94
-
95
- 2018-05-02 - version 1.8.7
96
- * Fix PHP Error Due to product_brand Taxonomy Not Existing
97
-
98
- 2018-04-26 - version 1.8.6
99
- * Potential Fix for Compatibility with Enhanced ECommerce Plugin
100
- * Reduce Fetch ID API Call for Hidden Products
101
- * Support Product Bundles Extension
102
- * Basic Support for WP All Import
103
- * Fix Trashing and Deletion
104
- * Fetch Brand and Support WooCommerce Brand Extension
105
- * Remove warning for duplicate SKUs
106
- * Remove upsell header on config page
107
- * Update Info Banner Content
108
- * Fix Warning When Deleting NON PRODUCT post
109
- * WPML Support : Language Selector
110
-
111
- 2018-04-17 - version 1.8.5
112
- * Fix ViewContent event incorrectly firing with content_type 'product'
113
- * Fix product group retailer id not matching ViewContent content id.
114
- * Added logging and defensive code to debug #348
115
- * Warning about Cart URL changes now clears with Force Resync
116
-
117
- 2018-04-06 - version 1.8.4
118
- * Fire AddToCart on Cart Viewed for shops which redirect to cart.
119
- * Fix HTML AJAX comment inside script breaking HTML optimizers.
120
- * Log product sync speed as a performance metric.
121
-
122
- 2018-03-30 - version 1.8.3
123
- * Fix Hidden Product Showing Up in Shop after Initial Sync.
124
- * Hiding a variable product now hides the product on FB.
125
- * Fix Variable Subscription Products Not Syncing Variants.
126
- * Support Default Variant in Plugin via Graph API: Create and Update.
127
- * Set Default Variant as Default Product in Feed.
128
-
129
- 2018-03-20 - version 1.8.2
130
- * Fix category column in feed.
131
- * Update Force Resync to use a feed if feed was used for initial sync. (~15x faster)
132
- * Fix all caps description products being rejected from feed.
133
- * Fix gallery images for variable products not showing in feed.
134
- * Fix multiple category error in feed upload.
135
-
136
- 2018-02-27 - version 1.8.1
137
- * Fix Upgrade TagName For Beta Version
138
-
139
- 2018-02-14 - version 1.8.0
140
- * Up to 15x Performance Improvement of Initial Product Sync by using a feed upload.
141
- * Fix Subscription Product Bug Due to API Change.
142
- * Fix Undefined Index Notices.
143
- * Ensure jQuery is Loaded before Using it.
144
- * Moved apply_filters from Constructor to wp_head Action.
145
- * Remove Atlernative Pixel Basecode Fetching.
146
- * [WordPress]Separate the Plugins to Different Directories
147
-
148
- 2018-02-01 - version 1.7.11
149
- * Fix permission error due to difference in data format between graphapi and feed uploading.
150
-
151
- 2018-01-31 - version 1.7.10
152
- * Disable Alternate Pixel Basecode fetching due to issues for some stores.
153
- * Fix variation carry main description if variant description is empty.
154
- * Add version check information.
155
-
156
- 2018-01-30 - version 1.7.9
157
- * Add Filter hook for other plugins to override pixel behavior.
158
- * Fix 500 errors when saving settings.
159
-
160
- 2018-01-25 - version 1.7.8
161
- * Fixes WC_Facebookcommerce_Pixel reference error
162
-
163
- 2018-01-22 - version 1.7.7
164
- * Fix purchase event not firing for Stripe.
165
- * Fix duplicate pixel issue for Initiate Checkout and Purchase events
166
- * Pixel basecode is fetched from Facebook when setup is completed.
167
- * Pixel proxy endpoints added for cases where pixel script cannot be loaded.
168
-
169
- 2018-01-03 - version 1.7.6
170
- * WordPress only plugin notice on missing Pixel ID.
171
- * WordPress only plugin direct link to settings page from plugin page.
172
-
173
- 2018-01-11 - version 1.7.5
174
- * Fix auto-updater to upgrade facebook-for-woocommerce only.
175
-
176
- 2018-01-05 - version 1.7.4
177
- * Fix purchase event not firing for some payment types.
178
- * Provide functionality to refresh API token when invalid.
179
- * Added Quick Edit Compatibility.
180
- * Fix Incompatibility with remove HTTP extension.
181
- * Added Bulk Edit Compatibility.
182
- * WPML compatibility: products in non-default language set to staging
183
- * Add support for variable subscription products.
184
-
185
- 2017-12-13 - version 1.7.3
186
- * Fix security hole that would allow a logged in user without
187
- manage_woocommerce permissions to toggle the fb_visibility of products.
188
- * Handle shortcodes.
189
- * Support product duplication.
190
-
191
- 2017-11-30 - version 1.7.2
192
- * Fix issue with get_plugin_data being called before it was loaded.
193
- * Improve perf of info dialog.
194
- * Move class loading and DB read into admin gate to fix site slowdown.
195
-
196
- 2017-11-29 - version 1.7.0
197
- * Enable auto-upgrading.
198
- * Remove the extra content IDs to fix match rate issue.
199
- * Only show 'Any' in attribute value string and show attribute name.
200
- * Fix trash/restore products visibility on Facebook Shop.
201
- * Fix deprecated function for variant description.
202
- * Add hook and fix checkout url for external products.
203
- * Clean HTML tag in product title.
204
- * Add info dialog on WooCommerce report, settings and status page.
205
-
206
- 2017-11-03 - version 1.6.6
207
- * Solve race condition to avoid minus remaining number in syncing process.
208
- * Fix sync of variant product description.
209
- * Enable line breaks in product main description.
210
- * Enable upsell message and redirect link visible for upgrading users.
211
- * Improve upsell message content and style.
212
-
213
- 2017-10-19 - version 1.6.5
214
- * Fix unterminated div tag. Thanks @pwag42
215
- * After 7 days, show a link to a new ads interface on the settings page.
216
-
217
- 2017-10-04 - version 1.6.4
218
- * Default to variant specific image as primary image for FB.
219
- * Add a Checkbox to allow override to use the parent product image.
220
- * Don't sync items which have zero price.
221
- * Existing 0 price items will be marked invisible after Force-Resync.
222
- * Support Syncing Bookable Items with 'Display Cost' set.
223
- * Fix 'Update' and 'Publish' for Bookable Items.
224
-
225
-
226
- 2017-10-03 - version 1.6.3
227
- * Use Bookable price when regular price doesn't exist.
228
- * Support Default Variations.
229
- * Fix warning when generating attribute names.
230
-
231
- 2017-09-28 - version 1.6.2
232
- * Fix "Invalid Parameter" API error caused by invalid sale dates.
233
- * Fix variable product unable to sync gallery images.
234
- * Cache gallery image urls for variable products to reduce DB load.
235
- * Fix exception during generation of some ViewCategory events.
236
-
237
- 2017-09-21 - version 1.6.1
238
- * Prevent save settings button for other WooCommerce plugins erasing FB settings.
239
-
240
- 2017-09-15 - version 1.6.0
241
- * Support sale start and end dates
242
- * Include tax on sale price as needed
243
- * Fix visibility toggle on the product page
244
- * No longer publish hidden products
245
- * Use variable products' attribute name, instead of slug name
246
- * Fix encoding issue in Variation names
247
- * Resolve W3 validation error caused by pixel code
248
- * Correctly sync variants where the attribute is not specified or set to 'any'.
249
- * Save Changes button no longer disappears in other settings pages
250
- * Fix a JS warning caused by ViewCategory events by switching to trackCustom
251
- * Fix quoted strings having unneeded slashes in the FB Description
252
- * Fix Unicode encoding in Category Names. Thanks @jancinert.
253
-
254
- 2017-09-05 - version 1.5.1
255
- * Fix critical issue with ViewContent events not matching products.
256
-
257
- 2017-09-05 - version 1.5.0
258
- * Added support for generic WordPress installations (without WooCommerce)
259
- * Added Search events
260
- * Setting page for Pixel ID and for enabling advanced measurement
261
- * Use featured image as primary image for variants, and variant images as additional images.
262
-
263
- 2017-08-25 - version 1.4.6
264
- * Fix issue where prices were rounded incorrectly
265
-
266
- 2017-08-15 - version 1.4.5
267
- * Prevent printed output from breaking the popupOrigin
268
- * Add composer.json file
269
-
270
- 2017-07-25 - version 1.4.4
271
- * Remove duplicate and blank content ids in pixel fires.
272
- * Fix warning when sale price is malformed.
273
-
274
- 2017-07-26 - version 1.4.3
275
- * Remove search event for admin panel searches (fix JS error preventing quick edit)
276
- * Prevent search event from firing twice
277
- * Add categories to items
278
- * Add re-configure button back if merchant is locked out (e.g. due to password change)
279
-
280
- 2017-06-20 - version 1.4.2
281
- * Add custom Facebook description box
282
- * Less yelling during product sync
283
- * Remove deprecation warnings
284
- * Fix RTL issue for JS loading (thanks Ariel!)
285
- * Validate gender param to FB enum
286
- * Fix product deletion for sub variants
287
-
288
- 2017-06-15 - version 1.4.1
289
- * Fix OOM/whitescreen of death error (constrain product count query to IDs)
290
-
291
- 2017-06-05 - version 1.4.0
292
- * Background sync! Products will now sync in the background if you keep your settings page open.
293
- * Fix purchase pixel fires for product variants
294
- * Fix state on hide/show buttons
295
- * Fix issue with incorrect price rounding
296
- * Delete FB metadata from product items as well as groups
297
- * Add GPL license to repo
298
-
299
- 2017-05-30 - version 1.3.3
300
- * Use display prices for regular prices (includes VAT if used)
301
- * Switch from checkboxes to buttons on Products page
302
- * Add "Reset Facebook metadata" option for merchants that duplicate products
303
- * Add "Delete on Facebook" option for products
304
- * Only delete products when actually deleted (vs trashed)
305
- * Allow pixel resetting after setup completion
306
- * Disable plugin if WP_DEBUG_DISPLAY is detected (prevent PHP notice injection)
307
-
308
- 2017-05-25 - version 1.3.2
309
- * Allow Force Resync button to resume stalled product syncs.
310
- * Handle WP_Error errors
311
-
312
- 2017-05-23 - version 1.3.1
313
- * Only use checkout urls if valid cart url is detected
314
- * Set priority and namespacing for all ajax functions
315
- * Workaround for staticxx domain redirects
316
-
317
- 2017-05-15 - version 1.3.0
318
- * Improve memory usage and remove OOM for large product catalogs
319
- * Split variant methods for product groups and product items (fix mismatches)
320
- * Make re-sync button work for updates as well as creates
321
- * Fix issue with newly added variants breaking out of their product group
322
- * Use default form values for variants if present
323
- * Strip registered shortcodes from descriptions via strip_shortcodes
324
- * Remove PHP warning about variable passed by reference
325
- * Capture and save existing FBIDs on "Duplicate Retailer ID" error
326
-
327
- 2017-05-11 - version 1.2.6
328
- * Remove use of post guid for product URLs
329
-
330
- 2017-05-06 - version 1.2.5
331
- * Ajax lockdown: transmit fb-specific code and set priority on save_settings
332
-
333
- 2017-05-05 - version 1.2.4
334
- * Fix issue with pixel selection step freezing due to missing site url.
335
-
336
- 2017-05-04 - version 1.2.3
337
- * Workaround for servers that send additional characters on ajax responses
338
-
339
- 2017-05-02 - version 1.2.2
340
- * Fix issues with non-english variant labels
341
- * Support gallery image urls
342
-
343
- 2017-05-02 - version 1.2.1
344
- * Use wc_get_cart_url to support custom cart URLs
345
- * Fix logging bug for users without manage_woocommerce attempting to set up
346
-
347
- 2017-04-27 - version 1.2.0
348
- * Allow regular admins with manage_woocommerce permission to use plugin
349
- * Lock settings during product sync
350
- * Do not allow product sync during an existing product sync
351
- * Validate catalog ID before product sync
352
- * Fix issue with resync products button
353
- * Fix issue with plugin breaking product search
354
-
355
- 2017-04-25 - version 1.1.0
356
- * Support duplicate SKUs via better retailer ID logic
357
- * Add "resync products" button
358
- * Update FB logging to support objects as well as strings
359
- * Add support for regular and sale pricing
360
-
361
- 2017-04-20 - version 1.0.3
362
- * Remove delete button (use Advanced Settings tab instead)
363
- * Cleaned up admin messages, prepended them with "Facebook for WooCommerce"
364
-
365
- 2017-04-13 - version 1.0.2
366
- * Remove Save Settings button when not in debug mode
367
-
368
- 2017-04-11 - version 1.0.1
369
- * Prevent blank item descriptions
370
- * Update copy on setup page
371
- * Sanitize settings before sending via fblog
372
- * Show better warnings for duplicate SKUs
373
-
374
- 2017-04-10 - version 1.0.0
375
- * First release! Woo!
376
-
377
- 2017-04-09 - version 0.8.0
378
- * Include status messages during background sync
379
- * Fix several PHP warnings and various WooCommerce 3.0 compatibility issues
380
-
381
- 2017-04-07 - version 0.7.2
382
- * Fix issue where products were not being created due to blank descriptions
383
-
384
- 2017-04-04 - version 0.7.1
385
- * Clean up output for checkboxes via printf
386
- * Bug fixes and performance improvements
387
-
388
- 2017-04-03 - version 0.7.0
389
- * Add new product visibility checkbox toggle for Facebook Shops on Product list
390
- * Official rename: Facebook for WooCommerce
391
-
392
- 2017-03-30 - version 0.6.0
393
- * Revert back to minor revisions for versioning
394
- * Add ability to reset all settings to start over
395
-
396
- 2017-03-20 - version 0.0.5
397
- * Fix "Get Started" button bug
398
- * Fix issue preventing "Use Advanced Matching" setting from properly saving
399
- * Various security fixes
400
-
401
- 2017-03-01 - version 0.0.4
402
-
403
- * Add Facebook metabox on product page with products' FB ID and a Visibility toggle (publish/unpublish).
404
-
405
- 2017-02-15 - version 0.0.3
406
-
407
- * Add a change log!
408
- * Modify plugin to check for both local and network installs of WooCommerce.
409
- * Disable plugin hooks that were triggering before setup was complete.
410
- * Escape HTML tags in product descriptions.
411
- * Use attribute label name instead of first-letter-capitalized slug.
412
-
413
- 2017-02-02 - version 0.0.2
414
-
415
- * Fix breaking when debug mode is disabled (null check in facebook-settings.js)
 
 
 
 
 
 
 
 
 
 
1
+ *** Facebook for WooCommerce Changelog ***
2
+
3
+ 2020.03.03 - version 1.10.0
4
+ * Feature - Exclude specific products, variations, product categories, and product tags from syncing to Facebook
5
+ * Feature - Add Facebook product settings like price and description to variations
6
+ * Feature - Revamped settings screen with on-site control over pixel, product sync, and Messenger behavior
7
+ * Tweak - Use Action Scheduler for the daily forced re-sync, if enabled
8
+ * Fix - Improve pixel tracking accuracy for add-to-cart events
9
+ * Misc. - Add the SkyVerge plugin framework as the plugin base
10
+ * Misc. - Require WooCommerce 3.5 and above
11
+
12
+ 2019-06-27 - Version 1.9.15
13
+ * CSRF handling for Ajax calls like ajax_woo_infobanner_post_click, ajax_woo_infobanner_post_xout, ajax_fb_toggle_visibility
14
+ * use phpcs to adhere to WP coding standards
15
+ * Minor UI changes on the iFrame
16
+
17
+ 2019-06-20 - Version 1.9.14
18
+ * Revisit CSRF security issue
19
+ * Remove rest controller which is not used
20
+
21
+ 2019-06-18 - Version 1.9.13
22
+ * Fix security issue
23
+ * Add more contributors to the plugin
24
+
25
+ 2019-05-02 - Version 1.9.12
26
+ * Remove dead code which causes exception (Issue 975)
27
+
28
+ 2019-02-26 - Version 1.9.11
29
+ * changing contributor to facebook from facebook4woocommerce, so that
30
+ woo plugin will be shown under https://profiles.wordpress.org/facebook/#content-plugins
31
+ * adding changelog in readme.txt so that notifications will be sent for updates and
32
+ changelog will be shown under https://wordpress.org/plugins/facebook-for-woocommerce/#developers
33
+ * removing debug flags notice under facebook-for-woocommerce.php so that developers will
34
+ be able to debug with debug logs
35
+
36
+ 2019-02-11 - Version 1.9.10
37
+ * Add facebook support link, this will help merchants to reach out to facebook customer service.
38
+ * Make plugin wordpress compatible by removing woocommerce updater and removing woo_include
39
+
40
+ 2018-12-30 - Version 1.9.9
41
+ * Fix issue with missing file in v1.9.8
42
+ * Remove misleading content relating to Instagram which is not launched yet.
43
+
44
+ 2018-11-30 - Version 1.9.8
45
+ * Prevent Show/Hide button auto scroll.
46
+ * DIY entry field for FB product Image.
47
+ * Initial Support for Advanced Bulk Edit.
48
+ * Added Option to Use short description instead of description.
49
+ * Added checkbox for disable sync with FB for dev environment.
50
+ * Add New Advanced Option : Auto Force Resync on Schedule.
51
+ * Don't sync out of stock items to FB depending on Woo setting.
52
+ * Add custom price.
53
+ * Fix 502 Bad Gateway Error When Redirect.
54
+ * Sync composite product with calculating the price based on the price of sub-items.
55
+ * Advanced Options Toggle in Configuration Screen.
56
+
57
+ 2018-11-01 - Version 1.9.7
58
+ * Support messenger chat customization dialog
59
+ * Add Copyright header.
60
+ * Fix lowercasting product description.
61
+ * Fix Connect Woo AYMT Logic Flow and improve Logging.
62
+
63
+ 2018-09-21 - Version 1.9.6
64
+ * Update plugin description of new design for WooCommerce.
65
+ * Remove get_date() in Woocommerce plugin.
66
+ * Add External Action-to-Make Channel Support.
67
+
68
+ 2018-08-14 - Version 1.9.5
69
+ * Fix Subscription Event Injection
70
+
71
+ 2018-08-10 - Version 1.9.4
72
+ * Support Lead Gen Event - Contact Form 7
73
+ * Separate Redirect Entry Point Logging
74
+ * Fix undefined variable warnings.
75
+ * Add a Settings link to the plugin config page from the WP Plugins page.
76
+ * Adding filter to Pixel init for injection of fbq(consent, revoke) (GDPR)
77
+
78
+ 2018-08-01 - Version 1.9.3
79
+ * Add Edge Cases for Integration Test.
80
+ * Fix Undefined PHP Warning.
81
+ * Add subscribe event.
82
+ * Fix Unable to Change Pixel after Setup.
83
+ * Fix Integration Test Confirmation Dialog.
84
+
85
+ 2018-07-08 - Version 1.9.2
86
+ * Exclude Virtual Variation and Set Staging.
87
+ * Add Version Number for Logging.
88
+
89
+ 2018-06-22 - version 1.9.1
90
+ * Fix Page Name Extra Space .
91
+ * Remove Strange Box in Design.
92
+
93
+ 2018-06-15 - version 1.9.0
94
+ * Fix Performance Issue by Reusing Existing wp_query Object Contents.
95
+ * Rename Long Column Which Causes UI Issues for Users in Product Overview.
96
+ * Update Admin Notice Content.
97
+ * Fix Weird ID Error Message.
98
+ * Fix Product ID for Brand Attribute.
99
+ * Don't use Checkout URL for Unknown Product Types.
100
+ * Add Beta Integration Test for Syncing Products by Uploading Feed.
101
+ * Fix Pixel Fired on Related Products.
102
+ * Redesign Admin Panel.
103
+ * Add Integration Test Entry Point Button.
104
+
105
+ 2018-05-02 - version 1.8.7
106
+ * Fix PHP Error Due to product_brand Taxonomy Not Existing
107
+
108
+ 2018-04-26 - version 1.8.6
109
+ * Potential Fix for Compatibility with Enhanced ECommerce Plugin
110
+ * Reduce Fetch ID API Call for Hidden Products
111
+ * Support Product Bundles Extension
112
+ * Basic Support for WP All Import
113
+ * Fix Trashing and Deletion
114
+ * Fetch Brand and Support WooCommerce Brand Extension
115
+ * Remove warning for duplicate SKUs
116
+ * Remove upsell header on config page
117
+ * Update Info Banner Content
118
+ * Fix Warning When Deleting NON PRODUCT post
119
+ * WPML Support : Language Selector
120
+
121
+ 2018-04-17 - version 1.8.5
122
+ * Fix ViewContent event incorrectly firing with content_type 'product'
123
+ * Fix product group retailer id not matching ViewContent content id.
124
+ * Added logging and defensive code to debug #348
125
+ * Warning about Cart URL changes now clears with Force Resync
126
+
127
+ 2018-04-06 - version 1.8.4
128
+ * Fire AddToCart on Cart Viewed for shops which redirect to cart.
129
+ * Fix HTML AJAX comment inside script breaking HTML optimizers.
130
+ * Log product sync speed as a performance metric.
131
+
132
+ 2018-03-30 - version 1.8.3
133
+ * Fix Hidden Product Showing Up in Shop after Initial Sync.
134
+ * Hiding a variable product now hides the product on FB.
135
+ * Fix Variable Subscription Products Not Syncing Variants.
136
+ * Support Default Variant in Plugin via Graph API: Create and Update.
137
+ * Set Default Variant as Default Product in Feed.
138
+
139
+ 2018-03-20 - version 1.8.2
140
+ * Fix category column in feed.
141
+ * Update Force Resync to use a feed if feed was used for initial sync. (~15x faster)
142
+ * Fix all caps description products being rejected from feed.
143
+ * Fix gallery images for variable products not showing in feed.
144
+ * Fix multiple category error in feed upload.
145
+
146
+ 2018-02-27 - version 1.8.1
147
+ * Fix Upgrade TagName For Beta Version
148
+
149
+ 2018-02-14 - version 1.8.0
150
+ * Up to 15x Performance Improvement of Initial Product Sync by using a feed upload.
151
+ * Fix Subscription Product Bug Due to API Change.
152
+ * Fix Undefined Index Notices.
153
+ * Ensure jQuery is Loaded before Using it.
154
+ * Moved apply_filters from Constructor to wp_head Action.
155
+ * Remove Atlernative Pixel Basecode Fetching.
156
+ * [WordPress]Separate the Plugins to Different Directories
157
+
158
+ 2018-02-01 - version 1.7.11
159
+ * Fix permission error due to difference in data format between graphapi and feed uploading.
160
+
161
+ 2018-01-31 - version 1.7.10
162
+ * Disable Alternate Pixel Basecode fetching due to issues for some stores.
163
+ * Fix variation carry main description if variant description is empty.
164
+ * Add version check information.
165
+
166
+ 2018-01-30 - version 1.7.9
167
+ * Add Filter hook for other plugins to override pixel behavior.
168
+ * Fix 500 errors when saving settings.
169
+
170
+ 2018-01-25 - version 1.7.8
171
+ * Fixes WC_Facebookcommerce_Pixel reference error
172
+
173
+ 2018-01-22 - version 1.7.7
174
+ * Fix purchase event not firing for Stripe.
175
+ * Fix duplicate pixel issue for Initiate Checkout and Purchase events
176
+ * Pixel basecode is fetched from Facebook when setup is completed.
177
+ * Pixel proxy endpoints added for cases where pixel script cannot be loaded.
178
+
179
+ 2018-01-03 - version 1.7.6
180
+ * WordPress only plugin notice on missing Pixel ID.
181
+ * WordPress only plugin direct link to settings page from plugin page.
182
+
183
+ 2018-01-11 - version 1.7.5
184
+ * Fix auto-updater to upgrade facebook-for-woocommerce only.
185
+
186
+ 2018-01-05 - version 1.7.4
187
+ * Fix purchase event not firing for some payment types.
188
+ * Provide functionality to refresh API token when invalid.
189
+ * Added Quick Edit Compatibility.
190
+ * Fix Incompatibility with remove HTTP extension.
191
+ * Added Bulk Edit Compatibility.
192
+ * WPML compatibility: products in non-default language set to staging
193
+ * Add support for variable subscription products.
194
+
195
+ 2017-12-13 - version 1.7.3
196
+ * Fix security hole that would allow a logged in user without
197
+ manage_woocommerce permissions to toggle the fb_visibility of products.
198
+ * Handle shortcodes.
199
+ * Support product duplication.
200
+
201
+ 2017-11-30 - version 1.7.2
202
+ * Fix issue with get_plugin_data being called before it was loaded.
203
+ * Improve perf of info dialog.
204
+ * Move class loading and DB read into admin gate to fix site slowdown.
205
+
206
+ 2017-11-29 - version 1.7.0
207
+ * Enable auto-upgrading.
208
+ * Remove the extra content IDs to fix match rate issue.
209
+ * Only show 'Any' in attribute value string and show attribute name.
210
+ * Fix trash/restore products visibility on Facebook Shop.
211
+ * Fix deprecated function for variant description.
212
+ * Add hook and fix checkout url for external products.
213
+ * Clean HTML tag in product title.
214
+ * Add info dialog on WooCommerce report, settings and status page.
215
+
216
+ 2017-11-03 - version 1.6.6
217
+ * Solve race condition to avoid minus remaining number in syncing process.
218
+ * Fix sync of variant product description.
219
+ * Enable line breaks in product main description.
220
+ * Enable upsell message and redirect link visible for upgrading users.
221
+ * Improve upsell message content and style.
222
+
223
+ 2017-10-19 - version 1.6.5
224
+ * Fix unterminated div tag. Thanks @pwag42
225
+ * After 7 days, show a link to a new ads interface on the settings page.
226
+
227
+ 2017-10-04 - version 1.6.4
228
+ * Default to variant specific image as primary image for FB.
229
+ * Add a Checkbox to allow override to use the parent product image.
230
+ * Don't sync items which have zero price.
231
+ * Existing 0 price items will be marked invisible after Force-Resync.
232
+ * Support Syncing Bookable Items with 'Display Cost' set.
233
+ * Fix 'Update' and 'Publish' for Bookable Items.
234
+
235
+
236
+ 2017-10-03 - version 1.6.3
237
+ * Use Bookable price when regular price doesn't exist.
238
+ * Support Default Variations.
239
+ * Fix warning when generating attribute names.
240
+
241
+ 2017-09-28 - version 1.6.2
242
+ * Fix "Invalid Parameter" API error caused by invalid sale dates.
243
+ * Fix variable product unable to sync gallery images.
244
+ * Cache gallery image urls for variable products to reduce DB load.
245
+ * Fix exception during generation of some ViewCategory events.
246
+
247
+ 2017-09-21 - version 1.6.1
248
+ * Prevent save settings button for other WooCommerce plugins erasing FB settings.
249
+
250
+ 2017-09-15 - version 1.6.0
251
+ * Support sale start and end dates
252
+ * Include tax on sale price as needed
253
+ * Fix visibility toggle on the product page
254
+ * No longer publish hidden products
255
+ * Use variable products' attribute name, instead of slug name
256
+ * Fix encoding issue in Variation names
257
+ * Resolve W3 validation error caused by pixel code
258
+ * Correctly sync variants where the attribute is not specified or set to 'any'.
259
+ * Save Changes button no longer disappears in other settings pages
260
+ * Fix a JS warning caused by ViewCategory events by switching to trackCustom
261
+ * Fix quoted strings having unneeded slashes in the FB Description
262
+ * Fix Unicode encoding in Category Names. Thanks @jancinert.
263
+
264
+ 2017-09-05 - version 1.5.1
265
+ * Fix critical issue with ViewContent events not matching products.
266
+
267
+ 2017-09-05 - version 1.5.0
268
+ * Added support for generic WordPress installations (without WooCommerce)
269
+ * Added Search events
270
+ * Setting page for Pixel ID and for enabling advanced measurement
271
+ * Use featured image as primary image for variants, and variant images as additional images.
272
+
273
+ 2017-08-25 - version 1.4.6
274
+ * Fix issue where prices were rounded incorrectly
275
+
276
+ 2017-08-15 - version 1.4.5
277
+ * Prevent printed output from breaking the popupOrigin
278
+ * Add composer.json file
279
+
280
+ 2017-07-25 - version 1.4.4
281
+ * Remove duplicate and blank content ids in pixel fires.
282
+ * Fix warning when sale price is malformed.
283
+
284
+ 2017-07-26 - version 1.4.3
285
+ * Remove search event for admin panel searches (fix JS error preventing quick edit)
286
+ * Prevent search event from firing twice
287
+ * Add categories to items
288
+ * Add re-configure button back if merchant is locked out (e.g. due to password change)
289
+
290
+ 2017-06-20 - version 1.4.2
291
+ * Add custom Facebook description box
292
+ * Less yelling during product sync
293
+ * Remove deprecation warnings
294
+ * Fix RTL issue for JS loading (thanks Ariel!)
295
+ * Validate gender param to FB enum
296
+ * Fix product deletion for sub variants
297
+
298
+ 2017-06-15 - version 1.4.1
299
+ * Fix OOM/whitescreen of death error (constrain product count query to IDs)
300
+
301
+ 2017-06-05 - version 1.4.0
302
+ * Background sync! Products will now sync in the background if you keep your settings page open.
303
+ * Fix purchase pixel fires for product variants
304
+ * Fix state on hide/show buttons
305
+ * Fix issue with incorrect price rounding
306
+ * Delete FB metadata from product items as well as groups
307
+ * Add GPL license to repo
308
+
309
+ 2017-05-30 - version 1.3.3
310
+ * Use display prices for regular prices (includes VAT if used)
311
+ * Switch from checkboxes to buttons on Products page
312
+ * Add "Reset Facebook metadata" option for merchants that duplicate products
313
+ * Add "Delete on Facebook" option for products
314
+ * Only delete products when actually deleted (vs trashed)
315
+ * Allow pixel resetting after setup completion
316
+ * Disable plugin if WP_DEBUG_DISPLAY is detected (prevent PHP notice injection)
317
+
318
+ 2017-05-25 - version 1.3.2
319
+ * Allow Force Resync button to resume stalled product syncs.
320
+ * Handle WP_Error errors
321
+
322
+ 2017-05-23 - version 1.3.1
323
+ * Only use checkout urls if valid cart url is detected
324
+ * Set priority and namespacing for all ajax functions
325
+ * Workaround for staticxx domain redirects
326
+
327
+ 2017-05-15 - version 1.3.0
328
+ * Improve memory usage and remove OOM for large product catalogs
329
+ * Split variant methods for product groups and product items (fix mismatches)
330
+ * Make re-sync button work for updates as well as creates
331
+ * Fix issue with newly added variants breaking out of their product group
332
+ * Use default form values for variants if present
333
+ * Strip registered shortcodes from descriptions via strip_shortcodes
334
+ * Remove PHP warning about variable passed by reference
335
+ * Capture and save existing FBIDs on "Duplicate Retailer ID" error
336
+
337
+ 2017-05-11 - version 1.2.6
338
+ * Remove use of post guid for product URLs
339
+
340
+ 2017-05-06 - version 1.2.5
341
+ * Ajax lockdown: transmit fb-specific code and set priority on save_settings
342
+
343
+ 2017-05-05 - version 1.2.4
344
+ * Fix issue with pixel selection step freezing due to missing site url.
345
+
346
+ 2017-05-04 - version 1.2.3
347
+ * Workaround for servers that send additional characters on ajax responses
348
+
349
+ 2017-05-02 - version 1.2.2
350
+ * Fix issues with non-english variant labels
351
+ * Support gallery image urls
352
+
353
+ 2017-05-02 - version 1.2.1
354
+ * Use wc_get_cart_url to support custom cart URLs
355
+ * Fix logging bug for users without manage_woocommerce attempting to set up
356
+
357
+ 2017-04-27 - version 1.2.0
358
+ * Allow regular admins with manage_woocommerce permission to use plugin
359
+ * Lock settings during product sync
360
+ * Do not allow product sync during an existing product sync
361
+ * Validate catalog ID before product sync
362
+ * Fix issue with resync products button
363
+ * Fix issue with plugin breaking product search
364
+
365
+ 2017-04-25 - version 1.1.0
366
+ * Support duplicate SKUs via better retailer ID logic
367
+ * Add "resync products" button
368
+ * Update FB logging to support objects as well as strings
369
+ * Add support for regular and sale pricing
370
+
371
+ 2017-04-20 - version 1.0.3
372
+ * Remove delete button (use Advanced Settings tab instead)
373
+ * Cleaned up admin messages, prepended them with "Facebook for WooCommerce"
374
+
375
+ 2017-04-13 - version 1.0.2
376
+ * Remove Save Settings button when not in debug mode
377
+
378
+ 2017-04-11 - version 1.0.1
379
+ * Prevent blank item descriptions
380
+ * Update copy on setup page
381
+ * Sanitize settings before sending via fblog
382
+ * Show better warnings for duplicate SKUs
383
+
384
+ 2017-04-10 - version 1.0.0
385
+ * First release! Woo!
386
+
387
+ 2017-04-09 - version 0.8.0
388
+ * Include status messages during background sync
389
+ * Fix several PHP warnings and various WooCommerce 3.0 compatibility issues
390
+
391
+ 2017-04-07 - version 0.7.2
392
+ * Fix issue where products were not being created due to blank descriptions
393
+
394
+ 2017-04-04 - version 0.7.1
395
+ * Clean up output for checkboxes via printf
396
+ * Bug fixes and performance improvements
397
+
398
+ 2017-04-03 - version 0.7.0
399
+ * Add new product visibility checkbox toggle for Facebook Shops on Product list
400
+ * Official rename: Facebook for WooCommerce
401
+
402
+ 2017-03-30 - version 0.6.0
403
+ * Revert back to minor revisions for versioning
404
+ * Add ability to reset all settings to start over
405
+
406
+ 2017-03-20 - version 0.0.5
407
+ * Fix "Get Started" button bug
408
+ * Fix issue preventing "Use Advanced Matching" setting from properly saving
409
+ * Various security fixes
410
+
411
+ 2017-03-01 - version 0.0.4
412
+
413
+ * Add Facebook metabox on product page with products' FB ID and a Visibility toggle (publish/unpublish).
414
+
415
+ 2017-02-15 - version 0.0.3
416
+
417
+ * Add a change log!
418
+ * Modify plugin to check for both local and network installs of WooCommerce.
419
+ * Disable plugin hooks that were triggering before setup was complete.
420
+ * Escape HTML tags in product descriptions.
421
+ * Use attribute label name instead of first-letter-capitalized slug.
422
+
423
+ 2017-02-02 - version 0.0.2
424
+
425
+ * Fix breaking when debug mode is disabled (null check in facebook-settings.js)
class-wc-facebookcommerce.php ADDED
@@ -0,0 +1,381 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
4
+ *
5
+ * This source code is licensed under the license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ *
8
+ * @package FacebookCommerce
9
+ */
10
+
11
+ use SkyVerge\WooCommerce\Facebook\Lifecycle;
12
+ use SkyVerge\WooCommerce\PluginFramework\v5_5_4 as Framework;
13
+
14
+ if ( ! class_exists( 'WC_Facebookcommerce' ) ) :
15
+
16
+ include_once 'includes/fbutils.php';
17
+
18
+ class WC_Facebookcommerce extends Framework\SV_WC_Plugin {
19
+
20
+
21
+ /** @var string the plugin version */
22
+ const VERSION = '1.10.0';
23
+
24
+ /** @var string for backwards compatibility TODO: remove this in v2.0.0 {CW 2020-02-06} */
25
+ const PLUGIN_VERSION = self::VERSION;
26
+
27
+ /** @var string the plugin ID */
28
+ const PLUGIN_ID = 'facebook_for_woocommerce';
29
+
30
+ /** @var string the integration ID */
31
+ const INTEGRATION_ID = 'facebookcommerce';
32
+
33
+ /** @var string the integration class name (including namespaces) */
34
+ const INTEGRATION_CLASS = '\\WC_Facebookcommerce_Integration';
35
+
36
+
37
+ /** @var \WC_Facebookcommerce singleton instance */
38
+ protected static $instance;
39
+
40
+ /** @var \WC_Facebookcommerce_Integration instance */
41
+ private $integration;
42
+
43
+ /** @var \SkyVerge\WooCommerce\Facebook\Admin admin handler instance */
44
+ private $admin;
45
+
46
+ /** @var \SkyVerge\WooCommerce\Facebook\AJAX Ajax handler instance */
47
+ private $ajax;
48
+
49
+
50
+ /**
51
+ * Constructs the plugin.
52
+ *
53
+ * @since 1.0.0
54
+ */
55
+ public function __construct() {
56
+
57
+ parent::__construct(
58
+ self::PLUGIN_ID,
59
+ self::VERSION,
60
+ [
61
+ 'text_domain' => 'facebook-for-woocommerce',
62
+ ]
63
+ );
64
+
65
+ $this->init();
66
+ }
67
+
68
+
69
+ /**
70
+ * Initializes the plugin.
71
+ *
72
+ * @internal
73
+ */
74
+ public function init() {
75
+
76
+ if ( \WC_Facebookcommerce_Utils::isWoocommerceIntegration() ) {
77
+
78
+ if ( ! defined( 'WOOCOMMERCE_FACEBOOK_PLUGIN_SETTINGS_URL' ) ) {
79
+ define( 'WOOCOMMERCE_FACEBOOK_PLUGIN_SETTINGS_URL', get_admin_url() . '/admin.php?page=wc-settings&tab=integration' . '&section=facebookcommerce' );
80
+ }
81
+
82
+ include_once 'facebook-commerce.php';
83
+
84
+ require_once __DIR__ . '/includes/Products.php';
85
+ require_once __DIR__ . '/facebook-commerce-messenger-chat.php';
86
+
87
+ if ( is_ajax() ) {
88
+
89
+ require_once __DIR__ . '/includes/AJAX.php';
90
+
91
+ $this->ajax = new \SkyVerge\WooCommerce\Facebook\AJAX();
92
+ }
93
+
94
+ // register the WooCommerce integration
95
+ add_filter( 'woocommerce_integrations', [ $this, 'add_woocommerce_integration' ] );
96
+ }
97
+ }
98
+
99
+
100
+ /**
101
+ * Initializes the admin handling.
102
+ *
103
+ * @internal
104
+ *
105
+ * @since 1.10.0
106
+ */
107
+ public function init_admin() {
108
+
109
+ require_once __DIR__ . '/includes/Admin.php';
110
+
111
+ $this->admin = new \SkyVerge\WooCommerce\Facebook\Admin();
112
+ }
113
+
114
+
115
+ /**
116
+ * Adds a Facebook integration to WooCommerce.
117
+ *
118
+ * @internal
119
+ *
120
+ * @since 1.0.0
121
+ *
122
+ * @param string[] $integrations class names
123
+ * @return string[]
124
+ */
125
+ public function add_woocommerce_integration( $integrations = [] ) {
126
+
127
+ if ( ! class_exists( self::INTEGRATION_CLASS ) ) {
128
+ include_once __DIR__ . '/facebook-commerce.php';
129
+ }
130
+
131
+ $integrations[ self::INTEGRATION_ID ] = self::INTEGRATION_CLASS;
132
+
133
+ return $integrations;
134
+ }
135
+
136
+
137
+ public function add_wordpress_integration() {
138
+ new WP_Facebook_Integration();
139
+ }
140
+
141
+
142
+ /** Getter methods ********************************************************************************************/
143
+
144
+
145
+ /**
146
+ * Gets the admin handler instance.
147
+ *
148
+ * @since 1.10.0
149
+ *
150
+ * @return \SkyVerge\WooCommerce\Facebook\Admin|null
151
+ */
152
+ public function get_admin_handler() {
153
+
154
+ return $this->admin;
155
+ }
156
+
157
+
158
+ /**
159
+ * Gets the AJAX handler instance.
160
+ *
161
+ * @sinxe 1.10.0
162
+ *
163
+ * @return \SkyVerge\WooCommerce\Facebook\AJAX|null
164
+ */
165
+ public function get_ajax_handler() {
166
+
167
+ return $this->ajax;
168
+ }
169
+
170
+
171
+ /**
172
+ * Gets the integration instance.
173
+ *
174
+ * @since 1.10.0
175
+ *
176
+ * @return \WC_Facebookcommerce_Integration instance
177
+ */
178
+ public function get_integration() {
179
+
180
+ if ( null === $this->integration ) {
181
+
182
+ $integrations = null === WC()->integrations ? [] : WC()->integrations->get_integrations();
183
+ $integration = self::INTEGRATION_CLASS;
184
+
185
+ if ( isset( $integrations[ self::INTEGRATION_ID ] ) && $integrations[ self::INTEGRATION_ID ] instanceof $integration ) {
186
+
187
+ $this->integration = $integrations[ self::INTEGRATION_ID ];
188
+
189
+ } else {
190
+
191
+ $this->add_woocommerce_integration();
192
+
193
+ $this->integration = new $integration();
194
+ }
195
+ }
196
+
197
+ return $this->integration;
198
+ }
199
+
200
+
201
+ /**
202
+ * Gets the settings page URL.
203
+ *
204
+ * @since 1.10.0
205
+ *
206
+ * @param null $plugin_id unused
207
+ * @return string
208
+ */
209
+ public function get_settings_url( $plugin_id = null ) {
210
+
211
+ return admin_url( 'admin.php?page=wc-settings&tab=integration&section=' . self::INTEGRATION_ID );
212
+ }
213
+
214
+
215
+ /**
216
+ * Gets the plugin's documentation URL.
217
+ *
218
+ * @since 1.10.0
219
+ *
220
+ * @return string
221
+ */
222
+ public function get_documentation_url() {
223
+
224
+ return 'https://docs.woocommerce.com/document/facebook-for-woocommerce/';
225
+ }
226
+
227
+
228
+ /**
229
+ * Gets the plugin's support URL.
230
+ *
231
+ * @since 1.10.0
232
+ *
233
+ * @return string
234
+ */
235
+ public function get_support_url() {
236
+
237
+ return 'https://woocommerce.com/my-account/tickets/';
238
+ }
239
+
240
+
241
+ /**
242
+ * Gets the plugin's sales page URL.
243
+ *
244
+ * @since 1.10.0
245
+ *
246
+ * @return string
247
+ */
248
+ public function get_sales_page_url() {
249
+
250
+ return 'https://woocommerce.com/products/facebook/';
251
+ }
252
+
253
+
254
+ /**
255
+ * Gets the plugin's reviews URL.
256
+ *
257
+ * @since 1.10.0
258
+ *
259
+ * @return string
260
+ */
261
+ public function get_reviews_url() {
262
+
263
+ return 'https://wordpress.org/support/plugin/facebook-for-woocommerce/reviews/';
264
+ }
265
+
266
+
267
+ /**
268
+ * Gets the plugin name.
269
+ *
270
+ * @since 1.10.0
271
+ *
272
+ * @return string
273
+ */
274
+ public function get_plugin_name() {
275
+
276
+ return __( 'Facebook for WooCommerce', 'facebook-for-woocommerce' );
277
+ }
278
+
279
+
280
+ /** Conditional methods ***************************************************************************************/
281
+
282
+
283
+ /**
284
+ * Determines if viewing the plugin settings in the admin.
285
+ *
286
+ * @since 1.10.0
287
+ *
288
+ * @return bool
289
+ */
290
+ public function is_plugin_settings() {
291
+
292
+ $page = Framework\SV_WC_Helper::get_requested_value( 'page' );
293
+ $tab = Framework\SV_WC_Helper::get_requested_value( 'tab' );
294
+ $section = Framework\SV_WC_Helper::get_requested_value( 'section' );
295
+
296
+ return is_admin() && 'wc-settings' === $page && 'integration' === $tab && self::INTEGRATION_ID === $section;
297
+ }
298
+
299
+
300
+ /** Utility methods *******************************************************************************************/
301
+
302
+
303
+ /**
304
+ * Initializes the lifecycle handler.
305
+ *
306
+ * @since 1.10.0
307
+ */
308
+ protected function init_lifecycle_handler() {
309
+
310
+ require_once __DIR__ . '/includes/Lifecycle.php';
311
+
312
+ $this->lifecycle_handler = new Lifecycle( $this );
313
+ }
314
+
315
+
316
+ /**
317
+ * Gets the plugin singleton instance.
318
+ *
319
+ * @see \facebook_for_woocommerce()
320
+ *
321
+ * @since 1.10.0
322
+ *
323
+ * @return \WC_Facebookcommerce the plugin singleton instance
324
+ */
325
+ public static function instance() {
326
+
327
+ if ( null === self::$instance ) {
328
+ self::$instance = new self();
329
+ }
330
+
331
+ return self::$instance;
332
+ }
333
+
334
+
335
+ /**
336
+ * Gets the plugin file.
337
+ *
338
+ * @since 1.10.0
339
+ *
340
+ * @return string
341
+ */
342
+ protected function get_file() {
343
+
344
+ return __FILE__;
345
+ }
346
+
347
+
348
+ /** Deprecated methods ****************************************************************************************/
349
+
350
+
351
+ /**
352
+ * Adds the settings link on the plugin page.
353
+ *
354
+ * @internal
355
+ *
356
+ * @since 1.10.0
357
+ * @deprecated 1.10.0
358
+ */
359
+ public function add_settings_link() {
360
+
361
+ wc_deprecated_function( __METHOD__, '1.10.0' );
362
+ }
363
+
364
+
365
+ }
366
+
367
+
368
+ /**
369
+ * Gets the Facebook for WooCommerce plugin instance.
370
+ *
371
+ * @since 1.10.0
372
+ *
373
+ * @return \WC_Facebookcommerce instance of the plugin
374
+ */
375
+ function facebook_for_woocommerce() {
376
+
377
+ return \WC_Facebookcommerce::instance();
378
+ }
379
+
380
+
381
+ endif;
composer.json DELETED
@@ -1,16 +0,0 @@
1
- {
2
- "name": "facebookincubator/facebook-for-woocommerce",
3
- "description": "Grow your business with Facebook for WooCommerce! This plugin will install a Facebook Pixel and optionally create a shop on your Facebook page.",
4
- "type": "wordpress-plugin",
5
- "license": "GPL-2.0+",
6
- "require": {
7
- "composer/installers": "~1.0",
8
- "php": ">=5.6"
9
- },
10
- "repositories": [
11
- {
12
- "type": "git",
13
- "url" : "git@github.com:facebookincubator/facebook-for-woocommerce.git"
14
- }
15
- ]
16
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
facebook-commerce-events-tracker.php CHANGED
@@ -38,11 +38,10 @@ if ( ! class_exists( 'WC_Facebookcommerce_EventsTracker' ) ) :
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' )
@@ -51,20 +50,17 @@ if ( ! class_exists( 'WC_Facebookcommerce_EventsTracker' ) ) :
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' )
@@ -94,27 +90,33 @@ if ( ! class_exists( 'WC_Facebookcommerce_EventsTracker' ) ) :
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
  */
@@ -202,178 +204,237 @@ if ( ! class_exists( 'WC_Facebookcommerce_EventsTracker' ) ) :
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.
@@ -428,6 +489,90 @@ if ( ! class_exists( 'WC_Facebookcommerce_EventsTracker' ) ) :
428
  );
429
  }
430
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
431
  }
432
 
433
  endif;
38
  'wp_footer',
39
  array( $this, 'inject_base_pixel_noscript' )
40
  );
41
+
42
+ // ViewContent for individual products
43
+ add_action( 'woocommerce_after_single_product', [ $this, 'inject_view_content_event' ] );
44
+
 
45
  add_action(
46
  'woocommerce_after_shop_loop',
47
  array( $this, 'inject_view_category_event' )
50
  'pre_get_posts',
51
  array( $this, 'inject_search_event' )
52
  );
53
+
54
+ // AddToCart
55
+ add_action( 'woocommerce_add_to_cart', [ $this, 'inject_add_to_cart_event' ], 40, 4 );
56
+ // AddToCart while AJAX is enabled
57
+ add_action( 'woocommerce_ajax_added_to_cart', [ $this, 'add_filter_for_add_to_cart_fragments' ] );
58
+ // AddToCart while using redirect to cart page
59
+ if ( 'yes' === get_option( 'woocommerce_cart_redirect_after_add' ) ) {
60
+ add_filter( 'woocommerce_add_to_cart_redirect', [ $this, 'set_last_product_added_to_cart_upon_redirect' ], 10, 2 );
61
+ add_action( 'woocommerce_after_cart', [ $this, 'inject_add_to_cart_redirect_event' ], 10, 2 );
62
+ }
63
+
 
 
 
64
  add_action(
65
  'woocommerce_after_checkout_form',
66
  array( $this, 'inject_initiate_checkout_event' )
90
  );
91
  }
92
 
93
+
94
  /**
95
+ * Prints the base JavaScript pixel code.
 
 
96
  */
97
  public function inject_base_pixel() {
98
+
99
  if ( self::$isEnabled ) {
100
+ // phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped
101
  echo $this->pixel->pixel_base_code();
102
  }
103
  }
104
 
105
+
106
  /**
107
+ * Prints the base <noscript> pixel code.
108
+ *
109
+ * This is necessary to avoid W3 validation errors.
110
  */
111
  public function inject_base_pixel_noscript() {
112
+
113
  if ( self::$isEnabled ) {
114
+ // phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped
115
  echo $this->pixel->pixel_base_code_noscript();
116
  }
117
  }
118
 
119
+
120
  /**
121
  * Triggers ViewCategory for product category listings
122
  */
204
  );
205
  }
206
 
 
 
 
 
 
 
 
 
 
 
 
 
 
207
 
208
  /**
209
+ * Triggers ViewContent event on product pages
210
+ *
211
+ * @internal
212
  */
213
  public function inject_view_content_event() {
214
+ global $post;
215
+
216
+ if ( ! self::$isEnabled || ! isset( $post->ID ) ) {
217
  return;
218
  }
219
+
220
+ $product = wc_get_product( $post->ID );
221
+
222
+ if ( ! $product instanceof \WC_Product ) {
223
  return;
224
  }
225
 
226
+ // if product is variable or grouped, fire the pixel with content_type: product_group
227
+ if ( $product->is_type( [ 'variable', 'grouped' ] ) ) {
228
+ $content_type = 'product_group';
229
+ } else {
230
  $content_type = 'product';
231
  }
232
 
233
+ $this->pixel->inject_event( 'ViewContent', [
234
+ 'content_name' => $product->get_title(),
235
+ 'content_ids' => wp_json_encode( \WC_Facebookcommerce_Utils::get_fb_content_ids( $product ) ),
236
+ 'content_type' => $content_type,
237
+ 'value' => $product->get_price(),
238
+ 'currency' => get_woocommerce_currency(),
239
+ ] );
 
 
 
 
240
  }
241
 
242
+
243
  /**
244
+ * Triggers an AddToCart event when a product is added to cart.
245
+ *
246
+ * @internal
247
+ *
248
+ * @param string $cart_item_key the cart item key
249
+ * @param int $product_id the product identifier
250
+ * @param int $quantity the added product quantity
251
+ * @param int $variation_id the product variation identifier
252
  */
253
+ public function inject_add_to_cart_event( $cart_item_key, $product_id, $quantity, $variation_id ) {
254
+
255
+ // bail if pixel tracking disabled or invalid variables
256
+ if ( ! self::$isEnabled || ! $product_id || ! $quantity ) {
257
  return;
258
  }
259
 
260
+ $product = wc_get_product( $variation_id ?: $product_id );
261
 
262
+ // bail if invalid product or error
263
+ if ( ! $product instanceof \WC_Product ) {
264
+ return;
265
+ }
266
+
267
+ $this->pixel->inject_event( 'AddToCart', [
268
+ 'content_ids' => $this->get_cart_content_ids(),
269
+ 'content_type' => 'product',
270
+ 'contents' => $this->get_cart_contents(),
271
+ 'value' => $this->get_cart_total(),
272
+ 'currency' => get_woocommerce_currency(),
273
+ ] );
274
  }
275
 
276
+
277
  /**
278
+ * Setups a filter to add an add to cart fragment whenever a product is added to the cart through Ajax.
279
+ *
280
+ * @see \WC_Facebookcommerce_EventsTracker::add_add_to_cart_event_fragment
281
+ *
282
+ * @internal
283
+ *
284
+ * @since x.y.z
285
  */
286
+ public function add_filter_for_add_to_cart_fragments() {
287
+
288
+ if ( 'no' === get_option( 'woocommerce_cart_redirect_after_add' ) ) {
289
+ add_filter( 'woocommerce_add_to_cart_fragments', [ $this, 'add_add_to_cart_event_fragment' ] );
290
  }
291
+ }
292
 
 
293
 
294
+ /**
295
+ * Adds an add to cart fragment to trigger an AddToCart event.
296
+ *
297
+ * @internal
298
+ *
299
+ * @since x.y.z
300
+ *
301
+ * @param array $fragments add to cart fragments
302
+ * @return array
303
+ */
304
+ public function add_add_to_cart_event_fragment( $fragments ) {
305
 
306
+ if ( self::$isEnabled ) {
307
 
308
+ $script = $this->pixel->get_event_script( 'AddToCart', [
309
+ 'content_ids' => $this->get_cart_content_ids(),
 
 
310
  'content_type' => 'product',
311
+ 'contents' => $this->get_cart_contents(),
312
+ 'value' => $this->get_cart_total(),
313
  'currency' => get_woocommerce_currency(),
314
+ ] );
315
+
316
+ $fragments['div.wc-facebook-pixel-event-placeholder'] = '<div class="wc-facebook-pixel-event-placeholder">' . $script . '</div>';
317
+ }
318
+
319
+ return $fragments;
320
+ }
321
+
322
+
323
+ /**
324
+ * Sends a JSON response with the JavaScript code to track an AddToCart event.
325
+ *
326
+ * @internal
327
+ * @deprecated since x.y.z
328
+ */
329
+ public function inject_ajax_add_to_cart_event() {
330
+
331
+ wc_deprecated_function( __METHOD__, 'x.y.z' );
332
+ }
333
+
334
 
335
+ /**
336
+ * Sets last product added to cart to session when adding to cart a product and redirection to cart is enabled.
337
+ *
338
+ * @internal
339
+ *
340
+ * @since x.y.z
341
+ *
342
+ * @param string $redirect URL redirecting to (usually cart)
343
+ * @param \WC_Product $product the product just added to the cart
344
+ * @return string
345
+ */
346
+ public function set_last_product_added_to_cart_upon_redirect( $redirect, $product ) {
347
 
348
+ if ( $product instanceof \WC_Product ) {
349
+ WC()->session->set( 'facebook_for_woocommerce_last_product_added_to_cart', $product->get_id() );
350
+ }
351
+
352
+ return $redirect;
353
  }
354
 
355
+
356
  /**
357
+ * Triggers an AddToCart event when redirecting to the cart page.
358
+ *
359
+ * @internal
360
  */
361
  public function inject_add_to_cart_redirect_event() {
362
+
363
  if ( ! self::$isEnabled ) {
364
  return;
365
  }
366
+
367
+ $last_product_id = WC()->session->get( 'facebook_for_woocommerce_last_product_added_to_cart', 0 );
368
+
369
+ if ( $last_product_id > 0 ) {
370
+
371
+ $this->inject_add_to_cart_event( '', $last_product_id, 1, 0 );
372
+
373
+ WC()->session->set( 'facebook_for_woocommerce_last_product_added_to_cart', 0 );
374
  }
375
  }
376
 
377
+
378
  /**
379
+ * Triggers InitiateCheckout for checkout page.
380
  */
381
  public function inject_initiate_checkout_event() {
382
+
383
+ if ( ! self::$isEnabled || $this->pixel->check_last_event( 'InitiateCheckout' ) ) {
384
  return;
385
  }
386
 
387
+ $this->pixel->inject_event( 'InitiateCheckout', [
388
+ 'num_items' => $this->get_cart_num_items(),
389
+ 'content_ids' => $this->get_cart_content_ids(),
390
+ 'content_type' => 'product',
391
+ 'value' => $this->get_cart_total(),
392
+ 'currency' => get_woocommerce_currency(),
393
+ ] );
 
 
 
 
 
394
  }
395
 
396
+
397
  /**
398
+ * Triggers Purchase for payment transaction complete and for the thank you page in cases of delayed payment.
399
+ *
400
+ * @param int $order_id order identifier
401
  */
402
  public function inject_purchase_event( $order_id ) {
403
+
404
+ if ( ! self::$isEnabled || $this->pixel->check_last_event( 'Purchase' ) ) {
405
  return;
406
  }
407
 
408
  $this->inject_subscribe_event( $order_id );
409
 
410
+ $order = new \WC_Order( $order_id );
411
  $content_type = 'product';
412
+ $product_ids = [ [] ];
413
+
414
  foreach ( $order->get_items() as $item ) {
415
+
416
+ if ( $product = isset( $item['product_id'] ) ? wc_get_product( $item['product_id'] ) : null ) {
417
+
418
+ $product_ids[] = \WC_Facebookcommerce_Utils::get_fb_content_ids( $product );
419
+
420
+ if ( 'product_group' !== $content_type && $product->is_type( 'variable' ) ) {
421
+ $content_type = 'product_group';
422
+ }
423
  }
424
  }
425
 
426
+ $product_ids = wp_json_encode( array_merge( ... $product_ids ) );
427
+
428
+ $this->pixel->inject_event( 'Purchase', [
429
+ 'num_items' => $this->get_cart_num_items(),
430
+ 'content_ids' => $product_ids,
431
+ 'content_type' => $content_type,
432
+ 'value' => $order->get_total(),
433
+ 'currency' => get_woocommerce_currency(),
434
+ ] );
435
  }
436
 
437
+
438
  /**
439
  * Triggers Subscribe for payment transaction complete of purchase with
440
  * subscription.
489
  );
490
  }
491
  }
492
+
493
+
494
+ /**
495
+ * Gets the cart content items count.
496
+ *
497
+ * @since x.y.z
498
+ *
499
+ * @return int
500
+ */
501
+ private function get_cart_num_items() {
502
+
503
+ return WC()->cart ? WC()->cart->get_cart_contents_count() : 0;
504
+ }
505
+
506
+
507
+ /**
508
+ * Gets all content IDs from cart.
509
+ *
510
+ * @since x.y.z
511
+ *
512
+ * @return string JSON data
513
+ */
514
+ private function get_cart_content_ids() {
515
+
516
+ $product_ids = [ [] ];
517
+
518
+ if ( $cart = WC()->cart ) {
519
+
520
+ foreach ( $cart->get_cart() as $item ) {
521
+
522
+ if ( isset( $item['data'] ) && $item['data'] instanceof \WC_Product ) {
523
+
524
+ $product_ids[] = \WC_Facebookcommerce_Utils::get_fb_content_ids( $item['data'] );
525
+ }
526
+ }
527
+ }
528
+
529
+ return wp_json_encode( array_unique( array_merge( ... $product_ids ) ) );
530
+ }
531
+
532
+
533
+ /**
534
+ * Gets the cart content data.
535
+ *
536
+ * @since x.y.z
537
+ *
538
+ * @return string JSON data
539
+ */
540
+ private function get_cart_contents() {
541
+
542
+ $cart_contents = [];
543
+
544
+ if ( $cart = WC()->cart ) {
545
+
546
+ foreach ( $cart->get_cart() as $item ) {
547
+
548
+ if ( ! isset( $item['data'], $item['quantity'] ) || ! $item['data'] instanceof \WC_Product ) {
549
+ continue;
550
+ }
551
+
552
+ $content = new \stdClass();
553
+
554
+ $content->id = \WC_Facebookcommerce_Utils::get_fb_retailer_id( $item['data'] );
555
+ $content->quantity = $item['quantity'];
556
+
557
+ $cart_contents[] = $content;
558
+ }
559
+ }
560
+
561
+ return wp_json_encode( $cart_contents );
562
+ }
563
+
564
+
565
+ /**
566
+ * Gets the cart total.
567
+ *
568
+ * @return float|int
569
+ */
570
+ private function get_cart_total() {
571
+
572
+ return WC()->cart ? WC()->cart->total : 0;
573
+ }
574
+
575
+
576
  }
577
 
578
  endif;
facebook-commerce-messenger-chat.php CHANGED
@@ -16,74 +16,263 @@ if ( ! class_exists( 'WC_Facebookcommerce_MessengerChat' ) ) :
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\"
54
- %s
55
- %s
56
- %s /></div>
57
- <!-- Facebook JSSDK -->
58
- <script>
59
- window.fbAsyncInit = function() {
60
- FB.init({
61
- appId : '',
62
- autoLogAppEvents : true,
63
- xfbml : true,
64
- version : '%s'
65
- });
66
- };
67
-
68
- (function(d, s, id){
69
- var js, fjs = d.getElementsByTagName(s)[0];
70
- if (d.getElementById(id)) {return;}
71
- js = d.createElement(s); js.id = id;
72
- js.src = 'https://connect.facebook.net/%s/sdk/xfbml.customerchat.js';
73
- fjs.parentNode.insertBefore(js, fjs);
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;
16
 
17
  class WC_Facebookcommerce_MessengerChat {
18
 
19
+
20
+ /** @var string[] list of supported locales */
21
+ private static $supported_locales = [
22
+ 'af_ZA',
23
+ 'ar_AR',
24
+ 'as_IN',
25
+ 'az_AZ',
26
+ 'be_BY',
27
+ 'bg_BG',
28
+ 'bn_IN',
29
+ 'br_FR',
30
+ 'bs_BA',
31
+ 'ca_ES',
32
+ 'cb_IQ',
33
+ 'co_FR',
34
+ 'cs_CZ',
35
+ 'cx_PH',
36
+ 'cy_GB',
37
+ 'da_DK',
38
+ 'de_DE',
39
+ 'el_GR',
40
+ 'en_GB',
41
+ 'en_US',
42
+ 'es_ES',
43
+ 'es_LA',
44
+ 'et_EE',
45
+ 'eu_ES',
46
+ 'fa_IR',
47
+ 'ff_NG',
48
+ 'fi_FI',
49
+ 'fo_FO',
50
+ 'fr_CA',
51
+ 'fr_FR',
52
+ 'fy_NL',
53
+ 'ga_IE',
54
+ 'gl_ES',
55
+ 'gn_PY',
56
+ 'gu_IN',
57
+ 'ha_NG',
58
+ 'he_IL',
59
+ 'hi_IN',
60
+ 'hr_HR',
61
+ 'hu_HU',
62
+ 'hy_AM',
63
+ 'id_ID',
64
+ 'is_IS',
65
+ 'it_IT',
66
+ 'ja_JP',
67
+ 'ja_KS',
68
+ 'jv_ID',
69
+ 'ka_GE',
70
+ 'kk_KZ',
71
+ 'km_KH',
72
+ 'kn_IN',
73
+ 'ko_KR',
74
+ 'ku_TR',
75
+ 'lt_LT',
76
+ 'lv_LV',
77
+ 'mg_MG',
78
+ 'mk_MK',
79
+ 'ml_IN',
80
+ 'mn_MN',
81
+ 'mr_IN',
82
+ 'ms_MY',
83
+ 'mt_MT',
84
+ 'my_MM',
85
+ 'nb_NO',
86
+ 'ne_NP',
87
+ 'nl_BE',
88
+ 'nl_NL',
89
+ 'nn_NO',
90
+ 'or_IN',
91
+ 'pa_IN',
92
+ 'pl_PL',
93
+ 'ps_AF',
94
+ 'pt_BR',
95
+ 'pt_PT',
96
+ 'qz_MM',
97
+ 'ro_RO',
98
+ 'ru_RU',
99
+ 'rw_RW',
100
+ 'sc_IT',
101
+ 'si_LK',
102
+ 'sk_SK',
103
+ 'sl_SI',
104
+ 'so_SO',
105
+ 'sq_AL',
106
+ 'sr_RS',
107
+ 'sv_SE',
108
+ 'sw_KE',
109
+ 'sz_PL',
110
+ 'ta_IN',
111
+ 'te_IN',
112
+ 'tg_TJ',
113
+ 'th_TH',
114
+ 'tl_PH',
115
+ 'tr_TR',
116
+ 'tz_MA',
117
+ 'uk_UA',
118
+ 'ur_PK',
119
+ 'uz_UZ',
120
+ 'vi_VN',
121
+ 'zh_CN',
122
+ 'zh_HK',
123
+ 'zh_TW',
124
+ ];
125
+
126
+
127
  public function __construct( $settings ) {
128
  $this->enabled = isset( $settings['is_messenger_chat_plugin_enabled'] )
129
+ ? $settings['is_messenger_chat_plugin_enabled']
130
+ : 'no';
131
 
132
  $this->page_id = isset( $settings['fb_page_id'] )
133
+ ? $settings['fb_page_id']
134
+ : '';
135
 
136
  $this->jssdk_version = isset( $settings['facebook_jssdk_version'] )
137
+ ? $settings['facebook_jssdk_version']
138
+ : '';
139
 
140
  $this->greeting_text_code = isset( $settings['msger_chat_customization_greeting_text_code'] )
141
+ ? $settings['msger_chat_customization_greeting_text_code']
142
+ : null;
143
 
144
  $this->locale = isset( $settings['msger_chat_customization_locale'] )
145
+ ? $settings['msger_chat_customization_locale']
146
+ : null;
147
 
148
  $this->theme_color_code = isset( $settings['msger_chat_customization_theme_color_code'] )
149
+ ? $settings['msger_chat_customization_theme_color_code']
150
+ : null;
151
 
152
  add_action( 'wp_footer', array( $this, 'inject_messenger_chat_plugin' ) );
153
  }
154
 
155
+
156
+ /**
157
+ * Outputs the Facebook Messenger chat script.
158
+ *
159
+ * @internal
160
+ */
161
  public function inject_messenger_chat_plugin() {
162
+
163
+ if ( $this->enabled === 'yes' ) :
164
+
165
+ printf( "
166
+ <div
167
+ attribution=\"fbe_woocommerce\"
168
+ class=\"fb-customerchat\"
169
+ page_id=\"%s\"
170
+ %s
171
+ %s
172
+ %s
173
+ ></div>
174
+ <!-- Facebook JSSDK -->
175
+ <script>
176
+ window.fbAsyncInit = function() {
177
+ FB.init({
178
+ appId : '',
179
+ autoLogAppEvents : true,
180
+ xfbml : true,
181
+ version : '%s'
182
+ });
183
+ };
184
+
185
+ (function(d, s, id){
186
+ var js, fjs = d.getElementsByTagName(s)[0];
187
+ if (d.getElementById(id)) {return;}
188
+ js = d.createElement(s); js.id = id;
189
+ js.src = 'https://connect.facebook.net/%s/sdk/xfbml.customerchat.js';
190
+ fjs.parentNode.insertBefore(js, fjs);
191
+ }(document, 'script', 'facebook-jssdk'));
192
+ </script>
193
+ <div></div>
194
+ ",
195
+ esc_attr( $this->page_id ),
196
+ // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
197
+ $this->theme_color_code ? 'theme_color="' . esc_attr( $this->theme_color_code ) . '"' : '',
198
+ // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
199
+ $this->greeting_text_code ? 'logged_in_greeting="' . esc_attr( $this->greeting_text_code ) . '"' : '',
200
+ // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
201
+ $this->greeting_text_code ? 'logged_out_greeting="' . esc_attr( $this->greeting_text_code ) . '"' : '',
202
+ esc_js( $this->jssdk_version ?: 'v5.0' ),
203
+ esc_js( $this->locale ?: 'en_US' )
204
  );
205
+
206
+ endif;
207
+ }
208
+
209
+
210
+ /**
211
+ * Gets the locales supported by Facebook Messenger.
212
+ *
213
+ * Returns the format $locale => $name for display in options.
214
+ * @link https://developers.facebook.com/docs/messenger-platform/messenger-profile/supported-locales/
215
+ * If the Locale extension is not available, will attempt to match locales to WordPress available language names.
216
+ *
217
+ * @since 1.10.0
218
+ *
219
+ * @return array associative array of locale codes and names
220
+ */
221
+ public static function get_supported_locales() {
222
+ global $wp_version;
223
+
224
+ $locales = [];
225
+
226
+ if ( class_exists( 'Locale' ) ) {
227
+
228
+ foreach ( self::$supported_locales as $locale ) {
229
+
230
+ if ( $name = \Locale::getDisplayName( $locale, substr( $locale, 0, 2 ) ) ) {
231
+ $locales[ $locale ] = ucfirst( $name );
232
+ }
233
+ }
234
+
235
+ } else {
236
+
237
+ include_once( ABSPATH . '/wp-admin/includes/translation-install.php' );
238
+
239
+ $translations = wp_get_available_translations();
240
+
241
+ foreach ( self::$supported_locales as $locale ) {
242
+
243
+ if ( isset( $translations[ $locale ]['native_name'] ) ) {
244
+
245
+ $locales[ $locale ] = $translations[ $locale ]['native_name'];
246
+
247
+ } else { // generic match e.g. <it>_IT, <it>_CH (any language in the the <it> group )
248
+
249
+ $matched_locale = substr( $locale, 0, 2 );
250
+
251
+ if ( isset( $translations[ $matched_locale ]['native_name'] ) ) {
252
+ $locales[ $locale ] = $translations[ $matched_locale ]['native_name'];
253
+ }
254
+ }
255
+ }
256
+
257
+ // always include US English
258
+ $locales['en_US'] = _x( 'English (United States)', 'language', 'facebook-for-woocommerce' );
259
  }
260
+
261
+ /**
262
+ * Filters the locales supported by Facebook Messenger.
263
+ *
264
+ * @since 1.10.0
265
+ *
266
+ * @param array $locales locales supported by Facebook Messenger, in $locale => $name format
267
+ */
268
+ $locales = (array) apply_filters( 'wc_facebook_messenger_supported_locales', array_unique( $locales ) );
269
+
270
+ natcasesort( $locales );
271
+
272
+ return $locales;
273
  }
274
 
275
+
276
  }
277
 
278
  endif;
facebook-commerce-pixel-event.php CHANGED
@@ -93,28 +93,25 @@ document,'script','https://connect.facebook.net/en_US/fbevents.js');
93
  %s
94
  <script>
95
  %s
96
- fbq('track', 'PageView', %s);
97
-
98
- document.addEventListener('DOMContentLoaded', function() {
99
- jQuery && jQuery(function($){
100
- $('body').on('added_to_cart', function(event) {
101
- // Ajax action.
102
- $.get('?wc-ajax=fb_inject_add_to_cart_event', function(data) {
103
- $('head').append(data);
104
- });
105
- });
106
- });
107
- }, false);
108
 
109
  </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
 
@@ -125,35 +122,94 @@ document.addEventListener('DOMContentLoaded', function() {
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';
@@ -161,8 +217,7 @@ document.addEventListener('DOMContentLoaded', function() {
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) {
@@ -170,12 +225,13 @@ document.addEventListener('%s', function (event) {
170
  }, false );
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
  */
@@ -204,7 +260,7 @@ src="https://www.facebook.com/tr?id=%s&ev=PageView&noscript=1"/>
204
  <!-- DO NOT MODIFY -->
205
  <!-- End Facebook Pixel Code -->
206
  ',
207
- esc_js( $pixel_id )
208
  );
209
  }
210
 
@@ -218,8 +274,8 @@ src="https://www.facebook.com/tr?id=%s&ev=PageView&noscript=1"/>
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
  }
93
  %s
94
  <script>
95
  %s
96
+ fbq( 'track', 'PageView', %s );
97
+
98
+ document.addEventListener( 'DOMContentLoaded', function() {
99
+ jQuery && jQuery( function( $ ) {
100
+
101
+ // insert placeholder for events injected when a product is added to the cart through Ajax
102
+ $( document.body ).append( '<div class=\"wc-facebook-pixel-event-placeholder\"></div>' );
103
+ } );
104
+ }, false );
 
 
 
105
 
106
  </script>
107
  <!-- DO NOT MODIFY -->
108
  <!-- %s Facebook Integration end -->
109
  ",
110
+ esc_html( WC_Facebookcommerce_Utils::getIntegrationName() ),
111
  self::get_basecode(),
112
  $this->pixel_init_code(),
113
  json_encode( $params, JSON_PRETTY_PRINT | JSON_FORCE_OBJECT ),
114
+ esc_html( WC_Facebookcommerce_Utils::getIntegrationName() )
115
  );
116
  }
117
 
122
  return $event_name === $this->last_event;
123
  }
124
 
125
+
126
  /**
127
+ * Gets the JavaScript code to track an event.
128
+ *
129
+ * Updates the last event property and returns the code.
130
+ *
131
+ * Use {@see \WC_Facebookcommerce_Pixel::inject_event()} to print or enqueue the code.
132
+ *
133
+ * @since x.y.z
134
+ *
135
+ * @param string $event_name the name of the event to track
136
+ * @param array $params custom event parameters
137
+ * @param string $method name of the pixel's fbq() function to call
138
  */
139
+ public function get_event_code( $event_name, $params, $method = 'track' ) {
140
+
141
  $this->last_event = $event_name;
142
 
143
+ return self::build_event( $event_name, $params, $method );
144
+ }
145
+
146
+
147
+ /**
148
+ * Gets the JavaScript code to track an event wrapped in <script> tag.
149
+ *
150
+ * @see \WC_Facebookcommerce_Pixel::get_event_code()
151
+ *
152
+ * @since x.y.z
153
+ *
154
+ * @param string $event_name the name of the event to track
155
+ * @param array $params custom event parameters
156
+ * @param string $method name of the pixel's fbq() function to call
157
+ */
158
+ public function get_event_script( $event_name, $params, $method = 'track' ) {
159
+
160
+ $output = '
161
  <!-- Facebook Pixel Event Code -->
162
  <script>
163
  %s
164
  </script>
165
  <!-- End Facebook Pixel Event Code -->
166
+ ';
167
+
168
+ return sprintf( $output, $this->get_event_code( $event_name, $params, $method ) );
169
+ }
170
+
171
+
172
+ /**
173
+ * Prints or enqueues the JavaScript code to track an event.
174
+ *
175
+ * Preferred method to inject events in a page.
176
+ *
177
+ * @see \WC_Facebookcommerce_Pixel::build_event()
178
+ *
179
+ * @param string $event_name the name of the event to track
180
+ * @param array $params custom event parameters
181
+ * @param string $method name of the pixel's fbq() function to call
182
+ */
183
+ public function inject_event( $event_name, $params, $method = 'track' ) {
184
+
185
+ if ( WC_Facebookcommerce_Utils::isWoocommerceIntegration() ) {
186
+
187
+ WC_Facebookcommerce_Utils::wc_enqueue_js( $this->get_event_code( $event_name, $params, $method ) );
188
+
189
+ } else {
190
+
191
+ // phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped
192
+ printf( $this->get_event_script( $event_name, $params, $method ) );
193
  }
194
  }
195
 
196
+
197
+ /**
198
+ * Prints the JavaScript code to track a conditional event.
199
+ *
200
+ * The tracking code will be executed when the given JavaScript event is triggered.
201
+ *
202
+ * @param string $event_name
203
+ * @param array $params custom event parameters
204
+ * @param string $listener name of the JavaScript event to listen for
205
+ * @param string $jsonified_pii JavaScript code representing an object of data for Advanced Matching
206
+ */
207
+ public function inject_conditional_event( $event_name, $params, $listener, $jsonified_pii = '' ) {
208
+
209
  $code = self::build_event( $event_name, $params, 'track' );
210
  $this->last_event = $event_name;
211
 
212
+ /** TODO: use the settings stored by {@see \WC_Facebookcommerce_Integration}. The use_pii setting here is currently always disabled regardless of the value configured in the plugin settings page {WV-2020-01-02} */
213
  // Prepends fbq(...) with pii information to the injected code.
214
  if ( $jsonified_pii && get_option( self::SETTINGS_KEY )[ self::USE_PII_KEY ] ) {
215
  $this->user_info = '%s';
217
  sprintf( $this->pixel_init_code(), '" || ' . $jsonified_pii . ' || "' ) . $code;
218
  }
219
 
220
+ $output = "
 
221
  <!-- Facebook Pixel Event Code -->
222
  <script>
223
  document.addEventListener('%s', function (event) {
225
  }, false );
226
  </script>
227
  <!-- End Facebook Pixel Event Code -->
228
+ ";
229
+
230
+ // phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped
231
+ printf( $output, esc_js( $listener ), $code );
232
  }
233
 
234
+
235
  /**
236
  * Returns FB pixel code noscript part to avoid W3 validation error
237
  */
260
  <!-- DO NOT MODIFY -->
261
  <!-- End Facebook Pixel Code -->
262
  ',
263
+ esc_attr( $pixel_id )
264
  );
265
  }
266
 
274
  "/* %s Facebook Integration Event Tracking */\n" .
275
  "fbq('%s', '%s', %s);",
276
  WC_Facebookcommerce_Utils::getIntegrationName(),
277
+ esc_js( $method ),
278
+ esc_js( $event_name ),
279
  json_encode( $params, JSON_PRETTY_PRINT | JSON_FORCE_OBJECT )
280
  );
281
  }
facebook-commerce.php CHANGED
@@ -8,6 +8,9 @@
8
  * @package FacebookCommerce
9
  */
10
 
 
 
 
11
  if ( ! defined( 'ABSPATH' ) ) {
12
  exit; // Exit if accessed directly
13
  }
@@ -18,11 +21,103 @@ 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
 
@@ -31,6 +126,7 @@ class WC_Facebookcommerce_Integration extends WC_Integration {
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/>';
@@ -42,9 +138,6 @@ class WC_Facebookcommerce_Integration extends WC_Integration {
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();
@@ -53,9 +146,10 @@ class WC_Facebookcommerce_Integration extends WC_Integration {
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 ) ||
@@ -64,6 +158,11 @@ class WC_Facebookcommerce_Integration extends WC_Integration {
64
  ) {
65
  WC_Facebookcommerce_Pixel::set_pixel_id( $settings_pixel_id );
66
  }
 
 
 
 
 
67
  }
68
  }
69
 
@@ -78,7 +177,7 @@ class WC_Facebookcommerce_Integration extends WC_Integration {
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'
@@ -91,65 +190,49 @@ class WC_Facebookcommerce_Integration extends WC_Integration {
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', '' ),
@@ -162,7 +245,7 @@ class WC_Facebookcommerce_Integration extends WC_Integration {
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
  );
@@ -175,17 +258,10 @@ class WC_Facebookcommerce_Integration extends WC_Integration {
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',
@@ -246,55 +322,9 @@ class WC_Facebookcommerce_Integration extends WC_Integration {
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',
@@ -319,15 +349,6 @@ class WC_Facebookcommerce_Integration extends WC_Integration {
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' ),
@@ -335,17 +356,6 @@ class WC_Facebookcommerce_Integration extends WC_Integration {
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' )
@@ -373,13 +383,14 @@ class WC_Facebookcommerce_Integration extends WC_Integration {
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' ) ) {
@@ -390,23 +401,23 @@ class WC_Facebookcommerce_Integration extends WC_Integration {
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
 
@@ -430,9 +441,9 @@ class WC_Facebookcommerce_Integration extends WC_Integration {
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();
@@ -459,222 +470,83 @@ class WC_Facebookcommerce_Integration extends WC_Integration {
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
@@ -685,78 +557,121 @@ class WC_Facebookcommerce_Integration extends WC_Integration {
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
  */
@@ -769,7 +684,7 @@ class WC_Facebookcommerce_Integration extends WC_Integration {
769
  wp_enqueue_script(
770
  'wc_facebook_infobanner_jsx',
771
  plugins_url(
772
- '/assets/js/facebook-infobanner.js?ts=' . time(),
773
  __FILE__
774
  )
775
  );
@@ -791,58 +706,65 @@ class WC_Facebookcommerce_Integration extends WC_Integration {
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
  );
@@ -860,19 +782,127 @@ class WC_Facebookcommerce_Integration extends WC_Integration {
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
@@ -899,7 +929,19 @@ class WC_Facebookcommerce_Integration extends WC_Integration {
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
@@ -912,17 +954,26 @@ class WC_Facebookcommerce_Integration extends WC_Integration {
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 );
@@ -942,14 +993,23 @@ class WC_Facebookcommerce_Integration extends WC_Integration {
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 ) {
@@ -960,21 +1020,6 @@ class WC_Facebookcommerce_Integration extends WC_Integration {
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,
@@ -982,33 +1027,44 @@ class WC_Facebookcommerce_Integration extends WC_Integration {
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
 
@@ -1020,40 +1076,32 @@ class WC_Facebookcommerce_Integration extends WC_Integration {
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(),
@@ -1062,16 +1110,21 @@ class WC_Facebookcommerce_Integration extends WC_Integration {
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
  }
@@ -1136,7 +1189,7 @@ class WC_Facebookcommerce_Integration extends WC_Integration {
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'] =
@@ -1145,7 +1198,7 @@ class WC_Facebookcommerce_Integration extends WC_Integration {
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,
@@ -1181,7 +1234,7 @@ class WC_Facebookcommerce_Integration extends WC_Integration {
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(
@@ -1284,101 +1337,118 @@ class WC_Facebookcommerce_Integration extends WC_Integration {
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
 
@@ -1402,7 +1472,7 @@ class WC_Facebookcommerce_Integration extends WC_Integration {
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!',
@@ -1413,18 +1483,18 @@ class WC_Facebookcommerce_Integration extends WC_Integration {
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
 
@@ -1459,7 +1529,7 @@ class WC_Facebookcommerce_Integration extends WC_Integration {
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',
@@ -1470,7 +1540,7 @@ class WC_Facebookcommerce_Integration extends WC_Integration {
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
  }
@@ -1564,12 +1634,18 @@ class WC_Facebookcommerce_Integration extends WC_Integration {
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,
@@ -1577,67 +1653,105 @@ class WC_Facebookcommerce_Integration extends WC_Integration {
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
  }
@@ -1688,67 +1802,80 @@ class WC_Facebookcommerce_Integration extends WC_Integration {
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',
@@ -1758,14 +1885,17 @@ class WC_Facebookcommerce_Integration extends WC_Integration {
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
  /**
@@ -1775,7 +1905,7 @@ class WC_Facebookcommerce_Integration extends WC_Integration {
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
 
@@ -1856,7 +1986,7 @@ class WC_Facebookcommerce_Integration extends WC_Integration {
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 );
@@ -1873,7 +2003,7 @@ class WC_Facebookcommerce_Integration extends WC_Integration {
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();
@@ -1886,18 +2016,16 @@ class WC_Facebookcommerce_Integration extends WC_Integration {
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;
@@ -1929,7 +2057,7 @@ class WC_Facebookcommerce_Integration extends WC_Integration {
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!' );
@@ -1956,9 +2084,6 @@ class WC_Facebookcommerce_Integration extends WC_Integration {
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,
@@ -1977,7 +2102,7 @@ class WC_Facebookcommerce_Integration extends WC_Integration {
1977
  WC_Facebookcommerce_Utils::fblog(
1978
  'Attempting to sync ' . $total . ' ( ' .
1979
  $total_new . ' new) products with settings: ',
1980
- $sanitized_settings,
1981
  false
1982
  );
1983
 
@@ -2069,27 +2194,31 @@ class WC_Facebookcommerce_Integration extends WC_Integration {
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!' );
@@ -2106,7 +2235,6 @@ class WC_Facebookcommerce_Integration extends WC_Integration {
2106
  "Advanced Options > Advanced Settings > Remove"
2107
  and try setup again'
2108
  );
2109
- $this->fb_wp_die();
2110
  return false;
2111
  }
2112
 
@@ -2121,21 +2249,21 @@ class WC_Facebookcommerce_Integration extends WC_Integration {
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(),
@@ -2146,7 +2274,6 @@ class WC_Facebookcommerce_Integration extends WC_Integration {
2146
  )
2147
  );
2148
  wp_reset_postdata();
2149
- $this->fb_wp_die();
2150
  return true;
2151
  }
2152
  WC_Facebookcommerce_Utils::fblog(
@@ -2157,573 +2284,1595 @@ class WC_Facebookcommerce_Integration extends WC_Integration {
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 );
@@ -2769,19 +3918,28 @@ class WC_Facebookcommerce_Integration extends WC_Integration {
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
@@ -2790,63 +3948,93 @@ class WC_Facebookcommerce_Integration extends WC_Integration {
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(
@@ -2896,40 +4084,121 @@ class WC_Facebookcommerce_Integration extends WC_Integration {
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
  }
8
  * @package FacebookCommerce
9
  */
10
 
11
+ use SkyVerge\WooCommerce\PluginFramework\v5_5_4 as Framework;
12
+ use SkyVerge\WooCommerce\Facebook\Products;
13
+
14
  if ( ! defined( 'ABSPATH' ) ) {
15
  exit; // Exit if accessed directly
16
  }
21
 
22
  class WC_Facebookcommerce_Integration extends WC_Integration {
23
 
24
+
25
+ /** @var string the WordPress option name where the page access token is stored */
26
+ const OPTION_PAGE_ACCESS_TOKEN = 'wc_facebook_page_access_token';
27
+
28
+ /** @var string the WordPress option name where the product catalog ID is stored */
29
+ const OPTION_PRODUCT_CATALOG_ID = 'wc_facebook_product_catalog_id';
30
+
31
+ /** @var string the WordPress option name where the external merchant settings ID is stored */
32
+ const OPTION_EXTERNAL_MERCHANT_SETTINGS_ID = 'wc_facebook_external_merchant_settings_id';
33
+
34
+ /** @var string the WordPress option name where the feed ID is stored */
35
+ const OPTION_FEED_ID = 'wc_facebook_feed_id';
36
+
37
+ /** @var string the WordPress option name where the JS SDK version is stored */
38
+ const OPTION_JS_SDK_VERSION = 'wc_facebook_js_sdk_version';
39
+
40
+ /** @var string the WordPress option name where the latest pixel install time is stored */
41
+ const OPTION_PIXEL_INSTALL_TIME = 'wc_facebook_pixel_install_time';
42
+
43
+ /** @var string the facebook page ID setting ID */
44
+ const SETTING_FACEBOOK_PAGE_ID = 'facebook_page_id';
45
+
46
+ /** @var string the facebook pixel ID setting ID */
47
+ const SETTING_FACEBOOK_PIXEL_ID = 'facebook_pixel_id';
48
+
49
+ /** @var string the "enable advanced matching" setting ID */
50
+ const SETTING_ENABLE_ADVANCED_MATCHING = 'enable_advanced_matching';
51
+
52
+ /** @var string the "enable product sync" setting ID */
53
+ const SETTING_ENABLE_PRODUCT_SYNC = 'enable_product_sync';
54
+
55
+ /** @var string the excluded product category IDs setting ID */
56
+ const SETTING_EXCLUDED_PRODUCT_CATEGORY_IDS = 'excluded_product_category_ids';
57
+
58
+ /** @var string the excluded product tag IDs setting ID */
59
+ const SETTING_EXCLUDED_PRODUCT_TAG_IDS = 'excluded_product_tag_ids';
60
+
61
+ /** @var string the product description mode setting ID */
62
+ const SETTING_PRODUCT_DESCRIPTION_MODE = 'product_description_mode';
63
+
64
+ /** @var string the scheduled resync offset setting ID */
65
+ const SETTING_SCHEDULED_RESYNC_OFFSET = 'scheduled_resync_offset';
66
+
67
+ /** @var string the "enable messenger" setting ID */
68
+ const SETTING_ENABLE_MESSENGER = 'enable_messenger';
69
+
70
+ /** @var string the messenger locale setting ID */
71
+ const SETTING_MESSENGER_LOCALE = 'messenger_locale';
72
+
73
+ /** @var string the messenger greeting setting ID */
74
+ const SETTING_MESSENGER_GREETING = 'messenger_greeting';
75
+
76
+ /** @var string the messenger color HEX setting ID */
77
+ const SETTING_MESSENGER_COLOR_HEX = 'messenger_color_hex';
78
+
79
+ /** @var string the standard product description mode name */
80
+ const PRODUCT_DESCRIPTION_MODE_STANDARD = 'standard';
81
+
82
+ /** @var string the short product description mode name */
83
+ const PRODUCT_DESCRIPTION_MODE_SHORT = 'short';
84
+
85
+ /** @var string the hook for the recurreing action that syncs products */
86
+ const ACTION_HOOK_SCHEDULED_RESYNC = 'sync_all_fb_products_using_feed';
87
+
88
+
89
+ /** @var string|null the configured page access token */
90
+ private $page_access_token;
91
+
92
+ /** @var string|null the configured product catalog ID */
93
+ public $product_catalog_id;
94
+
95
+ /** @var string|null the configured external merchant settings ID */
96
+ public $external_merchant_settings_id;
97
+
98
+ /** @var string|null the configured feed ID */
99
+ public $feed_id;
100
+
101
+ /** @var string|null the configured pixel install time */
102
+ public $pixel_install_time;
103
+
104
+ /** @var string|null the configured JS SDK version */
105
+ private $js_sdk_version;
106
+
107
+
108
+ /** Legacy properties *********************************************************************************************/
109
+
110
+
111
+ // TODO probably some of these meta keys need to be moved to Facebook\Products {FN 2020-01-13}
112
  const FB_PRODUCT_GROUP_ID = 'fb_product_group_id';
113
  const FB_PRODUCT_ITEM_ID = 'fb_product_item_id';
114
  const FB_PRODUCT_DESCRIPTION = 'fb_product_description';
115
 
116
+ /** @var string the API flag to set a product as visible in the Facebook shop */
117
+ const FB_SHOP_PRODUCT_VISIBLE = 'published';
118
+
119
+ /** @var string the API flag to set a product as not visible in the Facebook shop */
120
+ const FB_SHOP_PRODUCT_HIDDEN = 'staging';
121
 
122
  const FB_CART_URL = 'fb_cart_url';
123
 
126
  // Number of days to query tip.
127
  const FB_TIP_QUERY = 1;
128
 
129
+ // TODO: this constant is no longer used and can probably be removed {WV 2020-01-21}
130
  const FB_VARIANT_IMAGE = 'fb_image';
131
 
132
  const FB_ADMIN_MESSAGE_PREPEND = '<b>Facebook for WooCommerce</b><br/>';
138
 
139
  private $test_mode = false;
140
 
 
 
 
141
 
142
  public function init_pixel() {
143
  WC_Facebookcommerce_Pixel::initialize();
146
  // This is part of a larger effort to consolidate all the FB-specific
147
  // settings for all plugin integrations.
148
  if ( is_admin() ) {
149
+
150
  $pixel_id = WC_Facebookcommerce_Pixel::get_pixel_id();
151
+ $settings_pixel_id = $this->get_facebook_pixel_id();
152
+
153
  if (
154
  WC_Facebookcommerce_Utils::is_valid_id( $settings_pixel_id ) &&
155
  ( ! WC_Facebookcommerce_Utils::is_valid_id( $pixel_id ) ||
158
  ) {
159
  WC_Facebookcommerce_Pixel::set_pixel_id( $settings_pixel_id );
160
  }
161
+
162
+ // migrate Advanced Matching enabled (use_pii) from the integration setting to the pixel option,
163
+ // so that it works the same way the pixel ID does
164
+ $settings_advanced_matching_enabled = $this->is_advanced_matching_enabled();
165
+ WC_Facebookcommerce_Pixel::set_use_pii_key( $settings_advanced_matching_enabled );
166
  }
167
  }
168
 
177
  include_once 'facebook-commerce-events-tracker.php';
178
  }
179
 
180
+ $this->id = WC_Facebookcommerce::INTEGRATION_ID;
181
  $this->method_title = __(
182
  'Facebook for WooCommerce',
183
  'facebook-for-commerce'
190
  // Load the settings.
191
  $this->init_settings();
192
 
 
 
 
 
 
 
 
 
193
  $pixel_id = WC_Facebookcommerce_Pixel::get_pixel_id();
 
 
 
 
 
 
 
 
 
 
 
194
 
195
+ // if there is a pixel option saved and no integration setting saved, inherit the pixel option
196
+ if ( $pixel_id && ! $this->get_facebook_pixel_id() ) {
197
+ $this->settings[ self::SETTING_FACEBOOK_PIXEL_ID ] = $pixel_id;
198
+ }
199
 
200
+ $advanced_matching_enabled = WC_Facebookcommerce_Pixel::get_use_pii_key();
 
 
201
 
202
+ // if Advanced Matching (use_pii) is enabled on the saved pixel option and not on the saved integration setting,
203
+ // inherit the pixel option
204
+ if ( $advanced_matching_enabled && ! $this->is_advanced_matching_enabled() ) {
205
+ $this->settings[ self::SETTING_ENABLE_ADVANCED_MATCHING ] = $advanced_matching_enabled;
206
+ }
207
 
208
  if ( ! class_exists( 'WC_Facebookcommerce_Utils' ) ) {
209
  include_once 'includes/fbutils.php';
210
  }
211
 
212
+ WC_Facebookcommerce_Utils::$ems = $this->get_external_merchant_settings_id();
213
 
214
  if ( ! class_exists( 'WC_Facebookcommerce_Graph_API' ) ) {
215
  include_once 'includes/fbgraph.php';
216
+ $this->fbgraph = new WC_Facebookcommerce_Graph_API( $this->get_page_access_token() );
217
  }
218
 
219
  WC_Facebookcommerce_Utils::$fbgraph = $this->fbgraph;
 
 
 
220
 
221
  // Hooks
222
  if ( is_admin() ) {
223
+
224
  $this->init_pixel();
225
+
226
  $this->init_form_fields();
227
+
228
+ if ( ! class_exists( 'WC_Facebookcommerce_EventsTracker' ) ) {
229
+ include_once 'includes/fbutils.php';
230
+ }
231
+
232
  // Display an info banner for eligible pixel and user.
233
+ if ( $this->get_external_merchant_settings_id()
234
+ && $this->get_facebook_pixel_id()
235
+ && $this->get_pixel_install_time() ) {
236
  $should_query_tip =
237
  WC_Facebookcommerce_Utils::check_time_cap(
238
  get_option( 'fb_info_banner_last_query_time', '' ),
245
  include_once 'includes/fbinfobanner.php';
246
  }
247
  WC_Facebookcommerce_Info_Banner::get_instance(
248
+ $this->get_external_merchant_settings_id(),
249
  $this->fbgraph,
250
  $should_query_tip
251
  );
258
  $integration_test = WC_Facebook_Integration_Test::get_instance( $this );
259
  $integration_test::$fbgraph = $this->fbgraph;
260
 
261
+ if ( ! $this->get_pixel_install_time() && $this->get_facebook_pixel_id() ) {
262
+ $this->update_pixel_install_time( time() );
 
 
 
 
 
 
 
 
263
  }
264
+
265
  add_action( 'admin_notices', array( $this, 'checks' ) );
266
  add_action(
267
  'woocommerce_update_options_integration_facebookcommerce',
322
  );
323
 
324
  // Only load product processing hooks if we have completed setup.
325
+ if ( $this->get_page_access_token() && $this->get_product_catalog_id() ) {
 
 
 
 
 
 
 
 
 
 
 
 
 
326
 
327
+ add_action( 'woocommerce_process_product_meta', [ $this, 'on_product_save' ], 20 );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
328
 
329
  add_action(
330
  'woocommerce_product_quick_edit_save',
349
 
350
  add_action( 'add_meta_boxes', array( $this, 'fb_product_metabox' ), 10, 1 );
351
 
 
 
 
 
 
 
 
 
 
352
  add_action(
353
  'transition_post_status',
354
  array( $this, 'fb_change_product_published_status' ),
356
  3
357
  );
358
 
 
 
 
 
 
 
 
 
 
 
 
359
  add_action(
360
  'wp_ajax_ajax_fb_toggle_visibility',
361
  array( $this, 'ajax_fb_toggle_visibility' )
383
 
384
  add_action(
385
  'wp_ajax_wpmelon_adv_bulk_edit',
386
+ [ $this, 'ajax_woo_adv_bulk_edit_compat' ],
387
  self::FB_PRIORITY_MID
388
  );
389
 
390
+ // used to remove the 'you need to resync' message
391
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended
392
  if ( isset( $_GET['remove_sticky'] ) ) {
393
+ $this->remove_sticky_message();
394
  }
395
 
396
  if ( defined( 'ICL_LANGUAGE_CODE' ) ) {
401
  $this->load_background_sync_process();
402
  }
403
  // Must be outside of admin for cron to schedule correctly.
404
+ add_action( 'sync_all_fb_products_using_feed', [ $this, 'handle_scheduled_resync_action' ], self::FB_PRIORITY_MID );
 
 
 
 
405
 
406
+ if ( $this->get_facebook_pixel_id() ) {
407
+ $user_info = WC_Facebookcommerce_Utils::get_user_info( $this->is_advanced_matching_enabled() );
408
  $this->events_tracker = new WC_Facebookcommerce_EventsTracker( $user_info );
409
  }
410
 
411
+ if ( $this->is_messenger_enabled() ) {
412
+
413
+ $this->messenger_chat = new WC_Facebookcommerce_MessengerChat( [
414
+ 'fb_page_id' => $this->get_facebook_page_id(),
415
+ 'is_messenger_chat_plugin_enabled' => wc_bool_to_string( $this->is_messenger_enabled() ),
416
+ 'msger_chat_customization_greeting_text_code' => $this->get_messenger_greeting(),
417
+ 'msger_chat_customization_locale' => $this->get_messenger_locale(),
418
+ 'msger_chat_customization_theme_color_code' => $this->get_messenger_color_hex(),
419
+ 'facebook_jssdk_version' => $this->get_js_sdk_version(),
420
+ ] );
421
  }
422
  }
423
 
441
  check_ajax_referer( 'wc_facebook_settings_jsx' );
442
  $request_time = null;
443
  if ( isset( $_POST['request_time'] ) ) {
444
+ $request_time = esc_js( sanitize_text_field( wp_unslash( $_POST['request_time'] ) ) );
445
  }
446
+ if ( $this->get_page_access_token() ) {
447
  if ( isset( $this->background_processor ) ) {
448
  $is_processing = $this->background_processor->handle_cron_healthcheck();
449
  $remaining = $this->background_processor->get_item_count();
470
  wp_die();
471
  }
472
 
473
+
474
+ /**
475
+ * Adds a new tab to the Product edit page.
476
+ *
477
+ * @internal
478
+ * @deprecated since 1.10.0
479
+ *
480
+ * @param array $tabs array of tabs
481
+ * @return array
482
+ */
483
  public function fb_new_product_tab( $tabs ) {
 
 
 
 
 
 
 
484
 
485
+ wc_deprecated_function( __METHOD__, '1.10.0', '\\SkyVerge\\WooCommerce\\Facebook\\Admin::add_product_settings_tab()' );
 
 
 
 
 
 
 
486
 
487
+ return $tabs;
488
+ }
 
 
 
489
 
 
 
 
 
 
490
 
491
+ /**
492
+ * Adds content to the new Facebook tab on the Product edit page.
493
+ *
494
+ * @internal
495
+ * @deprecated since 1.10.0
496
+ */
497
+ public function fb_new_product_tab_content() {
498
 
499
+ wc_deprecated_function( __METHOD__, '1.10.0', '\\SkyVerge\\WooCommerce\\Facebook\\Admin::add_product_settings_tab_content()' );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
500
  }
501
 
 
 
 
 
502
 
503
+ /**
504
+ * Filters the product columns in the admin edit screen.
505
+ *
506
+ * @internal
507
+ * @deprecated since 1.10.0
508
+ *
509
+ * @param array $existing_columns array of columns and labels
510
+ * @return array
511
+ */
512
+ public function fb_product_columns( $existing_columns ) {
513
 
514
+ wc_deprecated_function( __METHOD__, '1.10.0', '\\SkyVerge\\WooCommerce\\Facebook\\Admin::add_product_list_table_column()' );
 
 
 
 
 
 
 
 
 
 
515
 
516
+ return $existing_columns;
517
  }
518
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
519
 
520
+ /**
521
+ * Outputs content for the FB Shop column in the edit screen.
522
+ *
523
+ * @internal
524
+ * @deprecated since 1.10.0
525
+ *
526
+ * @param string $column name of the column to display
527
+ */
528
+ public function fb_render_product_columns( $column ) {
 
 
 
 
 
 
 
 
 
 
 
529
 
530
+ wc_deprecated_function( __METHOD__, '1.10.0', '\\SkyVerge\\WooCommerce\\Facebook\\Admin::add_product_list_table_columns_content()' );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
531
  }
532
 
533
+
534
  public function fb_product_metabox() {
535
  $ajax_data = array(
536
+ 'nonce' => wp_create_nonce( 'wc_facebook_metabox_jsx' ),
537
+ );
538
  wp_enqueue_script(
539
  'wc_facebook_metabox_jsx',
540
  plugins_url(
541
+ '/assets/js/facebook-metabox.min.js?ts=' . time(),
542
  __FILE__
543
  )
544
  );
545
  wp_localize_script(
546
+ 'wc_facebook_metabox_jsx',
547
+ 'wc_facebook_metabox_jsx',
548
+ $ajax_data
549
+ );
550
 
551
  add_meta_box(
552
  'facebook_metabox', // Meta box ID
557
  );
558
  }
559
 
560
+
561
+ /**
562
+ * Renders the content of the product meta box.
563
+ */
564
  public function fb_product_meta_box_html() {
565
  global $post;
566
+
567
  $woo_product = new WC_Facebook_Product( $post->ID );
568
  $fb_product_group_id = $this->get_product_fbid(
569
  self::FB_PRODUCT_GROUP_ID,
570
  $post->ID,
571
  $woo_product
572
  );
573
+
574
+ ?>
575
+ <span id="fb_metadata">
576
+ <?php
577
+
578
  if ( $fb_product_group_id ) {
579
+
580
+ ?>
581
+ <?php echo esc_html__( 'Facebook ID:', 'facebook-for-woocommerce' ); ?>
582
+ <a href="https://facebook.com/<?php echo esc_attr( $fb_product_group_id ); ?>"
583
+ target="_blank">
584
+ <?php echo esc_html( $fb_product_group_id ); ?>
585
+ </a>
586
+ <p/>
587
+ <?php
588
+
589
  if ( WC_Facebookcommerce_Utils::is_variable_type( $woo_product->get_type() ) ) {
590
+
591
+ ?>
592
+ <p><?php echo esc_html__( 'Variant IDs:', 'facebook-for-woocommerce' ); ?><br/>
593
+ <?php
594
+
595
+ $children = $woo_product->get_children();
596
+
597
  foreach ( $children as $child_id ) {
598
+
599
  $fb_product_item_id = $this->get_product_fbid(
600
  self::FB_PRODUCT_ITEM_ID,
601
  $child_id
602
  );
603
+
604
+ ?>
605
+ <?php echo esc_html( $child_id ); ?>:
606
+ <a href="https://facebook.com/<?php echo esc_attr( $fb_product_item_id ); ?>"
607
+ target="_blank">
608
+ <?php echo esc_html( $fb_product_item_id ); ?>
609
+ </a><br/>
610
+ <?php
611
  }
612
+
613
+ ?>
614
+ </p>
615
+ <?php
616
  }
617
 
618
+ ?>
619
+ <?php echo esc_html__( 'Visible:', 'facebook-for-woocommerce' ); ?>
620
+ <input name="<?php echo esc_attr( Products::VISIBILITY_META_KEY ); ?>"
621
+ type="checkbox"
622
+ value="1"
623
+ <?php echo checked( ! $woo_product->woo_product instanceof \WC_Product || Products::is_product_visible( $woo_product->woo_product ) ); ?>/>
624
 
625
+ <p/>
626
+ <input name="is_product_page" type="hidden" value="1"/>
 
 
 
 
627
 
628
+ <p/>
629
+ <a href="#" onclick="fb_reset_product( <?php echo esc_js( $post->ID ); ?> )">
630
+ <?php echo esc_html__( 'Reset Facebook metadata', 'facebook-for-woocommerce' ); ?>
631
+ </a>
632
+
633
+ <p/>
634
+ <a href="#" onclick="fb_delete_product( <?php echo esc_js( $post->ID ); ?> )">
635
+ <?php echo esc_html__( 'Delete product(s) on Facebook', 'facebook-for-woocommerce' ); ?>
636
+ </a>
637
+
638
+ <?php
639
 
 
 
 
 
 
640
  } else {
641
+
642
+ ?>
643
+ <b><?php echo esc_html__( 'This product is not yet synced to Facebook.', 'facebook-for-woocommerce' ); ?></b>
644
+ <?php
645
  }
646
+
647
+ ?>
648
+ </span>
649
+ <?php
650
  }
651
 
652
+
653
+ /**
654
+ * Gets the total of published products.
655
+ *
656
+ * @return int
657
+ */
658
  private function get_product_count() {
659
+
660
+ $args = [
661
  'post_type' => 'product',
662
  'post_status' => 'publish',
663
  'posts_per_page' => -1,
664
  'fields' => 'ids',
665
+ ];
666
+
667
  $products = new WP_Query( $args );
668
 
669
  wp_reset_postdata();
670
 
671
+ return $products->found_posts;
672
  }
673
 
674
+
675
  /**
676
  * Load DIA specific JS Data
677
  */
684
  wp_enqueue_script(
685
  'wc_facebook_infobanner_jsx',
686
  plugins_url(
687
+ '/assets/js/facebook-infobanner.min.js?ts=' . time(),
688
  __FILE__
689
  )
690
  );
706
  return;
707
  }
708
 
709
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended
710
  if ( empty( $_GET['tab'] ) ) {
711
  return;
712
  }
713
 
714
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended
715
  if ( 'integration' !== $_GET['tab'] ) {
716
  return;
717
  }
718
 
719
  ?>
720
  <script>
721
+
722
  window.facebookAdsToolboxConfig = {
723
+ hasGzipSupport: '<?php echo extension_loaded( 'zlib' ) ? 'true' : 'false'; ?>',
724
+ enabledPlugins: ['MESSENGER_CHAT','INSTAGRAM_SHOP', 'PAGE_SHOP'],
725
+ enableSubscription: '<?php echo class_exists( 'WC_Subscriptions' ) ? 'true' : 'false'; ?>',
726
+ popupOrigin: '<?php echo isset( $_GET['url'] ) ? esc_js( sanitize_text_field( wp_unslash( $_GET['url'] ) ) ) : 'https://www.facebook.com/'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended ?>',
727
+ feedWasDisabled: 'true',
728
+ platform: 'WooCommerce',
729
+ pixel: {
730
+ pixelId: '<?php echo $this->get_facebook_pixel_id() ? esc_js( $this->get_facebook_pixel_id() ) : ''; ?>',
731
+ advanced_matching_supported: true
732
+ },
733
+ diaSettingId: '<?php echo $this->get_external_merchant_settings_id() ? esc_js( $this->get_external_merchant_settings_id() ) : ''; ?>',
734
+ store: {
735
+ baseUrl: window.location.protocol + '//' + window.location.host,
736
+ baseCurrency:'<?php echo esc_js( WC_Admin_Settings::get_option( 'woocommerce_currency' ) ); ?>',
737
+ timezoneId: '<?php echo esc_js( date( 'Z' ) ); ?>',
738
+ storeName: '<?php echo esc_js( WC_Facebookcommerce_Utils::get_store_name() ); ?>',
739
+ version: '<?php echo esc_js( WC()->version ) ; ?>',
740
+ php_version: '<?php echo PHP_VERSION; ?>',
741
+ plugin_version: '<?php echo esc_js( WC_Facebookcommerce_Utils::PLUGIN_VERSION ); ?>'
742
+ },
743
+ feed: {
744
+ totalVisibleProducts: '<?php echo esc_js( $this->get_product_count() ); ?>',
745
+ hasClientSideFeedUpload: '<?php echo esc_js( ! ! $this->get_feed_id() ); ?>'
746
+ },
747
+ feedPrepared: {
748
+ feedUrl: '',
749
+ feedPingUrl: '',
750
+ samples: <?php echo $this->get_sample_product_feed(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
751
+ },
752
+ tokenExpired: '<?php echo $this->get_page_access_token() && ! $this->get_page_name(); ?>',
753
+ excludedCategoryIDs: <?php echo json_encode( $this->get_excluded_product_category_ids() ); ?>,
754
+ excludedTagIDs: <?php echo json_encode( $this->get_excluded_product_tag_ids() ); ?>,
755
+ messengerGreetingMaxCharacters: <?php echo esc_js( $this->get_messenger_greeting_max_characters() ); ?>
756
  };
757
+
758
  </script>
759
+
760
  <?php
761
+ $ajax_data = [
762
  'nonce' => wp_create_nonce( 'wc_facebook_settings_jsx' ),
763
+ ];
764
  wp_enqueue_script(
765
  'wc_facebook_settings_jsx',
766
  plugins_url(
767
+ '/assets/js/facebook-settings.min.js?ts=' . time(),
768
  __FILE__
769
  )
770
  );
782
  );
783
  }
784
 
785
+
786
+ /**
787
+ * Checks the product type and calls the corresponding on publish method.
788
+ *
789
+ *
790
+ * @internal
791
+ *
792
+ * @since 1.10.0
793
+ *
794
+ * @param int $wp_id post ID
795
+ */
796
+ function on_product_save( $wp_id ) {
797
+
798
+ $product = wc_get_product( $wp_id );
799
+
800
+ if ( ! $product ) {
801
+ return;
802
+ }
803
+
804
+ // phpcs:ignore WordPress.Security.NonceVerification.Missing
805
+ $sync_enabled = ! empty( $_POST['fb_sync_enabled'] );
806
+ $is_visible = ! empty( $_POST['fb_visibility'] );
807
+
808
+ if ( ! $product->is_type( 'variable' ) ) {
809
+
810
+ if ( $sync_enabled ) {
811
+
812
+ Products::enable_sync_for_products( [ $product ] );
813
+
814
+ $this->save_product_settings( $product );
815
+
816
+ } else {
817
+
818
+ Products::disable_sync_for_products( [ $product ] );
819
+ }
820
+ }
821
+
822
+ $this->update_fb_visibility( $product->get_id(), $is_visible ? self::FB_SHOP_PRODUCT_VISIBLE : self::FB_SHOP_PRODUCT_HIDDEN );
823
+
824
+ if ( $sync_enabled ) {
825
+
826
+ switch ( $product->get_type() ) {
827
+
828
+ case 'simple':
829
+ case 'booking':
830
+ case 'external':
831
+ $this->on_simple_product_publish( $wp_id );
832
+ break;
833
+
834
+ case 'variable':
835
+ $this->on_variable_product_publish( $wp_id );
836
+ break;
837
+
838
+ case 'subscription':
839
+ case 'variable-subscription':
840
+ case 'bundle':
841
+ $this->on_product_publish( $wp_id );
842
+ break;
843
+ }
844
+ }
845
+ }
846
+
847
+
848
+ /**
849
+ * Saves the submitted Facebook settings for a product.
850
+ *
851
+ * @since 1.10.0
852
+ *
853
+ * @param \WC_Product $product the product object
854
+ */
855
+ private function save_product_settings( \WC_Product $product ) {
856
+
857
+ $woo_product = new WC_Facebook_Product( $product->get_id() );
858
+
859
+ // phpcs:disable WordPress.Security.NonceVerification.Missing
860
+ if ( isset( $_POST[ self::FB_PRODUCT_DESCRIPTION ] ) ) {
861
+ $woo_product->set_description( sanitize_text_field( wp_unslash( $_POST[ self::FB_PRODUCT_DESCRIPTION ] ) ) );
862
+ }
863
+
864
+ if ( isset( $_POST[ WC_Facebook_Product::FB_PRODUCT_PRICE ] ) ) {
865
+ $woo_product->set_price( sanitize_text_field( wp_unslash( $_POST[ WC_Facebook_Product::FB_PRODUCT_PRICE ] ) ) );
866
+ }
867
+
868
+ if ( isset( $_POST[ 'fb_product_image_source' ] ) ) {
869
+ $product->update_meta_data( Products::PRODUCT_IMAGE_SOURCE_META_KEY, sanitize_key( wp_unslash( $_POST[ 'fb_product_image_source' ] ) ) );
870
+ $product->save_meta_data();
871
+ }
872
+
873
+ if ( isset( $_POST[ WC_Facebook_Product::FB_PRODUCT_IMAGE ] ) ) {
874
+ $woo_product->set_product_image( sanitize_text_field( wp_unslash( $_POST[ WC_Facebook_Product::FB_PRODUCT_IMAGE ] ) ) );
875
+ }
876
+ // phpcs:enable WordPress.Security.NonceVerification.Missing
877
+ }
878
+
879
+
880
+ /**
881
+ * Deletes a product from Facebook.
882
+ *
883
+ * @param int $wp_id product ID
884
+ */
885
+ public function on_product_delete( $wp_id ) {
886
+
887
+ $woo_product = new WC_Facebook_Product( $wp_id );
888
+
889
+ if ( ! $woo_product->exists() ) {
890
+ // This happens when the wp_id is not a product or it's already
891
+ // been deleted.
892
+ return;
893
+ }
894
+
895
+ // skip if not enabled for sync
896
+ if ( ! $woo_product->woo_product instanceof \WC_Product || ! \SkyVerge\WooCommerce\Facebook\Products::product_should_be_synced( $woo_product->woo_product ) ) {
897
+ return;
898
+ }
899
+
900
+ $fb_product_group_id = $this->get_product_fbid(
901
+ self::FB_PRODUCT_GROUP_ID,
902
+ $wp_id,
903
+ $woo_product
904
+ );
905
+ $fb_product_item_id = $this->get_product_fbid(
906
  self::FB_PRODUCT_ITEM_ID,
907
  $wp_id,
908
  $woo_product
929
  */
930
  function fb_change_product_published_status( $new_status, $old_status, $post ) {
931
  global $post;
932
+
933
+ if ( ! $post ) {
934
+ return;
935
+ }
936
+
937
+ $visibility = $new_status === 'publish' ? self::FB_SHOP_PRODUCT_VISIBLE : self::FB_SHOP_PRODUCT_HIDDEN;
938
+
939
+ $product = wc_get_product( $post->ID );
940
+
941
+ // bail if this product isn't enabled for sync
942
+ if ( ! $product instanceof \WC_Product || ! Products::is_sync_enabled_for_product( $product ) ) {
943
+ return;
944
+ }
945
 
946
  // change from publish status -> unpublish status, e.g. trash, draft, etc.
947
  // change from trash status -> publish status
954
 
955
  /**
956
  * Generic function for use with any product publishing.
957
+ *
958
  * Will determine product type (simple or variable) and delegate to
959
  * appropriate handler.
960
+ *
961
+ * @param int $wp_id product ID
962
  */
963
+ public function on_product_publish( $wp_id ) {
964
+
965
  if ( get_post_status( $wp_id ) != 'publish' ) {
966
  return;
967
  }
968
 
969
  $woo_product = new WC_Facebook_Product( $wp_id );
970
+
971
+ // skip if not enabled for sync
972
+ if ( ! $woo_product->woo_product instanceof \WC_Product || ! \SkyVerge\WooCommerce\Facebook\Products::product_should_be_synced( $woo_product->woo_product ) ) {
973
+ return;
974
+ }
975
+
976
+ if ( $woo_product->woo_product->is_type( 'variable' ) ) {
977
  $this->on_variable_product_publish( $wp_id, $woo_product );
978
  } else {
979
  $this->on_simple_product_publish( $wp_id, $woo_product );
993
  return false;
994
  }
995
 
996
+
997
+ /**
998
+ * Syncs product to Facebook when saving a variable product.
999
+ *
1000
+ * @param int $wp_id product post ID
1001
+ * @param WC_Facebook_Product|null $woo_product product object
1002
+ */
1003
  function on_variable_product_publish( $wp_id, $woo_product = null ) {
1004
+
1005
+ if ( ! $this->is_product_sync_enabled() ) {
1006
  return;
1007
  }
1008
 
1009
  if ( get_post_status( $wp_id ) != 'publish' ) {
1010
  return;
1011
  }
1012
+
1013
  // Check if product group has been published to FB. If not, it's new.
1014
  // If yes, loop through variants and see if product items are published.
1015
  if ( ! $woo_product ) {
1020
  return;
1021
  }
1022
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1023
  $fb_product_group_id = $this->get_product_fbid(
1024
  self::FB_PRODUCT_GROUP_ID,
1025
  $wp_id,
1027
  );
1028
 
1029
  if ( $fb_product_group_id ) {
1030
+
1031
+ $woo_product->fb_visibility = Products::is_product_visible( $woo_product->woo_product );
1032
+
 
1033
  $this->update_product_group( $woo_product );
1034
+
1035
  $child_products = $woo_product->get_children();
1036
  $variation_id = $woo_product->find_matching_product_variation();
1037
+
1038
  // check if item_id is default variation. If yes, update in the end.
1039
  // If default variation value is to update, delete old fb_product_item_id
1040
  // and create new one in order to make it order correctly.
1041
  foreach ( $child_products as $item_id ) {
1042
+
1043
+ $fb_product_item_id = $this->on_simple_product_publish( $item_id, null, $woo_product );
1044
+
1045
  if ( $item_id == $variation_id && $fb_product_item_id ) {
1046
  $this->set_default_variant( $fb_product_group_id, $fb_product_item_id );
1047
  }
1048
  }
1049
+
1050
  } else {
1051
+
1052
  $this->create_product_variable( $woo_product );
1053
  }
1054
  }
1055
 
1056
+
1057
+ /**
1058
+ * Syncs product to Facebook when saving a simple product.
1059
+ *
1060
+ * @param int $wp_id product post ID
1061
+ * @param WC_Facebook_Product|null $woo_product product object
1062
+ * @param WC_Facebook_Product|null $parent_product parent object
1063
+ * @return int|mixed|void|null
1064
+ */
1065
+ function on_simple_product_publish( $wp_id, $woo_product = null, &$parent_product = null ) {
1066
+
1067
+ if ( ! $this->is_product_sync_enabled() ) {
1068
  return;
1069
  }
1070
 
1076
  $woo_product = new WC_Facebook_Product( $wp_id, $parent_product );
1077
  }
1078
 
1079
+ // skip if not enabled for sync
1080
+ if ( ! $woo_product->woo_product instanceof \WC_Product || ! \SkyVerge\WooCommerce\Facebook\Products::product_should_be_synced( $woo_product->woo_product ) ) {
1081
  return;
1082
  }
1083
 
1084
+ if ( $this->delete_on_out_of_stock( $wp_id, $woo_product ) ) {
1085
+ return;
 
 
 
 
 
 
 
 
1086
  }
1087
 
1088
  // Check if this product has already been published to FB.
1089
  // If not, it's new!
1090
+ $fb_product_item_id = $this->get_product_fbid( self::FB_PRODUCT_ITEM_ID, $wp_id, $woo_product );
 
 
 
 
1091
 
1092
  if ( $fb_product_item_id ) {
1093
+
1094
+ $woo_product->fb_visibility = Products::is_product_visible( $woo_product->woo_product );
1095
+
 
1096
  $this->update_product_item( $woo_product, $fb_product_item_id );
1097
+
1098
  return $fb_product_item_id;
1099
+
1100
  } else {
1101
+
1102
  // Check if this is a new product item for an existing product group
1103
  if ( $woo_product->get_parent_id() ) {
1104
+
1105
  $fb_product_group_id = $this->get_product_fbid(
1106
  self::FB_PRODUCT_GROUP_ID,
1107
  $woo_product->get_parent_id(),
1110
 
1111
  // New variant added
1112
  if ( $fb_product_group_id ) {
1113
+
1114
+ return $this->create_product_simple( $woo_product, $fb_product_group_id );
1115
+
1116
  } else {
1117
+
1118
  WC_Facebookcommerce_Utils::fblog(
1119
  'Wrong! simple_product_publish called without group ID for
1120
  a variable product!',
1121
+ [],
1122
  true
1123
  );
1124
  }
1125
+
1126
  } else {
1127
+
1128
  return $this->create_product_simple( $woo_product ); // new product
1129
  }
1130
  }
1189
 
1190
  // Default visibility on create = published
1191
  $woo_product->fb_visibility = true;
1192
+ update_post_meta( $woo_product->get_id(), Products::VISIBILITY_META_KEY, true );
1193
 
1194
  if ( $variants ) {
1195
  $product_group_data['variants'] =
1198
 
1199
  $create_product_group_result = $this->check_api_result(
1200
  $this->fbgraph->create_product_group(
1201
+ $this->get_product_catalog_id(),
1202
  $product_group_data
1203
  ),
1204
  $product_group_data,
1234
  return 0;
1235
  }
1236
 
1237
+ update_post_meta( $woo_product->get_id(), Products::VISIBILITY_META_KEY, true );
1238
 
1239
  $product_result = $this->check_api_result(
1240
  $this->fbgraph->create_product_item(
1337
  }
1338
 
1339
  /**
1340
+ * Saves settings via AJAX (to preserve window context for onboarding).
1341
+ *
1342
+ * @internal
1343
+ */
1344
+ public function ajax_save_fb_settings() {
1345
+
1346
  WC_Facebookcommerce_Utils::check_woo_ajax_permissions( 'save settings', true );
1347
  check_ajax_referer( 'wc_facebook_settings_jsx' );
1348
 
1349
+ if ( ! isset( $_REQUEST['facebook_for_woocommerce'] ) ) {
1350
+ // This is not a request from our plugin,
1351
+ // some other handler or plugin probably
1352
+ // wants to handle it and wp_die() after.
1353
+ return;
1354
+ }
1355
+
1356
+ if ( isset( $_REQUEST['api_key'] ) ) {
1357
 
1358
+ $api_key = sanitize_text_field( wp_unslash( $_REQUEST['api_key'] ) );
1359
+
1360
+ if ( ctype_alnum( $api_key ) ) {
1361
+ $this->update_page_access_token( $api_key );
1362
  }
1363
+ }
1364
+
1365
+ if ( isset( $_REQUEST['product_catalog_id'] ) ) {
1366
+
1367
+ $product_catalog_id = sanitize_text_field( wp_unslash( $_REQUEST['product_catalog_id'] ) );
1368
 
1369
+ if ( ctype_digit( $product_catalog_id ) ) {
1370
+
1371
+ if ( ! empty( $this->get_product_catalog_id() ) && $_REQUEST['product_catalog_id'] !== $this->get_product_catalog_id() ) {
1372
  $this->reset_all_products();
1373
  }
1374
+
1375
+ $this->update_product_catalog_id( sanitize_text_field( wp_unslash( $_REQUEST['product_catalog_id'] ) ) );
1376
  }
1377
+ }
1378
+
1379
+ if ( isset( $_REQUEST['pixel_id'] ) ) {
1380
+
1381
+ $pixel_id = sanitize_text_field( wp_unslash( $_REQUEST['pixel_id'] ) );
1382
+
1383
+ if ( ctype_digit( $pixel_id ) ) {
1384
+
1385
+ // to prevent race conditions with pixel-only settings, only save a pixel if we already have an access token
1386
+ if ( $this->get_page_access_token() ) {
1387
+
1388
+ if ( $this->get_facebook_pixel_id() !== $pixel_id ) {
1389
+ $this->update_pixel_install_time( time() );
1390
  }
1391
+
1392
+ $this->settings[ self::SETTING_FACEBOOK_PIXEL_ID ] = $pixel_id;
1393
+
1394
  } else {
1395
+
1396
+ WC_Facebookcommerce_Utils::log( 'Got pixel-only settings, doing nothing' );
 
1397
  echo 'Not saving pixel-only settings';
1398
+
1399
  wp_die();
1400
  }
1401
  }
1402
+ }
1403
+
1404
+ if ( isset( $_REQUEST['pixel_use_pii'] ) ) {
1405
+ $this->settings[ self::SETTING_ENABLE_ADVANCED_MATCHING ] = wc_bool_to_string( wc_clean( wp_unslash( $_REQUEST['pixel_use_pii'] ) ) );
1406
+ }
1407
+
1408
+ if ( isset( $_REQUEST['page_id'] ) ) {
1409
+
1410
+ $page_id = sanitize_text_field( wp_unslash( $_REQUEST['page_id'] ) );
1411
+
1412
+ if ( ctype_digit( $page_id ) ) {
1413
+ $this->settings[ self::SETTING_FACEBOOK_PAGE_ID ] = $page_id;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1414
  }
1415
+ }
1416
+
1417
+ if ( isset( $_REQUEST['external_merchant_settings_id'] ) ) {
1418
+
1419
+ $external_merchant_settings_id = sanitize_text_field( wp_unslash( $_REQUEST['external_merchant_settings_id'] ) );
1420
+
1421
+ if ( ctype_digit( $external_merchant_settings_id ) ) {
1422
+ $this->update_external_merchant_settings_id( $external_merchant_settings_id );
1423
  }
1424
+ }
1425
 
1426
+ if ( isset( $_REQUEST['is_messenger_chat_plugin_enabled'] ) ) {
1427
+ $this->settings[ self::SETTING_ENABLE_MESSENGER ] = wc_bool_to_string( wc_clean( wp_unslash( $_REQUEST['is_messenger_chat_plugin_enabled'] ) ) );
1428
+ }
 
 
 
 
1429
 
1430
+ if ( isset( $_REQUEST['facebook_jssdk_version'] ) ) {
1431
+ $this->update_js_sdk_version( sanitize_text_field( wp_unslash( $_REQUEST['facebook_jssdk_version'] ) ) );
1432
+ }
1433
+
1434
+ if ( ! empty( $_REQUEST['msger_chat_customization_greeting_text_code'] ) ) {
1435
+ $this->settings[ self::SETTING_MESSENGER_GREETING ] = sanitize_text_field( wp_unslash( $_REQUEST['msger_chat_customization_greeting_text_code'] ) );
1436
+ }
1437
+
1438
+ if ( isset( $_REQUEST['msger_chat_customization_locale'] ) ) {
1439
+ $this->settings[ self::SETTING_MESSENGER_LOCALE ] = sanitize_text_field( wp_unslash( $_REQUEST['msger_chat_customization_locale'] ) );
1440
+ }
1441
+
1442
+ if ( ! empty( $_REQUEST['msger_chat_customization_theme_color_code'] ) ) {
1443
+ $this->settings[ self::SETTING_MESSENGER_COLOR_HEX ] = sanitize_hex_color( wp_unslash( $_REQUEST['msger_chat_customization_theme_color_code'] ) );
1444
  }
1445
 
1446
+ /** This filter is defined by WooCommerce in includes/abstracts/abstract-wc-settings-api.php */
1447
+ update_option( $this->get_option_key(), apply_filters( 'woocommerce_settings_api_sanitized_fields_' . $this->id, $this->settings ) );
1448
+
1449
+ WC_Facebookcommerce_Utils::log( 'Settings saved!' );
1450
+ echo 'settings_saved';
1451
+
1452
  wp_die();
1453
  }
1454
 
1472
  }
1473
 
1474
  if ( isset( $_REQUEST ) ) {
1475
+ $ems = $this->get_external_merchant_settings_id();
1476
  if ( $ems ) {
1477
  WC_Facebookcommerce_Utils::fblog(
1478
  'Deleted all settings!',
1483
  }
1484
 
1485
  $this->init_settings();
1486
+ $this->update_page_access_token( '' );
1487
+ $this->update_product_catalog_id( '' );
1488
 
1489
+ $this->settings[ self::SETTING_FACEBOOK_PIXEL_ID ] = '';
1490
+ $this->settings[ self::SETTING_ENABLE_ADVANCED_MATCHING ] = 'no';
1491
+ $this->settings[ self::SETTING_FACEBOOK_PAGE_ID ] = '';
1492
 
1493
+ $this->update_external_merchant_settings_id( '' );
1494
+ $this->update_pixel_install_time( 0 );
1495
+ $this->update_feed_id( '' );
1496
+ $this->settings['fb_upload_id'] = '';
1497
+ $this->settings['upload_end_time'] = '';
 
1498
 
1499
  WC_Facebookcommerce_Pixel::set_pixel_id( 0 );
1500
 
1529
  function ajax_check_feed_upload_status() {
1530
  WC_Facebookcommerce_Utils::check_woo_ajax_permissions( 'check feed upload status', true );
1531
  check_ajax_referer( 'wc_facebook_settings_jsx' );
1532
+ if ( $this->get_page_access_token() ) {
1533
  $response = array(
1534
  'connected' => true,
1535
  'status' => 'in progress',
1540
  include_once 'includes/fbproductfeed.php';
1541
  }
1542
  $this->fbproductfeed = new WC_Facebook_Product_Feed(
1543
+ $this->get_product_catalog_id(),
1544
  $this->fbgraph
1545
  );
1546
  }
1634
  }
1635
  }
1636
 
1637
+
1638
  /**
1639
+ * Logs and stores custom error message (sugar).
1640
+ *
1641
+ * @param string $msg
1642
+ */
1643
  function display_error_message( $msg ) {
1644
+
1645
  $msg = self::FB_ADMIN_MESSAGE_PREPEND . $msg;
1646
+
1647
  WC_Facebookcommerce_Utils::log( $msg );
1648
+
1649
  set_transient(
1650
  'facebook_plugin_api_error',
1651
  $msg,
1653
  );
1654
  }
1655
 
1656
+
1657
  /**
1658
+ * Displays error message from API result (sugar).
1659
+ *
1660
+ * @param array $result
1661
+ */
1662
  function display_error_message_from_result( $result ) {
1663
+
1664
  $msg = json_decode( $result['body'] )->error->message;
1665
  $this->display_error_message( $msg );
1666
  }
1667
 
1668
+
1669
  /**
1670
+ * Deals with FB API responses, displays error if FB API returns error.
1671
  *
1672
+ * @param WP_Error|array $result API response
1673
+ * @param array|null $logdata additional data for logging
1674
+ * @param int|null $wpid post ID
1675
+ * @return array|null|void result if response is 200, null otherwise
1676
+ */
1677
  function check_api_result( $result, $logdata = null, $wpid = null ) {
1678
+
1679
  if ( is_wp_error( $result ) ) {
1680
+
1681
  WC_Facebookcommerce_Utils::log( $result->get_error_message() );
1682
+
1683
+ $message = sprintf(
1684
+ /* translators: Placeholders %1$s - original error message from Facebook API */
1685
+ esc_html__( 'There was an issue connecting to the Facebook API: %1$s', 'facebook-for-woocommerce' ),
1686
  $result->get_error_message()
1687
  );
1688
+
1689
+ $this->display_error_message( $message );
1690
+
1691
  return;
1692
  }
1693
+
1694
  if ( $result['response']['code'] != '200' ) {
1695
+
1696
  // Catch 10800 fb error code ("Duplicate retailer ID") and capture FBID
1697
  // if possible, otherwise let user know we found dupe SKUs
1698
  $body = WC_Facebookcommerce_Utils::decode_json( $result['body'] );
1699
+
1700
  if ( $body && $body->error->code == '10800' ) {
1701
+
1702
  $error_data = $body->error->error_data; // error_data may contain FBIDs
1703
+
1704
  if ( $error_data && $wpid ) {
1705
+
1706
  $existing_id = $this->get_existing_fbid( $error_data, $wpid );
1707
+
1708
  if ( $existing_id ) {
1709
+
1710
  // Add "existing_id" ID to result
1711
  $body->id = $existing_id;
1712
  $result['body'] = json_encode( $body );
1713
  return $result;
1714
  }
1715
  }
1716
+
1717
  } else {
1718
+
1719
  $this->display_error_message_from_result( $result );
1720
  }
1721
 
1722
  WC_Facebookcommerce_Utils::log( $result );
1723
+
1724
+ $data = [
1725
  'result' => $result,
1726
  'data' => $logdata,
1727
+ ];
1728
  WC_Facebookcommerce_Utils::fblog(
1729
  'Non-200 error code from FB',
1730
  $data,
1731
  true
1732
  );
1733
+
1734
  return null;
1735
  }
1736
+
1737
  return $result;
1738
  }
1739
 
1740
+
1741
+ /**
1742
+ * Displays out of sync message if products are edited using WooCommerce Advanced Bulk Edit.
1743
+ *
1744
+ * @param $import_id
1745
+ */
1746
  function ajax_woo_adv_bulk_edit_compat( $import_id ) {
1747
+
1748
  if ( ! WC_Facebookcommerce_Utils::check_woo_ajax_permissions( 'adv bulk edit', false ) ) {
1749
  return;
1750
  }
1751
+
1752
+ // phpcs:ignore WordPress.Security.NonceVerification.Missing
1753
+ $type = isset( $_POST['type'] ) ? sanitize_text_field( wp_unslash( $_POST['type'] ) ) : '';
1754
+
1755
  if ( strpos( $type, 'product' ) !== false && strpos( $type, 'load' ) === false ) {
1756
  $this->display_out_of_sync_message( 'advanced bulk edit' );
1757
  }
1802
  }
1803
 
1804
  /**
1805
+ * Checks for API key and other API errors.
1806
+ */
1807
+ public function checks() {
 
1808
 
1809
+ // TODO improve this by checking the settings page with Framework method and ensure error notices are displayed under the Integration sections {FN 2020-01-30}
1810
+ if ( isset( $_GET['page'], $_GET['section'] ) && 'wc-settings' === $_GET['page'] && \WC_Facebookcommerce::INTEGRATION_ID === $_GET['section'] ) {
1811
+ $this->display_errors();
 
 
 
 
 
 
 
 
 
 
 
 
 
1812
  }
1813
 
1814
+ // check required fields
1815
+ if ( ! $this->get_page_access_token() || ! $this->get_product_catalog_id() ) {
1816
+
1817
+ $message = sprintf(
1818
+ /* translators: Placeholders %1$s - opening strong HTML tag, %2$s - closing strong HTML tag, %3$s - opening link HTML tag, %4$s - closing link HTML tag */
1819
+ esc_html__(
1820
+ '%1$sFacebook for WooCommerce is almost ready.%2$s To complete your configuration, %3$scomplete the setup steps%4$s.',
1821
+ 'facebook-for-woocommerce'
1822
+ ),
1823
+ '<strong>',
1824
+ '</strong>',
1825
+ '<a href="' . esc_url( WOOCOMMERCE_FACEBOOK_PLUGIN_SETTINGS_URL ) . '">',
1826
+ '</a>'
1827
+ );
1828
+
1829
+ // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
1830
+ echo $this->get_message_html( $message, 'info' );
1831
+ }
1832
+
1833
+ // WooCommerce 2.x upgrade nag
1834
+ if ( $this->get_page_access_token() && ( ! isset( $this->background_processor ) ) ) {
1835
+
1836
+ $message = sprintf(
1837
+ /* translators: Placeholders %1$s - WooCommerce version */
1838
+ esc_html__(
1839
+ 'Facebook product sync may not work correctly in WooCommerce version %1$s. Please upgrade to WooCommerce 3.',
1840
+ 'facebook-for-woocommerce'
1841
  ),
1842
+ esc_html( WC()->version )
1843
  );
1844
+
1845
+ // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
1846
+ echo $this->get_message_html( $message, 'warning' );
1847
  }
1848
 
1849
  $this->maybe_display_facebook_api_messages();
1850
  }
1851
 
1852
+
1853
+ /**
1854
+ * Gets a sample feed with up to 12 published products.
1855
+ *
1856
+ * @return string
1857
+ */
1858
  function get_sample_product_feed() {
1859
+
1860
  ob_start();
1861
 
1862
+ // get up to 12 published posts that are products
1863
+ $args = [
1864
  'post_type' => 'product',
1865
  'post_status' => 'publish',
1866
  'posts_per_page' => 12,
1867
  'fields' => 'ids',
1868
+ ];
1869
 
1870
  $post_ids = get_posts( $args );
1871
+ $items = [];
1872
 
1873
  foreach ( $post_ids as $post_id ) {
1874
 
1875
  $woo_product = new WC_Facebook_Product( $post_id );
1876
  $product_data = $woo_product->prepare_product();
1877
 
1878
+ $feed_item = [
1879
  'title' => strip_tags( $product_data['name'] ),
1880
  'availability' => $woo_product->is_in_stock() ? 'in stock' :
1881
  'out of stock',
1885
  'brand' => strip_tags( WC_Facebookcommerce_Utils::get_store_name() ),
1886
  'link' => $product_data['url'],
1887
  'price' => $product_data['price'] . ' ' . get_woocommerce_currency(),
1888
+ ];
1889
 
1890
  array_push( $items, $feed_item );
1891
  }
1892
+
1893
  // https://codex.wordpress.org/Function_Reference/wp_reset_postdata
1894
  wp_reset_postdata();
1895
+
1896
  ob_end_clean();
1897
+
1898
+ return json_encode( [ $items ] );
1899
  }
1900
 
1901
  /**
1905
  foreach ( $products as $product_id ) {
1906
  delete_post_meta( $product_id, self::FB_PRODUCT_GROUP_ID );
1907
  delete_post_meta( $product_id, self::FB_PRODUCT_ITEM_ID );
1908
+ delete_post_meta( $product_id, Products::VISIBILITY_META_KEY );
1909
  }
1910
  }
1911
 
1986
  wp_die();
1987
  }
1988
 
1989
+ $wp_id = sanitize_text_field( wp_unslash( $_POST['wp_id'] ) );
1990
  $woo_product = new WC_Facebook_Product( $wp_id );
1991
  if ( $woo_product ) {
1992
  $this->reset_single_product( $wp_id );
2003
  wp_die();
2004
  }
2005
 
2006
+ $wp_id = sanitize_text_field( wp_unslash( $_POST['wp_id'] ) );
2007
  $this->on_product_delete( $wp_id );
2008
  $this->reset_single_product( $wp_id );
2009
  wp_reset_postdata();
2016
  function ajax_sync_all_fb_products() {
2017
  WC_Facebookcommerce_Utils::check_woo_ajax_permissions( 'syncall products', true );
2018
  check_ajax_referer( 'wc_facebook_settings_jsx' );
2019
+
2020
+ if ( ! $this->is_product_sync_enabled() ) {
2021
+ WC_Facebookcommerce_Utils::log( 'Sync to Facebook is disabled' );
 
2022
  wp_die();
 
2023
  }
2024
 
2025
+ if ( ! $this->get_page_access_token() || ! $this->get_product_catalog_id() ) {
2026
  WC_Facebookcommerce_Utils::log(
2027
  'No API key or catalog ID: ' .
2028
+ $this->get_page_access_token() . ' and ' . $this->get_product_catalog_id()
2029
  );
2030
  wp_die();
2031
  return;
2057
  }
2058
 
2059
  $is_valid_product_catalog =
2060
+ $this->fbgraph->validate_product_catalog( $this->get_product_catalog_id() );
2061
 
2062
  if ( ! $is_valid_product_catalog ) {
2063
  WC_Facebookcommerce_Utils::log( 'Not syncing, invalid product catalog!' );
2084
  update_option( self::FB_CART_URL, wc_get_cart_url() );
2085
  }
2086
 
 
 
 
2087
  // Get all published posts. First unsynced then already-synced.
2088
  $post_ids_new = WC_Facebookcommerce_Utils::get_wp_posts(
2089
  self::FB_PRODUCT_GROUP_ID,
2102
  WC_Facebookcommerce_Utils::fblog(
2103
  'Attempting to sync ' . $total . ' ( ' .
2104
  $total_new . ' new) products with settings: ',
2105
+ $this->settings,
2106
  false
2107
  );
2108
 
2194
  return $this->sync_all_fb_products_using_feed();
2195
  }
2196
 
2197
+
2198
+ /**
2199
+ * Syncs Facebook products using a Feed.
2200
+ *
2201
+ * @see https://developers.facebook.com/docs/marketing-api/fbe/fbe1/guides/feed-approach
2202
+ *
2203
+ * @return bool
2204
+ */
2205
+ public function sync_all_fb_products_using_feed() {
2206
+
2207
+ if ( ! $this->is_product_sync_enabled() ) {
2208
+ WC_Facebookcommerce_Utils::log( 'Sync to Facebook is disabled' );
2209
  return false;
2210
  }
2211
 
2212
+ if ( ! $this->get_page_access_token() || ! $this->get_product_catalog_id() ) {
2213
  self::log(
2214
+ 'No API key or catalog ID: ' . $this->get_page_access_token() .
2215
+ ' and ' . $this->get_product_catalog_id()
2216
  );
 
2217
  return false;
2218
  }
2219
  $this->remove_resync_message();
2220
  $is_valid_product_catalog =
2221
+ $this->fbgraph->validate_product_catalog( $this->get_product_catalog_id() );
2222
 
2223
  if ( ! $is_valid_product_catalog ) {
2224
  WC_Facebookcommerce_Utils::log( 'Not syncing, invalid product catalog!' );
2235
  "Advanced Options > Advanced Settings > Remove"
2236
  and try setup again'
2237
  );
 
2238
  return false;
2239
  }
2240
 
2249
  }
2250
  if ( $this->test_mode ) {
2251
  $this->fbproductfeed = new WC_Facebook_Product_Feed_Test_Mock(
2252
+ $this->get_product_catalog_id(),
2253
  $this->fbgraph,
2254
+ $this->get_feed_id()
2255
  );
2256
  } else {
2257
  $this->fbproductfeed = new WC_Facebook_Product_Feed(
2258
+ $this->get_product_catalog_id(),
2259
  $this->fbgraph,
2260
+ $this->get_feed_id()
2261
  );
2262
  }
2263
 
2264
  $upload_success = $this->fbproductfeed->sync_all_products_using_feed();
2265
  if ( $upload_success ) {
2266
+ $this->update_feed_id( $this->fbproductfeed->feed_id );
2267
  $this->settings['fb_upload_id'] = $this->fbproductfeed->upload_id;
2268
  update_option(
2269
  $this->get_option_key(),
2274
  )
2275
  );
2276
  wp_reset_postdata();
 
2277
  return true;
2278
  }
2279
  WC_Facebookcommerce_Utils::fblog(
2284
  return false;
2285
  }
2286
 
2287
+
2288
  /**
2289
+ * Toggles product visibility via AJAX.
2290
+ *
2291
+ * @internal
2292
+ * @deprecated since 1.10.0
2293
  **/
2294
+ public function ajax_fb_toggle_visibility() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2295
 
2296
+ wc_deprecated_function( __METHOD__, '1.10.0' );
 
 
 
 
 
2297
  }
2298
 
2299
+
2300
  /**
2301
+ * Initializes the settings form fields.
2302
  *
2303
+ * @since 1.0.0
2304
+ *
2305
+ * @internal
2306
  */
2307
+ public function init_form_fields() {
2308
+
2309
+ $term_query = new \WP_Term_Query( [
2310
+ 'taxonomy' => 'product_cat',
2311
+ 'hide_empty' => false,
2312
+ 'fields' => 'id=>name',
2313
+ ] );
2314
+
2315
+ $product_categories = $term_query->get_terms();
2316
+
2317
+ $term_query = new \WP_Term_Query( [
2318
+ 'taxonomy' => 'product_tag',
2319
+ 'hide_empty' => false,
2320
+ 'hierarchical' => false,
2321
+ 'fields' => 'id=>name',
2322
+ ] );
2323
+
2324
+ $product_tags = $term_query->get_terms();
2325
+
2326
+ $messenger_locales = \WC_Facebookcommerce_MessengerChat::get_supported_locales();
2327
+
2328
+ // tries matching with WordPress locale, otherwise English, otherwise first available language
2329
+ if ( isset( $messenger_locales[ get_locale() ] ) ) {
2330
+ $default_locale = get_locale();
2331
+ } elseif ( isset( $messenger_locales[ 'en_US' ] ) ) {
2332
+ $default_locale = 'en_US';
2333
+ } elseif ( ! empty( $messenger_locales ) && is_array( $messenger_locales ) ) {
2334
+ $default_locale = key( $messenger_locales );
2335
+ } else {
2336
+ // fallback to English in case of invalid/empty filtered list of languages
2337
+ $messenger_locales = [ 'en_US' => _x( 'English (United States)', 'language', 'facebook-for-woocommerce' ) ];
2338
+ $default_locale = 'en_US';
2339
+ }
2340
+
2341
+ $form_fields = [
2342
+
2343
+ /** @see \WC_Facebookcommerce_Integration::generate_manage_connection_title_html() */
2344
+ [
2345
+ 'type' => 'manage_connection_title',
2346
+ ],
2347
+
2348
+ /** @see \WC_Facebookcommerce_Integration::generate_facebook_page_name_html() */
2349
+ self::SETTING_FACEBOOK_PAGE_ID => [
2350
+ 'type' => 'facebook_page_name',
2351
+ 'default' => '',
2352
+ ],
2353
+
2354
+ /** @see \WC_Facebookcommerce_Integration::generate_facebook_pixel_id_html() */
2355
+ self::SETTING_FACEBOOK_PIXEL_ID => [
2356
+ 'type' => 'facebook_pixel_id',
2357
+ 'default' => '',
2358
+ ],
2359
+
2360
+ self::SETTING_ENABLE_ADVANCED_MATCHING => [
2361
+ 'title' => __( 'Use Advanced Matching', 'facebook-for-woocommerce' ),
2362
+ 'description' => sprintf(
2363
+ /* translators: Placeholders: %1$s - opening <a> HTML link tag, %2$s - closing </a> HTML link tag */
2364
+ __( 'Improve the ability to match site visitors to people on Facebook by passing additional site visitor information (such as email address or phone number). %1$sLearn more%2$s.', 'facebook-for-woocommerce' ),
2365
+ '<a href="https://developers.facebook.com/docs/facebook-pixel/advanced/advanced-matching" target="_blank">',
2366
+ '</a>'
2367
  ),
2368
  'type' => 'checkbox',
2369
+ 'label' => ' ',
 
 
 
 
2370
  'default' => 'yes',
2371
+ ],
2372
+
2373
+ /** @see \WC_Facebookcommerce_Integration::generate_create_ad_html() */
2374
+ [
2375
+ 'type' => 'create_ad',
2376
+ ],
2377
+
2378
+ /** @see \WC_Facebookcommerce_Integration::generate_product_sync_title_html() */
2379
+ [
2380
+ 'type' => 'product_sync_title',
2381
+ ],
2382
+
2383
+ self::SETTING_ENABLE_PRODUCT_SYNC => [
2384
+ 'title' => __( 'Enable product sync', 'facebook-for-woocommerce' ),
2385
+ 'type' => 'checkbox',
2386
+ 'class' => 'product-sync-field toggle-fields-group',
2387
+ 'label' => ' ',
2388
+ 'default' => 'yes',
2389
+ ],
2390
+
2391
+ self::SETTING_EXCLUDED_PRODUCT_CATEGORY_IDS => [
2392
+ 'title' => __( 'Exclude categories from sync', 'facebook-for-woocommerce' ),
2393
+ 'type' => 'multiselect',
2394
+ 'class' => 'wc-enhanced-select product-sync-field',
2395
+ 'css' => 'min-width: 300px;',
2396
+ 'desc_tip' => __( 'Products in one or more of these categories will not sync to Facebook.', 'facebook-for-woocommerce' ),
2397
+ 'default' => [],
2398
+ 'options' => is_array( $product_categories ) ? $product_categories : [],
2399
+ 'custom_attributes' => [
2400
+ 'data-placeholder' => __( 'Search for a product category&hellip;', 'facebook-for-woocommerce' ),
2401
+ ],
2402
+ ],
2403
+
2404
+ self::SETTING_EXCLUDED_PRODUCT_TAG_IDS => [
2405
+ 'title' => __( 'Exclude tags from sync', 'facebook-for-woocommerce' ),
2406
+ 'type' => 'multiselect',
2407
+ 'class' => 'wc-enhanced-select product-sync-field',
2408
+ 'css' => 'min-width: 300px;',
2409
+ 'desc_tip' => __( 'Products with one or more of these tags will not sync to Facebook.', 'facebook-for-woocommerce' ),
2410
+ 'default' => [],
2411
+ 'options' => is_array( $product_tags ) ? $product_tags : [],
2412
+ 'custom_attributes' => [
2413
+ 'data-placeholder' => __( 'Search for a product tag&hellip;', 'facebook-for-woocommerce' ),
2414
+ ],
2415
+ ],
2416
+
2417
+ self::SETTING_PRODUCT_DESCRIPTION_MODE => [
2418
+ 'title' => __( 'Product description sync', 'facebook-for-woocommerce' ),
2419
+ 'type' => 'select',
2420
+ 'class' => 'product-sync-field',
2421
+ 'desc_tip' => __( 'Choose which product description to display in the Facebook catalog.', 'facebook-for-woocommerce' ),
2422
+ 'default' => self::PRODUCT_DESCRIPTION_MODE_STANDARD,
2423
+ 'options' => [
2424
+ self::PRODUCT_DESCRIPTION_MODE_STANDARD => __( 'Standard description', 'facebook-for-woocommerce' ),
2425
+ self::PRODUCT_DESCRIPTION_MODE_SHORT => __( 'Short description', 'facebook-for-woocommerce' ),
2426
+ ],
2427
+ ],
2428
+
2429
+ /** @see \WC_Facebookcommerce_Integration::generate_resync_schedule_html() */
2430
+ /** @see \WC_Facebookcommerce_Integration::validate_resync_schedule_field() */
2431
+ self::SETTING_SCHEDULED_RESYNC_OFFSET => [
2432
+ 'title' => __( 'Force daily resync at', 'facebook-for-woocommerce' ),
2433
+ 'class' => 'product-sync-field resync-schedule-fieldset',
2434
+ 'type' => 'resync_schedule',
2435
+ ],
2436
+
2437
+ [
2438
+ 'title' => __( 'Messenger', 'facebook-for-woocommerce' ),
2439
+ 'type' => 'title',
2440
+ ],
2441
+
2442
+ self::SETTING_ENABLE_MESSENGER => [
2443
+ 'title' => __( 'Enable Messenger', 'facebook-for-woocommerce' ),
2444
+ 'type' => 'checkbox',
2445
+ 'class' => 'messenger-field toggle-fields-group',
2446
+ 'label' => ' ',
2447
+ 'desc_tip' => __( 'Enable and customize Facebook Messenger on your store.', 'facebook-for-woocommerce' ),
2448
+ 'default' => 'no',
2449
+ ],
2450
+
2451
+ self::SETTING_MESSENGER_LOCALE => [
2452
+ 'title' => __( 'Language', 'facebook-for-woocommerce' ),
2453
+ 'type' => 'select',
2454
+ 'class' => 'wc-enhanced-select messenger-field',
2455
+ 'default' => $default_locale,
2456
+ 'options' => $messenger_locales,
2457
+ ],
2458
+
2459
+ /** @see \WC_Facebookcommerce_Integration::generate_messenger_greeting_html() */
2460
+ /** @see \WC_Facebookcommerce_Integration::validate_messenger_greeting_field() */
2461
+ self::SETTING_MESSENGER_GREETING => [
2462
+ 'title' => __( 'Greeting', 'facebook-for-woocommerce' ),
2463
+ 'type' => 'messenger_greeting',
2464
+ 'class' => 'messenger-field',
2465
+ 'default' => __( "Hi! We're here to answer any questions you may have.", 'facebook-for-woocommerce' ),
2466
+ 'css' => 'max-width: 400px; margin-bottom: 10px',
2467
+ 'custom_attributes' => [
2468
+ 'maxlength' => $this->get_messenger_greeting_max_characters(),
2469
+ ],
2470
+ ],
2471
+
2472
+ self::SETTING_MESSENGER_COLOR_HEX => [
2473
+ 'title' => __( 'Colors', 'facebook-for-woocommerce' ),
2474
+ 'type' => 'color',
2475
+ 'class' => 'messenger-field',
2476
+ 'default' => '#0084ff',
2477
+ 'css' => 'width: 6em;',
2478
+ ],
2479
+
2480
+ ];
2481
+
2482
+ $this->form_fields = $form_fields;
2483
+ }
2484
 
2485
 
2486
  /**
2487
+ * Gets the "Manage connection" field HTML.
2488
+ *
2489
+ * @see \WC_Settings_API::generate_title_html()
2490
+ *
2491
+ * @since 1.10.0
2492
  *
2493
+ * @param string|int $key field key or index
2494
+ * @param array $args associative array of field arguments
2495
+ * @return string HTML
2496
  */
2497
+ protected function generate_manage_connection_title_html( $key, array $args = [] ) {
2498
+
2499
+ $key = $this->get_field_key( $key );
2500
+
2501
  ob_start();
2502
 
2503
  ?>
2504
+ </table>
2505
+ <h3 class="wc-settings-sub-title" id="<?php echo esc_attr( $key ); ?>">
2506
+ <?php esc_html_e( 'Connection', 'facebook-for-woocommerce' ); ?>
2507
+ <a
2508
+ id="woocommerce-facebook-settings-manage-connection"
2509
+ class="button"
2510
+ href="#"
2511
+ style="vertical-align: middle; margin-left: 20px;"
2512
+ onclick="facebookConfig();"
2513
+ ><?php esc_html_e( 'Manage connection', 'facebook-for-woocommerce' ); ?></a>
2514
+ </h3>
2515
+ <?php if ( empty( $this->get_page_name() ) ) : ?>
2516
+ <div id="connection-message-invalid">
2517
+ <p style="color: #DC3232;">
2518
+ <?php esc_html_e( 'Your connection has expired.', 'facebook-for-woocommerce' ); ?>
2519
+ <strong>
2520
+ <?php esc_html_e( 'Please click Manage connection > Advanced Options > Update Token to refresh your connection to Facebook.', 'facebook-for-woocommerce' ); ?>
2521
+ </strong>
2522
+ </p>
2523
+ </div>
2524
+ <div id="connection-message-refresh" style="display: none;">
2525
+ <p>
2526
+ <?php esc_html_e( 'Your access token has been updated.', 'facebook-for-woocommerce' ); ?>
2527
+ <strong>
2528
+ <?php esc_html_e( 'Please refresh the page.', 'facebook-for-woocommerce' ); ?>
2529
+ </strong>
2530
+ </p>
2531
+ </div>
2532
+ <?php endif; ?>
2533
+ <table class="form-table">
2534
  <?php
2535
+
2536
  return ob_get_clean();
2537
  }
2538
 
2539
+
2540
  /**
2541
+ * Gets the "Facebook page" field HTML.
2542
+ *
2543
+ * @see \WC_Settings_API::generate_settings_html()
2544
  *
2545
+ * @since 1.10.0
2546
+ *
2547
+ * @param string|int $key field key or index
2548
+ * @param array $args associative array of field arguments
2549
+ * @return string HTML
2550
  */
2551
+ protected function generate_facebook_page_name_html( $key, array $args = [] ) {
 
 
 
 
 
 
 
 
 
 
 
 
 
2552
 
2553
+ $key = $this->get_field_key( $key );
2554
+ $page_name = $this->get_page_name();
2555
+ $page_url = $this->get_page_url();
 
 
 
2556
 
2557
+ ob_start();
2558
 
2559
+ ?>
2560
+ <tr valign="top">
2561
+ <th scope="row" class="titledesc">
2562
+ <?php esc_html_e( 'Facebook page', 'facebook-for-woocommerce' ); ?>
2563
+ </th>
2564
+ <td class="forminp">
2565
+ <?php if ( $page_name ) : ?>
2566
 
2567
+ <?php if ( $page_url ) : ?>
2568
 
2569
+ <a
2570
+ href="<?php echo esc_url( $page_url ); ?>"
2571
+ target="_blank"
2572
+ style="text-decoration: none;">
2573
+ <?php echo esc_html( $page_name ); ?>
2574
+ <span
2575
+ class="dashicons dashicons-external"
2576
+ style="margin-right: 8px; vertical-align: bottom;"
2577
+ ></span>
2578
+ </a>
2579
 
2580
+ <?php else : ?>
2581
 
2582
+ <?php echo esc_html( $page_name ); ?>
 
 
 
 
 
 
2583
 
2584
+ <?php endif; ?>
2585
 
2586
+ <?php else : ?>
 
 
 
 
 
 
2587
 
2588
+ &mdash;
2589
 
2590
+ <?php endif; ?>
2591
+ <input
2592
+ type="hidden"
2593
+ name="<?php echo esc_attr( $key ); ?>"
2594
+ id="<?php echo esc_attr( $key ); ?>"
2595
+ value="<?php echo esc_attr( $this->get_facebook_page_id() ); ?>"
2596
+ />
2597
+ </td>
2598
+ </tr>
2599
+ <?php
2600
 
2601
+ return ob_get_clean();
 
 
 
 
 
2602
  }
2603
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2604
 
2605
  /**
2606
+ * Gets the "Facebook Pixel" field HTML.
2607
+ *
2608
+ * @see \WC_Settings_API::generate_settings_html()
2609
+ *
2610
+ * @since 1.10.0
2611
+ *
2612
+ * @param string|int $key field key or index
2613
+ * @param array $args associative array of field arguments
2614
+ * @return string HTML
2615
  */
2616
+ protected function generate_facebook_pixel_id_html( $key, array $args = [] ) {
 
 
 
2617
 
2618
+ $key = $this->get_field_key( $key );
2619
+ $pixel_id = $this->get_facebook_pixel_id();
 
 
2620
 
2621
+ ob_start();
 
 
 
 
 
 
 
 
 
 
 
 
2622
 
 
 
 
 
 
 
 
 
 
 
 
2623
  ?>
2624
+ <tr valign="top">
2625
+ <th scope="row" class="titledesc">
2626
+ <?php esc_html_e( 'Pixel', 'facebook-for-woocommerce' ); ?>
2627
+ </th>
2628
+ <td class="forminp">
2629
+ <?php if ( $pixel_id ) : ?>
2630
+ <code style="padding: 4px 8px; color: #333;"><?php echo esc_html( $pixel_id ); ?></code>
2631
+ <?php else : ?>
2632
+ &mdash;
2633
+ <?php endif; ?>
2634
+ <input
2635
+ type="hidden"
2636
+ name="<?php echo esc_attr( $key ); ?>"
2637
+ id="<?php echo esc_attr( $key ); ?>"
2638
+ value="<?php echo esc_attr( $pixel_id ); ?>"
2639
+ />
2640
+ </td>
2641
+ </tr>
2642
  <?php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2643
 
2644
+ return ob_get_clean();
2645
+ }
2646
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2647
 
2648
+ /**
2649
+ * Gets the "Create ad" field HTML.
2650
+ *
2651
+ * @see \WC_Settings_API::generate_settings_html()
2652
+ *
2653
+ * @since 1.10.0
2654
+ *
2655
+ * @param string|int $key field key or index
2656
+ * @param array $args associative array of field arguments
2657
+ * @return string HTML
2658
+ */
2659
+ protected function generate_create_ad_html( $key, array $args = [] ) {
2660
+
2661
+ $create_ad_url = sprintf( 'https://www.facebook.com/ads/dia/redirect/?settings_id=%s&version=2&entry_point=admin_panel', rawurlencode( $this->get_external_merchant_settings_id() ) );
2662
+
2663
+ ob_start();
2664
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2665
  ?>
2666
+ <tr valign="top">
2667
+ <th class="forminp" colspan="2">
2668
+ <a
2669
+ class="button button-primary"
2670
+ target="_blank"
2671
+ href="<?php echo esc_url( $create_ad_url ); ?>"
2672
+ ><?php esc_html_e( 'Create ad', 'facebook-for-woocommerce' ); ?></a>
2673
+ </th>
2674
+ </tr>
2675
  <?php
2676
 
2677
+ return ob_get_clean();
 
 
 
 
 
 
 
 
2678
  }
2679
 
2680
+
2681
+ /**
2682
+ * Gets the "Sync products" field HTML.
2683
+ *
2684
+ * @see \WC_Settings_API::generate_title_html()
2685
+ *
2686
+ * @since 1.10.0
2687
+ *
2688
+ * @param string|int $key field key or index
2689
+ * @param array $args associative array of field arguments
2690
+ * @return string HTML
2691
+ */
2692
+ protected function generate_product_sync_title_html( $key, array $args = [] ) {
2693
+
2694
+ $key = $this->get_field_key( $key );
2695
+
2696
+ ob_start();
2697
+
2698
+ ?>
2699
+ </table>
2700
+ <h3 class="wc-settings-sub-title" id="<?php echo esc_attr( $key ); ?>">
2701
+ <?php esc_html_e( 'Product sync', 'facebook-for-woocommerce' ); ?>
2702
+ <a
2703
+ id="woocommerce-facebook-settings-sync-products"
2704
+ class="button product-sync-field"
2705
+ href="#"
2706
+ style="vertical-align: middle; margin-left: 20px;"
2707
+ ><?php esc_html_e( 'Sync products', 'facebook-for-woocommerce' ); ?></a>
2708
+ <span id="sync_progress" style="margin-left: 10px"></span>
2709
+ </h3>
2710
+ <table class="form-table">
2711
+ <?php
2712
+
2713
+ return ob_get_clean();
2714
+ }
2715
+
2716
+
2717
+ /**
2718
+ * Processes and saves options.
2719
+ *
2720
+ * @see \WC_Settings_API::process_admin_options()
2721
+ *
2722
+ * @internal
2723
+ *
2724
+ * @since 1.10.0
2725
+ */
2726
+ public function process_admin_options() {
2727
+
2728
+ $current_resync_offset = $this->get_scheduled_resync_offset();
2729
+
2730
+ parent::process_admin_options();
2731
+
2732
+ $saved_resync_offset = $this->get_scheduled_resync_offset();
2733
+
2734
+ if ( null === $saved_resync_offset || ! $this->is_product_sync_enabled() ) {
2735
+ $this->unschedule_resync();
2736
+ } elseif ( $saved_resync_offset !== $current_resync_offset || false === $this->is_resync_scheduled() ) {
2737
+ $this->schedule_resync( $saved_resync_offset );
2738
+ }
2739
+ }
2740
+
2741
+
2742
+ /**
2743
+ * Generates the force resync fieldset HTML.
2744
+ *
2745
+ * @see \WC_Settings_API::generate_settings_html()
2746
+ *
2747
+ * @since 1.10.0
2748
+ *
2749
+ * @param string $key field key
2750
+ * @param array $data field data
2751
+ * @return string HTML
2752
+ */
2753
+ protected function generate_resync_schedule_html( $key, array $data ) {
2754
+
2755
+ $fieldset_key = $this->get_field_key( $key );
2756
+ $enabled_field_key = $this->get_field_key( 'scheduled_resync_enabled' );
2757
+ $hours_field_key = $this->get_field_key( 'scheduled_resync_hours' );
2758
+ $minutes_field_key = $this->get_field_key( 'scheduled_resync_minutes' );
2759
+ $meridiem_field_key = $this->get_field_key( 'scheduled_resync_meridiem' );
2760
+
2761
+ // check if the sites uses 12-hours or 24-hours time format
2762
+ $time_format = wc_time_format();
2763
+ // TODO replace these string search functions with Framework string helpers {FN 2020-01-31}
2764
+ $is_12_hours = ( false !== strpos( $time_format, 'g' ) || false !== strpos( $time_format, 'h' ) );
2765
+ // default to 24-hours format if no hour specifier is found on the string
2766
+ $is_24_hours = ! $is_12_hours;
2767
+
2768
+ if ( $this->is_scheduled_resync_enabled() ) {
2769
+ try {
2770
+ $offset = $this->get_scheduled_resync_offset();
2771
+ $resync_time = ( new DateTime( 'today' ) )->add( new DateInterval( "PT${offset}S" ) );
2772
+ $resync_hours = $is_24_hours ? $resync_time->format( 'G' ) : $resync_time->format( 'g' );
2773
+ $resync_minutes = $resync_time->format( 'i' );
2774
+ } catch ( \Exception $e ) {}
2775
+ }
2776
+
2777
+ // default to 23:00
2778
+ if ( empty( $resync_hours ) ) {
2779
+ $resync_hours = $is_24_hours ? '23' : '11';
2780
+ $resync_minutes = '00';
2781
+ $resync_meridiem = $is_24_hours ? '' : 'pm';
2782
+ }
2783
+
2784
+ $defaults = [
2785
+ 'title' => '',
2786
+ 'disabled' => false,
2787
+ 'class' => '',
2788
+ 'css' => '',
2789
+ 'desc_tip' => false,
2790
+ ];
2791
+
2792
+ $data = wp_parse_args( $data, $defaults );
2793
+
2794
+ ob_start();
2795
+ ?>
2796
+ <tr valign="top">
2797
+ <th scope="row" class="titledesc">
2798
+ <label for="<?php echo esc_attr( $fieldset_key ); ?>"><?php echo wp_kses_post( $data['title'] ); ?> <?php echo $this->get_tooltip_html( $data ); ?></label>
2799
+ </th>
2800
+ <td class="forminp">
2801
+ <fieldset class="<?php echo esc_attr( $data['class'] ); ?>">
2802
+ <legend class="screen-reader-text"><span><?php echo wp_kses_post( $data['title'] ); ?></span></legend>
2803
+ <input
2804
+ class="toggle-fields-group resync-schedule-field"
2805
+ <?php disabled( $data['disabled'], true ); ?>
2806
+ type="checkbox"
2807
+ name="<?php echo esc_attr( $enabled_field_key ); ?>"
2808
+ id="<?php echo esc_attr( $enabled_field_key ); ?>"
2809
+ style="<?php echo esc_attr( $data['css'] ); ?>"
2810
+ value="1"
2811
+ <?php checked( $this->is_scheduled_resync_enabled() ); ?>
2812
+ />
2813
+ <input
2814
+ class="input-number regular-input resync-schedule-field"
2815
+ type="number"
2816
+ min="0"
2817
+ max="<?php echo $is_24_hours ? 24 : 12; ?>"
2818
+ name="<?php echo esc_attr( $hours_field_key ); ?>"
2819
+ id="<?php echo esc_attr( $hours_field_key ); ?>"
2820
+ style="<?php echo esc_attr( $data['css'] ); ?>"
2821
+ value="<?php echo ! empty( $resync_hours ) ? esc_attr( $resync_hours ) : ''; ?>"
2822
+ <?php disabled( $data['disabled'], true ); ?>
2823
+ />
2824
+ <strong>:</strong>
2825
+ <input
2826
+ class="input-number regular-input resync-schedule-field"
2827
+ type="number"
2828
+ min="0"
2829
+ max="59"
2830
+ name="<?php echo esc_attr( $minutes_field_key ); ?>"
2831
+ id="<?php echo esc_attr( $minutes_field_key ); ?>"
2832
+ style="<?php echo esc_attr( $data['css'] ); ?>"
2833
+ value="<?php echo ! empty( $resync_minutes ) ? esc_attr( $resync_minutes ) : ''; ?>"
2834
+ <?php disabled( $data['disabled'], true ); ?>
2835
+ />
2836
+ <?php if ( ! $is_24_hours ) : ?>
2837
+
2838
+ <select
2839
+ class="resync-schedule-field"
2840
+ name="<?php echo esc_attr( $meridiem_field_key ); ?>"
2841
+ id="<?php echo esc_attr( $meridiem_field_key ); ?>"
2842
+ style="<?php echo esc_attr( $data['css'] ); ?>"
2843
+ <?php disabled( $data['disabled'], true ); ?>>
2844
+ <option
2845
+ <?php selected( true, $this->get_scheduled_resync_offset() < 12 * HOUR_IN_SECONDS, true ); ?>
2846
+ value="am">
2847
+ <?php esc_html_e( 'am', 'facebook-for-woocommerce' ); ?>
2848
+ </option>
2849
+ <option
2850
+ <?php selected( true, $this->get_scheduled_resync_offset() >= 12 * HOUR_IN_SECONDS || ( ! empty( $resync_meridiem ) && 'pm' === $resync_meridiem ), true ); ?>
2851
+ value="pm">
2852
+ <?php esc_html_e( 'pm', 'facebook-for-woocommerce' ); ?>
2853
+ </option>
2854
+ </select>
2855
+
2856
+ <?php endif; ?>
2857
+ <br/>
2858
+ </fieldset>
2859
+ </td>
2860
+ </tr>
2861
+ <?php
2862
+
2863
+ return ob_get_clean();
2864
+ }
2865
+
2866
+
2867
+ /**
2868
+ * Validates force resync field.
2869
+ *
2870
+ * @internal
2871
+ *
2872
+ * @since 1.10.0
2873
+ *
2874
+ * @param string $key field key
2875
+ * @param string $value posted value
2876
+ * @return int|string timestamp or empty string
2877
+ * @throws \Exception
2878
+ */
2879
+ public function validate_resync_schedule_field( $key, $value ) {
2880
+
2881
+ $enabled_field_key = $this->get_field_key( 'scheduled_resync_enabled' );
2882
+ $hours_field_key = $this->get_field_key( 'scheduled_resync_hours' );
2883
+ $minutes_field_key = $this->get_field_key( 'scheduled_resync_minutes' );
2884
+ $meridiem_field_key = $this->get_field_key( 'scheduled_resync_meridiem' );
2885
+
2886
+ // if not enabled or time is empty, return a blank string
2887
+ if ( empty( $_POST[ $enabled_field_key ] ) || empty( $_POST[ $hours_field_key ] ) ) {
2888
+ return '';
2889
+ }
2890
+
2891
+ $posted_hours = (int) sanitize_text_field( wp_unslash( $_POST[ $hours_field_key ] ) );
2892
+ $posted_minutes = (int) sanitize_text_field( wp_unslash( $_POST[ $minutes_field_key ] ) );
2893
+ $posted_minutes = str_pad( $posted_minutes, 2, '0', STR_PAD_LEFT );
2894
+ $posted_meridiem = ! empty( $_POST[ $meridiem_field_key ] ) ? sanitize_text_field( wp_unslash( $_POST[ $meridiem_field_key ] ) ) : '';
2895
+
2896
+ // attempts to parse the time (not using date_create_from_format because it considers 30:00 to be a valid time)
2897
+ $parsed_time = strtotime( "$posted_hours:$posted_minutes $posted_meridiem" );
2898
+
2899
+ if ( false === $parsed_time ) {
2900
+ throw new \Exception( "Invalid resync schedule time: $posted_hours:$posted_minutes $posted_meridiem" );
2901
+ }
2902
+
2903
+ $midnight = ( new DateTime() )->setTimestamp( $parsed_time )->setTime( 0,0,0 );
2904
+
2905
+ return $parsed_time - $midnight->getTimestamp();
2906
+ }
2907
+
2908
+
2909
+ /**
2910
+ * Gets the "Messenger greeting" field HTML.
2911
+ *
2912
+ * @see \WC_Settings_API::generate_textarea_html()
2913
+ *
2914
+ * @since 1.10.0
2915
+ *
2916
+ * @param string|int $key field key or index
2917
+ * @param array $args associative array of field arguments
2918
+ * @return string HTML
2919
+ */
2920
+ protected function generate_messenger_greeting_html( $key, array $args = [] ) {
2921
+
2922
+ // TODO replace strlen() here with Framework helper method to account for multibyte characters {FN 2020-01-30}
2923
+ $chars = max( 0, strlen( $this->get_messenger_greeting() ) );
2924
+ $max_chars = max( 0, $this->get_messenger_greeting_max_characters() );
2925
+ $field_id = $this->get_field_key( $key );
2926
+ $counter_class = $field_id . '-characters-count';
2927
+
2928
+ wc_enqueue_js( "
2929
+ jQuery( document ).ready( function( $ ) {
2930
+ $( 'span." . esc_js( $counter_class ) . "' ).insertAfter( 'textarea#" . esc_js( $field_id ) . "' );
2931
+ } );
2932
+ " );
2933
+
2934
+ ob_start();
2935
+
2936
+ ?>
2937
+ <span
2938
+ style="display: none; font-family: monospace; font-size: 0.9em;"
2939
+ class="<?php echo sanitize_html_class( $counter_class ); ?> characters-counter"
2940
+ ><?php echo esc_html( $chars . ' / ' . $max_chars ); ?> <span style="display:none;"><?php echo esc_html( $this->get_messenger_greeting_long_warning_text() ); ?></span></span>
2941
+ <?php
2942
+
2943
+ $counter = ob_get_clean();
2944
+
2945
+ return $this->generate_textarea_html( $key, $args ) . $counter;
2946
+ }
2947
+
2948
+
2949
+ /**
2950
+ * Validates the Messenger greeting field.
2951
+ *
2952
+ * @see \WC_Settings_API::validate_textarea_field()
2953
+ *
2954
+ * @since 1.10.0
2955
+ *
2956
+ * @param string|int $key field key or index
2957
+ * @param string $value field submitted value
2958
+ * @throws \Exception on validation errors
2959
+ * @return string some HTML allowed
2960
+ */
2961
+ protected function validate_messenger_greeting_field( $key, $value ) {
2962
+
2963
+ $value = is_string( $value ) ? trim( sanitize_text_field( wp_unslash( $value ) ) ) : '';
2964
+
2965
+ $max_chars = $this->get_messenger_greeting_max_characters();
2966
+ $value_length = function_exists( 'mb_strlen' ) ? mb_strlen( $value, Framework\SV_WC_Helper::MB_ENCODING ) : strlen( $value );
2967
+
2968
+ if ( $value_length > $max_chars ) {
2969
+
2970
+ throw new Framework\SV_WC_Plugin_Exception( sprintf(
2971
+ $this->get_messenger_greeting_long_warning_text() . ' %s',
2972
+ __( "The greeting hasn't been updated.", 'facebook-for-woocommerce' )
2973
+ ) );
2974
+ }
2975
+
2976
+ return $value;
2977
+ }
2978
+
2979
+
2980
+ /**
2981
+ * Gets a warning text to be displayed when the Messenger greeting text exceeds the maximum length.
2982
+ *
2983
+ * @since 1.10.0
2984
+ *
2985
+ * @return string
2986
+ */
2987
+ private function get_messenger_greeting_long_warning_text() {
2988
+
2989
+ return sprintf(
2990
+ /* translators: Placeholder: %d - maximum number of allowed characters */
2991
+ __( 'The Messenger greeting must be %d characters or less.', 'facebook-for-woocommerce' ),
2992
+ $this->get_messenger_greeting_max_characters()
2993
+ );
2994
+ }
2995
+
2996
+
2997
+ /** Getter methods ************************************************************************************************/
2998
+
2999
+
3000
+ /**
3001
+ * Gets the page access token.
3002
+ *
3003
+ * @since 1.10.0
3004
+ *
3005
+ * @return string
3006
+ */
3007
+ public function get_page_access_token() {
3008
+
3009
+ if ( ! is_string( $this->page_access_token ) ) {
3010
+
3011
+ $value = get_option( self::OPTION_PAGE_ACCESS_TOKEN, '' );
3012
+
3013
+ $this->page_access_token = is_string( $value ) ? $value : '';
3014
+ }
3015
+
3016
+ /**
3017
+ * Filters the Facebook page access token.
3018
+ *
3019
+ * @since 1.10.0
3020
+ *
3021
+ * @param string $page_access_token Facebook page access token
3022
+ * @param \WC_Facebookcommerce_Integration $integration the integration instance
3023
+ */
3024
+ return (string) apply_filters( 'wc_facebook_page_access_token', $this->page_access_token, $this );
3025
+ }
3026
+
3027
+
3028
+ /**
3029
+ * Gets the product catalog ID.
3030
+ *
3031
+ * @since 1.10.0
3032
+ *
3033
+ * @return string
3034
+ */
3035
+ public function get_product_catalog_id() {
3036
+
3037
+ if ( ! is_string( $this->product_catalog_id ) ) {
3038
+
3039
+ $value = get_option( self::OPTION_PRODUCT_CATALOG_ID, '' );
3040
+
3041
+ $this->product_catalog_id = is_string( $value ) ? $value : '';
3042
+ }
3043
+
3044
+ /**
3045
+ * Filters the Facebook product catalog ID.
3046
+ *
3047
+ * @since 1.10.0
3048
+ *
3049
+ * @param string $product_catalog_id Facebook product catalog ID
3050
+ * @param \WC_Facebookcommerce_Integration $integration the integration instance
3051
+ */
3052
+ return (string) apply_filters( 'wc_facebook_product_catalog_id', $this->product_catalog_id, $this );
3053
+ }
3054
+
3055
+
3056
+ /**
3057
+ * Gets the external merchant settings ID.
3058
+ *
3059
+ * @since 1.10.0
3060
+ *
3061
+ * @return string
3062
+ */
3063
+ public function get_external_merchant_settings_id() {
3064
+
3065
+ if ( ! is_string( $this->external_merchant_settings_id ) ) {
3066
+
3067
+ $value = get_option( self::OPTION_EXTERNAL_MERCHANT_SETTINGS_ID, '' );
3068
+
3069
+ $this->external_merchant_settings_id = is_string( $value ) ? $value : '';
3070
+ }
3071
+
3072
+ /**
3073
+ * Filters the Facebook external merchant settings ID.
3074
+ *
3075
+ * @since 1.10.0
3076
+ *
3077
+ * @param string $external_merchant_settings_id Facebook external merchant settings ID
3078
+ * @param \WC_Facebookcommerce_Integration $integration the integration instance
3079
+ */
3080
+ return (string) apply_filters( 'wc_facebook_external_merchant_settings_id', $this->external_merchant_settings_id, $this );
3081
+ }
3082
+
3083
+
3084
+ /**
3085
+ * Gets the feed ID.
3086
+ *
3087
+ * @since 1.10.0
3088
+ *
3089
+ * @return string
3090
+ */
3091
+ public function get_feed_id() {
3092
+
3093
+ if ( ! is_string( $this->feed_id ) ) {
3094
+
3095
+ $value = get_option( self::OPTION_FEED_ID, '' );
3096
+
3097
+ $this->feed_id = is_string( $value ) ? $value : '';
3098
+ }
3099
+
3100
+ /**
3101
+ * Filters the Facebook feed ID.
3102
+ *
3103
+ * @since 1.10.0
3104
+ *
3105
+ * @param string $feed_id Facebook feed ID
3106
+ * @param \WC_Facebookcommerce_Integration $integration the integration instance
3107
+ */
3108
+ return (string) apply_filters( 'wc_facebook_feed_id', $this->feed_id, $this );
3109
+ }
3110
+
3111
+
3112
+ /**
3113
+ * Gets the Facebook pixel install time in UTC seconds.
3114
+ *
3115
+ * @since 1.10.0
3116
+ *
3117
+ * @return int
3118
+ */
3119
+ public function get_pixel_install_time() {
3120
+
3121
+ if ( ! (int) $this->pixel_install_time ) {
3122
+
3123
+ $value = (int) get_option( self::OPTION_PIXEL_INSTALL_TIME, 0 );
3124
+
3125
+ $this->pixel_install_time = $value ?: null;
3126
+ }
3127
+
3128
+ /**
3129
+ * Filters the Facebook pixel install time.
3130
+ *
3131
+ * @since 1.10.0
3132
+ *
3133
+ * @param string $pixel_install_time Facebook pixel install time in UTC seconds, or null if none set
3134
+ * @param \WC_Facebookcommerce_Integration $integration the integration instance
3135
+ */
3136
+ return (int) apply_filters( 'wc_facebook_pixel_install_time', $this->pixel_install_time, $this );
3137
+ }
3138
+
3139
+
3140
+ /**
3141
+ * Gets the configured JS SDK version.
3142
+ *
3143
+ * @since 1.10.0
3144
+ *
3145
+ * @return string
3146
+ */
3147
+ public function get_js_sdk_version() {
3148
+
3149
+ if ( ! is_string( $this->js_sdk_version ) ) {
3150
+
3151
+ $value = get_option( self::OPTION_JS_SDK_VERSION, '' );
3152
+
3153
+ $this->js_sdk_version = is_string( $value ) ? $value : '';
3154
+ }
3155
+
3156
+ /**
3157
+ * Filters the Facebook JS SDK version.
3158
+ *
3159
+ * @since 1.10.0
3160
+ *
3161
+ * @param string $js_sdk_version Facebook JS SDK version
3162
+ * @param \WC_Facebookcommerce_Integration $integration the integration instance
3163
+ */
3164
+ return (string) apply_filters( 'wc_facebook_js_sdk_version', $this->js_sdk_version, $this );
3165
+ }
3166
+
3167
+
3168
+ /**
3169
+ * Gets the configured Facebook page ID.
3170
+ *
3171
+ * @since 1.10.0
3172
+ *
3173
+ * @return string
3174
+ */
3175
+ public function get_facebook_page_id() {
3176
+
3177
+ /**
3178
+ * Filters the configured Facebook page ID.
3179
+ *
3180
+ * @since 1.10.0
3181
+ *
3182
+ * @param string $page_id the configured Facebook page ID
3183
+ * @param \WC_Facebookcommerce_Integration $integration the integration instance
3184
+ */
3185
+ return (string) apply_filters( 'wc_facebook_page_id', $this->get_option( self::SETTING_FACEBOOK_PAGE_ID, '' ), $this );
3186
+ }
3187
+
3188
+
3189
+ /**
3190
+ * Gets the configured Facebook pixel ID.
3191
+ *
3192
+ * @since 1.10.0
3193
+ *
3194
+ * @return string
3195
+ */
3196
+ public function get_facebook_pixel_id() {
3197
+
3198
+ /**
3199
+ * Filters the configured Facebook pixel ID.
3200
+ *
3201
+ * @since 1.10.0
3202
+ *
3203
+ * @param string $pixel_id the configured Facebook pixel ID
3204
+ * @param \WC_Facebookcommerce_Integration $integration the integration instance
3205
+ */
3206
+ return (string) apply_filters( 'wc_facebook_pixel_id', $this->get_option( self::SETTING_FACEBOOK_PIXEL_ID, '' ), $this );
3207
+ }
3208
+
3209
+
3210
+ /**
3211
+ * Gets the IDs of the categories to be excluded from sync.
3212
+ *
3213
+ * @since 1.10.0
3214
+ *
3215
+ * @return int[]
3216
+ */
3217
+ public function get_excluded_product_category_ids() {
3218
+
3219
+ /**
3220
+ * Filters the configured excluded product category IDs.
3221
+ *
3222
+ * @since 1.10.0
3223
+ *
3224
+ * @param int[] $category_ids the configured excluded product category IDs
3225
+ * @param \WC_Facebookcommerce_Integration $integration the integration instance
3226
+ */
3227
+ return (array) apply_filters( 'wc_facebook_excluded_product_category_ids', $this->get_option( self::SETTING_EXCLUDED_PRODUCT_CATEGORY_IDS, [] ), $this );
3228
+ }
3229
+
3230
+
3231
+ /**
3232
+ * Gets the IDs of the tags to be excluded from sync.
3233
+ *
3234
+ * @since 1.10.0
3235
+ *
3236
+ * @return int[]
3237
+ */
3238
+ public function get_excluded_product_tag_ids() {
3239
+
3240
+ /**
3241
+ * Filters the configured excluded product tag IDs.
3242
+ *
3243
+ * @since 1.10.0
3244
+ *
3245
+ * @param int[] $tag_ids the configured excluded product tag IDs
3246
+ * @param \WC_Facebookcommerce_Integration $integration the integration instance
3247
+ */
3248
+ return (array) apply_filters( 'wc_facebook_excluded_product_tag_ids', $this->get_option( self::SETTING_EXCLUDED_PRODUCT_TAG_IDS, [] ), $this );
3249
+ }
3250
+
3251
+
3252
+ /**
3253
+ * Gets the configured product description mode.
3254
+ *
3255
+ * @since 1.10.0
3256
+ *
3257
+ * @return string
3258
+ */
3259
+ public function get_product_description_mode() {
3260
+
3261
+ /**
3262
+ * Filters the configured product description mode.
3263
+ *
3264
+ * @since 1.10.0
3265
+ *
3266
+ * @param string $mode the configured product description mode
3267
+ * @param \WC_Facebookcommerce_Integration $integration the integration instance
3268
+ */
3269
+ $mode = (string) apply_filters( 'wc_facebook_product_description_mode', $this->get_option( self::SETTING_PRODUCT_DESCRIPTION_MODE, self::PRODUCT_DESCRIPTION_MODE_STANDARD ), $this );
3270
+
3271
+ $valid_modes = [
3272
+ self::PRODUCT_DESCRIPTION_MODE_STANDARD,
3273
+ self::PRODUCT_DESCRIPTION_MODE_SHORT,
3274
+ ];
3275
+
3276
+ if ( ! in_array( $mode, $valid_modes, true ) ) {
3277
+ $mode = self::PRODUCT_DESCRIPTION_MODE_STANDARD;
3278
+ }
3279
+
3280
+ return $mode;
3281
+ }
3282
+
3283
+
3284
+ /**
3285
+ * Gets the configured scheduled re-sync offset in seconds.
3286
+ *
3287
+ * Returns null if no offset is configured.
3288
+ *
3289
+ * @since 1.10.0
3290
+ *
3291
+ * @return int|null
3292
+ */
3293
+ public function get_scheduled_resync_offset() {
3294
+
3295
+ /**
3296
+ * Filters the configured scheduled re-sync offset.
3297
+ *
3298
+ * @since 1.10.0
3299
+ *
3300
+ * @param int|null $offset the configured scheduled re-sync offset in seconds
3301
+ * @param \WC_Facebookcommerce_Integration $integration the integration instance
3302
+ */
3303
+ $offset = (int) apply_filters( 'wc_facebook_scheduled_resync_offset', $this->get_option( self::SETTING_SCHEDULED_RESYNC_OFFSET, null ), $this );
3304
+
3305
+ if ( ! $offset ) {
3306
+ $offset = null;
3307
+ }
3308
+
3309
+ return $offset;
3310
+ }
3311
+
3312
+
3313
+ /**
3314
+ * Gets the configured Facebook messenger locale.
3315
+ *
3316
+ * @since 1.10.0
3317
+ *
3318
+ * @return string
3319
+ */
3320
+ public function get_messenger_locale() {
3321
+
3322
+ /**
3323
+ * Filters the configured Facebook messenger locale.
3324
+ *
3325
+ * @since 1.10.0
3326
+ *
3327
+ * @param string $locale the configured Facebook messenger locale
3328
+ * @param \WC_Facebookcommerce_Integration $integration the integration instance
3329
+ */
3330
+ return (string) apply_filters( 'wc_facebook_messenger_locale', $this->get_option( self::SETTING_MESSENGER_LOCALE, 'en_US' ), $this );
3331
+ }
3332
+
3333
+
3334
+ /**
3335
+ * Gets the configured Facebook messenger greeting.
3336
+ *
3337
+ * @since 1.10.0
3338
+ *
3339
+ * @return string
3340
+ */
3341
+ public function get_messenger_greeting() {
3342
+
3343
+ /**
3344
+ * Filters the configured Facebook messenger greeting.
3345
+ *
3346
+ * @since 1.10.0
3347
+ *
3348
+ * @param string $greeting the configured Facebook messenger greeting
3349
+ * @param \WC_Facebookcommerce_Integration $integration the integration instance
3350
+ */
3351
+ $greeting = (string) apply_filters( 'wc_facebook_messenger_greeting', $this->get_option( self::SETTING_MESSENGER_GREETING, '' ), $this );
3352
+
3353
+ // TODO: update to SV_WC_Helper::str_truncate() when frameworked {CW 2020-01-22}
3354
+ return substr( $greeting, 0, $this->get_messenger_greeting_max_characters() );
3355
+ }
3356
+
3357
+
3358
+ /**
3359
+ * Gets the maximum number of characters allowed in the messenger greeting.
3360
+ *
3361
+ * @since 1.10.0
3362
+ *
3363
+ * @return int
3364
+ */
3365
+ public function get_messenger_greeting_max_characters() {
3366
+
3367
+ $default = 80;
3368
+
3369
+ /**
3370
+ * Filters the maximum number of characters allowed in the messenger greeting.
3371
+ *
3372
+ * @since 1.10.0
3373
+ *
3374
+ * @param int $max the maximum number of characters allowed in the messenger greeting
3375
+ * @param \WC_Facebookcommerce_Integration $integration the integration instance
3376
+ */
3377
+ $max = (int) apply_filters( 'wc_facebook_messenger_greeting_max_characters', $default, $this );
3378
+
3379
+ return $max < 1 ? $default : $max;
3380
+ }
3381
+
3382
+
3383
+ /**
3384
+ * Gets the configured Facebook messenger color hex.
3385
+ *
3386
+ * This is used to style the messenger UI.
3387
+ *
3388
+ * @since 1.10.0
3389
+ *
3390
+ * @return string
3391
+ */
3392
+ public function get_messenger_color_hex() {
3393
+
3394
+ /**
3395
+ * Filters the configured Facebook messenger color hex.
3396
+ *
3397
+ * @since 1.10.0
3398
+ *
3399
+ * @param string $hex the configured Facebook messenger color hex
3400
+ * @param \WC_Facebookcommerce_Integration $integration the integration instance
3401
+ */
3402
+ return (string) apply_filters( 'wc_facebook_messenger_color_hex', $this->get_option( self::SETTING_MESSENGER_COLOR_HEX, '' ), $this );
3403
+ }
3404
+
3405
+
3406
+ /** Setter methods ************************************************************************************************/
3407
+
3408
+
3409
+ /**
3410
+ * Updates the Facebook page access token.
3411
+ *
3412
+ * @since 1.10.0
3413
+ *
3414
+ * @param string $value page access token value
3415
+ */
3416
+ public function update_page_access_token( $value ) {
3417
+
3418
+ $this->page_access_token = $this->sanitize_facebook_credential( $value );
3419
+
3420
+ update_option( self::OPTION_PAGE_ACCESS_TOKEN, $this->page_access_token );
3421
+ }
3422
+
3423
+
3424
+ /**
3425
+ * Updates the Facebook product catalog ID.
3426
+ *
3427
+ * @since 1.10.0
3428
+ *
3429
+ * @param string $value product catalog ID value
3430
+ */
3431
+ public function update_product_catalog_id( $value ) {
3432
+
3433
+ $this->product_catalog_id = $this->sanitize_facebook_credential( $value );
3434
+
3435
+ update_option( self::OPTION_PRODUCT_CATALOG_ID, $this->product_catalog_id );
3436
+ }
3437
+
3438
+
3439
+ /**
3440
+ * Updates the Facebook external merchant settings ID.
3441
+ *
3442
+ * @since 1.10.0
3443
+ *
3444
+ * @param string $value external merchant settings ID value
3445
+ */
3446
+ public function update_external_merchant_settings_id( $value ) {
3447
+
3448
+ $this->external_merchant_settings_id = $this->sanitize_facebook_credential( $value );
3449
+
3450
+ update_option( self::OPTION_EXTERNAL_MERCHANT_SETTINGS_ID, $this->external_merchant_settings_id );
3451
+ }
3452
+
3453
+
3454
+ /**
3455
+ * Updates the Facebook feed ID.
3456
+ *
3457
+ * @since 1.10.0
3458
+ *
3459
+ * @param string $value feed ID value
3460
+ */
3461
+ public function update_feed_id( $value ) {
3462
+
3463
+ $this->feed_id = $this->sanitize_facebook_credential( $value );
3464
+
3465
+ update_option( self::OPTION_FEED_ID, $this->feed_id );
3466
+ }
3467
+
3468
+
3469
+ /**
3470
+ * Updates the Facebook pixel install time.
3471
+ *
3472
+ * @since 1.10.0
3473
+ *
3474
+ * @param int $value pixel install time, in UTC seconds
3475
+ */
3476
+ public function update_pixel_install_time( $value ) {
3477
+
3478
+ $value = (int) $value;
3479
+
3480
+ $this->pixel_install_time = $value ?: null;
3481
+
3482
+ update_option( self::OPTION_PIXEL_INSTALL_TIME, $value ?: '' );
3483
+ }
3484
+
3485
+
3486
+ /**
3487
+ * Updates the Facebook JS SDK version.
3488
+ *
3489
+ * @since 1.10.0
3490
+ *
3491
+ * @param string $value JS SDK version
3492
+ */
3493
+ public function update_js_sdk_version( $value ) {
3494
+
3495
+ $this->js_sdk_version = $this->sanitize_facebook_credential( $value );
3496
+
3497
+ update_option( self::OPTION_JS_SDK_VERSION, $this->js_sdk_version );
3498
+ }
3499
+
3500
+
3501
+ /**
3502
+ * Sanitizes a value that's a Facebook credential.
3503
+ *
3504
+ * @since 1.10.0
3505
+ *
3506
+ * @param string $value value to sanitize
3507
+ * @return string
3508
+ */
3509
+ private function sanitize_facebook_credential( $value ) {
3510
+
3511
+ return wc_clean( is_string( $value ) ? $value : '' );
3512
+ }
3513
+
3514
+
3515
+ /** Conditional methods *******************************************************************************************/
3516
+
3517
+
3518
+ /**
3519
+ * Determines whether Facebook for WooCommerce is configured.
3520
+ *
3521
+ * @since 1.10.0
3522
+ *
3523
+ * @return bool
3524
+ */
3525
+ public function is_configured() {
3526
+
3527
+ return $this->get_page_access_token() && $this->get_facebook_page_id();
3528
+ }
3529
+
3530
+
3531
+ /**
3532
+ * Determines whether advanced matching is enabled.
3533
+ *
3534
+ * @since 1.10.0
3535
+ *
3536
+ * @return bool
3537
+ */
3538
+ public function is_advanced_matching_enabled() {
3539
+
3540
+ /**
3541
+ * Filters whether advanced matching is enabled.
3542
+ *
3543
+ * @since 1.10.0
3544
+ *
3545
+ * @param bool $is_enabled whether advanced matching is enabled
3546
+ * @param \WC_Facebookcommerce_Integration $integration the integration instance
3547
+ */
3548
+ return (bool) apply_filters( 'wc_facebook_is_advanced_matching_enabled', 'yes' === $this->get_option( self::SETTING_ENABLE_ADVANCED_MATCHING ), $this );
3549
+ }
3550
+
3551
+
3552
+ /**
3553
+ * Determines whether product sync is enabled.
3554
+ *
3555
+ * @since 1.10.0
3556
+ *
3557
+ * @return bool
3558
+ */
3559
+ public function is_product_sync_enabled() {
3560
+
3561
+ /**
3562
+ * Filters whether product sync is enabled.
3563
+ *
3564
+ * @since 1.10.0
3565
+ *
3566
+ * @param bool $is_enabled whether product sync is enabled
3567
+ * @param \WC_Facebookcommerce_Integration $integration the integration instance
3568
+ */
3569
+ return (bool) apply_filters( 'wc_facebook_is_product_sync_enabled', 'yes' === $this->get_option( self::SETTING_ENABLE_PRODUCT_SYNC ), $this );
3570
+ }
3571
+
3572
+
3573
+ /**
3574
+ * Determines whether the scheduled re-sync is enabled.
3575
+ *
3576
+ * @since 1.10.0
3577
+ *
3578
+ * @return bool
3579
+ */
3580
+ public function is_scheduled_resync_enabled() {
3581
+
3582
+ /**
3583
+ * Filters whether the scheduled re-sync is enabled.
3584
+ *
3585
+ * @since 1.10.0
3586
+ *
3587
+ * @param bool $is_enabled whether the scheduled re-sync is enabled
3588
+ * @param \WC_Facebookcommerce_Integration $integration the integration instance
3589
+ */
3590
+ return (bool) apply_filters( 'wc_facebook_is_scheduled_resync_enabled', $this->get_scheduled_resync_offset(), $this );
3591
+ }
3592
+
3593
+
3594
+ /**
3595
+ * Determines whether the Facebook messenger is enabled.
3596
+ *
3597
+ * @since 1.10.0
3598
+ *
3599
+ * @return bool
3600
+ */
3601
+ public function is_messenger_enabled() {
3602
+
3603
+ /**
3604
+ * Filters whether the Facebook messenger is enabled.
3605
+ *
3606
+ * @since 1.10.0
3607
+ *
3608
+ * @param bool $is_enabled whether the Facebook messenger is enabled
3609
+ * @param \WC_Facebookcommerce_Integration $integration the integration instance
3610
+ */
3611
+ return (bool) apply_filters( 'wc_facebook_is_messenger_enabled', 'yes' === $this->get_option( self::SETTING_ENABLE_MESSENGER ), $this );
3612
+ }
3613
+
3614
+
3615
+ /**
3616
+ * Gets message HTML.
3617
+ *
3618
+ * @return string
3619
+ */
3620
+ private function get_message_html( $message, $type = 'error' ) {
3621
+ ob_start();
3622
+
3623
+ ?>
3624
+ <div class="notice is-dismissible notice-<?php echo esc_attr( $type ); ?>">
3625
+ <p>
3626
+ <?php
3627
+ // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
3628
+ echo $message;
3629
+ ?>
3630
+ </p>
3631
+ </div>
3632
+ <?php
3633
+
3634
+ return ob_get_clean();
3635
+ }
3636
+
3637
+
3638
+ /**
3639
+ * Displays relevant messages to user from transients, clear once displayed.
3640
+ */
3641
+ public function maybe_display_facebook_api_messages() {
3642
+
3643
+ $error_msg = get_transient( 'facebook_plugin_api_error' );
3644
+
3645
+ if ( $error_msg ) {
3646
+
3647
+ $message = sprintf(
3648
+ __(
3649
+ 'Facebook extension error: %s ',
3650
+ 'facebook-for-woocommerce'
3651
+ ),
3652
+ $error_msg
3653
+ );
3654
+
3655
+ // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
3656
+ echo $this->get_message_html( $message );
3657
+
3658
+ delete_transient( 'facebook_plugin_api_error' );
3659
+
3660
+ WC_Facebookcommerce_Utils::fblog(
3661
+ $error_msg,
3662
+ [],
3663
+ true
3664
+ );
3665
+ }
3666
+
3667
+ $warning_msg = get_transient( 'facebook_plugin_api_warning' );
3668
+
3669
+ if ( $warning_msg ) {
3670
+
3671
+ // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
3672
+ echo $this->get_message_html( $warning_msg, 'warning' );
3673
+
3674
+ delete_transient( 'facebook_plugin_api_warning' );
3675
+ }
3676
+
3677
+ $success_msg = get_transient( 'facebook_plugin_api_success' );
3678
+
3679
+ if ( $success_msg ) {
3680
+
3681
+ // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
3682
+ echo $this->get_message_html( $success_msg, 'success' );
3683
+
3684
+ delete_transient( 'facebook_plugin_api_success' );
3685
+ }
3686
+
3687
+ $info_msg = get_transient( 'facebook_plugin_api_info' );
3688
+
3689
+ if ( $info_msg ) {
3690
+
3691
+ // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
3692
+ echo $this->get_message_html( $info_msg, 'info' );
3693
+
3694
+ delete_transient( 'facebook_plugin_api_info' );
3695
+ }
3696
+
3697
+ $sticky_msg = get_transient( 'facebook_plugin_api_sticky' );
3698
+
3699
+ if ( $sticky_msg ) {
3700
+
3701
+ // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
3702
+ echo $this->get_message_html( $sticky_msg, 'info' );
3703
+
3704
+ // transient must be deleted elsewhere, or wait for timeout
3705
+ }
3706
+ }
3707
+
3708
+
3709
+ /**
3710
+ * Gets the name of the configured Facebook page.
3711
+ *
3712
+ * @return string
3713
+ */
3714
+ public function get_page_name() {
3715
+
3716
+ if ( $this->is_configured() ) {
3717
+ $page_name = $this->fbgraph->get_page_name( $this->get_facebook_page_id(), $this->get_page_access_token() );
3718
+ } else {
3719
+ $page_name = '';
3720
+ }
3721
+
3722
+ return is_string( $page_name ) ? $page_name : '';
3723
+ }
3724
+
3725
+
3726
+ /**
3727
+ * Gets the Facebook page URL.
3728
+ *
3729
+ * @since 1.10.0
3730
+ *
3731
+ * @return string
3732
+ */
3733
+ public function get_page_url() {
3734
+
3735
+ if ( $this->is_configured() ) {
3736
+ $page_url = $this->fbgraph->get_page_url( $this->get_facebook_page_id(), $this->get_page_access_token() );
3737
+ } else {
3738
+ $page_url = '';
3739
+ }
3740
+
3741
+ return is_string( $page_url ) ? $page_url : '';
3742
+ }
3743
+
3744
+
3745
+ /**
3746
+ * Gets Messenger or Instagram tooltip message.
3747
+ *
3748
+ * @return string
3749
+ */
3750
+ function get_nux_message_ifexist() {
3751
+
3752
+ $nux_type_to_elemid_map = [
3753
+ 'messenger_chat' => 'connect_button',
3754
+ 'instagram_shopping' => 'connect_button',
3755
+ ];
3756
+
3757
+ $nux_type_to_message_map = [
3758
+ 'messenger_chat' => __( 'Get started with Messenger Customer Chat' ),
3759
+ 'instagram_shopping' => __( 'Get started with Instagram Shopping' ),
3760
+ ];
3761
+
3762
+ $message = '';
3763
+
3764
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended
3765
+ if ( isset( $_GET['nux'] ) ) {
3766
+
3767
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended
3768
+ $nux_type = sanitize_text_field( wp_unslash( $_GET['nux'] ) );
3769
+
3770
+ ob_start();
3771
+
3772
+ ?>
3773
+
3774
+ <div class="nux-message" style="display: none;"
3775
+ data-target="<?php echo esc_attr( $nux_type_to_elemid_map[ $nux_type ] ); ?>">
3776
+ <div class="nux-message-text">
3777
+ <?php echo esc_attr( $nux_type_to_message_map[ $nux_type ] ); ?>
3778
+ </div>
3779
+ <div class="nux-message-arrow"></div>
3780
+ <i class="nux-message-close-btn">x</i>
3781
+ </div>
3782
+ <script>( function () { fbe_init_nux_messages(); } )();</script>
3783
+
3784
+ <?php
3785
+
3786
+ $message = ob_get_clean();
3787
+ }
3788
+
3789
+ return $message;
3790
+ }
3791
+
3792
+
3793
+ /**
3794
+ * Admin Panel Options
3795
+ */
3796
+ function admin_options() {
3797
+
3798
+ $page_name = $this->get_page_name();
3799
+ $can_manage = current_user_can( 'manage_woocommerce' );
3800
+ $pre_setup = empty( $this->get_facebook_page_id() ) || empty( $this->get_page_access_token() );
3801
+ $apikey_invalid = ! $pre_setup && $this->get_page_access_token() && ! $page_name;
3802
+
3803
+ $remove_http_active = is_plugin_active( 'remove-http/remove-http.php' );
3804
+ $https_will_be_stripped = $remove_http_active && ! get_option( 'factmaven_rhttp' )['external'];
3805
+
3806
+ if ( $https_will_be_stripped ) {
3807
+
3808
+ $this->display_sticky_message(
3809
+ __( 'You\'re using Remove HTTP which has incompatibilities with our extension. Please disable it, or select the "Ignore external links" option on the Remove HTTP settings page.' )
3810
+ );
3811
+ }
3812
+
3813
+ ?>
3814
+
3815
+ <h2><?php esc_html_e( 'Facebook', 'facebook-for-woocommerce' ); ?></h2>
3816
+
3817
+ <p>
3818
+ <?php esc_html_e( 'Control how WooCommerce integrates with your Facebook store.', 'facebook-for-woocommerce' ); ?>
3819
+ </p>
3820
+
3821
+ <div><input type="hidden" name="section" value="<?php echo esc_attr( $this->id ); ?>" /></div>
3822
+
3823
+ <div id="fbsetup" <?php echo $this->is_configured() ? 'style="display: none"' : ''; ?>>
3824
+ <div class="wrapper">
3825
+ <header>
3826
+ <div class="help-center">
3827
+ <a href="https://www.facebook.com/business/help/900699293402826" target="_blank">Help Center <i class="help-center-icon"></i></a>
3828
+ </div>
3829
+ </header>
3830
+
3831
+ <div class="content">
3832
+ <h1 id="setup_h1"><?php esc_html_e( 'Grow your business on Facebook', 'facebook-for-woocommerce' ); ?></h1>
3833
+
3834
+ <h2>
3835
+ <?php esc_html_e( 'Use this WooCommerce and Facebook integration to:', 'facebook-for-woocommerce' ); ?>
3836
+ </h2>
3837
+
3838
+ <ul>
3839
+ <li id="setup_l1"><?php esc_html_e( 'Easily install a tracking pixel', 'facebook-for-woocommerce' ); ?></li>
3840
+ <li id="setup_l2"><?php esc_html_e( 'Upload your products and create a shop', 'facebook-for-woocommerce' ); ?></li>
3841
+ <li id="setup_l3"><?php esc_html_e( 'Create dynamic ads with your products and pixel', 'facebook-for-woocommerce' ); ?></li>
3842
+ </ul>
3843
+
3844
+ <span
3845
+ <?php $external_merchant_settings_id = $this->get_external_merchant_settings_id(); ?>
3846
+ <?php echo ( ! $can_manage || $apikey_invalid || ! isset( $external_merchant_settings_id ) ) ? ' style="pointer-events: none;"' : ''; ?>>
3847
+
3848
+ <a href="#" class="btn pre-setup" onclick="facebookConfig()" id="cta_button">
3849
+ <?php esc_html_e( 'Get Started', 'facebook-for-woocommerce' ); ?>
3850
+ </a>
3851
+
3852
+ </span>
3853
+
3854
+ <?php
3855
+ // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
3856
+ echo $this->get_nux_message_ifexist();
3857
+ ?>
3858
+
3859
+ </div>
3860
+ </div>
3861
+ </div>
3862
+
3863
+ <div id="integration-settings" <?php echo ! $this->is_configured() ? 'style="display: none"' : ''; ?>>
3864
+ <table class="form-table"><?php $this->generate_settings_html( $this->get_form_fields() ); ?></table>
3865
+ </div>
3866
+
3867
+ <?php
3868
+ }
3869
+
3870
+
3871
+ function delete_product_item( $wp_id ) {
3872
+ $fb_product_item_id = $this->get_product_fbid(
3873
+ self::FB_PRODUCT_ITEM_ID,
3874
+ $wp_id
3875
+ );
3876
  if ( $fb_product_item_id ) {
3877
  $pi_result =
3878
  $this->fbgraph->delete_product_item( $fb_product_item_id );
3918
  )
3919
  );
3920
  if ( $result ) {
3921
+
3922
+ $is_visible = $visibility === self::FB_SHOP_PRODUCT_VISIBLE;
3923
+
3924
+ update_post_meta( $item_id, Products::VISIBILITY_META_KEY, wc_bool_to_string( $is_visible ) );
3925
+ update_post_meta( $wp_id, Products::VISIBILITY_META_KEY, wc_bool_to_string( $is_visible ) );
3926
  }
3927
  }
3928
  }
3929
 
3930
  function on_quick_and_bulk_edit_save( $product ) {
3931
+
3932
+ // bail if not a product or product is not enabled for sync
3933
+ if ( ! $product instanceof \WC_Product || ! Products::is_sync_enabled_for_product( $product ) ) {
3934
+ return;
3935
+ }
3936
+
3937
  $wp_id = $product->get_id();
3938
+ $visibility = get_post_status( $wp_id ) === 'publish'
3939
+ ? self::FB_SHOP_PRODUCT_VISIBLE
3940
+ : self::FB_SHOP_PRODUCT_HIDDEN;
3941
  // case 1: new status is 'publish' regardless of old status, sync to FB
3942
+ if ( $visibility === self::FB_SHOP_PRODUCT_VISIBLE ) {
3943
  $this->on_product_publish( $wp_id );
3944
  } else {
3945
  // case 2: product never publish to FB, new status is not publish
3948
  }
3949
  }
3950
 
3951
+
3952
+ /**
3953
+ * Gets Facebook product ID from meta or from Facebook API.
3954
+ *
3955
+ * @param string $fbid_type ID type (group or item)
3956
+ * @param int $wp_id post ID
3957
+ * @param WC_Facebook_Product|null $woo_product product
3958
+ * @return mixed|void|null
3959
+ */
3960
+ public function get_product_fbid( $fbid_type, $wp_id, $woo_product = null ) {
3961
+
3962
  $fb_id = WC_Facebookcommerce_Utils::get_fbid_post_meta(
3963
  $wp_id,
3964
  $fbid_type
3965
  );
3966
+
3967
  if ( $fb_id ) {
3968
  return $fb_id;
3969
  }
3970
+
3971
  if ( ! isset( $this->settings['upload_end_time'] ) ) {
3972
  return null;
3973
  }
3974
+
3975
  if ( ! $woo_product ) {
3976
  $woo_product = new WC_Facebook_Product( $wp_id );
3977
  }
3978
+
3979
  $products = WC_Facebookcommerce_Utils::get_product_array( $woo_product );
3980
  $woo_product = new WC_Facebook_Product( current( $products ) );
3981
+
3982
  // This is a generalized function used elsewhere
3983
  // Cannot call is_hidden for VC_Product_Variable Object
3984
  if ( $woo_product->is_hidden() ) {
3985
  return null;
3986
  }
3987
+
3988
+ $fb_retailer_id = WC_Facebookcommerce_Utils::get_fb_retailer_id( $woo_product );
3989
 
3990
  $product_fbid_result = $this->fbgraph->get_facebook_id(
3991
+ $this->get_product_catalog_id(),
3992
  $fb_retailer_id
3993
  );
3994
+
3995
  if ( is_wp_error( $product_fbid_result ) ) {
3996
+
3997
  WC_Facebookcommerce_Utils::log( $product_fbid_result->get_error_message() );
3998
+
3999
  $this->display_error_message(
4000
+ sprintf(
4001
+ /* translators: Placeholders %1$s - original error message from Facebook API */
4002
+ esc_html__( 'There was an issue connecting to the Facebook API: %s', 'facebook-for-woocommerce' ),
4003
+ $product_fbid_result->get_error_message()
4004
+ )
4005
  );
4006
+
4007
  return;
4008
  }
4009
 
4010
  if ( $product_fbid_result && isset( $product_fbid_result['body'] ) ) {
4011
+
4012
  $body = WC_Facebookcommerce_Utils::decode_json( $product_fbid_result['body'] );
4013
+
4014
  if ( $body && $body->id ) {
4015
+
4016
  if ( $fbid_type == self::FB_PRODUCT_GROUP_ID ) {
4017
  $fb_id = $body->product_group->id;
4018
  } else {
4019
  $fb_id = $body->id;
4020
  }
4021
+
4022
  update_post_meta(
4023
  $wp_id,
4024
  $fbid_type,
4025
  $fb_id
4026
  );
4027
+
4028
+ update_post_meta( $wp_id, Products::VISIBILITY_META_KEY, true );
4029
+
4030
  return $fb_id;
4031
  }
4032
  }
4033
+
4034
  return;
4035
  }
4036
 
4037
+
4038
  private function set_default_variant( $product_group_id, $product_item_id ) {
4039
  $result = $this->check_api_result(
4040
  $this->fbgraph->set_default_variant(
4084
  wp_die();
4085
  }
4086
 
4087
+
4088
  /**
4089
+ * Schedules a recurring event to sync products.
4090
+ *
4091
+ * @deprecated 1.10.0
4092
  */
4093
  function ajax_schedule_force_resync() {
4094
+
4095
+ wc_deprecated_function( __METHOD__, '1.10.0' );
4096
+ die;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4097
  }
4098
 
4099
+
4100
  function ajax_update_fb_option() {
4101
+
4102
  check_ajax_referer( 'wc_facebook_settings_jsx' );
4103
  WC_Facebookcommerce_Utils::check_woo_ajax_permissions( 'update fb options', true );
4104
+
4105
+ if ( isset( $_POST ) && ! empty( $_POST['option'] ) && isset( $_POST['option_value'] ) ) {
4106
+
4107
+ $option_name = sanitize_text_field( wp_unslash( $_POST['option'] ) );
4108
+ $option_value = sanitize_text_field( wp_unslash( $_POST['option_value'] ) );
4109
+
4110
+ if ( stripos( $option_name, 'fb_' ) === 0 ) {
4111
+ update_option( $option_name, $option_value );
4112
+ }
4113
  }
4114
+
4115
  wp_die();
4116
  }
4117
+
4118
+
4119
+ /**
4120
+ * Adds an recurring action to sync products.
4121
+ *
4122
+ * The action is scheduled using a cron schedule instead of a recurring interval (see https://en.wikipedia.org/wiki/Cron#Overview).
4123
+ * A cron schedule should allow for the action to run roughly at the same time every day regardless of the duration of the task.
4124
+ *
4125
+ * @since 1.10.0
4126
+ *
4127
+ * @param int $offset number of seconds since the beginning of the daay
4128
+ */
4129
+ public function schedule_resync( $offset ) {
4130
+
4131
+ try {
4132
+
4133
+ $current_time = new DateTime( 'now', new DateTimeZone( wc_timezone_string() ) );
4134
+ $first_scheduled_time = new DateTime( "today +{$offset} seconds", new DateTimeZone( wc_timezone_string() ) );
4135
+ $next_scheduled_time = new DateTime( "today +1 day {$offset} seconds", new DateTimeZone( wc_timezone_string() ) );
4136
+
4137
+ } catch ( \Exception $e ) {
4138
+ // TODO: log an error indicating that it was not possible to schedule a recurring action to sync products {WV 2020-01-28}
4139
+ return;
4140
+ }
4141
+
4142
+ // unschedule previously scheduled resync actions
4143
+ $this->unschedule_resync();
4144
+
4145
+ $timestamp = $first_scheduled_time >= $current_time ? $first_scheduled_time->getTimestamp() : $next_scheduled_time->getTimestamp();
4146
+
4147
+ // TODO: replace 'facebook-for-woocommerce' with the plugin ID once we stat using the Framework {WV 2020-01-30}
4148
+ as_schedule_single_action( $timestamp, self::ACTION_HOOK_SCHEDULED_RESYNC, [], 'facebook-for-woocommerce' );
4149
+ }
4150
+
4151
+
4152
+ /**
4153
+ * Removes the recurring action that syncs products.
4154
+ *
4155
+ * @since 1.10.0
4156
+ */
4157
+ private function unschedule_resync() {
4158
+
4159
+ // TODO: replace 'facebook-for-woocommerce' with the plugin ID once we stat using the Framework {WV 2020-01-30}
4160
+ as_unschedule_all_actions( self::ACTION_HOOK_SCHEDULED_RESYNC, [], 'facebook-for-woocommerce' );
4161
+ }
4162
+
4163
+
4164
+ /**
4165
+ * Determines whether a recurring action to sync products is scheduled and not running.
4166
+ *
4167
+ * @see \as_next_scheduled_action()
4168
+ *
4169
+ * @since 1.10.0
4170
+ *
4171
+ * @return bool
4172
+ */
4173
+ public function is_resync_scheduled() {
4174
+
4175
+ // TODO: replace 'facebook-for-woocommerce' with the plugin ID once we stat using the Framework {WV 2020-01-30}
4176
+ return is_int( as_next_scheduled_action( self::ACTION_HOOK_SCHEDULED_RESYNC, [], 'facebook-for-woocommerce' ) );
4177
+ }
4178
+
4179
+
4180
+ /**
4181
+ * Handles the scheduled action used to sync products daily.
4182
+ *
4183
+ * It will schedule a new action if product sync is enabled and the plugin is configured to resnyc procucts daily.
4184
+ *
4185
+ * @internal
4186
+ *
4187
+ * @see \WC_Facebookcommerce_Integration::schedule_resync()
4188
+ *
4189
+ * @since 1.10.0
4190
+ */
4191
+ public function handle_scheduled_resync_action() {
4192
+
4193
+ $this->sync_all_fb_products_using_feed();
4194
+
4195
+ $resync_offset = $this->get_scheduled_resync_offset();
4196
+
4197
+ // manually schedule the next product resync action if possible
4198
+ if ( null !== $resync_offset && $this->is_product_sync_enabled() && ! $this->is_resync_scheduled() ) {
4199
+ $this->schedule_resync( $resync_offset );
4200
+ }
4201
+ }
4202
+
4203
+
4204
  }
facebook-for-woocommerce.php CHANGED
@@ -10,107 +10,372 @@
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
17
- * WC tested up to: 3.3.5
18
  *
19
  * @package FacebookCommerce
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;
 
 
 
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.10.0
14
  * Woo: 2127297:0ea4fe4c2d7ca6338f8a322fb3e4e187
15
  * Text Domain: facebook-for-woocommerce
16
+ * WC requires at least: 3.5.0
17
+ * WC tested up to: 3.9.2
18
  *
19
  * @package FacebookCommerce
20
  */
21
 
22
+ defined( 'ABSPATH' ) or exit;
23
 
24
+ /**
25
+ * The plugin loader class.
26
+ *
27
+ * @since 1.10.0
28
+ */
29
+ class WC_Facebook_Loader {
30
+
31
+
32
+ /** minimum PHP version required by this plugin */
33
+ const MINIMUM_PHP_VERSION = '5.6.0';
34
+
35
+ /** minimum WordPress version required by this plugin */
36
+ const MINIMUM_WP_VERSION = '4.4';
37
+
38
+ /** minimum WooCommerce version required by this plugin */
39
+ const MINIMUM_WC_VERSION = '3.5.0';
40
+
41
+ /** SkyVerge plugin framework version used by this plugin */
42
+ const FRAMEWORK_VERSION = '5.5.4';
43
+
44
+ /** the plugin name, for displaying notices */
45
+ const PLUGIN_NAME = 'Facebook for WooCommerce';
46
+
47
+
48
+ /** @var \WC_Facebook_Loader single instance of this class */
49
+ private static $instance;
50
+
51
+ /** @var array the admin notices to add */
52
+ private $notices = array();
53
+
54
+
55
+ /**
56
+ * Constructs the class.
57
+ *
58
+ * @since 1.10.0
59
+ */
60
+ protected function __construct() {
61
 
62
+ register_activation_hook( __FILE__, array( $this, 'activation_check' ) );
63
 
64
+ add_action( 'admin_init', array( $this, 'check_environment' ) );
65
+ add_action( 'admin_init', array( $this, 'add_plugin_notices' ) );
66
 
67
+ add_action( 'admin_notices', array( $this, 'admin_notices' ), 15 );
68
+
69
+ // if the environment check fails, initialize the plugin
70
+ if ( $this->is_environment_compatible() ) {
71
+ add_action( 'plugins_loaded', array( $this, 'init_plugin' ) );
72
  }
73
+ }
74
+
75
+
76
+ /**
77
+ * Cloning instances is forbidden due to singleton pattern.
78
+ *
79
+ * @since 1.10.0
80
+ */
81
+ public function __clone() {
82
+
83
+ _doing_it_wrong( __FUNCTION__, sprintf( 'You cannot clone instances of %s.', get_class( $this ) ), '1.10.0' );
84
+ }
85
+
86
 
87
+ /**
88
+ * Unserializing instances is forbidden due to singleton pattern.
89
+ *
90
+ * @since 1.10.0
91
+ */
92
+ public function __wakeup() {
93
+
94
+ _doing_it_wrong( __FUNCTION__, sprintf( 'You cannot unserialize instances of %s.', get_class( $this ) ), '1.10.0' );
95
+ }
96
+
97
+
98
+ /**
99
+ * Initializes the plugin.
100
+ *
101
+ * @since 1.10.0
102
+ */
103
+ public function init_plugin() {
104
+
105
+ if ( ! $this->plugins_compatible() ) {
106
+ return;
 
 
 
 
 
 
 
 
 
 
 
107
  }
108
 
109
+ $this->load_framework();
110
+
111
+ require_once( plugin_dir_path( __FILE__ ) . 'class-wc-facebookcommerce.php' );
112
+
113
+ // fire it up!
114
+ if ( function_exists( 'facebook_for_woocommerce' ) ) {
115
+ facebook_for_woocommerce();
 
 
116
  }
117
+ }
118
+
119
+
120
+ /**
121
+ * Loads the base framework classes.
122
+ *
123
+ * @since 1.10.0
124
+ */
125
+ private function load_framework() {
126
+
127
+ if ( ! class_exists( '\\SkyVerge\\WooCommerce\\PluginFramework\\' . $this->get_framework_version_namespace() . '\\SV_WC_Plugin' ) ) {
128
+ require_once( plugin_dir_path( __FILE__ ) . 'vendor/skyverge/wc-plugin-framework/woocommerce/class-sv-wc-plugin.php' );
129
+ }
130
+ }
131
+
132
+
133
+ /**
134
+ * Gets the framework version in namespace form.
135
+ *
136
+ * @since 1.10.0
137
+ *
138
+ * @return string
139
+ */
140
+ public function get_framework_version_namespace() {
141
+
142
+ return 'v' . str_replace( '.', '_', $this->get_framework_version() );
143
+ }
144
+
145
+
146
+ /**
147
+ * Gets the framework version used by this plugin.
148
+ *
149
+ * @since 1.10.0
150
+ *
151
+ * @return string
152
+ */
153
+ public function get_framework_version() {
154
+
155
+ return self::FRAMEWORK_VERSION;
156
+ }
157
+
158
+
159
+ /**
160
+ * Checks the server environment and other factors and deactivates plugins as necessary.
161
+ *
162
+ * Based on http://wptavern.com/how-to-prevent-wordpress-plugins-from-activating-on-sites-with-incompatible-hosting-environments
163
+ *
164
+ * @internal
165
+ *
166
+ * @since 1.10.0
167
+ */
168
+ public function activation_check() {
169
+
170
+ if ( ! $this->is_environment_compatible() ) {
171
+
172
+ $this->deactivate_plugin();
173
+
174
+ wp_die( self::PLUGIN_NAME . ' could not be activated. ' . $this->get_environment_message() );
175
+ }
176
+ }
177
+
178
+
179
+ /**
180
+ * Checks the environment on loading WordPress, just in case the environment changes after activation.
181
+ *
182
+ * @internal
183
+ *
184
+ * @since 1.10.0
185
+ */
186
+ public function check_environment() {
187
+
188
+ if ( ! $this->is_environment_compatible() && is_plugin_active( plugin_basename( __FILE__ ) ) ) {
189
+
190
+ $this->deactivate_plugin();
191
+
192
+ $this->add_admin_notice( 'bad_environment', 'error', self::PLUGIN_NAME . ' has been deactivated. ' . $this->get_environment_message() );
193
+ }
194
+ }
195
+
196
+
197
+ /**
198
+ * Adds notices for out-of-date WordPress and/or WooCommerce versions.
199
+ *
200
+ * @internal
201
+ *
202
+ * @since 1.10.0
203
+ */
204
+ public function add_plugin_notices() {
205
+
206
+ if ( ! $this->is_wp_compatible() ) {
207
+
208
+ $this->add_admin_notice( 'update_wordpress', 'error', sprintf(
209
+ '%s requires WordPress version %s or higher. Please %supdate WordPress &raquo;%s',
210
+ '<strong>' . self::PLUGIN_NAME . '</strong>',
211
+ self::MINIMUM_WP_VERSION,
212
+ '<a href="' . esc_url( admin_url( 'update-core.php' ) ) . '">', '</a>'
213
+ ) );
214
+ }
215
+
216
+ if ( ! $this->is_wc_compatible() ) {
217
+
218
+ $this->add_admin_notice( 'update_woocommerce', 'error', sprintf(
219
+ '%1$s requires WooCommerce version %2$s or higher. Please %3$supdate WooCommerce%4$s to the latest version, or %5$sdownload the minimum required version &raquo;%6$s',
220
+ '<strong>' . self::PLUGIN_NAME . '</strong>',
221
+ self::MINIMUM_WC_VERSION,
222
+ '<a href="' . esc_url( admin_url( 'update-core.php' ) ) . '">', '</a>',
223
+ '<a href="' . esc_url( 'https://downloads.wordpress.org/plugin/woocommerce.' . self::MINIMUM_WC_VERSION . '.zip' ) . '">', '</a>'
224
+ ) );
225
+ }
226
+ }
227
+
228
+
229
+ /**
230
+ * Determines if the required plugins are compatible.
231
+ *
232
+ * @since 1.10.0
233
+ *
234
+ * @return bool
235
+ */
236
+ private function plugins_compatible() {
237
+
238
+ return $this->is_wp_compatible() && $this->is_wc_compatible();
239
+ }
240
+
241
+
242
+ /**
243
+ * Determines if the WordPress compatible.
244
+ *
245
+ * @since 1.10.0
246
+ *
247
+ * @return bool
248
+ */
249
+ private function is_wp_compatible() {
250
+
251
+ if ( ! self::MINIMUM_WP_VERSION ) {
252
+ return true;
253
+ }
254
+
255
+ return version_compare( get_bloginfo( 'version' ), self::MINIMUM_WP_VERSION, '>=' );
256
+ }
257
+
258
+
259
+ /**
260
+ * Determines if the WooCommerce compatible.
261
+ *
262
+ * @since 1.10.0
263
+ *
264
+ * @return bool
265
+ */
266
+ private function is_wc_compatible() {
267
+
268
+ if ( ! self::MINIMUM_WC_VERSION ) {
269
+ return true;
270
+ }
271
+
272
+ return defined( 'WC_VERSION' ) && version_compare( WC_VERSION, self::MINIMUM_WC_VERSION, '>=' );
273
+ }
274
+
275
+
276
+ /**
277
+ * Deactivates the plugin.
278
+ *
279
+ * @internal
280
+ *
281
+ * @since 1.10.0
282
+ */
283
+ protected function deactivate_plugin() {
284
+
285
+ deactivate_plugins( plugin_basename( __FILE__ ) );
286
+
287
+ if ( isset( $_GET['activate'] ) ) {
288
+ unset( $_GET['activate'] );
289
+ }
290
+ }
291
+
292
+
293
+ /**
294
+ * Adds an admin notice to be displayed.
295
+ *
296
+ * @since 1.10.0
297
+ *
298
+ * @param string $slug the slug for the notice
299
+ * @param string $class the css class for the notice
300
+ * @param string $message the notice message
301
+ */
302
+ private function add_admin_notice( $slug, $class, $message ) {
303
+
304
+ $this->notices[ $slug ] = array(
305
+ 'class' => $class,
306
+ 'message' => $message
307
+ );
308
+ }
309
+
310
+
311
+ /**
312
+ * Displays any admin notices added with \WC_Facebook_Loader::add_admin_notice()
313
+ *
314
+ * @internal
315
+ *
316
+ * @since 1.10.0
317
+ */
318
+ public function admin_notices() {
319
+
320
+ foreach ( (array) $this->notices as $notice_key => $notice ) {
321
 
 
 
 
 
 
 
 
 
 
 
 
 
 
322
  ?>
323
+ <div class="<?php echo esc_attr( $notice['class'] ); ?>">
324
+ <p><?php echo wp_kses( $notice['message'], array( 'a' => array( 'href' => array() ) ) ); ?></p>
325
+ </div>
326
  <?php
327
  }
328
+ }
329
 
 
 
 
 
 
 
 
330
 
331
+ /**
332
+ * Determines if the server environment is compatible with this plugin.
333
+ *
334
+ * Override this method to add checks for more than just the PHP version.
335
+ *
336
+ * @since 1.10.0
337
+ *
338
+ * @return bool
339
+ */
340
+ private function is_environment_compatible() {
341
+
342
+ return version_compare( PHP_VERSION, self::MINIMUM_PHP_VERSION, '>=' );
343
+ }
344
+
345
+
346
+ /**
347
+ * Gets the message for display when the environment is incompatible with this plugin.
348
+ *
349
+ * @since 1.10.0
350
+ *
351
+ * @return string
352
+ */
353
+ private function get_environment_message() {
354
+
355
+ return sprintf( 'The minimum PHP version required for this plugin is %1$s. You are running %2$s.', self::MINIMUM_PHP_VERSION, PHP_VERSION );
356
+ }
357
+
358
+
359
+ /**
360
+ * Gets the main \WC_Facebook_Loader instance.
361
+ *
362
+ * Ensures only one instance can be loaded.
363
+ *
364
+ * @since 1.10.0
365
+ *
366
+ * @return \WC_Facebook_Loader
367
+ */
368
+ public static function instance() {
369
+
370
+ if ( null === self::$instance ) {
371
+ self::$instance = new self();
372
  }
373
+
374
+ return self::$instance;
375
  }
376
 
 
377
 
378
+ }
379
+
380
+ // fire it up!
381
+ WC_Facebook_Loader::instance();
i18n/languages/facebook-for-woocommerce.pot ADDED
@@ -0,0 +1,531 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (C) 2020 Facebook
2
+ # This file is distributed under the same license as the Facebook for WooCommerce package.
3
+ msgid ""
4
+ msgstr ""
5
+ "Project-Id-Version: Facebook for WooCommerce 1.10.0\n"
6
+ "Report-Msgid-Bugs-To: "
7
+ "https://woocommerce.com/my-account/marketplace-ticket-form/\n"
8
+ "POT-Creation-Date: 2020-03-03 21:11:05+00:00\n"
9
+ "MIME-Version: 1.0\n"
10
+ "Content-Type: text/plain; charset=utf-8\n"
11
+ "Content-Transfer-Encoding: 8bit\n"
12
+ "PO-Revision-Date: 2020-MO-DA HO:MI+ZONE\n"
13
+ "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14
+ "Language-Team: LANGUAGE <LL@li.org>\n"
15
+
16
+ #. Plugin Name of the plugin/theme
17
+ msgid "Facebook for WooCommerce"
18
+ msgstr ""
19
+
20
+ #: facebook-commerce.php:185
21
+ msgid "Facebook Commerce and Dynamic Ads (Pixel) Extension"
22
+ msgstr ""
23
+
24
+ #: facebook-commerce.php:581
25
+ msgid "Facebook ID:"
26
+ msgstr ""
27
+
28
+ #: facebook-commerce.php:592
29
+ msgid "Variant IDs:"
30
+ msgstr ""
31
+
32
+ #: facebook-commerce.php:619
33
+ msgid "Visible:"
34
+ msgstr ""
35
+
36
+ #: facebook-commerce.php:630
37
+ msgid "Reset Facebook metadata"
38
+ msgstr ""
39
+
40
+ #: facebook-commerce.php:635
41
+ msgid "Delete product(s) on Facebook"
42
+ msgstr ""
43
+
44
+ #: facebook-commerce.php:643
45
+ msgid "This product is not yet synced to Facebook."
46
+ msgstr ""
47
+
48
+ #: facebook-commerce.php:1288
49
+ msgid "Nothing to update for product group for %1$s"
50
+ msgstr ""
51
+
52
+ #: facebook-commerce.php:1685
53
+ #. translators: Placeholders %1$s - original error message from Facebook API
54
+ msgid "There was an issue connecting to the Facebook API: %1$s"
55
+ msgstr ""
56
+
57
+ #: facebook-commerce.php:1819
58
+ #. translators: Placeholders %1$s - opening strong HTML tag, %2$s - closing
59
+ #. strong HTML tag, %3$s - opening link HTML tag, %4$s - closing link HTML tag
60
+ msgid ""
61
+ "%1$sFacebook for WooCommerce is almost ready.%2$s To complete your "
62
+ "configuration, %3$scomplete the setup steps%4$s."
63
+ msgstr ""
64
+
65
+ #: facebook-commerce.php:1838
66
+ #. translators: Placeholders %1$s - WooCommerce version
67
+ msgid ""
68
+ "Facebook product sync may not work correctly in WooCommerce version %1$s. "
69
+ "Please upgrade to WooCommerce 3."
70
+ msgstr ""
71
+
72
+ #: facebook-commerce.php:2361
73
+ msgid "Use Advanced Matching"
74
+ msgstr ""
75
+
76
+ #: facebook-commerce.php:2364
77
+ #. translators: Placeholders: %1$s - opening <a> HTML link tag, %2$s - closing
78
+ #. </a> HTML link tag
79
+ msgid ""
80
+ "Improve the ability to match site visitors to people on Facebook by passing "
81
+ "additional site visitor information (such as email address or phone "
82
+ "number). %1$sLearn more%2$s."
83
+ msgstr ""
84
+
85
+ #: facebook-commerce.php:2384
86
+ msgid "Enable product sync"
87
+ msgstr ""
88
+
89
+ #: facebook-commerce.php:2392
90
+ msgid "Exclude categories from sync"
91
+ msgstr ""
92
+
93
+ #: facebook-commerce.php:2396
94
+ msgid "Products in one or more of these categories will not sync to Facebook."
95
+ msgstr ""
96
+
97
+ #: facebook-commerce.php:2400
98
+ msgid "Search for a product category&hellip;"
99
+ msgstr ""
100
+
101
+ #: facebook-commerce.php:2405
102
+ msgid "Exclude tags from sync"
103
+ msgstr ""
104
+
105
+ #: facebook-commerce.php:2409
106
+ msgid "Products with one or more of these tags will not sync to Facebook."
107
+ msgstr ""
108
+
109
+ #: facebook-commerce.php:2413
110
+ msgid "Search for a product tag&hellip;"
111
+ msgstr ""
112
+
113
+ #: facebook-commerce.php:2418
114
+ msgid "Product description sync"
115
+ msgstr ""
116
+
117
+ #: facebook-commerce.php:2421
118
+ msgid "Choose which product description to display in the Facebook catalog."
119
+ msgstr ""
120
+
121
+ #: facebook-commerce.php:2424
122
+ msgid "Standard description"
123
+ msgstr ""
124
+
125
+ #: facebook-commerce.php:2425
126
+ msgid "Short description"
127
+ msgstr ""
128
+
129
+ #: facebook-commerce.php:2432
130
+ msgid "Force daily resync at"
131
+ msgstr ""
132
+
133
+ #: facebook-commerce.php:2438
134
+ msgid "Messenger"
135
+ msgstr ""
136
+
137
+ #: facebook-commerce.php:2443
138
+ msgid "Enable Messenger"
139
+ msgstr ""
140
+
141
+ #: facebook-commerce.php:2447
142
+ msgid "Enable and customize Facebook Messenger on your store."
143
+ msgstr ""
144
+
145
+ #: facebook-commerce.php:2452
146
+ msgid "Language"
147
+ msgstr ""
148
+
149
+ #: facebook-commerce.php:2462
150
+ msgid "Greeting"
151
+ msgstr ""
152
+
153
+ #: facebook-commerce.php:2465
154
+ msgid "Hi! We're here to answer any questions you may have."
155
+ msgstr ""
156
+
157
+ #: facebook-commerce.php:2473
158
+ msgid "Colors"
159
+ msgstr ""
160
+
161
+ #: facebook-commerce.php:2506
162
+ msgid "Connection"
163
+ msgstr ""
164
+
165
+ #: facebook-commerce.php:2513
166
+ msgid "Manage connection"
167
+ msgstr ""
168
+
169
+ #: facebook-commerce.php:2518
170
+ msgid "Your connection has expired."
171
+ msgstr ""
172
+
173
+ #: facebook-commerce.php:2520
174
+ msgid ""
175
+ "Please click Manage connection > Advanced Options > Update Token to refresh "
176
+ "your connection to Facebook."
177
+ msgstr ""
178
+
179
+ #: facebook-commerce.php:2526
180
+ msgid "Your access token has been updated."
181
+ msgstr ""
182
+
183
+ #: facebook-commerce.php:2528
184
+ msgid "Please refresh the page."
185
+ msgstr ""
186
+
187
+ #: facebook-commerce.php:2562
188
+ msgid "Facebook page"
189
+ msgstr ""
190
+
191
+ #: facebook-commerce.php:2626
192
+ msgid "Pixel"
193
+ msgstr ""
194
+
195
+ #: facebook-commerce.php:2672
196
+ msgid "Create ad"
197
+ msgstr ""
198
+
199
+ #: facebook-commerce.php:2701
200
+ msgid "Product sync"
201
+ msgstr ""
202
+
203
+ #: facebook-commerce.php:2707
204
+ msgid "Sync products"
205
+ msgstr ""
206
+
207
+ #: facebook-commerce.php:2847
208
+ msgid "am"
209
+ msgstr ""
210
+
211
+ #: facebook-commerce.php:2852
212
+ msgid "pm"
213
+ msgstr ""
214
+
215
+ #: facebook-commerce.php:2972
216
+ msgid "The greeting hasn't been updated."
217
+ msgstr ""
218
+
219
+ #: facebook-commerce.php:2991
220
+ #. translators: Placeholder: %d - maximum number of allowed characters
221
+ msgid "The Messenger greeting must be %d characters or less."
222
+ msgstr ""
223
+
224
+ #: facebook-commerce.php:3648
225
+ msgid "Facebook extension error: %s "
226
+ msgstr ""
227
+
228
+ #: facebook-commerce.php:3758
229
+ msgid "Get started with Messenger Customer Chat"
230
+ msgstr ""
231
+
232
+ #: facebook-commerce.php:3759
233
+ msgid "Get started with Instagram Shopping"
234
+ msgstr ""
235
+
236
+ #: facebook-commerce.php:3809
237
+ msgid ""
238
+ "You're using Remove HTTP which has incompatibilities with our extension. "
239
+ "Please disable it, or select the \"Ignore external links\" option on the "
240
+ "Remove HTTP settings page."
241
+ msgstr ""
242
+
243
+ #. Author of the plugin/theme
244
+ msgid "Facebook"
245
+ msgstr ""
246
+
247
+ #: facebook-commerce.php:3818
248
+ msgid "Control how WooCommerce integrates with your Facebook store."
249
+ msgstr ""
250
+
251
+ #: facebook-commerce.php:3832
252
+ msgid "Grow your business on Facebook"
253
+ msgstr ""
254
+
255
+ #: facebook-commerce.php:3835
256
+ msgid "Use this WooCommerce and Facebook integration to:"
257
+ msgstr ""
258
+
259
+ #: facebook-commerce.php:3839
260
+ msgid "Easily install a tracking pixel"
261
+ msgstr ""
262
+
263
+ #: facebook-commerce.php:3840
264
+ msgid "Upload your products and create a shop"
265
+ msgstr ""
266
+
267
+ #: facebook-commerce.php:3841
268
+ msgid "Create dynamic ads with your products and pixel"
269
+ msgstr ""
270
+
271
+ #: facebook-commerce.php:3849
272
+ msgid "Get Started"
273
+ msgstr ""
274
+
275
+ #: facebook-commerce.php:4002
276
+ #. translators: Placeholders %1$s - original error message from Facebook API
277
+ msgid "There was an issue connecting to the Facebook API: %s"
278
+ msgstr ""
279
+
280
+ #: includes/AJAX.php:76
281
+ msgid "Hide Product"
282
+ msgstr ""
283
+
284
+ #: includes/AJAX.php:80
285
+ msgid "Do Not Hide Product"
286
+ msgstr ""
287
+
288
+ #: includes/AJAX.php:86
289
+ msgid ""
290
+ "This product will no longer be updated in your Facebook catalog. Would you "
291
+ "like to hide this product from your Facebook shop?"
292
+ msgstr ""
293
+
294
+ #: includes/AJAX.php:125 includes/AJAX.php:220
295
+ msgid "Go to Settings"
296
+ msgstr ""
297
+
298
+ #: includes/AJAX.php:130 includes/AJAX.php:225 includes/AJAX.php:289
299
+ msgid "Cancel"
300
+ msgstr ""
301
+
302
+ #: includes/AJAX.php:138
303
+ #. translators: Placeholder %s - <br/> tag
304
+ msgid ""
305
+ "This product belongs to a category or tag that is excluded from the "
306
+ "Facebook catalog sync. It will not sync to Facebook. %sTo sync this product "
307
+ "to Facebook, click Go to Settings and remove the category or tag exclusion "
308
+ "or click Cancel and update the product's category / tag assignments."
309
+ msgstr ""
310
+
311
+ #: includes/AJAX.php:179
312
+ msgid "Hide Products"
313
+ msgstr ""
314
+
315
+ #: includes/AJAX.php:183
316
+ msgid "Do Not Hide Products"
317
+ msgstr ""
318
+
319
+ #: includes/AJAX.php:189
320
+ msgid ""
321
+ "The selected products will no longer be updated in your Facebook catalog. "
322
+ "Would you like to hide these products from your Facebook shop?"
323
+ msgstr ""
324
+
325
+ #: includes/AJAX.php:231
326
+ msgid ""
327
+ "One or more of the selected products belongs to a category or tag that is "
328
+ "excluded from the Facebook catalog sync. To sync these products to "
329
+ "Facebook, please remove the category or tag exclusion from the plugin "
330
+ "settings."
331
+ msgstr ""
332
+
333
+ #: includes/AJAX.php:284
334
+ msgid "Exclude Products"
335
+ msgstr ""
336
+
337
+ #: includes/AJAX.php:297
338
+ #. translators: Placeholder %s - <br/> tags
339
+ msgid ""
340
+ "The categories and/or tags that you have selected to exclude from sync "
341
+ "contain products that are currently synced to Facebook.%sTo exclude these "
342
+ "products from the Facebook sync, click Exclude Products. To review the "
343
+ "category / tag exclusion settings, click Cancel."
344
+ msgstr ""
345
+
346
+ #: includes/Admin.php:133
347
+ msgid "FB Sync Enabled"
348
+ msgstr ""
349
+
350
+ #: includes/Admin.php:134
351
+ msgid "FB Catalog Visibility"
352
+ msgstr ""
353
+
354
+ #: includes/Admin.php:157
355
+ msgid "Enabled"
356
+ msgstr ""
357
+
358
+ #: includes/Admin.php:159
359
+ msgid "Disabled"
360
+ msgstr ""
361
+
362
+ #: includes/Admin.php:175
363
+ msgid "Product is not synced with Facebook."
364
+ msgstr ""
365
+
366
+ #: includes/Admin.php:187
367
+ msgid "Product is synced and published (visible) on Facebook."
368
+ msgstr ""
369
+
370
+ #: includes/Admin.php:188
371
+ msgid "Product is synced but not marked as published (visible) on Facebook."
372
+ msgstr ""
373
+
374
+ #: includes/Admin.php:192
375
+ msgid ""
376
+ "Product is published (visible) on Facebook, but not synced, so updates "
377
+ "won’t be reflected on Facebook."
378
+ msgstr ""
379
+
380
+ #: includes/Admin.php:193
381
+ msgid "Product is not synced and not published (visible) on Facebook."
382
+ msgstr ""
383
+
384
+ #: includes/Admin.php:204
385
+ msgid "Show"
386
+ msgstr ""
387
+
388
+ #: includes/Admin.php:212
389
+ msgid "Hide"
390
+ msgstr ""
391
+
392
+ #: includes/Admin.php:240
393
+ msgid "Filter by Facebook sync setting"
394
+ msgstr ""
395
+
396
+ #: includes/Admin.php:241
397
+ msgid "Facebook sync enabled"
398
+ msgstr ""
399
+
400
+ #: includes/Admin.php:242
401
+ msgid "Facebook sync disabled"
402
+ msgstr ""
403
+
404
+ #: includes/Admin.php:401 includes/Admin.php:550 includes/Admin.php:642
405
+ msgid "Include in Facebook sync"
406
+ msgstr ""
407
+
408
+ #: includes/Admin.php:402
409
+ msgid "Exclude from Facebook sync"
410
+ msgstr ""
411
+
412
+ #: includes/Admin.php:489
413
+ #. translators: Placeholders: %1$s - Facebook for Woocommerce, %2$s - opening
414
+ #. HTML <a> link tag, %3$s - closing HTML </a> link tag
415
+ msgid ""
416
+ "%1$s: One or more of your products is using a checkout URL that may be "
417
+ "different than your shop checkout URL. %2$sRe-sync your products to update "
418
+ "checkout URLs on Facebook%3$s."
419
+ msgstr ""
420
+
421
+ #: includes/Admin.php:556 includes/Admin.php:651
422
+ msgid "Facebook Description"
423
+ msgstr ""
424
+
425
+ #: includes/Admin.php:558 includes/Admin.php:653
426
+ msgid ""
427
+ "Custom (plain-text only) description for product on Facebook. If blank, "
428
+ "product description will be used. If product description is blank, "
429
+ "shortname will be used."
430
+ msgstr ""
431
+
432
+ #: includes/Admin.php:567 includes/Admin.php:664
433
+ msgid "Facebook Product Image"
434
+ msgstr ""
435
+
436
+ #: includes/Admin.php:569 includes/Admin.php:666
437
+ msgid ""
438
+ "Choose the product image that should be synced to the Facebook catalog for "
439
+ "this product. If using a custom image, please enter an absolute URL (e.g. "
440
+ "https://domain.com/image.jpg)."
441
+ msgstr ""
442
+
443
+ #: includes/Admin.php:571
444
+ msgid "Use WooCommerce image"
445
+ msgstr ""
446
+
447
+ #: includes/Admin.php:572 includes/Admin.php:670
448
+ msgid "Use custom image"
449
+ msgstr ""
450
+
451
+ #: includes/Admin.php:581 includes/Admin.php:680
452
+ msgid "Custom Image URL"
453
+ msgstr ""
454
+
455
+ #: includes/Admin.php:590 includes/Admin.php:691
456
+ #. translators: Placeholders %1$s - WC currency symbol
457
+ msgid "Facebook Price (%1$s)"
458
+ msgstr ""
459
+
460
+ #: includes/Admin.php:594 includes/Admin.php:695
461
+ msgid ""
462
+ "Custom price for product on Facebook. Please enter in monetary decimal (.) "
463
+ "format without thousand separators and currency symbols. If blank, product "
464
+ "price will be used."
465
+ msgstr ""
466
+
467
+ #: includes/Admin.php:668
468
+ msgid "Use variation image"
469
+ msgstr ""
470
+
471
+ #: includes/Admin.php:669
472
+ msgid "Use parent image"
473
+ msgstr ""
474
+
475
+ #: includes/Admin.php:800
476
+ msgid "Close modal panel"
477
+ msgstr ""
478
+
479
+ #: includes/fbinfobanner.php:211
480
+ msgid "Click and redirect."
481
+ msgstr ""
482
+
483
+ #: includes/fbinfobanner.php:212
484
+ msgid "Dismiss this notice."
485
+ msgstr ""
486
+
487
+ #: includes/fbinfobanner.php:212
488
+ msgid "Dismiss"
489
+ msgstr ""
490
+
491
+ #: includes/fbwpml.php:105
492
+ msgid "Facebook Visibility"
493
+ msgstr ""
494
+
495
+ #: includes/fbwpml.php:108
496
+ msgid ""
497
+ "WooCommerce Products with languages that are selected here will be visible "
498
+ "to customers who see your Facebook Shop."
499
+ msgstr ""
500
+
501
+ #: includes/fbwpml.php:128
502
+ #. translators: Placeholders %1$s - opening link HTML tag, %2$s - closing link
503
+ #. HTML tag
504
+ msgid "Saved. You should now %1$sRe-Sync%2$s your products with Facebook."
505
+ msgstr ""
506
+
507
+ #: includes/fbwpml.php:136
508
+ msgid "Save"
509
+ msgstr ""
510
+
511
+ #. Plugin URI of the plugin/theme
512
+ msgid "https://github.com/facebookincubator/facebook-for-woocommerce/"
513
+ msgstr ""
514
+
515
+ #. Description of the plugin/theme
516
+ msgid ""
517
+ "Grow your business on Facebook! Use this official plugin to help sell more "
518
+ "of your products using Facebook. After completing the setup, you'll be "
519
+ "ready to create ads that promote your products and you can also create a "
520
+ "shop section on your Page where customers can browse your products on "
521
+ "Facebook."
522
+ msgstr ""
523
+
524
+ #. Author URI of the plugin/theme
525
+ msgid "https://www.facebook.com/"
526
+ msgstr ""
527
+
528
+ #: facebook-commerce-messenger-chat.php:258 facebook-commerce.php:2337
529
+ msgctxt "language"
530
+ msgid "English (United States)"
531
+ msgstr ""
includes/AJAX.php ADDED
@@ -0,0 +1,494 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
4
+ *
5
+ * This source code is licensed under the license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ *
8
+ * @package FacebookCommerce
9
+ */
10
+
11
+ namespace SkyVerge\WooCommerce\Facebook;
12
+
13
+ defined( 'ABSPATH' ) or exit;
14
+
15
+ /**
16
+ * AJAX handler.
17
+ *
18
+ * @since 1.10.0
19
+ */
20
+ class AJAX {
21
+
22
+
23
+ /**
24
+ * AJAX handler constructor.
25
+ *
26
+ * @since 1.10.0
27
+ */
28
+ public function __construct() {
29
+
30
+ // maybe output a modal prompt when toggling product sync in bulk or individual product actions
31
+ add_action( 'wp_ajax_facebook_for_woocommerce_set_product_sync_prompt', [ $this, 'handle_set_product_sync_prompt' ] );
32
+ add_action( 'wp_ajax_facebook_for_woocommerce_set_product_sync_bulk_action_prompt', [ $this, 'handle_set_product_sync_bulk_action_prompt' ] );
33
+
34
+ // maybe output a modal prompt when setting excluded terms
35
+ add_action( 'wp_ajax_facebook_for_woocommerce_set_excluded_terms_prompt', [ $this, 'handle_set_excluded_terms_prompt' ] );
36
+
37
+ // set product visibility in Facebook
38
+ add_action( 'wp_ajax_facebook_for_woocommerce_set_products_visibility', [ $this, 'set_products_visibility' ] );
39
+ }
40
+
41
+
42
+ /**
43
+ * Maybe triggers a modal warning when the merchant toggles sync enabled status on a product.
44
+ *
45
+ * @internal
46
+ *
47
+ * @since 1.10.0
48
+ */
49
+ public function handle_set_product_sync_prompt() {
50
+
51
+ check_ajax_referer( 'set-product-sync-prompt', 'security' );
52
+
53
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended
54
+ $product_id = isset( $_POST['product'] ) ? (int) $_POST['product'] : 0;
55
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended
56
+ $sync_enabled = isset( $_POST['sync_enabled'] ) ? (string) $_POST['sync_enabled'] : '';
57
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended
58
+ $product_cats = isset( $_POST['categories'] ) ? (array) $_POST['categories'] : [];
59
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended
60
+ $product_tags = isset( $_POST['tags'] ) ? (array) $_POST['tags'] : [];
61
+
62
+ if ( $product_id > 0 && in_array( $sync_enabled, [ 'enabled', 'disabled' ], true ) ) {
63
+
64
+ $product = wc_get_product( $product_id );
65
+
66
+ if ( $product instanceof \WC_Product ) {
67
+
68
+ if ( 'disabled' === $sync_enabled && Products::is_sync_enabled_for_product( $product ) ) {
69
+
70
+ ob_start();
71
+
72
+ ?>
73
+ <button
74
+ id="facebook-for-woocommerce-hide-products"
75
+ class="button button-large button-primary facebook-for-woocommerce-toggle-product-visibility hide-products"
76
+ ><?php esc_html_e( 'Hide Product', 'facebook-for-woocommerce' ); ?></button>
77
+ <button
78
+ id="facebook-for-woocommerce-do-not-hide-products"
79
+ class="button button-large button-primary facebook-for-woocommerce-toggle-product-visibility show-products"
80
+ ><?php esc_html_e( 'Do Not Hide Product', 'facebook-for-woocommerce' ); ?></button>
81
+ <?php
82
+
83
+ $buttons = ob_get_clean();
84
+
85
+ wp_send_json_error( [
86
+ 'message' => __( 'This product will no longer be updated in your Facebook catalog. Would you like to hide this product from your Facebook shop?', 'facebook-for-woocommerce' ),
87
+ 'buttons' => $buttons,
88
+ ] );
89
+
90
+ } elseif ( 'enabled' === $sync_enabled ) {
91
+
92
+ $has_excluded_terms = false;
93
+
94
+ if ( $integration = facebook_for_woocommerce()->get_integration() ) {
95
+
96
+ // try with categories first, since we have already IDs
97
+ $has_excluded_terms = ! empty( $product_cats ) && array_intersect( $product_cats, $integration->get_excluded_product_category_ids() );
98
+
99
+ // try next with tags, but WordPress only gives us tag names
100
+ if ( ! $has_excluded_terms && ! empty( $product_tags ) ) {
101
+
102
+ $product_tag_ids = [];
103
+
104
+ foreach ( $product_tags as $product_tag_name ) {
105
+
106
+ if ( $term = get_term_by( 'name', $product_tag_name, 'product_tag' ) ) {
107
+
108
+ $product_tag_ids[] = $term->term_id;
109
+ }
110
+ }
111
+
112
+ $has_excluded_terms = ! empty( $product_tag_ids ) && array_intersect( $product_tag_ids, $integration->get_excluded_product_tag_ids() );
113
+ }
114
+ }
115
+
116
+ if ( $has_excluded_terms ) {
117
+
118
+ ob_start();
119
+
120
+ ?>
121
+ <a
122
+ id="facebook-for-woocommerce-go-to-settings"
123
+ class="button button-large"
124
+ href="<?php echo esc_url( add_query_arg( 'section', \WC_Facebookcommerce::INTEGRATION_ID, admin_url( 'admin.php?page=wc-settings&tab=integration' ) ) ); ?>"
125
+ ><?php esc_html_e( 'Go to Settings', 'facebook-for-woocommerce' ); ?></a>
126
+ <button
127
+ id="facebook-for-woocommerce-cancel-sync"
128
+ class="button button-large button-primary"
129
+ onclick="jQuery( '.modal-close' ).trigger( 'click' )"
130
+ ><?php esc_html_e( 'Cancel', 'facebook-for-woocommerce' ); ?></button>
131
+ <?php
132
+
133
+ $buttons = ob_get_clean();
134
+
135
+ wp_send_json_error( [
136
+ 'message' => sprintf(
137
+ /* translators: Placeholder %s - <br/> tag */
138
+ __( 'This product belongs to a category or tag that is excluded from the Facebook catalog sync. It will not sync to Facebook. %sTo sync this product to Facebook, click Go to Settings and remove the category or tag exclusion or click Cancel and update the product\'s category / tag assignments.', 'facebook-for-woocommerce' ),
139
+ '<br/><br/>'
140
+ ),
141
+ 'buttons' => $buttons,
142
+ ] );
143
+ }
144
+ }
145
+ }
146
+ }
147
+
148
+ wp_send_json_success();
149
+ }
150
+
151
+
152
+ /**
153
+ * Maybe triggers a modal warning when the merchant toggles sync enabled status in bulk.
154
+ *
155
+ * @internal
156
+ *
157
+ * @since 1.10.0
158
+ */
159
+ public function handle_set_product_sync_bulk_action_prompt() {
160
+
161
+ check_ajax_referer( 'set-product-sync-bulk-action-prompt', 'security' );
162
+
163
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended
164
+ $product_ids = isset( $_POST['products'] ) ? (array) $_POST['products'] : [];
165
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended
166
+ $toggle = isset( $_POST['toggle'] ) ? (string) $_POST['toggle'] : '';
167
+
168
+ if ( ! empty( $product_ids ) && ! empty( $toggle ) ) {
169
+
170
+ // merchant wants to exclude products from sync: ask them what they want to do with their visibility status
171
+ if ( 'facebook_exclude' === $toggle ) {
172
+
173
+ ob_start();
174
+
175
+ ?>
176
+ <button
177
+ id="facebook-for-woocommerce-hide-products"
178
+ class="button button-large button-primary facebook-for-woocommerce-toggle-product-visibility hide-products"
179
+ ><?php esc_html_e( 'Hide Products', 'facebook-for-woocommerce' ); ?></button>
180
+ <button
181
+ id="facebook-for-woocommerce-do-not-hide-products"
182
+ class="button button-large button-primary facebook-for-woocommerce-toggle-product-visibility show-products"
183
+ ><?php esc_html_e( 'Do Not Hide Products', 'facebook-for-woocommerce' ); ?></button>
184
+ <?php
185
+
186
+ $buttons = ob_get_clean();
187
+
188
+ wp_send_json_error( [
189
+ 'message' => __( 'The selected products will no longer be updated in your Facebook catalog. Would you like to hide these products from your Facebook shop?', 'facebook-for-woocommerce' ),
190
+ 'buttons' => $buttons,
191
+ ] );
192
+
193
+ // merchant wants to enable sync in Facebook multiple products: we must check if they belong to excluded categories, and perhaps warn them
194
+ } elseif ( 'facebook_include' === $toggle && ( $integration = facebook_for_woocommerce()->get_integration() ) ) {
195
+
196
+ $has_excluded_term = false;
197
+
198
+ foreach ( $product_ids as $product_id ) {
199
+
200
+ $product = wc_get_product( $product_id );
201
+
202
+ // product belongs to at least one excluded term: break the loop
203
+ if ( $product instanceof \WC_Product && Products::is_sync_excluded_for_product_terms( $product ) ) {
204
+
205
+ $has_excluded_term = true;
206
+ break;
207
+ }
208
+ }
209
+
210
+ // show modal if there's at least one product that belongs to an excluded term
211
+ if ( $has_excluded_term ) {
212
+
213
+ ob_start();
214
+
215
+ ?>
216
+ <a
217
+ id="facebook-for-woocommerce-go-to-settings"
218
+ class="button button-large"
219
+ href="<?php echo esc_url( add_query_arg( 'section', \WC_Facebookcommerce::INTEGRATION_ID, admin_url( 'admin.php?page=wc-settings&tab=integration' ) ) ); ?>"
220
+ ><?php esc_html_e( 'Go to Settings', 'facebook-for-woocommerce' ); ?></a>
221
+ <button
222
+ id="facebook-for-woocommerce-cancel-sync"
223
+ class="button button-large button-primary"
224
+ onclick="jQuery( '.modal-close' ).trigger( 'click' )"
225
+ ><?php esc_html_e( 'Cancel', 'facebook-for-woocommerce' ); ?></button>
226
+ <?php
227
+
228
+ $buttons = ob_get_clean();
229
+
230
+ wp_send_json_error( [
231
+ 'message' => __( 'One or more of the selected products belongs to a category or tag that is excluded from the Facebook catalog sync. To sync these products to Facebook, please remove the category or tag exclusion from the plugin settings.', 'facebook-for-woocommerce' ),
232
+ 'buttons' => $buttons,
233
+ ] );
234
+ }
235
+ }
236
+ }
237
+
238
+ wp_send_json_success();
239
+ }
240
+
241
+
242
+ /**
243
+ * Maybe triggers a modal warning when the merchant adds terms to the excluded terms.
244
+ *
245
+ * @internal
246
+ *
247
+ * @since 1.10.0
248
+ */
249
+ public function handle_set_excluded_terms_prompt() {
250
+
251
+ check_ajax_referer( 'set-excluded-terms-prompt', 'security' );
252
+
253
+ // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
254
+ $posted_categories = isset( $_POST['categories'] ) ? wp_unslash( $_POST['categories'] ) : [];
255
+ // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
256
+ $posted_tags = isset( $_POST['tags'] ) ? wp_unslash( $_POST['tags'] ) : [];
257
+
258
+ $new_category_ids = [];
259
+ $new_tag_ids = [];
260
+
261
+ if ( ! empty( $posted_categories ) ) {
262
+ foreach ( $posted_categories as $posted_category_id ) {
263
+ $new_category_ids[] = sanitize_text_field( $posted_category_id );
264
+ }
265
+ }
266
+
267
+ if ( ! empty( $posted_tags ) ) {
268
+ foreach ( $posted_tags as $posted_tag_id ) {
269
+ $new_tag_ids[] = sanitize_text_field( $posted_tag_id );
270
+ }
271
+ }
272
+
273
+ // query for products with sync enabled, belonging to the added term IDs and not belonging to the term IDs that are already stored in the setting
274
+ $products = $this->get_products_to_be_excluded( $new_category_ids, $new_tag_ids );
275
+
276
+ if ( ! empty( $products ) ) {
277
+
278
+ ob_start();
279
+
280
+ ?>
281
+ <button
282
+ id="facebook-for-woocommerce-confirm-settings-change"
283
+ class="button button-large button-primary"
284
+ ><?php esc_html_e( 'Exclude Products', 'facebook-for-woocommerce' ); ?></button>
285
+ <button
286
+ id="facebook-for-woocommerce-cancel-settings-change"
287
+ class="button button-large button-primary"
288
+ onclick="jQuery( '.modal-close' ).trigger( 'click' )"
289
+ ><?php esc_html_e( 'Cancel', 'facebook-for-woocommerce' ); ?></button>
290
+ <?php
291
+
292
+ $buttons = ob_get_clean();
293
+
294
+ wp_send_json_error( [
295
+ 'message' => sprintf(
296
+ /* translators: Placeholder %s - <br/> tags */
297
+ __( 'The categories and/or tags that you have selected to exclude from sync contain products that are currently synced to Facebook.%sTo exclude these products from the Facebook sync, click Exclude Products. To review the category / tag exclusion settings, click Cancel.', 'facebook-for-woocommerce' ),
298
+ '<br/><br/>'
299
+ ),
300
+ 'buttons' => $buttons,
301
+ ] );
302
+
303
+ } else {
304
+
305
+ // the modal should not be displayed
306
+ wp_send_json_success();
307
+ }
308
+ }
309
+
310
+
311
+ /**
312
+ * Get the IDs of the products that would be excluded with the new settings.
313
+ *
314
+ * Queries products with sync enabled, belonging to the added term IDs
315
+ * and not belonging to the term IDs that are already stored in the setting.
316
+ *
317
+ * @since 1.10.0
318
+ *
319
+ * @param string[] $new_excluded_categories
320
+ * @param string[] $new_excluded_tags
321
+ * @return int[]
322
+ */
323
+ private function get_products_to_be_excluded( $new_excluded_categories = [], $new_excluded_tags = [] ) {
324
+
325
+ // products with sync enabled
326
+ $sync_enabled_meta_query = [
327
+ 'relation' => 'OR',
328
+ [
329
+ 'key' => Products::SYNC_ENABLED_META_KEY,
330
+ 'value' => 'yes',
331
+ ],
332
+ [
333
+ 'key' => Products::SYNC_ENABLED_META_KEY,
334
+ 'compare' => 'NOT EXISTS',
335
+ ],
336
+ ];
337
+
338
+ $products_query_vars = [
339
+ 'post_type' => 'product',
340
+ 'fields' => 'ids',
341
+ 'meta_query' => $sync_enabled_meta_query,
342
+ ];
343
+
344
+ if ( ! empty( $new_excluded_categories ) ) {
345
+
346
+ // products that belong to the new excluded categories
347
+ $categories_tax_query = [
348
+ 'taxonomy' => 'product_cat',
349
+ 'terms' => $new_excluded_categories,
350
+ ];
351
+
352
+ if ( $integration = facebook_for_woocommerce()->get_integration() ) {
353
+
354
+ // products that do not belong to the saved excluded categories
355
+ $saved_excluded_categories = $integration->get_excluded_product_category_ids();
356
+
357
+ if ( ! empty( $saved_excluded_categories ) ) {
358
+
359
+ $categories_tax_query = [
360
+ 'relation' => 'AND',
361
+ $categories_tax_query,
362
+ [
363
+ 'taxonomy' => 'product_cat',
364
+ 'terms' => $saved_excluded_categories,
365
+ 'operator' => 'NOT IN',
366
+ ],
367
+ ];
368
+ }
369
+ }
370
+
371
+ $products_query_vars['tax_query'] = $categories_tax_query;
372
+ }
373
+
374
+ if ( ! empty( $new_excluded_tags ) ) {
375
+
376
+ // products that belong to the new excluded tags
377
+ $tags_tax_query = [
378
+ 'taxonomy' => 'product_tag',
379
+ 'terms' => $new_excluded_tags,
380
+ ];
381
+
382
+ if ( $integration = facebook_for_woocommerce()->get_integration() ) {
383
+
384
+ $save_excluded_tags = $integration->get_excluded_product_tag_ids();
385
+
386
+ if ( ! empty( $save_excluded_tags ) ) {
387
+
388
+ // products that do not belong to the saved excluded tags
389
+ $tags_tax_query = [
390
+ 'relation' => 'AND',
391
+ $tags_tax_query,
392
+ [
393
+ 'taxonomy' => 'product_tag',
394
+ 'terms' => $save_excluded_tags,
395
+ 'operator' => 'NOT IN',
396
+ ],
397
+ ];
398
+ }
399
+ }
400
+
401
+ if ( empty( $products_query_vars['tax_query'] ) ) {
402
+
403
+ $products_query_vars['tax_query'] = $tags_tax_query;
404
+
405
+ } else {
406
+
407
+ $products_query_vars['tax_query'] = [
408
+ 'relation' => 'OR',
409
+ $products_query_vars,
410
+ $tags_tax_query,
411
+ ];
412
+ }
413
+ }
414
+
415
+ $products_query = new \WP_Query( $products_query_vars );
416
+
417
+ return $products_query->posts;
418
+ }
419
+
420
+
421
+ /**
422
+ * Sets products visibility in Facebook.
423
+ *
424
+ * @internal
425
+ *
426
+ * @since 1.10.0
427
+ */
428
+ public function set_products_visibility() {
429
+
430
+ check_ajax_referer( 'set-products-visibility', 'security' );
431
+
432
+ $integration = facebook_for_woocommerce()->get_integration();
433
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended
434
+ $products = isset( $_POST['products'] ) ? (array) $_POST['products'] : [];
435
+
436
+ if ( $integration && ! empty( $products ) ) {
437
+
438
+ foreach ( $products as $product_data ) {
439
+
440
+ $visibility_meta_value = isset( $product_data['visibility'] ) ? wc_string_to_bool( $product_data['visibility'] ) : null;
441
+
442
+ if ( ! is_bool( $visibility_meta_value ) ) {
443
+ continue;
444
+ }
445
+
446
+ $visibility_api_value = $visibility_meta_value ? $integration::FB_SHOP_PRODUCT_VISIBLE : $integration::FB_SHOP_PRODUCT_HIDDEN;
447
+
448
+ $product_id = isset( $product_data['product_id'] ) ? absint( $product_data['product_id'] ) : 0;
449
+ $product = $product_id > 0 ? wc_get_product( $product_id ) : null;
450
+
451
+ if ( $product instanceof \WC_Product ) {
452
+
453
+ // also extend toggle to child variations
454
+ if ( $product->is_type( 'variable' ) ) {
455
+
456
+ foreach ( $product->get_children() as $variation_id ) {
457
+
458
+ if ( $variation_product = wc_get_product( $variation_id ) ) {
459
+
460
+ $fb_item_id = $integration->get_product_fbid( \WC_Facebookcommerce_Integration::FB_PRODUCT_ITEM_ID, $variation_product->get_id() );
461
+ $fb_request = $integration->fbgraph->update_product_item( $fb_item_id, [
462
+ 'visibility' => $visibility_api_value,
463
+ ] );
464
+
465
+ if ( $integration->check_api_result( $fb_request ) ) {
466
+ Products::set_product_visibility( $variation_product, $visibility_meta_value );
467
+ }
468
+ }
469
+ }
470
+
471
+ Products::set_product_visibility( $product, $visibility_meta_value );
472
+
473
+ } else {
474
+
475
+ $fb_item_id = $integration->get_product_fbid( \WC_Facebookcommerce_Integration::FB_PRODUCT_ITEM_ID, $product->get_id() );
476
+ $fb_request = $integration->fbgraph->update_product_item( $fb_item_id, [
477
+ 'visibility' => $visibility_api_value,
478
+ ] );
479
+
480
+ if ( $integration->check_api_result( $fb_request ) ) {
481
+ Products::set_product_visibility( $product, $visibility_meta_value );
482
+ }
483
+ }
484
+ }
485
+ }
486
+
487
+ wp_send_json_success();
488
+ }
489
+
490
+ wp_send_json_error();
491
+ }
492
+
493
+
494
+ }
includes/Admin.php ADDED
@@ -0,0 +1,878 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
4
+ *
5
+ * This source code is licensed under the license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ *
8
+ * @package FacebookCommerce
9
+ */
10
+
11
+ namespace SkyVerge\WooCommerce\Facebook;
12
+
13
+ defined( 'ABSPATH' ) or exit;
14
+
15
+ /**
16
+ * Admin handler.
17
+ *
18
+ * @since 1.10.0
19
+ */
20
+ class Admin {
21
+
22
+
23
+ /**
24
+ * Admin constructor.
25
+ *
26
+ * @since 1.10.0
27
+ */
28
+ public function __construct() {
29
+
30
+ // enqueue admin scripts
31
+ add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_scripts' ] );
32
+
33
+ $integration = facebook_for_woocommerce()->get_integration();
34
+
35
+ // only alter the admin UI if the plugin is connected to Facebook and ready to sync products
36
+ if ( ! $integration->get_page_access_token() || ! $integration->get_product_catalog_id() ) {
37
+ return;
38
+ }
39
+
40
+ // add a modal in admin product pages
41
+ add_action( 'admin_footer', [ $this, 'render_modal_template' ] );
42
+ // may trigger the modal to open to warn the merchant about a conflict with the current product terms
43
+ add_action( 'admin_footer', [ $this, 'validate_product_excluded_terms' ] );
44
+
45
+ // add admin notification in case of site URL change
46
+ add_action( 'admin_notices', [ $this, 'validate_cart_url' ] );
47
+
48
+ // add columns for displaying Facebook sync enabled/disabled and catalog visibility status
49
+ add_filter( 'manage_product_posts_columns', [ $this, 'add_product_list_table_columns' ] );
50
+ add_action( 'manage_product_posts_custom_column', [ $this, 'add_product_list_table_columns_content' ] );
51
+
52
+ // add input to filter products by Facebook sync enabled
53
+ add_action( 'restrict_manage_posts', [ $this, 'add_products_by_sync_enabled_input_filter' ], 40 );
54
+ add_filter( 'request', [ $this, 'filter_products_by_sync_enabled' ] );
55
+
56
+ // add bulk actions to manage products sync
57
+ add_filter( 'bulk_actions-edit-product', [ $this, 'add_products_sync_bulk_actions' ], 40 );
58
+ add_action( 'handle_bulk_actions-edit-product', [ $this, 'handle_products_sync_bulk_actions' ] );
59
+
60
+ // add Product data tab
61
+ add_filter( 'woocommerce_product_data_tabs', [ $this, 'add_product_settings_tab' ] );
62
+ add_action( 'woocommerce_product_data_panels', [ $this, 'add_product_settings_tab_content' ] );
63
+
64
+ // add Variation edit fields
65
+ add_action( 'woocommerce_product_after_variable_attributes', [ $this, 'add_product_variation_edit_fields' ], 10, 3 );
66
+ add_action( 'woocommerce_save_product_variation', [ $this, 'save_product_variation_edit_fields' ], 10, 2 );
67
+ }
68
+
69
+
70
+ /**
71
+ * Enqueues admin scripts.
72
+ *
73
+ * @internal
74
+ *
75
+ * @since 1.10.0
76
+ */
77
+ public function enqueue_scripts() {
78
+ global $current_screen;
79
+
80
+ $modal_screens = [
81
+ 'product',
82
+ 'edit-product',
83
+ 'woocommerce_page_wc-settings',
84
+ ];
85
+
86
+ if ( isset( $current_screen->id ) ) {
87
+
88
+ if ( in_array( $current_screen->id, $modal_screens ) ) {
89
+
90
+ // enqueue modal functions
91
+ wp_enqueue_script( 'facebook-for-woocommerce-modal', plugins_url( '/facebook-for-woocommerce/assets/js/facebook-for-woocommerce-modal.min.js' ), [ 'jquery', 'wc-backbone-modal', 'jquery-blockui' ], \WC_Facebookcommerce::PLUGIN_VERSION );
92
+ }
93
+
94
+ if ( 'product' === $current_screen->id || 'edit-product' === $current_screen->id ) {
95
+
96
+ wp_enqueue_style( 'facebook-for-woocommerce-products-admin', plugins_url( '/facebook-for-woocommerce/assets/css/admin/facebook-for-woocommerce-products-admin.css' ), [], \WC_Facebookcommerce::PLUGIN_VERSION );
97
+
98
+ wp_enqueue_script( 'facebook-for-woocommerce-products-admin', plugins_url( '/facebook-for-woocommerce/assets/js/admin/facebook-for-woocommerce-products-admin.min.js' ), [ 'jquery', 'wc-backbone-modal', 'jquery-blockui', 'facebook-for-woocommerce-modal' ], \WC_Facebookcommerce::PLUGIN_VERSION );
99
+
100
+ wp_localize_script( 'facebook-for-woocommerce-products-admin', 'facebook_for_woocommerce_products_admin', [
101
+ 'ajax_url' => admin_url( 'admin-ajax.php' ),
102
+ 'set_product_visibility_nonce' => wp_create_nonce( 'set-products-visibility' ),
103
+ 'set_product_sync_prompt_nonce' => wp_create_nonce( 'set-product-sync-prompt' ),
104
+ 'set_product_sync_bulk_action_prompt_nonce' => wp_create_nonce( 'set-product-sync-bulk-action-prompt' ),
105
+ ] );
106
+ }
107
+
108
+ if ( 'woocommerce_page_wc-settings' === $current_screen->id ) {
109
+
110
+ wp_enqueue_script( 'facebook-for-woocommerce-settings-sync', plugins_url( '/facebook-for-woocommerce/assets/js/admin/facebook-for-woocommerce-settings-sync.min.js' ), [ 'jquery', 'wc-backbone-modal', 'jquery-blockui', 'facebook-for-woocommerce-modal' ], \WC_Facebookcommerce::PLUGIN_VERSION );
111
+
112
+ wp_localize_script( 'facebook-for-woocommerce-settings-sync', 'facebook_for_woocommerce_settings_sync', [
113
+ 'ajax_url' => admin_url( 'admin-ajax.php' ),
114
+ 'set_excluded_terms_prompt_nonce' => wp_create_nonce( 'set-excluded-terms-prompt' ),
115
+ ] );
116
+ }
117
+ }
118
+ }
119
+
120
+
121
+ /**
122
+ * Adds Facebook-related columns in the products edit screen.
123
+ *
124
+ * @internal
125
+ *
126
+ * @since 1.10.0
127
+ *
128
+ * @param array $columns array of keys and labels
129
+ * @return array
130
+ */
131
+ public function add_product_list_table_columns( $columns ) {
132
+
133
+ $columns['facebook_sync_enabled'] = __( 'FB Sync Enabled', 'facebook-for-woocommerce' );
134
+ $columns['facebook_catalog_visibility'] = __( 'FB Catalog Visibility', 'facebook-for-woocommerce' );
135
+
136
+ return $columns;
137
+ }
138
+
139
+
140
+ /**
141
+ * Outputs sync information for products in the edit screen.
142
+ *
143
+ * @internal
144
+ *
145
+ * @since 1.10.0
146
+ *
147
+ * @param string $column the current column in the posts table
148
+ */
149
+ public function add_product_list_table_columns_content( $column ) {
150
+ global $post;
151
+
152
+ if ( 'facebook_sync_enabled' === $column ) :
153
+
154
+ $product = wc_get_product( $post );
155
+
156
+ if ( $product && Products::product_should_be_synced( $product ) ) :
157
+ esc_html_e( 'Enabled', 'facebook-for-woocommerce' );
158
+ else :
159
+ esc_html_e( 'Disabled', 'facebook-for-woocommerce' );
160
+ endif;
161
+
162
+ elseif ( 'facebook_catalog_visibility' === $column ) :
163
+
164
+ $integration = facebook_for_woocommerce()->get_integration();
165
+ $product = wc_get_product( $post );
166
+ $fb_product = new \WC_Facebook_Product( $post );
167
+ $fb_product_group_id = $integration && $product && $integration->get_product_fbid( \WC_Facebookcommerce_Integration::FB_PRODUCT_GROUP_ID, $post->ID, $fb_product );
168
+
169
+ if ( ! $fb_product_group_id ) :
170
+
171
+ ?>
172
+ <span
173
+ class="facebook-for-woocommerce-product-visibility-toggle"
174
+ style="cursor:default;"
175
+ title="<?php esc_attr_e( 'Product is not synced with Facebook.', 'facebook-for-woocommerce' ); ?>"
176
+ >&ndash;</span>
177
+ <?php
178
+
179
+ else :
180
+
181
+ $is_sync_enabled = Products::product_should_be_synced( $product );
182
+ $is_visible = Products::is_product_visible( $product );
183
+ $is_hidden = ! $is_visible;
184
+
185
+ if ( $is_sync_enabled ) {
186
+
187
+ $visible_tooltip_text = __( 'Product is synced and published (visible) on Facebook.', 'facebook-for-woocommerce' );
188
+ $hidden_tooltip_text = __( 'Product is synced but not marked as published (visible) on Facebook.', 'facebook-for-woocommerce' );
189
+
190
+ } else {
191
+
192
+ $visible_tooltip_text = __( 'Product is published (visible) on Facebook, but not synced, so updates won’t be reflected on Facebook.', 'facebook-for-woocommerce' );
193
+ $hidden_tooltip_text = __( 'Product is not synced and not published (visible) on Facebook.', 'facebook-for-woocommerce' );
194
+ }
195
+
196
+ ?>
197
+ <button
198
+ id="facebook-for-woocommerce-product-visibility-show-<?php echo esc_attr( $post->ID ); ?>"
199
+ class="button button-primary button-large facebook-for-woocommerce-product-visibility-toggle facebook-for-woocommerce-product-visibility-show"
200
+ style="<?php echo $is_hidden ? 'display:block;' : 'display:none;'; ?>"
201
+ data-action="show"
202
+ data-product-id="<?php echo esc_attr( $post->ID ); ?>"
203
+ title="<?php echo esc_attr( $hidden_tooltip_text ); ?>"
204
+ ><?php esc_html_e( 'Show', 'facebook-for-woocommerce' ); ?></button>
205
+ <button
206
+ id="facebook-for-woocommerce-product-visibility-hide-<?php echo esc_attr( $post->ID ); ?>"
207
+ class="button button-large facebook-for-woocommerce-product-visibility-toggle facebook-for-woocommerce-product-visibility-hide"
208
+ style="<?php echo $is_visible ? 'display:block;' : 'display:none;'; ?>"
209
+ data-action="hide"
210
+ data-product-id="<?php echo esc_attr( $post->ID ); ?>"
211
+ title="<?php echo esc_attr( $visible_tooltip_text ); ?>"
212
+ ><?php esc_html_e( 'Hide', 'facebook-for-woocommerce' ); ?></button>
213
+ <?php
214
+
215
+ endif;
216
+
217
+ endif;
218
+ }
219
+
220
+
221
+ /**
222
+ * Adds a dropdown input to let shop managers filter products by sync setting.
223
+ *
224
+ * @internal
225
+ *
226
+ * @since 1.10.0
227
+ */
228
+ public function add_products_by_sync_enabled_input_filter() {
229
+ global $typenow;
230
+
231
+ if ( 'product' !== $typenow ) {
232
+ return;
233
+ }
234
+
235
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended
236
+ $choice = isset( $_GET['fb_sync_enabled'] ) ? (string) sanitize_text_field( wp_unslash( $_GET['fb_sync_enabled'] ) ) : '';
237
+
238
+ ?>
239
+ <select name="fb_sync_enabled">
240
+ <option value="" <?php selected( $choice, '' ); ?>><?php esc_html_e( 'Filter by Facebook sync setting', 'facebook-for-woocommerce' ); ?></option>
241
+ <option value="yes" <?php selected( $choice, 'yes' ); ?>><?php esc_html_e( 'Facebook sync enabled', 'facebook-for-woocommerce' ); ?></option>
242
+ <option value="no" <?php selected( $choice, 'no' ); ?>><?php esc_html_e( 'Facebook sync disabled', 'facebook-for-woocommerce' ); ?></option>
243
+ </select>
244
+ <?php
245
+ }
246
+
247
+
248
+ /**
249
+ * Filters products by Facebook sync setting.
250
+ *
251
+ * @internal
252
+ *
253
+ * @since 1.10.0
254
+ *
255
+ * @param array $query_vars product query vars for the edit screen
256
+ * @return array
257
+ */
258
+ public function filter_products_by_sync_enabled( $query_vars ) {
259
+
260
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended
261
+ if ( isset( $_REQUEST['fb_sync_enabled'] ) && in_array( $_REQUEST['fb_sync_enabled'], [ 'yes', 'no' ], true ) ) {
262
+
263
+ // by default use an "AND" clause if multiple conditions exist for a meta query
264
+ if ( ! empty( $query_vars['meta_query'] ) ) {
265
+ $query_vars['meta_query']['relation'] = 'AND';
266
+ } else {
267
+ $query_vars['meta_query'] = [];
268
+ }
269
+
270
+ // when checking for products with sync enabled we need to check both "yes" and meta not set, this requires adding an "OR" clause
271
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended
272
+ if ( 'yes' === $_REQUEST['fb_sync_enabled'] ) {
273
+
274
+ $query_vars = $this->add_query_vars_to_find_products_with_sync_enabled( $query_vars );
275
+
276
+ } else {
277
+
278
+ $integration = facebook_for_woocommerce()->get_integration();
279
+ $excluded_categories_ids = $integration ? $integration->get_excluded_product_category_ids() : [];
280
+ $exlcuded_tags_ids = $integration ? $integration->get_excluded_product_tag_ids() : [];
281
+
282
+ if ( $excluded_categories_ids || $exlcuded_tags_ids ) {
283
+
284
+ // find the IDs of products that have sync enabled
285
+ $products_query_vars = [
286
+ 'post_type' => 'product',
287
+ 'post_status' => ! empty( $query_vars['post_status'] ) ? $query_vars['post_status'] : 'any',
288
+ 'no_found_rows' => true,
289
+ 'update_post_meta_cache' => false,
290
+ 'update_post_term_cache' => false,
291
+ 'fields' => 'ids',
292
+ 'nopaging' => true,
293
+ ];
294
+
295
+ $products_query_vars = $this->add_query_vars_to_find_products_with_sync_enabled( $products_query_vars );
296
+
297
+ // exclude products that have sync enabled from the current query
298
+ $query_vars['post__not_in'] = get_posts( $products_query_vars );
299
+
300
+ } else {
301
+
302
+ $query_vars['meta_query'][] = [
303
+ 'key' => Products::SYNC_ENABLED_META_KEY,
304
+ 'value' => 'no',
305
+ ];
306
+ }
307
+ }
308
+ }
309
+
310
+ return $query_vars;
311
+ }
312
+
313
+
314
+ /**
315
+ * Adds query vars to limit the results to products that have sync enabled.
316
+ *
317
+ * @since 1.10.0
318
+ *
319
+ * @param array $query_vars
320
+ * @return array
321
+ */
322
+ private function add_query_vars_to_find_products_with_sync_enabled( array $query_vars ) {
323
+
324
+ $query_vars['meta_query']['relation'] = 'OR';
325
+ $query_vars['meta_query'][] = [
326
+ 'key' => Products::SYNC_ENABLED_META_KEY,
327
+ 'value' => 'yes',
328
+ ];
329
+ $query_vars['meta_query'][] = [
330
+ 'key' => Products::SYNC_ENABLED_META_KEY,
331
+ 'compare' => 'NOT EXISTS',
332
+ ];
333
+
334
+ // check whether the product belongs to an excluded product category or tag
335
+ $query_vars = $this->maybe_add_tax_query_for_excluded_taxonomies( $query_vars );
336
+
337
+ return $query_vars;
338
+ }
339
+
340
+
341
+ /**
342
+ * Adds a tax query to filter out products in excluded product categories and product tags.
343
+ *
344
+ * @since 1.10.0
345
+ *
346
+ * @param array $query_vars product query vars for the edit screen
347
+ * @return array
348
+ */
349
+ private function maybe_add_tax_query_for_excluded_taxonomies( $query_vars ) {
350
+
351
+ $integration = facebook_for_woocommerce()->get_integration();
352
+
353
+ if ( $integration ) {
354
+
355
+ $tax_query = [];
356
+ $excluded_categories_ids = $integration->get_excluded_product_category_ids();
357
+
358
+ if ( $excluded_categories_ids ) {
359
+ $tax_query[] = [
360
+ 'taxonomy' => 'product_cat',
361
+ 'terms' => $excluded_categories_ids,
362
+ 'field' => 'term_id',
363
+ 'operator' => 'NOT IN',
364
+ ];
365
+ }
366
+
367
+ $excluded_tags_ids = $integration->get_excluded_product_tag_ids();
368
+
369
+ if ( $excluded_tags_ids ) {
370
+ $tax_query[] = [
371
+ 'taxonomy' => 'product_tag',
372
+ 'terms' => $excluded_tags_ids,
373
+ 'field' => 'term_id',
374
+ 'operator' => 'NOT IN',
375
+ ];
376
+ }
377
+
378
+ if ( $tax_query && empty( $query_vars['tax_query'] ) ) {
379
+ $query_vars['tax_query'] = $tax_query;
380
+ } elseif ( $tax_query && is_array( $query_vars ) ) {
381
+ $query_vars['tax_query'][] = $tax_query;
382
+ }
383
+ }
384
+
385
+ return $query_vars;
386
+ }
387
+
388
+
389
+ /**
390
+ * Adds bulk actions in the products edit screen.
391
+ *
392
+ * @internal
393
+ *
394
+ * @since 1.10.0
395
+ *
396
+ * @param array $bulk_actions array of bulk action keys and labels
397
+ * @return array
398
+ */
399
+ public function add_products_sync_bulk_actions( $bulk_actions ) {
400
+
401
+ $bulk_actions['facebook_include'] = __( 'Include in Facebook sync', 'facebook-for-woocommerce' );
402
+ $bulk_actions['facebook_exclude'] = __( 'Exclude from Facebook sync', 'facebook-for-woocommerce' );
403
+
404
+ return $bulk_actions;
405
+ }
406
+
407
+
408
+ /**
409
+ * Handles a Facebook product sync bulk action.
410
+ *
411
+ * @internal
412
+ *
413
+ * @since 1.10.0
414
+ *
415
+ * @param string $redirect admin URL used by WordPress to redirect after performing the bulk action
416
+ * @return string
417
+ */
418
+ public function handle_products_sync_bulk_actions( $redirect ) {
419
+
420
+ // primary dropdown at the top of the list table
421
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended
422
+ $action = isset( $_REQUEST['action'] ) && -1 !== (int) $_REQUEST['action'] ? sanitize_text_field( wp_unslash( $_REQUEST['action'] ) ) : null;
423
+
424
+ // secondary dropdown at the bottom of the list table
425
+ if ( ! $action ) {
426
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended
427
+ $action = isset( $_REQUEST['action2'] ) && -1 !== (int) $_REQUEST['action2'] ? sanitize_text_field( wp_unslash( $_REQUEST['action2'] ) ) : null;
428
+ }
429
+
430
+ if ( $action && in_array( $action, [ 'facebook_include', 'facebook_exclude' ], true ) ) {
431
+
432
+ $products = [];
433
+
434
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended
435
+ $product_ids = isset( $_REQUEST['post'] ) && is_array( $_REQUEST['post'] ) ? array_map( 'absint', $_REQUEST['post'] ) : [];
436
+
437
+ if ( ! empty( $product_ids ) ) {
438
+
439
+ foreach ( $product_ids as $product_id ) {
440
+
441
+ if ( $product = wc_get_product( $product_id ) ) {
442
+
443
+ $products[] = $product;
444
+ }
445
+ }
446
+
447
+ if ( 'facebook_include' === $action ) {
448
+
449
+ Products::enable_sync_for_products( $products );
450
+
451
+ // re-sync each product
452
+ foreach ( $products as $product ) {
453
+ facebook_for_woocommerce()->get_integration()->on_product_publish( $product->get_id() );
454
+ }
455
+
456
+ } elseif ( 'facebook_exclude' === $action ) {
457
+
458
+ Products::disable_sync_for_products( $products );
459
+ }
460
+ }
461
+ }
462
+
463
+ return $redirect;
464
+ }
465
+
466
+
467
+ /**
468
+ * Prints a notice on products page in case the current cart URL is not the original sync URL.
469
+ *
470
+ * @internal
471
+ *
472
+ * TODO: update this method to use the notice handler once we framework the plugin {CW 2020-01-09}
473
+ *
474
+ * @since 1.10.0
475
+ */
476
+ public function validate_cart_url() {
477
+ global $current_screen;
478
+
479
+ if ( isset( $current_screen->id ) && in_array( $current_screen->id, [ 'edit-product', 'product' ], true ) ) :
480
+
481
+ $cart_url = get_option( \WC_Facebookcommerce_Integration::FB_CART_URL, '' );
482
+
483
+ if ( ! empty( $cart_url ) && $cart_url !== wc_get_cart_url() ) :
484
+
485
+ ?>
486
+ <div class="notice notice-warning">
487
+ <?php printf(
488
+ /* translators: Placeholders: %1$s - Facebook for Woocommerce, %2$s - opening HTML <a> link tag, %3$s - closing HTML </a> link tag */
489
+ '<p>' . esc_html__( '%1$s: One or more of your products is using a checkout URL that may be different than your shop checkout URL. %2$sRe-sync your products to update checkout URLs on Facebook%3$s.', 'facebook-for-woocommerce' ) . '</p>',
490
+ '<strong>' . esc_html__( 'Facebook for WooCommerce', 'facebook-for-woocommerce' ) . '</strong>',
491
+ '<a href="' . esc_url( WOOCOMMERCE_FACEBOOK_PLUGIN_SETTINGS_URL ) . '">',
492
+ '</a>'
493
+ ); ?>
494
+ </div>
495
+ <?php
496
+
497
+ endif;
498
+
499
+ endif;
500
+ }
501
+
502
+
503
+ /**
504
+ * Adds a new tab to the Product edit page.
505
+ *
506
+ * @internal
507
+ *
508
+ * @since 1.10.0
509
+ *
510
+ * @param array $tabs product tabs
511
+ * @return array
512
+ */
513
+ public function add_product_settings_tab( $tabs ) {
514
+
515
+ $tabs['fb_commerce_tab'] = [
516
+ 'label' => __( 'Facebook', 'facebook-for-woocommerce' ),
517
+ 'target' => 'facebook_options',
518
+ 'class' => [ 'show_if_simple' ],
519
+ ];
520
+
521
+ return $tabs;
522
+ }
523
+
524
+
525
+ /**
526
+ * Adds content to the new Facebook tab on the Product edit page.
527
+ *
528
+ * @internal
529
+ *
530
+ * @since 1.10.0
531
+ */
532
+ public function add_product_settings_tab_content() {
533
+ global $post;
534
+
535
+ // all products have sync enabled unless explicitly disabled
536
+ $sync_enabled = 'no' !== get_post_meta( $post->ID, Products::SYNC_ENABLED_META_KEY, true );
537
+ $description = get_post_meta( $post->ID, \WC_Facebookcommerce_Integration::FB_PRODUCT_DESCRIPTION, true );
538
+ $price = get_post_meta( $post->ID, \WC_Facebook_Product::FB_PRODUCT_PRICE, true );
539
+ $image_source = get_post_meta( $post->ID, Products::PRODUCT_IMAGE_SOURCE_META_KEY, true );
540
+ $image = get_post_meta( $post->ID, \WC_Facebook_Product::FB_PRODUCT_IMAGE, true );
541
+
542
+ // 'id' attribute needs to match the 'target' parameter set above
543
+ ?>
544
+ <div id='facebook_options' class='panel woocommerce_options_panel'>
545
+ <div class='options_group'>
546
+ <?php
547
+
548
+ woocommerce_wp_checkbox( [
549
+ 'id' => 'fb_sync_enabled',
550
+ 'label' => __( 'Include in Facebook sync', 'facebook-for-woocommerce' ),
551
+ 'value' => wc_bool_to_string( (bool) $sync_enabled ),
552
+ ] );
553
+
554
+ woocommerce_wp_textarea_input( [
555
+ 'id' => \WC_Facebookcommerce_Integration::FB_PRODUCT_DESCRIPTION,
556
+ 'label' => __( 'Facebook Description', 'facebook-for-woocommerce' ),
557
+ 'desc_tip' => true,
558
+ 'description' => __( 'Custom (plain-text only) description for product on Facebook. If blank, product description will be used. If product description is blank, shortname will be used.', 'facebook-for-woocommerce' ),
559
+ 'cols' => 40,
560
+ 'rows' => 20,
561
+ 'value' => $description,
562
+ 'class' => 'enable-if-sync-enabled',
563
+ ] );
564
+
565
+ woocommerce_wp_radio( [
566
+ 'id' => 'fb_product_image_source',
567
+ 'label' => __( 'Facebook Product Image', 'facebook-for-woocommerce' ),
568
+ 'desc_tip' => true,
569
+ 'description' => __( 'Choose the product image that should be synced to the Facebook catalog for this product. If using a custom image, please enter an absolute URL (e.g. https://domain.com/image.jpg).', 'facebook-for-woocommerce' ),
570
+ 'options' => [
571
+ Products::PRODUCT_IMAGE_SOURCE_PRODUCT => __( 'Use WooCommerce image', 'facebook-for-woocommerce' ),
572
+ Products::PRODUCT_IMAGE_SOURCE_CUSTOM => __( 'Use custom image', 'facebook-for-woocommerce' ),
573
+ ],
574
+ 'value' => $image_source ?: Products::PRODUCT_IMAGE_SOURCE_PRODUCT,
575
+ 'class' => 'short enable-if-sync-enabled js-fb-product-image-source',
576
+ 'wrapper_class' => 'fb-product-image-source-field',
577
+ ] );
578
+
579
+ woocommerce_wp_text_input( [
580
+ 'id' => \WC_Facebook_Product::FB_PRODUCT_IMAGE,
581
+ 'label' => __( 'Custom Image URL', 'facebook-for-woocommerce' ),
582
+ 'value' => $image,
583
+ 'class' => sprintf( 'enable-if-sync-enabled product-image-source-field show-if-product-image-source-%s', Products::PRODUCT_IMAGE_SOURCE_CUSTOM ),
584
+ ] );
585
+
586
+ woocommerce_wp_text_input( [
587
+ 'id' => \WC_Facebook_Product::FB_PRODUCT_PRICE,
588
+ 'label' => sprintf(
589
+ /* translators: Placeholders %1$s - WC currency symbol */
590
+ __( 'Facebook Price (%1$s)', 'facebook-for-woocommerce' ),
591
+ get_woocommerce_currency_symbol()
592
+ ),
593
+ 'desc_tip' => true,
594
+ 'description' => __( 'Custom price for product on Facebook. Please enter in monetary decimal (.) format without thousand separators and currency symbols. If blank, product price will be used.', 'facebook-for-woocommerce' ),
595
+ 'cols' => 40,
596
+ 'rows' => 60,
597
+ 'value' => $price,
598
+ 'class' => 'enable-if-sync-enabled',
599
+ ] );
600
+
601
+ ?>
602
+ </div>
603
+ </div>
604
+ <?php
605
+ }
606
+
607
+
608
+ /**
609
+ * Outputs the Facebook settings fields for a single variation.
610
+ *
611
+ * @internal
612
+ *
613
+ * @since 1.10.0
614
+ *
615
+ * @param int $index the index of the current variation
616
+ * @param array $variation_data unused
617
+ * @param \WC_Post $post the post type for the current variation
618
+ */
619
+ public function add_product_variation_edit_fields( $index, $variation_data, $post ) {
620
+
621
+ $variation = wc_get_product( $post );
622
+
623
+ if ( ! $variation instanceof \WC_Product_Variation ) {
624
+ return;
625
+ }
626
+
627
+ $parent = wc_get_product( $variation->get_parent_id() );
628
+
629
+ if ( ! $parent instanceof \WC_Product ) {
630
+ return;
631
+ }
632
+
633
+ $sync_enabled = $this->get_product_variation_meta( $variation, Products::SYNC_ENABLED_META_KEY, $parent );
634
+ $description = $this->get_product_variation_meta( $variation, \WC_Facebookcommerce_Integration::FB_PRODUCT_DESCRIPTION, $parent );
635
+ $price = $this->get_product_variation_meta( $variation, \WC_Facebook_Product::FB_PRODUCT_PRICE, $parent );
636
+ $image_url = $this->get_product_variation_meta( $variation, \WC_Facebook_Product::FB_PRODUCT_IMAGE, $parent );
637
+ $image_source = $variation->get_meta( Products::PRODUCT_IMAGE_SOURCE_META_KEY );
638
+
639
+ woocommerce_wp_checkbox( [
640
+ 'id' => "variable_fb_sync_enabled$index",
641
+ 'name' => "variable_fb_sync_enabled[$index]",
642
+ 'label' => __( 'Include in Facebook sync', 'facebook-for-woocommerce' ),
643
+ 'value' => wc_bool_to_string( 'no' !== $sync_enabled ),
644
+ 'class' => 'checkbox js-variable-fb-sync-toggle',
645
+ 'wrapper_class' => 'fb-sync-enabled-field'
646
+ ] );
647
+
648
+ woocommerce_wp_textarea_input( [
649
+ 'id' => sprintf( 'variable_%s%s', \WC_Facebookcommerce_Integration::FB_PRODUCT_DESCRIPTION, $index ),
650
+ 'name' => sprintf( "variable_%s[$index]", \WC_Facebookcommerce_Integration::FB_PRODUCT_DESCRIPTION ),
651
+ 'label' => __( 'Facebook Description', 'facebook-for-woocommerce' ),
652
+ 'desc_tip' => true,
653
+ 'description' => __( 'Custom (plain-text only) description for product on Facebook. If blank, product description will be used. If product description is blank, shortname will be used.', 'facebook-for-woocommerce' ),
654
+ 'cols' => 40,
655
+ 'rows' => 5,
656
+ 'value' => $description,
657
+ 'class' => 'enable-if-sync-enabled',
658
+ 'wrapper_class' => 'form-row form-row-full',
659
+ ] );
660
+
661
+ woocommerce_wp_radio( [
662
+ 'id' => "variable_fb_product_image_source$index",
663
+ 'name' => "variable_fb_product_image_source[$index]",
664
+ 'label' => __( 'Facebook Product Image', 'facebook-for-woocommerce' ),
665
+ 'desc_tip' => true,
666
+ 'description' => __( 'Choose the product image that should be synced to the Facebook catalog for this product. If using a custom image, please enter an absolute URL (e.g. https://domain.com/image.jpg).', 'facebook-for-woocommerce' ),
667
+ 'options' => [
668
+ Products::PRODUCT_IMAGE_SOURCE_PRODUCT => __( 'Use variation image', 'facebook-for-woocommerce' ),
669
+ Products::PRODUCT_IMAGE_SOURCE_PARENT_PRODUCT => __( 'Use parent image', 'facebook-for-woocommerce' ),
670
+ Products::PRODUCT_IMAGE_SOURCE_CUSTOM => __( 'Use custom image', 'facebook-for-woocommerce' ),
671
+ ],
672
+ 'value' => $image_source ?: Products::PRODUCT_IMAGE_SOURCE_PRODUCT,
673
+ 'class' => 'enable-if-sync-enabled js-fb-product-image-source',
674
+ 'wrapper_class' => 'fb-product-image-source-field',
675
+ ] );
676
+
677
+ woocommerce_wp_text_input( [
678
+ 'id' => sprintf( 'variable_%s%s', \WC_Facebook_Product::FB_PRODUCT_IMAGE, $index ),
679
+ 'name' => sprintf( "variable_%s[$index]", \WC_Facebook_Product::FB_PRODUCT_IMAGE ),
680
+ 'label' => __( 'Custom Image URL', 'facebook-for-woocommerce' ),
681
+ 'value' => $image_url,
682
+ 'class' => sprintf( 'enable-if-sync-enabled product-image-source-field show-if-product-image-source-%s', Products::PRODUCT_IMAGE_SOURCE_CUSTOM ),
683
+ 'wrapper_class' => 'form-row form-row-full'
684
+ ] );
685
+
686
+ woocommerce_wp_text_input( [
687
+ 'id' => sprintf( 'variable_%s%s', \WC_Facebook_Product::FB_PRODUCT_PRICE, $index ),
688
+ 'name' => sprintf( "variable_%s[$index]", \WC_Facebook_Product::FB_PRODUCT_PRICE ),
689
+ 'label' => sprintf(
690
+ /* translators: Placeholders %1$s - WC currency symbol */
691
+ __( 'Facebook Price (%1$s)', 'facebook-for-woocommerce' ),
692
+ get_woocommerce_currency_symbol()
693
+ ),
694
+ 'desc_tip' => true,
695
+ 'description' => __( 'Custom price for product on Facebook. Please enter in monetary decimal (.) format without thousand separators and currency symbols. If blank, product price will be used.', 'facebook-for-woocommerce' ),
696
+ 'value' => wc_format_decimal( $price ),
697
+ 'class' => 'enable-if-sync-enabled',
698
+ 'wrapper_class' => 'form-row form-full',
699
+ ] );
700
+ }
701
+
702
+
703
+ /**
704
+ * Gets the stored value for the given meta of a product variation.
705
+ *
706
+ * If no value is found, we try to use the value stored in the parent product.
707
+ *
708
+ * @since 1.10.0
709
+ *
710
+ * @param \WC_Product_Variation $variation the product variation
711
+ * @param string $key the name of the meta to retrieve
712
+ * @param \WC_Product $parent the parent product
713
+ * @return mixed
714
+ */
715
+ private function get_product_variation_meta( $variation, $key, $parent ) {
716
+
717
+ $value = $variation->get_meta( $key );
718
+
719
+ if ( '' === $value && $parent instanceof \WC_Product ) {
720
+ $value = $parent->get_meta( $key );
721
+ }
722
+
723
+ return $value;
724
+ }
725
+
726
+
727
+ /**
728
+ * Saves the submitted Facebook settings for each variation.
729
+ *
730
+ * @internal
731
+ *
732
+ * @since 1.10.0
733
+ *
734
+ * @param int $variation_id the ID of the product variation being edited
735
+ * @param int $index the index of the current variation
736
+ */
737
+ public function save_product_variation_edit_fields( $variation_id, $index ) {
738
+
739
+ $variation = wc_get_product( $variation_id );
740
+
741
+ if ( ! $variation instanceof \WC_Product_Variation ) {
742
+ return;
743
+ }
744
+
745
+ // phpcs:disable WordPress.Security.NonceVerification.Missing
746
+ if ( isset( $_POST['variable_fb_sync_enabled'][ $index ] ) && 'yes' === $_POST['variable_fb_sync_enabled'][ $index ] ) {
747
+
748
+ Products::enable_sync_for_products( [ $variation ] );
749
+
750
+ $posted_param = 'variable_' . \WC_Facebookcommerce_Integration::FB_PRODUCT_DESCRIPTION;
751
+ $description = isset( $_POST[ $posted_param ][ $index ] ) ? sanitize_text_field( wp_unslash( $_POST[ $posted_param ][ $index ] ) ) : null;
752
+
753
+ $posted_param = 'variable_fb_product_image_source';
754
+ $image_source = isset( $_POST[ $posted_param ][ $index ] ) ? sanitize_key( wp_unslash( $_POST[ $posted_param ][ $index ] ) ) : '';
755
+
756
+ $posted_param = 'variable_' . \WC_Facebook_Product::FB_PRODUCT_IMAGE;
757
+ $image_url = isset( $_POST[ $posted_param ][ $index ] ) ? esc_url_raw( wp_unslash( $_POST[ $posted_param ][ $index ] ) ) : null;
758
+
759
+ $posted_param = 'variable_' . \WC_Facebook_Product::FB_PRODUCT_PRICE;
760
+ $price = isset( $_POST[ $posted_param ][ $index ] ) ? wc_format_decimal( $_POST[ $posted_param ][ $index ] ) : '';
761
+
762
+ $variation->update_meta_data( \WC_Facebookcommerce_Integration::FB_PRODUCT_DESCRIPTION, $description );
763
+ $variation->update_meta_data( Products::PRODUCT_IMAGE_SOURCE_META_KEY, $image_source );
764
+ $variation->update_meta_data( \WC_Facebook_Product::FB_PRODUCT_IMAGE, $image_url );
765
+ $variation->update_meta_data( \WC_Facebook_Product::FB_PRODUCT_PRICE, $price );
766
+ $variation->save_meta_data();
767
+
768
+ } else {
769
+
770
+ Products::disable_sync_for_products( [ $variation ] );
771
+
772
+ }
773
+ // phpcs:enable WordPress.Security.NonceVerification.Missing
774
+ }
775
+
776
+
777
+ /**
778
+ * Outputs a modal template in admin product pages.
779
+ *
780
+ * @internal
781
+ *
782
+ * @since 1.10.0
783
+ */
784
+ public function render_modal_template() {
785
+ global $current_screen;
786
+
787
+ // bail if not on the products, product edit, or settings screen
788
+ if ( ! $current_screen || ! in_array( $current_screen->id, [ 'edit-product', 'product', 'woocommerce_page_wc-settings' ], true ) ) {
789
+ return;
790
+ }
791
+
792
+ ?>
793
+ <script type="text/template" id="tmpl-facebook-for-woocommerce-modal">
794
+ <div class="wc-backbone-modal facebook-for-woocommerce-modal">
795
+ <div class="wc-backbone-modal-content">
796
+ <section class="wc-backbone-modal-main" role="main">
797
+ <header class="wc-backbone-modal-header">
798
+ <h1><?php esc_html_e( 'Facebook for WooCommerce', 'facebook-for-woocommerce' ); ?></h1>
799
+ <button class="modal-close modal-close-link dashicons dashicons-no-alt">
800
+ <span class="screen-reader-text"><?php esc_html_e( 'Close modal panel', 'facebook-for-woocommerce' ); ?></span>
801
+ </button>
802
+ </header>
803
+ <article>{{{data.message}}}</article>
804
+ <footer>
805
+ <div class="inner">{{{data.buttons}}}</div>
806
+ </footer>
807
+ </section>
808
+ </div>
809
+ </div>
810
+ <div class="wc-backbone-modal-backdrop modal-close"></div>
811
+ </script>
812
+ <?php
813
+ }
814
+
815
+
816
+ /**
817
+ * Maybe triggers the modal to open on the product edit screen on page load.
818
+ *
819
+ * If the product is set to be synced in Facebook, but belongs to a term that is set to be excluded, the modal prompts the merchant for action.
820
+ *
821
+ * @internal
822
+ *
823
+ * @since 1.10.0
824
+ */
825
+ public function validate_product_excluded_terms() {
826
+ global $current_screen, $post;
827
+
828
+ if ( $post && $current_screen && $current_screen->id === 'product' ) :
829
+
830
+ $product = wc_get_product( $post );
831
+
832
+ if ( $product instanceof \WC_Product
833
+ && Products::is_sync_enabled_for_product( $product )
834
+ && Products::is_sync_excluded_for_product_terms( $product )
835
+ ) :
836
+
837
+ ?>
838
+ <script type="text/javascript">
839
+ jQuery( document ).ready( function( $ ) {
840
+
841
+ var productID = parseInt( $( 'input#post_ID' ).val(), 10 ),
842
+ productTag = $( 'textarea[name=\"tax_input[product_tag]\"]' ).val().split( ',' ),
843
+ productCat = [];
844
+
845
+ $( '#taxonomy-product_cat input[name=\"tax_input[product_cat][]\"]:checked' ).each( function() {
846
+ productCat.push( parseInt( $( this ).val(), 10 ) );
847
+ } );
848
+
849
+ $.post( facebook_for_woocommerce_products_admin.ajax_url, {
850
+ action: 'facebook_for_woocommerce_set_product_sync_prompt',
851
+ security: facebook_for_woocommerce_products_admin.set_product_sync_prompt_nonce,
852
+ sync_enabled: 'enabled',
853
+ product: productID,
854
+ categories: productCat,
855
+ tags: productTag
856
+ }, function( response ) {
857
+
858
+ if ( response && ! response.success ) {
859
+
860
+ $( '#wc-backbone-modal-dialog .modal-close' ).trigger( 'click' );
861
+
862
+ new $.WCBackboneModal.View( {
863
+ target: 'facebook-for-woocommerce-modal',
864
+ string: response.data
865
+ } );
866
+ }
867
+ } );
868
+ } );
869
+ </script>
870
+ <?php
871
+
872
+ endif;
873
+
874
+ endif;
875
+ }
876
+
877
+
878
+ }
includes/Lifecycle.php ADDED
@@ -0,0 +1,156 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
4
+ *
5
+ * This source code is licensed under the license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ *
8
+ * @package FacebookCommerce
9
+ */
10
+
11
+ namespace SkyVerge\WooCommerce\Facebook;
12
+
13
+ use SkyVerge\WooCommerce\PluginFramework\v5_5_4 as Framework;
14
+
15
+ defined( 'ABSPATH' ) or exit;
16
+
17
+ /**
18
+ * The Facebook for WooCommerce plugin lifecycle handler.
19
+ *
20
+ * @since 1.10.0
21
+ *
22
+ * @method \WC_Facebookcommerce get_plugin()
23
+ */
24
+ class Lifecycle extends Framework\Plugin\Lifecycle {
25
+
26
+
27
+ /**
28
+ * Lifecycle constructor.
29
+ *
30
+ * @since 1.10.0
31
+ *
32
+ * @param Framework\SV_WC_Plugin $plugin
33
+ */
34
+ public function __construct( $plugin ) {
35
+
36
+ parent::__construct( $plugin );
37
+
38
+ $this->upgrade_versions = [
39
+ '1.10.0',
40
+ ];
41
+ }
42
+
43
+
44
+ /**
45
+ * Migrates options from previous versions of the plugin, which did not use the Framework.
46
+ *
47
+ * @since 1.10.0
48
+ */
49
+ protected function install() {
50
+
51
+ /**
52
+ * Versions prior to 1.10.0 did not set a version option, so the upgrade method needs to be called manually.
53
+ * We do this by checking first if an old option exists, but a new one doesn't.
54
+ */
55
+ if ( get_option( 'woocommerce_facebookcommerce_settings' ) && ! get_option( 'wc_facebook_page_access_token' ) ) {
56
+
57
+ $this->upgrade( '1.9.15' );
58
+ }
59
+ }
60
+
61
+
62
+ /**
63
+ * Upgrades to version 1.10.0.
64
+ *
65
+ * @since 1.10.0
66
+ */
67
+ protected function upgrade_to_1_10_0() {
68
+
69
+ // preserve legacy values
70
+ $values = get_option( 'woocommerce_facebookcommerce_settings', [] );
71
+ update_option( 'woocommerce_facebookcommerce_legacy_settings', $values );
72
+
73
+ // migrate options from woocommerce_facebookcommerce_settings
74
+ $options = [
75
+ 'fb_api_key' => \WC_Facebookcommerce_Integration::OPTION_PAGE_ACCESS_TOKEN,
76
+ 'fb_product_catalog_id' => \WC_Facebookcommerce_Integration::OPTION_PRODUCT_CATALOG_ID,
77
+ 'fb_external_merchant_settings_id' => \WC_Facebookcommerce_Integration::OPTION_EXTERNAL_MERCHANT_SETTINGS_ID,
78
+ 'fb_feed_id' => \WC_Facebookcommerce_Integration::OPTION_FEED_ID,
79
+ 'facebook_jssdk_version' => \WC_Facebookcommerce_Integration::OPTION_JS_SDK_VERSION,
80
+ 'pixel_install_time' => \WC_Facebookcommerce_Integration::OPTION_PIXEL_INSTALL_TIME,
81
+ ];
82
+
83
+ foreach ( $options as $old_index => $new_option_name ) {
84
+
85
+ if ( isset( $values[ $old_index ] ) ) {
86
+
87
+ $new_value = $values[ $old_index ];
88
+
89
+ if ( 'pixel_install_time' === $old_index ) {
90
+
91
+ // convert to UTC timestamp
92
+ $pixel_install_time = \DateTime::createFromFormat( 'Y-m-d G:i:s', $new_value, new \DateTimeZone( wc_timezone_string() ) );
93
+ $new_value = $pixel_install_time->getTimestamp();
94
+ }
95
+
96
+ update_option( $new_option_name, $new_value );
97
+ }
98
+ }
99
+
100
+ $new_settings = [];
101
+
102
+ // migrate settings from woocommerce_facebookcommerce_settings
103
+ $settings = [
104
+ 'fb_page_id' => \WC_Facebookcommerce_Integration::SETTING_FACEBOOK_PAGE_ID,
105
+ 'fb_pixel_id' => \WC_Facebookcommerce_Integration::SETTING_FACEBOOK_PIXEL_ID,
106
+ 'fb_pixel_use_pii' => \WC_Facebookcommerce_Integration::SETTING_ENABLE_ADVANCED_MATCHING,
107
+ 'is_messenger_chat_plugin_enabled' => \WC_Facebookcommerce_Integration::SETTING_ENABLE_MESSENGER,
108
+ 'msger_chat_customization_locale' => \WC_Facebookcommerce_Integration::SETTING_MESSENGER_LOCALE,
109
+ 'msger_chat_customization_greeting_text_code' => \WC_Facebookcommerce_Integration::SETTING_MESSENGER_GREETING,
110
+ 'msger_chat_customization_theme_color_code' => \WC_Facebookcommerce_Integration::SETTING_MESSENGER_COLOR_HEX,
111
+ ];
112
+
113
+ foreach ( $settings as $old_index => $new_index ) {
114
+
115
+ if ( isset( $values[ $old_index ] ) ) {
116
+ $new_settings[ $new_index ] = $values[ $old_index ];
117
+ }
118
+ }
119
+
120
+ // migrate settings from standalone options
121
+ $product_sync_enabled = empty( get_option( 'fb_disable_sync_on_dev_environment', 0 ) );
122
+
123
+ $new_settings[ \WC_Facebookcommerce_Integration::SETTING_ENABLE_PRODUCT_SYNC ] = $product_sync_enabled ? 'yes' : 'no';
124
+ $new_settings[ \WC_Facebookcommerce_Integration::SETTING_PRODUCT_DESCRIPTION_MODE ] = ! empty( get_option( 'fb_sync_short_description', 0 ) ) ? \WC_Facebookcommerce_Integration::PRODUCT_DESCRIPTION_MODE_SHORT : \WC_Facebookcommerce_Integration::PRODUCT_DESCRIPTION_MODE_STANDARD;
125
+
126
+ $autosync_time = get_option( 'woocommerce_fb_autosync_time' );
127
+
128
+ if ( ! empty( $autosync_time ) ) {
129
+
130
+ $parsed_time = strtotime( $autosync_time );
131
+
132
+ if ( false !== $parsed_time ) {
133
+
134
+ $midnight = ( new \DateTime() )->setTimestamp( $parsed_time )->setTime( 0, 0, 0 );
135
+
136
+ $resync_offset = $parsed_time - $midnight->getTimestamp();
137
+
138
+ $new_settings[ \WC_Facebookcommerce_Integration::SETTING_SCHEDULED_RESYNC_OFFSET ] = $resync_offset;
139
+ }
140
+ }
141
+
142
+ update_option( 'woocommerce_' . \WC_Facebookcommerce::INTEGRATION_ID . '_settings', $new_settings );
143
+
144
+ // schedule the next product resync action
145
+ if ( isset( $resync_offset ) && $product_sync_enabled ) {
146
+
147
+ $integration = $this->get_plugin()->get_integration();
148
+
149
+ if ( ! $integration->is_resync_scheduled() ) {
150
+ $integration->schedule_resync( $resync_offset );
151
+ }
152
+ }
153
+ }
154
+
155
+
156
+ }
includes/Products.php ADDED
@@ -0,0 +1,254 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
4
+ *
5
+ * This source code is licensed under the license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ *
8
+ * @package FacebookCommerce
9
+ */
10
+
11
+ namespace SkyVerge\WooCommerce\Facebook;
12
+
13
+ defined( 'ABSPATH' ) or exit;
14
+
15
+ /**
16
+ * Products handler.
17
+ *
18
+ * @since 1.10.0
19
+ */
20
+ class Products {
21
+
22
+
23
+ /** @var string the meta key used to flag whether a product should be synced in Facebook */
24
+ const SYNC_ENABLED_META_KEY = '_wc_facebook_sync_enabled';
25
+
26
+ // TODO probably we'll want to run some upgrade routine or somehow move meta keys to follow the same patter e.g. _wc_facebook_visibility {FN 2020-01-17}
27
+ /** @var string the meta key used to flag whether a product should be visible in Facebook */
28
+ const VISIBILITY_META_KEY = 'fb_visibility';
29
+
30
+ /** @var string the meta key used to the source of the product in Facebook */
31
+ const PRODUCT_IMAGE_SOURCE_META_KEY = '_wc_facebook_product_image_source';
32
+
33
+ /** @var string product image source option to use the product image of simple products or the variation image of variations in Facebook */
34
+ const PRODUCT_IMAGE_SOURCE_PRODUCT = 'product';
35
+
36
+ /** @var string product image source option to use the parent product image in Facebook */
37
+ const PRODUCT_IMAGE_SOURCE_PARENT_PRODUCT = 'parent_product';
38
+
39
+ /** @var string product image source option to use the parent product image in Facebook */
40
+ const PRODUCT_IMAGE_SOURCE_CUSTOM = 'custom';
41
+
42
+
43
+ /** @var array memoized array of sync enabled status for products */
44
+ private static $products_sync_enabled = [];
45
+
46
+ /** @var array memoized array of visibility status for products */
47
+ private static $products_visibility = [];
48
+
49
+
50
+ /**
51
+ * Sets the sync handling for products to enabled or disabled.
52
+ *
53
+ * @since 1.10.0
54
+ *
55
+ * @param \WC_Product[] $products array of product objects
56
+ * @param bool $enabled whether sync should be enabled for $products
57
+ */
58
+ private static function set_sync_for_products( array $products, $enabled ) {
59
+
60
+ self::$products_sync_enabled = [];
61
+
62
+ $enabled = wc_bool_to_string( $enabled );
63
+
64
+ foreach ( $products as $product ) {
65
+
66
+ if ( $product instanceof \WC_Product ) {
67
+
68
+ if ( $product->is_type( 'variable' ) ) {
69
+
70
+ foreach ( $product->get_children() as $variation ) {
71
+
72
+ $product_variation = wc_get_product( $variation );
73
+
74
+ if ( $product_variation instanceof \WC_Product ) {
75
+
76
+ $product_variation->update_meta_data( self::SYNC_ENABLED_META_KEY, $enabled );
77
+ $product_variation->save_meta_data();
78
+ }
79
+ }
80
+
81
+ } else {
82
+
83
+ $product->update_meta_data( self::SYNC_ENABLED_META_KEY, $enabled );
84
+ $product->save_meta_data();
85
+ }
86
+ }
87
+ }
88
+ }
89
+
90
+
91
+ /**
92
+ * Enables sync for given products.
93
+ *
94
+ * @since 1.10.0
95
+ *
96
+ * @param \WC_Product[] $products an array of product objects
97
+ */
98
+ public static function enable_sync_for_products( array $products ) {
99
+
100
+ self::set_sync_for_products( $products, true );
101
+ }
102
+
103
+
104
+ /**
105
+ * Disables sync for given products.
106
+ *
107
+ * @since 1.10.0
108
+ *
109
+ * @param \WC_Product[] $products an array of product objects
110
+ */
111
+ public static function disable_sync_for_products( array $products ) {
112
+
113
+ self::set_sync_for_products( $products, false );
114
+ }
115
+
116
+
117
+ /**
118
+ * Determines whether the given product should be synced.
119
+ *
120
+ * If a product is enabled for sync, but belongs to an excluded term, it will return as excluded from sync:
121
+ * @see Products::is_sync_enabled_for_product()
122
+ * @see Products::is_sync_excluded_for_product_terms()
123
+ *
124
+ * @since 1.10.0
125
+ *
126
+ * @param \WC_Product $product
127
+ * @return bool
128
+ */
129
+ public static function product_should_be_synced( \WC_Product $product ) {
130
+
131
+ // define the product to check terms on
132
+ $terms_product = $product->is_type( 'variation' ) ? wc_get_product( $product->get_parent_id() ) : $product;
133
+
134
+ return self::is_sync_enabled_for_product( $product ) && $terms_product && ! self::is_sync_excluded_for_product_terms( $terms_product );
135
+ }
136
+
137
+
138
+ /**
139
+ * Determines whether a product is enabled to be synced in Facebook.
140
+ *
141
+ * If the product is not explicitly set to disable sync, it'll be considered enabled.
142
+ * This applies to products that may not have the meta value set.
143
+ *
144
+ * @since 1.10.0
145
+ *
146
+ * @param \WC_Product $product product object
147
+ * @return bool
148
+ */
149
+ public static function is_sync_enabled_for_product( \WC_Product $product ) {
150
+
151
+ if ( ! isset( self::$products_sync_enabled[ $product->get_id() ] ) ) {
152
+
153
+ if ( $product->is_type( 'variable' ) ) {
154
+
155
+ // assume variable products are not synced until a synced child is found
156
+ $enabled = false;
157
+
158
+ foreach ( $product->get_children() as $child_id ) {
159
+
160
+ $child_product = wc_get_product( $child_id );
161
+
162
+ if ( $child_product && self::is_sync_enabled_for_product( $child_product ) ) {
163
+
164
+ $enabled = true;
165
+ break;
166
+ }
167
+ }
168
+
169
+ } else {
170
+
171
+ $enabled = 'no' !== $product->get_meta( self::SYNC_ENABLED_META_KEY );
172
+ }
173
+
174
+ self::$products_sync_enabled[ $product->get_id() ] = $enabled;
175
+ }
176
+
177
+ return self::$products_sync_enabled[ $product->get_id() ];
178
+ }
179
+
180
+
181
+ /**
182
+ * Determines whether the product's terms would make it excluded to be synced from Facebook.
183
+ *
184
+ * @since 1.10.0
185
+ *
186
+ * @param \WC_Product $product product object
187
+ * @return bool if true, product should be excluded from sync, if false, product can be included in sync (unless manually excluded by individual product meta)
188
+ */
189
+ public static function is_sync_excluded_for_product_terms( \WC_Product $product ) {
190
+
191
+ if ( $integration = facebook_for_woocommerce()->get_integration() ) {
192
+ $excluded_categories = $integration->get_excluded_product_category_ids();
193
+ $excluded_tags = $integration->get_excluded_product_tag_ids();
194
+ } else {
195
+ $excluded_categories = $excluded_tags = [];
196
+ }
197
+
198
+ $categories = $product->get_category_ids();
199
+ $tags = $product->get_tag_ids();
200
+
201
+ // returns true if no terms on the product, or no terms excluded, or if the product does not contain any of the excluded terms
202
+ $matches = ( ! $categories || ! $excluded_categories || ! array_intersect( $categories, $excluded_categories ) )
203
+ && ( ! $tags || ! $excluded_tags || ! array_intersect( $tags, $excluded_tags ) );
204
+
205
+ return ! $matches;
206
+ }
207
+
208
+
209
+ /**
210
+ * Sets a product's visibility in the Facebook shop.
211
+ *
212
+ * @since 1.10.0
213
+ *
214
+ * @param \WC_Product $product product object
215
+ * @param bool $visibility true for 'published' or false for 'staging'
216
+ * @return bool success
217
+ */
218
+ public static function set_product_visibility( \WC_Product $product, $visibility ) {
219
+
220
+ unset( self::$products_visibility[ $product->get_id() ] );
221
+
222
+ if ( ! is_bool( $visibility ) ) {
223
+ return false;
224
+ }
225
+
226
+ $product->update_meta_data( self::VISIBILITY_META_KEY, wc_bool_to_string( $visibility ) );
227
+ $product->save_meta_data();
228
+
229
+ self::$products_visibility[ $product->get_id() ] = $visibility;
230
+
231
+ return true;
232
+ }
233
+
234
+
235
+ /**
236
+ * Checks whether a product should be visible on Facebook.
237
+ *
238
+ * @since 1.10.0
239
+ *
240
+ * @param \WC_Product $product
241
+ * @return bool
242
+ */
243
+ public static function is_product_visible( \WC_Product $product ) {
244
+
245
+ // accounts for a legacy bool value, current should be (string) 'yes' or (string) 'no'
246
+ if ( ! isset( self::$products_visibility[ $product->get_id() ] ) ) {
247
+ self::$products_visibility[ $product->get_id() ] = wc_string_to_bool( $product->get_meta( self::VISIBILITY_META_KEY ) );
248
+ }
249
+
250
+ return self::$products_visibility[ $product->get_id() ];
251
+ }
252
+
253
+
254
+ }
includes/fbgraph.php CHANGED
@@ -126,6 +126,37 @@ if ( ! class_exists( 'WC_Facebookcommerce_Graph_API' ) ) :
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 );
@@ -196,9 +227,12 @@ if ( ! class_exists( 'WC_Facebookcommerce_Graph_API' ) ) :
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,
126
  return json_decode( $response_body )->name;
127
  }
128
 
129
+
130
+ /**
131
+ * Gets a Facebook Page URL.
132
+ *
133
+ * Endpoint: https://graph.facebook.com/vX.X/{page-id}/?fields=link
134
+ *
135
+ * @param string|int $page_id page identifier
136
+ * @param string $api_key API key
137
+ * @return string URL
138
+ */
139
+ public function get_page_url( $page_id, $api_key = '' ) {
140
+
141
+ $api_key = $api_key ?: $this->api_key;
142
+ $request = $this->build_url( $page_id, '/?fields=link' );
143
+ $response = $this->_get( $request, $api_key );
144
+ $page_url = '';
145
+
146
+ if ( is_wp_error( $response ) ) {
147
+
148
+ \WC_Facebookcommerce_Utils::log( $response->get_error_message() );
149
+
150
+ } elseif ( 200 === (int) $response['response']['code'] ) {
151
+
152
+ $response_body = wp_remote_retrieve_body( $response );
153
+ $page_url = json_decode( $response_body )->link;
154
+ }
155
+
156
+ return $page_url;
157
+ }
158
+
159
+
160
  public function validate_product_catalog( $product_catalog_id ) {
161
  $url = $this->build_url( $product_catalog_id );
162
  $response = self::_get( $url );
227
  $facebook_feed_id,
228
  '/uploads?access_token=' . $this->api_key
229
  );
230
+
231
+ $data = [
232
+ 'file' => new CurlFile( $path_to_feed_file, 'text/csv' ),
233
+ 'update_only' => true,
234
+ ];
235
+
236
  $curl = curl_init();
237
  curl_setopt_array(
238
  $curl,
includes/fbinfobanner.php CHANGED
@@ -201,17 +201,16 @@ if ( ! class_exists( 'WC_Facebookcommerce_Info_Banner' ) ) :
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
  /**
@@ -234,11 +233,17 @@ if ( ! class_exists( 'WC_Facebookcommerce_Info_Banner' ) ) :
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
  }
@@ -259,6 +264,8 @@ if ( ! class_exists( 'WC_Facebookcommerce_Info_Banner' ) ) :
259
  wp_safe_redirect( admin_url( 'admin.php?page=wc-settings&tab=integration' ) );
260
  }
261
  }
 
 
262
  }
263
 
264
  endif;
201
  }
202
 
203
  $dismiss_url = $this->dismiss_url();
204
+
205
+ echo '<div class="updated fade">';
206
+ echo '<div id="fbinfobanner">';
207
+ echo '<div><img src="' . esc_url( $tip_img_url ) . '" class="iconDetails"></div>';
208
+ echo '<p class = "tipTitle"><strong>' . esc_html( $tip_title ) . "</strong></p>\n";
209
+ echo '<p class = "tipContent">' . esc_html( $tip_body ) . '</p>';
210
+ echo '<p class = "tipButton">';
211
+ echo '<a href="' . esc_url( $tip_action_link ) . '" class = "btn" onclick="fb_woo_infobanner_post_click(); return true;" title="' . esc_attr__( 'Click and redirect.', 'facebook-for-woocommerce' ) . '"> ' . esc_html( $tip_action ) . '</a>';
212
+ echo '<a href="' . esc_url( $dismiss_url ) . '" class = "btn dismiss grey" onclick="fb_woo_infobanner_post_xout(); return true;" title="' . esc_attr__( 'Dismiss this notice.', 'facebook-for-woocommerce' ) . '"> ' . esc_html__( 'Dismiss', 'facebook-for-woocommerce' ) . '</a>';
213
+ echo '</p></div></div>';
 
214
  }
215
 
216
  /**
233
  return wp_nonce_url( $url, 'woocommerce_info_banner_dismiss' );
234
  }
235
 
236
+
237
  /**
238
+ * Handles the action that dismisses the info banner.
239
+ *
240
+ * The banner will remain dismissed for at least one day and until a new info tip can be retrieved.
241
+ *
242
+ * @see \WC_Facebookcommerce_Integration::FB_TIP_QUERY
243
+ * @see \WC_Facebookcommerce_Graph_API::get_tip_info()
244
  */
245
  public function dismiss_banner() {
246
+
247
  if ( ! isset( $_GET['wc-notice'] ) ) {
248
  return;
249
  }
264
  wp_safe_redirect( admin_url( 'admin.php?page=wc-settings&tab=integration' ) );
265
  }
266
  }
267
+
268
+
269
  }
270
 
271
  endif;
includes/fbproduct.php CHANGED
@@ -8,6 +8,8 @@
8
  * @package FacebookCommerce
9
  */
10
 
 
 
11
  if ( ! defined( 'ABSPATH' ) ) {
12
  exit;
13
  }
@@ -43,26 +45,29 @@ if ( ! class_exists( 'WC_Facebook_Product' ) ) :
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
 
@@ -161,49 +166,50 @@ if ( ! class_exists( 'WC_Facebook_Product' ) ) :
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;
@@ -292,9 +298,9 @@ if ( ! class_exists( 'WC_Facebook_Product' ) ) :
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
  }
@@ -543,7 +549,7 @@ if ( ! class_exists( 'WC_Facebook_Product' ) ) :
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,
@@ -553,8 +559,8 @@ if ( ! class_exists( 'WC_Facebook_Product' ) ) :
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.
@@ -568,14 +574,14 @@ if ( ! class_exists( 'WC_Facebook_Product' ) ) :
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 ) {
8
  * @package FacebookCommerce
9
  */
10
 
11
+ use SkyVerge\WooCommerce\Facebook\Products;
12
+
13
  if ( ! defined( 'ABSPATH' ) ) {
14
  exit;
15
  }
45
  'variation' => 1,
46
  );
47
 
48
+ public function __construct( $wpid, $parent_product = null ) {
49
+
50
  $this->id = $wpid;
51
  $this->fb_description = '';
 
52
  $this->woo_product = wc_get_product( $wpid );
53
  $this->gallery_urls = null;
54
  $this->fb_use_parent_image = null;
55
  $this->fb_price = 0;
56
  $this->main_description = '';
57
+ $this->sync_short_description = \WC_Facebookcommerce_Integration::PRODUCT_DESCRIPTION_MODE_SHORT === facebook_for_woocommerce()->get_integration()->get_product_description_mode();
58
+
59
+ if ( $meta = get_post_meta( $wpid, self::FB_VISIBILITY, true ) ) {
60
+ $this->fb_visibility = wc_string_to_bool( $meta );
61
+ } else {
62
+ $this->fb_visibility = ''; // for products that haven't synced yet
63
+ }
64
 
65
  // Variable products should use some data from the parent_product
66
  // For performance reasons, that data shouldn't be regenerated every time.
67
  if ( $parent_product ) {
68
  $this->gallery_urls = $parent_product->get_gallery_urls();
69
  $this->fb_use_parent_image = $parent_product->get_use_parent_image();
70
+ $this->main_description = $parent_product->get_fb_description();
 
 
71
  }
72
  }
73
 
166
  return $this->fb_price;
167
  }
168
 
169
+
170
+ /**
171
+ * Gets a list of image URLs to use for this product in Facebook sync.
172
+ *
173
+ * @return array
174
+ */
175
  public function get_all_image_urls() {
 
 
 
 
 
176
 
177
+ $image_urls = [];
 
 
 
178
 
179
+ $product_image_url = wp_get_attachment_url( $this->woo_product->get_image_id() );
180
+ $parent_product_image_url = null;
181
+ $custom_image_url = $this->woo_product->get_meta( self::FB_PRODUCT_IMAGE );
182
+
183
+ if ( $this->woo_product->is_type( 'variation' ) ) {
184
+
185
+ if ( $parent_product = wc_get_product( $this->woo_product->get_parent_id() ) ) {
186
+
187
+ $parent_product_image_url = wp_get_attachment_url( $parent_product->get_image_id() );
 
 
 
188
  }
189
  }
190
 
191
+ switch ( $this->woo_product->get_meta( Products::PRODUCT_IMAGE_SOURCE_META_KEY ) ) {
 
 
192
 
193
+ case Products::PRODUCT_IMAGE_SOURCE_CUSTOM:
194
+ $image_urls = [ $custom_image_url, $product_image_url, $parent_product_image_url ];
195
+ break;
196
+
197
+ case Products::PRODUCT_IMAGE_SOURCE_PARENT_PRODUCT:
198
+ $image_urls = [ $parent_product_image_url, $product_image_url ];
199
+ break;
200
+
201
+ case Products::PRODUCT_IMAGE_SOURCE_PRODUCT:
202
+ default:
203
+ $image_urls = [ $product_image_url, $parent_product_image_url ];
204
+ break;
205
  }
206
 
207
+ $image_urls = array_merge( $image_urls, $this->get_gallery_urls() );
208
+ $image_urls = array_filter( array_unique( $image_urls ) );
209
+
210
+ if ( empty( $image_urls ) ) {
211
+ // TODO: replace or remove this placeholder - placeholdit.imgix.net is no longer available {WV 2020-01-21}
212
+ $image_urls[] = sprintf( 'https://placeholdit.imgix.net/~text?txtsize=33&name=%s&w=530&h=530', rawurlencode( strip_tags( $this->woo_product->get_title() ) ) );
213
  }
214
 
215
  return $image_urls;
298
  }
299
 
300
  if ( WC_Facebookcommerce_Utils::is_variation_type( $this->woo_product->get_type() ) ) {
301
+
302
+ $description = WC_Facebookcommerce_Utils::clean_string( $this->woo_product->get_description() );
303
+
304
  if ( $description ) {
305
  return $description;
306
  }
549
  ),
550
  'description' => $this->get_fb_description(),
551
  'image_url' => $image_urls[0], // The array can't be empty.
552
+ 'additional_image_urls' => array_slice( $image_urls, 1 ),
553
  'url' => $product_url,
554
  'category' => $categories['categories'],
555
  'brand' => $brand,
559
  'availability' => $this->is_in_stock() ? 'in stock' :
560
  'out of stock',
561
  'visibility' => ! $this->is_hidden()
562
+ ? \WC_Facebookcommerce_Integration::FB_SHOP_PRODUCT_VISIBLE
563
+ : \WC_Facebookcommerce_Integration::FB_SHOP_PRODUCT_HIDDEN,
564
  );
565
 
566
  // Only use checkout URLs if they exist.
574
  // default language. WPML >= 3.2 Supported.
575
  if ( defined( 'ICL_LANGUAGE_CODE' ) ) {
576
  if ( class_exists( 'WC_Facebook_WPML_Injector' ) && WC_Facebook_WPML_Injector::should_hide( $id ) ) {
577
+ $product_data['visibility'] = \WC_Facebookcommerce_Integration::FB_SHOP_PRODUCT_HIDDEN;
578
  }
579
  }
580
 
581
  // Exclude variations that are "virtual" products from export to Facebook &&
582
  // No Visibility Option for Variations
583
  if ( true === $this->get_virtual() ) {
584
+ $product_data['visibility'] = \WC_Facebookcommerce_Integration::FB_SHOP_PRODUCT_HIDDEN;
585
  }
586
 
587
  if ( ! $prepare_for_product_feed ) {
includes/fbproductfeed.php CHANGED
@@ -127,25 +127,37 @@ if ( ! class_exists( 'WC_Facebook_Product_Feed' ) ) :
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
127
  }
128
 
129
  public function write_product_feed_file( $wp_ids ) {
130
+
131
  try {
132
+
133
  $feed_file =
134
  fopen(
135
  dirname( __FILE__ ) . DIRECTORY_SEPARATOR .
136
  ( self::FACEBOOK_CATALOG_FEED_FILENAME ),
137
  'w'
138
  );
139
+
140
  fwrite( $feed_file, $this->get_product_feed_header_row() );
141
 
142
  $product_group_attribute_variants = array();
143
+
144
  foreach ( $wp_ids as $wp_id ) {
145
+
146
  $woo_product = new WC_Facebook_Product( $wp_id );
147
+
148
  if ( $woo_product->is_hidden() ) {
149
  continue;
150
  }
151
+
152
+ if ( get_option( 'woocommerce_hide_out_of_stock_items' ) === 'yes' && ! $woo_product->is_in_stock() ) {
153
  continue;
154
  }
155
+
156
+ // skip if not enabled for sync
157
+ if ( $woo_product->woo_product instanceof \WC_Product && ! \SkyVerge\WooCommerce\Facebook\Products::product_should_be_synced( $woo_product->woo_product ) ) {
158
+ continue;
159
+ }
160
+
161
  $product_data_as_feed_row = $this->prepare_product_for_feed(
162
  $woo_product,
163
  $product_group_attribute_variants
includes/fbutils.php CHANGED
@@ -20,8 +20,9 @@ if ( ! class_exists( 'WC_Facebookcommerce_Utils' ) ) :
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';
20
  class WC_Facebookcommerce_Utils {
21
 
22
  const FB_RETAILER_ID_PREFIX = 'wc_post_id_';
23
+ const PLUGIN_VERSION = \WC_Facebookcommerce::VERSION; // TODO: remove this in v2.0.0 {CW 2020-02-06}
24
 
25
+ // TODO: this constant is no longer used and can probably be removed {WV 2020-01-21}
26
  const FB_VARIANT_IMAGE = 'fb_image';
27
  const FB_VARIANT_SIZE = 'size';
28
  const FB_VARIANT_COLOR = 'color';
includes/fbwpml.php CHANGED
@@ -68,74 +68,92 @@ if ( ! class_exists( 'WC_Facebook_WPML_Injector' ) ) :
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;
68
  }
69
  }
70
 
71
+
72
+ /**
73
+ * Prints the content for Facebook Visibility section.
74
+ *
75
+ * The section is shown at the bottom of the WPML > Languages settings page.
76
+ */
77
  public function wpml_support() {
78
+ /** @var object $sitepress */
79
  global $sitepress;
80
+
81
+ // there is no nonce to check here and the value of $_GET['page] is being compared against a known and safe string
82
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended
83
+ if ( isset( $_GET['page'] ) && false !== strpos( esc_url_raw( wp_unslash( $_GET['page'] ) ), 'languages.php' ) ) {
84
+
85
+ /** @var array $active_languages */
86
  $active_languages = $sitepress->get_active_languages();
87
  $settings = get_option( self::OPTION );
88
 
89
  // Default setting is only show default lang.
90
  if ( ! $settings ) {
91
+
92
+ $settings = array_fill_keys(
93
  array_keys( $active_languages ),
94
  FB_WPML_Language_Status::HIDDEN
95
  );
96
+
97
+ if ( self::$default_lang ) {
98
+ $settings[ self::$default_lang ] = FB_WPML_Language_Status::VISIBLE;
99
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
  }
101
+
102
  ?>
103
+ <div id="lang-sec-fb" class="wpml-section wpml-section-languages">
104
+ <div class="wpml-section-header">
105
+ <h3><?php esc_html_e( 'Facebook Visibility', 'facebook-for-woocommerce' ); ?></h3>
106
+ </div>
107
+ <div class="wpml-section-content">
108
+ <?php esc_html_e( 'WooCommerce Products with languages that are selected here will be visible to customers who see your Facebook Shop.', 'facebook-for-woocommerce' ); ?>
109
+
110
+ <div class="wpml-section-content-inner">
111
+ <form id="icl_fb_woo" name="icl_fb_woo" action="">
112
+
113
+ <?php foreach ( $settings as $language => $set ) : ?>
114
+
115
+ <p>
116
+ <label>
117
+ <input type="checkbox" id="icl_fb_woo_chk" name="<?php echo esc_attr( $language ); ?>" <?php checked( $set, FB_WPML_Language_Status::VISIBLE ); ?>>
118
+ <?php echo isset( $active_languages[ $language ]['native_name'] ) ? esc_html( $active_languages[ $language ]['native_name'] ) : esc_html( $language ); ?>
119
+ </label>
120
+ </p>
121
+
122
+ <?php endforeach; ?>
123
+
124
+ <p class="buttons-wrap">
125
+ <span class="icl_ajx_response_fb" id="icl_ajx_response_fb" hidden="true">
126
+ <?php printf(
127
+ /* translators: Placeholders %1$s - opening link HTML tag, %2$s - closing link HTML tag */
128
+ esc_html__( 'Saved. You should now %1$sRe-Sync%2$s your products with Facebook.', 'facebook-for-woocommerce' ),
129
+ sprintf( '<a href="%s">', esc_url( add_query_arg( 'fb_force_resync', 'true', WOOCOMMERCE_FACEBOOK_PLUGIN_SETTINGS_URL ) ) ),
130
+ '</a>'
131
+ ); ?>
132
+ </span>
133
+ <input
134
+ class="button button-primary"
135
+ name="save"
136
+ value="<?php esc_attr_e( 'Save', 'sitepress' ); ?>"
137
+ type="submit"
138
+ />
139
+ </p>
140
+ </form>
141
+ <script type="text/javascript">
142
+ addLoadEvent( function() {
143
+ jQuery( '#icl_fb_woo' ).submit( iclSaveForm );
144
+ jQuery( '#icl_fb_woo' ).submit( function() {
145
+ jQuery( '#icl_ajx_response_fb' ).show();
146
+ } );
147
+ } );
148
+ </script>
149
+ </div>
150
+ </div>
151
+ </div>
152
  <?php
153
  }
154
  }
155
+
156
+
157
  }
158
 
159
  endif;
includes/test/facebook-integration-test.php CHANGED
@@ -38,7 +38,8 @@ if ( ! class_exists( 'WC_Facebook_Integration_Test' ) ) :
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
 
@@ -257,7 +258,7 @@ if ( ! class_exists( 'WC_Facebook_Integration_Test' ) ) :
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
  );
@@ -297,7 +298,7 @@ if ( ! class_exists( 'WC_Facebook_Integration_Test' ) ) :
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
  );
38
  /** Class Instance */
39
  private static $instance;
40
 
41
+ /** @var WC_Facebookcommerce_Integration full integration object */
42
+ public static $commerce = null;
43
  public static $fbgraph = null;
44
  public static $test_mode = false;
45
 
258
 
259
  function check_fbid_api( $fbid_type, $fb_retailer_id ) {
260
  $product_fbid_result = self::$fbgraph->get_facebook_id(
261
+ self::$commerce->get_product_catalog_id(),
262
  $fb_retailer_id,
263
  true
264
  );
298
 
299
  function check_product_info( $retailer_id, $has_variant, $data ) {
300
  $prod_info_result = self::$fbgraph->check_product_info(
301
+ self::$commerce->get_product_catalog_id(),
302
  $retailer_id,
303
  $has_variant
304
  );
readme.txt CHANGED
@@ -1,66 +1,76 @@
1
- === Facebook for WooCommerce ===
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
11
-
12
- Get the Official Facebook for WooCommerce plugin for two powerful ways to help grow your business, including an ads extension and shops tab for your page.
13
-
14
- == Description ==
15
-
16
- This is the official Facebook for WooCommerce plugin that connects your WooCommerce website to Facebook. With this plugin, you can install the Facebook pixel, upload your online store catalog, and create a shop on your Facebook page, enabling you to easily run dynamic ads.
17
-
18
- Marketing on Facebook helps your business build lasting relationships with people, find new customers, and increase sales for your online store. With this Facebook ad extension, reaching the people who matter most to your business is simple. This extension will track the results of your advertising across devices. It will also help you:
19
-
20
- * Maximize your campaign performance. By setting up the Facebook pixel and building your audience, you will optimize your ads for people likely to buy your products, and reach people with relevant ads on Facebook after they’ve visited your website.
21
- * Find more customers. Connecting your product catalog automatically creates carousel ads that showcase the products you sell and attract more shoppers to your website.
22
- * Generate sales among your website visitors. When you set up the Facebook pixel and connect your product catalog, you can use dynamic ads to reach shoppers when they’re on Facebook with ads for the products they viewed on your website. This will be included in a future release of Facebook for WooCommerce.
23
-
24
- == Installation ==
25
-
26
- Visit the Facebook Help Center [here](https://www.facebook.com/business/help/900699293402826).
27
-
28
- == Support ==
29
-
30
- If you believe you have found a security vulnerability on Facebook, we encourage you to let us know right away. We investigate all legitimate reports and do our best to quickly fix the problem. Before reporting, please review [this page](https://www.facebook.com/whitehat), which includes our responsible disclosure policy and reward guideline. You can submit bugs [here](https://github.com/facebookincubator/facebook-for-woocommerce/issues) or contact advertising support [here](https://www.facebook.com/business/help/900699293402826).
31
-
32
- When opening a bug on GitHub, please give us as many details as possible.
33
-
34
- * Symptoms of your problem
35
- * Screenshot, if possible
36
- * Your Facebook page URL
37
- * Your website URL
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
53
- * Add more contributors to the plugin
54
-
55
- = 1.9.12 - 2019-05-2 =
56
- * Remove dead code which causes exception (Issue 975)
57
-
58
- = 1.9.11 - 2019-02-26 =
59
- * changing contributor to facebook from facebook4woocommerce, so that
60
- woo plugin will be shown under
61
- https://profiles.wordpress.org/facebook/#content-plugins
62
- * adding changelog in readme.txt so that notifications will be sent for
63
- updates and changelog will be shown under
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
 
 
 
 
 
 
 
 
 
 
1
+ === Facebook for WooCommerce ===
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.10.0
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
11
+
12
+ Get the Official Facebook for WooCommerce plugin for two powerful ways to help grow your business, including an ads extension and shops tab for your page.
13
+
14
+ == Description ==
15
+
16
+ This is the official Facebook for WooCommerce plugin that connects your WooCommerce website to Facebook. With this plugin, you can install the Facebook pixel, upload your online store catalog, and create a shop on your Facebook page, enabling you to easily run dynamic ads.
17
+
18
+ Marketing on Facebook helps your business build lasting relationships with people, find new customers, and increase sales for your online store. With this Facebook ad extension, reaching the people who matter most to your business is simple. This extension will track the results of your advertising across devices. It will also help you:
19
+
20
+ * Maximize your campaign performance. By setting up the Facebook pixel and building your audience, you will optimize your ads for people likely to buy your products, and reach people with relevant ads on Facebook after they’ve visited your website.
21
+ * Find more customers. Connecting your product catalog automatically creates carousel ads that showcase the products you sell and attract more shoppers to your website.
22
+ * Generate sales among your website visitors. When you set up the Facebook pixel and connect your product catalog, you can use dynamic ads to reach shoppers when they’re on Facebook with ads for the products they viewed on your website. This will be included in a future release of Facebook for WooCommerce.
23
+
24
+ == Installation ==
25
+
26
+ Visit the Facebook Help Center [here](https://www.facebook.com/business/help/900699293402826).
27
+
28
+ == Support ==
29
+
30
+ If you believe you have found a security vulnerability on Facebook, we encourage you to let us know right away. We investigate all legitimate reports and do our best to quickly fix the problem. Before reporting, please review [this page](https://www.facebook.com/whitehat), which includes our responsible disclosure policy and reward guideline. You can submit bugs [here](https://github.com/facebookincubator/facebook-for-woocommerce/issues) or contact advertising support [here](https://www.facebook.com/business/help/900699293402826).
31
+
32
+ When opening a bug on GitHub, please give us as many details as possible.
33
+
34
+ * Symptoms of your problem
35
+ * Screenshot, if possible
36
+ * Your Facebook page URL
37
+ * Your website URL
38
+ * Current version of Facebook-for-WooCommerce, WooCommerce, Wordpress, PHP
39
+
40
+ == Changelog ==
41
+
42
+ = 2020.03.03 - version 1.10.0 =
43
+ * Feature - Exclude specific products, variations, product categories, and product tags from syncing to Facebook
44
+ * Feature - Add Facebook product settings like price and description to variations
45
+ * Feature - Revamped settings screen with on-site control over pixel, product sync, and Messenger behavior
46
+ * Tweak - Use Action Scheduler for the daily forced re-sync, if enabled
47
+ * Fix - Improve pixel tracking accuracy for add-to-cart events
48
+ * Misc. - Add the SkyVerge plugin framework as the plugin base
49
+ * Misc. - Require WooCommerce 3.5 and above
50
+
51
+ = 1.9.15 - 2019-06-27 =
52
+ * CSRF handling for Ajax calls like ajax_woo_infobanner_post_click, ajax_woo_infobanner_post_xout, ajax_fb_toggle_visibility
53
+ * use phpcs to adhere to WP coding standards
54
+ * Minor UI changes on the iFrame
55
+
56
+ = 1.9.14 - 2019-06-20 =
57
+ * Revisit CSRF security issue
58
+ * Remove rest controller which is not used
59
+ * Tested installation in wordpress 5.2.2, WooCommerce 3.64, php 5.6/7.3 with browser Chrome v75/Safari v12.1/Firefox v67.
60
+
61
+ = 1.9.13 - 2019-06-18 =
62
+ * Fix security issue
63
+ * Add more contributors to the plugin
64
+
65
+ = 1.9.12 - 2019-05-2 =
66
+ * Remove dead code which causes exception (Issue 975)
67
+
68
+ = 1.9.11 - 2019-02-26 =
69
+ * changing contributor to facebook from facebook4woocommerce, so that
70
+ woo plugin will be shown under
71
+ https://profiles.wordpress.org/facebook/#content-plugins
72
+ * adding changelog in readme.txt so that notifications will be sent for
73
+ updates and changelog will be shown under
74
+ https://wordpress.org/plugins/facebook-for-woocommerce/#developers
75
+ * removing debug flags notice under facebook-for-woocommerce.php so that
76
+ developers will be able to debug with debug logs
vendor/skyverge/wc-plugin-framework/license.txt ADDED
@@ -0,0 +1,694 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ WooCommerce Plugin Framework
2
+
3
+ Copyright 2013-2020, SkyVerge, Inc.
4
+
5
+ This program is free software; you can redistribute it and/or modify
6
+ it under the terms of the GNU General Public License as published by
7
+ the Free Software Foundation; either version 3 of the License, or
8
+ (at your option) any later version.
9
+
10
+ This program is distributed in the hope that it will be useful,
11
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ GNU General Public License for more details.
14
+
15
+ You should have received a copy of the GNU General Public License
16
+ along with this program; if not, write to the Free Software
17
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18
+
19
+ =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
20
+
21
+ GNU GENERAL PUBLIC LICENSE
22
+ Version 3, 29 June 2007
23
+
24
+ Copyright © 2007 Free Software Foundation, Inc. <http://fsf.org/>
25
+ Everyone is permitted to copy and distribute verbatim copies
26
+ of this license document, but changing it is not allowed.
27
+
28
+ Preamble
29
+
30
+ The GNU General Public License is a free, copyleft license for
31
+ software and other kinds of works.
32
+
33
+ The licenses for most software and other practical works are designed
34
+ to take away your freedom to share and change the works. By contrast,
35
+ the GNU General Public License is intended to guarantee your freedom to
36
+ share and change all versions of a program--to make sure it remains free
37
+ software for all its users. We, the Free Software Foundation, use the
38
+ GNU General Public License for most of our software; it applies also to
39
+ any other work released this way by its authors. You can apply it to
40
+ your programs, too.
41
+
42
+ When we speak of free software, we are referring to freedom, not
43
+ price. Our General Public Licenses are designed to make sure that you
44
+ have the freedom to distribute copies of free software (and charge for
45
+ them if you wish), that you receive source code or can get it if you
46
+ want it, that you can change the software or use pieces of it in new
47
+ free programs, and that you know you can do these things.
48
+
49
+ To protect your rights, we need to prevent others from denying you
50
+ these rights or asking you to surrender the rights. Therefore, you have
51
+ certain responsibilities if you distribute copies of the software, or if
52
+ you modify it: responsibilities to respect the freedom of others.
53
+
54
+ For example, if you distribute copies of such a program, whether
55
+ gratis or for a fee, you must pass on to the recipients the same
56
+ freedoms that you received. You must make sure that they, too, receive
57
+ or can get the source code. And you must show them these terms so they
58
+ know their rights.
59
+
60
+ Developers that use the GNU GPL protect your rights with two steps:
61
+ (1) assert copyright on the software, and (2) offer you this License
62
+ giving you legal permission to copy, distribute and/or modify it.
63
+
64
+ For the developers' and authors' protection, the GPL clearly explains
65
+ that there is no warranty for this free software. For both users' and
66
+ authors' sake, the GPL requires that modified versions be marked as
67
+ changed, so that their problems will not be attributed erroneously to
68
+ authors of previous versions.
69
+
70
+ Some devices are designed to deny users access to install or run
71
+ modified versions of the software inside them, although the manufacturer
72
+ can do so. This is fundamentally incompatible with the aim of
73
+ protecting users' freedom to change the software. The systematic
74
+ pattern of such abuse occurs in the area of products for individuals to
75
+ use, which is precisely where it is most unacceptable. Therefore, we
76
+ have designed this version of the GPL to prohibit the practice for those
77
+ products. If such problems arise substantially in other domains, we
78
+ stand ready to extend this provision to those domains in future versions
79
+ of the GPL, as needed to protect the freedom of users.
80
+
81
+ Finally, every program is threatened constantly by software patents.
82
+ States should not allow patents to restrict development and use of
83
+ software on general-purpose computers, but in those that do, we wish to
84
+ avoid the special danger that patents applied to a free program could
85
+ make it effectively proprietary. To prevent this, the GPL assures that
86
+ patents cannot be used to render the program non-free.
87
+
88
+ The precise terms and conditions for copying, distribution and
89
+ modification follow.
90
+
91
+ TERMS AND CONDITIONS
92
+
93
+ 0. Definitions.
94
+
95
+ "This License" refers to version 3 of the GNU General Public License.
96
+
97
+ "Copyright" also means copyright-like laws that apply to other kinds of
98
+ works, such as semiconductor masks.
99
+
100
+ "The Program" refers to any copyrightable work licensed under this
101
+ License. Each licensee is addressed as "you". "Licensees" and
102
+ "recipients" may be individuals or organizations.
103
+
104
+ To "modify" a work means to copy from or adapt all or part of the work
105
+ in a fashion requiring copyright permission, other than the making of an
106
+ exact copy. The resulting work is called a "modified version" of the
107
+ earlier work or a work "based on" the earlier work.
108
+
109
+ A "covered work" means either the unmodified Program or a work based
110
+ on the Program.
111
+
112
+ To "propagate" a work means to do anything with it that, without
113
+ permission, would make you directly or secondarily liable for
114
+ infringement under applicable copyright law, except executing it on a
115
+ computer or modifying a private copy. Propagation includes copying,
116
+ distribution (with or without modification), making available to the
117
+ public, and in some countries other activities as well.
118
+
119
+ To "convey" a work means any kind of propagation that enables other
120
+ parties to make or receive copies. Mere interaction with a user through
121
+ a computer network, with no transfer of a copy, is not conveying.
122
+
123
+ An interactive user interface displays "Appropriate Legal Notices"
124
+ to the extent that it includes a convenient and prominently visible
125
+ feature that (1) displays an appropriate copyright notice, and (2)
126
+ tells the user that there is no warranty for the work (except to the
127
+ extent that warranties are provided), that licensees may convey the
128
+ work under this License, and how to view a copy of this License. If
129
+ the interface presents a list of user commands or options, such as a
130
+ menu, a prominent item in the list meets this criterion.
131
+
132
+ 1. Source Code.
133
+
134
+ The "source code" for a work means the preferred form of the work
135
+ for making modifications to it. "Object code" means any non-source
136
+ form of a work.
137
+
138
+ A "Standard Interface" means an interface that either is an official
139
+ standard defined by a recognized standards body, or, in the case of
140
+ interfaces specified for a particular programming language, one that
141
+ is widely used among developers working in that language.
142
+
143
+ The "System Libraries" of an executable work include anything, other
144
+ than the work as a whole, that (a) is included in the normal form of
145
+ packaging a Major Component, but which is not part of that Major
146
+ Component, and (b) serves only to enable use of the work with that
147
+ Major Component, or to implement a Standard Interface for which an
148
+ implementation is available to the public in source code form. A
149
+ "Major Component", in this context, means a major essential component
150
+ (kernel, window system, and so on) of the specific operating system
151
+ (if any) on which the executable work runs, or a compiler used to
152
+ produce the work, or an object code interpreter used to run it.
153
+
154
+ The "Corresponding Source" for a work in object code form means all
155
+ the source code needed to generate, install, and (for an executable
156
+ work) run the object code and to modify the work, including scripts to
157
+ control those activities. However, it does not include the work's
158
+ System Libraries, or general-purpose tools or generally available free
159
+ programs which are used unmodified in performing those activities but
160
+ which are not part of the work. For example, Corresponding Source
161
+ includes interface definition files associated with source files for
162
+ the work, and the source code for shared libraries and dynamically
163
+ linked subprograms that the work is specifically designed to require,
164
+ such as by intimate data communication or control flow between those
165
+ subprograms and other parts of the work.
166
+
167
+ The Corresponding Source need not include anything that users
168
+ can regenerate automatically from other parts of the Corresponding
169
+ Source.
170
+
171
+ The Corresponding Source for a work in source code form is that
172
+ same work.
173
+
174
+ 2. Basic Permissions.
175
+
176
+ All rights granted under this License are granted for the term of
177
+ copyright on the Program, and are irrevocable provided the stated
178
+ conditions are met. This License explicitly affirms your unlimited
179
+ permission to run the unmodified Program. The output from running a
180
+ covered work is covered by this License only if the output, given its
181
+ content, constitutes a covered work. This License acknowledges your
182
+ rights of fair use or other equivalent, as provided by copyright law.
183
+
184
+ You may make, run and propagate covered works that you do not
185
+ convey, without conditions so long as your license otherwise remains
186
+ in force. You may convey covered works to others for the sole purpose
187
+ of having them make modifications exclusively for you, or provide you
188
+ with facilities for running those works, provided that you comply with
189
+ the terms of this License in conveying all material for which you do
190
+ not control copyright. Those thus making or running the covered works
191
+ for you must do so exclusively on your behalf, under your direction
192
+ and control, on terms that prohibit them from making any copies of
193
+ your copyrighted material outside their relationship with you.
194
+
195
+ Conveying under any other circumstances is permitted solely under
196
+ the conditions stated below. Sublicensing is not allowed; section 10
197
+ makes it unnecessary.
198
+
199
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
200
+
201
+ No covered work shall be deemed part of an effective technological
202
+ measure under any applicable law fulfilling obligations under article
203
+ 11 of the WIPO copyright treaty adopted on 20 December 1996, or
204
+ similar laws prohibiting or restricting circumvention of such
205
+ measures.
206
+
207
+ When you convey a covered work, you waive any legal power to forbid
208
+ circumvention of technological measures to the extent such circumvention
209
+ is effected by exercising rights under this License with respect to
210
+ the covered work, and you disclaim any intention to limit operation or
211
+ modification of the work as a means of enforcing, against the work's
212
+ users, your or third parties' legal rights to forbid circumvention of
213
+ technological measures.
214
+
215
+ 4. Conveying Verbatim Copies.
216
+
217
+ You may convey verbatim copies of the Program's source code as you
218
+ receive it, in any medium, provided that you conspicuously and
219
+ appropriately publish on each copy an appropriate copyright notice;
220
+ keep intact all notices stating that this License and any
221
+ non-permissive terms added in accord with section 7 apply to the code;
222
+ keep intact all notices of the absence of any warranty; and give all
223
+ recipients a copy of this License along with the Program.
224
+
225
+ You may charge any price or no price for each copy that you convey,
226
+ and you may offer support or warranty protection for a fee.
227
+
228
+ 5. Conveying Modified Source Versions.
229
+
230
+ You may convey a work based on the Program, or the modifications to
231
+ produce it from the Program, in the form of source code under the
232
+ terms of section 4, provided that you also meet all of these conditions:
233
+
234
+ a) The work must carry prominent notices stating that you modified
235
+ it, and giving a relevant date.
236
+
237
+ b) The work must carry prominent notices stating that it is
238
+ released under this License and any conditions added under section
239
+ 7. This requirement modifies the requirement in section 4 to
240
+ "keep intact all notices".
241
+
242
+ c) You must license the entire work, as a whole, under this
243
+ License to anyone who comes into possession of a copy. This
244
+ License will therefore apply, along with any applicable section 7
245
+ additional terms, to the whole of the work, and all its parts,
246
+ regardless of how they are packaged. This License gives no
247
+ permission to license the work in any other way, but it does not
248
+ invalidate such permission if you have separately received it.
249
+
250
+ d) If the work has interactive user interfaces, each must display
251
+ Appropriate Legal Notices; however, if the Program has interactive
252
+ interfaces that do not display Appropriate Legal Notices, your
253
+ work need not make them do so.
254
+
255
+ A compilation of a covered work with other separate and independent
256
+ works, which are not by their nature extensions of the covered work,
257
+ and which are not combined with it such as to form a larger program,
258
+ in or on a volume of a storage or distribution medium, is called an
259
+ "aggregate" if the compilation and its resulting copyright are not
260
+ used to limit the access or legal rights of the compilation's users
261
+ beyond what the individual works permit. Inclusion of a covered work
262
+ in an aggregate does not cause this License to apply to the other
263
+ parts of the aggregate.
264
+
265
+ 6. Conveying Non-Source Forms.
266
+
267
+ You may convey a covered work in object code form under the terms
268
+ of sections 4 and 5, provided that you also convey the
269
+ machine-readable Corresponding Source under the terms of this License,
270
+ in one of these ways:
271
+
272
+ a) Convey the object code in, or embodied in, a physical product
273
+ (including a physical distribution medium), accompanied by the
274
+ Corresponding Source fixed on a durable physical medium
275
+ customarily used for software interchange.
276
+
277
+ b) Convey the object code in, or embodied in, a physical product
278
+ (including a physical distribution medium), accompanied by a
279
+ written offer, valid for at least three years and valid for as
280
+ long as you offer spare parts or customer support for that product
281
+ model, to give anyone who possesses the object code either (1) a
282
+ copy of the Corresponding Source for all the software in the
283
+ product that is covered by this License, on a durable physical
284
+ medium customarily used for software interchange, for a price no
285
+ more than your reasonable cost of physically performing this
286
+ conveying of source, or (2) access to copy the
287
+ Corresponding Source from a network server at no charge.
288
+
289
+ c) Convey individual copies of the object code with a copy of the
290
+ written offer to provide the Corresponding Source. This
291
+ alternative is allowed only occasionally and noncommercially, and
292
+ only if you received the object code with such an offer, in accord
293
+ with subsection 6b.
294
+
295
+ d) Convey the object code by offering access from a designated
296
+ place (gratis or for a charge), and offer equivalent access to the
297
+ Corresponding Source in the same way through the same place at no
298
+ further charge. You need not require recipients to copy the
299
+ Corresponding Source along with the object code. If the place to
300
+ copy the object code is a network server, the Corresponding Source
301
+ may be on a different server (operated by you or a third party)
302
+ that supports equivalent copying facilities, provided you maintain
303
+ clear directions next to the object code saying where to find the
304
+ Corresponding Source. Regardless of what server hosts the
305
+ Corresponding Source, you remain obligated to ensure that it is
306
+ available for as long as needed to satisfy these requirements.
307
+
308
+ e) Convey the object code using peer-to-peer transmission, provided
309
+ you inform other peers where the object code and Corresponding
310
+ Source of the work are being offered to the general public at no
311
+ charge under subsection 6d.
312
+
313
+ A separable portion of the object code, whose source code is excluded
314
+ from the Corresponding Source as a System Library, need not be
315
+ included in conveying the object code work.
316
+
317
+ A "User Product" is either (1) a "consumer product", which means any
318
+ tangible personal property which is normally used for personal, family,
319
+ or household purposes, or (2) anything designed or sold for incorporation
320
+ into a dwelling. In determining whether a product is a consumer product,
321
+ doubtful cases shall be resolved in favor of coverage. For a particular
322
+ product received by a particular user, "normally used" refers to a
323
+ typical or common use of that class of product, regardless of the status
324
+ of the particular user or of the way in which the particular user
325
+ actually uses, or expects or is expected to use, the product. A product
326
+ is a consumer product regardless of whether the product has substantial
327
+ commercial, industrial or non-consumer uses, unless such uses represent
328
+ the only significant mode of use of the product.
329
+
330
+ "Installation Information" for a User Product means any methods,
331
+ procedures, authorization keys, or other information required to install
332
+ and execute modified versions of a covered work in that User Product from
333
+ a modified version of its Corresponding Source. The information must
334
+ suffice to ensure that the continued functioning of the modified object
335
+ code is in no case prevented or interfered with solely because
336
+ modification has been made.
337
+
338
+ If you convey an object code work under this section in, or with, or
339
+ specifically for use in, a User Product, and the conveying occurs as
340
+ part of a transaction in which the right of possession and use of the
341
+ User Product is transferred to the recipient in perpetuity or for a
342
+ fixed term (regardless of how the transaction is characterized), the
343
+ Corresponding Source conveyed under this section must be accompanied
344
+ by the Installation Information. But this requirement does not apply
345
+ if neither you nor any third party retains the ability to install
346
+ modified object code on the User Product (for example, the work has
347
+ been installed in ROM).
348
+
349
+ The requirement to provide Installation Information does not include a
350
+ requirement to continue to provide support service, warranty, or updates
351
+ for a work that has been modified or installed by the recipient, or for
352
+ the User Product in which it has been modified or installed. Access to a
353
+ network may be denied when the modification itself materially and
354
+ adversely affects the operation of the network or violates the rules and
355
+ protocols for communication across the network.
356
+
357
+ Corresponding Source conveyed, and Installation Information provided,
358
+ in accord with this section must be in a format that is publicly
359
+ documented (and with an implementation available to the public in
360
+ source code form), and must require no special password or key for
361
+ unpacking, reading or copying.
362
+
363
+ 7. Additional Terms.
364
+
365
+ "Additional permissions" are terms that supplement the terms of this
366
+ License by making exceptions from one or more of its conditions.
367
+ Additional permissions that are applicable to the entire Program shall
368
+ be treated as though they were included in this License, to the extent
369
+ that they are valid under applicable law. If additional permissions
370
+ apply only to part of the Program, that part may be used separately
371
+ under those permissions, but the entire Program remains governed by
372
+ this License without regard to the additional permissions.
373
+
374
+ When you convey a copy of a covered work, you may at your option
375
+ remove any additional permissions from that copy, or from any part of
376
+ it. (Additional permissions may be written to require their own
377
+ removal in certain cases when you modify the work.) You may place
378
+ additional permissions on material, added by you to a covered work,
379
+ for which you have or can give appropriate copyright permission.
380
+
381
+ Notwithstanding any other provision of this License, for material you
382
+ add to a covered work, you may (if authorized by the copyright holders of
383
+ that material) supplement the terms of this License with terms:
384
+
385
+ a) Disclaiming warranty or limiting liability differently from the
386
+ terms of sections 15 and 16 of this License; or
387
+
388
+ b) Requiring preservation of specified reasonable legal notices or
389
+ author attributions in that material or in the Appropriate Legal
390
+ Notices displayed by works containing it; or
391
+
392
+ c) Prohibiting misrepresentation of the origin of that material, or
393
+ requiring that modified versions of such material be marked in
394
+ reasonable ways as different from the original version; or
395
+
396
+ d) Limiting the use for publicity purposes of names of licensors or
397
+ authors of the material; or
398
+
399
+ e) Declining to grant rights under trademark law for use of some
400
+ trade names, trademarks, or service marks; or
401
+
402
+ f) Requiring indemnification of licensors and authors of that
403
+ material by anyone who conveys the material (or modified versions of
404
+ it) with contractual assumptions of liability to the recipient, for
405
+ any liability that these contractual assumptions directly impose on
406
+ those licensors and authors.
407
+
408
+ All other non-permissive additional terms are considered "further
409
+ restrictions" within the meaning of section 10. If the Program as you
410
+ received it, or any part of it, contains a notice stating that it is
411
+ governed by this License along with a term that is a further
412
+ restriction, you may remove that term. If a license document contains
413
+ a further restriction but permits relicensing or conveying under this
414
+ License, you may add to a covered work material governed by the terms
415
+ of that license document, provided that the further restriction does
416
+ not survive such relicensing or conveying.
417
+
418
+ If you add terms to a covered work in accord with this section, you
419
+ must place, in the relevant source files, a statement of the
420
+ additional terms that apply to those files, or a notice indicating
421
+ where to find the applicable terms.
422
+
423
+ Additional terms, permissive or non-permissive, may be stated in the
424
+ form of a separately written license, or stated as exceptions;
425
+ the above requirements apply either way.
426
+
427
+ 8. Termination.
428
+
429
+ You may not propagate or modify a covered work except as expressly
430
+ provided under this License. Any attempt otherwise to propagate or
431
+ modify it is void, and will automatically terminate your rights under
432
+ this License (including any patent licenses granted under the third
433
+ paragraph of section 11).
434
+
435
+ However, if you cease all violation of this License, then your
436
+ license from a particular copyright holder is reinstated (a)
437
+ provisionally, unless and until the copyright holder explicitly and
438
+ finally terminates your license, and (b) permanently, if the copyright
439
+ holder fails to notify you of the violation by some reasonable means
440
+ prior to 60 days after the cessation.
441
+
442
+ Moreover, your license from a particular copyright holder is
443
+ reinstated permanently if the copyright holder notifies you of the
444
+ violation by some reasonable means, this is the first time you have
445
+ received notice of violation of this License (for any work) from that
446
+ copyright holder, and you cure the violation prior to 30 days after
447
+ your receipt of the notice.
448
+
449
+ Termination of your rights under this section does not terminate the
450
+ licenses of parties who have received copies or rights from you under
451
+ this License. If your rights have been terminated and not permanently
452
+ reinstated, you do not qualify to receive new licenses for the same
453
+ material under section 10.
454
+
455
+ 9. Acceptance Not Required for Having Copies.
456
+
457
+ You are not required to accept this License in order to receive or
458
+ run a copy of the Program. Ancillary propagation of a covered work
459
+ occurring solely as a consequence of using peer-to-peer transmission
460
+ to receive a copy likewise does not require acceptance. However,
461
+ nothing other than this License grants you permission to propagate or
462
+ modify any covered work. These actions infringe copyright if you do
463
+ not accept this License. Therefore, by modifying or propagating a
464
+ covered work, you indicate your acceptance of this License to do so.
465
+
466
+ 10. Automatic Licensing of Downstream Recipients.
467
+
468
+ Each time you convey a covered work, the recipient automatically
469
+ receives a license from the original licensors, to run, modify and
470
+ propagate that work, subject to this License. You are not responsible
471
+ for enforcing compliance by third parties with this License.
472
+
473
+ An "entity transaction" is a transaction transferring control of an
474
+ organization, or substantially all assets of one, or subdividing an
475
+ organization, or merging organizations. If propagation of a covered
476
+ work results from an entity transaction, each party to that
477
+ transaction who receives a copy of the work also receives whatever
478
+ licenses to the work the party's predecessor in interest had or could
479
+ give under the previous paragraph, plus a right to possession of the
480
+ Corresponding Source of the work from the predecessor in interest, if
481
+ the predecessor has it or can get it with reasonable efforts.
482
+
483
+ You may not impose any further restrictions on the exercise of the
484
+ rights granted or affirmed under this License. For example, you may
485
+ not impose a license fee, royalty, or other charge for exercise of
486
+ rights granted under this License, and you may not initiate litigation
487
+ (including a cross-claim or counterclaim in a lawsuit) alleging that
488
+ any patent claim is infringed by making, using, selling, offering for
489
+ sale, or importing the Program or any portion of it.
490
+
491
+ 11. Patents.
492
+
493
+ A "contributor" is a copyright holder who authorizes use under this
494
+ License of the Program or a work on which the Program is based. The
495
+ work thus licensed is called the contributor's "contributor version".
496
+
497
+ A contributor's "essential patent claims" are all patent claims
498
+ owned or controlled by the contributor, whether already acquired or
499
+ hereafter acquired, that would be infringed by some manner, permitted
500
+ by this License, of making, using, or selling its contributor version,
501
+ but do not include claims that would be infringed only as a
502
+ consequence of further modification of the contributor version. For
503
+ purposes of this definition, "control" includes the right to grant
504
+ patent sublicenses in a manner consistent with the requirements of
505
+ this License.
506
+
507
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
508
+ patent license under the contributor's essential patent claims, to
509
+ make, use, sell, offer for sale, import and otherwise run, modify and
510
+ propagate the contents of its contributor version.
511
+
512
+ In the following three paragraphs, a "patent license" is any express
513
+ agreement or commitment, however denominated, not to enforce a patent
514
+ (such as an express permission to practice a patent or covenant not to
515
+ sue for patent infringement). To "grant" such a patent license to a
516
+ party means to make such an agreement or commitment not to enforce a
517
+ patent against the party.
518
+
519
+ If you convey a covered work, knowingly relying on a patent license,
520
+ and the Corresponding Source of the work is not available for anyone
521
+ to copy, free of charge and under the terms of this License, through a
522
+ publicly available network server or other readily accessible means,
523
+ then you must either (1) cause the Corresponding Source to be so
524
+ available, or (2) arrange to deprive yourself of the benefit of the
525
+ patent license for this particular work, or (3) arrange, in a manner
526
+ consistent with the requirements of this License, to extend the patent
527
+ license to downstream recipients. "Knowingly relying" means you have
528
+ actual knowledge that, but for the patent license, your conveying the
529
+ covered work in a country, or your recipient's use of the covered work
530
+ in a country, would infringe one or more identifiable patents in that
531
+ country that you have reason to believe are valid.
532
+
533
+ If, pursuant to or in connection with a single transaction or
534
+ arrangement, you convey, or propagate by procuring conveyance of, a
535
+ covered work, and grant a patent license to some of the parties
536
+ receiving the covered work authorizing them to use, propagate, modify
537
+ or convey a specific copy of the covered work, then the patent license
538
+ you grant is automatically extended to all recipients of the covered
539
+ work and works based on it.
540
+
541
+ A patent license is "discriminatory" if it does not include within
542
+ the scope of its coverage, prohibits the exercise of, or is
543
+ conditioned on the non-exercise of one or more of the rights that are
544
+ specifically granted under this License. You may not convey a covered
545
+ work if you are a party to an arrangement with a third party that is
546
+ in the business of distributing software, under which you make payment
547
+ to the third party based on the extent of your activity of conveying
548
+ the work, and under which the third party grants, to any of the
549
+ parties who would receive the covered work from you, a discriminatory
550
+ patent license (a) in connection with copies of the covered work
551
+ conveyed by you (or copies made from those copies), or (b) primarily
552
+ for and in connection with specific products or compilations that
553
+ contain the covered work, unless you entered into that arrangement,
554
+ or that patent license was granted, prior to 28 March 2007.
555
+
556
+ Nothing in this License shall be construed as excluding or limiting
557
+ any implied license or other defenses to infringement that may
558
+ otherwise be available to you under applicable patent law.
559
+
560
+ 12. No Surrender of Others' Freedom.
561
+
562
+ If conditions are imposed on you (whether by court order, agreement or
563
+ otherwise) that contradict the conditions of this License, they do not
564
+ excuse you from the conditions of this License. If you cannot convey a
565
+ covered work so as to satisfy simultaneously your obligations under this
566
+ License and any other pertinent obligations, then as a consequence you may
567
+ not convey it at all. For example, if you agree to terms that obligate you
568
+ to collect a royalty for further conveying from those to whom you convey
569
+ the Program, the only way you could satisfy both those terms and this
570
+ License would be to refrain entirely from conveying the Program.
571
+
572
+ 13. Use with the GNU Affero General Public License.
573
+
574
+ Notwithstanding any other provision of this License, you have
575
+ permission to link or combine any covered work with a work licensed
576
+ under version 3 of the GNU Affero General Public License into a single
577
+ combined work, and to convey the resulting work. The terms of this
578
+ License will continue to apply to the part which is the covered work,
579
+ but the special requirements of the GNU Affero General Public License,
580
+ section 13, concerning interaction through a network will apply to the
581
+ combination as such.
582
+
583
+ 14. Revised Versions of this License.
584
+
585
+ The Free Software Foundation may publish revised and/or new versions of
586
+ the GNU General Public License from time to time. Such new versions will
587
+ be similar in spirit to the present version, but may differ in detail to
588
+ address new problems or concerns.
589
+
590
+ Each version is given a distinguishing version number. If the
591
+ Program specifies that a certain numbered version of the GNU General
592
+ Public License "or any later version" applies to it, you have the
593
+ option of following the terms and conditions either of that numbered
594
+ version or of any later version published by the Free Software
595
+ Foundation. If the Program does not specify a version number of the
596
+ GNU General Public License, you may choose any version ever published
597
+ by the Free Software Foundation.
598
+
599
+ If the Program specifies that a proxy can decide which future
600
+ versions of the GNU General Public License can be used, that proxy's
601
+ public statement of acceptance of a version permanently authorizes you
602
+ to choose that version for the Program.
603
+
604
+ Later license versions may give you additional or different
605
+ permissions. However, no additional obligations are imposed on any
606
+ author or copyright holder as a result of your choosing to follow a
607
+ later version.
608
+
609
+ 15. Disclaimer of Warranty.
610
+
611
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
612
+ APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
613
+ HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
614
+ OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
615
+ THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
616
+ PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
617
+ IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
618
+ ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
619
+
620
+ 16. Limitation of Liability.
621
+
622
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
623
+ WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
624
+ THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
625
+ GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
626
+ USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
627
+ DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
628
+ PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
629
+ EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
630
+ SUCH DAMAGES.
631
+
632
+ 17. Interpretation of Sections 15 and 16.
633
+
634
+ If the disclaimer of warranty and limitation of liability provided
635
+ above cannot be given local legal effect according to their terms,
636
+ reviewing courts shall apply local law that most closely approximates
637
+ an absolute waiver of all civil liability in connection with the
638
+ Program, unless a warranty or assumption of liability accompanies a
639
+ copy of the Program in return for a fee.
640
+
641
+ END OF TERMS AND CONDITIONS
642
+
643
+ How to Apply These Terms to Your New Programs
644
+
645
+ If you develop a new program, and you want it to be of the greatest
646
+ possible use to the public, the best way to achieve this is to make it
647
+ free software which everyone can redistribute and change under these terms.
648
+
649
+ To do so, attach the following notices to the program. It is safest
650
+ to attach them to the start of each source file to most effectively
651
+ state the exclusion of warranty; and each file should have at least
652
+ the "copyright" line and a pointer to where the full notice is found.
653
+
654
+ <one line to give the program's name and a brief idea of what it does.>
655
+ Copyright © <year> <name of author>
656
+
657
+ This program is free software: you can redistribute it and/or modify
658
+ it under the terms of the GNU General Public License as published by
659
+ the Free Software Foundation, either version 3 of the License, or
660
+ (at your option) any later version.
661
+
662
+ This program is distributed in the hope that it will be useful,
663
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
664
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
665
+ GNU General Public License for more details.
666
+
667
+ You should have received a copy of the GNU General Public License
668
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
669
+
670
+ Also add information on how to contact you by electronic and paper mail.
671
+
672
+ If the program does terminal interaction, make it output a short
673
+ notice like this when it starts in an interactive mode:
674
+
675
+ <program> Copyright © <year> <name of author>
676
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
677
+ This is free software, and you are welcome to redistribute it
678
+ under certain conditions; type `show c' for details.
679
+
680
+ The hypothetical commands `show w' and `show c' should show the appropriate
681
+ parts of the General Public License. Of course, your program's commands
682
+ might be different; for a GUI interface, you would use an "about box".
683
+
684
+ You should also get your employer (if you work as a programmer) or school,
685
+ if any, to sign a "copyright disclaimer" for the program, if necessary.
686
+ For more information on this, and how to apply and follow the GNU GPL, see
687
+ <http://www.gnu.org/licenses/>.
688
+
689
+ The GNU General Public License does not permit incorporating your program
690
+ into proprietary programs. If your program is a subroutine library, you
691
+ may consider it more useful to permit linking proprietary applications with
692
+ the library. If this is what you want to do, use the GNU Lesser General
693
+ Public License instead of this License. But first, please read
694
+ <http://www.gnu.org/philosophy/why-not-lgpl.html>.
vendor/skyverge/wc-plugin-framework/woocommerce/Addresses/Address.php ADDED
@@ -0,0 +1,292 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * WooCommerce Plugin Framework
4
+ *
5
+ * This source file is subject to the GNU General Public License v3.0
6
+ * that is bundled with this package in the file license.txt.
7
+ * It is also available through the world-wide-web at this URL:
8
+ * http://www.gnu.org/licenses/gpl-3.0.html
9
+ * If you did not receive a copy of the license and are unable to
10
+ * obtain it through the world-wide-web, please send an email
11
+ * to license@skyverge.com so we can send you a copy immediately.
12
+ *
13
+ * DISCLAIMER
14
+ *
15
+ * Do not edit or add to this file if you wish to upgrade the plugin to newer
16
+ * versions in the future. If you wish to customize the plugin for your
17
+ * needs please refer to http://www.skyverge.com
18
+ *
19
+ * @package SkyVerge/WooCommerce/Plugin/Classes
20
+ * @author SkyVerge
21
+ * @copyright Copyright (c) 2013-2020, SkyVerge, Inc.
22
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GNU General Public License v3.0
23
+ */
24
+
25
+ namespace SkyVerge\WooCommerce\PluginFramework\v5_5_4\Addresses;
26
+
27
+ defined( 'ABSPATH' ) or exit;
28
+
29
+ if ( ! class_exists( '\\SkyVerge\\WooCommerce\\PluginFramework\\v5_5_4\\Addresses\\Address' ) ) :
30
+
31
+
32
+ /**
33
+ * The base address data class.
34
+ *
35
+ * This serves as a standard address object to be passed around by plugins whenever dealing with address data.
36
+ * Eliminates the need to rely on WooCommerce's address arrays.
37
+ *
38
+ * @since 5.3.0
39
+ */
40
+ class Address {
41
+
42
+
43
+ /** @var string line 1 of the street address */
44
+ protected $line_1 = '';
45
+
46
+ /** @var string line 2 of the street address */
47
+ protected $line_2 = '';
48
+
49
+ /** @var string line 3 of the street address */
50
+ protected $line_3 = '';
51
+
52
+ /** @var string address locality (city) */
53
+ protected $locality = '';
54
+
55
+ /** @var string address region (state) */
56
+ protected $region = '';
57
+
58
+ /** @var string address country */
59
+ protected $country = '';
60
+
61
+ /** @var string address postcode */
62
+ protected $postcode = '';
63
+
64
+
65
+ /** Getter methods ************************************************************************************************/
66
+
67
+
68
+ /**
69
+ * Gets line 1 of the street address.
70
+ *
71
+ * @since 5.3.0
72
+ *
73
+ * @return string
74
+ */
75
+ public function get_line_1() {
76
+
77
+ return $this->line_1;
78
+ }
79
+
80
+
81
+ /**
82
+ * Gets line 2 of the street address.
83
+ *
84
+ * @since 5.3.0
85
+ *
86
+ * @return string
87
+ */
88
+ public function get_line_2() {
89
+
90
+ return $this->line_2;
91
+ }
92
+
93
+
94
+ /**
95
+ * Gets line 3 of the street address.
96
+ *
97
+ * @since 5.3.0
98
+ *
99
+ * @return string
100
+ */
101
+ public function get_line_3() {
102
+
103
+ return $this->line_3;
104
+ }
105
+
106
+
107
+ /**
108
+ * Gets the locality or city.
109
+ *
110
+ * @since 5.3.0
111
+ *
112
+ * @return string
113
+ */
114
+ public function get_locality() {
115
+
116
+ return $this->locality;
117
+ }
118
+
119
+
120
+ /**
121
+ * Gets the region or state.
122
+ *
123
+ * @since 5.3.0
124
+ *
125
+ * @return string
126
+ */
127
+ public function get_region() {
128
+
129
+ return $this->region;
130
+ }
131
+
132
+
133
+ /**
134
+ * Gets the country.
135
+ *
136
+ * @since 5.3.0
137
+ *
138
+ * @return string
139
+ */
140
+ public function get_country() {
141
+
142
+ return $this->country;
143
+ }
144
+
145
+
146
+ /**
147
+ * Gets the postcode.
148
+ *
149
+ * @since 5.3.0
150
+ *
151
+ * @return string
152
+ */
153
+ public function get_postcode() {
154
+
155
+ return $this->postcode;
156
+ }
157
+
158
+
159
+ /**
160
+ * Gets the hash representation of this address.
161
+ *
162
+ * @see Address::get_hash_data()
163
+ *
164
+ * @since 5.3.0
165
+ *
166
+ * @return string
167
+ */
168
+ public function get_hash() {
169
+
170
+ return md5( json_encode( $this->get_hash_data() ) );
171
+ }
172
+
173
+
174
+ /**
175
+ * Gets the data used to generate a hash for the address.
176
+ *
177
+ * @since 5.3.0
178
+ *
179
+ * @return string[]
180
+ */
181
+ protected function get_hash_data() {
182
+
183
+ return [
184
+ $this->get_line_1(),
185
+ $this->get_line_2(),
186
+ $this->get_line_3(),
187
+ $this->get_locality(),
188
+ $this->get_region(),
189
+ $this->get_country(),
190
+ $this->get_postcode(),
191
+ ];
192
+ }
193
+
194
+
195
+ /** Setter methods ************************************************************************************************/
196
+
197
+
198
+ /**
199
+ * Sets line 1 of the street address.
200
+ *
201
+ * @since 5.3.0
202
+ *
203
+ * @param string $value line 1 value
204
+ */
205
+ public function set_line_1( $value ) {
206
+
207
+ $this->line_1 = $value;
208
+ }
209
+
210
+
211
+ /**
212
+ * Sets line 2 of the street address.
213
+ *
214
+ * @since 5.3.0
215
+ *
216
+ * @param string $value line 2 value
217
+ */
218
+ public function set_line_2( $value ) {
219
+
220
+ $this->line_2 = $value;
221
+ }
222
+
223
+
224
+ /**
225
+ * Gets line 3 of the street address.
226
+ *
227
+ * @since 5.3.0
228
+ *
229
+ * @param string $value line 3 value
230
+ */
231
+ public function set_line_3( $value ) {
232
+
233
+ $this->line_3 = $value;
234
+ }
235
+
236
+
237
+ /**
238
+ * Gets the locality or city.
239
+ *
240
+ * @since 5.3.0
241
+ *
242
+ * @param string $value locality value
243
+ */
244
+ public function set_locality( $value ) {
245
+
246
+ $this->locality = $value;
247
+ }
248
+
249
+
250
+ /**
251
+ * Gets the region or state.
252
+ *
253
+ * @since 5.3.0
254
+ *
255
+ * @param string $value region value
256
+ */
257
+ public function set_region( $value ) {
258
+
259
+ $this->region = $value;
260
+ }
261
+
262
+
263
+ /**
264
+ * Sets the country.
265
+ *
266
+ * @since 5.3.0
267
+ *
268
+ * @param string $value country value
269
+ */
270
+ public function set_country( $value ) {
271
+
272
+ $this->country = $value;
273
+ }
274
+
275
+
276
+ /**
277
+ * Sets the postcode.
278
+ *
279
+ * @since 5.3.0
280
+ *
281
+ * @param string $value postcode value
282
+ */
283
+ public function set_postcode( $value ) {
284
+
285
+ $this->postcode = $value;
286
+ }
287
+
288
+
289
+ }
290
+
291
+
292
+ endif;
vendor/skyverge/wc-plugin-framework/woocommerce/Addresses/Customer_Address.php ADDED
@@ -0,0 +1,150 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * WooCommerce Plugin Framework
4
+ *
5
+ * This source file is subject to the GNU General Public License v3.0
6
+ * that is bundled with this package in the file license.txt.
7
+ * It is also available through the world-wide-web at this URL:
8
+ * http://www.gnu.org/licenses/gpl-3.0.html
9
+ * If you did not receive a copy of the license and are unable to
10
+ * obtain it through the world-wide-web, please send an email
11
+ * to license@skyverge.com so we can send you a copy immediately.
12
+ *
13
+ * DISCLAIMER
14
+ *
15
+ * Do not edit or add to this file if you wish to upgrade the plugin to newer
16
+ * versions in the future. If you wish to customize the plugin for your
17
+ * needs please refer to http://www.skyverge.com
18
+ *
19
+ * @package SkyVerge/WooCommerce/Plugin/Classes
20
+ * @author SkyVerge
21
+ * @copyright Copyright (c) 2013-2020, SkyVerge, Inc.
22
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GNU General Public License v3.0
23
+ */
24
+
25
+ namespace SkyVerge\WooCommerce\PluginFramework\v5_5_4\Addresses;
26
+
27
+ defined( 'ABSPATH' ) or exit;
28
+
29
+ if ( ! class_exists( '\\SkyVerge\\WooCommerce\\PluginFramework\\v5_5_4\\Addresses\\Customer_Address' ) ) :
30
+
31
+
32
+ /**
33
+ * The customer address data class.
34
+ *
35
+ * Adds customer-specific data to a base address, as used for a billing or shipping address that can include first and last name.
36
+ *
37
+ * @since 5.3.0
38
+ */
39
+ class Customer_Address extends Address {
40
+
41
+
42
+ /** @var string customer first name */
43
+ protected $first_name = '';
44
+
45
+ /** @var string customer last name */
46
+ protected $last_name = '';
47
+
48
+
49
+ /** Getter Methods ************************************************************************************************/
50
+
51
+
52
+ /**
53
+ * Gets the customer first name.
54
+ *
55
+ * @since 5.3.0
56
+ *
57
+ * @return string
58
+ */
59
+ public function get_first_name() {
60
+
61
+ return $this->first_name;
62
+ }
63
+
64
+
65
+ /**
66
+ * Gets the customer first name.
67
+ *
68
+ * @since 5.3.0
69
+ *
70
+ * @return string
71
+ */
72
+ public function get_last_name() {
73
+
74
+ return $this->last_name;
75
+ }
76
+
77
+
78
+ /**
79
+ * Gets the data used to generate a hash for the address.
80
+ *
81
+ * @see Address::get_hash_data()
82
+ *
83
+ * @since 5.3.0
84
+ *
85
+ * @return string[]
86
+ */
87
+ protected function get_hash_data() {
88
+
89
+ // add the first & last name to data used to generate the hash
90
+ return array_merge( [
91
+ $this->get_first_name(),
92
+ $this->get_last_name(),
93
+ ], parent::get_hash_data() );
94
+ }
95
+
96
+
97
+ /** Setter Methods ************************************************************************************************/
98
+
99
+
100
+ /**
101
+ * Sets the customer first name.
102
+ *
103
+ * @since 5.3.0
104
+ *
105
+ * @param string $value first name value
106
+ */
107
+ public function set_first_name( $value ) {
108
+
109
+ $this->first_name = $value;
110
+ }
111
+
112
+
113
+ /**
114
+ * Sets the customer last name.
115
+ *
116
+ * @since 5.3.0
117
+ *
118
+ * @param string $value first name value
119
+ */
120
+ public function set_last_name( $value ) {
121
+
122
+ $this->last_name = $value;
123
+ }
124
+
125
+
126
+ /**
127
+ * Sets the full address based on a WooCommerce order.
128
+ *
129
+ * @since 5.3.0
130
+ *
131
+ * @param \WC_Order $order WooCommerce order object
132
+ * @param string $type address type, like billing or shipping
133
+ */
134
+ public function set_from_order( \WC_Order $order, $type = 'billing' ) {
135
+
136
+ $this->set_first_name( $order->{"get_{$type}_first_name"}() );
137
+ $this->set_last_name( $order->{"get_{$type}_last_name"}() );
138
+ $this->set_line_1( $order->{"get_{$type}_address_1"}() );
139
+ $this->set_line_2( $order->{"get_{$type}_address_2"}() );
140
+ $this->set_locality( $order->{"get_{$type}_city"}() );
141
+ $this->set_region( $order->{"get_{$type}_state"}() );
142
+ $this->set_country( $order->{"get_{$type}_country"}() );
143
+ $this->set_postcode( $order->{"get_{$type}_postcode"}() );
144
+ }
145
+
146
+
147
+ }
148
+
149
+
150
+ endif;
vendor/skyverge/wc-plugin-framework/woocommerce/Country_Helper.php ADDED
@@ -0,0 +1,661 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * WooCommerce Plugin Framework
4
+ *
5
+ * This source file is subject to the GNU General Public License v3.0
6
+ * that is bundled with this package in the file license.txt.
7
+ * It is also available through the world-wide-web at this URL:
8
+ * http://www.gnu.org/licenses/gpl-3.0.html
9
+ * If you did not receive a copy of the license and are unable to
10
+ * obtain it through the world-wide-web, please send an email
11
+ * to license@skyverge.com so we can send you a copy immediately.
12
+ *
13
+ * DISCLAIMER
14
+ *
15
+ * Do not edit or add to this file if you wish to upgrade the plugin to newer
16
+ * versions in the future. If you wish to customize the plugin for your
17
+ * needs please refer to http://www.skyverge.com
18
+ *
19
+ * @package SkyVerge/WooCommerce/Plugin/Classes
20
+ * @author SkyVerge
21
+ * @copyright Copyright (c) 2013-2020, SkyVerge, Inc.
22
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GNU General Public License v3.0
23
+ */
24
+
25
+ namespace SkyVerge\WooCommerce\PluginFramework\v5_5_4;
26
+
27
+ defined( 'ABSPATH' ) or exit;
28
+
29
+ if ( ! class_exists( '\\SkyVerge\\WooCommerce\\PluginFramework\\v5_5_4\\Country_Helper' ) ) :
30
+
31
+
32
+ /**
33
+ * SkyVerge Country Helper Class
34
+ *
35
+ * The purpose of this class is to centralize country-related utility
36
+ * functions that are commonly used in SkyVerge plugins
37
+ *
38
+ * @since 5.4.3
39
+ */
40
+ class Country_Helper {
41
+
42
+
43
+ /** @var array ISO 3166-alpha2 => ISO 3166-alpha3 */
44
+ static public $alpha3 = [
45
+ 'AF' => 'AFG', 'AL' => 'ALB', 'DZ' => 'DZA', 'AD' => 'AND', 'AO' => 'AGO',
46
+ 'AG' => 'ATG', 'AR' => 'ARG', 'AM' => 'ARM', 'AU' => 'AUS', 'AT' => 'AUT',
47
+ 'AZ' => 'AZE', 'BS' => 'BHS', 'BH' => 'BHR', 'BD' => 'BGD', 'BB' => 'BRB',
48
+ 'BY' => 'BLR', 'BE' => 'BEL', 'BZ' => 'BLZ', 'BJ' => 'BEN', 'BT' => 'BTN',
49
+ 'BO' => 'BOL', 'BA' => 'BIH', 'BW' => 'BWA', 'BR' => 'BRA', 'BN' => 'BRN',
50
+ 'BG' => 'BGR', 'BF' => 'BFA', 'BI' => 'BDI', 'KH' => 'KHM', 'CM' => 'CMR',
51
+ 'CA' => 'CAN', 'CV' => 'CPV', 'CF' => 'CAF', 'TD' => 'TCD', 'CL' => 'CHL',
52
+ 'CN' => 'CHN', 'CO' => 'COL', 'KM' => 'COM', 'CD' => 'COD', 'CG' => 'COG',
53
+ 'CR' => 'CRI', 'CI' => 'CIV', 'HR' => 'HRV', 'CU' => 'CUB', 'CY' => 'CYP',
54
+ 'CZ' => 'CZE', 'DK' => 'DNK', 'DJ' => 'DJI', 'DM' => 'DMA', 'DO' => 'DOM',
55
+ 'EC' => 'ECU', 'EG' => 'EGY', 'SV' => 'SLV', 'GQ' => 'GNQ', 'ER' => 'ERI',
56
+ 'EE' => 'EST', 'ET' => 'ETH', 'FJ' => 'FJI', 'FI' => 'FIN', 'FR' => 'FRA',
57
+ 'GA' => 'GAB', 'GM' => 'GMB', 'GE' => 'GEO', 'DE' => 'DEU', 'GH' => 'GHA',
58
+ 'GR' => 'GRC', 'GD' => 'GRD', 'GT' => 'GTM', 'GN' => 'GIN', 'GW' => 'GNB',
59
+ 'GY' => 'GUY', 'HT' => 'HTI', 'HN' => 'HND', 'HU' => 'HUN', 'IS' => 'ISL',
60
+ 'IN' => 'IND', 'ID' => 'IDN', 'IR' => 'IRN', 'IQ' => 'IRQ', 'IE' => 'IRL',
61
+ 'IL' => 'ISR', 'IT' => 'ITA', 'JM' => 'JAM', 'JP' => 'JPN', 'JO' => 'JOR',
62
+ 'KZ' => 'KAZ', 'KE' => 'KEN', 'KI' => 'KIR', 'KP' => 'PRK', 'KR' => 'KOR',
63
+ 'KW' => 'KWT', 'KG' => 'KGZ', 'LA' => 'LAO', 'LV' => 'LVA', 'LB' => 'LBN',
64
+ 'LS' => 'LSO', 'LR' => 'LBR', 'LY' => 'LBY', 'LI' => 'LIE', 'LT' => 'LTU',
65
+ 'LU' => 'LUX', 'MK' => 'MKD', 'MG' => 'MDG', 'MW' => 'MWI', 'MY' => 'MYS',
66
+ 'MV' => 'MDV', 'ML' => 'MLI', 'MT' => 'MLT', 'MH' => 'MHL', 'MR' => 'MRT',
67
+ 'MU' => 'MUS', 'MX' => 'MEX', 'FM' => 'FSM', 'MD' => 'MDA', 'MC' => 'MCO',
68
+ 'MN' => 'MNG', 'ME' => 'MNE', 'MA' => 'MAR', 'MZ' => 'MOZ', 'MM' => 'MMR',
69
+ 'NA' => 'NAM', 'NR' => 'NRU', 'NP' => 'NPL', 'NL' => 'NLD', 'NZ' => 'NZL',
70
+ 'NI' => 'NIC', 'NE' => 'NER', 'NG' => 'NGA', 'NO' => 'NOR', 'OM' => 'OMN',
71
+ 'PK' => 'PAK', 'PW' => 'PLW', 'PA' => 'PAN', 'PG' => 'PNG', 'PY' => 'PRY',
72
+ 'PE' => 'PER', 'PH' => 'PHL', 'PL' => 'POL', 'PT' => 'PRT', 'QA' => 'QAT',
73
+ 'RO' => 'ROU', 'RU' => 'RUS', 'RW' => 'RWA', 'KN' => 'KNA', 'LC' => 'LCA',
74
+ 'VC' => 'VCT', 'WS' => 'WSM', 'SM' => 'SMR', 'ST' => 'STP', 'SA' => 'SAU',
75
+ 'SN' => 'SEN', 'RS' => 'SRB', 'SC' => 'SYC', 'SL' => 'SLE', 'SG' => 'SGP',
76
+ 'SK' => 'SVK', 'SI' => 'SVN', 'SB' => 'SLB', 'SO' => 'SOM', 'ZA' => 'ZAF',
77
+ 'ES' => 'ESP', 'LK' => 'LKA', 'SD' => 'SDN', 'SR' => 'SUR', 'SZ' => 'SWZ',
78
+ 'SE' => 'SWE', 'CH' => 'CHE', 'SY' => 'SYR', 'TJ' => 'TJK', 'TZ' => 'TZA',
79
+ 'TH' => 'THA', 'TL' => 'TLS', 'TG' => 'TGO', 'TO' => 'TON', 'TT' => 'TTO',
80
+ 'TN' => 'TUN', 'TR' => 'TUR', 'TM' => 'TKM', 'TV' => 'TUV', 'UG' => 'UGA',
81
+ 'UA' => 'UKR', 'AE' => 'ARE', 'GB' => 'GBR', 'US' => 'USA', 'UY' => 'URY',
82
+ 'UZ' => 'UZB', 'VU' => 'VUT', 'VA' => 'VAT', 'VE' => 'VEN', 'VN' => 'VNM',
83
+ 'YE' => 'YEM', 'ZM' => 'ZMB', 'ZW' => 'ZWE', 'TW' => 'TWN', 'CX' => 'CXR',
84
+ 'CC' => 'CCK', 'HM' => 'HMD', 'NF' => 'NFK', 'NC' => 'NCL', 'PF' => 'PYF',
85
+ 'YT' => 'MYT', 'GP' => 'GLP', 'PM' => 'SPM', 'WF' => 'WLF', 'TF' => 'ATF',
86
+ 'BV' => 'BVT', 'CK' => 'COK', 'NU' => 'NIU', 'TK' => 'TKL', 'GG' => 'GGY',
87
+ 'IM' => 'IMN', 'JE' => 'JEY', 'AI' => 'AIA', 'BM' => 'BMU', 'IO' => 'IOT',
88
+ 'VG' => 'VGB', 'KY' => 'CYM', 'FK' => 'FLK', 'GI' => 'GIB', 'MS' => 'MSR',
89
+ 'PN' => 'PCN', 'SH' => 'SHN', 'GS' => 'SGS', 'TC' => 'TCA', 'MP' => 'MNP',
90
+ 'PR' => 'PRI', 'AS' => 'ASM', 'UM' => 'UMI', 'GU' => 'GUM', 'VI' => 'VIR',
91
+ 'HK' => 'HKG', 'MO' => 'MAC', 'FO' => 'FRO', 'GL' => 'GRL', 'GF' => 'GUF',
92
+ 'MQ' => 'MTQ', 'RE' => 'REU', 'AX' => 'ALA', 'AW' => 'ABW', 'AN' => 'ANT',
93
+ 'SJ' => 'SJM', 'AC' => 'ASC', 'TA' => 'TAA', 'AQ' => 'ATA', 'CW' => 'CUW',
94
+ ];
95
+
96
+ /** @var array ISO 3166-alpha2 => ISO 3166-numeric */
97
+ static public $numeric = [
98
+ 'AF' => '004', 'AX' => '248', 'AL' => '008', 'DZ' => '012', 'AS' => '016',
99
+ 'AD' => '020', 'AO' => '024', 'AI' => '660', 'AQ' => '010', 'AG' => '028',
100
+ 'AR' => '032', 'AM' => '051', 'AW' => '533', 'AU' => '036', 'AT' => '040',
101
+ 'AZ' => '031', 'BS' => '044', 'BH' => '048', 'BD' => '050', 'BB' => '052',
102
+ 'BY' => '112', 'BE' => '056', 'BZ' => '084', 'BJ' => '204', 'BM' => '060',
103
+ 'BT' => '064', 'BO' => '068', 'BQ' => '535', 'BA' => '070', 'BW' => '072',
104
+ 'BV' => '074', 'BR' => '076', 'IO' => '086', 'BN' => '096', 'BG' => '100',
105
+ 'BF' => '854', 'BI' => '108', 'KH' => '116', 'CM' => '120', 'CA' => '124',
106
+ 'CV' => '132', 'KY' => '136', 'CF' => '140', 'TD' => '148', 'CL' => '152',
107
+ 'CN' => '156', 'CX' => '162', 'CC' => '166', 'CO' => '170', 'KM' => '174',
108
+ 'CG' => '178', 'CD' => '180', 'CK' => '184', 'CR' => '188', 'CI' => '384',
109
+ 'HR' => '191', 'CU' => '192', 'CW' => '531', 'CY' => '196', 'CZ' => '203',
110
+ 'DK' => '208', 'DJ' => '262', 'DM' => '212', 'DO' => '214', 'EC' => '218',
111
+ 'EG' => '818', 'SV' => '222', 'GQ' => '226', 'ER' => '232', 'EE' => '233',
112
+ 'ET' => '231', 'FK' => '238', 'FO' => '234', 'FJ' => '242', 'FI' => '246',
113
+ 'FR' => '250', 'GF' => '254', 'PF' => '258', 'TF' => '260', 'GA' => '266',
114
+ 'GM' => '270', 'GE' => '268', 'DE' => '276', 'GH' => '288', 'GI' => '292',
115
+ 'GR' => '300', 'GL' => '304', 'GD' => '308', 'GP' => '312', 'GU' => '316',
116
+ 'GT' => '320', 'GG' => '831', 'GN' => '324', 'GW' => '624', 'GY' => '328',
117
+ 'HT' => '332', 'HM' => '334', 'VA' => '336', 'HN' => '340', 'HK' => '344',
118
+ 'HU' => '348', 'IS' => '352', 'IN' => '356', 'ID' => '360', 'IR' => '364',
119
+ 'IQ' => '368', 'IE' => '372', 'IM' => '833', 'IL' => '376', 'IT' => '380',
120
+ 'JM' => '388', 'JP' => '392', 'JE' => '832', 'JO' => '400', 'KZ' => '398',
121
+ 'KE' => '404', 'KI' => '296', 'KP' => '408', 'KR' => '410', 'KW' => '414',
122
+ 'KG' => '417', 'LA' => '418', 'LV' => '428', 'LB' => '422', 'LS' => '426',
123
+ 'LR' => '430', 'LY' => '434', 'LI' => '438', 'LT' => '440', 'LU' => '442',
124
+ 'MO' => '446', 'MK' => '807', 'MG' => '450', 'MW' => '454', 'MY' => '458',
125
+ 'MV' => '462', 'ML' => '466', 'MT' => '470', 'MH' => '584', 'MQ' => '474',
126
+ 'MR' => '478', 'MU' => '480', 'YT' => '175', 'MX' => '484', 'FM' => '583',
127
+ 'MD' => '498', 'MC' => '492', 'MN' => '496', 'ME' => '499', 'MS' => '500',
128
+ 'MA' => '504', 'MZ' => '508', 'MM' => '104', 'NA' => '516', 'NR' => '520',
129
+ 'NP' => '524', 'NL' => '528', 'NC' => '540', 'NZ' => '554', 'NI' => '558',
130
+ 'NE' => '562', 'NG' => '566', 'NU' => '570', 'NF' => '574', 'MP' => '580',
131
+ 'NO' => '578', 'OM' => '512', 'PK' => '586', 'PW' => '585', 'PS' => '275',
132
+ 'PA' => '591', 'PG' => '598', 'PY' => '600', 'PE' => '604', 'PH' => '608',
133
+ 'PN' => '612', 'PL' => '616', 'PT' => '620', 'PR' => '630', 'QA' => '634',
134
+ 'RE' => '638', 'RO' => '642', 'RU' => '643', 'RW' => '646', 'BL' => '652',
135
+ 'SH' => '654', 'KN' => '659', 'LC' => '662', 'MF' => '663', 'PM' => '666',
136
+ 'VC' => '670', 'WS' => '882', 'SM' => '674', 'ST' => '678', 'SA' => '682',
137
+ 'SN' => '686', 'RS' => '688', 'SC' => '690', 'SL' => '694', 'SG' => '702',
138
+ 'SX' => '534', 'SK' => '703', 'SI' => '705', 'SB' => '090', 'SO' => '706',
139
+ 'ZA' => '710', 'GS' => '239', 'SS' => '728', 'ES' => '724', 'LK' => '144',
140
+ 'SD' => '729', 'SR' => '740', 'SJ' => '744', 'SZ' => '748', 'SE' => '752',
141
+ 'CH' => '756', 'SY' => '760', 'TW' => '158', 'TJ' => '762', 'TZ' => '834',
142
+ 'TH' => '764', 'TL' => '626', 'TG' => '768', 'TK' => '772', 'TO' => '776',
143
+ 'TT' => '780', 'TN' => '788', 'TR' => '792', 'TM' => '795', 'TC' => '796',
144
+ 'TV' => '798', 'UG' => '800', 'UA' => '804', 'AE' => '784', 'GB' => '826',
145
+ 'US' => '840', 'UM' => '581', 'UY' => '858', 'UZ' => '860', 'VU' => '548',
146
+ 'VE' => '862', 'VN' => '704', 'VG' => '092', 'VI' => '850', 'WF' => '876',
147
+ 'EH' => '732', 'YE' => '887', 'ZM' => '894', 'ZW' => '716',
148
+ ];
149
+
150
+ /** @var array ISO 3166-alpha2 => phone calling code(s) */
151
+ static public $calling_codes = [
152
+ 'BD' => '+880',
153
+ 'BE' => '+32',
154
+ 'BF' => '+226',
155
+ 'BG' => '+359',
156
+ 'BA' => '+387',
157
+ 'BB' => '+1246',
158
+ 'WF' => '+681',
159
+ 'BL' => '+590',
160
+ 'BM' => '+1441',
161
+ 'BN' => '+673',
162
+ 'BO' => '+591',
163
+ 'BH' => '+973',
164
+ 'BI' => '+257',
165
+ 'BJ' => '+229',
166
+ 'BT' => '+975',
167
+ 'JM' => '+1876',
168
+ 'BV' => '',
169
+ 'BW' => '+267',
170
+ 'WS' => '+685',
171
+ 'BQ' => '+599',
172
+ 'BR' => '+55',
173
+ 'BS' => '+1242',
174
+ 'JE' => '+441534',
175
+ 'BY' => '+375',
176
+ 'BZ' => '+501',
177
+ 'RU' => '+7',
178
+ 'RW' => '+250',
179
+ 'RS' => '+381',
180
+ 'TL' => '+670',
181
+ 'RE' => '+262',
182
+ 'TM' => '+993',
183
+ 'TJ' => '+992',
184
+ 'RO' => '+40',
185
+ 'TK' => '+690',
186
+ 'GW' => '+245',
187
+ 'GU' => '+1671',
188
+ 'GT' => '+502',
189
+ 'GS' => '',
190
+ 'GR' => '+30',
191
+ 'GQ' => '+240',
192
+ 'GP' => '+590',
193
+ 'JP' => '+81',
194
+ 'GY' => '+592',
195
+ 'GG' => '+441481',
196
+ 'GF' => '+594',
197
+ 'GE' => '+995',
198
+ 'GD' => '+1473',
199
+ 'GB' => '+44',
200
+ 'GA' => '+241',
201
+ 'SV' => '+503',
202
+ 'GN' => '+224',
203
+ 'GM' => '+220',
204
+ 'GL' => '+299',
205
+ 'GI' => '+350',
206
+ 'GH' => '+233',
207
+ 'OM' => '+968',
208
+ 'TN' => '+216',
209
+ 'JO' => '+962',
210
+ 'HR' => '+385',
211
+ 'HT' => '+509',
212
+ 'HU' => '+36',
213
+ 'HK' => '+852',
214
+ 'HN' => '+504',
215
+ 'HM' => '',
216
+ 'VE' => '+58',
217
+ 'PR' => [
218
+ '+1787',
219
+ '+1939',
220
+ ],
221
+ 'PS' => '+970',
222
+ 'PW' => '+680',
223
+ 'PT' => '+351',
224
+ 'SJ' => '+47',
225
+ 'PY' => '+595',
226
+ 'IQ' => '+964',
227
+ 'PA' => '+507',
228
+ 'PF' => '+689',
229
+ 'PG' => '+675',
230
+ 'PE' => '+51',
231
+ 'PK' => '+92',
232
+ 'PH' => '+63',
233
+ 'PN' => '+870',
234
+ 'PL' => '+48',
235
+ 'PM' => '+508',
236
+ 'ZM' => '+260',
237
+ 'EH' => '+212',
238
+ 'EE' => '+372',
239
+ 'EG' => '+20',
240
+ 'ZA' => '+27',
241
+ 'EC' => '+593',
242
+ 'IT' => '+39',
243
+ 'VN' => '+84',
244
+ 'SB' => '+677',
245
+ 'ET' => '+251',
246
+ 'SO' => '+252',
247
+ 'ZW' => '+263',
248
+ 'SA' => '+966',
249
+ 'ES' => '+34',
250
+ 'ER' => '+291',
251
+ 'ME' => '+382',
252
+ 'MD' => '+373',
253
+ 'MG' => '+261',
254
+ 'MF' => '+590',
255
+ 'MA' => '+212',
256
+ 'MC' => '+377',
257
+ 'UZ' => '+998',
258
+ 'MM' => '+95',
259
+ 'ML' => '+223',
260
+ 'MO' => '+853',
261
+ 'MN' => '+976',
262
+ 'MH' => '+692',
263
+ 'MK' => '+389',
264
+ 'MU' => '+230',
265
+ 'MT' => '+356',
266
+ 'MW' => '+265',
267
+ 'MV' => '+960',
268
+ 'MQ' => '+596',
269
+ 'MP' => '+1670',
270
+ 'MS' => '+1664',
271
+ 'MR' => '+222',
272
+ 'IM' => '+441624',
273
+ 'UG' => '+256',
274
+ 'TZ' => '+255',
275
+ 'MY' => '+60',
276
+ 'MX' => '+52',
277
+ 'IL' => '+972',
278
+ 'FR' => '+33',
279
+ 'IO' => '+246',
280
+ 'SH' => '+290',
281
+ 'FI' => '+358',
282
+ 'FJ' => '+679',
283
+ 'FK' => '+500',
284
+ 'FM' => '+691',
285
+ 'FO' => '+298',
286
+ 'NI' => '+505',
287
+ 'NL' => '+31',
288
+ 'NO' => '+47',
289
+ 'NA' => '+264',
290
+ 'VU' => '+678',
291
+ 'NC' => '+687',
292
+ 'NE' => '+227',
293
+ 'NF' => '+672',
294
+ 'NG' => '+234',
295
+ 'NZ' => '+64',
296
+ 'NP' => '+977',
297
+ 'NR' => '+674',
298
+ 'NU' => '+683',
299
+ 'CK' => '+682',
300
+ 'XK' => '',
301
+ 'CI' => '+225',
302
+ 'CH' => '+41',
303
+ 'CO' => '+57',
304
+ 'CN' => '+86',
305
+ 'CM' => '+237',
306
+ 'CL' => '+56',
307
+ 'CC' => '+61',
308
+ 'CA' => '+1',
309
+ 'CG' => '+242',
310
+ 'CF' => '+236',
311
+ 'CD' => '+243',
312
+ 'CZ' => '+420',
313
+ 'CY' => '+357',
314
+ 'CX' => '+61',
315
+ 'CR' => '+506',
316
+ 'CW' => '+599',
317
+ 'CV' => '+238',
318
+ 'CU' => '+53',
319
+ 'SZ' => '+268',
320
+ 'SY' => '+963',
321
+ 'SX' => '+599',
322
+ 'KG' => '+996',
323
+ 'KE' => '+254',
324
+ 'SS' => '+211',
325
+ 'SR' => '+597',
326
+ 'KI' => '+686',
327
+ 'KH' => '+855',
328
+ 'KN' => '+1869',
329
+ 'KM' => '+269',
330
+ 'ST' => '+239',
331
+ 'SK' => '+421',
332
+ 'KR' => '+82',
333
+ 'SI' => '+386',
334
+ 'KP' => '+850',
335
+ 'KW' => '+965',
336
+ 'SN' => '+221',
337
+ 'SM' => '+378',
338
+ 'SL' => '+232',
339
+ 'SC' => '+248',
340
+ 'KZ' => '+7',
341
+ 'KY' => '+1345',
342
+ 'SG' => '+65',
343
+ 'SE' => '+46',
344
+ 'SD' => '+249',
345
+ 'DO' => [
346
+ '+1809',
347
+ '+1829',
348
+ '+1849',
349
+ ],
350
+ 'DM' => '+1767',
351
+ 'DJ' => '+253',
352
+ 'DK' => '+45',
353
+ 'VG' => '+1284',
354
+ 'DE' => '+49',
355
+ 'YE' => '+967',
356
+ 'DZ' => '+213',
357
+ 'US' => '+1',
358
+ 'UY' => '+598',
359
+ 'YT' => '+262',
360
+ 'UM' => '+1',
361
+ 'LB' => '+961',
362
+ 'LC' => '+1758',
363
+ 'LA' => '+856',
364
+ 'TV' => '+688',
365
+ 'TW' => '+886',
366
+ 'TT' => '+1868',
367
+ 'TR' => '+90',
368
+ 'LK' => '+94',
369
+ 'LI' => '+423',
370
+ 'LV' => '+371',
371
+ 'TO' => '+676',
372
+ 'LT' => '+370',
373
+ 'LU' => '+352',
374
+ 'LR' => '+231',
375
+ 'LS' => '+266',
376
+ 'TH' => '+66',
377
+ 'TF' => '',
378
+ 'TG' => '+228',
379
+ 'TD' => '+235',
380
+ 'TC' => '+1649',
381
+ 'LY' => '+218',
382
+ 'VA' => '+379',
383
+ 'VC' => '+1784',
384
+ 'AE' => '+971',
385
+ 'AD' => '+376',
386
+ 'AG' => '+1268',
387
+ 'AF' => '+93',
388
+ 'AI' => '+1264',
389
+ 'VI' => '+1340',
390
+ 'IS' => '+354',
391
+ 'IR' => '+98',
392
+ 'AM' => '+374',
393
+ 'AL' => '+355',
394
+ 'AO' => '+244',
395
+ 'AQ' => '',
396
+ 'AS' => '+1684',
397
+ 'AR' => '+54',
398
+ 'AU' => '+61',
399
+ 'AT' => '+43',
400
+ 'AW' => '+297',
401
+ 'IN' => '+91',
402
+ 'AX' => '+35818',
403
+ 'AZ' => '+994',
404
+ 'IE' => '+353',
405
+ 'ID' => '+62',
406
+ 'UA' => '+380',
407
+ 'QA' => '+974',
408
+ 'MZ' => '+258',
409
+ ];
410
+
411
+
412
+ /** @var array flipped calling codes */
413
+ protected static $flipped_calling_codes;
414
+
415
+
416
+ /**
417
+ * Convert a 2-character country code into its 3-character equivalent, or
418
+ * vice-versa, e.g.
419
+ *
420
+ * 1) given USA, returns US
421
+ * 2) given US, returns USA
422
+ *
423
+ * @since 5.4.3
424
+ *
425
+ * @param string $code ISO-3166-alpha-2 or ISO-3166-alpha-3 country code
426
+ * @return string country code
427
+ */
428
+ public static function convert_alpha_country_code( $code ) {
429
+
430
+ $countries = 3 === strlen( $code ) ? array_flip( self::$alpha3 ) : self::$alpha3;
431
+
432
+ return isset( $countries[ $code ] ) ? $countries[ $code ] : $code;
433
+ }
434
+
435
+
436
+ /**
437
+ * Converts an ISO 3166-alpha2 country code to an ISO 3166-alpha3 country code.
438
+ *
439
+ * @since 5.4.3
440
+ *
441
+ * @param string $alpha2_code ISO 3166-alpha2 country code
442
+ * @return string ISO 3166-alpha3 country code
443
+ */
444
+ public static function alpha2_to_alpha3( $alpha2_code ) {
445
+
446
+ return isset( self::$alpha3[ $alpha2_code ] ) ? self::$alpha3[ $alpha2_code ] : '';
447
+ }
448
+
449
+
450
+ /**
451
+ * Converts an ISO 3166-alpha2 country code to an ISO 3166-numeric country code.
452
+ *
453
+ * @since 5.4.3
454
+ *
455
+ * @param string $alpha2_code ISO 3166-alpha2 country code
456
+ * @return string ISO 3166-numeric country code
457
+ */
458
+ public static function alpha2_to_numeric( $alpha2_code ) {
459
+
460
+ return isset( self::$numeric[ $alpha2_code ] ) ? self::$numeric[ $alpha2_code ] : '';
461
+ }
462
+
463
+
464
+ /**
465
+ * Converts an ISO 3166-alpha2 country code to a calling code.
466
+ *
467
+ * This conversion is available in WC 3.6+ so we'll call out to that when available.
468
+ *
469
+ * @since 5.4.3
470
+ *
471
+ * @param string $alpha2_code ISO 3166-alpha2 country code
472
+ * @return string calling code
473
+ */
474
+ public static function alpha2_to_calling_code( $alpha2_code ) {
475
+
476
+ // check not only for the right version, but if the helper is loaded & available
477
+ if ( SV_WC_Plugin_Compatibility::is_wc_version_gte( '3.6.0' ) && WC() && isset( WC()->countries ) && is_callable( [ WC()->countries, 'get_country_calling_code' ] ) ) {
478
+
479
+ $calling_code = WC()->countries->get_country_calling_code( $alpha2_code );
480
+
481
+ } else {
482
+
483
+ $calling_code = isset( self::$calling_codes[ $alpha2_code ] ) ? self::$calling_codes[ $alpha2_code ] : '';
484
+
485
+ // we can't really know _which_ code is to be used, so use the first
486
+ $calling_code = is_array( $calling_code ) ? $calling_code[0] : $calling_code;
487
+ }
488
+
489
+ return $calling_code;
490
+ }
491
+
492
+
493
+ /**
494
+ * Converts an ISO 3166-alpha3 country code to an ISO 3166-alpha2 country code.
495
+ *
496
+ * @since 5.4.3
497
+ *
498
+ * @param string $alpha3_code ISO 3166-alpha3 country code
499
+ * @return string ISO 3166-alpha2 country code
500
+ */
501
+ public static function alpha3_to_alpha2( $alpha3_code ) {
502
+
503
+ $countries = array_flip( self::$alpha3 );
504
+
505
+ return isset( $countries[ $alpha3_code ] ) ? $countries[ $alpha3_code ] : '';
506
+ }
507
+
508
+
509
+ /**
510
+ * Converts an ISO 3166-alpha3 country code to an ISO 3166-numeric country code.
511
+ *
512
+ * @since 5.4.3
513
+ *
514
+ * @param string $alpha3_code ISO 3166-alpha3 country code
515
+ * @return string ISO 3166-numeric country code
516
+ */
517
+ public static function alpha3_to_numeric( $alpha3_code ) {
518
+ return self::alpha2_to_numeric( self::alpha3_to_alpha2( $alpha3_code ) );
519
+ }
520
+
521
+
522
+ /**
523
+ * Converts an ISO 3166-alpha3 country code to a calling code.
524
+ *
525
+ * @since 5.4.3
526
+ *
527
+ * @param string $alpha3_code ISO 3166-alpha3 country code
528
+ * @return string calling code
529
+ */
530
+ public static function alpha3_to_calling_code( $alpha3_code ) {
531
+ return self::alpha2_to_calling_code( self::alpha3_to_alpha2( $alpha3_code ) );
532
+ }
533
+
534
+
535
+ /**
536
+ * Converts an ISO 3166-numeric country code to an ISO 3166-alpha2 code.
537
+ *
538
+ * @since 5.4.3
539
+ *
540
+ * @param string $numeric ISO 3166-numeric country code
541
+ * @return string ISO 3166-alpha2 country code
542
+ */
543
+ public static function numeric_to_alpha2( $numeric ) {
544
+
545
+ $codes = array_flip( self::$numeric );
546
+
547
+ return isset( $codes[ $numeric ] ) ? $codes[ $numeric ] : '';
548
+ }
549
+
550
+
551
+ /**
552
+ * Converts an ISO 3166-numeric country code to an ISO 3166-alpha3 code.
553
+ *
554
+ * @since 5.4.3
555
+ *
556
+ * @param string $numeric ISO 3166-numeric country code
557
+ * @return string ISO 3166-alpha3 country code
558
+ */
559
+ public static function numeric_to_alpha3( $numeric ) {
560
+ return self::alpha2_to_alpha3( self::numeric_to_alpha2( $numeric ) );
561
+ }
562
+
563
+
564
+ /**
565
+ * Converts an ISO 3166-numeric country code to a calling code.
566
+ *
567
+ * @since 5.4.3
568
+ *
569
+ * @param string $numeric ISO 3166-numeric country code
570
+ * @return string calling code
571
+ */
572
+ public static function numeric_to_calling_code( $numeric ) {
573
+ return self::alpha2_to_calling_code( self::numeric_to_alpha2( $numeric ) );
574
+ }
575
+
576
+
577
+ /**
578
+ * Converts a country calling code to an ISO 3166-alpha2 code.
579
+ *
580
+ * @since 5.4.3
581
+ *
582
+ * @param string $calling_code country calling code (includes leading '+')
583
+ * @return string ISO 3166-alpha2 code
584
+ */
585
+ public static function calling_code_to_alpha2( $calling_code ) {
586
+
587
+ $flipped_calling_codes = self::get_flipped_calling_codes();
588
+
589
+ return isset( $flipped_calling_codes[ $calling_code ] ) ? $flipped_calling_codes[ $calling_code ] : '';
590
+ }
591
+
592
+
593
+ /**
594
+ * Converts a country calling code to an ISO 3166-alpha3 code.
595
+ *
596
+ * @since 5.4.3
597
+ *
598
+ * @param string $calling_code country calling code (includes leading '+')
599
+ * @return string ISO 3166-alpha3 code
600
+ */
601
+ public static function calling_code_to_alpha3( $calling_code ) {
602
+
603
+ return self::alpha2_to_alpha3( self::calling_code_to_alpha2( $calling_code ) );
604
+ }
605
+
606
+
607
+ /**
608
+ * Converts a country calling code to an ISO 3166-numeric code.
609
+ *
610
+ * @since 5.4.3
611
+ *
612
+ * @param string $calling_code country calling code (includes leading '+')
613
+ * @return string ISO 3166-numeric code
614
+ */
615
+ public static function calling_code_to_numeric( $calling_code ) {
616
+
617
+ return self::alpha2_to_numeric( self::calling_code_to_alpha2( $calling_code ) );
618
+ }
619
+
620
+
621
+ /**
622
+ * Gets the flipped version of the calling codes array.
623
+ *
624
+ * Since array_flip will fail on the calling codes array due to
625
+ * having some arrays as values, this custom function is necessary.
626
+ *
627
+ * @since 5.4.3
628
+ *
629
+ * @return array
630
+ */
631
+ public static function get_flipped_calling_codes() {
632
+
633
+ if ( null === self::$flipped_calling_codes ) {
634
+
635
+ $flipped_calling_codes = [];
636
+
637
+ foreach ( self::$calling_codes as $alpha2 => $calling_code ) {
638
+
639
+ if ( is_array( $calling_code ) ) {
640
+
641
+ foreach ( $calling_code as $sub_code ) {
642
+
643
+ $flipped_calling_codes[ $sub_code ] = $alpha2;
644
+ }
645
+ } else {
646
+
647
+ $flipped_calling_codes[ $calling_code ] = $alpha2;
648
+ }
649
+ }
650
+
651
+ self::$flipped_calling_codes = $flipped_calling_codes;
652
+ }
653
+
654
+ return self::$flipped_calling_codes;
655
+ }
656
+
657
+
658
+ }
659
+
660
+
661
+ endif;
vendor/skyverge/wc-plugin-framework/woocommerce/Lifecycle.php ADDED
@@ -0,0 +1,667 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * WooCommerce Plugin Framework
4
+ *
5
+ * This source file is subject to the GNU General Public License v3.0
6
+ * that is bundled with this package in the file license.txt.
7
+ * It is also available through the world-wide-web at this URL:
8
+ * http://www.gnu.org/licenses/gpl-3.0.html
9
+ * If you did not receive a copy of the license and are unable to
10
+ * obtain it through the world-wide-web, please send an email
11
+ * to license@skyverge.com so we can send you a copy immediately.
12
+ *
13
+ * DISCLAIMER
14
+ *
15
+ * Do not edit or add to this file if you wish to upgrade the plugin to newer
16
+ * versions in the future. If you wish to customize the plugin for your
17
+ * needs please refer to http://www.skyverge.com
18
+ *
19
+ * @package SkyVerge/WooCommerce/Plugin/Classes
20
+ * @author SkyVerge
21
+ * @copyright Copyright (c) 2013-2020, SkyVerge, Inc.
22
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GNU General Public License v3.0
23
+ */
24
+
25
+ namespace SkyVerge\WooCommerce\PluginFramework\v5_5_4\Plugin;
26
+
27
+ use SkyVerge\WooCommerce\PluginFramework\v5_5_4\SV_WC_Plugin;
28
+
29
+ defined( 'ABSPATH' ) or exit;
30
+
31
+ if ( ! class_exists( '\\SkyVerge\\WooCommerce\\PluginFramework\\v5_5_4\\Plugin\\Lifecycle' ) ) :
32
+
33
+
34
+ /**
35
+ * Plugin lifecycle handler.
36
+ *
37
+ * Registers and displays milestone notice prompts and eventually the plugin
38
+ * install, upgrade, activation, and deactivation routines.
39
+ *
40
+ * @since 5.1.0
41
+ */
42
+ class Lifecycle {
43
+
44
+
45
+ /** @var array the version numbers that have an upgrade routine */
46
+ protected $upgrade_versions = [];
47
+
48
+ /** @var string minimum milestone version */
49
+ private $milestone_version;
50
+
51
+ /** @var SV_WC_Plugin plugin instance */
52
+ private $plugin;
53
+
54
+
55
+ /**
56
+ * Constructs the class.
57
+ *
58
+ * @since 5.1.0
59
+ *
60
+ * @param SV_WC_Plugin $plugin plugin instance
61
+ */
62
+ public function __construct( SV_WC_Plugin $plugin ) {
63
+
64
+ $this->plugin = $plugin;
65
+
66
+ $this->add_hooks();
67
+ }
68
+
69
+
70
+ /**
71
+ * Adds the action & filter hooks.
72
+ *
73
+ * @since 5.1.0
74
+ */
75
+ protected function add_hooks() {
76
+
77
+ // handle activation
78
+ add_action( 'admin_init', array( $this, 'handle_activation' ) );
79
+
80
+ // handle deactivation
81
+ add_action( 'deactivate_' . $this->get_plugin()->get_plugin_file(), array( $this, 'handle_deactivation' ) );
82
+
83
+ if ( is_admin() && ! is_ajax() ) {
84
+
85
+ // initialize the plugin lifecycle
86
+ add_action( 'wp_loaded', array( $this, 'init' ) );
87
+
88
+ // add the admin notices
89
+ add_action( 'init', array( $this, 'add_admin_notices' ) );
90
+ }
91
+
92
+ // catch any milestones triggered by action
93
+ add_action( 'wc_' . $this->get_plugin()->get_id() . '_milestone_reached', array( $this, 'trigger_milestone' ), 10, 3 );
94
+ }
95
+
96
+
97
+ /**
98
+ * Initializes the plugin lifecycle.
99
+ *
100
+ * @since 5.2.0
101
+ */
102
+ public function init() {
103
+
104
+ // potentially handle a new activation
105
+ $this->handle_activation();
106
+
107
+ $installed_version = $this->get_installed_version();
108
+ $plugin_version = $this->get_plugin()->get_version();
109
+
110
+ // installed version lower than plugin version?
111
+ if ( version_compare( $installed_version, $plugin_version, '<' ) ) {
112
+
113
+ if ( ! $installed_version ) {
114
+
115
+ $this->install();
116
+
117
+ // store the upgrade event regardless if there was a routine for it
118
+ $this->store_event( 'install' );
119
+
120
+ /**
121
+ * Fires after the plugin has been installed.
122
+ *
123
+ * @since 5.1.0
124
+ */
125
+ do_action( 'wc_' . $this->get_plugin()->get_id() . '_installed' );
126
+
127
+ } else {
128
+
129
+ $this->upgrade( $installed_version );
130
+
131
+ // store the upgrade event regardless if there was a routine for it
132
+ $this->add_upgrade_event( $installed_version );
133
+
134
+ // if the plugin never had any previous milestones, consider them all reached so their notices aren't displayed
135
+ if ( ! $this->get_milestone_version() ) {
136
+ $this->set_milestone_version( $plugin_version );
137
+ }
138
+
139
+ /**
140
+ * Fires after the plugin has been updated.
141
+ *
142
+ * @since 5.1.0
143
+ *
144
+ * @param string $installed_version previously installed version
145
+ */
146
+ do_action( 'wc_' . $this->get_plugin()->get_id() . '_updated', $installed_version );
147
+ }
148
+
149
+ // new version number
150
+ $this->set_installed_version( $plugin_version );
151
+ }
152
+ }
153
+
154
+
155
+ /**
156
+ * Triggers plugin activation.
157
+ *
158
+ * We don't use register_activation_hook() as that can't be called inside
159
+ * the 'plugins_loaded' action. Instead, we rely on setting to track the
160
+ * plugin's activation status.
161
+ *
162
+ * @internal
163
+ *
164
+ * @link https://developer.wordpress.org/reference/functions/register_activation_hook/#comment-2100
165
+ *
166
+ * @since 5.2.0
167
+ */
168
+ public function handle_activation() {
169
+
170
+ if ( ! get_option( 'wc_' . $this->get_plugin()->get_id() . '_is_active', false ) ) {
171
+
172
+ $this->activate();
173
+
174
+ /**
175
+ * Fires when the plugin is activated.
176
+ *
177
+ * @since 5.2.0
178
+ */
179
+ do_action( 'wc_' . $this->get_plugin()->get_id() . '_activated' );
180
+
181
+ update_option( 'wc_' . $this->get_plugin()->get_id() . '_is_active', 'yes' );
182
+ }
183
+ }
184
+
185
+
186
+ /**
187
+ * Triggers plugin deactivation.
188
+ *
189
+ * @internal
190
+ *
191
+ * @since 5.2.0
192
+ */
193
+ public function handle_deactivation() {
194
+
195
+ $this->deactivate();
196
+
197
+ /**
198
+ * Fires when the plugin is deactivated.
199
+ *
200
+ * @since 5.2.0
201
+ */
202
+ do_action( 'wc_' . $this->get_plugin()->get_id() . '_deactivated' );
203
+
204
+ delete_option( 'wc_' . $this->get_plugin()->get_id() . '_is_active' );
205
+ }
206
+
207
+
208
+ /**
209
+ * Handles plugin activation.
210
+ *
211
+ * Plugins can override this to run their own activation tasks.
212
+ *
213
+ * Important Note: operations here should never be destructive for existing
214
+ * data. Since we rely on an option to track activation, it's possible for
215
+ * this to run outside of genuine activations.
216
+ *
217
+ * @since 5.2.0
218
+ */
219
+ public function activate() {
220
+
221
+ // stub
222
+ }
223
+
224
+
225
+ /**
226
+ * Handles plugin deactivation.
227
+ *
228
+ * Plugins can override this to run their own deactivation tasks.
229
+ *
230
+ * @since 5.2.0
231
+ */
232
+ public function deactivate() {
233
+
234
+ // stub
235
+ }
236
+
237
+
238
+ /**
239
+ * Helper method to install default settings for a plugin.
240
+ *
241
+ * @since 5.2.0
242
+ *
243
+ * @param array $settings settings in format required by WC_Admin_Settings
244
+ */
245
+ public function install_default_settings( array $settings ) {
246
+
247
+ foreach ( $settings as $setting ) {
248
+
249
+ if ( isset( $setting['id'], $setting['default'] ) ) {
250
+ update_option( $setting['id'], $setting['default'] );
251
+ }
252
+ }
253
+ }
254
+
255
+
256
+ /**
257
+ * Performs any install tasks.
258
+ *
259
+ * @since 5.2.0
260
+ */
261
+ protected function install() {
262
+
263
+ // stub
264
+ }
265
+
266
+
267
+ /**
268
+ * Performs any upgrade tasks based on the provided installed version.
269
+ *
270
+ * @since 5.2.0
271
+ *
272
+ * @param string $installed_version installed version
273
+ */
274
+ protected function upgrade( $installed_version ) {
275
+
276
+ foreach ( $this->upgrade_versions as $upgrade_version ) {
277
+
278
+ $upgrade_method = 'upgrade_to_' . str_replace( array( '.', '-' ), '_', $upgrade_version );
279
+
280
+ if ( version_compare( $installed_version, $upgrade_version, '<' ) && is_callable( array( $this, $upgrade_method ) ) ) {
281
+
282
+ $this->get_plugin()->log( "Starting upgrade to v{$upgrade_version}" );
283
+
284
+ $this->$upgrade_method( $installed_version );
285
+
286
+ $this->get_plugin()->log( "Upgrade to v{$upgrade_version} complete" );
287
+ }
288
+ }
289
+ }
290
+
291
+
292
+ /**
293
+ * Adds any lifecycle admin notices.
294
+ *
295
+ * @since 5.1.0
296
+ */
297
+ public function add_admin_notices() {
298
+
299
+ // display any milestone notices
300
+ foreach ( $this->get_milestone_messages() as $id => $message ) {
301
+
302
+ // bail if this notice was already dismissed
303
+ if ( ! $this->get_plugin()->get_admin_notice_handler()->should_display_notice( $id ) ) {
304
+ continue;
305
+ }
306
+
307
+ /**
308
+ * Filters a milestone notice message.
309
+ *
310
+ * @since 5.1.0
311
+ *
312
+ * @param string $message message text to be used for the milestone notice
313
+ * @param string $id milestone ID
314
+ */
315
+ $message = apply_filters( 'wc_' . $this->get_plugin()->get_id() . '_milestone_message', $this->generate_milestone_notice_message( $message ), $id );
316
+
317
+ if ( $message ) {
318
+
319
+ $this->get_plugin()->get_admin_notice_handler()->add_admin_notice( $message, $id, array(
320
+ 'always_show_on_settings' => false,
321
+ ) );
322
+
323
+ // only display one notice at a time
324
+ break;
325
+ }
326
+ }
327
+ }
328
+
329
+
330
+ /** Milestone Methods *****************************************************/
331
+
332
+
333
+ /**
334
+ * Triggers a milestone.
335
+ *
336
+ * This will only be triggered if the install's "milestone version" is lower
337
+ * than $since. Plugins can specify $since as the version at which a
338
+ * milestone's feature was added. This prevents existing installs from
339
+ * triggering notices for milestones that have long passed, like a payment
340
+ * gateway's first successful payment. Omitting $since will assume the
341
+ * milestone has always existed and should only trigger for fresh installs.
342
+ *
343
+ * @since 5.1.0
344
+ *
345
+ * @param string $id milestone ID
346
+ * @param string $message message to display to the user
347
+ * @param string $since the version since this milestone has existed in the plugin
348
+ * @return bool
349
+ */
350
+ public function trigger_milestone( $id, $message, $since = '1.0.0' ) {
351
+
352
+ // if the plugin was had milestones before this milestone was added, don't trigger it
353
+ if ( version_compare( $this->get_milestone_version(), $since, '>' ) ) {
354
+ return false;
355
+ }
356
+
357
+ return $this->register_milestone_message( $id, $message );
358
+ }
359
+
360
+
361
+ /**
362
+ * Generates a milestone notice message.
363
+ *
364
+ * @since 5.1.0
365
+ *
366
+ * @param string $custom_message custom text that notes what milestone was completed.
367
+ * @return string
368
+ */
369
+ protected function generate_milestone_notice_message( $custom_message ) {
370
+
371
+ $message = '';
372
+
373
+ if ( $this->get_plugin()->get_reviews_url() ) {
374
+
375
+ // to be prepended at random to each milestone notice
376
+ $exclamations = array(
377
+ __( 'Awesome', 'woocommerce-plugin-framework' ),
378
+ __( 'Fantastic', 'woocommerce-plugin-framework' ),
379
+ __( 'Cowabunga', 'woocommerce-plugin-framework' ),
380
+ __( 'Congratulations', 'woocommerce-plugin-framework' ),
381
+ __( 'Hot dog', 'woocommerce-plugin-framework' ),
382
+ );
383
+
384
+ $message = $exclamations[ array_rand( $exclamations ) ] . ', ' . esc_html( $custom_message ) . ' ';
385
+
386
+ $message .= sprintf(
387
+ /* translators: Placeholders: %1$s - plugin name, %2$s - <a> tag, %3$s - </a> tag, %4$s - <a> tag, %5$s - </a> tag */
388
+ __( 'Are you having a great experience with %1$s so far? Please consider %2$sleaving a review%3$s! If things aren\'t going quite as expected, we\'re happy to help -- please %4$sreach out to our support team%5$s.', 'woocommerce-plugin-framework' ),
389
+ '<strong>' . esc_html( $this->get_plugin()->get_plugin_name() ) . '</strong>',
390
+ '<a href="' . esc_url( $this->get_plugin()->get_reviews_url() ) . '">', '</a>',
391
+ '<a href="' . esc_url( $this->get_plugin()->get_support_url() ) . '">', '</a>'
392
+ );
393
+ }
394
+
395
+ return $message;
396
+ }
397
+
398
+
399
+ /**
400
+ * Registers a milestone message to be displayed in the admin.
401
+ *
402
+ * @since 5.1.0
403
+ * @see Lifecycle::generate_milestone_notice_message()
404
+ *
405
+ * @param string $id milestone ID
406
+ * @param string $message message to display to the user
407
+ * @return bool whether the message was successfully registered
408
+ */
409
+ public function register_milestone_message( $id, $message ) {
410
+
411
+ $milestone_messages = $this->get_milestone_messages();
412
+ $dismissed_notices = array_keys( $this->get_plugin()->get_admin_notice_handler()->get_dismissed_notices() );
413
+
414
+ // get the total number of dismissed milestone messages
415
+ $dismissed_milestone_messages = array_intersect( array_keys( $milestone_messages ), $dismissed_notices );
416
+
417
+ // if the user has dismissed more than three milestone messages already, don't add any more
418
+ if ( count( $dismissed_milestone_messages ) > 3 ) {
419
+ return false;
420
+ }
421
+
422
+ $milestone_messages[ $id ] = $message;
423
+
424
+ return update_option( 'wc_' . $this->get_plugin()->get_id() . '_milestone_messages', $milestone_messages );
425
+ }
426
+
427
+
428
+ /** Event history methods *****************************************************************************************/
429
+
430
+
431
+ /**
432
+ * Adds an upgrade lifecycle event.
433
+ *
434
+ * @since 5.4.0
435
+ *
436
+ * @param string $from_version version upgrading from
437
+ * @param array $data extra data to add
438
+ * @return false|int
439
+ */
440
+ public function add_upgrade_event( $from_version, array $data = array() ) {
441
+
442
+ $data = array_merge( array(
443
+ 'from_version' => $from_version,
444
+ ), $data );
445
+
446
+ return $this->store_event( 'upgrade', $data );
447
+ }
448
+
449
+
450
+ /**
451
+ * Adds a migration lifecycle event.
452
+ *
453
+ * @since 5.4.0
454
+ *
455
+ * @param string $from_plugin plugin migrating from
456
+ * @param string $from_version version migrating from
457
+ * @param array $data extra data to add
458
+ * @return false|int
459
+ */
460
+ public function add_migrate_event( $from_plugin, $from_version = '', array $data = array() ) {
461
+
462
+ $data = array_merge( array(
463
+ 'from_plugin' => $from_plugin,
464
+ 'from_version' => $from_version,
465
+ ), $data );
466
+
467
+ return $this->store_event( 'migrate', $data );
468
+ }
469
+
470
+
471
+ /**
472
+ * Stores a lifecycle event.
473
+ *
474
+ * This can be used to log installs, upgrades, etc...
475
+ *
476
+ * Uses a direct database query to avoid cache issues.
477
+ *
478
+ * @since 5.4.0
479
+ *
480
+ * @param string $name lifecycle event name
481
+ * @param array $data any extra data to store
482
+ * @return false|int
483
+ */
484
+ public function store_event( $name, array $data = array() ) {
485
+ global $wpdb;
486
+
487
+ $history = $this->get_event_history();
488
+
489
+ $event = array(
490
+ 'name' => wc_clean( $name ),
491
+ 'time' => (int) current_time( 'timestamp' ),
492
+ 'version' => wc_clean( $this->get_plugin()->get_version() ),
493
+ );
494
+
495
+ if ( ! empty( $data ) ) {
496
+ $event['data'] = wc_clean( $data );
497
+ }
498
+
499
+ array_unshift( $history, $event );
500
+
501
+ // limit to the last 30 events
502
+ $history = array_slice( $history, 0, 29 );
503
+
504
+ return $wpdb->replace(
505
+ $wpdb->options,
506
+ array(
507
+ 'option_name' => $this->get_event_history_option_name(),
508
+ 'option_value' => json_encode( $history ),
509
+ 'autoload' => 'no',
510
+ ),
511
+ array(
512
+ '%s',
513
+ '%s',
514
+ )
515
+ );
516
+ }
517
+
518
+
519
+ /**
520
+ * Gets the lifecycle event history.
521
+ *
522
+ * The last 30 events are stored, with the latest first.
523
+ *
524
+ * @since 5.4.0
525
+ *
526
+ * @return array
527
+ */
528
+ public function get_event_history() {
529
+ global $wpdb;
530
+
531
+ $history = array();
532
+
533
+ $results = $wpdb->get_var( $wpdb->prepare( "
534
+ SELECT option_value
535
+ FROM {$wpdb->options}
536
+ WHERE option_name = %s
537
+ ", $this->get_event_history_option_name() ) );
538
+
539
+ if ( $results ) {
540
+ $history = json_decode( $results, true );
541
+ }
542
+
543
+ return is_array( $history ) ? $history : array();
544
+ }
545
+
546
+
547
+ /**
548
+ * Gets the event history option name.
549
+ *
550
+ * @since 5.4.0
551
+ *
552
+ * @return string
553
+ */
554
+ protected function get_event_history_option_name() {
555
+
556
+ return 'wc_' . $this->get_plugin()->get_id() . '_lifecycle_events';
557
+ }
558
+
559
+
560
+ /** Utility Methods *******************************************************/
561
+
562
+
563
+ /**
564
+ * Gets the registered milestone messages.
565
+ *
566
+ * @since 5.1.0
567
+ *
568
+ * @return array
569
+ */
570
+ protected function get_milestone_messages() {
571
+
572
+ return get_option( 'wc_' . $this->get_plugin()->get_id() . '_milestone_messages', array() );
573
+ }
574
+
575
+
576
+ /**
577
+ * Sets the milestone version.
578
+ *
579
+ * @since 5.1.0
580
+ *
581
+ * @param string $version plugin version
582
+ * @return bool
583
+ */
584
+ public function set_milestone_version( $version ) {
585
+
586
+ $this->milestone_version = $version;
587
+
588
+ return update_option( 'wc_' . $this->get_plugin()->get_id() . '_milestone_version', $version );
589
+ }
590
+
591
+
592
+ /**
593
+ * Gets the milestone version.
594
+ *
595
+ * @since 5.1.0
596
+ *
597
+ * @return string
598
+ */
599
+ public function get_milestone_version() {
600
+
601
+ if ( ! $this->milestone_version ) {
602
+ $this->milestone_version = get_option( 'wc_' . $this->get_plugin()->get_id() . '_milestone_version', '' );
603
+ }
604
+
605
+ return $this->milestone_version;
606
+ }
607
+
608
+
609
+ /**
610
+ * Gets the currently installed plugin version.
611
+ *
612
+ * @since 5.2.0
613
+ *
614
+ * @return string
615
+ */
616
+ protected function get_installed_version() {
617
+
618
+ return get_option( $this->get_plugin()->get_plugin_version_name() );
619
+ }
620
+
621
+
622
+ /**
623
+ * Sets the installed plugin version.
624
+ *
625
+ * @since 5.2.0
626
+ *
627
+ * @param string $version version to set
628
+ */
629
+ protected function set_installed_version( $version ) {
630
+
631
+ update_option( $this->get_plugin()->get_plugin_version_name(), $version );
632
+ }
633
+
634
+
635
+ /**
636
+ * Gets the plugin instance.
637
+ *
638
+ * @since 5.1.0
639
+ *
640
+ * @return SV_WC_Plugin
641
+ */
642
+ protected function get_plugin() {
643
+
644
+ return $this->plugin;
645
+ }
646
+
647
+
648
+ /** Deprecated methods ****************************************************/
649
+
650
+
651
+ /**
652
+ * Handles tasks after the plugin has been updated.
653
+ *
654
+ * @internal
655
+ *
656
+ * @since 5.1.0
657
+ */
658
+ public function do_update() {
659
+
660
+ wc_deprecated_function( __METHOD__, '5.2.0' );
661
+ }
662
+
663
+
664
+ }
665
+
666
+
667
+ endif;
vendor/skyverge/wc-plugin-framework/woocommerce/admin/abstract-sv-wc-plugin-admin-setup-wizard.php ADDED
@@ -0,0 +1,1300 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * WooCommerce Plugin Framework
4
+ *
5
+ * This source file is subject to the GNU General Public License v3.0
6
+ * that is bundled with this package in the file license.txt.
7
+ * It is also available through the world-wide-web at this URL:
8
+ * http://www.gnu.org/licenses/gpl-3.0.html
9
+ * If you did not receive a copy of the license and are unable to
10
+ * obtain it through the world-wide-web, please send an email
11
+ * to license@skyverge.com so we can send you a copy immediately.
12
+ *
13
+ * DISCLAIMER
14
+ *
15
+ * Do not edit or add to this file if you wish to upgrade the plugin to newer
16
+ * versions in the future. If you wish to customize the plugin for your
17
+ * needs please refer to http://www.skyverge.com
18
+ *
19
+ * @author SkyVerge
20
+ * @copyright Copyright (c) 2013-2020, SkyVerge, Inc.
21
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GNU General Public License v3.0
22
+ */
23
+
24
+ namespace SkyVerge\WooCommerce\PluginFramework\v5_5_4\Admin;
25
+
26
+ defined( 'ABSPATH' ) or exit;
27
+
28
+ use SkyVerge\WooCommerce\PluginFramework\v5_5_4 as Framework;
29
+
30
+ if ( ! class_exists( '\\SkyVerge\\WooCommerce\\PluginFramework\\v5_5_4\\Admin\\Setup_Wizard' ) ) :
31
+
32
+
33
+ /**
34
+ * The plugin Setup Wizard class.
35
+ *
36
+ * This creates a setup wizard so that plugins can provide a user-friendly
37
+ * step-by-step interaction for configuring critical plugin options.
38
+ *
39
+ * Based on WooCommerce's \WC_Admin_Setup_Wizard
40
+ *
41
+ * @since 5.2.2
42
+ */
43
+ abstract class Setup_Wizard {
44
+
45
+
46
+ /** the "finish" step ID */
47
+ const ACTION_FINISH = 'finish';
48
+
49
+
50
+ /** @var string the user capability required to use this wizard */
51
+ protected $required_capability = 'manage_woocommerce';
52
+
53
+ /** @var string the current step ID */
54
+ protected $current_step = '';
55
+
56
+ /** @var array registered steps to be displayed */
57
+ protected $steps = array();
58
+
59
+ /** @var string setup handler ID */
60
+ private $id;
61
+
62
+ /** @var Framework\SV_WC_Plugin plugin instance */
63
+ private $plugin;
64
+
65
+
66
+ /**
67
+ * Constructs the class.
68
+ *
69
+ * @param Framework\SV_WC_Plugin $plugin plugin instance
70
+ */
71
+ public function __construct( Framework\SV_WC_Plugin $plugin ) {
72
+
73
+ // sanity check for admin and permissions
74
+ if ( ! is_admin() || ! current_user_can( $this->required_capability ) ) {
75
+ return;
76
+ }
77
+
78
+ $this->id = $plugin->get_id();
79
+ $this->plugin = $plugin;
80
+
81
+ // register the steps
82
+ $this->register_steps();
83
+
84
+ /**
85
+ * Filters the registered setup wizard steps.
86
+ *
87
+ * @since 5.2.2
88
+ *
89
+ * @param array $steps registered steps
90
+ */
91
+ $this->steps = apply_filters( "wc_{$this->id}_setup_wizard_steps", $this->steps, $this );
92
+
93
+ // only continue if there are registered steps
94
+ if ( $this->has_steps() ) {
95
+
96
+ // if requesting the wizard
97
+ if ( $this->is_setup_page() ) {
98
+
99
+ $this->init_setup();
100
+
101
+ // otherwise, add the hooks for customizing the regular admin
102
+ } else {
103
+
104
+ $this->add_hooks();
105
+
106
+ // mark the wizard as complete if specifically requested
107
+ if ( Framework\SV_WC_Helper::get_requested_value( "wc_{$this->id}_setup_wizard_complete" ) ) {
108
+ $this->complete_setup();
109
+ }
110
+ }
111
+ }
112
+ }
113
+
114
+
115
+ /**
116
+ * Registers the setup steps.
117
+ *
118
+ * Plugins should extend this to register their own steps.
119
+ *
120
+ * @since 5.2.2
121
+ */
122
+ abstract protected function register_steps();
123
+
124
+
125
+ /**
126
+ * Adds the action & filter hooks.
127
+ *
128
+ * @since 5.2.2
129
+ */
130
+ protected function add_hooks() {
131
+
132
+ // add any admin notices
133
+ add_action( 'admin_notices', array( $this, 'add_admin_notices' ) );
134
+
135
+ // add a 'Setup' link to the plugin action links if the wizard hasn't been completed
136
+ if ( ! $this->is_complete() ) {
137
+ add_filter( 'plugin_action_links_' . plugin_basename( $this->get_plugin()->get_plugin_file() ), array( $this, 'add_setup_link' ), 20 );
138
+ }
139
+ }
140
+
141
+
142
+ /**
143
+ * Adds any admin notices.
144
+ *
145
+ * @since 5.2.2
146
+ */
147
+ public function add_admin_notices() {
148
+
149
+ if ( Framework\SV_WC_Helper::is_current_screen( 'plugins' ) || $this->get_plugin()->is_plugin_settings() ) {
150
+
151
+ if ( $this->is_complete() && $this->get_documentation_notice_message() ) {
152
+ $notice_id = "wc_{$this->id}_docs";
153
+ $message = $this->get_documentation_notice_message();
154
+ } else {
155
+ $notice_id = "wc_{$this->id}_setup";
156
+ $message = $this->get_setup_notice_message();
157
+ }
158
+
159
+ $this->get_plugin()->get_admin_notice_handler()->add_admin_notice( $message, $notice_id, array(
160
+ 'always_show_on_settings' => false,
161
+ ) );
162
+ }
163
+ }
164
+
165
+
166
+ /**
167
+ * Gets the new installation documentation notice message.
168
+ *
169
+ * This prompts users to read the docs and is displayed if the wizard has
170
+ * already been completed.
171
+ *
172
+ * @since 5.2.2
173
+ *
174
+ * @return string
175
+ */
176
+ protected function get_documentation_notice_message() {
177
+
178
+ if ( $this->get_plugin()->get_documentation_url() ) {
179
+
180
+ $message = sprintf(
181
+ /** translators: Placeholders: %1$s - plugin name, %2$s - <a> tag, %3$s - </a> tag */
182
+ __( 'Thanks for installing %1$s! To get started, take a minute to %2$sread the documentation%3$s :)', 'woocommerce-plugin-framework' ),
183
+ esc_html( $this->get_plugin()->get_plugin_name() ),
184
+ '<a href="' . esc_url( $this->get_plugin()->get_documentation_url() ) . '" target="_blank">', '</a>'
185
+ );
186
+
187
+ } else {
188
+
189
+ $message = '';
190
+ }
191
+
192
+ return $message;
193
+ }
194
+
195
+
196
+ /**
197
+ * Gets the new installation setup notice message.
198
+ *
199
+ * This prompts users to start the setup wizard and is displayed if the
200
+ * wizard has not yet been completed.
201
+ *
202
+ * @since 5.2.2
203
+ *
204
+ * @return string
205
+ */
206
+ protected function get_setup_notice_message() {
207
+
208
+ return sprintf(
209
+ /** translators: Placeholders: %1$s - plugin name, %2$s - <a> tag, %3$s - </a> tag */
210
+ __( 'Thanks for installing %1$s! To get started, take a minute to complete these %2$squick and easy setup steps%3$s :)', 'woocommerce-plugin-framework' ),
211
+ esc_html( $this->get_plugin()->get_plugin_name() ),
212
+ '<a href="' . esc_url( $this->get_setup_url() ) . '">', '</a>'
213
+ );
214
+ }
215
+
216
+
217
+ /**
218
+ * Adds a 'Setup' link to the plugin action links if the wizard hasn't been completed.
219
+ *
220
+ * This will override the plugin's standard "Configure" link with a link to this setup wizard.
221
+ *
222
+ * @internal
223
+ *
224
+ * @since 5.2.2
225
+ *
226
+ * @param array $action_links plugin action links
227
+ * @return array
228
+ */
229
+ public function add_setup_link( $action_links ) {
230
+
231
+ // remove the standard plugin "Configure" link
232
+ unset( $action_links['configure'] );
233
+
234
+ $setup_link = array(
235
+ 'setup' => sprintf( '<a href="%s">%s</a>', $this->get_setup_url(), esc_html__( 'Setup', 'woocommerce-plugin-framework' ) ),
236
+ );
237
+
238
+ return array_merge( $setup_link, $action_links );
239
+ }
240
+
241
+
242
+ /**
243
+ * Initializes setup.
244
+ *
245
+ * @since 5.2.2
246
+ */
247
+ protected function init_setup() {
248
+
249
+ // get a step ID from $_GET
250
+ $current_step = sanitize_key( Framework\SV_WC_Helper::get_requested_value( 'step' ) );
251
+ $current_action = sanitize_key( Framework\SV_WC_Helper::get_requested_value( 'action' ) );
252
+
253
+ if ( ! $current_action ) {
254
+
255
+ if ( $this->has_step( $current_step ) ) {
256
+ $this->current_step = $current_step;
257
+ } elseif ( $first_step_url = $this->get_step_url( key( $this->steps ) ) ) {
258
+ wp_safe_redirect( $first_step_url );
259
+ exit;
260
+ } else {
261
+ wp_safe_redirect( $this->get_dashboard_url() );
262
+ exit;
263
+ }
264
+ }
265
+
266
+ // add the page to WP core
267
+ add_action( 'admin_menu', array( $this, 'add_page' ) );
268
+
269
+ // renders the entire setup page markup
270
+ add_action( 'admin_init', array( $this, 'render_page' ) );
271
+ }
272
+
273
+
274
+ /**
275
+ * Adds the page to WordPress core.
276
+ *
277
+ * While this doesn't output any markup/menu items, it is essential to officially register the page to avoid permissions issues.
278
+ *
279
+ * @internal
280
+ *
281
+ * @since 5.2.2
282
+ */
283
+ public function add_page() {
284
+
285
+ add_dashboard_page( '', '', $this->required_capability, $this->get_slug(), '' );
286
+ }
287
+
288
+
289
+ /**
290
+ * Renders the entire setup page markup.
291
+ *
292
+ * @internal
293
+ *
294
+ * @since 5.2.2
295
+ */
296
+ public function render_page() {
297
+
298
+ // maybe save and move onto the next step
299
+ $error_message = Framework\SV_WC_Helper::get_posted_value( 'save_step' ) ? $this->save_step( $this->current_step ) : '';
300
+
301
+ $page_title = sprintf(
302
+ /* translators: Placeholders: %s - plugin name */
303
+ __( '%s &rsaquo; Setup', 'woocommerce-plugin-framework' ),
304
+ $this->get_plugin()->get_plugin_name()
305
+ );
306
+
307
+ // add the step name to the page title
308
+ if ( ! empty( $this->steps[ $this->current_step ]['name'] ) ) {
309
+ $page_title .= " &rsaquo; {$this->steps[ $this->current_step ]['name']}";
310
+ }
311
+
312
+ $this->load_scripts_styles();
313
+
314
+ ob_start();
315
+
316
+ ?>
317
+ <!DOCTYPE html>
318
+ <html <?php language_attributes(); ?>>
319
+ <head>
320
+ <meta name="viewport" content="width=device-width" />
321
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
322
+ <title><?php echo esc_html( $page_title ); ?></title>
323
+ <?php wp_print_scripts( 'wc-setup' ); ?>
324
+ <?php do_action( 'admin_print_scripts' ); ?>
325
+ <?php do_action( 'admin_print_styles' ); ?>
326
+ <?php do_action( 'admin_head' ); ?>
327
+ </head>
328
+ <body class="wc-setup wp-core-ui <?php echo esc_attr( $this->get_slug() ); ?>">
329
+ <?php $this->render_header(); ?>
330
+ <?php $this->render_steps(); ?>
331
+ <?php $this->render_content( $error_message ); ?>
332
+ <?php $this->render_footer(); ?>
333
+ </body>
334
+ </html>
335
+ <?php
336
+
337
+ exit;
338
+ }
339
+
340
+
341
+ /**
342
+ * Saves a step.
343
+ *
344
+ * @since 5.2.2
345
+ *
346
+ * @param string $step_id the step ID being saved
347
+ * @return void|string redirects upon success, returns an error message upon failure
348
+ */
349
+ protected function save_step( $step_id ) {
350
+
351
+ $error_message = __( 'Oops! An error occurred, please try again.', 'woocommerce-plugin-framework' );
352
+
353
+ try {
354
+
355
+ // bail early if the nonce is bad
356
+ if ( ! wp_verify_nonce( Framework\SV_WC_Helper::get_posted_value( 'nonce' ), "wc_{$this->id}_setup_wizard_save" ) ) {
357
+ throw new Framework\SV_WC_Plugin_Exception( $error_message );
358
+ }
359
+
360
+ if ( $this->has_step( $step_id ) ) {
361
+
362
+ // if the step has a saving callback defined, save the form fields
363
+ if ( is_callable( $this->steps[ $step_id ]['save'] ) ) {
364
+ call_user_func( $this->steps[ $step_id ]['save'], $this );
365
+ }
366
+
367
+ // move to the next step
368
+ wp_safe_redirect( $this->get_next_step_url( $step_id ) );
369
+ exit;
370
+ }
371
+
372
+ } catch ( Framework\SV_WC_Plugin_Exception $exception ) {
373
+
374
+ return $exception->getMessage() ? $exception->getMessage() : $error_message;
375
+ }
376
+ }
377
+
378
+
379
+ /**
380
+ * Registers and enqueues the wizard's scripts and styles.
381
+ *
382
+ * @since 5.2.2
383
+ */
384
+ protected function load_scripts_styles() {
385
+
386
+ // block UI
387
+ wp_register_script( 'jquery-blockui', WC()->plugin_url() . '/assets/js/jquery-blockui/jquery.blockUI.min.js', array( 'jquery' ), '2.70', true );
388
+
389
+ // enhanced dropdowns
390
+ wp_register_script( 'selectWoo', WC()->plugin_url() . '/assets/js/selectWoo/selectWoo.full.min.js', array( 'jquery' ), '1.0.0' );
391
+ wp_register_script( 'wc-enhanced-select', WC()->plugin_url() . '/assets/js/admin/wc-enhanced-select.min.js', array( 'jquery', 'selectWoo' ), $this->get_plugin()->get_version() );
392
+ wp_localize_script(
393
+ 'wc-enhanced-select',
394
+ 'wc_enhanced_select_params',
395
+ array(
396
+ 'i18n_no_matches' => _x( 'No matches found', 'enhanced select', 'woocommerce-plugin-framework' ),
397
+ 'i18n_ajax_error' => _x( 'Loading failed', 'enhanced select', 'woocommerce-plugin-framework' ),
398
+ 'i18n_input_too_short_1' => _x( 'Please enter 1 or more characters', 'enhanced select', 'woocommerce-plugin-framework' ),
399
+ 'i18n_input_too_short_n' => _x( 'Please enter %qty% or more characters', 'enhanced select', 'woocommerce-plugin-framework' ),
400
+ 'i18n_input_too_long_1' => _x( 'Please delete 1 character', 'enhanced select', 'woocommerce-plugin-framework' ),
401
+ 'i18n_input_too_long_n' => _x( 'Please delete %qty% characters', 'enhanced select', 'woocommerce-plugin-framework' ),
402
+ 'i18n_selection_too_long_1' => _x( 'You can only select 1 item', 'enhanced select', 'woocommerce-plugin-framework' ),
403
+ 'i18n_selection_too_long_n' => _x( 'You can only select %qty% items', 'enhanced select', 'woocommerce-plugin-framework' ),
404
+ 'i18n_load_more' => _x( 'Loading more results&hellip;', 'enhanced select', 'woocommerce-plugin-framework' ),
405
+ 'i18n_searching' => _x( 'Searching&hellip;', 'enhanced select', 'woocommerce-plugin-framework' ),
406
+ 'ajax_url' => admin_url( 'admin-ajax.php' ),
407
+ 'search_products_nonce' => wp_create_nonce( 'search-products' ),
408
+ 'search_customers_nonce' => wp_create_nonce( 'search-customers' ),
409
+ )
410
+ );
411
+
412
+ // WooCommerce Setup core styles
413
+ wp_enqueue_style( 'woocommerce_admin_styles', WC()->plugin_url() . '/assets/css/admin.css', array(), $this->get_plugin()->get_version() );
414
+ wp_enqueue_style( 'wc-setup', WC()->plugin_url() . '/assets/css/wc-setup.css', array( 'dashicons', 'install' ), $this->get_plugin()->get_version() );
415
+
416
+ // framework bundled styles
417
+ wp_enqueue_style( 'sv-wc-admin-setup', $this->get_plugin()->get_framework_assets_url() . '/css/admin/sv-wc-plugin-admin-setup-wizard.min.css', array( 'wc-setup' ), $this->get_plugin()->get_version() );
418
+ wp_enqueue_script( 'sv-wc-admin-setup', $this->get_plugin()->get_framework_assets_url() . '/js/admin/sv-wc-plugin-admin-setup-wizard.min.js', array( 'jquery', 'wc-enhanced-select', 'jquery-blockui' ), $this->get_plugin()->get_version() );
419
+ }
420
+
421
+
422
+ /** Header Methods ************************************************************************************************/
423
+
424
+
425
+ /**
426
+ * Renders the header markup.
427
+ *
428
+ * @since 5.2.2
429
+ */
430
+ protected function render_header() {
431
+
432
+ $title = $this->get_plugin()->get_plugin_name();
433
+ $link_url = $this->get_plugin()->get_sales_page_url();
434
+ $image_url = $this->get_header_image_url();
435
+
436
+ $header_content = $image_url ? '<img src="' . esc_url( $image_url ) . '" alt="' . esc_attr( $title ) . '" />' : $title;
437
+
438
+ ?>
439
+ <h1 id="wc-logo" class="sv-wc-plugin-logo <?php echo esc_attr( 'wc-' . $this->get_plugin()->get_id_dasherized() . '-logo' ); ?>">
440
+ <?php if ( $link_url ) : ?>
441
+ <a href="<?php echo esc_url( $link_url ); ?>" target="_blank"><?php echo $header_content; ?></a>
442
+ <?php else : ?>
443
+ <?php echo esc_html( $header_content ); ?>
444
+ <?php endif; ?>
445
+ </h1>
446
+ <?php
447
+ }
448
+
449
+
450
+ /**
451
+ * Gets the header image URL.
452
+ *
453
+ * Plugins can override this to point to their own branding image URL.
454
+ *
455
+ * @since 5.2.2
456
+ *
457
+ * @return string
458
+ */
459
+ protected function get_header_image_url() {
460
+
461
+ return '';
462
+ }
463
+
464
+
465
+ /**
466
+ * Renders the step list.
467
+ *
468
+ * This displays a list of steps, marking them as complete or upcoming as sort of a progress bar.
469
+ *
470
+ * @since 5.2.2
471
+ */
472
+ protected function render_steps() {
473
+
474
+ ?>
475
+ <ol class="wc-setup-steps">
476
+
477
+ <?php foreach ( $this->steps as $id => $step ) : ?>
478
+
479
+ <?php if ( $id === $this->current_step ) : ?>
480
+ <li class="active"><?php echo esc_html( $step['name'] ); ?></li>
481
+ <?php elseif ( $this->is_step_complete( $id ) ) : ?>
482
+ <li class="done"><a href="<?php echo esc_url( $this->get_step_url( $id ) ); ?>"><?php echo esc_html( $step['name'] ); ?></a></li>
483
+ <?php else : ?>
484
+ <li><?php echo esc_html( $step['name'] ); ?></li>
485
+ <?php endif;?>
486
+
487
+ <?php endforeach; ?>
488
+
489
+ <li class="<?php echo $this->is_finished() ? 'done' : ''; ?>"><?php esc_html_e( 'Ready!', 'woocommerce-plugin-framework' ); ?></li>
490
+
491
+ </ol>
492
+ <?php
493
+ }
494
+
495
+
496
+ /** Content Methods ***********************************************************************************************/
497
+
498
+
499
+ /**
500
+ * Renders the setup content.
501
+ *
502
+ * This will display the welcome screen, finished screen, or a specific step's markup.
503
+ *
504
+ * @since 5.2.2
505
+ *
506
+ * @param string $error_message custom error message
507
+ */
508
+ protected function render_content( $error_message = '' ) {
509
+
510
+ ?>
511
+ <div class="wc-setup-content sv-wc-plugin-admin-setup-content <?php echo esc_attr( $this->get_slug() ) . '-content'; ?>">
512
+
513
+ <?php if ( $this->is_finished() ) : ?>
514
+
515
+ <?php $this->render_finished(); ?>
516
+
517
+ <?php $this->complete_setup(); ?>
518
+
519
+ <?php else : ?>
520
+
521
+ <?php // render a welcome message if the current is the first step ?>
522
+ <?php if ( $this->is_started() ) : ?>
523
+ <?php $this->render_welcome(); ?>
524
+ <?php endif; ?>
525
+
526
+ <?php // render any error message from a previous save ?>
527
+ <?php if ( ! empty( $error_message ) ) : ?>
528
+ <?php $this->render_error( $error_message ); ?>
529
+ <?php endif; ?>
530
+
531
+ <form method="post">
532
+ <?php $this->render_step( $this->current_step ); ?>
533
+ <?php wp_nonce_field( "wc_{$this->id}_setup_wizard_save", 'nonce' ); ?>
534
+ </form>
535
+
536
+ <?php endif; ?>
537
+
538
+ </div>
539
+ <?php
540
+ }
541
+
542
+
543
+ /**
544
+ * Renders a save error.
545
+ *
546
+ * @since 5.2.2
547
+ *
548
+ * @param string $message error message to render
549
+ */
550
+ protected function render_error( $message ) {
551
+
552
+ if ( ! empty( $message ) ) {
553
+
554
+ printf( '<p class="error">%s</p>', esc_html( $message ) );
555
+ }
556
+ }
557
+
558
+
559
+ /**
560
+ * Renders a default welcome note.
561
+ *
562
+ * @since 5.2.2
563
+ */
564
+ protected function render_welcome() {
565
+
566
+ ?>
567
+ <h1><?php $this->render_welcome_heading()?></h1>
568
+ <p class="welcome"><?php $this->render_welcome_text(); ?></p>
569
+ <?php
570
+ }
571
+
572
+
573
+ /**
574
+ * Renders the default welcome note heading.
575
+ *
576
+ * @since 5.2.2
577
+ */
578
+ protected function render_welcome_heading() {
579
+
580
+ printf(
581
+ /* translators: Placeholder: %s - plugin name */
582
+ esc_html__( 'Welcome to %s!', 'woocommerce-plugin-framework' ),
583
+ $this->get_plugin()->get_plugin_name()
584
+ );
585
+ }
586
+
587
+
588
+ /**
589
+ * Renders the default welcome note text.
590
+ *
591
+ * @since 5.2.2
592
+ */
593
+ protected function render_welcome_text() {
594
+
595
+ esc_html_e( 'This quick setup wizard will help you configure the basic settings and get you started.', 'woocommerce-plugin-framework' );
596
+ }
597
+
598
+
599
+ /**
600
+ * Renders the finished screen markup.
601
+ *
602
+ * This is what gets displayed after all of the steps have been completed or skipped.
603
+ *
604
+ * @since 5.2.2
605
+ */
606
+ protected function render_finished() {
607
+
608
+ ?>
609
+ <h1><?php printf( esc_html__( '%s is ready!', 'woocommerce-plugin-framework' ), esc_html( $this->get_plugin()->get_plugin_name() ) ); ?></h1>
610
+ <?php $this->render_before_next_steps(); ?>
611
+ <?php $this->render_next_steps(); ?>
612
+ <?php $this->render_after_next_steps(); ?>
613
+ <?php
614
+ }
615
+
616
+
617
+ /**
618
+ * Renders HTML before the next steps in the finished step screen.
619
+ *
620
+ * Plugins can implement this method to output additional HTML before the next steps are printed.
621
+ *
622
+ * @since 5.2.2
623
+ */
624
+ protected function render_before_next_steps() {
625
+ // stub method
626
+ }
627
+
628
+
629
+ /**
630
+ * Renders HTML after the next steps in the finished step screen.
631
+ *
632
+ * Plugins can implement this method to output additional HTML after the next steps are printed.
633
+ *
634
+ * @since 5.2.2
635
+ */
636
+ protected function render_after_next_steps() {
637
+ // stub method
638
+ }
639
+
640
+
641
+ /**
642
+ * Renders the next steps.
643
+ *
644
+ * @since 5.2.2
645
+ */
646
+ protected function render_next_steps() {
647
+
648
+ $next_steps = $this->get_next_steps();
649
+ $additional_actions = $this->get_additional_actions();
650
+
651
+ if ( ! empty( $next_steps ) || ! empty( $additional_actions ) ) :
652
+
653
+ ?>
654
+ <ul class="wc-wizard-next-steps">
655
+
656
+ <?php foreach ( $next_steps as $step ) : ?>
657
+
658
+ <li class="wc-wizard-next-step-item">
659
+ <div class="wc-wizard-next-step-description">
660
+
661
+ <p class="next-step-heading"><?php esc_html_e( 'Next step', 'woocommerce-plugin-framework' ); ?></p>
662
+ <h3 class="next-step-description"><?php echo esc_html( $step['label'] ); ?></h3>
663
+
664
+ <?php if ( ! empty( $step['description'] ) ) : ?>
665
+ <p class="next-step-extra-info"><?php echo esc_html( $step['description'] ); ?></p>
666
+ <?php endif; ?>
667
+
668
+ </div>
669
+
670
+ <div class="wc-wizard-next-step-action">
671
+ <p class="wc-setup-actions step">
672
+ <?php $button_class = isset( $step['button_class'] ) ? $step['button_class'] : 'button button-primary button-large'; ?>
673
+ <?php $button_class = is_string( $button_class ) || is_array( $button_class ) ? array_map( 'sanitize_html_class', explode( ' ', implode( ' ', (array) $button_class ) ) ) : ''; ?>
674
+ <a class="<?php echo implode( ' ', $button_class ); ?>" href="<?php echo esc_url( $step['url'] ); ?>">
675
+ <?php echo esc_html( $step['name'] ); ?>
676
+ </a>
677
+ </p>
678
+ </div>
679
+ </li>
680
+
681
+ <?php endforeach; ?>
682
+
683
+ <?php if ( ! empty( $additional_actions ) ) : ?>
684
+
685
+ <li class="wc-wizard-additional-steps">
686
+ <div class="wc-wizard-next-step-description">
687
+ <p class="next-step-heading"><?php esc_html_e( 'You can also:', 'woocommerce-plugin-framework' ); ?></p>
688
+ </div>
689
+ <div class="wc-wizard-next-step-action">
690
+
691
+ <p class="wc-setup-actions step">
692
+
693
+ <?php foreach ( $additional_actions as $name => $url ) : ?>
694
+
695
+ <a class="button button-large" href="<?php echo esc_url( $url ); ?>">
696
+ <?php echo esc_html( $name ); ?>
697
+ </a>
698
+
699
+ <?php endforeach; ?>
700
+
701
+ </p>
702
+ </div>
703
+ </li>
704
+
705
+ <?php endif; ?>
706
+
707
+ </ul>
708
+ <?php
709
+
710
+ endif;
711
+ }
712
+
713
+
714
+ /**
715
+ * Gets the next steps.
716
+ *
717
+ * These are major actions a user can take after finishing the setup wizard.
718
+ * For instance, things like "Create your first Add-On" could go here.
719
+ *
720
+ * @since 5.2.2
721
+ *
722
+ * @return array
723
+ */
724
+ protected function get_next_steps() {
725
+
726
+ $steps = array();
727
+
728
+ if ( $this->get_plugin()->get_documentation_url() ) {
729
+
730
+ $steps['view-docs'] = array(
731
+ 'name' => __( 'View the Docs', 'woocommerce-plugin-framework' ),
732
+ 'label' => __( 'See more setup options', 'woocommerce-plugin-framework' ),
733
+ 'description' => __( 'Learn more about customizing the plugin', 'woocommerce-plugin-framework' ),
734
+ 'url' => $this->get_plugin()->get_documentation_url(),
735
+ );
736
+ }
737
+
738
+ return $steps;
739
+ }
740
+
741
+
742
+ /**
743
+ * Gets the additional steps.
744
+ *
745
+ * These are secondary actions.
746
+ *
747
+ * @since 5.2.2
748
+ *
749
+ * @return array
750
+ */
751
+ protected function get_additional_actions() {
752
+
753
+ $next_steps = $this->get_next_steps();
754
+ $actions = array();
755
+
756
+ if ( $this->get_plugin()->get_settings_url() ) {
757
+ $actions[ __( 'Review Your Settings', 'woocommerce-plugin-framework' ) ] = $this->get_plugin()->get_settings_url();
758
+ }
759
+
760
+ if ( empty( $next_steps['view-docs'] ) && $this->get_plugin()->get_documentation_url() ) {
761
+ $actions[ __( 'View the Docs', 'woocommerce-plugin-framework' ) ] = $this->get_plugin()->get_documentation_url();
762
+ }
763
+
764
+ if ( $this->get_plugin()->get_reviews_url() ) {
765
+ $actions[ __( 'Leave a Review', 'woocommerce-plugin-framework' ) ] = $this->get_plugin()->get_reviews_url();
766
+ }
767
+
768
+ return $actions;
769
+ }
770
+
771
+
772
+ /**
773
+ * Renders a given step's markup.
774
+ *
775
+ * This will display a title, whatever get's rendered by the step's view
776
+ * callback, then the navigation buttons.
777
+ *
778
+ * @since 5.2.2
779
+ *
780
+ * @param string $step_id step ID to render
781
+ */
782
+ protected function render_step( $step_id ) {
783
+
784
+ call_user_func( $this->steps[ $step_id ]['view'], $this );
785
+
786
+ ?>
787
+ <p class="wc-setup-actions step">
788
+
789
+ <?php $label = __( 'Continue', 'woocommerce-plugin-framework' ); ?>
790
+
791
+ <?php if ( is_callable( $this->steps[ $step_id ]['save'] ) ) : ?>
792
+
793
+ <button
794
+ type="submit"
795
+ name="save_step"
796
+ class="button-primary button button-large button-next"
797
+ value="<?php echo esc_attr( $label ); ?>">
798
+ <?php echo esc_html( $label ); ?>
799
+ </button>
800
+
801
+ <?php else : ?>
802
+
803
+ <a class="button-primary button button-large button-next" href="<?php echo esc_url( $this->get_next_step_url( $step_id ) ); ?>"><?php echo esc_html( $label ); ?></a>
804
+
805
+ <?php endif; ?>
806
+ </p>
807
+ <?php
808
+ }
809
+
810
+
811
+ /**
812
+ * Renders a form field.
813
+ *
814
+ * Call this in the same way as woocommerce_form_field().
815
+ *
816
+ * @since 5.2.2
817
+ *
818
+ * @param string $key field key
819
+ * @param array $args field args - @see woocommerce_form_field()
820
+ * @param string|null $value field value
821
+ */
822
+ protected function render_form_field( $key, $args, $value = null ) {
823
+
824
+ if ( ! isset( $args['class'] ) ) {
825
+ $args['class'] = array();
826
+ } else {
827
+ $args['class'] = (array) $args['class'];
828
+ }
829
+
830
+ // the base wrapper class for our styling
831
+ $args['class'][] = 'sv-wc-plugin-admin-setup-control';
832
+
833
+ // add the "required" HTML attribute for browser form validation
834
+ if ( ! empty( $args['required'] ) ) {
835
+ $args['custom_attributes']['required'] = true;
836
+ }
837
+
838
+ // all dropdowns are treated as enhanced selects
839
+ if ( isset( $args['type'] ) && 'select' === $args['type'] ) {
840
+ $args['input_class'][] = 'wc-enhanced-select';
841
+ }
842
+
843
+ // always echo the field
844
+ $args['return'] = false;
845
+
846
+ if ( isset( $args['type'] ) && 'toggle' === $args['type'] ) {
847
+ $this->render_toggle_form_field( $key, $args, $value );
848
+ } else {
849
+ woocommerce_form_field( $key, $args, $value );
850
+ }
851
+ }
852
+
853
+
854
+ /**
855
+ * Renders the toggle form field.
856
+ *
857
+ * This requires special markup for the toggle UI.
858
+ *
859
+ * @since 5.2.2
860
+ *
861
+ * @param string $key field key
862
+ * @param array $args field args - @see woocommerce_form_field()
863
+ * @param string|null $value field value
864
+ */
865
+ public function render_toggle_form_field( $key, $args, $value ) {
866
+
867
+ $args = wp_parse_args( $args, array(
868
+ 'type' => 'text',
869
+ 'label' => '',
870
+ 'description' => '',
871
+ 'required' => false,
872
+ 'id' => $key,
873
+ 'class' => array(),
874
+ 'label_class' => array(),
875
+ 'input_class' => array(),
876
+ 'custom_attributes' => array(),
877
+ 'default' => false,
878
+ 'allow_html' => false,
879
+ ) );
880
+
881
+ $args['class'][] = 'toggle';
882
+
883
+ if ( $args['required'] ) {
884
+ $args['class'][] = 'validate-required';
885
+ }
886
+
887
+ if ( null === $value ) {
888
+ $value = $args['default'];
889
+ }
890
+
891
+ $custom_attributes = array();
892
+ $args['custom_attributes'] = array_filter( (array) $args['custom_attributes'], 'strlen' );
893
+
894
+ if ( $args['description'] ) {
895
+ $args['custom_attributes']['aria-describedby'] = $args['id'] . '-description';
896
+ }
897
+
898
+ if ( ! empty( $args['custom_attributes'] ) && is_array( $args['custom_attributes'] ) ) {
899
+ foreach ( $args['custom_attributes'] as $attribute => $attribute_value ) {
900
+ $custom_attributes[] = esc_attr( $attribute ) . '="' . esc_attr( $attribute_value ) . '"';
901
+ }
902
+ }
903
+
904
+ $enabled = $value || $args['default'];
905
+
906
+ if ( $enabled ) {
907
+ $args['class'][] = 'enabled';
908
+ }
909
+
910
+ ?>
911
+ <div class="form-row <?php echo esc_attr( implode( ' ', $args['class'] ) ); ?>">
912
+
913
+ <p class="name"><?php echo true === $args['allow_html'] ? $args['label'] : esc_html( $args['label'] ); ?></p>
914
+
915
+ <?php if ( true === $args['allow_html'] ) : ?>
916
+ <div class="content"><p class="description"><?php echo $args['description']; ?></p></div>
917
+ <?php else : ?>
918
+ <p class="content description"><?php echo esc_html( $args['description'] ); ?></p>
919
+ <?php endif; ?>
920
+
921
+ <div class="enable">
922
+ <span class="toggle <?php echo $enabled ? '' : 'disabled'; ?>">
923
+ <input
924
+ id="<?php echo esc_attr( $args['id'] ); ?>"
925
+ type="checkbox"
926
+ class="input-checkbox <?php echo esc_attr( implode( ' ', $args['input_class'] ) ); ?>"
927
+ name="<?php echo esc_attr( $key ); ?>"
928
+ value="yes" <?php checked( true, $value ); ?>
929
+ <?php echo implode( ' ', $custom_attributes ); ?>
930
+ />
931
+ <label for="<?php echo esc_attr( $args['id'] ); ?>" class="<?php implode( ' ', (array) $args['label_class'] ); ?>">
932
+ </span>
933
+ </div>
934
+
935
+ </div>
936
+ <?php
937
+ }
938
+
939
+
940
+ /**
941
+ * Renders the setup footer.
942
+ *
943
+ * @since 5.2.2
944
+ */
945
+ protected function render_footer() {
946
+
947
+ ?>
948
+ <?php if ( $this->is_finished() ) : ?>
949
+ <a class="wc-setup-footer-links" href="<?php echo esc_url( $this->get_dashboard_url() ); ?>"><?php esc_html_e( 'Return to the WordPress Dashboard', 'woocommerce-plugin-framework' ); ?></a>
950
+ <?php elseif ( $this->is_started() ) : ?>
951
+ <a class="wc-setup-footer-links" href="<?php echo esc_url( $this->get_dashboard_url() ); ?>"><?php esc_html_e( 'Not right now', 'woocommerce-plugin-framework' ); ?></a>
952
+ <?php else : ?>
953
+ <a class="wc-setup-footer-links" href="<?php echo esc_url( $this->get_next_step_url() ); ?>"><?php esc_html_e( 'Skip this step', 'woocommerce-plugin-framework' ); ?></a>
954
+ <?php endif; ?>
955
+ <?php
956
+
957
+ do_action( 'wp_print_footer_scripts' );
958
+ }
959
+
960
+
961
+ /** Helper Methods ************************************************************************************************/
962
+
963
+
964
+ /**
965
+ * Registers a step.
966
+ *
967
+ * @since 5.2.2
968
+ *
969
+ * @param string $id unique step ID
970
+ * @param string $name step name for display
971
+ * @param string|array $view_callback callback to render the step's content HTML
972
+ * @param string|array|null $save_callback callback to save the step's form values
973
+ * @return bool whether the step was successfully added
974
+ */
975
+ public function register_step( $id, $name, $view_callback, $save_callback = null ) {
976
+
977
+ try {
978
+
979
+ // invalid ID
980
+ if ( ! is_string( $id ) || empty( $id ) || $this->has_step( $id ) ) {
981
+ throw new Framework\SV_WC_Plugin_Exception( 'Invalid step ID' );
982
+ }
983
+
984
+ // invalid name
985
+ if ( ! is_string( $name ) || empty( $name ) ) {
986
+ throw new Framework\SV_WC_Plugin_Exception( 'Invalid step name' );
987
+ }
988
+
989
+ // invalid view callback
990
+ if ( ! is_callable( $view_callback ) ) {
991
+ throw new Framework\SV_WC_Plugin_Exception( 'Invalid view callback' );
992
+ }
993
+
994
+ // invalid save callback
995
+ if ( null !== $save_callback && ! is_callable( $save_callback ) ) {
996
+ throw new Framework\SV_WC_Plugin_Exception( 'Invalid save callback' );
997
+ }
998
+
999
+ $this->steps[ $id ] = array(
1000
+ 'name' => $name,
1001
+ 'view' => $view_callback,
1002
+ 'save' => $save_callback,
1003
+ );
1004
+
1005
+ return true;
1006
+
1007
+ } catch ( Framework\SV_WC_Plugin_Exception $exception ) {
1008
+
1009
+ wc_doing_it_wrong( __METHOD__, $exception->getMessage(), '5.2.2' );
1010
+
1011
+ return false;
1012
+ }
1013
+ }
1014
+
1015
+
1016
+ /**
1017
+ * Marks the setup as complete.
1018
+ *
1019
+ * @since 5.2.2
1020
+ *
1021
+ * @return bool
1022
+ */
1023
+ public function complete_setup() {
1024
+
1025
+ return update_option( "wc_{$this->id}_setup_wizard_complete", 'yes' );
1026
+ }
1027
+
1028
+
1029
+ /** Conditional Methods *******************************************************************************************/
1030
+
1031
+
1032
+ /**
1033
+ * Determines if the current page is the setup wizard page.
1034
+ *
1035
+ * @since 5.2.2
1036
+ *
1037
+ * @return bool
1038
+ */
1039
+ public function is_setup_page() {
1040
+
1041
+ return is_admin() && $this->get_slug() === Framework\SV_WC_Helper::get_requested_value( 'page' );
1042
+ }
1043
+
1044
+
1045
+ /**
1046
+ * Determines if a step is the current one displayed.
1047
+ *
1048
+ * @since 5.2.2
1049
+ *
1050
+ * @param string $step_id step ID
1051
+ * @return bool
1052
+ */
1053
+ public function is_current_step( $step_id ) {
1054
+
1055
+ return $this->current_step === $step_id;
1056
+ }
1057
+
1058
+
1059
+ /**
1060
+ * Determines if setup has started.
1061
+ *
1062
+ * @since 5.2.2
1063
+ *
1064
+ * @return bool
1065
+ */
1066
+ public function is_started() {
1067
+
1068
+ $steps = array_keys( $this->steps );
1069
+
1070
+ return $this->current_step && $this->current_step === reset( $steps );
1071
+ }
1072
+
1073
+
1074
+ /**
1075
+ * Determines if setup has completed all of the steps.
1076
+ *
1077
+ * @since 5.2.2
1078
+ *
1079
+ * @return bool
1080
+ */
1081
+ public function is_finished() {
1082
+
1083
+ return self::ACTION_FINISH === Framework\SV_WC_Helper::get_requested_value( 'action' );
1084
+ }
1085
+
1086
+
1087
+ /**
1088
+ * Determines if the setup wizard has been completed.
1089
+ *
1090
+ * This will be true if any user has been redirected back to the regular
1091
+ * WordPress dashboard, either manually or after finishing the steps.
1092
+ *
1093
+ * @since 5.2.2
1094
+ *
1095
+ * @return bool
1096
+ */
1097
+ public function is_complete() {
1098
+
1099
+ return 'yes' === get_option( "wc_{$this->id}_setup_wizard_complete", 'no' );
1100
+ }
1101
+
1102
+
1103
+ /**
1104
+ * Determines if the given step has been completed.
1105
+ *
1106
+ * @since 5.2.2
1107
+ *
1108
+ * @param string $step_id step ID to check
1109
+ * @return bool
1110
+ */
1111
+ public function is_step_complete( $step_id ) {
1112
+
1113
+ return array_search( $this->current_step, array_keys( $this->steps ), true ) > array_search( $step_id, array_keys( $this->steps ), true ) || $this->is_finished();
1114
+ }
1115
+
1116
+
1117
+ /**
1118
+ * Determines if the wizard has steps to display.
1119
+ *
1120
+ * @since 5.2.2
1121
+ *
1122
+ * @return bool
1123
+ */
1124
+ public function has_steps() {
1125
+
1126
+ return is_array( $this->steps ) && ! empty( $this->steps );
1127
+ }
1128
+
1129
+
1130
+ /**
1131
+ * Determines if this setup handler has a given step.
1132
+ *
1133
+ * @since 5.2.2
1134
+ *
1135
+ * @param string $step_id step ID to check
1136
+ * @return bool
1137
+ */
1138
+ public function has_step( $step_id ) {
1139
+
1140
+ return ! empty( $this->steps[ $step_id ] );
1141
+ }
1142
+
1143
+
1144
+ /** Getter Methods ************************************************************************************************/
1145
+
1146
+
1147
+ /**
1148
+ * Gets a given step's title.
1149
+ *
1150
+ * @since 5.2.2
1151
+ *
1152
+ * @param string $step_id step ID (optional: will assume the current step if unspecified)
1153
+ * @return string
1154
+ */
1155
+ public function get_step_title( $step_id = '' ) {
1156
+
1157
+ $step_title = '';
1158
+
1159
+ if ( ! $step_id ) {
1160
+ $step_id = $this->current_step;
1161
+ }
1162
+
1163
+ if ( isset( $this->steps[ $step_id ]['name'] ) ) {
1164
+ $step_title = $this->steps[ $step_id ]['name'];
1165
+ }
1166
+
1167
+ return $step_title;
1168
+ }
1169
+
1170
+
1171
+ /**
1172
+ * Gets the Setup Wizard URL.
1173
+ *
1174
+ * @since 5.2.2
1175
+ *
1176
+ * @return string
1177
+ */
1178
+ public function get_setup_url() {
1179
+
1180
+ return add_query_arg( 'page', $this->get_slug(), admin_url( 'index.php' ) );
1181
+ }
1182
+
1183
+
1184
+ /**
1185
+ * Gets the URL for the next step based on a current step.
1186
+ *
1187
+ * @since 5.2.2
1188
+ *
1189
+ * @param string $step_id step ID to base "next" off of - defaults to this class's internal pointer
1190
+ * @return string
1191
+ */
1192
+ public function get_next_step_url( $step_id = '' ) {
1193
+
1194
+ if ( ! $step_id ) {
1195
+ $step_id = $this->current_step;
1196
+ }
1197
+
1198
+ $steps = array_keys( $this->steps );
1199
+
1200
+ // if on the last step, next is the final finish step
1201
+ if ( end( $steps ) === $step_id ) {
1202
+
1203
+ $url = $this->get_finish_url();
1204
+
1205
+ } else {
1206
+
1207
+ $step_index = array_search( $step_id, $steps, true );
1208
+
1209
+ // if the current step is found, use the next in the array. otherwise, the first
1210
+ $step = false !== $step_index ? $steps[ $step_index + 1 ] : reset( $steps );
1211
+
1212
+ $url = add_query_arg( 'step', $step );
1213
+ }
1214
+
1215
+ return $url;
1216
+ }
1217
+
1218
+
1219
+ /**
1220
+ * Gets a given step's URL.
1221
+ *
1222
+ * @since 5.2.2
1223
+ *
1224
+ * @param string $step_id step ID
1225
+ * @return string|false
1226
+ */
1227
+ public function get_step_url( $step_id ) {
1228
+
1229
+ $url = false;
1230
+
1231
+ if ( $this->has_step( $step_id ) ) {
1232
+ $url = add_query_arg( 'step', $step_id, remove_query_arg( 'action' ) );
1233
+ }
1234
+
1235
+ return $url;
1236
+ }
1237
+
1238
+
1239
+ /**
1240
+ * Gets the "finish" action URL.
1241
+ *
1242
+ * @since 5.2.2
1243
+ *
1244
+ * @return string
1245
+ */
1246
+ protected function get_finish_url() {
1247
+
1248
+ return add_query_arg( 'action', self::ACTION_FINISH, remove_query_arg( 'step' ) );
1249
+ }
1250
+
1251
+
1252
+ /**
1253
+ * Gets the return URL.
1254
+ *
1255
+ * Can be used to return the user to the dashboard. The plugin's settings URL
1256
+ * will be used if it exists, otherwise the general dashboard URL.
1257
+ *
1258
+ * @since 5.2.2
1259
+ *
1260
+ * @return string
1261
+ */
1262
+ protected function get_dashboard_url() {
1263
+
1264
+ $settings_url = $this->get_plugin()->get_settings_url();
1265
+ $dashboard_url = ! empty( $settings_url ) ? $settings_url : admin_url();
1266
+
1267
+ return add_query_arg( "wc_{$this->id}_setup_wizard_complete", true, $dashboard_url );
1268
+ }
1269
+
1270
+
1271
+ /**
1272
+ * Gets the setup setup handler's slug.
1273
+ *
1274
+ * @since 5.2.2
1275
+ *
1276
+ * @return string
1277
+ */
1278
+ protected function get_slug() {
1279
+
1280
+ return 'wc-' . $this->get_plugin()->get_id_dasherized() . '-setup';
1281
+ }
1282
+
1283
+
1284
+ /**
1285
+ * Gets the plugin instance.
1286
+ *
1287
+ * @since 5.2.2
1288
+ *
1289
+ * @return Framework\SV_WC_Plugin|Framework\SV_WC_Payment_Gateway_Plugin
1290
+ */
1291
+ protected function get_plugin() {
1292
+
1293
+ return $this->plugin;
1294
+ }
1295
+
1296
+
1297
+ }
1298
+
1299
+
1300
+ endif;
vendor/skyverge/wc-plugin-framework/woocommerce/api/abstract-sv-wc-api-json-request.php ADDED
@@ -0,0 +1,135 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * WooCommerce Plugin Framework
4
+ *
5
+ * This source file is subject to the GNU General Public License v3.0
6
+ * that is bundled with this package in the file license.txt.
7
+ * It is also available through the world-wide-web at this URL:
8
+ * http://www.gnu.org/licenses/gpl-3.0.html
9
+ * If you did not receive a copy of the license and are unable to
10
+ * obtain it through the world-wide-web, please send an email
11
+ * to license@skyverge.com so we can send you a copy immediately.
12
+ *
13
+ * DISCLAIMER
14
+ *
15
+ * Do not edit or add to this file if you wish to upgrade the plugin to newer
16
+ * versions in the future. If you wish to customize the plugin for your
17
+ * needs please refer to http://www.skyverge.com
18
+ *
19
+ * @package SkyVerge/WooCommerce/API/Request
20
+ * @author SkyVerge
21
+ * @copyright Copyright (c) 2013-2020, SkyVerge, Inc.
22
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GNU General Public License v3.0
23
+ */
24
+
25
+ namespace SkyVerge\WooCommerce\PluginFramework\v5_5_4;
26
+
27
+ defined( 'ABSPATH' ) or exit;
28
+
29
+ if ( ! class_exists( '\\SkyVerge\\WooCommerce\\PluginFramework\\v5_5_4\\SV_WC_API_JSON_Request' ) ) :
30
+
31
+
32
+ /**
33
+ * Base JSON API request class.
34
+ *
35
+ * @since 4.3.0
36
+ */
37
+ abstract class SV_WC_API_JSON_Request implements SV_WC_API_Request {
38
+
39
+
40
+ /** @var string The request method, one of HEAD, GET, PUT, PATCH, POST, DELETE */
41
+ protected $method;
42
+
43
+ /** @var string The request path */
44
+ protected $path;
45
+
46
+ /** @var array The request parameters, if any */
47
+ protected $params = array();
48
+
49
+ /** @var array the request data */
50
+ protected $data = array();
51
+
52
+
53
+ /**
54
+ * Get the request method.
55
+ *
56
+ * @since 4.3.0
57
+ * @see SV_WC_API_Request::get_method()
58
+ * @return string
59
+ */
60
+ public function get_method() {
61
+ return $this->method;
62
+ }
63
+
64
+
65
+ /**
66
+ * Get the request path.
67
+ *
68
+ * @since 4.3.0
69
+ * @see SV_WC_API_Request::get_path()
70
+ * @return string
71
+ */
72
+ public function get_path() {
73
+ return $this->path;
74
+ }
75
+
76
+
77
+ /**
78
+ * Get the request parameters.
79
+ *
80
+ * @since 4.3.0
81
+ * @see SV_WC_API_Request::get_params()
82
+ * @return array
83
+ */
84
+ public function get_params() {
85
+ return $this->params;
86
+ }
87
+
88
+
89
+ /**
90
+ * Get the request data.
91
+ *
92
+ * @since 4.5.0
93
+ * @return array
94
+ */
95
+ public function get_data() {
96
+ return $this->data;
97
+ }
98
+
99
+
100
+ /** API Helper Methods ******************************************************/
101
+
102
+
103
+ /**
104
+ * Get the string representation of this request.
105
+ *
106
+ * @since 4.3.0
107
+ * @see SV_WC_API_Request::to_string()
108
+ * @return string
109
+ */
110
+ public function to_string() {
111
+
112
+ $data = $this->get_data();
113
+
114
+ return ! empty( $data ) ? json_encode( $data ) : '';
115
+ }
116
+
117
+
118
+ /**
119
+ * Get the string representation of this request with any and all sensitive elements masked
120
+ * or removed.
121
+ *
122
+ * @since 4.3.0
123
+ * @see SV_WC_API_Request::to_string_safe()
124
+ * @return string
125
+ */
126
+ public function to_string_safe() {
127
+
128
+ return $this->to_string();
129
+ }
130
+
131
+
132
+ }
133
+
134
+
135
+ endif;
vendor/skyverge/wc-plugin-framework/woocommerce/api/abstract-sv-wc-api-json-response.php ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * WooCommerce Plugin Framework
4
+ *
5
+ * This source file is subject to the GNU General Public License v3.0
6
+ * that is bundled with this package in the file license.txt.
7
+ * It is also available through the world-wide-web at this URL:
8
+ * http://www.gnu.org/licenses/gpl-3.0.html
9
+ * If you did not receive a copy of the license and are unable to
10
+ * obtain it through the world-wide-web, please send an email
11
+ * to license@skyverge.com so we can send you a copy immediately.
12
+ *
13
+ * DISCLAIMER
14
+ *
15
+ * Do not edit or add to this file if you wish to upgrade the plugin to newer
16
+ * versions in the future. If you wish to customize the plugin for your
17
+ * needs please refer to http://www.skyverge.com
18
+ *
19
+ * @package SkyVerge/WooCommerce/API/Response
20
+ * @author SkyVerge
21
+ * @copyright Copyright (c) 2013-2020, SkyVerge, Inc.
22
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GNU General Public License v3.0
23
+ */
24
+
25
+ namespace SkyVerge\WooCommerce\PluginFramework\v5_5_4;
26
+
27
+ defined( 'ABSPATH' ) or exit;
28
+
29
+ if ( ! class_exists( '\\SkyVerge\\WooCommerce\\PluginFramework\\v5_5_4\\SV_WC_API_JSON_Response' ) ) :
30
+
31
+
32
+ /**
33
+ * Base JSON API response class.
34
+ *
35
+ * @since 4.3.0
36
+ */
37
+ abstract class SV_WC_API_JSON_Response implements SV_WC_API_Response {
38
+
39
+
40
+ /** @var string string representation of this response */
41
+ protected $raw_response_json;
42
+
43
+ /** @var mixed decoded response data */
44
+ public $response_data;
45
+
46
+
47
+ /**
48
+ * Build the data object from the raw JSON.
49
+ *
50
+ * @since 4.3.0
51
+ * @param string $raw_response_json The raw JSON
52
+ */
53
+ public function __construct( $raw_response_json ) {
54
+
55
+ $this->raw_response_json = $raw_response_json;
56
+
57
+ $this->response_data = json_decode( $raw_response_json );
58
+ }
59
+
60
+
61
+ /**
62
+ * Magic accessor for response data attributes
63
+ *
64
+ * @since 4.3.0
65
+ * @param string $name The attribute name to get.
66
+ * @return mixed The attribute value
67
+ */
68
+ public function __get( $name ) {
69
+
70
+ // accessing the response_data object indirectly via attribute (useful when it's a class)
71
+ return isset( $this->response_data->$name ) ? $this->response_data->$name : null;
72
+ }
73
+
74
+
75
+ /**
76
+ * Get the string representation of this response.
77
+ *
78
+ * @since 4.3.0
79
+ * @see SV_WC_API_Response::to_string()
80
+ * @return string
81
+ */
82
+ public function to_string() {
83
+
84
+ return $this->raw_response_json;
85
+ }
86
+
87
+
88
+ /**
89
+ * Get the string representation of this response with any and all sensitive elements masked
90
+ * or removed.
91
+ *
92
+ * @since 4.3.0
93
+ * @see SV_WC_API_Response::to_string_safe()
94
+ * @return string
95
+ */
96
+ public function to_string_safe() {
97
+
98
+ return $this->to_string();
99
+ }
100
+
101
+
102
+ }
103
+
104
+
105
+ endif;
vendor/skyverge/wc-plugin-framework/woocommerce/api/abstract-sv-wc-api-xml-request.php ADDED
@@ -0,0 +1,216 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * WooCommerce Plugin Framework
4
+ *
5
+ * This source file is subject to the GNU General Public License v3.0
6
+ * that is bundled with this package in the file license.txt.
7
+ * It is also available through the world-wide-web at this URL:
8
+ * http://www.gnu.org/licenses/gpl-3.0.html
9
+ * If you did not receive a copy of the license and are unable to
10
+ * obtain it through the world-wide-web, please send an email
11
+ * to license@skyverge.com so we can send you a copy immediately.
12
+ *
13
+ * DISCLAIMER
14
+ *
15
+ * Do not edit or add to this file if you wish to upgrade the plugin to newer
16
+ * versions in the future. If you wish to customize the plugin for your
17
+ * needs please refer to http://www.skyverge.com
18
+ *
19
+ * @package SkyVerge/WooCommerce/API/Request
20
+ * @author SkyVerge
21
+ * @copyright Copyright (c) 2013-2020, SkyVerge, Inc.
22
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GNU General Public License v3.0
23
+ */
24
+
25
+ namespace SkyVerge\WooCommerce\PluginFramework\v5_5_4;
26
+
27
+ defined( 'ABSPATH' ) or exit;
28
+
29
+ if ( ! class_exists( '\\SkyVerge\\WooCommerce\\PluginFramework\\v5_5_4\\SV_WC_API_XML_Request' ) ) :
30
+
31
+
32
+ /**
33
+ * Base XML API request class.
34
+ *
35
+ * @since 4.3.0
36
+ */
37
+ abstract class SV_WC_API_XML_Request implements SV_WC_API_Request {
38
+
39
+
40
+ /** @var string the request method, one of HEAD, GET, PUT, PATCH, POST, DELETE */
41
+ protected $method;
42
+
43
+ /** @var string the request path */
44
+ protected $path = '';
45
+
46
+ /** @var array the request parameters */
47
+ protected $params = array();
48
+
49
+ /** @var array request data */
50
+ protected $request_data;
51
+
52
+ /** @var string root element for XML */
53
+ protected $root_element;
54
+
55
+ /** @var \XMLWriter $xml object */
56
+ protected $xml;
57
+
58
+ /** @var string complete request XML */
59
+ protected $request_xml;
60
+
61
+
62
+ /**
63
+ * Get the method for this request.
64
+ *
65
+ * @since 4.3.0
66
+ * @see SV_WC_API_Request::get_method()
67
+ * @return null|string
68
+ */
69
+ public function get_method() {
70
+ return $this->method;
71
+ }
72
+
73
+
74
+ /**
75
+ * Get the path for this request.
76
+ *
77
+ * @since 4.3.0
78
+ * @see SV_WC_API_Request::get_path()
79
+ * @return string
80
+ */
81
+ public function get_path() {
82
+ return $this->path;
83
+ }
84
+
85
+
86
+ /**
87
+ * Get the request parameters.
88
+ *
89
+ * @since 4.5.0
90
+ * @return array
91
+ */
92
+ public function get_params() {
93
+ return $this->params;
94
+ }
95
+
96
+
97
+ /**
98
+ * Convert the request data into XML.
99
+ *
100
+ * @since 4.3.0
101
+ * @return string
102
+ */
103
+ protected function to_xml() {
104
+
105
+ if ( ! empty( $this->request_xml ) ) {
106
+ return $this->request_xml;
107
+ }
108
+
109
+ $this->xml = new \XMLWriter();
110
+
111
+ // Create XML document in memory
112
+ $this->xml->openMemory();
113
+
114
+ // Set XML version & encoding
115
+ $this->xml->startDocument( '1.0', 'UTF-8' );
116
+
117
+ $request_data = $this->get_data();
118
+
119
+ SV_WC_Helper::array_to_xml( $this->xml, $this->get_root_element(), $request_data[ $this->get_root_element() ] );
120
+
121
+ $this->xml->endDocument();
122
+
123
+ return $this->request_xml = $this->xml->outputMemory();
124
+ }
125
+
126
+
127
+ /**
128
+ * Gets the request data to be converted to XML.
129
+ *
130
+ * @since 4.3.0
131
+ * @deprecated 5.0.0
132
+ *
133
+ * @return array
134
+ */
135
+ public function get_request_data() {
136
+
137
+ wc_deprecated_function( __METHOD__, '5.0.0', 'SV_WC_API_XML_Request::get_data' );
138
+
139
+ return $this->get_data();
140
+ }
141
+
142
+
143
+ /**
144
+ * Gets the request data to be converted to XML.
145
+ *
146
+ * @since 5.0.0
147
+ * @return array
148
+ */
149
+ public function get_data() {
150
+
151
+ return $this->request_data;
152
+ }
153
+
154
+
155
+ /**
156
+ * Get the string representation of this request
157
+ *
158
+ * @since 4.3.0
159
+ * @see SV_WC_API_Request::to_string()
160
+ * @return string
161
+ */
162
+ public function to_string() {
163
+
164
+ return $this->to_xml();
165
+ }
166
+
167
+
168
+ /**
169
+ * Get the string representation of this request with any and all sensitive elements masked
170
+ * or removed.
171
+ *
172
+ * @since 4.3.0
173
+ * @see SV_WC_API_Request::to_string_safe()
174
+ * @return string
175
+ */
176
+ public function to_string_safe() {
177
+
178
+ return $this->prettify_xml( $this->to_string() );
179
+ }
180
+
181
+
182
+ /**
183
+ * Helper method for making XML pretty, suitable for logging or rendering
184
+ *
185
+ * @since 4.3.0
186
+ * @param string $xml_string ugly XML string
187
+ * @return string
188
+ */
189
+ public function prettify_xml( $xml_string ) {
190
+
191
+ $dom = new \DOMDocument();
192
+
193
+ // suppress errors for invalid XML syntax issues
194
+ if ( @$dom->loadXML( $xml_string ) ) {
195
+ $dom->formatOutput = true;
196
+ $xml_string = $dom->saveXML();
197
+ }
198
+
199
+ return $xml_string;
200
+ }
201
+
202
+
203
+ /**
204
+ * Concrete classes must implement this method to return the root element
205
+ * for the XML document
206
+ *
207
+ * @since 4.3.0
208
+ * @return string
209
+ */
210
+ abstract protected function get_root_element();
211
+
212
+
213
+ }
214
+
215
+
216
+ endif;
vendor/skyverge/wc-plugin-framework/woocommerce/api/abstract-sv-wc-api-xml-response.php ADDED
@@ -0,0 +1,138 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * WooCommerce Plugin Framework
4
+ *
5
+ * This source file is subject to the GNU General Public License v3.0
6
+ * that is bundled with this package in the file license.txt.
7
+ * It is also available through the world-wide-web at this URL:
8
+ * http://www.gnu.org/licenses/gpl-3.0.html
9
+ * If you did not receive a copy of the license and are unable to
10
+ * obtain it through the world-wide-web, please send an email
11
+ * to license@skyverge.com so we can send you a copy immediately.
12
+ *
13
+ * DISCLAIMER
14
+ *
15
+ * Do not edit or add to this file if you wish to upgrade the plugin to newer
16
+ * versions in the future. If you wish to customize the plugin for your
17
+ * needs please refer to http://www.skyverge.com
18
+ *
19
+ * @package SkyVerge/WooCommerce/API/Response
20
+ * @author SkyVerge
21
+ * @copyright Copyright (c) 2013-2020, SkyVerge, Inc.
22
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GNU General Public License v3.0
23
+ */
24
+
25
+ namespace SkyVerge\WooCommerce\PluginFramework\v5_5_4;
26
+
27
+ defined( 'ABSPATH' ) or exit;
28
+
29
+ if ( ! class_exists( '\\SkyVerge\\WooCommerce\\PluginFramework\\v5_5_4\\SV_WC_API_XML_Response' ) ) :
30
+
31
+
32
+ /**
33
+ * Base XML API response class.
34
+ *
35
+ * @since 4.3.0
36
+ */
37
+ abstract class SV_WC_API_XML_Response implements SV_WC_API_Response {
38
+
39
+
40
+ /** @var string string representation of this response */
41
+ protected $raw_response_xml;
42
+
43
+ /** @var \SimpleXMLElement XML object */
44
+ protected $response_xml;
45
+
46
+ /** @var array|mixed|object XML data after conversion into an usable object */
47
+ protected $response_data;
48
+
49
+
50
+ /**
51
+ * Build an XML object from the raw response.
52
+ *
53
+ * @since 4.3.0
54
+ * @param string $raw_response_xml The raw response XML
55
+ */
56
+ public function __construct( $raw_response_xml ) {
57
+
58
+ $this->raw_response_xml = $raw_response_xml;
59
+
60
+ // LIBXML_NOCDATA ensures that any XML fields wrapped in [CDATA] will be included as text nodes
61
+ $this->response_xml = new \SimpleXMLElement( $raw_response_xml, LIBXML_NOCDATA );
62
+
63
+ /**
64
+ * workaround to convert the horrible data structure that SimpleXMLElement returns
65
+ * and provide a nice array of stdClass objects. Note there is some fidelity lost
66
+ * in the conversion (such as attributes), but implementing classes can access
67
+ * the response_xml member directly to retrieve them as needed.
68
+ */
69
+ $this->response_data = json_decode( json_encode( $this->response_xml ) );
70
+ }
71
+
72
+
73
+ /**
74
+ * Magic method for getting XML element data. Note the response data has
75
+ * already been casted into simple data types (string,int,array) and does not
76
+ * require further casting in order to use.
77
+ *
78
+ * @since 4.3.0
79
+ * @param string $key
80
+ * @return mixed
81
+ */
82
+ public function __get( $key ) {
83
+
84
+ if ( ! isset( $this->response_data->$key ) ) {
85
+ return null;
86
+ }
87
+
88
+ // array cast & empty check prevents fataling on empty stdClass objects
89
+ $array = (array) $this->response_data->$key;
90
+
91
+ if ( empty( $array ) ) {
92
+ return null;
93
+ }
94
+
95
+ return $this->response_data->$key;
96
+ }
97
+
98
+
99
+ /**
100
+ * Get the string representation of this response.
101
+ *
102
+ * @since 4.3.0
103
+ * @return string
104
+ */
105
+ public function to_string() {
106
+
107
+ $response = $this->raw_response_xml;
108
+
109
+ $dom = new \DOMDocument();
110
+
111
+ // suppress errors for invalid XML syntax issues
112
+ if ( @$dom->loadXML( $response ) ) {
113
+ $dom->formatOutput = true;
114
+ $response = $dom->saveXML();
115
+ }
116
+
117
+ return $response;
118
+ }
119
+
120
+
121
+ /**
122
+ * Get the string representation of this response with any and all sensitive elements masked
123
+ * or removed.
124
+ *
125
+ * @since 4.3.0
126
+ * @see SV_WC_API_Response::to_string_safe()
127
+ * @return string
128
+ */
129
+ public function to_string_safe() {
130
+
131
+ return $this->to_string();
132
+ }
133
+
134
+
135
+ }
136
+
137
+
138
+ endif;
vendor/skyverge/wc-plugin-framework/woocommerce/api/class-sv-wc-api-base.php ADDED
@@ -0,0 +1,836 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * WooCommerce Plugin Framework
4
+ *
5
+ * This source file is subject to the GNU General Public License v3.0
6
+ * that is bundled with this package in the file license.txt.
7
+ * It is also available through the world-wide-web at this URL:
8
+ * http://www.gnu.org/licenses/gpl-3.0.html
9
+ * If you did not receive a copy of the license and are unable to
10
+ * obtain it through the world-wide-web, please send an email
11
+ * to license@skyverge.com so we can send you a copy immediately.
12
+ *
13
+ * DISCLAIMER
14
+ *
15
+ * Do not edit or add to this file if you wish to upgrade the plugin to newer
16
+ * versions in the future. If you wish to customize the plugin for your
17
+ * needs please refer to http://www.skyverge.com
18
+ *
19
+ * @package SkyVerge/WooCommerce/API
20
+ * @author SkyVerge
21
+ * @copyright Copyright (c) 2013-2020, SkyVerge, Inc.
22
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GNU General Public License v3.0
23
+ */
24
+
25
+ namespace SkyVerge\WooCommerce\PluginFramework\v5_5_4;
26
+
27
+ defined( 'ABSPATH' ) or exit;
28
+
29
+ if ( ! class_exists( '\\SkyVerge\\WooCommerce\\PluginFramework\\v5_5_4\\SV_WC_API_Base' ) ) :
30
+
31
+
32
+ /**
33
+ * # WooCommerce Plugin Framework API Base Class
34
+ *
35
+ * This class provides a standardized framework for constructing an API wrapper
36
+ * to external services. It is designed to be extremely flexible.
37
+ *
38
+ * @version 2.2.0
39
+ */
40
+ abstract class SV_WC_API_Base {
41
+
42
+
43
+ /** @var string request method, defaults to POST */
44
+ protected $request_method = 'POST';
45
+
46
+ /** @var string URI used for the request */
47
+ protected $request_uri;
48
+
49
+ /** @var array request headers */
50
+ protected $request_headers = array();
51
+
52
+ /** @var string request user-agent */
53
+ protected $request_user_agent;
54
+
55
+ /** @var string request HTTP version, defaults to 1.0 */
56
+ protected $request_http_version = '1.0';
57
+
58
+ /** @var string request duration */
59
+ protected $request_duration;
60
+
61
+ /** @var SV_WC_API_Request|object request */
62
+ protected $request;
63
+
64
+ /** @var string response code */
65
+ protected $response_code;
66
+
67
+ /** @var string response message */
68
+ protected $response_message;
69
+
70
+ /** @var array response headers */
71
+ protected $response_headers;
72
+
73
+ /** @var string raw response body */
74
+ protected $raw_response_body;
75
+
76
+ /** @var string response handler class name */
77
+ protected $response_handler;
78
+
79
+ /** @var SV_WC_API_Response|object response */
80
+ protected $response;
81
+
82
+
83
+ /**
84
+ * Perform the request and return the parsed response
85
+ *
86
+ * @since 2.2.0
87
+ *
88
+ * @param SV_WC_API_Request|object $request class instance which implements SV_WC_API_Request
89
+ * @return SV_WC_API_Response|object class instance which implements SV_WC_API_Response
90
+ * @throws SV_WC_API_Exception may be thrown in implementations
91
+ */
92
+ protected function perform_request( $request ) {
93
+
94
+ // ensure API is in its default state
95
+ $this->reset_response();
96
+
97
+ // save the request object
98
+ $this->request = $request;
99
+
100
+ $start_time = microtime( true );
101
+
102
+ // if this API requires TLS v1.2, force it
103
+ if ( $this->get_plugin()->require_tls_1_2() ) {
104
+ add_action( 'http_api_curl', array( $this, 'set_tls_1_2_request' ), 10, 3 );
105
+ }
106
+
107
+ // perform the request
108
+ $response = $this->do_remote_request( $this->get_request_uri(), $this->get_request_args() );
109
+
110
+ // calculate request duration
111
+ $this->request_duration = round( microtime( true ) - $start_time, 5 );
112
+
113
+ try {
114
+
115
+ // parse & validate response
116
+ $response = $this->handle_response( $response );
117
+
118
+ } catch ( SV_WC_Plugin_Exception $e ) {
119
+
120
+ // alert other actors that a request has been made
121
+ $this->broadcast_request();
122
+
123
+ throw $e;
124
+ }
125
+
126
+ return $response;
127
+ }
128
+
129
+
130
+ /**
131
+ * Simple wrapper for wp_remote_request() so child classes can override this
132
+ * and provide their own transport mechanism if needed, e.g. a custom
133
+ * cURL implementation
134
+ *
135
+ * @since 2.2.0
136
+ *
137
+ * @param string $request_uri
138
+ * @param string $request_args
139
+ * @return array|\WP_Error
140
+ */
141
+ protected function do_remote_request( $request_uri, $request_args ) {
142
+
143
+ return wp_safe_remote_request( $request_uri, $request_args );
144
+ }
145
+
146
+
147
+ /**
148
+ * Handle and parse the response
149
+ *
150
+ * @since 2.2.0
151
+ * @param array|\WP_Error $response response data
152
+ * @throws SV_WC_API_Exception network issues, timeouts, API errors, etc
153
+ * @return SV_WC_API_Request|object request class instance that implements SV_WC_API_Request
154
+ */
155
+ protected function handle_response( $response ) {
156
+
157
+ // check for WP HTTP API specific errors (network timeout, etc)
158
+ if ( is_wp_error( $response ) ) {
159
+ throw new SV_WC_API_Exception( $response->get_error_message(), (int) $response->get_error_code() );
160
+ }
161
+
162
+ // set response data
163
+ $this->response_code = wp_remote_retrieve_response_code( $response );
164
+ $this->response_message = wp_remote_retrieve_response_message( $response );
165
+ $this->raw_response_body = wp_remote_retrieve_body( $response );
166
+
167
+ $response_headers = wp_remote_retrieve_headers( $response );
168
+
169
+ // WP 4.6+ returns an object
170
+ if ( is_object( $response_headers ) ) {
171
+ $response_headers = $response_headers->getAll();
172
+ }
173
+
174
+ $this->response_headers = $response_headers;
175
+
176
+ // allow child classes to validate response prior to parsing -- this is useful
177
+ // for checking HTTP status codes, etc.
178
+ $this->do_pre_parse_response_validation();
179
+
180
+ // parse the response body and tie it to the request
181
+ $this->response = $this->get_parsed_response( $this->raw_response_body );
182
+
183
+ // allow child classes to validate response after parsing -- this is useful
184
+ // for checking error codes/messages included in a parsed response
185
+ $this->do_post_parse_response_validation();
186
+
187
+ // fire do_action() so other actors can act on request/response data,
188
+ // primarily used for logging
189
+ $this->broadcast_request();
190
+
191
+ return $this->response;
192
+ }
193
+
194
+
195
+ /**
196
+ * Allow child classes to validate a response prior to instantiating the
197
+ * response object. Useful for checking response codes or messages, e.g.
198
+ * throw an exception if the response code is not 200.
199
+ *
200
+ * A child class implementing this method should simply return true if the response
201
+ * processing should continue, or throw a \SV_WC_API_Exception with a
202
+ * relevant error message & code to stop processing.
203
+ *
204
+ * Note: Child classes *must* sanitize the raw response body before throwing
205
+ * an exception, as it will be included in the broadcast_request() method
206
+ * which is typically used to log requests.
207
+ *
208
+ * @since 2.2.0
209
+ */
210
+ protected function do_pre_parse_response_validation() {
211
+ // stub method
212
+ }
213
+
214
+
215
+ /**
216
+ * Allow child classes to validate a response after it has been parsed
217
+ * and instantiated. This is useful for check error codes or messages that
218
+ * exist in the parsed response.
219
+ *
220
+ * A child class implementing this method should simply return true if the response
221
+ * processing should continue, or throw a \SV_WC_API_Exception with a
222
+ * relevant error message & code to stop processing.
223
+ *
224
+ * Note: Response body sanitization is handled automatically
225
+ *
226
+ * @since 2.2.0
227
+ */
228
+ protected function do_post_parse_response_validation() {
229
+ // stub method
230
+ }
231
+
232
+
233
+ /**
234
+ * Return the parsed response object for the request
235
+ *
236
+ * @since 2.2.0
237
+ * @param string $raw_response_body
238
+ * @return object|SV_WC_API_Request response class instance which implements SV_WC_API_Request
239
+ */
240
+ protected function get_parsed_response( $raw_response_body ) {
241
+
242
+ $handler_class = $this->get_response_handler();
243
+
244
+ return new $handler_class( $raw_response_body );
245
+ }
246
+
247
+
248
+ /**
249
+ * Alert other actors that a request has been performed. This is primarily used
250
+ * for request logging.
251
+ *
252
+ * @since 2.2.0
253
+ */
254
+ protected function broadcast_request() {
255
+
256
+ $request_data = array(
257
+ 'method' => $this->get_request_method(),
258
+ 'uri' => $this->get_request_uri(),
259
+ 'user-agent' => $this->get_request_user_agent(),
260
+ 'headers' => $this->get_sanitized_request_headers(),
261
+ 'body' => $this->get_sanitized_request_body(),
262
+ 'duration' => $this->get_request_duration() . 's', // seconds
263
+ );
264
+
265
+ $response_data = array(
266
+ 'code' => $this->get_response_code(),
267
+ 'message' => $this->get_response_message(),
268
+ 'headers' => $this->get_response_headers(),
269
+ 'body' => $this->get_sanitized_response_body() ? $this->get_sanitized_response_body() : $this->get_raw_response_body(),
270
+ );
271
+
272
+ /**
273
+ * API Base Request Performed Action.
274
+ *
275
+ * Fired when an API request is performed via this base class. Plugins can
276
+ * hook into this to log request/response data.
277
+ *
278
+ * @since 2.2.0
279
+ * @param array $request_data {
280
+ * @type string $method request method, e.g. POST
281
+ * @type string $uri request URI
282
+ * @type string $user-agent
283
+ * @type string $headers request headers
284
+ * @type string $body request body
285
+ * @type string $duration in seconds
286
+ * }
287
+ * @param array $response data {
288
+ * @type string $code response HTTP code
289
+ * @type string $message response message
290
+ * @type string $headers response HTTP headers
291
+ * @type string $body response body
292
+ * }
293
+ * @param SV_WC_API_Base $this instance
294
+ */
295
+ do_action( 'wc_' . $this->get_api_id() . '_api_request_performed', $request_data, $response_data, $this );
296
+ }
297
+
298
+
299
+ /**
300
+ * Reset the API response members to their
301
+ *
302
+ * @since 1.0.0
303
+ */
304
+ protected function reset_response() {
305
+
306
+ $this->response_code = null;
307
+ $this->response_message = null;
308
+ $this->response_headers = null;
309
+ $this->raw_response_body = null;
310
+ $this->response = null;
311
+ $this->request_duration = null;
312
+ }
313
+
314
+
315
+ /** Request Getters *******************************************************/
316
+
317
+
318
+ /**
319
+ * Get the request URI
320
+ *
321
+ * @since 2.2.0
322
+ * @return string
323
+ */
324
+ protected function get_request_uri() {
325
+
326
+ $uri = $this->request_uri . $this->get_request_path();
327
+
328
+ // append any query params to the URL when necessary
329
+ if ( $query = $this->get_request_query() ) {
330
+
331
+ $url_parts = parse_url( $uri );
332
+
333
+ // if the URL already has some query params, add to them
334
+ if ( ! empty( $url_parts['query'] ) ) {
335
+ $query = '&' . $query;
336
+ } else {
337
+ $query = '?' . $query;
338
+ }
339
+
340
+ $uri = untrailingslashit( $uri ) . $query;
341
+ }
342
+
343
+ /**
344
+ * Request URI Filter.
345
+ *
346
+ * Allow actors to filter the request URI. Note that child classes can override
347
+ * this method, which means this filter may be invoked prior to the overridden
348
+ * method.
349
+ *
350
+ * @since 4.1.0
351
+ *
352
+ * @param string $uri current request URI
353
+ * @param SV_WC_API_Base class instance
354
+ */
355
+ return apply_filters( 'wc_' . $this->get_api_id() . '_api_request_uri', $uri, $this );
356
+ }
357
+
358
+
359
+ /**
360
+ * Gets the request path.
361
+ *
362
+ * @since 4.5.0
363
+ * @return string
364
+ */
365
+ protected function get_request_path() {
366
+
367
+ return ( $this->get_request() ) ? $this->get_request()->get_path() : '';
368
+ }
369
+
370
+
371
+ /**
372
+ * Gets the request URL query.
373
+ *
374
+ * @since 4.5.0
375
+ *
376
+ * @return string
377
+ */
378
+ protected function get_request_query() {
379
+
380
+ $query = '';
381
+ $request = $this->get_request();
382
+
383
+ if ( $request && in_array( strtoupper( $this->get_request_method() ), array( 'GET', 'HEAD' ), true ) ) {
384
+
385
+ $params = $request->get_params();
386
+
387
+ if ( ! empty( $params ) ) {
388
+ $query = http_build_query( $params, '', '&' );
389
+ }
390
+ }
391
+
392
+ return $query;
393
+ }
394
+
395
+
396
+ /**
397
+ * Get the request arguments in the format required by wp_remote_request()
398
+ *
399
+ * @since 2.2.0
400
+ *
401
+ * @return array
402
+ */
403
+ protected function get_request_args() {
404
+
405
+ $args = array(
406
+ 'method' => $this->get_request_method(),
407
+ 'timeout' => MINUTE_IN_SECONDS,
408
+ 'redirection' => 0,
409
+ 'httpversion' => $this->get_request_http_version(),
410
+ 'sslverify' => true,
411
+ 'blocking' => true,
412
+ 'user-agent' => $this->get_request_user_agent(),
413
+ 'headers' => $this->get_request_headers(),
414
+ 'body' => $this->get_request_body(),
415
+ 'cookies' => array(),
416
+ );
417
+
418
+ /**
419
+ * Request arguments.
420
+ *
421
+ * Allow other actors to filter the request arguments. Note that
422
+ * child classes can override this method, which means this filter may
423
+ * not be invoked, or may be invoked prior to the overridden method
424
+ *
425
+ * @since 2.2.0
426
+ * @param array $args request arguments
427
+ * @param SV_WC_API_Base class instance
428
+ */
429
+ return apply_filters( 'wc_' . $this->get_api_id() . '_http_request_args', $args, $this );
430
+ }
431
+
432
+
433
+ /**
434
+ * Get the request method, POST by default
435
+ *
436
+ * @since 2.2.0
437
+ * @return string
438
+ */
439
+ protected function get_request_method() {
440
+ // if the request object specifies the method to use, use that, otherwise use the API default
441
+ return $this->get_request() && $this->get_request()->get_method() ? $this->get_request()->get_method() : $this->request_method;
442
+ }
443
+
444
+
445
+ /**
446
+ * Gets the request body.
447
+ *
448
+ * @since 4.5.0
449
+ * @return string
450
+ */
451
+ protected function get_request_body() {
452
+
453
+ // GET & HEAD requests don't support a body
454
+ if ( in_array( strtoupper( $this->get_request_method() ), array( 'GET', 'HEAD' ) ) ) {
455
+ return '';
456
+ }
457
+
458
+ return ( $this->get_request() && $this->get_request()->to_string() ) ? $this->get_request()->to_string() : '';
459
+ }
460
+
461
+
462
+ /**
463
+ * Gets the sanitized request body, for logging.
464
+ *
465
+ * @since 4.5.0
466
+ * @return string
467
+ */
468
+ protected function get_sanitized_request_body() {
469
+
470
+ // GET & HEAD requests don't support a body
471
+ if ( in_array( strtoupper( $this->get_request_method() ), array( 'GET', 'HEAD' ) ) ) {
472
+ return '';
473
+ }
474
+
475
+ return ( $this->get_request() && $this->get_request()->to_string_safe() ) ? $this->get_request()->to_string_safe() : '';
476
+ }
477
+
478
+
479
+ /**
480
+ * Get the request HTTP version, 1.1 by default
481
+ *
482
+ * @since 2.2.0
483
+ * @return string
484
+ */
485
+ protected function get_request_http_version() {
486
+
487
+ return $this->request_http_version;
488
+ }
489
+
490
+
491
+ /**
492
+ * Get the request headers
493
+ *
494
+ * @since 2.2.0
495
+ * @return array
496
+ */
497
+ protected function get_request_headers() {
498
+ return $this->request_headers;
499
+ }
500
+
501
+
502
+ /**
503
+ * Get sanitized request headers suitable for logging, stripped of any
504
+ * confidential information
505
+ *
506
+ * The `Authorization` header is sanitized automatically.
507
+ *
508
+ * Child classes that implement any custom authorization headers should
509
+ * override this method to perform sanitization.
510
+ *
511
+ * @since 2.2.0
512
+ * @return array
513
+ */
514
+ protected function get_sanitized_request_headers() {
515
+
516
+ $headers = $this->get_request_headers();
517
+
518
+ if ( ! empty( $headers['Authorization'] ) ) {
519
+ $headers['Authorization'] = str_repeat( '*', strlen( $headers['Authorization'] ) );
520
+ }
521
+
522
+ return $headers;
523
+ }
524
+
525
+
526
+ /**
527
+ * Get the request user agent, defaults to:
528
+ *
529
+ * Dasherized-Plugin-Name/Plugin-Version (WooCommerce/WC-Version; WordPress/WP-Version)
530
+ *
531
+ * @since 2.2.0
532
+ * @return string
533
+ */
534
+ protected function get_request_user_agent() {
535
+
536
+ return sprintf( '%s/%s (WooCommerce/%s; WordPress/%s)', str_replace( ' ', '-', $this->get_plugin()->get_plugin_name() ), $this->get_plugin()->get_version(), WC_VERSION, $GLOBALS['wp_version'] );
537
+ }
538
+
539
+
540
+ /**
541
+ * Get the request duration in seconds, rounded to the 5th decimal place
542
+ *
543
+ * @since 2.2.0
544
+ * @return string
545
+ */
546
+ protected function get_request_duration() {
547
+ return $this->request_duration;
548
+ }
549
+
550
+
551
+ /** Response Getters ******************************************************/
552
+
553
+
554
+ /**
555
+ * Get the response handler class name
556
+ *
557
+ * @since 2.2.0
558
+ * @return string
559
+ */
560
+ protected function get_response_handler() {
561
+ return $this->response_handler;
562
+ }
563
+
564
+
565
+ /**
566
+ * Get the response code
567
+ *
568
+ * @since 2.2.0
569
+ * @return string
570
+ */
571
+ protected function get_response_code() {
572
+ return $this->response_code;
573
+ }
574
+
575
+
576
+ /**
577
+ * Get the response message
578
+ *
579
+ * @since 2.2.0
580
+ * @return string
581
+ */
582
+ protected function get_response_message() {
583
+ return $this->response_message;
584
+ }
585
+
586
+
587
+ /**
588
+ * Get the response headers
589
+ *
590
+ * @since 2.2.0
591
+ * @return array
592
+ */
593
+ protected function get_response_headers() {
594
+ return $this->response_headers;
595
+ }
596
+
597
+
598
+ /**
599
+ * Get the raw response body, prior to any parsing or sanitization
600
+ *
601
+ * @since 2.2.0
602
+ * @return string
603
+ */
604
+ protected function get_raw_response_body() {
605
+ return $this->raw_response_body;
606
+ }
607
+
608
+
609
+ /**
610
+ * Get the sanitized response body, provided by the response class
611
+ * to_string_safe() method
612
+ *
613
+ * @since 2.2.0
614
+ * @return string|null
615
+ */
616
+ protected function get_sanitized_response_body() {
617
+ return is_callable( array( $this->get_response(), 'to_string_safe' ) ) ? $this->get_response()->to_string_safe() : null;
618
+ }
619
+
620
+
621
+ /** Misc Getters ******************************************************/
622
+
623
+
624
+ /**
625
+ * Returns the most recent request object.
626
+ *
627
+ * @since 2.2.0
628
+ *
629
+ * @return SV_WC_API_Request|object the most recent request object
630
+ */
631
+ public function get_request() {
632
+
633
+ return $this->request;
634
+ }
635
+
636
+
637
+ /**
638
+ * Returns the most recent response object.
639
+ *
640
+ * @since 2.2.0
641
+ *
642
+ * @return SV_WC_API_Response|object the most recent response object
643
+ */
644
+ public function get_response() {
645
+
646
+ return $this->response;
647
+ }
648
+
649
+
650
+ /**
651
+ * Get the ID for the API, used primarily to namespace the action name
652
+ * for broadcasting requests
653
+ *
654
+ * @since 2.2.0
655
+ * @return string
656
+ */
657
+ protected function get_api_id() {
658
+
659
+ return $this->get_plugin()->get_id();
660
+ }
661
+
662
+
663
+ /**
664
+ * Return a new request object
665
+ *
666
+ * Child classes must implement this to return an object that implements
667
+ * \SV_WC_API_Request which should be used in the child class API methods
668
+ * to build the request. The returned SV_WC_API_Request should be passed
669
+ * to self::perform_request() by your concrete API methods
670
+ *
671
+ * @since 2.2.0
672
+ *
673
+ * @param array $args optional request arguments
674
+ * @return SV_WC_API_Request|object
675
+ */
676
+ abstract protected function get_new_request( $args = array() );
677
+
678
+
679
+ /**
680
+ * Return the plugin class instance associated with this API
681
+ *
682
+ * Child classes must implement this to return their plugin class instance
683
+ *
684
+ * This is used for defining the plugin ID used in filter names, as well
685
+ * as the plugin name used for the default user agent.
686
+ *
687
+ * @since 2.2.0
688
+ *
689
+ * @return SV_WC_Plugin
690
+ */
691
+ abstract protected function get_plugin();
692
+
693
+
694
+ /** Setters ***************************************************************/
695
+
696
+
697
+ /**
698
+ * Set a request header
699
+ *
700
+ * @since 2.2.0
701
+ * @param string $name header name
702
+ * @param string $value header value
703
+ * @return string
704
+ */
705
+ protected function set_request_header( $name, $value ) {
706
+
707
+ $this->request_headers[ $name ] = $value;
708
+ }
709
+
710
+
711
+ /**
712
+ * Set multiple request headers at once
713
+ *
714
+ * @since 4.3.0
715
+ * @param array $headers
716
+ */
717
+ protected function set_request_headers( array $headers ) {
718
+
719
+ foreach ( $headers as $name => $value ) {
720
+
721
+ $this->request_headers[ $name ] = $value;
722
+ }
723
+ }
724
+
725
+
726
+ /**
727
+ * Set HTTP basic auth for the request
728
+ *
729
+ * Since 2.2.0
730
+ * @param string $username
731
+ * @param string $password
732
+ */
733
+ protected function set_http_basic_auth( $username, $password ) {
734
+
735
+ $this->request_headers['Authorization'] = sprintf( 'Basic %s', base64_encode( "{$username}:{$password}" ) );
736
+ }
737
+
738
+
739
+ /**
740
+ * Set the Content-Type request header
741
+ *
742
+ * @since 2.2.0
743
+ * @param string $content_type
744
+ */
745
+ protected function set_request_content_type_header( $content_type ) {
746
+ $this->request_headers['content-type'] = $content_type;
747
+ }
748
+
749
+
750
+ /**
751
+ * Set the Accept request header
752
+ *
753
+ * @since 2.2.0
754
+ * @param string $type the request accept type
755
+ */
756
+ protected function set_request_accept_header( $type ) {
757
+ $this->request_headers['accept'] = $type;
758
+ }
759
+
760
+
761
+ /**
762
+ * Set the response handler class name. This class will be instantiated
763
+ * to parse the response for the request.
764
+ *
765
+ * Note the class should implement SV_WC_API
766
+ *
767
+ * @since 2.2.0
768
+ *
769
+ * @param string $handler handle class name
770
+ */
771
+ protected function set_response_handler( $handler ) {
772
+
773
+ $this->response_handler = $handler;
774
+ }
775
+
776
+
777
+ /**
778
+ * Maybe force TLS v1.2 requests.
779
+ *
780
+ * @since 4.4.0
781
+ *
782
+ * @param resource $handle the cURL handle returned by curl_init() (passed by reference)
783
+ * @param array $r the HTTP request arguments
784
+ * @param $url string the request URL
785
+ */
786
+ public function set_tls_1_2_request( $handle, $r, $url ) {
787
+
788
+ if ( ! SV_WC_Helper::str_starts_with( $url, 'https://' ) ) {
789
+ return;
790
+ }
791
+
792
+ curl_setopt( $handle, CURLOPT_SSLVERSION, 6 );
793
+ }
794
+
795
+
796
+ /**
797
+ * Determines if TLS v1.2 is required for API requests.
798
+ *
799
+ * @since 4.4.0
800
+ * @deprecated 5.5.2
801
+ *
802
+ * @return bool
803
+ */
804
+ public function require_tls_1_2() {
805
+
806
+ wc_deprecated_function( __METHOD__, '5.5.2', 'SV_WC_Plugin::require_tls_1_2()' );
807
+
808
+ return $this->get_plugin()->require_tls_1_2();
809
+ }
810
+
811
+
812
+ /**
813
+ * Determines if TLS 1.2 is available.
814
+ *
815
+ * @since 4.6.5
816
+ *
817
+ * @return bool
818
+ */
819
+ public function is_tls_1_2_available() {
820
+
821
+ /**
822
+ * Filters whether TLS 1.2 is available.
823
+ *
824
+ * @since 4.7.1
825
+ *
826
+ * @param bool $is_available whether TLS 1.2 is available
827
+ * @param SV_WC_API_Base $api API class instance
828
+ */
829
+ return (bool) apply_filters( 'wc_' . $this->get_plugin()->get_id() . '_api_is_tls_1_2_available', $this->get_plugin()->is_tls_1_2_available(), $this );
830
+ }
831
+
832
+
833
+ }
834
+
835
+
836
+ endif;
vendor/skyverge/wc-plugin-framework/woocommerce/api/class-sv-wc-api-exception.php ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * WooCommerce Plugin Framework
4
+ *
5
+ * This source file is subject to the GNU General Public License v3.0
6
+ * that is bundled with this package in the file license.txt.
7
+ * It is also available through the world-wide-web at this URL:
8
+ * http://www.gnu.org/licenses/gpl-3.0.html
9
+ * If you did not receive a copy of the license and are unable to
10
+ * obtain it through the world-wide-web, please send an email
11
+ * to license@skyverge.com so we can send you a copy immediately.
12
+ *
13
+ * DISCLAIMER
14
+ *
15
+ * Do not edit or add to this file if you wish to upgrade the plugin to newer
16
+ * versions in the future. If you wish to customize the plugin for your
17
+ * needs please refer to http://www.skyverge.com
18
+ *
19
+ * @package SkyVerge/WooCommerce/API/Exceptions
20
+ * @author SkyVerge
21
+ * @copyright Copyright (c) 2013-2020, SkyVerge, Inc.
22
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GNU General Public License v3.0
23
+ */
24
+
25
+ namespace SkyVerge\WooCommerce\PluginFramework\v5_5_4;
26
+
27
+ defined( 'ABSPATH' ) or exit;
28
+
29
+ if ( ! class_exists( '\\SkyVerge\\WooCommerce\\PluginFramework\\v5_5_4\\SV_WC_API_Exception' ) ) :
30
+
31
+
32
+ /**
33
+ * Plugin Framework API Exception - generic API Exception
34
+ */
35
+ class SV_WC_API_Exception extends SV_WC_Plugin_Exception { }
36
+
37
+
38
+ endif;
vendor/skyverge/wc-plugin-framework/woocommerce/api/interface-sv-wc-api-request.php ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * WooCommerce Plugin Framework
4
+ *
5
+ * This source file is subject to the GNU General Public License v3.0
6
+ * that is bundled with this package in the file license.txt.
7
+ * It is also available through the world-wide-web at this URL:
8
+ * http://www.gnu.org/licenses/gpl-3.0.html
9
+ * If you did not receive a copy of the license and are unable to
10
+ * obtain it through the world-wide-web, please send an email
11
+ * to license@skyverge.com so we can send you a copy immediately.
12
+ *
13
+ * DISCLAIMER
14
+ *
15
+ * Do not edit or add to this file if you wish to upgrade the plugin to newer
16
+ * versions in the future. If you wish to customize the plugin for your
17
+ * needs please refer to http://www.skyverge.com
18
+ *
19
+ * @package SkyVerge/WooCommerce/API/Request
20
+ * @author SkyVerge
21
+ * @copyright Copyright (c) 2013-2020, SkyVerge, Inc.
22
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GNU General Public License v3.0
23
+ */
24
+
25
+ namespace SkyVerge\WooCommerce\PluginFramework\v5_5_4;
26
+
27
+ defined( 'ABSPATH' ) or exit;
28
+
29
+ if ( ! interface_exists( '\\SkyVerge\\WooCommerce\\PluginFramework\\v5_5_4\\SV_WC_API_Request' ) ) :
30
+
31
+
32
+ /**
33
+ * API Request
34
+ */
35
+ interface SV_WC_API_Request {
36
+
37
+
38
+ /**
39
+ * Returns the method for this request: one of HEAD, GET, PUT, PATCH, POST, DELETE
40
+ *
41
+ * @since 4.0.0
42
+ * @return string the request method, or null to use the API default
43
+ */
44
+ public function get_method();
45
+
46
+
47
+ /**
48
+ * Returns the request path
49
+ *
50
+ * @since 4.0.0
51
+ * @return string the request path, or '' if none
52
+ */
53
+ public function get_path();
54
+
55
+
56
+ /**
57
+ * Gets the request query params.
58
+ *
59
+ * @since 5.0.0
60
+ *
61
+ * @return array
62
+ */
63
+ public function get_params();
64
+
65
+
66
+ /**
67
+ * Gets the request data.
68
+ *
69
+ * @since 5.0.0
70
+ *
71
+ * @return array
72
+ */
73
+ public function get_data();
74
+
75
+
76
+ /**
77
+ * Returns the string representation of this request
78
+ *
79
+ * @since 2.2.0
80
+ * @return string the request
81
+ */
82
+ public function to_string();
83
+
84
+
85
+ /**
86
+ * Returns the string representation of this request with any and all
87
+ * sensitive elements masked or removed
88
+ *
89
+ * @since 2.2.0
90
+ * @return string the request, safe for logging/displaying
91
+ */
92
+ public function to_string_safe();
93
+
94
+ }
95
+
96
+
97
+ endif;
vendor/skyverge/wc-plugin-framework/woocommerce/api/interface-sv-wc-api-response.php ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * WooCommerce Plugin Framework
4
+ *
5
+ * This source file is subject to the GNU General Public License v3.0
6
+ * that is bundled with this package in the file license.txt.
7
+ * It is also available through the world-wide-web at this URL:
8
+ * http://www.gnu.org/licenses/gpl-3.0.html
9
+ * If you did not receive a copy of the license and are unable to
10
+ * obtain it through the world-wide-web, please send an email
11
+ * to license@skyverge.com so we can send you a copy immediately.
12
+ *
13
+ * DISCLAIMER
14
+ *
15
+ * Do not edit or add to this file if you wish to upgrade the plugin to newer
16
+ * versions in the future. If you wish to customize the plugin for your
17
+ * needs please refer to http://www.skyverge.com
18
+ *
19
+ * @package SkyVerge/WooCommerce/API/Request
20
+ * @author SkyVerge
21
+ * @copyright Copyright (c) 2013-2020, SkyVerge, Inc.
22
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GNU General Public License v3.0
23
+ */
24
+
25
+ namespace SkyVerge\WooCommerce\PluginFramework\v5_5_4;
26
+
27
+ defined( 'ABSPATH' ) or exit;
28
+
29
+ if ( ! interface_exists( '\\SkyVerge\\WooCommerce\\PluginFramework\\v5_5_4\\SV_WC_API_Response' ) ) :
30
+
31
+
32
+ /**
33
+ * API Response
34
+ */
35
+ interface SV_WC_API_Response {
36
+
37
+
38
+ /**
39
+ * Returns the string representation of this request
40
+ *
41
+ * @since 2.2.0
42
+ * @return string the request
43
+ */
44
+ public function to_string();
45
+
46
+
47
+ /**
48
+ * Returns the string representation of this request with any and all
49
+ * sensitive elements masked or removed
50
+ *
51
+ * @since 2.2.0
52
+ * @return string the request, safe for logging/displaying
53
+ */
54
+ public function to_string_safe();
55
+
56
+ }
57
+
58
+
59
+ endif;
vendor/skyverge/wc-plugin-framework/woocommerce/assets/css/admin/sv-wc-plugin-admin-setup-wizard.min.css ADDED
@@ -0,0 +1 @@
 
1
+ h1#wc-logo.sv-wc-plugin-logo{font-size:28px;font-weight:bold}h1#wc-logo.sv-wc-plugin-logo a{color:#444444;text-decoration:none}h1#wc-logo.sv-wc-plugin-logo a:hover{color:#222222;text-decoration:none}.sv-wc-plugin-admin-setup-content .error{background:#dc3232;border-radius:5px;color:#fff;padding:1em}.sv-wc-plugin-admin-setup-content .error a{color:#fff;text-decoration:none}.sv-wc-plugin-admin-setup-content .error a:hover{text-decoration:underline}.sv-wc-plugin-admin-setup-content .sv-wc-plugin-admin-setup-control{margin-bottom:20px}.sv-wc-plugin-admin-setup-content .sv-wc-plugin-admin-setup-control label{color:#666;display:inline-block;font-size:15px;font-weight:500;margin-top:.85em;margin-bottom:.5em}.sv-wc-plugin-admin-setup-content .sv-wc-plugin-admin-setup-control input[type="text"],.sv-wc-plugin-admin-setup-content .sv-wc-plugin-admin-setup-control input[type="number"],.sv-wc-plugin-admin-setup-content .sv-wc-plugin-admin-setup-control input[type="password"],.sv-wc-plugin-admin-setup-content .sv-wc-plugin-admin-setup-control select{background-color:#fff;border:1px solid #ddd;border-radius:4px;color:#444;display:inline-block;font-size:16px;height:30px;padding:0 24px 0 8px;width:calc(100% - 8px - 24px - 2px)}.sv-wc-plugin-admin-setup-content .sv-wc-plugin-admin-setup-control select{width:100%}.sv-wc-plugin-admin-setup-content .sv-wc-plugin-admin-setup-control textarea{background-color:#fff;border:1px solid #ddd;border-radius:4px;color:#444;font-size:16px;padding:10px}.sv-wc-plugin-admin-setup-content .sv-wc-plugin-admin-setup-control .description{color:#888;font-size:13px;margin:5px 0 0}.sv-wc-plugin-admin-setup-content .sv-wc-plugin-admin-setup-control.toggle{display:flex;flex-wrap:nowrap;justify-content:space-between;padding:0;border-bottom:1px solid #eee;color:#666;align-items:top}.sv-wc-plugin-admin-setup-content .sv-wc-plugin-admin-setup-control.toggle:last-child{border-bottom:0}.sv-wc-plugin-admin-setup-content .sv-wc-plugin-admin-setup-control.toggle .name{flex-basis:0;min-width:160px;text-align:center;font-weight:bold;align-self:stretch;display:flex;align-items:baseline}.sv-wc-plugin-admin-setup-content .sv-wc-plugin-admin-setup-control.toggle .content{flex-grow:1;padding:20px}.sv-wc-plugin-admin-setup-content .sv-wc-plugin-admin-setup-control.toggle .content p{margin-bottom:1em}.sv-wc-plugin-admin-setup-content .sv-wc-plugin-admin-setup-control.toggle .content p:last-child{margin-bottom:0}.sv-wc-plugin-admin-setup-content .sv-wc-plugin-admin-setup-control.toggle .enable{flex-basis:0;min-width:75px;text-align:center;cursor:pointer;padding:2em 0;position:relative;max-height:1.5em;align-self:flex-start}.sv-wc-plugin-admin-setup-content .sv-wc-plugin-admin-setup-control.toggle .toggle{height:16px;width:32px;border:2px solid #935687;background-color:#935687;display:inline-block;text-indent:-9999px;border-radius:10em;position:relative}.sv-wc-plugin-admin-setup-content .sv-wc-plugin-admin-setup-control.toggle .toggle input[type=checkbox]{display:none}.sv-wc-plugin-admin-setup-content .sv-wc-plugin-admin-setup-control.toggle .toggle:before{content:"";display:block;width:16px;height:16px;background:#fff;position:absolute;top:0;right:0;border-radius:100%}.sv-wc-plugin-admin-setup-content .sv-wc-plugin-admin-setup-control.toggle .toggle.disabled{border-color:#999;background-color:#999}.sv-wc-plugin-admin-setup-content .sv-wc-plugin-admin-setup-control.toggle .toggle.disabled:before{right:auto;left:0}
vendor/skyverge/wc-plugin-framework/woocommerce/assets/images/ajax-loader.gif ADDED
Binary file
vendor/skyverge/wc-plugin-framework/woocommerce/assets/js/admin/sv-wc-plugin-admin-setup-wizard.min.js ADDED
@@ -0,0 +1 @@
 
1
+ (function() { "use strict"; /** * WooCommerce Plugin Framework Setup Wizard scripts. * * @since 5.3.0 */ jQuery(document).ready(function($) { var blockWizardUI; blockWizardUI = function() { return $('.wc-setup-content').block({ message: null, overlayCSS: { background: '#fff', opacity: 0.6 } }); }; $('.sv-wc-plugin-admin-setup-control').on('change', '.enable input', function() { if ($(this).is(':checked')) { return $(this).closest('.toggle').removeClass('disabled'); } else { return $(this).closest('.toggle').addClass('disabled'); } }); return $('.sv-wc-plugin-admin-setup-control').on('click', '.enable', function(e) { var $checkbox; if ($(e.target).is('input')) { e.stopPropagation(); return; } $checkbox = $(this).find('input[type="checkbox"]'); return $checkbox.prop('checked', !$checkbox.is(':checked')).change(); }); });}).call(this);
vendor/skyverge/wc-plugin-framework/woocommerce/assets/js/admin/sv-wp-admin-job-batch-handler.min.js ADDED
@@ -0,0 +1 @@
 
1
+ /** * WordPress Batch Job Handler * * @since 4.8.0 */(function() { var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; jQuery(document).ready(function($) { 'use strict'; return window.SV_WP_Job_Batch_Handler = (function() { function SV_WP_Job_Batch_Handler(args) { this.process_job = bind(this.process_job, this); this.id = args.id; this.process_nonce = args.process_nonce; this.cancel_nonce = args.cancel_nonce; this.cancelled = false; } SV_WP_Job_Batch_Handler.prototype.process_job = function(job_id) { return new Promise((function(_this) { return function(resolve, reject) { var data; if (_this.cancelled === job_id) { return _this.cancel_job(job_id); } data = { action: _this.id + "_process_batch", security: _this.process_nonce, job_id: job_id }; return $.post(ajaxurl, data).done(function(response) { if (!(response.success && (response.data != null))) { return reject(response); } if (response.data.status !== 'processing') { return resolve(response); } $(document).trigger(_this.id + "_batch_progress_" + response.data.id, { percentage: response.data.percentage, progress: response.data.progress, total: response.data.total }); return resolve(_this.process_job(response.data.id)); }).fail(function(jqXHR, textStatus, error) { return reject(error); }); }; })(this)); }; SV_WP_Job_Batch_Handler.prototype.cancel_job = function(job_id) { return new Promise((function(_this) { return function(resolve, reject) { var data; _this.cancelled = false; data = { action: _this.id + "_cancel_job", security: _this.cancel_nonce, job_id: job_id }; return $.post(ajaxurl, data).done(function(response) { if (!response.success) { return reject(response); } return resolve(response); }).fail(function(jqXHR, textStatus, error) { return reject(error); }); }; })(this)); }; return SV_WP_Job_Batch_Handler; })(); });}).call(this);
vendor/skyverge/wc-plugin-framework/woocommerce/changelog.txt ADDED
@@ -0,0 +1,331 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ *** SkyVerge WooCommerce Plugin Framework Changelog ***
2
+
3
+ 2020.01.20 - version 5.5.4
4
+ * Tweak - Add a link to the site's terms and conditions page below Apple Pay buttons when available
5
+ * Tweak - Adjust the place order button label for redirect/hosted gateways
6
+ * Fix - Fix a JavaScript error triggered trying read the 'length' property of an undefined value in format_credit_card_inputs()
7
+
8
+ 2020.01.13 - version 5.5.3
9
+ * Fix - Fix a JavaScript error when instantiating a class that hasn't been loaded
10
+
11
+ 2020.01.09 - version 5.5.2
12
+ * Fix - `SV_WC_Payment_Gateway_Apple_Pay::process_payment()` now throws an exception if the result returned by the processing gateway doesn't indicate whether the transaction was successful or not
13
+ * Fix - Update `SV_WC_Payment_Gateway_Direct::process_payment()` to cover for and edge case in which `SV_WC_Payment_Gateway_Direct::do_transaction()` fails without throwing an exception
14
+ * Fix - On WooCommerce 3.9: prevent empty credit card fields from being marked as invalid before the user has entered any data
15
+ * Dev - TLS 1.2 helper methods moved from `SV_WC_API_Base` to `SV_WC_Plugin`
16
+ * Dev - Deprecated `SV_WC_API_Base::require_tls_1_2()`
17
+
18
+ 2019.11.14 - version 5.5.1
19
+ * Tweak - Refactor Apple Pay order creation to support the same filters and actions that are fired during regular checkout
20
+ * Tweak - Allow multiple old hooks to be mapped to a single new one via the hook deprecator
21
+ * Fix - Harden integration with WooCommerce Pre-Orders to avoid a PHP error in some circumstances
22
+ * Fix - Fix double product stock reduction when an order is held and payment is not completed
23
+
24
+ 2019.10.15 - version 5.5.0
25
+ * Feature - Add a plugin helper method to retrieve a template part while consistently passing the default template path to `wc_get_template()`
26
+ * Misc - Deprecate backwards compatibility methods for unsupported WooCommerce and PHP versions
27
+ * Misc - Replace `SV_WC_Helper::get_post()` and `SV_WC_Helper::get_request()` with `SV_WC_Helper::get_posted_value()` and `SV_WC_Helper::get_requested_value()`
28
+
29
+ 2019.09.05 - version 5.4.3
30
+ * Fix - Do not show the checkbox to save the payment method on the checkout page if not logged in and registration during checkout is disabled
31
+ * Misc - Add a Country_Helper class to assist converting country codes to and from various formats
32
+
33
+ 2019.08.27 - version 5.4.2
34
+ * Tweak - Add a standard set of subscription details to orders payment data set by a gateway
35
+ * Tweak - Add replacement helper methods to get the current screen in WordPress and check the screen ID
36
+ * Misc - Change SV_WC_Payment_Gateway::is_configured() from protected to public
37
+ * Misc - Add admin notice when a gateway is enabled but is not configured and is unable to take payments
38
+
39
+ 2019.08.06 - version 5.4.1
40
+ * Misc - Add a configurable admin notice for plugins running deprecated WooCommerce versions
41
+
42
+ 2019.03.13 - version 5.4.0
43
+ * Feature - Add abstract handlers for hosted payment processing
44
+ * Feature - Revamp the Lifecycle handler for easier upgrade routines and add event logging for important lifecycle events
45
+ * Tweak - Adjust the no-HTTPS notice to point to where the merchant can fix the problem
46
+ * Fix - Prevent the Capture button from showing on failed orders or orders without an original transaction ID
47
+ * Fix - Use the current order total when determining whether a captured order should change status
48
+
49
+ 2019.01.09 - version 5.3.1
50
+ * Fix - Fix a JavaScript error in the welcome wizard for missing parameters
51
+ * Fix - Correctly handle memory_limit shorthand from php.ini
52
+ * Misc - Adjust the Accepted Card Logos setting description to further clarify its purpose
53
+
54
+ 2018.09.25 - version 5.3.0
55
+ * Feature - Add support for updating payment methods via API on payment
56
+ * Tweak - Refactor capture handling and add a dedicated handling class
57
+ * Tweak - Add an admin notice for gateways when debug logging is enabled in production mode
58
+ * Fix - Ensure orders are automatically captured when the status is changes via the REST API if enabled
59
+ * Fix - Ensure the gateway capture UI is only displayed for regular orders
60
+ * Fix - Prevent capture JS errors when multiple gateways running the same framework version are activated
61
+ * Fix - Strip price HTML from the admin capture alert
62
+ * Dev - Add the wc_{gateway_id}_held_order_status filter
63
+
64
+ 2018.09.04 - version 5.2.2
65
+ * Tweak - Provide an abstract Setup Wizard for plugins to implement for easier onboarding
66
+
67
+ 2018.08.21 - version 5.2.1
68
+ * Fix - Prevent errors when triggering payment gateway payment and refund milestones
69
+ * Fix - Add escaping to some admin notice messages
70
+
71
+ 2018.07.24 - version 5.2.0
72
+ * Fix - Use the order currency for the gateway capture message currency symbol
73
+ * Dev - Introduce dedicated plugin methods for loading after init
74
+ * Dev - Move plugin lifecycle methods to the Lifecycle handler
75
+ * Dev - Introduce a dependency handler for PHP compatibility notices
76
+ * Dev - Introduce a REST API handler base
77
+ * Misc - Add default plugin and gateway data to the WooCommerce REST API System Status response
78
+
79
+ 2018.06.25 - version 5.1.5
80
+ * Fix - Ensure exceptions are caught for Subscriptions "change payment" and Pre-Orders tokenization failures
81
+
82
+ 2018.05.22 - version 5.1.4
83
+ * Tweak - Add a gateway privacy handler to export or remove order payment data and payment tokens on request
84
+ * Tweak - Add a warning for WooCommerce 2.6 installs that 3.0 will soon be required
85
+ * Misc - Add support for WooCommerce 3.4
86
+
87
+ 2018.04.17 - version 5.1.3
88
+ * Tweak - Add a method for gateways to call during failing captures
89
+
90
+ 2018.04.02 - version 5.1.2
91
+ * Fix - Prevent warnings in PHP 7.2 when building the gateway settings
92
+ * Fix - Fix namespaces in the Apple Pay framework
93
+
94
+ 2018.03.27 - version 5.1.1
95
+ * Tweak - Disable the Add Payment Method button when editing a method
96
+ * Fix - Ensure customers can't delete subscription payment methods from gateways that use integer token IDs
97
+ * Fix - Always pass user email to gateways when adding a payment method
98
+
99
+ 2018.02.27 - version 5.1.0
100
+ * Feature - Add payment method editing support
101
+ * Feature - Allow users to set nicknames for their payment methods
102
+ * Feature - Add support for auto-capturing orders when changed to a paid status
103
+ * Feature - Add a Milestones API for plugins to trigger milestone messages and prompt users for feedback after key plugin events
104
+ * Tweak - Improve the My Account Payment Methods table on desktop and mobile
105
+ * Tweak - Let gateway handle their own API errors when deleting payment methods
106
+ * Tweak - Improve the admin token editor with better error handling and improved display
107
+ * Tweak - Let plugins define a "reviews" URL to be displayed with the plugin action links
108
+ * Tweak - Adjust the gateway "Accepted Cards" setting wording to clarify that it doesn't affect payment processor card support
109
+ * Tweak - Support warning and info message types in the Admin Message Handler
110
+ * Fix - Prevent duplicate admin notices when running alongside legacy framework versions
111
+
112
+ 2018.01.17 - version 5.0.1
113
+ * Misc - Remove support for WooCommerce 2.5
114
+ * Misc - Require WordPress 4.4 or higher
115
+
116
+ 2018.01.11 - version 5.0.0
117
+ * Feature - Partial capture - add a UI for multiple partial captures in supported gateways
118
+ * Feature - Add CSC setting to enable or disable the field for tokenized methods
119
+ * Tweak - Improve the My Payment Methods table styling on mobile
120
+ * Dev - Add versioned namespaces
121
+ * Dev - Add a sample plugin loader class
122
+ * Dev - Add action hooks for My Payment Method actions
123
+ * Misc - Drop WooCommerce 2.5 support
124
+ * Misc - Drop Subscriptions 1.x support
125
+
126
+ 2017.12.11 - version 4.8.3
127
+ * Fix - Ensure failed order token meta is only copied to the parent subscription when a successful payment has occurred
128
+ * Fix - Don't reset the checkout password field if it's already visible
129
+
130
+ 2017.12.01 - version 4.8.2
131
+ * Fix - Fix a possible race condition when performing background processing health checks
132
+ * Fix - Account for possible false negatives when testing loopback connections in certain environments
133
+
134
+ 2017.11.27 - version 4.8.1
135
+ * Fix - Fix Apple Pay compatibility with WooCommerce 3.2+
136
+
137
+ 2017.10.31 - version 4.8.0
138
+ * Feature - Add a framework for batch job handling for when background processing is unavailable
139
+ * Feature - Debug tool for testing the site's environment for loopback connection support
140
+
141
+ 2017.10.05 - version 4.7.3
142
+ * Tweak - Add new methods for checking for specific WooCommerce versions
143
+ * Tweak - Adjust the PHP version notice to check for 5.6 by May 2018 and adjust the messaging when that date has passed
144
+ * Fix - Conflict with WooCommerce filtering of nonce checks for background jobs
145
+
146
+ 2017.09.12 - version 4.7.2
147
+ * Fix - Ensure failed Pre-Orders can be paid with a new method by bypassing the failed order's stored token
148
+ * Fix - Use the parameters passed to SV_WP_Admin_Message_Handler::show_messages()
149
+
150
+ 2017.08.14 - version 4.7.1
151
+ * Tweak - Refine the TLS 1.2 notice wording and appearance
152
+
153
+ 2017.07.25 - version 4.7.0
154
+ * Feature - Introduce the Apple Pay framework for developers
155
+
156
+ 2017.07.11 - version 4.6.6
157
+ * Fix - Ensure backwards compatibility with gateways that don't extend the SV_WC_API_Base class for their API
158
+
159
+ 2017.06.26 - version 4.6.5
160
+ * Misc. - Make a TLS 1.2 admin notice available for gateways that require it
161
+ * Misc. - Ensure WooCommerce 3.1 compatibility
162
+
163
+ 2017.05.20 - version 4.6.4
164
+ * Fix - Add dedicated subscriptions Change Payment handling to avoid subscription manipulation
165
+ * Fix - Ensure old payment methods can be removed after changing subscription payment to a new method
166
+
167
+ 2017.05.09 - version 4.6.3
168
+ * Tweak - Add optional notice for plugins that want to require PHP 5.3+ in the future
169
+ * Tweak - Improved background process handling for certain server & cache configurations
170
+
171
+ 2017.05.01 - version 4.6.2
172
+ * Fix - Ensure authorized, but not yet captured, transactions are marked "on hold" for off-site gateways
173
+
174
+ 2017.04.17 - version 4.6.1
175
+ * Tweak - Load admin translations based on the user's configured language in WordPress 4.7+
176
+ * Tweak - Added the SV_WC_Order_Compatibility::has_shipping_address() method
177
+ * Fix - Prevent some deprecated notices when processing subscriptions in WooCommerce 3.0+
178
+
179
+ 2017.03.27 - version 4.6.0
180
+ * Tweak - Add Payment Gateway debug mode to the System Status report
181
+ * Tweak - Plugin "Docs" links now open in a new tab
182
+ * Misc - Add helper method to get normalized WooCommerce screen IDs
183
+ * Misc - Added support for WooCommerce 3.0
184
+
185
+ 2017.01.06 - version 4.5.2
186
+ * Fix - Include Curaçao when converting country codes
187
+
188
+ 2016.11.18 - version 4.5.1
189
+ * Fix - Prevent a potential fatal error for plugins not using the latest JSON/XML request classes
190
+
191
+ 2016.11.07 - version 4.5.0
192
+ * Feature - Mobile-friendly credit card fields using the `tel` input
193
+ * Feature - Add setting to enable capture for virtual-only orders
194
+ * Feature - Define minimum php.ini requirements an display a notice when they are not met
195
+ * Feature - Allow deprecated hooks to be mapped to their replacements
196
+ * Tweak - Move capture handling to the base gateway class to make it available to hosted gateways
197
+ * Tweak - Add a "card not accepted" icon when a card number format is not accepted or recognized
198
+ * Tweak - Add full MasterCard BIN Series 2 support & update the card logo
199
+ * Tweak - Improve consistency of card type IDs and abbreviations
200
+ * Tweak - Refactor gateway settings inheritance
201
+ * Fix - Fix failed renewal payment data not updating for auth-only renewals
202
+ * Fix - The `load_translation()` method is no longer required for base plugins
203
+ * Fix - Prevent notices when running alongside bbPress or BuddyPress
204
+
205
+ 2016.09.14 - version 4.4.3
206
+ * Fix - Fix an error when processing guest pre-order payments
207
+
208
+ 2016.08.02 - version 4.4.2
209
+ * Tweak - Refactor background job data structure and processing
210
+
211
+ 2016.07.18 - version 4.4.1
212
+ * Misc - Add compatibility for WordPress 4.6
213
+
214
+ 2016.06.01 - version 4.4.0
215
+ * Feature - Allow bundled framework and plugin translations to be easily overridden
216
+ * Tweak - Allow plugins extending SV_WC_API_Base to declare TLS v1.2 as a requirement for requests
217
+ * Misc - Added support for WooCommerce 2.6
218
+ * Misc - Removed support for WooCommerce 2.3
219
+
220
+ 2016.04.18 - version 4.3.0
221
+ * Feature - Revamped admin payment token editor
222
+ * Feature - Prevent deleting subscription payment methods
223
+ * Feature - Add payment gateway environment information to the WooCommerce system status report
224
+ * Tweak - Support WordPress core dismissible notices
225
+ * Tweak - Misc Payment Gateway framework improvements
226
+ * Fix - Properly validate CSC if present for tokenized payment methods
227
+ * Fix - Fix double confirm messages when deleting a payment method in certain cases
228
+
229
+ 2016.02.08 - version 4.2.2
230
+ * Fix - Fix handling guest pre-orders
231
+
232
+ 2016.01.20 - version 4.2.1
233
+ * Fix - Fix `implode()` warnings in `SV_WC_Helper::get_order_line_items()`
234
+
235
+ 2016.01.13 - version 4.2.0
236
+ * Feature - Greatly improved compatibility with multi-language/translation plugins
237
+ * Misc - Switched to using a separate text domain for the framework strings - 'woocommerce-plugin-framework'
238
+ * Misc - Added support for WooCommerce 2.5
239
+ * Misc - Removed support for WooCommerce 2.2
240
+
241
+ 2015.11.05 - version 4.1.2
242
+ * Tweak - Misc Payment Gateway framework improvements
243
+
244
+ 2015.09.09 - version 4.1.1
245
+ * Fix - For Subscriptions 1.5, don't mark the original order as failed when a renewal payment fails
246
+
247
+ 2015.08.27 - version 4.1.0
248
+ * Feature - WooCommerce Subscriptions 2.0 Support
249
+ * Tweak - Add specific width/height styling for payment method icons
250
+ * Fix - Fix assert() warnings with certain gateway configurations on the My Account page
251
+
252
+ 2015.07.29 - version 4.0.1
253
+ * Fix - Fix typo in payment gateway frontend javascript
254
+ * Tweak - Add inline style for payment gateway icons
255
+
256
+ 2015.07.27 - version 4.0.0
257
+ * Feature - Standardized payment gateway form
258
+ * Feature - Add new payment method feature
259
+ * Feature - Standardized & unified My Payment Methods table
260
+ * Feature - New payment method icons in SVG format
261
+ * Tweak - Adds is_woocommerce_active() method to bootstrap class, to support non-WooThemes listed frameworked plugins
262
+ * Tweak - Payment gateway token support now allows for merging local data with remote data, and caching results in a transient
263
+ * Tweak - The order status for voided orders is now "cancelled" rather than "refunded"
264
+ * Tweak - Improved support for REST API development
265
+ * Tweak - Framework bootstrap now gives the option to easily deactivate backwards incompatible plugins, rather than only an instruction to update older plugins
266
+ * Tweak - Framework bootstrap option to declare minimum required WordPress version
267
+ * Misc - WooCommerce 2.4 compatibility
268
+
269
+ 2015.03.17 - version 3.1.2
270
+ * Fix - JS variable `wc_select_params` undefined in WC 2.3.6+
271
+
272
+ 2015.03.10 - version 3.1.1
273
+ * Tweak - Add `get_cancel_order_url_raw()` compatibility method
274
+
275
+ 2015.02.09 - version 3.1.0
276
+ * Feature - Refund/Void support for the payment gateway framework, huzzah!
277
+ * Misc - WooCommerce 2.3 compatibility
278
+
279
+ 2014.12.11 - version 3.0.4
280
+ * Fix - Bug when removing a tokenized credit card from the My Account page
281
+ * Tweak - Accept a notice-class parameter when rendering admin notice to avoid always using the "error" notice
282
+
283
+ 2014.11.20 - version 3.0.3
284
+ * Fix - Payment gateway framework now catches all SV_WC_Plugin_Exception exceptions. Fixes uncaught SV_WC_API_Exception error.
285
+
286
+ 2014.10.19 - version 3.0.2
287
+ * Fix - Add commonly used notice functions to avoid errors when renewing subscriptions
288
+
289
+ 2014.10.15 - version 3.0.1
290
+ * Tweak - Method visibility changed from private to protected to allow adjustment via sub-classes
291
+ * Fix - Fix "Wrong parameters for Exception" fatal error
292
+
293
+ 2014.09.07 - version 3.0.0
294
+ * Feature - Edit tokens from Admin Order Edit screen
295
+ * Tweak - Improved dismissible admin notices
296
+ * Misc - WooCommerce 2.2 compatibility
297
+ * Misc - Backwards incompatible
298
+
299
+ 2014.08.26 - version 2.2.0
300
+ * Feature - Added API base class and automatic request logging
301
+ * Feature - Introduced Helper class
302
+ * Feature - Optional detailed customer decline messages on checkout
303
+ * Tweak - Introduced named exceptions
304
+ * Tweak - Updates to support Chase Paymentech certification mode
305
+ * Tweak - Updates to the Hosted Gateway class for improved handling of redirect-back gateways
306
+ * Tweak - My Payment Methods template now uses Dashicons instead of images
307
+ * Tweak - Plugin active method now checks filename only
308
+ * Fix - Fixed product page URLs
309
+
310
+ 2014.05.26 - version 2.1.0
311
+ * Feature - Implemented hosted payment gateway framework
312
+ * Feature - Capture charge bulk order action for payment gateways
313
+ * Tweak - Authorized charges are no longer automatically capture when changing order status from on-hold to processing/completed
314
+ * Feature - Added function dependency checks
315
+
316
+ 2014.03.05 - version 2.0.3
317
+ * Fix - Fixed WC 2.1 compatibility for payment gateway charge captures
318
+
319
+ 2014.02.03 - version 2.0.2
320
+ * Fix - Improved WC 2.1 compatibility method to return the order id on the checkout pay page
321
+
322
+ 2014.01.29 - version 2.0.1
323
+ * Tweak - Additional WC 2.1 compatibility helpers
324
+
325
+ 2014.01.20 - version 2.0.0
326
+ * Feature - Generalized Plugin Framework
327
+ * Feature - Support for "tokenize with sale" gateways
328
+ * Tweak - Improved handling for credit card capture request
329
+
330
+ 2013.11.11 - version 1.0.0
331
+ * First Release
vendor/skyverge/wc-plugin-framework/woocommerce/class-sv-wc-admin-notice-handler.php ADDED
@@ -0,0 +1,433 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * WooCommerce Plugin Framework
4
+ *
5
+ * This source file is subject to the GNU General Public License v3.0
6
+ * that is bundled with this package in the file license.txt.
7
+ * It is also available through the world-wide-web at this URL:
8
+ * http://www.gnu.org/licenses/gpl-3.0.html
9
+ * If you did not receive a copy of the license and are unable to
10
+ * obtain it through the world-wide-web, please send an email
11
+ * to license@skyverge.com so we can send you a copy immediately.
12
+ *
13
+ * DISCLAIMER
14
+ *
15
+ * Do not edit or add to this file if you wish to upgrade the plugin to newer
16
+ * versions in the future. If you wish to customize the plugin for your
17
+ * needs please refer to http://www.skyverge.com
18
+ *
19
+ * @package SkyVerge/WooCommerce/Plugin/Classes
20
+ * @author SkyVerge
21
+ * @copyright Copyright (c) 2013-2020, SkyVerge, Inc.
22
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GNU General Public License v3.0
23
+ */
24
+
25
+ namespace SkyVerge\WooCommerce\PluginFramework\v5_5_4;
26
+
27
+ defined( 'ABSPATH' ) or exit;
28
+
29
+ if ( ! class_exists( '\\SkyVerge\\WooCommerce\\PluginFramework\\v5_5_4\\SV_WC_Admin_Notice_Handler' ) ) :
30
+
31
+
32
+ /**
33
+ * SkyVerge Admin Notice Handler Class
34
+ *
35
+ * The purpose of this class is to provide a facility for displaying
36
+ * conditional (often dismissible) admin notices during a single page
37
+ * request
38
+ *
39
+ * @since 3.0.0
40
+ */
41
+ class SV_WC_Admin_Notice_Handler {
42
+
43
+
44
+ /** @var SV_WC_Plugin the plugin */
45
+ private $plugin;
46
+
47
+ /** @var array associative array of id to notice text */
48
+ private $admin_notices = array();
49
+
50
+ /** @var boolean static member to enforce a single rendering of the admin notice placeholder element */
51
+ static private $admin_notice_placeholder_rendered = false;
52
+
53
+ /** @var boolean static member to enforce a single rendering of the admin notice javascript */
54
+ static private $admin_notice_js_rendered = false;
55
+
56
+
57
+ /**
58
+ * Initialize and setup the Admin Notice Handler
59
+ *
60
+ * @since 3.0.0
61
+ */
62
+ public function __construct( $plugin ) {
63
+
64
+ $this->plugin = $plugin;
65
+
66
+ // render any admin notices, delayed notices, and
67
+ add_action( 'admin_notices', array( $this, 'render_admin_notices' ), 15 );
68
+ add_action( 'admin_footer', array( $this, 'render_delayed_admin_notices' ), 15 );
69
+ add_action( 'admin_footer', array( $this, 'render_admin_notice_js' ), 20 );
70
+
71
+ // AJAX handler to dismiss any warning/error notices
72
+ add_action( 'wp_ajax_wc_plugin_framework_' . $this->get_plugin()->get_id() . '_dismiss_notice', array( $this, 'handle_dismiss_notice' ) );
73
+ }
74
+
75
+
76
+ /**
77
+ * Adds the given $message as a dismissible notice identified by $message_id,
78
+ * unless the notice has been dismissed, or we're on the plugin settings page
79
+ *
80
+ * @since 3.0.0
81
+ * @param string $message the notice message to display
82
+ * @param string $message_id the message id
83
+ * @param array $params {
84
+ * Optional parameters.
85
+ *
86
+ * @type bool $dismissible If the notice should be dismissible
87
+ * @type bool $always_show_on_settings If the notice should be forced to display on the
88
+ * plugin settings page, regardless of `$dismissible`.
89
+ * @type string $notice_class Additional classes for the notice.
90
+ * }
91
+ */
92
+ public function add_admin_notice( $message, $message_id, $params = array() ) {
93
+
94
+ $params = wp_parse_args( $params, array(
95
+ 'dismissible' => true,
96
+ 'always_show_on_settings' => true,
97
+ 'notice_class' => 'updated',
98
+ ) );
99
+
100
+ if ( $this->should_display_notice( $message_id, $params ) ) {
101
+ $this->admin_notices[ $message_id ] = array(
102
+ 'message' => $message,
103
+ 'rendered' => false,
104
+ 'params' => $params,
105
+ );
106
+ }
107
+ }
108
+
109
+
110
+ /**
111
+ * Returns true if the identified notice hasn't been cleared, or we're on
112
+ * the plugin settings page (where notices are always displayed)
113
+ *
114
+ * @since 3.0.0
115
+ * @param string $message_id the message id
116
+ * @param array $params {
117
+ * Optional parameters.
118
+ *
119
+ * @type bool $dismissible If the notice should be dismissible
120
+ * @type bool $always_show_on_settings If the notice should be forced to display on the
121
+ * plugin settings page, regardless of `$dismissible`.
122
+ * }
123
+ * @return bool
124
+ */
125
+ public function should_display_notice( $message_id, $params = array() ) {
126
+
127
+ // bail out if user is not a shop manager
128
+ if ( ! current_user_can( 'manage_woocommerce' ) ) {
129
+ return false;
130
+ }
131
+
132
+ $params = wp_parse_args( $params, array(
133
+ 'dismissible' => true,
134
+ 'always_show_on_settings' => true,
135
+ ) );
136
+
137
+ // if the notice is always shown on the settings page, and we're on the settings page
138
+ if ( $params['always_show_on_settings'] && $this->get_plugin()->is_plugin_settings() ) {
139
+ return true;
140
+ }
141
+
142
+ // non-dismissible, always display
143
+ if ( ! $params['dismissible'] ) {
144
+ return true;
145
+ }
146
+
147
+ // dismissible: display if notice has not been dismissed
148
+ return ! $this->is_notice_dismissed( $message_id );
149
+ }
150
+
151
+
152
+ /**
153
+ * Render any admin notices, as well as the admin notice placeholder
154
+ *
155
+ * @since 3.0.0
156
+ * @param boolean $is_visible true if the notices should be immediately visible, false otherwise
157
+ */
158
+ public function render_admin_notices( $is_visible = true ) {
159
+
160
+ // default for actions
161
+ if ( ! is_bool( $is_visible ) ) {
162
+ $is_visible = true;
163
+ }
164
+
165
+ foreach ( $this->admin_notices as $message_id => $message_data ) {
166
+ if ( ! $message_data['rendered'] ) {
167
+ $message_data['params']['is_visible'] = $is_visible;
168
+ $this->render_admin_notice( $message_data['message'], $message_id, $message_data['params'] );
169
+ $this->admin_notices[ $message_id ]['rendered'] = true;
170
+ }
171
+ }
172
+
173
+ if ( $is_visible && ! self::$admin_notice_placeholder_rendered ) {
174
+ // placeholder for moving delayed notices up into place
175
+ echo '<div class="js-wc-' . esc_attr( $this->get_plugin()->get_id_dasherized() ) . '-admin-notice-placeholder"></div>';
176
+ self::$admin_notice_placeholder_rendered = true;
177
+ }
178
+
179
+ }
180
+
181
+
182
+ /**
183
+ * Render any delayed admin notices, which have not yet already been rendered
184
+ *
185
+ * @since 3.0.0
186
+ */
187
+ public function render_delayed_admin_notices() {
188
+ $this->render_admin_notices( false );
189
+ }
190
+
191
+
192
+ /**
193
+ * Render a single admin notice
194
+ *
195
+ * @since 3.0.0
196
+ * @param string $message the notice message to display
197
+ * @param string $message_id the message id
198
+ * @param array $params {
199
+ * Optional parameters.
200
+ *
201
+ * @type bool $dismissible If the notice should be dismissible
202
+ * @type bool $is_visible If the notice should be immediately visible
203
+ * @type bool $always_show_on_settings If the notice should be forced to display on the
204
+ * plugin settings page, regardless of `$dismissible`.
205
+ * @type string $notice_class Additional classes for the notice.
206
+ * }
207
+ */
208
+ public function render_admin_notice( $message, $message_id, $params = array() ) {
209
+
210
+ $params = wp_parse_args( $params, array(
211
+ 'dismissible' => true,
212
+ 'is_visible' => true,
213
+ 'always_show_on_settings' => true,
214
+ 'notice_class' => 'updated',
215
+ ) );
216
+
217
+ $classes = array(
218
+ 'notice',
219
+ 'js-wc-plugin-framework-admin-notice',
220
+ $params['notice_class'],
221
+ );
222
+
223
+ // maybe make this notice dismissible
224
+ // uses a WP core class which handles the markup and styling
225
+ if ( $params['dismissible'] && ( ! $params['always_show_on_settings'] || ! $this->get_plugin()->is_plugin_settings() ) ) {
226
+ $classes[] = 'is-dismissible';
227
+ }
228
+
229
+ echo sprintf(
230
+ '<div class="%1$s" data-plugin-id="%2$s" data-message-id="%3$s" %4$s><p>%5$s</p></div>',
231
+ esc_attr( implode( ' ', $classes ) ),
232
+ esc_attr( $this->get_plugin()->get_id() ),
233
+ esc_attr( $message_id ),
234
+ ( ! $params['is_visible'] ) ? 'style="display:none;"' : '',
235
+ wp_kses_post( $message )
236
+ );
237
+ }
238
+
239
+
240
+ /**
241
+ * Render the javascript to handle the notice "dismiss" functionality
242
+ *
243
+ * @since 3.0.0
244
+ */
245
+ public function render_admin_notice_js() {
246
+
247
+ // if there were no notices, or we've already rendered the js, there's nothing to do
248
+ if ( empty( $this->admin_notices ) || self::$admin_notice_js_rendered ) {
249
+ return;
250
+ }
251
+
252
+ $plugin_slug = $this->get_plugin()->get_id_dasherized();
253
+
254
+ self::$admin_notice_js_rendered = true;
255
+
256
+ ob_start();
257
+ ?>
258
+
259
+ // Log dismissed notices
260
+ $( '.js-wc-plugin-framework-admin-notice' ).on( 'click.wp-dismiss-notice', '.notice-dismiss', function( e ) {
261
+
262
+ var $notice = $( this ).closest( '.js-wc-plugin-framework-admin-notice' );
263
+
264
+ log_dismissed_notice(
265
+ $( $notice ).data( 'plugin-id' ),
266
+ $( $notice ).data( 'message-id' )
267
+ );
268
+
269
+ } );
270
+
271
+ // Log and hide legacy notices
272
+ $( 'a.js-wc-plugin-framework-notice-dismiss' ).click( function( e ) {
273
+
274
+ e.preventDefault();
275
+
276
+ var $notice = $( this ).closest( '.js-wc-plugin-framework-admin-notice' );
277
+
278
+ log_dismissed_notice(
279
+ $( $notice ).data( 'plugin-id' ),
280
+ $( $notice ).data( 'message-id' )
281
+ );
282
+
283
+ $( $notice ).fadeOut();
284
+
285
+ } );
286
+
287
+ function log_dismissed_notice( pluginID, messageID ) {
288
+
289
+ $.get(
290
+ ajaxurl,
291
+ {
292
+ action: 'wc_plugin_framework_' + pluginID + '_dismiss_notice',
293
+ messageid: messageID
294
+ }
295
+ );
296
+ }
297
+
298
+ // move any delayed notices up into position .show();
299
+ $( '.js-wc-plugin-framework-admin-notice:hidden' ).insertAfter( '.js-wc-<?php echo esc_js( $plugin_slug ); ?>-admin-notice-placeholder' ).show();
300
+ <?php
301
+ $javascript = ob_get_clean();
302
+
303
+ wc_enqueue_js( $javascript );
304
+ }
305
+
306
+
307
+ /**
308
+ * Marks the identified admin notice as dismissed for the given user
309
+ *
310
+ * @since 3.0.0
311
+ * @param string $message_id the message identifier
312
+ * @param int $user_id optional user identifier, defaults to current user
313
+ */
314
+ public function dismiss_notice( $message_id, $user_id = null ) {
315
+
316
+ if ( is_null( $user_id ) ) {
317
+ $user_id = get_current_user_id();
318
+ }
319
+
320
+ $dismissed_notices = $this->get_dismissed_notices( $user_id );
321
+
322
+ $dismissed_notices[ $message_id ] = true;
323
+
324
+ update_user_meta( $user_id, '_wc_plugin_framework_' . $this->get_plugin()->get_id() . '_dismissed_messages', $dismissed_notices );
325
+
326
+ /**
327
+ * Admin Notice Dismissed Action.
328
+ *
329
+ * Fired when a user dismisses an admin notice.
330
+ *
331
+ * @since 3.0.0
332
+ * @param string $message_id notice identifier
333
+ * @param string|int $user_id
334
+ */
335
+ do_action( 'wc_' . $this->get_plugin()->get_id(). '_dismiss_notice', $message_id, $user_id );
336
+ }
337
+
338
+
339
+ /**
340
+ * Marks the identified admin notice as not dismissed for the identified user
341
+ *
342
+ * @since 3.0.0
343
+ * @param string $message_id the message identifier
344
+ * @param int $user_id optional user identifier, defaults to current user
345
+ */
346
+ public function undismiss_notice( $message_id, $user_id = null ) {
347
+
348
+ if ( is_null( $user_id ) ) {
349
+ $user_id = get_current_user_id();
350
+ }
351
+
352
+ $dismissed_notices = $this->get_dismissed_notices( $user_id );
353
+
354
+ $dismissed_notices[ $message_id ] = false;
355
+
356
+ update_user_meta( $user_id, '_wc_plugin_framework_' . $this->get_plugin()->get_id() . '_dismissed_messages', $dismissed_notices );
357
+ }
358
+
359
+
360
+ /**
361
+ * Returns true if the identified admin notice has been dismissed for the
362
+ * given user
363
+ *
364
+ * @since 3.0.0
365
+ * @param string $message_id the message identifier
366
+ * @param int $user_id optional user identifier, defaults to current user
367
+ * @return boolean true if the message has been dismissed by the admin user
368
+ */
369
+ public function is_notice_dismissed( $message_id, $user_id = null ) {
370
+
371
+ $dismissed_notices = $this->get_dismissed_notices( $user_id );
372
+
373
+ return isset( $dismissed_notices[ $message_id ] ) && $dismissed_notices[ $message_id ];
374
+ }
375
+
376
+
377
+ /**
378
+ * Returns the full set of dismissed notices for the user identified by
379
+ * $user_id, for this plugin
380
+ *
381
+ * @since 3.0.0
382
+ * @param int $user_id optional user identifier, defaults to current user
383
+ * @return array of message id to dismissed status (true or false)
384
+ */
385
+ public function get_dismissed_notices( $user_id = null ) {
386
+
387
+ if ( is_null( $user_id ) ) {
388
+ $user_id = get_current_user_id();
389
+ }
390
+
391
+ $dismissed_notices = get_user_meta( $user_id, '_wc_plugin_framework_' . $this->get_plugin()->get_id() . '_dismissed_messages', true );
392
+
393
+ if ( empty( $dismissed_notices ) ) {
394
+ return array();
395
+ } else {
396
+ return $dismissed_notices;
397
+ }
398
+ }
399
+
400
+
401
+ /** AJAX methods ******************************************************/
402
+
403
+
404
+ /**
405
+ * Dismiss the identified notice
406
+ *
407
+ * @since 3.0.0
408
+ */
409
+ public function handle_dismiss_notice() {
410
+
411
+ $this->dismiss_notice( $_REQUEST['messageid'] );
412
+
413
+ }
414
+
415
+
416
+ /** Getter methods ******************************************************/
417
+
418
+
419
+ /**
420
+ * Get the plugin
421
+ *
422
+ * @since 3.0.0
423
+ * @return SV_WC_Plugin returns the plugin instance
424
+ */
425
+ protected function get_plugin() {
426
+ return $this->plugin;
427
+ }
428
+
429
+
430
+ }
431
+
432
+
433
+ endif;
vendor/skyverge/wc-plugin-framework/woocommerce/class-sv-wc-framework-bootstrap.php ADDED
@@ -0,0 +1,407 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * WooCommerce Plugin Framework
4
+ *
5
+ * This source file is subject to the GNU General Public License v3.0
6
+ * that is bundled with this package in the file license.txt.
7
+ * It is also available through the world-wide-web at this URL:
8
+ * http://www.gnu.org/licenses/gpl-3.0.html
9
+ * If you did not receive a copy of the license and are unable to
10
+ * obtain it through the world-wide-web, please send an email
11
+ * to license@skyverge.com so we can send you a copy immediately.
12
+ *
13
+ * DISCLAIMER
14
+ *
15
+ * Do not edit or add to this file if you wish to upgrade the plugin to newer
16
+ * versions in the future. If you wish to customize the plugin for your
17
+ * needs please refer to http://www.skyverge.com
18
+ *
19
+ * @package SkyVerge/WooCommerce/Plugin/Classes
20
+ * @author SkyVerge
21
+ * @copyright Copyright (c) 2013-2020, SkyVerge, Inc.
22
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GNU General Public License v3.0
23
+ */
24
+
25
+ defined( 'ABSPATH' ) or exit;
26
+
27
+ if ( ! class_exists( 'SV_WC_Framework_Bootstrap' ) ) :
28
+
29
+
30
+ /**
31
+ * # SkyVerge WooCommerce Plugin Framework Bootstrap
32
+ *
33
+ * The purpose of this class is to find and load the highest versioned
34
+ * framework of the activated framework plugins, and then initialize any
35
+ * compatible framework plugins.
36
+ *
37
+ * @since 2.0.0
38
+ */
39
+ class SV_WC_Framework_Bootstrap {
40
+
41
+
42
+ /** @var SV_WC_Framework_Bootstrap The single instance of the class */
43
+ protected static $instance = null;
44
+
45
+ /** @var array registered framework plugins */
46
+ protected $registered_plugins = array();
47
+
48
+ /** @var array registered and active framework plugins */
49
+ protected $active_plugins = array();
50
+
51
+ /** @var array of plugins that need to be updated due to an outdated framework */
52
+ protected $incompatible_framework_plugins = array();
53
+
54
+ /** @var array of plugins that require a newer version of WC */
55
+ protected $incompatible_wc_version_plugins = array();
56
+
57
+ /** @var array of plugins that require a newer version of WP */
58
+ protected $incompatible_wp_version_plugins = array();
59
+
60
+
61
+ /**
62
+ * Hidden constructor
63
+ *
64
+ * @since 2.0.0
65
+ */
66
+ private function __construct() {
67
+
68
+ // load framework plugins once all plugins are loaded
69
+ add_action( 'plugins_loaded', array( $this, 'load_framework_plugins' ) );
70
+
71
+ // deactivate backwards-incompatible framework plugins if the admin isn't ready to upgrade old plugins
72
+ add_action( 'admin_init', array( $this, 'maybe_deactivate_framework_plugins' ) );
73
+ }
74
+
75
+
76
+ /**
77
+ * Instantiate the class singleton
78
+ *
79
+ * @since 2.0.0
80
+ * @return SV_WC_Framework_Bootstrap singleton instance
81
+ */
82
+ public static function instance() {
83
+ if ( is_null( self::$instance ) ) {
84
+ self::$instance = new self();
85
+ }
86
+
87
+ return self::$instance;
88
+ }
89
+
90
+
91
+ /**
92
+ * Register a frameworked plugin
93
+ *
94
+ * @since 2.0.0
95
+ * @param string $version the framework version
96
+ * @param string $plugin_name the plugin name
97
+ * @param string $path the plugin path
98
+ * @param callable $callback function to initialize the plugin
99
+ * @param array $args optional plugin arguments. Possible arguments: 'is_payment_gateway', 'backwards_compatible'
100
+ */
101
+ public function register_plugin( $version, $plugin_name, $path, $callback, $args = array() ) {
102
+ $this->registered_plugins[] = array( 'version' => $version, 'plugin_name' => $plugin_name, 'path' => $path, 'callback' => $callback, 'args' => $args );
103
+ }
104
+
105
+
106
+ /**
107
+ * Loads all registered framework plugins, first initializing the plugin
108
+ * framework by loading the highest versioned one.
109
+ *
110
+ * @since 2.0.0
111
+ */
112
+ public function load_framework_plugins() {
113
+
114
+ // first sort the registered plugins by framework version
115
+ usort( $this->registered_plugins, array( $this, 'compare_frameworks' ) );
116
+
117
+ $loaded_framework = null;
118
+
119
+ foreach ( $this->registered_plugins as $plugin ) {
120
+
121
+ // load the first found (highest versioned) plugin framework class
122
+ if ( ! class_exists( 'SV_WC_Plugin' ) ) {
123
+ require_once( $this->get_plugin_path( $plugin['path'] ) . '/lib/skyverge/woocommerce/class-sv-wc-plugin.php' );
124
+ $loaded_framework = $plugin;
125
+
126
+ // the loaded plugin is always considered active (for the
127
+ // purposes of handling conflicts between this and other plugins
128
+ // with incompatible framework versions)
129
+ $this->active_plugins[] = $plugin;
130
+ }
131
+
132
+ // if the loaded version of the framework has a backwards compatibility requirement
133
+ // which is not met by the current plugin add an admin notice and move on without
134
+ // loading the plugin
135
+ if ( ! empty( $loaded_framework['args']['backwards_compatible'] ) && version_compare( $loaded_framework['args']['backwards_compatible'], $plugin['version'], '>' ) ) {
136
+
137
+ $this->incompatible_framework_plugins[] = $plugin;
138
+
139
+ // next plugin
140
+ continue;
141
+ }
142
+
143
+ // if a plugin defines a minimum WC version which is not met, render a notice and skip loading the plugin
144
+ if ( ! empty( $plugin['args']['minimum_wc_version'] ) && version_compare( $this->get_wc_version(), $plugin['args']['minimum_wc_version'], '<' ) ) {
145
+
146
+ $this->incompatible_wc_version_plugins[] = $plugin;
147
+
148
+ // next plugin
149
+ continue;
150
+ }
151
+
152
+ // if a plugin defines a minimum WP version which is not met, render a notice and skip loading the plugin
153
+ if ( ! empty( $plugin['args']['minimum_wp_version'] ) && version_compare( get_bloginfo( 'version' ), $plugin['args']['minimum_wp_version'], '<' ) ) {
154
+
155
+ $this->incompatible_wp_version_plugins[] = $plugin;
156
+
157
+ // next plugin
158
+ continue;
159
+ }
160
+
161
+ // add this plugin to the active list
162
+ if ( ! in_array( $plugin, $this->active_plugins ) ) {
163
+ $this->active_plugins[] = $plugin;
164
+ }
165
+
166
+ // load the first found (highest versioned) payment gateway framework class, as needed
167
+ if ( isset( $plugin['args']['is_payment_gateway'] ) && ! class_exists( 'SV_WC_Payment_Gateway' ) ) {
168
+ require_once( $this->get_plugin_path( $plugin['path'] ) . '/lib/skyverge/woocommerce/payment-gateway/class-sv-wc-payment-gateway-plugin.php' );
169
+ }
170
+
171
+ // initialize the plugin
172
+ $plugin['callback']();
173
+ }
174
+
175
+ // render update notices
176
+ if ( ( $this->incompatible_framework_plugins || $this->incompatible_wc_version_plugins || $this->incompatible_wp_version_plugins ) && is_admin() && ! defined( 'DOING_AJAX' ) && ! has_action( 'admin_notices', array( $this, 'render_update_notices' ) ) ) {
177
+
178
+ add_action( 'admin_notices', array( $this, 'render_update_notices' ) );
179
+ }
180
+
181
+ /**
182
+ * WC Plugin Framework Plugins Loaded Action.
183
+ *
184
+ * Fired when all frameworked plugins are loaded. Frameworked plugins can
185
+ * hook into this action rather than `plugins_loaded`/`woocommerce_loaded`
186
+ * as needed.
187
+ *
188
+ * @since 2.0.0
189
+ */
190
+ do_action( 'sv_wc_framework_plugins_loaded' );
191
+ }
192
+
193
+
194
+ /** Admin methods ******************************************************/
195
+
196
+
197
+ /**
198
+ * Deactivate backwards-incompatible framework plugins, which will allow
199
+ * plugins with an older version of the framework to be active. Useful when
200
+ * the admin isn't ready to upgrade older plugins yet needs them to still
201
+ * function (e.g. a payment gateway)
202
+ *
203
+ * @since 4.0.0
204
+ */
205
+ public function maybe_deactivate_framework_plugins() {
206
+
207
+ if ( isset( $_GET['sv_wc_framework_deactivate_newer'] ) ) {
208
+ if ( 'yes' == $_GET['sv_wc_framework_deactivate_newer'] ) {
209
+
210
+ // don't want to just deactivate all active plugins willy-nilly if there's no incompatible plugins
211
+ if ( count( $this->incompatible_framework_plugins ) == 0 ) {
212
+ return;
213
+ }
214
+
215
+ $plugins = array();
216
+
217
+ foreach ( $this->active_plugins as $plugin ) {
218
+ $plugins[] = plugin_basename( $plugin['path'] );
219
+ }
220
+
221
+ // deactivate all "active" frameworked plugins, these will be the newest, backwards-incompatible ones
222
+ deactivate_plugins( $plugins );
223
+
224
+ // redirect to the inactive plugin admin page, with a message indicating the number of plugins deactivated
225
+ wp_redirect( admin_url( 'plugins.php?plugin_status=inactive&sv_wc_framework_deactivate_newer=' . count( $plugins ) ) );
226
+ exit;
227
+ } else {
228
+ // we're on the inactive plugin page and we've deactivated one or more plugins
229
+ add_action( 'admin_notices', array( $this, 'render_deactivation_notice' ) );
230
+ }
231
+ }
232
+ }
233
+
234
+
235
+ /**
236
+ * Render a notice with a count of the backwards incompatible frameworked
237
+ * plugins that were deactivated
238
+ *
239
+ * @since 4.0.0
240
+ */
241
+ public function render_deactivation_notice() {
242
+ echo '<div class="updated"><p>';
243
+ echo $_GET['sv_wc_framework_deactivate_newer'] > 1 ?
244
+ sprintf( 'Deactivated %d plugins', $_GET['sv_wc_framework_deactivate_newer'] ) :
245
+ 'Deactivated one plugin';
246
+ echo '</p></div>';
247
+ }
248
+
249
+
250
+ /**
251
+ * Render a notice to update any plugins with incompatible framework
252
+ * versions, or incompatiblities with the current WooCommerce or WordPress
253
+ * versions
254
+ *
255
+ * @since 2.0.0
256
+ */
257
+ public function render_update_notices() {
258
+
259
+ // must update plugin notice
260
+ if ( ! empty( $this->incompatible_framework_plugins ) ) {
261
+
262
+ $plugin_count = count( $this->incompatible_framework_plugins );
263
+
264
+ echo '<div class="error">';
265
+
266
+ // describe the problem
267
+ echo '<p>';
268
+ echo esc_html( _n( 'The following plugin is disabled because it is out of date and incompatible with newer plugins on your site:', 'The following plugins are disabled because they are out of date and incompatible with newer plugins on your site:', $plugin_count, 'woocommerce-plugin-framework' ) );
269
+ echo '</p>';
270
+
271
+ // add a incompatible plugin list
272
+ echo '<ul>';
273
+ foreach ( $this->incompatible_framework_plugins as $plugin ) {
274
+ printf( '<li>%s</li>', esc_html( $plugin['plugin_name'] ) );
275
+ }
276
+ echo '</ul>';
277
+
278
+ // describe the way to fix it
279
+ echo '<p>';
280
+ printf(
281
+ /** translators: Placeholders: %1$s - <a> tag, %2$s - </a> tag, %3$s - <em> tag, %4$s - </em> tag, %5$s - <a> tag, %6$s - </a> tag, %7$s - <a> tag, %8$s - </a> tag */
282
+ esc_html( _n( 'To resolve this, please %1$supdate%2$s (recommended) %3$sor%4$s %5$sdeactivate%6$s the above plugin, or %7$sdeactivate the following%8$s:', 'To resolve this, please %1$supdate%2$s (recommended) %3$sor%4$s %5$sdeactivate%6$s the above plugins, or %7$sdeactivate the following%8$s:', $plugin_count, 'woocommerce-plugin-framework' ) ),
283
+ '<a href="' . esc_url( admin_url( 'update-core.php' ) ) . '">', '</a>',
284
+ '<em>', '</em>',
285
+ '<a href="' . esc_url( admin_url( 'update-core.php' ) ) . '">', '</a>',
286
+ '<a href="' . esc_url( admin_url( 'plugins.php?sv_wc_framework_deactivate_newer=yes' ) ) . '">', '</a>'
287
+ );
288
+ echo '</p>';
289
+
290
+ // add the list of active plugins
291
+ echo '<ul>';
292
+ foreach ( $this->active_plugins as $plugin ) {
293
+ printf( '<li>%s</li>', esc_html( $plugin['plugin_name'] ) );
294
+ }
295
+ echo '</ul>';
296
+
297
+ echo '</div>';
298
+ }
299
+
300
+ // must update WC notice
301
+ if ( ! empty( $this->incompatible_wc_version_plugins ) ) {
302
+
303
+ printf( '<div class="error"><p>%s</p><ul>', count( $this->incompatible_wc_version_plugins ) > 1 ? esc_html__( 'The following plugins are inactive because they require a newer version of WooCommerce:', 'woocommerce-plugin-framework' ) : esc_html__( 'The following plugin is inactive because it requires a newer version of WooCommerce:', 'woocommerce-plugin-framework' ) );
304
+
305
+ foreach ( $this->incompatible_wc_version_plugins as $plugin ) {
306
+
307
+ /* translators: Placeholders: %1$s - plugin name, %2$s - WooCommerce version number */
308
+ echo '<li>' . sprintf( esc_html__( '%1$s requires WooCommerce %2$s or newer', 'woocommerce-plugin-framework' ), esc_html( $plugin['plugin_name'] ), esc_html( $plugin['args']['minimum_wc_version'] ) ) . '</li>';
309
+ }
310
+
311
+ /* translators: Placeholders: %1$s - <a> tag, %2$s - </a> tag */
312
+ echo '</ul><p>' . sprintf( esc_html__( 'Please %1$supdate WooCommerce%2$s', 'woocommerce-plugin-framework' ), '<a href="' . esc_url( admin_url( 'update-core.php' ) ) . '">', '&nbsp;&raquo;</a>' ) . '</p></div>';
313
+ }
314
+
315
+ // must update WP notice
316
+ if ( ! empty( $this->incompatible_wp_version_plugins ) ) {
317
+
318
+ printf( '<div class="error"><p>%s</p><ul>', count( $this->incompatible_wp_version_plugins ) > 1 ? 'The following plugins are inactive because they require a newer version of WordPress:' : 'The following plugin is inactive because it requires a newer version of WordPress:' );
319
+
320
+ foreach ( $this->incompatible_wp_version_plugins as $plugin ) {
321
+ printf( '<li>%s requires WordPress %s or newer</li>', esc_html( $plugin['plugin_name'] ), esc_html( $plugin['args']['minimum_wp_version'] ) );
322
+ }
323
+
324
+ echo '</ul><p>Please <a href="' . esc_url( admin_url( 'update-core.php' ) ) . '">update WordPress&nbsp;&raquo;</a></p></div>';
325
+ }
326
+ }
327
+
328
+
329
+ /** Helper methods ******************************************************/
330
+
331
+
332
+ /**
333
+ * Is the WooCommerce plugin installed and active? This method is handy for
334
+ * frameworked plugins that are listed on wordpress.org and thus don't have
335
+ * access to the Woo Helper functions bundled with WooThemes-listed plugins.
336
+ *
337
+ * Notice: For now you can't rely on this method being available, since the
338
+ * bootstrap class is the only piece of the framework which is loaded
339
+ * simply according to the lexical order of plugin directories. Therefore
340
+ * to use, you should first check that this method exists, or if you really
341
+ * need to check for WooCommerce being active, define your own method.
342
+ *
343
+ * @since 4.0.0
344
+ * @return boolean true if the WooCommerce plugin is installed and active
345
+ */
346
+ public static function is_woocommerce_active() {
347
+
348
+ $active_plugins = (array) get_option( 'active_plugins', array() );
349
+
350
+ if ( is_multisite() ) {
351
+ $active_plugins = array_merge( $active_plugins, get_site_option( 'active_sitewide_plugins', array() ) );
352
+ }
353
+
354
+ return in_array( 'woocommerce/woocommerce.php', $active_plugins ) || array_key_exists( 'woocommerce/woocommerce.php', $active_plugins );
355
+ }
356
+
357
+
358
+ /**
359
+ * Compare the two framework versions. Returns -1 if $a is less than $b, 0 if
360
+ * they're equal, and 1 if $a is greater than $b
361
+ *
362
+ * @since 2.0.0
363
+ * @param array $a first registered plugin to compare
364
+ * @param array $b second registered plugin to compare
365
+ * @return int -1 if $a is less than $b, 0 if they're equal, and 1 if $a is greater than $b
366
+ */
367
+ public function compare_frameworks( $a, $b ) {
368
+ // compare versions without the operator argument, so we get a -1, 0 or 1 result
369
+ return version_compare( $b['version'], $a['version'] );
370
+ }
371
+
372
+
373
+ /**
374
+ * Returns the plugin path for the given $file
375
+ *
376
+ * @since 2.0.0
377
+ * @param string $file the file
378
+ * @return string plugin path
379
+ */
380
+ public function get_plugin_path( $file ) {
381
+ return untrailingslashit( plugin_dir_path( $file ) );
382
+ }
383
+
384
+
385
+ /**
386
+ * Returns the WooCommerce version number, backwards compatible to
387
+ * WC 1.5
388
+ *
389
+ * @since 3.0.0
390
+ * @return null|string
391
+ */
392
+ protected function get_wc_version() {
393
+
394
+ if ( defined( 'WC_VERSION' ) && WC_VERSION ) return WC_VERSION;
395
+ if ( defined( 'WOOCOMMERCE_VERSION' ) && WOOCOMMERCE_VERSION ) return WOOCOMMERCE_VERSION;
396
+
397
+ return null;
398
+ }
399
+
400
+ }
401
+
402
+
403
+ // instantiate the class
404
+ SV_WC_Framework_Bootstrap::instance();
405
+
406
+
407
+ endif;
vendor/skyverge/wc-plugin-framework/woocommerce/class-sv-wc-helper.php ADDED
@@ -0,0 +1,1024 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * WooCommerce Plugin Framework
4
+ *
5
+ * This source file is subject to the GNU General Public License v3.0
6
+ * that is bundled with this package in the file license.txt.
7
+ * It is also available through the world-wide-web at this URL:
8
+ * http://www.gnu.org/licenses/gpl-3.0.html
9
+ * If you did not receive a copy of the license and are unable to
10
+ * obtain it through the world-wide-web, please send an email
11
+ * to license@skyverge.com so we can send you a copy immediately.
12
+ *
13
+ * DISCLAIMER
14
+ *
15
+ * Do not edit or add to this file if you wish to upgrade the plugin to newer
16
+ * versions in the future. If you wish to customize the plugin for your
17
+ * needs please refer to http://www.skyverge.com
18
+ *
19
+ * @package SkyVerge/WooCommerce/Plugin/Classes
20
+ * @author SkyVerge
21
+ * @copyright Copyright (c) 2013-2020, SkyVerge, Inc.
22
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GNU General Public License v3.0
23
+ */
24
+
25
+ namespace SkyVerge\WooCommerce\PluginFramework\v5_5_4;
26
+
27
+ defined( 'ABSPATH' ) or exit;
28
+
29
+ if ( ! class_exists( '\\SkyVerge\\WooCommerce\\PluginFramework\\v5_5_4\\SV_WC_Helper' ) ) :
30
+
31
+
32
+ /**
33
+ * SkyVerge Helper Class
34
+ *
35
+ * The purpose of this class is to centralize common utility functions that
36
+ * are commonly used in SkyVerge plugins
37
+ *
38
+ * @since 2.2.0
39
+ */
40
+ class SV_WC_Helper {
41
+
42
+
43
+ /** encoding used for mb_*() string functions */
44
+ const MB_ENCODING = 'UTF-8';
45
+
46
+
47
+ /** String manipulation functions (all multi-byte safe) ***************/
48
+
49
+ /**
50
+ * Returns true if the haystack string starts with needle
51
+ *
52
+ * Note: case-sensitive
53
+ *
54
+ * @since 2.2.0
55
+ * @param string $haystack
56
+ * @param string $needle
57
+ * @return bool
58
+ */
59
+ public static function str_starts_with( $haystack, $needle ) {
60
+
61
+ if ( self::multibyte_loaded() ) {
62
+
63
+ if ( '' === $needle ) {
64
+ return true;
65
+ }
66
+
67
+ return 0 === mb_strpos( $haystack, $needle, 0, self::MB_ENCODING );
68
+
69
+ } else {
70
+
71
+ $needle = self::str_to_ascii( $needle );
72
+
73
+ if ( '' === $needle ) {
74
+ return true;
75
+ }
76
+
77
+ return 0 === strpos( self::str_to_ascii( $haystack ), self::str_to_ascii( $needle ) );
78
+ }
79
+ }
80
+
81
+
82
+ /**
83
+ * Return true if the haystack string ends with needle
84
+ *
85
+ * Note: case-sensitive
86
+ *
87
+ * @since 2.2.0
88
+ * @param string $haystack
89
+ * @param string $needle
90
+ * @return bool
91
+ */
92
+ public static function str_ends_with( $haystack, $needle ) {
93
+
94
+ if ( '' === $needle ) {
95
+ return true;
96
+ }
97
+
98
+ if ( self::multibyte_loaded() ) {
99
+
100
+ return mb_substr( $haystack, -mb_strlen( $needle, self::MB_ENCODING ), null, self::MB_ENCODING ) === $needle;
101
+
102
+ } else {
103
+
104
+ $haystack = self::str_to_ascii( $haystack );
105
+ $needle = self::str_to_ascii( $needle );
106
+
107
+ return substr( $haystack, -strlen( $needle ) ) === $needle;
108
+ }
109
+ }
110
+
111
+
112
+ /**
113
+ * Returns true if the needle exists in haystack
114
+ *
115
+ * Note: case-sensitive
116
+ *
117
+ * @since 2.2.0
118
+ * @param string $haystack
119
+ * @param string $needle
120
+ * @return bool
121
+ */
122
+ public static function str_exists( $haystack, $needle ) {
123
+
124
+ if ( self::multibyte_loaded() ) {
125
+
126
+ if ( '' === $needle ) {
127
+ return false;
128
+ }
129
+
130
+ return false !== mb_strpos( $haystack, $needle, 0, self::MB_ENCODING );
131
+
132
+ } else {
133
+
134
+ $needle = self::str_to_ascii( $needle );
135
+
136
+ if ( '' === $needle ) {
137
+ return false;
138
+ }
139
+
140
+ return false !== strpos( self::str_to_ascii( $haystack ), self::str_to_ascii( $needle ) );
141
+ }
142
+ }
143
+
144
+
145
+ /**
146
+ * Truncates a given $string after a given $length if string is longer than
147
+ * $length. The last characters will be replaced with the $omission string
148
+ * for a total length not exceeding $length
149
+ *
150
+ * @since 2.2.0
151
+ * @param string $string text to truncate
152
+ * @param int $length total desired length of string, including omission
153
+ * @param string $omission omission text, defaults to '...'
154
+ * @return string
155
+ */
156
+ public static function str_truncate( $string, $length, $omission = '...' ) {
157
+
158
+ if ( self::multibyte_loaded() ) {
159
+
160
+ // bail if string doesn't need to be truncated
161
+ if ( mb_strlen( $string, self::MB_ENCODING ) <= $length ) {
162
+ return $string;
163
+ }
164
+
165
+ $length -= mb_strlen( $omission, self::MB_ENCODING );
166
+
167
+ return mb_substr( $string, 0, $length, self::MB_ENCODING ) . $omission;
168
+
169
+ } else {
170
+
171
+ $string = self::str_to_ascii( $string );
172
+
173
+ // bail if string doesn't need to be truncated
174
+ if ( strlen( $string ) <= $length ) {
175
+ return $string;
176
+ }
177
+
178
+ $length -= strlen( $omission );
179
+
180
+ return substr( $string, 0, $length ) . $omission;
181
+ }
182
+ }
183
+
184
+
185
+ /**
186
+ * Returns a string with all non-ASCII characters removed. This is useful
187
+ * for any string functions that expect only ASCII chars and can't
188
+ * safely handle UTF-8. Note this only allows ASCII chars in the range
189
+ * 33-126 (newlines/carriage returns are stripped)
190
+ *
191
+ * @since 2.2.0
192
+ * @param string $string string to make ASCII
193
+ * @return string
194
+ */
195
+ public static function str_to_ascii( $string ) {
196
+
197
+ // strip ASCII chars 32 and under
198
+ $string = filter_var( $string, FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_LOW );
199
+
200
+ // strip ASCII chars 127 and higher
201
+ return filter_var( $string, FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_HIGH );
202
+ }
203
+
204
+
205
+ /**
206
+ * Return a string with insane UTF-8 characters removed, like invisible
207
+ * characters, unused code points, and other weirdness. It should
208
+ * accept the common types of characters defined in Unicode.
209
+ *
210
+ * The following are allowed characters:
211
+ *
212
+ * p{L} - any kind of letter from any language
213
+ * p{Mn} - a character intended to be combined with another character without taking up extra space (e.g. accents, umlauts, etc.)
214
+ * p{Mc} - a character intended to be combined with another character that takes up extra space (vowel signs in many Eastern languages)
215
+ * p{Nd} - a digit zero through nine in any script except ideographic scripts
216
+ * p{Zs} - a whitespace character that is invisible, but does take up space
217
+ * p{P} - any kind of punctuation character
218
+ * p{Sm} - any mathematical symbol
219
+ * p{Sc} - any currency sign
220
+ *
221
+ * pattern definitions from http://www.regular-expressions.info/unicode.html
222
+ *
223
+ * @since 4.0.0
224
+ *
225
+ * @param string $string
226
+ * @return string
227
+ */
228
+ public static function str_to_sane_utf8( $string ) {
229
+
230
+ $sane_string = preg_replace( '/[^\p{L}\p{Mn}\p{Mc}\p{Nd}\p{Zs}\p{P}\p{Sm}\p{Sc}]/u', '', $string );
231
+
232
+ // preg_replace with the /u modifier can return null or false on failure
233
+ return ( is_null( $sane_string ) || false === $sane_string ) ? $string : $sane_string;
234
+ }
235
+
236
+
237
+ /**
238
+ * Helper method to check if the multibyte extension is loaded, which
239
+ * indicates it's safe to use the mb_*() string methods
240
+ *
241
+ * @since 2.2.0
242
+ * @return bool
243
+ */
244
+ protected static function multibyte_loaded() {
245
+
246
+ return extension_loaded( 'mbstring' );
247
+ }
248
+
249
+
250
+ /** Array functions ***************************************************/
251
+
252
+
253
+ /**
254
+ * Insert the given element after the given key in the array
255
+ *
256
+ * Sample usage:
257
+ *
258
+ * given
259
+ *
260
+ * array( 'item_1' => 'foo', 'item_2' => 'bar' )
261
+ *
262
+ * array_insert_after( $array, 'item_1', array( 'item_1.5' => 'w00t' ) )
263
+ *
264
+ * becomes
265
+ *
266
+ * array( 'item_1' => 'foo', 'item_1.5' => 'w00t', 'item_2' => 'bar' )
267
+ *
268
+ * @since 2.2.0
269
+ * @param array $array array to insert the given element into
270
+ * @param string $insert_key key to insert given element after
271
+ * @param array $element element to insert into array
272
+ * @return array
273
+ */
274
+ public static function array_insert_after( Array $array, $insert_key, Array $element ) {
275
+
276
+ $new_array = array();
277
+
278
+ foreach ( $array as $key => $value ) {
279
+
280
+ $new_array[ $key ] = $value;
281
+
282
+ if ( $insert_key == $key ) {
283
+
284
+ foreach ( $element as $k => $v ) {
285
+ $new_array[ $k ] = $v;
286
+ }
287
+ }
288
+ }
289
+
290
+ return $new_array;
291
+ }
292
+
293
+
294
+ /**
295
+ * Convert array into XML by recursively generating child elements
296
+ *
297
+ * First instantiate a new XML writer object:
298
+ *
299
+ * $xml = new XMLWriter();
300
+ *
301
+ * Open in memory (alternatively you can use a local URI for file output)
302
+ *
303
+ * $xml->openMemory();
304
+ *
305
+ * Then start the document
306
+ *
307
+ * $xml->startDocument( '1.0', 'UTF-8' );
308
+ *
309
+ * Don't forget to end the document and output the memory
310
+ *
311
+ * $xml->endDocument();
312
+ *
313
+ * $your_xml_string = $xml->outputMemory();
314
+ *
315
+ * @since 2.2.0
316
+ *
317
+ * @param \XMLWriter $xml_writer XML writer instance
318
+ * @param string|array $element_key name for element, e.g. <per_page>
319
+ * @param string|array $element_value value for element, e.g. 100
320
+ */
321
+ public static function array_to_xml( $xml_writer, $element_key, $element_value = array() ) {
322
+
323
+ if ( is_array( $element_value ) ) {
324
+
325
+ // handle attributes
326
+ if ( '@attributes' === $element_key ) {
327
+
328
+ foreach ( $element_value as $attribute_key => $attribute_value ) {
329
+
330
+ $xml_writer->startAttribute( $attribute_key );
331
+ $xml_writer->text( $attribute_value );
332
+ $xml_writer->endAttribute();
333
+ }
334
+
335
+ return;
336
+ }
337
+
338
+ // handle multi-elements (e.g. multiple <Order> elements)
339
+ if ( is_numeric( key( $element_value ) ) ) {
340
+
341
+ // recursively generate child elements
342
+ foreach ( $element_value as $child_element_key => $child_element_value ) {
343
+
344
+ $xml_writer->startElement( $element_key );
345
+
346
+ foreach ( $child_element_value as $sibling_element_key => $sibling_element_value ) {
347
+ self::array_to_xml( $xml_writer, $sibling_element_key, $sibling_element_value );
348
+ }
349
+
350
+ $xml_writer->endElement();
351
+ }
352
+
353
+ } else {
354
+
355
+ // start root element
356
+ $xml_writer->startElement( $element_key );
357
+
358
+ // recursively generate child elements
359
+ foreach ( $element_value as $child_element_key => $child_element_value ) {
360
+ self::array_to_xml( $xml_writer, $child_element_key, $child_element_value );
361
+ }
362
+
363
+ // end root element
364
+ $xml_writer->endElement();
365
+ }
366
+
367
+ } else {
368
+
369
+ // handle single elements
370
+ if ( '@value' === $element_key ) {
371
+
372
+ $xml_writer->text( $element_value );
373
+
374
+ } else {
375
+
376
+ // wrap element in CDATA tags if it contains illegal characters
377
+ if ( false !== strpos( $element_value, '<' ) || false !== strpos( $element_value, '>' ) ) {
378
+
379
+ $xml_writer->startElement( $element_key );
380
+ $xml_writer->writeCdata( $element_value );
381
+ $xml_writer->endElement();
382
+
383
+ } else {
384
+
385
+ $xml_writer->writeElement( $element_key, $element_value );
386
+ }
387
+ }
388
+ }
389
+ }
390
+
391
+
392
+ /**
393
+ * Lists an array as text.
394
+ *
395
+ * Takes an array and returns a list like "one, two, three, and four"
396
+ * with a (mandatory) oxford comma.
397
+ *
398
+ * @since 5.2.0
399
+ *
400
+ * @param array $items items to list
401
+ * @param string|null $conjunction coordinating conjunction, like "or" or "and"
402
+ * @param string $separator list separator, like a comma
403
+ * @return string
404
+ */
405
+ public static function list_array_items( array $items, $conjunction = null, $separator = '' ) {
406
+
407
+ if ( ! is_string( $conjunction ) ) {
408
+ $conjunction = _x( 'and', 'coordinating conjunction for a list of items: a, b, and c', 'woocommerce-plugin-framework' );
409
+ }
410
+
411
+ // append the conjunction to the last item
412
+ if ( count( $items ) > 1 ) {
413
+
414
+ $last_item = array_pop( $items );
415
+
416
+ array_push( $items, trim( "{$conjunction} {$last_item}" ) );
417
+
418
+ // only use a comma if needed and no separator was passed
419
+ if ( count( $items ) < 3 ) {
420
+ $separator = ' ';
421
+ } elseif ( ! is_string( $separator ) || '' === $separator ) {
422
+ $separator = ', ';
423
+ }
424
+ }
425
+
426
+ return implode( $separator, $items );
427
+ }
428
+
429
+
430
+ /** Number helper functions *******************************************/
431
+
432
+
433
+ /**
434
+ * Format a number with 2 decimal points, using a period for the decimal
435
+ * separator and no thousands separator.
436
+ *
437
+ * Commonly used for payment gateways which require amounts in this format.
438
+ *
439
+ * @since 3.0.0
440
+ * @param float $number
441
+ * @return string
442
+ */
443
+ public static function number_format( $number ) {
444
+
445
+ return number_format( (float) $number, 2, '.', '' );
446
+ }
447
+
448
+
449
+ /** WooCommerce helper functions **************************************/
450
+
451
+
452
+ /**
453
+ * Gets order line items (products) as an array of objects.
454
+ *
455
+ * Object properties:
456
+ *
457
+ * + id - item ID
458
+ * + name - item name, usually product title, processed through htmlentities()
459
+ * + description - formatted item meta (e.g. Size: Medium, Color: blue), processed through htmlentities()
460
+ * + quantity - item quantity
461
+ * + item_total - item total (line total divided by quantity, excluding tax & rounded)
462
+ * + line_total - line item total (excluding tax & rounded)
463
+ * + meta - formatted item meta array
464
+ * + product - item product or null if getting product from item failed
465
+ * + item - raw item array
466
+ *
467
+ * @since 3.0.0
468
+ *
469
+ * @param \WC_Order $order
470
+ * @return \stdClass[] array of line item objects
471
+ */
472
+ public static function get_order_line_items( $order ) {
473
+
474
+ $line_items = [];
475
+
476
+ /** @var \WC_Order_Item_Product $item */
477
+ foreach ( $order->get_items() as $id => $item ) {
478
+
479
+ $line_item = new \stdClass();
480
+ $product = $item->get_product();
481
+ $name = $item->get_name();
482
+ $quantity = $item->get_quantity();
483
+ $item_desc = [];
484
+
485
+ // add SKU to description if available
486
+ if ( $sku = $product->get_sku() ) {
487
+ $item_desc[] = sprintf( 'SKU: %s', $sku );
488
+ }
489
+
490
+ $item_meta = SV_WC_Order_Compatibility::get_item_formatted_meta_data( $item, '_', true );
491
+
492
+ if ( ! empty( $item_meta ) ) {
493
+
494
+ foreach ( $item_meta as $meta ) {
495
+
496
+ $item_desc[] = sprintf( '%s: %s', $meta['label'], $meta['value'] );
497
+ }
498
+ }
499
+
500
+ $item_desc = implode( ', ', $item_desc );
501
+
502
+ $line_item->id = $id;
503
+ $line_item->name = htmlentities( $name, ENT_QUOTES, 'UTF-8', false );
504
+ $line_item->description = htmlentities( $item_desc, ENT_QUOTES, 'UTF-8', false );
505
+ $line_item->quantity = $quantity;
506
+ $line_item->item_total = isset( $item['recurring_line_total'] ) ? $item['recurring_line_total'] : $order->get_item_total( $item );
507
+ $line_item->line_total = $order->get_line_total( $item );
508
+ $line_item->meta = $item_meta;
509
+ $line_item->product = is_object( $product ) ? $product : null;
510
+ $line_item->item = $item;
511
+
512
+ $line_items[] = $line_item;
513
+ }
514
+
515
+ return $line_items;
516
+ }
517
+
518
+
519
+ /**
520
+ * Determines if an order contains only virtual products.
521
+ *
522
+ * @since 4.5.0
523
+ *
524
+ * @param \WC_Order $order the order object
525
+ * @return bool
526
+ */
527
+ public static function is_order_virtual( \WC_Order $order ) {
528
+
529
+ $is_virtual = true;
530
+
531
+ /** @var \WC_Order_Item_Product $item */
532
+ foreach ( $order->get_items() as $item ) {
533
+
534
+ $product = $item->get_product();
535
+
536
+ // once we've found one non-virtual product we know we're done, break out of the loop
537
+ if ( $product && ! $product->is_virtual() ) {
538
+
539
+ $is_virtual = false;
540
+ break;
541
+ }
542
+ }
543
+
544
+ return $is_virtual;
545
+ }
546
+
547
+
548
+ /**
549
+ * Safely gets and trims data from $_POST.
550
+ *
551
+ * @since 3.0.0
552
+ * @deprecated 5.5.0
553
+ *
554
+ * @param string $key array key to get from $_POST array
555
+ * @return string value from $_POST or blank string if $_POST[ $key ] is not set
556
+ */
557
+ public static function get_post( $key ) {
558
+
559
+ wc_deprecated_function( __METHOD__, '5.5.0', __CLASS__ . '::get_posted_value()' );
560
+
561
+ return self::get_posted_value( $key );
562
+ }
563
+
564
+
565
+ /**
566
+ * Safely gets a value from $_POST.
567
+ *
568
+ * If the expected data is a string also trims it.
569
+ *
570
+ * @since 5.5.0
571
+ *
572
+ * @param string $key posted data key
573
+ * @param int|float|array|bool|null|string $default default data type to return (default empty string)
574
+ * @return int|float|array|bool|null|string posted data value if key found, or default
575
+ */
576
+ public static function get_posted_value( $key, $default = '' ) {
577
+
578
+ $value = $default;
579
+
580
+ if ( isset( $_POST[ $key ] ) ) {
581
+ $value = is_string( $_POST[ $key ] ) ? trim( $_POST[ $key ] ) : $_POST[ $key ];
582
+ }
583
+
584
+ return $value;
585
+ }
586
+
587
+
588
+ /**
589
+ * Safely gets and trims data from $_REQUEST.
590
+ *
591
+ * @since 3.0.0
592
+ * @deprecated 5.5.0
593
+ *
594
+ * @param string $key array key to get from $_REQUEST array
595
+ * @return string value from $_REQUEST or blank string if $_REQUEST[ $key ] is not set
596
+ */
597
+ public static function get_request( $key ) {
598
+
599
+ wc_deprecated_function( __METHOD__, '5.5.0', __CLASS__ . '::get_requested_value()' );
600
+
601
+ return self::get_requested_value( $key );
602
+ }
603
+
604
+
605
+ /**
606
+ * Safely gets a value from $_REQUEST.
607
+ *
608
+ * If the expected data is a string also trims it.
609
+ *
610
+ * @since 5.5.0
611
+ *
612
+ * @param string $key posted data key
613
+ * @param int|float|array|bool|null|string $default default data type to return (default empty string)
614
+ * @return int|float|array|bool|null|string posted data value if key found, or default
615
+ */
616
+ public static function get_requested_value( $key, $default = '' ) {
617
+
618
+ $value = $default;
619
+
620
+ if ( isset( $_REQUEST[ $key ] ) ) {
621
+ $value = is_string( $_REQUEST[ $key ] ) ? trim( $_REQUEST[ $key ] ) : $_REQUEST[ $key ];
622
+ }
623
+
624
+ return $value;
625
+ }
626
+
627
+
628
+ /**
629
+ * Get the count of notices added, either for all notices (default) or for one
630
+ * particular notice type specified by $notice_type.
631
+ *
632
+ * WC notice functions are not available in the admin
633
+ *
634
+ * @since 3.0.2
635
+ * @param string $notice_type The name of the notice type - either error, success or notice. [optional]
636
+ * @return int
637
+ */
638
+ public static function wc_notice_count( $notice_type = '' ) {
639
+
640
+ if ( function_exists( 'wc_notice_count' ) ) {
641
+ return wc_notice_count( $notice_type );
642
+ }
643
+
644
+ return 0;
645
+ }
646
+
647
+
648
+ /**
649
+ * Add and store a notice.
650
+ *
651
+ * WC notice functions are not available in the admin
652
+ *
653
+ * @since 3.0.2
654
+ * @param string $message The text to display in the notice.
655
+ * @param string $notice_type The singular name of the notice type - either error, success or notice. [optional]
656
+ */
657
+ public static function wc_add_notice( $message, $notice_type = 'success' ) {
658
+
659
+ if ( function_exists( 'wc_add_notice' ) ) {
660
+ wc_add_notice( $message, $notice_type );
661
+ }
662
+ }
663
+
664
+
665
+ /**
666
+ * Print a single notice immediately
667
+ *
668
+ * WC notice functions are not available in the admin
669
+ *
670
+ * @since 3.0.2
671
+ * @param string $message The text to display in the notice.
672
+ * @param string $notice_type The singular name of the notice type - either error, success or notice. [optional]
673
+ */
674
+ public static function wc_print_notice( $message, $notice_type = 'success' ) {
675
+
676
+ if ( function_exists( 'wc_print_notice' ) ) {
677
+ wc_print_notice( $message, $notice_type );
678
+ }
679
+ }
680
+
681
+
682
+ /**
683
+ * Gets the full URL to the log file for a given $handle
684
+ *
685
+ * @since 4.0.0
686
+ * @param string $handle log handle
687
+ * @return string URL to the WC log file identified by $handle
688
+ */
689
+ public static function get_wc_log_file_url( $handle ) {
690
+ return admin_url( sprintf( 'admin.php?page=wc-status&tab=logs&log_file=%s-%s-log', $handle, sanitize_file_name( wp_hash( $handle ) ) ) );
691
+ }
692
+
693
+
694
+ /**
695
+ * Gets the current WordPress site name.
696
+ *
697
+ * This is helpful for retrieving the actual site name instead of the
698
+ * network name on multisite installations.
699
+ *
700
+ * @since 4.6.0
701
+ * @return string
702
+ */
703
+ public static function get_site_name() {
704
+
705
+ return ( is_multisite() ) ? get_blog_details()->blogname : get_bloginfo( 'name' );
706
+ }
707
+
708
+
709
+ /** JavaScript helper functions ***************************************/
710
+
711
+
712
+ /**
713
+ * Enhanced search JavaScript (Select2)
714
+ *
715
+ * Enqueues JavaScript required for AJAX search with Select2.
716
+ *
717
+ * @codeCoverageIgnore no need to unit test this since it's mostly JavaScript
718
+ *
719
+ * @since 3.1.0
720
+ */
721
+ public static function render_select2_ajax() {
722
+
723
+ if ( ! did_action( 'sv_wc_select2_ajax_rendered' ) ) {
724
+
725
+ $javascript = "( function(){
726
+ if ( ! $().select2 ) return;
727
+ ";
728
+
729
+ // Ensure localized strings are used.
730
+ $javascript .= "
731
+
732
+ function getEnhancedSelectFormatString() {
733
+
734
+ if ( 'undefined' !== typeof wc_select_params ) {
735
+ wc_enhanced_select_params = wc_select_params;
736
+ }
737
+
738
+ if ( 'undefined' === typeof wc_enhanced_select_params ) {
739
+ return {};
740
+ }
741
+
742
+ var formatString = {
743
+ formatMatches: function( matches ) {
744
+ if ( 1 === matches ) {
745
+ return wc_enhanced_select_params.i18n_matches_1;
746
+ }
747
+
748
+ return wc_enhanced_select_params.i18n_matches_n.replace( '%qty%', matches );
749
+ },
750
+ formatNoMatches: function() {
751
+ return wc_enhanced_select_params.i18n_no_matches;
752
+ },
753
+ formatAjaxError: function( jqXHR, textStatus, errorThrown ) {
754
+ return wc_enhanced_select_params.i18n_ajax_error;
755
+ },
756
+ formatInputTooShort: function( input, min ) {
757
+ var number = min - input.length;
758
+
759
+ if ( 1 === number ) {
760
+ return wc_enhanced_select_params.i18n_input_too_short_1
761
+ }
762
+
763
+ return wc_enhanced_select_params.i18n_input_too_short_n.replace( '%qty%', number );
764
+ },
765
+ formatInputTooLong: function( input, max ) {
766
+ var number = input.length - max;
767
+
768
+ if ( 1 === number ) {
769
+ return wc_enhanced_select_params.i18n_input_too_long_1
770
+ }
771
+
772
+ return wc_enhanced_select_params.i18n_input_too_long_n.replace( '%qty%', number );
773
+ },
774
+ formatSelectionTooBig: function( limit ) {
775
+ if ( 1 === limit ) {
776
+ return wc_enhanced_select_params.i18n_selection_too_long_1;
777
+ }
778
+
779
+ return wc_enhanced_select_params.i18n_selection_too_long_n.replace( '%qty%', number );
780
+ },
781
+ formatLoadMore: function( pageNumber ) {
782
+ return wc_enhanced_select_params.i18n_load_more;
783
+ },
784
+ formatSearching: function() {
785
+ return wc_enhanced_select_params.i18n_searching;
786
+ }
787
+ };
788
+
789
+ return formatString;
790
+ }
791
+ ";
792
+
793
+ $javascript .= "
794
+
795
+ $( 'select.sv-wc-enhanced-search' ).filter( ':not(.enhanced)' ).each( function() {
796
+
797
+ var select2_args = {
798
+ allowClear: $( this ).data( 'allow_clear' ) ? true : false,
799
+ placeholder: $( this ).data( 'placeholder' ),
800
+ minimumInputLength: $( this ).data( 'minimum_input_length' ) ? $( this ).data( 'minimum_input_length' ) : '3',
801
+ escapeMarkup: function( m ) {
802
+ return m;
803
+ },
804
+ ajax: {
805
+ url: '" . esc_js( admin_url( 'admin-ajax.php' ) ) . "',
806
+ dataType: 'json',
807
+ cache: true,
808
+ delay: 250,
809
+ data: function( params ) {
810
+ return {
811
+ term: params.term,
812
+ request_data: $( this ).data( 'request_data' ) ? $( this ).data( 'request_data' ) : {},
813
+ action: $( this ).data( 'action' ) || 'woocommerce_json_search_products_and_variations',
814
+ security: $( this ).data( 'nonce' )
815
+ };
816
+ },
817
+ processResults: function( data, params ) {
818
+ var terms = [];
819
+ if ( data ) {
820
+ $.each( data, function( id, text ) {
821
+ terms.push( { id: id, text: text } );
822
+ });
823
+ }
824
+ return { results: terms };
825
+ }
826
+ }
827
+ };
828
+
829
+ select2_args = $.extend( select2_args, getEnhancedSelectFormatString() );
830
+
831
+ $( this ).select2( select2_args ).addClass( 'enhanced' );
832
+ } );
833
+ ";
834
+
835
+ $javascript .= '} )();';
836
+
837
+ wc_enqueue_js( $javascript );
838
+
839
+ /**
840
+ * WC Select2 Ajax Rendered Action.
841
+ *
842
+ * Fired when an Ajax select2 is rendered.
843
+ *
844
+ * @since 3.1.0
845
+ */
846
+ do_action( 'sv_wc_select2_ajax_rendered' );
847
+ }
848
+ }
849
+
850
+
851
+ /** Framework translation functions ***********************************/
852
+
853
+
854
+ /**
855
+ * Gettext `__()` wrapper for framework-translated strings
856
+ *
857
+ * Warning! This function should only be used if an existing
858
+ * translation from the framework is to be used. It should
859
+ * never be called for plugin-specific or untranslated strings!
860
+ * Untranslated = not registered via string literal.
861
+ *
862
+ * @since 4.1.0
863
+ * @param string $text
864
+ * @return string translated text
865
+ */
866
+ public static function f__( $text ) {
867
+
868
+ return __( $text, 'woocommerce-plugin-framework' );
869
+ }
870
+
871
+
872
+ /**
873
+ * Gettext `_e()` wrapper for framework-translated strings
874
+ *
875
+ * Warning! This function should only be used if an existing
876
+ * translation from the framework is to be used. It should
877
+ * never be called for plugin-specific or untranslated strings!
878
+ * Untranslated = not registered via string literal.
879
+ *
880
+ * @since 4.1.0
881
+ * @param string $text
882
+ */
883
+ public static function f_e( $text ) {
884
+
885
+ _e( $text, 'woocommerce-plugin-framework' );
886
+ }
887
+
888
+
889
+ /**
890
+ * Gettext `_x()` wrapper for framework-translated strings
891
+ *
892
+ * Warning! This function should only be used if an existing
893
+ * translation from the framework is to be used. It should
894
+ * never be called for plugin-specific or untranslated strings!
895
+ * Untranslated = not registered via string literal.
896
+ *
897
+ * @since 4.1.0
898
+ *
899
+ * @param string $text
900
+ * @param string $context
901
+ * @return string translated text
902
+ */
903
+ public static function f_x( $text, $context ) {
904
+
905
+ return _x( $text, $context, 'woocommerce-plugin-framework' );
906
+ }
907
+
908
+
909
+ /** Misc functions ****************************************************/
910
+
911
+
912
+ /**
913
+ * Gets the WordPress current screen.
914
+ *
915
+ * @see get_current_screen() replacement which is always available, unlike the WordPress core function
916
+ *
917
+ * @since 5.4.2
918
+ *
919
+ * @return \WP_Screen|null
920
+ */
921
+ public static function get_current_screen() {
922
+ global $current_screen;
923
+
924
+ return $current_screen ?: null;
925
+ }
926
+
927
+
928
+ /**
929
+ * Checks if the current screen matches a specified ID.
930
+ *
931
+ * This helps avoiding using the get_current_screen() function which is not always available,
932
+ * or setting the substitute global $current_screen every time a check needs to be performed.
933
+ *
934
+ * @since 5.4.2
935
+ *
936
+ * @param string $id id (or property) to compare
937
+ * @param string $prop optional property to compare, defaults to screen id
938
+ * @return bool
939
+ */
940
+ public static function is_current_screen( $id, $prop = 'id' ) {
941
+ global $current_screen;
942
+
943
+ return isset( $current_screen->$prop ) && $id === $current_screen->$prop;
944
+ }
945
+
946
+
947
+ /**
948
+ * Convert a 2-character country code into its 3-character equivalent, or
949
+ * vice-versa, e.g.
950
+ *
951
+ * 1) given USA, returns US
952
+ * 2) given US, returns USA
953
+ *
954
+ * @since 4.2.0
955
+ * @deprecated 5.4.3
956
+ *
957
+ * @param string $code ISO-3166-alpha-2 or ISO-3166-alpha-3 country code
958
+ * @return string country code
959
+ */
960
+ public static function convert_country_code( $code ) {
961
+
962
+ wc_deprecated_function( __METHOD__, '5.4.3', Country_Helper::class . '::convert_alpha_country_code()' );
963
+
964
+ return Country_Helper::convert_alpha_country_code( $code );
965
+ }
966
+
967
+
968
+ /**
969
+ * Displays a notice if the provided hook has not yet run.
970
+ *
971
+ * @since 5.2.0
972
+ *
973
+ * @param string $hook action hook to check
974
+ * @param string $method method/function name
975
+ * @param string $version version the notice was added
976
+ */
977
+ public static function maybe_doing_it_early( $hook, $method, $version ) {
978
+
979
+ if ( ! did_action( $hook ) ) {
980
+ wc_doing_it_wrong( $method, "This should only be called after '{$hook}'", $version );
981
+ }
982
+ }
983
+
984
+
985
+ /**
986
+ * Triggers a PHP error.
987
+ *
988
+ * This wrapper method ensures AJAX isn't broken in the process.
989
+ *
990
+ * @since 4.6.0
991
+ * @param string $message the error message
992
+ * @param int $type Optional. The error type. Defaults to E_USER_NOTICE
993
+ */
994
+ public static function trigger_error( $message, $type = E_USER_NOTICE ) {
995
+
996
+ if ( is_callable( 'is_ajax' ) && is_ajax() ) {
997
+
998
+ switch ( $type ) {
999
+
1000
+ case E_USER_NOTICE:
1001
+ $prefix = 'Notice: ';
1002
+ break;
1003
+
1004
+ case E_USER_WARNING:
1005
+ $prefix = 'Warning: ';
1006
+ break;
1007
+
1008
+ default:
1009
+ $prefix = '';
1010
+ }
1011
+
1012
+ error_log( $prefix . $message );
1013
+
1014
+ } else {
1015
+
1016
+ trigger_error( $message, $type );
1017
+ }
1018
+ }
1019
+
1020
+
1021
+ }
1022
+
1023
+
1024
+ endif;
vendor/skyverge/wc-plugin-framework/woocommerce/class-sv-wc-hook-deprecator.php ADDED
@@ -0,0 +1,199 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * WooCommerce Plugin Framework
4
+ *
5
+ * This source file is subject to the GNU General Public License v3.0
6
+ * that is bundled with this package in the file license.txt.
7
+ * It is also available through the world-wide-web at this URL:
8
+ * http://www.gnu.org/licenses/gpl-3.0.html
9
+ * If you did not receive a copy of the license and are unable to
10
+ * obtain it through the world-wide-web, please send an email
11
+ * to license@skyverge.com so we can send you a copy immediately.
12
+ *
13
+ * DISCLAIMER
14
+ *
15
+ * Do not edit or add to this file if you wish to upgrade the plugin to newer
16
+ * versions in the future. If you wish to customize the plugin for your
17
+ * needs please refer to http://www.skyverge.com
18
+ *
19
+ * @package SkyVerge/WooCommerce/Plugin/Classes
20
+ * @author SkyVerge
21
+ * @copyright Copyright (c) 2013-2020, SkyVerge, Inc.
22
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GNU General Public License v3.0
23
+ */
24
+
25
+ namespace SkyVerge\WooCommerce\PluginFramework\v5_5_4;
26
+
27
+ defined( 'ABSPATH' ) or exit;
28
+
29
+ if ( ! class_exists( '\\SkyVerge\\WooCommerce\\PluginFramework\\v5_5_4\\SV_WC_Hook_Deprecator' ) ) :
30
+
31
+
32
+ /**
33
+ * SkyVerge Hook Deprecator Class
34
+ *
35
+ * This class handles triggering PHP notices for deprecated and removed hooks
36
+ *
37
+ * @since 4.3.0
38
+ */
39
+ class SV_WC_Hook_Deprecator {
40
+
41
+
42
+ /** @var string plugin name */
43
+ protected $plugin_name;
44
+
45
+ /** @var array deprecated/removed hooks */
46
+ protected $hooks;
47
+
48
+
49
+ /**
50
+ * Setup class
51
+ *
52
+ * @param string $plugin_name
53
+ * @param array $hooks
54
+ */
55
+ public function __construct( $plugin_name, $hooks ) {
56
+
57
+ $this->plugin_name = $plugin_name;
58
+ $this->hooks = array_map( array( $this, 'set_hook_defaults' ), $hooks );
59
+
60
+ $this->map_deprecated_hooks();
61
+
62
+ add_action( 'shutdown', array( $this, 'trigger_deprecated_errors' ), 999 );
63
+ }
64
+
65
+
66
+ /**
67
+ * Sets the deprecated hook defaults.
68
+ *
69
+ * @since 4.5.0
70
+ * @param array $hook_params the hook parameters
71
+ * @return array
72
+ */
73
+ protected function set_hook_defaults( $hook_params ) {
74
+
75
+ $defaults = array(
76
+ 'removed' => false,
77
+ 'map' => false,
78
+ 'replacement' => '',
79
+ );
80
+
81
+ return wp_parse_args( $hook_params, $defaults );
82
+ }
83
+
84
+
85
+ /**
86
+ * Map each deprecated hook to its replacement.
87
+ *
88
+ * @since 4.5.0
89
+ */
90
+ protected function map_deprecated_hooks() {
91
+
92
+ foreach ( $this->hooks as $old_hook => $hook ) {
93
+
94
+ if ( ! empty( $hook['replacement'] ) && $hook['removed'] && $hook['map'] ) {
95
+ add_filter( $hook['replacement'], array( $this, 'map_deprecated_hook' ), 10, 10 );
96
+ }
97
+ }
98
+ }
99
+
100
+
101
+ /**
102
+ * Map a deprecated/renamed hook to a new one.
103
+ *
104
+ * This method works by hooking into the new, renamed version of the action/filter
105
+ * and checking if any actions/filters are hooked into the old hook. It then runs
106
+ * these and applies the data modifications in the new hook.
107
+ *
108
+ * @since 4.5.0
109
+ * @return mixed
110
+ */
111
+ public function map_deprecated_hook() {
112
+
113
+ $args = func_get_args();
114
+ $data = $args[0];
115
+ $new_hook = current_filter();
116
+
117
+ $new_hooks = wp_list_pluck( $this->hooks, 'replacement' );
118
+
119
+ // check if there are matching old hooks for the current hook
120
+ foreach ( array_keys( $new_hooks, $new_hook ) as $old_hook ) {
121
+
122
+ // check if there are any hooks added to the old hook
123
+ if ( has_filter( $old_hook ) ) {
124
+
125
+ // prepend old hook name to the args
126
+ array_unshift( $args, $old_hook );
127
+
128
+ // apply the hooks attached to the old hook to $data
129
+ $data = call_user_func_array( 'apply_filters', $args );
130
+ }
131
+ }
132
+
133
+ return $data;
134
+ }
135
+
136
+
137
+ /**
138
+ * Trigger a notice when other actors have attached callbacks to hooks that
139
+ * are either deprecated or removed. This only runs when WP_DEBUG is on.
140
+ *
141
+ * @since 4.3.0
142
+ */
143
+ public function trigger_deprecated_errors() {
144
+ global $wp_filter;
145
+
146
+ // follow WP core behavior for showing deprecated notices and only do so when WP_DEBUG is on
147
+ if ( defined( 'WP_DEBUG' ) && WP_DEBUG && apply_filters( 'sv_wc_plugin_framework_show_deprecated_hook_notices', true ) ) {
148
+
149
+ // sanity check
150
+ if ( ! is_array( $wp_filter ) || empty( $wp_filter ) ) {
151
+ return;
152
+ }
153
+
154
+ foreach ( $this->hooks as $old_hook_tag => $hook ) {
155
+
156
+ // if other actors have attached a callback to the deprecated/removed hook...
157
+ if ( isset( $wp_filter[ $old_hook_tag ] ) ) {
158
+
159
+ $this->trigger_error( $old_hook_tag, $hook );
160
+ }
161
+ }
162
+ }
163
+ }
164
+
165
+
166
+ /**
167
+ * Trigger the deprecated/removed notice
168
+ *
169
+ * @since 4.3.0
170
+ * @param string $old_hook_name deprecated/removed hook name
171
+ * @param array $hook {
172
+ * @type string $version version the hook was deprecated/removed in
173
+ * @type bool $removed if present and true, the message will indicate the hook was removed instead of deprecated
174
+ * @type string|bool $replacement if present and a string, the message will indicate the replacement hook to use,
175
+ * otherwise (if bool and false) the message will indicate there is no replacement available.
176
+ * }
177
+ */
178
+ protected function trigger_error( $old_hook_name, $hook ) {
179
+
180
+ // e.g. WooCommerce Memberships: "wc_memberships_some_hook" was deprecated in version 1.2.3.
181
+ $message = sprintf( '%1$s: action/filter "%2$s" was %3$s in version %4$s. ',
182
+ $this->plugin_name,
183
+ $old_hook_name,
184
+ $hook['removed'] ? 'removed' : 'deprecated',
185
+ $hook['version']
186
+ );
187
+
188
+ // e.g. Use "wc_memberships_some_new_hook" instead.
189
+ $message .= ! empty( $hook['replacement'] ) ? sprintf( 'Use %1$s instead.', $hook['replacement'] ) : 'There is no replacement available.';
190
+
191
+ // triggers as E_USER_NOTICE
192
+ SV_WC_Helper::trigger_error( $message );
193
+ }
194
+
195
+
196
+ }
197
+
198
+
199
+ endif;
vendor/skyverge/wc-plugin-framework/woocommerce/class-sv-wc-plugin-compatibility.php ADDED
@@ -0,0 +1,483 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * WooCommerce Plugin Framework
4
+ *
5
+ * This source file is subject to the GNU General Public License v3.0
6
+ * that is bundled with this package in the file license.txt.
7
+ * It is also available through the world-wide-web at this URL:
8
+ * http://www.gnu.org/licenses/gpl-3.0.html
9
+ * If you did not receive a copy of the license and are unable to
10
+ * obtain it through the world-wide-web, please send an email
11
+ * to license@skyverge.com so we can send you a copy immediately.
12
+ *
13
+ * DISCLAIMER
14
+ *
15
+ * Do not edit or add to this file if you wish to upgrade the plugin to newer
16
+ * versions in the future. If you wish to customize the plugin for your
17
+ * needs please refer to http://www.skyverge.com
18
+ *
19
+ * @package SkyVerge/WooCommerce/Plugin/Classes
20
+ * @author SkyVerge
21
+ * @copyright Copyright (c) 2013-2020, SkyVerge, Inc.
22
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GNU General Public License v3.0
23
+ */
24
+
25
+ namespace SkyVerge\WooCommerce\PluginFramework\v5_5_4;
26
+
27
+ defined( 'ABSPATH' ) or exit;
28
+
29
+ if ( ! class_exists( '\\SkyVerge\\WooCommerce\\PluginFramework\\v5_5_4\\SV_WC_Plugin_Compatibility' ) ) :
30
+
31
+
32
+ /**
33
+ * WooCommerce Compatibility Utility Class
34
+ *
35
+ * The unfortunate purpose of this class is to provide a single point of
36
+ * compatibility functions for dealing with supporting multiple versions
37
+ * of WooCommerce and various extensions.
38
+ *
39
+ * The expected procedure is to remove methods from this class, using the
40
+ * latest ones directly in code, as support for older versions of WooCommerce
41
+ * are dropped.
42
+ *
43
+ * Current Compatibility
44
+ * + Core 3.0.9 - 3.7.x
45
+ * + Subscriptions 2.2.x
46
+ *
47
+ * // TODO: move to /compatibility
48
+ *
49
+ * @since 2.0.0
50
+ */
51
+ class SV_WC_Plugin_Compatibility {
52
+
53
+
54
+ /**
55
+ * Gets the statuses that are considered "paid".
56
+ *
57
+ * @since 5.1.0
58
+ * @deprecated 5.5.0
59
+ *
60
+ * @return string[]
61
+ */
62
+ public static function wc_get_is_paid_statuses() {
63
+
64
+ wc_deprecated_function( __METHOD__, '5.5.0', '(array) wc_get_is_paid_statuses()' );
65
+
66
+ return (array) wc_get_is_paid_statuses();
67
+ }
68
+
69
+
70
+ /**
71
+ * Logs a doing_it_wrong message.
72
+ *
73
+ * @since 5.0.1
74
+ * @deprecated 5.5.0
75
+ *
76
+ * @param string $function function used
77
+ * @param string $message message to log
78
+ * @param string $version version the message was added in
79
+ */
80
+ public static function wc_doing_it_wrong( $function, $message, $version ) {
81
+
82
+ wc_deprecated_function( __METHOD__, '5.5.0', 'wc_doing_it_wrong()' );
83
+
84
+ wc_doing_it_wrong( $function, $message, $version );
85
+ }
86
+
87
+
88
+ /**
89
+ * Formats a date for output.
90
+ *
91
+ * @since 4.6.0
92
+ * @deprecated 5.5.0
93
+ *
94
+ * @param \WC_DateTime|SV_WC_DateTime $date date object
95
+ * @param string $format date format
96
+ * @return string
97
+ */
98
+ public static function wc_format_datetime( $date, $format = '' ) {
99
+
100
+ wc_deprecated_function( __METHOD__, '5.5.0', 'wc_format_datetime()' );
101
+
102
+ return wc_format_datetime( $date, $format );
103
+ }
104
+
105
+
106
+ /**
107
+ * Logs a deprecated function notice.
108
+ *
109
+ * @since 5.0.0
110
+ * @deprecated 5.5.0
111
+ *
112
+ * @param string $function deprecated function name
113
+ * @param string $version deprecated-since version
114
+ * @param string $replacement replacement function name
115
+ */
116
+ public static function wc_deprecated_function( $function, $version, $replacement = null ) {
117
+
118
+ wc_deprecated_function( __METHOD__, '5.5.0', 'wc_deprecated_function()' );
119
+
120
+ wc_deprecated_function( $function, $version, $replacement );
121
+ }
122
+
123
+
124
+ /**
125
+ * Retrieves a list of the latest available WooCommerce versions.
126
+ *
127
+ * Excludes betas, release candidates and development versions.
128
+ * Versions are sorted from most recent to least recent.
129
+ *
130
+ * @since 5.4.1
131
+ *
132
+ * @return string[] array of semver strings
133
+ */
134
+ public static function get_latest_wc_versions() {
135
+
136
+ $latest_wc_versions = get_transient( 'sv_wc_plugin_wc_versions' );
137
+
138
+ if ( ! is_array( $latest_wc_versions ) ) {
139
+
140
+ /** @link https://codex.wordpress.org/WordPress.org_API */
141
+ $wp_org_request = wp_remote_get( 'https://api.wordpress.org/plugins/info/1.0/woocommerce.json', [ 'timeout' => 1 ] );
142
+
143
+ if ( is_array( $wp_org_request ) && isset( $wp_org_request['body'] ) ) {
144
+
145
+ $plugin_info = json_decode( $wp_org_request['body'], true );
146
+
147
+ if ( is_array( $plugin_info ) && ! empty( $plugin_info['versions'] ) && is_array( $plugin_info['versions'] ) ) {
148
+
149
+ $latest_wc_versions = [];
150
+
151
+ // reverse array as WordPress supplies oldest version first, newest last
152
+ foreach ( array_keys( array_reverse( $plugin_info['versions'] ) ) as $wc_version ) {
153
+
154
+ // skip trunk, release candidates, betas and other non-final or irregular versions
155
+ if (
156
+ is_string( $wc_version )
157
+ && '' !== $wc_version
158
+ && is_numeric( $wc_version[0] )
159
+ && false === strpos( $wc_version, '-' )
160
+ ) {
161
+ $latest_wc_versions[] = $wc_version;
162
+ }
163
+ }
164
+
165
+ set_transient( 'sv_wc_plugin_wc_versions', $latest_wc_versions, WEEK_IN_SECONDS );
166
+ }
167
+ }
168
+ }
169
+
170
+ return is_array( $latest_wc_versions ) ? $latest_wc_versions : [];
171
+ }
172
+
173
+
174
+ /**
175
+ * Gets the version of the currently installed WooCommerce.
176
+ *
177
+ * @since 3.0.0
178
+ *
179
+ * @return string|null Woocommerce version number or null if undetermined
180
+ */
181
+ public static function get_wc_version() {
182
+
183
+ return defined( 'WC_VERSION' ) && WC_VERSION ? WC_VERSION : null;
184
+ }
185
+
186
+
187
+ /**
188
+ * Determines if the installed WooCommerce version matches a specific version.
189
+ *
190
+ * @since 5.5.0
191
+ *
192
+ * @param string $version semver
193
+ * @return bool
194
+ */
195
+ public static function is_wc_version( $version ) {
196
+
197
+ $wc_version = self::get_wc_version();
198
+
199
+ // accounts for semver cases like 3.0 being equal to 3.0.0
200
+ return $wc_version === $version || ( $wc_version && version_compare( $wc_version, $version, '=' ) );
201
+ }
202
+
203
+
204
+ /**
205
+ * Determines if the installed version of WooCommerce is 3.0 or greater.
206
+ *
207
+ * @since 4.6.0
208
+ * @deprecated 5.5.0
209
+ *
210
+ * @return bool
211
+ */
212
+ public static function is_wc_version_gte_3_0() {
213
+
214
+ wc_deprecated_function( __METHOD__, '5.5.0', __CLASS__ . '::is_wc_version_gte()' );
215
+
216
+ return self::is_wc_version_gte( '3.0' );
217
+ }
218
+
219
+
220
+ /**
221
+ * Determines if the installed version of WooCommerce is less than 3.0.
222
+ *
223
+ * @since 4.6.0
224
+ * @deprecated 5.5.0
225
+ *
226
+ * @return bool
227
+ */
228
+ public static function is_wc_version_lt_3_0() {
229
+
230
+ wc_deprecated_function( __METHOD__, '5.5.0', __CLASS__ . '::is_wc_version_lt()' );
231
+
232
+ return self::is_wc_version_lt( '3.0' );
233
+ }
234
+
235
+
236
+ /**
237
+ * Determines if the installed version of WooCommerce is 3.1 or greater.
238
+ *
239
+ * @since 4.6.5
240
+ * @deprecated 5.5.0
241
+ *
242
+ * @return bool
243
+ */
244
+ public static function is_wc_version_gte_3_1() {
245
+
246
+ wc_deprecated_function( __METHOD__, '5.5.0', __CLASS__ . '::is_wc_version_gte()' );
247
+
248
+ return self::is_wc_version_gte( '3.1' );
249
+ }
250
+
251
+
252
+ /**
253
+ * Determines if the installed version of WooCommerce is less than 3.1.
254
+ *
255
+ * @since 4.6.5
256
+ * @deprecated 5.5.0
257
+ *
258
+ * @return bool
259
+ */
260
+ public static function is_wc_version_lt_3_1() {
261
+
262
+ wc_deprecated_function( __METHOD__, '5.5.0', __CLASS__ . '::is_wc_version_lt()' );
263
+
264
+ return self::is_wc_version_lt( '3.1' );
265
+ }
266
+
267
+
268
+ /**
269
+ * Determines if the installed version of WooCommerce is equal or greater than a given version.
270
+ *
271
+ * @since 4.7.3
272
+ *
273
+ * @param string $version version number to compare
274
+ * @return bool
275
+ */
276
+ public static function is_wc_version_gte( $version ) {
277
+
278
+ $wc_version = self::get_wc_version();
279
+
280
+ return $wc_version && version_compare( $wc_version, $version, '>=' );
281
+ }
282
+
283
+
284
+ /**
285
+ * Determines if the installed version of WooCommerce is lower than a given version.
286
+ *
287
+ * @since 4.7.3
288
+ *
289
+ * @param string $version version number to compare
290
+ * @return bool
291
+ */
292
+ public static function is_wc_version_lt( $version ) {
293
+
294
+ $wc_version = self::get_wc_version();
295
+
296
+ return $wc_version && version_compare( $wc_version, $version, '<' );
297
+ }
298
+
299
+
300
+ /**
301
+ * Determines if the installed version of WooCommerce is greater than a given version.
302
+ *
303
+ * @since 2.0.0
304
+ *
305
+ * @param string $version the version to compare
306
+ * @return bool
307
+ */
308
+ public static function is_wc_version_gt( $version ) {
309
+
310
+ $wc_version = self::get_wc_version();
311
+
312
+ return $wc_version && version_compare( $wc_version, $version, '>' );
313
+ }
314
+
315
+
316
+ /** WordPress core ******************************************************/
317
+
318
+
319
+ /**
320
+ * Normalizes a WooCommerce page screen ID.
321
+ *
322
+ * Needed because WordPress uses a menu title (which is translatable), not slug, to generate screen ID.
323
+ * See details in: https://core.trac.wordpress.org/ticket/21454
324
+ * TODO: Add WP version check when https://core.trac.wordpress.org/ticket/18857 is addressed {BR 2016-12-12}
325
+ *
326
+ * @since 4.6.0
327
+ *
328
+ * @param string $slug slug for the screen ID to normalize (minus `woocommerce_page_`)
329
+ * @return string normalized screen ID
330
+ */
331
+ public static function normalize_wc_screen_id( $slug = 'wc-settings' ) {
332
+
333
+ // The textdomain usage is intentional here, we need to match the menu title.
334
+ $prefix = sanitize_title( __( 'WooCommerce', 'woocommerce' ) );
335
+
336
+ return $prefix . '_page_' . $slug;
337
+ }
338
+
339
+
340
+ /**
341
+ * Converts a shorthand byte value to an integer byte value.
342
+ *
343
+ * Wrapper for wp_convert_hr_to_bytes(), moved to load.php in WordPress 4.6 from media.php
344
+ *
345
+ * Based on ActionScheduler's compat wrapper for the same function:
346
+ * ActionScheduler_Compatibility::convert_hr_to_bytes()
347
+ *
348
+ * @link https://secure.php.net/manual/en/function.ini-get.php
349
+ * @link https://secure.php.net/manual/en/faq.using.php#faq.using.shorthandbytes
350
+ *
351
+ * @since 5.3.1
352
+ *
353
+ * @param string $value A (PHP ini) byte value, either shorthand or ordinary.
354
+ * @return int An integer byte value.
355
+ */
356
+ public static function convert_hr_to_bytes( $value ) {
357
+
358
+ if ( function_exists( 'wp_convert_hr_to_bytes' ) ) {
359
+
360
+ return wp_convert_hr_to_bytes( $value );
361
+ }
362
+
363
+ $value = strtolower( trim( $value ) );
364
+ $bytes = (int) $value;
365
+
366
+ if ( false !== strpos( $value, 'g' ) ) {
367
+
368
+ $bytes *= GB_IN_BYTES;
369
+
370
+ } elseif ( false !== strpos( $value, 'm' ) ) {
371
+
372
+ $bytes *= MB_IN_BYTES;
373
+
374
+ } elseif ( false !== strpos( $value, 'k' ) ) {
375
+
376
+ $bytes *= KB_IN_BYTES;
377
+ }
378
+
379
+ // deal with large (float) values which run into the maximum integer size
380
+ return min( $bytes, PHP_INT_MAX );
381
+ }
382
+
383
+
384
+ /** Subscriptions *********************************************************/
385
+
386
+
387
+ /**
388
+ * Determines if the installed version of WooCommerce Subscriptions is 2.0.0 or greater.
389
+ *
390
+ * @since 4.1.0
391
+ * @deprecated 5.5.0
392
+ *
393
+ * @return bool
394
+ */
395
+ public static function is_wc_subscriptions_version_gte_2_0() {
396
+
397
+ wc_deprecated_function( __METHOD__, '5.5.0', __CLASS__ . '::is_wc_subscriptions_version_gte()' );
398
+
399
+ return self::is_wc_subscriptions_version_gte( '2.0' );
400
+ }
401
+
402
+
403
+ /**
404
+ * Determines if the installed version of WooCommerce Subscriptions matches or exceeds a given version.
405
+ *
406
+ * @since 5.5.0
407
+ *
408
+ * @param string $version version number to compare
409
+ * @return bool
410
+ */
411
+ public static function is_wc_subscriptions_version_gte( $version ) {
412
+
413
+ $subscriptions_version = self::get_wc_subscriptions_version();
414
+
415
+ return $subscriptions_version && version_compare( $subscriptions_version, $version, '>=' );
416
+ }
417
+
418
+ /**
419
+ * Determines if the installed version of WooCommerce Subscriptions exceeds a given version.
420
+ *
421
+ * @since 5.5.0
422
+ *
423
+ * @param string $version version number to compare
424
+ * @return bool
425
+ */
426
+ public static function is_wc_subscriptions_version_gt( $version ) {
427
+
428
+ $subscriptions_version = self::get_wc_subscriptions_version();
429
+
430
+ return $subscriptions_version && version_compare( $subscriptions_version, $version, '>' );
431
+ }
432
+
433
+
434
+ /**
435
+ * Determines if the installed version of WooCommerce Subscriptions is lower than a given version.
436
+ *
437
+ * @since 5.5.0
438
+ *
439
+ * @param string $version version number to compare
440
+ * @return bool
441
+ */
442
+ public static function is_wc_subscriptions_version_lt( $version ) {
443
+
444
+ $subscriptions_version = self::get_wc_subscriptions_version();
445
+
446
+ return $subscriptions_version && version_compare( $subscriptions_version, $version, '<' );
447
+ }
448
+
449
+
450
+ /**
451
+ * Gets the version of the currently installed WooCommerce Subscriptions.
452
+ *
453
+ * @since 4.1.0
454
+ *
455
+ * @return string|null WooCommerce Subscriptions version number or null if not found
456
+ */
457
+ protected static function get_wc_subscriptions_version() {
458
+
459
+ return class_exists( 'WC_Subscriptions' ) && ! empty( \WC_Subscriptions::$version ) ? \WC_Subscriptions::$version : null;
460
+ }
461
+
462
+
463
+ /**
464
+ * Determines if the installed WooCommerce Subscriptions version matches a specific version.
465
+ *
466
+ * @since 5.5.0
467
+ *
468
+ * @param string $version semver
469
+ * @return bool
470
+ */
471
+ protected static function is_wc_subscriptions_version( $version ) {
472
+
473
+ $subscriptions_version = self::get_wc_subscriptions_version();
474
+
475
+ // accounts for semver cases like 2.2 being equal to 2.2.0
476
+ return $version === $subscriptions_version || ( $subscriptions_version && version_compare( $version, $subscriptions_version, '=' ) );
477
+ }
478
+
479
+
480
+ }
481
+
482
+
483
+ endif;
vendor/skyverge/wc-plugin-framework/woocommerce/class-sv-wc-plugin-dependencies.php ADDED
@@ -0,0 +1,468 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * WooCommerce Plugin Framework
4
+ *
5
+ * This source file is subject to the GNU General Public License v3.0
6
+ * that is bundled with this package in the file license.txt.
7
+ * It is also available through the world-wide-web at this URL:
8
+ * http://www.gnu.org/licenses/gpl-3.0.html
9
+ * If you did not receive a copy of the license and are unable to
10
+ * obtain it through the world-wide-web, please send an email
11
+ * to license@skyverge.com so we can send you a copy immediately.
12
+ *
13
+ * DISCLAIMER
14
+ *
15
+ * Do not edit or add to this file if you wish to upgrade the plugin to newer
16
+ * versions in the future. If you wish to customize the plugin for your
17
+ * needs please refer to http://www.skyverge.com
18
+ *
19
+ * @package SkyVerge/WooCommerce/Plugin/Classes
20
+ * @author SkyVerge
21
+ * @copyright Copyright (c) 2013-2020, SkyVerge, Inc.
22
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GNU General Public License v3.0
23
+ */
24
+
25
+ namespace SkyVerge\WooCommerce\PluginFramework\v5_5_4;
26
+
27
+ defined( 'ABSPATH' ) or exit;
28
+
29
+ if ( ! class_exists( '\\SkyVerge\\WooCommerce\\PluginFramework\\v5_5_4\\SV_WC_Plugin_Dependencies' ) ) :
30
+
31
+
32
+ /**
33
+ * Plugin dependencies handler.
34
+ *
35
+ * @since 5.2.0
36
+ */
37
+ class SV_WC_Plugin_Dependencies {
38
+
39
+
40
+ /** @var array required PHP extensions */
41
+ protected $php_extensions = array();
42
+
43
+ /** @var array required PHP functions */
44
+ protected $php_functions = array();
45
+
46
+ /** @var array required PHP settings */
47
+ protected $php_settings = array();
48
+
49
+ /** @var SV_WC_Plugin plugin instance */
50
+ protected $plugin;
51
+
52
+
53
+ /**
54
+ * Constructs the class.
55
+ *
56
+ * @since 5.2.0
57
+ *
58
+ * @param SV_WC_Plugin $plugin plugin instance
59
+ * @param array $args {
60
+ * PHP extension, function, and settings dependencies
61
+ *
62
+ * @type array $php_extensions PHP extension dependencies
63
+ * @type array $php_functions PHP function dependencies
64
+ * @type array $php_settings PHP settings dependencies
65
+ * }
66
+ */
67
+ public function __construct( SV_WC_Plugin $plugin, $args = array() ) {
68
+
69
+ $this->plugin = $plugin;
70
+
71
+ $dependencies = $this->parse_dependencies( $args );
72
+
73
+ $this->php_extensions = (array) $dependencies['php_extensions'];
74
+ $this->php_functions = (array) $dependencies['php_functions'];
75
+ $this->php_settings = (array) $dependencies['php_settings'];
76
+
77
+ // add the action & filter hooks
78
+ $this->add_hooks();
79
+ }
80
+
81
+
82
+ /**
83
+ * Parses the dependency arguments and sets defaults.
84
+ *
85
+ * @since 5.2.0
86
+ *
87
+ * @param array $args dependency args
88
+ * @return array
89
+ */
90
+ private function parse_dependencies( $args ) {
91
+
92
+ $dependencies = wp_parse_args( $args, array(
93
+ 'php_extensions' => array(),
94
+ 'php_functions' => array(),
95
+ 'php_settings' => array(),
96
+ ) );
97
+
98
+ $default_settings = array(
99
+ 'suhosin.post.max_array_index_length' => 256,
100
+ 'suhosin.post.max_totalname_length' => 65535,
101
+ 'suhosin.post.max_vars' => 1024,
102
+ 'suhosin.request.max_array_index_length' => 256,
103
+ 'suhosin.request.max_totalname_length' => 65535,
104
+ 'suhosin.request.max_vars' => 1024,
105
+ );
106
+
107
+ // override any default settings requirements if the plugin specifies them
108
+ if ( ! empty( $dependencies['php_settings'] ) ) {
109
+ $dependencies['php_settings'] = array_merge( $default_settings, $dependencies['php_settings'] );
110
+ }
111
+
112
+ return $dependencies;
113
+ }
114
+
115
+
116
+ /**
117
+ * Adds the action & filter hooks.
118
+ *
119
+ * @since 5.2.0
120
+ */
121
+ protected function add_hooks() {
122
+
123
+ // add the admin dependency notices
124
+ add_action( 'admin_init', array( $this, 'add_admin_notices' ) );
125
+ }
126
+
127
+
128
+ /**
129
+ * Adds the admin dependency notices.
130
+ *
131
+ * @since 5.2.0
132
+ */
133
+ public function add_admin_notices() {
134
+
135
+ $this->add_php_extension_notices();
136
+ $this->add_php_function_notices();
137
+ $this->add_php_settings_notices();
138
+
139
+ $this->add_deprecated_notices();
140
+ }
141
+
142
+
143
+ /**
144
+ * Adds notices for any missing PHP extensions.
145
+ *
146
+ * @since 5.2.0
147
+ */
148
+ public function add_php_extension_notices() {
149
+
150
+ $missing_extensions = $this->get_missing_php_extensions();
151
+
152
+ if ( count( $missing_extensions ) > 0 ) {
153
+
154
+ $message = sprintf(
155
+ /* translators: Placeholders: %1$s - plugin name, %2$s - a PHP extension/comma-separated list of PHP extensions */
156
+ _n(
157
+ '%1$s requires the %2$s PHP extension to function. Contact your host or server administrator to install and configure the missing extension.',
158
+ '%1$s requires the following PHP extensions to function: %2$s. Contact your host or server administrator to install and configure the missing extensions.',
159
+ count( $missing_extensions ),
160
+ 'woocommerce-plugin-framework'
161
+ ),
162
+ esc_html( $this->get_plugin()->get_plugin_name() ),
163
+ '<strong>' . implode( ', ', $missing_extensions ) . '</strong>'
164
+ );
165
+
166
+ $this->add_admin_notice( 'wc-' . $this->get_plugin()->get_id_dasherized() . '-missing-extensions', $message, 'error' );
167
+ }
168
+ }
169
+
170
+
171
+ /**
172
+ * Adds notices for any missing PHP functions.
173
+ *
174
+ * @since 5.2.0
175
+ */
176
+ public function add_php_function_notices() {
177
+
178
+ $missing_functions = $this->get_missing_php_functions();
179
+
180
+ if ( count( $missing_functions ) > 0 ) {
181
+
182
+ $message = sprintf(
183
+ /* translators: Placeholders: %1$s - plugin name, %2$s - a PHP function/comma-separated list of PHP functions */
184
+ _n(
185
+ '%1$s requires the %2$s PHP function to exist. Contact your host or server administrator to install and configure the missing function.',
186
+ '%1$s requires the following PHP functions to exist: %2$s. Contact your host or server administrator to install and configure the missing functions.',
187
+ count( $missing_functions ),
188
+ 'woocommerce-plugin-framework'
189
+ ),
190
+ esc_html( $this->get_plugin()->get_plugin_name() ),
191
+ '<strong>' . implode( ', ', $missing_functions ) . '</strong>'
192
+ );
193
+
194
+ $this->add_admin_notice( 'wc-' . $this->get_plugin()->get_id_dasherized() . '-missing-functions', $message, 'error' );
195
+ }
196
+ }
197
+
198
+
199
+ /**
200
+ * Adds notices for any incompatible PHP settings.
201
+ *
202
+ * @since 5.2.0
203
+ */
204
+ public function add_php_settings_notices() {
205
+
206
+ if ( isset( $_GET['page'] ) && 'wc-settings' === $_GET['page'] ) {
207
+
208
+ $bad_settings = $this->get_incompatible_php_settings();
209
+
210
+ if ( count( $bad_settings ) > 0 ) {
211
+
212
+ $message = sprintf(
213
+ /* translators: Placeholders: %s - plugin name */
214
+ __( '%s may behave unexpectedly because the following PHP configuration settings are required:' ),
215
+ '<strong>' . esc_html( $this->get_plugin()->get_plugin_name() ) . '</strong>'
216
+ );
217
+
218
+ $message .= '<ul>';
219
+
220
+ foreach ( $bad_settings as $setting => $values ) {
221
+
222
+ $setting_message = '<code>' . $setting . ' = ' . $values['expected'] . '</code>';
223
+
224
+ if ( ! empty( $values['type'] ) && 'min' === $values['type'] ) {
225
+
226
+ $setting_message = sprintf(
227
+ /** translators: Placeholders: %s - a PHP setting value */
228
+ __( '%s or higher', 'woocommerce-plugin-framework' ),
229
+ $setting_message
230
+ );
231
+ }
232
+
233
+ $message .= '<li>' . $setting_message . '</li>';
234
+ }
235
+
236
+ $message .= '</ul>';
237
+
238
+ $message .= __( 'Please contact your hosting provider or server administrator to configure these settings.', 'woocommerce-plugin-framework' );
239
+
240
+ $this->add_admin_notice( 'wc-' . $this->get_plugin()->get_id_dasherized() . '-incompatibile-php-settings', $message, 'warning' );
241
+ }
242
+ }
243
+ }
244
+
245
+
246
+ /**
247
+ * Gets any deprecated warning notices.
248
+ *
249
+ * @since 5.2.0
250
+ */
251
+ protected function add_deprecated_notices() {
252
+
253
+ // add a notice for PHP < 5.6
254
+ if ( version_compare( PHP_VERSION, '5.6.0', '<' ) ) {
255
+
256
+ $message = '<p>';
257
+
258
+ $message .= sprintf(
259
+ /* translators: Placeholders: %1$s - <strong>, %2$s - </strong> */
260
+ __( 'Hey there! We\'ve noticed that your server is running %1$san outdated version of PHP%2$s, which is the programming language that WooCommerce and its extensions are built on.
261
+ The PHP version that is currently used for your site is no longer maintained, nor %1$sreceives security updates%2$s; newer versions are faster and more secure.
262
+ As a result, %3$s no longer supports this version and you should upgrade PHP as soon as possible.
263
+ Your hosting provider can do this for you. %4$sHere are some resources to help you upgrade%5$s and to explain PHP versions further.', 'woocommerce-plugin-framework' ),
264
+ '<strong>', '</strong>',
265
+ esc_html( $this->get_plugin()->get_plugin_name() ),
266
+ '<a href="http://skyver.ge/upgradephp">', '</a>'
267
+ );
268
+
269
+ $message .= '</p>';
270
+
271
+ $this->add_admin_notice( 'sv-wc-deprecated-php-version', $message, 'error' );
272
+ }
273
+ }
274
+
275
+
276
+ /**
277
+ * Adds an admin notice.
278
+ *
279
+ * @since 5.2.0
280
+ *
281
+ * @param string $id notice ID
282
+ * @param string $message notice message
283
+ * @param string $type notice type
284
+ */
285
+ protected function add_admin_notice( $id, $message, $type = 'info' ) {
286
+
287
+ $notice_class = $type;
288
+
289
+ // translate the types into WP notice classes
290
+ switch ( $type ) {
291
+
292
+ case 'error':
293
+ $notice_class = 'notice-error';
294
+ break;
295
+
296
+ case 'warning':
297
+ $notice_class = 'notice-warning';
298
+ break;
299
+
300
+ case 'info':
301
+ $notice_class = 'notice-info';
302
+ break;
303
+
304
+ case 'success':
305
+ $notice_class = 'notice-success';
306
+ break;
307
+ }
308
+
309
+ $this->get_plugin()->get_admin_notice_handler()->add_admin_notice( $message, $id, array(
310
+ 'notice_class' => $notice_class,
311
+ ) );
312
+ }
313
+
314
+
315
+ /** Getter methods ********************************************************/
316
+
317
+
318
+ /**
319
+ * Gets any missing PHP extensions.
320
+ *
321
+ * @since 5.2.0
322
+ *
323
+ * @return array
324
+ */
325
+ public function get_missing_php_extensions() {
326
+
327
+ $missing_extensions = [];
328
+
329
+ foreach ( $this->get_php_extensions() as $extension ) {
330
+
331
+ if ( ! extension_loaded( $extension ) ) {
332
+ $missing_extensions[] = $extension;
333
+ }
334
+ }
335
+
336
+ return $missing_extensions;
337
+ }
338
+
339
+
340
+ /**
341
+ * Gets the required PHP extensions.
342
+ *
343
+ * @since 5.2.0
344
+ *
345
+ * @return array
346
+ */
347
+ public function get_php_extensions() {
348
+
349
+ return $this->php_extensions;
350
+ }
351
+
352
+
353
+ /**
354
+ * Gets any missing PHP functions.
355
+ *
356
+ * @since 5.2.0
357
+ *
358
+ * @return array
359
+ */
360
+ public function get_missing_php_functions() {
361
+
362
+ $missing_functions = [];
363
+
364
+ foreach ( $this->get_php_functions() as $function ) {
365
+
366
+ if ( ! extension_loaded( $function ) ) {
367
+ $missing_functions[] = $function;
368
+ }
369
+ }
370
+
371
+ return $missing_functions;
372
+ }
373
+
374
+
375
+ /**
376
+ * Gets the required PHP functions.
377
+ *
378
+ * @since 5.2.0
379
+ *
380
+ * @return array
381
+ */
382
+ public function get_php_functions() {
383
+
384
+ return $this->php_functions;
385
+ }
386
+
387
+
388
+ /**
389
+ * Gets any incompatible PHP settings.
390
+ *
391
+ * @since 5.2.0
392
+ *
393
+ * @return array
394
+ */
395
+ public function get_incompatible_php_settings() {
396
+
397
+ $incompatible_settings = [];
398
+
399
+ if ( function_exists( 'ini_get' ) ) {
400
+
401
+ foreach ( $this->get_php_settings() as $setting => $expected ) {
402
+
403
+ $actual = ini_get( $setting );
404
+
405
+ if ( ! $actual ) {
406
+ continue;
407
+ }
408
+
409
+ if ( is_int( $expected ) ) {
410
+
411
+ // determine if this is a size string, like "10MB"
412
+ $is_size = ! is_numeric( substr( $actual, -1 ) );
413
+
414
+ $actual_num = $is_size ? wc_let_to_num( $actual ) : $actual;
415
+
416
+ if ( $actual_num < $expected ) {
417
+
418
+ $incompatible_settings[ $setting ] = [
419
+ 'expected' => $is_size ? size_format( $expected ) : $expected,
420
+ 'actual' => $is_size ? size_format( $actual_num ) : $actual,
421
+ 'type' => 'min',
422
+ ];
423
+ }
424
+
425
+ } elseif ( $actual !== $expected ) {
426
+
427
+ $incompatible_settings[ $setting ] = [
428
+ 'expected' => $expected,
429
+ 'actual' => $actual,
430
+ ];
431
+ }
432
+ }
433
+ }
434
+
435
+ return $incompatible_settings;
436
+ }
437
+
438
+
439
+ /**
440
+ * Gets the required PHP settings.
441
+ *
442
+ * @since 5.2.0
443
+ *
444
+ * @return array
445
+ */
446
+ public function get_php_settings() {
447
+
448
+ return $this->php_settings;
449
+ }
450
+
451
+
452
+ /**
453
+ * Gets the plugin instance.
454
+ *
455
+ * @since 5.2.0
456
+ *
457
+ * @return SV_WC_Plugin
458
+ */
459
+ protected function get_plugin() {
460
+
461
+ return $this->plugin;
462
+ }
463
+
464
+
465
+ }
466
+
467
+
468
+ endif;
vendor/skyverge/wc-plugin-framework/woocommerce/class-sv-wc-plugin-exception.php ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * WooCommerce Plugin Framework
4
+ *
5
+ * This source file is subject to the GNU General Public License v3.0
6
+ * that is bundled with this package in the file license.txt.
7
+ * It is also available through the world-wide-web at this URL:
8
+ * http://www.gnu.org/licenses/gpl-3.0.html
9
+ * If you did not receive a copy of the license and are unable to
10
+ * obtain it through the world-wide-web, please send an email
11
+ * to license@skyverge.com so we can send you a copy immediately.
12
+ *
13
+ * DISCLAIMER
14
+ *
15
+ * Do not edit or add to this file if you wish to upgrade the plugin to newer
16
+ * versions in the future. If you wish to customize the plugin for your
17
+ * needs please refer to http://www.skyverge.com
18
+ *
19
+ * @package SkyVerge/WooCommerce/Exceptions
20
+ * @author SkyVerge
21
+ * @copyright Copyright (c) 2013-2020, SkyVerge, Inc.
22
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GNU General Public License v3.0
23
+ */
24
+
25
+ namespace SkyVerge\WooCommerce\PluginFramework\v5_5_4;
26
+
27
+ defined( 'ABSPATH' ) or exit;
28
+
29
+ if ( ! class_exists( '\\SkyVerge\\WooCommerce\\PluginFramework\\v5_5_4\\SV_WC_Plugin_Exception' ) ) :
30
+
31
+
32
+ /**
33
+ * Plugin Framework Exception - generic Exception
34
+ */
35
+ class SV_WC_Plugin_Exception extends \Exception { }
36
+
37
+
38
+ endif;
vendor/skyverge/wc-plugin-framework/woocommerce/class-sv-wc-plugin.php ADDED
@@ -0,0 +1,1457 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * WooCommerce Plugin Framework
4
+ *
5
+ * This source file is subject to the GNU General Public License v3.0
6
+ * that is bundled with this package in the file license.txt.
7
+ * It is also available through the world-wide-web at this URL:
8
+ * http://www.gnu.org/licenses/gpl-3.0.html
9
+ * If you did not receive a copy of the license and are unable to
10
+ * obtain it through the world-wide-web, please send an email
11
+ * to license@skyverge.com so we can send you a copy immediately.
12
+ *
13
+ * DISCLAIMER
14
+ *
15
+ * Do not edit or add to this file if you wish to upgrade the plugin to newer
16
+ * versions in the future. If you wish to customize the plugin for your
17
+ * needs please refer to http://www.skyverge.com
18
+ *
19
+ * @package SkyVerge/WooCommerce/Plugin/Classes
20
+ * @author SkyVerge
21
+ * @copyright Copyright (c) 2013-2020, SkyVerge, Inc.
22
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GNU General Public License v3.0
23
+ */
24
+
25
+ namespace SkyVerge\WooCommerce\PluginFramework\v5_5_4;
26
+
27
+ defined( 'ABSPATH' ) or exit;
28
+
29
+ if ( ! class_exists( '\\SkyVerge\\WooCommerce\\PluginFramework\\v5_5_4\\SV_WC_Plugin' ) ) :
30
+
31
+
32
+ /**
33
+ * # WooCommerce Plugin Framework
34
+ *
35
+ * This framework class provides a base level of configurable and overrideable
36
+ * functionality and features suitable for the implementation of a WooCommerce
37
+ * plugin. This class handles all the "non-feature" support tasks such
38
+ * as verifying dependencies are met, loading the text domain, etc.
39
+ *
40
+ * @version 5.5.0
41
+ */
42
+ abstract class SV_WC_Plugin {
43
+
44
+
45
+ /** Plugin Framework Version */
46
+ const VERSION = '5.5.4';
47
+
48
+ /** @var object single instance of plugin */
49
+ protected static $instance;
50
+
51
+ /** @var string plugin id */
52
+ private $id;
53
+
54
+ /** @var string version number */
55
+ private $version;
56
+
57
+ /** @var string plugin path, without trailing slash */
58
+ private $plugin_path;
59
+
60
+ /** @var string plugin URL */
61
+ private $plugin_url;
62
+
63
+ /** @var string template path, without trailing slash */
64
+ private $template_path;
65
+
66
+ /** @var \WC_Logger instance */
67
+ private $logger;
68
+
69
+ /** @var SV_WP_Admin_Message_Handler instance */
70
+ private $message_handler;
71
+
72
+ /** @var string the plugin text domain */
73
+ private $text_domain;
74
+
75
+ /** @var array memoized list of active plugins */
76
+ private $active_plugins = [];
77
+
78
+ /** @var int|float minimum supported WooCommerce versions before the latest (units for major releases, decimals for minor) */
79
+ private $min_wc_semver;
80
+
81
+ /** @var SV_WC_Plugin_Dependencies dependency handler instance */
82
+ private $dependency_handler;
83
+
84
+ /** @var SV_WC_Hook_Deprecator hook deprecator instance */
85
+ private $hook_deprecator;
86
+
87
+ /** @var Plugin\Lifecycle lifecycle handler instance */
88
+ protected $lifecycle_handler;
89
+
90
+ /** @var REST_API REST API handler instance */
91
+ protected $rest_api_handler;
92
+
93
+ /** @var Admin\Setup_Wizard handler instance */
94
+ protected $setup_wizard_handler;
95
+
96
+ /** @var SV_WC_Admin_Notice_Handler the admin notice handler class */
97
+ private $admin_notice_handler;
98
+
99
+
100
+ /**
101
+ * Initialize the plugin.
102
+ *
103
+ * Child plugin classes may add their own optional arguments.
104
+ *
105
+ * @since 2.0.0
106
+ *
107
+ * @param string $id plugin id
108
+ * @param string $version plugin version number
109
+ * @param array $args {
110
+ * optional plugin arguments
111
+ *
112
+ * @type int|float $latest_wc_versions the last supported versions of WooCommerce, as a major.minor float relative to the latest available version
113
+ * @type string $text_domain the plugin textdomain, used to set up translations
114
+ * @type array $dependencies {
115
+ * PHP extension, function, and settings dependencies
116
+ *
117
+ * @type array $php_extensions PHP extension dependencies
118
+ * @type array $php_functions PHP function dependencies
119
+ * @type array $php_settings PHP settings dependencies
120
+ * }
121
+ * }
122
+ */
123
+ public function __construct( $id, $version, $args = [] ) {
124
+
125
+ // required params
126
+ $this->id = $id;
127
+ $this->version = $version;
128
+
129
+ $args = wp_parse_args( $args, [
130
+ 'min_wc_semver' => 0.2, // by default, 2 minor versions behind the latest published are supported
131
+ 'text_domain' => '',
132
+ 'dependencies' => [],
133
+ ] );
134
+
135
+ $this->min_wc_semver = is_numeric( $args['min_wc_semver'] ) ? abs( $args['min_wc_semver'] ) : null;
136
+ $this->text_domain = $args['text_domain'];
137
+
138
+ // includes that are required to be available at all times
139
+ $this->includes();
140
+
141
+ // initialize the dependencies manager
142
+ $this->init_dependencies( $args['dependencies'] );
143
+
144
+ // build the admin message handler instance
145
+ $this->init_admin_message_handler();
146
+
147
+ // build the admin notice handler instance
148
+ $this->init_admin_notice_handler();
149
+
150
+ // build the hook deprecator instance
151
+ $this->init_hook_deprecator();
152
+
153
+ // build the lifecycle handler instance
154
+ $this->init_lifecycle_handler();
155
+
156
+ // build the REST API handler instance
157
+ $this->init_rest_api_handler();
158
+
159
+ // build the setup handler instance
160
+ $this->init_setup_wizard_handler();
161
+
162
+ // add the action & filter hooks
163
+ $this->add_hooks();
164
+ }
165
+
166
+
167
+ /** Init methods **********************************************************/
168
+
169
+
170
+ /**
171
+ * Initializes the plugin dependency handler.
172
+ *
173
+ * @since 5.2.0
174
+ *
175
+ * @param array $dependencies {
176
+ * PHP extension, function, and settings dependencies
177
+ *
178
+ * @type array $php_extensions PHP extension dependencies
179
+ * @type array $php_functions PHP function dependencies
180
+ * @type array $php_settings PHP settings dependencies
181
+ * }
182
+ */
183
+ protected function init_dependencies( $dependencies ) {
184
+
185
+ $this->dependency_handler = new SV_WC_Plugin_Dependencies( $this, $dependencies );
186
+ }
187
+
188
+
189
+ /**
190
+ * Builds the admin message handler instance.
191
+ *
192
+ * Plugins can override this with their own handler.
193
+ *
194
+ * @since 5.2.0
195
+ */
196
+ protected function init_admin_message_handler() {
197
+
198
+ $this->message_handler = new SV_WP_Admin_Message_Handler( $this->get_id() );
199
+ }
200
+
201
+
202
+ /**
203
+ * Builds the admin notice handler instance.
204
+ *
205
+ * Plugins can override this with their own handler.
206
+ *
207
+ * @since 5.2.0
208
+ */
209
+ protected function init_admin_notice_handler() {
210
+
211
+ $this->admin_notice_handler = new SV_WC_Admin_Notice_Handler( $this );
212
+ }
213
+
214
+
215
+ /**
216
+ * Builds the hook deprecator instance.
217
+ *
218
+ * Plugins can override this with their own handler.
219
+ *
220
+ * @since 5.2.0
221
+ */
222
+ protected function init_hook_deprecator() {
223
+
224
+ $this->hook_deprecator = new SV_WC_Hook_Deprecator( $this->get_plugin_name(), $this->get_deprecated_hooks() );
225
+ }
226
+
227
+
228
+ /**
229
+ * Builds the lifecycle handler instance.
230
+ *
231
+ * Plugins can override this with their own handler to perform install and
232
+ * upgrade routines.
233
+ *
234
+ * @since 5.2.0
235
+ */
236
+ protected function init_lifecycle_handler() {
237
+
238
+ $this->lifecycle_handler = new Plugin\Lifecycle( $this );
239
+ }
240
+
241
+
242
+ /**
243
+ * Builds the REST API handler instance.
244
+ *
245
+ * Plugins can override this to add their own data and/or routes.
246
+ *
247
+ * @since 5.2.0
248
+ */
249
+ protected function init_rest_api_handler() {
250
+
251
+ $this->rest_api_handler = new REST_API( $this );
252
+ }
253
+
254
+
255
+ /**
256
+ * Builds the Setup Wizard handler instance.
257
+ *
258
+ * Plugins can override and extend this method to add their own setup wizard.
259
+ *
260
+ * @since 5.3.0
261
+ */
262
+ protected function init_setup_wizard_handler() {
263
+
264
+ require_once( $this->get_framework_path() . '/admin/abstract-sv-wc-plugin-admin-setup-wizard.php' );
265
+ }
266
+
267
+
268
+ /**
269
+ * Adds the action & filter hooks.
270
+ *
271
+ * @since 5.2.0
272
+ */
273
+ private function add_hooks() {
274
+
275
+ // initialize the plugin
276
+ add_action( 'plugins_loaded', array( $this, 'init_plugin' ), 15 );
277
+
278
+ // initialize the plugin admin
279
+ add_action( 'admin_init', array( $this, 'init_admin' ), 0 );
280
+
281
+ // hook for translations separately to ensure they're loaded
282
+ add_action( 'init', array( $this, 'load_translations' ) );
283
+
284
+ // add the admin notices
285
+ add_action( 'admin_notices', array( $this, 'add_admin_notices' ) );
286
+ add_action( 'admin_footer', array( $this, 'add_delayed_admin_notices' ) );
287
+
288
+ // add a 'Configure' link to the plugin action links
289
+ add_filter( 'plugin_action_links_' . plugin_basename( $this->get_plugin_file() ), array( $this, 'plugin_action_links' ) );
290
+
291
+ // automatically log HTTP requests from SV_WC_API_Base
292
+ $this->add_api_request_logging();
293
+
294
+ // add any PHP incompatibilities to the system status report
295
+ add_filter( 'woocommerce_system_status_environment_rows', array( $this, 'add_system_status_php_information' ) );
296
+ }
297
+
298
+
299
+ /**
300
+ * Cloning instances is forbidden due to singleton pattern.
301
+ *
302
+ * @since 3.1.0
303
+ */
304
+ public function __clone() {
305
+ /* translators: Placeholders: %s - plugin name */
306
+ _doing_it_wrong( __FUNCTION__, sprintf( esc_html__( 'You cannot clone instances of %s.', 'woocommerce-plugin-framework' ), esc_html( $this->get_plugin_name() ) ), '3.1.0' );
307
+ }
308
+
309
+
310
+ /**
311
+ * Unserializing instances is forbidden due to singleton pattern.
312
+ *
313
+ * @since 3.1.0
314
+ */
315
+ public function __wakeup() {
316
+ /* translators: Placeholders: %s - plugin name */
317
+ _doing_it_wrong( __FUNCTION__, sprintf( esc_html__( 'You cannot unserialize instances of %s.', 'woocommerce-plugin-framework' ), esc_html( $this->get_plugin_name() ) ), '3.1.0' );
318
+ }
319
+
320
+
321
+ /**
322
+ * Load plugin & framework text domains.
323
+ *
324
+ * @internal
325
+ *
326
+ * @since 4.2.0
327
+ */
328
+ public function load_translations() {
329
+
330
+ $this->load_framework_textdomain();
331
+
332
+ // if this plugin passes along its text domain, load its translation files
333
+ if ( $this->text_domain ) {
334
+ $this->load_plugin_textdomain();
335
+ }
336
+ }
337
+
338
+
339
+ /**
340
+ * Loads the framework textdomain.
341
+ *
342
+ * @since 4.5.0
343
+ */
344
+ protected function load_framework_textdomain() {
345
+ $this->load_textdomain( 'woocommerce-plugin-framework', dirname( plugin_basename( $this->get_framework_file() ) ) );
346
+ }
347
+
348
+
349
+ /**
350
+ * Loads the plugin textdomain.
351
+ *
352
+ * @since 4.5.0
353
+ */
354
+ protected function load_plugin_textdomain() {
355
+ $this->load_textdomain( $this->text_domain, dirname( plugin_basename( $this->get_plugin_file() ) ) );
356
+ }
357
+
358
+
359
+ /**
360
+ * Loads the plugin textdomain.
361
+ *
362
+ * @since 4.5.0
363
+ * @param string $textdomain the plugin textdomain
364
+ * @param string $path the i18n path
365
+ */
366
+ protected function load_textdomain( $textdomain, $path ) {
367
+
368
+ // user's locale if in the admin for WP 4.7+, or the site locale otherwise
369
+ $locale = is_admin() && is_callable( 'get_user_locale' ) ? get_user_locale() : get_locale();
370
+
371
+ $locale = apply_filters( 'plugin_locale', $locale, $textdomain );
372
+
373
+ load_textdomain( $textdomain, WP_LANG_DIR . '/' . $textdomain . '/' . $textdomain . '-' . $locale . '.mo' );
374
+
375
+ load_plugin_textdomain( $textdomain, false, untrailingslashit( $path ) . '/i18n/languages' );
376
+ }
377
+
378
+
379
+ /**
380
+ * Initializes the plugin.
381
+ *
382
+ * Plugins can override this to set up any handlers after WordPress is ready.
383
+ *
384
+ * @since 5.2.0
385
+ */
386
+ public function init_plugin() {
387
+
388
+ // stub
389
+ }
390
+
391
+
392
+ /**
393
+ * Initializes the plugin admin.
394
+ *
395
+ * Plugins can override this to set up any handlers after the WordPress admin is ready.
396
+ *
397
+ * @since 5.2.0
398
+ */
399
+ public function init_admin() {
400
+
401
+ // stub
402
+ }
403
+
404
+
405
+ /**
406
+ * Include any critical files which must be available as early as possible,
407
+ *
408
+ * @since 2.0.0
409
+ */
410
+ private function includes() {
411
+
412
+ $framework_path = $this->get_framework_path();
413
+
414
+ // common exception class
415
+ require_once( $framework_path . '/class-sv-wc-plugin-exception.php' );
416
+
417
+ // addresses
418
+ require_once( $framework_path . '/Addresses/Address.php' );
419
+ require_once( $framework_path . '/Addresses/Customer_Address.php' );
420
+
421
+ // common utility methods
422
+ require_once( $framework_path . '/class-sv-wc-helper.php' );
423
+ require_once( $framework_path . '/Country_Helper.php' );
424
+
425
+ // backwards compatibility for older WC versions
426
+ require_once( $framework_path . '/class-sv-wc-plugin-compatibility.php' );
427
+ require_once( $framework_path . '/compatibility/abstract-sv-wc-data-compatibility.php' );
428
+ require_once( $framework_path . '/compatibility/class-sv-wc-order-compatibility.php' );
429
+ require_once( $framework_path . '/compatibility/class-sv-wc-product-compatibility.php' );
430
+
431
+ // TODO: Remove this when WC 3.x can be required {CW 2017-03-16}
432
+ require_once( $framework_path . '/compatibility/class-sv-wc-datetime.php' );
433
+
434
+ // generic API base
435
+ require_once( $framework_path . '/api/class-sv-wc-api-exception.php' );
436
+ require_once( $framework_path . '/api/class-sv-wc-api-base.php' );
437
+ require_once( $framework_path . '/api/interface-sv-wc-api-request.php' );
438
+ require_once( $framework_path . '/api/interface-sv-wc-api-response.php' );
439
+
440
+ // XML API base
441
+ require_once( $framework_path . '/api/abstract-sv-wc-api-xml-request.php' );
442
+ require_once( $framework_path . '/api/abstract-sv-wc-api-xml-response.php' );
443
+
444
+ // JSON API base
445
+ require_once( $framework_path . '/api/abstract-sv-wc-api-json-request.php' );
446
+ require_once( $framework_path . '/api/abstract-sv-wc-api-json-response.php' );
447
+
448
+ // Handlers
449
+ require_once( $framework_path . '/class-sv-wc-plugin-dependencies.php' );
450
+ require_once( $framework_path . '/class-sv-wc-hook-deprecator.php' );
451
+ require_once( $framework_path . '/class-sv-wp-admin-message-handler.php' );
452
+ require_once( $framework_path . '/class-sv-wc-admin-notice-handler.php' );
453
+ require_once( $framework_path . '/Lifecycle.php' );
454
+ require_once( $framework_path . '/rest-api/class-sv-wc-plugin-rest-api.php' );
455
+ }
456
+
457
+
458
+ /**
459
+ * Return deprecated/removed hooks. Implementing classes should override this
460
+ * and return an array of deprecated/removed hooks in the following format:
461
+ *
462
+ * $old_hook_name = array {
463
+ * @type string $version version the hook was deprecated/removed in
464
+ * @type bool $removed if present and true, the message will indicate the hook was removed instead of deprecated
465
+ * @type string|bool $replacement if present and a string, the message will indicate the replacement hook to use,
466
+ * otherwise (if bool and false) the message will indicate there is no replacement available.
467
+ * }
468
+ *
469
+ * @since 4.3.0
470
+ * @return array
471
+ */
472
+ protected function get_deprecated_hooks() {
473
+
474
+ // stub method
475
+ return array();
476
+ }
477
+
478
+
479
+ /** Admin methods ******************************************************/
480
+
481
+
482
+ /**
483
+ * Returns true if on the admin plugin settings page, if any
484
+ *
485
+ * @since 2.0.0
486
+ * @return boolean true if on the admin plugin settings page
487
+ */
488
+ public function is_plugin_settings() {
489
+ // optional method, not all plugins *have* a settings page
490
+ return false;
491
+ }
492
+
493
+
494
+ /**
495
+ * Adds admin notices upon initialization.
496
+ *
497
+ * This may also produce notices if running an unsupported version of WooCommerce.
498
+ *
499
+ * @since 3.0.0
500
+ */
501
+ public function add_admin_notices() {
502
+
503
+ // bail if there's no defined versions to compare
504
+ if ( empty( $this->min_wc_semver ) || ! is_numeric( $this->min_wc_semver ) ) {
505
+ return;
506
+ }
507
+
508
+ $latest_wc_versions = SV_WC_Plugin_Compatibility::get_latest_wc_versions();
509
+ $current_wc_version = SV_WC_Plugin_Compatibility::get_wc_version();
510
+
511
+ // bail if the latest WooCommerce version or the current WooCommerce versions can't be determined
512
+ if ( empty( $latest_wc_versions ) || empty( $current_wc_version ) ) {
513
+ return;
514
+ }
515
+
516
+ // grab latest published version
517
+ $supported_wc_version = $latest_wc_version = current( $latest_wc_versions );
518
+
519
+ // grab semver parts
520
+ $latest_semver = explode( '.', $latest_wc_version );
521
+ $supported_semver = explode( '.', (string) $this->min_wc_semver );
522
+ $supported_major = max( 0, (int) $latest_semver[0] - (int) $supported_semver[0] );
523
+ $supported_minor = isset( $supported_semver[1] ) ? (int) $supported_semver[1] : 0;
524
+ $previous_minor = null;
525
+
526
+ // loop known WooCommerce versions from the most recent until we get the oldest supported one
527
+ foreach ( $latest_wc_versions as $older_wc_version ) {
528
+
529
+ // as we loop through versions, the latest one before we break the loop will be the minimum supported one
530
+ $supported_wc_version = $older_wc_version;
531
+
532
+ $older_semver = explode( '.', $older_wc_version );
533
+ $older_major = (int) $older_semver[0];
534
+ $older_minor = isset( $older_semver[1] ) ? (int) $older_semver[1] : 0;
535
+
536
+ // if major is ignored, skip; if the minor hasn't changed (patch must be), skip
537
+ if ( $older_major > $supported_major || $older_minor === $previous_minor ) {
538
+ continue;
539
+ }
540
+
541
+ // we reached the maximum number of supported minor versions
542
+ if ( $supported_minor <= 0 ) {
543
+ break;
544
+ }
545
+
546
+ // store the previous minor while we loop patch versions, which we ignore
547
+ $previous_minor = $older_minor;
548
+
549
+ $supported_minor--;
550
+ }
551
+
552
+ // for strict comparison, we strip the patch version from the determined versions and compare only major, minor versions, ignoring patches (i.e. 1.2.3 becomes 1.2)
553
+ $current_wc_version = substr( $current_wc_version, 0, strpos( $current_wc_version, '.', strpos( $current_wc_version, '.' ) + 1 ) );
554
+ $supported_wc_version = substr( $supported_wc_version, 0, strpos( $supported_wc_version, '.', strpos( $supported_wc_version, '.' ) + 1 ) );
555
+ $compared_wc_version = $current_wc_version && $supported_wc_version ? version_compare( $current_wc_version, $supported_wc_version ) : null;
556
+
557
+ // installed version is at more than 2 minor versions ($min_wc_semver value) behind the last published version
558
+ if ( -1 === $compared_wc_version ) {
559
+
560
+ $this->get_admin_notice_handler()->add_admin_notice(
561
+ sprintf(
562
+ /* translators: Placeholders: %1$s - plugin name, %2$s - WooCommerce version number, %3$s - opening <a> HTML link tag, %4$s - closing </a> HTML link tag */
563
+ __( 'Heads up! %1$s will soon discontinue support for WooCommerce %2$s. Please %3$supdate WooCommerce%4$s to take advantage of the latest updates and features.', 'woocommerce-plugin-framework' ),
564
+ $this->get_plugin_name(),
565
+ $current_wc_version,
566
+ '<a href="' . esc_url( admin_url( 'update-core.php' ) ) .'">', '</a>'
567
+ ),
568
+ $this->get_id_dasherized() . '-deprecated-wc-version-as-of-' . str_replace( '.', '-', $supported_wc_version ),
569
+ [ 'notice_class' => 'notice-info' ]
570
+ );
571
+ }
572
+ }
573
+
574
+
575
+ /**
576
+ * Convenience method to add delayed admin notices, which may depend upon
577
+ * some setting being saved prior to determining whether to render
578
+ *
579
+ * @since 3.0.0
580
+ */
581
+ public function add_delayed_admin_notices() {
582
+ // stub method
583
+ }
584
+
585
+
586
+ /**
587
+ * Return the plugin action links. This will only be called if the plugin
588
+ * is active.
589
+ *
590
+ * @since 2.0.0
591
+ * @param array $actions associative array of action names to anchor tags
592
+ * @return array associative array of plugin action links
593
+ */
594
+ public function plugin_action_links( $actions ) {
595
+
596
+ $custom_actions = array();
597
+
598
+ // settings url(s)
599
+ if ( $this->get_settings_link( $this->get_id() ) ) {
600
+ $custom_actions['configure'] = $this->get_settings_link( $this->get_id() );
601
+ }
602
+
603
+ // documentation url if any
604
+ if ( $this->get_documentation_url() ) {
605
+ /* translators: Docs as in Documentation */
606
+ $custom_actions['docs'] = sprintf( '<a href="%s" target="_blank">%s</a>', $this->get_documentation_url(), esc_html__( 'Docs', 'woocommerce-plugin-framework' ) );
607
+ }
608
+
609
+ // support url if any
610
+ if ( $this->get_support_url() ) {
611
+ $custom_actions['support'] = sprintf( '<a href="%s">%s</a>', $this->get_support_url(), esc_html_x( 'Support', 'noun', 'woocommerce-plugin-framework' ) );
612
+ }
613
+
614
+ // review url if any
615
+ if ( $this->get_reviews_url() ) {
616
+ $custom_actions['review'] = sprintf( '<a href="%s">%s</a>', $this->get_reviews_url(), esc_html_x( 'Review', 'verb', 'woocommerce-plugin-framework' ) );
617
+ }
618
+
619
+ // add the links to the front of the actions list
620
+ return array_merge( $custom_actions, $actions );
621
+ }
622
+
623
+
624
+ /** Helper methods ******************************************************/
625
+
626
+
627
+ /**
628
+ * Automatically log API requests/responses when using SV_WC_API_Base
629
+ *
630
+ * @since 2.2.0
631
+ * @see SV_WC_API_Base::broadcast_request()
632
+ */
633
+ public function add_api_request_logging() {
634
+
635
+ if ( ! has_action( 'wc_' . $this->get_id() . '_api_request_performed' ) ) {
636
+ add_action( 'wc_' . $this->get_id() . '_api_request_performed', array( $this, 'log_api_request' ), 10, 2 );
637
+ }
638
+ }
639
+
640
+
641
+ /**
642
+ * Log API requests/responses
643
+ *
644
+ * @since 2.2.0
645
+ * @param array $request request data, see SV_WC_API_Base::broadcast_request() for format
646
+ * @param array $response response data
647
+ * @param string|null $log_id log to write data to
648
+ */
649
+ public function log_api_request( $request, $response, $log_id = null ) {
650
+
651
+ $this->log( "Request\n" . $this->get_api_log_message( $request ), $log_id );
652
+
653
+ if ( ! empty( $response ) ) {
654
+ $this->log( "Response\n" . $this->get_api_log_message( $response ), $log_id );
655
+ }
656
+ }
657
+
658
+
659
+ /**
660
+ * Transform the API request/response data into a string suitable for logging
661
+ *
662
+ * @since 2.2.0
663
+ * @param array $data
664
+ * @return string
665
+ */
666
+ public function get_api_log_message( $data ) {
667
+
668
+ $messages = array();
669
+
670
+ $messages[] = isset( $data['uri'] ) && $data['uri'] ? 'Request' : 'Response';
671
+
672
+ foreach ( (array) $data as $key => $value ) {
673
+ $messages[] = trim( sprintf( '%s: %s', $key, is_array( $value ) || ( is_object( $value ) && 'stdClass' == get_class( $value ) ) ? print_r( (array) $value, true ) : $value ) );
674
+ }
675
+
676
+ return implode( "\n", $messages ) . "\n";
677
+ }
678
+
679
+
680
+ /**
681
+ * Adds any PHP incompatibilities to the system status report.
682
+ *
683
+ * @since 4.5.0
684
+ *
685
+ * @param array $rows WooCommerce system status rows
686
+ * @return array
687
+ */
688
+ public function add_system_status_php_information( $rows ) {
689
+
690
+ foreach ( $this->get_dependency_handler()->get_incompatible_php_settings() as $setting => $values ) {
691
+
692
+ if ( isset( $values['type'] ) && 'min' === $values['type'] ) {
693
+
694
+ // if this setting already has a higher minimum from another plugin, skip it
695
+ if ( isset( $rows[ $setting ]['expected'] ) && $values['expected'] < $rows[ $setting ]['expected'] ) {
696
+ continue;
697
+ }
698
+
699
+ $note = __( '%1$s - A minimum of %2$s is required.', 'woocommerce-plugin-framework' );
700
+
701
+ } else {
702
+
703
+ // if this requirement is already listed, skip it
704
+ if ( isset( $rows[ $setting ] ) ) {
705
+ continue;
706
+ }
707
+
708
+ $note = __( 'Set as %1$s - %2$s is required.', 'woocommerce-plugin-framework' );
709
+ }
710
+
711
+ $note = sprintf( $note, $values['actual'], $values['expected'] );
712
+
713
+ $rows[ $setting ] = array(
714
+ 'name' => $setting,
715
+ 'note' => $note,
716
+ 'success' => false,
717
+ 'expected' => $values['expected'], // WC doesn't use this, but it's useful for us
718
+ );
719
+ }
720
+
721
+ return $rows;
722
+ }
723
+
724
+
725
+ /**
726
+ * Saves errors or messages to WooCommerce Log (woocommerce/logs/plugin-id-xxx.txt)
727
+ *
728
+ * @since 2.0.0
729
+ * @param string $message error or message to save to log
730
+ * @param string $log_id optional log id to segment the files by, defaults to plugin id
731
+ */
732
+ public function log( $message, $log_id = null ) {
733
+
734
+ if ( is_null( $log_id ) ) {
735
+ $log_id = $this->get_id();
736
+ }
737
+
738
+ if ( ! is_object( $this->logger ) ) {
739
+ $this->logger = new \WC_Logger();
740
+ }
741
+
742
+ $this->logger->add( $log_id, $message );
743
+ }
744
+
745
+
746
+ /**
747
+ * Require and instantiate a class
748
+ *
749
+ * @since 4.2.0
750
+ * @param string $local_path path to class file in plugin, e.g. '/includes/class-wc-foo.php'
751
+ * @param string $class_name class to instantiate
752
+ * @return object instantiated class instance
753
+ */
754
+ public function load_class( $local_path, $class_name ) {
755
+
756
+ require_once( $this->get_plugin_path() . $local_path );
757
+
758
+ return new $class_name;
759
+ }
760
+
761
+
762
+ /**
763
+ * Determines if TLS v1.2 is required for API requests.
764
+ *
765
+ * Subclasses should override this to return true if TLS v1.2 is required.
766
+ *
767
+ * @since 5.5.2
768
+ *
769
+ * @return bool
770
+ */
771
+ public function require_tls_1_2() {
772
+
773
+ return false;
774
+ }
775
+
776
+
777
+ /**
778
+ * Determines if TLS 1.2 is available.
779
+ *
780
+ * @since 5.5.2
781
+ *
782
+ * @return bool
783
+ */
784
+ public function is_tls_1_2_available() {
785
+
786
+ // assume availability to avoid notices for unknown SSL types
787
+ $is_available = true;
788
+
789
+ // check the cURL version if installed
790
+ if ( is_callable( 'curl_version' ) ) {
791
+
792
+ $versions = curl_version();
793
+
794
+ // cURL 7.34.0 is considered the minimum version that supports TLS 1.2
795
+ if ( version_compare( $versions['version'], '7.34.0', '<' ) ) {
796
+ $is_available = false;
797
+ }
798
+ }
799
+
800
+ return $is_available;
801
+ }
802
+
803
+
804
+ /** Getter methods ******************************************************/
805
+
806
+
807
+ /**
808
+ * Gets the main plugin file.
809
+ *
810
+ * @since 5.0.0
811
+ *
812
+ * @return string
813
+ */
814
+ public function get_plugin_file() {
815
+
816
+ $slug = dirname( plugin_basename( $this->get_file() ) );
817
+
818
+ return trailingslashit( $slug ) . $slug . '.php';
819
+ }
820
+
821
+
822
+ /**
823
+ * The implementation for this abstract method should simply be:
824
+ *
825
+ * return __FILE__;
826
+ *
827
+ * @since 2.0.0
828
+ * @return string the full path and filename of the plugin file
829
+ */
830
+ abstract protected function get_file();
831
+
832
+
833
+ /**
834
+ * Returns the plugin id
835
+ *
836
+ * @since 2.0.0
837
+ * @return string plugin id
838
+ */
839
+ public function get_id() {
840
+ return $this->id;
841
+ }
842
+
843
+
844
+ /**
845
+ * Returns the plugin id with dashes in place of underscores, and
846
+ * appropriate for use in frontend element names, classes and ids
847
+ *
848
+ * @since 2.0.0
849
+ * @return string plugin id with dashes in place of underscores
850
+ */
851
+ public function get_id_dasherized() {
852
+ return str_replace( '_', '-', $this->get_id() );
853
+ }
854
+
855
+
856
+ /**
857
+ * Returns the plugin full name including "WooCommerce", ie
858
+ * "WooCommerce X". This method is defined abstract for localization purposes
859
+ *
860
+ * @since 2.0.0
861
+ * @return string plugin name
862
+ */
863
+ abstract public function get_plugin_name();
864
+
865
+
866
+ /** Handler methods *******************************************************/
867
+
868
+
869
+ /**
870
+ * Gets the dependency handler.
871
+ *
872
+ * @since 5.2.0.1
873
+ *
874
+ * @return SV_WC_Plugin_Dependencies
875
+ */
876
+ public function get_dependency_handler() {
877
+
878
+ return $this->dependency_handler;
879
+ }
880
+
881
+
882
+ /**
883
+ * Gets the lifecycle handler instance.
884
+ *
885
+ * @since 5.1.0
886
+ *
887
+ * @return Plugin\Lifecycle
888
+ */
889
+ public function get_lifecycle_handler() {
890
+
891
+ return $this->lifecycle_handler;
892
+ }
893
+
894
+
895
+ /**
896
+ * Gets the Setup Wizard handler instance.
897
+ *
898
+ * @since 5.3.0
899
+ *
900
+ * @return null|Admin\Setup_Wizard
901
+ */
902
+ public function get_setup_wizard_handler() {
903
+
904
+ return $this->setup_wizard_handler;
905
+ }
906
+
907
+
908
+ /**
909
+ * Gets the admin message handler.
910
+ *
911
+ * @since 2.0.0
912
+ *
913
+ * @return SV_WP_Admin_Message_Handler
914
+ */
915
+ public function get_message_handler() {
916
+
917
+ return $this->message_handler;
918
+ }
919
+
920
+
921
+ /**
922
+ * Gets the admin notice handler instance.
923
+ *
924
+ * @since 3.0.0
925
+ *
926
+ * @return SV_WC_Admin_Notice_Handler
927
+ */
928
+ public function get_admin_notice_handler() {
929
+
930
+ return $this->admin_notice_handler;
931
+ }
932
+
933
+
934
+ /**
935
+ * Returns the plugin version name. Defaults to wc_{plugin id}_version
936
+ *
937
+ * @since 2.0.0
938
+ * @return string the plugin version name
939
+ */
940
+ public function get_plugin_version_name() {
941
+
942
+ return 'wc_' . $this->get_id() . '_version';
943
+ }
944
+
945
+
946
+ /**
947
+ * Returns the current version of the plugin
948
+ *
949
+ * @since 2.0.0
950
+ * @return string plugin version
951
+ */
952
+ public function get_version() {
953
+ return $this->version;
954
+ }
955
+
956
+
957
+ /**
958
+ * Returns the "Configure" plugin action link to go directly to the plugin
959
+ * settings page (if any)
960
+ *
961
+ * @since 2.0.0
962
+ * @see SV_WC_Plugin::get_settings_url()
963
+ * @param string $plugin_id optional plugin identifier. Note that this can be a
964
+ * sub-identifier for plugins with multiple parallel settings pages
965
+ * (ie a gateway that supports both credit cards and echecks)
966
+ * @return string plugin configure link
967
+ */
968
+ public function get_settings_link( $plugin_id = null ) {
969
+
970
+ $settings_url = $this->get_settings_url( $plugin_id );
971
+
972
+ if ( $settings_url ) {
973
+ return sprintf( '<a href="%s">%s</a>', $settings_url, esc_html__( 'Configure', 'woocommerce-plugin-framework' ) );
974
+ }
975
+
976
+ // no settings
977
+ return '';
978
+ }
979
+
980
+
981
+ /**
982
+ * Gets the plugin configuration URL
983
+ *
984
+ * @since 2.0.0
985
+ * @see SV_WC_Plugin::get_settings_link()
986
+ * @param string $plugin_id optional plugin identifier. Note that this can be a
987
+ * sub-identifier for plugins with multiple parallel settings pages
988
+ * (ie a gateway that supports both credit cards and echecks)
989
+ * @return string plugin settings URL
990
+ */
991
+ public function get_settings_url( $plugin_id = null ) {
992
+
993
+ // stub method
994
+ return '';
995
+ }
996
+
997
+
998
+ /**
999
+ * Returns true if the current page is the admin general configuration page
1000
+ *
1001
+ * @since 3.0.0
1002
+ * @return boolean true if the current page is the admin general configuration page
1003
+ */
1004
+ public function is_general_configuration_page() {
1005
+
1006
+ return isset( $_GET['page'] ) && 'wc-settings' === $_GET['page'] && ( ! isset( $_GET['tab'] ) || 'general' === $_GET['tab'] );
1007
+ }
1008
+
1009
+
1010
+ /**
1011
+ * Returns the admin configuration url for the admin general configuration page
1012
+ *
1013
+ * @since 3.0.0
1014
+ * @return string admin configuration url for the admin general configuration page
1015
+ */
1016
+ public function get_general_configuration_url() {
1017
+
1018
+ return admin_url( 'admin.php?page=wc-settings&tab=general' );
1019
+ }
1020
+
1021
+
1022
+ /**
1023
+ * Gets the plugin documentation url, used for the 'Docs' plugin action
1024
+ *
1025
+ * @since 2.0.0
1026
+ * @return string documentation URL
1027
+ */
1028
+ public function get_documentation_url() {
1029
+
1030
+ return null;
1031
+ }
1032
+
1033
+
1034
+ /**
1035
+ * Gets the support URL, used for the 'Support' plugin action link
1036
+ *
1037
+ * @since 4.0.0
1038
+ * @return string support url
1039
+ */
1040
+ public function get_support_url() {
1041
+
1042
+ return null;
1043
+ }
1044
+
1045
+
1046
+ /**
1047
+ * Gets the plugin sales page URL.
1048
+ *
1049
+ * @since 5.1.0
1050
+ *
1051
+ * @return string
1052
+ */
1053
+ public function get_sales_page_url() {
1054
+
1055
+ return '';
1056
+ }
1057
+
1058
+
1059
+ /**
1060
+ * Gets the plugin reviews page URL.
1061
+ *
1062
+ * Used for the 'Reviews' plugin action and review prompts.
1063
+ *
1064
+ * @since 5.1.0
1065
+ *
1066
+ * @return string
1067
+ */
1068
+ public function get_reviews_url() {
1069
+
1070
+ return $this->get_sales_page_url() ? $this->get_sales_page_url() . '#comments' : '';
1071
+ }
1072
+
1073
+
1074
+ /**
1075
+ * Gets the plugin's path without a trailing slash.
1076
+ *
1077
+ * e.g. /path/to/wp-content/plugins/plugin-directory
1078
+ *
1079
+ * @since 2.0.0
1080
+ *
1081
+ * @return string
1082
+ */
1083
+ public function get_plugin_path() {
1084
+
1085
+ if ( null === $this->plugin_path ) {
1086
+ $this->plugin_path = untrailingslashit( plugin_dir_path( $this->get_file() ) );
1087
+ }
1088
+
1089
+ return $this->plugin_path;
1090
+ }
1091
+
1092
+
1093
+ /**
1094
+ * Gets the plugin's URL without a trailing slash.
1095
+ *
1096
+ * E.g. http://skyverge.com/wp-content/plugins/plugin-directory
1097
+ *
1098
+ * @since 2.0.0
1099
+ *
1100
+ * @return string
1101
+ */
1102
+ public function get_plugin_url() {
1103
+
1104
+ if ( null === $this->plugin_url ) {
1105
+ $this->plugin_url = untrailingslashit( plugins_url( '/', $this->get_file() ) );
1106
+ }
1107
+
1108
+ return $this->plugin_url;
1109
+ }
1110
+
1111
+
1112
+ /**
1113
+ * Gets the woocommerce uploads path, without trailing slash.
1114
+ *
1115
+ * Oddly WooCommerce core does not provide a way to get this.
1116
+ *
1117
+ * @since 2.0.0
1118
+ *
1119
+ * @return string
1120
+ */
1121
+ public static function get_woocommerce_uploads_path() {
1122
+
1123
+ $upload_dir = wp_upload_dir();
1124
+
1125
+ return $upload_dir['basedir'] . '/woocommerce_uploads';
1126
+ }
1127
+
1128
+
1129
+ /**
1130
+ * Returns the loaded framework __FILE__
1131
+ *
1132
+ * @since 4.0.0
1133
+ * @return string
1134
+ */
1135
+ public function get_framework_file() {
1136
+
1137
+ return __FILE__;
1138
+ }
1139
+
1140
+
1141
+ /**
1142
+ * Gets the loaded framework path, without trailing slash.
1143
+ *
1144
+ * This matches the path to the highest version of the framework currently loaded.
1145
+ *
1146
+ * @since 4.0.0
1147
+ * @return string
1148
+ */
1149
+ public function get_framework_path() {
1150
+
1151
+ return untrailingslashit( plugin_dir_path( $this->get_framework_file() ) );
1152
+ }
1153
+
1154
+
1155
+ /**
1156
+ * Gets the absolute path to the loaded framework image directory, without a trailing slash.
1157
+ *
1158
+ * @since 4.0.0
1159
+ *
1160
+ * @return string
1161
+ */
1162
+ public function get_framework_assets_path() {
1163
+
1164
+ return $this->get_framework_path() . '/assets';
1165
+ }
1166
+
1167
+
1168
+ /**
1169
+ * Gets the loaded framework assets URL without a trailing slash.
1170
+ *
1171
+ * @since 4.0.0
1172
+ *
1173
+ * @return string
1174
+ */
1175
+ public function get_framework_assets_url() {
1176
+
1177
+ return untrailingslashit( plugins_url( '/assets', $this->get_framework_file() ) );
1178
+ }
1179
+
1180
+
1181
+ /**
1182
+ * Gets the plugin default template path, without a trailing slash.
1183
+ *
1184
+ * @since 5.5.0
1185
+ *
1186
+ * @return string
1187
+ */
1188
+ public function get_template_path() {
1189
+
1190
+ if ( null === $this->template_path ) {
1191
+ $this->template_path = $this->get_plugin_path() . '/templates';
1192
+ }
1193
+
1194
+ return $this->template_path;
1195
+ }
1196
+
1197
+
1198
+ /**
1199
+ * Loads and outputs a template file HTML.
1200
+ *
1201
+ * @see \wc_get_template() except we define automatically the default path
1202
+ *
1203
+ * @since 5.5.0
1204
+ *
1205
+ * @param string $template template name/part
1206
+ * @param array $args associative array of optional template arguments
1207
+ * @param string $path optional template path, can be empty, as themes can override this
1208
+ * @param string $default_path optional default template path, will normally use the plugin's own template path unless overridden
1209
+ */
1210
+ public function load_template( $template, array $args = [], $path = '', $default_path = '' ) {
1211
+
1212
+ if ( '' === $default_path || ! is_string( $default_path ) ) {
1213
+ $default_path = trailingslashit( $this->get_template_path() );
1214
+ }
1215
+
1216
+ wc_get_template( $template, $args, $path, $default_path );
1217
+ }
1218
+
1219
+
1220
+ /**
1221
+ * Determines whether a plugin is active.
1222
+ *
1223
+ * @since 2.0.0
1224
+ *
1225
+ * @param string $plugin_name plugin name, as the plugin-filename.php
1226
+ * @return boolean true if the named plugin is installed and active
1227
+ */
1228
+ public function is_plugin_active( $plugin_name ) {
1229
+
1230
+ $is_active = false;
1231
+
1232
+ if ( is_string( $plugin_name ) ) {
1233
+
1234
+ if ( ! array_key_exists( $plugin_name, $this->active_plugins ) ) {
1235
+
1236
+ $active_plugins = (array) get_option( 'active_plugins', array() );
1237
+
1238
+ if ( is_multisite() ) {
1239
+ $active_plugins = array_merge( $active_plugins, array_keys( get_site_option( 'active_sitewide_plugins', array() ) ) );
1240
+ }
1241
+
1242
+ $plugin_filenames = array();
1243
+
1244
+ foreach ( $active_plugins as $plugin ) {
1245
+
1246
+ if ( SV_WC_Helper::str_exists( $plugin, '/' ) ) {
1247
+
1248
+ // normal plugin name (plugin-dir/plugin-filename.php)
1249
+ list( , $filename ) = explode( '/', $plugin );
1250
+
1251
+ } else {
1252
+
1253
+ // no directory, just plugin file
1254
+ $filename = $plugin;
1255
+ }
1256
+
1257
+ $plugin_filenames[] = $filename;
1258
+ }
1259
+
1260
+ $this->active_plugins[ $plugin_name ] = in_array( $plugin_name, $plugin_filenames, true );
1261
+ }
1262
+
1263
+ $is_active = (bool) $this->active_plugins[ $plugin_name ];
1264
+ }
1265
+
1266
+ return $is_active;
1267
+ }
1268
+
1269
+
1270
+ /** Deprecated methods ****************************************************/
1271
+
1272
+
1273
+ /**
1274
+ * Handles version checking.
1275
+ *
1276
+ * @since 2.0.0
1277
+ * @deprecated 5.2.0
1278
+ */
1279
+ public function do_install() {
1280
+
1281
+ wc_deprecated_function( __METHOD__, '5.2.0', get_class( $this->get_lifecycle_handler() ) . '::init()' );
1282
+
1283
+ $this->get_lifecycle_handler()->init();
1284
+ }
1285
+
1286
+
1287
+ /**
1288
+ * Helper method to install default settings for a plugin.
1289
+ *
1290
+ * @since 4.2.0
1291
+ * @deprecated 5.2.0
1292
+ *
1293
+ * @param array $settings array of settings in format required by WC_Admin_Settings
1294
+ */
1295
+ public function install_default_settings( array $settings ) {
1296
+
1297
+ wc_deprecated_function( __METHOD__, '5.2.0', get_class( $this->get_lifecycle_handler() ) . '::install_default_settings()' );
1298
+
1299
+ $this->get_lifecycle_handler()->install_default_settings( $settings );
1300
+ }
1301
+
1302
+
1303
+ /**
1304
+ * Plugin activated method. Perform any activation tasks here.
1305
+ * Note that this _does not_ run during upgrades.
1306
+ *
1307
+ * @since 4.2.0
1308
+ * @deprecated 5.2.0
1309
+ */
1310
+ public function activate() {
1311
+
1312
+ wc_deprecated_function( __METHOD__, '5.2.0' );
1313
+ }
1314
+
1315
+
1316
+ /**
1317
+ * Plugin deactivation method. Perform any deactivation tasks here.
1318
+ *
1319
+ * @since 4.2.0
1320
+ * @deprecated 5.2.0
1321
+ */
1322
+ public function deactivate() {
1323
+
1324
+ wc_deprecated_function( __METHOD__, '5.2.0' );
1325
+ }
1326
+
1327
+
1328
+ /**
1329
+ * Gets the string name of any required PHP extensions that are not loaded.
1330
+ *
1331
+ * @since 4.5.0
1332
+ * @deprecated 5.2.0
1333
+ *
1334
+ * @return array
1335
+ */
1336
+ public function get_missing_extension_dependencies() {
1337
+
1338
+ wc_deprecated_function( __METHOD__, '5.2.0', get_class( $this->get_dependency_handler() ) . '::get_missing_php_extensions()' );
1339
+
1340
+ return $this->get_dependency_handler()->get_missing_php_extensions();
1341
+ }
1342
+
1343
+
1344
+ /**
1345
+ * Gets the string name of any required PHP functions that are not loaded.
1346
+ *
1347
+ * @since 2.1.0
1348
+ * @deprecated 5.2.0
1349
+ *
1350
+ * @return array
1351
+ */
1352
+ public function get_missing_function_dependencies() {
1353
+
1354
+ wc_deprecated_function( __METHOD__, '5.2.0', get_class( $this->get_dependency_handler() ) . '::get_missing_php_functions()' );
1355
+
1356
+ return $this->get_dependency_handler()->get_missing_php_functions();
1357
+ }
1358
+
1359
+
1360
+ /**
1361
+ * Gets the string name of any required PHP extensions that are not loaded.
1362
+ *
1363
+ * @since 4.5.0
1364
+ * @deprecated 5.2.0
1365
+ *
1366
+ * @return array
1367
+ */
1368
+ public function get_incompatible_php_settings() {
1369
+
1370
+ wc_deprecated_function( __METHOD__, '5.2.0', get_class( $this->get_dependency_handler() ) . '::get_incompatible_php_settings()' );
1371
+
1372
+ return $this->get_dependency_handler()->get_incompatible_php_settings();
1373
+ }
1374
+
1375
+
1376
+ /**
1377
+ * Gets the PHP dependencies.
1378
+ *
1379
+ * @since 2.0.0
1380
+ * @deprecated 5.2.0
1381
+ *
1382
+ * @return array
1383
+ */
1384
+ protected function get_dependencies() {
1385
+
1386
+ wc_deprecated_function( __METHOD__, '5.2.0' );
1387
+
1388
+ return array();
1389
+ }
1390
+
1391
+
1392
+ /**
1393
+ * Gets the PHP extension dependencies.
1394
+ *
1395
+ * @since 4.5.0
1396
+ * @deprecated 5.2.0
1397
+ *
1398
+ * @return array
1399
+ */
1400
+ protected function get_extension_dependencies() {
1401
+
1402
+ wc_deprecated_function( __METHOD__, '5.2.0', get_class( $this->get_dependency_handler() ) . '::get_php_extensions()' );
1403
+
1404
+ return $this->get_dependency_handler()->get_php_extensions();
1405
+ }
1406
+
1407
+
1408
+ /**
1409
+ * Gets the PHP function dependencies.
1410
+ *
1411
+ * @since 2.1.0
1412
+ * @deprecated 5.2.0
1413
+ *
1414
+ * @return array
1415
+ */
1416
+ protected function get_function_dependencies() {
1417
+
1418
+ wc_deprecated_function( __METHOD__, '5.2.0', get_class( $this->get_dependency_handler() ) . '::get_php_functions()' );
1419
+
1420
+ return $this->get_dependency_handler()->get_php_functions();
1421
+ }
1422
+
1423
+
1424
+ /**
1425
+ * Gets the PHP settings dependencies.
1426
+ *
1427
+ * @since 4.5.0
1428
+ * @deprecated 5.2.0
1429
+ *
1430
+ * @return array
1431
+ */
1432
+ protected function get_php_settings_dependencies() {
1433
+
1434
+ wc_deprecated_function( __METHOD__, '5.2.0', get_class( $this->get_dependency_handler() ) . '::get_php_settings()' );
1435
+
1436
+ return $this->get_dependency_handler()->get_php_settings();
1437
+ }
1438
+
1439
+
1440
+ /**
1441
+ * Sets the plugin dependencies.
1442
+ *
1443
+ * @since 4.5.0
1444
+ * @deprecated 5.2.0
1445
+ *
1446
+ * @param array $dependencies the environment dependencies
1447
+ */
1448
+ protected function set_dependencies( $dependencies = [] ) {
1449
+
1450
+ wc_deprecated_function( __METHOD__, '5.2.0' );
1451
+ }
1452
+
1453
+
1454
+ }
1455
+
1456
+
1457
+ endif;
vendor/skyverge/wc-plugin-framework/woocommerce/class-sv-wp-admin-message-handler.php ADDED
@@ -0,0 +1,438 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * WooCommerce Admin Message Handler
4
+ *
5
+ * This source file is subject to the GNU General Public License v3.0
6
+ * that is bundled with this package in the file license.txt.
7
+ * It is also available through the world-wide-web at this URL:
8
+ * http://www.gnu.org/licenses/gpl-3.0.html
9
+ * If you did not receive a copy of the license and are unable to
10
+ * obtain it through the world-wide-web, please send an email
11
+ * to license@skyverge.com so we can send you a copy immediately.
12
+ *
13
+ * DISCLAIMER
14
+ *
15
+ * Do not edit or add to this file if you wish to upgrade the plugin to newer
16
+ * versions in the future. If you wish to customize the plugin for your
17
+ * needs please refer to http://www.skyverge.com
18
+ *
19
+ * @package SkyVerge/WordPress/WP-Admin-Message-Handler
20
+ * @author SkyVerge
21
+ * @copyright Copyright (c) 2013-2020, SkyVerge, Inc.
22
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GNU General Public License v3.0
23
+ */
24
+
25
+ namespace SkyVerge\WooCommerce\PluginFramework\v5_5_4;
26
+
27
+ defined( 'ABSPATH' ) or exit;
28
+
29
+ if ( ! class_exists( '\\SkyVerge\\WooCommerce\\PluginFramework\\v5_5_4\\SV_WP_Admin_Message_Handler' ) ) :
30
+
31
+
32
+ /**
33
+ * # WordPress Admin Message Handler Class
34
+ *
35
+ * This class provides a reusable wordpress admin messaging facility for setting
36
+ * and displaying messages and error messages across admin page requests without
37
+ * resorting to passing the messages as query vars.
38
+ *
39
+ * ## Usage
40
+ *
41
+ * To use simple instantiate the class then set one or more messages:
42
+ *
43
+ * `
44
+ * $admin_message_handler = new WP_Admin_Message_Handler( __FILE__ );
45
+ * $admin_message_handler->add_message( 'Hello World!' );
46
+ * `
47
+ *
48
+ * Then show the messages wherever you need, either with the built-in method
49
+ * or by writing your own:
50
+ *
51
+ * `$admin_message_handler->show_messages();`
52
+ *
53
+ * @version 1.0.1
54
+ */
55
+ class SV_WP_Admin_Message_Handler {
56
+
57
+
58
+ /** transient message prefix */
59
+ const MESSAGE_TRANSIENT_PREFIX = '_wp_admin_message_';
60
+
61
+ /** the message id GET name */
62
+ const MESSAGE_ID_GET_NAME = 'wpamhid';
63
+
64
+
65
+ /** @var string unique message identifier, defaults to __FILE__ unless otherwise set */
66
+ private $message_id;
67
+
68
+ /** @var array array of messages */
69
+ private $messages = array();
70
+
71
+ /** @var array array of error messages */
72
+ private $errors = array();
73
+
74
+ /** @var array array of warning messages */
75
+ private $warnings = array();
76
+
77
+ /** @var array array of info messages */
78
+ private $infos = array();
79
+
80
+
81
+ /**
82
+ * Construct and initialize the admin message handler class
83
+ *
84
+ * @since 1.0.0
85
+ * @param string $message_id optional message id. Best practice is to set
86
+ * this to a unique identifier based on the client plugin, such as __FILE__
87
+ */
88
+ public function __construct( $message_id = null ) {
89
+
90
+ $this->message_id = $message_id;
91
+
92
+ // load any available messages
93
+ $this->load_messages();
94
+
95
+ add_filter( 'wp_redirect', array( $this, 'redirect' ), 1, 2 );
96
+ }
97
+
98
+
99
+ /**
100
+ * Persist messages
101
+ *
102
+ * @since 1.0.0
103
+ * @return boolean true if any messages were set, false otherwise
104
+ */
105
+ public function set_messages() {
106
+
107
+ // any messages to persist?
108
+ if ( $this->message_count() > 0 || $this->info_count() > 0 || $this->warning_count() > 0 || $this->error_count() > 0 ) {
109
+
110
+ set_transient(
111
+ self::MESSAGE_TRANSIENT_PREFIX . $this->get_message_id(),
112
+ array(
113
+ 'errors' => $this->errors,
114
+ 'warnings' => $this->warnings,
115
+ 'infos' => $this->infos,
116
+ 'messages' => $this->messages,
117
+ ),
118
+ 60 * 60
119
+ );
120
+
121
+ return true;
122
+ }
123
+
124
+ return false;
125
+ }
126
+
127
+
128
+ /**
129
+ * Loads messages
130
+ *
131
+ * @since 1.0.0
132
+ */
133
+ public function load_messages() {
134
+
135
+ if ( isset( $_GET[ self::MESSAGE_ID_GET_NAME ] ) && $this->get_message_id() == $_GET[ self::MESSAGE_ID_GET_NAME ] ) {
136
+
137
+ $memo = get_transient( self::MESSAGE_TRANSIENT_PREFIX . $_GET[ self::MESSAGE_ID_GET_NAME ] );
138
+
139
+ if ( isset( $memo['errors'] ) ) $this->errors = $memo['errors'];
140
+ if ( isset( $memo['warnings'] ) ) $this->warnings = $memo['warnings'];
141
+ if ( isset( $memo['infos'] ) ) $this->infos = $memo['infos'];
142
+ if ( isset( $memo['messages'] ) ) $this->messages = $memo['messages'];
143
+
144
+ $this->clear_messages( $_GET[ self::MESSAGE_ID_GET_NAME ] );
145
+ }
146
+ }
147
+
148
+
149
+ /**
150
+ * Clear messages and errors
151
+ *
152
+ * @since 1.0.0
153
+ * @param string $id the messages identifier
154
+ */
155
+ public function clear_messages( $id ) {
156
+ delete_transient( self::MESSAGE_TRANSIENT_PREFIX . $id );
157
+ }
158
+
159
+
160
+ /**
161
+ * Add an error message.
162
+ *
163
+ * @since 1.0.0
164
+ * @param string $error error message
165
+ */
166
+ public function add_error( $error ) {
167
+ $this->errors[] = $error;
168
+ }
169
+
170
+
171
+ /**
172
+ * Adds a warning message.
173
+ *
174
+ * @since 5.1.0
175
+ *
176
+ * @param string $message warning message to add
177
+ */
178
+ public function add_warning( $message ) {
179
+ $this->warnings[] = $message;
180
+ }
181
+
182
+
183
+ /**
184
+ * Adds a info message.
185
+ *
186
+ * @since 5.1.0
187
+ *
188
+ * @param string $message info message to add
189
+ */
190
+ public function add_info( $message ) {
191
+ $this->infos[] = $message;
192
+ }
193
+
194
+
195
+ /**
196
+ * Add a message.
197
+ *
198
+ * @since 1.0.0
199
+ * @param string $message the message to add
200
+ */
201
+ public function add_message( $message ) {
202
+ $this->messages[] = $message;
203
+ }
204
+
205
+
206
+ /**
207
+ * Get error count.
208
+ *
209
+ * @since 1.0.0
210
+ * @return int error message count
211
+ */
212
+ public function error_count() {
213
+ return sizeof( $this->errors );
214
+ }
215
+
216
+
217
+ /**
218
+ * Gets the warning message count.
219
+ *
220
+ * @since 5.1.0
221
+ *
222
+ * @return int warning message count
223
+ */
224
+ public function warning_count() {
225
+ return sizeof( $this->warnings );
226
+ }
227
+
228
+
229
+ /**
230
+ * Gets the info message count.
231
+ *
232
+ * @since 5.1.0
233
+ *
234
+ * @return int info message count
235
+ */
236
+ public function info_count() {
237
+ return sizeof( $this->infos );
238
+ }
239
+
240
+
241
+ /**
242
+ * Get message count.
243
+ *
244
+ * @since 1.0.0
245
+ * @return int message count
246
+ */
247
+ public function message_count() {
248
+ return sizeof( $this->messages );
249
+ }
250
+
251
+
252
+ /**
253
+ * Get error messages
254
+ *
255
+ * @since 1.0.0
256
+ * @return array of error message strings
257
+ */
258
+ public function get_errors() {
259
+ return $this->errors;
260
+ }
261
+
262
+
263
+ /**
264
+ * Get an error message
265
+ *
266
+ * @since 1.0.0
267
+ * @param int $index the error index
268
+ * @return string the error message
269
+ */
270
+ public function get_error( $index ) {
271
+ return isset( $this->errors[ $index ] ) ? $this->errors[ $index ] : '';
272
+ }
273
+
274
+
275
+ /**
276
+ * Gets all warning messages.
277
+ *
278
+ * @since 5.1.0
279
+ *
280
+ * @return array
281
+ */
282
+ public function get_warnings() {
283
+ return $this->warnings;
284
+ }
285
+
286
+
287
+ /**
288
+ * Gets a specific warning message.
289
+ *
290
+ * @since 5.1.0
291
+ *
292
+ * @param int $index warning message index
293
+ * @return string
294
+ */
295
+ public function get_warning( $index ) {
296
+ return isset( $this->warnings[ $index ] ) ? $this->warnings[ $index ] : '';
297
+ }
298
+
299
+
300
+ /**
301
+ * Gets all info messages.
302
+ *
303
+ * @since 5.1.0
304
+ *
305
+ * @return array
306
+ */
307
+ public function get_infos() {
308
+ return $this->infos;
309
+ }
310
+
311
+
312
+ /**
313
+ * Gets a specific info message.
314
+ *
315
+ * @since 5.0.0
316
+ *
317
+ * @param int $index info message index
318
+ * @return string
319
+ */
320
+ public function get_info( $index ) {
321
+ return isset( $this->infos[ $index ] ) ? $this->infos[ $index ] : '';
322
+ }
323
+
324
+
325
+ /**
326
+ * Get messages
327
+ *
328
+ * @since 1.0.0
329
+ * @return array of message strings
330
+ */
331
+ public function get_messages() {
332
+ return $this->messages;
333
+ }
334
+
335
+
336
+ /**
337
+ * Get a message
338
+ *
339
+ * @since 1.0.0
340
+ * @param int $index the message index
341
+ * @return string the message
342
+ */
343
+ public function get_message( $index ) {
344
+ return isset( $this->messages[ $index ] ) ? $this->messages[ $index ] : '';
345
+ }
346
+
347
+
348
+ /**
349
+ * Render the errors and messages.
350
+ *
351
+ * @since 1.0.0
352
+ * @param array $params {
353
+ * Optional parameters.
354
+ *
355
+ * @type array $capabilities Any user capabilities to check if the user is allowed to view the messages,
356
+ * default: `manage_woocommerce`
357
+ * }
358
+ */
359
+ public function show_messages( $params = array() ) {
360
+
361
+ $params = wp_parse_args( $params, array(
362
+ 'capabilities' => array(
363
+ 'manage_woocommerce',
364
+ ),
365
+ ) );
366
+
367
+ $check_user_capabilities = array();
368
+
369
+ // check if user has at least one capability that allows to see messages
370
+ foreach ( $params['capabilities'] as $capability ) {
371
+ $check_user_capabilities[] = current_user_can( $capability );
372
+ }
373
+
374
+ // bail out if user has no minimum capabilities to see messages
375
+ if ( ! in_array( true, $check_user_capabilities, true ) ) {
376
+ return;
377
+ }
378
+
379
+ $output = '';
380
+
381
+ if ( $this->error_count() > 0 ) {
382
+ $output .= '<div id="wp-admin-message-handler-error" class="notice-error notice"><ul><li><strong>' . implode( '</strong></li><li><strong>', $this->get_errors() ) . '</strong></li></ul></div>';
383
+ }
384
+
385
+ if ( $this->warning_count() > 0 ) {
386
+ $output .= '<div id="wp-admin-message-handler-warning" class="notice-warning notice"><ul><li><strong>' . implode( '</strong></li><li><strong>', $this->get_warnings() ) . '</strong></li></ul></div>';
387
+ }
388
+
389
+ if ( $this->info_count() > 0 ) {
390
+ $output .= '<div id="wp-admin-message-handler-warning" class="notice-info notice"><ul><li><strong>' . implode( '</strong></li><li><strong>', $this->get_infos() ) . '</strong></li></ul></div>';
391
+ }
392
+
393
+ if ( $this->message_count() > 0 ) {
394
+ $output .= '<div id="wp-admin-message-handler-message" class="notice-success notice"><ul><li><strong>' . implode( '</strong></li><li><strong>', $this->get_messages() ) . '</strong></li></ul></div>';
395
+ }
396
+
397
+ echo wp_kses_post( $output );
398
+ }
399
+
400
+
401
+ /**
402
+ * Redirection hook which persists messages into session data.
403
+ *
404
+ * @since 1.0.0
405
+ * @param string $location the URL to redirect to
406
+ * @param int $status the http status
407
+ * @return string the URL to redirect to
408
+ */
409
+ public function redirect( $location, $status ) {
410
+
411
+ // add the admin message id param to the
412
+ if ( $this->set_messages() ) {
413
+ $location = add_query_arg( self::MESSAGE_ID_GET_NAME, $this->get_message_id(), $location );
414
+ }
415
+
416
+ return $location;
417
+ }
418
+
419
+
420
+ /**
421
+ * Generate a unique id to identify the messages
422
+ *
423
+ * @since 1.0.0
424
+ * @return string unique identifier
425
+ */
426
+ protected function get_message_id() {
427
+
428
+ if ( ! isset( $this->message_id ) ) $this->message_id = __FILE__;
429
+
430
+ return wp_create_nonce( $this->message_id );
431
+
432
+ }
433
+
434
+
435
+ }
436
+
437
+
438
+ endif;
vendor/skyverge/wc-plugin-framework/woocommerce/compatibility/abstract-sv-wc-data-compatibility.php ADDED
@@ -0,0 +1,169 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * WooCommerce Plugin Framework
4
+ *
5
+ * This source file is subject to the GNU General Public License v3.0
6
+ * that is bundled with this package in the file license.txt.
7
+ * It is also available through the world-wide-web at this URL:
8
+ * http://www.gnu.org/licenses/gpl-3.0.html
9
+ * If you did not receive a copy of the license and are unable to
10
+ * obtain it through the world-wide-web, please send an email
11
+ * to license@skyverge.com so we can send you a copy immediately.
12
+ *
13
+ * DISCLAIMER
14
+ *
15
+ * Do not edit or add to this file if you wish to upgrade the plugin to newer
16
+ * versions in the future. If you wish to customize the plugin for your
17
+ * needs please refer to http://www.skyverge.com
18
+ *
19
+ * @package SkyVerge/WooCommerce/Compatibility
20
+ * @author SkyVerge
21
+ * @copyright Copyright (c) 2013-2020, SkyVerge, Inc.
22
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GNU General Public License v3.0
23
+ */
24
+
25
+ namespace SkyVerge\WooCommerce\PluginFramework\v5_5_4;
26
+
27
+ defined( 'ABSPATH' ) or exit;
28
+
29
+ if ( ! class_exists( '\\SkyVerge\\WooCommerce\\PluginFramework\\v5_5_4\\SV_WC_Data_Compatibility' ) ) :
30
+
31
+
32
+ /**
33
+ * WooCommerce data compatibility class.
34
+ *
35
+ * @since 4.6.0
36
+ * @deprecated 5.5.0
37
+ */
38
+ abstract class SV_WC_Data_Compatibility {
39
+
40
+
41
+ /** @deprecated 5.5.0 backwards compatibility property map */
42
+ protected static $compat_props = [];
43
+
44
+
45
+ /**
46
+ * Gets an object property.
47
+ *
48
+ * @see \WC_Data::get_prop()
49
+ *
50
+ * @since 4.6.0
51
+ * @deprecated 5.5.0
52
+ *
53
+ * @param \WC_Data $object the data object, likely \WC_Order or \WC_Product
54
+ * @param string $prop the property name
55
+ * @param string $context if 'view' then the value will be filtered
56
+ * @param array $compat_props compatibility properties unused since 5.5.0
57
+ * @return null|mixed
58
+ */
59
+ public static function get_prop( $object, $prop, $context = 'edit', $compat_props = [] ) {
60
+
61
+ wc_deprecated_function( __METHOD__, '5.5.0', 'WC_Data::get_prop()' );
62
+
63
+ return is_callable( [ $object, "get_{$prop}" ] ) ? $object->{"get_{$prop}"}( $context ) : null;
64
+ }
65
+
66
+
67
+ /**
68
+ * Sets an object's properties.
69
+ *
70
+ * Note that this does not save any data to the database.
71
+ *
72
+ * @since 4.6.0
73
+ * @deprecated 5.5.0
74
+ *
75
+ * @param \WC_Data $object the data object, likely \WC_Order or \WC_Product
76
+ * @param array $props the new properties as $key => $value
77
+ * @param array $compat_props compatibility properties, unused since 5.5.0
78
+ * @return bool|\WP_Error
79
+ */
80
+ public static function set_props( $object, $props, $compat_props = [] ) {
81
+
82
+ wc_deprecated_function( __METHOD__, '5.5.0', 'WC_Data::set_props()' );
83
+
84
+ return $object->set_props( $props );
85
+ }
86
+
87
+
88
+ /**
89
+ * Gets an object's stored meta value.
90
+ *
91
+ * @since 4.6.0
92
+ * @deprecated 5.5.0
93
+ *
94
+ * @param \WC_Data $object the data object, likely \WC_Order or \WC_Product
95
+ * @param string $key the meta key
96
+ * @param bool $single whether to get the meta as a single item. Defaults to `true`
97
+ * @param string $context if 'view' then the value will be filtered
98
+ * @return mixed
99
+ */
100
+ public static function get_meta( $object, $key = '', $single = true, $context = 'edit' ) {
101
+
102
+ wc_deprecated_function( __METHOD__, '5.5.0', 'WC_Data::get_meta()' );
103
+
104
+ return $object->get_meta( $key, $single, $context );
105
+ }
106
+
107
+
108
+ /**
109
+ * Stores an object meta value.
110
+ *
111
+ * @since 4.6.0
112
+ * @deprecated 5.5.0
113
+ *
114
+ * @param \WC_Data $object the data object, likely \WC_Order or \WC_Product
115
+ * @param string $key the meta key
116
+ * @param string $value the meta value
117
+ * @param bool $unique optional: whether the meta should be unique
118
+ */
119
+ public static function add_meta_data( $object, $key, $value, $unique = false ) {
120
+
121
+ wc_deprecated_function( __METHOD__, '5.5.0', 'WC_Data::add_meta_data()' );
122
+
123
+ $object->add_meta_data( $key, $value, $unique );
124
+ $object->save_meta_data();
125
+ }
126
+
127
+
128
+ /**
129
+ * Updates an object's stored meta value.
130
+ *
131
+ * @since 4.6.0
132
+ * @deprecated 5.5.0
133
+ *
134
+ * @param \WC_Data $object the data object, likely \WC_Order or \WC_Product
135
+ * @param string $key the meta key
136
+ * @param string $value the meta value
137
+ * @param int|string $meta_id optional: the specific meta ID to update
138
+ */
139
+ public static function update_meta_data( $object, $key, $value, $meta_id = '' ) {
140
+
141
+ wc_deprecated_function( __METHOD__, '5.5.0', 'WC_Data::update_meta_data()' );
142
+
143
+ $object->update_meta_data( $key, $value, $meta_id );
144
+ $object->save_meta_data();
145
+ }
146
+
147
+
148
+ /**
149
+ * Deletes an object's stored meta value.
150
+ *
151
+ * @since 4.6.0
152
+ * @deprecated 5.5.0
153
+ *
154
+ * @param \WC_Data $object the data object, likely \WC_Order or \WC_Product
155
+ * @param string $key the meta key
156
+ */
157
+ public static function delete_meta_data( $object, $key ) {
158
+
159
+ wc_deprecated_function( __METHOD__, '5.5.0', 'WC_Data::delete_meta_data()' );
160
+
161
+ $object->delete_meta_data( $key );
162
+ $object->save_meta_data();
163
+ }
164
+
165
+
166
+ }
167
+
168
+
169
+ endif;
vendor/skyverge/wc-plugin-framework/woocommerce/compatibility/class-sv-wc-datetime.php ADDED
@@ -0,0 +1,146 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * WooCommerce Plugin Framework
4
+ *
5
+ * This source file is subject to the GNU General Public License v3.0
6
+ * that is bundled with this package in the file license.txt.
7
+ * It is also available through the world-wide-web at this URL:
8
+ * http://www.gnu.org/licenses/gpl-3.0.html
9
+ * If you did not receive a copy of the license and are unable to
10
+ * obtain it through the world-wide-web, please send an email
11
+ * to license@skyverge.com so we can send you a copy immediately.
12
+ *
13
+ * DISCLAIMER
14
+ *
15
+ * Do not edit or add to this file if you wish to upgrade the plugin to newer
16
+ * versions in the future. If you wish to customize the plugin for your
17
+ * needs please refer to http://www.skyverge.com
18
+ *
19
+ * @package SkyVerge/WooCommerce/Compatibility
20
+ * @author SkyVerge
21
+ * @copyright Copyright (c) 2013-2020, SkyVerge, Inc.
22
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GNU General Public License v3.0
23
+ */
24
+
25
+ namespace SkyVerge\WooCommerce\PluginFramework\v5_5_4;
26
+
27
+ use DateTimeZone;
28
+
29
+ defined( 'ABSPATH' ) or exit;
30
+
31
+ if ( ! class_exists( '\\SkyVerge\\WooCommerce\\PluginFramework\\v5_5_4\\SV_WC_DateTime' ) ) :
32
+
33
+
34
+ /**
35
+ * Extends the DateTime object for backwards compatibility.
36
+ *
37
+ * @since 4.6.0
38
+ * @deprecated 5.5.0
39
+ */
40
+ class SV_WC_DateTime extends \DateTime {
41
+
42
+
43
+ /**
44
+ * SV_WC_DateTime constructor.
45
+ *
46
+ * @since 5.5.0
47
+ * @deprecated 5.5.0
48
+ *
49
+ * @param string $time
50
+ * @param \DateTimeZone|null $timezone
51
+ * @throws \Exception
52
+ */
53
+ public function __construct( $time = 'now', \DateTimeZone $timezone = null ) {
54
+
55
+ wc_deprecated_function( 'SV_WC_DateTime', '5.5.0', \DateTime::class );
56
+
57
+ parent::__construct( $time, $timezone );
58
+ }
59
+
60
+
61
+ /**
62
+ * Outputs an ISO 8601 date string in local timezone.
63
+ *
64
+ * @since 4.6.0
65
+ * @deprecated 5.5.0
66
+ *
67
+ * @return string
68
+ */
69
+ public function __toString() {
70
+
71
+ wc_deprecated_function( __METHOD__, '5.5.0', 'DateTime::format( DATE_ATOM )' );
72
+
73
+ return $this->format( DATE_ATOM );
74
+ }
75
+
76
+
77
+ /**
78
+ * Gets the UTC timestamp.
79
+ *
80
+ * @since 4.6.0
81
+ * @deprecated 5.5.0
82
+ *
83
+ * @return int
84
+ */
85
+ public function getTimestamp() {
86
+
87
+ wc_deprecated_function( __METHOD__, '5.5.0', 'DateTime::getTimestamp()' );
88
+
89
+ return parent::getTimestamp();
90
+ }
91
+
92
+
93
+ /**
94
+ * Gets the timestamp with the WordPress timezone offset added or subtracted.
95
+ *
96
+ * @since 4.6.0
97
+ * @deprecated 5.5.0
98
+ *
99
+ * @return int
100
+ */
101
+ public function getOffsetTimestamp() {
102
+
103
+ wc_deprecated_function( __METHOD__, '5.5.0', 'DateTime::getOffset()' );
104
+
105
+ return $this->getTimestamp() + $this->getOffset();
106
+ }
107
+
108
+
109
+ /**
110
+ * Gets a date based on the offset timestamp.
111
+ *
112
+ * @since 4.6.0
113
+ * @deprecated 5.5.0
114
+ *
115
+ * @param string $format date format
116
+ * @return string
117
+ */
118
+ public function date( $format ) {
119
+
120
+ wc_deprecated_function( __METHOD__, '5.5.0', 'gmdate()' );
121
+
122
+ return gmdate( $format, $this->getOffsetTimestamp() );
123
+ }
124
+
125
+
126
+ /**
127
+ * Gets a localised date based on offset timestamp.
128
+ *
129
+ * @since 4.6.0
130
+ * @deprecated 5.5.0
131
+ *
132
+ * @param string $format date format
133
+ * @return string
134
+ */
135
+ public function date_i18n( $format = 'Y-m-d' ) {
136
+
137
+ wc_deprecated_function( __METHOD__, '5.5.0', 'date_i18n()' );
138
+
139
+ return date_i18n( $format, $this->getOffsetTimestamp() );
140
+ }
141
+
142
+
143
+ }
144
+
145
+
146
+ endif;
vendor/skyverge/wc-plugin-framework/woocommerce/compatibility/class-sv-wc-order-compatibility.php ADDED
@@ -0,0 +1,528 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * WooCommerce Plugin Framework
4
+ *
5
+ * This source file is subject to the GNU General Public License v3.0
6
+ * that is bundled with this package in the file license.txt.
7
+ * It is also available through the world-wide-web at this URL:
8
+ * http://www.gnu.org/licenses/gpl-3.0.html
9
+ * If you did not receive a copy of the license and are unable to
10
+ * obtain it through the world-wide-web, please send an email
11
+ * to license@skyverge.com so we can send you a copy immediately.
12
+ *
13
+ * DISCLAIMER
14
+ *
15
+ * Do not edit or add to this file if you wish to upgrade the plugin to newer
16
+ * versions in the future. If you wish to customize the plugin for your
17
+ * needs please refer to http://www.skyverge.com
18
+ *
19
+ * @package SkyVerge/WooCommerce/Compatibility
20
+ * @author SkyVerge
21
+ * @copyright Copyright (c) 2013-2020, SkyVerge, Inc.
22
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GNU General Public License v3.0
23
+ */
24
+
25
+ namespace SkyVerge\WooCommerce\PluginFramework\v5_5_4;
26
+
27
+ defined( 'ABSPATH' ) or exit;
28
+
29
+ if ( ! class_exists( '\\SkyVerge\\WooCommerce\\PluginFramework\\v5_5_4\\SV_WC_Order_Compatibility' ) ) :
30
+
31
+
32
+ /**
33
+ * WooCommerce order compatibility class.
34
+ *
35
+ * @since 4.6.0
36
+ */
37
+ class SV_WC_Order_Compatibility extends SV_WC_Data_Compatibility {
38
+
39
+
40
+ /**
41
+ * Gets an order's created date.
42
+ *
43
+ * @since 4.6.0
44
+ * @deprecated 5.5.0
45
+ *
46
+ * @param \WC_Order $order order object
47
+ * @param string $context if 'view' then the value will be filtered
48
+ *
49
+ * @return \WC_DateTime|null
50
+ */
51
+ public static function get_date_created( \WC_Order $order, $context = 'edit' ) {
52
+
53
+ wc_deprecated_function( __METHOD__, '5.5.0', 'WC_Order::get_date_created()' );
54
+
55
+ return self::get_date_prop( $order, 'created', $context );
56
+ }
57
+
58
+
59
+ /**
60
+ * Gets an order's last modified date.
61
+ *
62
+ * @since 4.6.0
63
+ * @deprecated 5.5.0
64
+ *
65
+ * @param \WC_Order $order order object
66
+ * @param string $context if 'view' then the value will be filtered
67
+ *
68
+ * @return \WC_DateTime|null
69
+ */
70
+ public static function get_date_modified( \WC_Order $order, $context = 'edit' ) {
71
+
72
+ wc_deprecated_function( __METHOD__, '5.5.0', 'WC_Order::get_date_modified()' );
73
+
74
+ return self::get_date_prop( $order, 'modified', $context );
75
+ }
76
+
77
+
78
+ /**
79
+ * Gets an order's paid date.
80
+ *
81
+ * @since 4.6.0
82
+ * @deprecated 5.5.0
83
+ *
84
+ * @param \WC_Order $order order object
85
+ * @param string $context if 'view' then the value will be filtered
86
+ *
87
+ * @return \WC_DateTime|null
88
+ */
89
+ public static function get_date_paid( \WC_Order $order, $context = 'edit' ) {
90
+
91
+ wc_deprecated_function( __METHOD__, '5.5.0', 'WC_Order::get_date_paid()' );
92
+
93
+ return self::get_date_prop( $order, 'paid', $context );
94
+ }
95
+
96
+
97
+ /**
98
+ * Gets an order's completed date.
99
+ *
100
+ * @since 4.6.0
101
+ * @deprecated 5.5.0
102
+ *
103
+ * @param \WC_Order $order order object
104
+ * @param string $context if 'view' then the value will be filtered
105
+ *
106
+ * @return \WC_DateTime|null
107
+ */
108
+ public static function get_date_completed( \WC_Order $order, $context = 'edit' ) {
109
+
110
+ wc_deprecated_function( __METHOD__, '5.5.0', 'WC_Order::get_date_completed()' );
111
+
112
+ return self::get_date_prop( $order, 'completed', $context );
113
+ }
114
+
115
+
116
+ /**
117
+ * Gets an order date.
118
+ *
119
+ * This should only be used to retrieve WC core date properties.
120
+ *
121
+ * @since 4.6.0
122
+ * @deprecated 5.5.0
123
+ *
124
+ * @param \WC_Order $order order object
125
+ * @param string $type type of date to get
126
+ * @param string $context if 'view' then the value will be filtered
127
+ *
128
+ * @return \WC_DateTime|null
129
+ */
130
+ public static function get_date_prop( \WC_Order $order, $type, $context = 'edit' ) {
131
+
132
+ wc_deprecated_function( __METHOD__, '5.5.0', 'WC_Order' );
133
+
134
+ $prop = "date_{$type}";
135
+ $date = is_callable( [ $order, "get_{$prop}" ] ) ? $order->{"get_{$prop}"}( $context ) : null;
136
+
137
+ return $date;
138
+ }
139
+
140
+
141
+ /**
142
+ * Gets an order property.
143
+ *
144
+ * @since 4.6.0
145
+ * @deprecated 5.5.0
146
+ *
147
+ * @param \WC_Order $object the order object
148
+ * @param string $prop the property name
149
+ * @param string $context if 'view' then the value will be filtered
150
+ * @param array $compat_props compatibility arguments, unused since 5.5.0
151
+ * @return mixed
152
+ */
153
+ public static function get_prop( $object, $prop, $context = 'edit', $compat_props = [] ) {
154
+
155
+ wc_deprecated_function( __METHOD__, '5.5.0', 'WC_Order::get_prop()' );
156
+
157
+ return parent::get_prop( $object, $prop, $context, self::$compat_props );
158
+ }
159
+
160
+
161
+ /**
162
+ * Sets an order's properties.
163
+ *
164
+ * Note that this does not save any data to the database.
165
+ *
166
+ * @since 4.6.0
167
+ * @deprecated 5.5.0
168
+ *
169
+ * @param \WC_Order $object the order object
170
+ * @param array $props the new properties as $key => $value
171
+ * @param array $compat_props compatibility arguments, unused since 5.5.0
172
+ * @return bool|\WP_Error
173
+ */
174
+ public static function set_props( $object, $props, $compat_props = [] ) {
175
+
176
+ return parent::set_props( $object, $props, self::$compat_props );
177
+ }
178
+
179
+
180
+ /**
181
+ * Adds a coupon to an order item.
182
+ *
183
+ * Order item CRUD compatibility method to add a coupon to an order.
184
+ *
185
+ * @since 4.6.0
186
+ * @deprecated 5.5.0
187
+ *
188
+ * @param \WC_Order $order the order object
189
+ * @param array $code the coupon code
190
+ * @param int $discount the discount amount.
191
+ * @param int $discount_tax the discount tax amount.
192
+ * @return int the order item ID
193
+ */
194
+ public static function add_coupon( \WC_Order $order, $code = [], $discount = 0, $discount_tax = 0 ) {
195
+
196
+ wc_deprecated_function( __METHOD__, '5.5.0', 'WC_Order::add_item()' );
197
+
198
+ $item = new \WC_Order_Item_Coupon();
199
+
200
+ $item->set_props( [
201
+ 'code' => $code,
202
+ 'discount' => $discount,
203
+ 'discount_tax' => $discount_tax,
204
+ 'order_id' => $order->get_id(),
205
+ ] );
206
+
207
+ $item->save();
208
+
209
+ $order->add_item( $item );
210
+
211
+ return $item->get_id();
212
+ }
213
+
214
+
215
+ /**
216
+ * Adds a fee to an order.
217
+ *
218
+ * Order item CRUD compatibility method to add a fee to an order.
219
+ *
220
+ * @since 4.6.0
221
+ * @deprecated 5.5.0
222
+ *
223
+ * @param \WC_Order $order the order object
224
+ * @param object $fee the fee to add
225
+ * @return int the order item ID
226
+ */
227
+ public static function add_fee( \WC_Order $order, $fee ) {
228
+
229
+ wc_deprecated_function( __METHOD__, '5.5.0', 'WC_Order::add_item()' );
230
+
231
+ $item = new \WC_Order_Item_Fee();
232
+
233
+ $item->set_props( [
234
+ 'name' => $fee->name,
235
+ 'tax_class' => $fee->taxable ? $fee->tax_class : 0,
236
+ 'total' => $fee->amount,
237
+ 'total_tax' => $fee->tax,
238
+ 'taxes' => [
239
+ 'total' => $fee->tax_data,
240
+ ],
241
+ 'order_id' => $order->get_id(),
242
+ ] );
243
+
244
+ $item->save();
245
+
246
+ $order->add_item( $item );
247
+
248
+ return $item->get_id();
249
+ }
250
+
251
+
252
+ /**
253
+ * Adds shipping line to order.
254
+ *
255
+ * Order item CRUD compatibility method to add a shipping line to an order.
256
+ *
257
+ * @since 4.7.0
258
+ * @deprecated 5.5.0
259
+ *
260
+ * @param \WC_Order $order order object
261
+ * @param \WC_Shipping_Rate $shipping_rate shipping rate to add
262
+ * @return int the order item ID
263
+ */
264
+ public static function add_shipping( \WC_Order $order, $shipping_rate ) {
265
+
266
+ wc_deprecated_function( __METHOD__, '5.5.0', 'WC_Order::add_item()' );
267
+
268
+ $item = new \WC_Order_Item_Shipping();
269
+
270
+ $item->set_props( [
271
+ 'method_title' => $shipping_rate->label,
272
+ 'method_id' => $shipping_rate->id,
273
+ 'total' => wc_format_decimal( $shipping_rate->cost ),
274
+ 'taxes' => $shipping_rate->taxes,
275
+ 'order_id' => $order->get_id(),
276
+ ] );
277
+
278
+ foreach ( $shipping_rate->get_meta_data() as $key => $value ) {
279
+ $item->add_meta_data( $key, $value, true );
280
+ $item->save_meta_data();
281
+ }
282
+
283
+ $item->save();
284
+
285
+ $order->add_item( $item );
286
+
287
+ return $item->get_id();
288
+ }
289
+
290
+
291
+ /**
292
+ * Adds tax line to an order.
293
+ *
294
+ * Order item CRUD compatibility method to add a tax line to an order.
295
+ *
296
+ * @since 4.7.0
297
+ * @deprecated 5.5.0
298
+ *
299
+ * @param \WC_Order $order order object
300
+ * @param int $tax_rate_id tax rate ID
301
+ * @param int|float $tax_amount cart tax amount
302
+ * @param int|float $shipping_tax_amount shipping tax amount
303
+ * @return int order item ID
304
+ * @throws \WC_Data_Exception
305
+ *
306
+ */
307
+ public static function add_tax( \WC_Order $order, $tax_rate_id, $tax_amount = 0, $shipping_tax_amount = 0 ) {
308
+
309
+ wc_deprecated_function( __METHOD__, '5.5.0', 'WC_Order::add_item()' );
310
+
311
+ $item = new \WC_Order_Item_Tax();
312
+
313
+ $item->set_props( [
314
+ 'rate_id' => $tax_rate_id,
315
+ 'tax_total' => $tax_amount,
316
+ 'shipping_tax_total' => $shipping_tax_amount,
317
+ ] );
318
+
319
+ $item->set_rate( $tax_rate_id );
320
+ $item->set_order_id( $order->get_id() );
321
+ $item->save();
322
+
323
+ $order->add_item( $item );
324
+
325
+ return $item->get_id();
326
+ }
327
+
328
+
329
+ /**
330
+ * Updates an order coupon.
331
+ *
332
+ * Order item CRUD compatibility method to update an order coupon.
333
+ *
334
+ * @since 4.6.0
335
+ * @deprecated 5.5.0
336
+ *
337
+ * @param \WC_Order $order the order object
338
+ * @param int|\WC_Order_Item $item the order item ID
339
+ * @param array $args {
340
+ * The coupon item args.
341
+ *
342
+ * @type string $code the coupon code
343
+ * @type float $discount the coupon discount amount
344
+ * @type float $discount_tax the coupon discount tax amount
345
+ * }
346
+ * @return int|bool the order item ID or false on failure
347
+ * @throws \WC_Data_Exception
348
+ */
349
+ public static function update_coupon( \WC_Order $order, $item, $args ) {
350
+
351
+ wc_deprecated_function( __METHOD__, '5.5.0', 'WC_Order_Item_Coupon' );
352
+
353
+ if ( is_numeric( $item ) ) {
354
+ $item = $order->get_item( $item );
355
+ }
356
+
357
+ if ( ! is_object( $item ) || ! $item->is_type( 'coupon' ) ) {
358
+ return false;
359
+ }
360
+
361
+ if ( ! $order->get_id() ) {
362
+ $order->save();
363
+ }
364
+
365
+ $item->set_order_id( $order->get_id() );
366
+ $item->set_props( $args );
367
+ $item->save();
368
+
369
+ return $item->get_id();
370
+ }
371
+
372
+
373
+ /**
374
+ * Updates an order fee.
375
+ *
376
+ * Order item CRUD compatibility method to update an order fee.
377
+ *
378
+ * @since 4.6.0
379
+ * @deprecated 5.5.0
380
+ *
381
+ * @param \WC_Order $order the order object
382
+ * @param int|\WC_Order_Item $item the order item ID
383
+ * @param array $args {
384
+ * The fee item args.
385
+ *
386
+ * @type string $name the fee name
387
+ * @type string $tax_class the fee's tax class
388
+ * @type float $line_total the fee total amount
389
+ * @type float $line_tax the fee tax amount
390
+ * }
391
+ * @return int|bool the order item ID or false on failure
392
+ * @throws \WC_Data_Exception
393
+ */
394
+ public static function update_fee( \WC_Order $order, $item, $args ) {
395
+
396
+ wc_deprecated_function( __METHOD__, '5.5.0', 'WC_Order_Item_Fee' );
397
+
398
+ if ( is_numeric( $item ) ) {
399
+ $item = $order->get_item( $item );
400
+ }
401
+
402
+ if ( ! is_object( $item ) || ! $item->is_type( 'fee' ) ) {
403
+ return false;
404
+ }
405
+
406
+ if ( ! $order->get_id() ) {
407
+ $order->save();
408
+ }
409
+
410
+ $item->set_order_id( $order->get_id() );
411
+ $item->set_props( $args );
412
+ $item->save();
413
+
414
+ return $item->get_id();
415
+ }
416
+
417
+
418
+ /**
419
+ * Reduces stock levels for products in order.
420
+ *
421
+ * @since 4.6.0
422
+ * @deprecated 5.5.0
423
+ *
424
+ * @param \WC_Order $order the order object
425
+ */
426
+ public static function reduce_stock_levels( \WC_Order $order ) {
427
+
428
+ wc_deprecated_function( __METHOD__, '5.5.0', 'wc_reduce_stock_levels()' );
429
+
430
+ wc_reduce_stock_levels( $order->get_id() );
431
+ }
432
+
433
+
434
+ /**
435
+ * Updates total product sales count for a given order.
436
+ *
437
+ * @since 4.6.0
438
+ * @deprecated 5.5.0
439
+ *
440
+ * @param \WC_Order $order the order object
441
+ */
442
+ public static function update_total_sales_counts( \WC_Order $order ) {
443
+
444
+ wc_deprecated_function( __METHOD__, '5.5.0', 'wc_update_total_sales_counts()' );
445
+
446
+ wc_update_total_sales_counts( $order->get_id() );
447
+ }
448
+
449
+
450
+ /**
451
+ * Determines if an order has an available shipping address.
452
+ *
453
+ * @since 4.6.1
454
+ * @deprecated 5.5.0
455
+ *
456
+ * @param \WC_Order $order order object
457
+ * @return bool
458
+ */
459
+ public static function has_shipping_address( \WC_Order $order ) {
460
+
461
+ wc_deprecated_function( __METHOD__, '5.5.0', 'WC_Order::has_shipping_address()' );
462
+
463
+ return $order->has_shipping_address();
464
+ }
465
+
466
+
467
+ /**
468
+ * Gets the formatted meta data for an order item.
469
+ *
470
+ * @since 4.6.5
471
+ *
472
+ * @param \WC_Order_Item $item order item object
473
+ * @param string $hide_prefix prefix for meta that is considered hidden
474
+ * @param bool $include_all whether to include all meta (attributes, etc...), or just custom fields
475
+ * @return array $item_meta {
476
+ * @type string $label meta field label
477
+ * @type mixed $value meta value
478
+ * }
479
+ */
480
+ public static function get_item_formatted_meta_data( $item, $hide_prefix = '_', $include_all = false ) {
481
+
482
+ if ( $item instanceof \WC_Order_Item && SV_WC_Plugin_Compatibility::is_wc_version_gte( '3.1' ) ) {
483
+
484
+ $meta_data = $item->get_formatted_meta_data( $hide_prefix, $include_all );
485
+ $item_meta = [];
486
+
487
+ foreach ( $meta_data as $meta ) {
488
+
489
+ $item_meta[] = array(
490
+ 'label' => $meta->display_key,
491
+ 'value' => $meta->value,
492
+ );
493
+ }
494
+
495
+ } else {
496
+
497
+ $item_meta = new \WC_Order_Item_Meta( $item );
498
+ $item_meta = $item_meta->get_formatted( $hide_prefix );
499
+ }
500
+
501
+ return $item_meta;
502
+ }
503
+
504
+
505
+ /**
506
+ * Gets the admin Edit screen URL for an order.
507
+ *
508
+ * @since 5.0.1
509
+ *
510
+ * @param \WC_Order $order order object
511
+ * @return string
512
+ */
513
+ public static function get_edit_order_url( \WC_Order $order ) {
514
+
515
+ if ( SV_WC_Plugin_Compatibility::is_wc_version_gte( '3.3' ) ) {
516
+ $order_url = $order->get_edit_order_url();
517
+ } else {
518
+ $order_url = apply_filters( 'woocommerce_get_edit_order_url', get_admin_url( null, 'post.php?post=' . self::get_prop( $order, 'id' ) . '&action=edit' ), $order );
519
+ }
520
+
521
+ return $order_url;
522
+ }
523
+
524
+
525
+ }
526
+
527
+
528
+ endif;
vendor/skyverge/wc-plugin-framework/woocommerce/compatibility/class-sv-wc-product-compatibility.php ADDED
@@ -0,0 +1,242 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * WooCommerce Plugin Framework
4
+ *
5
+ * This source file is subject to the GNU General Public License v3.0
6
+ * that is bundled with this package in the file license.txt.
7
+ * It is also available through the world-wide-web at this URL:
8
+ * http://www.gnu.org/licenses/gpl-3.0.html
9
+ * If you did not receive a copy of the license and are unable to
10
+ * obtain it through the world-wide-web, please send an email
11
+ * to license@skyverge.com so we can send you a copy immediately.
12
+ *
13
+ * DISCLAIMER
14
+ *
15
+ * Do not edit or add to this file if you wish to upgrade the plugin to newer
16
+ * versions in the future. If you wish to customize the plugin for your
17
+ * needs please refer to http://www.skyverge.com
18
+ *
19
+ * @package SkyVerge/WooCommerce/Compatibility
20
+ * @author SkyVerge
21
+ * @copyright Copyright (c) 2013-2020, SkyVerge, Inc.
22
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GNU General Public License v3.0
23
+ */
24
+
25
+ namespace SkyVerge\WooCommerce\PluginFramework\v5_5_4;
26
+
27
+ defined( 'ABSPATH' ) or exit;
28
+
29
+ if ( ! class_exists( '\\SkyVerge\\WooCommerce\\PluginFramework\\v5_5_4\\SV_WC_Product_Compatibility' ) ) :
30
+
31
+
32
+ /**
33
+ * WooCommerce product compatibility class.
34
+ *
35
+ * @since 4.6.0
36
+ */
37
+ class SV_WC_Product_Compatibility extends SV_WC_Data_Compatibility {
38
+
39
+
40
+ /**
41
+ * Gets a product property.
42
+ *
43
+ * @since 4.6.0
44
+ * @deprecated 5.5.0
45
+ *
46
+ * @param \WC_Product $object the product object
47
+ * @param string $prop the property name
48
+ * @param string $context if 'view' then the value will be filtered
49
+ * @param array $compat_props compatibility arguments, unused since 5.5.0
50
+ * @return mixed
51
+ */
52
+ public static function get_prop( $object, $prop, $context = 'edit', $compat_props = [] ) {
53
+
54
+ wc_deprecated_function( __METHOD__, '5.5.0', 'WC_Product::get_prop()' );
55
+
56
+ return parent::get_prop( $object, $prop, $context, self::$compat_props );
57
+ }
58
+
59
+
60
+ /**
61
+ * Sets an products's properties.
62
+ *
63
+ * Note that this does not save any data to the database.
64
+ *
65
+ * @since 4.6.0
66
+ * @deprecated 5.5.0
67
+ *
68
+ * @param \WC_Product $object the product object
69
+ * @param array $props the new properties as $key => $value
70
+ * @param array $compat_props compatibility arguments, unused since 5.5.0
71
+ * @return bool|\WP_Error
72
+ */
73
+ public static function set_props( $object, $props, $compat_props = [] ) {
74
+
75
+ wc_deprecated_function( __METHOD__, '5.5.0', 'WC_Product::set_props()' );
76
+
77
+ return parent::set_props( $object, $props, self::$compat_props );
78
+ }
79
+
80
+
81
+ /**
82
+ * Gets a product's parent product.
83
+ *
84
+ * @since 4.6.0
85
+ * @deprecated 5.5.0
86
+ *
87
+ * @param \WC_Product $product the product object
88
+ * @return \WC_Product|bool
89
+ */
90
+ public static function get_parent( \WC_Product $product ) {
91
+
92
+ wc_deprecated_function( __METHOD__, '5.5.0', 'wc_get_product( \WC_Product::get_parent_id() )' );
93
+
94
+ return wc_get_product( $product->get_parent_id() );
95
+ }
96
+
97
+
98
+ /**
99
+ * Updates product stock.
100
+ *
101
+ * @since 4.6.0
102
+ * @deprecated 5.5.0
103
+ *
104
+ * @param \WC_Product $product the product object
105
+ * @param null|int $amount optional: the new stock quantity
106
+ * @param string $mode optional: can be set (default), add, or subtract
107
+ * @return int
108
+ */
109
+ public static function wc_update_product_stock( \WC_Product $product, $amount = null, $mode = 'set' ) {
110
+
111
+ wc_deprecated_function( __METHOD__, '5.5.0', 'wc_update_product_stock()' );
112
+
113
+ return wc_update_product_stock( $product, $amount, $mode );
114
+ }
115
+
116
+
117
+ /**
118
+ * Gets the product price HTML from text.
119
+ *
120
+ * @since 4.6.0
121
+ * @deprecated 5.5.0
122
+ *
123
+ * @param \WC_Product $product the product object
124
+ * @return string
125
+ */
126
+ public static function wc_get_price_html_from_text( \WC_Product $product ) {
127
+
128
+ wc_deprecated_function( __METHOD__, '5.5.0', 'wc_get_price_html_from_text()' );
129
+
130
+ return wc_get_price_html_from_text();
131
+ }
132
+
133
+
134
+ /**
135
+ * Gets the product price including tax.
136
+ *
137
+ * @since 4.6.0
138
+ * @deprecated 5.5.0
139
+ *
140
+ * @param \WC_Product $product the product object
141
+ * @param int $qty optional: the quantity
142
+ * @param string $price optional: the product price
143
+ * @return string
144
+ */
145
+ public static function wc_get_price_including_tax( \WC_Product $product, $qty = 1, $price = '' ) {
146
+
147
+ wc_deprecated_function( __METHOD__, '5.5.0', 'wc_get_price_including_tax()' );
148
+
149
+ return wc_get_price_including_tax( $product, [
150
+ 'qty' => $qty,
151
+ 'price' => $price,
152
+ ] );
153
+ }
154
+
155
+
156
+ /**
157
+ * Gets the product price excluding tax.
158
+ *
159
+ * @since 4.6.0
160
+ * @deprecated 5.5.0
161
+ *
162
+ * @param \WC_Product $product the product object
163
+ * @param int $qty optional: The quantity
164
+ * @param string $price optional: the product price
165
+ * @return string
166
+ */
167
+ public static function wc_get_price_excluding_tax( \WC_Product $product, $qty = 1, $price = '' ) {
168
+
169
+ wc_deprecated_function( __METHOD__, '5.5.0', 'wc_get_price_excluding_tax()' );
170
+
171
+ return wc_get_price_excluding_tax( $product, [
172
+ 'qty' => $qty,
173
+ 'price' => $price,
174
+ ] );
175
+ }
176
+
177
+
178
+ /**
179
+ * Gets the product price to display.
180
+ *
181
+ * @since 4.6.0
182
+ *
183
+ * @param \WC_Product $product the product object
184
+ * @param string $price optional: the product price
185
+ * @param int $qty optional: the quantity
186
+ * @return string
187
+ */
188
+ public static function wc_get_price_to_display( \WC_Product $product, $price = '', $qty = 1 ) {
189
+
190
+ wc_deprecated_function( __METHOD__, '5.5.0', 'wc_get_price_to_display()' );
191
+
192
+ return wc_get_price_to_display( $product, [
193
+ 'qty' => $qty,
194
+ 'price' => $price,
195
+ ] );
196
+ }
197
+
198
+
199
+ /**
200
+ * Gets the product category list.
201
+ *
202
+ * @since 4.6.0
203
+ * @deprecated 5.5.0
204
+ *
205
+ * @param \WC_Product $product the product object
206
+ * @param string $sep optional: the list separator
207
+ * @param string $before optional: to display before the list
208
+ * @param string $after optional: to display after the list
209
+ * @return string
210
+ */
211
+ public static function wc_get_product_category_list( \WC_Product $product, $sep = ', ', $before = '', $after = '' ) {
212
+
213
+ wc_deprecated_function( __METHOD__, '5.5.0', 'wc_get_product_category_list()' );
214
+
215
+ $id = $product->is_type( 'variation' ) ? $product->get_parent_id() : $product->get_id();
216
+
217
+ return wc_get_product_category_list( $id, $sep, $before, $after );
218
+ }
219
+
220
+
221
+ /**
222
+ * Formats the product rating HTML.
223
+ *
224
+ * @since 4.6.0
225
+ * @deprecated 5.5.0
226
+ *
227
+ * @param \WC_Product $product the product object, unused since 5.5.0
228
+ * @param null|string $rating optional: the product rating
229
+ * @return string
230
+ */
231
+ public static function wc_get_rating_html( \WC_Product $product, $rating = null ) {
232
+
233
+ wc_deprecated_function( __METHOD__, '5.5.0', 'wc_get_rating_html()' );
234
+
235
+ return wc_get_rating_html( $rating );
236
+ }
237
+
238
+
239
+ }
240
+
241
+
242
+ endif;
vendor/skyverge/wc-plugin-framework/woocommerce/i18n/languages/woocommerce-plugin-framework-et.mo ADDED
Binary file
vendor/skyverge/wc-plugin-framework/woocommerce/i18n/languages/woocommerce-plugin-framework-et.po ADDED
@@ -0,0 +1,2176 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (C) 2015
2
+ # This file is distributed under the same license as the package.
3
+ # Translators:
4
+ # Illimar Tambek <illimar.tambek@gmail.com>, 2015
5
+ msgid ""
6
+ msgstr ""
7
+ "Project-Id-Version: WC Plugin Framework\n"
8
+ "Report-Msgid-Bugs-To: https://support.woocommerce.com/hc/\n"
9
+ "POT-Creation-Date: 2015-07-22 12:09:16+00:00\n"
10
+ "PO-Revision-Date: 2017-03-27 11:59-0700\n"
11
+ "Last-Translator: Illimar Tambek <illimar.tambek@gmail.com>\n"
12
+ "Language-Team: Estonian (http://www.transifex.com/projects/p/wc-plugin-"
13
+ "framework/language/et/)\n"
14
+ "Language: et\n"
15
+ "MIME-Version: 1.0\n"
16
+ "Content-Type: text/plain; charset=UTF-8\n"
17
+ "Content-Transfer-Encoding: 8bit\n"
18
+ "Plural-Forms: nplurals=2; plural=(n != 1);\n"
19
+ "X-Generator: Poedit 1.8.11\n"
20
+
21
+ #: Lifecycle.php:377
22
+ msgid "Awesome"
23
+ msgstr ""
24
+
25
+ #: Lifecycle.php:378
26
+ msgid "Fantastic"
27
+ msgstr ""
28
+
29
+ #: Lifecycle.php:379
30
+ msgid "Cowabunga"
31
+ msgstr ""
32
+
33
+ #: Lifecycle.php:380
34
+ msgid "Congratulations"
35
+ msgstr ""
36
+
37
+ #: Lifecycle.php:381
38
+ msgid "Hot dog"
39
+ msgstr ""
40
+
41
+ #. translators: Placeholders: %1$s - plugin name, %2$s - <a> tag, %3$s - </a>
42
+ #. tag, %4$s - <a> tag, %5$s - </a> tag
43
+ #: Lifecycle.php:388
44
+ msgid ""
45
+ "Are you having a great experience with %1$s so far? Please consider "
46
+ "%2$sleaving a review%3$s! If things aren't going quite as expected, we're "
47
+ "happy to help -- please %4$sreach out to our support team%5$s."
48
+ msgstr ""
49
+
50
+ #: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:182
51
+ msgid ""
52
+ "Thanks for installing %1$s! To get started, take a minute to %2$sread the "
53
+ "documentation%3$s :)"
54
+ msgstr ""
55
+
56
+ #: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:210
57
+ msgid ""
58
+ "Thanks for installing %1$s! To get started, take a minute to complete these "
59
+ "%2$squick and easy setup steps%3$s :)"
60
+ msgstr ""
61
+
62
+ #: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:235
63
+ msgid "Setup"
64
+ msgstr ""
65
+
66
+ #. translators: Placeholders: %s - plugin name
67
+ #: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:303
68
+ msgid "%s &rsaquo; Setup"
69
+ msgstr ""
70
+
71
+ #: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:351
72
+ #, fuzzy
73
+ msgid "Oops! An error occurred, please try again."
74
+ msgstr "Sinu päringuga esines viga, palun proovi uuesti."
75
+
76
+ #: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:489
77
+ msgid "Ready!"
78
+ msgstr ""
79
+
80
+ #. translators: Placeholder: %s - plugin name
81
+ #: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:582
82
+ msgid "Welcome to %s!"
83
+ msgstr ""
84
+
85
+ #: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:595
86
+ msgid ""
87
+ "This quick setup wizard will help you configure the basic settings and get "
88
+ "you started."
89
+ msgstr ""
90
+
91
+ #: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:609
92
+ msgid "%s is ready!"
93
+ msgstr ""
94
+
95
+ #: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:661
96
+ msgid "Next step"
97
+ msgstr ""
98
+
99
+ #: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:687
100
+ msgid "You can also:"
101
+ msgstr ""
102
+
103
+ #: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:731
104
+ #: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:761
105
+ msgid "View the Docs"
106
+ msgstr ""
107
+
108
+ #: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:732
109
+ msgid "See more setup options"
110
+ msgstr ""
111
+
112
+ #: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:733
113
+ msgid "Learn more about customizing the plugin"
114
+ msgstr ""
115
+
116
+ #: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:757
117
+ msgid "Review Your Settings"
118
+ msgstr ""
119
+
120
+ #: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:765
121
+ msgid "Leave a Review"
122
+ msgstr ""
123
+
124
+ #: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:789
125
+ msgid "Continue"
126
+ msgstr "Jätka"
127
+
128
+ #: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:949
129
+ msgid "Return to the WordPress Dashboard"
130
+ msgstr ""
131
+
132
+ #: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:951
133
+ msgid "Not right now"
134
+ msgstr ""
135
+
136
+ #: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:953
137
+ msgid "Skip this step"
138
+ msgstr ""
139
+
140
+ #: class-sv-wc-framework-bootstrap.php:268
141
+ msgid ""
142
+ "The following plugin is disabled because it is out of date and incompatible "
143
+ "with newer plugins on your site:"
144
+ msgid_plural ""
145
+ "The following plugins are disabled because they are out of date and "
146
+ "incompatible with newer plugins on your site:"
147
+ msgstr[0] ""
148
+ msgstr[1] ""
149
+
150
+ #: class-sv-wc-framework-bootstrap.php:282
151
+ msgid ""
152
+ "To resolve this, please %1$supdate%2$s (recommended) %3$sor%4$s "
153
+ "%5$sdeactivate%6$s the above plugin, or %7$sdeactivate the following%8$s:"
154
+ msgid_plural ""
155
+ "To resolve this, please %1$supdate%2$s (recommended) %3$sor%4$s "
156
+ "%5$sdeactivate%6$s the above plugins, or %7$sdeactivate the following%8$s:"
157
+ msgstr[0] ""
158
+ msgstr[1] ""
159
+
160
+ #: class-sv-wc-framework-bootstrap.php:303
161
+ msgid ""
162
+ "The following plugins are inactive because they require a newer version of "
163
+ "WooCommerce:"
164
+ msgstr ""
165
+ "Järgnevad pluginad ei ole aktiivsed, kuna vajavad korrektselt töötamiseks "
166
+ "uuemat WooCommerce'i versiooni:"
167
+
168
+ #: class-sv-wc-framework-bootstrap.php:303
169
+ msgid ""
170
+ "The following plugin is inactive because it requires a newer version of "
171
+ "WooCommerce:"
172
+ msgstr ""
173
+ "Järgnev plugin ei ole aktiivne, kuna vajab korrektselt töötamiseks uuemat "
174
+ "WooCommerce'i versiooni:"
175
+
176
+ #. translators: Placeholders: %1$s - plugin name, %2$s - WooCommerce version
177
+ #. number
178
+ #: class-sv-wc-framework-bootstrap.php:308
179
+ msgid "%1$s requires WooCommerce %2$s or newer"
180
+ msgstr "%1$s vajab WooCommerce'i versiooni %2$s või uuemat"
181
+
182
+ #. translators: Placeholders: %1$s - <a> tag, %2$s - </a> tag
183
+ #: class-sv-wc-framework-bootstrap.php:312
184
+ msgid "Please %1$supdate WooCommerce%2$s"
185
+ msgstr "Palun %1$suuenda WooCommerce'i%2$s"
186
+
187
+ #: class-sv-wc-plugin-compatibility.php:334
188
+ msgid "WooCommerce"
189
+ msgstr ""
190
+
191
+ #. translators: Placeholders: %1$s - plugin name, %2$s - a PHP
192
+ #. extension/comma-separated list of PHP extensions
193
+ #: class-sv-wc-plugin-dependencies.php:156
194
+ msgid ""
195
+ "%1$s requires the %2$s PHP extension to function. Contact your host or "
196
+ "server administrator to install and configure the missing extension."
197
+ msgid_plural ""
198
+ "%1$s requires the following PHP extensions to function: %2$s. Contact your "
199
+ "host or server administrator to install and configure the missing extensions."
200
+ msgstr[0] ""
201
+ msgstr[1] ""
202
+
203
+ #. translators: Placeholders: %1$s - plugin name, %2$s - a PHP
204
+ #. function/comma-separated list of PHP functions
205
+ #: class-sv-wc-plugin-dependencies.php:184
206
+ msgid ""
207
+ "%1$s requires the %2$s PHP function to exist. Contact your host or server "
208
+ "administrator to install and configure the missing function."
209
+ msgid_plural ""
210
+ "%1$s requires the following PHP functions to exist: %2$s. Contact your host "
211
+ "or server administrator to install and configure the missing functions."
212
+ msgstr[0] ""
213
+ msgstr[1] ""
214
+
215
+ #. translators: Placeholders: %s - plugin name
216
+ #: class-sv-wc-plugin-dependencies.php:214
217
+ msgid ""
218
+ "%s may behave unexpectedly because the following PHP configuration settings "
219
+ "are required:"
220
+ msgstr ""
221
+
222
+ #: class-sv-wc-plugin-dependencies.php:228
223
+ msgid "%s or higher"
224
+ msgstr ""
225
+
226
+ #: class-sv-wc-plugin-dependencies.php:238
227
+ msgid ""
228
+ "Please contact your hosting provider or server administrator to configure "
229
+ "these settings."
230
+ msgstr ""
231
+
232
+ #. translators: Placeholders: %1$s - <strong>, %2$s - </strong>
233
+ #: class-sv-wc-plugin-dependencies.php:260
234
+ msgid ""
235
+ "Hey there! We've noticed that your server is running %1$san outdated version "
236
+ "of PHP%2$s, which is the programming language that WooCommerce and its "
237
+ "extensions are built on.\n"
238
+ "\t\t\t\t\tThe PHP version that is currently used for your site is no longer "
239
+ "maintained, nor %1$sreceives security updates%2$s; newer versions are faster "
240
+ "and more secure.\n"
241
+ "\t\t\t\t\tAs a result, %3$s no longer supports this version and you should "
242
+ "upgrade PHP as soon as possible.\n"
243
+ "\t\t\t\t\tYour hosting provider can do this for you. %4$sHere are some "
244
+ "resources to help you upgrade%5$s and to explain PHP versions further."
245
+ msgstr ""
246
+
247
+ #. translators: Placeholders: %s - plugin name
248
+ #: class-sv-wc-plugin.php:306
249
+ msgid "You cannot clone instances of %s."
250
+ msgstr "%s eksemplari ei saa kloonida."
251
+
252
+ #. translators: Placeholders: %s - plugin name
253
+ #: class-sv-wc-plugin.php:317
254
+ msgid "You cannot unserialize instances of %s."
255
+ msgstr "%s eksemplari ei saa deserialiseerida (unserialize)."
256
+
257
+ #. translators: Placeholders: %1$s - plugin name, %2$s - WooCommerce version
258
+ #. number, %3$s - opening <a> HTML link tag, %4$s - closing </a> HTML link tag
259
+ #: class-sv-wc-plugin.php:563
260
+ msgid ""
261
+ "Heads up! %1$s will soon discontinue support for WooCommerce %2$s. Please "
262
+ "%3$supdate WooCommerce%4$s to take advantage of the latest updates and "
263
+ "features."
264
+ msgstr ""
265
+
266
+ #. translators: Docs as in Documentation
267
+ #: class-sv-wc-plugin.php:606
268
+ msgid "Docs"
269
+ msgstr "Dokumentatsioon"
270
+
271
+ #: class-sv-wc-plugin.php:699
272
+ msgid "%1$s - A minimum of %2$s is required."
273
+ msgstr ""
274
+
275
+ #: class-sv-wc-plugin.php:708
276
+ msgid "Set as %1$s - %2$s is required."
277
+ msgstr ""
278
+
279
+ #: class-sv-wc-plugin.php:973
280
+ msgid "Configure"
281
+ msgstr "Seadista"
282
+
283
+ #: payment-gateway/Handlers/Abstract_Hosted_Payment_Handler.php:179
284
+ #, fuzzy
285
+ msgid ""
286
+ "There was a problem processing your order and it is being placed on hold for "
287
+ "review. Please contact us to complete the transaction."
288
+ msgstr ""
289
+ "Tellimus on pandud ülevaatuseks ootele. Tehingu sooritamiseks võta palun "
290
+ "meiega ühendust."
291
+
292
+ #: payment-gateway/Handlers/Abstract_Hosted_Payment_Handler.php:217
293
+ #: payment-gateway/class-sv-wc-payment-gateway.php:2762
294
+ #: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-subscriptions.php:489
295
+ msgid ""
296
+ "An error occurred, please try again or try an alternate form of payment."
297
+ msgstr "Esines viga, palun proovi uuesti või kasuta teistsugust makseviisi."
298
+
299
+ #. translators: Placeholders: %s - a WooCommerce order ID
300
+ #: payment-gateway/Handlers/Abstract_Hosted_Payment_Handler.php:320
301
+ #: payment-gateway/class-sv-wc-payment-gateway-hosted.php:445
302
+ msgid "Could not find order %s"
303
+ msgstr ""
304
+
305
+ #. translators: Placeholders: %1$s - status code, %2$s - status message
306
+ #. translators: Placeholders: %1$s - payment request response status code, %2$s
307
+ #. - payment request response status message
308
+ #: payment-gateway/Handlers/Abstract_Payment_Handler.php:152
309
+ #: payment-gateway/class-sv-wc-payment-gateway.php:2355
310
+ #: payment-gateway/payment-tokens/class-sv-wc-payment-gateway-payment-tokens-handler.php:165
311
+ msgid "Status code %1$s: %2$s"
312
+ msgstr "Staatuse kood %1$s: %2$s"
313
+
314
+ #. translators: Placeholders: %s - status code
315
+ #. translators: Placeholders: %s - payment request response status code
316
+ #: payment-gateway/Handlers/Abstract_Payment_Handler.php:155
317
+ #: payment-gateway/class-sv-wc-payment-gateway.php:2358
318
+ #: payment-gateway/payment-tokens/class-sv-wc-payment-gateway-payment-tokens-handler.php:168
319
+ msgid "Status code: %s"
320
+ msgstr "Staatuse kood: %s"
321
+
322
+ #. translators: Placeholders; %s - status message
323
+ #. translators: Placeholders: %s - payment request response status message
324
+ #: payment-gateway/Handlers/Abstract_Payment_Handler.php:158
325
+ #: payment-gateway/class-sv-wc-payment-gateway.php:2361
326
+ #: payment-gateway/payment-tokens/class-sv-wc-payment-gateway-payment-tokens-handler.php:171
327
+ msgid "Status message: %s"
328
+ msgstr "Staatuse teade: %s"
329
+
330
+ #: payment-gateway/Handlers/Abstract_Payment_Handler.php:163
331
+ #: payment-gateway/class-sv-wc-payment-gateway.php:2366
332
+ #: payment-gateway/payment-tokens/class-sv-wc-payment-gateway-payment-tokens-handler.php:178
333
+ msgid "Transaction ID %s"
334
+ msgstr "Tehingu ID %s"
335
+
336
+ #. translators: Placeholders: %s - payment gateway title (such as
337
+ #. Authorize.net, Braintree, etc)
338
+ #: payment-gateway/Handlers/Abstract_Payment_Handler.php:204
339
+ #: payment-gateway/class-sv-wc-payment-gateway-hosted.php:509
340
+ msgid "%s duplicate transaction received"
341
+ msgstr "%s: duplikaattehing"
342
+
343
+ #: payment-gateway/Handlers/Abstract_Payment_Handler.php:207
344
+ #: payment-gateway/class-sv-wc-payment-gateway-hosted.php:512
345
+ msgid "Order %s is already paid for."
346
+ msgstr ""
347
+
348
+ #: payment-gateway/Handlers/Abstract_Payment_Handler.php:267
349
+ #: payment-gateway/class-sv-wc-payment-gateway.php:2698
350
+ msgid ""
351
+ "Your order has been received and is being reviewed. Thank you for your "
352
+ "business."
353
+ msgstr ""
354
+ "Sinu tellimus on vastu võetud ja on ülevaatamisel. Täname koostöö eest."
355
+
356
+ #. translators: This is a message describing that the transaction in question
357
+ #. only performed a credit card authorization and did not capture any funds.
358
+ #: payment-gateway/Handlers/Abstract_Payment_Handler.php:274
359
+ #: payment-gateway/class-sv-wc-payment-gateway-direct.php:861
360
+ #: payment-gateway/class-sv-wc-payment-gateway.php:1733
361
+ #: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-pre-orders.php:370
362
+ msgid "Authorization only transaction"
363
+ msgstr "Autoriseerimise tehing"
364
+
365
+ #. translators: Placeholders: %s - payment gateway title
366
+ #: payment-gateway/Handlers/Abstract_Payment_Handler.php:364
367
+ #, fuzzy
368
+ msgid "%s Transaction Held for Review"
369
+ msgstr "%1$s: tehning pandi ülevaatuseks ootele (%2$s)"
370
+
371
+ #. translators: Placeholders: %s - payment gateway title
372
+ #: payment-gateway/Handlers/Abstract_Payment_Handler.php:435
373
+ #, fuzzy
374
+ msgid "%s Payment Failed"
375
+ msgstr "%1$s: makse ebaõnnestus (%2$s)"
376
+
377
+ #. translators: Placeholders: %s - payment gateway title
378
+ #: payment-gateway/Handlers/Abstract_Payment_Handler.php:462
379
+ #, fuzzy
380
+ msgid "%s Transaction Cancelled"
381
+ msgstr "%1$s: tehing tühistatud (%2$s)"
382
+
383
+ #: payment-gateway/Handlers/Capture.php:158
384
+ msgid "Order cannot be captured"
385
+ msgstr ""
386
+
387
+ #: payment-gateway/Handlers/Capture.php:163
388
+ msgid "Transaction authorization has expired"
389
+ msgstr ""
390
+
391
+ #: payment-gateway/Handlers/Capture.php:168
392
+ msgid "Transaction has already been fully captured"
393
+ msgstr ""
394
+
395
+ #: payment-gateway/Handlers/Capture.php:173
396
+ #, fuzzy
397
+ msgid "Transaction cannot be captured"
398
+ msgstr "Tehingu tüüp"
399
+
400
+ #. translators: Placeholders: %1$s - payment gateway title (such as
401
+ #. Authorize.net, Braintree, etc), %2$s - transaction amount. Definitions:
402
+ #. Capture, as in capture funds from a credit card.
403
+ #: payment-gateway/Handlers/Capture.php:189
404
+ msgid "%1$s Capture of %2$s Approved"
405
+ msgstr "%1$s: tasumine summas %2$s kinnitatud"
406
+
407
+ #. translators: Placeholders: %s - transaction ID
408
+ #: payment-gateway/Handlers/Capture.php:198
409
+ #: payment-gateway/class-sv-wc-payment-gateway-direct.php:680
410
+ #: payment-gateway/class-sv-wc-payment-gateway-direct.php:765
411
+ #: payment-gateway/class-sv-wc-payment-gateway.php:2034
412
+ #: payment-gateway/class-sv-wc-payment-gateway.php:2267
413
+ #: payment-gateway/class-sv-wc-payment-gateway.php:2579
414
+ #: payment-gateway/class-sv-wc-payment-gateway.php:2624
415
+ #: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-pre-orders.php:353
416
+ msgid "(Transaction ID %s)"
417
+ msgstr "(Tehingu ID %s)"
418
+
419
+ #. translators: Placeholders: %1$s - payment gateway title (such as
420
+ #. Authorize.net, Braintree, etc), %2$s - failure message. Definitions:
421
+ #. "capture" as in capturing funds from a credit card.
422
+ #: payment-gateway/Handlers/Capture.php:229
423
+ msgid "%1$s Capture Failed: %2$s"
424
+ msgstr "%1$s: makse teostamine ebaõnnestus: %2$s"
425
+
426
+ #: payment-gateway/admin/class-sv-wc-payment-gateway-admin-order.php:120
427
+ #, fuzzy
428
+ msgid ""
429
+ "Are you sure you wish to process this capture? The action cannot be undone."
430
+ msgstr ""
431
+ "Oled kindel, et soovid seda teha? Muudatust ei rakendata enne kui klikid "
432
+ "\"Uuenda\""
433
+
434
+ #: payment-gateway/admin/class-sv-wc-payment-gateway-admin-order.php:123
435
+ msgid ""
436
+ "Something went wrong, and the capture could no be completed. Please try "
437
+ "again."
438
+ msgstr ""
439
+
440
+ #. translators: verb, as in "Capture credit card charge". Used when an
441
+ #. amount has been pre-authorized before, but funds have not yet been captured
442
+ #. (taken) from the card. Capturing the charge will take the money from the
443
+ #. credit card and put it in the merchant's pockets.
444
+ #: payment-gateway/admin/class-sv-wc-payment-gateway-admin-order.php:167
445
+ #: payment-gateway/admin/class-sv-wc-payment-gateway-admin-order.php:242
446
+ #: payment-gateway/admin/class-sv-wc-payment-gateway-admin-order.php:305
447
+ msgid "Capture Charge"
448
+ msgstr "Teosta makse"
449
+
450
+ #: payment-gateway/admin/class-sv-wc-payment-gateway-admin-order.php:295
451
+ msgid "This charge has been fully captured."
452
+ msgstr ""
453
+
454
+ #: payment-gateway/admin/class-sv-wc-payment-gateway-admin-order.php:297
455
+ msgid "This charge can no longer be captured."
456
+ msgstr ""
457
+
458
+ #: payment-gateway/admin/class-sv-wc-payment-gateway-admin-order.php:299
459
+ msgid "This charge cannot be captured."
460
+ msgstr ""
461
+
462
+ #: payment-gateway/admin/class-sv-wc-payment-gateway-admin-payment-token-editor.php:91
463
+ msgid "Are you sure you want to remove this token?"
464
+ msgstr ""
465
+
466
+ #: payment-gateway/admin/class-sv-wc-payment-gateway-admin-payment-token-editor.php:101
467
+ msgid "Invalid token data"
468
+ msgstr ""
469
+
470
+ #: payment-gateway/admin/class-sv-wc-payment-gateway-admin-payment-token-editor.php:105
471
+ #, fuzzy
472
+ msgid "An error occurred. Please try again."
473
+ msgstr "Sinu päringuga esines viga, palun proovi uuesti."
474
+
475
+ #: payment-gateway/admin/class-sv-wc-payment-gateway-admin-payment-token-editor.php:454
476
+ #: payment-gateway/admin/class-sv-wc-payment-gateway-admin-user-handler.php:305
477
+ msgid "(%s)"
478
+ msgstr ""
479
+
480
+ #: payment-gateway/admin/class-sv-wc-payment-gateway-admin-payment-token-editor.php:484
481
+ #: payment-gateway/class-sv-wc-payment-gateway-my-payment-methods.php:667
482
+ msgid "Default"
483
+ msgstr ""
484
+
485
+ #: payment-gateway/admin/class-sv-wc-payment-gateway-admin-payment-token-editor.php:520
486
+ #: payment-gateway/admin/class-sv-wc-payment-gateway-admin-payment-token-editor.php:553
487
+ msgid "Token ID"
488
+ msgstr ""
489
+
490
+ #: payment-gateway/admin/class-sv-wc-payment-gateway-admin-payment-token-editor.php:525
491
+ #: payment-gateway/class-sv-wc-payment-gateway-privacy.php:300
492
+ msgid "Card Type"
493
+ msgstr "Kaardi tüüp"
494
+
495
+ #: payment-gateway/admin/class-sv-wc-payment-gateway-admin-payment-token-editor.php:530
496
+ #: payment-gateway/admin/class-sv-wc-payment-gateway-admin-payment-token-editor.php:566
497
+ #: payment-gateway/class-sv-wc-payment-gateway-privacy.php:192
498
+ #: payment-gateway/class-sv-wc-payment-gateway-privacy.php:298
499
+ msgid "Last Four"
500
+ msgstr "Viimased 4 numbrit"
501
+
502
+ #: payment-gateway/admin/class-sv-wc-payment-gateway-admin-payment-token-editor.php:537
503
+ #: payment-gateway/class-sv-wc-payment-gateway-payment-form.php:331
504
+ msgid "Expiration (MM/YY)"
505
+ msgstr "Aegub (KK/AA)"
506
+
507
+ #. translators: e-check account type, HTML form field label
508
+ #: payment-gateway/admin/class-sv-wc-payment-gateway-admin-payment-token-editor.php:558
509
+ #: payment-gateway/class-sv-wc-payment-gateway-payment-form.php:438
510
+ #: payment-gateway/class-sv-wc-payment-gateway-privacy.php:299
511
+ msgid "Account Type"
512
+ msgstr "Konto tüüp"
513
+
514
+ #: payment-gateway/admin/class-sv-wc-payment-gateway-admin-payment-token-editor.php:561
515
+ msgid "Checking"
516
+ msgstr ""
517
+
518
+ #: payment-gateway/admin/class-sv-wc-payment-gateway-admin-payment-token-editor.php:562
519
+ msgid "Savings"
520
+ msgstr ""
521
+
522
+ #: payment-gateway/admin/class-sv-wc-payment-gateway-admin-payment-token-editor.php:663
523
+ msgid "Refresh"
524
+ msgstr ""
525
+
526
+ #: payment-gateway/admin/class-sv-wc-payment-gateway-admin-payment-token-editor.php:665
527
+ msgid "Add New"
528
+ msgstr ""
529
+
530
+ #: payment-gateway/admin/class-sv-wc-payment-gateway-admin-payment-token-editor.php:668
531
+ #: payment-gateway/class-sv-wc-payment-gateway-my-payment-methods.php:762
532
+ msgid "Save"
533
+ msgstr ""
534
+
535
+ #: payment-gateway/admin/class-sv-wc-payment-gateway-admin-payment-token-editor.php:691
536
+ msgid "Remove"
537
+ msgstr ""
538
+
539
+ #: payment-gateway/admin/class-sv-wc-payment-gateway-admin-user-handler.php:224
540
+ #: payment-gateway/class-sv-wc-payment-gateway-privacy.php:209
541
+ msgid "%s Payment Tokens"
542
+ msgstr "%s maksevahendid"
543
+
544
+ #: payment-gateway/admin/class-sv-wc-payment-gateway-admin-user-handler.php:302
545
+ #: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-subscriptions.php:779
546
+ msgid "Customer ID"
547
+ msgstr "Kliendi ID"
548
+
549
+ #: payment-gateway/admin/views/html-admin-gateway-status.php:32
550
+ msgid "This section contains configuration settings for this gateway."
551
+ msgstr ""
552
+
553
+ #. translators: environment as in a software environment (test/production)
554
+ #: payment-gateway/admin/views/html-admin-gateway-status.php:53
555
+ #: payment-gateway/class-sv-wc-payment-gateway.php:1266
556
+ msgid "Environment"
557
+ msgstr "Keskkond"
558
+
559
+ #: payment-gateway/admin/views/html-admin-gateway-status.php:54
560
+ msgid "The transaction environment for this gateway."
561
+ msgstr ""
562
+
563
+ #: payment-gateway/admin/views/html-admin-gateway-status.php:61
564
+ msgid "Tokenization Enabled"
565
+ msgstr ""
566
+
567
+ #: payment-gateway/admin/views/html-admin-gateway-status.php:62
568
+ msgid "Displays whether or not tokenization is enabled for this gateway."
569
+ msgstr ""
570
+
571
+ #: payment-gateway/admin/views/html-admin-gateway-status.php:75
572
+ #: payment-gateway/class-sv-wc-payment-gateway.php:1196
573
+ msgid "Debug Mode"
574
+ msgstr "Veaotsingu režiim"
575
+
576
+ #: payment-gateway/admin/views/html-admin-gateway-status.php:76
577
+ msgid "Displays whether or not debug logging is enabled for this gateway."
578
+ msgstr ""
579
+
580
+ #: payment-gateway/admin/views/html-admin-gateway-status.php:79
581
+ msgid "Display at Checkout & Log"
582
+ msgstr ""
583
+
584
+ #: payment-gateway/admin/views/html-admin-gateway-status.php:81
585
+ msgid "Display at Checkout"
586
+ msgstr ""
587
+
588
+ #: payment-gateway/admin/views/html-admin-gateway-status.php:83
589
+ #: payment-gateway/class-sv-wc-payment-gateway.php:1204
590
+ msgid "Save to Log"
591
+ msgstr "Salvesta logifaili"
592
+
593
+ #: payment-gateway/admin/views/html-admin-gateway-status.php:85
594
+ #: payment-gateway/class-sv-wc-payment-gateway.php:1202
595
+ msgid "Off"
596
+ msgstr "Välja lülitatud"
597
+
598
+ #: payment-gateway/admin/views/html-order-partial-capture.php:30
599
+ #, fuzzy
600
+ msgid "Authorization total"
601
+ msgstr "Autoriseerimine"
602
+
603
+ #: payment-gateway/admin/views/html-order-partial-capture.php:34
604
+ msgid "Amount already captured"
605
+ msgstr ""
606
+
607
+ #: payment-gateway/admin/views/html-order-partial-capture.php:40
608
+ msgid "Remaining order total"
609
+ msgstr ""
610
+
611
+ #: payment-gateway/admin/views/html-order-partial-capture.php:46
612
+ #, fuzzy
613
+ msgid "Capture amount"
614
+ msgstr "Teosta makse"
615
+
616
+ #: payment-gateway/admin/views/html-order-partial-capture.php:53
617
+ msgid "Comment (optional):"
618
+ msgstr ""
619
+
620
+ #: payment-gateway/admin/views/html-order-partial-capture.php:65
621
+ #, fuzzy
622
+ msgid "Capture %s"
623
+ msgstr "Teosta makse"
624
+
625
+ #: payment-gateway/admin/views/html-order-partial-capture.php:66
626
+ #: payment-gateway/class-sv-wc-payment-gateway-my-payment-methods.php:229
627
+ #, fuzzy
628
+ msgid "Cancel"
629
+ msgstr "Tühista tellimus"
630
+
631
+ #: payment-gateway/admin/views/html-user-payment-token-editor-token.php:48
632
+ msgid "-- Select an option --"
633
+ msgstr ""
634
+
635
+ #: payment-gateway/admin/views/html-user-payment-token-editor.php:59
636
+ msgid "No saved payment tokens"
637
+ msgstr ""
638
+
639
+ #: payment-gateway/admin/views/html-user-profile-field-customer-id.php:30
640
+ msgid "The gateway customer ID for the user. Only edit this if necessary."
641
+ msgstr ""
642
+ "Kasutajale makseviisi poolt määratud kliendi tunnus. Muuda seda ainult siis, "
643
+ "kui tõesti vajalik."
644
+
645
+ #: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:99
646
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-frontend.php:132
647
+ msgid "An error occurred, please try again or try an alternate form of payment"
648
+ msgstr "Esines viga, palun proovi uuesti või kasuta teistsugust makseviisi"
649
+
650
+ #: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:100
651
+ msgid ""
652
+ "We cannot process your order with the payment information that you provided. "
653
+ "Please use a different payment account or an alternate payment method."
654
+ msgstr ""
655
+ "Me ei saa sinu tellimust antud makseinfo alusel töödelda. Palun kasuta teist "
656
+ "maksekontot või teistsugust makseviisi."
657
+
658
+ #: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:101
659
+ msgid ""
660
+ "This order is being placed on hold for review. Please contact us to complete "
661
+ "the transaction."
662
+ msgstr ""
663
+ "Tellimus on pandud ülevaatuseks ootele. Tehingu sooritamiseks võta palun "
664
+ "meiega ühendust."
665
+
666
+ #: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:106
667
+ msgid ""
668
+ "This order is being placed on hold for review due to an incorrect card "
669
+ "verification number. You may contact the store to complete the transaction."
670
+ msgstr ""
671
+ "Tellimus pandi ootele, kuna kaardi turvakood oli vale. Tehingu "
672
+ "lõpuleviimiseks võid poega ühendust võtta."
673
+
674
+ #: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:107
675
+ msgid "The card verification number is invalid, please try again."
676
+ msgstr "Kaardi turvakood on vale, palun proovi uuesti."
677
+
678
+ #: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:108
679
+ msgid "Please enter your card verification number and try again."
680
+ msgstr "Palun sisesta oma kaardi turvakood ja proovi uuesti."
681
+
682
+ #: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:111
683
+ msgid ""
684
+ "That card type is not accepted, please use an alternate card or other form "
685
+ "of payment."
686
+ msgstr ""
687
+ "Sellist tüüpi kaarti ei võeta vastu, palun proovi mõnda teist kaarti või "
688
+ "teistsugust makseviisi."
689
+
690
+ #: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:112
691
+ #: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:116
692
+ msgid ""
693
+ "The card type is invalid or does not correlate with the credit card number. "
694
+ "Please try again or use an alternate card or other form of payment."
695
+ msgstr ""
696
+ "Kaardi tüüp on vigane või ei vasta kaardi numbrile. Palun proovi uuesti, "
697
+ "proovi mõnda teist kaarti või teistsugust makseviisi."
698
+
699
+ #: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:113
700
+ msgid "Please select the card type and try again."
701
+ msgstr "Palun vali kaardi tüüp ja proovi uuesti."
702
+
703
+ #: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:117
704
+ msgid "The card number is invalid, please re-enter and try again."
705
+ msgstr "Kaardi number on vigane, palun sisesta uuesti ja proovi veelkord."
706
+
707
+ #: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:118
708
+ msgid "Please enter your card number and try again."
709
+ msgstr "Palun sisesta oma kaardi number ja proovi uuesti."
710
+
711
+ #: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:121
712
+ msgid "The card expiration date is invalid, please re-enter and try again."
713
+ msgstr ""
714
+ "Kaardi aegumiskuupäev on vale, palun sisesta uuesti ja proovi veelkord."
715
+
716
+ #: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:122
717
+ msgid "The card expiration month is invalid, please re-enter and try again."
718
+ msgstr "Kaardi aegumise kuu on vale, palun sisesta uuesti ja proovi veelkord."
719
+
720
+ #: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:123
721
+ msgid "The card expiration year is invalid, please re-enter and try again."
722
+ msgstr ""
723
+ "Kaardi aegumise aasta on vale, palun sisesta uuesti ja proovi veelkord."
724
+
725
+ #: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:124
726
+ msgid "Please enter your card expiration date and try again."
727
+ msgstr "Palun sisesta oma kaardi aegumiskuupäev ja proovi uuesti."
728
+
729
+ #: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:127
730
+ msgid "The bank routing number is invalid, please re-enter and try again."
731
+ msgstr ""
732
+ "Panga suunakood ei ole korrektne, palun sisesta uuesti ja proovi veelkord."
733
+
734
+ #: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:128
735
+ msgid "The bank account number is invalid, please re-enter and try again."
736
+ msgstr ""
737
+ "Pangakonto number ei ole korrektne, palun sisesta uuesti ja proovi veelkord."
738
+
739
+ #: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:131
740
+ msgid ""
741
+ "The provided card is expired, please use an alternate card or other form of "
742
+ "payment."
743
+ msgstr ""
744
+ "Antud kaart on aegunud, palun kasuta mõnda teist kaarti või teistsugust "
745
+ "makseviisi."
746
+
747
+ #: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:132
748
+ msgid ""
749
+ "The provided card was declined, please use an alternate card or other form "
750
+ "of payment."
751
+ msgstr ""
752
+ "Antud kaart klükati tagasi, palun kasuta mõnda teist kaarti või teistsugust "
753
+ "makseviisi."
754
+
755
+ #: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:133
756
+ msgid ""
757
+ "Insufficient funds in account, please use an alternate card or other form of "
758
+ "payment."
759
+ msgstr ""
760
+ "Kontol pole piisavalt vahendeid, palun kasuta mõnda teist kaarti või "
761
+ "teistsugust makseviisi."
762
+
763
+ #: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:134
764
+ msgid ""
765
+ "The card is inactivate or not authorized for card-not-present transactions, "
766
+ "please use an alternate card or other form of payment."
767
+ msgstr ""
768
+ "Antud kaart ei ole aktiveeritud või ei ole sellega internetimaksed lubatud. "
769
+ "Palun kasuta mõnda teist kaarti või teistsugust makseviisi."
770
+
771
+ #: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:135
772
+ msgid ""
773
+ "The credit limit for the card has been reached, please use an alternate card "
774
+ "or other form of payment."
775
+ msgstr ""
776
+ "Kaardi krediitilimiit on ära kasutatud, palun kasuta mõnda teist kaarti või "
777
+ "teistsugust makseviisi."
778
+
779
+ #: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:136
780
+ msgid ""
781
+ "The card verification number does not match. Please re-enter and try again."
782
+ msgstr "Kaardi turvakood ei klapi. Palun sisesta uuesti ja proovi veelkord."
783
+
784
+ #: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:137
785
+ msgid ""
786
+ "The provided address does not match the billing address for cardholder. "
787
+ "Please verify the address and try again."
788
+ msgstr ""
789
+ "Antud aadress ei kattu kaardi omaniku aadressiga. Palun kontrolli, et "
790
+ "sisestaid õige aadressi ning proovi uuesti."
791
+
792
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:84
793
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:102
794
+ msgid "Apple Pay"
795
+ msgstr ""
796
+
797
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:108
798
+ #: payment-gateway/class-sv-wc-payment-gateway.php:1142
799
+ msgid "Enable / Disable"
800
+ msgstr "Luba / Keela"
801
+
802
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:109
803
+ msgid "Accept Apple Pay"
804
+ msgstr ""
805
+
806
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:116
807
+ msgid "Allow Apple Pay on"
808
+ msgstr ""
809
+
810
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:126
811
+ msgid "Button Style"
812
+ msgstr ""
813
+
814
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:129
815
+ msgid "Black"
816
+ msgstr ""
817
+
818
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:130
819
+ msgid "White"
820
+ msgstr ""
821
+
822
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:131
823
+ msgid "White with outline"
824
+ msgstr ""
825
+
826
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:143
827
+ #: payment-gateway/class-sv-wc-payment-gateway.php:1317
828
+ msgid "Connection Settings"
829
+ msgstr ""
830
+
831
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:149
832
+ msgid "Apple Merchant ID"
833
+ msgstr ""
834
+
835
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:153
836
+ msgid "This is found in your %1$sApple developer account%2$s"
837
+ msgstr ""
838
+
839
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:160
840
+ msgid "Certificate Path"
841
+ msgstr ""
842
+
843
+ #. translators: Placeholders: %s - the server's web root path
844
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:165
845
+ msgid "For reference, your current web root path is: %s"
846
+ msgstr ""
847
+
848
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:178
849
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:188
850
+ msgid "Processing Gateway"
851
+ msgstr ""
852
+
853
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:196
854
+ #, fuzzy
855
+ msgid "Test Mode"
856
+ msgstr "Veaotsingu režiim"
857
+
858
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:197
859
+ msgid ""
860
+ "Enable to test Apple Pay functionality throughout your sites without "
861
+ "processing real payments."
862
+ msgstr ""
863
+
864
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:306
865
+ msgid "Your site must be served over HTTPS with a valid SSL certificate."
866
+ msgstr ""
867
+
868
+ #. translators: Placeholders: %1$s - plugin name, %2$s - a
869
+ #. currency/comma-separated list of currencies, %3$s - <a> tag, %4$s - </a> tag
870
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:316
871
+ msgid ""
872
+ "Accepts payment in %1$s only. %2$sConfigure%3$s WooCommerce to accept %1$s "
873
+ "to enable Apple Pay."
874
+ msgid_plural ""
875
+ "Accepts payment in one of %1$s only. %2$sConfigure%3$s WooCommerce to accept "
876
+ "one of %1$s to enable Apple Pay."
877
+ msgstr[0] ""
878
+ msgstr[1] ""
879
+
880
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:335
881
+ msgid ""
882
+ "Your %1$sMerchant Identity Certificate%2$s cannot be found. Please check "
883
+ "your path configuration."
884
+ msgstr ""
885
+
886
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:342
887
+ msgid "Apple Pay is disabled."
888
+ msgstr ""
889
+
890
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:381
891
+ msgid "Single products"
892
+ msgstr ""
893
+
894
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:382
895
+ msgid "Cart"
896
+ msgstr ""
897
+
898
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:383
899
+ #, fuzzy
900
+ msgid "Checkout"
901
+ msgstr "E-tšekk"
902
+
903
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-frontend.php:169
904
+ msgid "Buy with"
905
+ msgstr ""
906
+
907
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-frontend.php:202
908
+ msgid ""
909
+ "By submitting your payment, you agree to our %1$sterms and conditions%2$s."
910
+ msgstr ""
911
+
912
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-frontend.php:351
913
+ msgid "or"
914
+ msgstr ""
915
+
916
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-orders.php:123
917
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-orders.php:136
918
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-orders.php:140
919
+ msgid "Error %d: Unable to create order. Please try again."
920
+ msgstr ""
921
+
922
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php:121
923
+ msgid "Apple Pay payment authorized."
924
+ msgstr ""
925
+
926
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php:155
927
+ #, fuzzy
928
+ msgid "Apple Pay payment failed. %s"
929
+ msgstr "%1$s: makse ebaõnnestus (%2$s)"
930
+
931
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php:532
932
+ msgid "Subtotal"
933
+ msgstr ""
934
+
935
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php:542
936
+ #, fuzzy
937
+ msgid "Discount"
938
+ msgstr "Konto"
939
+
940
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php:552
941
+ msgid "Shipping"
942
+ msgstr ""
943
+
944
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php:562
945
+ msgid "Fees"
946
+ msgstr ""
947
+
948
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php:572
949
+ msgid "Taxes"
950
+ msgstr ""
951
+
952
+ #: payment-gateway/class-sv-wc-payment-gateway-direct.php:61
953
+ msgid ""
954
+ "Payment error, please try another payment method or contact us to complete "
955
+ "your transaction."
956
+ msgstr ""
957
+ "Viga maksega, palun proovi teistsugust makseviisi või võta meiega ühendust."
958
+
959
+ #: payment-gateway/class-sv-wc-payment-gateway-direct.php:161
960
+ #: payment-gateway/class-sv-wc-payment-gateway.php:480
961
+ msgid "Card expiration date is invalid"
962
+ msgstr "Kaardi aegumiskuupäev ei ole korrektne"
963
+
964
+ #: payment-gateway/class-sv-wc-payment-gateway-direct.php:185
965
+ #: payment-gateway/class-sv-wc-payment-gateway.php:473
966
+ msgid "Card number is missing"
967
+ msgstr "Kaardi number on puudu"
968
+
969
+ #: payment-gateway/class-sv-wc-payment-gateway-direct.php:191
970
+ #: payment-gateway/class-sv-wc-payment-gateway.php:476
971
+ msgid "Card number is invalid (wrong length)"
972
+ msgstr "Kaardi number ei ole korrektne (pikkus on vale)"
973
+
974
+ #: payment-gateway/class-sv-wc-payment-gateway-direct.php:196
975
+ #: payment-gateway/class-sv-wc-payment-gateway.php:475
976
+ msgid "Card number is invalid (only digits allowed)"
977
+ msgstr "Kaardi number ei ole korrektne (lubatud on ainult numbrid)"
978
+
979
+ #: payment-gateway/class-sv-wc-payment-gateway-direct.php:201
980
+ #: payment-gateway/class-sv-wc-payment-gateway.php:474
981
+ msgid "Card number is invalid"
982
+ msgstr "Kaardi number ei ole korrektne"
983
+
984
+ #: payment-gateway/class-sv-wc-payment-gateway-direct.php:228
985
+ #: payment-gateway/class-sv-wc-payment-gateway.php:478
986
+ msgid "Card security code is invalid (only digits are allowed)"
987
+ msgstr "Kaardi turvakood ei ole korrektne (lubatud on ainult numbrid)"
988
+
989
+ #: payment-gateway/class-sv-wc-payment-gateway-direct.php:234
990
+ #: payment-gateway/class-sv-wc-payment-gateway.php:479
991
+ msgid "Card security code is invalid (must be 3 or 4 digits)"
992
+ msgstr "Kaardi turvakood ei ole korrektne (peab olema 3 või 4 numbrit)"
993
+
994
+ #: payment-gateway/class-sv-wc-payment-gateway-direct.php:240
995
+ #: payment-gateway/class-sv-wc-payment-gateway.php:477
996
+ msgid "Card security code is missing"
997
+ msgstr "Kaardi turvakood on puudu"
998
+
999
+ #: payment-gateway/class-sv-wc-payment-gateway-direct.php:267
1000
+ #: payment-gateway/class-sv-wc-payment-gateway.php:489
1001
+ msgid "Routing Number is missing"
1002
+ msgstr "Suunakood on puudu"
1003
+
1004
+ #: payment-gateway/class-sv-wc-payment-gateway-direct.php:274
1005
+ #: payment-gateway/class-sv-wc-payment-gateway.php:490
1006
+ msgid "Routing Number is invalid (only digits are allowed)"
1007
+ msgstr "Suunakood ei ole korrektne (lubatud on ainult numbrid)"
1008
+
1009
+ #: payment-gateway/class-sv-wc-payment-gateway-direct.php:280
1010
+ #: payment-gateway/class-sv-wc-payment-gateway.php:491
1011
+ msgid "Routing number is invalid (must be 9 digits)"
1012
+ msgstr "Suunakood ei ole korrektne (peab olemas 9 numbrit)"
1013
+
1014
+ #: payment-gateway/class-sv-wc-payment-gateway-direct.php:289
1015
+ #: payment-gateway/class-sv-wc-payment-gateway.php:486
1016
+ msgid "Account Number is missing"
1017
+ msgstr "Konto number on puudu"
1018
+
1019
+ #: payment-gateway/class-sv-wc-payment-gateway-direct.php:296
1020
+ #: payment-gateway/class-sv-wc-payment-gateway.php:487
1021
+ msgid "Account Number is invalid (only digits are allowed)"
1022
+ msgstr "Konto number ei ole korrektne (lubatud on ainult numbrid)"
1023
+
1024
+ #: payment-gateway/class-sv-wc-payment-gateway-direct.php:302
1025
+ #: payment-gateway/class-sv-wc-payment-gateway.php:488
1026
+ msgid "Account number is invalid (must be between 5 and 17 digits)"
1027
+ msgstr "Konto number ei ole korrektne (peab olemas 5-17 numbrit)"
1028
+
1029
+ #: payment-gateway/class-sv-wc-payment-gateway-direct.php:309
1030
+ #: payment-gateway/class-sv-wc-payment-gateway.php:485
1031
+ msgid "Drivers license number is invalid"
1032
+ msgstr "Juhiloa number ei ole korrektne"
1033
+
1034
+ #: payment-gateway/class-sv-wc-payment-gateway-direct.php:315
1035
+ #: payment-gateway/class-sv-wc-payment-gateway.php:481
1036
+ msgid "Check Number is invalid (only digits are allowed)"
1037
+ msgstr "TÅ¡eki number ei ole korrektne (lubatud on ainult numbrid)"
1038
+
1039
+ #: payment-gateway/class-sv-wc-payment-gateway-direct.php:494
1040
+ #, fuzzy
1041
+ msgid "Unknown error"
1042
+ msgstr "Esines tundmatu viga"
1043
+
1044
+ #: payment-gateway/class-sv-wc-payment-gateway-direct.php:503
1045
+ #, fuzzy
1046
+ msgid "Payment method address could not be updated. %s"
1047
+ msgstr "Maksevahend kustutatud."
1048
+
1049
+ #. translators: Placeholders: %1$s - payment method title, %2$s - payment
1050
+ #. account type (savings/checking) (may or may not be available), %3$s - last
1051
+ #. four digits of the account
1052
+ #: payment-gateway/class-sv-wc-payment-gateway-direct.php:670
1053
+ #: payment-gateway/class-sv-wc-payment-gateway.php:2614
1054
+ msgid "%1$s Check Transaction Approved: %2$s account ending in %3$s"
1055
+ msgstr "%1$s: tšeki tehing vastu võetud: %2$s konto, lõpeb numbritega %3$s"
1056
+
1057
+ #. translators: Placeholders: %s - check number
1058
+ #: payment-gateway/class-sv-wc-payment-gateway-direct.php:675
1059
+ #: payment-gateway/class-sv-wc-payment-gateway.php:2619
1060
+ msgid "Check number %s"
1061
+ msgstr "TÅ¡eki number %s"
1062
+
1063
+ #. translators: Placeholders: %1$s - payment method title, %2$s - environment
1064
+ #. ("Test"), %3$s - transaction type (authorization/charge), %4$s - card type
1065
+ #. (mastercard, visa, ...), %5$s - last four digits of the card
1066
+ #: payment-gateway/class-sv-wc-payment-gateway-direct.php:744
1067
+ #, fuzzy
1068
+ msgid "%1$s %2$s %3$s Approved: %4$s ending in %5$s"
1069
+ msgstr "%1$s %2$s: %3$s kinnitatud: %4$s lõpeb numbritega %5$s (aegub %6$s)"
1070
+
1071
+ #. translators: Placeholders: %s - expiry date
1072
+ #: payment-gateway/class-sv-wc-payment-gateway-direct.php:757
1073
+ #: payment-gateway/class-sv-wc-payment-gateway-payment-form.php:686
1074
+ #: payment-gateway/class-sv-wc-payment-gateway.php:2571
1075
+ msgid "(expires %s)"
1076
+ msgstr "(aegub %s)"
1077
+
1078
+ #. translators: Placeholders: %s - failure message
1079
+ #: payment-gateway/class-sv-wc-payment-gateway-direct.php:829
1080
+ msgid "Tokenization Request Failed: %s"
1081
+ msgstr "Maksevahendi salvestamise päring ebaõnnestus: %s"
1082
+
1083
+ #. translators: Placeholders: %1$s - payment method title, %2$s - failure
1084
+ #. message
1085
+ #: payment-gateway/class-sv-wc-payment-gateway-direct.php:840
1086
+ msgid "%1$s Tokenization Request Failed: %2$s"
1087
+ msgstr "%1$s: maksevahendi salvestamise päring ebaõnnestus: %2$s"
1088
+
1089
+ #. translators: Placeholders: %s - failure message. Payment method as in a
1090
+ #. specific credit card, e-check or bank account
1091
+ #: payment-gateway/class-sv-wc-payment-gateway-direct.php:898
1092
+ msgid "Oops, adding your new payment method failed: %s"
1093
+ msgstr "Oih, sinu maksevahendi lisamine ebaõnnestus: %s"
1094
+
1095
+ #. translators: Payment method as in a specific credit card. Placeholders: %1$s
1096
+ #. - card type (visa, mastercard, ...), %2$s - last four digits of the card,
1097
+ #. %3$s - card expiry date
1098
+ #: payment-gateway/class-sv-wc-payment-gateway-direct.php:939
1099
+ msgid "Nice! New payment method added: %1$s ending in %2$s (expires %3$s)"
1100
+ msgstr ""
1101
+ "Lahe! Uus maksevahend lisatud: %1$s, lõpeb numbritega %2$s (aegub %3$s)"
1102
+
1103
+ #. translators: Payment method as in a specific e-check account. Placeholders:
1104
+ #. %1$s - account type (checking/savings), %2$s - last four digits of the
1105
+ #. account
1106
+ #: payment-gateway/class-sv-wc-payment-gateway-direct.php:949
1107
+ msgid "Nice! New payment method added: %1$s account ending in %2$s"
1108
+ msgstr "Lahe! Uus maksevahend lisatud: %1$s konto, lõpeb numbritega %2$s"
1109
+
1110
+ #. translators: Payment method as in a specific credit card, e-check or bank
1111
+ #. account
1112
+ #: payment-gateway/class-sv-wc-payment-gateway-direct.php:956
1113
+ msgid "Nice! New payment method added."
1114
+ msgstr "Lahe! Uus maksevahend lisatud."
1115
+
1116
+ #. translators: Placeholders: %1$s - site title, %2$s - customer email. Payment
1117
+ #. method as in a specific credit card, e-check or bank account
1118
+ #: payment-gateway/class-sv-wc-payment-gateway-direct.php:1079
1119
+ msgid "%1$s - Add Payment Method for %2$s"
1120
+ msgstr "%1$s - Lisa maksevahend kliendile %2$s"
1121
+
1122
+ #: payment-gateway/class-sv-wc-payment-gateway-helper.php:180
1123
+ msgid "PayPal"
1124
+ msgstr "PayPal"
1125
+
1126
+ #: payment-gateway/class-sv-wc-payment-gateway-helper.php:181
1127
+ msgid "Checking Account"
1128
+ msgstr "TÅ¡ekikonto"
1129
+
1130
+ #: payment-gateway/class-sv-wc-payment-gateway-helper.php:182
1131
+ msgid "Savings Account"
1132
+ msgstr "Hoiuarve"
1133
+
1134
+ #: payment-gateway/class-sv-wc-payment-gateway-helper.php:183
1135
+ msgid "Credit / Debit Card"
1136
+ msgstr "Deebet- või krediitkaart"
1137
+
1138
+ #: payment-gateway/class-sv-wc-payment-gateway-helper.php:184
1139
+ msgid "Bank Account"
1140
+ msgstr "Pangakonto"
1141
+
1142
+ #: payment-gateway/class-sv-wc-payment-gateway-hosted.php:297
1143
+ msgid "Thank you for your order, please click the button below to pay."
1144
+ msgstr "Aitäh tellimuse eest. Palun kliki maksmiseks alloleval nupul."
1145
+
1146
+ #: payment-gateway/class-sv-wc-payment-gateway-hosted.php:298
1147
+ msgid ""
1148
+ "Thank you for your order. We are now redirecting you to complete payment."
1149
+ msgstr "Aitäh tellimuse eest. Makse teostamiseks suunatakse sind nüüd edasi."
1150
+
1151
+ #: payment-gateway/class-sv-wc-payment-gateway-hosted.php:299
1152
+ msgid "Pay Now"
1153
+ msgstr "Maksa"
1154
+
1155
+ #: payment-gateway/class-sv-wc-payment-gateway-hosted.php:300
1156
+ msgid "Cancel Order"
1157
+ msgstr "Tühista tellimus"
1158
+
1159
+ #. translators: Placeholders: %1$s - payment gateway title (such as
1160
+ #. Authorize.net, Braintree, etc), %2$s - payment method name (mastercard, bank
1161
+ #. account, etc), %3$s - last four digits of the card/account, %4$s -
1162
+ #. card/account expiry date
1163
+ #: payment-gateway/class-sv-wc-payment-gateway-hosted.php:597
1164
+ #: payment-gateway/payment-tokens/class-sv-wc-payment-gateway-payment-tokens-handler.php:834
1165
+ msgid "%1$s Payment Method Saved: %2$s ending in %3$s (expires %4$s)"
1166
+ msgstr ""
1167
+ "%1$s: maksevahend salvestatud: %2$s lõpeb numbritega in %3$s (aegub %4$s)"
1168
+
1169
+ #. translators: Placeholders: %1$s - payment gateway title (such as CyberSouce,
1170
+ #. NETbilling, etc), %2$s - account type (checking/savings - may or may not be
1171
+ #. available), %3$s - last four digits of the account
1172
+ #: payment-gateway/class-sv-wc-payment-gateway-hosted.php:608
1173
+ #: payment-gateway/payment-tokens/class-sv-wc-payment-gateway-payment-tokens-handler.php:845
1174
+ msgid "%1$s eCheck Payment Method Saved: %2$s account ending in %3$s"
1175
+ msgstr ""
1176
+ "%1$s: e-tšeki maksevahend salvestatud: %2$s konto, lõpeb numbritega %3$s"
1177
+
1178
+ #. translators: Placeholders: %s - payment gateway title (such as CyberSouce,
1179
+ #. NETbilling, etc)
1180
+ #: payment-gateway/class-sv-wc-payment-gateway-hosted.php:617
1181
+ #, fuzzy
1182
+ msgid "%s Payment Method Saved"
1183
+ msgstr "Minu maksevahendid."
1184
+
1185
+ #. translators: Placeholders: %s - a failed tokenization API error
1186
+ #: payment-gateway/class-sv-wc-payment-gateway-hosted.php:626
1187
+ #, fuzzy
1188
+ msgid "Tokenization failed. %s"
1189
+ msgstr "Maksevahendi salvestamise päring ebaõnnestus: %s"
1190
+
1191
+ #: payment-gateway/class-sv-wc-payment-gateway-my-payment-methods.php:228
1192
+ #: payment-gateway/class-sv-wc-payment-gateway-my-payment-methods.php:761
1193
+ msgid "Edit"
1194
+ msgstr ""
1195
+
1196
+ #: payment-gateway/class-sv-wc-payment-gateway-my-payment-methods.php:230
1197
+ #, fuzzy
1198
+ msgid ""
1199
+ "Oops, there was an error updating your payment method. Please try again."
1200
+ msgstr "Sinu päringuga esines viga, palun proovi uuesti."
1201
+
1202
+ #: payment-gateway/class-sv-wc-payment-gateway-my-payment-methods.php:231
1203
+ msgid "Are you sure you want to delete this payment method?"
1204
+ msgstr "Oled sa kindel, et soovid selle maksevahendi kustutada?"
1205
+
1206
+ #. translators: Payment method as in a specific credit card, eCheck or bank
1207
+ #. account
1208
+ #: payment-gateway/class-sv-wc-payment-gateway-my-payment-methods.php:290
1209
+ msgid "You do not have any saved payment methods."
1210
+ msgstr "Sul ei ole salvestatud maksevahendeid."
1211
+
1212
+ #. translators: Payment method as in a specific credit card, eCheck or bank
1213
+ #. account
1214
+ #: payment-gateway/class-sv-wc-payment-gateway-my-payment-methods.php:329
1215
+ msgid "My Payment Methods"
1216
+ msgstr "Minu maksevahendid."
1217
+
1218
+ #. translators: Payment method as in a specific credit card, e-check or bank
1219
+ #. account
1220
+ #: payment-gateway/class-sv-wc-payment-gateway-my-payment-methods.php:337
1221
+ msgid "Add New Payment Method"
1222
+ msgstr "Lisa uus maksevahend"
1223
+
1224
+ #: payment-gateway/class-sv-wc-payment-gateway-my-payment-methods.php:425
1225
+ msgid "Method"
1226
+ msgstr "Maksevahend"
1227
+
1228
+ #: payment-gateway/class-sv-wc-payment-gateway-my-payment-methods.php:426
1229
+ msgid "Details"
1230
+ msgstr ""
1231
+
1232
+ #: payment-gateway/class-sv-wc-payment-gateway-my-payment-methods.php:427
1233
+ msgid "Expires"
1234
+ msgstr "Aegub"
1235
+
1236
+ #: payment-gateway/class-sv-wc-payment-gateway-my-payment-methods.php:428
1237
+ #, fuzzy
1238
+ msgid "Default?"
1239
+ msgstr "(vaikimisi)"
1240
+
1241
+ #: payment-gateway/class-sv-wc-payment-gateway-my-payment-methods.php:429
1242
+ msgid "Actions"
1243
+ msgstr ""
1244
+
1245
+ #: payment-gateway/class-sv-wc-payment-gateway-my-payment-methods.php:465
1246
+ msgid "Credit/Debit Cards"
1247
+ msgstr "Deebet- ja krediitkaardid"
1248
+
1249
+ #: payment-gateway/class-sv-wc-payment-gateway-my-payment-methods.php:474
1250
+ msgid "Bank Accounts"
1251
+ msgstr "Pangakontod"
1252
+
1253
+ #: payment-gateway/class-sv-wc-payment-gateway-my-payment-methods.php:529
1254
+ msgid "N/A"
1255
+ msgstr "-"
1256
+
1257
+ #: payment-gateway/class-sv-wc-payment-gateway-my-payment-methods.php:641
1258
+ #: payment-gateway/class-sv-wc-payment-gateway-privacy.php:200
1259
+ msgid "Nickname"
1260
+ msgstr ""
1261
+
1262
+ #: payment-gateway/class-sv-wc-payment-gateway-my-payment-methods.php:822
1263
+ msgid "Delete"
1264
+ msgstr "Kustuta"
1265
+
1266
+ #: payment-gateway/class-sv-wc-payment-gateway-my-payment-methods.php:976
1267
+ msgid "Oops, you took too long, please try again."
1268
+ msgstr "Oih, sul läks liiga kaua aega - palun proovi uuesti."
1269
+
1270
+ #: payment-gateway/class-sv-wc-payment-gateway-my-payment-methods.php:989
1271
+ msgid "There was an error with your request, please try again."
1272
+ msgstr "Sinu päringuga esines viga, palun proovi uuesti."
1273
+
1274
+ #. translators: Payment method as in a specific credit card, e-check or bank
1275
+ #. account
1276
+ #: payment-gateway/class-sv-wc-payment-gateway-my-payment-methods.php:1002
1277
+ msgid "Error removing payment method"
1278
+ msgstr "Viga maksevahendi eemaldamisel"
1279
+
1280
+ #. translators: Payment method as in a specific credit card, e-check or bank
1281
+ #. account
1282
+ #: payment-gateway/class-sv-wc-payment-gateway-my-payment-methods.php:1007
1283
+ msgid "Payment method deleted."
1284
+ msgstr "Maksevahend kustutatud."
1285
+
1286
+ #: payment-gateway/class-sv-wc-payment-gateway-payment-form.php:313
1287
+ msgid "Card Number"
1288
+ msgstr "Kaardi number"
1289
+
1290
+ #: payment-gateway/class-sv-wc-payment-gateway-payment-form.php:334
1291
+ msgid "MM / YY"
1292
+ msgstr "KK / AA"
1293
+
1294
+ #: payment-gateway/class-sv-wc-payment-gateway-payment-form.php:352
1295
+ msgid "Card Security Code"
1296
+ msgstr "Kaardi turvakood"
1297
+
1298
+ #: payment-gateway/class-sv-wc-payment-gateway-payment-form.php:355
1299
+ msgid "CSC"
1300
+ msgstr "Turvakood"
1301
+
1302
+ #: payment-gateway/class-sv-wc-payment-gateway-payment-form.php:395
1303
+ msgid "Where do I find this?"
1304
+ msgstr "Kust ma selle leian?"
1305
+
1306
+ #. translators: e-check routing number, HTML form field label,
1307
+ #. https:en.wikipedia.org/wiki/Routing_transit_number
1308
+ #: payment-gateway/class-sv-wc-payment-gateway-payment-form.php:401
1309
+ msgid "Routing Number"
1310
+ msgstr "Suunakood"
1311
+
1312
+ #. translators: e-check account number, HTML form field label
1313
+ #: payment-gateway/class-sv-wc-payment-gateway-payment-form.php:420
1314
+ msgid "Account Number"
1315
+ msgstr "Kontonumber"
1316
+
1317
+ #. translators: Test mode refers to the current software environment
1318
+ #: payment-gateway/class-sv-wc-payment-gateway-payment-form.php:486
1319
+ msgid "TEST MODE ENABLED"
1320
+ msgstr "TESTREŽIIM SISSE LÜLITATUD"
1321
+
1322
+ #: payment-gateway/class-sv-wc-payment-gateway-payment-form.php:513
1323
+ #, fuzzy
1324
+ msgid "Sample Check"
1325
+ msgstr "E-tšekk"
1326
+
1327
+ #. translators: Payment method as in a specific credit card, eCheck or bank
1328
+ #. account
1329
+ #: payment-gateway/class-sv-wc-payment-gateway-payment-form.php:588
1330
+ msgid "Manage Payment Methods"
1331
+ msgstr "Halda maksevahendeid"
1332
+
1333
+ #: payment-gateway/class-sv-wc-payment-gateway-payment-form.php:725
1334
+ msgid "Use a new card"
1335
+ msgstr "Kasuta uut kaarti"
1336
+
1337
+ #: payment-gateway/class-sv-wc-payment-gateway-payment-form.php:725
1338
+ msgid "Use a new bank account"
1339
+ msgstr "Kasuta uut pangakontot"
1340
+
1341
+ #. translators: account as in customer's account on the eCommerce site
1342
+ #: payment-gateway/class-sv-wc-payment-gateway-payment-form.php:788
1343
+ msgid "Securely Save to Account"
1344
+ msgstr "Salvesta turvaliselt oma kontole"
1345
+
1346
+ #: payment-gateway/class-sv-wc-payment-gateway-payment-form.php:919
1347
+ #, fuzzy
1348
+ msgid "Payment Info"
1349
+ msgstr "Maksevahendid"
1350
+
1351
+ #. translators: Placeholders: %1$s - plugin name, %2$s - <a> tag, %3$s - </a>
1352
+ #. tag
1353
+ #: payment-gateway/class-sv-wc-payment-gateway-plugin.php:617
1354
+ #, fuzzy
1355
+ msgid ""
1356
+ "%1$s: WooCommerce is not being forced over SSL; your customers' payment data "
1357
+ "may be at risk. %2$sVerify your site URLs here%3$s"
1358
+ msgstr ""
1359
+ "%s: WooCommerce'i ei sunnita SSLi kasutama; sinu klientide andmed võivad "
1360
+ "olla ohus."
1361
+
1362
+ #. translators: Placeholders: %s - payment gateway name
1363
+ #: payment-gateway/class-sv-wc-payment-gateway-plugin.php:634
1364
+ msgid ""
1365
+ "%s will soon require TLS 1.2 support to process transactions and your server "
1366
+ "environment may need to be updated. Please contact your hosting provider to "
1367
+ "confirm that your site can send and receive TLS 1.2 connections and request "
1368
+ "they make any necessary updates."
1369
+ msgstr ""
1370
+
1371
+ #. translators: Placeholders: %1$s - plugin name, %2$s - a
1372
+ #. currency/comma-separated list of currencies, %3$s - <a> tag, %4$s - </a> tag
1373
+ #: payment-gateway/class-sv-wc-payment-gateway-plugin.php:690
1374
+ msgid ""
1375
+ "%1$s accepts payment in %2$s only. %3$sConfigure%4$s WooCommerce to accept "
1376
+ "%2$s to enable this gateway for checkout."
1377
+ msgid_plural ""
1378
+ "%1$s accepts payment in one of %2$s only. %3$sConfigure%4$s WooCommerce to "
1379
+ "accept one of %2$s to enable this gateway for checkout."
1380
+ msgstr[0] ""
1381
+ msgstr[1] ""
1382
+
1383
+ #. translators: Placeholders: %1$s - payment gateway name, %2$s - opening <a>
1384
+ #. tag, %3$s - closing </a> tag
1385
+ #: payment-gateway/class-sv-wc-payment-gateway-plugin.php:725
1386
+ msgid ""
1387
+ "Heads up! %1$s is currently configured to log transaction data for debugging "
1388
+ "purposes. If you are not experiencing any problems with payment processing, "
1389
+ "we recommend %2$sturning off Debug Mode%3$s"
1390
+ msgstr ""
1391
+
1392
+ #. translators: Placeholders: %1$s - plugin name, %2$s - opening <a> HTML link
1393
+ #. tag, %3$s - closing </a> HTML link tag
1394
+ #: payment-gateway/class-sv-wc-payment-gateway-plugin.php:771
1395
+ msgid ""
1396
+ "Heads up! Apple Pay for %1$s requires WooCommerce version 3.2 or greater. "
1397
+ "Please %2$supdate WooCommerce%3$s."
1398
+ msgstr ""
1399
+
1400
+ #. translators: Placeholders: %1$s - payment gateway title (such as
1401
+ #. Authorize.net, Braintree, etc), %2$s - <a> tag, %3$s - </a> tag
1402
+ #: payment-gateway/class-sv-wc-payment-gateway-plugin.php:807
1403
+ msgid ""
1404
+ "%1$s is inactive for subscription transactions. Please %2$senable "
1405
+ "tokenization%3$s to activate %1$s for Subscriptions."
1406
+ msgstr ""
1407
+ "%1$s ei ole korduvtellimuste jaoks kasutatav. Palun %2$slülita "
1408
+ "maksevahendite salvestamine%3$s sisse, et aktiveerida %1$s Korduvellimuste "
1409
+ "(Subscriptions) jaoks."
1410
+
1411
+ #. translators: Placeholders: %1$s - payment gateway title (such as
1412
+ #. Authorize.net, Braintree, etc), %2$s - <a> tag, %3$s - </a> tag
1413
+ #: payment-gateway/class-sv-wc-payment-gateway-plugin.php:825
1414
+ msgid ""
1415
+ "%1$s is inactive for pre-order transactions. Please %2$senable tokenization"
1416
+ "%3$s to activate %1$s for Pre-Orders."
1417
+ msgstr ""
1418
+ "%1$s ei ole eeltellimuste maksete jaoks kasutatav. Palun %2$slülita "
1419
+ "maksevahendite salvestamine%3$s sisse, et aktiveerida %1$s Eeltellimuste "
1420
+ "(Pre-Orders) jaoks."
1421
+
1422
+ #: payment-gateway/class-sv-wc-payment-gateway-plugin.php:862
1423
+ msgid ""
1424
+ "You must enable tokenization for this gateway in order to support automatic "
1425
+ "renewal payments with the WooCommerce Subscriptions extension."
1426
+ msgstr ""
1427
+
1428
+ #: payment-gateway/class-sv-wc-payment-gateway-plugin.php:863
1429
+ msgid "Inactive"
1430
+ msgstr ""
1431
+
1432
+ #: payment-gateway/class-sv-wc-payment-gateway-privacy.php:115
1433
+ #, fuzzy
1434
+ msgid "%s Customer ID"
1435
+ msgstr "Kliendi ID"
1436
+
1437
+ #: payment-gateway/class-sv-wc-payment-gateway-privacy.php:184
1438
+ #, fuzzy
1439
+ msgid "Type"
1440
+ msgstr "Kaardi tüüp"
1441
+
1442
+ #: payment-gateway/class-sv-wc-payment-gateway-privacy.php:254
1443
+ msgid "Removed payment token \"%d\""
1444
+ msgstr ""
1445
+
1446
+ #: payment-gateway/class-sv-wc-payment-gateway-privacy.php:301
1447
+ #, fuzzy
1448
+ msgid "Expiry Date"
1449
+ msgstr "Aegumiskuupäev (01/%s)"
1450
+
1451
+ #: payment-gateway/class-sv-wc-payment-gateway.php:341
1452
+ msgid "you successfully processed a payment!"
1453
+ msgstr ""
1454
+
1455
+ #: payment-gateway/class-sv-wc-payment-gateway.php:346
1456
+ msgid "you successfully processed a refund!"
1457
+ msgstr ""
1458
+
1459
+ #: payment-gateway/class-sv-wc-payment-gateway.php:482
1460
+ msgid "Check Number is missing"
1461
+ msgstr "TÅ¡eki number on puudu"
1462
+
1463
+ #: payment-gateway/class-sv-wc-payment-gateway.php:483
1464
+ msgid "Drivers license state is missing"
1465
+ msgstr "Juhiloa osariik on puudu"
1466
+
1467
+ #: payment-gateway/class-sv-wc-payment-gateway.php:484
1468
+ msgid "Drivers license number is missing"
1469
+ msgstr "Juhiloa number on puudu"
1470
+
1471
+ #: payment-gateway/class-sv-wc-payment-gateway.php:659
1472
+ msgid "Continue to Payment"
1473
+ msgstr ""
1474
+
1475
+ #: payment-gateway/class-sv-wc-payment-gateway.php:659
1476
+ msgid "Place order"
1477
+ msgstr "Esita tellimus"
1478
+
1479
+ #: payment-gateway/class-sv-wc-payment-gateway.php:691
1480
+ msgid "Thank you for your order."
1481
+ msgstr "Aitäh tellimuse eest."
1482
+
1483
+ #: payment-gateway/class-sv-wc-payment-gateway.php:1101
1484
+ msgid "Credit Card"
1485
+ msgstr "Krediitkaart"
1486
+
1487
+ #: payment-gateway/class-sv-wc-payment-gateway.php:1103
1488
+ msgid "eCheck"
1489
+ msgstr "E-tšekk"
1490
+
1491
+ #: payment-gateway/class-sv-wc-payment-gateway.php:1121
1492
+ msgid "Pay securely using your credit card."
1493
+ msgstr "Maksa turvaliselt oma krediitkaardiga."
1494
+
1495
+ #: payment-gateway/class-sv-wc-payment-gateway.php:1123
1496
+ msgid "Pay securely using your checking account."
1497
+ msgstr "Maksa turvaliselt oma tšekikontoga."
1498
+
1499
+ #: payment-gateway/class-sv-wc-payment-gateway.php:1143
1500
+ msgid "Enable this gateway"
1501
+ msgstr "Lülita see makseviis sisse"
1502
+
1503
+ #: payment-gateway/class-sv-wc-payment-gateway.php:1149
1504
+ msgid "Title"
1505
+ msgstr "Nimetus"
1506
+
1507
+ #: payment-gateway/class-sv-wc-payment-gateway.php:1151
1508
+ msgid "Payment method title that the customer will see during checkout."
1509
+ msgstr "Kliendile kassas nähtav makseviisi nimetus."
1510
+
1511
+ #: payment-gateway/class-sv-wc-payment-gateway.php:1156
1512
+ msgid "Description"
1513
+ msgstr "Kirjeldus"
1514
+
1515
+ #: payment-gateway/class-sv-wc-payment-gateway.php:1158
1516
+ msgid "Payment method description that the customer will see during checkout."
1517
+ msgstr "Kliendile kassas nähtav makseviisi kirjeldus."
1518
+
1519
+ #: payment-gateway/class-sv-wc-payment-gateway.php:1187
1520
+ msgid "Detailed Decline Messages"
1521
+ msgstr "Täpsemad maksest keeldumise teated"
1522
+
1523
+ #: payment-gateway/class-sv-wc-payment-gateway.php:1189
1524
+ msgid ""
1525
+ "Check to enable detailed decline messages to the customer during checkout "
1526
+ "when possible, rather than a generic decline message."
1527
+ msgstr ""
1528
+ "Lülita see valik sisse, kui soovid klientidele üldise maksest keeldumise "
1529
+ "teate asemel näidata võimaluse korral täpsemaid põhjusi."
1530
+
1531
+ #. translators: Placeholders: %1$s - <a> tag, %2$s - </a> tag
1532
+ #: payment-gateway/class-sv-wc-payment-gateway.php:1199
1533
+ msgid ""
1534
+ "Show Detailed Error Messages and API requests/responses on the checkout page "
1535
+ "and/or save them to the %1$sdebug log%2$s"
1536
+ msgstr ""
1537
+ "Näita üksikasjalikke veateateud ja API päringuid/vastuseid kassas ja/või "
1538
+ "salvesta need %1$slogifaili%2$s"
1539
+
1540
+ #: payment-gateway/class-sv-wc-payment-gateway.php:1203
1541
+ msgid "Show on Checkout Page"
1542
+ msgstr "Näita kassas"
1543
+
1544
+ #. translators: show debugging information on both checkout page and in the log
1545
+ #: payment-gateway/class-sv-wc-payment-gateway.php:1206
1546
+ msgid "Both"
1547
+ msgstr "Mõlemad"
1548
+
1549
+ #: payment-gateway/class-sv-wc-payment-gateway.php:1269
1550
+ msgid "Select the gateway environment to use for transactions."
1551
+ msgstr "Vali makseviisi tehingute teostamise keskkond."
1552
+
1553
+ #: payment-gateway/class-sv-wc-payment-gateway.php:1323
1554
+ msgid "Share connection settings"
1555
+ msgstr "Jaga ühenduse andmeid"
1556
+
1557
+ #: payment-gateway/class-sv-wc-payment-gateway.php:1325
1558
+ msgid "Use connection/authentication settings from other gateway"
1559
+ msgstr "Kasuta teise makseviisi ühenduse/autentimise seadeid"
1560
+
1561
+ #: payment-gateway/class-sv-wc-payment-gateway.php:1328
1562
+ msgid "Disabled because the other gateway is using these settings"
1563
+ msgstr "Ei saa muuta, kuna teine makseviis kasutab neid seadeid"
1564
+
1565
+ #: payment-gateway/class-sv-wc-payment-gateway.php:1345
1566
+ msgid "Card Verification (CSC)"
1567
+ msgstr "Kaardi turvakood (CSC)"
1568
+
1569
+ #: payment-gateway/class-sv-wc-payment-gateway.php:1346
1570
+ msgid "Display the Card Security Code (CV2) field on checkout"
1571
+ msgstr "Näita kassas kaardi turvakoodi (CV2) välja"
1572
+
1573
+ #: payment-gateway/class-sv-wc-payment-gateway.php:1354
1574
+ #, fuzzy
1575
+ msgid "Saved Card Verification"
1576
+ msgstr "Kaardi turvakood (CSC)"
1577
+
1578
+ #: payment-gateway/class-sv-wc-payment-gateway.php:1355
1579
+ #, fuzzy
1580
+ msgid "Display the Card Security Code field when paying with a saved card"
1581
+ msgstr "Näita kassas kaardi turvakoodi (CV2) välja"
1582
+
1583
+ #. translators: Placeholders: %1$s - site title, %2$s - order number
1584
+ #: payment-gateway/class-sv-wc-payment-gateway.php:1686
1585
+ msgid "%1$s - Order %2$s"
1586
+ msgstr "%1$s - Tellimus %2$s"
1587
+
1588
+ #. translators: Placeholders: %1$s - site title, %2$s - order number.
1589
+ #. Definitions: Capture as in capture funds from a credit card.
1590
+ #: payment-gateway/class-sv-wc-payment-gateway.php:1815
1591
+ msgid "%1$s - Capture for Order %2$s"
1592
+ msgstr "%1$s - Tasumine tellimuse %2$s eest"
1593
+
1594
+ #. translators: Placeholders: %1$s - site title, %2$s - order number
1595
+ #: payment-gateway/class-sv-wc-payment-gateway.php:1958
1596
+ msgid "%1$s - Refund for Order %2$s"
1597
+ msgstr "%1$s - Tagasimakse tellimuse %2$s eest"
1598
+
1599
+ #. translators: Placeholders: %1$s - payment gateway title (such as
1600
+ #. Authorize.net, Braintree, etc), %2$s - a monetary amount
1601
+ #: payment-gateway/class-sv-wc-payment-gateway.php:2025
1602
+ msgid "%1$s Refund in the amount of %2$s approved."
1603
+ msgstr "%1$s: tagasimakse summas %2$s kinnitatud."
1604
+
1605
+ #. translators: Placeholders: %1$s - payment gateway title (such as
1606
+ #. Authorize.net, Braintree, etc), %2$s - error code, %3$s - error message
1607
+ #: payment-gateway/class-sv-wc-payment-gateway.php:2055
1608
+ msgid "%1$s Refund Failed: %2$s - %3$s"
1609
+ msgstr "%1$s: tagasimakse ebaõnnestus: %2$s - %3$s"
1610
+
1611
+ #. translators: Placeholders: %1$s - payment gateway title (such as
1612
+ #. Authorize.net, Braintree, etc), %2$s - error message
1613
+ #: payment-gateway/class-sv-wc-payment-gateway.php:2063
1614
+ msgid "%1$s Refund Failed: %2$s"
1615
+ msgstr "%1$s: tagasimakse ebaõnnestus: %2$s"
1616
+
1617
+ #. translators: Placeholders: %s - payment gateway title (such as
1618
+ #. Authorize.net, Braintree, etc)
1619
+ #: payment-gateway/class-sv-wc-payment-gateway.php:2084
1620
+ msgid "%s Order completely refunded."
1621
+ msgstr "%s: tellimus täielikult tagasi makstud."
1622
+
1623
+ #: payment-gateway/class-sv-wc-payment-gateway.php:2139
1624
+ msgid ""
1625
+ "Oops, you cannot partially void this order. Please use the full order amount."
1626
+ msgstr ""
1627
+ "Oih, sa ei saa seda tellimust osaliselt tühistada. Palun kasuta tellimuse "
1628
+ "täissummat."
1629
+
1630
+ #. translators: Placeholders: %1$s - payment gateway title, %2$s - error code,
1631
+ #. %3$s - error message. Void as in to void an order.
1632
+ #: payment-gateway/class-sv-wc-payment-gateway.php:2226
1633
+ msgid "%1$s Void Failed: %2$s - %3$s"
1634
+ msgstr "%1$s: tühistamine ebaõnnestus: %2$s - %3$s"
1635
+
1636
+ #. translators: Placeholders: %1$s - payment gateway title, %2$s - error
1637
+ #. message. Void as in to void an order.
1638
+ #: payment-gateway/class-sv-wc-payment-gateway.php:2234
1639
+ msgid "%1$s Void Failed: %2$s"
1640
+ msgstr "%1$s: tühistamine ebaõnnestus: %2$s"
1641
+
1642
+ #. translators: Placeholders: %1$s - payment gateway title, %2$s - a monetary
1643
+ #. amount. Void as in to void an order.
1644
+ #: payment-gateway/class-sv-wc-payment-gateway.php:2258
1645
+ msgid "%1$s Void in the amount of %2$s approved."
1646
+ msgstr "%1$s: tühistamine summas %2$s kinnitatud."
1647
+
1648
+ #. translators: Placeholders: %1$s - payment method title, %2$s - environment
1649
+ #. ("Test"), %3$s - transaction type (authorization/charge)
1650
+ #: payment-gateway/class-sv-wc-payment-gateway.php:2550
1651
+ #, fuzzy
1652
+ msgid "%1$s %2$s %3$s Approved"
1653
+ msgstr "%1$s: %2$s tehing kinnitatud"
1654
+
1655
+ #. translators: Placeholders: %1$s - credit card type (MasterCard, Visa,
1656
+ #. etc...), %2$s - last four digits of the card
1657
+ #: payment-gateway/class-sv-wc-payment-gateway.php:2560
1658
+ msgid "%1$s ending in %2$s"
1659
+ msgstr ""
1660
+
1661
+ #. translators: Placeholders: %1$s - payment gateway title, %2$s - message
1662
+ #. (probably reason for the transaction being held for review)
1663
+ #: payment-gateway/class-sv-wc-payment-gateway.php:2656
1664
+ msgid "%1$s Transaction Held for Review (%2$s)"
1665
+ msgstr "%1$s: tehning pandi ülevaatuseks ootele (%2$s)"
1666
+
1667
+ #. translators: Placeholders: %1$s - payment gateway title, %2$s - error
1668
+ #. message; e.g. Order Note: [Payment method] Payment failed [error]
1669
+ #: payment-gateway/class-sv-wc-payment-gateway.php:2745
1670
+ msgid "%1$s Payment Failed (%2$s)"
1671
+ msgstr "%1$s: makse ebaõnnestus (%2$s)"
1672
+
1673
+ #. translators: Placeholders: %1$s - payment gateway title, %2$s -
1674
+ #. message/error
1675
+ #: payment-gateway/class-sv-wc-payment-gateway.php:2780
1676
+ msgid "%1$s Transaction Cancelled (%2$s)"
1677
+ msgstr "%1$s: tehing tühistatud (%2$s)"
1678
+
1679
+ #: payment-gateway/class-sv-wc-payment-gateway.php:3028
1680
+ msgid "Transaction Type"
1681
+ msgstr "Tehingu tüüp"
1682
+
1683
+ #: payment-gateway/class-sv-wc-payment-gateway.php:3030
1684
+ msgid ""
1685
+ "Select how transactions should be processed. Charge submits all transactions "
1686
+ "for settlement, Authorization simply authorizes the order total for capture "
1687
+ "later."
1688
+ msgstr ""
1689
+ "Vali, kuidas peaks tehinguid töötlema. \"Makse\" saadab kõik tehingud "
1690
+ "koheselt tasumisele, \"Autoriseerimine\" lihtsalt autoriseerib tellimuse "
1691
+ "summa hilisemaks tasumiseks."
1692
+
1693
+ #: payment-gateway/class-sv-wc-payment-gateway.php:3041
1694
+ msgid "Charge Virtual-Only Orders"
1695
+ msgstr ""
1696
+
1697
+ #: payment-gateway/class-sv-wc-payment-gateway.php:3043
1698
+ msgid ""
1699
+ "If the order contains exclusively virtual items, enable this to immediately "
1700
+ "charge, rather than authorize, the transaction."
1701
+ msgstr ""
1702
+
1703
+ #: payment-gateway/class-sv-wc-payment-gateway.php:3051
1704
+ #, fuzzy
1705
+ msgid "Enable Partial Capture"
1706
+ msgstr "Lülita see makseviis sisse"
1707
+
1708
+ #: payment-gateway/class-sv-wc-payment-gateway.php:3053
1709
+ msgid "Allow orders to be partially captured multiple times."
1710
+ msgstr ""
1711
+
1712
+ #: payment-gateway/class-sv-wc-payment-gateway.php:3065
1713
+ #, fuzzy
1714
+ msgid "Capture Paid Orders"
1715
+ msgstr "Teosta makse"
1716
+
1717
+ #: payment-gateway/class-sv-wc-payment-gateway.php:3068
1718
+ msgid "Automatically capture orders when they are changed to %s."
1719
+ msgstr ""
1720
+
1721
+ #: payment-gateway/class-sv-wc-payment-gateway.php:3069
1722
+ msgid "a paid status"
1723
+ msgstr ""
1724
+
1725
+ #: payment-gateway/class-sv-wc-payment-gateway.php:3259
1726
+ #, fuzzy
1727
+ msgid "Accepted Card Logos"
1728
+ msgstr "Vastuvõetavad kaardid"
1729
+
1730
+ #: payment-gateway/class-sv-wc-payment-gateway.php:3261
1731
+ #, fuzzy
1732
+ msgid ""
1733
+ "These are the card logos that are displayed to customers as accepted during "
1734
+ "checkout."
1735
+ msgstr "Kliendile kassas nähtav makseviisi nimetus."
1736
+
1737
+ #. translators: Placeholders: %1$s - <strong> tag, %2$s - </strong> tag
1738
+ #: payment-gateway/class-sv-wc-payment-gateway.php:3264
1739
+ msgid ""
1740
+ "This setting %1$sdoes not%2$s change which card types the gateway will "
1741
+ "accept. Accepted cards are configured from your payment processor account."
1742
+ msgstr ""
1743
+
1744
+ #. translators:
1745
+ #. http:www.cybersource.com/products/payment_security/payment_tokenization/ and
1746
+ #. https:en.wikipedia.org/wiki/Tokenization_(data_security)
1747
+ #: payment-gateway/class-sv-wc-payment-gateway.php:3355
1748
+ msgid "Tokenization"
1749
+ msgstr "Maksevahendite salvestamine"
1750
+
1751
+ #: payment-gateway/class-sv-wc-payment-gateway.php:3356
1752
+ msgid ""
1753
+ "Allow customers to securely save their payment details for future checkout."
1754
+ msgstr ""
1755
+ "Võimalda klientidel oma makseandmeid edaspidisteks tehinguteks turvaliselt "
1756
+ "talletada."
1757
+
1758
+ #. translators: %1$s - gateway name, %2$s - <a> tag, %3$s - </a> tag, %4$s -
1759
+ #. <a> tag, %5$s - </a> tag
1760
+ #: payment-gateway/class-sv-wc-payment-gateway.php:4175
1761
+ msgid ""
1762
+ "Heads up! %1$s is not fully configured and cannot accept payments. Please "
1763
+ "%2$sreview the documentation%3$s and configure the %4$sgateway settings%5$s."
1764
+ msgstr ""
1765
+
1766
+ #: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-pre-orders.php:261
1767
+ msgid "Pre-Order Tokenization attempt failed (%s)"
1768
+ msgstr ""
1769
+
1770
+ #: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-pre-orders.php:307
1771
+ msgid "%s - Pre-Order Release Payment for Order %s"
1772
+ msgstr ""
1773
+
1774
+ #: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-pre-orders.php:311
1775
+ msgid "Payment token missing/invalid."
1776
+ msgstr "Maksevahend on puudu või vigane."
1777
+
1778
+ #: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-pre-orders.php:336
1779
+ msgid "%s %s Pre-Order Release Payment Approved: %s ending in %s (expires %s)"
1780
+ msgstr ""
1781
+
1782
+ #: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-pre-orders.php:347
1783
+ msgid "%s eCheck Pre-Order Release Payment Approved: %s ending in %s"
1784
+ msgstr ""
1785
+
1786
+ #: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-pre-orders.php:391
1787
+ msgid "Pre-Order Release Payment Failed: %s"
1788
+ msgstr "Eeltellimuse väljastamise makse ebaõnnestus: %s"
1789
+
1790
+ #: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-subscriptions.php:326
1791
+ msgid "Subscription Renewal: payment token is missing/invalid."
1792
+ msgstr ""
1793
+
1794
+ #: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-subscriptions.php:352
1795
+ msgid "%1$s - Subscription Renewal Order %2$s"
1796
+ msgstr ""
1797
+
1798
+ #. translators: Placeholders: %1$s - payment gateway title, %2$s - error
1799
+ #. message; e.g. Order Note: [Payment method] Payment Change failed [error]
1800
+ #: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-subscriptions.php:484
1801
+ #, fuzzy
1802
+ msgid "%1$s Payment Change Failed (%2$s)"
1803
+ msgstr "%1$s: makse ebaõnnestus (%2$s)"
1804
+
1805
+ #: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-subscriptions.php:627
1806
+ msgid "Via %s ending in %s"
1807
+ msgstr ""
1808
+
1809
+ #: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-subscriptions.php:654
1810
+ msgid "Subscriptions"
1811
+ msgstr ""
1812
+
1813
+ #: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-subscriptions.php:723
1814
+ msgid ""
1815
+ "This payment method is tied to a subscription and cannot be deleted. Please "
1816
+ "switch the subscription to another method first."
1817
+ msgstr ""
1818
+
1819
+ #: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-subscriptions.php:775
1820
+ msgid "Payment Token"
1821
+ msgstr ""
1822
+
1823
+ #: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-subscriptions.php:804
1824
+ #: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-subscriptions.php:809
1825
+ msgid "%s is required."
1826
+ msgstr ""
1827
+
1828
+ #: payment-gateway/payment-tokens/class-sv-wc-payment-gateway-payment-tokens-handler.php:173
1829
+ msgid "Unknown Error"
1830
+ msgstr "Esines tundmatu viga"
1831
+
1832
+ #: utilities/class-sv-wp-background-job-handler.php:644
1833
+ msgid "Job data key \"%s\" not set"
1834
+ msgstr ""
1835
+
1836
+ #: utilities/class-sv-wp-background-job-handler.php:648
1837
+ msgid "Job data key \"%s\" is not an array"
1838
+ msgstr ""
1839
+
1840
+ #: utilities/class-sv-wp-background-job-handler.php:884
1841
+ msgid "Every %d Minutes"
1842
+ msgstr ""
1843
+
1844
+ #: utilities/class-sv-wp-background-job-handler.php:1048
1845
+ msgid "Background Processing Test"
1846
+ msgstr ""
1847
+
1848
+ #: utilities/class-sv-wp-background-job-handler.php:1049
1849
+ #, fuzzy
1850
+ msgid "Run Test"
1851
+ msgstr "test"
1852
+
1853
+ #: utilities/class-sv-wp-background-job-handler.php:1050
1854
+ msgid ""
1855
+ "This tool will test whether your server is capable of processing background "
1856
+ "jobs."
1857
+ msgstr ""
1858
+
1859
+ #: utilities/class-sv-wp-background-job-handler.php:1068
1860
+ msgid "Success! You should be able to process background jobs."
1861
+ msgstr ""
1862
+
1863
+ #: utilities/class-sv-wp-background-job-handler.php:1071
1864
+ msgid ""
1865
+ "Could not connect. Please ask your hosting company to ensure your server has "
1866
+ "loopback connections enabled."
1867
+ msgstr ""
1868
+
1869
+ #: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:396
1870
+ msgctxt "enhanced select"
1871
+ msgid "No matches found"
1872
+ msgstr ""
1873
+
1874
+ #: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:397
1875
+ msgctxt "enhanced select"
1876
+ msgid "Loading failed"
1877
+ msgstr ""
1878
+
1879
+ #: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:398
1880
+ msgctxt "enhanced select"
1881
+ msgid "Please enter 1 or more characters"
1882
+ msgstr ""
1883
+
1884
+ #: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:399
1885
+ msgctxt "enhanced select"
1886
+ msgid "Please enter %qty% or more characters"
1887
+ msgstr ""
1888
+
1889
+ #: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:400
1890
+ msgctxt "enhanced select"
1891
+ msgid "Please delete 1 character"
1892
+ msgstr ""
1893
+
1894
+ #: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:401
1895
+ msgctxt "enhanced select"
1896
+ msgid "Please delete %qty% characters"
1897
+ msgstr ""
1898
+
1899
+ #: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:402
1900
+ msgctxt "enhanced select"
1901
+ msgid "You can only select 1 item"
1902
+ msgstr ""
1903
+
1904
+ #: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:403
1905
+ msgctxt "enhanced select"
1906
+ msgid "You can only select %qty% items"
1907
+ msgstr ""
1908
+
1909
+ #: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:404
1910
+ msgctxt "enhanced select"
1911
+ msgid "Loading more results&hellip;"
1912
+ msgstr ""
1913
+
1914
+ #: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:405
1915
+ msgctxt "enhanced select"
1916
+ msgid "Searching&hellip;"
1917
+ msgstr ""
1918
+
1919
+ #: class-sv-wc-helper.php:408
1920
+ msgctxt "coordinating conjunction for a list of items: a, b, and c"
1921
+ msgid "and"
1922
+ msgstr ""
1923
+
1924
+ #: class-sv-wc-plugin.php:611
1925
+ msgctxt "noun"
1926
+ msgid "Support"
1927
+ msgstr "Kasutajatugi"
1928
+
1929
+ #: class-sv-wc-plugin.php:616
1930
+ msgctxt "verb"
1931
+ msgid "Review"
1932
+ msgstr ""
1933
+
1934
+ #: payment-gateway/class-sv-wc-payment-gateway-direct.php:746
1935
+ #: payment-gateway/class-sv-wc-payment-gateway.php:2552
1936
+ msgctxt "noun, software environment"
1937
+ msgid "Test"
1938
+ msgstr "test"
1939
+
1940
+ #: payment-gateway/class-sv-wc-payment-gateway-direct.php:747
1941
+ #: payment-gateway/class-sv-wc-payment-gateway.php:2553
1942
+ #: payment-gateway/class-sv-wc-payment-gateway.php:3034
1943
+ msgctxt "credit card transaction type"
1944
+ msgid "Authorization"
1945
+ msgstr "Autoriseerimine"
1946
+
1947
+ #: payment-gateway/class-sv-wc-payment-gateway-direct.php:747
1948
+ #: payment-gateway/class-sv-wc-payment-gateway.php:2553
1949
+ #: payment-gateway/class-sv-wc-payment-gateway.php:3033
1950
+ msgctxt "noun, credit card transaction type"
1951
+ msgid "Charge"
1952
+ msgstr "Makse"
1953
+
1954
+ #: payment-gateway/class-sv-wc-payment-gateway-helper.php:193
1955
+ msgctxt "payment method type"
1956
+ msgid "Account"
1957
+ msgstr "Konto"
1958
+
1959
+ #: payment-gateway/class-sv-wc-payment-gateway-helper.php:229
1960
+ #: payment-gateway/class-sv-wc-payment-gateway.php:3292
1961
+ msgctxt "credit card type"
1962
+ msgid "Visa"
1963
+ msgstr "Visa"
1964
+
1965
+ #: payment-gateway/class-sv-wc-payment-gateway-helper.php:233
1966
+ #: payment-gateway/class-sv-wc-payment-gateway.php:3293
1967
+ msgctxt "credit card type"
1968
+ msgid "MasterCard"
1969
+ msgstr "MasterCard"
1970
+
1971
+ #: payment-gateway/class-sv-wc-payment-gateway-helper.php:237
1972
+ #: payment-gateway/class-sv-wc-payment-gateway.php:3294
1973
+ msgctxt "credit card type"
1974
+ msgid "American Express"
1975
+ msgstr "American Express"
1976
+
1977
+ #: payment-gateway/class-sv-wc-payment-gateway-helper.php:241
1978
+ msgctxt "credit card type"
1979
+ msgid "Diners Club"
1980
+ msgstr ""
1981
+
1982
+ #: payment-gateway/class-sv-wc-payment-gateway-helper.php:245
1983
+ #: payment-gateway/class-sv-wc-payment-gateway.php:3295
1984
+ msgctxt "credit card type"
1985
+ msgid "Discover"
1986
+ msgstr "Discover"
1987
+
1988
+ #: payment-gateway/class-sv-wc-payment-gateway-helper.php:249
1989
+ #: payment-gateway/class-sv-wc-payment-gateway.php:3297
1990
+ msgctxt "credit card type"
1991
+ msgid "JCB"
1992
+ msgstr "JCB"
1993
+
1994
+ #: payment-gateway/class-sv-wc-payment-gateway-helper.php:253
1995
+ msgctxt "credit card type"
1996
+ msgid "CarteBleue"
1997
+ msgstr "CarteBleue"
1998
+
1999
+ #: payment-gateway/class-sv-wc-payment-gateway-helper.php:257
2000
+ msgctxt "credit card type"
2001
+ msgid "Maestro"
2002
+ msgstr ""
2003
+
2004
+ #: payment-gateway/class-sv-wc-payment-gateway-helper.php:261
2005
+ msgctxt "credit card type"
2006
+ msgid "Laser"
2007
+ msgstr ""
2008
+
2009
+ #: payment-gateway/class-sv-wc-payment-gateway.php:3296
2010
+ msgctxt "credit card type"
2011
+ msgid "Diners"
2012
+ msgstr "Diners"
2013
+
2014
+ #. translators: http:www.investopedia.com/terms/c/checkingaccount.asp
2015
+ #: payment-gateway/class-sv-wc-payment-gateway-payment-form.php:446
2016
+ msgctxt "account type"
2017
+ msgid "Checking"
2018
+ msgstr "TÅ¡ekikonto"
2019
+
2020
+ #. translators: http:www.investopedia.com/terms/s/savingsaccount.asp
2021
+ #: payment-gateway/class-sv-wc-payment-gateway-payment-form.php:448
2022
+ msgctxt "account type"
2023
+ msgid "Savings"
2024
+ msgstr "Hoiuarve"
2025
+
2026
+ #: payment-gateway/class-sv-wc-payment-gateway.php:2333
2027
+ msgctxt "hash before order number"
2028
+ msgid "#"
2029
+ msgstr "#"
2030
+
2031
+ #: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-subscriptions.php:684
2032
+ msgctxt "hash before order number"
2033
+ msgid "#%s"
2034
+ msgstr ""
2035
+
2036
+ #: payment-gateway/class-sv-wc-payment-gateway.php:3062
2037
+ msgctxt ""
2038
+ "coordinating conjunction for a list of order statuses: on-hold, processing, "
2039
+ "or completed"
2040
+ msgid "or"
2041
+ msgstr ""
2042
+
2043
+ #. translators: https:www.skyverge.com/for-translators-environments/
2044
+ #: payment-gateway/class-sv-wc-payment-gateway.php:3901
2045
+ msgctxt "software environment"
2046
+ msgid "Production"
2047
+ msgstr "Töö/avalik"
2048
+
2049
+ #, fuzzy
2050
+ #~ msgid "Pay with"
2051
+ #~ msgstr "Maksa"
2052
+
2053
+ #~ msgid "%1$s Capture Failed: %2$s - %3$s"
2054
+ #~ msgstr "%1$s: tasumine ebaõnnestus: %2$s - %3$s"
2055
+
2056
+ #~ msgid "Dismiss"
2057
+ #~ msgstr "Loobu"
2058
+
2059
+ #~ msgid "(check number %s)"
2060
+ #~ msgstr "(tšeki number %s)"
2061
+
2062
+ #~ msgid "Make Default"
2063
+ #~ msgstr "Määra vaikimisi valikuks"
2064
+
2065
+ #~ msgid "ending in %s"
2066
+ #~ msgstr "lõpeb numbritega %s"
2067
+
2068
+ #~ msgid "Default payment method updated."
2069
+ #~ msgstr "Vaikimisi maksevahend muudetud."
2070
+
2071
+ #~ msgid "Select which card types you accept."
2072
+ #~ msgstr "Vali, millist tüüpi kaarte soovid vastu võtta."
2073
+
2074
+ #~ msgid ""
2075
+ #~ "The following plugins are inactive because they require a newer version "
2076
+ #~ "to function properly:"
2077
+ #~ msgstr ""
2078
+ #~ "Järgnevad pluginad ei ole aktiivsed ja vajavad toimimiseks uuendamist:"
2079
+
2080
+ #~ msgid ""
2081
+ #~ "The following plugin is inactive because it requires a newer version to "
2082
+ #~ "function properly:"
2083
+ #~ msgstr "Järgnev plugin ei ole aktiivne ja vajab toimimiseks uuendamist:"
2084
+
2085
+ #~ msgid ""
2086
+ #~ "To reactivate these plugins, please %1$supdate now (recommended)%2$s "
2087
+ #~ "%3$sor%4$s %5$sdeactivate the following%6$s:"
2088
+ #~ msgstr ""
2089
+ #~ "Et neid pluginaid taas sisse lülitada, %1$suuenda palun kohe "
2090
+ #~ "(soovitatud)%2$s %3$svõi%4$s %5$slülita järgnevad välja%6$s:"
2091
+
2092
+ #~ msgid ""
2093
+ #~ "To reactivate this plugin, please %1$supdate now (recommended)%2$s %3$sor"
2094
+ #~ "%4$s %5$sdeactivate the following%6$s:"
2095
+ #~ msgstr ""
2096
+ #~ "Et see plugin taas sisse lülitada, %1$suuenda palun kohe (soovitatud)%2$s "
2097
+ #~ "%3$svõi%4$s %5$slülita järgnevad välja%6$s:"
2098
+
2099
+ #~ msgid "%s Customer Details"
2100
+ #~ msgstr "%s: kliendi andmed"
2101
+
2102
+ #~ msgid "Customer ID (%s)"
2103
+ #~ msgstr "Kliendi ID (%s)"
2104
+
2105
+ #~ msgid ""
2106
+ #~ "The gateway customer ID for the user in the %s environment. Only edit "
2107
+ #~ "this if necessary."
2108
+ #~ msgstr ""
2109
+ #~ "Kasutajale makseviisi poolt määratud kliendi tunnus %s keskkonnas. Muuda "
2110
+ #~ "seda ainult siis, kui tõesti vajalik."
2111
+
2112
+ #~ msgid "This customer has no saved payment tokens"
2113
+ #~ msgstr "Sel kliendil ei ole salvestatud maksevahendeid"
2114
+
2115
+ #~ msgid "Default card"
2116
+ #~ msgstr "Vaikimisi kaart"
2117
+
2118
+ #~ msgid "Add a Payment Token"
2119
+ #~ msgstr "Lisa maksevahend"
2120
+
2121
+ #~ msgid "Token"
2122
+ #~ msgstr "Vahend"
2123
+
2124
+ #~ msgid "%s Subscription Renewal Approved"
2125
+ #~ msgstr "Korduvtellimus summas %s uuendus kinnitatud"
2126
+
2127
+ #~ msgid "Subscription Renewal: Payment Token or User ID is missing/invalid."
2128
+ #~ msgstr ""
2129
+ #~ "Korduvtellimuse uuendus: maksevahend või kasutajatunnus on puudu või "
2130
+ #~ "vigane."
2131
+
2132
+ #~ msgid ""
2133
+ #~ "%1$s %2$s Subscription Renewal Payment Approved: %3$s ending in %4$s "
2134
+ #~ "(expires %5$s)"
2135
+ #~ msgstr ""
2136
+ #~ "%1$s: %2$s korduvtellimuse uuendamise makse kinnitatud: %3$s lõpeb "
2137
+ #~ "numbritega %4$s (aegub %5$s)"
2138
+
2139
+ #~ msgid ""
2140
+ #~ "%1$s Check Subscription Renewal Payment Approved: %2$s account ending in "
2141
+ #~ "%3$s"
2142
+ #~ msgstr ""
2143
+ #~ "%1$s: tšekiga tasutav korduvtellimuse uuendamise makse kinnitatud: %2$s "
2144
+ #~ "konto, lõpeb numbritega %3$s"
2145
+
2146
+ #~ msgid "Via %1$s ending in %2$s"
2147
+ #~ msgstr "%1$s, lõpeb numbritega %2$s"
2148
+
2149
+ #~ msgid "%1$s Pre-Order Tokenization attempt failed (%2$s)"
2150
+ #~ msgstr ""
2151
+ #~ "%1$s: eeltellimuse maksevahendi salvestamise katse ebaõnnestus (%2$s)"
2152
+
2153
+ #~ msgid "%1$s - Pre-Order Release Payment for Order %2$s"
2154
+ #~ msgstr "%1$s - Eeltellimuse väljastamise makse tellimuse %2$s eest"
2155
+
2156
+ #~ msgid ""
2157
+ #~ "%1$s %2$s Pre-Order Release Payment Approved: %3$s ending in %4$s "
2158
+ #~ "(expires %5$s)"
2159
+ #~ msgstr ""
2160
+ #~ "%1$s: %2$s eeltellimuse väljastamise makse vastu kinnitatud: %3$s lõpeb "
2161
+ #~ "numbritega %4$s (aegub %5$s)"
2162
+
2163
+ #~ msgid "%1$s eCheck Pre-Order Release Payment Approved: %2$s ending in %3$s"
2164
+ #~ msgstr ""
2165
+ #~ "%1$s: e-tšeki eeltellimuse väljastamise makse kinnitatud: %2$s lõpeb "
2166
+ #~ "numbritega %3$s"
2167
+
2168
+ #~ msgid "IPN processing error: %s duplicate transaction received"
2169
+ #~ msgstr "IPN töötlemise viga: %s duplikaattehing"
2170
+
2171
+ #~ msgid "%1$s %2$s Transaction Approved: %3$s ending in %4$s"
2172
+ #~ msgstr "%1$s: %2$s tehing kinnitatud: %3$s lõpeb numbritega %4$s"
2173
+
2174
+ #~ msgctxt "Supports capture charge"
2175
+ #~ msgid "Capture Charge"
2176
+ #~ msgstr "Makse "
vendor/skyverge/wc-plugin-framework/woocommerce/i18n/languages/woocommerce-plugin-framework.pot ADDED
@@ -0,0 +1,1939 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (C) 2020
2
+ # This file is distributed under the same license as the package.
3
+ msgid ""
4
+ msgstr ""
5
+ "Project-Id-Version: WooCommerce Plugin Framework 5.5.4\n"
6
+ "Report-Msgid-Bugs-To: https://support.woocommerce.com/hc/\n"
7
+ "POT-Creation-Date: 2015-07-22 12:09:16+00:00\n"
8
+ "MIME-Version: 1.0\n"
9
+ "Content-Type: text/plain; charset=utf-8\n"
10
+ "Content-Transfer-Encoding: 8bit\n"
11
+ "PO-Revision-Date: 2020-MO-DA HO:MI+ZONE\n"
12
+ "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
13
+ "Language-Team: LANGUAGE <LL@li.org>\n"
14
+
15
+ #: Lifecycle.php:377
16
+ msgid "Awesome"
17
+ msgstr ""
18
+
19
+ #: Lifecycle.php:378
20
+ msgid "Fantastic"
21
+ msgstr ""
22
+
23
+ #: Lifecycle.php:379
24
+ msgid "Cowabunga"
25
+ msgstr ""
26
+
27
+ #: Lifecycle.php:380
28
+ msgid "Congratulations"
29
+ msgstr ""
30
+
31
+ #: Lifecycle.php:381
32
+ msgid "Hot dog"
33
+ msgstr ""
34
+
35
+ #: Lifecycle.php:388
36
+ #. translators: Placeholders: %1$s - plugin name, %2$s - <a> tag, %3$s - </a>
37
+ #. tag, %4$s - <a> tag, %5$s - </a> tag
38
+ msgid ""
39
+ "Are you having a great experience with %1$s so far? Please consider "
40
+ "%2$sleaving a review%3$s! If things aren't going quite as expected, we're "
41
+ "happy to help -- please %4$sreach out to our support team%5$s."
42
+ msgstr ""
43
+
44
+ #: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:182
45
+ msgid ""
46
+ "Thanks for installing %1$s! To get started, take a minute to %2$sread the "
47
+ "documentation%3$s :)"
48
+ msgstr ""
49
+
50
+ #: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:210
51
+ msgid ""
52
+ "Thanks for installing %1$s! To get started, take a minute to complete these "
53
+ "%2$squick and easy setup steps%3$s :)"
54
+ msgstr ""
55
+
56
+ #: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:235
57
+ msgid "Setup"
58
+ msgstr ""
59
+
60
+ #: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:303
61
+ #. translators: Placeholders: %s - plugin name
62
+ msgid "%s &rsaquo; Setup"
63
+ msgstr ""
64
+
65
+ #: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:351
66
+ msgid "Oops! An error occurred, please try again."
67
+ msgstr ""
68
+
69
+ #: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:489
70
+ msgid "Ready!"
71
+ msgstr ""
72
+
73
+ #: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:582
74
+ #. translators: Placeholder: %s - plugin name
75
+ msgid "Welcome to %s!"
76
+ msgstr ""
77
+
78
+ #: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:595
79
+ msgid ""
80
+ "This quick setup wizard will help you configure the basic settings and get "
81
+ "you started."
82
+ msgstr ""
83
+
84
+ #: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:609
85
+ msgid "%s is ready!"
86
+ msgstr ""
87
+
88
+ #: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:661
89
+ msgid "Next step"
90
+ msgstr ""
91
+
92
+ #: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:687
93
+ msgid "You can also:"
94
+ msgstr ""
95
+
96
+ #: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:731
97
+ #: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:761
98
+ msgid "View the Docs"
99
+ msgstr ""
100
+
101
+ #: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:732
102
+ msgid "See more setup options"
103
+ msgstr ""
104
+
105
+ #: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:733
106
+ msgid "Learn more about customizing the plugin"
107
+ msgstr ""
108
+
109
+ #: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:757
110
+ msgid "Review Your Settings"
111
+ msgstr ""
112
+
113
+ #: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:765
114
+ msgid "Leave a Review"
115
+ msgstr ""
116
+
117
+ #: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:789
118
+ msgid "Continue"
119
+ msgstr ""
120
+
121
+ #: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:949
122
+ msgid "Return to the WordPress Dashboard"
123
+ msgstr ""
124
+
125
+ #: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:951
126
+ msgid "Not right now"
127
+ msgstr ""
128
+
129
+ #: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:953
130
+ msgid "Skip this step"
131
+ msgstr ""
132
+
133
+ #: class-sv-wc-framework-bootstrap.php:268
134
+ msgid ""
135
+ "The following plugin is disabled because it is out of date and incompatible "
136
+ "with newer plugins on your site:"
137
+ msgid_plural ""
138
+ "The following plugins are disabled because they are out of date and "
139
+ "incompatible with newer plugins on your site:"
140
+ msgstr[0] ""
141
+ msgstr[1] ""
142
+
143
+ #: class-sv-wc-framework-bootstrap.php:282
144
+ msgid ""
145
+ "To resolve this, please %1$supdate%2$s (recommended) %3$sor%4$s "
146
+ "%5$sdeactivate%6$s the above plugin, or %7$sdeactivate the following%8$s:"
147
+ msgid_plural ""
148
+ "To resolve this, please %1$supdate%2$s (recommended) %3$sor%4$s "
149
+ "%5$sdeactivate%6$s the above plugins, or %7$sdeactivate the following%8$s:"
150
+ msgstr[0] ""
151
+ msgstr[1] ""
152
+
153
+ #: class-sv-wc-framework-bootstrap.php:303
154
+ msgid ""
155
+ "The following plugins are inactive because they require a newer version of "
156
+ "WooCommerce:"
157
+ msgstr ""
158
+
159
+ #: class-sv-wc-framework-bootstrap.php:303
160
+ msgid ""
161
+ "The following plugin is inactive because it requires a newer version of "
162
+ "WooCommerce:"
163
+ msgstr ""
164
+
165
+ #: class-sv-wc-framework-bootstrap.php:308
166
+ #. translators: Placeholders: %1$s - plugin name, %2$s - WooCommerce version
167
+ #. number
168
+ msgid "%1$s requires WooCommerce %2$s or newer"
169
+ msgstr ""
170
+
171
+ #: class-sv-wc-framework-bootstrap.php:312
172
+ #. translators: Placeholders: %1$s - <a> tag, %2$s - </a> tag
173
+ msgid "Please %1$supdate WooCommerce%2$s"
174
+ msgstr ""
175
+
176
+ #: class-sv-wc-plugin-compatibility.php:334
177
+ msgid "WooCommerce"
178
+ msgstr ""
179
+
180
+ #: class-sv-wc-plugin-dependencies.php:156
181
+ #. translators: Placeholders: %1$s - plugin name, %2$s - a PHP
182
+ #. extension/comma-separated list of PHP extensions
183
+ msgid ""
184
+ "%1$s requires the %2$s PHP extension to function. Contact your host or "
185
+ "server administrator to install and configure the missing extension."
186
+ msgid_plural ""
187
+ "%1$s requires the following PHP extensions to function: %2$s. Contact your "
188
+ "host or server administrator to install and configure the missing "
189
+ "extensions."
190
+ msgstr[0] ""
191
+ msgstr[1] ""
192
+
193
+ #: class-sv-wc-plugin-dependencies.php:184
194
+ #. translators: Placeholders: %1$s - plugin name, %2$s - a PHP
195
+ #. function/comma-separated list of PHP functions
196
+ msgid ""
197
+ "%1$s requires the %2$s PHP function to exist. Contact your host or server "
198
+ "administrator to install and configure the missing function."
199
+ msgid_plural ""
200
+ "%1$s requires the following PHP functions to exist: %2$s. Contact your "
201
+ "host or server administrator to install and configure the missing functions."
202
+ msgstr[0] ""
203
+ msgstr[1] ""
204
+
205
+ #: class-sv-wc-plugin-dependencies.php:214
206
+ #. translators: Placeholders: %s - plugin name
207
+ msgid ""
208
+ "%s may behave unexpectedly because the following PHP configuration settings "
209
+ "are required:"
210
+ msgstr ""
211
+
212
+ #: class-sv-wc-plugin-dependencies.php:228
213
+ msgid "%s or higher"
214
+ msgstr ""
215
+
216
+ #: class-sv-wc-plugin-dependencies.php:238
217
+ msgid ""
218
+ "Please contact your hosting provider or server administrator to configure "
219
+ "these settings."
220
+ msgstr ""
221
+
222
+ #: class-sv-wc-plugin-dependencies.php:260
223
+ #. translators: Placeholders: %1$s - <strong>, %2$s - </strong>
224
+ msgid ""
225
+ "Hey there! We've noticed that your server is running %1$san outdated "
226
+ "version of PHP%2$s, which is the programming language that WooCommerce and "
227
+ "its extensions are built on.\n"
228
+ "\t\t\t\t\tThe PHP version that is currently used for your site is no longer "
229
+ "maintained, nor %1$sreceives security updates%2$s; newer versions are "
230
+ "faster and more secure.\n"
231
+ "\t\t\t\t\tAs a result, %3$s no longer supports this version and you should "
232
+ "upgrade PHP as soon as possible.\n"
233
+ "\t\t\t\t\tYour hosting provider can do this for you. %4$sHere are some "
234
+ "resources to help you upgrade%5$s and to explain PHP versions further."
235
+ msgstr ""
236
+
237
+ #: class-sv-wc-plugin.php:306
238
+ #. translators: Placeholders: %s - plugin name
239
+ msgid "You cannot clone instances of %s."
240
+ msgstr ""
241
+
242
+ #: class-sv-wc-plugin.php:317
243
+ #. translators: Placeholders: %s - plugin name
244
+ msgid "You cannot unserialize instances of %s."
245
+ msgstr ""
246
+
247
+ #: class-sv-wc-plugin.php:563
248
+ #. translators: Placeholders: %1$s - plugin name, %2$s - WooCommerce version
249
+ #. number, %3$s - opening <a> HTML link tag, %4$s - closing </a> HTML link tag
250
+ msgid ""
251
+ "Heads up! %1$s will soon discontinue support for WooCommerce %2$s. Please "
252
+ "%3$supdate WooCommerce%4$s to take advantage of the latest updates and "
253
+ "features."
254
+ msgstr ""
255
+
256
+ #: class-sv-wc-plugin.php:606
257
+ #. translators: Docs as in Documentation
258
+ msgid "Docs"
259
+ msgstr ""
260
+
261
+ #: class-sv-wc-plugin.php:699
262
+ msgid "%1$s - A minimum of %2$s is required."
263
+ msgstr ""
264
+
265
+ #: class-sv-wc-plugin.php:708
266
+ msgid "Set as %1$s - %2$s is required."
267
+ msgstr ""
268
+
269
+ #: class-sv-wc-plugin.php:973
270
+ msgid "Configure"
271
+ msgstr ""
272
+
273
+ #: payment-gateway/Handlers/Abstract_Hosted_Payment_Handler.php:179
274
+ msgid ""
275
+ "There was a problem processing your order and it is being placed on hold "
276
+ "for review. Please contact us to complete the transaction."
277
+ msgstr ""
278
+
279
+ #: payment-gateway/Handlers/Abstract_Hosted_Payment_Handler.php:217
280
+ #: payment-gateway/class-sv-wc-payment-gateway.php:2762
281
+ #: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-subscriptions.php:489
282
+ msgid "An error occurred, please try again or try an alternate form of payment."
283
+ msgstr ""
284
+
285
+ #: payment-gateway/Handlers/Abstract_Hosted_Payment_Handler.php:320
286
+ #: payment-gateway/class-sv-wc-payment-gateway-hosted.php:445
287
+ #. translators: Placeholders: %s - a WooCommerce order ID
288
+ msgid "Could not find order %s"
289
+ msgstr ""
290
+
291
+ #: payment-gateway/Handlers/Abstract_Payment_Handler.php:152
292
+ #: payment-gateway/class-sv-wc-payment-gateway.php:2355
293
+ #: payment-gateway/payment-tokens/class-sv-wc-payment-gateway-payment-tokens-handler.php:165
294
+ #. translators: Placeholders: %1$s - status code, %2$s - status message
295
+ #. translators: Placeholders: %1$s - payment request response status code, %2$s
296
+ #. - payment request response status message
297
+ msgid "Status code %1$s: %2$s"
298
+ msgstr ""
299
+
300
+ #: payment-gateway/Handlers/Abstract_Payment_Handler.php:155
301
+ #: payment-gateway/class-sv-wc-payment-gateway.php:2358
302
+ #: payment-gateway/payment-tokens/class-sv-wc-payment-gateway-payment-tokens-handler.php:168
303
+ #. translators: Placeholders: %s - status code
304
+ #. translators: Placeholders: %s - payment request response status code
305
+ msgid "Status code: %s"
306
+ msgstr ""
307
+
308
+ #: payment-gateway/Handlers/Abstract_Payment_Handler.php:158
309
+ #: payment-gateway/class-sv-wc-payment-gateway.php:2361
310
+ #: payment-gateway/payment-tokens/class-sv-wc-payment-gateway-payment-tokens-handler.php:171
311
+ #. translators: Placeholders; %s - status message
312
+ #. translators: Placeholders: %s - payment request response status message
313
+ msgid "Status message: %s"
314
+ msgstr ""
315
+
316
+ #: payment-gateway/Handlers/Abstract_Payment_Handler.php:163
317
+ #: payment-gateway/class-sv-wc-payment-gateway.php:2366
318
+ #: payment-gateway/payment-tokens/class-sv-wc-payment-gateway-payment-tokens-handler.php:178
319
+ msgid "Transaction ID %s"
320
+ msgstr ""
321
+
322
+ #: payment-gateway/Handlers/Abstract_Payment_Handler.php:204
323
+ #: payment-gateway/class-sv-wc-payment-gateway-hosted.php:509
324
+ #. translators: Placeholders: %s - payment gateway title (such as
325
+ #. Authorize.net, Braintree, etc)
326
+ msgid "%s duplicate transaction received"
327
+ msgstr ""
328
+
329
+ #: payment-gateway/Handlers/Abstract_Payment_Handler.php:207
330
+ #: payment-gateway/class-sv-wc-payment-gateway-hosted.php:512
331
+ msgid "Order %s is already paid for."
332
+ msgstr ""
333
+
334
+ #: payment-gateway/Handlers/Abstract_Payment_Handler.php:267
335
+ #: payment-gateway/class-sv-wc-payment-gateway.php:2698
336
+ msgid ""
337
+ "Your order has been received and is being reviewed. Thank you for your "
338
+ "business."
339
+ msgstr ""
340
+
341
+ #: payment-gateway/Handlers/Abstract_Payment_Handler.php:274
342
+ #: payment-gateway/class-sv-wc-payment-gateway-direct.php:861
343
+ #: payment-gateway/class-sv-wc-payment-gateway.php:1733
344
+ #: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-pre-orders.php:370
345
+ #. translators: This is a message describing that the transaction in question
346
+ #. only performed a credit card authorization and did not capture any funds.
347
+ msgid "Authorization only transaction"
348
+ msgstr ""
349
+
350
+ #: payment-gateway/Handlers/Abstract_Payment_Handler.php:364
351
+ #. translators: Placeholders: %s - payment gateway title
352
+ msgid "%s Transaction Held for Review"
353
+ msgstr ""
354
+
355
+ #: payment-gateway/Handlers/Abstract_Payment_Handler.php:435
356
+ #. translators: Placeholders: %s - payment gateway title
357
+ msgid "%s Payment Failed"
358
+ msgstr ""
359
+
360
+ #: payment-gateway/Handlers/Abstract_Payment_Handler.php:462
361
+ #. translators: Placeholders: %s - payment gateway title
362
+ msgid "%s Transaction Cancelled"
363
+ msgstr ""
364
+
365
+ #: payment-gateway/Handlers/Capture.php:158
366
+ msgid "Order cannot be captured"
367
+ msgstr ""
368
+
369
+ #: payment-gateway/Handlers/Capture.php:163
370
+ msgid "Transaction authorization has expired"
371
+ msgstr ""
372
+
373
+ #: payment-gateway/Handlers/Capture.php:168
374
+ msgid "Transaction has already been fully captured"
375
+ msgstr ""
376
+
377
+ #: payment-gateway/Handlers/Capture.php:173
378
+ msgid "Transaction cannot be captured"
379
+ msgstr ""
380
+
381
+ #: payment-gateway/Handlers/Capture.php:189
382
+ #. translators: Placeholders: %1$s - payment gateway title (such as
383
+ #. Authorize.net, Braintree, etc), %2$s - transaction amount. Definitions:
384
+ #. Capture, as in capture funds from a credit card.
385
+ msgid "%1$s Capture of %2$s Approved"
386
+ msgstr ""
387
+
388
+ #: payment-gateway/Handlers/Capture.php:198
389
+ #: payment-gateway/class-sv-wc-payment-gateway-direct.php:680
390
+ #: payment-gateway/class-sv-wc-payment-gateway-direct.php:765
391
+ #: payment-gateway/class-sv-wc-payment-gateway.php:2034
392
+ #: payment-gateway/class-sv-wc-payment-gateway.php:2267
393
+ #: payment-gateway/class-sv-wc-payment-gateway.php:2579
394
+ #: payment-gateway/class-sv-wc-payment-gateway.php:2624
395
+ #: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-pre-orders.php:353
396
+ #. translators: Placeholders: %s - transaction ID
397
+ msgid "(Transaction ID %s)"
398
+ msgstr ""
399
+
400
+ #: payment-gateway/Handlers/Capture.php:229
401
+ #. translators: Placeholders: %1$s - payment gateway title (such as
402
+ #. Authorize.net, Braintree, etc), %2$s - failure message. Definitions:
403
+ #. "capture" as in capturing funds from a credit card.
404
+ msgid "%1$s Capture Failed: %2$s"
405
+ msgstr ""
406
+
407
+ #: payment-gateway/admin/class-sv-wc-payment-gateway-admin-order.php:120
408
+ msgid "Are you sure you wish to process this capture? The action cannot be undone."
409
+ msgstr ""
410
+
411
+ #: payment-gateway/admin/class-sv-wc-payment-gateway-admin-order.php:123
412
+ msgid ""
413
+ "Something went wrong, and the capture could no be completed. Please try "
414
+ "again."
415
+ msgstr ""
416
+
417
+ #: payment-gateway/admin/class-sv-wc-payment-gateway-admin-order.php:167
418
+ #: payment-gateway/admin/class-sv-wc-payment-gateway-admin-order.php:242
419
+ #: payment-gateway/admin/class-sv-wc-payment-gateway-admin-order.php:305
420
+ #. translators: verb, as in "Capture credit card charge". Used when an
421
+ #. amount has been pre-authorized before, but funds have not yet been captured
422
+ #. (taken) from the card. Capturing the charge will take the money from the
423
+ #. credit card and put it in the merchant's pockets.
424
+ msgid "Capture Charge"
425
+ msgstr ""
426
+
427
+ #: payment-gateway/admin/class-sv-wc-payment-gateway-admin-order.php:295
428
+ msgid "This charge has been fully captured."
429
+ msgstr ""
430
+
431
+ #: payment-gateway/admin/class-sv-wc-payment-gateway-admin-order.php:297
432
+ msgid "This charge can no longer be captured."
433
+ msgstr ""
434
+
435
+ #: payment-gateway/admin/class-sv-wc-payment-gateway-admin-order.php:299
436
+ msgid "This charge cannot be captured."
437
+ msgstr ""
438
+
439
+ #: payment-gateway/admin/class-sv-wc-payment-gateway-admin-payment-token-editor.php:91
440
+ msgid "Are you sure you want to remove this token?"
441
+ msgstr ""
442
+
443
+ #: payment-gateway/admin/class-sv-wc-payment-gateway-admin-payment-token-editor.php:101
444
+ msgid "Invalid token data"
445
+ msgstr ""
446
+
447
+ #: payment-gateway/admin/class-sv-wc-payment-gateway-admin-payment-token-editor.php:105
448
+ msgid "An error occurred. Please try again."
449
+ msgstr ""
450
+
451
+ #: payment-gateway/admin/class-sv-wc-payment-gateway-admin-payment-token-editor.php:454
452
+ #: payment-gateway/admin/class-sv-wc-payment-gateway-admin-user-handler.php:305
453
+ msgid "(%s)"
454
+ msgstr ""
455
+
456
+ #: payment-gateway/admin/class-sv-wc-payment-gateway-admin-payment-token-editor.php:484
457
+ #: payment-gateway/class-sv-wc-payment-gateway-my-payment-methods.php:667
458
+ msgid "Default"
459
+ msgstr ""
460
+
461
+ #: payment-gateway/admin/class-sv-wc-payment-gateway-admin-payment-token-editor.php:520
462
+ #: payment-gateway/admin/class-sv-wc-payment-gateway-admin-payment-token-editor.php:553
463
+ msgid "Token ID"
464
+ msgstr ""
465
+
466
+ #: payment-gateway/admin/class-sv-wc-payment-gateway-admin-payment-token-editor.php:525
467
+ #: payment-gateway/class-sv-wc-payment-gateway-privacy.php:300
468
+ msgid "Card Type"
469
+ msgstr ""
470
+
471
+ #: payment-gateway/admin/class-sv-wc-payment-gateway-admin-payment-token-editor.php:530
472
+ #: payment-gateway/admin/class-sv-wc-payment-gateway-admin-payment-token-editor.php:566
473
+ #: payment-gateway/class-sv-wc-payment-gateway-privacy.php:192
474
+ #: payment-gateway/class-sv-wc-payment-gateway-privacy.php:298
475
+ msgid "Last Four"
476
+ msgstr ""
477
+
478
+ #: payment-gateway/admin/class-sv-wc-payment-gateway-admin-payment-token-editor.php:537
479
+ #: payment-gateway/class-sv-wc-payment-gateway-payment-form.php:331
480
+ msgid "Expiration (MM/YY)"
481
+ msgstr ""
482
+
483
+ #: payment-gateway/admin/class-sv-wc-payment-gateway-admin-payment-token-editor.php:558
484
+ #: payment-gateway/class-sv-wc-payment-gateway-payment-form.php:438
485
+ #: payment-gateway/class-sv-wc-payment-gateway-privacy.php:299
486
+ #. translators: e-check account type, HTML form field label
487
+ msgid "Account Type"
488
+ msgstr ""
489
+
490
+ #: payment-gateway/admin/class-sv-wc-payment-gateway-admin-payment-token-editor.php:561
491
+ msgid "Checking"
492
+ msgstr ""
493
+
494
+ #: payment-gateway/admin/class-sv-wc-payment-gateway-admin-payment-token-editor.php:562
495
+ msgid "Savings"
496
+ msgstr ""
497
+
498
+ #: payment-gateway/admin/class-sv-wc-payment-gateway-admin-payment-token-editor.php:663
499
+ msgid "Refresh"
500
+ msgstr ""
501
+
502
+ #: payment-gateway/admin/class-sv-wc-payment-gateway-admin-payment-token-editor.php:665
503
+ msgid "Add New"
504
+ msgstr ""
505
+
506
+ #: payment-gateway/admin/class-sv-wc-payment-gateway-admin-payment-token-editor.php:668
507
+ #: payment-gateway/class-sv-wc-payment-gateway-my-payment-methods.php:762
508
+ msgid "Save"
509
+ msgstr ""
510
+
511
+ #: payment-gateway/admin/class-sv-wc-payment-gateway-admin-payment-token-editor.php:691
512
+ msgid "Remove"
513
+ msgstr ""
514
+
515
+ #: payment-gateway/admin/class-sv-wc-payment-gateway-admin-user-handler.php:224
516
+ #: payment-gateway/class-sv-wc-payment-gateway-privacy.php:209
517
+ msgid "%s Payment Tokens"
518
+ msgstr ""
519
+
520
+ #: payment-gateway/admin/class-sv-wc-payment-gateway-admin-user-handler.php:302
521
+ #: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-subscriptions.php:779
522
+ msgid "Customer ID"
523
+ msgstr ""
524
+
525
+ #: payment-gateway/admin/views/html-admin-gateway-status.php:32
526
+ msgid "This section contains configuration settings for this gateway."
527
+ msgstr ""
528
+
529
+ #: payment-gateway/admin/views/html-admin-gateway-status.php:53
530
+ #: payment-gateway/class-sv-wc-payment-gateway.php:1266
531
+ #. translators: environment as in a software environment (test/production)
532
+ msgid "Environment"
533
+ msgstr ""
534
+
535
+ #: payment-gateway/admin/views/html-admin-gateway-status.php:54
536
+ msgid "The transaction environment for this gateway."
537
+ msgstr ""
538
+
539
+ #: payment-gateway/admin/views/html-admin-gateway-status.php:61
540
+ msgid "Tokenization Enabled"
541
+ msgstr ""
542
+
543
+ #: payment-gateway/admin/views/html-admin-gateway-status.php:62
544
+ msgid "Displays whether or not tokenization is enabled for this gateway."
545
+ msgstr ""
546
+
547
+ #: payment-gateway/admin/views/html-admin-gateway-status.php:75
548
+ #: payment-gateway/class-sv-wc-payment-gateway.php:1196
549
+ msgid "Debug Mode"
550
+ msgstr ""
551
+
552
+ #: payment-gateway/admin/views/html-admin-gateway-status.php:76
553
+ msgid "Displays whether or not debug logging is enabled for this gateway."
554
+ msgstr ""
555
+
556
+ #: payment-gateway/admin/views/html-admin-gateway-status.php:79
557
+ msgid "Display at Checkout & Log"
558
+ msgstr ""
559
+
560
+ #: payment-gateway/admin/views/html-admin-gateway-status.php:81
561
+ msgid "Display at Checkout"
562
+ msgstr ""
563
+
564
+ #: payment-gateway/admin/views/html-admin-gateway-status.php:83
565
+ #: payment-gateway/class-sv-wc-payment-gateway.php:1204
566
+ msgid "Save to Log"
567
+ msgstr ""
568
+
569
+ #: payment-gateway/admin/views/html-admin-gateway-status.php:85
570
+ #: payment-gateway/class-sv-wc-payment-gateway.php:1202
571
+ msgid "Off"
572
+ msgstr ""
573
+
574
+ #: payment-gateway/admin/views/html-order-partial-capture.php:30
575
+ msgid "Authorization total"
576
+ msgstr ""
577
+
578
+ #: payment-gateway/admin/views/html-order-partial-capture.php:34
579
+ msgid "Amount already captured"
580
+ msgstr ""
581
+
582
+ #: payment-gateway/admin/views/html-order-partial-capture.php:40
583
+ msgid "Remaining order total"
584
+ msgstr ""
585
+
586
+ #: payment-gateway/admin/views/html-order-partial-capture.php:46
587
+ msgid "Capture amount"
588
+ msgstr ""
589
+
590
+ #: payment-gateway/admin/views/html-order-partial-capture.php:53
591
+ msgid "Comment (optional):"
592
+ msgstr ""
593
+
594
+ #: payment-gateway/admin/views/html-order-partial-capture.php:65
595
+ msgid "Capture %s"
596
+ msgstr ""
597
+
598
+ #: payment-gateway/admin/views/html-order-partial-capture.php:66
599
+ #: payment-gateway/class-sv-wc-payment-gateway-my-payment-methods.php:229
600
+ msgid "Cancel"
601
+ msgstr ""
602
+
603
+ #: payment-gateway/admin/views/html-user-payment-token-editor-token.php:48
604
+ msgid "-- Select an option --"
605
+ msgstr ""
606
+
607
+ #: payment-gateway/admin/views/html-user-payment-token-editor.php:59
608
+ msgid "No saved payment tokens"
609
+ msgstr ""
610
+
611
+ #: payment-gateway/admin/views/html-user-profile-field-customer-id.php:30
612
+ msgid "The gateway customer ID for the user. Only edit this if necessary."
613
+ msgstr ""
614
+
615
+ #: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:99
616
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-frontend.php:132
617
+ msgid "An error occurred, please try again or try an alternate form of payment"
618
+ msgstr ""
619
+
620
+ #: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:100
621
+ msgid ""
622
+ "We cannot process your order with the payment information that you "
623
+ "provided. Please use a different payment account or an alternate payment "
624
+ "method."
625
+ msgstr ""
626
+
627
+ #: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:101
628
+ msgid ""
629
+ "This order is being placed on hold for review. Please contact us to "
630
+ "complete the transaction."
631
+ msgstr ""
632
+
633
+ #: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:106
634
+ msgid ""
635
+ "This order is being placed on hold for review due to an incorrect card "
636
+ "verification number. You may contact the store to complete the transaction."
637
+ msgstr ""
638
+
639
+ #: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:107
640
+ msgid "The card verification number is invalid, please try again."
641
+ msgstr ""
642
+
643
+ #: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:108
644
+ msgid "Please enter your card verification number and try again."
645
+ msgstr ""
646
+
647
+ #: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:111
648
+ msgid ""
649
+ "That card type is not accepted, please use an alternate card or other form "
650
+ "of payment."
651
+ msgstr ""
652
+
653
+ #: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:112
654
+ #: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:116
655
+ msgid ""
656
+ "The card type is invalid or does not correlate with the credit card number. "
657
+ " Please try again or use an alternate card or other form of payment."
658
+ msgstr ""
659
+
660
+ #: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:113
661
+ msgid "Please select the card type and try again."
662
+ msgstr ""
663
+
664
+ #: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:117
665
+ msgid "The card number is invalid, please re-enter and try again."
666
+ msgstr ""
667
+
668
+ #: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:118
669
+ msgid "Please enter your card number and try again."
670
+ msgstr ""
671
+
672
+ #: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:121
673
+ msgid "The card expiration date is invalid, please re-enter and try again."
674
+ msgstr ""
675
+
676
+ #: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:122
677
+ msgid "The card expiration month is invalid, please re-enter and try again."
678
+ msgstr ""
679
+
680
+ #: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:123
681
+ msgid "The card expiration year is invalid, please re-enter and try again."
682
+ msgstr ""
683
+
684
+ #: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:124
685
+ msgid "Please enter your card expiration date and try again."
686
+ msgstr ""
687
+
688
+ #: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:127
689
+ msgid "The bank routing number is invalid, please re-enter and try again."
690
+ msgstr ""
691
+
692
+ #: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:128
693
+ msgid "The bank account number is invalid, please re-enter and try again."
694
+ msgstr ""
695
+
696
+ #: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:131
697
+ msgid ""
698
+ "The provided card is expired, please use an alternate card or other form of "
699
+ "payment."
700
+ msgstr ""
701
+
702
+ #: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:132
703
+ msgid ""
704
+ "The provided card was declined, please use an alternate card or other form "
705
+ "of payment."
706
+ msgstr ""
707
+
708
+ #: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:133
709
+ msgid ""
710
+ "Insufficient funds in account, please use an alternate card or other form "
711
+ "of payment."
712
+ msgstr ""
713
+
714
+ #: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:134
715
+ msgid ""
716
+ "The card is inactivate or not authorized for card-not-present transactions, "
717
+ "please use an alternate card or other form of payment."
718
+ msgstr ""
719
+
720
+ #: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:135
721
+ msgid ""
722
+ "The credit limit for the card has been reached, please use an alternate "
723
+ "card or other form of payment."
724
+ msgstr ""
725
+
726
+ #: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:136
727
+ msgid "The card verification number does not match. Please re-enter and try again."
728
+ msgstr ""
729
+
730
+ #: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:137
731
+ msgid ""
732
+ "The provided address does not match the billing address for cardholder. "
733
+ "Please verify the address and try again."
734
+ msgstr ""
735
+
736
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:84
737
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:102
738
+ msgid "Apple Pay"
739
+ msgstr ""
740
+
741
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:108
742
+ #: payment-gateway/class-sv-wc-payment-gateway.php:1142
743
+ msgid "Enable / Disable"
744
+ msgstr ""
745
+
746
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:109
747
+ msgid "Accept Apple Pay"
748
+ msgstr ""
749
+
750
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:116
751
+ msgid "Allow Apple Pay on"
752
+ msgstr ""
753
+
754
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:126
755
+ msgid "Button Style"
756
+ msgstr ""
757
+
758
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:129
759
+ msgid "Black"
760
+ msgstr ""
761
+
762
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:130
763
+ msgid "White"
764
+ msgstr ""
765
+
766
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:131
767
+ msgid "White with outline"
768
+ msgstr ""
769
+
770
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:143
771
+ #: payment-gateway/class-sv-wc-payment-gateway.php:1317
772
+ msgid "Connection Settings"
773
+ msgstr ""
774
+
775
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:149
776
+ msgid "Apple Merchant ID"
777
+ msgstr ""
778
+
779
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:153
780
+ msgid "This is found in your %1$sApple developer account%2$s"
781
+ msgstr ""
782
+
783
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:160
784
+ msgid "Certificate Path"
785
+ msgstr ""
786
+
787
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:165
788
+ #. translators: Placeholders: %s - the server's web root path
789
+ msgid "For reference, your current web root path is: %s"
790
+ msgstr ""
791
+
792
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:178
793
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:188
794
+ msgid "Processing Gateway"
795
+ msgstr ""
796
+
797
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:196
798
+ msgid "Test Mode"
799
+ msgstr ""
800
+
801
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:197
802
+ msgid ""
803
+ "Enable to test Apple Pay functionality throughout your sites without "
804
+ "processing real payments."
805
+ msgstr ""
806
+
807
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:306
808
+ msgid "Your site must be served over HTTPS with a valid SSL certificate."
809
+ msgstr ""
810
+
811
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:316
812
+ #. translators: Placeholders: %1$s - plugin name, %2$s - a
813
+ #. currency/comma-separated list of currencies, %3$s - <a> tag, %4$s - </a> tag
814
+ msgid ""
815
+ "Accepts payment in %1$s only. %2$sConfigure%3$s WooCommerce to accept %1$s "
816
+ "to enable Apple Pay."
817
+ msgid_plural ""
818
+ "Accepts payment in one of %1$s only. %2$sConfigure%3$s WooCommerce to "
819
+ "accept one of %1$s to enable Apple Pay."
820
+ msgstr[0] ""
821
+ msgstr[1] ""
822
+
823
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:335
824
+ msgid ""
825
+ "Your %1$sMerchant Identity Certificate%2$s cannot be found. Please check "
826
+ "your path configuration."
827
+ msgstr ""
828
+
829
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:342
830
+ msgid "Apple Pay is disabled."
831
+ msgstr ""
832
+
833
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:381
834
+ msgid "Single products"
835
+ msgstr ""
836
+
837
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:382
838
+ msgid "Cart"
839
+ msgstr ""
840
+
841
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:383
842
+ msgid "Checkout"
843
+ msgstr ""
844
+
845
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-frontend.php:169
846
+ msgid "Buy with"
847
+ msgstr ""
848
+
849
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-frontend.php:202
850
+ msgid "By submitting your payment, you agree to our %1$sterms and conditions%2$s."
851
+ msgstr ""
852
+
853
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-frontend.php:351
854
+ msgid "or"
855
+ msgstr ""
856
+
857
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-orders.php:123
858
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-orders.php:136
859
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-orders.php:140
860
+ msgid "Error %d: Unable to create order. Please try again."
861
+ msgstr ""
862
+
863
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php:121
864
+ msgid "Apple Pay payment authorized."
865
+ msgstr ""
866
+
867
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php:155
868
+ msgid "Apple Pay payment failed. %s"
869
+ msgstr ""
870
+
871
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php:532
872
+ msgid "Subtotal"
873
+ msgstr ""
874
+
875
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php:542
876
+ msgid "Discount"
877
+ msgstr ""
878
+
879
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php:552
880
+ msgid "Shipping"
881
+ msgstr ""
882
+
883
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php:562
884
+ msgid "Fees"
885
+ msgstr ""
886
+
887
+ #: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php:572
888
+ msgid "Taxes"
889
+ msgstr ""
890
+
891
+ #: payment-gateway/class-sv-wc-payment-gateway-direct.php:61
892
+ msgid ""
893
+ "Payment error, please try another payment method or contact us to complete "
894
+ "your transaction."
895
+ msgstr ""
896
+
897
+ #: payment-gateway/class-sv-wc-payment-gateway-direct.php:161
898
+ #: payment-gateway/class-sv-wc-payment-gateway.php:480
899
+ msgid "Card expiration date is invalid"
900
+ msgstr ""
901
+
902
+ #: payment-gateway/class-sv-wc-payment-gateway-direct.php:185
903
+ #: payment-gateway/class-sv-wc-payment-gateway.php:473
904
+ msgid "Card number is missing"
905
+ msgstr ""
906
+
907
+ #: payment-gateway/class-sv-wc-payment-gateway-direct.php:191
908
+ #: payment-gateway/class-sv-wc-payment-gateway.php:476
909
+ msgid "Card number is invalid (wrong length)"
910
+ msgstr ""
911
+
912
+ #: payment-gateway/class-sv-wc-payment-gateway-direct.php:196
913
+ #: payment-gateway/class-sv-wc-payment-gateway.php:475
914
+ msgid "Card number is invalid (only digits allowed)"
915
+ msgstr ""
916
+
917
+ #: payment-gateway/class-sv-wc-payment-gateway-direct.php:201
918
+ #: payment-gateway/class-sv-wc-payment-gateway.php:474
919
+ msgid "Card number is invalid"
920
+ msgstr ""
921
+
922
+ #: payment-gateway/class-sv-wc-payment-gateway-direct.php:228
923
+ #: payment-gateway/class-sv-wc-payment-gateway.php:478
924
+ msgid "Card security code is invalid (only digits are allowed)"
925
+ msgstr ""
926
+
927
+ #: payment-gateway/class-sv-wc-payment-gateway-direct.php:234
928
+ #: payment-gateway/class-sv-wc-payment-gateway.php:479
929
+ msgid "Card security code is invalid (must be 3 or 4 digits)"
930
+ msgstr ""
931
+
932
+ #: payment-gateway/class-sv-wc-payment-gateway-direct.php:240
933
+ #: payment-gateway/class-sv-wc-payment-gateway.php:477
934
+ msgid "Card security code is missing"
935
+ msgstr ""
936
+
937
+ #: payment-gateway/class-sv-wc-payment-gateway-direct.php:267
938
+ #: payment-gateway/class-sv-wc-payment-gateway.php:489
939
+ msgid "Routing Number is missing"
940
+ msgstr ""
941
+
942
+ #: payment-gateway/class-sv-wc-payment-gateway-direct.php:274
943
+ #: payment-gateway/class-sv-wc-payment-gateway.php:490
944
+ msgid "Routing Number is invalid (only digits are allowed)"
945
+ msgstr ""
946
+
947
+ #: payment-gateway/class-sv-wc-payment-gateway-direct.php:280
948
+ #: payment-gateway/class-sv-wc-payment-gateway.php:491
949
+ msgid "Routing number is invalid (must be 9 digits)"
950
+ msgstr ""
951
+
952
+ #: payment-gateway/class-sv-wc-payment-gateway-direct.php:289
953
+ #: payment-gateway/class-sv-wc-payment-gateway.php:486
954
+ msgid "Account Number is missing"
955
+ msgstr ""
956
+
957
+ #: payment-gateway/class-sv-wc-payment-gateway-direct.php:296
958
+ #: payment-gateway/class-sv-wc-payment-gateway.php:487
959
+ msgid "Account Number is invalid (only digits are allowed)"
960
+ msgstr ""
961
+
962
+ #: payment-gateway/class-sv-wc-payment-gateway-direct.php:302
963
+ #: payment-gateway/class-sv-wc-payment-gateway.php:488
964
+ msgid "Account number is invalid (must be between 5 and 17 digits)"
965
+ msgstr ""
966
+
967
+ #: payment-gateway/class-sv-wc-payment-gateway-direct.php:309
968
+ #: payment-gateway/class-sv-wc-payment-gateway.php:485
969
+ msgid "Drivers license number is invalid"
970
+ msgstr ""
971
+
972
+ #: payment-gateway/class-sv-wc-payment-gateway-direct.php:315
973
+ #: payment-gateway/class-sv-wc-payment-gateway.php:481
974
+ msgid "Check Number is invalid (only digits are allowed)"
975
+ msgstr ""
976
+
977
+ #: payment-gateway/class-sv-wc-payment-gateway-direct.php:494
978
+ msgid "Unknown error"
979
+ msgstr ""
980
+
981
+ #: payment-gateway/class-sv-wc-payment-gateway-direct.php:503
982
+ msgid "Payment method address could not be updated. %s"
983
+ msgstr ""
984
+
985
+ #: payment-gateway/class-sv-wc-payment-gateway-direct.php:670
986
+ #: payment-gateway/class-sv-wc-payment-gateway.php:2614
987
+ #. translators: Placeholders: %1$s - payment method title, %2$s - payment
988
+ #. account type (savings/checking) (may or may not be available), %3$s - last
989
+ #. four digits of the account
990
+ msgid "%1$s Check Transaction Approved: %2$s account ending in %3$s"
991
+ msgstr ""
992
+
993
+ #: payment-gateway/class-sv-wc-payment-gateway-direct.php:675
994
+ #: payment-gateway/class-sv-wc-payment-gateway.php:2619
995
+ #. translators: Placeholders: %s - check number
996
+ msgid "Check number %s"
997
+ msgstr ""
998
+
999
+ #: payment-gateway/class-sv-wc-payment-gateway-direct.php:744
1000
+ #. translators: Placeholders: %1$s - payment method title, %2$s - environment
1001
+ #. ("Test"), %3$s - transaction type (authorization/charge), %4$s - card type
1002
+ #. (mastercard, visa, ...), %5$s - last four digits of the card
1003
+ msgid "%1$s %2$s %3$s Approved: %4$s ending in %5$s"
1004
+ msgstr ""
1005
+
1006
+ #: payment-gateway/class-sv-wc-payment-gateway-direct.php:757
1007
+ #: payment-gateway/class-sv-wc-payment-gateway-payment-form.php:686
1008
+ #: payment-gateway/class-sv-wc-payment-gateway.php:2571
1009
+ #. translators: Placeholders: %s - expiry date
1010
+ msgid "(expires %s)"
1011
+ msgstr ""
1012
+
1013
+ #: payment-gateway/class-sv-wc-payment-gateway-direct.php:829
1014
+ #. translators: Placeholders: %s - failure message
1015
+ msgid "Tokenization Request Failed: %s"
1016
+ msgstr ""
1017
+
1018
+ #: payment-gateway/class-sv-wc-payment-gateway-direct.php:840
1019
+ #. translators: Placeholders: %1$s - payment method title, %2$s - failure
1020
+ #. message
1021
+ msgid "%1$s Tokenization Request Failed: %2$s"
1022
+ msgstr ""
1023
+
1024
+ #: payment-gateway/class-sv-wc-payment-gateway-direct.php:898
1025
+ #. translators: Placeholders: %s - failure message. Payment method as in a
1026
+ #. specific credit card, e-check or bank account
1027
+ msgid "Oops, adding your new payment method failed: %s"
1028
+ msgstr ""
1029
+
1030
+ #: payment-gateway/class-sv-wc-payment-gateway-direct.php:939
1031
+ #. translators: Payment method as in a specific credit card. Placeholders: %1$s
1032
+ #. - card type (visa, mastercard, ...), %2$s - last four digits of the card,
1033
+ #. %3$s - card expiry date
1034
+ msgid "Nice! New payment method added: %1$s ending in %2$s (expires %3$s)"
1035
+ msgstr ""
1036
+
1037
+ #: payment-gateway/class-sv-wc-payment-gateway-direct.php:949
1038
+ #. translators: Payment method as in a specific e-check account. Placeholders:
1039
+ #. %1$s - account type (checking/savings), %2$s - last four digits of the
1040
+ #. account
1041
+ msgid "Nice! New payment method added: %1$s account ending in %2$s"
1042
+ msgstr ""
1043
+
1044
+ #: payment-gateway/class-sv-wc-payment-gateway-direct.php:956
1045
+ #. translators: Payment method as in a specific credit card, e-check or bank
1046
+ #. account
1047
+ msgid "Nice! New payment method added."
1048
+ msgstr ""
1049
+
1050
+ #: payment-gateway/class-sv-wc-payment-gateway-direct.php:1079
1051
+ #. translators: Placeholders: %1$s - site title, %2$s - customer email. Payment
1052
+ #. method as in a specific credit card, e-check or bank account
1053
+ msgid "%1$s - Add Payment Method for %2$s"
1054
+ msgstr ""
1055
+
1056
+ #: payment-gateway/class-sv-wc-payment-gateway-helper.php:180
1057
+ msgid "PayPal"
1058
+ msgstr ""
1059
+
1060
+ #: payment-gateway/class-sv-wc-payment-gateway-helper.php:181
1061
+ msgid "Checking Account"
1062
+ msgstr ""
1063
+
1064
+ #: payment-gateway/class-sv-wc-payment-gateway-helper.php:182
1065
+ msgid "Savings Account"
1066
+ msgstr ""
1067
+
1068
+ #: payment-gateway/class-sv-wc-payment-gateway-helper.php:183
1069
+ msgid "Credit / Debit Card"
1070
+ msgstr ""
1071
+
1072
+ #: payment-gateway/class-sv-wc-payment-gateway-helper.php:184
1073
+ msgid "Bank Account"
1074
+ msgstr ""
1075
+
1076
+ #: payment-gateway/class-sv-wc-payment-gateway-hosted.php:297
1077
+ msgid "Thank you for your order, please click the button below to pay."
1078
+ msgstr ""
1079
+
1080
+ #: payment-gateway/class-sv-wc-payment-gateway-hosted.php:298
1081
+ msgid "Thank you for your order. We are now redirecting you to complete payment."
1082
+ msgstr ""
1083
+
1084
+ #: payment-gateway/class-sv-wc-payment-gateway-hosted.php:299
1085
+ msgid "Pay Now"
1086
+ msgstr ""
1087
+
1088
+ #: payment-gateway/class-sv-wc-payment-gateway-hosted.php:300
1089
+ msgid "Cancel Order"
1090
+ msgstr ""
1091
+
1092
+ #: payment-gateway/class-sv-wc-payment-gateway-hosted.php:597
1093
+ #: payment-gateway/payment-tokens/class-sv-wc-payment-gateway-payment-tokens-handler.php:834
1094
+ #. translators: Placeholders: %1$s - payment gateway title (such as
1095
+ #. Authorize.net, Braintree, etc), %2$s - payment method name (mastercard, bank
1096
+ #. account, etc), %3$s - last four digits of the card/account, %4$s -
1097
+ #. card/account expiry date
1098
+ msgid "%1$s Payment Method Saved: %2$s ending in %3$s (expires %4$s)"
1099
+ msgstr ""
1100
+
1101
+ #: payment-gateway/class-sv-wc-payment-gateway-hosted.php:608
1102
+ #: payment-gateway/payment-tokens/class-sv-wc-payment-gateway-payment-tokens-handler.php:845
1103
+ #. translators: Placeholders: %1$s - payment gateway title (such as CyberSouce,
1104
+ #. NETbilling, etc), %2$s - account type (checking/savings - may or may not be
1105
+ #. available), %3$s - last four digits of the account
1106
+ msgid "%1$s eCheck Payment Method Saved: %2$s account ending in %3$s"
1107
+ msgstr ""
1108
+
1109
+ #: payment-gateway/class-sv-wc-payment-gateway-hosted.php:617
1110
+ #. translators: Placeholders: %s - payment gateway title (such as CyberSouce,
1111
+ #. NETbilling, etc)
1112
+ msgid "%s Payment Method Saved"
1113
+ msgstr ""
1114
+
1115
+ #: payment-gateway/class-sv-wc-payment-gateway-hosted.php:626
1116
+ #. translators: Placeholders: %s - a failed tokenization API error
1117
+ msgid "Tokenization failed. %s"
1118
+ msgstr ""
1119
+
1120
+ #: payment-gateway/class-sv-wc-payment-gateway-my-payment-methods.php:228
1121
+ #: payment-gateway/class-sv-wc-payment-gateway-my-payment-methods.php:761
1122
+ msgid "Edit"
1123
+ msgstr ""
1124
+
1125
+ #: payment-gateway/class-sv-wc-payment-gateway-my-payment-methods.php:230
1126
+ msgid "Oops, there was an error updating your payment method. Please try again."
1127
+ msgstr ""
1128
+
1129
+ #: payment-gateway/class-sv-wc-payment-gateway-my-payment-methods.php:231
1130
+ msgid "Are you sure you want to delete this payment method?"
1131
+ msgstr ""
1132
+
1133
+ #: payment-gateway/class-sv-wc-payment-gateway-my-payment-methods.php:290
1134
+ #. translators: Payment method as in a specific credit card, eCheck or bank
1135
+ #. account
1136
+ msgid "You do not have any saved payment methods."
1137
+ msgstr ""
1138
+
1139
+ #: payment-gateway/class-sv-wc-payment-gateway-my-payment-methods.php:329
1140
+ #. translators: Payment method as in a specific credit card, eCheck or bank
1141
+ #. account
1142
+ msgid "My Payment Methods"
1143
+ msgstr ""
1144
+
1145
+ #: payment-gateway/class-sv-wc-payment-gateway-my-payment-methods.php:337
1146
+ #. translators: Payment method as in a specific credit card, e-check or bank
1147
+ #. account
1148
+ msgid "Add New Payment Method"
1149
+ msgstr ""
1150
+
1151
+ #: payment-gateway/class-sv-wc-payment-gateway-my-payment-methods.php:425
1152
+ msgid "Method"
1153
+ msgstr ""
1154
+
1155
+ #: payment-gateway/class-sv-wc-payment-gateway-my-payment-methods.php:426
1156
+ msgid "Details"
1157
+ msgstr ""
1158
+
1159
+ #: payment-gateway/class-sv-wc-payment-gateway-my-payment-methods.php:427
1160
+ msgid "Expires"
1161
+ msgstr ""
1162
+
1163
+ #: payment-gateway/class-sv-wc-payment-gateway-my-payment-methods.php:428
1164
+ msgid "Default?"
1165
+ msgstr ""
1166
+
1167
+ #: payment-gateway/class-sv-wc-payment-gateway-my-payment-methods.php:429
1168
+ msgid "Actions"
1169
+ msgstr ""
1170
+
1171
+ #: payment-gateway/class-sv-wc-payment-gateway-my-payment-methods.php:465
1172
+ msgid "Credit/Debit Cards"
1173
+ msgstr ""
1174
+
1175
+ #: payment-gateway/class-sv-wc-payment-gateway-my-payment-methods.php:474
1176
+ msgid "Bank Accounts"
1177
+ msgstr ""
1178
+
1179
+ #: payment-gateway/class-sv-wc-payment-gateway-my-payment-methods.php:529
1180
+ msgid "N/A"
1181
+ msgstr ""
1182
+
1183
+ #: payment-gateway/class-sv-wc-payment-gateway-my-payment-methods.php:641
1184
+ #: payment-gateway/class-sv-wc-payment-gateway-privacy.php:200
1185
+ msgid "Nickname"
1186
+ msgstr ""
1187
+
1188
+ #: payment-gateway/class-sv-wc-payment-gateway-my-payment-methods.php:822
1189
+ msgid "Delete"
1190
+ msgstr ""
1191
+
1192
+ #: payment-gateway/class-sv-wc-payment-gateway-my-payment-methods.php:976
1193
+ msgid "Oops, you took too long, please try again."
1194
+ msgstr ""
1195
+
1196
+ #: payment-gateway/class-sv-wc-payment-gateway-my-payment-methods.php:989
1197
+ msgid "There was an error with your request, please try again."
1198
+ msgstr ""
1199
+
1200
+ #: payment-gateway/class-sv-wc-payment-gateway-my-payment-methods.php:1002
1201
+ #. translators: Payment method as in a specific credit card, e-check or bank
1202
+ #. account
1203
+ msgid "Error removing payment method"
1204
+ msgstr ""
1205
+
1206
+ #: payment-gateway/class-sv-wc-payment-gateway-my-payment-methods.php:1007
1207
+ #. translators: Payment method as in a specific credit card, e-check or bank
1208
+ #. account
1209
+ msgid "Payment method deleted."
1210
+ msgstr ""
1211
+
1212
+ #: payment-gateway/class-sv-wc-payment-gateway-payment-form.php:313
1213
+ msgid "Card Number"
1214
+ msgstr ""
1215
+
1216
+ #: payment-gateway/class-sv-wc-payment-gateway-payment-form.php:334
1217
+ msgid "MM / YY"
1218
+ msgstr ""
1219
+
1220
+ #: payment-gateway/class-sv-wc-payment-gateway-payment-form.php:352
1221
+ msgid "Card Security Code"
1222
+ msgstr ""
1223
+
1224
+ #: payment-gateway/class-sv-wc-payment-gateway-payment-form.php:355
1225
+ msgid "CSC"
1226
+ msgstr ""
1227
+
1228
+ #: payment-gateway/class-sv-wc-payment-gateway-payment-form.php:395
1229
+ msgid "Where do I find this?"
1230
+ msgstr ""
1231
+
1232
+ #: payment-gateway/class-sv-wc-payment-gateway-payment-form.php:401
1233
+ #. translators: e-check routing number, HTML form field label,
1234
+ #. https:en.wikipedia.org/wiki/Routing_transit_number
1235
+ msgid "Routing Number"
1236
+ msgstr ""
1237
+
1238
+ #: payment-gateway/class-sv-wc-payment-gateway-payment-form.php:420
1239
+ #. translators: e-check account number, HTML form field label
1240
+ msgid "Account Number"
1241
+ msgstr ""
1242
+
1243
+ #: payment-gateway/class-sv-wc-payment-gateway-payment-form.php:486
1244
+ #. translators: Test mode refers to the current software environment
1245
+ msgid "TEST MODE ENABLED"
1246
+ msgstr ""
1247
+
1248
+ #: payment-gateway/class-sv-wc-payment-gateway-payment-form.php:513
1249
+ msgid "Sample Check"
1250
+ msgstr ""
1251
+
1252
+ #: payment-gateway/class-sv-wc-payment-gateway-payment-form.php:588
1253
+ #. translators: Payment method as in a specific credit card, eCheck or bank
1254
+ #. account
1255
+ msgid "Manage Payment Methods"
1256
+ msgstr ""
1257
+
1258
+ #: payment-gateway/class-sv-wc-payment-gateway-payment-form.php:725
1259
+ msgid "Use a new card"
1260
+ msgstr ""
1261
+
1262
+ #: payment-gateway/class-sv-wc-payment-gateway-payment-form.php:725
1263
+ msgid "Use a new bank account"
1264
+ msgstr ""
1265
+
1266
+ #: payment-gateway/class-sv-wc-payment-gateway-payment-form.php:788
1267
+ #. translators: account as in customer's account on the eCommerce site
1268
+ msgid "Securely Save to Account"
1269
+ msgstr ""
1270
+
1271
+ #: payment-gateway/class-sv-wc-payment-gateway-payment-form.php:919
1272
+ msgid "Payment Info"
1273
+ msgstr ""
1274
+
1275
+ #: payment-gateway/class-sv-wc-payment-gateway-plugin.php:617
1276
+ #. translators: Placeholders: %1$s - plugin name, %2$s - <a> tag, %3$s - </a>
1277
+ #. tag
1278
+ msgid ""
1279
+ "%1$s: WooCommerce is not being forced over SSL; your customers' payment "
1280
+ "data may be at risk. %2$sVerify your site URLs here%3$s"
1281
+ msgstr ""
1282
+
1283
+ #: payment-gateway/class-sv-wc-payment-gateway-plugin.php:634
1284
+ #. translators: Placeholders: %s - payment gateway name
1285
+ msgid ""
1286
+ "%s will soon require TLS 1.2 support to process transactions and your "
1287
+ "server environment may need to be updated. Please contact your hosting "
1288
+ "provider to confirm that your site can send and receive TLS 1.2 connections "
1289
+ "and request they make any necessary updates."
1290
+ msgstr ""
1291
+
1292
+ #: payment-gateway/class-sv-wc-payment-gateway-plugin.php:690
1293
+ #. translators: Placeholders: %1$s - plugin name, %2$s - a
1294
+ #. currency/comma-separated list of currencies, %3$s - <a> tag, %4$s - </a> tag
1295
+ msgid ""
1296
+ "%1$s accepts payment in %2$s only. %3$sConfigure%4$s WooCommerce to accept "
1297
+ "%2$s to enable this gateway for checkout."
1298
+ msgid_plural ""
1299
+ "%1$s accepts payment in one of %2$s only. %3$sConfigure%4$s WooCommerce to "
1300
+ "accept one of %2$s to enable this gateway for checkout."
1301
+ msgstr[0] ""
1302
+ msgstr[1] ""
1303
+
1304
+ #: payment-gateway/class-sv-wc-payment-gateway-plugin.php:725
1305
+ #. translators: Placeholders: %1$s - payment gateway name, %2$s - opening <a>
1306
+ #. tag, %3$s - closing </a> tag
1307
+ msgid ""
1308
+ "Heads up! %1$s is currently configured to log transaction data for "
1309
+ "debugging purposes. If you are not experiencing any problems with payment "
1310
+ "processing, we recommend %2$sturning off Debug Mode%3$s"
1311
+ msgstr ""
1312
+
1313
+ #: payment-gateway/class-sv-wc-payment-gateway-plugin.php:771
1314
+ #. translators: Placeholders: %1$s - plugin name, %2$s - opening <a> HTML link
1315
+ #. tag, %3$s - closing </a> HTML link tag
1316
+ msgid ""
1317
+ "Heads up! Apple Pay for %1$s requires WooCommerce version 3.2 or greater. "
1318
+ "Please %2$supdate WooCommerce%3$s."
1319
+ msgstr ""
1320
+
1321
+ #: payment-gateway/class-sv-wc-payment-gateway-plugin.php:807
1322
+ #. translators: Placeholders: %1$s - payment gateway title (such as
1323
+ #. Authorize.net, Braintree, etc), %2$s - <a> tag, %3$s - </a> tag
1324
+ msgid ""
1325
+ "%1$s is inactive for subscription transactions. Please %2$senable "
1326
+ "tokenization%3$s to activate %1$s for Subscriptions."
1327
+ msgstr ""
1328
+
1329
+ #: payment-gateway/class-sv-wc-payment-gateway-plugin.php:825
1330
+ #. translators: Placeholders: %1$s - payment gateway title (such as
1331
+ #. Authorize.net, Braintree, etc), %2$s - <a> tag, %3$s - </a> tag
1332
+ msgid ""
1333
+ "%1$s is inactive for pre-order transactions. Please %2$senable "
1334
+ "tokenization%3$s to activate %1$s for Pre-Orders."
1335
+ msgstr ""
1336
+
1337
+ #: payment-gateway/class-sv-wc-payment-gateway-plugin.php:862
1338
+ msgid ""
1339
+ "You must enable tokenization for this gateway in order to support automatic "
1340
+ "renewal payments with the WooCommerce Subscriptions extension."
1341
+ msgstr ""
1342
+
1343
+ #: payment-gateway/class-sv-wc-payment-gateway-plugin.php:863
1344
+ msgid "Inactive"
1345
+ msgstr ""
1346
+
1347
+ #: payment-gateway/class-sv-wc-payment-gateway-privacy.php:115
1348
+ msgid "%s Customer ID"
1349
+ msgstr ""
1350
+
1351
+ #: payment-gateway/class-sv-wc-payment-gateway-privacy.php:184
1352
+ msgid "Type"
1353
+ msgstr ""
1354
+
1355
+ #: payment-gateway/class-sv-wc-payment-gateway-privacy.php:254
1356
+ msgid "Removed payment token \"%d\""
1357
+ msgstr ""
1358
+
1359
+ #: payment-gateway/class-sv-wc-payment-gateway-privacy.php:301
1360
+ msgid "Expiry Date"
1361
+ msgstr ""
1362
+
1363
+ #: payment-gateway/class-sv-wc-payment-gateway.php:341
1364
+ msgid "you successfully processed a payment!"
1365
+ msgstr ""
1366
+
1367
+ #: payment-gateway/class-sv-wc-payment-gateway.php:346
1368
+ msgid "you successfully processed a refund!"
1369
+ msgstr ""
1370
+
1371
+ #: payment-gateway/class-sv-wc-payment-gateway.php:482
1372
+ msgid "Check Number is missing"
1373
+ msgstr ""
1374
+
1375
+ #: payment-gateway/class-sv-wc-payment-gateway.php:483
1376
+ msgid "Drivers license state is missing"
1377
+ msgstr ""
1378
+
1379
+ #: payment-gateway/class-sv-wc-payment-gateway.php:484
1380
+ msgid "Drivers license number is missing"
1381
+ msgstr ""
1382
+
1383
+ #: payment-gateway/class-sv-wc-payment-gateway.php:659
1384
+ msgid "Continue to Payment"
1385
+ msgstr ""
1386
+
1387
+ #: payment-gateway/class-sv-wc-payment-gateway.php:659
1388
+ msgid "Place order"
1389
+ msgstr ""
1390
+
1391
+ #: payment-gateway/class-sv-wc-payment-gateway.php:691
1392
+ msgid "Thank you for your order."
1393
+ msgstr ""
1394
+
1395
+ #: payment-gateway/class-sv-wc-payment-gateway.php:1101
1396
+ msgid "Credit Card"
1397
+ msgstr ""
1398
+
1399
+ #: payment-gateway/class-sv-wc-payment-gateway.php:1103
1400
+ msgid "eCheck"
1401
+ msgstr ""
1402
+
1403
+ #: payment-gateway/class-sv-wc-payment-gateway.php:1121
1404
+ msgid "Pay securely using your credit card."
1405
+ msgstr ""
1406
+
1407
+ #: payment-gateway/class-sv-wc-payment-gateway.php:1123
1408
+ msgid "Pay securely using your checking account."
1409
+ msgstr ""
1410
+
1411
+ #: payment-gateway/class-sv-wc-payment-gateway.php:1143
1412
+ msgid "Enable this gateway"
1413
+ msgstr ""
1414
+
1415
+ #: payment-gateway/class-sv-wc-payment-gateway.php:1149
1416
+ msgid "Title"
1417
+ msgstr ""
1418
+
1419
+ #: payment-gateway/class-sv-wc-payment-gateway.php:1151
1420
+ msgid "Payment method title that the customer will see during checkout."
1421
+ msgstr ""
1422
+
1423
+ #: payment-gateway/class-sv-wc-payment-gateway.php:1156
1424
+ msgid "Description"
1425
+ msgstr ""
1426
+
1427
+ #: payment-gateway/class-sv-wc-payment-gateway.php:1158
1428
+ msgid "Payment method description that the customer will see during checkout."
1429
+ msgstr ""
1430
+
1431
+ #: payment-gateway/class-sv-wc-payment-gateway.php:1187
1432
+ msgid "Detailed Decline Messages"
1433
+ msgstr ""
1434
+
1435
+ #: payment-gateway/class-sv-wc-payment-gateway.php:1189
1436
+ msgid ""
1437
+ "Check to enable detailed decline messages to the customer during checkout "
1438
+ "when possible, rather than a generic decline message."
1439
+ msgstr ""
1440
+
1441
+ #: payment-gateway/class-sv-wc-payment-gateway.php:1199
1442
+ #. translators: Placeholders: %1$s - <a> tag, %2$s - </a> tag
1443
+ msgid ""
1444
+ "Show Detailed Error Messages and API requests/responses on the checkout "
1445
+ "page and/or save them to the %1$sdebug log%2$s"
1446
+ msgstr ""
1447
+
1448
+ #: payment-gateway/class-sv-wc-payment-gateway.php:1203
1449
+ msgid "Show on Checkout Page"
1450
+ msgstr ""
1451
+
1452
+ #: payment-gateway/class-sv-wc-payment-gateway.php:1206
1453
+ #. translators: show debugging information on both checkout page and in the log
1454
+ msgid "Both"
1455
+ msgstr ""
1456
+
1457
+ #: payment-gateway/class-sv-wc-payment-gateway.php:1269
1458
+ msgid "Select the gateway environment to use for transactions."
1459
+ msgstr ""
1460
+
1461
+ #: payment-gateway/class-sv-wc-payment-gateway.php:1323
1462
+ msgid "Share connection settings"
1463
+ msgstr ""
1464
+
1465
+ #: payment-gateway/class-sv-wc-payment-gateway.php:1325
1466
+ msgid "Use connection/authentication settings from other gateway"
1467
+ msgstr ""
1468
+
1469
+ #: payment-gateway/class-sv-wc-payment-gateway.php:1328
1470
+ msgid "Disabled because the other gateway is using these settings"
1471
+ msgstr ""
1472
+
1473
+ #: payment-gateway/class-sv-wc-payment-gateway.php:1345
1474
+ msgid "Card Verification (CSC)"
1475
+ msgstr ""
1476
+
1477
+ #: payment-gateway/class-sv-wc-payment-gateway.php:1346
1478
+ msgid "Display the Card Security Code (CV2) field on checkout"
1479
+ msgstr ""
1480
+
1481
+ #: payment-gateway/class-sv-wc-payment-gateway.php:1354
1482
+ msgid "Saved Card Verification"
1483
+ msgstr ""
1484
+
1485
+ #: payment-gateway/class-sv-wc-payment-gateway.php:1355
1486
+ msgid "Display the Card Security Code field when paying with a saved card"
1487
+ msgstr ""
1488
+
1489
+ #: payment-gateway/class-sv-wc-payment-gateway.php:1686
1490
+ #. translators: Placeholders: %1$s - site title, %2$s - order number
1491
+ msgid "%1$s - Order %2$s"
1492
+ msgstr ""
1493
+
1494
+ #: payment-gateway/class-sv-wc-payment-gateway.php:1815
1495
+ #. translators: Placeholders: %1$s - site title, %2$s - order number.
1496
+ #. Definitions: Capture as in capture funds from a credit card.
1497
+ msgid "%1$s - Capture for Order %2$s"
1498
+ msgstr ""
1499
+
1500
+ #: payment-gateway/class-sv-wc-payment-gateway.php:1958
1501
+ #. translators: Placeholders: %1$s - site title, %2$s - order number
1502
+ msgid "%1$s - Refund for Order %2$s"
1503
+ msgstr ""
1504
+
1505
+ #: payment-gateway/class-sv-wc-payment-gateway.php:2025
1506
+ #. translators: Placeholders: %1$s - payment gateway title (such as
1507
+ #. Authorize.net, Braintree, etc), %2$s - a monetary amount
1508
+ msgid "%1$s Refund in the amount of %2$s approved."
1509
+ msgstr ""
1510
+
1511
+ #: payment-gateway/class-sv-wc-payment-gateway.php:2055
1512
+ #. translators: Placeholders: %1$s - payment gateway title (such as
1513
+ #. Authorize.net, Braintree, etc), %2$s - error code, %3$s - error message
1514
+ msgid "%1$s Refund Failed: %2$s - %3$s"
1515
+ msgstr ""
1516
+
1517
+ #: payment-gateway/class-sv-wc-payment-gateway.php:2063
1518
+ #. translators: Placeholders: %1$s - payment gateway title (such as
1519
+ #. Authorize.net, Braintree, etc), %2$s - error message
1520
+ msgid "%1$s Refund Failed: %2$s"
1521
+ msgstr ""
1522
+
1523
+ #: payment-gateway/class-sv-wc-payment-gateway.php:2084
1524
+ #. translators: Placeholders: %s - payment gateway title (such as
1525
+ #. Authorize.net, Braintree, etc)
1526
+ msgid "%s Order completely refunded."
1527
+ msgstr ""
1528
+
1529
+ #: payment-gateway/class-sv-wc-payment-gateway.php:2139
1530
+ msgid ""
1531
+ "Oops, you cannot partially void this order. Please use the full order "
1532
+ "amount."
1533
+ msgstr ""
1534
+
1535
+ #: payment-gateway/class-sv-wc-payment-gateway.php:2226
1536
+ #. translators: Placeholders: %1$s - payment gateway title, %2$s - error code,
1537
+ #. %3$s - error message. Void as in to void an order.
1538
+ msgid "%1$s Void Failed: %2$s - %3$s"
1539
+ msgstr ""
1540
+
1541
+ #: payment-gateway/class-sv-wc-payment-gateway.php:2234
1542
+ #. translators: Placeholders: %1$s - payment gateway title, %2$s - error
1543
+ #. message. Void as in to void an order.
1544
+ msgid "%1$s Void Failed: %2$s"
1545
+ msgstr ""
1546
+
1547
+ #: payment-gateway/class-sv-wc-payment-gateway.php:2258
1548
+ #. translators: Placeholders: %1$s - payment gateway title, %2$s - a monetary
1549
+ #. amount. Void as in to void an order.
1550
+ msgid "%1$s Void in the amount of %2$s approved."
1551
+ msgstr ""
1552
+
1553
+ #: payment-gateway/class-sv-wc-payment-gateway.php:2550
1554
+ #. translators: Placeholders: %1$s - payment method title, %2$s - environment
1555
+ #. ("Test"), %3$s - transaction type (authorization/charge)
1556
+ msgid "%1$s %2$s %3$s Approved"
1557
+ msgstr ""
1558
+
1559
+ #: payment-gateway/class-sv-wc-payment-gateway.php:2560
1560
+ #. translators: Placeholders: %1$s - credit card type (MasterCard, Visa,
1561
+ #. etc...), %2$s - last four digits of the card
1562
+ msgid "%1$s ending in %2$s"
1563
+ msgstr ""
1564
+
1565
+ #: payment-gateway/class-sv-wc-payment-gateway.php:2656
1566
+ #. translators: Placeholders: %1$s - payment gateway title, %2$s - message
1567
+ #. (probably reason for the transaction being held for review)
1568
+ msgid "%1$s Transaction Held for Review (%2$s)"
1569
+ msgstr ""
1570
+
1571
+ #: payment-gateway/class-sv-wc-payment-gateway.php:2745
1572
+ #. translators: Placeholders: %1$s - payment gateway title, %2$s - error
1573
+ #. message; e.g. Order Note: [Payment method] Payment failed [error]
1574
+ msgid "%1$s Payment Failed (%2$s)"
1575
+ msgstr ""
1576
+
1577
+ #: payment-gateway/class-sv-wc-payment-gateway.php:2780
1578
+ #. translators: Placeholders: %1$s - payment gateway title, %2$s -
1579
+ #. message/error
1580
+ msgid "%1$s Transaction Cancelled (%2$s)"
1581
+ msgstr ""
1582
+
1583
+ #: payment-gateway/class-sv-wc-payment-gateway.php:3028
1584
+ msgid "Transaction Type"
1585
+ msgstr ""
1586
+
1587
+ #: payment-gateway/class-sv-wc-payment-gateway.php:3030
1588
+ msgid ""
1589
+ "Select how transactions should be processed. Charge submits all "
1590
+ "transactions for settlement, Authorization simply authorizes the order "
1591
+ "total for capture later."
1592
+ msgstr ""
1593
+
1594
+ #: payment-gateway/class-sv-wc-payment-gateway.php:3041
1595
+ msgid "Charge Virtual-Only Orders"
1596
+ msgstr ""
1597
+
1598
+ #: payment-gateway/class-sv-wc-payment-gateway.php:3043
1599
+ msgid ""
1600
+ "If the order contains exclusively virtual items, enable this to immediately "
1601
+ "charge, rather than authorize, the transaction."
1602
+ msgstr ""
1603
+
1604
+ #: payment-gateway/class-sv-wc-payment-gateway.php:3051
1605
+ msgid "Enable Partial Capture"
1606
+ msgstr ""
1607
+
1608
+ #: payment-gateway/class-sv-wc-payment-gateway.php:3053
1609
+ msgid "Allow orders to be partially captured multiple times."
1610
+ msgstr ""
1611
+
1612
+ #: payment-gateway/class-sv-wc-payment-gateway.php:3065
1613
+ msgid "Capture Paid Orders"
1614
+ msgstr ""
1615
+
1616
+ #: payment-gateway/class-sv-wc-payment-gateway.php:3068
1617
+ msgid "Automatically capture orders when they are changed to %s."
1618
+ msgstr ""
1619
+
1620
+ #: payment-gateway/class-sv-wc-payment-gateway.php:3069
1621
+ msgid "a paid status"
1622
+ msgstr ""
1623
+
1624
+ #: payment-gateway/class-sv-wc-payment-gateway.php:3259
1625
+ msgid "Accepted Card Logos"
1626
+ msgstr ""
1627
+
1628
+ #: payment-gateway/class-sv-wc-payment-gateway.php:3261
1629
+ msgid ""
1630
+ "These are the card logos that are displayed to customers as accepted during "
1631
+ "checkout."
1632
+ msgstr ""
1633
+
1634
+ #: payment-gateway/class-sv-wc-payment-gateway.php:3264
1635
+ #. translators: Placeholders: %1$s - <strong> tag, %2$s - </strong> tag
1636
+ msgid ""
1637
+ "This setting %1$sdoes not%2$s change which card types the gateway will "
1638
+ "accept. Accepted cards are configured from your payment processor account."
1639
+ msgstr ""
1640
+
1641
+ #: payment-gateway/class-sv-wc-payment-gateway.php:3355
1642
+ #. translators:
1643
+ #. http:www.cybersource.com/products/payment_security/payment_tokenization/ and
1644
+ #. https:en.wikipedia.org/wiki/Tokenization_(data_security)
1645
+ msgid "Tokenization"
1646
+ msgstr ""
1647
+
1648
+ #: payment-gateway/class-sv-wc-payment-gateway.php:3356
1649
+ msgid "Allow customers to securely save their payment details for future checkout."
1650
+ msgstr ""
1651
+
1652
+ #: payment-gateway/class-sv-wc-payment-gateway.php:4175
1653
+ #. translators: %1$s - gateway name, %2$s - <a> tag, %3$s - </a> tag, %4$s -
1654
+ #. <a> tag, %5$s - </a> tag
1655
+ msgid ""
1656
+ "Heads up! %1$s is not fully configured and cannot accept payments. Please "
1657
+ "%2$sreview the documentation%3$s and configure the %4$sgateway settings%5$s."
1658
+ msgstr ""
1659
+
1660
+ #: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-pre-orders.php:261
1661
+ msgid "Pre-Order Tokenization attempt failed (%s)"
1662
+ msgstr ""
1663
+
1664
+ #: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-pre-orders.php:307
1665
+ msgid "%s - Pre-Order Release Payment for Order %s"
1666
+ msgstr ""
1667
+
1668
+ #: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-pre-orders.php:311
1669
+ msgid "Payment token missing/invalid."
1670
+ msgstr ""
1671
+
1672
+ #: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-pre-orders.php:336
1673
+ msgid "%s %s Pre-Order Release Payment Approved: %s ending in %s (expires %s)"
1674
+ msgstr ""
1675
+
1676
+ #: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-pre-orders.php:347
1677
+ msgid "%s eCheck Pre-Order Release Payment Approved: %s ending in %s"
1678
+ msgstr ""
1679
+
1680
+ #: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-pre-orders.php:391
1681
+ msgid "Pre-Order Release Payment Failed: %s"
1682
+ msgstr ""
1683
+
1684
+ #: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-subscriptions.php:326
1685
+ msgid "Subscription Renewal: payment token is missing/invalid."
1686
+ msgstr ""
1687
+
1688
+ #: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-subscriptions.php:352
1689
+ msgid "%1$s - Subscription Renewal Order %2$s"
1690
+ msgstr ""
1691
+
1692
+ #: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-subscriptions.php:484
1693
+ #. translators: Placeholders: %1$s - payment gateway title, %2$s - error
1694
+ #. message; e.g. Order Note: [Payment method] Payment Change failed [error]
1695
+ msgid "%1$s Payment Change Failed (%2$s)"
1696
+ msgstr ""
1697
+
1698
+ #: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-subscriptions.php:627
1699
+ msgid "Via %s ending in %s"
1700
+ msgstr ""
1701
+
1702
+ #: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-subscriptions.php:654
1703
+ msgid "Subscriptions"
1704
+ msgstr ""
1705
+
1706
+ #: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-subscriptions.php:723
1707
+ msgid ""
1708
+ "This payment method is tied to a subscription and cannot be deleted. Please "
1709
+ "switch the subscription to another method first."
1710
+ msgstr ""
1711
+
1712
+ #: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-subscriptions.php:775
1713
+ msgid "Payment Token"
1714
+ msgstr ""
1715
+
1716
+ #: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-subscriptions.php:804
1717
+ #: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-subscriptions.php:809
1718
+ msgid "%s is required."
1719
+ msgstr ""
1720
+
1721
+ #: payment-gateway/payment-tokens/class-sv-wc-payment-gateway-payment-tokens-handler.php:173
1722
+ msgid "Unknown Error"
1723
+ msgstr ""
1724
+
1725
+ #: utilities/class-sv-wp-background-job-handler.php:644
1726
+ msgid "Job data key \"%s\" not set"
1727
+ msgstr ""
1728
+
1729
+ #: utilities/class-sv-wp-background-job-handler.php:648
1730
+ msgid "Job data key \"%s\" is not an array"
1731
+ msgstr ""
1732
+
1733
+ #: utilities/class-sv-wp-background-job-handler.php:884
1734
+ msgid "Every %d Minutes"
1735
+ msgstr ""
1736
+
1737
+ #: utilities/class-sv-wp-background-job-handler.php:1048
1738
+ msgid "Background Processing Test"
1739
+ msgstr ""
1740
+
1741
+ #: utilities/class-sv-wp-background-job-handler.php:1049
1742
+ msgid "Run Test"
1743
+ msgstr ""
1744
+
1745
+ #: utilities/class-sv-wp-background-job-handler.php:1050
1746
+ msgid ""
1747
+ "This tool will test whether your server is capable of processing background "
1748
+ "jobs."
1749
+ msgstr ""
1750
+
1751
+ #: utilities/class-sv-wp-background-job-handler.php:1068
1752
+ msgid "Success! You should be able to process background jobs."
1753
+ msgstr ""
1754
+
1755
+ #: utilities/class-sv-wp-background-job-handler.php:1071
1756
+ msgid ""
1757
+ "Could not connect. Please ask your hosting company to ensure your server "
1758
+ "has loopback connections enabled."
1759
+ msgstr ""
1760
+
1761
+ #: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:396
1762
+ msgctxt "enhanced select"
1763
+ msgid "No matches found"
1764
+ msgstr ""
1765
+
1766
+ #: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:397
1767
+ msgctxt "enhanced select"
1768
+ msgid "Loading failed"
1769
+ msgstr ""
1770
+
1771
+ #: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:398
1772
+ msgctxt "enhanced select"
1773
+ msgid "Please enter 1 or more characters"
1774
+ msgstr ""
1775
+
1776
+ #: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:399
1777
+ msgctxt "enhanced select"
1778
+ msgid "Please enter %qty% or more characters"
1779
+ msgstr ""
1780
+
1781
+ #: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:400
1782
+ msgctxt "enhanced select"
1783
+ msgid "Please delete 1 character"
1784
+ msgstr ""
1785
+
1786
+ #: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:401
1787
+ msgctxt "enhanced select"
1788
+ msgid "Please delete %qty% characters"
1789
+ msgstr ""
1790
+
1791
+ #: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:402
1792
+ msgctxt "enhanced select"
1793
+ msgid "You can only select 1 item"
1794
+ msgstr ""
1795
+
1796
+ #: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:403
1797
+ msgctxt "enhanced select"
1798
+ msgid "You can only select %qty% items"
1799
+ msgstr ""
1800
+
1801
+ #: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:404
1802
+ msgctxt "enhanced select"
1803
+ msgid "Loading more results&hellip;"
1804
+ msgstr ""
1805
+
1806
+ #: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:405
1807
+ msgctxt "enhanced select"
1808
+ msgid "Searching&hellip;"
1809
+ msgstr ""
1810
+
1811
+ #: class-sv-wc-helper.php:408
1812
+ msgctxt "coordinating conjunction for a list of items: a, b, and c"
1813
+ msgid "and"
1814
+ msgstr ""
1815
+
1816
+ #: class-sv-wc-plugin.php:611
1817
+ msgctxt "noun"
1818
+ msgid "Support"
1819
+ msgstr ""
1820
+
1821
+ #: class-sv-wc-plugin.php:616
1822
+ msgctxt "verb"
1823
+ msgid "Review"
1824
+ msgstr ""
1825
+
1826
+ #: payment-gateway/class-sv-wc-payment-gateway-direct.php:746
1827
+ #: payment-gateway/class-sv-wc-payment-gateway.php:2552
1828
+ msgctxt "noun, software environment"
1829
+ msgid "Test"
1830
+ msgstr ""
1831
+
1832
+ #: payment-gateway/class-sv-wc-payment-gateway-direct.php:747
1833
+ #: payment-gateway/class-sv-wc-payment-gateway.php:2553
1834
+ #: payment-gateway/class-sv-wc-payment-gateway.php:3034
1835
+ msgctxt "credit card transaction type"
1836
+ msgid "Authorization"
1837
+ msgstr ""
1838
+
1839
+ #: payment-gateway/class-sv-wc-payment-gateway-direct.php:747
1840
+ #: payment-gateway/class-sv-wc-payment-gateway.php:2553
1841
+ #: payment-gateway/class-sv-wc-payment-gateway.php:3033
1842
+ msgctxt "noun, credit card transaction type"
1843
+ msgid "Charge"
1844
+ msgstr ""
1845
+
1846
+ #: payment-gateway/class-sv-wc-payment-gateway-helper.php:193
1847
+ msgctxt "payment method type"
1848
+ msgid "Account"
1849
+ msgstr ""
1850
+
1851
+ #: payment-gateway/class-sv-wc-payment-gateway-helper.php:229
1852
+ #: payment-gateway/class-sv-wc-payment-gateway.php:3292
1853
+ msgctxt "credit card type"
1854
+ msgid "Visa"
1855
+ msgstr ""
1856
+
1857
+ #: payment-gateway/class-sv-wc-payment-gateway-helper.php:233
1858
+ #: payment-gateway/class-sv-wc-payment-gateway.php:3293
1859
+ msgctxt "credit card type"
1860
+ msgid "MasterCard"
1861
+ msgstr ""
1862
+
1863
+ #: payment-gateway/class-sv-wc-payment-gateway-helper.php:237
1864
+ #: payment-gateway/class-sv-wc-payment-gateway.php:3294
1865
+ msgctxt "credit card type"
1866
+ msgid "American Express"
1867
+ msgstr ""
1868
+
1869
+ #: payment-gateway/class-sv-wc-payment-gateway-helper.php:241
1870
+ msgctxt "credit card type"
1871
+ msgid "Diners Club"
1872
+ msgstr ""
1873
+
1874
+ #: payment-gateway/class-sv-wc-payment-gateway-helper.php:245
1875
+ #: payment-gateway/class-sv-wc-payment-gateway.php:3295
1876
+ msgctxt "credit card type"
1877
+ msgid "Discover"
1878
+ msgstr ""
1879
+
1880
+ #: payment-gateway/class-sv-wc-payment-gateway-helper.php:249
1881
+ #: payment-gateway/class-sv-wc-payment-gateway.php:3297
1882
+ msgctxt "credit card type"
1883
+ msgid "JCB"
1884
+ msgstr ""
1885
+
1886
+ #: payment-gateway/class-sv-wc-payment-gateway-helper.php:253
1887
+ msgctxt "credit card type"
1888
+ msgid "CarteBleue"
1889
+ msgstr ""
1890
+
1891
+ #: payment-gateway/class-sv-wc-payment-gateway-helper.php:257
1892
+ msgctxt "credit card type"
1893
+ msgid "Maestro"
1894
+ msgstr ""
1895
+
1896
+ #: payment-gateway/class-sv-wc-payment-gateway-helper.php:261
1897
+ msgctxt "credit card type"
1898
+ msgid "Laser"
1899
+ msgstr ""
1900
+
1901
+ #: payment-gateway/class-sv-wc-payment-gateway.php:3296
1902
+ msgctxt "credit card type"
1903
+ msgid "Diners"
1904
+ msgstr ""
1905
+
1906
+ #: payment-gateway/class-sv-wc-payment-gateway-payment-form.php:446
1907
+ #. translators: http:www.investopedia.com/terms/c/checkingaccount.asp
1908
+ msgctxt "account type"
1909
+ msgid "Checking"
1910
+ msgstr ""
1911
+
1912
+ #: payment-gateway/class-sv-wc-payment-gateway-payment-form.php:448
1913
+ #. translators: http:www.investopedia.com/terms/s/savingsaccount.asp
1914
+ msgctxt "account type"
1915
+ msgid "Savings"
1916
+ msgstr ""
1917
+
1918
+ #: payment-gateway/class-sv-wc-payment-gateway.php:2333
1919
+ msgctxt "hash before order number"
1920
+ msgid "#"
1921
+ msgstr ""
1922
+
1923
+ #: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-subscriptions.php:684
1924
+ msgctxt "hash before order number"
1925
+ msgid "#%s"
1926
+ msgstr ""
1927
+
1928
+ #: payment-gateway/class-sv-wc-payment-gateway.php:3062
1929
+ msgctxt ""
1930
+ "coordinating conjunction for a list of order statuses: on-hold, processing, "
1931
+ "or completed"
1932
+ msgid "or"
1933
+ msgstr ""
1934
+
1935
+ #: payment-gateway/class-sv-wc-payment-gateway.php:3901
1936
+ #. translators: https:www.skyverge.com/for-translators-environments/
1937
+ msgctxt "software environment"
1938
+ msgid "Production"
1939
+ msgstr ""
vendor/skyverge/wc-plugin-framework/woocommerce/index.php ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * WooCommerce Plugin Framework
4
+ *
5
+ * This source file is subject to the GNU General Public License v3.0
6
+ * that is bundled with this package in the file license.txt.
7
+ * It is also available through the world-wide-web at this URL:
8
+ * http://www.gnu.org/licenses/gpl-3.0.html
9
+ * If you did not receive a copy of the license and are unable to
10
+ * obtain it through the world-wide-web, please send an email
11
+ * to license@skyverge.com so we can send you a copy immediately.
12
+ *
13
+ * DISCLAIMER
14
+ *
15
+ * Do not edit or add to this file if you wish to upgrade the plugin to newer
16
+ * versions in the future. If you wish to customize the plugin for your
17
+ * needs please refer to http://www.skyverge.com
18
+ *
19
+ * @package SkyVerge/WooCommerce
20
+ * @author SkyVerge
21
+ * @copyright Copyright (c) 2013-2020, SkyVerge, Inc.
22
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GNU General Public License v3.0
23
+ */
24
+
25
+ /**
26
+ * The sole purpose of this file is to support WordPress i18n tools to
27
+ * extract gettext messages from the framework files. For some reason,
28
+ * a root-level entry file is required for it to work.
29
+ */
vendor/skyverge/wc-plugin-framework/woocommerce/rest-api/class-sv-wc-plugin-rest-api.php ADDED
@@ -0,0 +1,157 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * WooCommerce Plugin Framework
4
+ *
5
+ * This source file is subject to the GNU General Public License v3.0
6
+ * that is bundled with this package in the file license.txt.
7
+ * It is also available through the world-wide-web at this URL:
8
+ * http://www.gnu.org/licenses/gpl-3.0.html
9
+ * If you did not receive a copy of the license and are unable to
10
+ * obtain it through the world-wide-web, please send an email
11
+ * to license@skyverge.com so we can send you a copy immediately.
12
+ *
13
+ * DISCLAIMER
14
+ *
15
+ * Do not edit or add to this file if you wish to upgrade the plugin to newer
16
+ * versions in the future. If you wish to customize the plugin for your
17
+ * needs please refer to http://www.skyverge.com
18
+ *
19
+ * @package SkyVerge/WooCommerce/Plugin/Classes
20
+ * @author SkyVerge
21
+ * @copyright Copyright (c) 2013-2020, SkyVerge, Inc.
22
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GNU General Public License v3.0
23
+ */
24
+
25
+ namespace SkyVerge\WooCommerce\PluginFramework\v5_5_4;
26
+
27
+ defined( 'ABSPATH' ) or exit;
28
+
29
+ if ( ! class_exists( '\\SkyVerge\\WooCommerce\\PluginFramework\\v5_5_4\\REST_API' ) ) :
30
+
31
+
32
+ /**
33
+ * The plugin REST API handler class.
34
+ *
35
+ * This is responsible for hooking in to the WC REST API to add data for existing
36
+ * routes and/or register new routes.
37
+ *
38
+ * @since 5.2.0
39
+ */
40
+ class REST_API {
41
+
42
+
43
+ /** @var SV_WC_Plugin plugin instance */
44
+ private $plugin;
45
+
46
+
47
+ /**
48
+ * Constructs the class.
49
+ *
50
+ * @since 5.2.0
51
+ *
52
+ * @param SV_WC_Plugin $plugin plugin instance
53
+ */
54
+ public function __construct( SV_WC_Plugin $plugin ) {
55
+
56
+ $this->plugin = $plugin;
57
+
58
+ $this->add_hooks();
59
+ }
60
+
61
+
62
+ /**
63
+ * Adds the action and filter hooks.
64
+ *
65
+ * @since 5.2.0
66
+ */
67
+ protected function add_hooks() {
68
+
69
+ // add plugin data to the system status
70
+ add_filter( 'woocommerce_rest_prepare_system_status', array( $this, 'add_system_status_data' ), 10, 3 );
71
+
72
+ // registers new WC REST API routes
73
+ add_action( 'rest_api_init', array( $this, 'register_routes' ) );
74
+ }
75
+
76
+
77
+ /**
78
+ * Adds plugin data to the system status.
79
+ *
80
+ * @internal
81
+ *
82
+ * @since 5.2.0
83
+ *
84
+ * @param \WP_REST_Response $response REST API response object
85
+ * @param array $system_status system status data
86
+ * @param \WP_REST_Request $request REST API request object
87
+ * @return \WP_REST_Response
88
+ */
89
+ public function add_system_status_data( $response, $system_status, $request ) {
90
+
91
+ $data = array(
92
+ 'is_payment_gateway' => $this->get_plugin() instanceof SV_WC_Payment_Gateway_Plugin,
93
+ 'lifecycle_events' => $this->get_plugin()->get_lifecycle_handler()->get_event_history(),
94
+ );
95
+
96
+ $data = array_merge( $data, $this->get_system_status_data() );
97
+
98
+ /**
99
+ * Filters the data added to the WooCommerce REST API System Status response.
100
+ *
101
+ * @since 5.2.0
102
+ *
103
+ * @param array $data system status response data
104
+ * @param \WP_REST_Response $response REST API response object
105
+ * @param \WP_REST_Request $request REST API request object
106
+ */
107
+ $data = apply_filters( 'wc_' . $this->get_plugin()->get_id() . '_rest_api_system_status_data', $data, $response, $request );
108
+
109
+ $response->data[ 'wc_' . $this->get_plugin()->get_id() ] = $data;
110
+
111
+ return $response;
112
+ }
113
+
114
+
115
+ /**
116
+ * Gets the data to add to the WooCommerce REST API System Status response.
117
+ *
118
+ * Plugins can override this to add their own data.
119
+ *
120
+ * @since 5.2.0
121
+ *
122
+ * @return array
123
+ */
124
+ protected function get_system_status_data() {
125
+
126
+ return array();
127
+ }
128
+
129
+
130
+ /**
131
+ * Registers new WC REST API routes.
132
+ *
133
+ * @since 5.2.0
134
+ */
135
+ public function register_routes() {
136
+
137
+ // stub
138
+ }
139
+
140
+
141
+ /**
142
+ * Gets the plugin instance.
143
+ *
144
+ * @since 5.2.0
145
+ *
146
+ * @return SV_WC_Plugin|SV_WC_Payment_Gateway_Plugin
147
+ */
148
+ protected function get_plugin() {
149
+
150
+ return $this->plugin;
151
+ }
152
+
153
+
154
+ }
155
+
156
+
157
+ endif;
vendor/skyverge/wc-plugin-framework/woocommerce/utilities/class-sv-wp-async-request.php ADDED
@@ -0,0 +1,192 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * WooCommerce Plugin Framework
4
+ *
5
+ * This source file is subject to the GNU General Public License v3.0
6
+ * that is bundled with this package in the file license.txt.
7
+ * It is also available through the world-wide-web at this URL:
8
+ * http://www.gnu.org/licenses/gpl-3.0.html
9
+ * If you did not receive a copy of the license and are unable to
10
+ * obtain it through the world-wide-web, please send an email
11
+ * to license@skyverge.com so we can send you a copy immediately.
12
+ *
13
+ * DISCLAIMER
14
+ *
15
+ * Do not edit or add to this file if you wish to upgrade the plugin to newer
16
+ * versions in the future. If you wish to customize the plugin for your
17
+ * needs please refer to http://www.skyverge.com
18
+ *
19
+ * @package SkyVerge/WooCommerce/Utilities
20
+ * @author SkyVerge / Delicious Brains
21
+ * @copyright Copyright (c) 2015-2020 Delicious Brains Inc.
22
+ * @copyright Copyright (c) 2013-2020, SkyVerge, Inc.
23
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GNU General Public License v3.0
24
+ */
25
+
26
+ namespace SkyVerge\WooCommerce\PluginFramework\v5_5_4;
27
+
28
+ defined( 'ABSPATH' ) or exit;
29
+
30
+ if ( ! class_exists( '\\SkyVerge\\WooCommerce\\PluginFramework\\v5_5_4\\SV_WP_Async_Request' ) ) :
31
+
32
+
33
+ /**
34
+ * SkyVerge Wordpress Async Request class
35
+ *
36
+ * Based on the incredible work by deliciousbrains - most of the code is from
37
+ * here: https://github.com/A5hleyRich/wp-background-processing
38
+ *
39
+ * Forked & namespaced to prevent dependency conflicts and to facilitate
40
+ * further customizations.
41
+ *
42
+ * Use SV_WP_Async_Request::set_data() to set request data, instead of ::data().
43
+ *
44
+ * @since 4.4.0
45
+ */
46
+ abstract class SV_WP_Async_Request {
47
+
48
+
49
+ /** @var string request prefix */
50
+ protected $prefix = 'wp';
51
+
52
+ /** @var string request action name */
53
+ protected $action = 'async_request';
54
+
55
+ /** @var string request identifier */
56
+ protected $identifier;
57
+
58
+ /** @var array request data */
59
+ protected $data = array();
60
+
61
+
62
+ /**
63
+ * Initiate a new async request
64
+ *
65
+ * @since 4.4.0
66
+ */
67
+ public function __construct() {
68
+ $this->identifier = $this->prefix . '_' . $this->action;
69
+
70
+ add_action( 'wp_ajax_' . $this->identifier, array( $this, 'maybe_handle' ) );
71
+ add_action( 'wp_ajax_nopriv_' . $this->identifier, array( $this, 'maybe_handle' ) );
72
+ }
73
+
74
+
75
+ /**
76
+ * Set data used during the async request
77
+ *
78
+ * @since 4.4.0
79
+ * @param array $data
80
+ * @return SV_WP_Async_Request
81
+ */
82
+ public function set_data( $data ) {
83
+ $this->data = $data;
84
+
85
+ return $this;
86
+ }
87
+
88
+
89
+ /**
90
+ * Dispatch the async request
91
+ *
92
+ * @since 4.4.0
93
+ * @return array|\WP_Error
94
+ */
95
+ public function dispatch() {
96
+
97
+ $url = add_query_arg( $this->get_query_args(), $this->get_query_url() );
98
+ $args = $this->get_request_args();
99
+
100
+ return wp_safe_remote_get( esc_url_raw( $url ), $args );
101
+ }
102
+
103
+
104
+ /**
105
+ * Get query args
106
+ *
107
+ * @since 4.4.0
108
+ * @return array
109
+ */
110
+ protected function get_query_args() {
111
+
112
+ if ( property_exists( $this, 'query_args' ) ) {
113
+ return $this->query_args;
114
+ }
115
+
116
+ return array(
117
+ 'action' => $this->identifier,
118
+ 'nonce' => wp_create_nonce( $this->identifier ),
119
+ );
120
+ }
121
+
122
+
123
+ /**
124
+ * Get query URL
125
+ *
126
+ * @since 4.4.0
127
+ * @return string
128
+ */
129
+ protected function get_query_url() {
130
+
131
+ if ( property_exists( $this, 'query_url' ) ) {
132
+ return $this->query_url;
133
+ }
134
+
135
+ return admin_url( 'admin-ajax.php' );
136
+ }
137
+
138
+
139
+ /**
140
+ * Get request args
141
+ *
142
+ * In 4.6.3 renamed from get_post_args to get_request_args
143
+ *
144
+ * @since 4.4.0
145
+ * @return array
146
+ */
147
+ protected function get_request_args() {
148
+
149
+ if ( property_exists( $this, 'request_args' ) ) {
150
+ return $this->request_args;
151
+ }
152
+
153
+ return array(
154
+ 'timeout' => 0.01,
155
+ 'blocking' => false,
156
+ 'body' => $this->data,
157
+ 'cookies' => $_COOKIE,
158
+ 'sslverify' => apply_filters( 'https_local_ssl_verify', false ),
159
+ );
160
+ }
161
+
162
+
163
+ /**
164
+ * Maybe handle
165
+ *
166
+ * Check for correct nonce and pass to handler.
167
+ * @since 4.4.0
168
+ */
169
+ public function maybe_handle() {
170
+ check_ajax_referer( $this->identifier, 'nonce' );
171
+
172
+ $this->handle();
173
+
174
+ wp_die();
175
+ }
176
+
177
+
178
+ /**
179
+ * Handle
180
+ *
181
+ * Override this method to perform any actions required
182
+ * during the async request.
183
+ *
184
+ * @since 4.4.0
185
+ */
186
+ abstract protected function handle();
187
+
188
+
189
+ }
190
+
191
+
192
+ endif;
vendor/skyverge/wc-plugin-framework/woocommerce/utilities/class-sv-wp-background-job-handler.php ADDED
@@ -0,0 +1,1121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * WooCommerce Plugin Framework
4
+ *
5
+ * This source file is subject to the GNU General Public License v3.0
6
+ * that is bundled with this package in the file license.txt.
7
+ * It is also available through the world-wide-web at this URL:
8
+ * http://www.gnu.org/licenses/gpl-3.0.html
9
+ * If you did not receive a copy of the license and are unable to
10
+ * obtain it through the world-wide-web, please send an email
11
+ * to license@skyverge.com so we can send you a copy immediately.
12
+ *
13
+ * DISCLAIMER
14
+ *
15
+ * Do not edit or add to this file if you wish to upgrade the plugin to newer
16
+ * versions in the future. If you wish to customize the plugin for your
17
+ * needs please refer to http://www.skyverge.com
18
+ *
19
+ * @package SkyVerge/WooCommerce/Utilities
20
+ * @author SkyVerge / Delicious Brains
21
+ * @copyright Copyright (c) 2015-2020 Delicious Brains Inc.
22
+ * @copyright Copyright (c) 2013-2020, SkyVerge, Inc.
23
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GNU General Public License v3.0
24
+ */
25
+
26
+ namespace SkyVerge\WooCommerce\PluginFramework\v5_5_4;
27
+
28
+ defined( 'ABSPATH' ) or exit;
29
+
30
+ if ( ! class_exists( '\\SkyVerge\\WooCommerce\\PluginFramework\\v5_5_4\\SV_WP_Background_Job_Handler' ) ) :
31
+
32
+
33
+ /**
34
+ * SkyVerge WordPress Background Job Handler class
35
+ *
36
+ * Based on the wonderful WP_Background_Process class by deliciousbrains:
37
+ * https://github.com/A5hleyRich/wp-background-processing
38
+ *
39
+ * Subclasses SV_WP_Async_Request. Instead of the concept of `batches` used in
40
+ * the Delicious Brains' version, however, this takes a more object-oriented approach
41
+ * of background `jobs`, allowing greater control over manipulating job data and
42
+ * processing.
43
+ *
44
+ * A batch implicitly expected an array of items to process, whereas a job does
45
+ * not expect any particular data structure (although it does default to
46
+ * looping over job data) and allows subclasses to provide their own
47
+ * processing logic.
48
+ *
49
+ * # Sample usage:
50
+ *
51
+ * $background_job_handler = new SV_WP_Background_Job_Handler();
52
+ * $job = $background_job_handler->create_job( $attrs );
53
+ * $background_job_handler->dispatch();
54
+ *
55
+ * @since 4.4.0
56
+ */
57
+ abstract class SV_WP_Background_Job_Handler extends SV_WP_Async_Request {
58
+
59
+
60
+ /** @var string async request prefix */
61
+ protected $prefix = 'sv_wp';
62
+
63
+ /** @var string async request action */
64
+ protected $action = 'background_job';
65
+
66
+ /** @var string data key */
67
+ protected $data_key = 'data';
68
+
69
+ /** @var int start time of current process */
70
+ protected $start_time = 0;
71
+
72
+ /** @var string cron hook identifier */
73
+ protected $cron_hook_identifier;
74
+
75
+ /** @var string cron interval identifier */
76
+ protected $cron_interval_identifier;
77
+
78
+ /** @var string debug message, used by the system status tool */
79
+ protected $debug_message;
80
+
81
+
82
+ /**
83
+ * Initiate new background job handler
84
+ *
85
+ * @since 4.4.0
86
+ */
87
+ public function __construct() {
88
+
89
+ parent::__construct();
90
+
91
+ $this->cron_hook_identifier = $this->identifier . '_cron';
92
+ $this->cron_interval_identifier = $this->identifier . '_cron_interval';
93
+
94
+ $this->add_hooks();
95
+ }
96
+
97
+
98
+ /**
99
+ * Adds the necessary action and filter hooks.
100
+ *
101
+ * @since 4.8.0
102
+ */
103
+ protected function add_hooks() {
104
+
105
+ // cron healthcheck
106
+ add_action( $this->cron_hook_identifier, array( $this, 'handle_cron_healthcheck' ) );
107
+ add_filter( 'cron_schedules', array( $this, 'schedule_cron_healthcheck' ) );
108
+
109
+ // debugging & testing
110
+ add_action( "wp_ajax_nopriv_{$this->identifier}_test", array( $this, 'handle_connection_test_response' ) );
111
+ add_filter( 'woocommerce_debug_tools', array( $this, 'add_debug_tool' ) );
112
+ add_filter( 'gettext', array( $this, 'translate_success_message' ), 10, 3 );
113
+ }
114
+
115
+
116
+ /**
117
+ * Dispatch
118
+ *
119
+ * @since 4.4.0
120
+ * @return array|WP_Error
121
+ */
122
+ public function dispatch() {
123
+
124
+ // schedule the cron healthcheck
125
+ $this->schedule_event();
126
+
127
+ // perform remote post
128
+ return parent::dispatch();
129
+ }
130
+
131
+
132
+ /**
133
+ * Maybe processes job queue.
134
+ *
135
+ * Checks whether data exists within the job queue and that the background process is not already running.
136
+ *
137
+ * @since 4.4.0
138
+ *
139
+ * @throws \Exception upon error
140
+ */
141
+ public function maybe_handle() {
142
+
143
+ if ( $this->is_process_running() ) {
144
+ // background process already running
145
+ wp_die();
146
+ }
147
+
148
+ if ( $this->is_queue_empty() ) {
149
+ // no data to process
150
+ wp_die();
151
+ }
152
+
153
+ // WC core does 2 things here that can interfere with our nonce check:
154
+ // 1. WooCommerce starts a session due to our GET request to dispatch a job
155
+ // However, this happens *after* we've generated a nonce without a session (in CRON context)
156
+ // 2. it then filters nonces for logged-out users indiscriminately without checking the nonce action; if
157
+ // there is a session created (and now the server does have one), it tries to filter every.single.nonce
158
+ // for logged-out users to use the customer session ID instead of 0 for user ID. We *want* to check
159
+ // against a UID of 0 (since that's how the nonce was created), so we temporarily pause the
160
+ // logged-out nonce hijacking before standing aside.
161
+ remove_filter( 'nonce_user_logged_out', array( WC()->session, 'nonce_user_logged_out' ) );
162
+
163
+ check_ajax_referer( $this->identifier, 'nonce' );
164
+
165
+ // sorry, later nonce users! please play again
166
+ add_filter( 'nonce_user_logged_out', array( WC()->session, 'nonce_user_logged_out' ) );
167
+
168
+ $this->handle();
169
+
170
+ wp_die();
171
+ }
172
+
173
+
174
+ /**
175
+ * Check whether job queue is empty or not
176
+ *
177
+ * @since 4.4.0
178
+ * @return bool True if queue is empty, false otherwise
179
+ */
180
+ protected function is_queue_empty() {
181
+ global $wpdb;
182
+
183
+ $key = $this->identifier . '_job_%';
184
+
185
+ // only queued or processing jobs count
186
+ $queued = '%"status":"queued"%';
187
+ $processing = '%"status":"processing"%';
188
+
189
+ $count = $wpdb->get_var( $wpdb->prepare( "
190
+ SELECT COUNT(*)
191
+ FROM {$wpdb->options}
192
+ WHERE option_name LIKE %s
193
+ AND ( option_value LIKE %s OR option_value LIKE %s )
194
+ ", $key, $queued, $processing ) );
195
+
196
+ return ( $count > 0 ) ? false : true;
197
+ }
198
+
199
+
200
+ /**
201
+ * Check whether background process is running or not
202
+ *
203
+ * Check whether the current process is already running
204
+ * in a background process.
205
+ *
206
+ * @since 4.4.0
207
+ * @return bool True if processing is running, false otherwise
208
+ */
209
+ protected function is_process_running() {
210
+
211
+ // add a random artificial delay to prevent a race condition if 2 or more processes are trying to
212
+ // process the job queue at the very same moment in time and neither of them have yet set the lock
213
+ // before the others are calling this method
214
+ usleep( rand( 100000, 300000 ) );
215
+
216
+ return (bool) get_transient( "{$this->identifier}_process_lock" );
217
+ }
218
+
219
+
220
+ /**
221
+ * Lock process
222
+ *
223
+ * Lock the process so that multiple instances can't run simultaneously.
224
+ * Override if applicable, but the duration should be greater than that
225
+ * defined in the time_exceeded() method.
226
+ *
227
+ * @since 4.4.0
228
+ */
229
+ protected function lock_process() {
230
+
231
+ // set start time of current process
232
+ $this->start_time = time();
233
+
234
+ // set lock duration to 1 minute by default
235
+ $lock_duration = ( property_exists( $this, 'queue_lock_time' ) ) ? $this->queue_lock_time : 60;
236
+
237
+ /**
238
+ * Filter the queue lock time
239
+ *
240
+ * @since 4.4.0
241
+ * @param int $lock_duration Lock duration in seconds
242
+ */
243
+ $lock_duration = apply_filters( "{$this->identifier}_queue_lock_time", $lock_duration );
244
+
245
+ set_transient( "{$this->identifier}_process_lock", microtime(), $lock_duration );
246
+ }
247
+
248
+
249
+ /**
250
+ * Unlock process
251
+ *
252
+ * Unlock the process so that other instances can spawn.
253
+ *
254
+ * @since 4.4.0
255
+ * @return SV_WP_Background_Job_Handler
256
+ */
257
+ protected function unlock_process() {
258
+
259
+ delete_transient( "{$this->identifier}_process_lock" );
260
+
261
+ return $this;
262
+ }
263
+
264
+
265
+ /**
266
+ * Check if memory limit is exceeded
267
+ *
268
+ * Ensures the background job handler process never exceeds 90%
269
+ * of the maximum WordPress memory.
270
+ *
271
+ * @since 4.4.0
272
+ *
273
+ * @return bool True if exceeded memory limit, false otherwise
274
+ */
275
+ protected function memory_exceeded() {
276
+
277
+ $memory_limit = $this->get_memory_limit() * 0.9; // 90% of max memory
278
+ $current_memory = memory_get_usage( true );
279
+ $return = false;
280
+
281
+ if ( $current_memory >= $memory_limit ) {
282
+ $return = true;
283
+ }
284
+
285
+ /**
286
+ * Filter whether memory limit has been exceeded or not
287
+ *
288
+ * @since 4.4.0
289
+ *
290
+ * @param bool $exceeded
291
+ */
292
+ return apply_filters( "{$this->identifier}_memory_exceeded", $return );
293
+ }
294
+
295
+
296
+ /**
297
+ * Get memory limit
298
+ *
299
+ * @since 4.4.0
300
+ *
301
+ * @return int memory limit in bytes
302
+ */
303
+ protected function get_memory_limit() {
304
+
305
+ if ( function_exists( 'ini_get' ) ) {
306
+ $memory_limit = ini_get( 'memory_limit' );
307
+ } else {
308
+ // sensible default
309
+ $memory_limit = '128M';
310
+ }
311
+
312
+ if ( ! $memory_limit || -1 === (int) $memory_limit ) {
313
+ // unlimited, set to 32GB
314
+ $memory_limit = '32G';
315
+ }
316
+
317
+ return SV_WC_Plugin_Compatibility::convert_hr_to_bytes( $memory_limit );
318
+ }
319
+
320
+
321
+ /**
322
+ * Check whether request time limit has been exceeded or not
323
+ *
324
+ * Ensures the background job handler never exceeds a sensible time limit.
325
+ * A timeout limit of 30s is common on shared hosting.
326
+ *
327
+ * @since 4.4.0
328
+ *
329
+ * @return bool True, if time limit exceeded, false otherwise
330
+ */
331
+ protected function time_exceeded() {
332
+
333
+ /**
334
+ * Filter default time limit for background job execution, defaults to
335
+ * 20 seconds
336
+ *
337
+ * @since 4.4.0
338
+ *
339
+ * @param int $time Time in seconds
340
+ */
341
+ $finish = $this->start_time + apply_filters( "{$this->identifier}_default_time_limit", 20 );
342
+ $return = false;
343
+
344
+ if ( time() >= $finish ) {
345
+ $return = true;
346
+ }
347
+
348
+ /**
349
+ * Filter whether maximum execution time has exceeded or not
350
+ *
351
+ * @since 4.4.0
352
+ * @param bool $exceeded true if execution time exceeded, false otherwise
353
+ */
354
+ return apply_filters( "{$this->identifier}_time_exceeded", $return );
355
+ }
356
+
357
+
358
+ /**
359
+ * Create a background job
360
+ *
361
+ * Delicious Brains' versions alternative would be using ->data()->save().
362
+ * Allows passing in any kind of job attributes, which will be available at item data processing time.
363
+ * This allows sharing common options between items without the need to repeat
364
+ * the same information for every single item in queue.
365
+ *
366
+ * Instead of returning self, returns the job instance, which gives greater
367
+ * control over the job.
368
+ *
369
+ * @since 4.4.0
370
+ *
371
+ * @param array|mixed $attrs Job attributes.
372
+ * @return \stdClass|object|null
373
+ */
374
+ public function create_job( $attrs ) {
375
+ global $wpdb;
376
+
377
+ if ( empty( $attrs ) ) {
378
+ return null;
379
+ }
380
+
381
+ // generate a unique ID for the job
382
+ $job_id = md5( microtime() . mt_rand() );
383
+
384
+ /**
385
+ * Filter new background job attributes
386
+ *
387
+ * @since 4.4.0
388
+ *
389
+ * @param array $attrs Job attributes
390
+ * @param string $id Job ID
391
+ */
392
+ $attrs = apply_filters( "{$this->identifier}_new_job_attrs", $attrs, $job_id );
393
+
394
+ // ensure a few must-have attributes
395
+ $attrs = wp_parse_args( array(
396
+ 'id' => $job_id,
397
+ 'created_at' => current_time( 'mysql' ),
398
+ 'created_by' => get_current_user_id(),
399
+ 'status' => 'queued',
400
+ ), $attrs );
401
+
402
+ $wpdb->insert( $wpdb->options, array(
403
+ 'option_name' => "{$this->identifier}_job_{$job_id}",
404
+ 'option_value' => json_encode( $attrs ),
405
+ 'autoload' => 'no'
406
+ ) );
407
+
408
+ $job = new \stdClass();
409
+
410
+ foreach ( $attrs as $key => $value ) {
411
+ $job->{$key} = $value;
412
+ }
413
+
414
+ /**
415
+ * Runs when a job is created.
416
+ *
417
+ * @since 4.4.0
418
+ *
419
+ * @param \stdClass|object $job the created job
420
+ */
421
+ do_action( "{$this->identifier}_job_created", $job );
422
+
423
+ return $job;
424
+ }
425
+
426
+
427
+ /**
428
+ * Get a job (by default the first in the queue)
429
+ *
430
+ * @since 4.4.0
431
+ *
432
+ * @param string $id Optional. Job ID. Will return first job in queue if not
433
+ * provided. Will not return completed or failed jobs from queue.
434
+ * @return \stdClass|object|null The found job object or null
435
+ */
436
+ public function get_job( $id = null ) {
437
+ global $wpdb;
438
+
439
+ if ( ! $id ) {
440
+
441
+ $key = $this->identifier . '_job_%';
442
+ $queued = '%"status":"queued"%';
443
+ $processing = '%"status":"processing"%';
444
+
445
+ $results = $wpdb->get_var( $wpdb->prepare( "
446
+ SELECT option_value
447
+ FROM {$wpdb->options}
448
+ WHERE option_name LIKE %s
449
+ AND ( option_value LIKE %s OR option_value LIKE %s )
450
+ ORDER BY option_id ASC
451
+ LIMIT 1
452
+ ", $key, $queued, $processing ) );
453
+
454
+ } else {
455
+
456
+ $results = $wpdb->get_var( $wpdb->prepare( "
457
+ SELECT option_value
458
+ FROM {$wpdb->options}
459
+ WHERE option_name = %s
460
+ ", "{$this->identifier}_job_{$id}" ) );
461
+
462
+ }
463
+
464
+ if ( ! empty( $results ) ) {
465
+
466
+ $job = new \stdClass();
467
+
468
+ foreach ( json_decode( $results, true ) as $key => $value ) {
469
+ $job->{$key} = $value;
470
+ }
471
+
472
+ } else {
473
+ return null;
474
+ }
475
+
476
+ /**
477
+ * Filters the job as returned from the database.
478
+ *
479
+ * @since 4.4.0
480
+ *
481
+ * @param \stdClass|object $job
482
+ */
483
+ return apply_filters( "{$this->identifier}_returned_job", $job );
484
+ }
485
+
486
+
487
+ /**
488
+ * Gets jobs.
489
+ *
490
+ * @since 4.4.2
491
+ *
492
+ * @param array $args {
493
+ * Optional. An array of arguments
494
+ *
495
+ * @type string|array $status Job status(es) to include
496
+ * @type string $order ASC or DESC. Defaults to DESC
497
+ * @type string $orderby Field to order by. Defaults to option_id
498
+ * }
499
+ * @return \stdClass[]|object[]|null Found jobs or null if none found
500
+ */
501
+ public function get_jobs( $args = array() ) {
502
+ global $wpdb;
503
+
504
+ $args = wp_parse_args( $args, array(
505
+ 'order' => 'DESC',
506
+ 'orderby' => 'option_id',
507
+ ) );
508
+
509
+ $replacements = array( $this->identifier . '_job_%' );
510
+ $status_query = '';
511
+
512
+ // prepare status query
513
+ if ( ! empty( $args['status'] ) ) {
514
+
515
+ $statuses = (array) $args['status'];
516
+ $placeholders = array();
517
+
518
+ foreach ( $statuses as $status ) {
519
+
520
+ $placeholders[] = '%s';
521
+ $replacements[] = '%"status":"' . sanitize_key( $status ) . '"%';
522
+ }
523
+
524
+ $status_query = 'AND ( option_value LIKE ' . implode( ' OR option_value LIKE ', $placeholders ) . ' )';
525
+ }
526
+
527
+ // prepare sorting vars
528
+ $order = sanitize_key( $args['order'] );
529
+ $orderby = sanitize_key( $args['orderby'] );
530
+
531
+ // put it all together now
532
+ $query = $wpdb->prepare( "
533
+ SELECT option_value
534
+ FROM {$wpdb->options}
535
+ WHERE option_name LIKE %s
536
+ {$status_query}
537
+ ORDER BY {$orderby} {$order}
538
+ ", $replacements );
539
+
540
+ $results = $wpdb->get_col( $query );
541
+
542
+ if ( empty( $results ) ) {
543
+ return null;
544
+ }
545
+
546
+ $jobs = array();
547
+
548
+ foreach ( $results as $result ) {
549
+
550
+ $job = new \stdClass();
551
+
552
+ foreach ( json_decode( $result, true ) as $key => $value ) {
553
+ $job->{$key} = $value;
554
+ }
555
+
556
+ /** This filter is documented above */
557
+ $job = apply_filters( "{$this->identifier}_returned_job", $job );
558
+
559
+ $jobs[] = $job;
560
+ }
561
+
562
+ return $jobs;
563
+ }
564
+
565
+
566
+ /**
567
+ * Handles jobs.
568
+ *
569
+ * Process jobs while remaining within server memory and time limit constraints.
570
+ *
571
+ * @since 4.4.0
572
+ *
573
+ * @throws \Exception
574
+ */
575
+ protected function handle() {
576
+
577
+ $this->lock_process();
578
+
579
+ do {
580
+
581
+ // Get next job in the queue
582
+ $job = $this->get_job();
583
+
584
+ // handle PHP errors from here on out
585
+ register_shutdown_function( array( $this, 'handle_shutdown' ), $job );
586
+
587
+ // Start processing
588
+ $this->process_job( $job );
589
+
590
+ } while ( ! $this->time_exceeded() && ! $this->memory_exceeded() && ! $this->is_queue_empty() );
591
+
592
+ $this->unlock_process();
593
+
594
+ // Start next job or complete process
595
+ if ( ! $this->is_queue_empty() ) {
596
+ $this->dispatch();
597
+ } else {
598
+ $this->complete();
599
+ }
600
+
601
+ wp_die();
602
+ }
603
+
604
+
605
+ /**
606
+ * Process a job
607
+ *
608
+ * Default implementation is to loop over job data and passing each item to
609
+ * the item processor. Subclasses are, however, welcome to override this method
610
+ * to create totally different job processing implementations - see
611
+ * WC_CSV_Import_Suite_Background_Import in CSV Import for an example.
612
+ *
613
+ * If using the default implementation, the job must have a $data_key property set.
614
+ * Subclasses can override the data key, but the contents must be an array which
615
+ * the job processor can loop over. By default, the data key is `data`.
616
+ *
617
+ * If no data is set, the job will completed right away.
618
+ *
619
+ * @since 4.4.0
620
+ *
621
+ * @param \stdClass|object $job
622
+ * @param int $items_per_batch number of items to process in a single request. Defaults to unlimited.
623
+ * @throws \Exception when job data is incorrect
624
+ * @return \stdClass $job
625
+ */
626
+ public function process_job( $job, $items_per_batch = null ) {
627
+
628
+ if ( ! $this->start_time ) {
629
+ $this->start_time = time();
630
+ }
631
+
632
+ // Indicate that the job has started processing
633
+ if ( 'processing' !== $job->status ) {
634
+
635
+ $job->status = 'processing';
636
+ $job->started_processing_at = current_time( 'mysql' );
637
+
638
+ $job = $this->update_job( $job );
639
+ }
640
+
641
+ $data_key = $this->data_key;
642
+
643
+ if ( ! isset( $job->{$data_key} ) ) {
644
+ throw new \Exception( sprintf( __( 'Job data key "%s" not set', 'woocommerce-plugin-framework' ), $data_key ) );
645
+ }
646
+
647
+ if ( ! is_array( $job->{$data_key} ) ) {
648
+ throw new \Exception( sprintf( __( 'Job data key "%s" is not an array', 'woocommerce-plugin-framework' ), $data_key ) );
649
+ }
650
+
651
+ $data = $job->{$data_key};
652
+
653
+ $job->total = count( $data );
654
+
655
+ // progress indicates how many items have been processed, it
656
+ // does NOT indicate the processed item key in any way
657
+ if ( ! isset( $job->progress ) ) {
658
+ $job->progress = 0;
659
+ }
660
+
661
+ // skip already processed items
662
+ if ( $job->progress && ! empty( $data ) ) {
663
+ $data = array_slice( $data, $job->progress, null, true );
664
+ }
665
+
666
+ // loop over unprocessed items and process them
667
+ if ( ! empty( $data ) ) {
668
+
669
+ $processed = 0;
670
+ $items_per_batch = (int) $items_per_batch;
671
+
672
+ foreach ( $data as $item ) {
673
+
674
+ // process the item
675
+ $this->process_item( $item, $job );
676
+
677
+ $processed++;
678
+ $job->progress++;
679
+
680
+ // update job progress
681
+ $job = $this->update_job( $job );
682
+
683
+ // job limits reached
684
+ if ( ( $items_per_batch && $processed >= $items_per_batch ) || $this->time_exceeded() || $this->memory_exceeded() ) {
685
+ break;
686
+ }
687
+ }
688
+ }
689
+
690
+ // complete current job
691
+ if ( $job->progress >= count( $job->{$data_key} ) ) {
692
+ $job = $this->complete_job( $job );
693
+ }
694
+
695
+ return $job;
696
+ }
697
+
698
+
699
+ /**
700
+ * Update job attrs
701
+ *
702
+ * @since 4.4.0
703
+ *
704
+ * @param \stdClass|object|string $job Job instance or ID
705
+ * @return \stdClass|object|false on failure
706
+ */
707
+ public function update_job( $job ) {
708
+
709
+ if ( is_string( $job ) ) {
710
+ $job = $this->get_job( $job );
711
+ }
712
+
713
+ if ( ! $job ) {
714
+ return false;
715
+ }
716
+
717
+ $job->updated_at = current_time( 'mysql' );
718
+
719
+ $this->update_job_option( $job );
720
+
721
+ /**
722
+ * Runs when a job is updated.
723
+ *
724
+ * @since 4.4.0
725
+ *
726
+ * @param \stdClass|object $job the updated job
727
+ */
728
+ do_action( "{$this->identifier}_job_updated", $job );
729
+
730
+ return $job;
731
+ }
732
+
733
+
734
+ /**
735
+ * Handles job completion.
736
+ *
737
+ * @since 4.4.0
738
+ *
739
+ * @param \stdClass|object|string $job Job instance or ID
740
+ * @return \stdClass|object|false on failure
741
+ */
742
+ public function complete_job( $job ) {
743
+
744
+ if ( is_string( $job ) ) {
745
+ $job = $this->get_job( $job );
746
+ }
747
+
748
+ if ( ! $job ) {
749
+ return false;
750
+ }
751
+
752
+ $job->status = 'completed';
753
+ $job->completed_at = current_time( 'mysql' );
754
+
755
+ $this->update_job_option( $job );
756
+
757
+ /**
758
+ * Runs when a job is completed.
759
+ *
760
+ * @since 4.4.0
761
+ *
762
+ * @param \stdClass|object $job the completed job
763
+ */
764
+ do_action( "{$this->identifier}_job_complete", $job );
765
+
766
+ return $job;
767
+ }
768
+
769
+
770
+ /**
771
+ * Handle job failure
772
+ *
773
+ * Default implementation does not call this method directly, but it's
774
+ * provided as a convenience method for subclasses that may call this to
775
+ * indicate that a particular job has failed for some reason.
776
+ *
777
+ * @since 4.4.0
778
+ *
779
+ * @param \stdClass|object|string $job Job instance or ID
780
+ * @param string $reason Optional. Reason for failure.
781
+ * @return \stdClass|false on failure
782
+ */
783
+ public function fail_job( $job, $reason = '' ) {
784
+
785
+ if ( is_string( $job ) ) {
786
+ $job = $this->get_job( $job );
787
+ }
788
+
789
+ if ( ! $job ) {
790
+ return false;
791
+ }
792
+
793
+ $job->status = 'failed';
794
+ $job->failed_at = current_time( 'mysql' );
795
+
796
+ if ( $reason ) {
797
+ $job->failure_reason = $reason;
798
+ }
799
+
800
+ $this->update_job_option( $job );
801
+
802
+ /**
803
+ * Runs when a job is failed.
804
+ *
805
+ * @since 4.4.0
806
+ *
807
+ * @param \stdClass|object $job the failed job
808
+ */
809
+ do_action( "{$this->identifier}_job_failed", $job );
810
+
811
+ return $job;
812
+ }
813
+
814
+
815
+ /**
816
+ * Delete a job
817
+ *
818
+ * @since 4.4.2
819
+ *
820
+ * @param \stdClass|object|string $job Job instance or ID
821
+ * @return false on failure
822
+ */
823
+ public function delete_job( $job ) {
824
+ global $wpdb;
825
+
826
+ if ( is_string( $job ) ) {
827
+ $job = $this->get_job( $job );
828
+ }
829
+
830
+ if ( ! $job ) {
831
+ return false;
832
+ }
833
+
834
+ $wpdb->delete( $wpdb->options, array( 'option_name' => "{$this->identifier}_job_{$job->id}" ) );
835
+
836
+ /**
837
+ * Runs after a job is deleted.
838
+ *
839
+ * @since 4.4.2
840
+ *
841
+ * @param \stdClass|object $job the job that was deleted from database
842
+ */
843
+ do_action( "{$this->identifier}_job_deleted", $job );
844
+ }
845
+
846
+
847
+ /**
848
+ * Handle job queue completion
849
+ *
850
+ * Override if applicable, but ensure that the below actions are
851
+ * performed, or, call parent::complete().
852
+ *
853
+ * @since 4.4.0
854
+ */
855
+ protected function complete() {
856
+
857
+ // unschedule the cron healthcheck
858
+ $this->clear_scheduled_event();
859
+ }
860
+
861
+
862
+ /**
863
+ * Schedule cron healthcheck
864
+ *
865
+ * @since 4.4.0
866
+ * @param array $schedules
867
+ * @return array
868
+ */
869
+ public function schedule_cron_healthcheck( $schedules ) {
870
+
871
+ $interval = property_exists( $this, 'cron_interval' ) ? $this->cron_interval : 5;
872
+
873
+ /**
874
+ * Filter cron health check interval
875
+ *
876
+ * @since 4.4.0
877
+ * @param int $interval Interval in minutes
878
+ */
879
+ $interval = apply_filters( "{$this->identifier}_cron_interval", $interval );
880
+
881
+ // adds every 5 minutes to the existing schedules.
882
+ $schedules[ $this->identifier . '_cron_interval' ] = array(
883
+ 'interval' => MINUTE_IN_SECONDS * $interval,
884
+ 'display' => sprintf( __( 'Every %d Minutes' ), $interval ),
885
+ );
886
+
887
+ return $schedules;
888
+ }
889
+
890
+
891
+ /**
892
+ * Handle cron healthcheck
893
+ *
894
+ * Restart the background process if not already running
895
+ * and data exists in the queue.
896
+ *
897
+ * @since 4.4.0
898
+ */
899
+ public function handle_cron_healthcheck() {
900
+
901
+ if ( $this->is_process_running() ) {
902
+ // background process already running
903
+ exit;
904
+ }
905
+
906
+ if ( $this->is_queue_empty() ) {
907
+ // no data to process
908
+ $this->clear_scheduled_event();
909
+ exit;
910
+ }
911
+
912
+ $this->dispatch();
913
+ }
914
+
915
+
916
+ /**
917
+ * Schedule cron health check event
918
+ *
919
+ * @since 4.4.0
920
+ */
921
+ protected function schedule_event() {
922
+
923
+ if ( ! wp_next_scheduled( $this->cron_hook_identifier ) ) {
924
+
925
+ // schedule the health check to fire after 30 seconds from now, as to not create a race condition
926
+ // with job process lock on servers that fire & handle cron events instantly
927
+ wp_schedule_event( time() + 30, $this->cron_interval_identifier, $this->cron_hook_identifier );
928
+ }
929
+ }
930
+
931
+
932
+ /**
933
+ * Clear scheduled health check event
934
+ *
935
+ * @since 4.4.0
936
+ */
937
+ protected function clear_scheduled_event() {
938
+
939
+ $timestamp = wp_next_scheduled( $this->cron_hook_identifier );
940
+
941
+ if ( $timestamp ) {
942
+ wp_unschedule_event( $timestamp, $this->cron_hook_identifier );
943
+ }
944
+ }
945
+
946
+
947
+ /**
948
+ * Process an item from job data
949
+ *
950
+ * Implement this method to perform any actions required on each
951
+ * item in job data.
952
+ *
953
+ * @since 4.4.2
954
+ *
955
+ * @param mixed $item Job data item to iterate over
956
+ * @param \stdClass|object $job Job instance
957
+ * @return mixed
958
+ */
959
+ abstract protected function process_item( $item, $job );
960
+
961
+
962
+ /**
963
+ * Handles PHP shutdown, say after a fatal error.
964
+ *
965
+ * @since 4.5.0
966
+ *
967
+ * @param \stdClass|object $job the job being processed
968
+ */
969
+ public function handle_shutdown( $job ) {
970
+
971
+ $error = error_get_last();
972
+
973
+ // if shutting down because of a fatal error, fail the job
974
+ if ( $error && E_ERROR === $error['type'] ) {
975
+
976
+ $this->fail_job( $job, $error['message'] );
977
+
978
+ $this->unlock_process();
979
+ }
980
+ }
981
+
982
+
983
+ /**
984
+ * Update a job option in options database.
985
+ *
986
+ * @since 4.6.3
987
+ *
988
+ * @param \stdClass|object $job the job instance to update in database
989
+ * @return int|bool number of rows updated or false on failure, see wpdb::update()
990
+ */
991
+ private function update_job_option( $job ) {
992
+ global $wpdb;
993
+
994
+ return $wpdb->update(
995
+ $wpdb->options,
996
+ array( 'option_value' => json_encode( $job ) ),
997
+ array( 'option_name' => "{$this->identifier}_job_{$job->id}" )
998
+ );
999
+ }
1000
+
1001
+
1002
+ /** Debug & Testing Methods ***********************************************/
1003
+
1004
+
1005
+ /**
1006
+ * Tests the background handler's connection.
1007
+ *
1008
+ * @since 4.8.0
1009
+ *
1010
+ * @return bool
1011
+ */
1012
+ public function test_connection() {
1013
+
1014
+ $test_url = add_query_arg( 'action', "{$this->identifier}_test", admin_url( 'admin-ajax.php' ) );
1015
+ $result = wp_safe_remote_get( $test_url );
1016
+ $body = ! is_wp_error( $result ) ? wp_remote_retrieve_body( $result ) : null;
1017
+
1018
+ // some servers may add a UTF8-BOM at the beginning of the response body, so we check if our test
1019
+ // string is included in the body, as an equal check would produce a false negative test result
1020
+ return $body && SV_WC_Helper::str_exists( $body, '[TEST_LOOPBACK]' );
1021
+ }
1022
+
1023
+
1024
+ /**
1025
+ * Handles the connection test request.
1026
+ *
1027
+ * @since 4.8.0
1028
+ */
1029
+ public function handle_connection_test_response() {
1030
+
1031
+ echo '[TEST_LOOPBACK]';
1032
+ exit;
1033
+ }
1034
+
1035
+
1036
+ /**
1037
+ * Adds the WooCommerce debug tool.
1038
+ *
1039
+ * @since 4.8.0
1040
+ *
1041
+ * @param array $tools WooCommerce core tools
1042
+ * @return array
1043
+ */
1044
+ public function add_debug_tool( $tools ) {
1045
+
1046
+ // this key is not unique to the plugin to avoid duplicate tools
1047
+ $tools['sv_wc_background_job_test'] = array(
1048
+ 'name' => __( 'Background Processing Test', 'woocommerce-plugin-framework' ),
1049
+ 'button' => __( 'Run Test', 'woocommerce-plugin-framework' ),
1050
+ 'desc' => __( 'This tool will test whether your server is capable of processing background jobs.', 'woocommerce-plugin-framework' ),
1051
+ 'callback' => array( $this, 'run_debug_tool' ),
1052
+ );
1053
+
1054
+ return $tools;
1055
+ }
1056
+
1057
+
1058
+ /**
1059
+ * Runs the test connection debug tool.
1060
+ *
1061
+ * @since 4.8.0
1062
+ *
1063
+ * @return string
1064
+ */
1065
+ public function run_debug_tool() {
1066
+
1067
+ if ( $this->test_connection() ) {
1068
+ $this->debug_message = esc_html__( 'Success! You should be able to process background jobs.', 'woocommerce-plugin-framework' );
1069
+ $result = true;
1070
+ } else {
1071
+ $this->debug_message = esc_html__( 'Could not connect. Please ask your hosting company to ensure your server has loopback connections enabled.', 'woocommerce-plugin-framework' );
1072
+ $result = false;
1073
+ }
1074
+
1075
+ return $result;
1076
+ }
1077
+
1078
+
1079
+ /**
1080
+ * Translate the tool success message.
1081
+ *
1082
+ * This can be removed in favor of returning the message string in `run_debug_tool()`
1083
+ * when WC 3.1 is required, though that means the message will always be "success" styled.
1084
+ *
1085
+ * @since 4.8.0
1086
+ *
1087
+ * @param string $translated the text to output
1088
+ * @param string $original the original text
1089
+ * @param string $domain the textdomain
1090
+ * @return string the updated text
1091
+ */
1092
+ public function translate_success_message( $translated, $original, $domain ) {
1093
+
1094
+ if ( 'woocommerce' === $domain && ( 'Tool ran.' === $original || 'There was an error calling %s' === $original ) ) {
1095
+ $translated = $this->debug_message;
1096
+ }
1097
+
1098
+ return $translated;
1099
+ }
1100
+
1101
+
1102
+ /** Helper Methods ********************************************************/
1103
+
1104
+
1105
+ /**
1106
+ * Gets the job handler identifier.
1107
+ *
1108
+ * @since 4.8.0
1109
+ *
1110
+ * @return string
1111
+ */
1112
+ public function get_identifier() {
1113
+
1114
+ return $this->identifier;
1115
+ }
1116
+
1117
+
1118
+ }
1119
+
1120
+
1121
+ endif;
vendor/skyverge/wc-plugin-framework/woocommerce/utilities/class-sv-wp-job-batch-handler.php ADDED
@@ -0,0 +1,310 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * WooCommerce Plugin Framework
4
+ *
5
+ * This source file is subject to the GNU General Public License v3.0
6
+ * that is bundled with this package in the file license.txt.
7
+ * It is also available through the world-wide-web at this URL:
8
+ * http://www.gnu.org/licenses/gpl-3.0.html
9
+ * If you did not receive a copy of the license and are unable to
10
+ * obtain it through the world-wide-web, please send an email
11
+ * to license@skyverge.com so we can send you a copy immediately.
12
+ *
13
+ * DISCLAIMER
14
+ *
15
+ * Do not edit or add to this file if you wish to upgrade the plugin to newer
16
+ * versions in the future. If you wish to customize the plugin for your
17
+ * needs please refer to http://www.skyverge.com
18
+ *
19
+ * @package SkyVerge/WooCommerce/Utilities
20
+ * @author SkyVerge
21
+ * @copyright Copyright (c) 2017-2020, SkyVerge, Inc.
22
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GNU General Public License v3.0
23
+ */
24
+
25
+ namespace SkyVerge\WooCommerce\PluginFramework\v5_5_4;
26
+
27
+ defined( 'ABSPATH' ) or exit;
28
+
29
+ if ( ! class_exists( '\\SkyVerge\\WooCommerce\\PluginFramework\\v5_5_4\\SV_WP_Job_Batch_Handler' ) ) :
30
+
31
+
32
+ /**
33
+ * The job batch handler class.
34
+ *
35
+ * This provides a way for plugins to process "background" jobs in batches when
36
+ * regular background processing isn't available.
37
+ *
38
+ * @since 4.8.0
39
+ */
40
+ class SV_WP_Job_Batch_Handler {
41
+
42
+
43
+ /** @var SV_WP_Background_Job_Handler job handler instance */
44
+ protected $job_handler;
45
+
46
+ /** @var SV_WC_Plugin $plugin WC plugin instance */
47
+ protected $plugin;
48
+
49
+ /** @var int default items per batch */
50
+ protected $items_per_batch = 20;
51
+
52
+
53
+ /**
54
+ * Constructs the class.
55
+ *
56
+ * @since 4.8.0
57
+ *
58
+ * @param SV_WP_Background_Job_Handler $job_handler job handler instance
59
+ * @param SV_WC_Plugin $plugin WC plugin instance
60
+ */
61
+ public function __construct( $job_handler, SV_WC_Plugin $plugin ) {
62
+
63
+ if ( ! is_admin() ) {
64
+ return;
65
+ }
66
+
67
+ $this->job_handler = $job_handler;
68
+ $this->plugin = $plugin;
69
+
70
+ $this->add_hooks();
71
+
72
+ $this->render_js();
73
+ }
74
+
75
+
76
+ /**
77
+ * Adds the necessary action and filter hooks.
78
+ *
79
+ * @since 4.8.0
80
+ */
81
+ protected function add_hooks() {
82
+
83
+ add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
84
+
85
+ add_action( 'wp_ajax_' . $this->get_job_handler()->get_identifier() . '_process_batch', array( $this, 'ajax_process_batch' ) );
86
+ add_action( 'wp_ajax_' . $this->get_job_handler()->get_identifier() . '_cancel_job', array( $this, 'ajax_cancel_job' ) );
87
+ }
88
+
89
+
90
+ /**
91
+ * Enqueues the scripts.
92
+ *
93
+ * @since 4.8.0
94
+ */
95
+ public function enqueue_scripts() {
96
+
97
+ wp_enqueue_script( $this->get_job_handler()->get_identifier() . '_batch_handler', $this->get_plugin()->get_framework_assets_url() . '/js/admin/sv-wp-admin-job-batch-handler.min.js', array( 'jquery' ), $this->get_plugin()->get_version() );
98
+ }
99
+
100
+
101
+ /**
102
+ * Renders the inline JavaScript for instantiating the batch handler class.
103
+ *
104
+ * @since 4.8.0
105
+ */
106
+ protected function render_js() {
107
+
108
+ /**
109
+ * Filters the JavaScript batch handler arguments.
110
+ *
111
+ * @since 4.8.0
112
+ *
113
+ * @param array $args arguments to pass to the JavaScript batch handler
114
+ * @param SV_WP_Job_Batch_Handler $handler handler object
115
+ */
116
+ $args = apply_filters( $this->get_job_handler()->get_identifier() . '_batch_handler_js_args', $this->get_js_args(), $this );
117
+
118
+ wc_enqueue_js( sprintf( 'window.%1$s_batch_handler = new %2$s( %3$s );',
119
+ esc_js( $this->get_job_handler()->get_identifier() ),
120
+ esc_js( $this->get_js_class() ),
121
+ json_encode( $args )
122
+ ) );
123
+ }
124
+
125
+
126
+ /**
127
+ * Gets the JavaScript batch handler arguments.
128
+ *
129
+ * @since 4.8.0
130
+ *
131
+ * @return array
132
+ */
133
+ protected function get_js_args() {
134
+
135
+ return array(
136
+ 'id' => $this->get_job_handler()->get_identifier(),
137
+ 'process_nonce' => wp_create_nonce( $this->get_job_handler()->get_identifier() . '_process_batch' ),
138
+ 'cancel_nonce' => wp_create_nonce( $this->get_job_handler()->get_identifier() . '_cancel_job' ),
139
+ );
140
+ }
141
+
142
+
143
+ /**
144
+ * Gets the JavaScript batch handler class name.
145
+ *
146
+ * Plugins can override this with their own handler that extends the base.
147
+ *
148
+ * @since 4.8.0
149
+ *
150
+ * @return string
151
+ */
152
+ protected function get_js_class() {
153
+
154
+ return 'SV_WP_Job_Batch_Handler';
155
+ }
156
+
157
+
158
+ /**
159
+ * Processes a job batch via AJAX.
160
+ *
161
+ * @internal
162
+ *
163
+ * @since 4.8.0
164
+ *
165
+ * @throws \Exception upon error.
166
+ */
167
+ public function ajax_process_batch() {
168
+
169
+ check_ajax_referer( $this->get_job_handler()->get_identifier() . '_process_batch', 'security' );
170
+
171
+ if ( empty( $_POST['job_id'] ) ) {
172
+ return;
173
+ }
174
+
175
+ try {
176
+
177
+ $job = $this->process_batch( $_POST['job_id'] );
178
+
179
+ $job = $this->process_job_status( $job );
180
+
181
+ wp_send_json_success( (array) $job );
182
+
183
+ } catch( SV_WC_Plugin_Exception $e ) {
184
+
185
+ $data = ( ! empty( $job ) ) ? (array) $job : array();
186
+
187
+ $data['message'] = $e->getMessage();
188
+
189
+ wp_send_json_error( $data );
190
+ }
191
+ }
192
+
193
+
194
+ /**
195
+ * Cancels a job via AJAX.
196
+ *
197
+ * @internal
198
+ *
199
+ * @since 4.8.0
200
+ */
201
+ public function ajax_cancel_job() {
202
+
203
+ check_ajax_referer( $this->get_job_handler()->get_identifier() . '_cancel_job', 'security' );
204
+
205
+ if ( empty( $_POST['job_id'] ) ) {
206
+ return;
207
+ }
208
+
209
+ $this->get_job_handler()->delete_job( $_POST['job_id'] );
210
+
211
+ wp_send_json_success();
212
+ }
213
+
214
+
215
+ /**
216
+ * Handles a job after processing one of its batches.
217
+ *
218
+ * Allows plugins to add extra job properties and handle certain statuses.
219
+ * Implementations may throw a SV_WC_Plugin_Exception.
220
+ *
221
+ * @since 4.8.0
222
+ *
223
+ * @param \stdClass|object $job job object
224
+ * @return \stdClass|object $job job object
225
+ */
226
+ protected function process_job_status( $job ) {
227
+
228
+ $job->percentage = SV_WC_Helper::number_format( (int) $job->progress / (int) $job->total * 100 );
229
+
230
+ return $job;
231
+ }
232
+
233
+
234
+ /**
235
+ * Processes a batch of items for the given job.
236
+ *
237
+ * A batch consists of the number of items defined by self::get_items_per_batch()
238
+ * or the number we're able to process before exceeding time or memory limits.
239
+ *
240
+ * @since 4.8.0
241
+ *
242
+ * @param string $job_id job to process
243
+ * @return \stdClass|object $job job after processing the batch
244
+ * @throws \Exception
245
+ * @throws SV_WC_Plugin_Exception
246
+ */
247
+ public function process_batch( $job_id ) {
248
+
249
+ $job = $this->get_job_handler()->get_job( $job_id );
250
+
251
+ if ( ! $job ) {
252
+ throw new SV_WC_Plugin_Exception( 'Invalid job ID' );
253
+ }
254
+
255
+ return $this->get_job_handler()->process_job( $job, $this->get_items_per_batch() );
256
+ }
257
+
258
+
259
+ /**
260
+ * Gets the number of items to process in a single request when processing job item batches.
261
+ *
262
+ * @since 4.8.0
263
+ *
264
+ * @return int
265
+ */
266
+ protected function get_items_per_batch() {
267
+
268
+ /**
269
+ * Filters the number of items to process in a single request when processing job item batches.
270
+ *
271
+ * @since 4.8.0
272
+ *
273
+ * @param int $items_per_batch
274
+ */
275
+ $items_per_batch = absint( apply_filters( $this->get_job_handler()->get_identifier() . '_batch_handler_items_per_batch', $this->items_per_batch ) );
276
+
277
+ return $items_per_batch > 0 ? $items_per_batch : 1;
278
+ }
279
+
280
+
281
+ /**
282
+ * Gets the job handler.
283
+ *
284
+ * @since 4.8.0
285
+ *
286
+ * @return SV_WP_Background_Job_Handler
287
+ */
288
+ protected function get_job_handler() {
289
+
290
+ return $this->job_handler;
291
+ }
292
+
293
+
294
+ /**
295
+ * Gets the plugin instance.
296
+ *
297
+ * @since 4.8.0
298
+ *
299
+ * @return SV_WC_Plugin
300
+ */
301
+ protected function get_plugin() {
302
+
303
+ return $this->plugin;
304
+ }
305
+
306
+
307
+ }
308
+
309
+
310
+ endif;