File "class-mailchimp-woocommerce-transform-orders-wc3.php"
Full Path: /home/vantageo/public_html/cache/.wp-cli/wp-content/plugins/mailchimp-for-woocommerce_bk/includes/api/class-mailchimp-woocommerce-transform-orders-wc3.php
File size: 16.39 KB
MIME-type: text/x-php
Charset: utf-8
<?php
/**
* Class MailChimp_WooCommerce_Transform_Orders
*/
class MailChimp_WooCommerce_Transform_Orders {
public $campaign_id = null;
protected $is_syncing = false;
/**
* @param int $page
* @param int $limit
*
* @return object
*/
public function compile( $page = 1, $limit = 5 ) {
$this->is_syncing = true;
$response = (object) array(
'endpoint' => 'orders',
'page' => $page ? $page : 1,
'limit' => (int) $limit,
'count' => 0,
'valid' => 0,
'drafts' => 0,
'stuffed' => false,
'items' => array(),
);
if ( ( ( $orders = $this->getOrderPosts( $page, $limit ) ) && ! empty( $orders ) ) ) {
foreach ( $orders as $post_id ) {
$response->items[] = $post_id;
$response->count++;
}
}
$response->stuffed = $response->count > 0 && (int) $response->count === (int) $limit;
$this->is_syncing = false;
return $response;
}
/**
* @param WP_Post $post
*
* @return MailChimp_WooCommerce_Order|mixed|void
* @throws MailChimp_WooCommerce_Error
* @throws MailChimp_WooCommerce_RateLimitError
* @throws MailChimp_WooCommerce_ServerError
*/
public function transform( WP_Post $post ) {
$woo = wc_get_order( $post );
$order = new MailChimp_WooCommerce_Order();
// if the woo get order returns an empty value, we need to skip the whole thing.
if ( empty( $woo ) ) {
mailchimp_error( 'sync', 'get woo post was not found for order ' . $post->ID );
return $order;
}
// this is a fallback safety check to make sure we're not submitting these orders.
if ( $woo->get_status() === 'checkout-draft' ) {
$order->setOriginalWooStatus( 'checkout-draft' );
$order->flagAsIgnoreIfNotInMailchimp( true );
return $order;
}
// if the woo object does not have a "get_billing_email" method, then we need to skip this until
// we know how to resolve these types of things.
// mailchimp_log('get_billing_mail', method_exists($woo, 'get_billing_email'), array($order->toArray(), $woo));
if ( ! method_exists( $woo, 'get_billing_email' ) ) {
$message = "Post ID {$post->ID} was an order refund. Skipping this.";
if ( $this->is_syncing ) {
throw new MailChimp_WooCommerce_Error( $message );
}
mailchimp_error(
'initial_sync',
$message,
array(
'post' => $post,
'order_class' => get_class( $woo ),
)
);
return $order;
}
$customer = $this->buildCustomerFromOrder( $woo );
$email = $woo->get_billing_email();
// just skip these altogether because we can't submit any amazon orders anyway.
if ( mailchimp_email_is_amazon( $email ) ) {
return $order->flagAsAmazonOrder( true );
} elseif ( mailchimp_email_is_privacy_protected( $email ) ) {
return $order->flagAsPrivacyProtected( true );
}
$order->setId( $woo->get_order_number() );
// if we have a campaign id let's set it now.
if ( ! empty( $this->campaign_id ) ) {
try {
$order->setCampaignId( $this->campaign_id );
} catch ( Exception $e ) {
mailchimp_log( 'transform_order_set_campaign_id.error', 'No campaign added to order, with provided ID: ' . $this->campaign_id . ' :: ' . $e->getMessage() . ' :: in ' . $e->getFile() . ' :: on ' . $e->getLine() );
}
}
$order->setProcessedAt( $woo->get_date_created()->setTimezone( new DateTimeZone( 'UTC' ) ) );
$order->setCurrencyCode( $woo->get_currency() );
// grab the current statuses - this will end up being custom at some point.
$statuses = $this->getOrderStatuses();
// grab the order status and set it into the object for future comparison.
$order->setOriginalWooStatus( ( $status = $woo->get_status() ) );
// if the order is "on-hold" status, and is not currently in Mailchimp, we need to ignore it
// because the payment gateways are putting this on hold while they navigate to the payment processor
// and they technically haven't paid yet.
if ( in_array( $status, array( 'on-hold', 'failed' ) ) ) {
$order->flagAsIgnoreIfNotInMailchimp( true );
}
// map the fulfillment and financial statuses based on the map above.
$fulfillment_status = array_key_exists( $status, $statuses ) ? $statuses[ $status ]->fulfillment : null;
$financial_status = array_key_exists( $status, $statuses ) ? $statuses[ $status ]->financial : $status;
// set the fulfillment_status
$order->setFulfillmentStatus( $fulfillment_status );
// set the financial status
$order->setFinancialStatus( $financial_status );
// if the status is processing, we need to send this one first, then send a 'paid' status right after.
if ( $status === 'processing' ) {
$order->confirmAndPay( true );
}
// only set this if the order is cancelled.
if ( $status === 'cancelled' ) {
if ( method_exists( $woo, 'get_date_modified' ) ) {
$order->setCancelledAt( $woo->get_date_modified()->setTimezone( new DateTimeZone( 'UTC' ) ) );
}
}
// set the total
$order->setOrderTotal( $order_total = $woo->get_total() );
// set the order URL if it's valid.
if ( ( $view_order_url = $woo->get_view_order_url() ) && wc_is_valid_url( $view_order_url ) ) {
$order->setOrderURL( $woo->get_view_order_url() );
}
// set the total if refund
if ( ( $refund = $woo->get_total_refunded() ) && $refund > 0 ) {
// If there's a refund, apply to order total.
$order_spent = $order_total - $refund;
$order->setOrderTotal( $order_spent );
}
// if we have any tax
$order->setTaxTotal( $woo->get_total_tax() );
// if we have shipping
if ( method_exists( $woo, 'get_shipping_total' ) ) {
$order->setShippingTotal( $woo->get_shipping_total() );
}
// set the order discount
$order->setDiscountTotal( $woo->get_total_discount() );
// set the customer
$order->setCustomer( $customer );
// apply the addresses to the order
$order->setShippingAddress( $this->transformShippingAddress( $woo ) );
$order->setBillingAddress( $this->transformBillingAddress( $woo ) );
// loop through all the order items
foreach ( $woo->get_items() as $key => $order_detail ) {
/** @var WC_Order_Item_Product $order_detail */
// add it into the order item container.
$item = $this->transformLineItem( $key, $order_detail );
$product = $order_detail->get_product();
// if we can't find the product, we need to populate this
if ( empty( $product ) ) {
if ( ( $empty_order_item = MailChimp_WooCommerce_Transform_Products::missing_order_item( $order_detail ) ) ) {
$item->setFallbackTitle( $empty_order_item->getTitle() );
$item->setProductId( $empty_order_item->getId() );
$item->setProductVariantId( $empty_order_item->getId() );
$order->addItem( $item );
continue;
}
}
// if we don't have a product post with this id, we need to add a deleted product to the MC side
if ( ! $product || ( $trashed = 'trash' === $product->get_status() ) ) {
$pid = $order_detail->get_product_id();
$title = $order_detail->get_name();
try {
$deleted_product = MailChimp_WooCommerce_Transform_Products::deleted( $pid, $title );
} catch ( Exception $e ) {
mailchimp_log( 'order.items.error', "Order #{$woo->get_id()} :: Product {$pid} does not exist!" );
continue;
}
// check if it exists, otherwise create a new one.
if ( $deleted_product ) {
// swap out the old item id and product variant id with the deleted version.
$item->setProductId( "deleted_{$pid}" );
$item->setProductVariantId( "deleted_{$pid}" );
// add the item and continue on the loop.
$order->addItem( $item );
continue;
}
mailchimp_log( 'order.items.error', "Order #{$woo->get_id()} :: Product {$pid} does not exist!" );
continue;
}
$order->addItem( $item );
}
// let the store owner alter this if they need to use on-hold orders
return apply_filters( 'mailchimp_filter_ecommerce_order', $order, $woo );
}
/**
* @param WC_Order|WC_Order_Refund $order
*
* @return MailChimp_WooCommerce_Customer
*/
public function buildCustomerFromOrder( $order ) {
$customer = new MailChimp_WooCommerce_Customer();
// attach the WordPress user to the Mailchimp customer object.
$customer->setWordpressUser( $order->get_user() );
$customer->setId( mailchimp_hash_trim_lower( $order->get_billing_email() ) );
$customer->setCompany( $order->get_billing_company() );
$customer->setEmailAddress( trim( $order->get_billing_email() ) );
$customer->setFirstName( $order->get_billing_first_name() );
$customer->setLastName( $order->get_billing_last_name() );
$customer->setAddress( $this->transformBillingAddress( $order ) );
// removing this because it's causing issues with the order counts
// if (!($stats = $this->getCustomerOrderTotals($order))) {
// $stats = (object) array('count' => 0, 'total' => 0);
// }
//
// $customer->setOrdersCount($stats->count);
// $customer->setTotalSpent($stats->total);
// we now hold this data inside the customer object for usage in the order handler class
// we only update the subscriber status on a member IF they were subscribed.
$subscribed_on_order = $customer->wasSubscribedOnOrder( $order->get_id() );
$customer->setOptInStatus( $subscribed_on_order );
try {
$doi = mailchimp_list_has_double_optin();
} catch ( Exception $e ) {
$doi = false;
}
$status_if_new = $doi ? false : $subscribed_on_order;
$customer->setOptInStatus( $status_if_new );
// if they didn't subscribe on the order, we need to check to make sure they're not already a subscriber
// if they are, we just need to make sure that we don't unsubscribe them just because they unchecked this box.
if ( $doi || ! $subscribed_on_order ) {
try {
$subscriber = mailchimp_get_api()->member( mailchimp_get_list_id(), $customer->getEmailAddress() );
if ( $subscriber['status'] === 'transactional' ) {
$customer->setOptInStatus( false );
// when the list requires a double opt in - flag it here.
if ( $doi ) {
$customer->requireDoubleOptIn( true );
}
return $customer;
} elseif ( $subscriber['status'] === 'pending' ) {
$customer->setOptInStatus( false );
return $customer;
}
$customer->setOptInStatus( $subscriber['status'] === 'subscribed' );
} catch ( Exception $e ) {
// if double opt in is enabled - we need to make a request now that subscribes the customer as pending
// so that the double opt in will actually fire.
if ( $doi && ( ! isset( $subscriber ) || empty( $subscriber ) ) ) {
$customer->requireDoubleOptIn( true );
}
}
}
return $customer;
}
/**
* @param $key
* @param $order_detail
*
* @return MailChimp_WooCommerce_LineItem
*/
protected function transformLineItem( $key, $order_detail ) {
// fire up a new MC line item
$item = new MailChimp_WooCommerce_LineItem();
$item->setId( $key );
// set the fallback title for the order detail name just in case we need to create a product
// from this order item.
$item->setFallbackTitle( $order_detail->get_name() );
$item->setPrice( $order_detail->get_total() );
$item->setProductId( $order_detail->get_product_id() );
$variation_id = $order_detail->get_variation_id();
if ( empty( $variation_id ) ) {
$variation_id = $order_detail->get_product_id();
}
$item->setProductVariantId( $variation_id );
$item->setQuantity( $order_detail->get_quantity() );
if ( $item->getQuantity() > 1 ) {
$current_price = $item->getPrice();
$price = ( $current_price / $item->getQuantity() );
$item->setPrice( $price );
}
return $item;
}
/**
* @param WC_Abstract_Order $order
* @return MailChimp_WooCommerce_Address
*/
public function transformBillingAddress( WC_Abstract_Order $order ) {
// use the info from the order to compile an address.
$address = new MailChimp_WooCommerce_Address();
$address->setAddress1( $order->get_billing_address_1() );
$address->setAddress2( $order->get_billing_address_2() );
$address->setCity( $order->get_billing_city() );
$address->setProvince( $order->get_billing_state() );
$address->setPostalCode( $order->get_billing_postcode() );
$address->setCountry( $order->get_billing_country() );
$address->setPhone( $order->get_billing_phone() );
$bfn = $order->get_billing_first_name();
$bln = $order->get_billing_last_name();
// if we have billing names set it here
if ( ! empty( $bfn ) && ! empty( $bln ) ) {
$address->setName( "{$bfn} {$bln}" );
}
return $address;
}
/**
* @param WC_Abstract_Order $order
* @return MailChimp_WooCommerce_Address
*/
public function transformShippingAddress( WC_Abstract_Order $order ) {
$address = new MailChimp_WooCommerce_Address();
$address->setAddress1( $order->get_shipping_address_1() );
$address->setAddress2( $order->get_shipping_address_2() );
$address->setCity( $order->get_shipping_city() );
$address->setProvince( $order->get_shipping_state() );
$address->setPostalCode( $order->get_shipping_postcode() );
$address->setCountry( $order->get_shipping_country() );
// shipping does not have a phone number, so maybe use this?
$address->setPhone( $order->get_billing_phone() );
$sfn = $order->get_shipping_first_name();
$sln = $order->get_shipping_last_name();
// if we have billing names set it here
if ( ! empty( $sfn ) && ! empty( $sln ) ) {
$address->setName( "{$sfn} {$sln}" );
}
return $address;
}
/**
* @param int $page
* @param int $posts
* @return array|bool
*/
public function getOrderPosts( $page = 1, $posts = 5 ) {
$offset = 0;
if ( $page > 1 ) {
$offset = ( $page - 1 ) * $posts;
}
$params = array(
'post_type' => 'shop_order',
// 'post_status' => array_keys(wc_get_order_statuses()),
'post_status' => 'wc-completed',
'posts_per_page' => $posts,
'offset' => $offset,
'orderby' => 'id',
'order' => 'ASC',
'fields' => 'ids',
);
$orders = get_posts( $params );
if ( empty( $orders ) ) {
sleep( 2 );
$orders = get_posts( $params );
}
return empty( $orders ) ? false : $orders;
}
/**
* @param $order
*
* @return object
* @throws Exception
*/
public function getCustomerOrderTotals( $order ) {
if ( ! function_exists( 'wc_get_orders' ) ) {
return $this->getSingleCustomerOrderTotals( $order->get_user_id() );
}
$orders = wc_get_orders(
array(
'customer' => trim( $order->get_billing_email() ),
)
);
$stats = (object) array(
'count' => 0,
'total' => 0,
);
foreach ( $orders as $order ) {
$order = wc_get_order( $order );
if ( $order->get_status() !== 'cancelled' && ( method_exists( $order, 'is_paid' ) && $order->is_paid() ) ) {
$stats->total += $order->get_total();
$stats->count ++;
}
}
return $stats;
}
/**
* @param $user_id
* @return object
* @throws Exception
*/
protected function getSingleCustomerOrderTotals( $user_id ) {
$customer = new WC_Customer( $user_id );
$customer->get_order_count();
$customer->get_total_spent();
return (object) array(
'count' => $customer->get_order_count(),
'total' => $customer->get_total_spent(),
);
}
/**
* "Pending payment" in the UI fires the order confirmation email MailChimp
* "Completed” in the UI fires the MailChimp Order Invoice
* "Cancelled" does what we think it does
*
* @return array
*/
public function getOrderStatuses() {
return array(
// Order received (unpaid)
'pending' => (object) array(
'financial' => 'pending',
'fulfillment' => null,
),
// Payment received and stock has been reduced – the order is awaiting fulfillment.
// All product orders require processing, except those for digital downloads
'processing' => (object) array(
'financial' => 'pending',
'fulfillment' => null,
),
// Awaiting payment – stock is reduced, but you need to confirm payment
'on-hold' => (object) array(
'financial' => 'on-hold',
'fulfillment' => null,
),
// Order fulfilled and complete – requires no further action
'completed' => (object) array(
'financial' => 'paid',
'fulfillment' => 'fulfilled',
),
// Cancelled by an admin or the customer – no further action required
'cancelled' => (object) array(
'financial' => 'cancelled',
'fulfillment' => null,
),
// Refunded by an admin – no further action required
'refunded' => (object) array(
'financial' => 'refunded',
'fulfillment' => null,
),
// Payment failed or was declined (unpaid). Note that this status may not show immediately and
// instead show as Pending until verified (i.e., PayPal)
'failed' => (object) array(
'financial' => 'failed',
'fulfillment' => null,
),
);
}
}