*/ private $coupon_before = array(); /** * Previous product price fingerprints. * * @var array */ private $product_before = array(); /** * @var FreshnessService */ private $freshness; /** * @param FreshnessService|null $freshness Service. */ public function __construct( $freshness = null ) { $this->freshness = $freshness ? $freshness : new FreshnessService(); } /** * @return void */ public function register() { add_action( 'admin_init', array( $this, 'boot_verification_check' ), 5 ); add_action( 'woocommerce_before_coupon_object_save', array( $this, 'capture_coupon_before' ), 10, 1 ); add_action( 'woocommerce_coupon_object_updated_props', array( $this, 'on_coupon_updated_props' ), 10, 2 ); // Fallback for older save paths / admin list edits. add_action( 'woocommerce_update_coupon', array( $this, 'on_coupon_id_saved' ), 20, 1 ); add_action( 'woocommerce_before_product_object_save', array( $this, 'capture_product_before' ), 10, 1 ); add_action( 'woocommerce_update_product', array( $this, 'on_product_id_saved' ), 20, 1 ); add_action( 'woocommerce_update_product_variation', array( $this, 'on_product_id_saved' ), 20, 1 ); } /** * @return void */ public function boot_verification_check() { $this->freshness->maybe_mark_needs_verification(); } /** * @param \WC_Coupon $coupon Coupon. * @return void */ public function capture_coupon_before( $coupon ) { if ( ! is_object( $coupon ) || ! method_exists( $coupon, 'get_id' ) ) { return; } $id = (int) $coupon->get_id(); if ( $id <= 0 ) { return; } $this->coupon_before[ $id ] = CouponMaterialHasher::hash_coupon( $coupon ); } /** * @param \WC_Coupon $coupon Coupon. * @param array $updated_props Props. * @return void */ public function on_coupon_updated_props( $coupon, $updated_props ) { if ( ! is_object( $coupon ) || ! method_exists( $coupon, 'get_id' ) ) { return; } $id = (int) $coupon->get_id(); if ( $id <= 0 ) { return; } $after = CouponMaterialHasher::hash_coupon( $coupon ); $before = isset( $this->coupon_before[ $id ] ) ? $this->coupon_before[ $id ] : ''; if ( '' !== $before && hash_equals( $before, $after ) ) { return; } // First save without before snapshot, or material hash changed. if ( '' === $before || ! hash_equals( $before, $after ) ) { $this->freshness->on_coupon_material_change( $id ); } } /** * @param int $coupon_id Coupon ID. * @return void */ public function on_coupon_id_saved( $coupon_id ) { $coupon_id = (int) $coupon_id; if ( $coupon_id <= 0 || ! class_exists( '\WC_Coupon' ) ) { return; } // If CRUD path already handled this ID in-request, skip duplicate. if ( isset( $this->coupon_before[ $coupon_id ] ) ) { return; } $this->freshness->on_coupon_material_change( $coupon_id ); } /** * @param \WC_Product $product Product. * @return void */ public function capture_product_before( $product ) { if ( ! is_object( $product ) || ! method_exists( $product, 'get_id' ) ) { return; } $id = (int) $product->get_id(); if ( $id <= 0 ) { return; } $this->product_before[ $id ] = $this->product_price_fingerprint( $product ); } /** * @param int $product_id Product or variation ID. * @return void */ public function on_product_id_saved( $product_id ) { $product_id = (int) $product_id; if ( $product_id <= 0 || ! function_exists( 'wc_get_product' ) ) { return; } $product = wc_get_product( $product_id ); if ( ! $product ) { return; } $after = $this->product_price_fingerprint( $product ); $before = isset( $this->product_before[ $product_id ] ) ? $this->product_before[ $product_id ] : ''; if ( '' !== $before && hash_equals( $before, $after ) ) { return; } $parent = (int) $product->get_parent_id(); $this->freshness->on_product_material_change( $parent > 0 ? $parent : $product_id, $parent > 0 ? $product_id : 0 ); } /** * Manual COGS meta change (called from ProductCostFields). * * @param int $product_id Product or variation ID. * @return void */ public function on_manual_cost_change( $product_id ) { $product_id = (int) $product_id; if ( $product_id <= 0 || ! function_exists( 'wc_get_product' ) ) { return; } $product = wc_get_product( $product_id ); $parent = $product ? (int) $product->get_parent_id() : 0; $this->freshness->on_product_material_change( $parent > 0 ? $parent : $product_id, $parent > 0 ? $product_id : 0 ); } /** * Price-related fingerprint (material for preflight). * * @param \WC_Product $product Product. * @return string */ private function product_price_fingerprint( $product ) { $payload = array( 'regular' => (string) $product->get_regular_price(), 'sale' => (string) $product->get_sale_price(), 'price' => (string) $product->get_price(), ); return hash( 'sha256', (string) wp_json_encode( $payload ) ); } }