Version Description
- 2018-01-29 =
- Tweaks - Error handling.
- Public release on .org
Download this release
Release Info
Developer | royho |
Plugin | WooCommerce Square |
Version | 1.0.25 |
Comparing to | |
See all releases |
Version 1.0.25
- assets/css/wc-square-admin-styles.css +46 -0
- assets/css/wc-square-frontend-styles.css +14 -0
- assets/js/wc-square-admin-scripts.js +115 -0
- assets/js/wc-square-admin-scripts.min.js +1 -0
- assets/js/wc-square-payments.js +155 -0
- assets/js/wc-square-payments.min.js +1 -0
- changelog.txt +106 -0
- includes/admin/class-wc-square-admin-integration.php +333 -0
- includes/admin/class-wc-square-admin-product-meta-box.php +155 -0
- includes/admin/class-wc-square-bulk-sync-handler.php +444 -0
- includes/class-wc-square-client.php +379 -0
- includes/class-wc-square-connect.php +678 -0
- includes/class-wc-square-deactivation.php +31 -0
- includes/class-wc-square-install.php +78 -0
- includes/class-wc-square-inventory-poll.php +49 -0
- includes/class-wc-square-sync-from-square.php +384 -0
- includes/class-wc-square-sync-logger.php +52 -0
- includes/class-wc-square-sync-to-square-wp-hooks.php +312 -0
- includes/class-wc-square-sync-to-square.php +440 -0
- includes/class-wc-square-utils.php +918 -0
- includes/class-wc-square-wc-products.php +2458 -0
- includes/payment/class-wc-square-gateway.php +542 -0
- includes/payment/class-wc-square-payment-logger.php +29 -0
- includes/payment/class-wc-square-payments-connect.php +211 -0
- includes/payment/class-wc-square-payments.php +200 -0
- readme.txt +72 -0
- uninstall.php +23 -0
- woocommerce-square.php +419 -0
assets/css/wc-square-admin-styles.css
ADDED
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
.wc-square-progress-bar {
|
2 |
+
background-color: #23282d;
|
3 |
+
height: 15px;
|
4 |
+
padding: 5px;
|
5 |
+
width: 100%;
|
6 |
+
margin: 50px 0;
|
7 |
+
border-radius: 3px;
|
8 |
+
box-shadow: 0 1px 3px #000 inset, 0 1px 0 #444;
|
9 |
+
}
|
10 |
+
|
11 |
+
.wc-square-progress-bar span {
|
12 |
+
display: inline-block;
|
13 |
+
height: 100%;
|
14 |
+
width:1%;
|
15 |
+
border-radius: 3px;
|
16 |
+
box-shadow: 0 1px 0 rgba(255, 255, 255, .3) inset;
|
17 |
+
transition: width .4s ease-in-out;
|
18 |
+
background-color:#00a0d2;
|
19 |
+
}
|
20 |
+
|
21 |
+
.sq-input {
|
22 |
+
height: 60px;
|
23 |
+
}
|
24 |
+
|
25 |
+
.wc-square-connect-button, .wc-square-connect-button:active, .wc-square-connect-button:visited {
|
26 |
+
text-decoration: none;
|
27 |
+
display: inline-block;
|
28 |
+
border-radius: 3px;
|
29 |
+
background-color: #333333;
|
30 |
+
padding: 7px 10px;
|
31 |
+
height: 30px;
|
32 |
+
color: #ffffff;
|
33 |
+
box-shadow: 1px 1px 1px 0px #999999;
|
34 |
+
}
|
35 |
+
|
36 |
+
.wc-square-connect-button:hover, .wc-square-connect-button:focus {
|
37 |
+
color: #ffffff;
|
38 |
+
background-color: #444444;
|
39 |
+
}
|
40 |
+
|
41 |
+
.wc-square-connect-button span {
|
42 |
+
vertical-align: top;
|
43 |
+
display: inline-block;
|
44 |
+
padding-top: 7px;
|
45 |
+
padding-left: 10px;
|
46 |
+
}
|
assets/css/wc-square-frontend-styles.css
ADDED
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
.sq-input {
|
2 |
+
margin: 0 !important;
|
3 |
+
font-size: 1.387em;
|
4 |
+
background-color: #f2f2f2;
|
5 |
+
box-shadow: inset 0 1px 1px rgba(0,0,0,.125);
|
6 |
+
box-sizing: border-box;
|
7 |
+
}
|
8 |
+
|
9 |
+
@media only screen and (max-width: 500px) {
|
10 |
+
div.payment_box.payment_method_square .form-row.form-row-first, div.payment_box.payment_method_square .form-row.form-row-last {
|
11 |
+
float:none;
|
12 |
+
width:100%;
|
13 |
+
}
|
14 |
+
}
|
assets/js/wc-square-admin-scripts.js
ADDED
@@ -0,0 +1,115 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
jQuery( document ).ready( function( $ ) {
|
2 |
+
'use strict';
|
3 |
+
|
4 |
+
// create namespace to avoid any possible conflicts
|
5 |
+
$.wc_square_admin = {
|
6 |
+
/**
|
7 |
+
* Loops through the sync process
|
8 |
+
*
|
9 |
+
* @since 1.0.0
|
10 |
+
* @version 1.0.14
|
11 |
+
* @param int process the current step in the loop
|
12 |
+
* @param string type the type of the AJAX call
|
13 |
+
*/
|
14 |
+
sync: function( process, type ) {
|
15 |
+
var data = {
|
16 |
+
security: wc_square_local.ajaxSyncNonce,
|
17 |
+
process: parseInt( process, 10 ),
|
18 |
+
action: 'wc-to-square' === type ? 'wc_to_square' : 'square_to_wc'
|
19 |
+
};
|
20 |
+
|
21 |
+
$.ajax({
|
22 |
+
type: 'POST',
|
23 |
+
data: data,
|
24 |
+
url: wc_square_local.admin_ajax_url
|
25 |
+
}).done( function( response ) {
|
26 |
+
if ( 'done' === response.process ) {
|
27 |
+
// Triggers when all processing is done.
|
28 |
+
$( 'body' ).trigger( 'woocommerce_square_wc_to_square_sync_complete', [ response ] );
|
29 |
+
|
30 |
+
$( 'table.form-table' ).unblock();
|
31 |
+
|
32 |
+
$( '.wc-square-progress-bar span' ).css( { width: response.percentage + '%' } ).parent( '.wc-square-progress-bar' ).fadeOut( 'slow', function() {
|
33 |
+
alert( response.message );
|
34 |
+
});
|
35 |
+
|
36 |
+
} else {
|
37 |
+
$( '.wc-square-progress-bar span' ).css( { width: response.percentage + '%' } );
|
38 |
+
|
39 |
+
$.wc_square_admin.sync( parseInt( response.process, 10 ), response.type );
|
40 |
+
}
|
41 |
+
}).fail( function( jqXHR, textStatus, errorThrown ) {
|
42 |
+
$( 'table.form-table' ).unblock();
|
43 |
+
console.log( errorThrown );
|
44 |
+
alert( errorThrown );
|
45 |
+
});
|
46 |
+
},
|
47 |
+
|
48 |
+
init: function() {
|
49 |
+
|
50 |
+
$( '.woocommerce_page_wc-settings' ).on( 'click', '#wc-to-square, #square-to-wc', function( e ) {
|
51 |
+
e.preventDefault();
|
52 |
+
|
53 |
+
var confirmed = confirm( wc_square_local.i18n.confirm_sync );
|
54 |
+
|
55 |
+
if ( ! confirmed ) {
|
56 |
+
return;
|
57 |
+
}
|
58 |
+
|
59 |
+
var page = $( this ).parents( 'table.form-table' ),
|
60 |
+
progress_bar = $( '<div class="wc-square-progress-bar wc-square-stripes"><span class="step"></span></div>' );
|
61 |
+
|
62 |
+
// remove the progress bar on each trigger
|
63 |
+
$( '.wc-square-progress-bar' ).remove();
|
64 |
+
|
65 |
+
page.block({
|
66 |
+
message: null,
|
67 |
+
overlayCSS: {
|
68 |
+
background: '#fff',
|
69 |
+
opacity: 0.6
|
70 |
+
}
|
71 |
+
});
|
72 |
+
|
73 |
+
// add the progress bar
|
74 |
+
page.after( progress_bar );
|
75 |
+
|
76 |
+
$.wc_square_admin.sync( 0, $( this ).attr( 'id' ) );
|
77 |
+
});
|
78 |
+
|
79 |
+
$( document.body ).on( 'change', '#woocommerce_square_testmode', function() {
|
80 |
+
if ( $( this ).is( ':checked' ) ) {
|
81 |
+
$( '#woocommerce_square_application_id' ).parents( 'tr' ).eq(0).hide();
|
82 |
+
$( '#woocommerce_square_token' ).parents( 'tr' ).eq(0).hide();
|
83 |
+
|
84 |
+
$( '#woocommerce_square_sandbox_application_id' ).parents( 'tr' ).eq(0).show();
|
85 |
+
$( '#woocommerce_square_sandbox_token' ).parents( 'tr' ).eq(0).show();
|
86 |
+
} else {
|
87 |
+
$( '#woocommerce_square_application_id' ).parents( 'tr' ).eq(0).show();
|
88 |
+
$( '#woocommerce_square_token' ).parents( 'tr' ).eq(0).show();
|
89 |
+
|
90 |
+
$( '#woocommerce_square_sandbox_application_id' ).parents( 'tr' ).eq(0).hide();
|
91 |
+
$( '#woocommerce_square_sandbox_token' ).parents( 'tr' ).eq(0).hide();
|
92 |
+
}
|
93 |
+
});
|
94 |
+
|
95 |
+
$( '#woocommerce_square_testmode' ).trigger( 'change' );
|
96 |
+
|
97 |
+
$( document.body ).on( 'change', '#woocommerce_squareconnect_sync_products', function() {
|
98 |
+
if ( $( this ).is( ':checked' ) ) {
|
99 |
+
$( '#woocommerce_squareconnect_sync_categories' ).parents( 'tr' ).eq(0).show();
|
100 |
+
$( '#woocommerce_squareconnect_sync_inventory' ).parents( 'tr' ).eq(0).show();
|
101 |
+
$( '#woocommerce_squareconnect_sync_images' ).parents( 'tr' ).eq(0).show();
|
102 |
+
} else {
|
103 |
+
$( '#woocommerce_squareconnect_sync_categories' ).parents( 'tr' ).eq(0).hide();
|
104 |
+
$( '#woocommerce_squareconnect_sync_inventory' ).parents( 'tr' ).eq(0).hide();
|
105 |
+
$( '#woocommerce_squareconnect_sync_images' ).parents( 'tr' ).eq(0).hide();
|
106 |
+
}
|
107 |
+
});
|
108 |
+
|
109 |
+
$( '#woocommerce_squareconnect_sync_products' ).trigger( 'change' );
|
110 |
+
}
|
111 |
+
}; // close namespace
|
112 |
+
|
113 |
+
$.wc_square_admin.init();
|
114 |
+
// end document ready
|
115 |
+
});
|
assets/js/wc-square-admin-scripts.min.js
ADDED
@@ -0,0 +1 @@
|
|
|
1 |
+
jQuery(document).ready(function(e){"use strict";e.wc_square_admin={sync:function(o,c){var r={security:wc_square_local.ajaxSyncNonce,process:parseInt(o,10),action:"wc-to-square"===c?"wc_to_square":"square_to_wc"};e.ajax({type:"POST",data:r,url:wc_square_local.admin_ajax_url}).done(function(o){"done"===o.process?(e("body").trigger("woocommerce_square_wc_to_square_sync_complete",[o]),e("table.form-table").unblock(),e(".wc-square-progress-bar span").css({width:o.percentage+"%"}).parent(".wc-square-progress-bar").fadeOut("slow",function(){alert(o.message)})):(e(".wc-square-progress-bar span").css({width:o.percentage+"%"}),e.wc_square_admin.sync(parseInt(o.process,10),o.type))}).fail(function(o,c,r){e("table.form-table").unblock(),console.log(r),alert(r)})},init:function(){e(".woocommerce_page_wc-settings").on("click","#wc-to-square, #square-to-wc",function(o){o.preventDefault();if(confirm(wc_square_local.i18n.confirm_sync)){var c=e(this).parents("table.form-table"),r=e('<div class="wc-square-progress-bar wc-square-stripes"><span class="step"></span></div>');e(".wc-square-progress-bar").remove(),c.block({message:null,overlayCSS:{background:"#fff",opacity:.6}}),c.after(r),e.wc_square_admin.sync(0,e(this).attr("id"))}}),e(document.body).on("change","#woocommerce_square_testmode",function(){e(this).is(":checked")?(e("#woocommerce_square_application_id").parents("tr").eq(0).hide(),e("#woocommerce_square_token").parents("tr").eq(0).hide(),e("#woocommerce_square_sandbox_application_id").parents("tr").eq(0).show(),e("#woocommerce_square_sandbox_token").parents("tr").eq(0).show()):(e("#woocommerce_square_application_id").parents("tr").eq(0).show(),e("#woocommerce_square_token").parents("tr").eq(0).show(),e("#woocommerce_square_sandbox_application_id").parents("tr").eq(0).hide(),e("#woocommerce_square_sandbox_token").parents("tr").eq(0).hide())}),e("#woocommerce_square_testmode").trigger("change"),e(document.body).on("change","#woocommerce_squareconnect_sync_products",function(){e(this).is(":checked")?(e("#woocommerce_squareconnect_sync_categories").parents("tr").eq(0).show(),e("#woocommerce_squareconnect_sync_inventory").parents("tr").eq(0).show(),e("#woocommerce_squareconnect_sync_images").parents("tr").eq(0).show()):(e("#woocommerce_squareconnect_sync_categories").parents("tr").eq(0).hide(),e("#woocommerce_squareconnect_sync_inventory").parents("tr").eq(0).hide(),e("#woocommerce_squareconnect_sync_images").parents("tr").eq(0).hide())}),e("#woocommerce_squareconnect_sync_products").trigger("change")}},e.wc_square_admin.init()});
|
assets/js/wc-square-payments.js
ADDED
@@ -0,0 +1,155 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
(function ( $ ) {
|
2 |
+
'use strict';
|
3 |
+
|
4 |
+
var wcSquarePaymentForm;
|
5 |
+
|
6 |
+
// create namespace to avoid any possible conflicts
|
7 |
+
$.wc_square_payments = {
|
8 |
+
init: function() {
|
9 |
+
// Checkout page
|
10 |
+
$( document.body ).on( 'updated_checkout', function() {
|
11 |
+
$.wc_square_payments.loadForm();
|
12 |
+
});
|
13 |
+
|
14 |
+
// Pay order form page
|
15 |
+
if ( $( 'form#order_review' ).length ) {
|
16 |
+
$.wc_square_payments.loadForm();
|
17 |
+
}
|
18 |
+
|
19 |
+
var custom_element = square_params.custom_form_trigger_element;
|
20 |
+
|
21 |
+
// custom click trigger for 3rd party forms that initially hides the payment form
|
22 |
+
// such as multistep checkout plugins
|
23 |
+
if ( custom_element.length ) {
|
24 |
+
$( document.body ).on( 'click', custom_element, function() {
|
25 |
+
$.wc_square_payments.loadForm();
|
26 |
+
});
|
27 |
+
}
|
28 |
+
|
29 |
+
// work around for iFrame not loading if elements being replaced is hidden
|
30 |
+
$( document.body ).on( 'click', '#payment_method_square', function() {
|
31 |
+
$( '.payment_box.payment_method_square' ).css( { 'display': 'block', 'visibility': 'visible', 'height': 'auto' } );
|
32 |
+
});
|
33 |
+
},
|
34 |
+
loadForm: function() {
|
35 |
+
if ( $( '#payment_method_square' ).length ) {
|
36 |
+
// work around for iFrame not loading if elements being replaced is hidden
|
37 |
+
if ( ! $( '#payment_method_square' ).is( ':checked' ) ) {
|
38 |
+
$( '.payment_box.payment_method_square' ).css( { 'display': 'block', 'visibility': 'hidden', 'height': '0' } );
|
39 |
+
}
|
40 |
+
|
41 |
+
// destroy the form and rebuild on each init
|
42 |
+
if ( 'object' === $.type( wcSquarePaymentForm ) ) {
|
43 |
+
wcSquarePaymentForm.destroy();
|
44 |
+
}
|
45 |
+
|
46 |
+
wcSquarePaymentForm = new SqPaymentForm({
|
47 |
+
env: square_params.environment,
|
48 |
+
applicationId: square_params.application_id,
|
49 |
+
inputClass: 'sq-input',
|
50 |
+
cardNumber: {
|
51 |
+
elementId: 'sq-card-number',
|
52 |
+
placeholder: square_params.placeholder_card_number
|
53 |
+
},
|
54 |
+
cvv: {
|
55 |
+
elementId: 'sq-cvv',
|
56 |
+
placeholder: square_params.placeholder_card_cvv
|
57 |
+
},
|
58 |
+
expirationDate: {
|
59 |
+
elementId: 'sq-expiration-date',
|
60 |
+
placeholder: square_params.placeholder_card_expiration
|
61 |
+
},
|
62 |
+
postalCode: {
|
63 |
+
elementId: 'sq-postal-code',
|
64 |
+
placeholder: square_params.placeholder_card_postal_code
|
65 |
+
},
|
66 |
+
callbacks: {
|
67 |
+
cardNonceResponseReceived: function( errors, nonce, cardData ) {
|
68 |
+
if ( errors ) {
|
69 |
+
var html = '';
|
70 |
+
|
71 |
+
html += '<ul class="woocommerce_error woocommerce-error">';
|
72 |
+
|
73 |
+
// handle errors
|
74 |
+
$( errors ).each( function( index, error ) {
|
75 |
+
html += '<li>' + error.message + '</li>';
|
76 |
+
});
|
77 |
+
|
78 |
+
html += '</ul>';
|
79 |
+
|
80 |
+
// append it to DOM
|
81 |
+
$( '.payment_method_square fieldset' ).eq(0).prepend( html );
|
82 |
+
} else {
|
83 |
+
var $form = $( 'form.woocommerce-checkout, form#order_review' );
|
84 |
+
|
85 |
+
// inject nonce to a hidden field to be submitted
|
86 |
+
$form.append( '<input type="hidden" class="square-nonce" name="square_nonce" value="' + nonce + '" />' );
|
87 |
+
|
88 |
+
$form.submit();
|
89 |
+
}
|
90 |
+
},
|
91 |
+
|
92 |
+
paymentFormLoaded: function() {
|
93 |
+
wcSquarePaymentForm.setPostalCode( $( '#billing_postcode' ).val() );
|
94 |
+
},
|
95 |
+
|
96 |
+
unsupportedBrowserDetected: function() {
|
97 |
+
var html = '';
|
98 |
+
|
99 |
+
html += '<ul class="woocommerce_error woocommerce-error">';
|
100 |
+
html += '<li>' + square_params.unsupported_browser + '</li>';
|
101 |
+
html += '</ul>';
|
102 |
+
|
103 |
+
// append it to DOM
|
104 |
+
$( '.payment_method_square fieldset' ).eq(0).prepend( html );
|
105 |
+
}
|
106 |
+
},
|
107 |
+
inputStyles: $.parseJSON( square_params.payment_form_input_styles )
|
108 |
+
});
|
109 |
+
|
110 |
+
wcSquarePaymentForm.build();
|
111 |
+
|
112 |
+
// when checkout form is submitted on checkout page
|
113 |
+
$( 'form.woocommerce-checkout' ).on( 'checkout_place_order_square', function( event ) {
|
114 |
+
// remove any error messages first
|
115 |
+
$( '.payment_method_square .woocommerce-error' ).remove();
|
116 |
+
|
117 |
+
if ( $( '#payment_method_square' ).is( ':checked' ) && $( 'input.square-nonce' ).size() === 0 ) {
|
118 |
+
wcSquarePaymentForm.requestCardNonce();
|
119 |
+
|
120 |
+
return false;
|
121 |
+
}
|
122 |
+
|
123 |
+
return true;
|
124 |
+
});
|
125 |
+
|
126 |
+
// when checkout form is submitted on pay order page
|
127 |
+
$( 'form#order_review' ).on( 'submit', function( event ) {
|
128 |
+
// remove any error messages first
|
129 |
+
$( '.payment_method_square .woocommerce-error' ).remove();
|
130 |
+
|
131 |
+
if ( $( '#payment_method_square' ).is( ':checked' ) && $( 'input.square-nonce' ).size() === 0 ) {
|
132 |
+
wcSquarePaymentForm.requestCardNonce();
|
133 |
+
|
134 |
+
return false;
|
135 |
+
}
|
136 |
+
|
137 |
+
return true;
|
138 |
+
});
|
139 |
+
|
140 |
+
$( document.body ).on( 'checkout_error', function() {
|
141 |
+
$( 'input.square-nonce' ).remove();
|
142 |
+
});
|
143 |
+
|
144 |
+
// work around for iFrame not loading if elements being replaced is hidden
|
145 |
+
setTimeout( function() {
|
146 |
+
if ( ! $( '#payment_method_square' ).is( ':checked' ) ) {
|
147 |
+
$( '.payment_box.payment_method_square' ).css( { 'display': 'none', 'visibility': 'visible', 'height': 'auto' } );
|
148 |
+
}
|
149 |
+
}, 1000 );
|
150 |
+
}
|
151 |
+
}
|
152 |
+
}; // close namespace
|
153 |
+
|
154 |
+
$.wc_square_payments.init();
|
155 |
+
}( jQuery ) );
|
assets/js/wc-square-payments.min.js
ADDED
@@ -0,0 +1 @@
|
|
|
1 |
+
!function(e){"use strict";var o;e.wc_square_payments={init:function(){e(document.body).on("updated_checkout",function(){e.wc_square_payments.loadForm()}),e("form#order_review").length&&e.wc_square_payments.loadForm();var o=square_params.custom_form_trigger_element;o.length&&e(document.body).on("click",o,function(){e.wc_square_payments.loadForm()}),e(document.body).on("click","#payment_method_square",function(){e(".payment_box.payment_method_square").css({display:"block",visibility:"visible",height:"auto"})})},loadForm:function(){e("#payment_method_square").length&&(e("#payment_method_square").is(":checked")||e(".payment_box.payment_method_square").css({display:"block",visibility:"hidden",height:"0"}),"object"===e.type(o)&&o.destroy(),(o=new SqPaymentForm({env:square_params.environment,applicationId:square_params.application_id,inputClass:"sq-input",cardNumber:{elementId:"sq-card-number",placeholder:square_params.placeholder_card_number},cvv:{elementId:"sq-cvv",placeholder:square_params.placeholder_card_cvv},expirationDate:{elementId:"sq-expiration-date",placeholder:square_params.placeholder_card_expiration},postalCode:{elementId:"sq-postal-code",placeholder:square_params.placeholder_card_postal_code},callbacks:{cardNonceResponseReceived:function(o,r,a){if(o){var t="";t+='<ul class="woocommerce_error woocommerce-error">',e(o).each(function(e,o){t+="<li>"+o.message+"</li>"}),t+="</ul>",e(".payment_method_square fieldset").eq(0).prepend(t)}else{var n=e("form.woocommerce-checkout, form#order_review");n.append('<input type="hidden" class="square-nonce" name="square_nonce" value="'+r+'" />'),n.submit()}},paymentFormLoaded:function(){o.setPostalCode(e("#billing_postcode").val())},unsupportedBrowserDetected:function(){var o="";o+='<ul class="woocommerce_error woocommerce-error">',o+="<li>"+square_params.unsupported_browser+"</li>",o+="</ul>",e(".payment_method_square fieldset").eq(0).prepend(o)}},inputStyles:e.parseJSON(square_params.payment_form_input_styles)})).build(),e("form.woocommerce-checkout").on("checkout_place_order_square",function(r){return e(".payment_method_square .woocommerce-error").remove(),!e("#payment_method_square").is(":checked")||0!==e("input.square-nonce").size()||(o.requestCardNonce(),!1)}),e("form#order_review").on("submit",function(r){return e(".payment_method_square .woocommerce-error").remove(),!e("#payment_method_square").is(":checked")||0!==e("input.square-nonce").size()||(o.requestCardNonce(),!1)}),e(document.body).on("checkout_error",function(){e("input.square-nonce").remove()}),setTimeout(function(){e("#payment_method_square").is(":checked")||e(".payment_box.payment_method_square").css({display:"none",visibility:"visible",height:"auto"})},1e3))}},e.wc_square_payments.init()}(jQuery);
|
changelog.txt
ADDED
@@ -0,0 +1,106 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
*** WooCommerce Square Changelog ***
|
2 |
+
|
3 |
+
2018-01-29 - version 1.0.25
|
4 |
+
* Tweaks - Error handling.
|
5 |
+
* Add - Public Release on .org
|
6 |
+
|
7 |
+
2017-12-13 - version 1.0.24
|
8 |
+
* Fix - In some cases rounding issues occur causing payment unable to process.
|
9 |
+
* Update - WC tested up to version.
|
10 |
+
* Add - Readme.txt.
|
11 |
+
|
12 |
+
2017-11-10 - version 1.0.23
|
13 |
+
* Add - WC minimum requirements in header.
|
14 |
+
* Fix - WC Product sometimes is not a proper object preventing sync.
|
15 |
+
* Fix - PHP 7 notice with amount not cast an int.
|
16 |
+
|
17 |
+
2017-09-06 - version 1.0.22
|
18 |
+
* Tweak - Increase inventory polling cache and add timeout limit.
|
19 |
+
* Fix - CRON scheduling time not using UTC causes delay in CRON job.
|
20 |
+
|
21 |
+
2017-07-26 - version 1.0.21
|
22 |
+
* Fix - Non decimal place price such as Japanese Yen is not formatted properly when syncing Square to WC.
|
23 |
+
|
24 |
+
2017-06-29 - version 1.0.20
|
25 |
+
* Fix - Schedule sale price not updating on Square when sale is over.
|
26 |
+
* Fix - Pass price to Square tax exclusive instead of inclusive.
|
27 |
+
* Fix - Create customer error on payments when phone number is not provided.
|
28 |
+
* Update - Settings location description to be more clear.
|
29 |
+
|
30 |
+
2017-06-06 - version 1.0.19
|
31 |
+
* Fix - Sync currency amounts that do not contain decimals correctly.
|
32 |
+
|
33 |
+
2017-05-19 - version 1.0.18
|
34 |
+
* Fix - Better logic of sale price on WC when syncing so it doesn't override regular price unintented.
|
35 |
+
* Fix - If image exists for a product, don't sync the image to prevent duplicated images being created.
|
36 |
+
* Fix - Enable status not showing for Square payments in gateway list.
|
37 |
+
|
38 |
+
2017-05-08 - version 1.0.17
|
39 |
+
* Fix - WC 3.0 debug tools compatibility.
|
40 |
+
* Add - Support for Japan market.
|
41 |
+
|
42 |
+
2017-03-31 - version 1.0.16
|
43 |
+
* Add - Confirmation message before bulk sync.
|
44 |
+
* Add - Cron schedules for when product stock changes instead of firing off immediately.
|
45 |
+
|
46 |
+
2017-03-27 - version 1.0.15
|
47 |
+
* Update - Additional updates for WooCommerce 3.0.0 compatibility.
|
48 |
+
* Add - Support for UK market.
|
49 |
+
|
50 |
+
2017-03-13 - version 1.0.14
|
51 |
+
* Fix - When multiple notify emails are set, causes 500 server error.
|
52 |
+
* Fix - Sync issues when syncing large datasets.
|
53 |
+
* Fix - Sync item with no sku sometimes gets corrupted with other item data.
|
54 |
+
* Update - WooCommerce 3.0.0 compatibility.
|
55 |
+
|
56 |
+
2017-01-17 - version 1.0.13
|
57 |
+
* Add - Support for Australian markets.
|
58 |
+
|
59 |
+
2017-01-09 - version 1.0.12
|
60 |
+
* Fix - When syncing inventory WC to Square, stock level becomes zero.
|
61 |
+
|
62 |
+
2016-10-26 - version 1.0.11
|
63 |
+
* Fix - When WC API is used, it can cause duplicate WC API Exception.
|
64 |
+
|
65 |
+
2016-10-14 - version 1.0.10
|
66 |
+
* Update - Make sure Square only works for US and CA merchants.
|
67 |
+
|
68 |
+
2016-09-12 - version 1.0.9
|
69 |
+
* Fix - Normalize price when syncing Square to WC to prevent errors.
|
70 |
+
* Add - Option to disable sync per product.
|
71 |
+
|
72 |
+
2016-09-08 - version 1.0.8
|
73 |
+
* Add - WC Products CRUD to replace REST API for creating products to prevent interference with wc-api.
|
74 |
+
* Add - Filter to add custom DOM elements for payments to render payment form "woocommerce_square_payment_form_trigger_element".
|
75 |
+
* Fix - Payments token expiration was not handled correctly due to using API v2.
|
76 |
+
|
77 |
+
2016-08-30 - version 1.0.7
|
78 |
+
* Tweak - Replace all wp_remote_requests with raw cURL.
|
79 |
+
* Fix - In IE, the browser won't replace iFrame form fields when it is hidden.
|
80 |
+
|
81 |
+
2016-08-27 - version 1.0.6
|
82 |
+
* Add - Clear cache/transient tool in system status->tool.
|
83 |
+
* Fix - Prevent infinite loop when polling Square inventory.
|
84 |
+
|
85 |
+
2016-08-25 - version 1.0.5
|
86 |
+
* Fix - Image not syncing when using wp_remote_request. Changed to raw cURL.
|
87 |
+
|
88 |
+
2016-08-19 - version 1.0.4
|
89 |
+
* Tweak - Payment form field styles.
|
90 |
+
* Add - woocommerce_square_payment_input_styles filter to allow manipulation of the form styles.
|
91 |
+
* Fix - When duplicating product in WC, it replaces the original product on Square.
|
92 |
+
* Fix - In FireFox, the browser won't replace iFrame form fields when it is hidden.
|
93 |
+
|
94 |
+
2016-08-16 - version 1.0.3
|
95 |
+
* Fix - When duplicating product in WC, it replaces the original product on Square.
|
96 |
+
* Fix - oAuth tokens were not renewing properly.
|
97 |
+
|
98 |
+
2016-08-04 - version 1.0.2
|
99 |
+
* Update - HTTP protocol to version 1.1 to prevent timeouts.
|
100 |
+
|
101 |
+
2016-08-01 - version 1.0.1
|
102 |
+
* Tweak - Make credit card form fields more responsive in mobile devices.
|
103 |
+
* Fix - Square list endpoints may return duplicates, so put in place a check for that.
|
104 |
+
|
105 |
+
2016-07-26 - version 1.0.0
|
106 |
+
* First Release
|
includes/admin/class-wc-square-admin-integration.php
ADDED
@@ -0,0 +1,333 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
if ( ! defined( 'ABSPATH' ) ) {
|
3 |
+
exit; // Exit if accessed directly
|
4 |
+
}
|
5 |
+
|
6 |
+
/**
|
7 |
+
* Class WC_Square_Integration
|
8 |
+
*
|
9 |
+
* Settings CRUD for the extension.
|
10 |
+
*/
|
11 |
+
class WC_Square_Integration extends WC_Integration {
|
12 |
+
private $oauth_connect_url;
|
13 |
+
private $merchant_access_token;
|
14 |
+
|
15 |
+
/**
|
16 |
+
* Constructor
|
17 |
+
*/
|
18 |
+
public function __construct() {
|
19 |
+
$this->id = 'squareconnect';
|
20 |
+
$this->method_title = __( 'Square', 'woocommerce-square' );
|
21 |
+
$this->method_description = __( 'Connect with Square to start syncing your products and inventory and also accept credit card and debit card payments in your checkout.', 'woocommerce-square' );
|
22 |
+
$this->merchant_access_token = get_option( 'woocommerce_square_merchant_access_token' );
|
23 |
+
|
24 |
+
$this->maybe_save_token();
|
25 |
+
$this->maybe_delete_token();
|
26 |
+
|
27 |
+
$this->init_form_fields();
|
28 |
+
$this->init_settings();
|
29 |
+
|
30 |
+
$this->oauth_connect_url = 'https://connect.woocommerce.com/login/square';
|
31 |
+
|
32 |
+
if ( WC_SQUARE_ENABLE_STAGING ) {
|
33 |
+
$this->oauth_connect_url = 'https://connect.woocommerce.com/login/squaresandbox';
|
34 |
+
}
|
35 |
+
|
36 |
+
add_action( 'woocommerce_update_options_integration_' . $this->id, array( $this, 'process_admin_options' ) );
|
37 |
+
add_filter( 'woocommerce_settings_api_form_fields_' . $this->id, array( $this, 'maybe_render_locations' ) );
|
38 |
+
}
|
39 |
+
|
40 |
+
/**
|
41 |
+
* Adds the form fields for the settings
|
42 |
+
*
|
43 |
+
* @access public
|
44 |
+
* @since 1.0.0
|
45 |
+
* @version 1.0.0
|
46 |
+
* @return bool
|
47 |
+
*/
|
48 |
+
public function init_form_fields() {
|
49 |
+
$application_dashboard_url = 'https://connect.squareup.com/apps';
|
50 |
+
|
51 |
+
$form_fields = array(
|
52 |
+
'location' => array(
|
53 |
+
'title' => __( 'Business Location', 'woocommerce-square' ),
|
54 |
+
'type' => 'select',
|
55 |
+
'description' => __( 'Select the location you wish to link to this site. You must have <a href="https://squareup.com/dashboard/locations" target="_blank">locations</a> set in Square and approved.', 'woocommerce-square' ),
|
56 |
+
'desc_tip' => false,
|
57 |
+
'options' => array( '' => __( 'Select a Location', 'woocommerce-square' ) ),
|
58 |
+
'disabled' => true,
|
59 |
+
),
|
60 |
+
'sync_email' => array(
|
61 |
+
'title' => __( 'Notification Email', 'woocommerce-square' ),
|
62 |
+
'type' => 'text',
|
63 |
+
'description' => __( 'Enter the email(s) to be notified when a sync has ended. Separate each email with a comma.', 'woocommerce-square' ),
|
64 |
+
'desc_tip' => false,
|
65 |
+
'default' => '',
|
66 |
+
'placeholder' => get_option( 'admin_email' ),
|
67 |
+
),
|
68 |
+
'logging' => array(
|
69 |
+
'title' => __( 'Logging', 'woocommerce-square' ),
|
70 |
+
'label' => __( 'Log debug messages', 'woocommerce-square' ),
|
71 |
+
'type' => 'checkbox',
|
72 |
+
'description' => __( 'Save debug messages to the WooCommerce System Status log.', 'woocommerce-square' ),
|
73 |
+
'default' => 'no',
|
74 |
+
),
|
75 |
+
'sync_title' => array(
|
76 |
+
'title' => __( 'Synchronization', 'woocommerce-square' ),
|
77 |
+
'type' => 'title',
|
78 |
+
'description' => __( 'Determine which aspects of your product catalog to synchronize between WooCommerce and Square. Products need to have SKUs set for each variation.', 'woocommerce-square' ),
|
79 |
+
),
|
80 |
+
'sync_products' => array(
|
81 |
+
'title' => __( 'Enabled', 'woocommerce-square' ),
|
82 |
+
'type' => 'checkbox',
|
83 |
+
'label' => __( 'Products', 'woocommerce-square' ),
|
84 |
+
'description' => __( 'Basic Product information will be synced, excluding Categories and Inventory.', 'woocommerce-square' ),
|
85 |
+
'default' => 'yes',
|
86 |
+
),
|
87 |
+
'sync_categories' => array(
|
88 |
+
'title' => __( 'Include Categories', 'woocommerce-square' ),
|
89 |
+
'type' => 'checkbox',
|
90 |
+
'label' => __( 'Sync Categories', 'woocommerce-square' ),
|
91 |
+
'description' => __( 'Categories will sync on creation or update, and Products will have their Categories synced.', 'woocommerce-square' ),
|
92 |
+
'default' => 'yes',
|
93 |
+
),
|
94 |
+
'sync_inventory' => array(
|
95 |
+
'title' => __( 'Include Inventory', 'woocommerce-square' ),
|
96 |
+
'type' => 'checkbox',
|
97 |
+
'label' => __( 'Sync Inventory', 'woocommerce-square' ),
|
98 |
+
'description' => __( 'Inventory will sync on manual update or after a Product is ordered.', 'woocommerce-square' ),
|
99 |
+
'default' => 'yes',
|
100 |
+
),
|
101 |
+
'sync_images' => array(
|
102 |
+
'title' => __( 'Include Images', 'woocommerce-square' ),
|
103 |
+
'type' => 'checkbox',
|
104 |
+
'label' => __( 'Sync Images', 'woocommerce-square' ),
|
105 |
+
'description' => __( 'Product Image will be synced.', 'woocommerce-square' ),
|
106 |
+
'default' => 'yes',
|
107 |
+
),
|
108 |
+
'inventory_polling' => array(
|
109 |
+
'title' => __( 'Square Inventory Sync', 'woocommerce-square' ),
|
110 |
+
'type' => 'checkbox',
|
111 |
+
'label' => __( 'Enable', 'woocommerce-square' ),
|
112 |
+
'description' => __( 'For automatic inventory syncing from Square to WooCommerce, this needs to be enabled. It will poll the inventory from Square on an hourly basis.', 'woocommerce-square' ),
|
113 |
+
'default' => 'no',
|
114 |
+
),
|
115 |
+
);
|
116 |
+
|
117 |
+
$this->form_fields = apply_filters( 'woocommerce_square_integration_settings_args', $form_fields );
|
118 |
+
|
119 |
+
return true;
|
120 |
+
}
|
121 |
+
|
122 |
+
/**
|
123 |
+
* Add in our own custom options
|
124 |
+
*
|
125 |
+
* @access public
|
126 |
+
* @since 1.0.0
|
127 |
+
* @version 1.0.0
|
128 |
+
* @return bool
|
129 |
+
*/
|
130 |
+
public function admin_options() {
|
131 |
+
$current_user = wp_get_current_user();
|
132 |
+
|
133 |
+
$redirect_url = add_query_arg(
|
134 |
+
array(
|
135 |
+
'page' => 'wc-settings',
|
136 |
+
'tab' => 'integration',
|
137 |
+
'section' => $this->id,
|
138 |
+
),
|
139 |
+
admin_url( 'admin.php' )
|
140 |
+
);
|
141 |
+
|
142 |
+
$redirect_url = wp_nonce_url( $redirect_url, 'connect_square', 'wc_square_token_nonce' );
|
143 |
+
|
144 |
+
$query_args = array(
|
145 |
+
'redirect' => urlencode( urlencode( $redirect_url ) ),
|
146 |
+
'scopes' => 'MERCHANT_PROFILE_READ,PAYMENTS_READ,PAYMENTS_WRITE,CUSTOMERS_READ,CUSTOMERS_WRITE,SETTLEMENTS_READ,ITEMS_READ,ITEMS_WRITE',
|
147 |
+
);
|
148 |
+
|
149 |
+
$production_connect_url = add_query_arg( $query_args, $this->oauth_connect_url );
|
150 |
+
|
151 |
+
$disconnect_url = add_query_arg(
|
152 |
+
array(
|
153 |
+
'page' => 'wc-settings',
|
154 |
+
'tab' => 'integration',
|
155 |
+
'section' => $this->id,
|
156 |
+
'disconnect_square' => 1,
|
157 |
+
),
|
158 |
+
admin_url( 'admin.php' )
|
159 |
+
);
|
160 |
+
|
161 |
+
$disconnect_url = wp_nonce_url( $disconnect_url, 'disconnect_square', 'wc_square_token_nonce' );
|
162 |
+
|
163 |
+
echo '<h2>' . esc_html( $this->get_method_title() ) . '</h2>';
|
164 |
+
echo wp_kses_post( wpautop( $this->get_method_description() ) );
|
165 |
+
echo '<div><input type="hidden" name="section" value="' . esc_attr( $this->id ) . '" /></div>';
|
166 |
+
?>
|
167 |
+
|
168 |
+
<table class="form-table">
|
169 |
+
<tbody>
|
170 |
+
<tr>
|
171 |
+
<th>
|
172 |
+
<?php esc_html_e( 'Connect/Disconnect', 'woocommerce-square' ); ?>
|
173 |
+
</th>
|
174 |
+
<td>
|
175 |
+
<?php if ( ! empty( $this->merchant_access_token ) ) { ?>
|
176 |
+
<a href="<?php echo esc_attr( $disconnect_url ); ?>" class='button-primary'>
|
177 |
+
<?php echo esc_html__( 'Disconnect from Square', 'woocommerce-square' ); ?>
|
178 |
+
</a>
|
179 |
+
<?php } else { ?>
|
180 |
+
<a href="<?php echo esc_attr( $production_connect_url ); ?>" class="wc-square-connect-button">
|
181 |
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 44 44" width="30" height="30">
|
182 |
+
<path fill="#FFFFFF" d="M36.65 0h-29.296c-4.061 0-7.354 3.292-7.354 7.354v29.296c0 4.062 3.293 7.354 7.354 7.354h29.296c4.062 0 7.354-3.292 7.354-7.354v-29.296c.001-4.062-3.291-7.354-7.354-7.354zm-.646 33.685c0 1.282-1.039 2.32-2.32 2.32h-23.359c-1.282 0-2.321-1.038-2.321-2.32v-23.36c0-1.282 1.039-2.321 2.321-2.321h23.359c1.281 0 2.32 1.039 2.32 2.321v23.36z" />
|
183 |
+
<path fill="#FFFFFF" d="M17.333 28.003c-.736 0-1.332-.6-1.332-1.339v-9.324c0-.739.596-1.339 1.332-1.339h9.338c.738 0 1.332.6 1.332 1.339v9.324c0 .739-.594 1.339-1.332 1.339h-9.338z" />
|
184 |
+
</svg>
|
185 |
+
<span><?php esc_html_e( 'Connect with Square', 'woocommerce-square' ); ?></span>
|
186 |
+
</a>
|
187 |
+
<?php } ?>
|
188 |
+
</td>
|
189 |
+
</tr>
|
190 |
+
</tbody>
|
191 |
+
</table>
|
192 |
+
|
193 |
+
<?php
|
194 |
+
if ( ! empty( $this->merchant_access_token ) ) { ?>
|
195 |
+
<?php echo '<table class="form-table">' . $this->generate_settings_html( $this->get_form_fields(), false ) . '</table>'; ?>
|
196 |
+
<?php
|
197 |
+
|
198 |
+
// only show rest of the settings if a location is selected
|
199 |
+
if ( $this->get_option( 'location' ) ) :
|
200 |
+
?>
|
201 |
+
<h3 class="wc-settings-sub-title"><?php esc_html_e( 'Initiate a Manual Sync', 'woocommerce-square' ); ?></h3>
|
202 |
+
<table class="form-table">
|
203 |
+
<tr valign="top">
|
204 |
+
<th scope="row" class="titledesc">
|
205 |
+
<label for="wc-to-square"><?php esc_html_e( 'WC → Square Sync', 'woocommerce-square' ); ?></label>
|
206 |
+
<?php echo wc_help_tip( __( 'Click button to perform a one time sync from WooCommerce to Square.', 'woocommerce-square' ) ); ?>
|
207 |
+
</th>
|
208 |
+
<td class="forminp">
|
209 |
+
<a href="#" id="wc-to-square" class="button button-secondary"><?php esc_html_e( 'WC → Square', 'woocommerce-square' ); ?></a>
|
210 |
+
</td>
|
211 |
+
</tr>
|
212 |
+
<tr valign="top">
|
213 |
+
<th scope="row" class="titledesc">
|
214 |
+
<label for="square-to-wc"><?php esc_html_e( 'Square → WC Sync', 'woocommerce-square' ); ?></label>
|
215 |
+
<?php echo wc_help_tip( __( 'Click button to perform a one time sync from Square to WooCommerce.', 'woocommerce-square' ) ); ?>
|
216 |
+
</th>
|
217 |
+
<td class="forminp">
|
218 |
+
<a href="#" id="square-to-wc" class="button button-secondary"><?php esc_html_e( 'Square → WC', 'woocommerce-square' ); ?></a>
|
219 |
+
</td>
|
220 |
+
</tr>
|
221 |
+
</table>
|
222 |
+
<?php endif;
|
223 |
+
|
224 |
+
do_action( 'woocommerce_square_integration_custom_settings' );
|
225 |
+
}
|
226 |
+
}
|
227 |
+
|
228 |
+
/**
|
229 |
+
* Maybe save the token from authentication.
|
230 |
+
*
|
231 |
+
* @access public
|
232 |
+
* @since 1.0.0
|
233 |
+
* @version 1.0.0
|
234 |
+
* @return bool
|
235 |
+
*/
|
236 |
+
public function maybe_save_token() {
|
237 |
+
if ( empty( $_GET['square_access_token'] ) ) {
|
238 |
+
return false;
|
239 |
+
}
|
240 |
+
|
241 |
+
if ( function_exists( 'wp_verify_nonce' ) && ! wp_verify_nonce( $_GET['wc_square_token_nonce'], 'connect_square' ) ) {
|
242 |
+
wp_die( __( 'Cheatin’ huh?', 'woocommerce-square' ) );
|
243 |
+
}
|
244 |
+
|
245 |
+
$existing_token = get_option( 'woocommerce_square_merchant_access_token' );
|
246 |
+
|
247 |
+
// if token already exists, don't continue
|
248 |
+
if ( ! empty( $existing_token ) ) {
|
249 |
+
return false;
|
250 |
+
}
|
251 |
+
|
252 |
+
update_option( 'woocommerce_square_merchant_access_token', sanitize_text_field( urldecode( $_GET['square_access_token'] ) ) );
|
253 |
+
|
254 |
+
// let's set the token instance again so settings option is refreshed
|
255 |
+
$this->merchant_access_token = get_option( 'woocommerce_square_merchant_access_token' );
|
256 |
+
|
257 |
+
delete_transient( WC_Square_Connect::LOCATIONS_CACHE_KEY );
|
258 |
+
|
259 |
+
return true;
|
260 |
+
}
|
261 |
+
|
262 |
+
/**
|
263 |
+
* Maybe delete the token for merchant.
|
264 |
+
*
|
265 |
+
* @todo perhaps we should also revoke the token fromm connect.woocommerce.com??
|
266 |
+
* @access public
|
267 |
+
* @since 1.0.0
|
268 |
+
* @version 1.0.0
|
269 |
+
* @return bool
|
270 |
+
*/
|
271 |
+
public function maybe_delete_token() {
|
272 |
+
if ( empty( $_GET['disconnect_square'] ) ) {
|
273 |
+
return false;
|
274 |
+
}
|
275 |
+
|
276 |
+
if ( function_exists( 'wp_verify_nonce' ) && ! wp_verify_nonce( $_GET['wc_square_token_nonce'], 'disconnect_square' ) ) {
|
277 |
+
wp_die( __( 'Cheatin’ huh?', 'woocommerce-square' ) );
|
278 |
+
}
|
279 |
+
|
280 |
+
$existing_token = get_option( 'woocommerce_square_merchant_access_token' );
|
281 |
+
|
282 |
+
// if token does not exist, don't continue
|
283 |
+
if ( empty( $existing_token ) ) {
|
284 |
+
return false;
|
285 |
+
}
|
286 |
+
|
287 |
+
delete_option( 'woocommerce_square_merchant_access_token' );
|
288 |
+
|
289 |
+
// let's set the token instance again so settings option is refreshed
|
290 |
+
$this->merchant_access_token = get_option( 'woocommerce_square_merchant_access_token' );
|
291 |
+
|
292 |
+
delete_transient( WC_Square_Connect::LOCATIONS_CACHE_KEY );
|
293 |
+
|
294 |
+
return true;
|
295 |
+
}
|
296 |
+
|
297 |
+
/**
|
298 |
+
* Validates location field.
|
299 |
+
*
|
300 |
+
* @access public
|
301 |
+
* @since 1.0.0
|
302 |
+
* @version 1.0.0
|
303 |
+
* @return bool
|
304 |
+
*/
|
305 |
+
public function validate_location_field( $key ) {
|
306 |
+
$field = $this->get_field_key( $key );
|
307 |
+
$value = ! empty( $_POST[ $field ] ) ? $_POST[ $field ] : '';
|
308 |
+
|
309 |
+
$token = $this->get_option( 'token' );
|
310 |
+
if ( empty( $token ) && empty( $value ) ) {
|
311 |
+
delete_transient( WC_Square_Connect::LOCATIONS_CACHE_KEY );
|
312 |
+
}
|
313 |
+
|
314 |
+
return $this->validate_select_field( $key, $value );
|
315 |
+
}
|
316 |
+
|
317 |
+
/**
|
318 |
+
* Maybe render list of locations.
|
319 |
+
*
|
320 |
+
* @param array $form_fields Form fields
|
321 |
+
*
|
322 |
+
* @return array Form fields
|
323 |
+
*/
|
324 |
+
public function maybe_render_locations( $form_fields ) {
|
325 |
+
$locations = Woocommerce_Square::instance()->square_connect->get_square_business_locations();
|
326 |
+
if ( ! empty( $locations ) && ! empty( $form_fields ) ) {
|
327 |
+
$form_fields['location']['options'] = array_merge( $form_fields['location']['options'], $locations );
|
328 |
+
$form_fields['location']['disabled'] = false;
|
329 |
+
}
|
330 |
+
|
331 |
+
return $form_fields;
|
332 |
+
}
|
333 |
+
}
|
includes/admin/class-wc-square-admin-product-meta-box.php
ADDED
@@ -0,0 +1,155 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
if ( ! defined( 'ABSPATH' ) ) {
|
3 |
+
exit;
|
4 |
+
}
|
5 |
+
|
6 |
+
/**
|
7 |
+
* Class WC_Square_Admin_Product_Meta_Box
|
8 |
+
*
|
9 |
+
* Adds product specific sync options via meta box.
|
10 |
+
*
|
11 |
+
*/
|
12 |
+
class WC_Square_Admin_Product_Meta_Box {
|
13 |
+
/**
|
14 |
+
* Constructor
|
15 |
+
*
|
16 |
+
* @version 1.0.9
|
17 |
+
* @since 1.0.9
|
18 |
+
*/
|
19 |
+
public function __construct() {
|
20 |
+
// add a sync field to the product general tab
|
21 |
+
add_action( 'woocommerce_product_options_general_product_data', array( $this, 'add_product_sync_checkbox_general' ) );
|
22 |
+
|
23 |
+
// save sync field for the product general tab
|
24 |
+
add_action( 'woocommerce_process_product_meta_simple', array( $this, 'save_product_sync_checkbox_general' ) );
|
25 |
+
add_action( 'woocommerce_process_product_meta_booking', array( $this, 'save_product_sync_checkbox_general' ) );
|
26 |
+
|
27 |
+
// save sync field for variable product general tab
|
28 |
+
add_action( 'woocommerce_process_product_meta_variable', array( $this, 'save_product_sync_checkbox_general' ) );
|
29 |
+
|
30 |
+
// add sync to product bulk edit menu
|
31 |
+
add_action( 'woocommerce_product_bulk_edit_end', array( $this, 'add_product_bulk_edit_sync' ) );
|
32 |
+
|
33 |
+
// save sync to product bulk edit
|
34 |
+
add_action( 'woocommerce_product_bulk_edit_save', array( $this, 'save_product_bulk_edit_sync' ) );
|
35 |
+
}
|
36 |
+
|
37 |
+
/**
|
38 |
+
* Add a sync field to the product general tab
|
39 |
+
*
|
40 |
+
* @access public
|
41 |
+
* @since 1.0.9
|
42 |
+
* @version 1.0.9
|
43 |
+
* @return bool
|
44 |
+
*/
|
45 |
+
public function add_product_sync_checkbox_general() {
|
46 |
+
global $post;
|
47 |
+
|
48 |
+
$sync = get_post_meta( $post->ID, '_wcsquare_disable_sync', true );
|
49 |
+
|
50 |
+
// set default to no if nothing is set
|
51 |
+
if ( empty( $sync ) ) {
|
52 |
+
$sync = 'no';
|
53 |
+
}
|
54 |
+
|
55 |
+
$output = '';
|
56 |
+
|
57 |
+
$output .= '<div class="options_group show_if_simple show_if_variable show_if_booking">' . PHP_EOL;
|
58 |
+
|
59 |
+
$output .= '<p class="form-field wcsquare_product_default_sync_field"><label for="wcsquare_product_default_sync">' . wp_kses_post( __( '(Square) Disable Sync', 'woocommerce-square' ) ) . '</label><input type="checkbox" name="_wcsquare_disable_sync" id="wcsquare_product_default_sync" value="yes" ' . checked( 'yes', $sync, false ) . '/>' . PHP_EOL;
|
60 |
+
|
61 |
+
$output .= '<span class="description">' . wp_kses_post( __( 'Check box to disable this product from syncing.', 'woocommerce-square' ) ) . '</span>' . PHP_EOL;
|
62 |
+
|
63 |
+
$output .= '</p>' . PHP_EOL;
|
64 |
+
|
65 |
+
$output .= '</div>';
|
66 |
+
|
67 |
+
echo $output;
|
68 |
+
|
69 |
+
return true;
|
70 |
+
}
|
71 |
+
|
72 |
+
/**
|
73 |
+
* Save the sync field for the product general tab
|
74 |
+
*
|
75 |
+
* @access public
|
76 |
+
* @since 1.0.9
|
77 |
+
* @version 1.0.9
|
78 |
+
* @param int $post_id
|
79 |
+
* @return bool
|
80 |
+
*/
|
81 |
+
public function save_product_sync_checkbox_general( $post_id ) {
|
82 |
+
if ( empty( $post_id ) ) {
|
83 |
+
return;
|
84 |
+
}
|
85 |
+
|
86 |
+
if ( ! empty( $_POST['_wcsquare_disable_sync'] ) ) {
|
87 |
+
update_post_meta( $post_id, '_wcsquare_disable_sync', 'yes' );
|
88 |
+
|
89 |
+
} else {
|
90 |
+
|
91 |
+
update_post_meta( $post_id, '_wcsquare_disable_sync', 'no' );
|
92 |
+
}
|
93 |
+
|
94 |
+
return true;
|
95 |
+
}
|
96 |
+
|
97 |
+
/**
|
98 |
+
* Add sync setting to product bulk edit menu
|
99 |
+
*
|
100 |
+
* @access public
|
101 |
+
* @since 1.0.9
|
102 |
+
* @version 1.0.9
|
103 |
+
* @return bool
|
104 |
+
*/
|
105 |
+
public function add_product_bulk_edit_sync() {
|
106 |
+
?>
|
107 |
+
<label>
|
108 |
+
<span class="title"><?php esc_html_e( 'Disable Sync', 'woocommerce-square' ); ?></span>
|
109 |
+
<span class="input-text-wrap">
|
110 |
+
<select class="square-sync-product" name="_wcsquare_disable_sync">
|
111 |
+
<?php
|
112 |
+
$options = array(
|
113 |
+
'' => __( '— No Change —', 'woocommerce-square' ),
|
114 |
+
'yes' => __( 'Yes', 'woocommerce-square' ),
|
115 |
+
'no' => __( 'No', 'woocommerce-square' )
|
116 |
+
);
|
117 |
+
|
118 |
+
foreach ( $options as $key => $value ) {
|
119 |
+
echo '<option value="' . esc_attr( $key ) . '">' . esc_html( $value ) . '</option>';
|
120 |
+
}
|
121 |
+
?>
|
122 |
+
</select>
|
123 |
+
</span>
|
124 |
+
</label>
|
125 |
+
<?php
|
126 |
+
}
|
127 |
+
|
128 |
+
/**
|
129 |
+
* Save sync setting to product bulk edit
|
130 |
+
*
|
131 |
+
* @access public
|
132 |
+
* @since 1.0.9
|
133 |
+
* @version 1.0.9
|
134 |
+
* @param object $product
|
135 |
+
* @return bool
|
136 |
+
*/
|
137 |
+
public function save_product_bulk_edit_sync( $product ) {
|
138 |
+
if ( empty( $product ) ) {
|
139 |
+
return;
|
140 |
+
}
|
141 |
+
|
142 |
+
if ( ! empty( $_GET['_wcsquare_disable_sync'] ) && 'yes' === $_GET['_wcsquare_disable_sync'] ) {
|
143 |
+
update_post_meta( version_compare( WC_VERSION, '3.0.0', '<' ) ? $product->id : $product->get_id(), '_wcsquare_disable_sync', 'yes' );
|
144 |
+
|
145 |
+
} else {
|
146 |
+
update_post_meta( version_compare( WC_VERSION, '3.0.0', '<' ) ? $product->id : $product->get_id(), '_wcsquare_disable_sync', 'no' );
|
147 |
+
}
|
148 |
+
|
149 |
+
return true;
|
150 |
+
}
|
151 |
+
|
152 |
+
}
|
153 |
+
|
154 |
+
new WC_Square_Admin_Product_Meta_Box();
|
155 |
+
|
includes/admin/class-wc-square-bulk-sync-handler.php
ADDED
@@ -0,0 +1,444 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
if ( ! defined( 'ABSPATH' ) ) {
|
3 |
+
exit;
|
4 |
+
}
|
5 |
+
|
6 |
+
/**
|
7 |
+
* Class WC_Square_Bulk_Sync_Handler
|
8 |
+
*
|
9 |
+
* Facilitates Bulk syncing to/from Square/WC. Handles AJAX initiation of
|
10 |
+
* sync, progress updates, actual sync method calls, and sync completion emails.
|
11 |
+
*/
|
12 |
+
class WC_Square_Bulk_Sync_Handler {
|
13 |
+
|
14 |
+
public $connect;
|
15 |
+
public $wc_to_square;
|
16 |
+
public $square_to_wc;
|
17 |
+
|
18 |
+
public function __construct( WC_Square_Connect $connect, WC_Square_Sync_To_Square $to_square, WC_Square_Sync_From_Square $from_square ) {
|
19 |
+
|
20 |
+
$this->connect = $connect;
|
21 |
+
$this->wc_to_square = $to_square;
|
22 |
+
$this->square_to_wc = $from_square;
|
23 |
+
|
24 |
+
add_action( 'wp_ajax_square_to_wc', array( $this, 'square_to_wc_ajax' ) );
|
25 |
+
add_action( 'wp_ajax_wc_to_square', array( $this, 'wc_to_square_ajax' ) );
|
26 |
+
}
|
27 |
+
|
28 |
+
/**
|
29 |
+
* Process Square to WC ajax
|
30 |
+
*
|
31 |
+
* @since 1.0.0
|
32 |
+
* @version 1.0.14
|
33 |
+
* @return bool
|
34 |
+
*/
|
35 |
+
public function square_to_wc_ajax() {
|
36 |
+
check_ajax_referer( 'square-sync', 'security' );
|
37 |
+
|
38 |
+
/**
|
39 |
+
* Fires if a valid bulk Square to WC sync is being processed.
|
40 |
+
*
|
41 |
+
* @since 1.0.0
|
42 |
+
*/
|
43 |
+
do_action( 'woocommerce_square_bulk_syncing_square_to_wc' );
|
44 |
+
|
45 |
+
$settings = get_option( 'woocommerce_squareconnect_settings' );
|
46 |
+
$emails = ! empty( $settings['sync_email'] ) ? explode( ',', str_replace( ' ', '', $settings['sync_email'] ) ) : '';
|
47 |
+
|
48 |
+
$sync_products = ( 'yes' === $settings['sync_products'] );
|
49 |
+
$sync_categories = ( 'yes' === $settings['sync_categories'] );
|
50 |
+
$sync_inventory = ( 'yes' === $settings['sync_inventory'] );
|
51 |
+
$sync_images = ( 'yes' === $settings['sync_images'] );
|
52 |
+
$cache_age = apply_filters( 'woocommerce_square_syncing_square_ids_cache', DAY_IN_SECONDS );
|
53 |
+
$message = '';
|
54 |
+
|
55 |
+
if ( ! $sync_products ) {
|
56 |
+
wp_send_json( array( 'process' => 'done', 'percentage' => 100, 'type' => 'square-to-wc', 'message' => __( 'Product Sync is disabled. Sync aborted.', 'woocommerce-square' ) ) );
|
57 |
+
}
|
58 |
+
|
59 |
+
// we need to check for cURL
|
60 |
+
if ( ! function_exists( 'curl_init' ) ) {
|
61 |
+
wp_send_json( array( 'process' => 'done', 'percentage' => 100, 'type' => 'square-to-wc', 'message' => __( 'cURL is not available. Sync aborted. Please contact your host to install cURL.', 'woocommerce-square' ) ) );
|
62 |
+
}
|
63 |
+
|
64 |
+
// if a WC to Square process still needs to be completed reset the caches
|
65 |
+
// as the two processes ( WC -> Square and Square -> WC ) use the same cache
|
66 |
+
if ( 'wc_to_square' === get_transient( 'sq_wc_sync_current_process' ) ) {
|
67 |
+
|
68 |
+
$this->connect->delete_all_caches();
|
69 |
+
|
70 |
+
}
|
71 |
+
|
72 |
+
// set Square->WC as the current active process
|
73 |
+
set_transient( 'sq_wc_sync_current_process', 'square_to_wc', $cache_age );
|
74 |
+
|
75 |
+
// index for the current item in the process
|
76 |
+
$process = $this->get_process_index();
|
77 |
+
|
78 |
+
// only sync categories on the first pass
|
79 |
+
if ( ( 0 === $process ) && $sync_categories ) {
|
80 |
+
|
81 |
+
$this->square_to_wc->sync_categories();
|
82 |
+
|
83 |
+
}
|
84 |
+
|
85 |
+
if ( ( 0 === $process ) && $sync_inventory ) {
|
86 |
+
// ensure this manual update gets the freshest item counts
|
87 |
+
delete_transient( 'wc_square_inventory' );
|
88 |
+
|
89 |
+
$this->connect->get_square_inventory();
|
90 |
+
|
91 |
+
}
|
92 |
+
|
93 |
+
// products
|
94 |
+
// get all product ids
|
95 |
+
$square_item_ids = $this->get_processing_ids();
|
96 |
+
|
97 |
+
// run this only on first process
|
98 |
+
if ( $process === 0 ) {
|
99 |
+
$square_items = $this->connect->get_square_products();
|
100 |
+
$square_item_ids = ! empty( $square_items ) ? array_unique( wp_list_pluck( $square_items, 'id' ) ) : array();
|
101 |
+
|
102 |
+
// cache it
|
103 |
+
set_transient( 'wc_square_processing_total_count', count( $square_item_ids ), $cache_age );
|
104 |
+
set_transient( 'wc_square_processing_ids', $square_item_ids, $cache_age );
|
105 |
+
|
106 |
+
}
|
107 |
+
|
108 |
+
if ( $square_item_ids && $sync_products ) {
|
109 |
+
|
110 |
+
$square_item_id = array_pop( $square_item_ids );
|
111 |
+
$square_item = $this->connect->get_square_product( $square_item_id );
|
112 |
+
|
113 |
+
if ( $square_item ) {
|
114 |
+
|
115 |
+
$this->square_to_wc->sync_product( $square_item, $sync_categories, $sync_inventory, $sync_images );
|
116 |
+
|
117 |
+
} else {
|
118 |
+
|
119 |
+
WC_Square_Sync_Logger::log( sprintf( '[Square -> WC] Bulk Sync: Error retrieving Square Item with ID %s.', $square_item_id ) );
|
120 |
+
|
121 |
+
}
|
122 |
+
|
123 |
+
$process++;
|
124 |
+
|
125 |
+
$percentage = $this->get_process_percentage( $process );
|
126 |
+
|
127 |
+
$this->delete_processed_id( $square_item_id );
|
128 |
+
|
129 |
+
$remaining_ids = $this->get_processing_ids();
|
130 |
+
|
131 |
+
// run this only on last process
|
132 |
+
if ( empty( $remaining_ids ) ) {
|
133 |
+
$process = 'done';
|
134 |
+
|
135 |
+
$percentage = 100;
|
136 |
+
|
137 |
+
// send sync email
|
138 |
+
$this->send_sync_email( $emails, __( 'Sync Completed', 'woocommerce-square' ) );
|
139 |
+
|
140 |
+
// reset the processed ids
|
141 |
+
$this->connect->delete_all_caches();
|
142 |
+
|
143 |
+
$message = __( 'Sync completed', 'woocommerce-square' );
|
144 |
+
}
|
145 |
+
|
146 |
+
wp_send_json( array( 'process' => $process, 'percentage' => $percentage, 'type' => 'square-to-wc', 'message' => $message ) );
|
147 |
+
}
|
148 |
+
}
|
149 |
+
|
150 |
+
/**
|
151 |
+
* Process WC to Square ajax
|
152 |
+
*
|
153 |
+
* @since 1.0.0
|
154 |
+
* @version 1.0.0
|
155 |
+
* @return bool
|
156 |
+
*/
|
157 |
+
public function wc_to_square_ajax() {
|
158 |
+
check_ajax_referer( 'square-sync', 'security' );
|
159 |
+
|
160 |
+
$settings = get_option( 'woocommerce_squareconnect_settings' );
|
161 |
+
$emails = ! empty( $settings['sync_email'] ) ? $settings['sync_email'] : '';
|
162 |
+
|
163 |
+
$sync_products = ( 'yes' === $settings['sync_products'] );
|
164 |
+
$sync_categories = ( 'yes' === $settings['sync_categories'] );
|
165 |
+
$sync_inventory = ( 'yes' === $settings['sync_inventory'] );
|
166 |
+
$sync_images = ( 'yes' === $settings['sync_images'] );
|
167 |
+
$cache_age = apply_filters( 'woocommerce_square_syncing_wc_product_ids_cache', DAY_IN_SECONDS );
|
168 |
+
$message = '';
|
169 |
+
|
170 |
+
if ( ! $sync_products ) {
|
171 |
+
wp_send_json( array( 'process' => 'done', 'percentage' => 100, 'type' => 'wc-to-square', 'message' => __( 'Product Sync is disabled. Sync aborted.', 'woocommerce-square' ) ) );
|
172 |
+
}
|
173 |
+
|
174 |
+
// we need to check for cURL
|
175 |
+
if ( ! function_exists( 'curl_init' ) ) {
|
176 |
+
wp_send_json( array( 'process' => 'done', 'percentage' => 100, 'type' => 'wc-to-square', 'message' => __( 'cURL is not available. Sync aborted. Please contact your host to install cURL.', 'woocommerce-square' ) ) );
|
177 |
+
}
|
178 |
+
|
179 |
+
// if a Square to WC process still needs to be completed reset the caches
|
180 |
+
// as the two processes ( WC -> Square and Square -> WC ) use the same cache
|
181 |
+
if ( 'square_to_wc' === get_transient( 'sq_wc_sync_current_process' ) ) {
|
182 |
+
|
183 |
+
$this->connect->delete_all_caches();
|
184 |
+
|
185 |
+
}
|
186 |
+
|
187 |
+
// set WC->Square as the current active process
|
188 |
+
set_transient( 'sq_wc_sync_current_process', 'wc_to_square', $cache_age );
|
189 |
+
|
190 |
+
$process = $this->get_process_index();
|
191 |
+
|
192 |
+
// only sync categories on the first pass
|
193 |
+
if ( ( 0 === $process ) && $sync_categories ) {
|
194 |
+
|
195 |
+
$this->wc_to_square->sync_categories();
|
196 |
+
|
197 |
+
}
|
198 |
+
|
199 |
+
// products
|
200 |
+
// get all product ids
|
201 |
+
$wc_product_ids = $this->get_processing_ids();
|
202 |
+
|
203 |
+
// run the following only on first process and cache it
|
204 |
+
if ( ( 0 === $process ) && $sync_products ) {
|
205 |
+
|
206 |
+
$wc_product_ids = $this->get_all_product_ids();
|
207 |
+
|
208 |
+
// cache it
|
209 |
+
set_transient( 'wc_square_processing_total_count', count( $wc_product_ids ), $cache_age );
|
210 |
+
set_transient( 'wc_square_processing_ids', $wc_product_ids, $cache_age );
|
211 |
+
}
|
212 |
+
|
213 |
+
if ( $sync_products && ! empty( $wc_product_ids ) ) {
|
214 |
+
|
215 |
+
$wc_product_id = array_pop( $wc_product_ids );
|
216 |
+
|
217 |
+
$wc_product = wc_get_product( $wc_product_id );
|
218 |
+
|
219 |
+
if ( is_object( $wc_product ) && is_a( $wc_product, 'WC_Product' ) ) {
|
220 |
+
$this->wc_to_square->sync_product( $wc_product, $sync_categories, $sync_inventory, $sync_images );
|
221 |
+
}
|
222 |
+
|
223 |
+
$process++;
|
224 |
+
|
225 |
+
$percentage = $this->get_process_percentage( $process );
|
226 |
+
|
227 |
+
if ( is_object( $wc_product ) ) {
|
228 |
+
$this->delete_processed_id( version_compare( WC_VERSION, '3.0.0', '<' ) ? $wc_product->id : $wc_product->get_id() );
|
229 |
+
} else {
|
230 |
+
$this->delete_processed_id( $wc_product_id );
|
231 |
+
}
|
232 |
+
|
233 |
+
$remaining_ids = $this->get_processing_ids();
|
234 |
+
|
235 |
+
// run this only on last process
|
236 |
+
if ( empty( $remaining_ids ) ) {
|
237 |
+
$process = 'done';
|
238 |
+
|
239 |
+
$percentage = 100;
|
240 |
+
|
241 |
+
// send sync email
|
242 |
+
$this->send_sync_email( $emails, __( 'Sync Completed', 'woocommerce-square' ) );
|
243 |
+
|
244 |
+
// reset the processed ids
|
245 |
+
$this->connect->delete_all_caches();
|
246 |
+
|
247 |
+
$message = __( 'Sync completed', 'woocommerce-square' );
|
248 |
+
}
|
249 |
+
|
250 |
+
wp_send_json( array( 'process' => $process, 'percentage' => $percentage, 'type' => 'wc-to-square', 'message' => $message ) );
|
251 |
+
|
252 |
+
}
|
253 |
+
|
254 |
+
wp_send_json( array( 'process' => 'done', 'percentage' => 100, 'type' => 'wc-to-square', 'message' => __( 'No Products to Sync.', 'woocommerce-square' ) ) );
|
255 |
+
}
|
256 |
+
|
257 |
+
/**
|
258 |
+
* Figure out at which product index we are
|
259 |
+
* at using the total count and the remaining item.
|
260 |
+
* The index stats at 0 .
|
261 |
+
*
|
262 |
+
* @since 1.0.0
|
263 |
+
*
|
264 |
+
* @return int $process_index
|
265 |
+
*/
|
266 |
+
public function get_process_index() {
|
267 |
+
|
268 |
+
$total_items = (int) get_transient( 'wc_square_processing_total_count' );
|
269 |
+
$remaining_ids_count = count( $this->get_processing_ids() );
|
270 |
+
$process_index = $total_items - $remaining_ids_count;
|
271 |
+
|
272 |
+
if ( empty( $process_index ) ) {
|
273 |
+
$process_index = 0;
|
274 |
+
}
|
275 |
+
|
276 |
+
return $process_index;
|
277 |
+
}
|
278 |
+
|
279 |
+
/**
|
280 |
+
* Gets all product ids that are sync-eligible (they have SKUs).
|
281 |
+
*
|
282 |
+
* This looks for products as well as variations, if a variant has a SKU, the
|
283 |
+
* parent product will be included in the result set.
|
284 |
+
*
|
285 |
+
* @access public
|
286 |
+
* @since 1.0.0
|
287 |
+
* @version 1.0.14
|
288 |
+
* @return array $ids
|
289 |
+
*/
|
290 |
+
public function get_all_product_ids() {
|
291 |
+
|
292 |
+
$args = apply_filters( 'woocommerce_square_get_all_product_ids_args', array(
|
293 |
+
'posts_per_page' => -1,
|
294 |
+
'post_type' => array( 'product', 'product_variation' ),
|
295 |
+
'post_status' => 'publish',
|
296 |
+
'fields' => 'id=>parent',
|
297 |
+
'meta_query' => array(
|
298 |
+
array(
|
299 |
+
'key' => '_sku',
|
300 |
+
'compare' => '!=',
|
301 |
+
'value' => ''
|
302 |
+
)
|
303 |
+
)
|
304 |
+
) );
|
305 |
+
|
306 |
+
$products_with_skus = get_posts( $args );
|
307 |
+
$product_ids = array();
|
308 |
+
|
309 |
+
/*
|
310 |
+
* Our result set contains products and variations. We're only concerned with
|
311 |
+
* returning top-level products, so favor the parent ID if present (denotes a variation)
|
312 |
+
*/
|
313 |
+
foreach ( $products_with_skus as $product_id => $parent_id ) {
|
314 |
+
$post_id = 0 == $parent_id ? $product_id : $parent_id;
|
315 |
+
|
316 |
+
// check if product sync is disable, if so skip
|
317 |
+
if ( WC_Square_Utils::skip_product_sync( $post_id ) ) {
|
318 |
+
WC_Square_Sync_Logger::log( sprintf( '[WC -> Square] Syncing disabled for this WC Product %d', $post_id ) );
|
319 |
+
|
320 |
+
continue;
|
321 |
+
}
|
322 |
+
|
323 |
+
// when it is a variation, we need to check its parent for publish
|
324 |
+
// post status.
|
325 |
+
if ( 0 == $parent_id ) {
|
326 |
+
$product_ids[] = $product_id;
|
327 |
+
} else {
|
328 |
+
$post = get_post( $parent_id );
|
329 |
+
|
330 |
+
if ( is_object( $post ) && 'publish' === $post->post_status ) {
|
331 |
+
$product_ids[] = $parent_id;
|
332 |
+
}
|
333 |
+
}
|
334 |
+
}
|
335 |
+
|
336 |
+
/*
|
337 |
+
* Products can have multiple variants, so we might end up with
|
338 |
+
* duplicate parent product IDs in our list.
|
339 |
+
*/
|
340 |
+
$unique_product_ids = array_unique( $product_ids );
|
341 |
+
|
342 |
+
return $unique_product_ids;
|
343 |
+
}
|
344 |
+
|
345 |
+
/**
|
346 |
+
* Deletes the product ID from the list so we can continue if sync is terminated early.
|
347 |
+
* This function can take both the WC product id or Square product ID
|
348 |
+
*
|
349 |
+
* @access public
|
350 |
+
* @since 1.0.0
|
351 |
+
* @version 1.0.0
|
352 |
+
* @param string $product_id
|
353 |
+
* @return bool
|
354 |
+
*/
|
355 |
+
public function delete_processed_id( $product_id = null ) {
|
356 |
+
|
357 |
+
if ( null === $product_id ) {
|
358 |
+
return false;
|
359 |
+
}
|
360 |
+
|
361 |
+
$ids = $this->get_processing_ids();
|
362 |
+
|
363 |
+
if ( ( $key = array_search( $product_id, $ids ) ) !== false ) {
|
364 |
+
unset( $ids[ $key ] );
|
365 |
+
}
|
366 |
+
|
367 |
+
set_transient( 'wc_square_processing_ids', $ids, apply_filters( 'woocommerce_square_sync_processing_ids_cache', DAY_IN_SECONDS ) );
|
368 |
+
|
369 |
+
return true;
|
370 |
+
}
|
371 |
+
|
372 |
+
/**
|
373 |
+
* Gets the already processed product IDs ( both Square and WC )
|
374 |
+
*
|
375 |
+
* @access public
|
376 |
+
* @since 1.0.0
|
377 |
+
* @version 1.0.0
|
378 |
+
* @return array $ids
|
379 |
+
*/
|
380 |
+
public function get_processing_ids() {
|
381 |
+
if ( $ids = get_transient( 'wc_square_processing_ids' ) ) {
|
382 |
+
return $ids;
|
383 |
+
}
|
384 |
+
|
385 |
+
return array();
|
386 |
+
}
|
387 |
+
|
388 |
+
/**
|
389 |
+
* Get process percentage
|
390 |
+
*
|
391 |
+
* @access public
|
392 |
+
* @since 1.0.0
|
393 |
+
* @version 1.0.0
|
394 |
+
* @param int $process the current process step
|
395 |
+
* @return string $percentage
|
396 |
+
*/
|
397 |
+
public function get_process_percentage( $process ) {
|
398 |
+
|
399 |
+
$total_count = (int) get_transient( 'wc_square_processing_total_count' );
|
400 |
+
$percentage = 0;
|
401 |
+
|
402 |
+
if ( $total_count > 0 ) {
|
403 |
+
$percentage = ( $process / $total_count );
|
404 |
+
}
|
405 |
+
|
406 |
+
if ( 0 === $process ) {
|
407 |
+
// 10% is added to offset the category process
|
408 |
+
$percentage = $percentage + 0.10;
|
409 |
+
}
|
410 |
+
|
411 |
+
return round( $percentage, 2 ) * 100;
|
412 |
+
}
|
413 |
+
|
414 |
+
/**
|
415 |
+
* Sends the sync notification email when operation ends
|
416 |
+
*
|
417 |
+
* @access public
|
418 |
+
* @since 1.0.0
|
419 |
+
* @version 1.0.14
|
420 |
+
* @param string $emails
|
421 |
+
* @param string $message
|
422 |
+
* @return bool
|
423 |
+
*/
|
424 |
+
public function send_sync_email( $emails, $message = '' ) {
|
425 |
+
// default to admin's email
|
426 |
+
if ( empty( $emails ) ) {
|
427 |
+
$emails = array();
|
428 |
+
$emails[] = get_option( 'admin_email' );
|
429 |
+
}
|
430 |
+
|
431 |
+
$subject = sprintf( __( '%s - WooCommerce Square Sync Operation', 'woocommerce-square' ), wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES ) );
|
432 |
+
|
433 |
+
$headers = array();
|
434 |
+
|
435 |
+
foreach ( $emails as $email ) {
|
436 |
+
$headers[] = sprintf( __( '%s', 'woocommerce-square' ) . ' ' . wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES ) . ' <' . $email . '>', 'From:' ) . PHP_EOL;
|
437 |
+
|
438 |
+
wp_mail( $email, $subject, $message, $headers );
|
439 |
+
}
|
440 |
+
|
441 |
+
return true;
|
442 |
+
}
|
443 |
+
|
444 |
+
}
|
includes/class-wc-square-client.php
ADDED
@@ -0,0 +1,379 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
if ( ! defined( 'ABSPATH' ) ) {
|
3 |
+
exit; // Exit if accessed directly
|
4 |
+
}
|
5 |
+
|
6 |
+
/**
|
7 |
+
* Class WC_Square_Client
|
8 |
+
*
|
9 |
+
* Makes actual HTTP requests to the Square API.
|
10 |
+
* Handles:
|
11 |
+
* - Authentication
|
12 |
+
* - Endpoint selection (API version, Merchant ID in path)
|
13 |
+
* - Request retries
|
14 |
+
* - Paginated results
|
15 |
+
* - Content-Type negotiation (JSON)
|
16 |
+
*/
|
17 |
+
class WC_Square_Client {
|
18 |
+
|
19 |
+
/**
|
20 |
+
* @var
|
21 |
+
*/
|
22 |
+
protected $access_token;
|
23 |
+
|
24 |
+
/**
|
25 |
+
* @var
|
26 |
+
*/
|
27 |
+
protected $merchant_id;
|
28 |
+
|
29 |
+
/**
|
30 |
+
* @var string
|
31 |
+
*/
|
32 |
+
protected $api_version = 'v1';
|
33 |
+
|
34 |
+
/**
|
35 |
+
* @return mixed
|
36 |
+
*/
|
37 |
+
public function get_access_token() {
|
38 |
+
|
39 |
+
return $this->access_token;
|
40 |
+
|
41 |
+
}
|
42 |
+
|
43 |
+
/**
|
44 |
+
* @param $token
|
45 |
+
*/
|
46 |
+
public function set_access_token( $token ) {
|
47 |
+
|
48 |
+
$this->access_token = $token;
|
49 |
+
|
50 |
+
}
|
51 |
+
|
52 |
+
/**
|
53 |
+
* @return mixed
|
54 |
+
*/
|
55 |
+
public function get_merchant_id() {
|
56 |
+
|
57 |
+
return $this->merchant_id;
|
58 |
+
|
59 |
+
}
|
60 |
+
|
61 |
+
/**
|
62 |
+
* @param $merchant_id
|
63 |
+
*/
|
64 |
+
public function set_merchant_id( $merchant_id ) {
|
65 |
+
|
66 |
+
$this->merchant_id = $merchant_id;
|
67 |
+
|
68 |
+
}
|
69 |
+
|
70 |
+
/**
|
71 |
+
* @return string
|
72 |
+
*/
|
73 |
+
public function get_api_version() {
|
74 |
+
|
75 |
+
return $this->api_version;
|
76 |
+
|
77 |
+
}
|
78 |
+
|
79 |
+
/**
|
80 |
+
* @param $version
|
81 |
+
*/
|
82 |
+
public function set_api_version( $version ) {
|
83 |
+
|
84 |
+
$this->api_version = $version;
|
85 |
+
|
86 |
+
}
|
87 |
+
|
88 |
+
/**
|
89 |
+
* @return int|mixed|void
|
90 |
+
*/
|
91 |
+
public function get_api_url_base() {
|
92 |
+
if ( WC_SQUARE_ENABLE_STAGING ) {
|
93 |
+
return apply_filters( 'woocommerce_square_api_url', 'https://connect.squareupstaging.com/' );
|
94 |
+
}
|
95 |
+
|
96 |
+
return apply_filters( 'woocommerce_square_api_url', 'https://connect.squareup.com/' );
|
97 |
+
}
|
98 |
+
|
99 |
+
/**
|
100 |
+
* @return string
|
101 |
+
*/
|
102 |
+
public function get_api_url() {
|
103 |
+
|
104 |
+
$url = trailingslashit( $this->get_api_url_base() );
|
105 |
+
$url .= trailingslashit( $this->get_api_version() );
|
106 |
+
|
107 |
+
return $url;
|
108 |
+
|
109 |
+
}
|
110 |
+
|
111 |
+
/**
|
112 |
+
* Initializes the header arguments.
|
113 |
+
*
|
114 |
+
* @since 1.0.0
|
115 |
+
* @version 1.0.14
|
116 |
+
* @return int|mixed|void
|
117 |
+
*/
|
118 |
+
public function get_request_args() {
|
119 |
+
|
120 |
+
$args = array(
|
121 |
+
'headers' => array(
|
122 |
+
'Authorization' => 'Bearer ' . sanitize_text_field( $this->get_access_token() ),
|
123 |
+
'Accept' => 'application/json',
|
124 |
+
'Content-Type' => 'application/json',
|
125 |
+
),
|
126 |
+
'user-agent' => 'WooCommerceSquare/' . WC_SQUARE_VERSION . '; ' . get_bloginfo( 'url' ),
|
127 |
+
'timeout' => 0,
|
128 |
+
'httpversion' => '1.1',
|
129 |
+
);
|
130 |
+
|
131 |
+
return apply_filters( 'woocommerce_square_request_args', $args );
|
132 |
+
}
|
133 |
+
|
134 |
+
/**
|
135 |
+
* @param $path
|
136 |
+
*
|
137 |
+
* @return string
|
138 |
+
*/
|
139 |
+
protected function get_request_url( $path ) {
|
140 |
+
|
141 |
+
$api_url_base = trailingslashit( $this->get_api_url() );
|
142 |
+
$merchant_id = '';
|
143 |
+
|
144 |
+
// Add merchant ID to the request URL if we aren't hitting /me/*
|
145 |
+
if ( strpos( trim( $path, '/' ), 'me' ) !== 0 ) {
|
146 |
+
|
147 |
+
$merchant_id = trailingslashit( $this->get_merchant_id() );
|
148 |
+
|
149 |
+
}
|
150 |
+
|
151 |
+
$request_path = ltrim( $path, '/' );
|
152 |
+
$request_url = trailingslashit( $api_url_base . $merchant_id . $request_path );
|
153 |
+
|
154 |
+
return $request_url;
|
155 |
+
|
156 |
+
}
|
157 |
+
|
158 |
+
/**
|
159 |
+
* Gets the number of retries per request
|
160 |
+
*
|
161 |
+
* @access public
|
162 |
+
* @since 1.0.0
|
163 |
+
* @version 1.0.0
|
164 |
+
* @param int $count
|
165 |
+
* @return int
|
166 |
+
*/
|
167 |
+
public function request_retries( $count = 5 ) {
|
168 |
+
|
169 |
+
return apply_filters( 'woocommerce_square_request_retries', $count );
|
170 |
+
|
171 |
+
}
|
172 |
+
|
173 |
+
/**
|
174 |
+
* Wrapper around http_request() that handles pagination for List endpoints.
|
175 |
+
*
|
176 |
+
* @since 1.0.25 Switch to use WP native remote requests.
|
177 |
+
* @param string $debug_label Description of the request, for logging.
|
178 |
+
* @param string $path API endpoint path to hit. E.g. /items/
|
179 |
+
* @param string $method HTTP method to use. Defaults to 'GET'.
|
180 |
+
* @param mixed $body Optional. Request payload - will be JSON encoded if non-scalar.
|
181 |
+
*
|
182 |
+
* @return bool|object|WP_Error
|
183 |
+
*/
|
184 |
+
public function request( $debug_label, $path, $method = 'GET', $body = null ) {
|
185 |
+
// we need to check for cURL
|
186 |
+
if ( ! function_exists( 'curl_init' ) ) {
|
187 |
+
WC_Square_Sync_Logger::log( 'cURL is not available. Sync aborted. Please contact your host to install cURL.' );
|
188 |
+
|
189 |
+
return false;
|
190 |
+
}
|
191 |
+
|
192 |
+
// The access token is required for all requests
|
193 |
+
$access_token = $this->get_access_token();
|
194 |
+
|
195 |
+
if ( empty( $access_token ) ) {
|
196 |
+
|
197 |
+
return false;
|
198 |
+
|
199 |
+
}
|
200 |
+
|
201 |
+
$request_url = $this->get_request_url( $path );
|
202 |
+
$return_data = array();
|
203 |
+
|
204 |
+
while ( true ) {
|
205 |
+
|
206 |
+
$response = $this->http_request( $debug_label, $request_url, $method, $body );
|
207 |
+
|
208 |
+
if ( ! $response ) {
|
209 |
+
|
210 |
+
return $response;
|
211 |
+
|
212 |
+
}
|
213 |
+
|
214 |
+
$response_data = json_decode( wp_remote_retrieve_body( $response ) );
|
215 |
+
|
216 |
+
// A paged list result will be an array, so let's merge if we're already returning an array
|
217 |
+
if ( ( 'GET' === $method ) && is_array( $return_data ) && is_array( $response_data ) ) {
|
218 |
+
|
219 |
+
$return_data = array_merge( $return_data, $response_data );
|
220 |
+
|
221 |
+
} else {
|
222 |
+
|
223 |
+
$return_data = $response_data;
|
224 |
+
|
225 |
+
}
|
226 |
+
|
227 |
+
$link_header = wp_remote_retrieve_header( $response, 'Link' );
|
228 |
+
|
229 |
+
// Look for the next page, if specified
|
230 |
+
if ( ! preg_match( '/Link:( |)<(.+)>;rel=("|\')next("|\')/i', $link_header ) ) {
|
231 |
+
return $return_data;
|
232 |
+
}
|
233 |
+
|
234 |
+
$rel_link_matches = array();
|
235 |
+
|
236 |
+
// Set up the next page URL for the following loop
|
237 |
+
if ( ( 'GET' === $method ) && preg_match( '/Link:( |)<(.+)>;rel=("|\')next("|\')/i', $link_header, $rel_link_matches ) ) {
|
238 |
+
|
239 |
+
$request_url = $rel_link_matches[2];
|
240 |
+
$body = null;
|
241 |
+
|
242 |
+
} else {
|
243 |
+
|
244 |
+
return $return_data;
|
245 |
+
|
246 |
+
}
|
247 |
+
}
|
248 |
+
}
|
249 |
+
|
250 |
+
/**
|
251 |
+
* Helper method to make HTTP requests to the Square API, with retries.
|
252 |
+
*
|
253 |
+
* @since 1.0.25 Switch to use WP native remote requests.
|
254 |
+
* @param string $debug_label Description of the request, for logging.
|
255 |
+
* @param string $request_url URL to request.
|
256 |
+
* @param string $method HTTP method to use. Defaults to 'GET'.
|
257 |
+
* @param mixed $body Optional. Request payload - will be JSON encoded if non-scalar.
|
258 |
+
*
|
259 |
+
* @return bool|object|WP_Error
|
260 |
+
*/
|
261 |
+
private function http_request( $debug_label, $request_url, $method = 'GET', $body = null ) {
|
262 |
+
$request_args = $this->get_request_args();
|
263 |
+
|
264 |
+
if ( ! is_null( $body ) ) {
|
265 |
+
if ( ! empty( $request_args['headers']['Content-Type'] ) && ( 'application/json' === $request_args['headers']['Content-Type'] ) ) {
|
266 |
+
$request_args['body'] = json_encode( $body );
|
267 |
+
} else {
|
268 |
+
$request_args['body'] = $body;
|
269 |
+
}
|
270 |
+
}
|
271 |
+
|
272 |
+
$request_args['method'] = $method;
|
273 |
+
|
274 |
+
// Make actual request in a retry loop
|
275 |
+
$try_count = 1;
|
276 |
+
$max_retries = $this->request_retries();
|
277 |
+
|
278 |
+
while ( true ) {
|
279 |
+
$start_time = current_time( 'timestamp' );
|
280 |
+
$response = wp_remote_request( untrailingslashit( $request_url ), $request_args );
|
281 |
+
$end_time = current_time( 'timestamp' );
|
282 |
+
|
283 |
+
WC_Square_Sync_Logger::log( sprintf( '%s', $debug_label ), $start_time, $end_time );
|
284 |
+
|
285 |
+
$decoded_response = json_decode( wp_remote_retrieve_body( $response ) );
|
286 |
+
|
287 |
+
if ( is_object( $decoded_response ) && ! empty( $decoded_response->type ) ) {
|
288 |
+
if ( preg_match( '/bad_request/', $decoded_response->type ) || preg_match( '/not_found/', $decoded_response->type ) ) {
|
289 |
+
WC_Square_Sync_Logger::log( sprintf( '%s - %s', $decoded_response->type, $decoded_response->message ), $start_time, $end_time );
|
290 |
+
|
291 |
+
return false;
|
292 |
+
}
|
293 |
+
}
|
294 |
+
|
295 |
+
// handle expired tokens
|
296 |
+
if ( is_object( $decoded_response ) &&
|
297 |
+
( ! empty( $decoded_response->type ) && 'oauth.expired' === $decoded_response->type ) ||
|
298 |
+
( ! empty( $decoded_response->errors ) && 'ACCESS_TOKEN_EXPIRED' === $decoded_response->errors[0]->code ) ) {
|
299 |
+
|
300 |
+
$oauth_connect_url = 'https://connect.woocommerce.com/renew/square';
|
301 |
+
|
302 |
+
if ( WC_SQUARE_ENABLE_STAGING ) {
|
303 |
+
$oauth_connect_url = 'https://connect.woocommerce.com/renew/squaresandbox';
|
304 |
+
}
|
305 |
+
|
306 |
+
$args = array(
|
307 |
+
'body' => array(
|
308 |
+
'token' => $this->access_token,
|
309 |
+
),
|
310 |
+
'timeout' => 45,
|
311 |
+
);
|
312 |
+
|
313 |
+
$start_time = current_time( 'timestamp' );
|
314 |
+
$oauth_response = wp_remote_post( $oauth_connect_url, $args );
|
315 |
+
$end_time = current_time( 'timestamp' );
|
316 |
+
|
317 |
+
$decoded_oauth_response = json_decode( wp_remote_retrieve_body( $oauth_response ) );
|
318 |
+
|
319 |
+
if ( is_wp_error( $oauth_response ) ) {
|
320 |
+
|
321 |
+
WC_Square_Sync_Logger::log( sprintf( 'Renewing expired token error - %s', $parsed_oauth_response['curl_error'] ), $start_time, $end_time );
|
322 |
+
|
323 |
+
return false;
|
324 |
+
|
325 |
+
} elseif ( is_object( $decoded_oauth_response ) && ! empty( $decoded_oauth_response->error ) ) {
|
326 |
+
|
327 |
+
WC_Square_Sync_Logger::log( sprintf( 'Renewing expired token error - %s', $decoded_oauth_response->type ), $start_time, $end_time );
|
328 |
+
|
329 |
+
return false;
|
330 |
+
|
331 |
+
} elseif ( is_object( $decoded_oauth_response ) && ! empty( $decoded_oauth_response->access_token ) ) {
|
332 |
+
update_option( 'woocommerce_square_merchant_access_token', sanitize_text_field( urldecode( $decoded_oauth_response->access_token ) ) );
|
333 |
+
|
334 |
+
// let's set the token instance again so settings option is refreshed
|
335 |
+
$this->set_access_token( sanitize_text_field( urldecode( $decoded_oauth_response->access_token ) ) );
|
336 |
+
$request_args['headers']['Authorization'] = 'Bearer ' . sanitize_text_field( $this->get_access_token() );
|
337 |
+
|
338 |
+
WC_Square_Sync_Logger::log( sprintf( 'Retrying with new refreshed token' ), $start_time, $end_time );
|
339 |
+
|
340 |
+
// start at the beginning again
|
341 |
+
continue;
|
342 |
+
} else {
|
343 |
+
WC_Square_Sync_Logger::log( sprintf( 'Renewing expired token error - Unknown Error' ), $start_time, $end_time );
|
344 |
+
|
345 |
+
return false;
|
346 |
+
}
|
347 |
+
}
|
348 |
+
|
349 |
+
// handle revoked tokens
|
350 |
+
if ( is_object( $decoded_response ) && ! empty( $decoded_response->type ) && 'oauth.revoked' === $decoded_response->type ) {
|
351 |
+
WC_Square_Sync_Logger::log( sprintf( 'Token is revoked!' ), $start_time, $end_time );
|
352 |
+
|
353 |
+
return false;
|
354 |
+
}
|
355 |
+
|
356 |
+
if ( is_wp_error( $response ) ) {
|
357 |
+
|
358 |
+
WC_Square_Sync_Logger::log( sprintf( '(%s) Try #%d - %s', $debug_label, $try_count, $response->get_error_message() ), $start_time, $end_time );
|
359 |
+
|
360 |
+
} else {
|
361 |
+
|
362 |
+
return $response;
|
363 |
+
|
364 |
+
}
|
365 |
+
|
366 |
+
$try_count++;
|
367 |
+
|
368 |
+
if ( $try_count > $max_retries ) {
|
369 |
+
break;
|
370 |
+
}
|
371 |
+
|
372 |
+
sleep( 1 );
|
373 |
+
|
374 |
+
}
|
375 |
+
|
376 |
+
return false;
|
377 |
+
|
378 |
+
}
|
379 |
+
}
|
includes/class-wc-square-connect.php
ADDED
@@ -0,0 +1,678 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
if ( ! defined( 'ABSPATH' ) ) {
|
3 |
+
exit;
|
4 |
+
}
|
5 |
+
|
6 |
+
/**
|
7 |
+
* Class WC_Square_Connect
|
8 |
+
*
|
9 |
+
* A layer on top of WC_Square_Client, providing convenient wrapper methods
|
10 |
+
* to work with the Square API in terms of WC Products and Square Items.
|
11 |
+
*/
|
12 |
+
class WC_Square_Connect {
|
13 |
+
|
14 |
+
protected $_client;
|
15 |
+
public $wc;
|
16 |
+
public $log;
|
17 |
+
|
18 |
+
const MULTIPLE_LOCATION_ACCOUNT_TYPE = 'BUSINESS';
|
19 |
+
const SINGLE_LOCATION_ACCOUNT_TYPE = 'LOCATION';
|
20 |
+
const ITEM_IMAGE_MULTIPART_BOUNDARY = 'SQUARE-ITEM-IMAGE';
|
21 |
+
const MERCHANT_ACCOUNT_TYPE_CACHE_KEY = 'wc_square_merchant_account_type';
|
22 |
+
const LOCATIONS_CACHE_KEY = 'wc_square_locations';
|
23 |
+
const INVENTORY_CACHE_KEY = 'wc_square_inventory';
|
24 |
+
const ITEM_SKU_MAP_CACHE_KEY = 'square_item_sku_map';
|
25 |
+
|
26 |
+
/**
|
27 |
+
* WC_Square_Connect constructor.
|
28 |
+
*/
|
29 |
+
public function __construct( WC_Square_Client $client ) {
|
30 |
+
|
31 |
+
$this->_client = $client;
|
32 |
+
|
33 |
+
add_action( 'wp_loaded', array( $this, 'init' ) );
|
34 |
+
|
35 |
+
}
|
36 |
+
|
37 |
+
public function init() {
|
38 |
+
|
39 |
+
add_action( 'woocommerce_square_bulk_syncing_square_to_wc', array( $this, 'clear_item_sku_map_cache' ) );
|
40 |
+
|
41 |
+
$this->wc = new WC_Square_WC_Products();
|
42 |
+
|
43 |
+
// add clear transients button in WC system tools
|
44 |
+
add_filter( 'woocommerce_debug_tools', array( $this, 'add_debug_tool' ) );
|
45 |
+
}
|
46 |
+
|
47 |
+
/**
|
48 |
+
* Add debug tool button
|
49 |
+
*
|
50 |
+
* @access public
|
51 |
+
* @since 1.0.5
|
52 |
+
* @version 1.0.17
|
53 |
+
* @return array $tools
|
54 |
+
*/
|
55 |
+
public function add_debug_tool( $tools ) {
|
56 |
+
if ( ! empty( $_GET['action'] ) && 'wcsquare_clear_transients' === $_GET['action'] && version_compare( WC_VERSION, '3.0', '<' ) ) {
|
57 |
+
$this->delete_all_caches();
|
58 |
+
|
59 |
+
echo '<div class="updated"><p>' . esc_html__( 'Square Sync Transients Cleared', 'woocommerce-square' ) . '</p></div>';
|
60 |
+
}
|
61 |
+
|
62 |
+
$tools['wcsquare_clear_transients'] = array(
|
63 |
+
'name' => __( 'Square Sync Transients', 'woocommerce-square' ),
|
64 |
+
'button' => __( 'Clear all transients/cache', 'woocommerce-square' ),
|
65 |
+
'desc' => __( 'This will clear all Square Sync related transients/caches to start fresh. Useful when sync failed halfway through.', 'woocommerce-square' ),
|
66 |
+
);
|
67 |
+
|
68 |
+
if ( version_compare( WC_VERSION, '3.0', '>=' ) ) {
|
69 |
+
$tools['wcsquare_clear_transients']['callback'] = 'WC_Square_Utils::delete_transients';
|
70 |
+
}
|
71 |
+
|
72 |
+
return $tools;
|
73 |
+
}
|
74 |
+
|
75 |
+
/**
|
76 |
+
* Deletes cached data ( both Square and WC )
|
77 |
+
*
|
78 |
+
* @access public
|
79 |
+
* @since 1.0.5
|
80 |
+
* @version 1.0.14
|
81 |
+
* @return bool
|
82 |
+
*/
|
83 |
+
public function delete_all_caches() {
|
84 |
+
|
85 |
+
delete_transient( 'wc_square_processing_total_count' );
|
86 |
+
|
87 |
+
delete_transient( 'wc_square_processing_ids' );
|
88 |
+
|
89 |
+
delete_transient( 'wc_square_syncing_square_inventory' );
|
90 |
+
|
91 |
+
delete_transient( 'sq_wc_sync_current_process' );
|
92 |
+
|
93 |
+
delete_transient( 'wc_square_inventory' );
|
94 |
+
|
95 |
+
delete_transient( 'wc_square_polling' );
|
96 |
+
|
97 |
+
return true;
|
98 |
+
}
|
99 |
+
|
100 |
+
/**
|
101 |
+
* Checks to see if token is valid.
|
102 |
+
*
|
103 |
+
* There is no formal way to check this other than to
|
104 |
+
* retrieve the merchant account details and if it comes back
|
105 |
+
* with a code 200, we assume it is valid.
|
106 |
+
*
|
107 |
+
* @access public
|
108 |
+
* @since 1.0.0
|
109 |
+
* @version 1.0.0
|
110 |
+
* @return bool
|
111 |
+
*/
|
112 |
+
public function is_valid_token() {
|
113 |
+
|
114 |
+
$merchant_account_type = $this->get_square_merchant_account_type();
|
115 |
+
|
116 |
+
return ( false !== $merchant_account_type );
|
117 |
+
|
118 |
+
}
|
119 |
+
|
120 |
+
/**
|
121 |
+
* Retrieve merchant's account information, such as business name and email address.
|
122 |
+
*
|
123 |
+
* Endpoint doc: https://docs.connect.squareup.com/api/connect/v1/#navsection-merchant
|
124 |
+
* Return value doc: https://docs.connect.squareup.com/api/connect/v1/#datatype-merchant
|
125 |
+
*
|
126 |
+
* @access public
|
127 |
+
* @since 1.0.0
|
128 |
+
* @version 1.0.0
|
129 |
+
* @return false|null|object False if an HTTP error or non-200 status is encountered. Null on JSON decode error. Object on success.
|
130 |
+
*/
|
131 |
+
public function get_square_merchant() {
|
132 |
+
|
133 |
+
return $this->_client->request( 'Retrieving Merchant', 'me' );
|
134 |
+
|
135 |
+
}
|
136 |
+
|
137 |
+
/**
|
138 |
+
* Get the account type for the merchant.
|
139 |
+
*
|
140 |
+
* See: https://docs.connect.squareup.com/api/connect/v1/#enum-merchantaccounttype
|
141 |
+
*
|
142 |
+
* @access public
|
143 |
+
* @since 1.0.0
|
144 |
+
* @version 1.0.0
|
145 |
+
* @return bool|string Boolean false on failure, string account type (LOCATION or BUSINESS) on success.
|
146 |
+
*/
|
147 |
+
public function get_square_merchant_account_type() {
|
148 |
+
|
149 |
+
$account_type = get_transient( self::MERCHANT_ACCOUNT_TYPE_CACHE_KEY );
|
150 |
+
|
151 |
+
if ( false === $account_type ) {
|
152 |
+
|
153 |
+
$merchant = $this->get_square_merchant();
|
154 |
+
|
155 |
+
if ( is_null( $merchant ) || false === $merchant ) {
|
156 |
+
|
157 |
+
return false;
|
158 |
+
|
159 |
+
}
|
160 |
+
|
161 |
+
if ( isset( $merchant->account_type ) ) {
|
162 |
+
|
163 |
+
$account_type = $merchant->account_type;
|
164 |
+
|
165 |
+
set_transient( self::MERCHANT_ACCOUNT_TYPE_CACHE_KEY, $account_type, DAY_IN_SECONDS );
|
166 |
+
|
167 |
+
}
|
168 |
+
|
169 |
+
}
|
170 |
+
|
171 |
+
return $account_type;
|
172 |
+
|
173 |
+
}
|
174 |
+
|
175 |
+
/**
|
176 |
+
* Gets the locations of the business.
|
177 |
+
*
|
178 |
+
* @access public
|
179 |
+
* @since 1.0.0
|
180 |
+
* @version 1.0.0
|
181 |
+
* @return array $locations
|
182 |
+
*/
|
183 |
+
public function get_square_business_locations() {
|
184 |
+
|
185 |
+
if ( false !== ( $locations = get_transient( self::LOCATIONS_CACHE_KEY ) ) ) {
|
186 |
+
|
187 |
+
if ( ! empty( $locations ) ) {
|
188 |
+
return $locations;
|
189 |
+
}
|
190 |
+
|
191 |
+
}
|
192 |
+
|
193 |
+
$locations = array();
|
194 |
+
$account_type = $this->get_square_merchant_account_type();
|
195 |
+
|
196 |
+
/*
|
197 |
+
* Only "BUSINESS" accounts have multiple locations that need to be
|
198 |
+
* retrieved from a separate endpoint
|
199 |
+
* See: https://docs.connect.squareup.com/api/connect/v1/#get-locations
|
200 |
+
*/
|
201 |
+
if ( self::MULTIPLE_LOCATION_ACCOUNT_TYPE === $account_type ) {
|
202 |
+
|
203 |
+
$items = $this->_client->request( 'Retrieve Business Locations', 'me/locations' );
|
204 |
+
|
205 |
+
if ( ! empty( $items ) ) {
|
206 |
+
|
207 |
+
foreach( $items as $item ) {
|
208 |
+
if ( is_object( $item ) ) {
|
209 |
+
$locations[ $item->id ] = $item->name;
|
210 |
+
}
|
211 |
+
}
|
212 |
+
}
|
213 |
+
}
|
214 |
+
|
215 |
+
/*
|
216 |
+
* Single location accounts have all the details under the Merchant object
|
217 |
+
*/
|
218 |
+
if ( self::SINGLE_LOCATION_ACCOUNT_TYPE === $account_type ) {
|
219 |
+
|
220 |
+
$merchant = $this->get_square_merchant();
|
221 |
+
|
222 |
+
if ( isset( $merchant->id ) ) {
|
223 |
+
|
224 |
+
$locations[ $merchant->id ] = $merchant->name;
|
225 |
+
|
226 |
+
}
|
227 |
+
|
228 |
+
}
|
229 |
+
|
230 |
+
set_transient( self::LOCATIONS_CACHE_KEY, $locations, apply_filters( 'woocommerce_square_business_location_cache', DAY_IN_SECONDS ) );
|
231 |
+
|
232 |
+
return $locations;
|
233 |
+
|
234 |
+
}
|
235 |
+
|
236 |
+
/**
|
237 |
+
* Create a Square Item for a WC Product.
|
238 |
+
*
|
239 |
+
* See: https://docs.connect.squareup.com/api/connect/v1/#post-items
|
240 |
+
*
|
241 |
+
* @param WC_Product $wc_product
|
242 |
+
* @param bool $include_category
|
243 |
+
* @param bool $include_inventory
|
244 |
+
* @return object|bool Created Square Item object on success, boolean False on failure.
|
245 |
+
*/
|
246 |
+
public function create_square_product( $wc_product, $include_category = false, $include_inventory = false ) {
|
247 |
+
|
248 |
+
// We can only handle simple products or ones with variations
|
249 |
+
if ( ! in_array( ( version_compare( WC_VERSION, '3.0.0', '<' ) ? $wc_product->product_type : $wc_product->get_type() ), array( 'simple', 'variable' ) ) ) {
|
250 |
+
|
251 |
+
return false;
|
252 |
+
|
253 |
+
}
|
254 |
+
|
255 |
+
// TODO: Consider making this method "dumber" - remove this formatting call.
|
256 |
+
$product = WC_Square_Utils::format_wc_product_create_for_square_api(
|
257 |
+
$wc_product,
|
258 |
+
$include_category,
|
259 |
+
$include_inventory
|
260 |
+
);
|
261 |
+
|
262 |
+
return $this->_client->request( 'Creating Square Base Product from: ' . ( version_compare( WC_VERSION, '3.0.0', '<' ) ? $wc_product->id : $wc_product->get_id() ), 'items', 'POST', $product );
|
263 |
+
|
264 |
+
}
|
265 |
+
|
266 |
+
/**
|
267 |
+
* Update the corresponding Square Item for a WC Product.
|
268 |
+
*
|
269 |
+
* See: https://docs.connect.squareup.com/api/connect/v1/#put-itemid
|
270 |
+
*
|
271 |
+
* @param WC_Product $wc_product
|
272 |
+
* @param string $square_item_id
|
273 |
+
* @param bool $include_category
|
274 |
+
* @param bool $include_inventory
|
275 |
+
* @return object|bool Updated Square Item object on success, boolean False on failure.
|
276 |
+
*/
|
277 |
+
public function update_square_product( $wc_product, $square_item_id, $include_category = false, $include_inventory = false ) {
|
278 |
+
|
279 |
+
// We can only handle simple products or ones with variations
|
280 |
+
if ( ! in_array( ( version_compare( WC_VERSION, '3.0.0', '<' ) ? $wc_product->product_type : $wc_product->get_type() ), array( 'simple', 'variable' ) ) ) {
|
281 |
+
|
282 |
+
return false;
|
283 |
+
|
284 |
+
}
|
285 |
+
|
286 |
+
// TODO: Consider making this method "dumber" - remove this formatting call.
|
287 |
+
$product = WC_Square_Utils::format_wc_product_update_for_square_api( $wc_product, $include_category );
|
288 |
+
|
289 |
+
$endpoint = 'items/' . $square_item_id;
|
290 |
+
|
291 |
+
return $this->_client->request( 'Updating a Square Base Product for: ' . $square_item_id, $endpoint, 'PUT', $product );
|
292 |
+
|
293 |
+
}
|
294 |
+
|
295 |
+
/**
|
296 |
+
* Set the HTTP request Content-Type header to multipart/form-data for uploading Item images.
|
297 |
+
*
|
298 |
+
* @param array $http_args
|
299 |
+
* @return array
|
300 |
+
*/
|
301 |
+
public function square_product_image_update_filter_http_args( $http_args ) {
|
302 |
+
|
303 |
+
if ( empty( $http_args['headers'] ) ) {
|
304 |
+
|
305 |
+
$http_args['headers'] = array();
|
306 |
+
|
307 |
+
}
|
308 |
+
|
309 |
+
$http_args['headers']['Content-Type'] = 'multipart/form-data; boundary=' . self::ITEM_IMAGE_MULTIPART_BOUNDARY;
|
310 |
+
|
311 |
+
return $http_args;
|
312 |
+
|
313 |
+
}
|
314 |
+
|
315 |
+
/**
|
316 |
+
* Updates the master image for an Item
|
317 |
+
* See: https://docs.connect.squareup.com/api/connect/v1/#post-image
|
318 |
+
*
|
319 |
+
* @param string $square_item_id Square Item ID to upload image for.
|
320 |
+
* @param string $mime_type Mime type of the image.
|
321 |
+
* @param string $path_to_image Full path to image, accessible using file_get_contents().
|
322 |
+
* @return bool|object Response object on success, boolean false on failure.
|
323 |
+
*/
|
324 |
+
public function update_square_product_image( $square_item_id, $mime_type, $path_to_image ) {
|
325 |
+
|
326 |
+
// The WP HTTP API doesn't natively support multipart form data, so we must build the body ourselves
|
327 |
+
// See: http://lists.automattic.com/pipermail/wp-hackers/2013-January/045105.html
|
328 |
+
$request_body = '--' . self::ITEM_IMAGE_MULTIPART_BOUNDARY . "\r\n";
|
329 |
+
$request_body .= 'Content-Disposition: form-data; name="image_data"; filename="' . basename( $path_to_image ) . '"' . "\r\n";
|
330 |
+
$request_body .= 'Content-Type: ' . $mime_type . "\r\n\r\n"; // requires two CRLFs
|
331 |
+
$request_body .= file_get_contents( $path_to_image );
|
332 |
+
$request_body .= "\r\n--" . self::ITEM_IMAGE_MULTIPART_BOUNDARY . "--\r\n\r\n"; // requires two CRLFs
|
333 |
+
|
334 |
+
$api_path = '/items/' . $square_item_id . '/image';
|
335 |
+
|
336 |
+
add_filter( 'woocommerce_square_request_args', array( $this, 'square_product_image_update_filter_http_args' ) );
|
337 |
+
|
338 |
+
$result = $this->_client->request( 'Updating Square Item Image for: ' . $square_item_id, $api_path, 'POST', $request_body );
|
339 |
+
|
340 |
+
remove_filter( 'woocommerce_square_request_args', array( $this, 'square_product_image_update_filter_http_args' ) );
|
341 |
+
|
342 |
+
return $result;
|
343 |
+
|
344 |
+
}
|
345 |
+
|
346 |
+
/**
|
347 |
+
* Updates a product for a particular location
|
348 |
+
* Square API does not allow updates to a product along with variations (sucks)
|
349 |
+
* So a separate requests have to be made to update the variations see - update_square_variation()
|
350 |
+
*
|
351 |
+
* @access public
|
352 |
+
* @since 1.0.0
|
353 |
+
* @version 1.0.0
|
354 |
+
* @param int $product_id id from Square
|
355 |
+
* @param object $wc_product
|
356 |
+
* @return mixed
|
357 |
+
*/
|
358 |
+
public function update_square_base_product( $s_product_id, $wc_product ) {
|
359 |
+
|
360 |
+
$product = array(
|
361 |
+
'name' => $wc_product->get_title(),
|
362 |
+
'description' => version_compare( WC_VERSION, '3.0.0', '<' ) ? $wc_product->post->post_content : $wc_product->get_description(),
|
363 |
+
'visibility' => 'PUBLIC',
|
364 |
+
);
|
365 |
+
|
366 |
+
$category = wp_get_post_terms( version_compare( WC_VERSION, '3.0.0', '<' ) ? $wc_product->id : $wc_product->get_id(), 'product_cat', array( 'parent' => 0 ) );
|
367 |
+
|
368 |
+
if ( ! empty( $category ) ) {
|
369 |
+
|
370 |
+
$square_cat_id = get_woocommerce_term_meta( $category[0]->term_id, 'square_cat_id', true );
|
371 |
+
|
372 |
+
$product['category_id'] = $square_cat_id;
|
373 |
+
|
374 |
+
}
|
375 |
+
|
376 |
+
$endpoint = 'items/' . $s_product_id;
|
377 |
+
|
378 |
+
return $this->_client->request( 'Updating a Square Base Product for: ' . $s_product_id, $endpoint, 'PUT', $product );
|
379 |
+
|
380 |
+
}
|
381 |
+
|
382 |
+
/**
|
383 |
+
* Updates a single product variation
|
384 |
+
* Note that each product has at least one variation in Square
|
385 |
+
* Square does not allow multiple variation to be updated at the same time
|
386 |
+
*
|
387 |
+
* @param string $square_item_id id from Square
|
388 |
+
* @param object|array $variation_data Data to create ItemVariation with
|
389 |
+
* @return mixed
|
390 |
+
*/
|
391 |
+
public function create_square_variation( $square_item_id, $variation_data ) {
|
392 |
+
if ( empty( $square_item_id ) ) {
|
393 |
+
return false;
|
394 |
+
}
|
395 |
+
|
396 |
+
$endpoint = '/items/' . $square_item_id . '/variations';
|
397 |
+
|
398 |
+
return $this->_client->request( 'Creating a Square Product Variation for: ' . $square_item_id, $endpoint, 'POST', $variation_data );
|
399 |
+
|
400 |
+
}
|
401 |
+
|
402 |
+
/**
|
403 |
+
* Updates a single product variation
|
404 |
+
* Note that each product has at least one variation in Square
|
405 |
+
* Square does not allow multiple variation to be updated at the same time
|
406 |
+
*
|
407 |
+
* @param string $square_item_id id from Square
|
408 |
+
* @param string $square_variation_id id from Square
|
409 |
+
* @param object|array $variation_data Data to update ItemVariation with
|
410 |
+
* @return mixed
|
411 |
+
*/
|
412 |
+
public function update_square_variation( $square_item_id, $square_variation_id, $variation_data ) {
|
413 |
+
if ( empty( $square_item_id ) ) {
|
414 |
+
return false;
|
415 |
+
}
|
416 |
+
|
417 |
+
$endpoint = '/items/' . $square_item_id . '/variations/' . $square_variation_id;
|
418 |
+
|
419 |
+
return $this->_client->request( 'Updating a Square Product Variation for: ' . $square_variation_id, $endpoint, 'PUT', $variation_data );
|
420 |
+
|
421 |
+
}
|
422 |
+
|
423 |
+
/**
|
424 |
+
* Deletes a single product variation
|
425 |
+
*
|
426 |
+
* @access public
|
427 |
+
* @since 1.0.0
|
428 |
+
* @version 1.0.0
|
429 |
+
* @param int $s_product_id id from Square
|
430 |
+
* @param int $s_variation_id id from Square
|
431 |
+
* @return mixed
|
432 |
+
*/
|
433 |
+
public function delete_square_variation( $s_product_id, $s_variation_id ) {
|
434 |
+
|
435 |
+
$endpoint = '/items/' . $s_product_id . '/variations/' . $s_variation_id;
|
436 |
+
|
437 |
+
return $this->_client->request( 'Deleting a Square Product Variation for: ' . $s_variation_id, $endpoint, 'DELETE' );
|
438 |
+
|
439 |
+
}
|
440 |
+
|
441 |
+
/**
|
442 |
+
* Gets the products for a particular location
|
443 |
+
*
|
444 |
+
* @access public
|
445 |
+
* @since 1.0.0
|
446 |
+
* @version 1.0.0
|
447 |
+
* @return mixed
|
448 |
+
*/
|
449 |
+
public function get_square_products() {
|
450 |
+
|
451 |
+
$response = $this->_client->request( 'Retrieving Products', 'items' );
|
452 |
+
|
453 |
+
return $response ? $response : array();
|
454 |
+
|
455 |
+
}
|
456 |
+
|
457 |
+
/**
|
458 |
+
* Gets a product for a particular location
|
459 |
+
*
|
460 |
+
* @access public
|
461 |
+
* @since 1.0.0
|
462 |
+
* @version 1.0.0
|
463 |
+
* @param string $s_product_id
|
464 |
+
* @return mixed
|
465 |
+
*/
|
466 |
+
public function get_square_product( $s_product_id ) {
|
467 |
+
|
468 |
+
if ( empty( $s_product_id ) ) {
|
469 |
+
|
470 |
+
return array();
|
471 |
+
|
472 |
+
}
|
473 |
+
|
474 |
+
$endpoint = 'items/' . $s_product_id;
|
475 |
+
$response = $this->_client->request( 'Retrieving a Square Product for: ' . $s_product_id, $endpoint );
|
476 |
+
|
477 |
+
return $response ? $response : array();
|
478 |
+
|
479 |
+
}
|
480 |
+
|
481 |
+
/**
|
482 |
+
* Clear the Square Item SKU Map Cache
|
483 |
+
*/
|
484 |
+
public function clear_item_sku_map_cache() {
|
485 |
+
|
486 |
+
delete_transient( self::ITEM_SKU_MAP_CACHE_KEY );
|
487 |
+
|
488 |
+
}
|
489 |
+
|
490 |
+
/**
|
491 |
+
* Generate a mapping of Square Item IDs to their associated SKUs.
|
492 |
+
*
|
493 |
+
* @return array List of Square Item IDs and their variation SKUs.
|
494 |
+
*/
|
495 |
+
public function get_square_product_sku_map() {
|
496 |
+
|
497 |
+
if ( $cached = get_transient( self::ITEM_SKU_MAP_CACHE_KEY ) ) {
|
498 |
+
|
499 |
+
return $cached;
|
500 |
+
|
501 |
+
}
|
502 |
+
|
503 |
+
$square_products = $this->get_square_products();
|
504 |
+
$square_product_sku_map = array();
|
505 |
+
$processed_ids = array();
|
506 |
+
|
507 |
+
foreach ( $square_products as $s_product ) {
|
508 |
+
// Square may return dups so make sure we check for that
|
509 |
+
if ( in_array( $s_product->id, $processed_ids ) ) {
|
510 |
+
continue;
|
511 |
+
}
|
512 |
+
|
513 |
+
$square_product_sku_map[ $s_product->id ] = array();
|
514 |
+
|
515 |
+
foreach ( $s_product->variations as $s_variation ) {
|
516 |
+
|
517 |
+
if ( ! empty( $s_variation->sku ) ) {
|
518 |
+
|
519 |
+
$square_product_sku_map[ $s_product->id ][] = $s_variation->sku;
|
520 |
+
|
521 |
+
}
|
522 |
+
}
|
523 |
+
|
524 |
+
$processed_ids[] = $s_product;
|
525 |
+
}
|
526 |
+
|
527 |
+
set_transient( self::ITEM_SKU_MAP_CACHE_KEY, $square_product_sku_map, apply_filters( 'woocommerce_square_item_sku_cache', DAY_IN_SECONDS ) );
|
528 |
+
|
529 |
+
return $square_product_sku_map;
|
530 |
+
|
531 |
+
}
|
532 |
+
|
533 |
+
/**
|
534 |
+
* Checks if product exists on square
|
535 |
+
*
|
536 |
+
* @access public
|
537 |
+
* @since 1.0.0
|
538 |
+
* @version 1.0.0
|
539 |
+
* @param string|array $sku_list One or more SKUs to get a product by
|
540 |
+
* @return false if not exists or product object
|
541 |
+
*/
|
542 |
+
public function square_product_exists( $sku_list ) {
|
543 |
+
|
544 |
+
$square_item_sku_map = $this->get_square_product_sku_map();
|
545 |
+
|
546 |
+
$sku_list = (array) $sku_list;
|
547 |
+
|
548 |
+
foreach ( $square_item_sku_map as $square_item_id => $square_item_skus ) {
|
549 |
+
|
550 |
+
if ( array_intersect( $sku_list, $square_item_skus ) ) {
|
551 |
+
|
552 |
+
return $this->get_square_product( $square_item_id );
|
553 |
+
|
554 |
+
}
|
555 |
+
|
556 |
+
}
|
557 |
+
|
558 |
+
return false;
|
559 |
+
|
560 |
+
}
|
561 |
+
|
562 |
+
/**
|
563 |
+
* Gets the categories for a particular location
|
564 |
+
*
|
565 |
+
* @access public
|
566 |
+
* @since 1.0.0
|
567 |
+
* @version 1.0.0
|
568 |
+
* @return mixed
|
569 |
+
*/
|
570 |
+
public function get_square_categories() {
|
571 |
+
|
572 |
+
$response = $this->_client->request( 'Retrieving Square Categories', 'categories' );
|
573 |
+
|
574 |
+
return $response ? $response : array();
|
575 |
+
|
576 |
+
}
|
577 |
+
|
578 |
+
/**
|
579 |
+
* Create a square category
|
580 |
+
*
|
581 |
+
* @access public
|
582 |
+
* @since 1.0.0
|
583 |
+
* @version 1.0.0
|
584 |
+
* @param string $name
|
585 |
+
* @return mixed
|
586 |
+
*/
|
587 |
+
public function create_square_category( $name ) {
|
588 |
+
|
589 |
+
$category = array(
|
590 |
+
'name' => sanitize_text_field( $name ),
|
591 |
+
);
|
592 |
+
|
593 |
+
return $this->_client->request( 'Creating Square Category for: ' . sanitize_text_field( $name ), 'categories', 'POST', $category );
|
594 |
+
|
595 |
+
}
|
596 |
+
|
597 |
+
/**
|
598 |
+
* Update a Square Category
|
599 |
+
*
|
600 |
+
* @param string $square_category_id
|
601 |
+
* @param string $name
|
602 |
+
*
|
603 |
+
* @return bool|object|WP_Error
|
604 |
+
*/
|
605 |
+
public function update_square_category( $square_category_id, $name ) {
|
606 |
+
|
607 |
+
$endpoint = 'categories/' . $square_category_id;
|
608 |
+
$category = array(
|
609 |
+
'name' => sanitize_text_field( $name ),
|
610 |
+
);
|
611 |
+
|
612 |
+
return $this->_client->request( 'Updating Category for: ' . sanitize_text_field( $name ), $endpoint, 'PUT', $category );
|
613 |
+
|
614 |
+
}
|
615 |
+
|
616 |
+
/**
|
617 |
+
* Get square variation inventory.
|
618 |
+
*
|
619 |
+
* Note: There is no current way to get a specific product variation's inventory.
|
620 |
+
*
|
621 |
+
* @return array
|
622 |
+
*/
|
623 |
+
public function get_square_inventory() {
|
624 |
+
if ( $cached = get_transient( self::INVENTORY_CACHE_KEY ) ) {
|
625 |
+
|
626 |
+
return $cached;
|
627 |
+
|
628 |
+
}
|
629 |
+
|
630 |
+
$response = $this->_client->request( 'Getting All Square Inventory', 'inventory' ); // default 1000 max limit
|
631 |
+
|
632 |
+
$square_inventory = array();
|
633 |
+
|
634 |
+
if ( is_array( $response ) ) {
|
635 |
+
|
636 |
+
$square_inventory_ids = wp_list_pluck( $response, 'variation_id' );
|
637 |
+
$square_inventory_quantities = wp_list_pluck( $response, 'quantity_on_hand' );
|
638 |
+
$square_inventory = array_combine( $square_inventory_ids, $square_inventory_quantities );
|
639 |
+
|
640 |
+
}
|
641 |
+
|
642 |
+
set_transient( self::INVENTORY_CACHE_KEY, $square_inventory, apply_filters( 'woocommerce_square_inventory_cache', DAY_IN_SECONDS ) );
|
643 |
+
|
644 |
+
return $square_inventory;
|
645 |
+
|
646 |
+
}
|
647 |
+
|
648 |
+
/**
|
649 |
+
* Updates square variation inventory
|
650 |
+
*
|
651 |
+
* @access public
|
652 |
+
* @since 1.0.0
|
653 |
+
* @version 1.0.0
|
654 |
+
* @param string $square_variation_id
|
655 |
+
* @param int $quantity_delta
|
656 |
+
* @param string $type the type of adjustment MANUAL_ADJUST, RECEIVE_STOCK, SALE
|
657 |
+
* @return bool
|
658 |
+
*/
|
659 |
+
public function update_square_inventory( $square_variation_id, $quantity_delta, $type = 'MANUAL_ADJUST' ) {
|
660 |
+
|
661 |
+
$endpoint = 'inventory/' . $square_variation_id;
|
662 |
+
$inventory = array(
|
663 |
+
'quantity_delta' => $quantity_delta,
|
664 |
+
'adjustment_type' => $type,
|
665 |
+
);
|
666 |
+
$response = $this->_client->request( 'Updating Square Inventory for: ' . $square_variation_id, $endpoint, 'POST', $inventory );
|
667 |
+
|
668 |
+
return ( false !== $response );
|
669 |
+
|
670 |
+
}
|
671 |
+
|
672 |
+
/**
|
673 |
+
* Refresh the Square Inventory cache.
|
674 |
+
*/
|
675 |
+
public function refresh_inventory_cache() {
|
676 |
+
delete_transient( self::INVENTORY_CACHE_KEY );
|
677 |
+
}
|
678 |
+
}
|
includes/class-wc-square-deactivation.php
ADDED
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
if ( ! defined( 'ABSPATH' ) ) {
|
3 |
+
exit; // Exit if accessed directly
|
4 |
+
}
|
5 |
+
|
6 |
+
class WC_Square_Deactivation {
|
7 |
+
|
8 |
+
/**
|
9 |
+
* Constructor not to be instantiated
|
10 |
+
*
|
11 |
+
* @access private
|
12 |
+
* @since 1.0.0
|
13 |
+
* @version 1.0.0
|
14 |
+
* @return bool
|
15 |
+
*/
|
16 |
+
private function __construct() {}
|
17 |
+
|
18 |
+
/**
|
19 |
+
* Perform deactivation tasks
|
20 |
+
*
|
21 |
+
* @access public
|
22 |
+
* @since 1.0.0
|
23 |
+
* @version 1.0.0
|
24 |
+
* @return bool
|
25 |
+
*/
|
26 |
+
public static function deactivate() {
|
27 |
+
wp_clear_scheduled_hook( 'woocommerce_square_inventory_poll' );
|
28 |
+
|
29 |
+
return true;
|
30 |
+
}
|
31 |
+
}
|
includes/class-wc-square-install.php
ADDED
@@ -0,0 +1,78 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
if ( ! defined( 'ABSPATH' ) ) {
|
3 |
+
exit; // Exit if accessed directly
|
4 |
+
}
|
5 |
+
|
6 |
+
/**
|
7 |
+
* Installation/Activation Class.
|
8 |
+
*
|
9 |
+
* Handles the activation/installation of the plugin.
|
10 |
+
*
|
11 |
+
* @category Installation
|
12 |
+
* @package WooCommerce Square/Install
|
13 |
+
* @version 1.0.0
|
14 |
+
*/
|
15 |
+
class WC_Square_Install {
|
16 |
+
/**
|
17 |
+
* Intialize
|
18 |
+
*
|
19 |
+
* @access public
|
20 |
+
* @version 1.0.0
|
21 |
+
* @since 1.0.0
|
22 |
+
* @return bool
|
23 |
+
*/
|
24 |
+
public static function init() {
|
25 |
+
add_action( 'admin_init', array( __CLASS__, 'check_version' ), 5 );
|
26 |
+
|
27 |
+
return true;
|
28 |
+
}
|
29 |
+
|
30 |
+
/**
|
31 |
+
* Checks the plugin version
|
32 |
+
*
|
33 |
+
* @access public
|
34 |
+
* @since 1.0.0
|
35 |
+
* @version 1.0.0
|
36 |
+
* @return bool
|
37 |
+
*/
|
38 |
+
public static function check_version() {
|
39 |
+
if ( ! defined( 'IFRAME_REQUEST' ) && ( get_option( 'wc_square_version' ) != WC_SQUARE_VERSION ) ) {
|
40 |
+
self::install();
|
41 |
+
|
42 |
+
do_action( 'wc_square_updated' );
|
43 |
+
}
|
44 |
+
|
45 |
+
return true;
|
46 |
+
}
|
47 |
+
|
48 |
+
/**
|
49 |
+
* Do installs.
|
50 |
+
*
|
51 |
+
* @access public
|
52 |
+
* @since 1.0.0
|
53 |
+
* @version 1.0.0
|
54 |
+
* @return bool
|
55 |
+
*/
|
56 |
+
public static function install() {
|
57 |
+
self::update_plugin_version();
|
58 |
+
|
59 |
+
return true;
|
60 |
+
}
|
61 |
+
|
62 |
+
/**
|
63 |
+
* Updates the plugin version in db
|
64 |
+
*
|
65 |
+
* @access public
|
66 |
+
* @since 1.0.0
|
67 |
+
* @version 1.0.0
|
68 |
+
* @return bool
|
69 |
+
*/
|
70 |
+
private static function update_plugin_version() {
|
71 |
+
delete_option( 'wc_square_version' );
|
72 |
+
add_option( 'wc_square_version', WC_SQUARE_VERSION );
|
73 |
+
|
74 |
+
return true;
|
75 |
+
}
|
76 |
+
}
|
77 |
+
|
78 |
+
WC_Square_Install::init();
|
includes/class-wc-square-inventory-poll.php
ADDED
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
if ( ! defined( 'ABSPATH' ) ) {
|
3 |
+
exit;
|
4 |
+
}
|
5 |
+
|
6 |
+
/**
|
7 |
+
* Class WC_Square_Inventory_Poll
|
8 |
+
*
|
9 |
+
* Cron driven methods to poll Square's inventory at intervals.
|
10 |
+
* This is to replace the webhook method as it is not recommended by Square.
|
11 |
+
*/
|
12 |
+
class WC_Square_Inventory_Poll {
|
13 |
+
protected $integration;
|
14 |
+
protected $to_wc;
|
15 |
+
|
16 |
+
/**
|
17 |
+
* Constructor
|
18 |
+
*
|
19 |
+
* @since 1.0.0
|
20 |
+
* @version 1.0.0
|
21 |
+
* @param object $integration
|
22 |
+
* @param object $to_wc
|
23 |
+
*/
|
24 |
+
public function __construct( WC_Square_Integration $integration, WC_Square_Sync_From_Square $to_wc ) {
|
25 |
+
$this->integration = $integration;
|
26 |
+
$this->to_wc = $to_wc;
|
27 |
+
|
28 |
+
add_action( 'init', array( $this, 'run_schedule' ) );
|
29 |
+
|
30 |
+
// the scheduled cron will trigger this event
|
31 |
+
add_action( 'woocommerce_square_inventory_poll', array( $this, 'sync' ) );
|
32 |
+
}
|
33 |
+
|
34 |
+
public function run_schedule() {
|
35 |
+
$frequency = apply_filters( 'woocommerce_square_inventory_poll_frequency', 'hourly' );
|
36 |
+
|
37 |
+
if ( ! wp_next_scheduled( 'woocommerce_square_inventory_poll' ) ) {
|
38 |
+
wp_schedule_event( time(), $frequency, 'woocommerce_square_inventory_poll' );
|
39 |
+
}
|
40 |
+
}
|
41 |
+
|
42 |
+
public function sync() {
|
43 |
+
$sync = ( 'yes' === $this->integration->get_option( 'inventory_polling' ) );
|
44 |
+
|
45 |
+
if ( $sync ) {
|
46 |
+
$this->to_wc->sync_all_inventory();
|
47 |
+
}
|
48 |
+
}
|
49 |
+
}
|
includes/class-wc-square-sync-from-square.php
ADDED
@@ -0,0 +1,384 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
if ( ! defined( 'ABSPATH' ) ) {
|
3 |
+
exit; // Exit if accessed directly
|
4 |
+
}
|
5 |
+
|
6 |
+
/**
|
7 |
+
* Class WC_Square_Sync_From_Square
|
8 |
+
*
|
9 |
+
* Methods to Sync from Square to WC. Organized as "sync" methods that
|
10 |
+
* determine if "create" or "update" actions should be taken on the entities
|
11 |
+
* involved.
|
12 |
+
*
|
13 |
+
* NOTE: Existing WC Products are *not* updated with data from their
|
14 |
+
* corresponding Square Item due to a lack of modified timestamp
|
15 |
+
* on the Square API side.
|
16 |
+
*/
|
17 |
+
class WC_Square_Sync_From_Square {
|
18 |
+
|
19 |
+
/**
|
20 |
+
* @var WC_Square_Connect
|
21 |
+
*/
|
22 |
+
protected $connect;
|
23 |
+
|
24 |
+
/**
|
25 |
+
* WC_Square_Sync_From_Square constructor.
|
26 |
+
*/
|
27 |
+
public function __construct( WC_Square_Connect $connect ) {
|
28 |
+
|
29 |
+
$this->connect = $connect;
|
30 |
+
|
31 |
+
}
|
32 |
+
|
33 |
+
/**
|
34 |
+
* Sync Square categories to WooCommerce.
|
35 |
+
*
|
36 |
+
* Looks for category names that don't exist in WooCommerce, and creates them.
|
37 |
+
*/
|
38 |
+
public function sync_categories() {
|
39 |
+
|
40 |
+
$square_category_objects = $this->connect->get_square_categories();
|
41 |
+
$square_categories = array();
|
42 |
+
$processed_categories = array();
|
43 |
+
|
44 |
+
foreach ( $square_category_objects as $square_category ) {
|
45 |
+
// Square list endpoints may return dups so we must check for that
|
46 |
+
if ( in_array( $square_category->id, $processed_categories ) ) {
|
47 |
+
continue;
|
48 |
+
}
|
49 |
+
|
50 |
+
if ( is_object( $square_category ) && ! empty( $square_category->name ) && ! empty( $square_category->id ) ) {
|
51 |
+
$square_categories[ $square_category->name ] = $square_category->id;
|
52 |
+
$processed_categories[] = $square_category->id;
|
53 |
+
}
|
54 |
+
}
|
55 |
+
|
56 |
+
if ( empty( $square_categories ) ) {
|
57 |
+
|
58 |
+
WC_Square_Sync_Logger::log( '[Square -> WC] No categories found to sync.' );
|
59 |
+
return;
|
60 |
+
|
61 |
+
}
|
62 |
+
|
63 |
+
$wc_category_objects = $this->connect->wc->get_product_categories();
|
64 |
+
$wc_categories = array();
|
65 |
+
|
66 |
+
if ( is_wp_error( $wc_category_objects ) ) {
|
67 |
+
|
68 |
+
WC_Square_Sync_Logger::log( '[Square -> WC] Error encountered retrieving WC Product Categories: ' . $wc_category_objects->get_error_message() );
|
69 |
+
return;
|
70 |
+
|
71 |
+
}
|
72 |
+
|
73 |
+
if ( ! empty( $wc_category_objects['product_categories'] ) ) {
|
74 |
+
|
75 |
+
foreach ( $wc_category_objects['product_categories'] as $wc_category ) {
|
76 |
+
|
77 |
+
if ( empty( $wc_category['name'] ) || empty( $wc_category['id'] ) || ( 0 !== $wc_category['parent'] ) ) {
|
78 |
+
continue;
|
79 |
+
}
|
80 |
+
|
81 |
+
$wc_categories[ $wc_category['name'] ] = $wc_category['id'];
|
82 |
+
|
83 |
+
}
|
84 |
+
}
|
85 |
+
|
86 |
+
// Look for previously synced categories and update them with data from Square
|
87 |
+
foreach ( $wc_categories as $wc_cat_name => $wc_cat_id ) {
|
88 |
+
|
89 |
+
$wc_square_cat_id = WC_Square_Utils::get_wc_term_square_id( $wc_cat_id );
|
90 |
+
|
91 |
+
// Make sure the associated Square ID still exists on the Square side
|
92 |
+
if ( $wc_square_cat_id && ( $square_cat_name = array_search( $wc_square_cat_id, $square_categories ) ) ) {
|
93 |
+
|
94 |
+
$result = $this->connect->wc->edit_product_category( $wc_cat_id, array(
|
95 |
+
'product_category' => array(
|
96 |
+
'name' => $square_cat_name,
|
97 |
+
),
|
98 |
+
) );
|
99 |
+
|
100 |
+
if ( is_wp_error( $result ) ) {
|
101 |
+
|
102 |
+
WC_Square_Sync_Logger::log( sprintf( '[Square -> WC] Error updating WC Product Category %d for Square ID %s: %s', $wc_cat_id, $wc_square_cat_id, $result->get_error_message() ) );
|
103 |
+
continue;
|
104 |
+
|
105 |
+
} elseif ( empty( $result['product_category'] ) ) {
|
106 |
+
|
107 |
+
WC_Square_Sync_Logger::log( sprintf( '[Square -> WC] Unexpected empty result updating WC Product Category %d for Square ID %s.', $wc_cat_id, $wc_square_cat_id ) );
|
108 |
+
continue;
|
109 |
+
|
110 |
+
}
|
111 |
+
|
112 |
+
// We no longer need to process this Square Category, so remove from list
|
113 |
+
unset( $square_categories[ $square_cat_name ] );
|
114 |
+
|
115 |
+
} else {
|
116 |
+
|
117 |
+
WC_Square_Sync_Logger::log( sprintf( '[Square -> WC] Cannot sync Square Category ID %s, it no longer exists.', $wc_square_cat_id ) );
|
118 |
+
|
119 |
+
}
|
120 |
+
}
|
121 |
+
|
122 |
+
/*
|
123 |
+
* Go through the remaining Square Categories and either:
|
124 |
+
* 1) Match them to an existing WC Category
|
125 |
+
* 2) Create a new WC Category
|
126 |
+
*/
|
127 |
+
foreach ( $square_categories as $name => $square_id ) {
|
128 |
+
|
129 |
+
if ( empty( $wc_categories[ $name ] ) ) {
|
130 |
+
|
131 |
+
$result = $this->connect->wc->create_product_category( array(
|
132 |
+
'product_category' => array(
|
133 |
+
'name' => $name,
|
134 |
+
),
|
135 |
+
) );
|
136 |
+
|
137 |
+
if ( is_wp_error( $result ) ) {
|
138 |
+
|
139 |
+
WC_Square_Sync_Logger::log( sprintf( '[Square -> WC] Error creating WC Product Category for Square ID %s: %s', $wc_square_cat_id, $result->get_error_message() ) );
|
140 |
+
continue;
|
141 |
+
|
142 |
+
} elseif ( empty( $result['product_category'] ) ) {
|
143 |
+
|
144 |
+
WC_Square_Sync_Logger::log( sprintf( '[Square -> WC] Unexpected empty result creating WC Product Category for Square ID %s.', $wc_square_cat_id ) );
|
145 |
+
continue;
|
146 |
+
|
147 |
+
}
|
148 |
+
|
149 |
+
$wc_term_id = $result['product_category']['id'];
|
150 |
+
|
151 |
+
} else {
|
152 |
+
|
153 |
+
$wc_term_id = $wc_categories[ $name ];
|
154 |
+
|
155 |
+
}
|
156 |
+
|
157 |
+
WC_Square_Utils::update_wc_term_square_id( $wc_term_id, $square_id );
|
158 |
+
|
159 |
+
}
|
160 |
+
|
161 |
+
}
|
162 |
+
|
163 |
+
/**
|
164 |
+
* Sync a Square Item to WC, optionally including Categories and Inventory.
|
165 |
+
*
|
166 |
+
* @param object $square_item
|
167 |
+
* @param bool $include_category
|
168 |
+
* @param bool $include_inventory
|
169 |
+
* @param bool $include_image
|
170 |
+
*/
|
171 |
+
public function sync_product( $square_item, $include_category = false, $include_inventory = false, $include_image = false ) {
|
172 |
+
$wc_product = WC_Square_Utils::get_wc_product_for_square_item( $square_item );
|
173 |
+
$is_new_product = ( false === $wc_product );
|
174 |
+
|
175 |
+
// If none of the Square item variations have sku we must skip the sync.
|
176 |
+
if ( ! WC_Square_Utils::is_square_item_skus_set( $square_item ) ) {
|
177 |
+
WC_Square_Sync_Logger::log( sprintf( '[Square -> WC] Skipping sync for Square item ID %s missing one or more SKU.', $square_item->id ) );
|
178 |
+
|
179 |
+
return;
|
180 |
+
}
|
181 |
+
|
182 |
+
// Only create items that don't yet exist in WC
|
183 |
+
if ( $is_new_product ) {
|
184 |
+
|
185 |
+
$wc_product = $this->create_product( $square_item, $include_category, $include_inventory, $include_image );
|
186 |
+
|
187 |
+
WC_Square_Sync_Logger::log( sprintf( '[Square -> WC] Creating WC product for Square Item ID %s.', $square_item->id ) );
|
188 |
+
}
|
189 |
+
|
190 |
+
if ( $wc_product ) {
|
191 |
+
|
192 |
+
WC_Square_Utils::set_square_ids_on_wc_product_by_sku( $wc_product, $square_item );
|
193 |
+
|
194 |
+
$wc_product_id = version_compare( WC_VERSION, '3.0.0', '<' ) ? $wc_product->id : $wc_product->get_id();
|
195 |
+
|
196 |
+
if ( ! $is_new_product ) {
|
197 |
+
|
198 |
+
$this->update_product( $wc_product, $square_item, $include_category, $include_inventory, $include_image );
|
199 |
+
|
200 |
+
WC_Square_Sync_Logger::log( sprintf( '[Square -> WC] Updating WC product for Square Item ID %s.', $square_item->id ) );
|
201 |
+
|
202 |
+
}
|
203 |
+
|
204 |
+
if ( $include_inventory ) {
|
205 |
+
if ( WC_Square_Utils::skip_product_sync( $wc_product_id ) ) {
|
206 |
+
WC_Square_Sync_Logger::log( sprintf( '[Square -> WC] Syncing disabled for this WC Product %d for Square ID %s', $wc_product_id, $square_item->id ) );
|
207 |
+
|
208 |
+
return;
|
209 |
+
}
|
210 |
+
|
211 |
+
$this->sync_inventory( $wc_product, $square_item );
|
212 |
+
|
213 |
+
WC_Square_Sync_Logger::log( sprintf( '[Square -> WC] Syncing WC product inventory for Square Item ID %s.', $square_item->id ) );
|
214 |
+
|
215 |
+
}
|
216 |
+
} else {
|
217 |
+
|
218 |
+
WC_Square_Sync_Logger::log( sprintf( '[Square -> WC] Error creating WC Product found for Square Item ID %s.', $square_item->id ) );
|
219 |
+
return;
|
220 |
+
|
221 |
+
}
|
222 |
+
|
223 |
+
}
|
224 |
+
|
225 |
+
/**
|
226 |
+
* Create a new WC Product using data from Square.
|
227 |
+
*
|
228 |
+
* @param object $square_item
|
229 |
+
* @param bool $include_category
|
230 |
+
* @param bool $include_inventory
|
231 |
+
* @param bool $include_image
|
232 |
+
*
|
233 |
+
* @return bool|WC_Product Created WC_Product on success, boolean false on failure.
|
234 |
+
*/
|
235 |
+
public function create_product( $square_item, $include_category = false, $include_inventory = false, $include_image = false ) {
|
236 |
+
|
237 |
+
$product_update = WC_Square_Utils::format_square_item_for_wc_api_create( $square_item, $include_category, $include_inventory, $include_image );
|
238 |
+
|
239 |
+
// note here that when creating variations via WC API, if the parent product
|
240 |
+
// is in the trash and the SKU matches the variation of the parent, the variations
|
241 |
+
// won't be created. This is because the WC API is not checking if variations are
|
242 |
+
// published.
|
243 |
+
$result = $this->connect->wc->create_product( array( 'product' => $product_update ) );
|
244 |
+
|
245 |
+
if ( is_wp_error( $result ) ) {
|
246 |
+
|
247 |
+
WC_Square_Sync_Logger::log( sprintf( '[Square -> WC] Error creating WC Product for Square ID %s: %s', $square_item->id, $result->get_error_message() ) );
|
248 |
+
|
249 |
+
} elseif ( isset( $result['product']['id'] ) ) {
|
250 |
+
|
251 |
+
return wc_get_product( $result['product']['id'] );
|
252 |
+
|
253 |
+
}
|
254 |
+
|
255 |
+
return false;
|
256 |
+
|
257 |
+
}
|
258 |
+
|
259 |
+
/**
|
260 |
+
* Update an existing WC Product using data from Square.
|
261 |
+
*
|
262 |
+
* @param WC_Product $wc_product
|
263 |
+
* @param object $square_item
|
264 |
+
* @param bool $include_category
|
265 |
+
* @param bool $include_inventory
|
266 |
+
* @param bool $include_image
|
267 |
+
*
|
268 |
+
* @return bool|WC_Product Updated WC_Product on success, boolean false on failure.
|
269 |
+
*/
|
270 |
+
public function update_product( WC_Product $wc_product, $square_item, $include_category = false, $include_inventory = false, $include_image = false ) {
|
271 |
+
|
272 |
+
$wc_product_id = version_compare( WC_VERSION, '3.0.0', '<' ) ? $wc_product->id : $wc_product->get_id();
|
273 |
+
|
274 |
+
if ( WC_Square_Utils::skip_product_sync( $wc_product_id ) ) {
|
275 |
+
WC_Square_Sync_Logger::log( sprintf( '[Square -> WC] Syncing disabled for this WC Product %d for Square ID %s', $wc_product_id, $square_item->id ) );
|
276 |
+
|
277 |
+
return false;
|
278 |
+
}
|
279 |
+
|
280 |
+
$product_update = WC_Square_Utils::format_square_item_for_wc_api_update( $square_item, $wc_product, $include_category, $include_inventory, $include_image );
|
281 |
+
|
282 |
+
$result = $this->connect->wc->edit_product( $wc_product_id, array( 'product' => $product_update ) );
|
283 |
+
|
284 |
+
if ( is_wp_error( $result ) ) {
|
285 |
+
|
286 |
+
WC_Square_Sync_Logger::log( sprintf( '[Square -> WC] Error updating WC Product %d for Square ID %s: %s', $wc_product_id, $square_item->id, $result->get_error_message() ) );
|
287 |
+
|
288 |
+
} elseif ( isset( $result['product']['id'] ) ) {
|
289 |
+
|
290 |
+
return wc_get_product( $result['product']['id'] );
|
291 |
+
|
292 |
+
}
|
293 |
+
|
294 |
+
return false;
|
295 |
+
|
296 |
+
}
|
297 |
+
|
298 |
+
/**
|
299 |
+
* Sync a WC Product's inventory with data from Square
|
300 |
+
*
|
301 |
+
* @param WC_Product $wc_product
|
302 |
+
* @param stdClass $square_item
|
303 |
+
*/
|
304 |
+
public function sync_inventory( WC_Product $wc_product, $square_item ) {
|
305 |
+
|
306 |
+
$wc_variation_ids = WC_Square_Utils::get_stock_managed_wc_variation_ids( $wc_product );
|
307 |
+
$square_inventory = $this->connect->get_square_inventory();
|
308 |
+
|
309 |
+
foreach ( $wc_variation_ids as $wc_variation_id ) {
|
310 |
+
|
311 |
+
$square_variation_id = WC_Square_Utils::get_wc_variation_square_id( $wc_variation_id );
|
312 |
+
|
313 |
+
if ( ! $square_variation_id || ! isset( $square_inventory[ $square_variation_id ] ) ) {
|
314 |
+
|
315 |
+
continue;
|
316 |
+
|
317 |
+
}
|
318 |
+
|
319 |
+
// check each variation stock_tracking setting and set stock if tracking is enabled
|
320 |
+
foreach ( $square_item->variations as $variation_item ) {
|
321 |
+
|
322 |
+
if ( $variation_item->id == $square_variation_id && $variation_item->track_inventory ) {
|
323 |
+
|
324 |
+
$square_stock = (int) $square_inventory[ $square_variation_id ];
|
325 |
+
$wc_variation = wc_get_product( $wc_variation_id );
|
326 |
+
$result = version_compare( WC_VERSION, '3.0.0', '<' ) ? $wc_variation->set_stock( $square_stock ) : wc_update_product_stock( $wc_variation, $square_stock );
|
327 |
+
|
328 |
+
}
|
329 |
+
}
|
330 |
+
}
|
331 |
+
}
|
332 |
+
|
333 |
+
/**
|
334 |
+
* Sync all inventory from Square (expensive)
|
335 |
+
* @todo if searching for square id fails, check for SKU
|
336 |
+
*/
|
337 |
+
public function sync_all_inventory() {
|
338 |
+
try {
|
339 |
+
set_time_limit( apply_filters( 'woocommerce_square_inventory_sync_timeout_limit', 200 ) );
|
340 |
+
|
341 |
+
// refresh cache first to get the latest inventory
|
342 |
+
$this->connect->refresh_inventory_cache();
|
343 |
+
|
344 |
+
$square_inventory = $this->connect->get_square_inventory();
|
345 |
+
|
346 |
+
// To prevent infinite loop when stock is updated in WC.
|
347 |
+
set_transient( 'wc_square_polling', 'yes', 60 * MINUTE_IN_SECONDS );
|
348 |
+
|
349 |
+
// hopefully there has been a manual sync prior so that square item id
|
350 |
+
// has already been saved in the product/variation metas to prevent
|
351 |
+
// unnecessary round trip requests to Square to find the SKU
|
352 |
+
foreach ( $square_inventory as $variation_id => $stock ) {
|
353 |
+
WC_Square_Sync_Logger::log( sprintf( '[Square -> WC] Syncing WC product inventory for Square Item ID %s.', $variation_id ) );
|
354 |
+
|
355 |
+
$wc_variation_product = WC_Square_Utils::get_wc_product_for_square_item_variation_id( $variation_id );
|
356 |
+
|
357 |
+
if ( ! is_object( $wc_variation_product ) ) {
|
358 |
+
WC_Square_Sync_Logger::log( sprintf( '[Square -> WC] Syncing WC product inventory for Square Item ID %s - WC product/variation not found skipping.', $variation_id ) );
|
359 |
+
continue;
|
360 |
+
}
|
361 |
+
|
362 |
+
$product_id = 0;
|
363 |
+
|
364 |
+
// check if we need to skip
|
365 |
+
if ( 'simple' === ( version_compare( WC_VERSION, '3.0.0', '<' ) ? $wc_variation_product->product_type : $wc_variation_product->get_type() ) ) {
|
366 |
+
$product_id = version_compare( WC_VERSION, '3.0.0', '<' ) ? $wc_variation_product->id : $wc_variation_product->get_id();
|
367 |
+
} elseif ( 'variation' === ( version_compare( WC_VERSION, '3.0.0', '<' ) ? $wc_variation_product->product_type : $wc_variation_product->get_type() ) ) {
|
368 |
+
$product_id = version_compare( WC_VERSION, '3.0.0', '<' ) ? $wc_variation_product->parent->id : $wc_variation_product->get_parent_id();
|
369 |
+
}
|
370 |
+
|
371 |
+
if ( is_object( $wc_variation_product ) && ! WC_Square_Utils::skip_product_sync( $product_id ) ) {
|
372 |
+
version_compare( WC_VERSION, '3.0.0', '<' ) ? $wc_variation_product->set_stock( (int) $stock ) : wc_update_product_stock( $wc_variation_product, (int) $stock );
|
373 |
+
}
|
374 |
+
}
|
375 |
+
|
376 |
+
delete_transient( 'wc_square_polling' );
|
377 |
+
|
378 |
+
return true;
|
379 |
+
} catch ( Exception $e ) {
|
380 |
+
delete_transient( 'wc_square_polling' );
|
381 |
+
WC_Square_Sync_Logger::log( sprintf( '[Square -> WC] Inventory Poll: ', $e->getMessage() ) );
|
382 |
+
}
|
383 |
+
}
|
384 |
+
}
|
includes/class-wc-square-sync-logger.php
ADDED
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
if ( ! defined( 'ABSPATH' ) ) {
|
3 |
+
exit; // Exit if accessed directly
|
4 |
+
}
|
5 |
+
|
6 |
+
/**
|
7 |
+
* Square sync logging class which saves important data to the log
|
8 |
+
*
|
9 |
+
* @since 1.0.0
|
10 |
+
* @version 1.0.0
|
11 |
+
*/
|
12 |
+
class WC_Square_Sync_Logger {
|
13 |
+
|
14 |
+
public static $logger;
|
15 |
+
const WC_LOG_FILENAME = 'woocommerce-square-sync';
|
16 |
+
|
17 |
+
/**
|
18 |
+
* Utilize WC logger class
|
19 |
+
*
|
20 |
+
* @since 1.0.0
|
21 |
+
* @version 1.0.0
|
22 |
+
*/
|
23 |
+
public static function log( $message, $start_time = null, $end_time = null ) {
|
24 |
+
if ( empty( self::$logger ) ) {
|
25 |
+
self::$logger = new WC_Logger();
|
26 |
+
}
|
27 |
+
|
28 |
+
$settings = get_option( 'woocommerce_squareconnect_settings', '' );
|
29 |
+
|
30 |
+
if ( ! empty( $settings['logging'] ) && 'yes' !== $settings['logging'] ) {
|
31 |
+
return;
|
32 |
+
}
|
33 |
+
|
34 |
+
if ( ! is_null( $start_time ) ) {
|
35 |
+
|
36 |
+
$formatted_start_time = date_i18n( get_option( 'date_format' ) . ' g:ia', $start_time );
|
37 |
+
$end_time = is_null( $end_time ) ? current_time( 'timestamp' ) : $end_time;
|
38 |
+
$formatted_end_time = date_i18n( get_option( 'date_format' ) . ' g:ia', $end_time );
|
39 |
+
$elapsed_time = round( abs( $end_time - $start_time ) / 60, 2 );
|
40 |
+
|
41 |
+
$log_entry = '====Start Log ' . $formatted_start_time . '====' . "\n" . $message . "\n";
|
42 |
+
$log_entry .= '====End Log ' . $formatted_end_time . ' (' . $elapsed_time . ')====' . "\n\n";
|
43 |
+
|
44 |
+
} else {
|
45 |
+
|
46 |
+
$log_entry = '====Start Log====' . "\n" . $message . "\n" . '====End Log====' . "\n\n";
|
47 |
+
|
48 |
+
}
|
49 |
+
|
50 |
+
self::$logger->add( self::WC_LOG_FILENAME, $log_entry );
|
51 |
+
}
|
52 |
+
}
|
includes/class-wc-square-sync-to-square-wp-hooks.php
ADDED
@@ -0,0 +1,312 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
if ( ! defined( 'ABSPATH' ) ) {
|
3 |
+
exit; // Exit if accessed directly
|
4 |
+
}
|
5 |
+
|
6 |
+
/**
|
7 |
+
* Class WC_Square_Sync_To_Square_WordPress_Hooks
|
8 |
+
*
|
9 |
+
* Attach WC_Square_Sync_To_Square methods to WordPress and WooCommerce core hooks.
|
10 |
+
*/
|
11 |
+
class WC_Square_Sync_To_Square_WordPress_Hooks {
|
12 |
+
|
13 |
+
/**
|
14 |
+
* @var WC_Integration
|
15 |
+
*/
|
16 |
+
protected $integration;
|
17 |
+
|
18 |
+
/**
|
19 |
+
* @var WC_Square_Sync_To_Square
|
20 |
+
*/
|
21 |
+
protected $square;
|
22 |
+
|
23 |
+
/**
|
24 |
+
* Whether or not to sync product data to Square.
|
25 |
+
*
|
26 |
+
* @var bool
|
27 |
+
*/
|
28 |
+
protected $sync_products;
|
29 |
+
|
30 |
+
/**
|
31 |
+
* @var bool
|
32 |
+
*/
|
33 |
+
protected $sync_categories;
|
34 |
+
|
35 |
+
/**
|
36 |
+
* @var bool
|
37 |
+
*/
|
38 |
+
protected $sync_images;
|
39 |
+
|
40 |
+
/**
|
41 |
+
* @var bool
|
42 |
+
*/
|
43 |
+
protected $sync_inventory;
|
44 |
+
|
45 |
+
/**
|
46 |
+
* Whether or not hooks should fire.
|
47 |
+
*
|
48 |
+
* @var bool
|
49 |
+
*/
|
50 |
+
protected $enabled = true;
|
51 |
+
|
52 |
+
/**
|
53 |
+
* WC_Square_Sync_To_Square_WordPress_Hooks constructor.
|
54 |
+
*
|
55 |
+
* @param WC_Integration $integration
|
56 |
+
* @param WC_Square_Sync_To_Square $square
|
57 |
+
*/
|
58 |
+
public function __construct( WC_Integration $integration, WC_Square_Sync_To_Square $square ) {
|
59 |
+
|
60 |
+
$this->integration = $integration;
|
61 |
+
$this->square = $square;
|
62 |
+
|
63 |
+
$this->sync_products = ( 'yes' === $integration->get_option( 'sync_products' ) );
|
64 |
+
$this->sync_categories = ( 'yes' === $integration->get_option( 'sync_categories' ) );
|
65 |
+
$this->sync_images = ( 'yes' === $integration->get_option( 'sync_images' ) );
|
66 |
+
$this->sync_inventory = ( 'yes' === $integration->get_option( 'sync_inventory' ) );
|
67 |
+
|
68 |
+
add_action( 'wc_square_loaded', array( $this, 'attach_hooks' ) );
|
69 |
+
add_action( 'wc_square_save_post_event', array( $this, 'process_save_post_event' ), 10, 2 );
|
70 |
+
add_action( 'wc_square_on_product_set_stock_event', array( $this, 'on_product_set_stock' ) );
|
71 |
+
add_action( 'wc_square_on_variation_set_stock_event', array( $this, 'on_variation_set_stock' ) );
|
72 |
+
}
|
73 |
+
|
74 |
+
/**
|
75 |
+
* Dynamically enable WP/WC hook callbacks.
|
76 |
+
*/
|
77 |
+
public function enable() {
|
78 |
+
|
79 |
+
$this->enabled = true;
|
80 |
+
|
81 |
+
}
|
82 |
+
|
83 |
+
/**
|
84 |
+
* Dynamically disable WP/WC hook callbacks.
|
85 |
+
*/
|
86 |
+
public function disable() {
|
87 |
+
|
88 |
+
$this->enabled = false;
|
89 |
+
|
90 |
+
}
|
91 |
+
|
92 |
+
/**
|
93 |
+
* Hook into WordPress and WooCommerce core.
|
94 |
+
*/
|
95 |
+
public function attach_hooks() {
|
96 |
+
|
97 |
+
if ( $this->sync_products ) {
|
98 |
+
if ( version_compare( WC_VERSION, '3.0.0', '<' ) ) {
|
99 |
+
add_action( 'save_post', array( $this, 'pre_wc_30_on_save_post' ), 10, 2 );
|
100 |
+
} else {
|
101 |
+
add_action( 'woocommerce_before_product_object_save', array( $this, 'on_save_post' ), 10, 2 );
|
102 |
+
}
|
103 |
+
}
|
104 |
+
|
105 |
+
if ( $this->sync_categories ) {
|
106 |
+
|
107 |
+
add_action( 'created_product_cat', array( $this, 'on_category_modified' ) );
|
108 |
+
|
109 |
+
add_action( 'edited_product_cat', array( $this, 'on_category_modified' ) );
|
110 |
+
|
111 |
+
}
|
112 |
+
|
113 |
+
if ( $this->sync_inventory ) {
|
114 |
+
$param = isset( $_GET['wc-api'] ) ? $_GET['wc-api'] : '';
|
115 |
+
|
116 |
+
if ( 'WC_Square_Integration' !== $param ) {
|
117 |
+
|
118 |
+
add_action( 'woocommerce_product_set_stock', array( $this, 'schedule_on_product_set_stock' ) );
|
119 |
+
|
120 |
+
add_action( 'woocommerce_variation_set_stock', array( $this, 'schedule_on_variation_set_stock' ) );
|
121 |
+
}
|
122 |
+
}
|
123 |
+
|
124 |
+
}
|
125 |
+
|
126 |
+
/**
|
127 |
+
* Sync a WC Product to Square when it is saved.
|
128 |
+
*
|
129 |
+
* @param int $post_id
|
130 |
+
* @param bool $sync_categories
|
131 |
+
* @param bool $sync_inventory
|
132 |
+
* @param bool $sync_images
|
133 |
+
*/
|
134 |
+
public function process_save_post_event( $post_id ) {
|
135 |
+
// clear inventory cache
|
136 |
+
delete_transient( 'wc_square_inventory' );
|
137 |
+
|
138 |
+
$wc_product = wc_get_product( $post_id );
|
139 |
+
|
140 |
+
if ( WC_Square_Utils::skip_product_sync( $post_id ) ) {
|
141 |
+
return;
|
142 |
+
}
|
143 |
+
|
144 |
+
if ( is_object( $wc_product ) && ! empty( $wc_product ) ) {
|
145 |
+
$this->square->sync_product( $wc_product, $this->sync_categories, $this->sync_inventory, $this->sync_images );
|
146 |
+
}
|
147 |
+
|
148 |
+
$this->delete_all_caches();
|
149 |
+
|
150 |
+
if ( version_compare( WC_VERSION, '3.0.0', '<' ) ) {
|
151 |
+
add_action( 'save_post', array( $this, 'pre_wc_30_on_save_post' ), 10, 2 );
|
152 |
+
} else {
|
153 |
+
add_action( 'woocommerce_before_product_object_save', array( $this, 'on_save_post' ), 10, 2 );
|
154 |
+
}
|
155 |
+
}
|
156 |
+
|
157 |
+
/**
|
158 |
+
* Trigger the save post event.
|
159 |
+
*
|
160 |
+
* @since 1.0.0
|
161 |
+
* @version 1.0.20
|
162 |
+
* @param object $product
|
163 |
+
* @param object $data_store
|
164 |
+
*/
|
165 |
+
public function on_save_post( $product, $data_store ) {
|
166 |
+
$post = get_post( $product->get_id() );
|
167 |
+
|
168 |
+
if ( ! $this->enabled
|
169 |
+
|| ( defined( 'DOING_AJAX' ) && DOING_AJAX ) // TODO: Look into removing this check.
|
170 |
+
|| ( defined( 'WP_LOAD_IMPORTERS' ) && WP_LOAD_IMPORTERS )
|
171 |
+
|| wp_is_post_revision( $post )
|
172 |
+
|| wp_is_post_autosave( $post )
|
173 |
+
|| ( 'publish' !== get_post_status( $post ) )
|
174 |
+
|| 'product' !== $post->post_type
|
175 |
+
) {
|
176 |
+
return;
|
177 |
+
}
|
178 |
+
|
179 |
+
$args = array(
|
180 |
+
$product->get_id(),
|
181 |
+
uniqid(), // this is needed due to WP not scheduling new events with same name and args
|
182 |
+
);
|
183 |
+
|
184 |
+
wp_schedule_single_event( time() + 60, 'wc_square_save_post_event', $args );
|
185 |
+
|
186 |
+
remove_action( 'woocommerce_before_product_object_save', array( $this, 'on_save_post' ) );
|
187 |
+
}
|
188 |
+
|
189 |
+
/**
|
190 |
+
* Trigger the save post event.
|
191 |
+
*
|
192 |
+
* @see 'save_post'
|
193 |
+
* @since 1.0.0
|
194 |
+
* @version 1.0.20
|
195 |
+
* @param $post_id
|
196 |
+
* @param $post
|
197 |
+
*/
|
198 |
+
public function pre_wc_30_on_save_post( $post_id, $post ) {
|
199 |
+
if ( ! $this->enabled
|
200 |
+
|| ( defined( 'DOING_AJAX' ) && DOING_AJAX ) // TODO: Look into removing this check.
|
201 |
+
|| ( defined( 'WP_LOAD_IMPORTERS' ) && WP_LOAD_IMPORTERS )
|
202 |
+
|| wp_is_post_revision( $post )
|
203 |
+
|| wp_is_post_autosave( $post )
|
204 |
+
|| ( 'publish' !== get_post_status( $post ) )
|
205 |
+
|| 'product' !== $post->post_type
|
206 |
+
) {
|
207 |
+
return;
|
208 |
+
}
|
209 |
+
|
210 |
+
$args = array(
|
211 |
+
$post_id,
|
212 |
+
uniqid(), // this is needed due to WP not scheduling new events with same name and args
|
213 |
+
);
|
214 |
+
|
215 |
+
wp_schedule_single_event( time() + 60, 'wc_square_save_post_event', $args );
|
216 |
+
|
217 |
+
remove_action( 'save_post', array( $this, 'pre_wc_30_on_save_post' ) );
|
218 |
+
}
|
219 |
+
|
220 |
+
/**
|
221 |
+
* Sync categories to Square when a category is created or altered.
|
222 |
+
*/
|
223 |
+
public function on_category_modified() {
|
224 |
+
|
225 |
+
if ( $this->enabled ) {
|
226 |
+
|
227 |
+
$this->square->sync_categories();
|
228 |
+
|
229 |
+
}
|
230 |
+
|
231 |
+
}
|
232 |
+
|
233 |
+
/**
|
234 |
+
* Schedule cron job when product stock is changed.
|
235 |
+
*
|
236 |
+
* @since 1.0.16
|
237 |
+
* @version 1.0.16
|
238 |
+
*/
|
239 |
+
public function schedule_on_product_set_stock( WC_Product $wc_product ) {
|
240 |
+
$args = array(
|
241 |
+
$wc_product,
|
242 |
+
uniqid(), // this is needed due to WP not scheduling new events with same name and args
|
243 |
+
);
|
244 |
+
|
245 |
+
$polling = get_transient( 'wc_square_polling' );
|
246 |
+
|
247 |
+
if ( $this->enabled && ! $polling ) {
|
248 |
+
wp_schedule_single_event( time() + 60, 'wc_square_on_product_set_stock_event', $args );
|
249 |
+
}
|
250 |
+
}
|
251 |
+
|
252 |
+
/**
|
253 |
+
* Schedule cron job when product variation stock is changed.
|
254 |
+
*
|
255 |
+
* @since 1.0.16
|
256 |
+
* @version 1.0.16
|
257 |
+
*/
|
258 |
+
public function schedule_on_variation_set_stock( WC_Product_Variation $wc_variation ) {
|
259 |
+
$args = array(
|
260 |
+
$wc_variation,
|
261 |
+
uniqid(), // this is needed due to WP not scheduling new events with same name and args
|
262 |
+
);
|
263 |
+
|
264 |
+
$polling = get_transient( 'wc_square_polling' );
|
265 |
+
|
266 |
+
if ( $this->enabled && ! $polling ) {
|
267 |
+
wp_schedule_single_event( time() + 60, 'wc_square_on_variation_set_stock_event', $args );
|
268 |
+
}
|
269 |
+
}
|
270 |
+
|
271 |
+
/**
|
272 |
+
* Sync inventory to Square when a product's stock is altered.
|
273 |
+
*
|
274 |
+
* @param array $wc_product
|
275 |
+
*/
|
276 |
+
public function on_product_set_stock( $wc_product ) {
|
277 |
+
$this->square->sync_inventory( $wc_product );
|
278 |
+
}
|
279 |
+
|
280 |
+
/**
|
281 |
+
* Sync inventory to Square when a variation's stock is altered.
|
282 |
+
*
|
283 |
+
* @param array $wc_variation
|
284 |
+
*/
|
285 |
+
public function on_variation_set_stock( $wc_variation ) {
|
286 |
+
$product = version_compare( WC_VERSION, '3.0.0', '<' ) ? $wc_variation->parent : wc_get_product( $wc_variation->get_parent_id() );
|
287 |
+
$this->square->sync_inventory( $product );
|
288 |
+
}
|
289 |
+
|
290 |
+
/**
|
291 |
+
* Deletes cached data ( both Square and WC )
|
292 |
+
*
|
293 |
+
* @access public
|
294 |
+
* @since 1.0.14
|
295 |
+
* @version 1.0.14
|
296 |
+
* @return bool
|
297 |
+
*/
|
298 |
+
public function delete_all_caches() {
|
299 |
+
|
300 |
+
delete_transient( 'wc_square_processing_total_count' );
|
301 |
+
|
302 |
+
delete_transient( 'wc_square_processing_ids' );
|
303 |
+
|
304 |
+
delete_transient( 'wc_square_syncing_square_inventory' );
|
305 |
+
|
306 |
+
delete_transient( 'sq_wc_sync_current_process' );
|
307 |
+
|
308 |
+
delete_transient( 'wc_square_inventory' );
|
309 |
+
|
310 |
+
return true;
|
311 |
+
}
|
312 |
+
}
|
includes/class-wc-square-sync-to-square.php
ADDED
@@ -0,0 +1,440 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
if ( ! defined( 'ABSPATH' ) ) {
|
3 |
+
exit; // Exit if accessed directly
|
4 |
+
}
|
5 |
+
|
6 |
+
/**
|
7 |
+
* Class WC_Square_Sync_To_Square
|
8 |
+
*
|
9 |
+
* Methods to Sync from WC to Square. Organized as "sync" methods that
|
10 |
+
* determine if "create" or "update" actions should be taken on the entities
|
11 |
+
* involved.
|
12 |
+
*/
|
13 |
+
class WC_Square_Sync_To_Square {
|
14 |
+
|
15 |
+
/**
|
16 |
+
* @var WC_Square_Connect
|
17 |
+
*/
|
18 |
+
protected $connect;
|
19 |
+
|
20 |
+
/**
|
21 |
+
* WC_Square_Sync_To_Square constructor.
|
22 |
+
*/
|
23 |
+
public function __construct( WC_Square_Connect $connect ) {
|
24 |
+
add_filter( 'woocommerce_duplicate_product_exclude_meta', array( $this, 'duplicate_product_remove_meta' ) );
|
25 |
+
|
26 |
+
$this->connect = $connect;
|
27 |
+
|
28 |
+
}
|
29 |
+
|
30 |
+
/**
|
31 |
+
* Removes certain product meta when product is duplicated in WC to
|
32 |
+
* prevent overwriting the original item on Square.
|
33 |
+
*
|
34 |
+
* @access public
|
35 |
+
* @since 1.0.4
|
36 |
+
* @version 1.0.4
|
37 |
+
* @return array $metas;
|
38 |
+
*/
|
39 |
+
public function duplicate_product_remove_meta( $metas ) {
|
40 |
+
$metas[] = '_square_item_id';
|
41 |
+
$metas[] = '_square_item_variation_id';
|
42 |
+
|
43 |
+
return $metas;
|
44 |
+
}
|
45 |
+
|
46 |
+
/**
|
47 |
+
* Sync WooCommerce categories to Square.
|
48 |
+
*
|
49 |
+
* Looks for category names that don't exist in Square, and creates them.
|
50 |
+
*/
|
51 |
+
public function sync_categories() {
|
52 |
+
|
53 |
+
$wc_category_objects = $this->connect->wc->get_product_categories();
|
54 |
+
$wc_categories = array();
|
55 |
+
|
56 |
+
if ( is_wp_error( $wc_category_objects ) || empty( $wc_category_objects['product_categories'] ) ) {
|
57 |
+
return;
|
58 |
+
}
|
59 |
+
|
60 |
+
foreach ( $wc_category_objects['product_categories'] as $wc_category ) {
|
61 |
+
|
62 |
+
if ( empty( $wc_category['name'] ) || empty( $wc_category['id'] ) || ( $wc_category['parent'] !== 0 ) ) {
|
63 |
+
continue;
|
64 |
+
}
|
65 |
+
|
66 |
+
$wc_categories[ $wc_category['name'] ] = $wc_category['id'];
|
67 |
+
|
68 |
+
}
|
69 |
+
|
70 |
+
$square_category_objects = $this->connect->get_square_categories();
|
71 |
+
$square_categories = array();
|
72 |
+
$processed_categories = array();
|
73 |
+
|
74 |
+
foreach ( $square_category_objects as $square_category ) {
|
75 |
+
// Square list endpoints may return dups so we need to check for that
|
76 |
+
if ( in_array( $square_category->id, $processed_categories ) ) {
|
77 |
+
continue;
|
78 |
+
}
|
79 |
+
|
80 |
+
if ( is_object( $square_category ) && ! empty( $square_category->name ) && ! empty( $square_category->id ) ) {
|
81 |
+
$square_categories[ $square_category->name ] = $square_category->id;
|
82 |
+
$processed_categories[] = $square_category->id;
|
83 |
+
}
|
84 |
+
}
|
85 |
+
|
86 |
+
foreach ( $wc_categories as $wc_cat_name => $wc_cat_id ) {
|
87 |
+
|
88 |
+
$square_cat_id = WC_Square_Utils::get_wc_term_square_id( $wc_cat_id );
|
89 |
+
|
90 |
+
if ( $square_cat_id && ( $square_cat_name = array_search( $square_cat_id, $square_categories ) ) ) {
|
91 |
+
|
92 |
+
// Update a known Square Category whose name has changed in WC.
|
93 |
+
if ( $wc_cat_name !== $square_cat_name ) {
|
94 |
+
|
95 |
+
$this->connect->update_square_category( $square_cat_id, $wc_cat_name );
|
96 |
+
|
97 |
+
}
|
98 |
+
|
99 |
+
} elseif ( isset( $square_categories[ $wc_cat_name ] ) ) {
|
100 |
+
|
101 |
+
// Store the Square Category ID on a WC term that matches.
|
102 |
+
$square_category_id = $square_categories[ $wc_cat_name ];
|
103 |
+
|
104 |
+
WC_Square_Utils::update_wc_term_square_id( $wc_cat_id, $square_category_id );
|
105 |
+
|
106 |
+
} else {
|
107 |
+
|
108 |
+
// Create a new Square Category for a WC term that doesn't yet exist.
|
109 |
+
$response = $this->connect->create_square_category( $wc_cat_name );
|
110 |
+
|
111 |
+
if ( ! empty( $response->id ) ) {
|
112 |
+
|
113 |
+
$square_category_id = $response->id;
|
114 |
+
|
115 |
+
WC_Square_Utils::update_wc_term_square_id( $wc_cat_id, $square_category_id );
|
116 |
+
|
117 |
+
}
|
118 |
+
|
119 |
+
}
|
120 |
+
|
121 |
+
}
|
122 |
+
|
123 |
+
}
|
124 |
+
|
125 |
+
/**
|
126 |
+
* Find the Square Item that corresponds to the given WC Product.
|
127 |
+
*
|
128 |
+
* First searches for a Square Item ID in the WC Product metadata,
|
129 |
+
* then compares all WC Product SKUs against all Square Items.
|
130 |
+
*
|
131 |
+
* @param WC_Product $wc_product
|
132 |
+
* @return object|bool Square Item object on success, boolean false if no Square Item found.
|
133 |
+
*/
|
134 |
+
public function get_square_item_for_wc_product( WC_Product $wc_product ) {
|
135 |
+
|
136 |
+
$product_id = version_compare( WC_VERSION, '3.0.0', '<' ) ? $wc_product->id : $wc_product->get_id();
|
137 |
+
|
138 |
+
if ( 'variation' === ( version_compare( WC_VERSION, '3.0.0', '<' ) ? $wc_product->product_type : $wc_product->get_type() ) ) {
|
139 |
+
|
140 |
+
$product_id = version_compare( WC_VERSION, '3.0.0', '<' ) ? $wc_product->variation_id : $wc_product->get_id();
|
141 |
+
|
142 |
+
}
|
143 |
+
|
144 |
+
if ( $square_item_id = WC_Square_Utils::get_wc_product_square_id( $product_id ) ) {
|
145 |
+
|
146 |
+
return $this->connect->get_square_product( $square_item_id );
|
147 |
+
|
148 |
+
}
|
149 |
+
|
150 |
+
$wc_product_skus = WC_Square_Utils::get_wc_product_skus( $wc_product );
|
151 |
+
|
152 |
+
return $this->connect->square_product_exists( $wc_product_skus );
|
153 |
+
|
154 |
+
}
|
155 |
+
|
156 |
+
/**
|
157 |
+
* Sync a WC Product to Square, optionally including Categories and Inventory.
|
158 |
+
*
|
159 |
+
* @param WC_Product $wc_product
|
160 |
+
* @param bool $include_category
|
161 |
+
* @param bool $include_inventory
|
162 |
+
* @param bool $include_image
|
163 |
+
*/
|
164 |
+
public function sync_product( WC_Product $wc_product, $include_category = false, $include_inventory = false, $include_image = false ) {
|
165 |
+
$create = false;
|
166 |
+
|
167 |
+
// Only sync products with a SKU
|
168 |
+
$wc_product_skus = WC_Square_Utils::get_wc_product_skus( $wc_product );
|
169 |
+
|
170 |
+
if ( empty( $wc_product_skus ) ) {
|
171 |
+
|
172 |
+
WC_Square_Sync_Logger::log( sprintf( '[WC -> Square] Skipping WC Product %d since it has no SKUs.', version_compare( WC_VERSION, '3.0.0', '<' ) ? $wc_product->id : $wc_product->get_id() ) );
|
173 |
+
return;
|
174 |
+
|
175 |
+
}
|
176 |
+
|
177 |
+
// Look for a Square Item with a matching SKU
|
178 |
+
$square_item = $this->get_square_item_for_wc_product( $wc_product );
|
179 |
+
|
180 |
+
// SKU found, update Item
|
181 |
+
if ( WC_Square_Utils::is_square_item_found( $square_item ) ) {
|
182 |
+
|
183 |
+
$result = $this->update_product( $wc_product, $square_item, $include_category, $include_inventory );
|
184 |
+
|
185 |
+
// No matching SKU found, create new Item
|
186 |
+
} else {
|
187 |
+
$create = true;
|
188 |
+
$result = $this->create_product( $wc_product, $include_category, $include_inventory );
|
189 |
+
}
|
190 |
+
|
191 |
+
// Sync inventory if create/update was successful
|
192 |
+
// TODO: consider whether or not this should be part of sync_product()..
|
193 |
+
if ( $result ) {
|
194 |
+
if ( $include_inventory ) {
|
195 |
+
$this->sync_inventory( $wc_product, $create );
|
196 |
+
|
197 |
+
}
|
198 |
+
|
199 |
+
if ( $include_image ) {
|
200 |
+
|
201 |
+
$this->sync_product_image( $wc_product, $result );
|
202 |
+
|
203 |
+
}
|
204 |
+
|
205 |
+
} else {
|
206 |
+
|
207 |
+
WC_Square_Sync_Logger::log( sprintf( '[WC -> Square] Error syncing WC Product %d.', version_compare( WC_VERSION, '3.0.0', '<' ) ? $wc_product->id : $wc_product->get_id() ) );
|
208 |
+
|
209 |
+
}
|
210 |
+
|
211 |
+
}
|
212 |
+
|
213 |
+
/**
|
214 |
+
* Sync a WC Product's inventory to Square
|
215 |
+
*
|
216 |
+
* @since 1.0.0
|
217 |
+
* @version 1.0.14
|
218 |
+
* @param WC_Product $wc_product
|
219 |
+
* @param bool $create
|
220 |
+
*/
|
221 |
+
public function sync_inventory( WC_Product $wc_product, $create = false ) {
|
222 |
+
// refresh cache first to get the latest inventory
|
223 |
+
$this->connect->refresh_inventory_cache();
|
224 |
+
|
225 |
+
// If not creating new product we need to first retrieve inventory.
|
226 |
+
if ( ! $create ) {
|
227 |
+
$square_inventory = $this->connect->get_square_inventory();
|
228 |
+
}
|
229 |
+
|
230 |
+
$wc_variation_ids = WC_Square_Utils::get_stock_managed_wc_variation_ids( $wc_product );
|
231 |
+
|
232 |
+
foreach ( $wc_variation_ids as $wc_variation_id ) {
|
233 |
+
|
234 |
+
$square_variation_id = WC_Square_Utils::get_wc_variation_square_id( $wc_variation_id );
|
235 |
+
|
236 |
+
if ( $square_variation_id || ( ! $create && isset( $square_inventory[ $square_variation_id ] ) ) ) {
|
237 |
+
|
238 |
+
$wc_stock = (int) get_post_meta( $wc_variation_id, '_stock', true );
|
239 |
+
|
240 |
+
$square_stock = 0;
|
241 |
+
|
242 |
+
if ( ! $create && isset( $square_inventory[ $square_variation_id ] ) ) {
|
243 |
+
$square_stock = (int) $square_inventory[ $square_variation_id ];
|
244 |
+
}
|
245 |
+
|
246 |
+
$delta = $wc_stock - $square_stock;
|
247 |
+
|
248 |
+
$result = $this->connect->update_square_inventory( $square_variation_id, $delta );
|
249 |
+
|
250 |
+
if ( ! $result ) {
|
251 |
+
|
252 |
+
WC_Square_Sync_Logger::log( sprintf( '[WC -> Square] Error syncing inventory for WC Product %d.', version_compare( WC_VERSION, '3.0.0', '<' ) ? $wc_product->id : $wc_product->get_id() ) );
|
253 |
+
|
254 |
+
}
|
255 |
+
|
256 |
+
}
|
257 |
+
|
258 |
+
}
|
259 |
+
|
260 |
+
}
|
261 |
+
|
262 |
+
/**
|
263 |
+
* Create a Square Item for a WC Product
|
264 |
+
*
|
265 |
+
* @param WC_Product $wc_product
|
266 |
+
* @param bool $include_category
|
267 |
+
* @param bool $include_inventory
|
268 |
+
*
|
269 |
+
* @return object|bool Created Square Item object on success, boolean False on failure.
|
270 |
+
*/
|
271 |
+
public function create_product( WC_Product $wc_product, $include_category = false, $include_inventory = false ) {
|
272 |
+
|
273 |
+
$square_item = $this->connect->create_square_product( $wc_product, $include_category, $include_inventory );
|
274 |
+
|
275 |
+
if ( $square_item ) {
|
276 |
+
|
277 |
+
WC_Square_Utils::set_square_ids_on_wc_product_by_sku( $wc_product, $square_item );
|
278 |
+
|
279 |
+
return $square_item;
|
280 |
+
|
281 |
+
}
|
282 |
+
|
283 |
+
WC_Square_Sync_Logger::log( sprintf( '[WC -> Square] Error creating Square Item for WC Product %d.', version_compare( WC_VERSION, '3.0.0', '<' ) ? $wc_product->id : $wc_product->get_id() ) );
|
284 |
+
|
285 |
+
return false;
|
286 |
+
|
287 |
+
}
|
288 |
+
|
289 |
+
/**
|
290 |
+
* Update a Square Item for a WC Product
|
291 |
+
*
|
292 |
+
* @param WC_Product $wc_product
|
293 |
+
* @param object $square_item
|
294 |
+
* @param bool $include_category
|
295 |
+
* @param bool $include_inventory
|
296 |
+
*
|
297 |
+
* @return object|bool Updated Square Item object on success, boolean False on failure.
|
298 |
+
*/
|
299 |
+
public function update_product( WC_Product $wc_product, $square_item, $include_category = false, $include_inventory = false ) {
|
300 |
+
|
301 |
+
$square_item = $this->connect->update_square_product( $wc_product, $square_item->id, $include_category, $include_inventory );
|
302 |
+
|
303 |
+
if ( ! $square_item ) {
|
304 |
+
|
305 |
+
WC_Square_Sync_Logger::log( sprintf( '[WC -> Square] Error updating Square Item ID %s (WC Product %d).', $square_item->id, version_compare( WC_VERSION, '3.0.0', '<' ) ? $wc_product->id : $wc_product->get_id() ) );
|
306 |
+
return false;
|
307 |
+
|
308 |
+
}
|
309 |
+
|
310 |
+
WC_Square_Utils::update_wc_product_square_id( version_compare( WC_VERSION, '3.0.0', '<' ) ? $wc_product->id : $wc_product->get_id(), $square_item->id );
|
311 |
+
|
312 |
+
if ( 'simple' === ( version_compare( WC_VERSION, '3.0.0', '<' ) ? $wc_product->product_type : $wc_product->get_type() ) ) {
|
313 |
+
|
314 |
+
$wc_variations = array( $wc_product );
|
315 |
+
|
316 |
+
} elseif ( 'variable' === ( version_compare( WC_VERSION, '3.0.0', '<' ) ? $wc_product->product_type : $wc_product->get_type() ) ) {
|
317 |
+
|
318 |
+
$wc_variations = WC_Square_Utils::get_wc_product_variations( $wc_product );
|
319 |
+
|
320 |
+
}
|
321 |
+
|
322 |
+
foreach ( $wc_variations as $wc_variation ) {
|
323 |
+
$product_type = version_compare( WC_VERSION, '3.0.0', '<' ) ? $wc_variation->product_type : $wc_variation->get_type();
|
324 |
+
$variation_data = WC_Square_Utils::format_wc_variation_for_square_api( $wc_variation, $include_inventory );
|
325 |
+
|
326 |
+
if ( 'variation' === $product_type ) {
|
327 |
+
$wc_variation_id = version_compare( WC_VERSION, '3.0.0', '<' ) ? $wc_variation->variation_id : $wc_variation->get_id();
|
328 |
+
} else {
|
329 |
+
$wc_variation_id = version_compare( WC_VERSION, '3.0.0', '<' ) ? $wc_variation->id : $wc_variation->get_id();
|
330 |
+
}
|
331 |
+
|
332 |
+
if ( $square_variation_id = WC_Square_Utils::get_wc_variation_square_id( $wc_variation_id ) ) {
|
333 |
+
|
334 |
+
$result = $this->connect->update_square_variation( $square_item->id, $square_variation_id, $variation_data );
|
335 |
+
|
336 |
+
} else {
|
337 |
+
|
338 |
+
$result = $this->connect->create_square_variation( $square_item->id, $variation_data );
|
339 |
+
|
340 |
+
if ( $result && isset( $result->id ) ) {
|
341 |
+
|
342 |
+
WC_Square_Utils::update_wc_variation_square_id( $wc_variation_id, $result->id );
|
343 |
+
|
344 |
+
}
|
345 |
+
|
346 |
+
}
|
347 |
+
|
348 |
+
if ( ! $result ) {
|
349 |
+
|
350 |
+
if ( $square_variation_id ) {
|
351 |
+
|
352 |
+
WC_Square_Sync_Logger::log( sprintf( '[WC -> Square] Error updating Square ItemVariation %s for WC Variation %d.', $square_variation_id, $wc_variation_id ) );
|
353 |
+
|
354 |
+
} else {
|
355 |
+
|
356 |
+
WC_Square_Sync_Logger::log( sprintf( '[WC -> Square] Error creating Square ItemVariation for WC Variation %d.', $wc_variation_id ) );
|
357 |
+
|
358 |
+
}
|
359 |
+
|
360 |
+
}
|
361 |
+
|
362 |
+
}
|
363 |
+
|
364 |
+
return $square_item;
|
365 |
+
|
366 |
+
}
|
367 |
+
|
368 |
+
/**
|
369 |
+
* Sync a WC Product's Image to Square
|
370 |
+
*
|
371 |
+
* @param WC_Product $wc_product WC Product to sync Item Image for.
|
372 |
+
* @param object $square_item Optional. Corresponding Square Item object for $wc_product.
|
373 |
+
*
|
374 |
+
* @return bool Success.
|
375 |
+
*/
|
376 |
+
public function sync_product_image( WC_Product $wc_product, $square_item = null ) {
|
377 |
+
|
378 |
+
if ( is_null( $square_item ) ) {
|
379 |
+
|
380 |
+
$square_item = $this->get_square_item_for_wc_product( $wc_product );
|
381 |
+
|
382 |
+
}
|
383 |
+
|
384 |
+
if ( ! $square_item ) {
|
385 |
+
|
386 |
+
WC_Square_Sync_Logger::log( sprintf( '[WC -> Square] Image Sync: No Square Item found for WC Product %d.', version_compare( WC_VERSION, '3.0.0', '<' ) ? $wc_product->id : $wc_product->get_id() ) );
|
387 |
+
return false;
|
388 |
+
|
389 |
+
}
|
390 |
+
|
391 |
+
if ( ! has_post_thumbnail( version_compare( WC_VERSION, '3.0.0', '<' ) ? $wc_product->id : $wc_product->get_id() ) ) {
|
392 |
+
|
393 |
+
WC_Square_Sync_Logger::log( sprintf( '[WC -> Square] Image Sync: Skipping WC Product %d (no post thumbnail set).', version_compare( WC_VERSION, '3.0.0', '<' ) ? $wc_product->id : $wc_product->get_id() ) );
|
394 |
+
return true;
|
395 |
+
|
396 |
+
}
|
397 |
+
|
398 |
+
return $this->update_product_image( $wc_product, $square_item->id );
|
399 |
+
|
400 |
+
}
|
401 |
+
|
402 |
+
/**
|
403 |
+
* Update a Square Item Image for a WC Product
|
404 |
+
*
|
405 |
+
* @param WC_Product $wc_product
|
406 |
+
* @param string $square_item_id
|
407 |
+
* @return bool Success.
|
408 |
+
*/
|
409 |
+
public function update_product_image( WC_Product $wc_product, $square_item_id ) {
|
410 |
+
|
411 |
+
$image_id = get_post_thumbnail_id( version_compare( WC_VERSION, '3.0.0', '<' ) ? $wc_product->id : $wc_product->get_id() );
|
412 |
+
|
413 |
+
if ( empty( $image_id ) ) {
|
414 |
+
|
415 |
+
WC_Square_Sync_Logger::log( sprintf( '[WC -> Square] Update Product Image: No thumbnail ID for WC Product %d.', version_compare( WC_VERSION, '3.0.0', '<' ) ? $wc_product->id : $wc_product->get_id() ) );
|
416 |
+
return true;
|
417 |
+
|
418 |
+
}
|
419 |
+
|
420 |
+
$mime_type = get_post_field( 'post_mime_type', $image_id, 'raw' );
|
421 |
+
$image_path = get_attached_file( $image_id );
|
422 |
+
|
423 |
+
$result = $this->connect->update_square_product_image( $square_item_id, $mime_type, $image_path );
|
424 |
+
|
425 |
+
if ( $result && isset( $result->id ) ) {
|
426 |
+
|
427 |
+
WC_Square_Utils::update_wc_product_image_square_id( version_compare( WC_VERSION, '3.0.0', '<' ) ? $wc_product->id : $wc_product->get_id(), $result->id );
|
428 |
+
|
429 |
+
return true;
|
430 |
+
|
431 |
+
} else {
|
432 |
+
|
433 |
+
WC_Square_Sync_Logger::log( sprintf( '[WC -> Square] Error updating Product Image for WC Product %d.', version_compare( WC_VERSION, '3.0.0', '<' ) ? $wc_product->id : $wc_product->get_id() ) );
|
434 |
+
return false;
|
435 |
+
|
436 |
+
}
|
437 |
+
|
438 |
+
}
|
439 |
+
|
440 |
+
}
|
includes/class-wc-square-utils.php
ADDED
@@ -0,0 +1,918 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
if ( ! defined( 'ABSPATH' ) ) {
|
3 |
+
exit; // Exit if accessed directly
|
4 |
+
}
|
5 |
+
|
6 |
+
/**
|
7 |
+
* Class WC_Square_Utils
|
8 |
+
*
|
9 |
+
* Static helper methods for the WC <-> Square integration, used in multiple
|
10 |
+
* places throughout the extension, with no dependencies of their own.
|
11 |
+
*
|
12 |
+
* Mostly data formatting and entity retrieval methods.
|
13 |
+
*/
|
14 |
+
class WC_Square_Utils {
|
15 |
+
|
16 |
+
const WC_TERM_SQUARE_ID = 'square_cat_id';
|
17 |
+
const WC_PRODUCT_SQUARE_ID = '_square_item_id';
|
18 |
+
const WC_VARIATION_SQUARE_ID = '_square_item_variation_id';
|
19 |
+
const WC_PRODUCT_IMAGE_SQUARE_ID = '_square_item_image_id';
|
20 |
+
|
21 |
+
/**
|
22 |
+
* Convert a WC Product or Variation into a Square ItemVariation
|
23 |
+
* See: https://docs.connect.squareup.com/api/connect/v1/#datatype-itemvariation
|
24 |
+
*
|
25 |
+
* @param WC_Product|WC_Product_Variation $variation
|
26 |
+
* @param bool $include_inventory
|
27 |
+
* @return array Formatted as a Square ItemVariation
|
28 |
+
*/
|
29 |
+
public static function format_wc_variation_for_square_api( $variation, $include_inventory = false ) {
|
30 |
+
|
31 |
+
$formatted = array(
|
32 |
+
'name' => null,
|
33 |
+
'pricing_type' => null,
|
34 |
+
'price_money' => null,
|
35 |
+
'sku' => null,
|
36 |
+
'track_inventory' => null,
|
37 |
+
'inventory_alert_type' => null,
|
38 |
+
'inventory_alert_threshold' => null,
|
39 |
+
'user_data' => null,
|
40 |
+
);
|
41 |
+
|
42 |
+
if ( $variation instanceof WC_Product ) {
|
43 |
+
|
44 |
+
$formatted['name'] = __( 'Regular', 'woocommerce-square' );
|
45 |
+
$formatted['price_money'] = array(
|
46 |
+
'currency_code' => apply_filters( 'woocommerce_square_currency', get_woocommerce_currency() ),
|
47 |
+
'amount' => (int) WC_Square_Utils::format_amount_to_square( version_compare( WC_VERSION, '3.0.0', '<' ) ? $variation->get_display_price() : wc_get_price_excluding_tax( $variation ) ),
|
48 |
+
);
|
49 |
+
$formatted['sku'] = $variation->get_sku();
|
50 |
+
|
51 |
+
if ( $include_inventory && $variation->managing_stock() ) {
|
52 |
+
$formatted['track_inventory'] = true;
|
53 |
+
}
|
54 |
+
}
|
55 |
+
|
56 |
+
if ( $variation instanceof WC_Product_Variation ) {
|
57 |
+
|
58 |
+
$formatted['name'] = implode( ', ', $variation->get_variation_attributes() );
|
59 |
+
|
60 |
+
}
|
61 |
+
|
62 |
+
return array_filter( $formatted );
|
63 |
+
|
64 |
+
}
|
65 |
+
|
66 |
+
/**
|
67 |
+
* Convert a WC Product into a Square Item for Update
|
68 |
+
*
|
69 |
+
* Updates (PUT) don't accept the same parameters (namely variations) as Creation
|
70 |
+
* See: https://docs.connect.squareup.com/api/connect/v1/index.html#put-itemid
|
71 |
+
*
|
72 |
+
* @param WC_Product $wc_product
|
73 |
+
* @param bool $include_category
|
74 |
+
* @return array
|
75 |
+
*/
|
76 |
+
public static function format_wc_product_update_for_square_api( WC_Product $wc_product, $include_category = false ) {
|
77 |
+
|
78 |
+
$formatted = array(
|
79 |
+
'name' => $wc_product->get_title(),
|
80 |
+
'description' => wp_strip_all_tags( version_compare( WC_VERSION, '3.0.0', '<' ) ? $wc_product->post->post_content : $wc_product->get_description() ),
|
81 |
+
'visibility' => 'PUBLIC',
|
82 |
+
);
|
83 |
+
|
84 |
+
if ( $include_category ) {
|
85 |
+
$square_cat_id = self::get_square_category_id_for_wc_product( $wc_product );
|
86 |
+
|
87 |
+
if ( $square_cat_id ) {
|
88 |
+
$formatted['category_id'] = $square_cat_id;
|
89 |
+
}
|
90 |
+
}
|
91 |
+
|
92 |
+
return array_filter( $formatted );
|
93 |
+
}
|
94 |
+
|
95 |
+
/**
|
96 |
+
* Convert a WC Product into a Square Item for Create
|
97 |
+
*
|
98 |
+
* Creation (POST) allows more parameters than Updating, namely variations
|
99 |
+
* See: https://docs.connect.squareup.com/api/connect/v1/index.html#post-items
|
100 |
+
*
|
101 |
+
* @param WC_Product $wc_product
|
102 |
+
* @param bool $include_category
|
103 |
+
* @param bool $include_inventory
|
104 |
+
* @return array
|
105 |
+
*/
|
106 |
+
public static function format_wc_product_create_for_square_api( WC_Product $wc_product, $include_category = false, $include_inventory = false ) {
|
107 |
+
|
108 |
+
$formatted = self::format_wc_product_update_for_square_api( $wc_product, $include_category );
|
109 |
+
|
110 |
+
// check product type
|
111 |
+
if ( 'simple' === ( version_compare( WC_VERSION, '3.0.0', '<' ) ? $wc_product->product_type : $wc_product->get_type() ) ) {
|
112 |
+
|
113 |
+
$formatted['variations'] = array(
|
114 |
+
WC_Square_Utils::format_wc_variation_for_square_api( $wc_product, $include_inventory ),
|
115 |
+
);
|
116 |
+
|
117 |
+
} elseif ( 'variable' === ( version_compare( WC_VERSION, '3.0.0', '<' ) ? $wc_product->product_type : $wc_product->get_type() ) ) {
|
118 |
+
|
119 |
+
$wc_variations = self::get_wc_product_variations( $wc_product );
|
120 |
+
|
121 |
+
foreach ( (array) $wc_variations as $wc_variation ) {
|
122 |
+
|
123 |
+
$formatted['variations'][] = WC_Square_Utils::format_wc_variation_for_square_api( $wc_variation, $include_inventory );
|
124 |
+
}
|
125 |
+
}
|
126 |
+
|
127 |
+
return array_filter( $formatted );
|
128 |
+
}
|
129 |
+
|
130 |
+
/**
|
131 |
+
* Map existing WC Variation IDs to a formatted product update array.
|
132 |
+
*
|
133 |
+
* Square ItemVariations are matched to their WC Variation equivalents via SKU.
|
134 |
+
*
|
135 |
+
* @param WC_Product $wc_product
|
136 |
+
* @param array $product_update Formatted product update. @see WC_Square_Utils::format_square_item_for_wc_api
|
137 |
+
*
|
138 |
+
* @return array WC Product formatted for update, with Variation IDs mapped.
|
139 |
+
*/
|
140 |
+
public static function set_wc_variation_ids_for_update( WC_Product $wc_product, $product_update ) {
|
141 |
+
|
142 |
+
if ( ( 'variable' === ( version_compare( WC_VERSION, '3.0.0', '<' ) ? $wc_product->product_type : $wc_product->get_type() ) ) && isset( $product_update['variations'] ) ) {
|
143 |
+
|
144 |
+
$wc_variations = self::get_wc_product_variations( $wc_product );
|
145 |
+
|
146 |
+
$wc_variation_sku_id_map = array();
|
147 |
+
|
148 |
+
foreach ( $wc_variations as $wc_variation ) {
|
149 |
+
$wc_variation_sku = $wc_variation->get_sku();
|
150 |
+
|
151 |
+
$wc_variation_id = version_compare( WC_VERSION, '3.0.0', '<' ) ? $wc_variation->variation_id : $wc_variation->get_id();
|
152 |
+
|
153 |
+
if ( ! empty( $wc_variation_sku ) && ! empty( $wc_variation_id ) ) {
|
154 |
+
|
155 |
+
$wc_variation_sku_id_map[ $wc_variation_sku ] = $wc_variation_id;
|
156 |
+
}
|
157 |
+
}
|
158 |
+
|
159 |
+
foreach ( (array) $product_update['variations'] as $idx => $variation ) {
|
160 |
+
if ( ! empty( $variation['sku'] ) && isset( $wc_variation_sku_id_map[ $variation['sku'] ] ) ) {
|
161 |
+
$product_update['variations'][ $idx ]['id'] = $wc_variation_sku_id_map[ $variation['sku'] ];
|
162 |
+
}
|
163 |
+
}
|
164 |
+
}
|
165 |
+
|
166 |
+
return $product_update;
|
167 |
+
}
|
168 |
+
|
169 |
+
/**
|
170 |
+
* Format a Square Item for an UPDATE through the WC Product API
|
171 |
+
*
|
172 |
+
* See: https://docs.connect.squareup.com/api/connect/v1/#datatype-item
|
173 |
+
* See: https://woothemes.github.io/woocommerce-rest-api-docs/#products-properties
|
174 |
+
*
|
175 |
+
* @param object $square_item
|
176 |
+
* @param WC_Product $wc_product
|
177 |
+
* @param bool $include_category
|
178 |
+
* @param bool $include_inventory
|
179 |
+
* @param bool $include_image
|
180 |
+
*
|
181 |
+
* @return array
|
182 |
+
*/
|
183 |
+
public static function format_square_item_for_wc_api_update( $square_item, WC_Product $wc_product, $include_category = false, $include_inventory = false, $include_image = false ) {
|
184 |
+
|
185 |
+
$formatted = self::format_square_item_for_wc_api_create( $square_item, $include_category, $include_inventory, $include_image );
|
186 |
+
|
187 |
+
return self::set_wc_variation_ids_for_update( $wc_product, $formatted );
|
188 |
+
}
|
189 |
+
|
190 |
+
/**
|
191 |
+
* Format a Square Item for a CREATE through the WC Product API
|
192 |
+
*
|
193 |
+
* See: https://docs.connect.squareup.com/api/connect/v1/#datatype-item
|
194 |
+
* See: https://woothemes.github.io/woocommerce-rest-api-docs/#products-properties
|
195 |
+
*
|
196 |
+
* @param object $square_item
|
197 |
+
* @param bool $include_inventory
|
198 |
+
* @param bool $include_image
|
199 |
+
*
|
200 |
+
* @return array
|
201 |
+
*/
|
202 |
+
public static function format_square_item_for_wc_api_create( $square_item, $include_category = false, $include_inventory = false, $include_image = false ) {
|
203 |
+
|
204 |
+
$formatted = array(
|
205 |
+
'title' => $square_item->name,
|
206 |
+
);
|
207 |
+
|
208 |
+
if ( apply_filters( 'woocommerce_square_sync_from_square_description', false ) ) {
|
209 |
+
$description = ! empty( $square_item->description ) ? $square_item->description : '';
|
210 |
+
$formatted['description'] = $description;
|
211 |
+
}
|
212 |
+
|
213 |
+
if ( $include_image && isset( $square_item->master_image->url ) ) {
|
214 |
+
|
215 |
+
$formatted['images'] = array(
|
216 |
+
array(
|
217 |
+
'position' => 0,
|
218 |
+
'src' => $square_item->master_image->url,
|
219 |
+
),
|
220 |
+
);
|
221 |
+
}
|
222 |
+
|
223 |
+
if ( $include_category && isset( $square_item->category->id ) ) {
|
224 |
+
$wc_cat_id = self::get_wc_category_id_for_square_category_id( $square_item->category->id );
|
225 |
+
|
226 |
+
if ( $wc_cat_id ) {
|
227 |
+
$formatted['categories'] = array( $wc_cat_id );
|
228 |
+
}
|
229 |
+
}
|
230 |
+
|
231 |
+
if ( count( $square_item->variations ) > 1 ) {
|
232 |
+
|
233 |
+
$formatted['type'] = 'variable';
|
234 |
+
$formatted['variations'] = array();
|
235 |
+
|
236 |
+
foreach ( $square_item->variations as $square_item_variation ) {
|
237 |
+
|
238 |
+
$formatted['variations'][] = self::format_square_item_variation_for_wc_api( $square_item_variation, $include_inventory );
|
239 |
+
|
240 |
+
}
|
241 |
+
|
242 |
+
$formatted['attributes'] = array(
|
243 |
+
array(
|
244 |
+
'name' => 'Attribute',
|
245 |
+
'slug' => 'attribute',
|
246 |
+
'position' => 0,
|
247 |
+
'visible' => true,
|
248 |
+
'variation' => true,
|
249 |
+
'options' => wp_list_pluck( $square_item->variations, 'name' ),
|
250 |
+
),
|
251 |
+
);
|
252 |
+
|
253 |
+
} else {
|
254 |
+
|
255 |
+
$variation = self::format_square_item_variation_for_wc_api( $square_item->variations[0], $include_inventory );
|
256 |
+
|
257 |
+
$formatted['type'] = 'simple';
|
258 |
+
$formatted['sku'] = isset( $variation['sku'] ) ? $variation['sku'] : null;
|
259 |
+
$formatted['regular_price'] = isset( $variation['regular_price'] ) ? $variation['regular_price'] : null;
|
260 |
+
$formatted['stock_quantity'] = isset( $variation['stock_quantity'] ) ? $variation['stock_quantity'] : null;
|
261 |
+
$formatted['managing_stock'] = isset( $variation['managing_stock'] ) ? $variation['managing_stock'] : null;
|
262 |
+
|
263 |
+
}
|
264 |
+
|
265 |
+
return array_filter( $formatted );
|
266 |
+
}
|
267 |
+
|
268 |
+
/**
|
269 |
+
* Convert a Square ItemVariation for the WC Product API
|
270 |
+
*
|
271 |
+
* See: https://docs.connect.squareup.com/api/connect/v1/#datatype-itemvariation
|
272 |
+
* See: https://woothemes.github.io/woocommerce-rest-api-docs/#products-properties
|
273 |
+
*
|
274 |
+
* @param object $square_item_variation
|
275 |
+
* @return array
|
276 |
+
*/
|
277 |
+
public static function format_square_item_variation_for_wc_api( $square_item_variation, $include_inventory = false ) {
|
278 |
+
|
279 |
+
$formatted = array(
|
280 |
+
'sku' => ! empty( $square_item_variation->sku ) ? $square_item_variation->sku : '',
|
281 |
+
'regular_price' => self::format_square_price_for_wc( $square_item_variation->price_money->amount ),
|
282 |
+
'stock_quantity' => null,
|
283 |
+
'attributes' => array(
|
284 |
+
array(
|
285 |
+
'name' => 'Attribute',
|
286 |
+
'option' => ! empty( $square_item_variation->name ) ? $square_item_variation->name : '',
|
287 |
+
),
|
288 |
+
),
|
289 |
+
);
|
290 |
+
|
291 |
+
if ( $include_inventory ) {
|
292 |
+
$formatted['managing_stock'] = $square_item_variation->track_inventory ? true : null;
|
293 |
+
}
|
294 |
+
|
295 |
+
return array_filter( $formatted );
|
296 |
+
}
|
297 |
+
|
298 |
+
/**
|
299 |
+
* Formats the price coming from Square as they use the lowest denominator ex. cents
|
300 |
+
*
|
301 |
+
* See: https://docs.connect.squareup.com/api/connect/v1/#workingwithmonetaryamounts
|
302 |
+
*
|
303 |
+
* @param int $price
|
304 |
+
* @return int
|
305 |
+
*/
|
306 |
+
public static function format_square_price_for_wc( $price = 0 ) {
|
307 |
+
return apply_filters( 'woocommerce_square_format_price', self::format_amount_from_square( $price ) );
|
308 |
+
}
|
309 |
+
|
310 |
+
/**
|
311 |
+
* Retrieve the Square ID for a WC Term
|
312 |
+
*
|
313 |
+
* @param int $wc_term_id
|
314 |
+
* @return mixed See get_woocommerce_term_meta()
|
315 |
+
*/
|
316 |
+
public static function get_wc_term_square_id( $wc_term_id ) {
|
317 |
+
return get_woocommerce_term_meta( $wc_term_id, self::WC_TERM_SQUARE_ID );
|
318 |
+
}
|
319 |
+
|
320 |
+
/**
|
321 |
+
* Update the Square ID for a WC Term
|
322 |
+
*
|
323 |
+
* @param int $wc_term_id
|
324 |
+
* @param string $square_id
|
325 |
+
* @return bool See update_woocommerce_term_meta()
|
326 |
+
*/
|
327 |
+
public static function update_wc_term_square_id( $wc_term_id, $square_id ) {
|
328 |
+
return update_woocommerce_term_meta( $wc_term_id, self::WC_TERM_SQUARE_ID, $square_id );
|
329 |
+
}
|
330 |
+
|
331 |
+
/**
|
332 |
+
* Retrieve the Square ID for a WC Product
|
333 |
+
*
|
334 |
+
* @param int $wc_product_id
|
335 |
+
* @return array|mixed See get_post_meta()
|
336 |
+
*/
|
337 |
+
public static function get_wc_product_square_id( $wc_product_id ) {
|
338 |
+
return get_post_meta( $wc_product_id, self::WC_PRODUCT_SQUARE_ID, true );
|
339 |
+
}
|
340 |
+
|
341 |
+
/**
|
342 |
+
* Update the Square ID for a WC Product
|
343 |
+
*
|
344 |
+
* @param int $wc_product_id
|
345 |
+
* @param string $square_id
|
346 |
+
* @return bool|int See update_post_meta()
|
347 |
+
*/
|
348 |
+
public static function update_wc_product_square_id( $wc_product_id, $square_id ) {
|
349 |
+
return update_post_meta( $wc_product_id, self::WC_PRODUCT_SQUARE_ID, $square_id );
|
350 |
+
}
|
351 |
+
|
352 |
+
/**
|
353 |
+
* Retrieve the Square ID for a WC Product Variation
|
354 |
+
*
|
355 |
+
* @param int $wc_variation_id
|
356 |
+
* @return array|mixed See get_post_meta()
|
357 |
+
*/
|
358 |
+
public static function get_wc_variation_square_id( $wc_variation_id ) {
|
359 |
+
return get_post_meta( $wc_variation_id, self::WC_VARIATION_SQUARE_ID, true );
|
360 |
+
}
|
361 |
+
|
362 |
+
/**
|
363 |
+
* Update the Square ID for a WC Product Variation
|
364 |
+
*
|
365 |
+
* @param int $wc_variation_id
|
366 |
+
* @param string $square_id
|
367 |
+
* @return bool|int See update_post_meta()
|
368 |
+
*/
|
369 |
+
public static function update_wc_variation_square_id( $wc_variation_id, $square_id ) {
|
370 |
+
return update_post_meta( $wc_variation_id, self::WC_VARIATION_SQUARE_ID, $square_id );
|
371 |
+
}
|
372 |
+
|
373 |
+
/**
|
374 |
+
* Get all SKUs associated with a WC Product (could be many, if variable).
|
375 |
+
*
|
376 |
+
* @param WC_Product $wc_product
|
377 |
+
* @return array
|
378 |
+
*/
|
379 |
+
public static function get_wc_product_skus( WC_Product $wc_product ) {
|
380 |
+
$wc_product_skus = array();
|
381 |
+
|
382 |
+
if ( 'simple' === ( version_compare( WC_VERSION, '3.0.0', '<' ) ? $wc_product->product_type : $wc_product->get_type() ) ) {
|
383 |
+
|
384 |
+
$wc_product_skus[] = $wc_product->get_sku();
|
385 |
+
|
386 |
+
} elseif ( 'variable' === ( version_compare( WC_VERSION, '3.0.0', '<' ) ? $wc_product->product_type : $wc_product->get_type() ) ) {
|
387 |
+
$wc_variations = self::get_wc_product_variations( $wc_product );
|
388 |
+
|
389 |
+
if ( version_compare( WC_VERSION, '3.0.0', '<' ) ) {
|
390 |
+
$wc_product_skus = wp_list_pluck( $wc_variations, 'sku' );
|
391 |
+
} else {
|
392 |
+
foreach ( $wc_variations as $wc_variation ) {
|
393 |
+
$wc_product_skus[] = $wc_variation->get_sku();
|
394 |
+
}
|
395 |
+
}
|
396 |
+
}
|
397 |
+
|
398 |
+
// SKUs are optional, so let's only return ones that have values
|
399 |
+
return array_filter( $wc_product_skus );
|
400 |
+
}
|
401 |
+
|
402 |
+
/**
|
403 |
+
* Determine which WC Product Category to send to Square.
|
404 |
+
*
|
405 |
+
* Returns the first top-level Category that has an associated Square ID.
|
406 |
+
*
|
407 |
+
* @param WC_Product $wc_product
|
408 |
+
* @return bool|mixed
|
409 |
+
*/
|
410 |
+
public static function get_square_category_id_for_wc_product( WC_Product $wc_product ) {
|
411 |
+
$wc_categories = wp_get_post_terms( version_compare( WC_VERSION, '3.0.0', '<' ) ? $wc_product->id : $wc_product->get_id(), 'product_cat' );
|
412 |
+
|
413 |
+
if ( is_wp_error( $wc_categories ) && empty( $wc_categories ) ) {
|
414 |
+
|
415 |
+
return false;
|
416 |
+
|
417 |
+
}
|
418 |
+
|
419 |
+
foreach ( $wc_categories as $category ) {
|
420 |
+
|
421 |
+
if ( $category->parent ) {
|
422 |
+
|
423 |
+
$ancestors = get_ancestors( $category->term_id, 'product_cat', 'taxonomy' );
|
424 |
+
$top_level_id = end( $ancestors );
|
425 |
+
|
426 |
+
} else {
|
427 |
+
|
428 |
+
$top_level_id = $category->term_id;
|
429 |
+
|
430 |
+
}
|
431 |
+
|
432 |
+
$square_cat_id = self::get_wc_term_square_id( $top_level_id );
|
433 |
+
|
434 |
+
if ( $square_cat_id ) {
|
435 |
+
return $square_cat_id;
|
436 |
+
}
|
437 |
+
}
|
438 |
+
|
439 |
+
return false;
|
440 |
+
}
|
441 |
+
|
442 |
+
/**
|
443 |
+
* Retrieve the Square Item Image ID for a WC Product
|
444 |
+
*
|
445 |
+
* @param int $wc_product_id
|
446 |
+
* @return array|mixed See get_post_meta()
|
447 |
+
*/
|
448 |
+
public static function get_wc_product_image_square_id( $wc_product_id ) {
|
449 |
+
return get_post_meta( $wc_product_id, self::WC_PRODUCT_IMAGE_SQUARE_ID, true );
|
450 |
+
}
|
451 |
+
|
452 |
+
/**
|
453 |
+
* Update the Square Item Image ID for a WC Product
|
454 |
+
*
|
455 |
+
* @param int $wc_product_id
|
456 |
+
* @param string $square_id
|
457 |
+
* @return bool|int See update_post_meta()
|
458 |
+
*/
|
459 |
+
public static function update_wc_product_image_square_id( $wc_product_id, $square_image_id ) {
|
460 |
+
return update_post_meta( $wc_product_id, self::WC_PRODUCT_IMAGE_SQUARE_ID, $square_image_id );
|
461 |
+
}
|
462 |
+
|
463 |
+
/**
|
464 |
+
* Retrieve the WC Category ID that corresponds to a given Square Category ID.
|
465 |
+
*
|
466 |
+
* @param string $square_cat_id
|
467 |
+
* @return bool|int WC Category ID on successful match, boolean false otherwise.
|
468 |
+
*/
|
469 |
+
public static function get_wc_category_id_for_square_category_id( $square_cat_id ) {
|
470 |
+
$categories = get_terms( 'product_cat', array(
|
471 |
+
'parent' => 0,
|
472 |
+
'hide_empty' => false,
|
473 |
+
'fields' => 'ids',
|
474 |
+
) );
|
475 |
+
|
476 |
+
if ( is_wp_error( $categories ) ) {
|
477 |
+
WC_Square_Sync_Logger::log( sprintf( '%s::%s - Taxonomy "product_cat" not found. Make sure WooCommerce is enabled.', __CLASS__, __FUNCTION__ ) );
|
478 |
+
return false;
|
479 |
+
}
|
480 |
+
|
481 |
+
foreach ( $categories as $wc_category ) {
|
482 |
+
$wc_square_cat_id = self::get_wc_term_square_id( $wc_category );
|
483 |
+
|
484 |
+
if ( $wc_square_cat_id && ( $square_cat_id === $wc_square_cat_id ) ) {
|
485 |
+
return $wc_category;
|
486 |
+
}
|
487 |
+
}
|
488 |
+
|
489 |
+
return false;
|
490 |
+
}
|
491 |
+
|
492 |
+
/**
|
493 |
+
* Attempt to find a WC Product that corresponds to a given Square Item.
|
494 |
+
*
|
495 |
+
* This function first queries for a WC Product already associated to the
|
496 |
+
* Square Item's ID. If none found, all WC Products (and variations) are
|
497 |
+
* queried using the SKUs present in the Square Item's Variations. If a
|
498 |
+
* match is found, the parent (non-variant) WC Product is returned.
|
499 |
+
*
|
500 |
+
* @param object $square_item
|
501 |
+
* @return bool|WC_Product Corresponding WC_Product on successful match, boolean false otherwise.
|
502 |
+
*/
|
503 |
+
public static function get_wc_product_for_square_item( $square_item ) {
|
504 |
+
if ( ! is_object( $square_item ) || ! property_exists( $square_item, 'id' ) ) {
|
505 |
+
return false;
|
506 |
+
}
|
507 |
+
|
508 |
+
$wc_product_ids = get_posts( array(
|
509 |
+
'post_type' => 'product',
|
510 |
+
'post_status' => 'publish', // this is ignored
|
511 |
+
'meta_query' => array(
|
512 |
+
array(
|
513 |
+
'key' => self::WC_PRODUCT_SQUARE_ID,
|
514 |
+
'compare' => '=',
|
515 |
+
'value' => $square_item->id,
|
516 |
+
),
|
517 |
+
),
|
518 |
+
'posts_per_page' => 1,
|
519 |
+
'fields' => 'ids',
|
520 |
+
) );
|
521 |
+
|
522 |
+
if ( ! empty( $wc_product_ids ) ) {
|
523 |
+
$wc_product = wc_get_product( $wc_product_ids[0] );
|
524 |
+
|
525 |
+
// only return publish products
|
526 |
+
if ( 'publish' === ( version_compare( WC_VERSION, '3.0.0', '<' ) ? $wc_product->post->post_status : $wc_product->get_status() ) ) {
|
527 |
+
return $wc_product;
|
528 |
+
}
|
529 |
+
}
|
530 |
+
|
531 |
+
$square_item_skus = self::get_square_item_skus( $square_item );
|
532 |
+
|
533 |
+
if ( empty( $square_item_skus ) ) {
|
534 |
+
return false;
|
535 |
+
}
|
536 |
+
|
537 |
+
$wc_product_ids = get_posts( array(
|
538 |
+
'post_type' => array( 'product', 'product_variation' ),
|
539 |
+
'post_status' => 'publish', // this is ignored
|
540 |
+
'meta_query' => array(
|
541 |
+
array(
|
542 |
+
'key' => '_sku',
|
543 |
+
'compare' => 'IN',
|
544 |
+
'value' => $square_item_skus,
|
545 |
+
),
|
546 |
+
),
|
547 |
+
'posts_per_page' => 1,
|
548 |
+
'fields' => 'ids',
|
549 |
+
) );
|
550 |
+
|
551 |
+
if ( ! empty( $wc_product_ids ) ) {
|
552 |
+
$wc_product = wc_get_product( $wc_product_ids[0] );
|
553 |
+
|
554 |
+
if ( ! is_object( $wc_product ) ) {
|
555 |
+
return false;
|
556 |
+
}
|
557 |
+
|
558 |
+
if ( 'publish' === ( version_compare( WC_VERSION, '3.0.0', '<' ) ? $wc_product->post->post_status : $wc_product->get_status() ) ) {
|
559 |
+
if ( 'simple' === ( version_compare( WC_VERSION, '3.0.0', '<' ) ? $wc_product->product_type : $wc_product->get_type() ) ) {
|
560 |
+
|
561 |
+
return $wc_product;
|
562 |
+
|
563 |
+
}
|
564 |
+
|
565 |
+
return wc_get_product( ( version_compare( WC_VERSION, '3.0.0', '<' ) ? $wc_product->parent : $wc_product->get_parent_id() ) );
|
566 |
+
}
|
567 |
+
}
|
568 |
+
|
569 |
+
return false;
|
570 |
+
}
|
571 |
+
|
572 |
+
/**
|
573 |
+
* Attempt to find a WC Product that corresponds to a given Square Item.
|
574 |
+
*
|
575 |
+
* @param string $square_variation_id
|
576 |
+
* @return bool|WC_Product Corresponding WC_Product on successful match, boolean false otherwise.
|
577 |
+
*/
|
578 |
+
public static function get_wc_product_for_square_item_variation_id( $square_variation_id ) {
|
579 |
+
$wc_product_ids = get_posts( array(
|
580 |
+
'post_type' => array( 'product', 'product_variation' ),
|
581 |
+
'post_status' => 'publish', // this is ignored
|
582 |
+
'meta_query' => array(
|
583 |
+
array(
|
584 |
+
'key' => self::WC_VARIATION_SQUARE_ID,
|
585 |
+
'compare' => '=',
|
586 |
+
'value' => $square_variation_id,
|
587 |
+
),
|
588 |
+
),
|
589 |
+
'posts_per_page' => 1,
|
590 |
+
'fields' => 'ids',
|
591 |
+
) );
|
592 |
+
|
593 |
+
if ( ! empty( $wc_product_ids ) ) {
|
594 |
+
$product = wc_get_product( $wc_product_ids[0] );
|
595 |
+
|
596 |
+
// only return publish products
|
597 |
+
if ( 'publish' === ( version_compare( WC_VERSION, '3.0.0', '<' ) ? $product->post->post_status : $product->get_status() ) ) {
|
598 |
+
return $product;
|
599 |
+
}
|
600 |
+
}
|
601 |
+
|
602 |
+
return false;
|
603 |
+
}
|
604 |
+
|
605 |
+
/**
|
606 |
+
* Checks if all SKUs have been set for the Square Item.
|
607 |
+
* We do not want to sync the item if not all SKU is set.
|
608 |
+
*
|
609 |
+
* @since 1.0.14
|
610 |
+
* @version 1.0.14
|
611 |
+
* @param object $square_item
|
612 |
+
* @return bool
|
613 |
+
*/
|
614 |
+
public static function is_square_item_skus_set( $square_item ) {
|
615 |
+
if ( empty( $square_item ) || empty( $square_item->variations ) ) {
|
616 |
+
|
617 |
+
return false;
|
618 |
+
}
|
619 |
+
|
620 |
+
foreach ( $square_item->variations as $item_variation ) {
|
621 |
+
// If even one sku is missing we don't want to sync.
|
622 |
+
if ( empty( $item_variation->sku ) ) {
|
623 |
+
|
624 |
+
return false;
|
625 |
+
}
|
626 |
+
}
|
627 |
+
|
628 |
+
return true;
|
629 |
+
}
|
630 |
+
|
631 |
+
/**
|
632 |
+
* Return array of SKUs from all variations of a Square Item
|
633 |
+
*
|
634 |
+
* @param object $square_item
|
635 |
+
* @return array
|
636 |
+
*/
|
637 |
+
public static function get_square_item_skus( $square_item ) {
|
638 |
+
|
639 |
+
$item_skus = array();
|
640 |
+
|
641 |
+
if ( empty( $square_item->variations ) ) {
|
642 |
+
|
643 |
+
return $item_skus;
|
644 |
+
}
|
645 |
+
|
646 |
+
foreach ( $square_item->variations as $item_variation ) {
|
647 |
+
if ( ! empty( $item_variation->sku ) ) {
|
648 |
+
|
649 |
+
$item_skus[] = $item_variation->sku;
|
650 |
+
}
|
651 |
+
}
|
652 |
+
|
653 |
+
return $item_skus;
|
654 |
+
}
|
655 |
+
|
656 |
+
/**
|
657 |
+
* Store Square Item ID and ItemVariation IDs on a WC Product and it's variations,
|
658 |
+
* matching using the SKU value.
|
659 |
+
*
|
660 |
+
* This is most useful in WC->Square creation, and Square->WC operations.
|
661 |
+
*
|
662 |
+
* @param WC_Product $wc_product
|
663 |
+
* @param object $square_item
|
664 |
+
*/
|
665 |
+
public static function set_square_ids_on_wc_product_by_sku( WC_Product $wc_product, $square_item ) {
|
666 |
+
if ( ! is_object( $square_item ) || ! property_exists( $square_item, 'id' ) ) {
|
667 |
+
return false;
|
668 |
+
}
|
669 |
+
|
670 |
+
self::update_wc_product_square_id( version_compare( WC_VERSION, '3.0.0', '<' ) ? $wc_product->id : $wc_product->get_id(), $square_item->id );
|
671 |
+
|
672 |
+
if ( 'simple' === ( version_compare( WC_VERSION, '3.0.0', '<' ) ? $wc_product->product_type : $wc_product->get_type() ) ) {
|
673 |
+
|
674 |
+
self::update_wc_variation_square_id( version_compare( WC_VERSION, '3.0.0', '<' ) ? $wc_product->id : $wc_product->get_id(), $square_item->variations[0]->id );
|
675 |
+
|
676 |
+
} elseif ( 'variable' === ( version_compare( WC_VERSION, '3.0.0', '<' ) ? $wc_product->product_type : $wc_product->get_type() ) ) {
|
677 |
+
|
678 |
+
// Create mapping of Square ItemVariation SKU => ID
|
679 |
+
$square_variations = array();
|
680 |
+
|
681 |
+
foreach ( $square_item->variations as $square_variation ) {
|
682 |
+
|
683 |
+
if ( ! empty( $square_variation->sku ) ) {
|
684 |
+
|
685 |
+
$square_variations[ $square_variation->sku ] = $square_variation->id;
|
686 |
+
|
687 |
+
}
|
688 |
+
}
|
689 |
+
|
690 |
+
// Create mapping of WC Variation SKU => ID
|
691 |
+
$wc_item_variations = self::get_wc_product_variations( $wc_product );
|
692 |
+
$wc_variations = array();
|
693 |
+
|
694 |
+
foreach ( $wc_item_variations as $wc_item_variation ) {
|
695 |
+
$wc_variation_sku = $wc_item_variation->get_sku();
|
696 |
+
|
697 |
+
if ( ! empty( $wc_variation_sku ) ) {
|
698 |
+
$wc_variations[ $wc_variation_sku ] = version_compare( WC_VERSION, '3.0.0', '<' ) ? $wc_item_variation->variation_id : $wc_item_variation->get_id();
|
699 |
+
}
|
700 |
+
}
|
701 |
+
|
702 |
+
// Map the WC Variations to their Square ItemVariation counterparts
|
703 |
+
foreach ( $wc_variations as $sku => $wc_variation_id ) {
|
704 |
+
if ( array_key_exists( $sku, $square_variations ) ) {
|
705 |
+
|
706 |
+
self::update_wc_variation_square_id( $wc_variation_id, $square_variations[ $sku ] );
|
707 |
+
}
|
708 |
+
}
|
709 |
+
}
|
710 |
+
}
|
711 |
+
|
712 |
+
/**
|
713 |
+
* Retrieve WC Variation IDs for a given WC Product, that we're managing stock for.
|
714 |
+
*
|
715 |
+
* @param WC_Product $wc_product
|
716 |
+
* @return array
|
717 |
+
*/
|
718 |
+
public static function get_stock_managed_wc_variation_ids( WC_Product $wc_product ) {
|
719 |
+
$wc_product = wc_get_product( $wc_product->get_id() );
|
720 |
+
|
721 |
+
$wc_variation_ids = array();
|
722 |
+
|
723 |
+
if ( ! is_object( $wc_product ) ) {
|
724 |
+
return $wc_variation_ids;
|
725 |
+
}
|
726 |
+
|
727 |
+
if ( 'simple' === ( version_compare( WC_VERSION, '3.0.0', '<' ) ? $wc_product->product_type : $wc_product->get_type() ) ) {
|
728 |
+
|
729 |
+
if ( $wc_product->managing_stock() ) {
|
730 |
+
|
731 |
+
$wc_variation_ids = array( version_compare( WC_VERSION, '3.0.0', '<' ) ? $wc_product->id : $wc_product->get_id() );
|
732 |
+
|
733 |
+
}
|
734 |
+
} elseif ( 'variable' === ( version_compare( WC_VERSION, '3.0.0', '<' ) ? $wc_product->product_type : $wc_product->get_type() ) ) {
|
735 |
+
|
736 |
+
$variations = self::get_wc_product_variations( $wc_product );
|
737 |
+
|
738 |
+
foreach ( (array) $variations as $variation ) {
|
739 |
+
|
740 |
+
if ( $variation->managing_stock() ) {
|
741 |
+
|
742 |
+
$wc_variation_ids[] = version_compare( WC_VERSION, '3.0.0', '<' ) ? $variation->variation_id : $variation->get_id();
|
743 |
+
|
744 |
+
}
|
745 |
+
}
|
746 |
+
}
|
747 |
+
|
748 |
+
return $wc_variation_ids;
|
749 |
+
}
|
750 |
+
|
751 |
+
/**
|
752 |
+
* Get all variations of a given WC_Product_Variable.
|
753 |
+
*
|
754 |
+
* @param WC_Product_Variable $wc_variable_product
|
755 |
+
* @return array Array of WC_Product_Variation objects.
|
756 |
+
*/
|
757 |
+
public static function get_wc_product_variations( WC_Product_Variable $wc_variable_product ) {
|
758 |
+
$variations = array();
|
759 |
+
|
760 |
+
foreach ( $wc_variable_product->get_children() as $child_id ) {
|
761 |
+
|
762 |
+
$variation = version_compare( WC_VERSION, '3.0.0', '<' ) ? $wc_variable_product->get_child( $child_id ) : wc_get_product( $child_id );
|
763 |
+
|
764 |
+
$variation_id = version_compare( WC_VERSION, '3.0.0', '<' ) ? $variation->variation_id : $variation->get_id();
|
765 |
+
|
766 |
+
if ( empty( $variation_id ) ) {
|
767 |
+
continue;
|
768 |
+
}
|
769 |
+
|
770 |
+
$variations[] = $variation;
|
771 |
+
}
|
772 |
+
|
773 |
+
return $variations;
|
774 |
+
}
|
775 |
+
|
776 |
+
/**
|
777 |
+
* Check if the square item is found
|
778 |
+
*
|
779 |
+
* @param object $square_item
|
780 |
+
* @return bool
|
781 |
+
*/
|
782 |
+
public static function is_square_item_found( $square_item ) {
|
783 |
+
if ( is_object( $square_item ) && 'not_found' !== $square_item->type ) {
|
784 |
+
return true;
|
785 |
+
}
|
786 |
+
|
787 |
+
return false;
|
788 |
+
}
|
789 |
+
|
790 |
+
/**
|
791 |
+
* Checks to see if product disable sync is enabled
|
792 |
+
*
|
793 |
+
* @param int $product_id parent product id
|
794 |
+
* @return bool
|
795 |
+
*/
|
796 |
+
public static function skip_product_sync( $product_id = null ) {
|
797 |
+
if ( null === $product_id ) {
|
798 |
+
return false;
|
799 |
+
}
|
800 |
+
|
801 |
+
$skip_sync = get_post_meta( $product_id, '_wcsquare_disable_sync', true );
|
802 |
+
|
803 |
+
if ( 'yes' === $skip_sync ) {
|
804 |
+
return true;
|
805 |
+
}
|
806 |
+
|
807 |
+
return false;
|
808 |
+
}
|
809 |
+
|
810 |
+
/**
|
811 |
+
* Process amount to be passed to Square.
|
812 |
+
* @return float
|
813 |
+
*/
|
814 |
+
public static function format_amount_to_square( $total, $currency = '' ) {
|
815 |
+
if ( ! $currency ) {
|
816 |
+
$currency = get_woocommerce_currency();
|
817 |
+
}
|
818 |
+
|
819 |
+
switch ( strtoupper( $currency ) ) {
|
820 |
+
// Zero decimal currencies
|
821 |
+
case 'BIF':
|
822 |
+
case 'CLP':
|
823 |
+
case 'DJF':
|
824 |
+
case 'GNF':
|
825 |
+
case 'JPY':
|
826 |
+
case 'KMF':
|
827 |
+
case 'KRW':
|
828 |
+
case 'MGA':
|
829 |
+
case 'PYG':
|
830 |
+
case 'RWF':
|
831 |
+
case 'VND':
|
832 |
+
case 'VUV':
|
833 |
+
case 'XAF':
|
834 |
+
case 'XOF':
|
835 |
+
case 'XPF':
|
836 |
+
$total = absint( $total );
|
837 |
+
break;
|
838 |
+
default:
|
839 |
+
$total = absint( wc_format_decimal( ( (float) $total * 100 ), wc_get_price_decimals() ) ); // In cents.
|
840 |
+
break;
|
841 |
+
}
|
842 |
+
|
843 |
+
return $total;
|
844 |
+
}
|
845 |
+
|
846 |
+
/**
|
847 |
+
* Process amount to be passed from Square.
|
848 |
+
* @return float
|
849 |
+
*/
|
850 |
+
public static function format_amount_from_square( $total, $currency = '' ) {
|
851 |
+
if ( ! $currency ) {
|
852 |
+
$currency = get_woocommerce_currency();
|
853 |
+
}
|
854 |
+
|
855 |
+
switch ( strtoupper( $currency ) ) {
|
856 |
+
// Zero decimal currencies
|
857 |
+
case 'BIF':
|
858 |
+
case 'CLP':
|
859 |
+
case 'DJF':
|
860 |
+
case 'GNF':
|
861 |
+
case 'JPY':
|
862 |
+
case 'KMF':
|
863 |
+
case 'KRW':
|
864 |
+
case 'MGA':
|
865 |
+
case 'PYG':
|
866 |
+
case 'RWF':
|
867 |
+
case 'VND':
|
868 |
+
case 'VUV':
|
869 |
+
case 'XAF':
|
870 |
+
case 'XOF':
|
871 |
+
case 'XPF':
|
872 |
+
$total = absint( $total );
|
873 |
+
break;
|
874 |
+
default:
|
875 |
+
$total = wc_format_decimal( absint( $total ) / 100 );
|
876 |
+
break;
|
877 |
+
}
|
878 |
+
|
879 |
+
return $total;
|
880 |
+
}
|
881 |
+
|
882 |
+
/**
|
883 |
+
* This is for developers to test with their own staging access with Square.
|
884 |
+
* This is usually not accessible by regular merchants.
|
885 |
+
*
|
886 |
+
* @param string $environment
|
887 |
+
* @return string
|
888 |
+
*/
|
889 |
+
public static function is_staging( $environment = null ) {
|
890 |
+
if ( ! empty( $environment ) && 'staging' === $environment && defined( 'WP_DEBUG' ) && WP_DEBUG ) {
|
891 |
+
return true;
|
892 |
+
}
|
893 |
+
|
894 |
+
return false;
|
895 |
+
}
|
896 |
+
|
897 |
+
/**
|
898 |
+
* Deletes all transients.
|
899 |
+
*
|
900 |
+
* @since 1.0.17
|
901 |
+
* @version 1.0.17
|
902 |
+
*/
|
903 |
+
public static function delete_transients() {
|
904 |
+
delete_transient( 'wc_square_processing_total_count' );
|
905 |
+
|
906 |
+
delete_transient( 'wc_square_processing_ids' );
|
907 |
+
|
908 |
+
delete_transient( 'wc_square_syncing_square_inventory' );
|
909 |
+
|
910 |
+
delete_transient( 'sq_wc_sync_current_process' );
|
911 |
+
|
912 |
+
delete_transient( 'wc_square_inventory' );
|
913 |
+
|
914 |
+
delete_transient( 'wc_square_polling' );
|
915 |
+
|
916 |
+
return true;
|
917 |
+
}
|
918 |
+
}
|
includes/class-wc-square-wc-products.php
ADDED
@@ -0,0 +1,2458 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
if ( ! defined( 'ABSPATH' ) ) {
|
3 |
+
exit;
|
4 |
+
}
|
5 |
+
|
6 |
+
class WC_Square_API_Exception extends Exception {
|
7 |
+
|
8 |
+
/** @var string sanitized error code */
|
9 |
+
protected $error_code;
|
10 |
+
|
11 |
+
/**
|
12 |
+
* Setup exception, requires 3 params:
|
13 |
+
*
|
14 |
+
* error code - machine-readable, e.g. `woocommerce_invalid_product_id`
|
15 |
+
* error message - friendly message, e.g. 'Product ID is invalid'
|
16 |
+
* http status code - proper HTTP status code to respond with, e.g. 400
|
17 |
+
*
|
18 |
+
* @since 2.2
|
19 |
+
* @param string $error_code
|
20 |
+
* @param string $error_message user-friendly translated error message
|
21 |
+
* @param int $http_status_code HTTP status code to respond with
|
22 |
+
*/
|
23 |
+
public function __construct( $error_code, $error_message, $http_status_code ) {
|
24 |
+
$this->error_code = $error_code;
|
25 |
+
parent::__construct( $error_message, $http_status_code );
|
26 |
+
}
|
27 |
+
|
28 |
+
/**
|
29 |
+
* Returns the error code
|
30 |
+
*
|
31 |
+
* @since 2.2
|
32 |
+
* @return string
|
33 |
+
*/
|
34 |
+
public function getErrorCode() {
|
35 |
+
return $this->error_code;
|
36 |
+
}
|
37 |
+
}
|
38 |
+
|
39 |
+
/**
|
40 |
+
* Class WC_Square_WC_Products.
|
41 |
+
*
|
42 |
+
* A WC CRUD class for products.
|
43 |
+
*
|
44 |
+
* @since 1.0.8
|
45 |
+
* @version 1.0.8
|
46 |
+
*/
|
47 |
+
class WC_Square_WC_Products {
|
48 |
+
/**
|
49 |
+
* Validate the request by checking:
|
50 |
+
*
|
51 |
+
* 1) the ID is a valid integer
|
52 |
+
* 2) the ID returns a valid post object and matches the provided post type
|
53 |
+
* 3) the current user has the proper permissions to read/edit/delete the post
|
54 |
+
*
|
55 |
+
* @since 2.1
|
56 |
+
* @param string|int $id the post ID
|
57 |
+
* @param string $type the post type, either `shop_order`, `shop_coupon`, or `product`
|
58 |
+
* @param string $context the context of the request, either `read`, `edit` or `delete`
|
59 |
+
* @return int|WP_Error valid post ID or WP_Error if any of the checks fails
|
60 |
+
*/
|
61 |
+
protected function validate_request( $id, $type, $context ) {
|
62 |
+
|
63 |
+
if ( 'shop_order' === $type || 'shop_coupon' === $type || 'shop_webhook' === $type ) {
|
64 |
+
$resource_name = str_replace( 'shop_', '', $type );
|
65 |
+
} else {
|
66 |
+
$resource_name = $type;
|
67 |
+
}
|
68 |
+
|
69 |
+
$id = absint( $id );
|
70 |
+
|
71 |
+
// Validate ID
|
72 |
+
if ( empty( $id ) ) {
|
73 |
+
return new WP_Error( "woocommerce_api_invalid_{$resource_name}_id", sprintf( __( 'Invalid %s ID', 'woocommerce' ), $type ), array( 'status' => 404 ) );
|
74 |
+
}
|
75 |
+
|
76 |
+
// Only custom post types have per-post type/permission checks
|
77 |
+
if ( 'customer' !== $type ) {
|
78 |
+
|
79 |
+
$post = get_post( $id );
|
80 |
+
|
81 |
+
if ( null === $post ) {
|
82 |
+
return new WP_Error( "woocommerce_api_no_{$resource_name}_found", sprintf( __( 'No %1$s found with the ID equal to %2$s', 'woocommerce' ), $resource_name, $id ), array( 'status' => 404 ) );
|
83 |
+
}
|
84 |
+
|
85 |
+
// For checking permissions, product variations are the same as the product post type
|
86 |
+
$post_type = ( 'product_variation' === $post->post_type ) ? 'product' : $post->post_type;
|
87 |
+
|
88 |
+
// Validate post type
|
89 |
+
if ( $type !== $post_type ) {
|
90 |
+
return new WP_Error( "woocommerce_api_invalid_{$resource_name}", sprintf( __( 'Invalid %s', 'woocommerce' ), $resource_name ), array( 'status' => 404 ) );
|
91 |
+
}
|
92 |
+
|
93 |
+
// Validate permissions
|
94 |
+
switch ( $context ) {
|
95 |
+
|
96 |
+
case 'read':
|
97 |
+
if ( ! $this->is_readable( $post ) ) {
|
98 |
+
return new WP_Error( "woocommerce_api_user_cannot_read_{$resource_name}", sprintf( __( 'You do not have permission to read this %s', 'woocommerce' ), $resource_name ), array( 'status' => 401 ) );
|
99 |
+
}
|
100 |
+
break;
|
101 |
+
|
102 |
+
case 'edit':
|
103 |
+
if ( ! $this->is_editable( $post ) ) {
|
104 |
+
return new WP_Error( "woocommerce_api_user_cannot_edit_{$resource_name}", sprintf( __( 'You do not have permission to edit this %s', 'woocommerce' ), $resource_name ), array( 'status' => 401 ) );
|
105 |
+
}
|
106 |
+
break;
|
107 |
+
|
108 |
+
case 'delete':
|
109 |
+
if ( ! $this->is_deletable( $post ) ) {
|
110 |
+
return new WP_Error( "woocommerce_api_user_cannot_delete_{$resource_name}", sprintf( __( 'You do not have permission to delete this %s', 'woocommerce' ), $resource_name ), array( 'status' => 401 ) );
|
111 |
+
}
|
112 |
+
break;
|
113 |
+
}
|
114 |
+
}
|
115 |
+
|
116 |
+
return $id;
|
117 |
+
}
|
118 |
+
|
119 |
+
/**
|
120 |
+
* Checks if the given post is readable by the current user
|
121 |
+
*
|
122 |
+
* @since 2.1
|
123 |
+
* @see WC_API_Resource::check_permission()
|
124 |
+
* @param WP_Post|int $post
|
125 |
+
* @return bool
|
126 |
+
*/
|
127 |
+
protected function is_readable( $post ) {
|
128 |
+
|
129 |
+
return $this->check_permission( $post, 'read' );
|
130 |
+
}
|
131 |
+
|
132 |
+
/**
|
133 |
+
* Checks if the given post is editable by the current user
|
134 |
+
*
|
135 |
+
* @since 2.1
|
136 |
+
* @see WC_API_Resource::check_permission()
|
137 |
+
* @param WP_Post|int $post
|
138 |
+
* @return bool
|
139 |
+
*/
|
140 |
+
protected function is_editable( $post ) {
|
141 |
+
|
142 |
+
return $this->check_permission( $post, 'edit' );
|
143 |
+
|
144 |
+
}
|
145 |
+
|
146 |
+
/**
|
147 |
+
* Checks if the given post is deletable by the current user
|
148 |
+
*
|
149 |
+
* @since 2.1
|
150 |
+
* @see WC_API_Resource::check_permission()
|
151 |
+
* @param WP_Post|int $post
|
152 |
+
* @return bool
|
153 |
+
*/
|
154 |
+
protected function is_deletable( $post ) {
|
155 |
+
|
156 |
+
return $this->check_permission( $post, 'delete' );
|
157 |
+
}
|
158 |
+
|
159 |
+
/**
|
160 |
+
* Checks the permissions for the current user given a post and context
|
161 |
+
*
|
162 |
+
* @since 2.1
|
163 |
+
* @param WP_Post|int $post
|
164 |
+
* @param string $context the type of permission to check, either `read`, `write`, or `delete`
|
165 |
+
* @return bool true if the current user has the permissions to perform the context on the post
|
166 |
+
*/
|
167 |
+
private function check_permission( $post, $context ) {
|
168 |
+
$permission = false;
|
169 |
+
|
170 |
+
if ( ! is_a( $post, 'WP_Post' ) ) {
|
171 |
+
$post = get_post( $post );
|
172 |
+
}
|
173 |
+
|
174 |
+
if ( is_null( $post ) ) {
|
175 |
+
return $permission;
|
176 |
+
}
|
177 |
+
|
178 |
+
$post_type = get_post_type_object( $post->post_type );
|
179 |
+
|
180 |
+
if ( 'read' === $context ) {
|
181 |
+
$permission = 'revision' !== $post->post_type && current_user_can( $post_type->cap->read_private_posts, $post->ID );
|
182 |
+
} elseif ( 'edit' === $context ) {
|
183 |
+
$permission = current_user_can( $post_type->cap->edit_post, $post->ID );
|
184 |
+
} elseif ( 'delete' === $context ) {
|
185 |
+
$permission = current_user_can( $post_type->cap->delete_post, $post->ID );
|
186 |
+
}
|
187 |
+
|
188 |
+
return apply_filters( 'woocommerce_api_check_permission', $permission, $context, $post, $post_type );
|
189 |
+
}
|
190 |
+
|
191 |
+
/**
|
192 |
+
* Get the product for the given ID
|
193 |
+
*
|
194 |
+
* @since 2.1
|
195 |
+
* @param int $id the product ID
|
196 |
+
* @param string $fields
|
197 |
+
* @return array
|
198 |
+
*/
|
199 |
+
public function get_product( $id, $fields = null ) {
|
200 |
+
|
201 |
+
$id = $this->validate_request( $id, 'product', 'read' );
|
202 |
+
|
203 |
+
if ( is_wp_error( $id ) ) {
|
204 |
+
return $id;
|
205 |
+
}
|
206 |
+
|
207 |
+
$product = wc_get_product( $id );
|
208 |
+
|
209 |
+
// add data that applies to every product type
|
210 |
+
$product_data = $this->get_product_data( $product );
|
211 |
+
|
212 |
+
// add variations to variable products
|
213 |
+
if ( $product->is_type( 'variable' ) && $product->has_child() ) {
|
214 |
+
$product_data['variations'] = $this->get_variation_data( $product );
|
215 |
+
}
|
216 |
+
|
217 |
+
// add the parent product data to an individual variation
|
218 |
+
if ( $product->is_type( 'variation' ) && $product->parent ) {
|
219 |
+
$product_data['parent'] = $this->get_product_data( $product->parent );
|
220 |
+
}
|
221 |
+
|
222 |
+
// Add grouped products data
|
223 |
+
if ( $product->is_type( 'grouped' ) && $product->has_child() ) {
|
224 |
+
$product_data['grouped_products'] = $this->get_grouped_products_data( $product );
|
225 |
+
}
|
226 |
+
|
227 |
+
$product_post_parent = version_compare( WC_VERSION, '3.0.0', '<' ) ? $product->post->post_parent : $product->get_parent_id();
|
228 |
+
|
229 |
+
if ( $product->is_type( 'simple' ) && ! empty( $product_post_parent ) ) {
|
230 |
+
$_product = wc_get_product( $product_post_parent );
|
231 |
+
$product_data['parent'] = $this->get_product_data( $_product );
|
232 |
+
}
|
233 |
+
|
234 |
+
return array( 'product' => apply_filters( 'woocommerce_api_product_response', $product_data, $product, $fields ) );
|
235 |
+
}
|
236 |
+
|
237 |
+
/**
|
238 |
+
* Create a new product.
|
239 |
+
*
|
240 |
+
* @since 2.2
|
241 |
+
* @param array $data posted data
|
242 |
+
* @return array
|
243 |
+
*/
|
244 |
+
public function create_product( $data ) {
|
245 |
+
$id = 0;
|
246 |
+
|
247 |
+
try {
|
248 |
+
if ( ! isset( $data['product'] ) ) {
|
249 |
+
throw new WC_Square_API_Exception( 'woocommerce_api_missing_product_data', sprintf( __( 'No %1$s data specified to create %1$s', 'woocommerce' ), 'product' ), 400 );
|
250 |
+
}
|
251 |
+
|
252 |
+
$data = $data['product'];
|
253 |
+
|
254 |
+
// Check permissions.
|
255 |
+
if ( ! current_user_can( 'publish_products' ) ) {
|
256 |
+
throw new WC_Square_API_Exception( 'woocommerce_api_user_cannot_create_product', __( 'You do not have permission to create products', 'woocommerce' ), 401 );
|
257 |
+
}
|
258 |
+
|
259 |
+
$data = apply_filters( 'woocommerce_api_create_product_data', $data, $this );
|
260 |
+
|
261 |
+
// Check if product title is specified.
|
262 |
+
if ( ! isset( $data['title'] ) ) {
|
263 |
+
throw new WC_Square_API_Exception( 'woocommerce_api_missing_product_title', sprintf( __( 'Missing parameter %s', 'woocommerce' ), 'title' ), 400 );
|
264 |
+
}
|
265 |
+
|
266 |
+
// Check product type.
|
267 |
+
if ( ! isset( $data['type'] ) ) {
|
268 |
+
$data['type'] = 'simple';
|
269 |
+
}
|
270 |
+
|
271 |
+
// Set visible visibility when not sent.
|
272 |
+
if ( ! isset( $data['catalog_visibility'] ) ) {
|
273 |
+
$data['catalog_visibility'] = 'visible';
|
274 |
+
}
|
275 |
+
|
276 |
+
// Validate the product type.
|
277 |
+
if ( ! in_array( wc_clean( $data['type'] ), array_keys( wc_get_product_types() ) ) ) {
|
278 |
+
throw new WC_Square_API_Exception( 'woocommerce_api_invalid_product_type', sprintf( __( 'Invalid product type - the product type must be any of these: %s', 'woocommerce' ), implode( ', ', array_keys( wc_get_product_types() ) ) ), 400 );
|
279 |
+
}
|
280 |
+
|
281 |
+
// Enable description html tags.
|
282 |
+
$post_content = isset( $data['description'] ) ? wc_clean( $data['description'] ) : '';
|
283 |
+
if ( $post_content && isset( $data['enable_html_description'] ) && true === $data['enable_html_description'] ) {
|
284 |
+
|
285 |
+
$post_content = $data['description'];
|
286 |
+
}
|
287 |
+
|
288 |
+
// Enable short description html tags.
|
289 |
+
$post_excerpt = isset( $data['short_description'] ) ? wc_clean( $data['short_description'] ) : '';
|
290 |
+
if ( $post_excerpt && isset( $data['enable_html_short_description'] ) && true === $data['enable_html_short_description'] ) {
|
291 |
+
$post_excerpt = $data['short_description'];
|
292 |
+
}
|
293 |
+
|
294 |
+
$new_product = array(
|
295 |
+
'post_title' => wc_clean( $data['title'] ),
|
296 |
+
'post_status' => isset( $data['status'] ) ? wc_clean( $data['status'] ) : 'publish',
|
297 |
+
'post_type' => 'product',
|
298 |
+
'post_excerpt' => isset( $data['short_description'] ) ? $post_excerpt : '',
|
299 |
+
'post_content' => isset( $data['description'] ) ? $post_content : '',
|
300 |
+
'post_author' => get_current_user_id(),
|
301 |
+
'menu_order' => isset( $data['menu_order'] ) ? intval( $data['menu_order'] ) : 0,
|
302 |
+
);
|
303 |
+
|
304 |
+
if ( ! empty( $data['name'] ) ) {
|
305 |
+
$new_product = array_merge( $new_product, array( 'post_name' => sanitize_title( $data['name'] ) ) );
|
306 |
+
}
|
307 |
+
|
308 |
+
// Attempts to create the new product.
|
309 |
+
$id = wp_insert_post( $new_product, true );
|
310 |
+
|
311 |
+
// Checks for an error in the product creation.
|
312 |
+
if ( is_wp_error( $id ) ) {
|
313 |
+
throw new WC_Square_API_Exception( 'woocommerce_api_cannot_create_product', $id->get_error_message(), 400 );
|
314 |
+
}
|
315 |
+
|
316 |
+
// Check for featured/gallery images, upload it and set it.
|
317 |
+
if ( isset( $data['images'] ) ) {
|
318 |
+
$this->save_product_images( $id, $data['images'] );
|
319 |
+
}
|
320 |
+
|
321 |
+
// Save product meta fields.
|
322 |
+
$this->save_product_meta( $id, $data );
|
323 |
+
|
324 |
+
// Save variations.
|
325 |
+
if ( isset( $data['type'] ) && 'variable' == $data['type'] && isset( $data['variations'] ) && is_array( $data['variations'] ) ) {
|
326 |
+
$this->save_variations( $id, $data );
|
327 |
+
}
|
328 |
+
|
329 |
+
do_action( 'woocommerce_api_create_product', $id, $data );
|
330 |
+
|
331 |
+
// Clear cache/transients.
|
332 |
+
wc_delete_product_transients( $id );
|
333 |
+
|
334 |
+
return $this->get_product( $id );
|
335 |
+
} catch ( WC_Square_API_Exception $e ) {
|
336 |
+
// Remove the product when fails.
|
337 |
+
$this->clear_product( $id );
|
338 |
+
|
339 |
+
return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) );
|
340 |
+
}
|
341 |
+
}
|
342 |
+
|
343 |
+
/**
|
344 |
+
* Edit a product
|
345 |
+
*
|
346 |
+
* @since 2.2
|
347 |
+
* @param int $id the product ID
|
348 |
+
* @param array $data
|
349 |
+
* @return array
|
350 |
+
*/
|
351 |
+
public function edit_product( $id, $data ) {
|
352 |
+
try {
|
353 |
+
if ( ! isset( $data['product'] ) ) {
|
354 |
+
throw new WC_Square_API_Exception( 'woocommerce_api_missing_product_data', sprintf( __( 'No %1$s data specified to edit %1$s', 'woocommerce' ), 'product' ), 400 );
|
355 |
+
}
|
356 |
+
|
357 |
+
$data = $data['product'];
|
358 |
+
|
359 |
+
$id = $this->validate_request( $id, 'product', 'edit' );
|
360 |
+
|
361 |
+
if ( is_wp_error( $id ) ) {
|
362 |
+
return $id;
|
363 |
+
}
|
364 |
+
|
365 |
+
$data = apply_filters( 'woocommerce_api_edit_product_data', $data, $this );
|
366 |
+
|
367 |
+
// Product title.
|
368 |
+
if ( isset( $data['title'] ) ) {
|
369 |
+
wp_update_post( array( 'ID' => $id, 'post_title' => wc_clean( $data['title'] ) ) );
|
370 |
+
}
|
371 |
+
|
372 |
+
// Product name (slug).
|
373 |
+
if ( isset( $data['name'] ) ) {
|
374 |
+
wp_update_post( array( 'ID' => $id, 'post_name' => sanitize_title( $data['name'] ) ) );
|
375 |
+
}
|
376 |
+
|
377 |
+
// Product status.
|
378 |
+
if ( isset( $data['status'] ) ) {
|
379 |
+
wp_update_post( array( 'ID' => $id, 'post_status' => wc_clean( $data['status'] ) ) );
|
380 |
+
}
|
381 |
+
|
382 |
+
// Product short description.
|
383 |
+
if ( isset( $data['short_description'] ) ) {
|
384 |
+
// Enable short description html tags.
|
385 |
+
$post_excerpt = ( isset( $data['enable_html_short_description'] ) && true === $data['enable_html_short_description'] ) ? $data['short_description'] : wc_clean( $data['short_description'] );
|
386 |
+
|
387 |
+
wp_update_post( array( 'ID' => $id, 'post_excerpt' => $post_excerpt ) );
|
388 |
+
}
|
389 |
+
|
390 |
+
// Product description.
|
391 |
+
if ( isset( $data['description'] ) ) {
|
392 |
+
// Enable description html tags.
|
393 |
+
$post_content = ( isset( $data['enable_html_description'] ) && true === $data['enable_html_description'] ) ? $data['description'] : wc_clean( $data['description'] );
|
394 |
+
|
395 |
+
wp_update_post( array( 'ID' => $id, 'post_content' => $post_content ) );
|
396 |
+
}
|
397 |
+
|
398 |
+
// Menu order.
|
399 |
+
if ( isset( $data['menu_order'] ) ) {
|
400 |
+
wp_update_post( array( 'ID' => $id, 'menu_order' => intval( $data['menu_order'] ) ) );
|
401 |
+
}
|
402 |
+
|
403 |
+
// Validate the product type.
|
404 |
+
if ( isset( $data['type'] ) && ! in_array( wc_clean( $data['type'] ), array_keys( wc_get_product_types() ) ) ) {
|
405 |
+
throw new WC_Square_API_Exception( 'woocommerce_api_invalid_product_type', sprintf( __( 'Invalid product type - the product type must be any of these: %s', 'woocommerce' ), implode( ', ', array_keys( wc_get_product_types() ) ) ), 400 );
|
406 |
+
}
|
407 |
+
|
408 |
+
// Check for featured/gallery images, upload it and set it.
|
409 |
+
if ( isset( $data['images'] ) ) {
|
410 |
+
$this->save_product_images( $id, $data['images'] );
|
411 |
+
}
|
412 |
+
|
413 |
+
// Save product meta fields.
|
414 |
+
$this->save_product_meta( $id, $data );
|
415 |
+
|
416 |
+
// Save variations.
|
417 |
+
$product = wc_get_product( $id );
|
418 |
+
if ( $product->is_type( 'variable' ) ) {
|
419 |
+
if ( isset( $data['variations'] ) && is_array( $data['variations'] ) ) {
|
420 |
+
$this->save_variations( $id, $data );
|
421 |
+
} else {
|
422 |
+
// Just sync variations
|
423 |
+
WC_Product_Variable::sync( $id );
|
424 |
+
WC_Product_Variable::sync_stock_status( $id );
|
425 |
+
}
|
426 |
+
}
|
427 |
+
|
428 |
+
do_action( 'woocommerce_api_edit_product', $id, $data );
|
429 |
+
|
430 |
+
// Clear cache/transients.
|
431 |
+
wc_delete_product_transients( $id );
|
432 |
+
|
433 |
+
return $this->get_product( $id );
|
434 |
+
} catch ( WC_Square_API_Exception $e ) {
|
435 |
+
return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) );
|
436 |
+
}
|
437 |
+
}
|
438 |
+
|
439 |
+
/**
|
440 |
+
* Get a listing of product categories
|
441 |
+
*
|
442 |
+
* @since 2.2
|
443 |
+
* @param string|null $fields fields to limit response to
|
444 |
+
* @return array
|
445 |
+
*/
|
446 |
+
public function get_product_categories( $fields = null ) {
|
447 |
+
try {
|
448 |
+
// Permissions check
|
449 |
+
if ( ! current_user_can( 'manage_product_terms' ) ) {
|
450 |
+
throw new WC_Square_API_Exception( 'woocommerce_api_user_cannot_read_product_categories', __( 'You do not have permission to read product categories', 'woocommerce' ), 401 );
|
451 |
+
}
|
452 |
+
|
453 |
+
$product_categories = array();
|
454 |
+
|
455 |
+
$terms = get_terms( 'product_cat', array( 'hide_empty' => false, 'fields' => 'ids' ) );
|
456 |
+
|
457 |
+
foreach ( $terms as $term_id ) {
|
458 |
+
$product_categories[] = current( $this->get_product_category( $term_id, $fields ) );
|
459 |
+
}
|
460 |
+
|
461 |
+
return array( 'product_categories' => apply_filters( 'woocommerce_api_product_categories_response', $product_categories, $terms, $fields, $this ) );
|
462 |
+
} catch ( WC_Square_API_Exception $e ) {
|
463 |
+
return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) );
|
464 |
+
}
|
465 |
+
}
|
466 |
+
|
467 |
+
/**
|
468 |
+
* Get the product category for the given ID
|
469 |
+
*
|
470 |
+
* @since 2.2
|
471 |
+
* @param string $id product category term ID
|
472 |
+
* @param string|null $fields fields to limit response to
|
473 |
+
* @return array
|
474 |
+
*/
|
475 |
+
public function get_product_category( $id, $fields = null ) {
|
476 |
+
try {
|
477 |
+
$id = absint( $id );
|
478 |
+
|
479 |
+
// Validate ID
|
480 |
+
if ( empty( $id ) ) {
|
481 |
+
throw new WC_Square_API_Exception( 'woocommerce_api_invalid_product_category_id', __( 'Invalid product category ID', 'woocommerce' ), 400 );
|
482 |
+
}
|
483 |
+
|
484 |
+
// Permissions check
|
485 |
+
if ( ! current_user_can( 'manage_product_terms' ) ) {
|
486 |
+
throw new WC_Square_API_Exception( 'woocommerce_api_user_cannot_read_product_categories', __( 'You do not have permission to read product categories', 'woocommerce' ), 401 );
|
487 |
+
}
|
488 |
+
|
489 |
+
$term = get_term( $id, 'product_cat' );
|
490 |
+
|
491 |
+
if ( is_wp_error( $term ) || is_null( $term ) ) {
|
492 |
+
throw new WC_Square_API_Exception( 'woocommerce_api_invalid_product_category_id', __( 'A product category with the provided ID could not be found', 'woocommerce' ), 404 );
|
493 |
+
}
|
494 |
+
|
495 |
+
$term_id = intval( $term->term_id );
|
496 |
+
|
497 |
+
// Get category display type
|
498 |
+
$display_type = get_woocommerce_term_meta( $term_id, 'display_type' );
|
499 |
+
|
500 |
+
// Get category image
|
501 |
+
$image = '';
|
502 |
+
if ( $image_id = get_woocommerce_term_meta( $term_id, 'thumbnail_id' ) ) {
|
503 |
+
$image = wp_get_attachment_url( $image_id );
|
504 |
+
}
|
505 |
+
|
506 |
+
$product_category = array(
|
507 |
+
'id' => $term_id,
|
508 |
+
'name' => $term->name,
|
509 |
+
'slug' => $term->slug,
|
510 |
+
'parent' => $term->parent,
|
511 |
+
'description' => $term->description,
|
512 |
+
'display' => $display_type ? $display_type : 'default',
|
513 |
+
'image' => $image ? esc_url( $image ) : '',
|
514 |
+
'count' => intval( $term->count ),
|
515 |
+
);
|
516 |
+
|
517 |
+
return array( 'product_category' => apply_filters( 'woocommerce_api_product_category_response', $product_category, $id, $fields, $term, $this ) );
|
518 |
+
} catch ( WC_Square_API_Exception $e ) {
|
519 |
+
return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) );
|
520 |
+
}
|
521 |
+
}
|
522 |
+
|
523 |
+
/**
|
524 |
+
* Create a new product category.
|
525 |
+
*
|
526 |
+
* @since 2.5.0
|
527 |
+
* @param array $data Posted data
|
528 |
+
* @return array|WP_Error Product category if succeed, otherwise WP_Error
|
529 |
+
* will be returned
|
530 |
+
*/
|
531 |
+
public function create_product_category( $data ) {
|
532 |
+
global $wpdb;
|
533 |
+
|
534 |
+
try {
|
535 |
+
if ( ! isset( $data['product_category'] ) ) {
|
536 |
+
throw new WC_Square_API_Exception( 'woocommerce_api_missing_product_category_data', sprintf( __( 'No %1$s data specified to create %1$s', 'woocommerce' ), 'product_category' ), 400 );
|
537 |
+
}
|
538 |
+
|
539 |
+
// Check permissions
|
540 |
+
if ( ! current_user_can( 'manage_product_terms' ) ) {
|
541 |
+
throw new WC_Square_API_Exception( 'woocommerce_api_user_cannot_create_product_category', __( 'You do not have permission to create product categories', 'woocommerce' ), 401 );
|
542 |
+
}
|
543 |
+
|
544 |
+
$defaults = array(
|
545 |
+
'name' => '',
|
546 |
+
'slug' => '',
|
547 |
+
'description' => '',
|
548 |
+
'parent' => 0,
|
549 |
+
'display' => 'default',
|
550 |
+
'image' => '',
|
551 |
+
);
|
552 |
+
|
553 |
+
$data = wp_parse_args( $data['product_category'], $defaults );
|
554 |
+
$data = apply_filters( 'woocommerce_api_create_product_category_data', $data, $this );
|
555 |
+
|
556 |
+
// Check parent.
|
557 |
+
$data['parent'] = absint( $data['parent'] );
|
558 |
+
if ( $data['parent'] ) {
|
559 |
+
$parent = get_term_by( 'id', $data['parent'], 'product_cat' );
|
560 |
+
if ( ! $parent ) {
|
561 |
+
throw new WC_Square_API_Exception( 'woocommerce_api_invalid_product_category_parent', __( 'Product category parent is invalid', 'woocommerce' ), 400 );
|
562 |
+
}
|
563 |
+
}
|
564 |
+
|
565 |
+
// If value of image is numeric, assume value as image_id.
|
566 |
+
$image = $data['image'];
|
567 |
+
$image_id = 0;
|
568 |
+
if ( is_numeric( $image ) ) {
|
569 |
+
$image_id = absint( $image );
|
570 |
+
} elseif ( ! empty( $image ) ) {
|
571 |
+
$upload = $this->upload_product_category_image( esc_url_raw( $image ) );
|
572 |
+
$image_id = $this->set_product_category_image_as_attachment( $upload );
|
573 |
+
}
|
574 |
+
|
575 |
+
$insert = wp_insert_term( $data['name'], 'product_cat', $data );
|
576 |
+
if ( is_wp_error( $insert ) ) {
|
577 |
+
throw new WC_Square_API_Exception( 'woocommerce_api_cannot_create_product_category', $insert->get_error_message(), 400 );
|
578 |
+
}
|
579 |
+
|
580 |
+
$id = $insert['term_id'];
|
581 |
+
|
582 |
+
update_woocommerce_term_meta( $id, 'display_type', 'default' === $data['display'] ? '' : sanitize_text_field( $data['display'] ) );
|
583 |
+
|
584 |
+
// Check if image_id is a valid image attachment before updating the term meta.
|
585 |
+
if ( $image_id && wp_attachment_is_image( $image_id ) ) {
|
586 |
+
update_woocommerce_term_meta( $id, 'thumbnail_id', $image_id );
|
587 |
+
}
|
588 |
+
|
589 |
+
do_action( 'woocommerce_api_create_product_category', $id, $data );
|
590 |
+
|
591 |
+
return $this->get_product_category( $id );
|
592 |
+
} catch ( WC_Square_API_Exception $e ) {
|
593 |
+
return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) );
|
594 |
+
}
|
595 |
+
}
|
596 |
+
|
597 |
+
/**
|
598 |
+
* Edit a product category.
|
599 |
+
*
|
600 |
+
* @since 2.5.0
|
601 |
+
* @param int $id Product category term ID
|
602 |
+
* @param array $data Posted data
|
603 |
+
* @return array|WP_Error Product category if succeed, otherwise WP_Error
|
604 |
+
* will be returned
|
605 |
+
*/
|
606 |
+
public function edit_product_category( $id, $data ) {
|
607 |
+
global $wpdb;
|
608 |
+
|
609 |
+
try {
|
610 |
+
if ( ! isset( $data['product_category'] ) ) {
|
611 |
+
throw new WC_Square_API_Exception( 'woocommerce_api_missing_product_category', sprintf( __( 'No %1$s data specified to edit %1$s', 'woocommerce' ), 'product_category' ), 400 );
|
612 |
+
}
|
613 |
+
|
614 |
+
$id = absint( $id );
|
615 |
+
$data = $data['product_category'];
|
616 |
+
|
617 |
+
// Check permissions.
|
618 |
+
if ( ! current_user_can( 'manage_product_terms' ) ) {
|
619 |
+
throw new WC_Square_API_Exception( 'woocommerce_api_user_cannot_edit_product_category', __( 'You do not have permission to edit product categories', 'woocommerce' ), 401 );
|
620 |
+
}
|
621 |
+
|
622 |
+
$data = apply_filters( 'woocommerce_api_edit_product_category_data', $data, $this );
|
623 |
+
$category = $this->get_product_category( $id );
|
624 |
+
|
625 |
+
if ( is_wp_error( $category ) ) {
|
626 |
+
return $category;
|
627 |
+
}
|
628 |
+
|
629 |
+
if ( isset( $data['image'] ) ) {
|
630 |
+
$image_id = 0;
|
631 |
+
|
632 |
+
// If value of image is numeric, assume value as image_id.
|
633 |
+
$image = $data['image'];
|
634 |
+
if ( is_numeric( $image ) ) {
|
635 |
+
$image_id = absint( $image );
|
636 |
+
} elseif ( ! empty( $image ) ) {
|
637 |
+
$upload = $this->upload_product_category_image( esc_url_raw( $image ) );
|
638 |
+
$image_id = $this->set_product_category_image_as_attachment( $upload );
|
639 |
+
}
|
640 |
+
|
641 |
+
// In case client supplies invalid image or wants to unset category image.
|
642 |
+
if ( ! wp_attachment_is_image( $image_id ) ) {
|
643 |
+
$image_id = '';
|
644 |
+
}
|
645 |
+
}
|
646 |
+
|
647 |
+
$update = wp_update_term( $id, 'product_cat', $data );
|
648 |
+
if ( is_wp_error( $update ) ) {
|
649 |
+
throw new WC_Square_API_Exception( 'woocommerce_api_cannot_edit_product_catgory', __( 'Could not edit the category', 'woocommerce' ), 400 );
|
650 |
+
}
|
651 |
+
|
652 |
+
if ( ! empty( $data['display'] ) ) {
|
653 |
+
update_woocommerce_term_meta( $id, 'display_type', 'default' === $data['display'] ? '' : sanitize_text_field( $data['display'] ) );
|
654 |
+
}
|
655 |
+
|
656 |
+
if ( isset( $image_id ) ) {
|
657 |
+
update_woocommerce_term_meta( $id, 'thumbnail_id', $image_id );
|
658 |
+
}
|
659 |
+
|
660 |
+
do_action( 'woocommerce_api_edit_product_category', $id, $data );
|
661 |
+
|
662 |
+
return $this->get_product_category( $id );
|
663 |
+
} catch ( WC_Square_API_Exception $e ) {
|
664 |
+
return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) );
|
665 |
+
}
|
666 |
+
}
|
667 |
+
|
668 |
+
/**
|
669 |
+
* Get standard product data that applies to every product type
|
670 |
+
*
|
671 |
+
* @since 2.1
|
672 |
+
* @param WC_Product $product
|
673 |
+
* @return WC_Product
|
674 |
+
*/
|
675 |
+
private function get_product_data( $product ) {
|
676 |
+
$product_post = version_compare( WC_VERSION, '3.0.0', '<' ) ? $product->get_post_data() : get_post( version_compare( WC_VERSION, '3.0.0', '<' ) ? $product->id : $product->get_id() );
|
677 |
+
|
678 |
+
return array(
|
679 |
+
'title' => $product->get_title(),
|
680 |
+
'id' => (int) $product->is_type( 'variation' ) ? ( version_compare( WC_VERSION, '3.0.0', '<' ) ? $product->get_variation_id() : $product->get_id() ) : ( version_compare( WC_VERSION, '3.0.0', '<' ) ? $product->id : $product->get_id() ),
|
681 |
+
'created_at' => $this->format_datetime( $product_post->post_date_gmt ),
|
682 |
+
'updated_at' => $this->format_datetime( $product_post->post_modified_gmt ),
|
683 |
+
'type' => version_compare( WC_VERSION, '3.0.0', '<' ) ? $product->product_type : $product->get_type(),
|
684 |
+
'status' => $product_post->post_status,
|
685 |
+
'downloadable' => $product->is_downloadable(),
|
686 |
+
'virtual' => $product->is_virtual(),
|
687 |
+
'permalink' => $product->get_permalink(),
|
688 |
+
'sku' => $product->get_sku(),
|
689 |
+
'price' => $product->get_price(),
|
690 |
+
'regular_price' => $product->get_regular_price(),
|
691 |
+
'sale_price' => $product->get_sale_price() ? $product->get_sale_price() : null,
|
692 |
+
'price_html' => $product->get_price_html(),
|
693 |
+
'taxable' => $product->is_taxable(),
|
694 |
+
'tax_status' => $product->get_tax_status(),
|
695 |
+
'tax_class' => $product->get_tax_class(),
|
696 |
+
'managing_stock' => $product->managing_stock(),
|
697 |
+
'stock_quantity' => $product->get_stock_quantity(),
|
698 |
+
'in_stock' => $product->is_in_stock(),
|
699 |
+
'backorders_allowed' => $product->backorders_allowed(),
|
700 |
+
'backordered' => $product->is_on_backorder(),
|
701 |
+
'sold_individually' => $product->is_sold_individually(),
|
702 |
+
'purchaseable' => $product->is_purchasable(),
|
703 |
+
'featured' => $product->is_featured(),
|
704 |
+
'visible' => $product->is_visible(),
|
705 |
+
'catalog_visibility' => version_compare( WC_VERSION, '3.0.0', '<' ) ? $product->visibility : $product->get_catalog_visibility(),
|
706 |
+
'on_sale' => $product->is_on_sale(),
|
707 |
+
'product_url' => $product->is_type( 'external' ) ? $product->get_product_url() : '',
|
708 |
+
'button_text' => $product->is_type( 'external' ) ? $product->get_button_text() : '',
|
709 |
+
'weight' => $product->get_weight() ? $product->get_weight() : null,
|
710 |
+
'dimensions' => array(
|
711 |
+
'length' => version_compare( WC_VERSION, '3.0.0', '<' ) ? $product->length : $product->get_length(),
|
712 |
+
'width' => version_compare( WC_VERSION, '3.0.0', '<' ) ? $product->width : $product->get_width(),
|
713 |
+
'height' => version_compare( WC_VERSION, '3.0.0', '<' ) ? $product->height : $product->get_height(),
|
714 |
+
'unit' => get_option( 'woocommerce_dimension_unit' ),
|
715 |
+
),
|
716 |
+
'shipping_required' => $product->needs_shipping(),
|
717 |
+
'shipping_taxable' => $product->is_shipping_taxable(),
|
718 |
+
'shipping_class' => $product->get_shipping_class(),
|
719 |
+
'shipping_class_id' => ( 0 !== $product->get_shipping_class_id() ) ? $product->get_shipping_class_id() : null,
|
720 |
+
'description' => wpautop( do_shortcode( $product_post->post_content ) ),
|
721 |
+
'short_description' => apply_filters( 'woocommerce_short_description', $product_post->post_excerpt ),
|
722 |
+
'reviews_allowed' => ( 'open' === $product_post->comment_status ),
|
723 |
+
'average_rating' => wc_format_decimal( $product->get_average_rating(), 2 ),
|
724 |
+
'rating_count' => (int) $product->get_rating_count(),
|
725 |
+
'related_ids' => array_map( 'absint', array_values( ( version_compare( WC_VERSION, '3.0.0', '<' ) ? $product->get_related() : wc_get_related_products( $product->get_id() ) ) ) ),
|
726 |
+
'upsell_ids' => array_map( 'absint', ( version_compare( WC_VERSION, '3.0.0', '<' ) ? $product->get_upsells() : $product->get_upsell_ids() ) ),
|
727 |
+
'cross_sell_ids' => array_map( 'absint', ( version_compare( WC_VERSION, '3.0.0', '<' ) ? $product->get_cross_sells() : $product->get_cross_sell_ids() ) ),
|
728 |
+
'parent_id' => $product->is_type( 'variation' ) ? ( version_compare( WC_VERSION, '3.0.0', '<' ) ? $product->parent->id : $product->get_parent_id() ) : ( version_compare( WC_VERSION, '3.0.0', '<' ) ? $product->post->post_parent : $product->get_id() ),
|
729 |
+
'categories' => wp_get_post_terms( version_compare( WC_VERSION, '3.0.0', '<' ) ? $product->id : $product->get_id(), 'product_cat', array( 'fields' => 'names' ) ),
|
730 |
+
'tags' => wp_get_post_terms( version_compare( WC_VERSION, '3.0.0', '<' ) ? $product->id : $product->get_id(), 'product_tag', array( 'fields' => 'names' ) ),
|
731 |
+
'images' => $this->get_images( $product ),
|
732 |
+
'featured_src' => (string) wp_get_attachment_url( get_post_thumbnail_id( $product->is_type( 'variation' ) ? ( version_compare( WC_VERSION, '3.0.0', '<' ) ? $product->variation_id : $product->get_id() ) : ( version_compare( WC_VERSION, '3.0.0', '<' ) ? $product->id : $product->get_id() ) ) ),
|
733 |
+
'attributes' => $this->get_attributes( $product ),
|
734 |
+
'downloads' => $this->get_downloads( $product ),
|
735 |
+
'download_limit' => (int) version_compare( WC_VERSION, '3.0.0', '<' ) ? $product->download_limit : $product->get_download_limit(),
|
736 |
+
'download_expiry' => (int) version_compare( WC_VERSION, '3.0.0', '<' ) ? $product->download_expiry : $product->get_download_expiry(),
|
737 |
+
'download_type' => 'standard',
|
738 |
+
'purchase_note' => wpautop( do_shortcode( wp_kses_post( version_compare( WC_VERSION, '3.0.0', '<' ) ? $product->purchase_note : $product->get_purchase_note() ) ) ),
|
739 |
+
'total_sales' => metadata_exists( 'post', version_compare( WC_VERSION, '3.0.0', '<' ) ? $product->id : $product->get_id(), 'total_sales' ) ? (int) get_post_meta( version_compare( WC_VERSION, '3.0.0', '<' ) ? $product->id : $product->get_id(), 'total_sales', true ) : 0,
|
740 |
+
'variations' => array(),
|
741 |
+
'parent' => array(),
|
742 |
+
'grouped_products' => array(),
|
743 |
+
'menu_order' => $this->get_product_menu_order( $product ),
|
744 |
+
);
|
745 |
+
}
|
746 |
+
|
747 |
+
/**
|
748 |
+
* Get product menu order.
|
749 |
+
*
|
750 |
+
* @since 2.5.3
|
751 |
+
* @param WC_Product $product
|
752 |
+
* @return int
|
753 |
+
*/
|
754 |
+
private function get_product_menu_order( $product ) {
|
755 |
+
$product_post = version_compare( WC_VERSION, '3.0.0', '<' ) ? $product->get_post_data() : get_post( version_compare( WC_VERSION, '3.0.0', '<' ) ? $product->id : $product->get_id() );
|
756 |
+
|
757 |
+
$menu_order = $product_post->menu_order;
|
758 |
+
|
759 |
+
if ( $product->is_type( 'variation' ) ) {
|
760 |
+
$_product = get_post( version_compare( WC_VERSION, '3.0.0', '<' ) ? $product->get_variation_id() : $product->get_id() );
|
761 |
+
$menu_order = $_product->menu_order;
|
762 |
+
}
|
763 |
+
|
764 |
+
return apply_filters( 'woocommerce_api_product_menu_order', $menu_order, $product );
|
765 |
+
}
|
766 |
+
|
767 |
+
/**
|
768 |
+
* Get an individual variation's data
|
769 |
+
*
|
770 |
+
* @since 2.1
|
771 |
+
* @param WC_Product $product
|
772 |
+
* @return array
|
773 |
+
*/
|
774 |
+
private function get_variation_data( $product ) {
|
775 |
+
$variations = array();
|
776 |
+
|
777 |
+
foreach ( $product->get_children() as $child_id ) {
|
778 |
+
|
779 |
+
$variation = version_compare( WC_VERSION, '3.0.0', '<' ) ? $product->get_child( $child_id ) : wc_get_product( $child_id );
|
780 |
+
|
781 |
+
if ( ! $variation->exists() ) {
|
782 |
+
continue;
|
783 |
+
}
|
784 |
+
|
785 |
+
$post_data = get_post( version_compare( WC_VERSION, '3.0.0', '<' ) ? $variation->get_variation_id() : $variation->get_id() );
|
786 |
+
|
787 |
+
$variations[] = array(
|
788 |
+
'id' => version_compare( WC_VERSION, '3.0.0', '<' ) ? $variation->get_variation_id() : $variation->get_id(),
|
789 |
+
'created_at' => $this->format_datetime( $post_data->post_date_gmt ),
|
790 |
+
'updated_at' => $this->format_datetime( $post_data->post_modified_gmt ),
|
791 |
+
'downloadable' => $variation->is_downloadable(),
|
792 |
+
'virtual' => $variation->is_virtual(),
|
793 |
+
'permalink' => $variation->get_permalink(),
|
794 |
+
'sku' => $variation->get_sku(),
|
795 |
+
'price' => $variation->get_price(),
|
796 |
+
'regular_price' => $variation->get_regular_price(),
|
797 |
+
'sale_price' => $variation->get_sale_price() ? $variation->get_sale_price() : null,
|
798 |
+
'taxable' => $variation->is_taxable(),
|
799 |
+
'tax_status' => $variation->get_tax_status(),
|
800 |
+
'tax_class' => $variation->get_tax_class(),
|
801 |
+
'managing_stock' => $variation->managing_stock(),
|
802 |
+
'stock_quantity' => $variation->get_stock_quantity(),
|
803 |
+
'in_stock' => $variation->is_in_stock(),
|
804 |
+
'backorders_allowed' => $variation->backorders_allowed(),
|
805 |
+
'backordered' => $variation->is_on_backorder(),
|
806 |
+
'purchaseable' => $variation->is_purchasable(),
|
807 |
+
'visible' => $variation->variation_is_visible(),
|
808 |
+
'on_sale' => $variation->is_on_sale(),
|
809 |
+
'weight' => $variation->get_weight() ? $variation->get_weight() : null,
|
810 |
+
'dimensions' => array(
|
811 |
+
'length' => version_compare( WC_VERSION, '3.0.0', '<' ) ? $variation->length : $variation->get_length(),
|
812 |
+
'width' => version_compare( WC_VERSION, '3.0.0', '<' ) ? $variation->width : $variation->get_width(),
|
813 |
+
'height' => version_compare( WC_VERSION, '3.0.0', '<' ) ? $variation->height : $variation->get_height(),
|
814 |
+
'unit' => get_option( 'woocommerce_dimension_unit' ),
|
815 |
+
),
|
816 |
+
'shipping_class' => $variation->get_shipping_class(),
|
817 |
+
'shipping_class_id' => ( 0 !== $variation->get_shipping_class_id() ) ? $variation->get_shipping_class_id() : null,
|
818 |
+
'image' => $this->get_images( $variation ),
|
819 |
+
'attributes' => $this->get_attributes( $variation ),
|
820 |
+
'downloads' => $this->get_downloads( $variation ),
|
821 |
+
'download_limit' => (int) version_compare( WC_VERSION, '3.0.0', '<' ) ? $product->download_limit : $product->get_download_limit(),
|
822 |
+
'download_expiry' => (int) version_compare( WC_VERSION, '3.0.0', '<' ) ? $product->download_expiry : $product->get_download_expiry(),
|
823 |
+
);
|
824 |
+
}
|
825 |
+
|
826 |
+
return $variations;
|
827 |
+
}
|
828 |
+
|
829 |
+
/**
|
830 |
+
* Get grouped products data
|
831 |
+
*
|
832 |
+
* @since 2.5.0
|
833 |
+
* @param WC_Product $product
|
834 |
+
*
|
835 |
+
* @return array
|
836 |
+
*/
|
837 |
+
private function get_grouped_products_data( $product ) {
|
838 |
+
$products = array();
|
839 |
+
|
840 |
+
foreach ( $product->get_children() as $child_id ) {
|
841 |
+
$_product = version_compare( WC_VERSION, '3.0.0', '<' ) ? $product->get_child( $child_id ) : wc_get_product( $child_id );
|
842 |
+
|
843 |
+
if ( ! $_product->exists() ) {
|
844 |
+
continue;
|
845 |
+
}
|
846 |
+
|
847 |
+
$products[] = $this->get_product_data( $_product );
|
848 |
+
|
849 |
+
}
|
850 |
+
|
851 |
+
return $products;
|
852 |
+
}
|
853 |
+
|
854 |
+
/**
|
855 |
+
* Save product meta.
|
856 |
+
*
|
857 |
+
* @since 2.2
|
858 |
+
* @param int $product_id
|
859 |
+
* @param array $data
|
860 |
+
* @return bool
|
861 |
+
* @throws WC_Square_API_Exception
|
862 |
+
*/
|
863 |
+
protected function save_product_meta( $product_id, $data ) {
|
864 |
+
global $wpdb;
|
865 |
+
|
866 |
+
// Product Type.
|
867 |
+
$product_type = null;
|
868 |
+
if ( isset( $data['type'] ) ) {
|
869 |
+
$product_type = wc_clean( $data['type'] );
|
870 |
+
wp_set_object_terms( $product_id, $product_type, 'product_type' );
|
871 |
+
} else {
|
872 |
+
$_product_type = get_the_terms( $product_id, 'product_type' );
|
873 |
+
if ( is_array( $_product_type ) ) {
|
874 |
+
$_product_type = current( $_product_type );
|
875 |
+
$product_type = $_product_type->slug;
|
876 |
+
}
|
877 |
+
}
|
878 |
+
|
879 |
+
// Default total sales.
|
880 |
+
add_post_meta( $product_id, 'total_sales', '0', true );
|
881 |
+
|
882 |
+
// Virtual.
|
883 |
+
if ( isset( $data['virtual'] ) ) {
|
884 |
+
update_post_meta( $product_id, '_virtual', ( true === $data['virtual'] ) ? 'yes' : 'no' );
|
885 |
+
}
|
886 |
+
|
887 |
+
// Tax status.
|
888 |
+
if ( isset( $data['tax_status'] ) ) {
|
889 |
+
update_post_meta( $product_id, '_tax_status', wc_clean( $data['tax_status'] ) );
|
890 |
+
}
|
891 |
+
|
892 |
+
// Tax Class.
|
893 |
+
if ( isset( $data['tax_class'] ) ) {
|
894 |
+
update_post_meta( $product_id, '_tax_class', wc_clean( $data['tax_class'] ) );
|
895 |
+
}
|
896 |
+
|
897 |
+
// Catalog Visibility.
|
898 |
+
if ( isset( $data['catalog_visibility'] ) ) {
|
899 |
+
update_post_meta( $product_id, '_visibility', wc_clean( $data['catalog_visibility'] ) );
|
900 |
+
}
|
901 |
+
|
902 |
+
// Purchase Note.
|
903 |
+
if ( isset( $data['purchase_note'] ) ) {
|
904 |
+
update_post_meta( $product_id, '_purchase_note', wc_clean( $data['purchase_note'] ) );
|
905 |
+
}
|
906 |
+
|
907 |
+
// Featured Product.
|
908 |
+
if ( isset( $data['featured'] ) ) {
|
909 |
+
update_post_meta( $product_id, '_featured', ( true === $data['featured'] ) ? 'yes' : 'no' );
|
910 |
+
}
|
911 |
+
|
912 |
+
// Shipping data.
|
913 |
+
$this->save_product_shipping_data( $product_id, $data );
|
914 |
+
|
915 |
+
// SKU.
|
916 |
+
if ( isset( $data['sku'] ) ) {
|
917 |
+
$sku = get_post_meta( $product_id, '_sku', true );
|
918 |
+
$new_sku = wc_clean( $data['sku'] );
|
919 |
+
|
920 |
+
if ( '' == $new_sku ) {
|
921 |
+
update_post_meta( $product_id, '_sku', '' );
|
922 |
+
} elseif ( $new_sku !== $sku ) {
|
923 |
+
if ( ! empty( $new_sku ) ) {
|
924 |
+
$unique_sku = wc_product_has_unique_sku( $product_id, $new_sku );
|
925 |
+
if ( ! $unique_sku ) {
|
926 |
+
throw new WC_Square_API_Exception( 'woocommerce_api_product_sku_already_exists', __( 'The SKU already exists on another product', 'woocommerce' ), 400 );
|
927 |
+
} else {
|
928 |
+
update_post_meta( $product_id, '_sku', $new_sku );
|
929 |
+
}
|
930 |
+
} else {
|
931 |
+
update_post_meta( $product_id, '_sku', '' );
|
932 |
+
}
|
933 |
+
}
|
934 |
+
}
|
935 |
+
|
936 |
+
// Attributes.
|
937 |
+
if ( isset( $data['attributes'] ) ) {
|
938 |
+
$attributes = array();
|
939 |
+
|
940 |
+
foreach ( $data['attributes'] as $attribute ) {
|
941 |
+
$is_taxonomy = 0;
|
942 |
+
$taxonomy = 0;
|
943 |
+
|
944 |
+
if ( ! isset( $attribute['name'] ) ) {
|
945 |
+
continue;
|
946 |
+
}
|
947 |
+
|
948 |
+
$attribute_slug = sanitize_title( $attribute['name'] );
|
949 |
+
|
950 |
+
if ( isset( $attribute['slug'] ) ) {
|
951 |
+
$taxonomy = $this->get_attribute_taxonomy_by_slug( $attribute['slug'] );
|
952 |
+
$attribute_slug = sanitize_title( $attribute['slug'] );
|
953 |
+
}
|
954 |
+
|
955 |
+
if ( $taxonomy ) {
|
956 |
+
$is_taxonomy = 1;
|
957 |
+
}
|
958 |
+
|
959 |
+
if ( $is_taxonomy ) {
|
960 |
+
|
961 |
+
if ( isset( $attribute['options'] ) ) {
|
962 |
+
$options = $attribute['options'];
|
963 |
+
|
964 |
+
if ( ! is_array( $attribute['options'] ) ) {
|
965 |
+
// Text based attributes - Posted values are term names.
|
966 |
+
$options = explode( WC_DELIMITER, $options );
|
967 |
+
}
|
968 |
+
|
969 |
+
$values = array_map( 'wc_sanitize_term_text_based', $options );
|
970 |
+
$values = array_filter( $values, 'strlen' );
|
971 |
+
} else {
|
972 |
+
$values = array();
|
973 |
+
}
|
974 |
+
|
975 |
+
// Update post terms.
|
976 |
+
if ( taxonomy_exists( $taxonomy ) ) {
|
977 |
+
wp_set_object_terms( $product_id, $values, $taxonomy );
|
978 |
+
}
|
979 |
+
|
980 |
+
if ( ! empty( $values ) ) {
|
981 |
+
// Add attribute to array, but don't set values.
|
982 |
+
$attributes[ $taxonomy ] = array(
|
983 |
+
'name' => $taxonomy,
|
984 |
+
'value' => '',
|
985 |
+
'position' => isset( $attribute['position'] ) ? (string) absint( $attribute['position'] ) : '0',
|
986 |
+
'is_visible' => ( isset( $attribute['visible'] ) && $attribute['visible'] ) ? 1 : 0,
|
987 |
+
'is_variation' => ( isset( $attribute['variation'] ) && $attribute['variation'] ) ? 1 : 0,
|
988 |
+
'is_taxonomy' => $is_taxonomy,
|
989 |
+
);
|
990 |
+
}
|
991 |
+
} elseif ( isset( $attribute['options'] ) ) {
|
992 |
+
// Array based.
|
993 |
+
if ( is_array( $attribute['options'] ) ) {
|
994 |
+
$values = implode( ' ' . WC_DELIMITER . ' ', array_map( 'wc_clean', $attribute['options'] ) );
|
995 |
+
|
996 |
+
// Text based, separate by pipe.
|
997 |
+
} else {
|
998 |
+
$values = implode( ' ' . WC_DELIMITER . ' ', array_map( 'wc_clean', explode( WC_DELIMITER, $attribute['options'] ) ) );
|
999 |
+
}
|
1000 |
+
|
1001 |
+
// Custom attribute - Add attribute to array and set the values.
|
1002 |
+
$attributes[ $attribute_slug ] = array(
|
1003 |
+
'name' => wc_clean( $attribute['name'] ),
|
1004 |
+
'value' => $values,
|
1005 |
+
'position' => isset( $attribute['position'] ) ? (string) absint( $attribute['position'] ) : '0',
|
1006 |
+
'is_visible' => ( isset( $attribute['visible'] ) && $attribute['visible'] ) ? 1 : 0,
|
1007 |
+
'is_variation' => ( isset( $attribute['variation'] ) && $attribute['variation'] ) ? 1 : 0,
|
1008 |
+
'is_taxonomy' => $is_taxonomy,
|
1009 |
+
);
|
1010 |
+
}
|
1011 |
+
}
|
1012 |
+
|
1013 |
+
uasort( $attributes, 'wc_product_attribute_uasort_comparison' );
|
1014 |
+
|
1015 |
+
update_post_meta( $product_id, '_product_attributes', $attributes );
|
1016 |
+
}
|
1017 |
+
|
1018 |
+
// Sales and prices.
|
1019 |
+
if ( in_array( $product_type, array( 'variable', 'grouped' ) ) ) {
|
1020 |
+
|
1021 |
+
// Variable and grouped products have no prices.
|
1022 |
+
update_post_meta( $product_id, '_regular_price', '' );
|
1023 |
+
update_post_meta( $product_id, '_sale_price', '' );
|
1024 |
+
update_post_meta( $product_id, '_sale_price_dates_from', '' );
|
1025 |
+
update_post_meta( $product_id, '_sale_price_dates_to', '' );
|
1026 |
+
update_post_meta( $product_id, '_price', '' );
|
1027 |
+
|
1028 |
+
} else {
|
1029 |
+
$current_regular_price = get_post_meta( $product_id, '_regular_price', true );
|
1030 |
+
$current_sale_price = get_post_meta( $product_id, '_sale_price', true );
|
1031 |
+
|
1032 |
+
// Regular Price passed from Square ( They only have one price ).
|
1033 |
+
if ( isset( $data['regular_price'] ) ) {
|
1034 |
+
// Check if current product is on sale.
|
1035 |
+
if ( $current_regular_price > $current_sale_price && $data['regular_price'] < $current_regular_price ) {
|
1036 |
+
$regular_price = get_post_meta( $product_id, '_regular_price', true );
|
1037 |
+
$sale_price = $data['regular_price'];
|
1038 |
+
} else {
|
1039 |
+
$regular_price = ( '' === $data['regular_price'] ) ? '' : $data['regular_price'];
|
1040 |
+
$sale_price = '';
|
1041 |
+
}
|
1042 |
+
} else {
|
1043 |
+
$regular_price = get_post_meta( $product_id, '_regular_price', true );
|
1044 |
+
$sale_price = get_post_meta( $product_id, '_sale_price', true );
|
1045 |
+
}
|
1046 |
+
|
1047 |
+
if ( isset( $data['sale_price_dates_from'] ) ) {
|
1048 |
+
$date_from = $data['sale_price_dates_from'];
|
1049 |
+
} else {
|
1050 |
+
$date_from = get_post_meta( $product_id, '_sale_price_dates_from', true );
|
1051 |
+
$date_from = ( '' === $date_from ) ? '' : date( 'Y-m-d', $date_from );
|
1052 |
+
}
|
1053 |
+
|
1054 |
+
if ( isset( $data['sale_price_dates_to'] ) ) {
|
1055 |
+
$date_to = $data['sale_price_dates_to'];
|
1056 |
+
} else {
|
1057 |
+
$date_to = get_post_meta( $product_id, '_sale_price_dates_to', true );
|
1058 |
+
$date_to = ( '' === $date_to ) ? '' : date( 'Y-m-d', $date_to );
|
1059 |
+
}
|
1060 |
+
|
1061 |
+
$this->wc_save_product_price( $product_id, $regular_price, $sale_price, $date_from, $date_to );
|
1062 |
+
}
|
1063 |
+
|
1064 |
+
// Product parent ID for groups.
|
1065 |
+
if ( isset( $data['parent_id'] ) ) {
|
1066 |
+
wp_update_post( array( 'ID' => $product_id, 'post_parent' => absint( $data['parent_id'] ) ) );
|
1067 |
+
}
|
1068 |
+
|
1069 |
+
// Update parent if grouped so price sorting works and stays in sync with the cheapest child.
|
1070 |
+
$_product = wc_get_product( $product_id );
|
1071 |
+
|
1072 |
+
if ( ( version_compare( WC_VERSION, '3.0.0', '<' ) ? $_product->post->post_parent : $_product->get_parent_id() ) > 0 || 'grouped' === $product_type ) {
|
1073 |
+
|
1074 |
+
$clear_parent_ids = array();
|
1075 |
+
|
1076 |
+
if ( ( version_compare( WC_VERSION, '3.0.0', '<' ) ? $_product->post->post_parent : $_product->get_parent_id() ) > 0 ) {
|
1077 |
+
$clear_parent_ids[] = ( version_compare( WC_VERSION, '3.0.0', '<' ) ? $_product->post->post_parent : $_product->get_parent_id() );
|
1078 |
+
}
|
1079 |
+
|
1080 |
+
if ( 'grouped' === $product_type ) {
|
1081 |
+
$clear_parent_ids[] = $product_id;
|
1082 |
+
}
|
1083 |
+
|
1084 |
+
if ( ! empty( $clear_parent_ids ) ) {
|
1085 |
+
foreach ( $clear_parent_ids as $clear_id ) {
|
1086 |
+
|
1087 |
+
$children_by_price = get_posts( array(
|
1088 |
+
'post_parent' => $clear_id,
|
1089 |
+
'orderby' => 'meta_value_num',
|
1090 |
+
'order' => 'asc',
|
1091 |
+
'meta_key' => '_price',
|
1092 |
+
'posts_per_page' => 1,
|
1093 |
+
'post_type' => 'product',
|
1094 |
+
'fields' => 'ids',
|
1095 |
+
) );
|
1096 |
+
|
1097 |
+
if ( $children_by_price ) {
|
1098 |
+
foreach ( $children_by_price as $child ) {
|
1099 |
+
$child_price = get_post_meta( $child, '_price', true );
|
1100 |
+
update_post_meta( $clear_id, '_price', $child_price );
|
1101 |
+
}
|
1102 |
+
}
|
1103 |
+
}
|
1104 |
+
}
|
1105 |
+
}
|
1106 |
+
|
1107 |
+
// Sold Individually.
|
1108 |
+
if ( isset( $data['sold_individually'] ) ) {
|
1109 |
+
update_post_meta( $product_id, '_sold_individually', ( true === $data['sold_individually'] ) ? 'yes' : '' );
|
1110 |
+
}
|
1111 |
+
|
1112 |
+
// Stock status.
|
1113 |
+
if ( isset( $data['in_stock'] ) ) {
|
1114 |
+
$stock_status = ( true === $data['in_stock'] ) ? 'instock' : 'outofstock';
|
1115 |
+
} else {
|
1116 |
+
$stock_status = get_post_meta( $product_id, '_stock_status', true );
|
1117 |
+
|
1118 |
+
if ( '' === $stock_status ) {
|
1119 |
+
$stock_status = 'instock';
|
1120 |
+
}
|
1121 |
+
}
|
1122 |
+
|
1123 |
+
// Stock Data.
|
1124 |
+
if ( 'yes' === get_option( 'woocommerce_manage_stock' ) ) {
|
1125 |
+
// Manage stock.
|
1126 |
+
if ( isset( $data['managing_stock'] ) ) {
|
1127 |
+
$managing_stock = ( true === $data['managing_stock'] ) ? 'yes' : 'no';
|
1128 |
+
update_post_meta( $product_id, '_manage_stock', $managing_stock );
|
1129 |
+
} else {
|
1130 |
+
$managing_stock = get_post_meta( $product_id, '_manage_stock', true );
|
1131 |
+
}
|
1132 |
+
|
1133 |
+
// Backorders.
|
1134 |
+
if ( isset( $data['backorders'] ) ) {
|
1135 |
+
if ( 'notify' === $data['backorders'] ) {
|
1136 |
+
$backorders = 'notify';
|
1137 |
+
} else {
|
1138 |
+
$backorders = ( true === $data['backorders'] ) ? 'yes' : 'no';
|
1139 |
+
}
|
1140 |
+
|
1141 |
+
update_post_meta( $product_id, '_backorders', $backorders );
|
1142 |
+
} else {
|
1143 |
+
$backorders = get_post_meta( $product_id, '_backorders', true );
|
1144 |
+
}
|
1145 |
+
|
1146 |
+
if ( 'grouped' === $product_type ) {
|
1147 |
+
|
1148 |
+
update_post_meta( $product_id, '_manage_stock', 'no' );
|
1149 |
+
update_post_meta( $product_id, '_backorders', 'no' );
|
1150 |
+
update_post_meta( $product_id, '_stock', '' );
|
1151 |
+
|
1152 |
+
wc_update_product_stock_status( $product_id, $stock_status );
|
1153 |
+
|
1154 |
+
} elseif ( 'external' === $product_type ) {
|
1155 |
+
|
1156 |
+
update_post_meta( $product_id, '_manage_stock', 'no' );
|
1157 |
+
update_post_meta( $product_id, '_backorders', 'no' );
|
1158 |
+
update_post_meta( $product_id, '_stock', '' );
|
1159 |
+
|
1160 |
+
wc_update_product_stock_status( $product_id, 'instock' );
|
1161 |
+
} elseif ( 'yes' === $managing_stock ) {
|
1162 |
+
update_post_meta( $product_id, '_backorders', $backorders );
|
1163 |
+
|
1164 |
+
// Stock status is always determined by children so sync later.
|
1165 |
+
if ( 'variable' !== $product_type ) {
|
1166 |
+
wc_update_product_stock_status( $product_id, $stock_status );
|
1167 |
+
}
|
1168 |
+
|
1169 |
+
// Stock quantity.
|
1170 |
+
if ( isset( $data['stock_quantity'] ) ) {
|
1171 |
+
wc_update_product_stock( $product_id, wc_stock_amount( $data['stock_quantity'] ) );
|
1172 |
+
} elseif ( isset( $data['inventory_delta'] ) ) {
|
1173 |
+
$stock_quantity = wc_stock_amount( get_post_meta( $product_id, '_stock', true ) );
|
1174 |
+
$stock_quantity += wc_stock_amount( $data['inventory_delta'] );
|
1175 |
+
|
1176 |
+
wc_update_product_stock( $product_id, wc_stock_amount( $stock_quantity ) );
|
1177 |
+
}
|
1178 |
+
} else {
|
1179 |
+
|
1180 |
+
// Don't manage stock.
|
1181 |
+
update_post_meta( $product_id, '_manage_stock', 'no' );
|
1182 |
+
update_post_meta( $product_id, '_backorders', $backorders );
|
1183 |
+
update_post_meta( $product_id, '_stock', '' );
|
1184 |
+
|
1185 |
+
wc_update_product_stock_status( $product_id, $stock_status );
|
1186 |
+
}
|
1187 |
+
} elseif ( 'variable' !== $product_type ) {
|
1188 |
+
wc_update_product_stock_status( $product_id, $stock_status );
|
1189 |
+
}
|
1190 |
+
|
1191 |
+
// Upsells.
|
1192 |
+
if ( isset( $data['upsell_ids'] ) ) {
|
1193 |
+
$upsells = array();
|
1194 |
+
$ids = $data['upsell_ids'];
|
1195 |
+
|
1196 |
+
if ( ! empty( $ids ) ) {
|
1197 |
+
foreach ( $ids as $id ) {
|
1198 |
+
if ( $id && $id > 0 ) {
|
1199 |
+
$upsells[] = $id;
|
1200 |
+
}
|
1201 |
+
}
|
1202 |
+
|
1203 |
+
update_post_meta( $product_id, '_upsell_ids', $upsells );
|
1204 |
+
} else {
|
1205 |
+
delete_post_meta( $product_id, '_upsell_ids' );
|
1206 |
+
}
|
1207 |
+
}
|
1208 |
+
|
1209 |
+
// Cross sells.
|
1210 |
+
if ( isset( $data['cross_sell_ids'] ) ) {
|
1211 |
+
$crosssells = array();
|
1212 |
+
$ids = $data['cross_sell_ids'];
|
1213 |
+
|
1214 |
+
if ( ! empty( $ids ) ) {
|
1215 |
+
foreach ( $ids as $id ) {
|
1216 |
+
if ( $id && $id > 0 ) {
|
1217 |
+
$crosssells[] = $id;
|
1218 |
+
}
|
1219 |
+
}
|
1220 |
+
|
1221 |
+
update_post_meta( $product_id, '_crosssell_ids', $crosssells );
|
1222 |
+
} else {
|
1223 |
+
delete_post_meta( $product_id, '_crosssell_ids' );
|
1224 |
+
}
|
1225 |
+
}
|
1226 |
+
|
1227 |
+
// Product categories.
|
1228 |
+
if ( isset( $data['categories'] ) && is_array( $data['categories'] ) ) {
|
1229 |
+
$term_ids = array_unique( array_map( 'intval', $data['categories'] ) );
|
1230 |
+
wp_set_object_terms( $product_id, $term_ids, 'product_cat' );
|
1231 |
+
}
|
1232 |
+
|
1233 |
+
// Product tags.
|
1234 |
+
if ( isset( $data['tags'] ) && is_array( $data['tags'] ) ) {
|
1235 |
+
$term_ids = array_unique( array_map( 'intval', $data['tags'] ) );
|
1236 |
+
wp_set_object_terms( $product_id, $term_ids, 'product_tag' );
|
1237 |
+
}
|
1238 |
+
|
1239 |
+
// Downloadable.
|
1240 |
+
if ( isset( $data['downloadable'] ) ) {
|
1241 |
+
$is_downloadable = ( true === $data['downloadable'] ) ? 'yes' : 'no';
|
1242 |
+
update_post_meta( $product_id, '_downloadable', $is_downloadable );
|
1243 |
+
} else {
|
1244 |
+
$is_downloadable = get_post_meta( $product_id, '_downloadable', true );
|
1245 |
+
}
|
1246 |
+
|
1247 |
+
// Downloadable options.
|
1248 |
+
if ( 'yes' == $is_downloadable ) {
|
1249 |
+
|
1250 |
+
// Downloadable files.
|
1251 |
+
if ( isset( $data['downloads'] ) && is_array( $data['downloads'] ) ) {
|
1252 |
+
$this->save_downloadable_files( $product_id, $data['downloads'] );
|
1253 |
+
}
|
1254 |
+
|
1255 |
+
// Download limit.
|
1256 |
+
if ( isset( $data['download_limit'] ) ) {
|
1257 |
+
update_post_meta( $product_id, '_download_limit', ( '' === $data['download_limit'] ) ? '' : absint( $data['download_limit'] ) );
|
1258 |
+
}
|
1259 |
+
|
1260 |
+
// Download expiry.
|
1261 |
+
if ( isset( $data['download_expiry'] ) ) {
|
1262 |
+
update_post_meta( $product_id, '_download_expiry', ( '' === $data['download_expiry'] ) ? '' : absint( $data['download_expiry'] ) );
|
1263 |
+
}
|
1264 |
+
|
1265 |
+
// Download type.
|
1266 |
+
if ( isset( $data['download_type'] ) ) {
|
1267 |
+
update_post_meta( $product_id, '_download_type', wc_clean( $data['download_type'] ) );
|
1268 |
+
}
|
1269 |
+
}
|
1270 |
+
|
1271 |
+
// Product url.
|
1272 |
+
if ( 'external' === $product_type ) {
|
1273 |
+
if ( isset( $data['product_url'] ) ) {
|
1274 |
+
update_post_meta( $product_id, '_product_url', wc_clean( $data['product_url'] ) );
|
1275 |
+
}
|
1276 |
+
|
1277 |
+
if ( isset( $data['button_text'] ) ) {
|
1278 |
+
update_post_meta( $product_id, '_button_text', wc_clean( $data['button_text'] ) );
|
1279 |
+
}
|
1280 |
+
}
|
1281 |
+
|
1282 |
+
// Reviews allowed.
|
1283 |
+
if ( isset( $data['reviews_allowed'] ) ) {
|
1284 |
+
$reviews_allowed = ( true === $data['reviews_allowed'] ) ? 'open' : 'closed';
|
1285 |
+
|
1286 |
+
$wpdb->update( $wpdb->posts, array( 'comment_status' => $reviews_allowed ), array( 'ID' => $product_id ) );
|
1287 |
+
}
|
1288 |
+
|
1289 |
+
// Do action for product type
|
1290 |
+
do_action( 'woocommerce_api_process_product_meta_' . $product_type, $product_id, $data );
|
1291 |
+
|
1292 |
+
return true;
|
1293 |
+
}
|
1294 |
+
|
1295 |
+
/**
|
1296 |
+
* Save variations
|
1297 |
+
*
|
1298 |
+
* @since 2.2
|
1299 |
+
* @param int $id
|
1300 |
+
* @param array $data
|
1301 |
+
* @return bool
|
1302 |
+
* @throws WC_Square_API_Exception
|
1303 |
+
*/
|
1304 |
+
protected function save_variations( $id, $data ) {
|
1305 |
+
global $wpdb;
|
1306 |
+
|
1307 |
+
$variations = $data['variations'];
|
1308 |
+
$attributes = (array) maybe_unserialize( get_post_meta( $id, '_product_attributes', true ) );
|
1309 |
+
|
1310 |
+
foreach ( $variations as $menu_order => $variation ) {
|
1311 |
+
$variation_id = isset( $variation['id'] ) ? absint( $variation['id'] ) : 0;
|
1312 |
+
|
1313 |
+
if ( ! $variation_id && isset( $variation['sku'] ) ) {
|
1314 |
+
$variation_sku = wc_clean( $variation['sku'] );
|
1315 |
+
$variation_id = wc_get_product_id_by_sku( $variation_sku );
|
1316 |
+
}
|
1317 |
+
|
1318 |
+
// Generate a useful post title
|
1319 |
+
$variation_post_title = sprintf( __( 'Variation #%1$s of %2$s', 'woocommerce' ), $variation_id, esc_html( get_the_title( $id ) ) );
|
1320 |
+
|
1321 |
+
// Update or Add post
|
1322 |
+
if ( ! $variation_id ) {
|
1323 |
+
$post_status = ( isset( $variation['visible'] ) && false === $variation['visible'] ) ? 'private' : 'publish';
|
1324 |
+
|
1325 |
+
$new_variation = array(
|
1326 |
+
'post_title' => $variation_post_title,
|
1327 |
+
'post_content' => '',
|
1328 |
+
'post_status' => $post_status,
|
1329 |
+
'post_author' => get_current_user_id(),
|
1330 |
+
'post_parent' => $id,
|
1331 |
+
'post_type' => 'product_variation',
|
1332 |
+
'menu_order' => $menu_order,
|
1333 |
+
);
|
1334 |
+
|
1335 |
+
$variation_id = wp_insert_post( $new_variation );
|
1336 |
+
|
1337 |
+
do_action( 'woocommerce_create_product_variation', $variation_id );
|
1338 |
+
} else {
|
1339 |
+
$update_variation = array( 'post_title' => $variation_post_title, 'menu_order' => $menu_order );
|
1340 |
+
if ( isset( $variation['visible'] ) ) {
|
1341 |
+
$post_status = ( false === $variation['visible'] ) ? 'private' : 'publish';
|
1342 |
+
$update_variation['post_status'] = $post_status;
|
1343 |
+
}
|
1344 |
+
|
1345 |
+
$wpdb->update( $wpdb->posts, $update_variation, array( 'ID' => $variation_id ) );
|
1346 |
+
|
1347 |
+
do_action( 'woocommerce_update_product_variation', $variation_id );
|
1348 |
+
}
|
1349 |
+
|
1350 |
+
// Stop with we don't have a variation ID
|
1351 |
+
if ( is_wp_error( $variation_id ) ) {
|
1352 |
+
throw new WC_Square_API_Exception( 'woocommerce_api_cannot_save_product_variation', $variation_id->get_error_message(), 400 );
|
1353 |
+
}
|
1354 |
+
|
1355 |
+
// SKU
|
1356 |
+
if ( isset( $variation['sku'] ) ) {
|
1357 |
+
$sku = get_post_meta( $variation_id, '_sku', true );
|
1358 |
+
$new_sku = wc_clean( $variation['sku'] );
|
1359 |
+
|
1360 |
+
if ( '' == $new_sku ) {
|
1361 |
+
update_post_meta( $variation_id, '_sku', '' );
|
1362 |
+
} elseif ( $new_sku !== $sku ) {
|
1363 |
+
if ( ! empty( $new_sku ) ) {
|
1364 |
+
$unique_sku = wc_product_has_unique_sku( $variation_id, $new_sku );
|
1365 |
+
if ( ! $unique_sku ) {
|
1366 |
+
throw new WC_Square_API_Exception( 'woocommerce_api_product_sku_already_exists', __( 'The SKU already exists on another product', 'woocommerce' ), 400 );
|
1367 |
+
} else {
|
1368 |
+
update_post_meta( $variation_id, '_sku', $new_sku );
|
1369 |
+
}
|
1370 |
+
} else {
|
1371 |
+
update_post_meta( $variation_id, '_sku', '' );
|
1372 |
+
}
|
1373 |
+
}
|
1374 |
+
}
|
1375 |
+
|
1376 |
+
// Thumbnail.
|
1377 |
+
if ( isset( $variation['image'] ) && is_array( $variation['image'] ) ) {
|
1378 |
+
$image = current( $variation['image'] );
|
1379 |
+
if ( $image && is_array( $image ) ) {
|
1380 |
+
if ( isset( $image['position'] ) && 0 == $image['position'] ) {
|
1381 |
+
if ( isset( $image['src'] ) ) {
|
1382 |
+
$upload = $this->upload_product_image( wc_clean( $image['src'] ) );
|
1383 |
+
|
1384 |
+
if ( is_wp_error( $upload ) ) {
|
1385 |
+
throw new WC_Square_API_Exception( 'woocommerce_api_cannot_upload_product_image', $upload->get_error_message(), 400 );
|
1386 |
+
}
|
1387 |
+
|
1388 |
+
$attachment_id = $this->set_product_image_as_attachment( $upload, $id );
|
1389 |
+
} elseif ( isset( $image['id'] ) ) {
|
1390 |
+
$attachment_id = $image['id'];
|
1391 |
+
}
|
1392 |
+
|
1393 |
+
// Set the image alt if present.
|
1394 |
+
if ( ! empty( $image['alt'] ) ) {
|
1395 |
+
update_post_meta( $attachment_id, '_wp_attachment_image_alt', wc_clean( $image['alt'] ) );
|
1396 |
+
}
|
1397 |
+
|
1398 |
+
// Set the image title if present.
|
1399 |
+
if ( ! empty( $image['title'] ) ) {
|
1400 |
+
wp_update_post( array( 'ID' => $attachment_id, 'post_title' => $image['title'] ) );
|
1401 |
+
}
|
1402 |
+
|
1403 |
+
update_post_meta( $variation_id, '_thumbnail_id', $attachment_id );
|
1404 |
+
}
|
1405 |
+
} else {
|
1406 |
+
delete_post_meta( $variation_id, '_thumbnail_id' );
|
1407 |
+
}
|
1408 |
+
}
|
1409 |
+
|
1410 |
+
// Virtual variation
|
1411 |
+
if ( isset( $variation['virtual'] ) ) {
|
1412 |
+
$is_virtual = ( true === $variation['virtual'] ) ? 'yes' : 'no';
|
1413 |
+
update_post_meta( $variation_id, '_virtual', $is_virtual );
|
1414 |
+
}
|
1415 |
+
|
1416 |
+
// Downloadable variation
|
1417 |
+
if ( isset( $variation['downloadable'] ) ) {
|
1418 |
+
$is_downloadable = ( true === $variation['downloadable'] ) ? 'yes' : 'no';
|
1419 |
+
update_post_meta( $variation_id, '_downloadable', $is_downloadable );
|
1420 |
+
} else {
|
1421 |
+
$is_downloadable = get_post_meta( $variation_id, '_downloadable', true );
|
1422 |
+
}
|
1423 |
+
|
1424 |
+
// Shipping data
|
1425 |
+
$this->save_product_shipping_data( $variation_id, $variation );
|
1426 |
+
|
1427 |
+
// Stock handling
|
1428 |
+
if ( isset( $variation['managing_stock'] ) ) {
|
1429 |
+
$managing_stock = ( true === $variation['managing_stock'] ) ? 'yes' : 'no';
|
1430 |
+
} else {
|
1431 |
+
$managing_stock = get_post_meta( $variation_id, '_manage_stock', true );
|
1432 |
+
}
|
1433 |
+
|
1434 |
+
update_post_meta( $variation_id, '_manage_stock', '' === $managing_stock ? 'no' : $managing_stock );
|
1435 |
+
|
1436 |
+
if ( isset( $variation['in_stock'] ) ) {
|
1437 |
+
$stock_status = ( true === $variation['in_stock'] ) ? 'instock' : 'outofstock';
|
1438 |
+
} else {
|
1439 |
+
$stock_status = get_post_meta( $variation_id, '_stock_status', true );
|
1440 |
+
}
|
1441 |
+
|
1442 |
+
wc_update_product_stock_status( $variation_id, '' === $stock_status ? 'instock' : $stock_status );
|
1443 |
+
|
1444 |
+
if ( 'yes' === $managing_stock ) {
|
1445 |
+
$backorders = get_post_meta( $variation_id, '_backorders', true );
|
1446 |
+
|
1447 |
+
if ( isset( $variation['backorders'] ) ) {
|
1448 |
+
if ( 'notify' === $variation['backorders'] ) {
|
1449 |
+
$backorders = 'notify';
|
1450 |
+
} else {
|
1451 |
+
$backorders = ( true === $variation['backorders'] ) ? 'yes' : 'no';
|
1452 |
+
}
|
1453 |
+
}
|
1454 |
+
|
1455 |
+
update_post_meta( $variation_id, '_backorders', '' === $backorders ? 'no' : $backorders );
|
1456 |
+
|
1457 |
+
if ( isset( $variation['stock_quantity'] ) ) {
|
1458 |
+
wc_update_product_stock( $variation_id, wc_stock_amount( $variation['stock_quantity'] ) );
|
1459 |
+
} elseif ( isset( $data['inventory_delta'] ) ) {
|
1460 |
+
$stock_quantity = wc_stock_amount( get_post_meta( $variation_id, '_stock', true ) );
|
1461 |
+
$stock_quantity += wc_stock_amount( $data['inventory_delta'] );
|
1462 |
+
|
1463 |
+
wc_update_product_stock( $variation_id, wc_stock_amount( $stock_quantity ) );
|
1464 |
+
}
|
1465 |
+
} else {
|
1466 |
+
delete_post_meta( $variation_id, '_backorders' );
|
1467 |
+
delete_post_meta( $variation_id, '_stock' );
|
1468 |
+
}
|
1469 |
+
|
1470 |
+
$current_regular_price = get_post_meta( $variation_id, '_regular_price', true );
|
1471 |
+
$current_sale_price = get_post_meta( $variation_id, '_sale_price', true );
|
1472 |
+
|
1473 |
+
// Regular Price passed from Square ( They only have one price ).
|
1474 |
+
if ( isset( $variation['regular_price'] ) ) {
|
1475 |
+
// Check if current product is on sale.
|
1476 |
+
if ( $current_regular_price > $current_sale_price && $variation['regular_price'] < $current_regular_price ) {
|
1477 |
+
$regular_price = get_post_meta( $variation_id, '_regular_price', true );
|
1478 |
+
$sale_price = $variation['regular_price'];
|
1479 |
+
} else {
|
1480 |
+
$regular_price = ( '' === $variation['regular_price'] ) ? '' : $variation['regular_price'];
|
1481 |
+
$sale_price = '';
|
1482 |
+
}
|
1483 |
+
} else {
|
1484 |
+
$regular_price = get_post_meta( $variation_id, '_regular_price', true );
|
1485 |
+
$sale_price = get_post_meta( $variation_id, '_sale_price', true );
|
1486 |
+
}
|
1487 |
+
|
1488 |
+
if ( isset( $variation['sale_price_dates_from'] ) ) {
|
1489 |
+
$date_from = $variation['sale_price_dates_from'];
|
1490 |
+
} else {
|
1491 |
+
$date_from = get_post_meta( $variation_id, '_sale_price_dates_from', true );
|
1492 |
+
$date_from = ( '' === $date_from ) ? '' : date( 'Y-m-d', $date_from );
|
1493 |
+
}
|
1494 |
+
|
1495 |
+
if ( isset( $variation['sale_price_dates_to'] ) ) {
|
1496 |
+
$date_to = $variation['sale_price_dates_to'];
|
1497 |
+
} else {
|
1498 |
+
$date_to = get_post_meta( $variation_id, '_sale_price_dates_to', true );
|
1499 |
+
$date_to = ( '' === $date_to ) ? '' : date( 'Y-m-d', $date_to );
|
1500 |
+
}
|
1501 |
+
|
1502 |
+
$this->wc_save_product_price( $variation_id, $regular_price, $sale_price, $date_from, $date_to );
|
1503 |
+
|
1504 |
+
// Tax class
|
1505 |
+
if ( isset( $variation['tax_class'] ) ) {
|
1506 |
+
if ( 'parent' !== $variation['tax_class'] ) {
|
1507 |
+
update_post_meta( $variation_id, '_tax_class', wc_clean( $variation['tax_class'] ) );
|
1508 |
+
} else {
|
1509 |
+
delete_post_meta( $variation_id, '_tax_class' );
|
1510 |
+
}
|
1511 |
+
}
|
1512 |
+
|
1513 |
+
// Downloads
|
1514 |
+
if ( 'yes' == $is_downloadable ) {
|
1515 |
+
// Downloadable files
|
1516 |
+
if ( isset( $variation['downloads'] ) && is_array( $variation['downloads'] ) ) {
|
1517 |
+
$this->save_downloadable_files( $id, $variation['downloads'], $variation_id );
|
1518 |
+
}
|
1519 |
+
|
1520 |
+
// Download limit
|
1521 |
+
if ( isset( $variation['download_limit'] ) ) {
|
1522 |
+
$download_limit = absint( $variation['download_limit'] );
|
1523 |
+
update_post_meta( $variation_id, '_download_limit', ( ! $download_limit ) ? '' : $download_limit );
|
1524 |
+
}
|
1525 |
+
|
1526 |
+
// Download expiry
|
1527 |
+
if ( isset( $variation['download_expiry'] ) ) {
|
1528 |
+
$download_expiry = absint( $variation['download_expiry'] );
|
1529 |
+
update_post_meta( $variation_id, '_download_expiry', ( ! $download_expiry ) ? '' : $download_expiry );
|
1530 |
+
}
|
1531 |
+
} else {
|
1532 |
+
update_post_meta( $variation_id, '_download_limit', '' );
|
1533 |
+
update_post_meta( $variation_id, '_download_expiry', '' );
|
1534 |
+
update_post_meta( $variation_id, '_downloadable_files', '' );
|
1535 |
+
}
|
1536 |
+
|
1537 |
+
// Description.
|
1538 |
+
if ( isset( $variation['description'] ) ) {
|
1539 |
+
update_post_meta( $variation_id, '_variation_description', wp_kses_post( $variation['description'] ) );
|
1540 |
+
}
|
1541 |
+
|
1542 |
+
// Update taxonomies
|
1543 |
+
if ( isset( $variation['attributes'] ) ) {
|
1544 |
+
$updated_attribute_keys = array();
|
1545 |
+
|
1546 |
+
foreach ( $variation['attributes'] as $attribute_key => $attribute ) {
|
1547 |
+
if ( ! isset( $attribute['name'] ) ) {
|
1548 |
+
continue;
|
1549 |
+
}
|
1550 |
+
|
1551 |
+
$taxonomy = 0;
|
1552 |
+
$_attribute = array();
|
1553 |
+
|
1554 |
+
if ( isset( $attribute['slug'] ) ) {
|
1555 |
+
$taxonomy = $this->get_attribute_taxonomy_by_slug( $attribute['slug'] );
|
1556 |
+
}
|
1557 |
+
|
1558 |
+
if ( ! $taxonomy ) {
|
1559 |
+
$taxonomy = sanitize_title( $attribute['name'] );
|
1560 |
+
}
|
1561 |
+
|
1562 |
+
if ( isset( $attributes[ $taxonomy ] ) ) {
|
1563 |
+
$_attribute = $attributes[ $taxonomy ];
|
1564 |
+
}
|
1565 |
+
|
1566 |
+
if ( isset( $_attribute['is_variation'] ) && $_attribute['is_variation'] ) {
|
1567 |
+
$_attribute_key = 'attribute_' . sanitize_title( $_attribute['name'] );
|
1568 |
+
$updated_attribute_keys[] = $_attribute_key;
|
1569 |
+
|
1570 |
+
if ( isset( $_attribute['is_taxonomy'] ) && $_attribute['is_taxonomy'] ) {
|
1571 |
+
// Don't use wc_clean as it destroys sanitized characters
|
1572 |
+
$_attribute_value = isset( $attribute['option'] ) ? sanitize_title( stripslashes( $attribute['option'] ) ) : '';
|
1573 |
+
} else {
|
1574 |
+
$_attribute_value = isset( $attribute['option'] ) ? wc_clean( stripslashes( $attribute['option'] ) ) : '';
|
1575 |
+
}
|
1576 |
+
|
1577 |
+
update_post_meta( $variation_id, $_attribute_key, $_attribute_value );
|
1578 |
+
}
|
1579 |
+
}
|
1580 |
+
|
1581 |
+
// Remove old taxonomies attributes so data is kept up to date - first get attribute key names
|
1582 |
+
$delete_attribute_keys = $wpdb->get_col( $wpdb->prepare( "SELECT meta_key FROM {$wpdb->postmeta} WHERE meta_key LIKE 'attribute_%%' AND meta_key NOT IN ( '" . implode( "','", $updated_attribute_keys ) . "' ) AND post_id = %d;", $variation_id ) );
|
1583 |
+
|
1584 |
+
foreach ( $delete_attribute_keys as $key ) {
|
1585 |
+
delete_post_meta( $variation_id, $key );
|
1586 |
+
}
|
1587 |
+
}
|
1588 |
+
|
1589 |
+
do_action( 'woocommerce_api_save_product_variation', $variation_id, $menu_order, $variation );
|
1590 |
+
}
|
1591 |
+
|
1592 |
+
// Update parent if variable so price sorting works and stays in sync with the cheapest child
|
1593 |
+
WC_Product_Variable::sync( $id );
|
1594 |
+
|
1595 |
+
// Update default attributes options setting
|
1596 |
+
if ( isset( $data['default_attribute'] ) ) {
|
1597 |
+
$data['default_attributes'] = $data['default_attribute'];
|
1598 |
+
}
|
1599 |
+
|
1600 |
+
if ( isset( $data['default_attributes'] ) && is_array( $data['default_attributes'] ) ) {
|
1601 |
+
$default_attributes = array();
|
1602 |
+
|
1603 |
+
foreach ( $data['default_attributes'] as $default_attr_key => $default_attr ) {
|
1604 |
+
if ( ! isset( $default_attr['name'] ) ) {
|
1605 |
+
continue;
|
1606 |
+
}
|
1607 |
+
|
1608 |
+
$taxonomy = sanitize_title( $default_attr['name'] );
|
1609 |
+
|
1610 |
+
if ( isset( $default_attr['slug'] ) ) {
|
1611 |
+
$taxonomy = $this->get_attribute_taxonomy_by_slug( $default_attr['slug'] );
|
1612 |
+
}
|
1613 |
+
|
1614 |
+
if ( isset( $attributes[ $taxonomy ] ) ) {
|
1615 |
+
$_attribute = $attributes[ $taxonomy ];
|
1616 |
+
|
1617 |
+
if ( $_attribute['is_variation'] ) {
|
1618 |
+
$value = '';
|
1619 |
+
|
1620 |
+
if ( isset( $default_attr['option'] ) ) {
|
1621 |
+
if ( $_attribute['is_taxonomy'] ) {
|
1622 |
+
// Don't use wc_clean as it destroys sanitized characters
|
1623 |
+
$value = sanitize_title( trim( stripslashes( $default_attr['option'] ) ) );
|
1624 |
+
} else {
|
1625 |
+
$value = wc_clean( trim( stripslashes( $default_attr['option'] ) ) );
|
1626 |
+
}
|
1627 |
+
}
|
1628 |
+
|
1629 |
+
if ( $value ) {
|
1630 |
+
$default_attributes[ $taxonomy ] = $value;
|
1631 |
+
}
|
1632 |
+
}
|
1633 |
+
}
|
1634 |
+
}
|
1635 |
+
|
1636 |
+
update_post_meta( $id, '_default_attributes', $default_attributes );
|
1637 |
+
}
|
1638 |
+
|
1639 |
+
return true;
|
1640 |
+
}
|
1641 |
+
|
1642 |
+
/**
|
1643 |
+
* Save product shipping data
|
1644 |
+
*
|
1645 |
+
* @since 2.2
|
1646 |
+
* @param int $id
|
1647 |
+
* @param array $data
|
1648 |
+
*/
|
1649 |
+
private function save_product_shipping_data( $id, $data ) {
|
1650 |
+
if ( isset( $data['weight'] ) ) {
|
1651 |
+
update_post_meta( $id, '_weight', ( '' === $data['weight'] ) ? '' : wc_format_decimal( $data['weight'] ) );
|
1652 |
+
}
|
1653 |
+
|
1654 |
+
// Product dimensions
|
1655 |
+
if ( isset( $data['dimensions'] ) ) {
|
1656 |
+
// Height
|
1657 |
+
if ( isset( $data['dimensions']['height'] ) ) {
|
1658 |
+
update_post_meta( $id, '_height', ( '' === $data['dimensions']['height'] ) ? '' : wc_format_decimal( $data['dimensions']['height'] ) );
|
1659 |
+
}
|
1660 |
+
|
1661 |
+
// Width
|
1662 |
+
if ( isset( $data['dimensions']['width'] ) ) {
|
1663 |
+
update_post_meta( $id, '_width', ( '' === $data['dimensions']['width'] ) ? '' : wc_format_decimal( $data['dimensions']['width'] ) );
|
1664 |
+
}
|
1665 |
+
|
1666 |
+
// Length
|
1667 |
+
if ( isset( $data['dimensions']['length'] ) ) {
|
1668 |
+
update_post_meta( $id, '_length', ( '' === $data['dimensions']['length'] ) ? '' : wc_format_decimal( $data['dimensions']['length'] ) );
|
1669 |
+
}
|
1670 |
+
}
|
1671 |
+
|
1672 |
+
// Virtual
|
1673 |
+
if ( isset( $data['virtual'] ) ) {
|
1674 |
+
$virtual = ( true === $data['virtual'] ) ? 'yes' : 'no';
|
1675 |
+
|
1676 |
+
if ( 'yes' == $virtual ) {
|
1677 |
+
update_post_meta( $id, '_weight', '' );
|
1678 |
+
update_post_meta( $id, '_length', '' );
|
1679 |
+
update_post_meta( $id, '_width', '' );
|
1680 |
+
update_post_meta( $id, '_height', '' );
|
1681 |
+
}
|
1682 |
+
}
|
1683 |
+
|
1684 |
+
// Shipping class
|
1685 |
+
if ( isset( $data['shipping_class'] ) ) {
|
1686 |
+
wp_set_object_terms( $id, wc_clean( $data['shipping_class'] ), 'product_shipping_class' );
|
1687 |
+
}
|
1688 |
+
}
|
1689 |
+
|
1690 |
+
/**
|
1691 |
+
* Save downloadable files
|
1692 |
+
*
|
1693 |
+
* @since 2.2
|
1694 |
+
* @param int $product_id
|
1695 |
+
* @param array $downloads
|
1696 |
+
* @param int $variation_id
|
1697 |
+
*/
|
1698 |
+
private function save_downloadable_files( $product_id, $downloads, $variation_id = 0 ) {
|
1699 |
+
$files = array();
|
1700 |
+
|
1701 |
+
// File paths will be stored in an array keyed off md5(file path)
|
1702 |
+
foreach ( $downloads as $key => $file ) {
|
1703 |
+
if ( isset( $file['url'] ) ) {
|
1704 |
+
$file['file'] = $file['url'];
|
1705 |
+
}
|
1706 |
+
|
1707 |
+
if ( ! isset( $file['file'] ) ) {
|
1708 |
+
continue;
|
1709 |
+
}
|
1710 |
+
|
1711 |
+
$file_name = isset( $file['name'] ) ? wc_clean( $file['name'] ) : '';
|
1712 |
+
|
1713 |
+
if ( 0 === strpos( $file['file'], 'http' ) ) {
|
1714 |
+
$file_url = esc_url_raw( $file['file'] );
|
1715 |
+
} else {
|
1716 |
+
$file_url = wc_clean( $file['file'] );
|
1717 |
+
}
|
1718 |
+
|
1719 |
+
$files[ md5( $file_url ) ] = array(
|
1720 |
+
'name' => $file_name,
|
1721 |
+
'file' => $file_url,
|
1722 |
+
);
|
1723 |
+
}
|
1724 |
+
|
1725 |
+
// Grant permission to any newly added files on any existing orders for this product prior to saving
|
1726 |
+
do_action( 'woocommerce_process_product_file_download_paths', $product_id, $variation_id, $files );
|
1727 |
+
|
1728 |
+
$id = ( 0 === $variation_id ) ? $product_id : $variation_id;
|
1729 |
+
update_post_meta( $id, '_downloadable_files', $files );
|
1730 |
+
}
|
1731 |
+
|
1732 |
+
/**
|
1733 |
+
* Get attribute taxonomy by slug.
|
1734 |
+
*
|
1735 |
+
* @since 2.2
|
1736 |
+
* @param string $slug
|
1737 |
+
* @return string|null
|
1738 |
+
*/
|
1739 |
+
private function get_attribute_taxonomy_by_slug( $slug ) {
|
1740 |
+
$taxonomy = null;
|
1741 |
+
$attribute_taxonomies = wc_get_attribute_taxonomies();
|
1742 |
+
|
1743 |
+
foreach ( $attribute_taxonomies as $key => $tax ) {
|
1744 |
+
if ( $slug == $tax->attribute_name ) {
|
1745 |
+
$taxonomy = 'pa_' . $tax->attribute_name;
|
1746 |
+
|
1747 |
+
break;
|
1748 |
+
}
|
1749 |
+
}
|
1750 |
+
|
1751 |
+
return $taxonomy;
|
1752 |
+
}
|
1753 |
+
|
1754 |
+
/**
|
1755 |
+
* Get the images for a product or product variation
|
1756 |
+
*
|
1757 |
+
* @since 2.1
|
1758 |
+
* @param WC_Product|WC_Product_Variation $product
|
1759 |
+
* @return array
|
1760 |
+
*/
|
1761 |
+
private function get_images( $product ) {
|
1762 |
+
|
1763 |
+
$images = $attachment_ids = array();
|
1764 |
+
|
1765 |
+
if ( $product->is_type( 'variation' ) ) {
|
1766 |
+
|
1767 |
+
if ( has_post_thumbnail( version_compare( WC_VERSION, '3.0.0', '<' ) ? $product->get_variation_id() : $product->get_id() ) ) {
|
1768 |
+
|
1769 |
+
// Add variation image if set
|
1770 |
+
$attachment_ids[] = get_post_thumbnail_id( version_compare( WC_VERSION, '3.0.0', '<' ) ? $product->get_variation_id() : $product->get_id() );
|
1771 |
+
|
1772 |
+
} elseif ( has_post_thumbnail( version_compare( WC_VERSION, '3.0.0', '<' ) ? $product->id : $product->get_id() ) ) {
|
1773 |
+
|
1774 |
+
// Otherwise use the parent product featured image if set
|
1775 |
+
$attachment_ids[] = get_post_thumbnail_id( version_compare( WC_VERSION, '3.0.0', '<' ) ? $product->id : $product->get_id() );
|
1776 |
+
}
|
1777 |
+
} else {
|
1778 |
+
|
1779 |
+
// Add featured image
|
1780 |
+
if ( has_post_thumbnail( version_compare( WC_VERSION, '3.0.0', '<' ) ? $product->id : $product->get_id() ) ) {
|
1781 |
+
$attachment_ids[] = get_post_thumbnail_id( version_compare( WC_VERSION, '3.0.0', '<' ) ? $product->id : $product->get_id() );
|
1782 |
+
}
|
1783 |
+
|
1784 |
+
// Add gallery images
|
1785 |
+
$attachment_ids = array_merge( $attachment_ids, ( version_compare( WC_VERSION, '3.0.0', '<' ) ? $product->get_gallery_attachment_ids() : $product->get_gallery_image_ids() ) );
|
1786 |
+
}
|
1787 |
+
|
1788 |
+
// Build image data
|
1789 |
+
foreach ( $attachment_ids as $position => $attachment_id ) {
|
1790 |
+
|
1791 |
+
$attachment_post = get_post( $attachment_id );
|
1792 |
+
|
1793 |
+
if ( is_null( $attachment_post ) ) {
|
1794 |
+
continue;
|
1795 |
+
}
|
1796 |
+
|
1797 |
+
$attachment = wp_get_attachment_image_src( $attachment_id, 'full' );
|
1798 |
+
|
1799 |
+
if ( ! is_array( $attachment ) ) {
|
1800 |
+
continue;
|
1801 |
+
}
|
1802 |
+
|
1803 |
+
$images[] = array(
|
1804 |
+
'id' => (int) $attachment_id,
|
1805 |
+
'created_at' => $this->format_datetime( $attachment_post->post_date_gmt ),
|
1806 |
+
'updated_at' => $this->format_datetime( $attachment_post->post_modified_gmt ),
|
1807 |
+
'src' => current( $attachment ),
|
1808 |
+
'title' => get_the_title( $attachment_id ),
|
1809 |
+
'alt' => get_post_meta( $attachment_id, '_wp_attachment_image_alt', true ),
|
1810 |
+
'position' => (int) $position,
|
1811 |
+
);
|
1812 |
+
}
|
1813 |
+
|
1814 |
+
// Set a placeholder image if the product has no images set
|
1815 |
+
if ( empty( $images ) ) {
|
1816 |
+
|
1817 |
+
$images[] = array(
|
1818 |
+
'id' => 0,
|
1819 |
+
'created_at' => $this->format_datetime( time() ), // Default to now
|
1820 |
+
'updated_at' => $this->format_datetime( time() ),
|
1821 |
+
'src' => wc_placeholder_img_src(),
|
1822 |
+
'title' => __( 'Placeholder', 'woocommerce' ),
|
1823 |
+
'alt' => __( 'Placeholder', 'woocommerce' ),
|
1824 |
+
'position' => 0,
|
1825 |
+
);
|
1826 |
+
}
|
1827 |
+
|
1828 |
+
return $images;
|
1829 |
+
}
|
1830 |
+
|
1831 |
+
/**
|
1832 |
+
* Save product images.
|
1833 |
+
*
|
1834 |
+
* @since 2.2
|
1835 |
+
* @param array $images
|
1836 |
+
* @param int $id
|
1837 |
+
* @throws WC_Square_API_Exception
|
1838 |
+
*/
|
1839 |
+
protected function save_product_images( $id, $images ) {
|
1840 |
+
if ( is_array( $images ) ) {
|
1841 |
+
$gallery = array();
|
1842 |
+
|
1843 |
+
$product_image = get_the_post_thumbnail_url( $id, 'full' );
|
1844 |
+
|
1845 |
+
foreach ( $images as $image ) {
|
1846 |
+
/**
|
1847 |
+
* Because Square saves all images as original.jpeg when passed back,
|
1848 |
+
* we can't really check against filename. So we have no choice here but
|
1849 |
+
* to not sync the image if one already exists to prevent WP from creating
|
1850 |
+
* duplicate images all over the place. See https://github.com/woocommerce/woocommerce-square/issues/131
|
1851 |
+
*/
|
1852 |
+
if ( $product_image ) {
|
1853 |
+
continue;
|
1854 |
+
}
|
1855 |
+
|
1856 |
+
if ( isset( $image['position'] ) && 0 == $image['position'] ) {
|
1857 |
+
$attachment_id = isset( $image['id'] ) ? absint( $image['id'] ) : 0;
|
1858 |
+
|
1859 |
+
if ( 0 === $attachment_id && isset( $image['src'] ) ) {
|
1860 |
+
$upload = $this->upload_product_image( esc_url_raw( $image['src'] ) );
|
1861 |
+
|
1862 |
+
if ( is_wp_error( $upload ) ) {
|
1863 |
+
throw new WC_Square_API_Exception( 'woocommerce_api_cannot_upload_product_image', $upload->get_error_message(), 400 );
|
1864 |
+
}
|
1865 |
+
|
1866 |
+
$attachment_id = $this->set_product_image_as_attachment( $upload, $id );
|
1867 |
+
}
|
1868 |
+
|
1869 |
+
set_post_thumbnail( $id, $attachment_id );
|
1870 |
+
} else {
|
1871 |
+
$attachment_id = isset( $image['id'] ) ? absint( $image['id'] ) : 0;
|
1872 |
+
|
1873 |
+
if ( 0 === $attachment_id && isset( $image['src'] ) ) {
|
1874 |
+
$upload = $this->upload_product_image( esc_url_raw( $image['src'] ) );
|
1875 |
+
|
1876 |
+
if ( is_wp_error( $upload ) ) {
|
1877 |
+
throw new WC_Square_API_Exception( 'woocommerce_api_cannot_upload_product_image', $upload->get_error_message(), 400 );
|
1878 |
+
}
|
1879 |
+
|
1880 |
+
$attachment_id = $this->set_product_image_as_attachment( $upload, $id );
|
1881 |
+
}
|
1882 |
+
|
1883 |
+
$gallery[] = $attachment_id;
|
1884 |
+
}
|
1885 |
+
|
1886 |
+
// Set the image alt if present.
|
1887 |
+
if ( ! empty( $image['alt'] ) && $attachment_id ) {
|
1888 |
+
update_post_meta( $attachment_id, '_wp_attachment_image_alt', wc_clean( $image['alt'] ) );
|
1889 |
+
}
|
1890 |
+
|
1891 |
+
// Set the image title if present.
|
1892 |
+
if ( ! empty( $image['title'] ) && $attachment_id ) {
|
1893 |
+
wp_update_post( array( 'ID' => $attachment_id, 'post_title' => $image['title'] ) );
|
1894 |
+
}
|
1895 |
+
}
|
1896 |
+
|
1897 |
+
if ( ! empty( $gallery ) ) {
|
1898 |
+
update_post_meta( $id, '_product_image_gallery', implode( ',', $gallery ) );
|
1899 |
+
}
|
1900 |
+
} else {
|
1901 |
+
delete_post_thumbnail( $id );
|
1902 |
+
update_post_meta( $id, '_product_image_gallery', '' );
|
1903 |
+
}
|
1904 |
+
}
|
1905 |
+
|
1906 |
+
/**
|
1907 |
+
* Upload image from URL
|
1908 |
+
*
|
1909 |
+
* @since 2.2
|
1910 |
+
* @param string $image_url
|
1911 |
+
* @return int|WP_Error attachment id
|
1912 |
+
*/
|
1913 |
+
public function upload_product_image( $image_url ) {
|
1914 |
+
return $this->upload_image_from_url( $image_url, 'product_image' );
|
1915 |
+
}
|
1916 |
+
|
1917 |
+
/**
|
1918 |
+
* Upload product category image from URL.
|
1919 |
+
*
|
1920 |
+
* @since 2.5.0
|
1921 |
+
* @param string $image_url
|
1922 |
+
* @return int|WP_Error attachment id
|
1923 |
+
*/
|
1924 |
+
public function upload_product_category_image( $image_url ) {
|
1925 |
+
return $this->upload_image_from_url( $image_url, 'product_category_image' );
|
1926 |
+
}
|
1927 |
+
|
1928 |
+
/**
|
1929 |
+
* Upload image from URL.
|
1930 |
+
*
|
1931 |
+
* @throws WC_Square_API_Exception
|
1932 |
+
*
|
1933 |
+
* @since 2.5.0
|
1934 |
+
* @param string $image_url
|
1935 |
+
* @param string $upload_for
|
1936 |
+
* @return int|WP_Error Attachment id
|
1937 |
+
*/
|
1938 |
+
protected function upload_image_from_url( $image_url, $upload_for = 'product_image' ) {
|
1939 |
+
$file_name = basename( current( explode( '?', $image_url ) ) );
|
1940 |
+
$parsed_url = @parse_url( $image_url );
|
1941 |
+
|
1942 |
+
// Check parsed URL.
|
1943 |
+
if ( ! $parsed_url || ! is_array( $parsed_url ) ) {
|
1944 |
+
throw new WC_Square_API_Exception( 'woocommerce_api_invalid_' . $upload_for, sprintf( __( 'Invalid URL %s', 'woocommerce' ), $image_url ), 400 );
|
1945 |
+
}
|
1946 |
+
|
1947 |
+
// Ensure url is valid.
|
1948 |
+
$image_url = str_replace( ' ', '%20', $image_url );
|
1949 |
+
|
1950 |
+
// Get the file.
|
1951 |
+
$response = wp_safe_remote_get( $image_url, array(
|
1952 |
+
'timeout' => 10,
|
1953 |
+
) );
|
1954 |
+
|
1955 |
+
if ( is_wp_error( $response ) ) {
|
1956 |
+
throw new WC_Square_API_Exception( 'woocommerce_api_invalid_remote_' . $upload_for, sprintf( __( 'Error getting remote image %s.', 'woocommerce' ), $image_url ) . ' ' . sprintf( __( 'Error: %s.', 'woocommerce' ), $response->get_error_message() ), 400 );
|
1957 |
+
} elseif ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
|
1958 |
+
throw new WC_Square_API_Exception( 'woocommerce_api_invalid_remote_' . $upload_for, sprintf( __( 'Error getting remote image %s.', 'woocommerce' ), $image_url ), 400 );
|
1959 |
+
}
|
1960 |
+
|
1961 |
+
// Ensure we have a file name and type.
|
1962 |
+
$wp_filetype = wp_check_filetype( $file_name, wc_rest_allowed_image_mime_types() );
|
1963 |
+
|
1964 |
+
if ( ! $wp_filetype['type'] ) {
|
1965 |
+
$headers = wp_remote_retrieve_headers( $response );
|
1966 |
+
if ( isset( $headers['content-disposition'] ) && strstr( $headers['content-disposition'], 'filename=' ) ) {
|
1967 |
+
$disposition = end( explode( 'filename=', $headers['content-disposition'] ) );
|
1968 |
+
$disposition = sanitize_file_name( $disposition );
|
1969 |
+
$file_name = $disposition;
|
1970 |
+
} elseif ( isset( $headers['content-type'] ) && strstr( $headers['content-type'], 'image/' ) ) {
|
1971 |
+
$file_name = 'image.' . str_replace( 'image/', '', $headers['content-type'] );
|
1972 |
+
}
|
1973 |
+
unset( $headers );
|
1974 |
+
|
1975 |
+
// Recheck filetype
|
1976 |
+
$wp_filetype = wp_check_filetype( $file_name, wc_rest_allowed_image_mime_types() );
|
1977 |
+
|
1978 |
+
if ( ! $wp_filetype['type'] ) {
|
1979 |
+
throw new WC_Square_API_Exception( 'woocommerce_api_invalid_' . $upload_for, __( 'Invalid image type.', 'woocommerce' ), 400 );
|
1980 |
+
}
|
1981 |
+
}
|
1982 |
+
|
1983 |
+
// Upload the file.
|
1984 |
+
$upload = wp_upload_bits( $file_name, '', wp_remote_retrieve_body( $response ) );
|
1985 |
+
|
1986 |
+
if ( $upload['error'] ) {
|
1987 |
+
throw new WC_Square_API_Exception( 'woocommerce_api_' . $upload_for . '_upload_error', $upload['error'], 400 );
|
1988 |
+
}
|
1989 |
+
|
1990 |
+
// Get filesize.
|
1991 |
+
$filesize = filesize( $upload['file'] );
|
1992 |
+
|
1993 |
+
if ( 0 == $filesize ) {
|
1994 |
+
@unlink( $upload['file'] );
|
1995 |
+
unset( $upload );
|
1996 |
+
throw new WC_Square_API_Exception( 'woocommerce_api_' . $upload_for . '_upload_file_error', __( 'Zero size file downloaded', 'woocommerce' ), 400 );
|
1997 |
+
}
|
1998 |
+
|
1999 |
+
unset( $response );
|
2000 |
+
|
2001 |
+
do_action( 'woocommerce_api_uploaded_image_from_url', $upload, $image_url, $upload_for );
|
2002 |
+
|
2003 |
+
return $upload;
|
2004 |
+
}
|
2005 |
+
|
2006 |
+
/**
|
2007 |
+
* Sets product image as attachment and returns the attachment ID.
|
2008 |
+
*
|
2009 |
+
* @since 2.2
|
2010 |
+
* @param array $upload
|
2011 |
+
* @param int $id
|
2012 |
+
* @return int
|
2013 |
+
*/
|
2014 |
+
protected function set_product_image_as_attachment( $upload, $id ) {
|
2015 |
+
return $this->set_uploaded_image_as_attachment( $upload, $id );
|
2016 |
+
}
|
2017 |
+
|
2018 |
+
/**
|
2019 |
+
* Sets uploaded category image as attachment and returns the attachment ID.
|
2020 |
+
*
|
2021 |
+
* @since 2.5.0
|
2022 |
+
* @param integer $upload Upload information from wp_upload_bits
|
2023 |
+
* @return int Attachment ID
|
2024 |
+
*/
|
2025 |
+
protected function set_product_category_image_as_attachment( $upload ) {
|
2026 |
+
return $this->set_uploaded_image_as_attachment( $upload );
|
2027 |
+
}
|
2028 |
+
|
2029 |
+
/**
|
2030 |
+
* Set uploaded image as attachment.
|
2031 |
+
*
|
2032 |
+
* @since 2.5.0
|
2033 |
+
* @param array $upload Upload information from wp_upload_bits
|
2034 |
+
* @param int $id Post ID. Default to 0.
|
2035 |
+
* @return int Attachment ID
|
2036 |
+
*/
|
2037 |
+
protected function set_uploaded_image_as_attachment( $upload, $id = 0 ) {
|
2038 |
+
$info = wp_check_filetype( $upload['file'] );
|
2039 |
+
$title = '';
|
2040 |
+
$content = '';
|
2041 |
+
|
2042 |
+
if ( $image_meta = @wp_read_image_metadata( $upload['file'] ) ) {
|
2043 |
+
if ( trim( $image_meta['title'] ) && ! is_numeric( sanitize_title( $image_meta['title'] ) ) ) {
|
2044 |
+
$title = wc_clean( $image_meta['title'] );
|
2045 |
+
}
|
2046 |
+
if ( trim( $image_meta['caption'] ) ) {
|
2047 |
+
$content = wc_clean( $image_meta['caption'] );
|
2048 |
+
}
|
2049 |
+
}
|
2050 |
+
|
2051 |
+
$attachment = array(
|
2052 |
+
'post_mime_type' => $info['type'],
|
2053 |
+
'guid' => $upload['url'],
|
2054 |
+
'post_parent' => $id,
|
2055 |
+
'post_title' => $title,
|
2056 |
+
'post_content' => $content,
|
2057 |
+
);
|
2058 |
+
|
2059 |
+
$attachment_id = wp_insert_attachment( $attachment, $upload['file'], $id );
|
2060 |
+
if ( ! is_wp_error( $attachment_id ) ) {
|
2061 |
+
wp_update_attachment_metadata( $attachment_id, wp_generate_attachment_metadata( $attachment_id, $upload['file'] ) );
|
2062 |
+
}
|
2063 |
+
|
2064 |
+
return $attachment_id;
|
2065 |
+
}
|
2066 |
+
|
2067 |
+
/**
|
2068 |
+
* Get attribute options.
|
2069 |
+
*
|
2070 |
+
* @param int $product_id
|
2071 |
+
* @param array $attribute
|
2072 |
+
* @return array
|
2073 |
+
*/
|
2074 |
+
protected function get_attribute_options( $product_id, $attribute ) {
|
2075 |
+
if ( isset( $attribute['is_taxonomy'] ) && $attribute['is_taxonomy'] ) {
|
2076 |
+
return wc_get_product_terms( $product_id, $attribute['name'], array( 'fields' => 'names' ) );
|
2077 |
+
} elseif ( isset( $attribute['value'] ) ) {
|
2078 |
+
return array_map( 'trim', explode( '|', $attribute['value'] ) );
|
2079 |
+
}
|
2080 |
+
|
2081 |
+
return array();
|
2082 |
+
}
|
2083 |
+
|
2084 |
+
/**
|
2085 |
+
* Get the attributes for a product or product variation
|
2086 |
+
*
|
2087 |
+
* @since 2.1
|
2088 |
+
* @param WC_Product|WC_Product_Variation $product
|
2089 |
+
* @return array
|
2090 |
+
*/
|
2091 |
+
private function get_attributes( $product ) {
|
2092 |
+
|
2093 |
+
$attributes = array();
|
2094 |
+
|
2095 |
+
if ( $product->is_type( 'variation' ) ) {
|
2096 |
+
|
2097 |
+
// variation attributes
|
2098 |
+
foreach ( $product->get_variation_attributes() as $attribute_name => $attribute ) {
|
2099 |
+
|
2100 |
+
// taxonomy-based attributes are prefixed with `pa_`, otherwise simply `attribute_`
|
2101 |
+
$attributes[] = array(
|
2102 |
+
'name' => wc_attribute_label( str_replace( 'attribute_', '', $attribute_name ), $product ),
|
2103 |
+
'slug' => str_replace( 'attribute_', '', str_replace( 'pa_', '', $attribute_name ) ),
|
2104 |
+
'option' => $attribute,
|
2105 |
+
);
|
2106 |
+
}
|
2107 |
+
} else {
|
2108 |
+
foreach ( $product->get_attributes() as $attribute ) {
|
2109 |
+
$attributes[] = array(
|
2110 |
+
'name' => wc_attribute_label( $attribute['name'], $product ),
|
2111 |
+
'slug' => str_replace( 'pa_', '', $attribute['name'] ),
|
2112 |
+
'position' => (int) $attribute['position'],
|
2113 |
+
'visible' => (bool) $attribute['is_visible'],
|
2114 |
+
'variation' => (bool) $attribute['is_variation'],
|
2115 |
+
'options' => $this->get_attribute_options( version_compare( WC_VERSION, '3.0.0', '<' ) ? $product->id : $product->get_id(), $attribute ),
|
2116 |
+
);
|
2117 |
+
}
|
2118 |
+
}
|
2119 |
+
|
2120 |
+
return $attributes;
|
2121 |
+
}
|
2122 |
+
|
2123 |
+
/**
|
2124 |
+
* Get the downloads for a product or product variation
|
2125 |
+
*
|
2126 |
+
* @since 2.1
|
2127 |
+
* @param WC_Product|WC_Product_Variation $product
|
2128 |
+
* @return array
|
2129 |
+
*/
|
2130 |
+
private function get_downloads( $product ) {
|
2131 |
+
|
2132 |
+
$downloads = array();
|
2133 |
+
|
2134 |
+
if ( $product->is_downloadable() ) {
|
2135 |
+
|
2136 |
+
$files = version_compare( WC_VERSION, '3.0', '<' ) ? $product->get_files() : $product->get_downloads();
|
2137 |
+
|
2138 |
+
foreach ( $files as $file_id => $file ) {
|
2139 |
+
|
2140 |
+
$downloads[] = array(
|
2141 |
+
'id' => $file_id, // do not cast as int as this is a hash
|
2142 |
+
'name' => $file['name'],
|
2143 |
+
'file' => $file['file'],
|
2144 |
+
);
|
2145 |
+
}
|
2146 |
+
}
|
2147 |
+
|
2148 |
+
return $downloads;
|
2149 |
+
}
|
2150 |
+
|
2151 |
+
/**
|
2152 |
+
* Get a listing of product attributes
|
2153 |
+
*
|
2154 |
+
* @since 2.5.0
|
2155 |
+
* @param string|null $fields fields to limit response to
|
2156 |
+
* @return array
|
2157 |
+
*/
|
2158 |
+
public function get_product_attributes( $fields = null ) {
|
2159 |
+
try {
|
2160 |
+
// Permissions check.
|
2161 |
+
if ( ! current_user_can( 'manage_product_terms' ) ) {
|
2162 |
+
throw new WC_Square_API_Exception( 'woocommerce_api_user_cannot_read_product_attributes', __( 'You do not have permission to read product attributes', 'woocommerce' ), 401 );
|
2163 |
+
}
|
2164 |
+
|
2165 |
+
$product_attributes = array();
|
2166 |
+
$attribute_taxonomies = wc_get_attribute_taxonomies();
|
2167 |
+
|
2168 |
+
foreach ( $attribute_taxonomies as $attribute ) {
|
2169 |
+
$product_attributes[] = array(
|
2170 |
+
'id' => intval( $attribute->attribute_id ),
|
2171 |
+
'name' => $attribute->attribute_label,
|
2172 |
+
'slug' => wc_attribute_taxonomy_name( $attribute->attribute_name ),
|
2173 |
+
'type' => $attribute->attribute_type,
|
2174 |
+
'order_by' => $attribute->attribute_orderby,
|
2175 |
+
'has_archives' => (bool) $attribute->attribute_public,
|
2176 |
+
);
|
2177 |
+
}
|
2178 |
+
|
2179 |
+
return array( 'product_attributes' => apply_filters( 'woocommerce_api_product_attributes_response', $product_attributes, $attribute_taxonomies, $fields, $this ) );
|
2180 |
+
} catch ( WC_Square_API_Exception $e ) {
|
2181 |
+
return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) );
|
2182 |
+
}
|
2183 |
+
}
|
2184 |
+
|
2185 |
+
/**
|
2186 |
+
* Get the product attribute for the given ID
|
2187 |
+
*
|
2188 |
+
* @since 2.5.0
|
2189 |
+
* @param string $id product attribute term ID
|
2190 |
+
* @param string|null $fields fields to limit response to
|
2191 |
+
* @return array
|
2192 |
+
*/
|
2193 |
+
public function get_product_attribute( $id, $fields = null ) {
|
2194 |
+
global $wpdb;
|
2195 |
+
|
2196 |
+
try {
|
2197 |
+
$id = absint( $id );
|
2198 |
+
|
2199 |
+
// Validate ID
|
2200 |
+
if ( empty( $id ) ) {
|
2201 |
+
throw new WC_Square_API_Exception( 'woocommerce_api_invalid_product_attribute_id', __( 'Invalid product attribute ID', 'woocommerce' ), 400 );
|
2202 |
+
}
|
2203 |
+
|
2204 |
+
// Permissions check
|
2205 |
+
if ( ! current_user_can( 'manage_product_terms' ) ) {
|
2206 |
+
throw new WC_Square_API_Exception( 'woocommerce_api_user_cannot_read_product_attributes', __( 'You do not have permission to read product attributes', 'woocommerce' ), 401 );
|
2207 |
+
}
|
2208 |
+
|
2209 |
+
$attribute = $wpdb->get_row( $wpdb->prepare( "
|
2210 |
+
SELECT *
|
2211 |
+
FROM {$wpdb->prefix}woocommerce_attribute_taxonomies
|
2212 |
+
WHERE attribute_id = %d
|
2213 |
+
", $id ) );
|
2214 |
+
|
2215 |
+
if ( is_wp_error( $attribute ) || is_null( $attribute ) ) {
|
2216 |
+
throw new WC_Square_API_Exception( 'woocommerce_api_invalid_product_attribute_id', __( 'A product attribute with the provided ID could not be found', 'woocommerce' ), 404 );
|
2217 |
+
}
|
2218 |
+
|
2219 |
+
$product_attribute = array(
|
2220 |
+
'id' => intval( $attribute->attribute_id ),
|
2221 |
+
'name' => $attribute->attribute_label,
|
2222 |
+
'slug' => wc_attribute_taxonomy_name( $attribute->attribute_name ),
|
2223 |
+
'type' => $attribute->attribute_type,
|
2224 |
+
'order_by' => $attribute->attribute_orderby,
|
2225 |
+
'has_archives' => (bool) $attribute->attribute_public,
|
2226 |
+
);
|
2227 |
+
|
2228 |
+
return array( 'product_attribute' => apply_filters( 'woocommerce_api_product_attribute_response', $product_attribute, $id, $fields, $attribute, $this ) );
|
2229 |
+
} catch ( WC_Square_API_Exception $e ) {
|
2230 |
+
return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) );
|
2231 |
+
}
|
2232 |
+
}
|
2233 |
+
|
2234 |
+
/**
|
2235 |
+
* Validate attribute data.
|
2236 |
+
*
|
2237 |
+
* @since 2.5.0
|
2238 |
+
* @param string $name
|
2239 |
+
* @param string $slug
|
2240 |
+
* @param string $type
|
2241 |
+
* @param string $order_by
|
2242 |
+
* @param bool $new_data
|
2243 |
+
* @return bool
|
2244 |
+
* @throws WC_Square_API_Exception
|
2245 |
+
*/
|
2246 |
+
protected function validate_attribute_data( $name, $slug, $type, $order_by, $new_data = true ) {
|
2247 |
+
if ( empty( $name ) ) {
|
2248 |
+
throw new WC_Square_API_Exception( 'woocommerce_api_missing_product_attribute_name', sprintf( __( 'Missing parameter %s', 'woocommerce' ), 'name' ), 400 );
|
2249 |
+
}
|
2250 |
+
|
2251 |
+
if ( strlen( $slug ) >= 28 ) {
|
2252 |
+
throw new WC_Square_API_Exception( 'woocommerce_api_invalid_product_attribute_slug_too_long', sprintf( __( 'Slug "%s" is too long (28 characters max). Shorten it, please.', 'woocommerce' ), $slug ), 400 );
|
2253 |
+
} elseif ( wc_check_if_attribute_name_is_reserved( $slug ) ) {
|
2254 |
+
throw new WC_Square_API_Exception( 'woocommerce_api_invalid_product_attribute_slug_reserved_name', sprintf( __( 'Slug "%s" is not allowed because it is a reserved term. Change it, please.', 'woocommerce' ), $slug ), 400 );
|
2255 |
+
} elseif ( $new_data && taxonomy_exists( wc_attribute_taxonomy_name( $slug ) ) ) {
|
2256 |
+
throw new WC_Square_API_Exception( 'woocommerce_api_invalid_product_attribute_slug_already_exists', sprintf( __( 'Slug "%s" is already in use. Change it, please.', 'woocommerce' ), $slug ), 400 );
|
2257 |
+
}
|
2258 |
+
|
2259 |
+
// Validate the attribute type
|
2260 |
+
if ( ! in_array( wc_clean( $type ), array_keys( wc_get_attribute_types() ) ) ) {
|
2261 |
+
throw new WC_Square_API_Exception( 'woocommerce_api_invalid_product_attribute_type', sprintf( __( 'Invalid product attribute type - the product attribute type must be any of these: %s', 'woocommerce' ), implode( ', ', array_keys( wc_get_attribute_types() ) ) ), 400 );
|
2262 |
+
}
|
2263 |
+
|
2264 |
+
// Validate the attribute order by
|
2265 |
+
if ( ! in_array( wc_clean( $order_by ), array( 'menu_order', 'name', 'name_num', 'id' ) ) ) {
|
2266 |
+
throw new WC_Square_API_Exception( 'woocommerce_api_invalid_product_attribute_order_by', sprintf( __( 'Invalid product attribute order_by type - the product attribute order_by type must be any of these: %s', 'woocommerce' ), implode( ', ', array( 'menu_order', 'name', 'name_num', 'id' ) ) ), 400 );
|
2267 |
+
}
|
2268 |
+
|
2269 |
+
return true;
|
2270 |
+
}
|
2271 |
+
|
2272 |
+
/**
|
2273 |
+
* Create a new product attribute.
|
2274 |
+
*
|
2275 |
+
* @since 2.5.0
|
2276 |
+
* @param array $data Posted data.
|
2277 |
+
* @return array
|
2278 |
+
*/
|
2279 |
+
public function create_product_attribute( $data ) {
|
2280 |
+
global $wpdb;
|
2281 |
+
|
2282 |
+
try {
|
2283 |
+
if ( ! isset( $data['product_attribute'] ) ) {
|
2284 |
+
throw new WC_Square_API_Exception( 'woocommerce_api_missing_product_attribute_data', sprintf( __( 'No %1$s data specified to create %1$s', 'woocommerce' ), 'product_attribute' ), 400 );
|
2285 |
+
}
|
2286 |
+
|
2287 |
+
$data = $data['product_attribute'];
|
2288 |
+
|
2289 |
+
// Check permissions.
|
2290 |
+
if ( ! current_user_can( 'manage_product_terms' ) ) {
|
2291 |
+
throw new WC_Square_API_Exception( 'woocommerce_api_user_cannot_create_product_attribute', __( 'You do not have permission to create product attributes', 'woocommerce' ), 401 );
|
2292 |
+
}
|
2293 |
+
|
2294 |
+
$data = apply_filters( 'woocommerce_api_create_product_attribute_data', $data, $this );
|
2295 |
+
|
2296 |
+
if ( ! isset( $data['name'] ) ) {
|
2297 |
+
$data['name'] = '';
|
2298 |
+
}
|
2299 |
+
|
2300 |
+
// Set the attribute slug.
|
2301 |
+
if ( ! isset( $data['slug'] ) ) {
|
2302 |
+
$data['slug'] = wc_sanitize_taxonomy_name( stripslashes( $data['name'] ) );
|
2303 |
+
} else {
|
2304 |
+
$data['slug'] = preg_replace( '/^pa\_/', '', wc_sanitize_taxonomy_name( stripslashes( $data['slug'] ) ) );
|
2305 |
+
}
|
2306 |
+
|
2307 |
+
// Set attribute type when not sent.
|
2308 |
+
if ( ! isset( $data['type'] ) ) {
|
2309 |
+
$data['type'] = 'select';
|
2310 |
+
}
|
2311 |
+
|
2312 |
+
// Set order by when not sent.
|
2313 |
+
if ( ! isset( $data['order_by'] ) ) {
|
2314 |
+
$data['order_by'] = 'menu_order';
|
2315 |
+
}
|
2316 |
+
|
2317 |
+
// Validate the attribute data.
|
2318 |
+
$this->validate_attribute_data( $data['name'], $data['slug'], $data['type'], $data['order_by'], true );
|
2319 |
+
|
2320 |
+
$insert = $wpdb->insert(
|
2321 |
+
$wpdb->prefix . 'woocommerce_attribute_taxonomies',
|
2322 |
+
array(
|
2323 |
+
'attribute_label' => $data['name'],
|
2324 |
+
'attribute_name' => $data['slug'],
|
2325 |
+
'attribute_type' => $data['type'],
|
2326 |
+
'attribute_orderby' => $data['order_by'],
|
2327 |
+
'attribute_public' => isset( $data['has_archives'] ) && true === $data['has_archives'] ? 1 : 0,
|
2328 |
+
),
|
2329 |
+
array( '%s', '%s', '%s', '%s', '%d' )
|
2330 |
+
);
|
2331 |
+
|
2332 |
+
// Checks for an error in the product creation.
|
2333 |
+
if ( is_wp_error( $insert ) ) {
|
2334 |
+
throw new WC_Square_API_Exception( 'woocommerce_api_cannot_create_product_attribute', $insert->get_error_message(), 400 );
|
2335 |
+
}
|
2336 |
+
|
2337 |
+
$id = $wpdb->insert_id;
|
2338 |
+
|
2339 |
+
do_action( 'woocommerce_api_create_product_attribute', $id, $data );
|
2340 |
+
|
2341 |
+
// Clear transients.
|
2342 |
+
flush_rewrite_rules();
|
2343 |
+
delete_transient( 'wc_attribute_taxonomies' );
|
2344 |
+
|
2345 |
+
return $this->get_product_attribute( $id );
|
2346 |
+
} catch ( WC_Square_API_Exception $e ) {
|
2347 |
+
return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) );
|
2348 |
+
}
|
2349 |
+
}
|
2350 |
+
|
2351 |
+
/**
|
2352 |
+
* Clear product
|
2353 |
+
*/
|
2354 |
+
protected function clear_product( $product_id ) {
|
2355 |
+
if ( ! is_numeric( $product_id ) || 0 >= $product_id ) {
|
2356 |
+
return;
|
2357 |
+
}
|
2358 |
+
|
2359 |
+
// Delete product attachments
|
2360 |
+
$attachments = get_children( array(
|
2361 |
+
'post_parent' => $product_id,
|
2362 |
+
'post_status' => 'any',
|
2363 |
+
'post_type' => 'attachment',
|
2364 |
+
) );
|
2365 |
+
|
2366 |
+
foreach ( (array) $attachments as $attachment ) {
|
2367 |
+
wp_delete_attachment( $attachment->ID, true );
|
2368 |
+
}
|
2369 |
+
|
2370 |
+
// Delete product
|
2371 |
+
wp_delete_post( $product_id, true );
|
2372 |
+
}
|
2373 |
+
|
2374 |
+
/**
|
2375 |
+
* Format a unix timestamp or MySQL datetime into an RFC3339 datetime
|
2376 |
+
*
|
2377 |
+
* @since 2.1
|
2378 |
+
* @param int|string $timestamp unix timestamp or MySQL datetime
|
2379 |
+
* @param bool $convert_to_utc
|
2380 |
+
* @return string RFC3339 datetime
|
2381 |
+
*/
|
2382 |
+
public function format_datetime( $timestamp, $convert_to_utc = false ) {
|
2383 |
+
|
2384 |
+
if ( $convert_to_utc ) {
|
2385 |
+
$timezone = new DateTimeZone( wc_timezone_string() );
|
2386 |
+
} else {
|
2387 |
+
$timezone = new DateTimeZone( 'UTC' );
|
2388 |
+
}
|
2389 |
+
|
2390 |
+
try {
|
2391 |
+
|
2392 |
+
if ( is_numeric( $timestamp ) ) {
|
2393 |
+
$date = new DateTime( "@{$timestamp}" );
|
2394 |
+
} else {
|
2395 |
+
$date = new DateTime( $timestamp, $timezone );
|
2396 |
+
}
|
2397 |
+
|
2398 |
+
// convert to UTC by adjusting the time based on the offset of the site's timezone
|
2399 |
+
if ( $convert_to_utc ) {
|
2400 |
+
$date->modify( -1 * $date->getOffset() . ' seconds' );
|
2401 |
+
}
|
2402 |
+
} catch ( Exception $e ) {
|
2403 |
+
|
2404 |
+
$date = new DateTime( '@0' );
|
2405 |
+
}
|
2406 |
+
|
2407 |
+
return $date->format( 'Y-m-d\TH:i:s\Z' );
|
2408 |
+
}
|
2409 |
+
|
2410 |
+
/**
|
2411 |
+
* Save product price.
|
2412 |
+
*
|
2413 |
+
* This is a private function (internal use ONLY) used until a data manipulation api is built.
|
2414 |
+
*
|
2415 |
+
* @deprecated 2.7.0
|
2416 |
+
* @param int $product_id
|
2417 |
+
* @param float $regular_price
|
2418 |
+
* @param float $sale_price
|
2419 |
+
* @param string $date_from
|
2420 |
+
* @param string $date_to
|
2421 |
+
*/
|
2422 |
+
public function wc_save_product_price( $product_id, $regular_price, $sale_price = '', $date_from = '', $date_to = '' ) {
|
2423 |
+
$product_id = absint( $product_id );
|
2424 |
+
$regular_price = wc_format_decimal( $regular_price );
|
2425 |
+
$sale_price = '' === $sale_price ? '' : wc_format_decimal( $sale_price );
|
2426 |
+
$date_from = wc_clean( $date_from );
|
2427 |
+
$date_to = wc_clean( $date_to );
|
2428 |
+
|
2429 |
+
update_post_meta( $product_id, '_regular_price', $regular_price );
|
2430 |
+
update_post_meta( $product_id, '_sale_price', $sale_price );
|
2431 |
+
|
2432 |
+
// Save Dates
|
2433 |
+
update_post_meta( $product_id, '_sale_price_dates_from', $date_from ? strtotime( $date_from ) : '' );
|
2434 |
+
update_post_meta( $product_id, '_sale_price_dates_to', $date_to ? strtotime( $date_to ) : '' );
|
2435 |
+
|
2436 |
+
if ( $date_to && ! $date_from ) {
|
2437 |
+
$date_from = strtotime( 'NOW', current_time( 'timestamp' ) );
|
2438 |
+
update_post_meta( $product_id, '_sale_price_dates_from', $date_from );
|
2439 |
+
}
|
2440 |
+
|
2441 |
+
// Update price if on sale
|
2442 |
+
if ( '' !== $sale_price && '' === $date_to && '' === $date_from ) {
|
2443 |
+
update_post_meta( $product_id, '_price', $sale_price );
|
2444 |
+
} else {
|
2445 |
+
update_post_meta( $product_id, '_price', $regular_price );
|
2446 |
+
}
|
2447 |
+
|
2448 |
+
if ( '' !== $sale_price && $date_from && strtotime( $date_from ) < strtotime( 'NOW', current_time( 'timestamp' ) ) ) {
|
2449 |
+
update_post_meta( $product_id, '_price', $sale_price );
|
2450 |
+
}
|
2451 |
+
|
2452 |
+
if ( $date_to && strtotime( $date_to ) < strtotime( 'NOW', current_time( 'timestamp' ) ) ) {
|
2453 |
+
update_post_meta( $product_id, '_price', $regular_price );
|
2454 |
+
update_post_meta( $product_id, '_sale_price_dates_from', '' );
|
2455 |
+
update_post_meta( $product_id, '_sale_price_dates_to', '' );
|
2456 |
+
}
|
2457 |
+
}
|
2458 |
+
}
|
includes/payment/class-wc-square-gateway.php
ADDED
@@ -0,0 +1,542 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
if ( ! defined( 'ABSPATH' ) ) {
|
3 |
+
exit; // Exit if accessed directly
|
4 |
+
}
|
5 |
+
|
6 |
+
class WC_Square_Gateway extends WC_Payment_Gateway {
|
7 |
+
protected $connect;
|
8 |
+
protected $token;
|
9 |
+
public $log;
|
10 |
+
|
11 |
+
/**
|
12 |
+
* Constructor
|
13 |
+
*/
|
14 |
+
public function __construct() {
|
15 |
+
$this->id = 'square';
|
16 |
+
$this->method_title = __( 'Square', 'woocommerce-square' );
|
17 |
+
$this->method_description = __( 'Square works by adding payments fields in an iframe and then sending the details to Square for verification and processing.', 'woocommerce-square' );
|
18 |
+
$this->has_fields = true;
|
19 |
+
$this->supports = array(
|
20 |
+
'products',
|
21 |
+
'refunds',
|
22 |
+
);
|
23 |
+
|
24 |
+
// Load the form fields
|
25 |
+
$this->init_form_fields();
|
26 |
+
|
27 |
+
// Load the settings.
|
28 |
+
$this->init_settings();
|
29 |
+
|
30 |
+
// Get setting values
|
31 |
+
$this->title = $this->get_option( 'title' );
|
32 |
+
$this->description = $this->get_option( 'description' );
|
33 |
+
$this->enabled = $this->get_option( 'enabled' );
|
34 |
+
$this->capture = $this->get_option( 'capture' ) === 'yes' ? true : false;
|
35 |
+
$this->create_customer = $this->get_option( 'create_customer' ) === 'yes' ? true : false;
|
36 |
+
$this->logging = $this->get_option( 'logging' ) === 'yes' ? true : false;
|
37 |
+
$this->connect = new WC_Square_Payments_Connect(); // decouple in future when v2 is ready
|
38 |
+
$this->token = get_option( 'woocommerce_square_merchant_access_token' );
|
39 |
+
|
40 |
+
$this->connect->set_access_token( $this->token );
|
41 |
+
|
42 |
+
if ( WC_SQUARE_ENABLE_STAGING ) {
|
43 |
+
$this->description .= ' ' . __( 'STAGING MODE ENABLED. In staging mode, you can use the card number 4111111111111111 with any CVC and a valid expiration date.', 'woocommerce-square' );
|
44 |
+
|
45 |
+
$this->description = trim( $this->description );
|
46 |
+
}
|
47 |
+
|
48 |
+
// Hooks
|
49 |
+
add_action( 'wp_enqueue_scripts', array( $this, 'payment_scripts' ) );
|
50 |
+
add_action( 'admin_notices', array( $this, 'admin_notices' ) );
|
51 |
+
add_action( 'woocommerce_update_options_payment_gateways_' . $this->id, array( $this, 'process_admin_options' ) );
|
52 |
+
}
|
53 |
+
|
54 |
+
/**
|
55 |
+
* get_icon function.
|
56 |
+
*
|
57 |
+
* @access public
|
58 |
+
* @return string
|
59 |
+
*/
|
60 |
+
public function get_icon() {
|
61 |
+
$icon = '<img src="' . WC_HTTPS::force_https_url( WC()->plugin_url() . '/assets/images/icons/credit-cards/visa.svg' ) . '" alt="Visa" width="32" style="margin-left: 0.3em" />';
|
62 |
+
$icon .= '<img src="' . WC_HTTPS::force_https_url( WC()->plugin_url() . '/assets/images/icons/credit-cards/mastercard.svg' ) . '" alt="Mastercard" width="32" style="margin-left: 0.3em" />';
|
63 |
+
$icon .= '<img src="' . WC_HTTPS::force_https_url( WC()->plugin_url() . '/assets/images/icons/credit-cards/amex.svg' ) . '" alt="Amex" width="32" style="margin-left: 0.3em" />';
|
64 |
+
|
65 |
+
$icon .= '<img src="' . WC_HTTPS::force_https_url( WC()->plugin_url() . '/assets/images/icons/credit-cards/discover.svg' ) . '" alt="Discover" width="32" style="margin-left: 0.3em" />';
|
66 |
+
|
67 |
+
return apply_filters( 'woocommerce_gateway_icon', $icon, $this->id );
|
68 |
+
}
|
69 |
+
|
70 |
+
/**
|
71 |
+
* Check if required fields are set
|
72 |
+
*/
|
73 |
+
public function admin_notices() {
|
74 |
+
if ( 'yes' !== $this->enabled ) {
|
75 |
+
return;
|
76 |
+
}
|
77 |
+
|
78 |
+
// Show message if enabled and FORCE SSL is disabled and WordpressHTTPS plugin is not detected
|
79 |
+
if ( ! WC_SQUARE_ENABLE_STAGING && get_option( 'woocommerce_force_ssl_checkout' ) === 'no' && ! class_exists( 'WordPressHTTPS' ) ) {
|
80 |
+
echo '<div class="error"><p>' . sprintf( __( 'Square is enabled, but the <a href="%s">force SSL option</a> is disabled; your checkout is not secured! Please enable SSL and ensure your server has a valid SSL certificate.', 'woocommerce-square' ), admin_url( 'admin.php?page=wc-settings&tab=checkout' ) ) . '</p></div>';
|
81 |
+
}
|
82 |
+
}
|
83 |
+
|
84 |
+
/**
|
85 |
+
* Check if this gateway is enabled
|
86 |
+
*/
|
87 |
+
public function is_available() {
|
88 |
+
$is_available = true;
|
89 |
+
|
90 |
+
if ( 'yes' === $this->enabled ) {
|
91 |
+
if ( ! WC_SQUARE_ENABLE_STAGING && ! wc_checkout_is_https() ) {
|
92 |
+
$is_available = false;
|
93 |
+
}
|
94 |
+
|
95 |
+
if ( ! WC_SQUARE_ENABLE_STAGING && empty( $this->token ) ) {
|
96 |
+
$is_available = false;
|
97 |
+
}
|
98 |
+
|
99 |
+
// Square only supports Australia, Canada, Japan, UK, and US for now.
|
100 |
+
if ( ( 'US' !== WC()->countries->get_base_country() && 'CA' !== WC()->countries->get_base_country() && 'AU' !== WC()->countries->get_base_country() && 'GB' !== WC()->countries->get_base_country() && 'JP' !== WC()->countries->get_base_country() ) || ( 'USD' !== get_woocommerce_currency() && 'CAD' !== get_woocommerce_currency() && 'AUD' !== get_woocommerce_currency() && 'GBP' !== get_woocommerce_currency() && 'JPY' !== get_woocommerce_currency() ) ) {
|
101 |
+
$is_available = false;
|
102 |
+
}
|
103 |
+
} else {
|
104 |
+
$is_available = false;
|
105 |
+
}
|
106 |
+
|
107 |
+
return apply_filters( 'woocommerce_square_payment_gateway_is_available', $is_available );
|
108 |
+
}
|
109 |
+
|
110 |
+
/**
|
111 |
+
* Initialize Gateway Settings Form Fields
|
112 |
+
*/
|
113 |
+
public function init_form_fields() {
|
114 |
+
$this->form_fields = apply_filters( 'woocommerce_square_gateway_settings', array(
|
115 |
+
'enabled' => array(
|
116 |
+
'title' => __( 'Enable/Disable', 'woocommerce-square' ),
|
117 |
+
'label' => __( 'Enable Square', 'woocommerce-square' ),
|
118 |
+
'type' => 'checkbox',
|
119 |
+
'description' => '',
|
120 |
+
'default' => 'no',
|
121 |
+
),
|
122 |
+
'title' => array(
|
123 |
+
'title' => __( 'Title', 'woocommerce-square' ),
|
124 |
+
'type' => 'text',
|
125 |
+
'description' => __( 'This controls the title which the user sees during checkout.', 'woocommerce-square' ),
|
126 |
+
'default' => __( 'Credit card (Square)', 'woocommerce-square' ),
|
127 |
+
),
|
128 |
+
'description' => array(
|
129 |
+
'title' => __( 'Description', 'woocommerce-square' ),
|
130 |
+
'type' => 'textarea',
|
131 |
+
'description' => __( 'This controls the description which the user sees during checkout.', 'woocommerce-square' ),
|
132 |
+
'default' => __( 'Pay with your credit card via Square.', 'woocommerce-square' ),
|
133 |
+
),
|
134 |
+
'capture' => array(
|
135 |
+
'title' => __( 'Delay Capture', 'woocommerce-square' ),
|
136 |
+
'label' => __( 'Enable Delay Capture', 'woocommerce-square' ),
|
137 |
+
'type' => 'checkbox',
|
138 |
+
'description' => __( 'When enabled, the request will only perform an Auth on the provided card. You can then later perform either a Capture or Void.', 'woocommerce-square' ),
|
139 |
+
'default' => 'no',
|
140 |
+
),
|
141 |
+
'create_customer' => array(
|
142 |
+
'title' => __( 'Create Customer', 'woocommerce-square' ),
|
143 |
+
'label' => __( 'Enable Create Customer', 'woocommerce-square' ),
|
144 |
+
'type' => 'checkbox',
|
145 |
+
'description' => __( 'When enabled, processing a payment will create a customer profile on Square.', 'woocommerce-square' ),
|
146 |
+
'default' => 'no',
|
147 |
+
),
|
148 |
+
'logging' => array(
|
149 |
+
'title' => __( 'Logging', 'woocommerce-square' ),
|
150 |
+
'label' => __( 'Log debug messages', 'woocommerce-square' ),
|
151 |
+
'type' => 'checkbox',
|
152 |
+
'description' => __( 'Save debug messages to the WooCommerce System Status log.', 'woocommerce-square' ),
|
153 |
+
'default' => 'no',
|
154 |
+
),
|
155 |
+
) );
|
156 |
+
}
|
157 |
+
|
158 |
+
/**
|
159 |
+
* Payment form on checkout page
|
160 |
+
*/
|
161 |
+
public function payment_fields() {
|
162 |
+
?>
|
163 |
+
<fieldset>
|
164 |
+
<?php
|
165 |
+
$allowed = array(
|
166 |
+
'a' => array(
|
167 |
+
'href' => array(),
|
168 |
+
'title' => array(),
|
169 |
+
),
|
170 |
+
'br' => array(),
|
171 |
+
'em' => array(),
|
172 |
+
'strong' => array(),
|
173 |
+
'span' => array(
|
174 |
+
'class' => array(),
|
175 |
+
),
|
176 |
+
);
|
177 |
+
if ( $this->description ) {
|
178 |
+
echo apply_filters( 'woocommerce_square_description', wpautop( wp_kses( $this->description, $allowed ) ) );
|
179 |
+
}
|
180 |
+
?>
|
181 |
+
<p class="form-row form-row-wide">
|
182 |
+
<label for="sq-card-number"><?php esc_html_e( 'Card Number', 'woocommerce-square' ); ?> <span class="required">*</span></label>
|
183 |
+
<input id="sq-card-number" type="text" maxlength="20" autocomplete="off" placeholder="•••• •••• •••• ••••" name="<?php echo esc_attr( $this->id ); ?>-card-number" />
|
184 |
+
</p>
|
185 |
+
|
186 |
+
<p class="form-row form-row-first">
|
187 |
+
<label for="sq-expiration-date"><?php esc_html_e( 'Expiry (MM/YY)', 'woocommerce-square' ); ?> <span class="required">*</span></label>
|
188 |
+
<input id="sq-expiration-date" type="text" autocomplete="off" placeholder="<?php esc_attr_e( 'MM / YY', 'woocommerce-square' ); ?>" name="<?php echo esc_attr( $this->id ); ?>-card-expiry" />
|
189 |
+
</p>
|
190 |
+
|
191 |
+
<p class="form-row form-row-last">
|
192 |
+
<label for="sq-cvv"><?php esc_html_e( 'Card Code', 'woocommerce-square' ); ?> <span class="required">*</span></label>
|
193 |
+
<input id="sq-cvv" type="text" autocomplete="off" placeholder="<?php esc_attr_e( 'CVV', 'woocommerce-square' ); ?>" name="<?php echo esc_attr( $this->id ); ?>-card-cvv" />
|
194 |
+
</p>
|
195 |
+
|
196 |
+
<p class="form-row form-row-wide">
|
197 |
+
<label for="sq-postal-code"><?php esc_html_e( 'Card Postal Code', 'woocommerce-square' ); ?> <span class="required">*</span></label>
|
198 |
+
<input id="sq-postal-code" type="text" autocomplete="off" placeholder="<?php esc_attr_e( 'Card Postal Code', 'woocommerce-square' ); ?>" name="<?php echo esc_attr( $this->id ); ?>-card-postal-code" />
|
199 |
+
</p>
|
200 |
+
</fieldset>
|
201 |
+
<?php
|
202 |
+
}
|
203 |
+
|
204 |
+
/**
|
205 |
+
* Get payment form input styles.
|
206 |
+
* This function is pass to the JS script in order to style the
|
207 |
+
* input fields within the iFrame.
|
208 |
+
*
|
209 |
+
* Possible styles are: mediaMinWidth, mediaMaxWidth, backgroundColor, boxShadow,
|
210 |
+
* color, fontFamily, fontSize, fontWeight, lineHeight and padding.
|
211 |
+
*
|
212 |
+
* @since 1.0.4
|
213 |
+
* @version 1.0.4
|
214 |
+
* @access public
|
215 |
+
* @return json $styles
|
216 |
+
*/
|
217 |
+
public function get_input_styles() {
|
218 |
+
$styles = array(
|
219 |
+
array(
|
220 |
+
'fontSize' => '1.2em',
|
221 |
+
'padding' => '.618em',
|
222 |
+
'fontWeight' => 400,
|
223 |
+
'backgroundColor' => 'transparent',
|
224 |
+
'lineHeight' => 1.7,
|
225 |
+
),
|
226 |
+
array(
|
227 |
+
'mediaMaxWidth' => '1200px',
|
228 |
+
'fontSize' => '1em',
|
229 |
+
),
|
230 |
+
);
|
231 |
+
|
232 |
+
return apply_filters( 'woocommerce_square_payment_input_styles', wp_json_encode( $styles ) );
|
233 |
+
}
|
234 |
+
|
235 |
+
/**
|
236 |
+
* payment_scripts function.
|
237 |
+
*
|
238 |
+
*
|
239 |
+
* @access public
|
240 |
+
*/
|
241 |
+
public function payment_scripts() {
|
242 |
+
if ( ! is_checkout() ) {
|
243 |
+
return;
|
244 |
+
}
|
245 |
+
|
246 |
+
$suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
|
247 |
+
|
248 |
+
wp_register_script( 'square', 'https://js.squareup.com/v2/paymentform', '', '0.0.2', true );
|
249 |
+
wp_register_script( 'woocommerce-square', WC_SQUARE_PLUGIN_URL . '/assets/js/wc-square-payments' . $suffix . '.js', array( 'jquery', 'square' ), WC_SQUARE_VERSION, true );
|
250 |
+
|
251 |
+
wp_localize_script( 'woocommerce-square', 'square_params', array(
|
252 |
+
'application_id' => SQUARE_APPLICATION_ID,
|
253 |
+
'environment' => WC_SQUARE_ENABLE_STAGING ? 'staging' : 'production',
|
254 |
+
'placeholder_card_number' => __( '•••• •••• •••• ••••', 'woocommerce-square' ),
|
255 |
+
'placeholder_card_expiration' => __( 'MM / YY', 'woocommerce-square' ),
|
256 |
+
'placeholder_card_cvv' => __( 'CVV', 'woocommerce-square' ),
|
257 |
+
'placeholder_card_postal_code' => __( 'Card Postal Code', 'woocommerce-square' ),
|
258 |
+
'payment_form_input_styles' => esc_js( $this->get_input_styles() ),
|
259 |
+
'custom_form_trigger_element' => apply_filters( 'woocommerce_square_payment_form_trigger_element', esc_js( '' ) ),
|
260 |
+
) );
|
261 |
+
|
262 |
+
wp_enqueue_script( 'woocommerce-square' );
|
263 |
+
|
264 |
+
wp_enqueue_style( 'woocommerce-square-styles', WC_SQUARE_PLUGIN_URL . '/assets/css/wc-square-frontend-styles.css' );
|
265 |
+
|
266 |
+
return true;
|
267 |
+
}
|
268 |
+
|
269 |
+
/**
|
270 |
+
* Process the payment
|
271 |
+
*/
|
272 |
+
public function process_payment( $order_id, $retry = true ) {
|
273 |
+
$order = wc_get_order( $order_id );
|
274 |
+
$nonce = isset( $_POST['square_nonce'] ) ? wc_clean( $_POST['square_nonce'] ) : '';
|
275 |
+
$currency = version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->get_order_currency() : $order->get_currency();
|
276 |
+
|
277 |
+
$this->log( "Info: Begin processing payment for order {$order_id} for the amount of {$order->get_total()}" );
|
278 |
+
|
279 |
+
try {
|
280 |
+
$data = array(
|
281 |
+
'idempotency_key' => uniqid(),
|
282 |
+
'amount_money' => array(
|
283 |
+
'amount' => (int) WC_Square_Utils::format_amount_to_square( $order->get_total(), $currency ),
|
284 |
+
'currency' => $currency,
|
285 |
+
),
|
286 |
+
'reference_id' => (string) $order->get_order_number(),
|
287 |
+
'delay_capture' => $this->capture ? true : false,
|
288 |
+
'card_nonce' => $nonce,
|
289 |
+
'buyer_email_address' => version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->billing_email : $order->get_billing_email(),
|
290 |
+
'billing_address' => array(
|
291 |
+
'address_line_1' => version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->billing_address_1 : $order->get_billing_address_1(),
|
292 |
+
'address_line_2' => version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->billing_address_2 : $order->get_billing_address_2(),
|
293 |
+
'locality' => version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->billing_city : $order->get_billing_city(),
|
294 |
+
'administrative_district_level_1' => version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->billing_state : $order->get_billing_state(),
|
295 |
+
'postal_code' => version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->billing_postcode : $order->get_billing_postcode(),
|
296 |
+
'country' => version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->billing_country : $order->get_billing_country(),
|
297 |
+
),
|
298 |
+
'note' => apply_filters( 'woocommerce_square_payment_order_note', 'WooCommerce: Order #' . (string) $order->get_order_number(), $order ),
|
299 |
+
);
|
300 |
+
|
301 |
+
if ( $order->needs_shipping_address() ) {
|
302 |
+
$data['shipping_address'] = array(
|
303 |
+
'address_line_1' => version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->shipping_address_1 : $order->get_shipping_address_1(),
|
304 |
+
'address_line_2' => version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->shipping_address_2 : $order->get_shipping_address_2(),
|
305 |
+
'locality' => version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->shipping_city : $order->get_shipping_city(),
|
306 |
+
'administrative_district_level_1' => version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->shipping_state : $order->get_shipping_state(),
|
307 |
+
'postal_code' => version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->shipping_postcode : $order->get_shipping_postcode(),
|
308 |
+
'country' => version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->shipping_country : $order->get_shipping_country(),
|
309 |
+
);
|
310 |
+
}
|
311 |
+
|
312 |
+
$result = $this->connect->charge_card_nonce( Woocommerce_Square::instance()->integration->get_option( 'location' ), $data );
|
313 |
+
|
314 |
+
if ( is_wp_error( $result ) ) {
|
315 |
+
wc_add_notice( __( 'Error: Square was unable to complete the transaction. Please try again later or use another means of payment.', 'woocommerce-square' ), 'error' );
|
316 |
+
|
317 |
+
throw new Exception( $result->get_error_message() );
|
318 |
+
}
|
319 |
+
|
320 |
+
if ( ! empty( $result->errors ) ) {
|
321 |
+
if ( 'INVALID_REQUEST_ERROR' === $result->errors[0]->category ) {
|
322 |
+
wc_add_notice( __( 'Error: Square was unable to complete the transaction. Please try again later or use another means of payment.', 'woocommerce-square' ), 'error' );
|
323 |
+
}
|
324 |
+
|
325 |
+
if ( 'PAYMENT_METHOD_ERROR' === $result->errors[0]->category || 'VALIDATION_ERROR' === $result->errors[0]->category ) {
|
326 |
+
// format errors for display
|
327 |
+
$error_html = __( 'Payment Error: ', 'woocommerce-square' );
|
328 |
+
$error_html .= '<br />';
|
329 |
+
$error_html .= '<ul>';
|
330 |
+
|
331 |
+
foreach ( $result->errors as $error ) {
|
332 |
+
$error_html .= '<li>' . $error->detail . '</li>';
|
333 |
+
}
|
334 |
+
|
335 |
+
$error_html .= '</ul>';
|
336 |
+
|
337 |
+
wc_add_notice( $error_html, 'error' );
|
338 |
+
}
|
339 |
+
|
340 |
+
$errors = print_r( $result->errors, true );
|
341 |
+
|
342 |
+
throw new Exception( $errors );
|
343 |
+
}
|
344 |
+
|
345 |
+
if ( empty( $result ) ) {
|
346 |
+
wc_add_notice( __( 'Error: Square was unable to complete the transaction. Please try again later or use another means of payment.', 'woocommerce-square' ), 'error' );
|
347 |
+
|
348 |
+
throw new Exception( 'Unknown Error' );
|
349 |
+
}
|
350 |
+
|
351 |
+
if ( 'CAPTURED' === $result->transaction->tenders[0]->card_details->status ) {
|
352 |
+
// Store captured value
|
353 |
+
update_post_meta( $order_id, '_square_charge_captured', 'yes' );
|
354 |
+
|
355 |
+
// Payment complete
|
356 |
+
$order->payment_complete( $result->transaction->id );
|
357 |
+
|
358 |
+
// Add order note
|
359 |
+
$complete_message = sprintf( __( 'Square charge complete (Charge ID: %s)', 'woocommerce-square' ), $result->transaction->id );
|
360 |
+
$order->add_order_note( $complete_message );
|
361 |
+
$this->log( "Success: $complete_message" );
|
362 |
+
} else {
|
363 |
+
// Store captured value
|
364 |
+
update_post_meta( $order_id, '_square_charge_captured', 'no' );
|
365 |
+
update_post_meta( $order_id, '_transaction_id', $result->transaction->id );
|
366 |
+
|
367 |
+
// Mark as on-hold
|
368 |
+
$authorized_message = sprintf( __( 'Square charge authorized (Authorized ID: %s). Process order to take payment, or cancel to remove the pre-authorization.', 'woocommerce-square' ), $result->transaction->id );
|
369 |
+
$order->update_status( 'on-hold', $authorized_message );
|
370 |
+
$this->log( "Success: $authorized_message" );
|
371 |
+
|
372 |
+
// Reduce stock levels
|
373 |
+
version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->reduce_order_stock() : wc_reduce_stock_levels( $order_id );
|
374 |
+
}
|
375 |
+
|
376 |
+
// we got this far which means the transaction went through
|
377 |
+
if ( $this->create_customer ) {
|
378 |
+
$this->maybe_create_customer( $order );
|
379 |
+
}
|
380 |
+
|
381 |
+
// Remove cart
|
382 |
+
WC()->cart->empty_cart();
|
383 |
+
|
384 |
+
// Return thank you page redirect
|
385 |
+
return array(
|
386 |
+
'result' => 'success',
|
387 |
+
'redirect' => $this->get_return_url( $order ),
|
388 |
+
);
|
389 |
+
} catch ( Exception $e ) {
|
390 |
+
$this->log( sprintf( __( 'Error: %s', 'woocommerce-square' ), $e->getMessage() ) );
|
391 |
+
|
392 |
+
$order->update_status( 'failed', $e->getMessage() );
|
393 |
+
|
394 |
+
return;
|
395 |
+
}
|
396 |
+
}
|
397 |
+
|
398 |
+
/**
|
399 |
+
* Tries to create the customer on Square
|
400 |
+
*
|
401 |
+
* @param object $order
|
402 |
+
*/
|
403 |
+
public function maybe_create_customer( $order ) {
|
404 |
+
$user = get_current_user_id();
|
405 |
+
$square_customer_id = get_user_meta( $user, '_square_customer_id', true );
|
406 |
+
$create_customer = true;
|
407 |
+
$phone_number = (string) version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->billing_phone : $order->get_billing_phone();
|
408 |
+
|
409 |
+
$customer = array(
|
410 |
+
'given_name' => version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->billing_first_name : $order->get_billing_first_name(),
|
411 |
+
'family_name' => version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->billing_last_name : $order->get_billing_last_name(),
|
412 |
+
'email_address' => version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->billing_email : $order->get_billing_email(),
|
413 |
+
'address' => array(
|
414 |
+
'address_line_1' => version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->billing_address_1 : $order->get_billing_address_1(),
|
415 |
+
'address_line_2' => version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->billing_address_2 : $order->get_billing_address_2(),
|
416 |
+
'locality' => version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->billing_city : $order->get_billing_city(),
|
417 |
+
'administrative_district_level_1' => version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->billing_state : $order->get_billing_state(),
|
418 |
+
'postal_code' => version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->billing_postcode : $order->get_billing_postcode(),
|
419 |
+
'country' => version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->billing_country : $order->get_billing_country(),
|
420 |
+
),
|
421 |
+
'phone_number' => ! empty( $phone_number ) ? $phone_number : null,
|
422 |
+
'reference_id' => ! empty( $user ) ? (string) $user : __( 'Guest', 'woocommerce-square' ),
|
423 |
+
);
|
424 |
+
|
425 |
+
// to prevent creating duplicate customer
|
426 |
+
// check to make sure this customer does not exist on Square
|
427 |
+
if ( ! empty( $square_customer_id ) ) {
|
428 |
+
$square_customer = $this->connect->get_customer( $square_customer_id );
|
429 |
+
|
430 |
+
if ( empty( $square_customer->errors ) ) {
|
431 |
+
// customer already exist on Square
|
432 |
+
$create_customer = false;
|
433 |
+
}
|
434 |
+
}
|
435 |
+
|
436 |
+
if ( $create_customer ) {
|
437 |
+
$result = $this->connect->create_customer( $customer );
|
438 |
+
|
439 |
+
// we don't want to halt any processes here just log it
|
440 |
+
if ( is_wp_error( $result ) ) {
|
441 |
+
$this->log( sprintf( __( 'Error creating customer: %s', 'woocommerce-square' ), $result->get_error_message() ) );
|
442 |
+
$order->add_order_note( sprintf( __( 'Error creating customer: %s', 'woocommerce-square' ), $result->get_error_message() ) );
|
443 |
+
}
|
444 |
+
|
445 |
+
// we don't want to halt any processes here just log it
|
446 |
+
if ( ! empty( $result->errors ) ) {
|
447 |
+
$this->log( sprintf( __( 'Error creating customer: %s', 'woocommerce-square' ), print_r( $result->errors, true ) ) );
|
448 |
+
$order->add_order_note( sprintf( __( 'Error creating customer: %s', 'woocommerce-square' ), print_r( $result->errors, true ) ) );
|
449 |
+
}
|
450 |
+
|
451 |
+
// if no errors save Square customer ID to user meta
|
452 |
+
if ( ! is_wp_error( $result ) && empty( $result->errors ) && ! empty( $user ) ) {
|
453 |
+
update_user_meta( $user, '_square_customer_id', $result->customer->id );
|
454 |
+
$order->add_order_note( sprintf( __( 'Customer created on Square: %s', 'woocommerce-square' ), $result->customer->id ) );
|
455 |
+
}
|
456 |
+
}
|
457 |
+
}
|
458 |
+
|
459 |
+
/**
|
460 |
+
* Refund a charge
|
461 |
+
* @param int $order_id
|
462 |
+
* @param float $amount
|
463 |
+
* @return bool
|
464 |
+
*/
|
465 |
+
public function process_refund( $order_id, $amount = null, $reason = '' ) {
|
466 |
+
$order = wc_get_order( $order_id );
|
467 |
+
|
468 |
+
if ( ! $order || ! $order->get_transaction_id() ) {
|
469 |
+
return false;
|
470 |
+
}
|
471 |
+
|
472 |
+
if ( 'square' === ( version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->payment_method : $order->get_payment_method() ) ) {
|
473 |
+
try {
|
474 |
+
$this->log( "Info: Begin refund for order {$order_id} for the amount of {$amount}" );
|
475 |
+
|
476 |
+
$trans_id = get_post_meta( $order_id, '_transaction_id', true );
|
477 |
+
$captured = get_post_meta( $order_id, '_square_charge_captured', true );
|
478 |
+
$location = Woocommerce_Square::instance()->integration->get_option( 'location' );
|
479 |
+
|
480 |
+
$transaction_status = $this->connect->get_transaction_status( $location, $trans_id );
|
481 |
+
|
482 |
+
if ( 'CAPTURED' === $transaction_status ) {
|
483 |
+
$tender_id = $this->connect->get_tender_id( $location, $trans_id );
|
484 |
+
|
485 |
+
$body = array();
|
486 |
+
|
487 |
+
$body['idempotency_key'] = uniqid();
|
488 |
+
$body['tender_id'] = $tender_id;
|
489 |
+
|
490 |
+
if ( ! is_null( $amount ) ) {
|
491 |
+
$body['amount_money'] = array(
|
492 |
+
'amount' => (int) WC_Square_Utils::format_amount_to_square( $amount ),
|
493 |
+
'currency' => version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->get_order_currency() : $order->get_currency(),
|
494 |
+
);
|
495 |
+
}
|
496 |
+
|
497 |
+
if ( $reason ) {
|
498 |
+
$body['reason'] = $reason;
|
499 |
+
}
|
500 |
+
|
501 |
+
$result = $this->connect->refund_transaction( $location, $trans_id, $body );
|
502 |
+
|
503 |
+
if ( is_wp_error( $result ) ) {
|
504 |
+
throw new Exception( $result->get_error_message() );
|
505 |
+
|
506 |
+
} elseif ( ! empty( $result->errors ) ) {
|
507 |
+
throw new Exception( 'Error: ' . print_r( $result->errors, true ) );
|
508 |
+
|
509 |
+
} else {
|
510 |
+
if ( 'APPROVED' === $result->refund->status || 'PENDING' === $result->refund->status ) {
|
511 |
+
$refund_message = sprintf( __( 'Refunded %1$s - Refund ID: %2$s - Reason: %3$s', 'woocommerce-square' ), wc_price( $result->refund->amount_money->amount / 100 ), $result->refund->id, $reason );
|
512 |
+
|
513 |
+
$order->add_order_note( $refund_message );
|
514 |
+
|
515 |
+
$this->log( 'Success: ' . html_entity_decode( strip_tags( $refund_message ) ) );
|
516 |
+
|
517 |
+
return true;
|
518 |
+
}
|
519 |
+
}
|
520 |
+
}
|
521 |
+
} catch ( Exception $e ) {
|
522 |
+
$this->log( sprintf( __( 'Error: %s', 'woocommerce-square' ), $e->getMessage() ) );
|
523 |
+
|
524 |
+
return false;
|
525 |
+
}
|
526 |
+
}
|
527 |
+
}
|
528 |
+
|
529 |
+
/**
|
530 |
+
* Logs
|
531 |
+
*
|
532 |
+
* @since 1.0.0
|
533 |
+
* @version 1.0.0
|
534 |
+
*
|
535 |
+
* @param string $message
|
536 |
+
*/
|
537 |
+
public function log( $message ) {
|
538 |
+
if ( $this->logging ) {
|
539 |
+
WC_Square_Payment_Logger::log( $message );
|
540 |
+
}
|
541 |
+
}
|
542 |
+
}
|
includes/payment/class-wc-square-payment-logger.php
ADDED
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
if ( ! defined( 'ABSPATH' ) ) {
|
3 |
+
exit; // Exit if accessed directly
|
4 |
+
}
|
5 |
+
|
6 |
+
/**
|
7 |
+
* Square payment logging class which saves important data to the log
|
8 |
+
*
|
9 |
+
* @since 1.0.0
|
10 |
+
* @version 1.0.0
|
11 |
+
*/
|
12 |
+
class WC_Square_Payment_Logger {
|
13 |
+
|
14 |
+
public static $logger;
|
15 |
+
|
16 |
+
/**
|
17 |
+
* Utilize WC logger class
|
18 |
+
*
|
19 |
+
* @since 1.0.0
|
20 |
+
* @version 1.0.0
|
21 |
+
*/
|
22 |
+
public static function log( $message ) {
|
23 |
+
if ( empty( self::$logger ) ) {
|
24 |
+
self::$logger = new WC_Logger();
|
25 |
+
}
|
26 |
+
|
27 |
+
self::$logger->add( 'woocommerce-gateway-square', $message );
|
28 |
+
}
|
29 |
+
}
|
includes/payment/class-wc-square-payments-connect.php
ADDED
@@ -0,0 +1,211 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
if ( ! defined( 'ABSPATH' ) ) {
|
3 |
+
exit; // Exit if accessed directly
|
4 |
+
}
|
5 |
+
|
6 |
+
/**
|
7 |
+
* Provides some overloaded methods from Square Client Class.
|
8 |
+
* This is needed because Square needs v2 of the API to work with payment endpoints.
|
9 |
+
* Once they have ported over all endpoints from v1 to v2, we can merge this back
|
10 |
+
* into the main Square Client Class.
|
11 |
+
*/
|
12 |
+
class WC_Square_Payments_Connect extends WC_Square_Client {
|
13 |
+
const LOCATIONS_CACHE_KEY = 'wc_square_payments_locations';
|
14 |
+
|
15 |
+
/**
|
16 |
+
* @var string
|
17 |
+
*/
|
18 |
+
protected $api_version = 'v2';
|
19 |
+
|
20 |
+
/**
|
21 |
+
* Checks to see if token is valid.
|
22 |
+
*
|
23 |
+
* There is no formal way to check this other than to
|
24 |
+
* retrieve the merchant account details and if it comes back
|
25 |
+
* with a code 200, we assume it is valid.
|
26 |
+
*
|
27 |
+
* @access public
|
28 |
+
* @since 1.0.0
|
29 |
+
* @version 1.0.0
|
30 |
+
* @return bool
|
31 |
+
*/
|
32 |
+
public function is_valid_token() {
|
33 |
+
|
34 |
+
$merchant = $this->request( 'Retrieving Merchant', 'locations' );
|
35 |
+
|
36 |
+
if ( is_wp_error( $merchant ) ) {
|
37 |
+
return false;
|
38 |
+
}
|
39 |
+
|
40 |
+
return true;
|
41 |
+
}
|
42 |
+
|
43 |
+
/**
|
44 |
+
* Charges the card nonce.
|
45 |
+
*
|
46 |
+
* @access public
|
47 |
+
* @since 1.0.0
|
48 |
+
* @version 1.0.0
|
49 |
+
* @param string $location_id
|
50 |
+
* @param array $data
|
51 |
+
* @return object
|
52 |
+
*/
|
53 |
+
public function charge_card_nonce( $location_id, $data ) {
|
54 |
+
$path = '/locations/' . $location_id . '/transactions';
|
55 |
+
|
56 |
+
return $this->request( 'Charge Card Nonce', $path, 'POST', $data );
|
57 |
+
}
|
58 |
+
|
59 |
+
/**
|
60 |
+
* Retrieves a transaction from Square
|
61 |
+
*
|
62 |
+
* @access public
|
63 |
+
* @since 1.0.0
|
64 |
+
* @version 1.0.0
|
65 |
+
* @param string $location_id
|
66 |
+
* @param string $transaction_id
|
67 |
+
* @return object
|
68 |
+
*/
|
69 |
+
public function get_transaction( $location_id, $transaction_id ) {
|
70 |
+
$path = '/locations/' . $location_id . '/transactions/' . $transaction_id;
|
71 |
+
|
72 |
+
return $this->request( 'Get Transaction', $path );
|
73 |
+
}
|
74 |
+
|
75 |
+
/**
|
76 |
+
* Gets the transaction status
|
77 |
+
*
|
78 |
+
* @access public
|
79 |
+
* @since 1.0.0
|
80 |
+
* @version 1.0.0
|
81 |
+
* @param string $location_id
|
82 |
+
* @param string $transaction_id
|
83 |
+
* @return object
|
84 |
+
*/
|
85 |
+
public function get_transaction_status( $location_id, $transaction_id ) {
|
86 |
+
$result = $this->get_transaction( $location_id, $transaction_id );
|
87 |
+
|
88 |
+
if ( is_wp_error( $result ) ) {
|
89 |
+
return null;
|
90 |
+
}
|
91 |
+
|
92 |
+
return $result->transaction->tenders[0]->card_details->status;
|
93 |
+
}
|
94 |
+
|
95 |
+
/**
|
96 |
+
* Gets the tender id of the transaction
|
97 |
+
*
|
98 |
+
* @access public
|
99 |
+
* @since 1.0.0
|
100 |
+
* @version 1.0.0
|
101 |
+
* @param string $location_id
|
102 |
+
* @param string $transaction_id
|
103 |
+
* @return object
|
104 |
+
*/
|
105 |
+
public function get_tender_id( $location_id, $transaction_id ) {
|
106 |
+
$result = $this->get_transaction( $location_id, $transaction_id );
|
107 |
+
|
108 |
+
if ( is_wp_error( $result ) ) {
|
109 |
+
return null;
|
110 |
+
}
|
111 |
+
|
112 |
+
return $result->transaction->tenders[0]->id;
|
113 |
+
}
|
114 |
+
|
115 |
+
/**
|
116 |
+
* Capture a previously authorized transaction ( delay/capture )
|
117 |
+
*
|
118 |
+
* @access public
|
119 |
+
* @since 1.0.0
|
120 |
+
* @version 1.0.0
|
121 |
+
* @param string $location_id
|
122 |
+
* @param string $transaction_id
|
123 |
+
* @return object
|
124 |
+
*/
|
125 |
+
public function capture_transaction( $location_id, $transaction_id ) {
|
126 |
+
$path = '/locations/' . $location_id . '/transactions/' . $transaction_id . '/capture';
|
127 |
+
|
128 |
+
return $this->request( 'Capture Transaction', $path, 'POST' );
|
129 |
+
}
|
130 |
+
|
131 |
+
/**
|
132 |
+
* Voids a previously authorized transaction ( delay/capture )
|
133 |
+
*
|
134 |
+
* @access public
|
135 |
+
* @since 1.0.0
|
136 |
+
* @version 1.0.0
|
137 |
+
* @param string $location_id
|
138 |
+
* @param string $transaction_id
|
139 |
+
* @return object
|
140 |
+
*/
|
141 |
+
public function void_transaction( $location_id, $transaction_id ) {
|
142 |
+
$path = '/locations/' . $location_id . '/transactions/' . $transaction_id . '/void';
|
143 |
+
|
144 |
+
return $this->request( 'Void Authorized Transaction', $path, 'POST' );
|
145 |
+
}
|
146 |
+
|
147 |
+
/**
|
148 |
+
* Refunds a transaction
|
149 |
+
*
|
150 |
+
* @access public
|
151 |
+
* @since 1.0.0
|
152 |
+
* @version 1.0.0
|
153 |
+
* @param string $location_id
|
154 |
+
* @param string $transaction_id
|
155 |
+
* @param array $data
|
156 |
+
* @return object
|
157 |
+
*/
|
158 |
+
public function refund_transaction( $location_id, $transaction_id, $data ) {
|
159 |
+
$path = '/locations/' . $location_id . '/transactions/' . $transaction_id . '/refund';
|
160 |
+
|
161 |
+
return $this->request( 'Refund Transaction', $path, 'POST', $data );
|
162 |
+
}
|
163 |
+
|
164 |
+
/**
|
165 |
+
* Create a customer
|
166 |
+
*
|
167 |
+
* @access public
|
168 |
+
* @since 1.0.0
|
169 |
+
* @version 1.0.0
|
170 |
+
* @param array $data
|
171 |
+
* @return object
|
172 |
+
*/
|
173 |
+
public function create_customer( $data ) {
|
174 |
+
$path = '/customers';
|
175 |
+
|
176 |
+
return $this->request( 'Create Customer', $path, 'POST', $data );
|
177 |
+
}
|
178 |
+
|
179 |
+
/**
|
180 |
+
* Get a customer
|
181 |
+
*
|
182 |
+
* @access public
|
183 |
+
* @since 1.0.0
|
184 |
+
* @version 1.0.0
|
185 |
+
* @param string $customer_id
|
186 |
+
* @return object
|
187 |
+
*/
|
188 |
+
public function get_customer( $customer_id = null ) {
|
189 |
+
if ( null === $customer_id ) {
|
190 |
+
return false;
|
191 |
+
}
|
192 |
+
|
193 |
+
$path = '/customers/' . $customer_id;
|
194 |
+
|
195 |
+
return $this->request( 'Get Customer', $path, 'GET' );
|
196 |
+
}
|
197 |
+
|
198 |
+
/**
|
199 |
+
* @param $path
|
200 |
+
*
|
201 |
+
* @return string
|
202 |
+
*/
|
203 |
+
protected function get_request_url( $path ) {
|
204 |
+
$api_url_base = trailingslashit( $this->get_api_url() );
|
205 |
+
|
206 |
+
$request_path = ltrim( $path, '/' );
|
207 |
+
$request_url = untrailingslashit( $api_url_base . $request_path );
|
208 |
+
|
209 |
+
return $request_url;
|
210 |
+
}
|
211 |
+
}
|
includes/payment/class-wc-square-payments.php
ADDED
@@ -0,0 +1,200 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
if ( ! defined( 'ABSPATH' ) ) {
|
3 |
+
exit; // Exit if accessed directly
|
4 |
+
}
|
5 |
+
|
6 |
+
include_once( dirname( __FILE__ ) . '/class-wc-square-payments-connect.php' );
|
7 |
+
|
8 |
+
class WC_Square_Payments {
|
9 |
+
protected $connect;
|
10 |
+
public $logging;
|
11 |
+
|
12 |
+
/**
|
13 |
+
* Constructor
|
14 |
+
*/
|
15 |
+
public function __construct( WC_Square_Payments_Connect $connect ) {
|
16 |
+
$this->init();
|
17 |
+
$this->connect = $connect;
|
18 |
+
|
19 |
+
add_filter( 'woocommerce_payment_gateways', array( $this, 'register_gateway' ) );
|
20 |
+
|
21 |
+
add_action( 'woocommerce_order_status_on-hold_to_processing', array( $this, 'capture_payment' ) );
|
22 |
+
add_action( 'woocommerce_order_status_on-hold_to_completed', array( $this, 'capture_payment' ) );
|
23 |
+
add_action( 'woocommerce_order_status_on-hold_to_cancelled', array( $this, 'cancel_payment' ) );
|
24 |
+
add_action( 'woocommerce_order_status_on-hold_to_refunded', array( $this, 'cancel_payment' ) );
|
25 |
+
|
26 |
+
if ( is_admin() ) {
|
27 |
+
add_filter( 'woocommerce_order_actions', array( $this, 'add_capture_charge_order_action' ) );
|
28 |
+
add_action( 'woocommerce_order_action_square_capture_charge', array( $this, 'maybe_capture_charge' ) );
|
29 |
+
}
|
30 |
+
|
31 |
+
$gateway_settings = get_option( 'woocommerce_square_settings' );
|
32 |
+
|
33 |
+
$this->logging = ! empty( $gateway_settings['logging'] ) ? true : false;
|
34 |
+
|
35 |
+
return true;
|
36 |
+
}
|
37 |
+
|
38 |
+
/**
|
39 |
+
* Init
|
40 |
+
*/
|
41 |
+
public function init() {
|
42 |
+
if ( ! class_exists( 'WC_Payment_Gateway' ) ) {
|
43 |
+
return;
|
44 |
+
}
|
45 |
+
|
46 |
+
// live/production app id from Square account
|
47 |
+
if ( ! defined( 'SQUARE_APPLICATION_ID' ) ) {
|
48 |
+
define( 'SQUARE_APPLICATION_ID', 'sq0idp-wGVapF8sNt9PLrdj5znuKA' );
|
49 |
+
}
|
50 |
+
|
51 |
+
// Includes
|
52 |
+
include_once( dirname( __FILE__ ) . '/class-wc-square-gateway.php' );
|
53 |
+
|
54 |
+
return true;
|
55 |
+
}
|
56 |
+
|
57 |
+
/**
|
58 |
+
* Register the gateway for use
|
59 |
+
*/
|
60 |
+
public function register_gateway( $methods ) {
|
61 |
+
$methods[] = 'WC_Square_Gateway';
|
62 |
+
|
63 |
+
return $methods;
|
64 |
+
}
|
65 |
+
|
66 |
+
public function add_capture_charge_order_action( $actions ) {
|
67 |
+
if ( ! isset( $_REQUEST['post'] ) ) {
|
68 |
+
return $actions;
|
69 |
+
}
|
70 |
+
|
71 |
+
$order = wc_get_order( $_REQUEST['post'] );
|
72 |
+
|
73 |
+
// bail if the order wasn't paid for with this gateway
|
74 |
+
if ( 'square' !== ( version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->payment_method : $order->get_payment_method() ) ) {
|
75 |
+
return $actions;
|
76 |
+
}
|
77 |
+
|
78 |
+
// bail if charge was already captured
|
79 |
+
if ( 'yes' === get_post_meta( version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->id : $order->get_id(), '_square_charge_captured', true ) ) {
|
80 |
+
return $actions;
|
81 |
+
}
|
82 |
+
|
83 |
+
$actions['square_capture_charge'] = esc_html__( 'Capture Charge', 'woocommerce-square' );
|
84 |
+
|
85 |
+
return $actions;
|
86 |
+
}
|
87 |
+
|
88 |
+
public function maybe_capture_charge( $order ) {
|
89 |
+
if ( ! is_object( $order ) ) {
|
90 |
+
$order = wc_get_order( $order );
|
91 |
+
}
|
92 |
+
|
93 |
+
$this->capture_payment( version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->id : $order->get_id() );
|
94 |
+
|
95 |
+
return true;
|
96 |
+
}
|
97 |
+
|
98 |
+
/**
|
99 |
+
* Capture payment when the order is changed from on-hold to complete or processing
|
100 |
+
*
|
101 |
+
* @param int $order_id
|
102 |
+
*/
|
103 |
+
public function capture_payment( $order_id ) {
|
104 |
+
$order = wc_get_order( $order_id );
|
105 |
+
|
106 |
+
if ( 'square' === ( version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->payment_method : $order->get_payment_method() ) ) {
|
107 |
+
try {
|
108 |
+
$this->log( "Info: Begin capture for order {$order_id}" );
|
109 |
+
|
110 |
+
$trans_id = get_post_meta( $order_id, '_transaction_id', true );
|
111 |
+
$captured = get_post_meta( $order_id, '_square_charge_captured', true );
|
112 |
+
$location = Woocommerce_Square::instance()->integration->get_option( 'location' );
|
113 |
+
$token = get_option( 'woocommerce_square_merchant_access_token' );
|
114 |
+
|
115 |
+
$this->connect->set_access_token( $token );
|
116 |
+
|
117 |
+
$transaction_status = $this->connect->get_transaction_status( $location, $trans_id );
|
118 |
+
|
119 |
+
if ( 'AUTHORIZED' === $transaction_status ) {
|
120 |
+
$result = $this->connect->capture_transaction( $location, $trans_id ); // returns empty object
|
121 |
+
|
122 |
+
if ( is_wp_error( $result ) ) {
|
123 |
+
$order->add_order_note( __( 'Unable to capture charge!', 'woocommerce-square' ) . ' ' . $result->get_error_message() );
|
124 |
+
|
125 |
+
throw new Exception( $result->get_error_message() );
|
126 |
+
} elseif ( ! empty( $result->errors ) ) {
|
127 |
+
$order->add_order_note( __( 'Unable to capture charge!', 'woocommerce-square' ) . ' ' . print_r( $result->errors, true ) );
|
128 |
+
|
129 |
+
throw new Exception( print_r( $result->errors, true ) );
|
130 |
+
} else {
|
131 |
+
$order->add_order_note( sprintf( __( 'Square charge complete (Charge ID: %s)', 'woocommerce-square' ), $trans_id ) );
|
132 |
+
update_post_meta( $order_id, '_square_charge_captured', 'yes' );
|
133 |
+
$this->log( "Info: Capture successful for {$order_id}" );
|
134 |
+
}
|
135 |
+
}
|
136 |
+
} catch ( Exception $e ) {
|
137 |
+
$this->log( sprintf( __( 'Error unable to capture charge: %s', 'woocommerce-square' ), $e->getMessage() ) );
|
138 |
+
}
|
139 |
+
}
|
140 |
+
}
|
141 |
+
|
142 |
+
/**
|
143 |
+
* Cancel authorization
|
144 |
+
*
|
145 |
+
* @param int $order_id
|
146 |
+
*/
|
147 |
+
public function cancel_payment( $order_id ) {
|
148 |
+
$order = wc_get_order( $order_id );
|
149 |
+
|
150 |
+
if ( 'square' === ( version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->payment_method : $order->get_payment_method() ) ) {
|
151 |
+
try {
|
152 |
+
$this->log( "Info: Cancel payment for order {$order_id}" );
|
153 |
+
|
154 |
+
$trans_id = get_post_meta( $order_id, '_transaction_id', true );
|
155 |
+
$captured = get_post_meta( $order_id, '_square_charge_captured', true );
|
156 |
+
$location = Woocommerce_Square::instance()->integration->get_option( 'location' );
|
157 |
+
$token = get_option( 'woocommerce_square_merchant_access_token' );
|
158 |
+
|
159 |
+
$this->connect->set_access_token( $token );
|
160 |
+
|
161 |
+
$transaction_status = $this->connect->get_transaction_status( $location, $trans_id );
|
162 |
+
|
163 |
+
if ( 'AUTHORIZED' === $transaction_status ) {
|
164 |
+
$result = $this->connect->void_transaction( $location, $trans_id ); // returns empty object
|
165 |
+
|
166 |
+
if ( is_wp_error( $result ) ) {
|
167 |
+
$order->add_order_note( __( 'Unable to void charge!', 'woocommerce-square' ) . ' ' . $result->get_error_message() );
|
168 |
+
throw new Exception( $result->get_error_message() );
|
169 |
+
} elseif ( ! empty( $result->errors ) ) {
|
170 |
+
$order->add_order_note( __( 'Unable to void charge!', 'woocommerce-square' ) . ' ' . print_r( $result->errors, true ) );
|
171 |
+
|
172 |
+
throw new Exception( print_r( $result->errors, true ) );
|
173 |
+
} else {
|
174 |
+
$order->add_order_note( sprintf( __( 'Square charge voided! (Charge ID: %s)', 'woocommerce-square' ), $trans_id ) );
|
175 |
+
delete_post_meta( $order_id, '_square_charge_captured' );
|
176 |
+
delete_post_meta( $order_id, '_transaction_id' );
|
177 |
+
}
|
178 |
+
}
|
179 |
+
} catch ( Exception $e ) {
|
180 |
+
$this->log( sprintf( __( 'Unable to void charge!: %s', 'woocommerce-square' ), $e->getMessage() ) );
|
181 |
+
}
|
182 |
+
}
|
183 |
+
}
|
184 |
+
|
185 |
+
/**
|
186 |
+
* Logs
|
187 |
+
*
|
188 |
+
* @since 1.0.0
|
189 |
+
* @version 1.0.0
|
190 |
+
*
|
191 |
+
* @param string $message
|
192 |
+
*/
|
193 |
+
public function log( $message ) {
|
194 |
+
if ( $this->logging ) {
|
195 |
+
WC_Square_Payment_Logger::log( $message );
|
196 |
+
}
|
197 |
+
}
|
198 |
+
}
|
199 |
+
|
200 |
+
new WC_Square_Payments( new WC_Square_Payments_Connect() );
|
readme.txt
ADDED
@@ -0,0 +1,72 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
=== WooCommerce Square ===
|
2 |
+
Contributors: automattic, royho, woothemes
|
3 |
+
Tags: credit card, square, woocommerce, inventory sync
|
4 |
+
Requires at least: 4.4
|
5 |
+
Tested up to: 4.9
|
6 |
+
Requires PHP: 5.6
|
7 |
+
Stable tag: 1.0.25
|
8 |
+
License: GPLv3
|
9 |
+
License URI: https://www.gnu.org/licenses/gpl-3.0.html
|
10 |
+
|
11 |
+
Adds ability to sync inventory between WooCommerce and Square POS. In addition, you can also make purchases through the Square payment gateway.
|
12 |
+
|
13 |
+
== Description ==
|
14 |
+
|
15 |
+
Adds ability to sync inventory between WooCommerce and Square POS. In addition, you can also make purchases through the Square payment gateway.
|
16 |
+
|
17 |
+
= Take Credit card payments easily and directly on your store =
|
18 |
+
|
19 |
+
The Square plugin extends WooCommerce allowing you to take payments directly on your store via Square’s API.
|
20 |
+
|
21 |
+
== Installation ==
|
22 |
+
|
23 |
+
You can download an [older version of this gateway for older versions of WooCommerce from here](https://wordpress.org/plugins/woocommerce-square/developers/).
|
24 |
+
|
25 |
+
= Automatic installation =
|
26 |
+
|
27 |
+
Automatic installation is the easiest option as WordPress handles the file transfers itself and you don’t need to leave your web browser. To
|
28 |
+
do an automatic install of, log in to your WordPress dashboard, navigate to the Plugins menu and click Add New.
|
29 |
+
|
30 |
+
In the search field type “WooCommerce Square” and click Search Plugins. Once you’ve found our plugin you can view details about it such as the point release, rating and description. Most importantly of course, you can install it by simply clicking "Install Now".
|
31 |
+
|
32 |
+
= Manual installation =
|
33 |
+
|
34 |
+
The manual installation method involves downloading our plugin and uploading it to your web server via your favorite FTP application. The WordPress codex contains [instructions on how to do this here](http://codex.wordpress.org/Managing_Plugins#Manual_Plugin_Installation).
|
35 |
+
|
36 |
+
= Updating =
|
37 |
+
|
38 |
+
Automatic updates should work like a charm; as always though, ensure you backup your site just in case.
|
39 |
+
|
40 |
+
== Frequently Asked Questions ==
|
41 |
+
|
42 |
+
= Does this require an SSL certificate? =
|
43 |
+
|
44 |
+
Yes! SSL certificate must be installed on your site to use Square.
|
45 |
+
|
46 |
+
= Where can I find documentation? =
|
47 |
+
|
48 |
+
For help setting up and configuring, please refer to our [user guide](https://docs.woocommerce.com/document/woocommerce-square/)
|
49 |
+
|
50 |
+
= Where can I get support or talk to other users? =
|
51 |
+
|
52 |
+
If you get stuck, you can ask for help in the Plugin Forum.
|
53 |
+
|
54 |
+
== Screenshots ==
|
55 |
+
|
56 |
+
1. The settings panel.
|
57 |
+
|
58 |
+
== Changelog ==
|
59 |
+
|
60 |
+
= 1.0.25 - 2018-01-29 =
|
61 |
+
* Tweaks - Error handling.
|
62 |
+
* Public release on .org
|
63 |
+
|
64 |
+
= 1.0.24 - 2017-12-13 =
|
65 |
+
* Fix - In some cases rounding issues occur causing payment unable to process.
|
66 |
+
* Update - WC tested up to version.
|
67 |
+
* Add - Readme.txt.
|
68 |
+
|
69 |
+
== Upgrade Notice ==
|
70 |
+
|
71 |
+
= 1.0.24 =
|
72 |
+
* Public Release!
|
uninstall.php
ADDED
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
if ( ! defined( 'ABSPATH' ) ) {
|
3 |
+
exit; // Exit if accessed directly
|
4 |
+
}
|
5 |
+
|
6 |
+
// if uninstall not called from WordPress exit
|
7 |
+
if ( ! defined( 'WP_UNINSTALL_PLUGIN' ) ) {
|
8 |
+
exit;
|
9 |
+
}
|
10 |
+
|
11 |
+
/*
|
12 |
+
* Only remove ALL product and page data if WC_REMOVE_ALL_DATA constant is set to true in user's
|
13 |
+
* wp-config.php. This is to prevent data loss when deleting the plugin from the backend
|
14 |
+
* and to ensure only the site owner can perform this action.
|
15 |
+
*/
|
16 |
+
if ( defined( 'WC_REMOVE_ALL_DATA' ) && true === WC_REMOVE_ALL_DATA ) {
|
17 |
+
delete_option( 'wc_square_endpoint_set' );
|
18 |
+
delete_option( 'woocommerce_squareconnect_settings' );
|
19 |
+
delete_option( 'wc_square_polling' );
|
20 |
+
delete_transient( 'wc_square_polling' );
|
21 |
+
delete_transient( 'wc_square_locations' );
|
22 |
+
delete_option( 'woocommerce_square_merchant_access_token' );
|
23 |
+
}
|
woocommerce-square.php
ADDED
@@ -0,0 +1,419 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* Plugin Name: WooCommerce Square
|
4 |
+
* Version: 1.0.25
|
5 |
+
* Plugin URI: https://woocommerce.com/products/square/
|
6 |
+
* Description: Adds ability to sync inventory between WooCommerce and Square POS. In addition, you can also make purchases through the Square payment gateway.
|
7 |
+
* Author: WooCommerce
|
8 |
+
* Author URI: https://www.woocommerce.com/
|
9 |
+
* Requires at least: 4.5.0
|
10 |
+
* Tested up to: 4.7.2
|
11 |
+
* WC requires at least: 2.6
|
12 |
+
* WC tested up to: 3.3
|
13 |
+
* Text Domain: woocommerce-square
|
14 |
+
* Domain Path: /languages
|
15 |
+
*
|
16 |
+
* @package WordPress
|
17 |
+
* @author WooCommerce
|
18 |
+
*/
|
19 |
+
|
20 |
+
if ( ! defined( 'ABSPATH' ) ) {
|
21 |
+
exit;
|
22 |
+
}
|
23 |
+
|
24 |
+
if ( ! class_exists( 'Woocommerce_Square' ) ) :
|
25 |
+
|
26 |
+
define( 'WC_SQUARE_VERSION', '1.0.25' );
|
27 |
+
|
28 |
+
/**
|
29 |
+
* Main class.
|
30 |
+
*
|
31 |
+
* @package Woocommerce_Square
|
32 |
+
* @since 1.0.0
|
33 |
+
* @version 1.0.0
|
34 |
+
*/
|
35 |
+
class Woocommerce_Square {
|
36 |
+
|
37 |
+
private static $_instance = null;
|
38 |
+
|
39 |
+
/**
|
40 |
+
* @var WC_Integration
|
41 |
+
*/
|
42 |
+
public $integration;
|
43 |
+
|
44 |
+
/**
|
45 |
+
* @var WC_Square_Client
|
46 |
+
*/
|
47 |
+
public $square_client;
|
48 |
+
|
49 |
+
/**
|
50 |
+
* @var WC_Square_Connect
|
51 |
+
*/
|
52 |
+
public $square_connect;
|
53 |
+
|
54 |
+
/**
|
55 |
+
* @var WC_Square_Sync_To_Square_WordPress_Hooks
|
56 |
+
*/
|
57 |
+
protected $wc_to_square_wp_hooks;
|
58 |
+
|
59 |
+
/**
|
60 |
+
* Get the single instance aka Singleton
|
61 |
+
*
|
62 |
+
* @access public
|
63 |
+
* @since 1.0.0
|
64 |
+
* @version 1.0.0
|
65 |
+
* @return bool
|
66 |
+
*/
|
67 |
+
public static function instance() {
|
68 |
+
if ( is_null( self::$_instance ) ) {
|
69 |
+
self::$_instance = new self();
|
70 |
+
}
|
71 |
+
|
72 |
+
return self::$_instance;
|
73 |
+
}
|
74 |
+
|
75 |
+
/**
|
76 |
+
* Prevent cloning
|
77 |
+
*
|
78 |
+
* @access public
|
79 |
+
* @since 1.0.0
|
80 |
+
* @version 1.0.0
|
81 |
+
* @return bool
|
82 |
+
*/
|
83 |
+
public function __clone() {
|
84 |
+
_doing_it_wrong( __FUNCTION__, __( 'Cheatin’ huh?', 'woocommerce-square' ), WC_SQUARE_VERSION );
|
85 |
+
}
|
86 |
+
|
87 |
+
/**
|
88 |
+
* Prevent unserializing instances
|
89 |
+
*
|
90 |
+
* @access public
|
91 |
+
* @since 1.0.0
|
92 |
+
* @version 1.0.0
|
93 |
+
* @return bool
|
94 |
+
*/
|
95 |
+
public function __wakeup() {
|
96 |
+
_doing_it_wrong( __FUNCTION__, __( 'Cheatin’ huh?', 'woocommerce-square' ), WC_SQUARE_VERSION );
|
97 |
+
}
|
98 |
+
|
99 |
+
/**
|
100 |
+
* Woocommerce_Square constructor.
|
101 |
+
*/
|
102 |
+
private function __construct() {
|
103 |
+
|
104 |
+
add_action( 'woocommerce_loaded', array( $this, 'bootstrap' ) );
|
105 |
+
|
106 |
+
}
|
107 |
+
|
108 |
+
public function bootstrap() {
|
109 |
+
add_action( 'admin_notices', array( $this, 'check_environment' ) );
|
110 |
+
|
111 |
+
$this->define_constants();
|
112 |
+
$this->includes();
|
113 |
+
$this->init();
|
114 |
+
$this->init_hooks();
|
115 |
+
|
116 |
+
do_action( 'wc_square_loaded' );
|
117 |
+
|
118 |
+
}
|
119 |
+
|
120 |
+
public function init() {
|
121 |
+
$this->integration = new WC_Square_Integration();
|
122 |
+
|
123 |
+
$square_client = new WC_Square_Client();
|
124 |
+
|
125 |
+
$access_token = get_option( 'woocommerce_square_merchant_access_token' );
|
126 |
+
$square_client->set_access_token( $access_token );
|
127 |
+
$square_client->set_merchant_id( $this->integration->get_option( 'location' ) );
|
128 |
+
$this->square_client = $square_client;
|
129 |
+
|
130 |
+
$this->square_connect = new WC_Square_Connect( $square_client );
|
131 |
+
|
132 |
+
$wc_to_square_sync = new WC_Square_Sync_To_Square( $this->square_connect );
|
133 |
+
$square_to_wc_sync = new WC_Square_Sync_From_Square( $this->square_connect );
|
134 |
+
|
135 |
+
$inventory_poll = new WC_Square_Inventory_Poll( $this->integration, $square_to_wc_sync );
|
136 |
+
|
137 |
+
if ( is_admin() ) {
|
138 |
+
|
139 |
+
$bulk_handler = new WC_Square_Bulk_Sync_Handler( $this->square_connect, $wc_to_square_sync, $square_to_wc_sync );
|
140 |
+
|
141 |
+
}
|
142 |
+
|
143 |
+
$this->wc_to_square_wp_hooks = new WC_Square_Sync_To_Square_WordPress_Hooks( $this->integration, $wc_to_square_sync );
|
144 |
+
}
|
145 |
+
|
146 |
+
/**
|
147 |
+
* Define constants
|
148 |
+
*
|
149 |
+
* @access public
|
150 |
+
* @since 1.0.0
|
151 |
+
* @version 1.0.0
|
152 |
+
* @return bool
|
153 |
+
*/
|
154 |
+
public function define_constants() {
|
155 |
+
define( 'WC_SQUARE_PATH', untrailingslashit( plugin_dir_path( __FILE__ ) ) );
|
156 |
+
define( 'WC_SQUARE_PLUGIN_URL', untrailingslashit( plugins_url( basename( plugin_dir_path( __FILE__ ) ), basename( __FILE__ ) ) ) );
|
157 |
+
|
158 |
+
// if using staging, define this in wp-config.php
|
159 |
+
if ( ! defined( 'WC_SQUARE_ENABLE_STAGING' ) ) {
|
160 |
+
define( 'WC_SQUARE_ENABLE_STAGING', false );
|
161 |
+
}
|
162 |
+
|
163 |
+
return true;
|
164 |
+
}
|
165 |
+
|
166 |
+
/**
|
167 |
+
* Check if country is set to allowed country.
|
168 |
+
*
|
169 |
+
* @since 1.0.10
|
170 |
+
* @version 1.0.10
|
171 |
+
*/
|
172 |
+
public function is_allowed_countries() {
|
173 |
+
if ( 'US' !== WC()->countries->get_base_country() && 'CA' !== WC()->countries->get_base_country() && 'AU' !== WC()->countries->get_base_country() && 'GB' !== WC()->countries->get_base_country() && 'JP' !== WC()->countries->get_base_country() ) {
|
174 |
+
return false;
|
175 |
+
}
|
176 |
+
|
177 |
+
return true;
|
178 |
+
}
|
179 |
+
|
180 |
+
/**
|
181 |
+
* Check if currency is set to allowed currency.
|
182 |
+
*
|
183 |
+
* @since 1.0.10
|
184 |
+
* @version 1.0.10
|
185 |
+
*/
|
186 |
+
public function is_allowed_currencies() {
|
187 |
+
if ( 'USD' !== get_woocommerce_currency() && 'CAD' !== get_woocommerce_currency() && 'AUD' !== get_woocommerce_currency() && 'GBP' !== get_woocommerce_currency() && 'JPY' !== get_woocommerce_currency() ) {
|
188 |
+
return false;
|
189 |
+
}
|
190 |
+
|
191 |
+
return true;
|
192 |
+
}
|
193 |
+
|
194 |
+
/**
|
195 |
+
* Check required environment
|
196 |
+
*
|
197 |
+
* @access public
|
198 |
+
* @since 1.0.10
|
199 |
+
* @version 1.0.10
|
200 |
+
* @return null
|
201 |
+
*/
|
202 |
+
public function check_environment() {
|
203 |
+
if ( ! current_user_can( 'manage_woocommerce' ) ) {
|
204 |
+
return;
|
205 |
+
}
|
206 |
+
|
207 |
+
if ( ! $this->is_allowed_countries() ) {
|
208 |
+
$admin_page = 'wc-settings';
|
209 |
+
|
210 |
+
echo '<div class="error">
|
211 |
+
<p>' . sprintf( __( 'Square requires that the <a href="%s">base country/region</a> is Australia, Canada, Japan, United Kingdom, or United States.', 'woocommerce-square' ), admin_url( 'admin.php?page=' . $admin_page . '&tab=general' ) ) . '</p>
|
212 |
+
</div>';
|
213 |
+
}
|
214 |
+
|
215 |
+
if ( ! $this->is_allowed_currencies() ) {
|
216 |
+
$admin_page = 'wc-settings';
|
217 |
+
|
218 |
+
echo '<div class="error">
|
219 |
+
<p>' . sprintf( __( 'Square requires that the <a href="%s">currency</a> is set to AUD, CAD, GBP, JPY, or USD.', 'woocommerce-square' ), admin_url( 'admin.php?page=' . $admin_page . '&tab=general' ) ) . '</p>
|
220 |
+
</div>';
|
221 |
+
}
|
222 |
+
}
|
223 |
+
|
224 |
+
/**
|
225 |
+
* Include all files needed
|
226 |
+
*
|
227 |
+
* @access public
|
228 |
+
* @since 1.0.0
|
229 |
+
* @version 1.0.0
|
230 |
+
* @return bool
|
231 |
+
*/
|
232 |
+
public function includes() {
|
233 |
+
require_once( dirname( __FILE__ ) . '/includes/class-wc-square-install.php' );
|
234 |
+
require_once( dirname( __FILE__ ) . '/includes/class-wc-square-deactivation.php' );
|
235 |
+
require_once( dirname( __FILE__ ) . '/includes/class-wc-square-sync-logger.php' );
|
236 |
+
require_once( dirname( __FILE__ ) . '/includes/class-wc-square-wc-products.php' );
|
237 |
+
require_once( dirname( __FILE__ ) . '/includes/class-wc-square-connect.php' );
|
238 |
+
require_once( dirname( __FILE__ ) . '/includes/class-wc-square-sync-to-square.php' );
|
239 |
+
require_once( dirname( __FILE__ ) . '/includes/class-wc-square-sync-from-square.php' );
|
240 |
+
require_once( dirname( __FILE__ ) . '/includes/admin/class-wc-square-admin-integration.php' );
|
241 |
+
require_once( dirname( __FILE__ ) . '/includes/class-wc-square-sync-to-square-wp-hooks.php' );
|
242 |
+
require_once( dirname( __FILE__ ) . '/includes/class-wc-square-client.php' );
|
243 |
+
require_once( dirname( __FILE__ ) . '/includes/class-wc-square-utils.php' );
|
244 |
+
require_once( dirname( __FILE__ ) . '/includes/class-wc-square-inventory-poll.php' );
|
245 |
+
require_once( dirname( __FILE__ ) . '/includes/payment/class-wc-square-payment-logger.php' );
|
246 |
+
require_once( dirname( __FILE__ ) . '/includes/payment/class-wc-square-payments.php' );
|
247 |
+
|
248 |
+
if ( is_admin() ) {
|
249 |
+
require_once( dirname( __FILE__ ) . '/includes/admin/class-wc-square-bulk-sync-handler.php' );
|
250 |
+
require_once( dirname( __FILE__ ) . '/includes/admin/class-wc-square-admin-product-meta-box.php' );
|
251 |
+
}
|
252 |
+
|
253 |
+
}
|
254 |
+
|
255 |
+
/**
|
256 |
+
* Add integration settings page
|
257 |
+
*
|
258 |
+
* @access public
|
259 |
+
* @since 1.0.0
|
260 |
+
* @version 1.0.0
|
261 |
+
* @return bool
|
262 |
+
*/
|
263 |
+
public function include_integration( $integrations ) {
|
264 |
+
// Square only supports US and Canada for now.
|
265 |
+
if ( $this->is_allowed_currencies() && $this->is_allowed_countries() ) {
|
266 |
+
$integrations[] = $this->integration;
|
267 |
+
}
|
268 |
+
|
269 |
+
return $integrations;
|
270 |
+
|
271 |
+
}
|
272 |
+
|
273 |
+
/**
|
274 |
+
* Initializes hooks
|
275 |
+
*
|
276 |
+
* @access public
|
277 |
+
* @since 1.0.0
|
278 |
+
* @version 1.0.0
|
279 |
+
* @return bool
|
280 |
+
*/
|
281 |
+
public function init_hooks() {
|
282 |
+
|
283 |
+
register_deactivation_hook( __FILE__, array( 'WC_Square_Deactivation', 'deactivate' ) );
|
284 |
+
|
285 |
+
if ( is_woocommerce_active() ) {
|
286 |
+
|
287 |
+
add_filter( 'woocommerce_integrations', array( $this, 'include_integration' ) );
|
288 |
+
|
289 |
+
add_action( 'plugins_loaded', array( $this, 'load_plugin_textdomain' ) );
|
290 |
+
|
291 |
+
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_scripts' ) );
|
292 |
+
|
293 |
+
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_styles' ) );
|
294 |
+
|
295 |
+
add_action( 'woocommerce_square_bulk_syncing_square_to_wc', array( $this->wc_to_square_wp_hooks, 'disable' ) );
|
296 |
+
|
297 |
+
add_action( 'admin_notices', array( $this, 'is_connected_to_square' ) );
|
298 |
+
|
299 |
+
} else {
|
300 |
+
|
301 |
+
add_action( 'admin_notices', array( $this, 'woocommerce_missing_notice' ) );
|
302 |
+
|
303 |
+
}
|
304 |
+
|
305 |
+
}
|
306 |
+
|
307 |
+
/**
|
308 |
+
* Loads the admin JS scripts
|
309 |
+
*
|
310 |
+
* @access public
|
311 |
+
* @since 1.0.0
|
312 |
+
* @version 1.0.0
|
313 |
+
* @return bool
|
314 |
+
*/
|
315 |
+
public function enqueue_admin_scripts() {
|
316 |
+
$current_screen = get_current_screen();
|
317 |
+
|
318 |
+
$suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
|
319 |
+
|
320 |
+
wp_register_script( 'wc-square-admin-scripts', WC_SQUARE_PLUGIN_URL . '/assets/js/wc-square-admin-scripts' . $suffix . '.js', array( 'jquery' ), WC_SQUARE_VERSION, true );
|
321 |
+
|
322 |
+
if ( 'woocommerce_page_wc-settings' === $current_screen->id ) {
|
323 |
+
|
324 |
+
wp_enqueue_script( 'wc-square-admin-scripts' );
|
325 |
+
|
326 |
+
$localized_vars = array(
|
327 |
+
'admin_ajax_url' => admin_url( 'admin-ajax.php' ),
|
328 |
+
'ajaxSyncNonce' => wp_create_nonce( 'square-sync' ),
|
329 |
+
'i18n' => array(
|
330 |
+
'confirm_sync' => __( 'This process may take awhile depending on the amount of items that need to be synced. Please do not close this tab/window or else the sync will terminate. Click OK to continue to sync.', 'woocommerce-square' ),
|
331 |
+
),
|
332 |
+
);
|
333 |
+
|
334 |
+
wp_localize_script( 'wc-square-admin-scripts', 'wc_square_local', $localized_vars );
|
335 |
+
}
|
336 |
+
|
337 |
+
return true;
|
338 |
+
}
|
339 |
+
|
340 |
+
/**
|
341 |
+
* Loads the admin CSS styles
|
342 |
+
*
|
343 |
+
* @access public
|
344 |
+
* @since 1.0.0
|
345 |
+
* @version 1.0.0
|
346 |
+
* @return bool
|
347 |
+
*/
|
348 |
+
public function enqueue_admin_styles() {
|
349 |
+
$current_screen = get_current_screen();
|
350 |
+
|
351 |
+
wp_register_style( 'wc-square-admin-styles', WC_SQUARE_PLUGIN_URL . '/assets/css/wc-square-admin-styles.css', null, WC_SQUARE_VERSION );
|
352 |
+
|
353 |
+
if ( 'woocommerce_page_wc-settings' === $current_screen->id ) {
|
354 |
+
|
355 |
+
wp_enqueue_style( 'wc-square-admin-styles' );
|
356 |
+
}
|
357 |
+
|
358 |
+
return true;
|
359 |
+
}
|
360 |
+
|
361 |
+
/**
|
362 |
+
* Load the plugin text domain for translation.
|
363 |
+
*
|
364 |
+
* @access public
|
365 |
+
* @since 1.0.0
|
366 |
+
* @version 1.0.0
|
367 |
+
* @return bool
|
368 |
+
*/
|
369 |
+
public function load_plugin_textdomain() {
|
370 |
+
$locale = apply_filters( 'woocommerce_square_plugin_locale', get_locale(), 'woocommerce-square' );
|
371 |
+
|
372 |
+
load_textdomain( 'woocommerce-square', trailingslashit( WP_LANG_DIR ) . 'woocommerce-square/woocommerce-square' . '-' . $locale . '.mo' );
|
373 |
+
|
374 |
+
load_plugin_textdomain( 'woocommerce-square', false, dirname( plugin_basename( __FILE__ ) ) . '/languages' );
|
375 |
+
|
376 |
+
return true;
|
377 |
+
}
|
378 |
+
|
379 |
+
/**
|
380 |
+
* WooCommerce fallback notice.
|
381 |
+
*
|
382 |
+
* @access public
|
383 |
+
* @since 1.0.0
|
384 |
+
* @version 1.0.0
|
385 |
+
* @return string
|
386 |
+
*/
|
387 |
+
public function woocommerce_missing_notice() {
|
388 |
+
echo '<div class="error"><p>' . sprintf( __( 'WooCommerce Square Plugin requires WooCommerce to be installed and active. You can download %s here.', 'woocommerce-square' ), '<a href="https://woocommerce.com/woocommerce/" target="_blank">WooCommerce</a>' ) . '</p></div>';
|
389 |
+
|
390 |
+
return true;
|
391 |
+
}
|
392 |
+
|
393 |
+
/**
|
394 |
+
* Shows a notice when the site is not yet connected to square.
|
395 |
+
*
|
396 |
+
* @access public
|
397 |
+
* @since 1.0.0
|
398 |
+
* @version 1.0.0
|
399 |
+
* @return string
|
400 |
+
*/
|
401 |
+
public function is_connected_to_square() {
|
402 |
+
$settings = get_option( 'woocommerce_squareconnect_settings', '' );
|
403 |
+
$existing_token = get_option( 'woocommerce_square_merchant_access_token' );
|
404 |
+
|
405 |
+
if ( empty( $existing_token ) ) {
|
406 |
+
echo '<div class="error"><p>' . sprintf( __( 'WooCommerce Square is almost ready. To get started, %1$sconnect your Square Account.%2$s', 'woocommerce-square' ), '<a href="' . admin_url( 'admin.php?page=wc-settings&tab=integration§ion=squareconnect' ) . '">', '</a>' ) . '</p></div>';
|
407 |
+
}
|
408 |
+
|
409 |
+
if ( empty( $settings ) || empty( $settings['location'] ) ) {
|
410 |
+
echo '<div class="error"><p>' . sprintf( __( 'WooCommerce Square is almost ready. Please %1$sset your business location.%2$s', 'woocommerce-square' ), '<a href="' . admin_url( 'admin.php?page=wc-settings&tab=integration§ion=squareconnect' ) . '">', '</a>' ) . '</p></div>';
|
411 |
+
}
|
412 |
+
|
413 |
+
return true;
|
414 |
+
}
|
415 |
+
}
|
416 |
+
|
417 |
+
Woocommerce_Square::instance();
|
418 |
+
|
419 |
+
endif;
|