Version Description
Download this release
Release Info
Developer | bor0 |
Plugin | ![]() |
Version | 4.1.23 |
Comparing to | |
See all releases |
Version 4.1.23
- assets/css/admin.css +11 -0
- assets/images/shipstation-logo-blue.png +0 -0
- changelog.txt +135 -0
- includes/api-requests/class-wc-safe-domdocument.php +98 -0
- includes/api-requests/class-wc-shipstation-api-export.php +375 -0
- includes/api-requests/class-wc-shipstation-api-request.php +59 -0
- includes/api-requests/class-wc-shipstation-api-shipnotify.php +351 -0
- includes/class-wc-shipstation-api.php +84 -0
- includes/class-wc-shipstation-integration.php +166 -0
- includes/class-wc-shipstation-privacy.php +165 -0
- includes/data/data-settings.php +72 -0
- readme.txt +52 -0
- woocommerce-shipstation.php +87 -0
assets/css/admin.css
ADDED
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
.shipstation-logo {
|
2 |
+
width: 180px;
|
3 |
+
margin: 0 0 -16px -16px;
|
4 |
+
}
|
5 |
+
|
6 |
+
.shipstation-external-link:after {
|
7 |
+
font-family: "dashicons";
|
8 |
+
content: "\f504";
|
9 |
+
display: inline-block;
|
10 |
+
vertical-align: bottom;
|
11 |
+
}
|
assets/images/shipstation-logo-blue.png
ADDED
Binary file
|
changelog.txt
ADDED
@@ -0,0 +1,135 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
*** ShipStation for WooCommerce ***
|
2 |
+
|
3 |
+
= 2018-09-12 - version 4.1.23 =
|
4 |
+
* Fix - Use correct textdomain on some strings.
|
5 |
+
* Tweak - Rework settings notice to correctly provide setup instructions.
|
6 |
+
* Tweak - Coding standards and making the plugin ready for wordpress.org.
|
7 |
+
|
8 |
+
= 2018-05-24 - version 4.1.22 =
|
9 |
+
* Fix - Order timestamp issue.
|
10 |
+
|
11 |
+
= 2018-05-23 - version 4.1.21 =
|
12 |
+
* Fix - Privacy policy updates.
|
13 |
+
|
14 |
+
= 2018-05-23 - version 4.1.20 =
|
15 |
+
* Fix - Paid date not showing actual payment date, but Order Date instead.
|
16 |
+
* Update - Privacy policy notification.
|
17 |
+
* Update - Export/erasure hooks added.
|
18 |
+
* Update - WC 3.4 compatibility.
|
19 |
+
|
20 |
+
= 2017-12-15 - version 4.1.19 =
|
21 |
+
* Fix - WC 3.3 compatibility.
|
22 |
+
|
23 |
+
= 2017-07-18 - version 4.1.18 =
|
24 |
+
* Fix - Update the order status to complete if XML from ShipStation is not present in request's body. Also log the request information.
|
25 |
+
* Fix - Adjusted text domain for two strings so that they are now translateable.
|
26 |
+
|
27 |
+
= 2017-07-06 - version 4.1.17 =
|
28 |
+
* Fix - Issue when a server couldn't read ShipNotify's XML posted in request's body, nothing is updated in the order.
|
29 |
+
* Tweak - Added setting, docs, and support links in plugin action links.
|
30 |
+
|
31 |
+
= 2017-06-14 - version 4.1.16 =
|
32 |
+
* Fix - Issue where legacy code for converting sequential order numbers still used.
|
33 |
+
* Fix - Make sure to not count non shippable item when get notified from ShipStation.
|
34 |
+
|
35 |
+
= 2017-05-12 - version 4.1.15 =
|
36 |
+
* Fix - Ensure some orders from previous version of ShipStation are able to be found on notifications.
|
37 |
+
|
38 |
+
= 2017-05-11 - version 4.1.14 =
|
39 |
+
* Fix - Possible error when order is not found during shipment notification.
|
40 |
+
* Tweak - Order numbers are now sent via own XML field and will not display in invoice.
|
41 |
+
|
42 |
+
= 2017-05-05 - version 4.1.13 =
|
43 |
+
* Fix - WC30 date/time not displaying correctly.
|
44 |
+
* Fix - Tax amount discrenpancy when sent to Shipstation.
|
45 |
+
* Fix - When using split orders, order does not get updated in WooCommerce.
|
46 |
+
* Tweak - Sequential Numbers Pro compatibility.
|
47 |
+
* Add - Exported order note when the order has been exported.
|
48 |
+
|
49 |
+
= 2017-05-02 - version 4.1.12 =
|
50 |
+
* Fix - Product attributes not passing to Shipstation under certain conditions.
|
51 |
+
|
52 |
+
= 2017-05-01 - version 4.1.11 =
|
53 |
+
* Fix - Export error due to WC30 incompatibility.
|
54 |
+
|
55 |
+
= 2017-04-10 - version 4.1.10 =
|
56 |
+
* Fix - Allow additional characters to be used for shipping service name
|
57 |
+
|
58 |
+
= 2017-04-06 - version 4.1.9 =
|
59 |
+
* Fix - Additional updates for WC 3.0 compatibility
|
60 |
+
|
61 |
+
= 2017-04-03 - version 4.1.8 =
|
62 |
+
* Fix - PHP 7 compatibility
|
63 |
+
* Fix - Update for WC 3.0 compatibility
|
64 |
+
|
65 |
+
= 2016-10-03 - version 4.1.7 =
|
66 |
+
* Fix - Digital products are also sent through.
|
67 |
+
* Fix - Checkout add on fee not being sent through.
|
68 |
+
|
69 |
+
= 2016-08-15 - version 4.1.6 =
|
70 |
+
* Tweak - Added filter for ShipNotify order ID
|
71 |
+
* Tweak - Send payment method ShipStation
|
72 |
+
* Fix - Issue where fee items not be exported to ShipStation
|
73 |
+
|
74 |
+
= 2016-02-24 - version 4.1.5 =
|
75 |
+
* Fix - Compatibility issue with WC Order Status Manager
|
76 |
+
|
77 |
+
= 2016-01-25 - version 4.1.4 =
|
78 |
+
* Fix - Compatibility issue with woocommerce-sequential-order-numbers-pro version 1-9-0
|
79 |
+
|
80 |
+
= 2015-09-23 - version 4.1.3 =
|
81 |
+
* Fix - Allow copy/paste from API key field in firefox
|
82 |
+
|
83 |
+
= 2015-08-21 - version 4.1.2 =
|
84 |
+
* Fix - Send pre-discount unit price.
|
85 |
+
|
86 |
+
= 2015-08-06 - version 4.1.1 =
|
87 |
+
* Fix - Send UnitPrice as single product total-
|
88 |
+
* Tweak - Date parsing.
|
89 |
+
|
90 |
+
= 2015-06-24 - version 4.1.0 =
|
91 |
+
* Fix - Sanitize XML response.
|
92 |
+
* Fix - Prevent API requests being callable when not authenticated.
|
93 |
+
* Fix - Prevent caching.
|
94 |
+
* Tweak - Use hash_equals to compare keys.
|
95 |
+
* Tweak - Send total discount to ShipStation.
|
96 |
+
|
97 |
+
= 2015-05-12 - version 4.0.9 =
|
98 |
+
* Tweak - woocommerce_shipstation_export_order filter.
|
99 |
+
* Tweak - Exclude system notes.
|
100 |
+
* Tweak - Custom field value filters.
|
101 |
+
|
102 |
+
= 2015-04-03 - version 4.0.8 =
|
103 |
+
* Fix - Don't automatically set to $is_customer_note to true
|
104 |
+
|
105 |
+
= 2015-03-12 - version 4.0.7 =
|
106 |
+
* Check if $product exists before checking if needs_shipping in export.
|
107 |
+
|
108 |
+
= 2015-01-16 - version 4.0.6 =
|
109 |
+
* Send negative discount.
|
110 |
+
|
111 |
+
= 2015-01-08 - version 4.0.5 =
|
112 |
+
* Export query based on post_modified_gmt rather than post_date_gmt
|
113 |
+
|
114 |
+
= 2014-11-19 - version 4.0.4 =
|
115 |
+
* Fix compatibility with Sequential order numbers.
|
116 |
+
|
117 |
+
= 2014-11-13 - version 4.0.3 =
|
118 |
+
* Extra logging in ShipNotify.
|
119 |
+
* Fixed completing orders with multiple lines.
|
120 |
+
|
121 |
+
= 2014-11-13 - version 4.0.2 =
|
122 |
+
* Order results by date.
|
123 |
+
* Enforce minimum page 1.
|
124 |
+
* Removed check to see if orders need shipping to prevent issues with offset/max pages. Exports all orders.
|
125 |
+
|
126 |
+
= 2014-11-12 - version 4.0.1 =
|
127 |
+
* Added 'pages' node to XML feed so ShipStation knows how many pages of results are present.
|
128 |
+
|
129 |
+
= 2014-11-01 - version 4.0.0 =
|
130 |
+
* Completely refactored by WooThemes!
|
131 |
+
* Supports split orders (only completes the order once all items are shipped).
|
132 |
+
* Exports orders (from statuses you define).
|
133 |
+
* Excludes orders and items which do not require shipping.
|
134 |
+
* Simplified setup process; just requires an auth key.
|
135 |
+
* Exports order-level discounts as line items.
|
includes/api-requests/class-wc-safe-domdocument.php
ADDED
@@ -0,0 +1,98 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
if ( ! defined( 'ABSPATH' ) ) {
|
3 |
+
exit;
|
4 |
+
}
|
5 |
+
|
6 |
+
/**
|
7 |
+
* Drop in replacement for DOMDocument that is secure against XML eXternal Entity (XXE) Injection.
|
8 |
+
* Bails if any DOCTYPE is found
|
9 |
+
*
|
10 |
+
* Comments in quotes come from the DOMDocument documentation: http://php.net/manual/en/class.domdocument.php
|
11 |
+
*/
|
12 |
+
class WC_Safe_DOMDocument extends DOMDocument {
|
13 |
+
/**
|
14 |
+
* When called non-statically (as an object method) with malicious data, no Exception is thrown, but the object is emptied of all DOM nodes.
|
15 |
+
*
|
16 |
+
* @param string $filename The path to the XML document.
|
17 |
+
* @param int $options Bitwise OR of the libxml option constants. http://us3.php.net/manual/en/libxml.constants.php
|
18 |
+
*
|
19 |
+
* @return bool|DOMDocument true on success, false on failure. If called statically (E_STRICT error), returns DOMDocument on success.
|
20 |
+
*/
|
21 |
+
public function load( $filename, $options = 0 ) {
|
22 |
+
if ( '' === $filename ) {
|
23 |
+
// "If an empty string is passed as the filename or an empty file is named, a warning will be generated."
|
24 |
+
// "This warning is not generated by libxml and cannot be handled using libxml's error handling functions."
|
25 |
+
trigger_error( 'WC_Safe_DOMDocument::load(): Empty string supplied as input', E_USER_WARNING );
|
26 |
+
return false;
|
27 |
+
}
|
28 |
+
|
29 |
+
if ( ! is_file( $filename ) || ! is_readable( $filename ) ) {
|
30 |
+
// This warning probably would have been generated by libxml and could have been handled handled using libxml's error handling functions.
|
31 |
+
// In WC_Safe_DOMDocument, however, we catch it before libxml, so it can't.
|
32 |
+
// The alternative is to let file_get_contents() handle the error, but that's annoying.
|
33 |
+
trigger_error( 'WC_Safe_DOMDocument::load(): I/O warning : failed to load external entity "' . $filename . '"', E_USER_WARNING );
|
34 |
+
return false;
|
35 |
+
}
|
36 |
+
|
37 |
+
if ( is_object( $this ) ) {
|
38 |
+
return $this->loadXML( file_get_contents( $filename ), $options );
|
39 |
+
} else {
|
40 |
+
// "This method *may* be called statically, but will issue an E_STRICT error."
|
41 |
+
return self::loadXML( file_get_contents( $filename ), $options );
|
42 |
+
}
|
43 |
+
}
|
44 |
+
|
45 |
+
/**
|
46 |
+
* When called non-statically (as an object method) with malicious data, no Exception is thrown, but the object is emptied of all DOM nodes.
|
47 |
+
*
|
48 |
+
* @param string $source The string containing the XML.
|
49 |
+
* @param int $options Bitwise OR of the libxml option constants. http://us3.php.net/manual/en/libxml.constants.php
|
50 |
+
*
|
51 |
+
* @return bool|DOMDocument true on success, false on failure. If called statically (E_STRICT error), returns DOMDocument on success.
|
52 |
+
*/
|
53 |
+
public function loadXML( $source, $options = 0 ) {
|
54 |
+
if ( '' === $source ) {
|
55 |
+
// "If an empty string is passed as the source, a warning will be generated."
|
56 |
+
// "This warning is not generated by libxml and cannot be handled using libxml's error handling functions."
|
57 |
+
trigger_error( 'WC_Safe_DOMDocument::loadXML(): Empty string supplied as input', E_USER_WARNING );
|
58 |
+
return false;
|
59 |
+
}
|
60 |
+
|
61 |
+
$old = null;
|
62 |
+
|
63 |
+
if ( function_exists( 'libxml_disable_entity_loader' ) ) {
|
64 |
+
$old = libxml_disable_entity_loader( true );
|
65 |
+
}
|
66 |
+
|
67 |
+
$return = parent::loadXML( $source, $options );
|
68 |
+
|
69 |
+
if ( ! is_null( $old ) ) {
|
70 |
+
libxml_disable_entity_loader( $old );
|
71 |
+
}
|
72 |
+
|
73 |
+
if ( ! $return ) {
|
74 |
+
return $return;
|
75 |
+
}
|
76 |
+
|
77 |
+
// "This method *may* be called statically, but will issue an E_STRICT error."
|
78 |
+
$is_this = is_object( $this );
|
79 |
+
|
80 |
+
$object = $is_this ? $this : $return;
|
81 |
+
|
82 |
+
if ( isset( $object->doctype ) ) {
|
83 |
+
if ( $is_this ) {
|
84 |
+
// Get rid of the dangerous input by removing *all* nodes
|
85 |
+
while ( $this->firstChild ) {
|
86 |
+
$this->removeChild( $this->firstChild );
|
87 |
+
}
|
88 |
+
}
|
89 |
+
|
90 |
+
trigger_error( 'WC_Safe_DOMDocument::loadXML(): Unsafe DOCTYPE Detected', E_USER_WARNING );
|
91 |
+
|
92 |
+
return false;
|
93 |
+
}
|
94 |
+
|
95 |
+
return $return;
|
96 |
+
}
|
97 |
+
}
|
98 |
+
|
includes/api-requests/class-wc-shipstation-api-export.php
ADDED
@@ -0,0 +1,375 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
if ( ! defined( 'ABSPATH' ) ) {
|
4 |
+
exit; // Exit if accessed directly
|
5 |
+
}
|
6 |
+
|
7 |
+
/**
|
8 |
+
* WC_Shipstation_API_Export Class
|
9 |
+
*/
|
10 |
+
class WC_Shipstation_API_Export extends WC_Shipstation_API_Request {
|
11 |
+
|
12 |
+
/**
|
13 |
+
* Constructor
|
14 |
+
*/
|
15 |
+
public function __construct() {
|
16 |
+
if ( ! WC_Shipstation_API::authenticated() ) {
|
17 |
+
exit;
|
18 |
+
}
|
19 |
+
}
|
20 |
+
|
21 |
+
/**
|
22 |
+
* Do the request
|
23 |
+
*/
|
24 |
+
public function request() {
|
25 |
+
global $wpdb;
|
26 |
+
|
27 |
+
$this->validate_input( array( 'start_date', 'end_date' ) );
|
28 |
+
|
29 |
+
header( 'Content-Type: text/xml' );
|
30 |
+
$xml = new DOMDocument( '1.0', 'utf-8' );
|
31 |
+
$xml->formatOutput = true;
|
32 |
+
$page = max( 1, isset( $_GET['page'] ) ? absint( $_GET['page'] ) : 1 );
|
33 |
+
$exported = 0;
|
34 |
+
$tz_offset = get_option( 'gmt_offset' ) * 3600;
|
35 |
+
$raw_start_date = wc_clean( urldecode( $_GET['start_date'] ) );
|
36 |
+
$raw_end_date = wc_clean( urldecode( $_GET['end_date'] ) );
|
37 |
+
|
38 |
+
// Parse start and end date
|
39 |
+
if ( $raw_start_date && false === strtotime( $raw_start_date ) ) {
|
40 |
+
$month = substr( $raw_start_date, 0, 2 );
|
41 |
+
$day = substr( $raw_start_date, 2, 2 );
|
42 |
+
$year = substr( $raw_start_date, 4, 4 );
|
43 |
+
$time = substr( $raw_start_date, 9, 4 );
|
44 |
+
$start_date = gmdate( 'Y-m-d H:i:s', strtotime( $year . '-' . $month . '-' . $day . ' ' . $time ) );
|
45 |
+
} else {
|
46 |
+
$start_date = gmdate( 'Y-m-d H:i:s', strtotime( $raw_start_date ) );
|
47 |
+
}
|
48 |
+
|
49 |
+
if ( $raw_end_date && false === strtotime( $raw_end_date ) ) {
|
50 |
+
$month = substr( $raw_end_date, 0, 2 );
|
51 |
+
$day = substr( $raw_end_date, 2, 2 );
|
52 |
+
$year = substr( $raw_end_date, 4, 4 );
|
53 |
+
$time = substr( $raw_end_date, 9, 4 );
|
54 |
+
$end_date = gmdate( 'Y-m-d H:i:s', strtotime( $year . '-' . $month . '-' . $day . ' ' . $time ) );
|
55 |
+
} else {
|
56 |
+
$end_date = gmdate( 'Y-m-d H:i:s', strtotime( $raw_end_date ) );
|
57 |
+
}
|
58 |
+
|
59 |
+
if ( version_compare( WC_VERSION, '3.1', '>=' ) ) {
|
60 |
+
$order_ids = wc_get_orders( array(
|
61 |
+
'date_modified' => $start_date . '...' . $end_date,
|
62 |
+
'type' => 'shop_order',
|
63 |
+
'status' => WC_ShipStation_Integration::$export_statuses,
|
64 |
+
'return' => 'ids',
|
65 |
+
'orderby' => 'date_modified',
|
66 |
+
'order' => 'DESC',
|
67 |
+
'paged' => $page,
|
68 |
+
'limit' => WC_SHIPSTATION_EXPORT_LIMIT,
|
69 |
+
) );
|
70 |
+
$order_ids = array_map( function( $order_or_id ) {
|
71 |
+
return is_a( $order_or_id, 'WC_Order' ) ? $order_or_id->get_id() : $order_or_id;
|
72 |
+
}, $order_ids );
|
73 |
+
} else {
|
74 |
+
$order_ids = $wpdb->get_col(
|
75 |
+
$wpdb->prepare( "
|
76 |
+
SELECT ID FROM {$wpdb->posts}
|
77 |
+
WHERE post_type = 'shop_order'
|
78 |
+
AND post_status IN ( '" . implode( "','", WC_ShipStation_Integration::$export_statuses ) . "' )
|
79 |
+
AND %s <= post_modified_gmt
|
80 |
+
AND post_modified_gmt <= %s
|
81 |
+
ORDER BY post_modified_gmt DESC
|
82 |
+
LIMIT %d, %d
|
83 |
+
",
|
84 |
+
$start_date,
|
85 |
+
$end_date,
|
86 |
+
WC_SHIPSTATION_EXPORT_LIMIT * ( $page - 1 ),
|
87 |
+
WC_SHIPSTATION_EXPORT_LIMIT
|
88 |
+
)
|
89 |
+
);
|
90 |
+
}
|
91 |
+
|
92 |
+
// Figure out how to retrieve this using WC Query class.
|
93 |
+
$max_results = $wpdb->get_var(
|
94 |
+
$wpdb->prepare( "
|
95 |
+
SELECT COUNT(ID) FROM {$wpdb->posts}
|
96 |
+
WHERE post_type = 'shop_order'
|
97 |
+
AND post_status IN ( '" . implode( "','", WC_ShipStation_Integration::$export_statuses ) . "' )
|
98 |
+
AND %s <= post_modified_gmt
|
99 |
+
AND post_modified_gmt <= %s
|
100 |
+
",
|
101 |
+
$start_date,
|
102 |
+
$end_date
|
103 |
+
)
|
104 |
+
);
|
105 |
+
|
106 |
+
$orders_xml = $xml->createElement( 'Orders' );
|
107 |
+
|
108 |
+
foreach ( $order_ids as $order_id ) {
|
109 |
+
if ( ! apply_filters( 'woocommerce_shipstation_export_order', true, $order_id ) ) {
|
110 |
+
continue;
|
111 |
+
}
|
112 |
+
|
113 |
+
$order = wc_get_order( $order_id );
|
114 |
+
$order_xml = $xml->createElement( 'Order' );
|
115 |
+
$wc_gte_30 = version_compare( WC_VERSION, '3.0', '>=' );// gte greater than or equal to 3.0
|
116 |
+
$formatted_order_number = ltrim( $order->get_order_number(), '#' );
|
117 |
+
$this->xml_append( $order_xml, 'OrderNumber', $formatted_order_number );
|
118 |
+
$this->xml_append( $order_xml, 'OrderID', $order_id );
|
119 |
+
|
120 |
+
if ( $wc_gte_30 ) {
|
121 |
+
// Sequence of date ordering: date paid > date completed > date created
|
122 |
+
$order_timestamp = $order->get_date_paid() ?: $order->get_date_completed() ?: $order->get_date_created();
|
123 |
+
$order_timestamp = $order_timestamp->getOffsetTimestamp();
|
124 |
+
} else {
|
125 |
+
$order_timestamp = $order->order_date;
|
126 |
+
}
|
127 |
+
|
128 |
+
$order_timestamp -= $tz_offset;
|
129 |
+
$this->xml_append( $order_xml, 'OrderDate', gmdate( 'm/d/Y H:i', $order_timestamp ), false );
|
130 |
+
$this->xml_append( $order_xml, 'OrderStatus', $order->get_status() );
|
131 |
+
$this->xml_append( $order_xml, 'PaymentMethod', $wc_gte_30 ? $order->get_payment_method() : $order->payment_method );
|
132 |
+
$this->xml_append( $order_xml, 'OrderPaymentMethodTitle', $wc_gte_30 ? $order->get_payment_method_title() : $order->payment_method_title );
|
133 |
+
$last_modified = strtotime( $wc_gte_30 ? $order->get_date_modified()->date( 'm/d/Y H:i' ) : $order->modified_date ) - $tz_offset;
|
134 |
+
$this->xml_append( $order_xml, 'LastModified', gmdate( 'm/d/Y H:i', $last_modified ), false );
|
135 |
+
$this->xml_append( $order_xml, 'ShippingMethod', implode( ' | ', $this->get_shipping_methods( $order ) ) );
|
136 |
+
|
137 |
+
$this->xml_append( $order_xml, 'OrderTotal', $order->get_total(), false );
|
138 |
+
$this->xml_append( $order_xml, 'TaxAmount', wc_round_tax_total( $order->get_total_tax() ), false );
|
139 |
+
|
140 |
+
if ( class_exists( 'WC_COG' ) ) {
|
141 |
+
$this->xml_append( $order_xml, 'CostOfGoods', wc_format_decimal( $order->wc_cog_order_total_cost ), false );
|
142 |
+
}
|
143 |
+
|
144 |
+
$this->xml_append( $order_xml, 'ShippingAmount', $wc_gte_30 ? $order->get_shipping_total() : $order->get_total_shipping(), false );
|
145 |
+
$this->xml_append( $order_xml, 'CustomerNotes', $wc_gte_30 ? $order->get_customer_note() : $order->customer_note );
|
146 |
+
$this->xml_append( $order_xml, 'InternalNotes', implode( ' | ', $this->get_order_notes( $order ) ) );
|
147 |
+
|
148 |
+
// Custom fields - 1 is used for coupon codes
|
149 |
+
$this->xml_append( $order_xml, 'CustomField1', implode( ' | ', $order->get_used_coupons() ) );
|
150 |
+
|
151 |
+
// Custom fields 2 and 3 can be mapped to a custom field via the following filters
|
152 |
+
$meta_key = apply_filters( 'woocommerce_shipstation_export_custom_field_2', '' );
|
153 |
+
if ( $meta_key ) {
|
154 |
+
$this->xml_append( $order_xml, 'CustomField2', apply_filters( 'woocommerce_shipstation_export_custom_field_2_value', get_post_meta( $order_id, $meta_key, true ), $order_id ) );
|
155 |
+
}
|
156 |
+
|
157 |
+
$meta_key = apply_filters( 'woocommerce_shipstation_export_custom_field_3', '' );
|
158 |
+
if ( $meta_key ) {
|
159 |
+
$this->xml_append( $order_xml, 'CustomField3', apply_filters( 'woocommerce_shipstation_export_custom_field_3_value', get_post_meta( $order_id, $meta_key, true ), $order_id ) );
|
160 |
+
}
|
161 |
+
|
162 |
+
// Customer data
|
163 |
+
$customer_xml = $xml->createElement( 'Customer' );
|
164 |
+
$this->xml_append( $customer_xml, 'CustomerCode', $wc_gte_30 ? $order->get_billing_email() : $order->billing_email );
|
165 |
+
|
166 |
+
$billto_xml = $xml->createElement( 'BillTo' );
|
167 |
+
$this->xml_append( $billto_xml, 'Name', ( $wc_gte_30 ? $order->get_billing_first_name() : $order->billing_first_name ) . ' ' . ( $wc_gte_30 ? $order->get_billing_last_name() : $order->billing_last_name ) );
|
168 |
+
$this->xml_append( $billto_xml, 'Company', $wc_gte_30 ? $order->get_billing_company() : $order->billing_company );
|
169 |
+
$this->xml_append( $billto_xml, 'Phone', $wc_gte_30 ? $order->get_billing_phone() : $order->billing_phone );
|
170 |
+
$this->xml_append( $billto_xml, 'Email', $wc_gte_30 ? $order->get_billing_email() : $order->billing_email );
|
171 |
+
$customer_xml->appendChild( $billto_xml );
|
172 |
+
|
173 |
+
$shipto_xml = $xml->createElement( 'ShipTo' );
|
174 |
+
|
175 |
+
$shipping_country = $wc_gte_30 ? $order->get_shipping_country() : $order->shipping_country;
|
176 |
+
if ( empty( $shipping_country ) ) {
|
177 |
+
$name = ( $wc_gte_30 ? $order->get_billing_first_name() : $order->billing_first_name ) . ' ' . ( $wc_gte_30 ? $order->get_billing_last_name() : $order->billing_last_name );
|
178 |
+
$this->xml_append( $shipto_xml, 'Name', $name );
|
179 |
+
$this->xml_append( $shipto_xml, 'Company', $wc_gte_30 ? $order->get_billing_company() : $order->billing_company );
|
180 |
+
$this->xml_append( $shipto_xml, 'Address1', $wc_gte_30 ? $order->get_billing_address_1() : $order->billing_address_1 );
|
181 |
+
$this->xml_append( $shipto_xml, 'Address2', $wc_gte_30 ? $order->get_billing_address_2() : $order->billing_address_2 );
|
182 |
+
$this->xml_append( $shipto_xml, 'City', $wc_gte_30 ? $order->get_billing_city() : $order->billing_city );
|
183 |
+
$this->xml_append( $shipto_xml, 'State', $wc_gte_30 ? $order->get_billing_state() : $order->billing_state );
|
184 |
+
$this->xml_append( $shipto_xml, 'PostalCode', $wc_gte_30 ? $order->get_billing_postcode() : $order->billing_postcode );
|
185 |
+
$this->xml_append( $shipto_xml, 'Country', $wc_gte_30 ? $order->get_billing_country() : $order->billing_country );
|
186 |
+
$this->xml_append( $shipto_xml, 'Phone', $wc_gte_30 ? $order->get_billing_phone() : $order->billing_phone );
|
187 |
+
} else {
|
188 |
+
$name = ( $wc_gte_30 ? $order->get_shipping_first_name() : $order->shipping_first_name ) . ' ' . ( $wc_gte_30 ? $order->get_shipping_last_name() : $order->shipping_last_name );
|
189 |
+
$this->xml_append( $shipto_xml, 'Name', $name );
|
190 |
+
$this->xml_append( $shipto_xml, 'Company', $wc_gte_30 ? $order->get_shipping_company() : $order->shipping_company );
|
191 |
+
$this->xml_append( $shipto_xml, 'Address1', $wc_gte_30 ? $order->get_shipping_address_1() : $order->shipping_address_1 );
|
192 |
+
$this->xml_append( $shipto_xml, 'Address2', $wc_gte_30 ? $order->get_shipping_address_2() : $order->shipping_address_2 );
|
193 |
+
$this->xml_append( $shipto_xml, 'City', $wc_gte_30 ? $order->get_shipping_city() : $order->shipping_city );
|
194 |
+
$this->xml_append( $shipto_xml, 'State', $wc_gte_30 ? $order->get_shipping_state() : $order->shipping_state );
|
195 |
+
$this->xml_append( $shipto_xml, 'PostalCode', $wc_gte_30 ? $order->get_shipping_postcode() : $order->shipping_postcode );
|
196 |
+
$this->xml_append( $shipto_xml, 'Country', $wc_gte_30 ? $order->get_shipping_country() : $order->shipping_country );
|
197 |
+
$this->xml_append( $shipto_xml, 'Phone', $wc_gte_30 ? $order->get_billing_phone() : $order->billing_phone );
|
198 |
+
}
|
199 |
+
$customer_xml->appendChild( $shipto_xml );
|
200 |
+
|
201 |
+
$order_xml->appendChild( $customer_xml );
|
202 |
+
|
203 |
+
// Item data
|
204 |
+
$found_item = false;
|
205 |
+
$items_xml = $xml->createElement( 'Items' );
|
206 |
+
// Merge arrays without loosing indexes.
|
207 |
+
$order_items = $order->get_items() + $order->get_items( 'fee' );
|
208 |
+
foreach ( $order_items as $item_id => $item ) {
|
209 |
+
if ( $wc_gte_30 ) {
|
210 |
+
$product = is_callable( array( $item, 'get_product' ) ) ? $item->get_product() : false;
|
211 |
+
} else {
|
212 |
+
$product = $order->get_product_from_item( $item );
|
213 |
+
}
|
214 |
+
$item_needs_no_shipping = ! $product || ! $product->needs_shipping();
|
215 |
+
$item_not_a_fee = 'fee' !== $item['type'];
|
216 |
+
if ( $item_needs_no_shipping && $item_not_a_fee ) {
|
217 |
+
continue;
|
218 |
+
}
|
219 |
+
|
220 |
+
$found_item = true;
|
221 |
+
$item_xml = $xml->createElement( 'Item' );
|
222 |
+
$this->xml_append( $item_xml, 'LineItemID', $item_id );
|
223 |
+
|
224 |
+
if ( 'fee' === $item['type'] ) {
|
225 |
+
$this->xml_append( $item_xml, 'Name', $item['name'] );
|
226 |
+
$this->xml_append( $item_xml, 'Quantity', 1, false );
|
227 |
+
$this->xml_append( $item_xml, 'UnitPrice', $order->get_item_total( $item, false, true ), false );
|
228 |
+
}
|
229 |
+
|
230 |
+
// handle product specific data
|
231 |
+
if ( $product && $product->needs_shipping() ) {
|
232 |
+
$this->xml_append( $item_xml, 'SKU', $product->get_sku() );
|
233 |
+
$this->xml_append( $item_xml, 'Name', $product->get_title() );
|
234 |
+
// image data
|
235 |
+
$image_id = $product->get_image_id();
|
236 |
+
$image_url = $image_id ? current( wp_get_attachment_image_src( $image_id, 'shop_thumbnail' ) ) : '';
|
237 |
+
$this->xml_append( $item_xml, 'ImageUrl', $image_url );
|
238 |
+
|
239 |
+
$this->xml_append( $item_xml, 'Weight', wc_get_weight( $product->get_weight(), 'oz' ), false );
|
240 |
+
$this->xml_append( $item_xml, 'WeightUnits', 'Ounces', false );
|
241 |
+
$this->xml_append( $item_xml, 'Quantity', $item['qty'], false );
|
242 |
+
$this->xml_append( $item_xml, 'UnitPrice', $order->get_item_subtotal( $item, false, true ), false );
|
243 |
+
}
|
244 |
+
|
245 |
+
if ( $item['item_meta'] ) {
|
246 |
+
if ( version_compare( WC_VERSION, '3.0.0', '<' ) ) {
|
247 |
+
$item_meta = new WC_Order_Item_Meta( $item, $product );
|
248 |
+
$formatted_meta = $item_meta->get_formatted( '_' );
|
249 |
+
} else {
|
250 |
+
add_filter( 'woocommerce_is_attribute_in_product_name', '__return_false' );
|
251 |
+
$formatted_meta = $item->get_formatted_meta_data();
|
252 |
+
}
|
253 |
+
|
254 |
+
if ( ! empty( $formatted_meta ) ) {
|
255 |
+
$options_xml = $xml->createElement( 'Options' );
|
256 |
+
|
257 |
+
foreach ( $formatted_meta as $meta_key => $meta ) {
|
258 |
+
$option_xml = $xml->createElement( 'Option' );
|
259 |
+
|
260 |
+
if ( version_compare( WC_VERSION, '3.0.0', '<' ) ) {
|
261 |
+
$this->xml_append( $option_xml, 'Name', $meta['label'] );
|
262 |
+
$this->xml_append( $option_xml, 'Value', $meta['value'] );
|
263 |
+
} else {
|
264 |
+
$this->xml_append( $option_xml, 'Name', $meta->display_key );
|
265 |
+
$this->xml_append( $option_xml, 'Value', wp_strip_all_tags( $meta->display_value ) );
|
266 |
+
}
|
267 |
+
|
268 |
+
$options_xml->appendChild( $option_xml );
|
269 |
+
}
|
270 |
+
|
271 |
+
$item_xml->appendChild( $options_xml );
|
272 |
+
}
|
273 |
+
}
|
274 |
+
|
275 |
+
$items_xml->appendChild( $item_xml );
|
276 |
+
}
|
277 |
+
|
278 |
+
if ( ! $found_item ) {
|
279 |
+
continue;
|
280 |
+
}
|
281 |
+
|
282 |
+
// Append cart level discount line
|
283 |
+
if ( $order->get_total_discount() ) {
|
284 |
+
$item_xml = $xml->createElement( 'Item' );
|
285 |
+
$this->xml_append( $item_xml, 'SKU', 'total-discount' );
|
286 |
+
$this->xml_append( $item_xml, 'Name', __( 'Total Discount', 'woocommerce-shipstation' ) );
|
287 |
+
$this->xml_append( $item_xml, 'Adjustment', 'true', false );
|
288 |
+
$this->xml_append( $item_xml, 'Quantity', 1, false );
|
289 |
+
$this->xml_append( $item_xml, 'UnitPrice', $order->get_total_discount() * -1, false );
|
290 |
+
$items_xml->appendChild( $item_xml );
|
291 |
+
}
|
292 |
+
|
293 |
+
// Append items XML
|
294 |
+
$order_xml->appendChild( $items_xml );
|
295 |
+
$orders_xml->appendChild( $order_xml );
|
296 |
+
|
297 |
+
$exported ++;
|
298 |
+
|
299 |
+
// Add order note to indicate it has been exported to Shipstation.
|
300 |
+
if ( 'yes' !== get_post_meta( $order_id, '_shipstation_exported', true ) ) {
|
301 |
+
$order->add_order_note( __( 'Order has been exported to Shipstation', 'woocommerce-shipstation' ) );
|
302 |
+
update_post_meta( $order_id, '_shipstation_exported', 'yes' );
|
303 |
+
}
|
304 |
+
}
|
305 |
+
|
306 |
+
$orders_xml->setAttribute( 'page', $page );
|
307 |
+
$orders_xml->setAttribute( 'pages', ceil( $max_results / WC_SHIPSTATION_EXPORT_LIMIT ) );
|
308 |
+
$xml->appendChild( $orders_xml );
|
309 |
+
echo $xml->saveXML();
|
310 |
+
|
311 |
+
/* translators: 1: total count */
|
312 |
+
$this->log( sprintf( __( 'Exported %s orders', 'woocommerce-shipstation' ), $exported ) );
|
313 |
+
}
|
314 |
+
|
315 |
+
/**
|
316 |
+
* Get shipping method names
|
317 |
+
* @param WC_Order $order
|
318 |
+
* @return array
|
319 |
+
*/
|
320 |
+
private function get_shipping_methods( $order ) {
|
321 |
+
$shipping_methods = $order->get_shipping_methods();
|
322 |
+
$shipping_method_names = array();
|
323 |
+
|
324 |
+
foreach ( $shipping_methods as $shipping_method ) {
|
325 |
+
// Replace non-AlNum characters with space
|
326 |
+
$method_name = preg_replace( '/[^A-Za-z0-9 \-\.\_,]/', '', $shipping_method['name'] );
|
327 |
+
$shipping_method_names[] = $method_name;
|
328 |
+
}
|
329 |
+
|
330 |
+
return $shipping_method_names;
|
331 |
+
}
|
332 |
+
|
333 |
+
/**
|
334 |
+
* Get Order Notes
|
335 |
+
* @param WC_Order $order
|
336 |
+
* @return array
|
337 |
+
*/
|
338 |
+
private function get_order_notes( $order ) {
|
339 |
+
$args = array(
|
340 |
+
'post_id' => version_compare( WC_VERSION, '3.0.0', '>=' ) ? $order->get_id() : $order->id,
|
341 |
+
'approve' => 'approve',
|
342 |
+
'type' => 'order_note',
|
343 |
+
);
|
344 |
+
|
345 |
+
remove_filter( 'comments_clauses', array( 'WC_Comments', 'exclude_order_comments' ), 10, 1 );
|
346 |
+
|
347 |
+
$notes = get_comments( $args );
|
348 |
+
|
349 |
+
add_filter( 'comments_clauses', array( 'WC_Comments', 'exclude_order_comments' ), 10, 1 );
|
350 |
+
|
351 |
+
$order_notes = array();
|
352 |
+
|
353 |
+
foreach ( $notes as $note ) {
|
354 |
+
if ( 'WooCommerce' !== $note->comment_author ) {
|
355 |
+
$order_notes[] = $note->comment_content;
|
356 |
+
}
|
357 |
+
}
|
358 |
+
|
359 |
+
return $order_notes;
|
360 |
+
}
|
361 |
+
|
362 |
+
/**
|
363 |
+
* Append XML as cdata
|
364 |
+
*/
|
365 |
+
private function xml_append( $append_to, $name, $value, $cdata = true ) {
|
366 |
+
$data = $append_to->appendChild( $append_to->ownerDocument->createElement( $name ) );
|
367 |
+
if ( $cdata ) {
|
368 |
+
$data->appendChild( $append_to->ownerDocument->createCDATASection( $value ) );
|
369 |
+
} else {
|
370 |
+
$data->appendChild( $append_to->ownerDocument->createTextNode( $value ) );
|
371 |
+
}
|
372 |
+
}
|
373 |
+
}
|
374 |
+
|
375 |
+
return new WC_Shipstation_API_Export();
|
includes/api-requests/class-wc-shipstation-api-request.php
ADDED
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
if ( ! defined( 'ABSPATH' ) ) {
|
4 |
+
exit; // Exit if accessed directly
|
5 |
+
}
|
6 |
+
|
7 |
+
/**
|
8 |
+
* WC_Shipstation_API_Request Class
|
9 |
+
*/
|
10 |
+
abstract class WC_Shipstation_API_Request {
|
11 |
+
|
12 |
+
/**
|
13 |
+
* Stores logger class
|
14 |
+
* @var WC_Logger
|
15 |
+
*/
|
16 |
+
private $log = null;
|
17 |
+
|
18 |
+
/**
|
19 |
+
* Log something
|
20 |
+
* @param string $message
|
21 |
+
*/
|
22 |
+
public function log( $message ) {
|
23 |
+
if ( 'no' === WC_ShipStation_Integration::$logging_enabled ) {
|
24 |
+
return;
|
25 |
+
}
|
26 |
+
if ( is_null( $this->log ) ) {
|
27 |
+
$this->log = new WC_Logger();
|
28 |
+
}
|
29 |
+
$this->log->add( 'shipstation', $message );
|
30 |
+
}
|
31 |
+
|
32 |
+
/**
|
33 |
+
* Run the request
|
34 |
+
*/
|
35 |
+
public function request() {}
|
36 |
+
|
37 |
+
/**
|
38 |
+
* Validate data
|
39 |
+
* @param array $required_fields fields to look for
|
40 |
+
*/
|
41 |
+
function validate_input( $required_fields ) {
|
42 |
+
foreach ( $required_fields as $required ) {
|
43 |
+
if ( empty( $_GET[ $required ] ) ) {
|
44 |
+
/* translators: 1: field name */
|
45 |
+
$this->trigger_error( sprintf( __( 'Missing required param: %s', 'woocommerce-shipstation' ), $required ) );
|
46 |
+
}
|
47 |
+
}
|
48 |
+
}
|
49 |
+
|
50 |
+
/**
|
51 |
+
* Trigger and log an error
|
52 |
+
* @param string $message
|
53 |
+
*/
|
54 |
+
public function trigger_error( $message ) {
|
55 |
+
$this->log( $message );
|
56 |
+
wp_send_json_error( $message );
|
57 |
+
}
|
58 |
+
}
|
59 |
+
|
includes/api-requests/class-wc-shipstation-api-shipnotify.php
ADDED
@@ -0,0 +1,351 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
if ( ! defined( 'ABSPATH' ) ) {
|
4 |
+
exit; // Exit if accessed directly
|
5 |
+
}
|
6 |
+
|
7 |
+
/**
|
8 |
+
* WC_Shipstation_API_Shipnotify Class
|
9 |
+
*/
|
10 |
+
class WC_Shipstation_API_Shipnotify extends WC_Shipstation_API_Request {
|
11 |
+
|
12 |
+
/**
|
13 |
+
* Constructor.
|
14 |
+
*/
|
15 |
+
public function __construct() {
|
16 |
+
if ( ! WC_Shipstation_API::authenticated() ) {
|
17 |
+
exit;
|
18 |
+
}
|
19 |
+
}
|
20 |
+
|
21 |
+
/**
|
22 |
+
* See how many items in the order need shipping.
|
23 |
+
*
|
24 |
+
* @param WC_Order $order Order object.
|
25 |
+
*
|
26 |
+
* @return int
|
27 |
+
*/
|
28 |
+
private function order_items_to_ship_count( $order ) {
|
29 |
+
$needs_shipping = 0;
|
30 |
+
|
31 |
+
foreach ( $order->get_items() as $item_id => $item ) {
|
32 |
+
$product = $order->get_product_from_item( $item );
|
33 |
+
|
34 |
+
if ( is_a( $product, 'WC_Product' ) && $product->needs_shipping() ) {
|
35 |
+
$needs_shipping += $item['qty'];
|
36 |
+
}
|
37 |
+
}
|
38 |
+
|
39 |
+
return $needs_shipping;
|
40 |
+
}
|
41 |
+
|
42 |
+
/**
|
43 |
+
* Check whether a given item ID is shippable item.
|
44 |
+
*
|
45 |
+
* @since 4.1.16
|
46 |
+
* @version 4.1.16
|
47 |
+
*
|
48 |
+
* @param WC_Order $order Order object.
|
49 |
+
* @param int $item_id Item ID.
|
50 |
+
*
|
51 |
+
* @return bool Returns true if item is shippable product.
|
52 |
+
*/
|
53 |
+
private function is_shippable_item( $order, $item_id ) {
|
54 |
+
if ( version_compare( WC_VERSION, '3.0', '>=' ) ) {
|
55 |
+
$item = $order->get_item( $item_id );
|
56 |
+
if ( ! is_callable( array( $item, 'get_product' ) ) ) {
|
57 |
+
return false;
|
58 |
+
}
|
59 |
+
|
60 |
+
$product = $item->get_product();
|
61 |
+
} else {
|
62 |
+
$items = $order->get_items();
|
63 |
+
if ( ! isset( $items[ $item_id ] ) ) {
|
64 |
+
return false;
|
65 |
+
}
|
66 |
+
|
67 |
+
$product = $order->get_product_from_item( $items[ $item_id ] );
|
68 |
+
}
|
69 |
+
|
70 |
+
if ( ! $product ) {
|
71 |
+
return false;
|
72 |
+
}
|
73 |
+
|
74 |
+
return $product->needs_shipping();
|
75 |
+
}
|
76 |
+
|
77 |
+
/**
|
78 |
+
* Get the order ID from the order number.
|
79 |
+
*
|
80 |
+
* @param string $order_number Order number.
|
81 |
+
* @return integer
|
82 |
+
*/
|
83 |
+
private function get_order_id( $order_number ) {
|
84 |
+
// Try to match an order number in brackets.
|
85 |
+
preg_match( '/\((.*?)\)/', $order_number, $matches );
|
86 |
+
if ( is_array( $matches ) && isset( $matches[1] ) ) {
|
87 |
+
$order_id = $matches[1];
|
88 |
+
|
89 |
+
} elseif ( function_exists( 'wc_sequential_order_numbers' ) ) {
|
90 |
+
// Try to convert number for Sequential Order Number.
|
91 |
+
$order_id = wc_sequential_order_numbers()->find_order_by_order_number( $order_number );
|
92 |
+
|
93 |
+
} elseif ( function_exists( 'wc_seq_order_number_pro' ) ) {
|
94 |
+
// Try to convert number for Sequential Order Number Pro.
|
95 |
+
$order_id = wc_seq_order_number_pro()->find_order_by_order_number( $order_number );
|
96 |
+
|
97 |
+
} else {
|
98 |
+
// Default to not converting order number.
|
99 |
+
$order_id = $order_number;
|
100 |
+
}
|
101 |
+
|
102 |
+
if ( 0 === $order_id ) {
|
103 |
+
$order_id = $order_number;
|
104 |
+
}
|
105 |
+
|
106 |
+
return apply_filters( 'woocommerce_shipstation_get_order_id', absint( $order_id ) );
|
107 |
+
}
|
108 |
+
|
109 |
+
/**
|
110 |
+
* Retrieves the raw request data (body).
|
111 |
+
*
|
112 |
+
* `$HTTP_RAW_POST_DATA` is deprecated in PHP 5.6 and removed in PHP 5.7,
|
113 |
+
* it's used here for server that has issue with reading `php://input`
|
114 |
+
* stream.
|
115 |
+
*
|
116 |
+
* @since 4.1.17
|
117 |
+
* @version 4.1.17
|
118 |
+
*
|
119 |
+
* @return string Raw request data.
|
120 |
+
*/
|
121 |
+
private function get_raw_post_data() {
|
122 |
+
global $HTTP_RAW_POST_DATA;
|
123 |
+
|
124 |
+
if ( ! isset( $HTTP_RAW_POST_DATA ) ) {
|
125 |
+
$HTTP_RAW_POST_DATA = file_get_contents( 'php://input' );
|
126 |
+
}
|
127 |
+
|
128 |
+
return $HTTP_RAW_POST_DATA;
|
129 |
+
}
|
130 |
+
|
131 |
+
/**
|
132 |
+
* Get Parsed XML response.
|
133 |
+
*
|
134 |
+
* @param string $xml XML.
|
135 |
+
* @return string|bool
|
136 |
+
*/
|
137 |
+
private function get_parsed_xml( $xml ) {
|
138 |
+
if ( ! class_exists( 'WC_Safe_DOMDocument' ) ) {
|
139 |
+
include_once( 'class-wc-safe-domdocument.php' );
|
140 |
+
}
|
141 |
+
|
142 |
+
libxml_use_internal_errors( true );
|
143 |
+
|
144 |
+
$dom = new WC_Safe_DOMDocument;
|
145 |
+
$success = $dom->loadXML( $xml );
|
146 |
+
|
147 |
+
if ( ! $success ) {
|
148 |
+
$this->log( 'wpcom_safe_simplexml_load_string(): Error loading XML string' );
|
149 |
+
return false;
|
150 |
+
}
|
151 |
+
|
152 |
+
if ( isset( $dom->doctype ) ) {
|
153 |
+
$this->log( 'wpcom_safe_simplexml_import_dom(): Unsafe DOCTYPE Detected' );
|
154 |
+
return false;
|
155 |
+
}
|
156 |
+
|
157 |
+
return simplexml_import_dom( $dom, 'SimpleXMLElement' );
|
158 |
+
}
|
159 |
+
|
160 |
+
/**
|
161 |
+
* Handling the request.
|
162 |
+
*
|
163 |
+
* @since 1.0.0
|
164 |
+
* @version 4.1.18
|
165 |
+
*/
|
166 |
+
public function request() {
|
167 |
+
|
168 |
+
$this->validate_input( array( 'order_number', 'carrier' ) );
|
169 |
+
|
170 |
+
$timestamp = current_time( 'timestamp' );
|
171 |
+
$shipstation_xml = $this->get_raw_post_data();
|
172 |
+
$shipped_items = array();
|
173 |
+
$shipped_item_count = 0;
|
174 |
+
$order_shipped = false;
|
175 |
+
$xml_order_id = 0;
|
176 |
+
|
177 |
+
$can_parse_xml = true;
|
178 |
+
|
179 |
+
if ( empty( $shipstation_xml ) ) {
|
180 |
+
$can_parse_xml = false;
|
181 |
+
$this->log( __( 'Missing ShipNotify XML input.', 'woocommerce-shipstation' ) );
|
182 |
+
|
183 |
+
// For unknown reason raw post data can be empty. Log all requests
|
184 |
+
// information might help figuring out the culprit.
|
185 |
+
//
|
186 |
+
// @see https://github.com/woocommerce/woocommerce-shipstation/issues/80.
|
187 |
+
$this->log( '$_REQUEST: ' . print_r( $_REQUEST, true ) );
|
188 |
+
}
|
189 |
+
|
190 |
+
if ( ! function_exists( 'simplexml_import_dom' ) ) {
|
191 |
+
$can_parse_xml = false;
|
192 |
+
$this->log( __( 'Missing SimpleXML extension for parsing ShipStation XML.', 'woocommerce-shipstation' ) );
|
193 |
+
}
|
194 |
+
|
195 |
+
// Try to parse XML first since it can contain the real OrderID.
|
196 |
+
if ( $can_parse_xml ) {
|
197 |
+
$this->log( __( 'ShipNotify XML: ', 'woocommerce-shipstation' ) . print_r( $shipstation_xml, true ) );
|
198 |
+
|
199 |
+
$xml = $this->get_parsed_xml( $shipstation_xml );
|
200 |
+
|
201 |
+
if ( ! $xml ) {
|
202 |
+
$this->log( __( 'Cannot parse XML', 'woocommerce-shipstation' ) );
|
203 |
+
status_header( 500 );
|
204 |
+
}
|
205 |
+
|
206 |
+
if ( isset( $xml->ShipDate ) ) {
|
207 |
+
$timestamp = strtotime( (string) $xml->ShipDate );
|
208 |
+
}
|
209 |
+
|
210 |
+
if ( isset( $xml->OrderID ) && $_GET['order_number'] !== (string) $xml->OrderID ) {
|
211 |
+
$xml_order_id = (int) $xml->OrderID;
|
212 |
+
}
|
213 |
+
}
|
214 |
+
|
215 |
+
// Get real order ID from XML otherwise try to convert it from the order number.
|
216 |
+
$order_id = ! $xml_order_id ? $this->get_order_id( wc_clean( $_GET['order_number'] ) ) : $xml_order_id;
|
217 |
+
$tracking_number = empty( $_GET['tracking_number'] ) ? '' : wc_clean( $_GET['tracking_number'] );
|
218 |
+
$carrier = empty( $_GET['carrier'] ) ? '' : wc_clean( $_GET['carrier'] );
|
219 |
+
$order = wc_get_order( $order_id );
|
220 |
+
|
221 |
+
if ( false === $order || ! is_object( $order ) ) {
|
222 |
+
/* translators: 1: order id */
|
223 |
+
$this->log( sprintf( __( 'Order %s can not be found.', 'woocommerce-shipstation' ), $order_id ) );
|
224 |
+
exit;
|
225 |
+
}
|
226 |
+
|
227 |
+
// Get real order ID from order object.
|
228 |
+
$order_id = version_compare( WC_VERSION, '3.0.0', '<' ) ? $order->id : $order->get_id();
|
229 |
+
if ( empty( $order_id ) ) {
|
230 |
+
/* translators: 1: order id */
|
231 |
+
$this->log( sprintf( __( 'Invalid order ID: %s', 'woocommerce-shipstation' ), $order_id ) );
|
232 |
+
exit;
|
233 |
+
}
|
234 |
+
|
235 |
+
// Maybe parse items from posted XML (if exists).
|
236 |
+
if ( $can_parse_xml && isset( $xml->Items ) ) {
|
237 |
+
$items = $xml->Items;
|
238 |
+
if ( $items ) {
|
239 |
+
foreach ( $items->Item as $item ) {
|
240 |
+
$this->log( __( 'ShipNotify Item: ', 'woocommerce-shipstation' ) . print_r( $item, true ) );
|
241 |
+
|
242 |
+
$item_sku = wc_clean( (string) $item->SKU );
|
243 |
+
$item_name = wc_clean( (string) $item->Name );
|
244 |
+
$qty_shipped = absint( $item->Quantity );
|
245 |
+
|
246 |
+
if ( $item_sku ) {
|
247 |
+
$item_sku = ' (' . $item_sku . ')';
|
248 |
+
}
|
249 |
+
|
250 |
+
$item_id = wc_clean( (int) $item->LineItemID );
|
251 |
+
if ( ! $this->is_shippable_item( $order, $item_id ) ) {
|
252 |
+
/* translators: 1: item name */
|
253 |
+
$this->log( sprintf( __( 'Item %s is not shippable product. Skipping.', 'woocommerce-shipstation' ), $item_name ) );
|
254 |
+
continue;
|
255 |
+
}
|
256 |
+
|
257 |
+
$shipped_item_count += $qty_shipped;
|
258 |
+
$shipped_items[] = $item_name . $item_sku . ' x ' . $qty_shipped;
|
259 |
+
}
|
260 |
+
}
|
261 |
+
}
|
262 |
+
|
263 |
+
// Number of items in WC order.
|
264 |
+
$total_item_count = $this->order_items_to_ship_count( $order );
|
265 |
+
|
266 |
+
// If we have a list of shipped items, we can customise the note + see
|
267 |
+
// if the order is not yet complete.
|
268 |
+
if ( sizeof( $shipped_items ) > 0 ) {
|
269 |
+
$order_note = sprintf(
|
270 |
+
/* translators: 1) shipped items 2) carrier's name 3) shipped date, 4) tracking number */
|
271 |
+
__( '%1$s shipped via %2$s on %3$s with tracking number %4$s.', 'woocommerce-shipstation' ),
|
272 |
+
esc_html( implode( ', ', $shipped_items ) ),
|
273 |
+
esc_html( $carrier ),
|
274 |
+
date_i18n( get_option( 'date_format' ), $timestamp ),
|
275 |
+
$tracking_number
|
276 |
+
);
|
277 |
+
|
278 |
+
$current_shipped_items = max( (int) get_post_meta( $order_id, '_shipstation_shipped_item_count', true ), 0 );
|
279 |
+
|
280 |
+
if ( ( $current_shipped_items + $shipped_item_count ) >= $total_item_count ) {
|
281 |
+
$order_shipped = true;
|
282 |
+
}
|
283 |
+
|
284 |
+
$this->log(
|
285 |
+
sprintf(
|
286 |
+
/* translators: 1) number of shipped items 2) total shipped items 3) order ID */
|
287 |
+
__( 'Shipped %1$d out of %2$d items in order %3$s', 'woocommerce-shipstation' ),
|
288 |
+
$shipped_item_count,
|
289 |
+
$total_item_count,
|
290 |
+
$order_id
|
291 |
+
)
|
292 |
+
);
|
293 |
+
|
294 |
+
update_post_meta( $order_id, '_shipstation_shipped_item_count', $current_shipped_items + $shipped_item_count );
|
295 |
+
|
296 |
+
} else {
|
297 |
+
// If we don't have items from SS and order items in WC, or cannot parse
|
298 |
+
// the XML, just complete the order as a whole.
|
299 |
+
$order_shipped = 0 === $total_item_count || ! $can_parse_xml;
|
300 |
+
|
301 |
+
$order_note = sprintf(
|
302 |
+
/* translators: 1) carrier's name 2) shipped date, 3) tracking number */
|
303 |
+
__( 'Items shipped via %1$s on %2$s with tracking number %3$s (Shipstation).', 'woocommerce-shipstation' ),
|
304 |
+
esc_html( $carrier ),
|
305 |
+
date_i18n( get_option( 'date_format' ), $timestamp ),
|
306 |
+
$tracking_number
|
307 |
+
);
|
308 |
+
|
309 |
+
/* translators: 1: order id */
|
310 |
+
$this->log( sprintf( __( 'No items found - shipping entire order %d.', 'woocommerce-shipstation' ), $order_id ) );
|
311 |
+
}
|
312 |
+
|
313 |
+
// Tracking information - WC Shipment Tracking extension.
|
314 |
+
if ( class_exists( 'WC_Shipment_Tracking' ) ) {
|
315 |
+
if ( function_exists( 'wc_st_add_tracking_number' ) ) {
|
316 |
+
wc_st_add_tracking_number( $order_id, $tracking_number, strtolower( $carrier ), $timestamp );
|
317 |
+
} else {
|
318 |
+
// You're using Shipment Tracking < 1.4.0. Please update!
|
319 |
+
update_post_meta( $order_id, '_tracking_provider', strtolower( $carrier ) );
|
320 |
+
update_post_meta( $order_id, '_tracking_number', $tracking_number );
|
321 |
+
update_post_meta( $order_id, '_date_shipped', $timestamp );
|
322 |
+
}
|
323 |
+
|
324 |
+
$is_customer_note = 0;
|
325 |
+
} else {
|
326 |
+
$is_customer_note = 1;
|
327 |
+
}
|
328 |
+
|
329 |
+
$order->add_order_note( $order_note, $is_customer_note );
|
330 |
+
|
331 |
+
// Update order status.
|
332 |
+
if ( $order_shipped ) {
|
333 |
+
$order->update_status( WC_ShipStation_Integration::$shipped_status );
|
334 |
+
|
335 |
+
/* translators: 1) order ID 2) shipment status */
|
336 |
+
$this->log( sprintf( __( 'Updated order %1$s to status %2$s', 'woocommerce-shipstation' ), $order_id, WC_ShipStation_Integration::$shipped_status ) );
|
337 |
+
}
|
338 |
+
|
339 |
+
// Trigger action for other integrations.
|
340 |
+
do_action( 'woocommerce_shipstation_shipnotify', $order, array(
|
341 |
+
'tracking_number' => $tracking_number,
|
342 |
+
'carrier' => $carrier,
|
343 |
+
'ship_date' => $timestamp,
|
344 |
+
'xml' => $shipstation_xml,
|
345 |
+
) );
|
346 |
+
|
347 |
+
status_header( 200 );
|
348 |
+
}
|
349 |
+
}
|
350 |
+
|
351 |
+
return new WC_Shipstation_API_Shipnotify();
|
includes/class-wc-shipstation-api.php
ADDED
@@ -0,0 +1,84 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
if ( ! defined( 'ABSPATH' ) ) {
|
4 |
+
exit; // Exit if accessed directly
|
5 |
+
}
|
6 |
+
|
7 |
+
include_once( 'api-requests/class-wc-shipstation-api-request.php' );
|
8 |
+
|
9 |
+
/**
|
10 |
+
* WC_Shipstation_API Class
|
11 |
+
*/
|
12 |
+
class WC_Shipstation_API extends WC_Shipstation_API_Request {
|
13 |
+
|
14 |
+
/** @var boolean Stores whether or not shipstation has been authenticated */
|
15 |
+
private static $authenticated = false;
|
16 |
+
|
17 |
+
/**
|
18 |
+
* Constructor
|
19 |
+
*/
|
20 |
+
public function __construct() {
|
21 |
+
nocache_headers();
|
22 |
+
|
23 |
+
if ( ! defined( 'DONOTCACHEPAGE' ) ) {
|
24 |
+
define( 'DONOTCACHEPAGE', 'true' );
|
25 |
+
}
|
26 |
+
|
27 |
+
if ( ! defined( 'DONOTCACHEOBJECT' ) ) {
|
28 |
+
define( 'DONOTCACHEOBJECT', 'true' );
|
29 |
+
}
|
30 |
+
|
31 |
+
if ( ! defined( 'DONOTCACHEDB' ) ) {
|
32 |
+
define( 'DONOTCACHEDB', 'true' );
|
33 |
+
}
|
34 |
+
|
35 |
+
self::$authenticated = false;
|
36 |
+
|
37 |
+
$this->request();
|
38 |
+
}
|
39 |
+
|
40 |
+
/**
|
41 |
+
* Has API been authenticated?
|
42 |
+
* @return bool
|
43 |
+
*/
|
44 |
+
public static function authenticated() {
|
45 |
+
return self::$authenticated;
|
46 |
+
}
|
47 |
+
|
48 |
+
/**
|
49 |
+
* Handle the request
|
50 |
+
*/
|
51 |
+
public function request() {
|
52 |
+
if ( empty( $_GET['auth_key'] ) ) {
|
53 |
+
$this->trigger_error( __( 'Authentication key is required!', 'woocommerce-shipstation' ) );
|
54 |
+
}
|
55 |
+
|
56 |
+
if ( ! hash_equals( sanitize_text_field( $_GET['auth_key'] ), WC_ShipStation_Integration::$auth_key ) ) {
|
57 |
+
$this->trigger_error( __( 'Invalid authentication key', 'woocommerce-shipstation' ) );
|
58 |
+
}
|
59 |
+
|
60 |
+
$request = $_GET;
|
61 |
+
|
62 |
+
if ( isset( $request['action'] ) ) {
|
63 |
+
$this->request = array_map( 'sanitize_text_field', $request );
|
64 |
+
} else {
|
65 |
+
$this->trigger_error( __( 'Invalid request', 'woocommerce-shipstation' ) );
|
66 |
+
}
|
67 |
+
|
68 |
+
self::$authenticated = true;
|
69 |
+
|
70 |
+
if ( in_array( $this->request['action'], array( 'export', 'shipnotify' ) ) ) {
|
71 |
+
/* translators: 1: query string */
|
72 |
+
$this->log( sprintf( __( 'Input params: %s', 'woocommerce-shipstation' ), http_build_query( $this->request ) ) );
|
73 |
+
$request_class = include( 'api-requests/class-wc-shipstation-api-' . $this->request['action'] . '.php' );
|
74 |
+
$request_class->request();
|
75 |
+
} else {
|
76 |
+
$this->trigger_error( __( 'Invalid request', 'woocommerce-shipstation' ) );
|
77 |
+
}
|
78 |
+
|
79 |
+
exit;
|
80 |
+
}
|
81 |
+
}
|
82 |
+
|
83 |
+
new WC_Shipstation_API();
|
84 |
+
|
includes/class-wc-shipstation-integration.php
ADDED
@@ -0,0 +1,166 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
if ( ! defined( 'ABSPATH' ) ) {
|
4 |
+
exit; // Exit if accessed directly
|
5 |
+
}
|
6 |
+
|
7 |
+
/**
|
8 |
+
* WC_ShipStation_Integration Class
|
9 |
+
*/
|
10 |
+
class WC_ShipStation_Integration extends WC_Integration {
|
11 |
+
|
12 |
+
public static $auth_key = null;
|
13 |
+
public static $export_statuses = array();
|
14 |
+
public static $logging_enabled = true;
|
15 |
+
public static $shipped_status = null;
|
16 |
+
|
17 |
+
/**
|
18 |
+
* Constructor
|
19 |
+
*/
|
20 |
+
public function __construct() {
|
21 |
+
$this->id = 'shipstation';
|
22 |
+
$this->method_title = __( 'ShipStation', 'woocommerce-shipstation' );
|
23 |
+
$this->method_description = __( 'ShipStation allows you to retrieve & manage orders, then print labels & packing slips with ease.', 'woocommerce-shipstation' );
|
24 |
+
|
25 |
+
if ( ! get_option( 'woocommerce_shipstation_auth_key', false ) ) {
|
26 |
+
update_option( 'woocommerce_shipstation_auth_key', $this->generate_key() );
|
27 |
+
}
|
28 |
+
|
29 |
+
// Load admin form
|
30 |
+
$this->init_form_fields();
|
31 |
+
|
32 |
+
// Load settings
|
33 |
+
$this->init_settings();
|
34 |
+
|
35 |
+
self::$auth_key = get_option( 'woocommerce_shipstation_auth_key', false );
|
36 |
+
self::$export_statuses = $this->get_option( 'export_statuses', array( 'wc-processing', 'wc-on-hold', 'wc-completed', 'wc-cancelled' ) );
|
37 |
+
self::$logging_enabled = 'yes' === $this->get_option( 'logging_enabled', 'yes' );
|
38 |
+
self::$shipped_status = $this->get_option( 'shipped_status', 'wc-completed' );
|
39 |
+
|
40 |
+
// Force saved value
|
41 |
+
$this->settings['auth_key'] = self::$auth_key;
|
42 |
+
|
43 |
+
// Hooks
|
44 |
+
add_action( 'woocommerce_update_options_integration_shipstation', array( $this, 'process_admin_options' ) );
|
45 |
+
add_filter( 'woocommerce_subscriptions_renewal_order_meta_query', array( $this, 'subscriptions_renewal_order_meta_query' ), 10, 4 );
|
46 |
+
|
47 |
+
$settings_notice_dismissed = get_user_meta( get_current_user_id(), 'dismissed_shipstation-setup_notice' );
|
48 |
+
|
49 |
+
if ( ! $settings_notice_dismissed ) {
|
50 |
+
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
|
51 |
+
add_action( 'admin_notices', array( $this, 'settings_notice' ) );
|
52 |
+
}
|
53 |
+
}
|
54 |
+
|
55 |
+
/**
|
56 |
+
* Enqueue admin scripts/styles
|
57 |
+
*/
|
58 |
+
public function enqueue_scripts() {
|
59 |
+
wp_enqueue_style( 'shipstation-admin', plugins_url( 'assets/css/admin.css', dirname( __FILE__ ) ) );
|
60 |
+
}
|
61 |
+
|
62 |
+
/**
|
63 |
+
* Generate a key
|
64 |
+
* @return string
|
65 |
+
*/
|
66 |
+
public function generate_key() {
|
67 |
+
$to_hash = get_current_user_id() . date( 'U' ) . mt_rand();
|
68 |
+
return 'WCSS-' . hash_hmac( 'md5', $to_hash, wp_hash( $to_hash ) );
|
69 |
+
}
|
70 |
+
|
71 |
+
/**
|
72 |
+
* Init integration form fields
|
73 |
+
*/
|
74 |
+
public function init_form_fields() {
|
75 |
+
$this->form_fields = include( 'data/data-settings.php' );
|
76 |
+
}
|
77 |
+
|
78 |
+
/**
|
79 |
+
* Prevents WooCommerce Subscriptions from copying across certain meta keys to renewal orders.
|
80 |
+
* @param array $order_meta_query
|
81 |
+
* @param int $original_order_id
|
82 |
+
* @param int $renewal_order_id
|
83 |
+
* @param string $new_order_role
|
84 |
+
* @return array
|
85 |
+
*/
|
86 |
+
public function subscriptions_renewal_order_meta_query( $order_meta_query, $original_order_id, $renewal_order_id, $new_order_role ) {
|
87 |
+
if ( 'parent' == $new_order_role ) {
|
88 |
+
$order_meta_query .= ' AND `meta_key` NOT IN ('
|
89 |
+
. "'_tracking_provider', "
|
90 |
+
. "'_tracking_number', "
|
91 |
+
. "'_date_shipped', "
|
92 |
+
. "'_order_custtrackurl', "
|
93 |
+
. "'_order_custcompname', "
|
94 |
+
. "'_order_trackno', "
|
95 |
+
. "'_order_trackurl' )";
|
96 |
+
}
|
97 |
+
return $order_meta_query;
|
98 |
+
}
|
99 |
+
|
100 |
+
/**
|
101 |
+
* Settings prompt
|
102 |
+
*/
|
103 |
+
public function settings_notice() {
|
104 |
+
if ( ! empty( $_GET['tab'] ) && 'integration' === $_GET['tab'] ) {
|
105 |
+
return;
|
106 |
+
}
|
107 |
+
|
108 |
+
$logo_title = __( 'ShipStation logo', 'woocommerce-shipstation' );
|
109 |
+
?>
|
110 |
+
<div id="message" class="updated woocommerce-message shipstation-setup">
|
111 |
+
<img class="shipstation-logo" alt="<?php echo esc_attr( $logo_title ); ?>" title="<?php echo esc_attr( $logo_title ); ?>" src="<?php echo plugins_url( 'assets/images/shipstation-logo-blue.png', dirname( __FILE__ ) ); ?>" />
|
112 |
+
<a class="woocommerce-message-close notice-dismiss" href="<?php echo esc_url( wp_nonce_url( add_query_arg( 'wc-hide-notice', 'shipstation-setup' ), 'woocommerce_hide_notices_nonce', '_wc_notice_nonce' ) ); ?>"><?php _e( 'Dismiss', 'woocommerce' ); ?></a>
|
113 |
+
<p>
|
114 |
+
<?php
|
115 |
+
printf(
|
116 |
+
wp_kses(
|
117 |
+
/* translators: %s: ShipStation URL */
|
118 |
+
__( 'To begin printing shipping labels with ShipStation head over to <a class="shipstation-external-link" href="%s" target="_blank">ShipStation.com</a> and log in or create a new account.', 'woocommerce-shipstation' ),
|
119 |
+
array(
|
120 |
+
'a' => array(
|
121 |
+
'class' => array(),
|
122 |
+
'href' => array(),
|
123 |
+
'target' => array(),
|
124 |
+
),
|
125 |
+
)
|
126 |
+
),
|
127 |
+
'https://www.shipstation.com/'
|
128 |
+
);
|
129 |
+
?>
|
130 |
+
</p>
|
131 |
+
<p>
|
132 |
+
<?php
|
133 |
+
printf(
|
134 |
+
wp_kses(
|
135 |
+
/* translators: %s: ShipStation Auth Key */
|
136 |
+
__( 'After logging in, add a selling channel for WooCommerce and use your Auth Key (<code>%s</code>) to connect your store.', 'woocommerce-shipstation' ),
|
137 |
+
array( 'code' => array() )
|
138 |
+
),
|
139 |
+
self::$auth_key
|
140 |
+
);
|
141 |
+
?>
|
142 |
+
</p>
|
143 |
+
<p><?php esc_html_e( "Once connected you're good to go!", 'woocommerce-shipstation' ); ?></p>
|
144 |
+
<hr>
|
145 |
+
<p>
|
146 |
+
<?php
|
147 |
+
printf(
|
148 |
+
wp_kses(
|
149 |
+
/* translators: %1$s: ShipStation plugin settings URL, %2$s: ShipStation documentation URL */
|
150 |
+
__( 'You can find other settings for this extension <a href="%1$s">here</a> and view the documentation <a href="%2$s">here</a>.', 'woocommerce-shipstation' ),
|
151 |
+
array(
|
152 |
+
'a' => array(
|
153 |
+
'href' => array(),
|
154 |
+
),
|
155 |
+
)
|
156 |
+
),
|
157 |
+
admin_url( 'admin.php?page=wc-settings&tab=integration§ion=shipstation' ),
|
158 |
+
'https://docs.woocommerce.com/document/shipstation-for-woocommerce/'
|
159 |
+
);
|
160 |
+
?>
|
161 |
+
</p>
|
162 |
+
</div>
|
163 |
+
<?php
|
164 |
+
}
|
165 |
+
}
|
166 |
+
|
includes/class-wc-shipstation-privacy.php
ADDED
@@ -0,0 +1,165 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|