Skip to content

Instantly share code, notes, and snippets.

@dwelch2344
Last active March 17, 2021 11:33
Show Gist options
  • Save dwelch2344/6ae4626bb6cd210ee5acdbb83d771060 to your computer and use it in GitHub Desktop.
Save dwelch2344/6ae4626bb6cd210ee5acdbb83d771060 to your computer and use it in GitHub Desktop.
Add hooks to WC_Shipstation_API_Export
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
/**
* WC_Shipstation_API_Export Class
*/
class WC_Shipstation_API_Export extends WC_Shipstation_API_Request {
/**
* Constructor
*/
public function __construct() {
if ( ! WC_Shipstation_API::authenticated() ) {
exit;
}
}
/**
* Do the request
*/
public function request() {
global $wpdb;
$this->validate_input( array( 'start_date', 'end_date' ) );
header( 'Content-Type: text/xml' );
$xml = new DOMDocument( '1.0', 'utf-8' );
$xml->formatOutput = true;
$page = max( 1, isset( $_GET['page'] ) ? absint( $_GET['page'] ) : 1 );
$exported = 0;
$tz_offset = get_option( 'gmt_offset' ) * 3600;
$raw_start_date = wc_clean( urldecode( $_GET['start_date'] ) );
$raw_end_date = wc_clean( urldecode( $_GET['end_date'] ) );
// Parse start and end date
if ( $raw_start_date && false === strtotime( $raw_start_date ) ) {
$month = substr( $raw_start_date, 0, 2 );
$day = substr( $raw_start_date, 2, 2 );
$year = substr( $raw_start_date, 4, 4 );
$time = substr( $raw_start_date, 9, 4 );
$start_date = gmdate( 'Y-m-d H:i:s', strtotime( $year . '-' . $month . '-' . $day . ' ' . $time ) );
} else {
$start_date = gmdate( 'Y-m-d H:i:s', strtotime( $raw_start_date ) );
}
if ( $raw_end_date && false === strtotime( $raw_end_date ) ) {
$month = substr( $raw_end_date, 0, 2 );
$day = substr( $raw_end_date, 2, 2 );
$year = substr( $raw_end_date, 4, 4 );
$time = substr( $raw_end_date, 9, 4 );
$end_date = gmdate( 'Y-m-d H:i:s', strtotime( $year . '-' . $month . '-' . $day . ' ' . $time ) );
} else {
$end_date = gmdate( 'Y-m-d H:i:s', strtotime( $raw_end_date ) );
}
if ( version_compare( WC_VERSION, '3.1', '>=' ) ) {
$args = apply_filters('woocommerce_shipstation_export_order_query_args', array(
'date_modified' => $start_date . '...' . $end_date,
'type' => 'shop_order',
'status' => WC_ShipStation_Integration::$export_statuses,
'return' => 'ids',
'orderby' => 'date_modified',
'order' => 'DESC',
'paged' => $page,
'limit' => WC_SHIPSTATION_EXPORT_LIMIT,
) );
$order_ids = wc_get_orders( $args );
$order_ids = apply_filters('woocommerce_shipstation_export_order_ids', $order_ids);
$order_ids = array_map( function( $order_or_id ) {
return is_a( $order_or_id, 'WC_Order' ) ? $order_or_id->get_id() : $order_or_id;
}, $order_ids );
} else {
$order_ids = $wpdb->get_col(
$wpdb->prepare( "
SELECT ID FROM {$wpdb->posts}
WHERE post_type = 'shop_order'
AND post_status IN ( '" . implode( "','", WC_ShipStation_Integration::$export_statuses ) . "' )
AND %s <= post_modified_gmt
AND post_modified_gmt <= %s
ORDER BY post_modified_gmt DESC
LIMIT %d, %d
",
$start_date,
$end_date,
WC_SHIPSTATION_EXPORT_LIMIT * ( $page - 1 ),
WC_SHIPSTATION_EXPORT_LIMIT
)
);
}
$max_results = apply_filters('woocommerce_shipstation_export_order_max_results', null);
if( $max_results === null ){
// Figure out how to retrieve this using WC Query class.
$max_results = $wpdb->get_var(
$wpdb->prepare( "
SELECT COUNT(ID) FROM {$wpdb->posts}
WHERE post_type = 'shop_order'
AND post_status IN ( '" . implode( "','", WC_ShipStation_Integration::$export_statuses ) . "' )
AND %s <= post_modified_gmt
AND post_modified_gmt <= %s
",
$start_date,
$end_date
)
);
}
$orders_xml = $xml->createElement( 'Orders' );
foreach ( $order_ids as $order_id ) {
if ( ! apply_filters( 'woocommerce_shipstation_export_order', true, $order_id ) ) {
continue;
}
$order = wc_get_order( $order_id );
$order_xml = $xml->createElement( 'Order' );
$wc_gte_30 = version_compare( WC_VERSION, '3.0', '>=' );// gte greater than or equal to 3.0
$formatted_order_number = ltrim( $order->get_order_number(), '#' );
$this->xml_append( $order_xml, 'OrderNumber', $formatted_order_number );
$this->xml_append( $order_xml, 'OrderID', $order_id );
if ( $wc_gte_30 ) {
// Sequence of date ordering: date paid > date completed > date created
$order_timestamp = $order->get_date_paid() ?: $order->get_date_completed() ?: $order->get_date_created();
$order_timestamp = $order_timestamp->getOffsetTimestamp();
} else {
$order_timestamp = $order->order_date;
}
$order_timestamp -= $tz_offset;
$this->xml_append( $order_xml, 'OrderDate', gmdate( 'm/d/Y H:i', $order_timestamp ), false );
$this->xml_append( $order_xml, 'OrderStatus', $order->get_status() );
$this->xml_append( $order_xml, 'PaymentMethod', $wc_gte_30 ? $order->get_payment_method() : $order->payment_method );
$this->xml_append( $order_xml, 'OrderPaymentMethodTitle', $wc_gte_30 ? $order->get_payment_method_title() : $order->payment_method_title );
$last_modified = strtotime( $wc_gte_30 ? $order->get_date_modified()->date( 'm/d/Y H:i' ) : $order->modified_date ) - $tz_offset;
$this->xml_append( $order_xml, 'LastModified', gmdate( 'm/d/Y H:i', $last_modified ), false );
$this->xml_append( $order_xml, 'ShippingMethod', implode( ' | ', $this->get_shipping_methods( $order ) ) );
$this->xml_append( $order_xml, 'OrderTotal', $order->get_total(), false );
$this->xml_append( $order_xml, 'TaxAmount', wc_round_tax_total( $order->get_total_tax() ), false );
if ( class_exists( 'WC_COG' ) ) {
$this->xml_append( $order_xml, 'CostOfGoods', wc_format_decimal( $order->wc_cog_order_total_cost ), false );
}
$this->xml_append( $order_xml, 'ShippingAmount', $wc_gte_30 ? $order->get_shipping_total() : $order->get_total_shipping(), false );
$this->xml_append( $order_xml, 'CustomerNotes', $wc_gte_30 ? $order->get_customer_note() : $order->customer_note );
$this->xml_append( $order_xml, 'InternalNotes', implode( ' | ', $this->get_order_notes( $order ) ) );
// Custom fields - 1 is used for coupon codes
$this->xml_append( $order_xml, 'CustomField1', implode( ' | ', version_compare( WC_VERSION, '3.7', 'ge' ) ? $order->get_coupon_codes() : $order->get_used_coupons() ) );
// Custom fields 2 and 3 can be mapped to a custom field via the following filters
$meta_key = apply_filters( 'woocommerce_shipstation_export_custom_field_2', '' );
if ( $meta_key ) {
$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 ) );
}
$meta_key = apply_filters( 'woocommerce_shipstation_export_custom_field_3', '' );
if ( $meta_key ) {
$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 ) );
}
// Customer data
$customer_xml = $xml->createElement( 'Customer' );
$this->xml_append( $customer_xml, 'CustomerCode', $wc_gte_30 ? $order->get_billing_email() : $order->billing_email );
$billto_xml = $xml->createElement( 'BillTo' );
$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 ) );
$this->xml_append( $billto_xml, 'Company', $wc_gte_30 ? $order->get_billing_company() : $order->billing_company );
$this->xml_append( $billto_xml, 'Phone', $wc_gte_30 ? $order->get_billing_phone() : $order->billing_phone );
$this->xml_append( $billto_xml, 'Email', $wc_gte_30 ? $order->get_billing_email() : $order->billing_email );
$customer_xml->appendChild( $billto_xml );
$shipto_xml = $xml->createElement( 'ShipTo' );
$shipping_country = $wc_gte_30 ? $order->get_shipping_country() : $order->shipping_country;
$shipping_address = $wc_gte_30 ? $order->get_shipping_address_1() : $order->shipping_address_1;
if ( empty( $shipping_country ) && empty( $shipping_address ) ) {
$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 );
$this->xml_append( $shipto_xml, 'Name', $name );
$this->xml_append( $shipto_xml, 'Company', $wc_gte_30 ? $order->get_billing_company() : $order->billing_company );
$this->xml_append( $shipto_xml, 'Address1', $wc_gte_30 ? $order->get_billing_address_1() : $order->billing_address_1 );
$this->xml_append( $shipto_xml, 'Address2', $wc_gte_30 ? $order->get_billing_address_2() : $order->billing_address_2 );
$this->xml_append( $shipto_xml, 'City', $wc_gte_30 ? $order->get_billing_city() : $order->billing_city );
$this->xml_append( $shipto_xml, 'State', $wc_gte_30 ? $order->get_billing_state() : $order->billing_state );
$this->xml_append( $shipto_xml, 'PostalCode', $wc_gte_30 ? $order->get_billing_postcode() : $order->billing_postcode );
$this->xml_append( $shipto_xml, 'Country', $wc_gte_30 ? $order->get_billing_country() : $order->billing_country );
$this->xml_append( $shipto_xml, 'Phone', $wc_gte_30 ? $order->get_billing_phone() : $order->billing_phone );
} else {
$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 );
$this->xml_append( $shipto_xml, 'Name', $name );
$this->xml_append( $shipto_xml, 'Company', $wc_gte_30 ? $order->get_shipping_company() : $order->shipping_company );
$this->xml_append( $shipto_xml, 'Address1', $wc_gte_30 ? $order->get_shipping_address_1() : $order->shipping_address_1 );
$this->xml_append( $shipto_xml, 'Address2', $wc_gte_30 ? $order->get_shipping_address_2() : $order->shipping_address_2 );
$this->xml_append( $shipto_xml, 'City', $wc_gte_30 ? $order->get_shipping_city() : $order->shipping_city );
$this->xml_append( $shipto_xml, 'State', $wc_gte_30 ? $order->get_shipping_state() : $order->shipping_state );
$this->xml_append( $shipto_xml, 'PostalCode', $wc_gte_30 ? $order->get_shipping_postcode() : $order->shipping_postcode );
$this->xml_append( $shipto_xml, 'Country', $wc_gte_30 ? $order->get_shipping_country() : $order->shipping_country );
$this->xml_append( $shipto_xml, 'Phone', $wc_gte_30 ? $order->get_billing_phone() : $order->billing_phone );
}
$customer_xml->appendChild( $shipto_xml );
$order_xml->appendChild( $customer_xml );
// Item data
$found_item = false;
$items_xml = $xml->createElement( 'Items' );
// Merge arrays without loosing indexes.
$order_items = $order->get_items() + $order->get_items( 'fee' );
foreach ( $order_items as $item_id => $item ) {
if ( $wc_gte_30 ) {
$product = is_callable( array( $item, 'get_product' ) ) ? $item->get_product() : false;
} else {
$product = $order->get_product_from_item( $item );
}
$item_needs_no_shipping = ! $product || ! $product->needs_shipping();
$item_not_a_fee = 'fee' !== $item['type'];
if ( apply_filters( 'woocommerce_shipstation_no_shipping_item', $item_needs_no_shipping && $item_not_a_fee, $product, $item ) ) {
continue;
}
$found_item = true;
$item_xml = $xml->createElement( 'Item' );
$this->xml_append( $item_xml, 'LineItemID', $item_id );
if ( 'fee' === $item['type'] ) {
$this->xml_append( $item_xml, 'Name', $wc_gte_30 ? $item->get_name() : $item['name'] );
$this->xml_append( $item_xml, 'Quantity', 1, false );
$this->xml_append( $item_xml, 'UnitPrice', $order->get_item_total( $item, false, true ), false );
}
// handle product specific data
if ( $product && $product->needs_shipping() ) {
$this->xml_append( $item_xml, 'SKU', $product->get_sku() );
$this->xml_append( $item_xml, 'Name', $product->get_title() );
// image data
$image_id = $product->get_image_id();
$image_url = $image_id ? current( wp_get_attachment_image_src( $image_id, 'woocommerce_gallery_thumbnail' ) ) : '';
$this->xml_append( $item_xml, 'ImageUrl', $image_url );
$this->xml_append( $item_xml, 'Weight', wc_get_weight( $product->get_weight(), 'oz' ), false );
$this->xml_append( $item_xml, 'WeightUnits', 'Ounces', false );
$this->xml_append( $item_xml, 'Quantity', $item['qty'], false );
$this->xml_append( $item_xml, 'UnitPrice', $order->get_item_subtotal( $item, false, true ), false );
}
if ( $item['item_meta'] ) {
if ( version_compare( WC_VERSION, '3.0.0', '<' ) ) {
$item_meta = new WC_Order_Item_Meta( $item, $product );
$formatted_meta = $item_meta->get_formatted( '_' );
} else {
add_filter( 'woocommerce_is_attribute_in_product_name', '__return_false' );
$formatted_meta = $item->get_formatted_meta_data();
}
if ( ! empty( $formatted_meta ) ) {
$options_xml = $xml->createElement( 'Options' );
foreach ( $formatted_meta as $meta_key => $meta ) {
$option_xml = $xml->createElement( 'Option' );
if ( version_compare( WC_VERSION, '3.0.0', '<' ) ) {
$this->xml_append( $option_xml, 'Name', $meta['label'] );
$this->xml_append( $option_xml, 'Value', $meta['value'] );
} else {
$this->xml_append( $option_xml, 'Name', $meta->display_key );
$this->xml_append( $option_xml, 'Value', wp_strip_all_tags( $meta->display_value ) );
}
$options_xml->appendChild( $option_xml );
}
$item_xml->appendChild( $options_xml );
}
}
$items_xml->appendChild( $item_xml );
}
if ( ! $found_item ) {
continue;
}
// Append cart level discount line
if ( $order->get_total_discount() ) {
$item_xml = $xml->createElement( 'Item' );
$this->xml_append( $item_xml, 'SKU', 'total-discount' );
$this->xml_append( $item_xml, 'Name', __( 'Total Discount', 'woocommerce-shipstation' ) );
$this->xml_append( $item_xml, 'Adjustment', 'true', false );
$this->xml_append( $item_xml, 'Quantity', 1, false );
$this->xml_append( $item_xml, 'UnitPrice', $order->get_total_discount() * -1, false );
$items_xml->appendChild( $item_xml );
}
// Append items XML
$order_xml->appendChild( $items_xml );
$orders_xml->appendChild( apply_filters( 'woocommerce_shipstation_export_order_xml', $order_xml ) );
$exported ++;
// Add order note to indicate it has been exported to Shipstation.
if ( 'yes' !== get_post_meta( $order_id, '_shipstation_exported', true ) ) {
$order->add_order_note( __( 'Order has been exported to Shipstation', 'woocommerce-shipstation' ) );
update_post_meta( $order_id, '_shipstation_exported', 'yes' );
}
}
$orders_xml->setAttribute( 'page', $page );
$orders_xml->setAttribute( 'pages', ceil( $max_results / WC_SHIPSTATION_EXPORT_LIMIT ) );
$xml->appendChild( $orders_xml );
echo $xml->saveXML();
/* translators: 1: total count */
$this->log( sprintf( __( 'Exported %s orders', 'woocommerce-shipstation' ), $exported ) );
}
/**
* Get shipping method names
* @param WC_Order $order
* @return array
*/
private function get_shipping_methods( $order ) {
$shipping_methods = $order->get_shipping_methods();
$shipping_method_names = array();
foreach ( $shipping_methods as $shipping_method ) {
// Replace non-AlNum characters with space
$method_name = preg_replace( '/[^A-Za-z0-9 \-\.\_,]/', '', $shipping_method['name'] );
$shipping_method_names[] = $method_name;
}
return $shipping_method_names;
}
/**
* Get Order Notes
* @param WC_Order $order
* @return array
*/
private function get_order_notes( $order ) {
$args = array(
'post_id' => version_compare( WC_VERSION, '3.0.0', '>=' ) ? $order->get_id() : $order->id,
'approve' => 'approve',
'type' => 'order_note',
);
remove_filter( 'comments_clauses', array( 'WC_Comments', 'exclude_order_comments' ), 10, 1 );
$notes = get_comments( $args );
add_filter( 'comments_clauses', array( 'WC_Comments', 'exclude_order_comments' ), 10, 1 );
$order_notes = array();
foreach ( $notes as $note ) {
if ( 'WooCommerce' !== $note->comment_author ) {
$order_notes[] = $note->comment_content;
}
}
return $order_notes;
}
/**
* Append XML as cdata
*/
private function xml_append( $append_to, $name, $value, $cdata = true ) {
$data = $append_to->appendChild( $append_to->ownerDocument->createElement( $name ) );
if ( $cdata ) {
$data->appendChild( $append_to->ownerDocument->createCDATASection( $value ) );
} else {
$data->appendChild( $append_to->ownerDocument->createTextNode( $value ) );
}
}
}
return new WC_Shipstation_API_Export();
@dwelch2344
Copy link
Author

Applying the filters on lines 60, 71, and 90 would allow for customizing requested how exported Order IDs are requested – which is useful if you have multiple ShipStation integrations hitting a single WC (common if running a store across multiple markets) or if you need to flag an older order to resync

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment