I need to add a meta_data field to a coupon of a third party plugin, dynamically when it’s loaded. WooCommerce uses the filter woocommerce_get_shop_coupon_data
so another plugin can react to it.
In class-wc-cart.php the coupon is returned like this:
foreach ( $this->get_applied_coupons() as $code ) { $coupon = new WC_Coupon( $code ); $coupons[ $code ] = $coupon; }
$code
is the coupon code as string. So in class-wc-coupon.php (WC_Coupon class) it will apply the filter woocommerce_get_shop_coupon_data
and set the properties based on the array fields which I return like so:
public function __construct( $data = '' ) { parent::__construct( $data ); // If we already have a coupon object, read it again. if ( $data instanceof WC_Coupon ) { $this->set_id( absint( $data->get_id() ) ); $this->read_object_from_database(); return; } // This filter allows custom coupon objects to be created on the fly. $coupon = apply_filters( 'woocommerce_get_shop_coupon_data', false, $data, $this ); if ( $coupon ) { $this->read_manual_coupon( $data, $coupon ); return; } // ... here it continues to build it as own WooCommerce coupon, not relevant } /** * Developers can programmatically return coupons. This function will read those values into our WC_Coupon class. * * @since 3.0.0 * @param string $code Coupon code. * @param array $coupon Array of coupon properties. */ public function read_manual_coupon( $code, $coupon ) { foreach ( $coupon as $key => $value ) { switch ( $key ) { case 'excluded_product_ids': case 'exclude_product_ids': if ( ! is_array( $coupon[ $key ] ) ) { wc_doing_it_wrong( $key, $key . ' should be an array instead of a string.', '3.0' ); $coupon['excluded_product_ids'] = wc_string_to_array( $value ); } break; case 'exclude_product_categories': case 'excluded_product_categories': if ( ! is_array( $coupon[ $key ] ) ) { wc_doing_it_wrong( $key, $key . ' should be an array instead of a string.', '3.0' ); $coupon['excluded_product_categories'] = wc_string_to_array( $value ); } break; case 'product_ids': if ( ! is_array( $coupon[ $key ] ) ) { wc_doing_it_wrong( $key, $key . ' should be an array instead of a string.', '3.0' ); $coupon[ $key ] = wc_string_to_array( $value ); } break; case 'individual_use': case 'free_shipping': case 'exclude_sale_items': if ( ! is_bool( $coupon[ $key ] ) ) { wc_doing_it_wrong( $key, $key . ' should be true or false instead of yes or no.', '3.0' ); $coupon[ $key ] = wc_string_to_bool( $value ); } break; case 'expiry_date': $coupon['date_expires'] = $value; break; } } $this->set_props( $coupon ); // <- this will map the coupon data to the WC_Coupon object $this->set_code( $code ); $this->set_id( 0 ); $this->set_virtual( true ); }
This is all code from WooCommerce. The external plugin will provide own coupons by adding a function to the woocommerce_get_shop_coupon_data
filter and I wanted to set some post_meta
data to extend it properly, like so:
add_filter( 'woocommerce_get_shop_coupon_data', function( $return_val ) { if ( is_array ( $return_val ) && array_key_exists ( 'id', $return_val ) ) { // id will be set for the custom coupons $return_val['meta_data'] = [ 'example_key' => 'example_value' ]; } return $return_val; }, 1000 );
My custom meta data will be extended onto the coupon. Eventually, it will reach this line in class-wc-coupon.php as shown in the 2nd code example of my question:
$this->set_props( $coupon );
and its implementation is in abstract-wc-data.php:
/** * Set a collection of props in one go, collect any errors, and return the result. * Only sets using public methods. * * @since 3.0.0 * * @param array $props Key value pairs to set. Key is the prop and should map to a setter function name. * @param string $context In what context to run this. * * @return bool|WP_Error */ public function set_props( $props, $context = 'set' ) { $errors = false; foreach ( $props as $prop => $value ) { try { /** * Checks if the prop being set is allowed, and the value is not null. */ if ( is_null( $value ) || in_array( $prop, array( 'prop', 'date_prop', 'meta_data' ), true ) ) { continue; } $setter = "set_$prop"; if ( is_callable( array( $this, $setter ) ) ) { $this->{$setter}( $value ); } } catch ( WC_Data_Exception $e ) { if ( ! $errors ) { $errors = new WP_Error(); } $errors->add( $e->getErrorCode(), $e->getMessage() ); } } return $errors && count( $errors->get_error_codes() ) ? $errors : true; }
And here lies the problem, this function will not map meta_data
to the meta_data
property of WC_Coupon (type: array of WC_Meta_Data), so I’m stuck. I need a solution that won’t modify any third party files.
Is there any way?
Advertisement
Answer
I came to the conclusion that it’s simply not possible. However, the support of the third-party plugin who reads the post meta fields from the coupon added a filter for us, so we can hook in and simply tell their plugin that we wish that functionality that was triggered by the post meta field.
The issue is solved for my particular case, however I suggested a feature request to allow post meta fields for dynamic coupons in WooCommerce.