Our goal this time is to have the WooCommerce Deposits extension work with both WooCommerce versions 2.6.x and 2.7.x, display no notices, and use the CRUD abstraction to future-proof our code. Like in our original 2.7 extension compatibility post, I used the WordPress debug log and the Query Monitor plugin to any errors and deprecated notices.

The Deposits extension interacts with products, orders, and order line items to make it’s functionality work. All of these data types have been updated in 2.7 to use the new CRUD system. After using the debugging tools to find various notices and functions that needed updating, I also began to read through the code to uncover what would else would need updated. I discovered meta calls and direct queries on the wp_posts table. There were a number of issues to update since Deposits is a more complex extension than the Shipment Tracking extension I previous looked at.

Luckily some of the updates needed were exactly like the ones I made to the Shipment Tracking extension, so I was able to quickly identify and update the code. This included things like switching $order->id (direct parameter access) to use a version safe call: $order->get_id() for 2.7 and $order->id for 2.6.

On top of the changes were other order properties that have been deprecated or renamed. For example, a function that runs when a payment is complete looks at the order status. It made a call to $order->post_status. Since all direct property access is deprecated in 2.7, we need to use a getter. The closest getter to $order->post_status is $order->get_status(), but there is one difference to note: $order->post_status contains a wc- prefix and $order->get_status() does not. The lesson here is to double check the code you are updating so that things like conditionals are looking for the correct return value.

Another update that I needed to make related to WC_Order was around function renaming. In 2.6 and below, WC_Order had a redundantly named get_order_currency() method. In 2.7, this was renamed to a get_currency() method on WC_Order.

I solved this by using an is_callable check and storing the value in a variable for later use:

$currency = is_callable( array( $order, 'get_currency' ) ) ? $order->get_currency() : $order->get_order_currency();

I also discovered a number of direct queries to the wp_posts. I was able to replace all of these with WooCommerce provided functions in 2.7.

One query looked for “related” orders, which were defined as orders with a certain parent ID set. Luckily, there was a function introduced in 2.6 called wc_get_orders() that we can use to do the same thing:

The original function:

public static function get_related_orders( $order_id ) {
	global $wpdb;
	$order_ids    = array();
	$found_orders = $wpdb->get_col( $wpdb->prepare( "SELECT ID FROM {$wpdb->posts} WHERE post_parent = %d AND post_status NOT IN ( 'draft', 'autodraft', 'trash' );", $order_id ) );
	foreach ( $found_orders as $found_order ) {
		if ( 'wc_deposits' === get_post_meta( $found_order, '_created_via', true ) ) {
			$order_ids[] = $found_order;
		}
	}
	return $order_ids;
}

The updated function:

public static function get_related_orders( $order_id ) {
	$order_ids    = array();
	$found_orders = wc_get_orders( array( 'parent' => $order_id ) );
	if ( version_compare( WC_VERSION, '2.7', '<' ) ) { 		foreach ( $found_orders as $found_order ) { 			if ( 'wc_deposits' === $found_order->created_via ) {
				$order_ids[] = $found_order->id;
			}
		}
	} else {
		foreach ( $found_orders as $found_order ) {
			if ( 'wc_deposits' === $found_order->get_created_via() ) {
				$order_ids[] = $found_order->get_id();
			}
		}
	}
	return $order_ids;
}

While we can use wc_get_orders() in both versions, a version check is still needed so we can use the correct getters and setters for 2.7.

Another query in the extension looked for orders made before a certain date with a certain order status — the function looked for scheduled orders. 2.7 adds before and after arguments to wc_get_orders, so I was able to replace the query for 2.7 using those arguments. In this case, I do have to keep the query around in a version check until we stop supporting 2.6.

In addition to the changes around WC_Order, I also had to update logic around WC_Product.

Luckily, in the instance of ID and get_id() calls, WC_Product has had a get_id() method since WooCommerce 2.5. I was able to update calls to $product->id with $product->get_id() without having to do version check or an is_callable check.

However, I did have to do some version specific code around some of the other WC_Product methods. For example the get_price_including_tax() and get_price_excluding_tax() methods have been deprecated. In 2.7, you can use the functions wc_get_price_including_tax(); and wc_get_price_excluding_tax(); instead.

There were a number of things calling those methods, so I created helper functions in the classes that needed it, and had that handle the version check for me:

/**
 * Provides a way to support both 2.6 and 2.7 since get_price_including_tax
 * gets deprecated in 2.7, and wc_get_price_including_tax gets introduced in
 * 2.7.
 *
 * @since  1.2
 * @param  WC_Product $product
 * @param  array      $args
 * @return float
 */
private function get_price_including_tax( $product, $args ) {
	if ( version_compare( WC_VERSION, '2.7', '<' ) ) { 		$args = wp_parse_args( $args, array( 			'qty'   => '',
			'price' => '',
		) );
		return $product->get_price_including_tax( $args['qty'], $args['price'] );
	} else {
		return wc_get_price_including_tax( $product, $args );
	}
}
/**
 * Provides a way to support both 2.6 and 2.7 since get_price_excluding_tax
 * gets deprecated in 2.7, and wc_get_price_excluding_tax gets introduced in
 * 2.7.
 *
 * @since  1.2
 * @param  WC_Product $product
 * @param  array      $args
 * @return float
 */
private function get_price_excluding_tax( $product, $args ) {
	if ( version_compare( WC_VERSION, '2.7', '<' ) ) { 		$args = wp_parse_args( $args, array( 			'qty'   => '',
			'price' => '',
		) );
		return $product->get_price_excluding_tax( $args['qty'], $args['price'] );
	} else {
		return wc_get_price_excluding_tax( $product, $args );
	}
}

Most of the calls that get and set extra meta present in the extension were also related to products. Since get_post_meta() and update_post_meta() were used all over the extension instead of just one or two files, I decided to create an entire helper class for getting and setting product meta. This class would work for both versions (and can later be dropped when we don’t have to support 2.6 anymore). I created a class named WC_Deposits_Product_Meta with get_meta() and update_meta methods. Using version checks, these functions will either use the post specific get_post_meta() and update_post_meta() functions, or the new methods present in 2.7. View the full gist for what this class looks like.

One final example I would like to share is around order item meta. In the previous version of the deposits extension, we used the woocommerce_add_order_item_meta hook to add certain information to an order item: Is this item a deposit? What’s the full amount of the deposit? What payment plan was selected?

This hook was deprecated in 2.7 and a new hook named woocommerce_checkout_create_order_line_item has been added. There is one difference to note, preventing us from just switching the hook over:

When woocommerce_checkout_create_order_line_item runs, the item has not been saved yet, so we do not have an item ID. If we had an item ID, we could continue using wc_add_order_item_meta() to set meta. What this hook does give us is access to the WC_Order_Item CRUD object instead. Since this is a full CRUD object, we have access to WC_Data‘s add_meta_data() method.

I ended up having a legacy function and a function for the new code, and hooking these in depending on versions:

WC_Deposit_Cart_Manager::__construct():

if ( version_compare( WC_VERSION, '2.7', '<' ) ) { 	add_action( 'woocommerce_add_order_item_meta', array( $this, 'add_order_item_meta_legacy' ), 50, 2 ); } else { 	add_action( 'woocommerce_checkout_create_order_line_item', array( $this, 'add_order_item_meta' ), 50, 3 ); } 
 /**  * Store cart info inside new orders.  * Runs on 2.6 and older.  *  * @param mixed $item_id  * @param mixed $cart_item  */ public function add_order_item_meta_legacy( $item_id, $cart_item ) { 	if ( ! empty( $cart_item['is_deposit'] ) ) { 		wc_add_order_item_meta( $item_id, '_is_deposit', 'yes' ); 		wc_add_order_item_meta( $item_id, '_deposit_full_amount', $cart_item['data']->get_price_including_tax( $cart_item['quantity'], $cart_item['full_amount'] ) );
		wc_add_order_item_meta( $item_id, '_deposit_full_amount_ex_tax', $cart_item['data']->get_price_excluding_tax( $cart_item['quantity'], $cart_item['full_amount'] ) );
		if ( ! empty( $cart_item['payment_plan'] ) ) {
			wc_add_order_item_meta( $item_id, '_payment_plan', $cart_item['payment_plan'] );
		}
	}
}

/**
 * Store cart info inside new orders.
 *
 * @param WC_Order_Item $item
 * @param string        $cart_item_key
 * @param array         $values
 */
public function add_order_item_meta( $item, $cart_item_key, $values ) {
	$cart      = WC()->cart->get_cart();
	$cart_item = $cart[ $cart_item_key ];
	if ( ! empty( $cart_item['is_deposit'] ) ) {
		$item->add_meta_data( '_is_deposit', 'yes' );
		$item->add_meta_data( '_deposit_full_amount', $this->get_price_including_tax( $cart_item['data'], array( 'qty' => $cart_item['quantity'], 'price' => $cart_item['full_amount'] ) ) );
		$item->add_meta_data( '_deposit_full_amount_ex_tax', $this->get_price_excluding_tax( $cart_item['data'], array( 'qty' => $cart_item['quantity'], 'price' => $cart_item['full_amount'] ) ) );
		if ( ! empty( $cart_item['payment_plan'] ) ) {
			$item->add_meta_data( '_payment_plan', $cart_item['payment_plan'] );
		}
	}
}

I continued making these changes around the extension until no more deprecated notices showed, and I did one final sweep for meta calls or direct queries. I then re-tested my extension on 2.7 and 2.6 to make sure everything worked correctly. WooCommerce Deposits is now compatible with both versions!