File "class-mailchimp-woocommerce-transform-orders-wc3.php"

Full Path: /home/vantageo/public_html/cache/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,
			),
		);
	}
}