Version Description
Download this release
Release Info
Developer | bor0 |
Plugin | WooCommerce ShipStation Gateway |
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 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
if ( ! class_exists( 'WC_Abstract_Privacy' ) ) {
|
3 |
+
return;
|
4 |
+
}
|
5 |
+
|
6 |
+
class WC_ShipStation_Privacy extends WC_Abstract_Privacy {
|
7 |
+
/**
|
8 |
+
* Constructor
|
9 |
+
*
|
10 |
+
*/
|
11 |
+
public function __construct() {
|
12 |
+
parent::__construct( __( 'ShipStation', 'woocommerce-shipstation' ) );
|
13 |
+
|
14 |
+
$this->add_exporter( 'woocommerce-shipstation-order-data', __( 'WooCommerce ShipStation Order Data', 'woocommerce-shipstation' ), array( $this, 'order_data_exporter' ) );
|
15 |
+
|
16 |
+
$this->add_eraser( 'woocommerce-shipstation-order-data', __( 'WooCommerce ShipStation Data', 'woocommerce-shipstation' ), array( $this, 'order_data_eraser' ) );
|
17 |
+
}
|
18 |
+
|
19 |
+
/**
|
20 |
+
* Returns a list of orders.
|
21 |
+
*
|
22 |
+
* @param string $email_address
|
23 |
+
* @param int $page
|
24 |
+
*
|
25 |
+
* @return array WP_Post
|
26 |
+
*/
|
27 |
+
protected function get_orders( $email_address, $page ) {
|
28 |
+
$user = get_user_by( 'email', $email_address ); // Check if user has an ID in the DB to load stored personal data.
|
29 |
+
|
30 |
+
$order_query = array(
|
31 |
+
'limit' => 10,
|
32 |
+
'page' => $page,
|
33 |
+
);
|
34 |
+
|
35 |
+
if ( $user instanceof WP_User ) {
|
36 |
+
$order_query['customer_id'] = (int) $user->ID;
|
37 |
+
} else {
|
38 |
+
$order_query['billing_email'] = $email_address;
|
39 |
+
}
|
40 |
+
|
41 |
+
return wc_get_orders( $order_query );
|
42 |
+
}
|
43 |
+
|
44 |
+
/**
|
45 |
+
* Gets the message of the privacy to display.
|
46 |
+
*
|
47 |
+
*/
|
48 |
+
public function get_privacy_message() {
|
49 |
+
/* translators: 1: URL to documentation */
|
50 |
+
return wpautop( sprintf( __( 'By using this extension, you may be storing personal data or sharing data with an external service. <a href="%s" target="_blank">Learn more about how this works, including what you may want to include in your privacy policy.</a>', 'woocommerce-shipstation' ), 'https://docs.woocommerce.com/document/privacy-shipping/#woocommerce-shipstation' ) );
|
51 |
+
}
|
52 |
+
|
53 |
+
/**
|
54 |
+
* Handle exporting data for Orders.
|
55 |
+
*
|
56 |
+
* @param string $email_address E-mail address to export.
|
57 |
+
* @param int $page Pagination of data.
|
58 |
+
*
|
59 |
+
* @return array
|
60 |
+
*/
|
61 |
+
public function order_data_exporter( $email_address, $page = 1 ) {
|
62 |
+
$done = false;
|
63 |
+
$data_to_export = array();
|
64 |
+
|
65 |
+
$orders = $this->get_orders( $email_address, (int) $page );
|
66 |
+
|
67 |
+
$done = true;
|
68 |
+
|
69 |
+
if ( 0 < count( $orders ) ) {
|
70 |
+
foreach ( $orders as $order ) {
|
71 |
+
$data_to_export[] = array(
|
72 |
+
'group_id' => 'woocommerce_orders',
|
73 |
+
'group_label' => __( 'Orders', 'woocommerce-shipstation' ),
|
74 |
+
'item_id' => 'order-' . $order->get_id(),
|
75 |
+
'data' => array(
|
76 |
+
array(
|
77 |
+
'name' => __( 'ShipStation tracking provider', 'woocommerce-shipstation' ),
|
78 |
+
'value' => get_post_meta( $order->get_id(), '_tracking_provider', true ),
|
79 |
+
),
|
80 |
+
array(
|
81 |
+
'name' => __( 'ShipStation tracking number', 'woocommerce-shipstation' ),
|
82 |
+
'value' => get_post_meta( $order->get_id(), '_tracking_number', true ),
|
83 |
+
),
|
84 |
+
array(
|
85 |
+
'name' => __( 'ShipStation date shipped', 'woocommerce-shipstation' ),
|
86 |
+
'value' => get_post_meta( $order->get_id(), '_date_shipped', true ) ? date( 'Y-m-d H:i:s', get_post_meta( $order->get_id(), '_date_shipped', true ) ) : '',
|
87 |
+
),
|
88 |
+
),
|
89 |
+
);
|
90 |
+
}
|
91 |
+
|
92 |
+
$done = 10 > count( $orders );
|
93 |
+
}
|
94 |
+
|
95 |
+
return array(
|
96 |
+
'data' => $data_to_export,
|
97 |
+
'done' => $done,
|
98 |
+
);
|
99 |
+
}
|
100 |
+
|
101 |
+
/**
|
102 |
+
* Finds and erases order data by email address.
|
103 |
+
*
|
104 |
+
* @since 3.4.0
|
105 |
+
* @param string $email_address The user email address.
|
106 |
+
* @param int $page Page.
|
107 |
+
* @return array An array of personal data in name value pairs
|
108 |
+
*/
|
109 |
+
public function order_data_eraser( $email_address, $page ) {
|
110 |
+
$orders = $this->get_orders( $email_address, (int) $page );
|
111 |
+
|
112 |
+
$items_removed = false;
|
113 |
+
$items_retained = false;
|
114 |
+
$messages = array();
|
115 |
+
|
116 |
+
foreach ( (array) $orders as $order ) {
|
117 |
+
$order = wc_get_order( $order->get_id() );
|
118 |
+
|
119 |
+
list( $removed, $retained, $msgs ) = $this->maybe_handle_order( $order );
|
120 |
+
$items_removed |= $removed;
|
121 |
+
$items_retained |= $retained;
|
122 |
+
$messages = array_merge( $messages, $msgs );
|
123 |
+
}
|
124 |
+
|
125 |
+
// Tell core if we have more orders to work on still
|
126 |
+
$done = count( $orders ) < 10;
|
127 |
+
|
128 |
+
return array(
|
129 |
+
'items_removed' => $items_removed,
|
130 |
+
'items_retained' => $items_retained,
|
131 |
+
'messages' => $messages,
|
132 |
+
'done' => $done,
|
133 |
+
);
|
134 |
+
}
|
135 |
+
|
136 |
+
/**
|
137 |
+
* Handle eraser of data tied to Orders
|
138 |
+
*
|
139 |
+
* @param WC_Order $order
|
140 |
+
* @return array
|
141 |
+
*/
|
142 |
+
protected function maybe_handle_order( $order ) {
|
143 |
+
$order_id = $order->get_id();
|
144 |
+
$item_count = get_post_meta( $order->get_id(), '_shipstation_shipped_item_count', true );
|
145 |
+
$tracking_provider = get_post_meta( $order->get_id(), '_tracking_provider', true );
|
146 |
+
$tracking_number = get_post_meta( $order->get_id(), '_tracking_number', true );
|
147 |
+
$date_shipped = get_post_meta( $order->get_id(), '_date_shipped', true );
|
148 |
+
$shipstation_exported = get_post_meta( $order->get_id(), '_shipstation_exported', true );
|
149 |
+
|
150 |
+
if ( empty( $item_count ) && empty( $tracking_provider ) && empty( $tracking_number )
|
151 |
+
&& empty( $date_shipped ) && empty( $shipstation_exported ) ) {
|
152 |
+
return array( false, false, array() );
|
153 |
+
}
|
154 |
+
|
155 |
+
delete_post_meta( $order->get_id(), '_shipstation_shipped_item_count' );
|
156 |
+
delete_post_meta( $order->get_id(), '_tracking_provider' );
|
157 |
+
delete_post_meta( $order->get_id(), '_tracking_number' );
|
158 |
+
delete_post_meta( $order->get_id(), '_date_shipped' );
|
159 |
+
delete_post_meta( $order->get_id(), '_shipstation_exported' );
|
160 |
+
|
161 |
+
return array( true, false, array( __( 'ShipStation Order Data Erased.', 'woocommerce-shipstation' ) ) );
|
162 |
+
}
|
163 |
+
}
|
164 |
+
|
165 |
+
new WC_ShipStation_Privacy();
|
includes/data/data-settings.php
ADDED
@@ -0,0 +1,72 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
$statuses = wc_get_order_statuses();
|
4 |
+
|
5 |
+
// When integration loaded custom statuses is not loaded yet, so we need to
|
6 |
+
// merge it manually.
|
7 |
+
if ( function_exists( 'wc_order_status_manager' ) ) {
|
8 |
+
$query = new WP_Query( array(
|
9 |
+
'post_type' => 'wc_order_status',
|
10 |
+
'post_status' => 'publish',
|
11 |
+
'posts_per_page' => -1,
|
12 |
+
'suppress_filters' => 1,
|
13 |
+
'orderby' => 'menu_order',
|
14 |
+
'order' => 'ASC',
|
15 |
+
) );
|
16 |
+
|
17 |
+
$filtered_statuses = array();
|
18 |
+
foreach ( $query->posts as $status ) {
|
19 |
+
$filtered_statuses[ 'wc-' . $status->post_name ] = $status->post_title;
|
20 |
+
}
|
21 |
+
$statuses = array_merge( $statuses, $filtered_statuses );
|
22 |
+
|
23 |
+
wp_reset_postdata();
|
24 |
+
}
|
25 |
+
|
26 |
+
foreach ( $statuses as $key => $value ) {
|
27 |
+
$statuses[ $key ] = str_replace( 'wc-', '', $key );
|
28 |
+
}
|
29 |
+
|
30 |
+
$fields = array(
|
31 |
+
'auth_key' => array(
|
32 |
+
'title' => __( 'Authentication Key', 'woocommerce-shipstation' ),
|
33 |
+
'description' => __( 'Copy and paste this key into ShipStation during setup.', 'woocommerce-shipstation' ),
|
34 |
+
'default' => '',
|
35 |
+
'type' => 'text',
|
36 |
+
'desc_tip' => __( 'This is the <code>Auth Key</code> you set in ShipStation and allows ShipStation to communicate with your store.', 'woocommerce-shipstation' ),
|
37 |
+
'custom_attributes' => array(
|
38 |
+
'readonly' => 'readonly',
|
39 |
+
),
|
40 |
+
'value' => WC_ShipStation_Integration::$auth_key,
|
41 |
+
),
|
42 |
+
'export_statuses' => array(
|
43 |
+
'title' => __( 'Export Order Statuses…', 'woocommerce-shipstation' ),
|
44 |
+
'type' => 'multiselect',
|
45 |
+
'options' => $statuses,
|
46 |
+
'class' => 'chosen_select',
|
47 |
+
'css' => 'width: 450px;',
|
48 |
+
'description' => __( 'Define the order statuses you wish to export to ShipStation.', 'woocommerce' ),
|
49 |
+
'desc_tip' => true,
|
50 |
+
'custom_attributes' => array(
|
51 |
+
'data-placeholder' => __( 'Select Order Statuses', 'woocommerce' ),
|
52 |
+
),
|
53 |
+
),
|
54 |
+
'shipped_status' => array(
|
55 |
+
'title' => __( 'Shipped Order Status…', 'woocommerce-shipstation' ),
|
56 |
+
'type' => 'select',
|
57 |
+
'options' => $statuses,
|
58 |
+
'description' => __( 'Define the order status you wish to update to once an order has been shipping via ShipStation. By default this is "Completed".', 'woocommerce' ),
|
59 |
+
'desc_tip' => true,
|
60 |
+
'default' => 'wc-completed',
|
61 |
+
),
|
62 |
+
'logging_enabled' => array(
|
63 |
+
'title' => __( 'Logging', 'woocommerce-shipstation' ),
|
64 |
+
'label' => __( 'Enable Logging', 'woocommerce-shipstation' ),
|
65 |
+
'type' => 'checkbox',
|
66 |
+
'description' => __( 'Log all API interations.', 'woocommerce' ),
|
67 |
+
'desc_tip' => true,
|
68 |
+
'default' => 'yes',
|
69 |
+
),
|
70 |
+
);
|
71 |
+
|
72 |
+
return $fields;
|
readme.txt
ADDED
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
=== WooCommerce ShipStation Gateway ===
|
2 |
+
Contributors: automattic, royho, akeda, mattyza, bor0, woothemes, dwainm, laurendavissmith001
|
3 |
+
Tags: shipping, woocommerce, automattic
|
4 |
+
Requires at least: 4.4
|
5 |
+
Tested up to: 4.9
|
6 |
+
Requires PHP: 5.6
|
7 |
+
Stable tag: 4.1.23
|
8 |
+
License: GPLv3
|
9 |
+
License URI: https://www.gnu.org/licenses/gpl-3.0.html
|
10 |
+
|
11 |
+
The official WooCommerce ShipStation plugin helps store owners integrate WooCommerce with ShipStation and expedite the shipping process.
|
12 |
+
|
13 |
+
== Description ==
|
14 |
+
|
15 |
+
ShipStation’s sophisticated automation features help you shave many hours off your fulfillment process. Print wirelessly and share your printer with ease thanks to ShipStation Connect. Run your business on-the-go with ShipStation Mobile, the industry’s only mobile app (free for iOS and Android) and do everything from creating orders to printing labels and emailing return labels all from your phone or tablet.
|
16 |
+
|
17 |
+
= Why choose ShipStation? =
|
18 |
+
|
19 |
+
ShipStation is a web-based shipping solution that streamlines the order fulfillment process for online retailers, handling everything from order import and batch label creation to customer communication. Advanced customization features allow ShipStation to fit businesses with any number of users or locations.
|
20 |
+
|
21 |
+
== Frequently Asked Questions ==
|
22 |
+
|
23 |
+
= Does ShipStation provide real-time shipping quotes that can be used at checkout? =
|
24 |
+
|
25 |
+
No. Store owners need a real-time shipping quote extension such as USPS, FedEx, UPS, etc. or have an alternate way to show shipping quotes (e.g., Flat rate charge).
|
26 |
+
|
27 |
+
= Does ShipStation send data when not being used (e.g., Free Shipping)? =
|
28 |
+
|
29 |
+
Yes, there isn’t conditional exporting. If the data is there, we export it!
|
30 |
+
|
31 |
+
= Why do multiple line items in an order on the WooCommerce side get combined when they reach ShipStation? =
|
32 |
+
|
33 |
+
This is most likely because unique Product SKUs have not been configured for each product and variation in the Store. To ensure that order line items show up correctly in ShipStation, we recommend assigning a unique SKU to each product as well as each variation within a product.
|
34 |
+
|
35 |
+
= Why do multiple line items in an order on the WooCommerce side get combined when they reach ShipStation? =
|
36 |
+
|
37 |
+
This is most likely because unique Product SKUs have not been configured for each product and variation in the Store. To ensure that order line items show up correctly in ShipStation, we recommend assigning a unique SKU to each product as well as each variation within a product.
|
38 |
+
|
39 |
+
= Where can I find documentation? =
|
40 |
+
|
41 |
+
For help setting up and configuring, please refer to our [user guide](https://docs.woocommerce.com/document/shipstation-for-woocommerce)
|
42 |
+
|
43 |
+
= Where can I get support or talk to other users? =
|
44 |
+
|
45 |
+
If you get stuck, you can ask for help in the Plugin Forum.
|
46 |
+
|
47 |
+
== Changelog ==
|
48 |
+
|
49 |
+
= 2018-09-12 - version 4.1.23 =
|
50 |
+
* Fix - Use correct textdomain on some strings.
|
51 |
+
* Tweak - Rework settings notice to correctly provide setup instructions.
|
52 |
+
* Tweak - Coding standards and making the plugin ready for wordpress.org.
|
woocommerce-shipstation.php
ADDED
@@ -0,0 +1,87 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* Plugin Name: WooCommerce - ShipStation Integration
|
4 |
+
* Plugin URI: https://woocommerce.com/products/shipstation-integration/
|
5 |
+
* Version: 4.1.23
|
6 |
+
* Description: Adds ShipStation label printing support to WooCommerce. Requires server DomDocument support.
|
7 |
+
* Author: WooCommerce
|
8 |
+
* Author URI: https://woocommerce.com/
|
9 |
+
* Text Domain: woocommerce-shipstation
|
10 |
+
* WC tested up to: 3.4
|
11 |
+
* WC requires at least: 2.6
|
12 |
+
*
|
13 |
+
*/
|
14 |
+
|
15 |
+
if ( ! defined( 'ABSPATH' ) ) {
|
16 |
+
exit;
|
17 |
+
}
|
18 |
+
|
19 |
+
/**
|
20 |
+
* Include shipstation class.
|
21 |
+
*
|
22 |
+
* @since 1.0.0
|
23 |
+
*/
|
24 |
+
function woocommerce_shipstation_init() {
|
25 |
+
define( 'WC_SHIPSTATION_VERSION', '4.1.23' );
|
26 |
+
define( 'WC_SHIPSTATION_FILE', __FILE__ );
|
27 |
+
|
28 |
+
if ( ! defined( 'WC_SHIPSTATION_EXPORT_LIMIT' ) ) {
|
29 |
+
define( 'WC_SHIPSTATION_EXPORT_LIMIT', 100 );
|
30 |
+
}
|
31 |
+
|
32 |
+
load_plugin_textdomain( 'woocommerce-shipstation', false, basename( dirname( __FILE__ ) ) . '/languages' );
|
33 |
+
|
34 |
+
include_once( 'includes/class-wc-shipstation-integration.php' );
|
35 |
+
include_once( 'includes/class-wc-shipstation-privacy.php' );
|
36 |
+
}
|
37 |
+
|
38 |
+
add_action( 'plugins_loaded', 'woocommerce_shipstation_init' );
|
39 |
+
|
40 |
+
/**
|
41 |
+
* Define integration.
|
42 |
+
*
|
43 |
+
* @since 1.0.0
|
44 |
+
*
|
45 |
+
* @param array $integrations Integrations.
|
46 |
+
* @return array Integrations.
|
47 |
+
*/
|
48 |
+
function woocommerce_shipstation_load_integration( $integrations ) {
|
49 |
+
$integrations[] = 'WC_ShipStation_Integration';
|
50 |
+
|
51 |
+
return $integrations;
|
52 |
+
}
|
53 |
+
|
54 |
+
add_filter( 'woocommerce_integrations', 'woocommerce_shipstation_load_integration' );
|
55 |
+
|
56 |
+
/**
|
57 |
+
* Listen for API requests.
|
58 |
+
*
|
59 |
+
* @since 1.0.0
|
60 |
+
*/
|
61 |
+
function woocommerce_shipstation_api() {
|
62 |
+
include_once( 'includes/class-wc-shipstation-api.php' );
|
63 |
+
}
|
64 |
+
|
65 |
+
add_action( 'woocommerce_api_wc_shipstation', 'woocommerce_shipstation_api' );
|
66 |
+
|
67 |
+
/**
|
68 |
+
* Added ShipStation custom plugin action links.
|
69 |
+
*
|
70 |
+
* @since 4.1.17
|
71 |
+
* @version 4.1.17
|
72 |
+
*
|
73 |
+
* @param array $links Links.
|
74 |
+
*
|
75 |
+
* @return array Links.
|
76 |
+
*/
|
77 |
+
function woocommerce_shipstation_api_plugin_action_links( $links ) {
|
78 |
+
$setting_link = admin_url( 'admin.php?page=wc-settings&tab=integration§ion=shipstation' );
|
79 |
+
$plugin_links = array(
|
80 |
+
'<a href="' . $setting_link . '">' . __( 'Settings', 'woocommerce-shipstation' ) . '</a>',
|
81 |
+
'<a href="https://woocommerce.com/my-account/tickets">' . __( 'Support', 'woocommerce-shipstation' ) . '</a>',
|
82 |
+
'<a href="https://docs.woocommerce.com/document/shipstation-for-woocommerce/">' . __( 'Docs', 'woocommerce-shipstation' ) . '</a>',
|
83 |
+
);
|
84 |
+
|
85 |
+
return array_merge( $plugin_links, $links );
|
86 |
+
}
|
87 |
+
add_filter( 'plugin_action_links_' . plugin_basename( __FILE__ ), 'woocommerce_shipstation_api_plugin_action_links' );
|