admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'wc-edm-track-email' ), ); $script = ' jQuery( function( $ ) { var wcEdm = ' . wp_json_encode( $params ) . '; var timer = null; var lastTracked = ""; function trackEmail( email ) { if ( ! email || email.indexOf( "@" ) < 1 || email === lastTracked ) { return; } lastTracked = email; $.post( wcEdm.ajax_url, { action: "wc_edm_track_email", nonce: wcEdm.nonce, email: email }, function() { $( document.body ).trigger( "update_checkout" ); } ); } $( document.body ).on( "keyup change blur", "#billing_email", function() { var email = $( this ).val(); clearTimeout( timer ); timer = setTimeout( function() { trackEmail( email ); }, 600 ); } ); // Track a prefilled email on page load. var initial = $( "#billing_email" ).val(); if ( initial ) { trackEmail( initial ); } } );'; wp_add_inline_script( 'jquery', $script ); } /** * AJAX: store the checkout email in the WooCommerce session so * discounts apply when totals are recalculated. */ public function ajax_track_email() { check_ajax_referer( 'wc-edm-track-email', 'nonce' ); $email = isset( $_POST['email'] ) ? sanitize_email( wp_unslash( $_POST['email'] ) ) : ''; if ( ! WC()->session ) { wp_send_json_error( array( 'message' => 'No session.' ) ); } if ( $email && is_email( $email ) ) { WC()->session->set( self::SESSION_EMAIL_KEY, $email ); } else { WC()->session->set( self::SESSION_EMAIL_KEY, null ); } $this->clear_shipping_cache(); $match = $email ? WC_EDM_Helpers::find_email_match( $email ) : null; wp_send_json_success( array( 'matched' => (bool) $match, ) ); } /** * Clear cached shipping rates so shipping rules re-evaluate. */ private function clear_shipping_cache() { if ( ! WC()->cart || ! WC()->session ) { return; } $packages = WC()->cart->get_shipping_packages(); foreach ( array_keys( $packages ) as $index ) { WC()->session->set( 'shipping_for_package_' . $index, null ); } } /** * Resolve the email address of the current customer. * * Logged-in users use their account email; guests use the tracked * session email or the billing email WooCommerce already knows. * * @return string */ public function get_customer_email() { if ( is_user_logged_in() ) { $user = wp_get_current_user(); if ( $user && $user->user_email ) { return $user->user_email; } } if ( WC()->session ) { $session_email = WC()->session->get( self::SESSION_EMAIL_KEY ); if ( $session_email ) { return $session_email; } } if ( WC()->customer ) { $billing_email = WC()->customer->get_billing_email(); if ( $billing_email ) { return $billing_email; } } return ''; } /** * Get the current customer's email assignment match, if any. * * @return object|null */ private function get_current_match() { $email = $this->get_customer_email(); if ( ! $email ) { return null; } return WC_EDM_Helpers::find_email_match( $email ); } /** * Apply group/custom discounts as negative fees. * * @param WC_Cart $cart Cart object. */ public function apply_discount_fees( $cart ) { if ( is_admin() && ! defined( 'DOING_AJAX' ) ) { return; } $match = $this->get_current_match(); if ( ! $match ) { return; } if ( 'group' === $match->assignment_type ) { $this->apply_group_fee( $cart, $match ); } elseif ( 'custom' === $match->assignment_type ) { $this->apply_custom_fee( $cart, $match ); } } /** * Group discount: percentage off cart items in the group's categories. * * @param WC_Cart $cart Cart object. * @param object $match Email assignment row. */ private function apply_group_fee( $cart, $match ) { if ( ! $match->group_id ) { return; } $group = WC_EDM_DB::get_group( (int) $match->group_id ); if ( ! $group || (float) $group->discount_percent <= 0 ) { return; } $category_ids = WC_EDM_DB::get_group_category_ids( (int) $group->id ); if ( empty( $category_ids ) ) { return; } $eligible_total = 0; foreach ( $cart->get_cart() as $cart_item ) { $product_id = (int) $cart_item['product_id']; // Parent ID for variations. if ( has_term( $category_ids, 'product_cat', $product_id ) ) { $eligible_total += (float) $cart_item['line_total']; } } if ( $eligible_total <= 0 ) { return; } $discount = $eligible_total * ( (float) $group->discount_percent / 100 ); $cart->add_fee( /* translators: %s: discount group name */ sprintf( __( 'Email discount: %s', 'wc-email-discount' ), $group->name ), -$discount, false ); } /** * Custom discount: percentage off the entire cart contents. * * @param WC_Cart $cart Cart object. * @param object $match Email assignment row. */ private function apply_custom_fee( $cart, $match ) { $percent = (float) $match->custom_discount; if ( $percent <= 0 ) { return; } $cart_total = 0; foreach ( $cart->get_cart() as $cart_item ) { $cart_total += (float) $cart_item['line_total']; } if ( $cart_total <= 0 ) { return; } $discount = $cart_total * ( $percent / 100 ); $cart->add_fee( /* translators: %s: discount percent */ sprintf( __( 'Email discount (%s%%)', 'wc-email-discount' ), wc_format_localized_decimal( $percent ) ), -$discount, false ); } /** * Auto-apply a coupon for coupon-type assignments. * * @param WC_Cart $cart Cart object. */ public function maybe_apply_coupon( $cart ) { if ( $this->coupon_applied || ( is_admin() && ! defined( 'DOING_AJAX' ) ) ) { return; } $match = $this->get_current_match(); if ( ! $match || 'coupon' !== $match->assignment_type || empty( $match->coupon_code ) ) { return; } $code = wc_format_coupon_code( $match->coupon_code ); if ( ! wc_get_coupon_id_by_code( $code ) ) { return; } if ( $cart->has_discount( $code ) ) { return; } $this->coupon_applied = true; $cart->apply_coupon( $code ); } /** * Filter shipping rates for the matched email assignment. * * Group assignments get their show/hide rules applied; any assignment * with the free shipping flag gets all remaining rates at zero cost. * * @param array $rates Available shipping rates (rate_id => WC_Shipping_Rate). * @param array $package Shipping package. * @return array */ public function filter_shipping_rates( $rates, $package ) { $match = $this->get_current_match(); if ( ! $match ) { return $rates; } if ( 'group' === $match->assignment_type && $match->group_id ) { $rates = $this->apply_group_shipping_rules( $rates, (int) $match->group_id ); } if ( ! empty( $match->free_shipping ) ) { foreach ( $rates as $rate ) { $rate->set_cost( 0 ); $rate->set_taxes( array() ); } } return $rates; } /** * Apply a group's show/hide shipping rules. * * If the group has no rules, all rates pass through untouched. * "show" rules act as a whitelist; "hide" rules remove rates. * Rates are reordered to follow the rules' sort order. * * @param array $rates Available shipping rates (rate_id => WC_Shipping_Rate). * @param int $group_id Discount group ID. * @return array */ private function apply_group_shipping_rules( $rates, $group_id ) { $rules = WC_EDM_DB::get_shipping_rules( $group_id ); if ( empty( $rules ) ) { return $rates; } $show = array(); $hide = array(); foreach ( $rules as $rule ) { if ( 'show' === $rule->action ) { $show[] = $rule->shipping_method_id; } else { $hide[] = $rule->shipping_method_id; } } $filtered = array(); foreach ( $rates as $rate_id => $rate ) { // Whitelist mode: only explicitly shown methods survive. if ( ! empty( $show ) && ! in_array( $rate_id, $show, true ) ) { continue; } if ( in_array( $rate_id, $hide, true ) ) { continue; } $filtered[ $rate_id ] = $rate; } // Never leave the customer without shipping because of misconfigured rules. if ( empty( $filtered ) ) { return $rates; } // Reorder rates to follow the rules' sort order; unmatched rates keep their position at the end. $rule_order = wp_list_pluck( $rules, 'shipping_method_id' ); $ordered = array(); foreach ( $rule_order as $method_id ) { if ( isset( $filtered[ $method_id ] ) ) { $ordered[ $method_id ] = $filtered[ $method_id ]; unset( $filtered[ $method_id ] ); } } return $ordered + $filtered; } }