0 ) { return $options; } // Re-run zone detection with the store base location. $fake_package = [ 'destination' => [ 'country' => WC()->countries->get_base_country(), 'state' => WC()->countries->get_base_state(), 'postcode' => '', 'city' => '', ], ]; $zone = wc_get_shipping_zone( $fake_package ); $th_sep = wc_get_price_thousand_separator(); $dc_sep = wc_get_price_decimal_separator(); foreach ( $zone->get_shipping_methods( true ) as $method ) { if ( 'free_shipping' !== $method->id ) { continue; } $instance = $method->instance_settings ?? []; $amount = isset( $instance['min_amount'] ) ? $instance['min_amount'] : 0; $requires = isset( $instance['requires'] ) ? $instance['requires'] : ''; $amount = floatval( str_replace( $dc_sep, '.', str_replace( $th_sep, '', $amount ) ) ); // Only use this fallback when the method just needs a minimum order // amount — coupon-gated thresholds can't be shown without a coupon. if ( $amount > 0 && 'min_amount' === $requires ) { $options['amount'] = $amount; $options['requires'] = $requires; $options['shipping'] = true; } break; } return $options; }, 10 ); /** * Main plugin class. */ class CommerceKit_Floating_Cart { private static $instance = null; public static function get_instance(): self { if ( null === self::$instance ) { self::$instance = new self(); } return self::$instance; } private function __construct() { add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_assets' ] ); add_action( 'wp_footer', [ $this, 'render_floating_cart' ] ); add_filter( 'woocommerce_add_to_cart_fragments', [ $this, 'cart_count_fragment' ] ); // Disable WooCommerce's "redirect to cart page after add" so the page // stays put and we can open the minicart instead. add_filter( 'pre_option_woocommerce_cart_redirect_after_add', [ $this, 'disable_cart_redirect' ] ); // Ensure WooCommerce's AJAX add-to-cart is active so archive/shop page // buttons fire the `added_to_cart` JS event without a page reload. add_filter( 'pre_option_woocommerce_enable_ajax_add_to_cart', [ $this, 'enable_ajax_add_to_cart' ] ); } /** * Return 'no' so WooCommerce never redirects to /cart after adding a product. */ public function disable_cart_redirect( string $value ): string { return 'no'; } /** * Return 'yes' so WooCommerce uses AJAX add-to-cart on archive/shop pages. */ public function enable_ajax_add_to_cart( string $value ): string { return ( 'yes' === $value ) ? $value : 'yes'; } /** * Enqueue frontend CSS and JS. */ public function enqueue_assets(): void { wp_enqueue_style( 'cgkit-floating-cart', CGKIT_FC_PLUGIN_URL . 'assets/css/floating-cart.css', [], CGKIT_FC_VERSION ); wp_enqueue_script( 'cgkit-floating-cart', CGKIT_FC_PLUGIN_URL . 'assets/js/floating-cart.js', [ 'jquery' ], CGKIT_FC_VERSION, true ); /** * Filter: cgkit_fc_minicart_trigger * CSS selector(s) for the theme's minicart open button. */ $trigger = apply_filters( 'cgkit_fc_minicart_trigger', '.shoptimizer-cart .cart-contents, .site-header-cart .cart-contents, .wcmenucart, .header-cart-link' ); /** Filter: cgkit_fc_auto_open — set false to disable auto-open after add. */ $auto_open = (bool) apply_filters( 'cgkit_fc_auto_open', true ); /** Filter: cgkit_fc_auto_open_delay — ms to wait before opening (default 400). */ $delay = (int) apply_filters( 'cgkit_fc_auto_open_delay', 400 ); wp_localize_script( 'cgkit-floating-cart', 'cgkitFC', [ 'minicartTrigger' => $trigger, 'autoOpen' => $auto_open ? 'yes' : 'no', 'autoOpenDelay' => $delay, 'cartUrl' => wc_get_cart_url(), // WooCommerce AJAX endpoint used for single-product AJAX add-to-cart. 'wcAjaxUrl' => WC_AJAX::get_endpoint( '%%endpoint%%' ), ] ); } /** * Output the floating cart button HTML in the footer. */ public function render_floating_cart(): void { $count = WC()->cart ? WC()->cart->get_cart_contents_count() : 0; ?>