mirror of
https://github.com/pronamic/woocommerce-subscriptions.git
synced 2025-10-07 10:04:03 +00:00
520 lines
20 KiB
PHP
520 lines
20 KiB
PHP
<?php
|
|
/**
|
|
* WooCommerce Compatibility functions
|
|
*
|
|
* Functions to take advantage of APIs added to new versions of WooCommerce while maintaining backward compatibility.
|
|
*
|
|
* @author Prospress
|
|
* @category Core
|
|
* @package WooCommerce Subscriptions/Functions
|
|
* @version 2.0
|
|
*/
|
|
|
|
if ( ! defined( 'ABSPATH' ) ) {
|
|
exit; // Exit if accessed directly
|
|
}
|
|
|
|
|
|
/**
|
|
* Display a tooltip in the WordPress administration area.
|
|
*
|
|
* Uses wc_help_tip() when WooCommerce 2.5+ is active, otherwise it manually prints the HTML for a tooltip.
|
|
*
|
|
* @param string $tip The content to display in the tooltip.
|
|
* @since 2.1.0
|
|
* @return string
|
|
*/
|
|
function wcs_help_tip( $tip, $allow_html = false ) {
|
|
|
|
if ( function_exists( 'wc_help_tip' ) ) {
|
|
|
|
$help_tip = wc_help_tip( $tip, $allow_html );
|
|
|
|
} else {
|
|
|
|
if ( $allow_html ) {
|
|
$tip = wc_sanitize_tooltip( $tip );
|
|
} else {
|
|
$tip = esc_attr( $tip );
|
|
}
|
|
|
|
$help_tip = sprintf( '<img class="help_tip" data-tip="%s" src="%s/assets/images/help.png" height="16" width="16" />', $tip, esc_url( WC()->plugin_url() ) );
|
|
}
|
|
|
|
return $help_tip;
|
|
}
|
|
|
|
/**
|
|
* Access an object's property in a way that is compatible with CRUD and non-CRUD APIs for different versions of WooCommerce.
|
|
*
|
|
* We don't want to force the use of a custom legacy class for orders, similar to WC_Subscription_Legacy, because 3rd party
|
|
* code may expect the object type to be WC_Order with strict type checks.
|
|
*
|
|
* A note on dates: in WC 3.0+, dates are returned a timestamps in the site's timezone :upside_down_face:. In WC < 3.0, they were
|
|
* returned as MySQL strings in the site's timezone. We return them from here as MySQL strings in UTC timezone because that's how
|
|
* dates are used in Subscriptions in almost all cases, for sanity's sake.
|
|
*
|
|
* @param WC_Order|WC_Product|WC_Subscription $object The object whose property we want to access.
|
|
* @param string $property The property name.
|
|
* @param string $single Whether to return just the first piece of meta data with the given property key, or all meta data.
|
|
* @param mixed $default (optional) The value to return if no value is found - defaults to single -> null, multiple -> array()
|
|
* @since 2.2.0
|
|
* @return mixed
|
|
*/
|
|
function wcs_get_objects_property( $object, $property, $single = 'single', $default = null ) {
|
|
|
|
$prefixed_key = wcs_maybe_prefix_key( $property );
|
|
|
|
$value = ! is_null( $default ) ? $default : ( ( 'single' == $single ) ? null : array() );
|
|
|
|
switch ( $property ) {
|
|
|
|
case 'name' : // the replacement for post_title added in 3.0
|
|
if ( WC_Subscriptions::is_woocommerce_pre( '3.0' ) ) {
|
|
$value = $object->post->post_title;
|
|
} else { // WC 3.0+
|
|
$value = $object->get_name();
|
|
}
|
|
break;
|
|
|
|
case 'post' :
|
|
if ( WC_Subscriptions::is_woocommerce_pre( '3.0' ) ) {
|
|
$value = $object->post;
|
|
} else { // WC 3.0+
|
|
// In order to keep backwards compatibility it's required to use the parent data for variations.
|
|
if ( method_exists( $object, 'is_type' ) && $object->is_type( 'variation' ) ) {
|
|
$value = get_post( $object->get_parent_id() );
|
|
} else {
|
|
$value = get_post( $object->get_id() );
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 'post_status' :
|
|
$value = wcs_get_objects_property( $object, 'post' )->post_status;
|
|
break;
|
|
|
|
case 'parent_id' :
|
|
if ( method_exists( $object, 'get_parent_id' ) ) { // WC 3.0+ or an instance of WC_Product_Subscription_Variation_Legacy with WC < 3.0
|
|
$value = $object->get_parent_id();
|
|
} else { // WC 2.1-2.6
|
|
$value = $object->get_parent();
|
|
}
|
|
break;
|
|
|
|
case 'variation_data' :
|
|
if ( function_exists( 'wc_get_product_variation_attributes' ) ) { // WC 3.0+
|
|
$value = wc_get_product_variation_attributes( $object->get_id() );
|
|
} else {
|
|
$value = $object->$property;
|
|
}
|
|
break;
|
|
|
|
case 'downloads' :
|
|
if ( method_exists( $object, 'get_downloads' ) ) { // WC 3.0+
|
|
$value = $object->get_downloads();
|
|
} else {
|
|
$value = $object->get_files();
|
|
}
|
|
break;
|
|
|
|
case 'order_version' :
|
|
case 'version' :
|
|
if ( method_exists( $object, 'get_version' ) ) { // WC 3.0+
|
|
$value = $object->get_version();
|
|
} else { // WC 2.1-2.6
|
|
$value = $object->order_version;
|
|
}
|
|
break;
|
|
|
|
case 'order_currency' :
|
|
case 'currency' :
|
|
if ( method_exists( $object, 'get_currency' ) ) { // WC 3.0+
|
|
$value = $object->get_currency();
|
|
} else { // WC 2.1-2.6
|
|
$value = $object->get_order_currency();
|
|
}
|
|
break;
|
|
|
|
// Always return a PHP DateTime object in site timezone (or null), the same thing the WC_Order::get_date_created() method returns in WC 3.0+ to make it easier to migrate away from WC < 3.0
|
|
case 'date_created' :
|
|
case 'order_date' :
|
|
case 'date' :
|
|
if ( method_exists( $object, 'get_date_created' ) ) { // WC 3.0+
|
|
$value = $object->get_date_created();
|
|
} else {
|
|
// Base the value off tht GMT value when possible and then set the DateTime's timezone based on the current site's timezone to avoid incorrect values when the timezone has changed
|
|
if ( '0000-00-00 00:00:00' != $object->post->post_date_gmt ) {
|
|
$value = new WC_DateTime( $object->post->post_date_gmt, new DateTimeZone( 'UTC' ) );
|
|
$value->setTimezone( new DateTimeZone( wc_timezone_string() ) );
|
|
} else {
|
|
$value = new WC_DateTime( $object->post->post_date, new DateTimeZone( wc_timezone_string() ) );
|
|
}
|
|
}
|
|
break;
|
|
|
|
// Always return a PHP DateTime object in site timezone (or null), the same thing the getter returns in WC 3.0+ to make it easier to migrate away from WC < 3.0
|
|
case 'date_paid' :
|
|
if ( method_exists( $object, 'get_date_paid' ) ) { // WC 3.0+
|
|
$value = $object->get_date_paid();
|
|
} else {
|
|
if ( ! empty( $object->paid_date ) ) {
|
|
// Because the paid_date post meta value was set in the site timezone at the time it was set, this won't always be correct, but is the best we can do with WC < 3.0
|
|
$value = new WC_DateTime( $object->paid_date, new DateTimeZone( wc_timezone_string() ) );
|
|
} else {
|
|
$value = null;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 'cart_discount' :
|
|
if ( method_exists( $object, 'get_total_discount' ) ) { // WC 3.0+
|
|
$value = $object->get_total_discount();
|
|
} else { // WC 2.1-2.6
|
|
$value = $object->cart_discount;
|
|
}
|
|
break;
|
|
|
|
default :
|
|
|
|
$function_name = 'get_' . $property;
|
|
|
|
if ( is_callable( array( $object, $function_name ) ) ) {
|
|
$value = $object->$function_name();
|
|
} else {
|
|
|
|
// If we don't have a method for this specific property, but we are using WC 3.0, it may be set as meta data on the object so check if we can use that
|
|
if ( method_exists( $object, 'get_meta' ) ) {
|
|
if ( $object->meta_exists( $prefixed_key ) ) {
|
|
if ( 'single' === $single ) {
|
|
$value = $object->get_meta( $prefixed_key, true );
|
|
} else {
|
|
// WC_Data::get_meta() returns an array of stdClass objects with id, key & value properties when meta is available
|
|
$value = wp_list_pluck( $object->get_meta( $prefixed_key, false ), 'value' );
|
|
}
|
|
}
|
|
} elseif ( 'single' === $single && isset( $object->$property ) ) { // WC < 3.0
|
|
$value = $object->$property;
|
|
} elseif ( strtolower( $property ) !== 'id' && metadata_exists( 'post', wcs_get_objects_property( $object, 'id' ), $prefixed_key ) ) {
|
|
// If we couldn't find a property or function, fallback to using post meta as that's what many __get() methods in WC < 3.0 did
|
|
if ( 'single' === $single ) {
|
|
$value = get_post_meta( wcs_get_objects_property( $object, 'id' ), $prefixed_key, true );
|
|
} else {
|
|
// Get all the meta values
|
|
$value = get_post_meta( wcs_get_objects_property( $object, 'id' ), $prefixed_key, false );
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
return $value;
|
|
}
|
|
|
|
/**
|
|
* Set an object's property in a way that is compatible with CRUD and non-CRUD APIs for different versions of WooCommerce.
|
|
*
|
|
* @param WC_Order|WC_Product|WC_Subscription $object The object whose property we want to access.
|
|
* @param string $key The meta key name without '_' prefix
|
|
* @param mixed $value The data to set as the value of the meta
|
|
* @param string $save Whether to write the data to the database or not. Use 'save' to write to the database, anything else to only update it in memory.
|
|
* @param int $meta_id The meta ID of exiting meta data if you wish to overwrite an existing piece of meta.
|
|
* @param bool|string $prefix An optional prefix to add to the $key. Default '_'. Set to boolean false to have no prefix added.
|
|
* @since 2.2.0
|
|
* @return mixed
|
|
*/
|
|
function wcs_set_objects_property( &$object, $key, $value, $save = 'save', $meta_id = '' ) {
|
|
|
|
$prefixed_key = wcs_maybe_prefix_key( $key );
|
|
|
|
// WC will automatically set/update these keys when a shipping/billing address attribute changes so we can ignore these keys
|
|
if ( in_array( $prefixed_key, array( '_shipping_address_index', '_billing_address_index' ) ) ) {
|
|
return;
|
|
}
|
|
|
|
// Special cases where properties with setters which don't map nicely to their function names
|
|
$meta_setters_map = array(
|
|
'_cart_discount' => 'set_discount_total',
|
|
'_cart_discount_tax' => 'set_discount_tax',
|
|
'_customer_user' => 'set_customer_id',
|
|
'_order_tax' => 'set_cart_tax',
|
|
'_order_shipping' => 'set_shipping_total',
|
|
'_sale_price_dates_from' => 'set_date_on_sale_from',
|
|
'_sale_price_dates_to' => 'set_date_on_sale_to',
|
|
);
|
|
|
|
// If we have a 3.0 object with a predefined setter function, use it
|
|
if ( isset( $meta_setters_map[ $prefixed_key ] ) && is_callable( array( $object, $meta_setters_map[ $prefixed_key ] ) ) ) {
|
|
$function = $meta_setters_map[ $prefixed_key ];
|
|
$object->$function( $value );
|
|
|
|
// If we have a 3.0 object, use the setter if available.
|
|
} elseif ( is_callable( array( $object, 'set' . $prefixed_key ) ) ) {
|
|
|
|
// Prices include tax is stored as a boolean in props but saved in the database as a string yes/no, so we need to normalise it here to make sure if we have a string (which can be passed to it by things like wcs_copy_order_meta()) that it's converted to a boolean before being set
|
|
if ( '_prices_include_tax' === $prefixed_key && ! is_bool( $value ) ) {
|
|
$value = 'yes' === $value ? true : false;
|
|
}
|
|
|
|
$object->{ "set$prefixed_key" }( $value );
|
|
|
|
// If there is a setter without the order prefix (eg set_order_total -> set_total)
|
|
} elseif ( is_callable( array( $object, 'set' . str_replace( '_order', '', $prefixed_key ) ) ) ) {
|
|
$function_name = 'set' . str_replace( '_order', '', $prefixed_key );
|
|
$object->$function_name( $value );
|
|
|
|
// If there is no setter, treat as meta within the 3.0.x object.
|
|
} elseif ( is_callable( array( $object, 'update_meta_data' ) ) ) {
|
|
$object->update_meta_data( $prefixed_key, $value, $meta_id );
|
|
|
|
// 2.6.x handling for name which is not meta.
|
|
} elseif ( 'name' === $key ) {
|
|
$object->post->post_title = $value;
|
|
|
|
// 2.6.x handling for everything else.
|
|
} else {
|
|
$object->$key = $value;
|
|
}
|
|
|
|
// Save the data
|
|
if ( 'save' === $save ) {
|
|
if ( is_callable( array( $object, 'save' ) ) ) { // WC 3.0+
|
|
$object->save();
|
|
} elseif ( 'date_created' == $key ) { // WC < 3.0+
|
|
wp_update_post( array( 'ID' => wcs_get_objects_property( $object, 'id' ), 'post_date' => get_date_from_gmt( $value ), 'post_date_gmt' => $value ) );
|
|
} elseif ( 'name' === $key ) { // the replacement for post_title added in 3.0, need to update post_title not post meta
|
|
wp_update_post( array( 'ID' => wcs_get_objects_property( $object, 'id' ), 'post_title' => $value ) );
|
|
} else {
|
|
|
|
if ( ! empty( $meta_id ) ) {
|
|
update_metadata_by_mid( 'post', $meta_id, $value, $prefixed_key );
|
|
} else {
|
|
update_post_meta( wcs_get_objects_property( $object, 'id' ), $prefixed_key, $value );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Delete an object's property in a way that is compatible with CRUD and non-CRUD APIs for different versions of WooCommerce.
|
|
*
|
|
* @param WC_Order|WC_Product|WC_Subscription $object The object whose property we want to access.
|
|
* @param string $key The meta key name without '_' prefix
|
|
* @param mixed $value The data to set as the value of the meta
|
|
* @param string $save Whether to save the data or not, 'save' to save the data, otherwise it won't be saved.
|
|
* @since 2.2.0
|
|
* @return mixed
|
|
*/
|
|
function wcs_delete_objects_property( &$object, $key, $save = 'save', $meta_id = '' ) {
|
|
|
|
$prefixed_key = wcs_maybe_prefix_key( $key );
|
|
|
|
if ( ! empty( $meta_id ) && method_exists( $object, 'delete_meta_data_by_mid' ) ) {
|
|
$object->delete_meta_data_by_mid( $meta_id );
|
|
} elseif ( method_exists( $object, 'delete_meta_data' ) ) {
|
|
$object->delete_meta_data( $prefixed_key );
|
|
} elseif ( isset( $object->$key ) ) {
|
|
unset( $object->$key );
|
|
}
|
|
|
|
// Save the data
|
|
if ( 'save' === $save ) {
|
|
if ( method_exists( $object, 'save' ) ) { // WC 3.0+
|
|
$object->save();
|
|
} elseif ( ! empty( $meta_id ) ) {
|
|
delete_metadata_by_mid( 'post', $meta_id );
|
|
} else {
|
|
delete_post_meta( wcs_get_objects_property( $object, 'id' ), $prefixed_key );
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check whether an order is a standard order (i.e. not a refund or subscription) in version compatible way.
|
|
*
|
|
* WC 3.0 has the $order->get_type() API which returns 'shop_order', while WC < 3.0 provided the $order->order_type
|
|
* property which returned 'simple', so we need to check for both.
|
|
*
|
|
* @param WC_Order $order
|
|
* @since 2.2.0
|
|
* @return bool
|
|
*/
|
|
function wcs_is_order( $order ) {
|
|
|
|
if ( method_exists( $order, 'get_type' ) ) {
|
|
$is_order = ( 'shop_order' === $order->get_type() );
|
|
} else {
|
|
$is_order = ( isset( $order->order_type ) && 'simple' === $order->order_type );
|
|
}
|
|
|
|
return $is_order;
|
|
}
|
|
|
|
/**
|
|
* Find and return the value for a deprecated property property.
|
|
*
|
|
* Product properties should not be accessed directly with WooCommerce 3.0+, because of that, a lot of properties
|
|
* have been deprecated/removed in the subscription product type classes. This function centralises the handling
|
|
* of deriving deprecated properties. This saves duplicating the __get() method in WC_Product_Subscription,
|
|
* WC_Product_Variable_Subscription and WC_Product_Subscription_Variation.
|
|
*
|
|
* @param string $property
|
|
* @param WC_Product $product
|
|
* @since 2.2.0
|
|
* @return mixed
|
|
*/
|
|
function wcs_product_deprecated_property_handler( $property, $product ) {
|
|
|
|
$message_prefix = 'Product properties should not be accessed directly with WooCommerce 3.0+.';
|
|
$function_name = 'get_' . str_replace( 'subscription_', '', str_replace( 'subscription_period_', '', $property ) );
|
|
$class_name = get_class( $product );
|
|
$value = null;
|
|
|
|
if ( in_array( $property, array( 'product_type', 'parent_product_type', 'limit_subscriptions', 'subscription_limit', 'subscription_payment_sync_date', 'subscription_one_time_shipping' ) ) || ( is_callable( array( 'WC_Subscriptions_Product', $function_name ) ) && false !== strpos( $property, 'subscription' ) ) ) {
|
|
|
|
switch ( $property ) {
|
|
case 'product_type':
|
|
$value = $product->get_type();
|
|
$alternative = $class_name . '::get_type()';
|
|
break;
|
|
|
|
case 'parent_product_type':
|
|
if ( $product->is_type( 'subscription_variation' ) ) {
|
|
$value = 'variation';
|
|
$alternative = 'WC_Product_Variation::get_type()';
|
|
} else {
|
|
$value = 'variable';
|
|
$alternative = 'WC_Product_Variable::get_type()';
|
|
}
|
|
break;
|
|
|
|
case 'limit_subscriptions':
|
|
case 'subscription_limit':
|
|
$value = wcs_get_product_limitation( $product );
|
|
$alternative = 'wcs_get_product_limitation( $product )';
|
|
break;
|
|
|
|
case 'subscription_one_time_shipping':
|
|
$value = WC_Subscriptions_Product::needs_one_time_shipping( $product );
|
|
$alternative = 'WC_Subscriptions_Product::needs_one_time_shipping( $product )';
|
|
break;
|
|
|
|
case 'subscription_payment_sync_date':
|
|
$value = WC_Subscriptions_Synchroniser::get_products_payment_day( $product );
|
|
$alternative = 'WC_Subscriptions_Synchroniser::get_products_payment_day( $product )';
|
|
break;
|
|
|
|
case 'max_variation_period':
|
|
case 'max_variation_period_interval':
|
|
$meta_key = '_' . $property;
|
|
if ( '' === $product->get_meta( $meta_key ) ) {
|
|
WC_Product_Variable::sync( $product->get_id() );
|
|
}
|
|
$value = $product->get_meta( $meta_key );
|
|
$alternative = $class_name . '::get_meta( ' . $meta_key . ' ) or wcs_get_min_max_variation_data( $product )';
|
|
break;
|
|
|
|
default:
|
|
$value = call_user_func( array( 'WC_Subscriptions_Product', $function_name ), $product );
|
|
$alternative = sprintf( 'WC_Subscriptions_Product::%s( $product )', $function_name );
|
|
break;
|
|
}
|
|
|
|
wcs_deprecated_argument( $class_name . '::$' . $property, '2.2.0', sprintf( '%s Use %s', $message_prefix, $alternative ) );
|
|
}
|
|
|
|
return $value;
|
|
}
|
|
|
|
/**
|
|
* Access a coupon's property in a way that is compatible with CRUD and non-CRUD APIs for different versions of WooCommerce.
|
|
*
|
|
* Similar to @see wcs_get_objects_property
|
|
*
|
|
* @param WC_Coupon $coupon The coupon whose property we want to access.
|
|
* @param string $property The property name.
|
|
* @since 2.2
|
|
* @return mixed
|
|
*/
|
|
function wcs_get_coupon_property( $coupon, $property ) {
|
|
|
|
$value = '';
|
|
|
|
if ( WC_Subscriptions::is_woocommerce_pre( '3.0' ) ) {
|
|
$value = $coupon->$property;
|
|
} else {
|
|
// Some coupon properties don't map nicely to their corresponding getter function. This array contains those exceptions.
|
|
$property_to_getter_map = array(
|
|
'type' => 'get_discount_type',
|
|
'exclude_product_ids' => 'get_excluded_product_ids',
|
|
'expiry_date' => 'get_date_expires',
|
|
'exclude_product_categories' => 'get_excluded_product_categories',
|
|
'customer_email' => 'get_email_restrictions',
|
|
'enable_free_shipping' => 'get_free_shipping',
|
|
'coupon_amount' => 'get_amount',
|
|
);
|
|
|
|
switch ( true ) {
|
|
case 'exists' == $property:
|
|
$value = ( $coupon->get_id() > 0 ) ? true : false;
|
|
break;
|
|
case isset( $property_to_getter_map[ $property ] ) && is_callable( array( $coupon, $property_to_getter_map[ $property ] ) ):
|
|
$function = $property_to_getter_map[ $property ];
|
|
$value = $coupon->$function();
|
|
break;
|
|
case is_callable( array( $coupon, 'get_' . $property ) ):
|
|
$value = $coupon->{ "get_$property" }();
|
|
break;
|
|
}
|
|
}
|
|
|
|
return $value;
|
|
}
|
|
|
|
/**
|
|
* Set a coupon's property in a way that is compatible with CRUD and non-CRUD APIs for different versions of WooCommerce.
|
|
*
|
|
* Similar to @see wcs_set_objects_property
|
|
*
|
|
* @param WC_Coupon $coupon The coupon whose property we want to set.
|
|
* @param string $property The property name.
|
|
* @param mixed $value The data to set as the value
|
|
* @since 2.2
|
|
*/
|
|
function wcs_set_coupon_property( &$coupon, $property, $value ) {
|
|
|
|
if ( WC_Subscriptions::is_woocommerce_pre( '3.0' ) ) {
|
|
$coupon->$property = $value;
|
|
} else {
|
|
// Some coupon properties don't map nicely to their corresponding setter function. This array contains those exceptions.
|
|
$property_to_setter_map = array(
|
|
'type' => 'set_discount_type',
|
|
'exclude_product_ids' => 'set_excluded_product_ids',
|
|
'expiry_date' => 'set_date_expires',
|
|
'exclude_product_categories' => 'set_excluded_product_categories',
|
|
'customer_email' => 'set_email_restrictions',
|
|
'enable_free_shipping' => 'set_free_shipping',
|
|
'coupon_amount' => 'set_amount',
|
|
);
|
|
|
|
switch ( true ) {
|
|
case 'individual_use' == $property:
|
|
// set_individual_use expects a boolean, the individual_use property use to be either 'yes' or 'no' so we need to accept both types
|
|
if ( ! is_bool( $value ) ) {
|
|
$value = ( 'yes' === $value ) ? true : false;
|
|
}
|
|
|
|
$coupon->set_individual_use( $value );
|
|
break;
|
|
case isset( $property_to_setter_map[ $property ] ) && is_callable( array( $coupon, $property_to_setter_map[ $property ] ) ):
|
|
$function = $property_to_setter_map[ $property ];
|
|
$coupon->$function( $value );
|
|
|
|
break;
|
|
case is_callable( array( $coupon, 'set_' . $property ) ):
|
|
$coupon->{ "set_$property" }( $value );
|
|
break;
|
|
}
|
|
}
|
|
}
|