fix: FSN for guests via option filter, AJAX add-to-cart on single product pages

This commit is contained in:
2026-03-05 08:38:04 +01:00
parent 99e83931eb
commit f8081c6b65
2 changed files with 180 additions and 151 deletions

View File

@@ -3,7 +3,7 @@
* Plugin Name: CommerceKit Floating Cart
* Plugin URI: https://www.commercegurus.com
* Description: Adds a floating cart icon (bottom-right) and auto-opens the CommerceKit minicart after add to cart. Requires CommerceGurus CommerceKit and WooCommerce.
* Version: 1.0.4
* Version: 1.1.0
* Author: CommerceGurus
* Author URI: https://www.commercegurus.com
* License: GPLv3
@@ -19,7 +19,7 @@ if ( ! defined( 'ABSPATH' ) ) {
exit;
}
define( 'CGKIT_FC_VERSION', '1.0.4' );
define( 'CGKIT_FC_VERSION', '1.1.0' );
define( 'CGKIT_FC_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
define( 'CGKIT_FC_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
@@ -32,6 +32,30 @@ add_action( 'before_woocommerce_init', function () {
}
} );
/**
* Force CommerceKit's "show FSN before shipping address is entered" flag on.
*
* Registered early (plugins_loaded priority 5) so it is in place before
* CommerceKit reads the option at plugins_loaded priority 10+.
*
* Why here rather than in the class: this filter must be attached BEFORE
* WooCommerce and CommerceKit finish loading so the very first option read
* during any request already sees fsn_before_ship = 1.
*
* What fsn_before_ship does inside CommerceKit:
* if ( ! $fsn_before_ship ) {
* if ( ! $show_shipping ) { return ''; } // bails for guests
* }
* Setting it to 1 skips that entire block, so the notification renders
* regardless of whether WooCommerce has calculated shipping yet.
*/
add_filter( 'option_commercekit', function ( $options ) {
if ( is_array( $options ) ) {
$options['fsn_before_ship'] = 1;
}
return $options;
}, 1 );
/**
* Main plugin class.
*/
@@ -55,40 +79,9 @@ class CommerceKit_Floating_Cart {
// 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 the body receives
// the `added_to_cart` JS event that our script listens for.
// 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' ] );
// Show the CommerceKit free shipping notification for guests.
// WC's show_shipping() returns false for logged-out users when the store
// hides shipping costs until an address is entered, which causes CommerceKit
// to skip rendering the bar entirely. We force shipping => true for any
// non-empty cart so the notification always renders.
add_filter( 'commercekit_fsn_get_cart_options', [ $this, 'fsn_show_for_guests' ] );
}
/**
* Force the CommerceKit free shipping notification to render for logged-out
* users. WC_Cart::show_shipping() returns false for guests when the store
* option "Hide shipping costs until an address is entered" is active, which
* makes CommerceKit bail before building the bar HTML. Overriding the
* `shipping` key here only affects the FSN display logic — WooCommerce's
* actual shipping calculation is untouched.
*
* @param array $options Result from commercekit_fsn_get_cart_options().
* @return array
*/
public function fsn_show_for_guests( array $options ): array {
if ( ! isset( $options['shipping'] ) || $options['shipping'] ) {
return $options; // already showing, nothing to do.
}
$cart = WC()->cart;
if ( $cart && $cart->get_cart_contents_count() > 0 && $options['amount'] > 0 ) {
$options['shipping'] = true;
}
return $options;
}
/**
@@ -100,10 +93,8 @@ class CommerceKit_Floating_Cart {
/**
* Return 'yes' so WooCommerce uses AJAX add-to-cart on archive/shop pages.
* This ensures `added_to_cart` JS event fires without a page reload.
*/
public function enable_ajax_add_to_cart( string $value ): string {
// Only override when it's not already enabled.
return ( 'yes' === $value ) ? $value : 'yes';
}
@@ -128,39 +119,17 @@ class CommerceKit_Floating_Cart {
/**
* Filter: cgkit_fc_minicart_trigger
*
* CSS selector(s) for the theme's minicart open button. The floating
* cart icon and the add-to-cart auto-open both programmatically click
* the first matching element.
*
* For Shoptimizer / CommerceKit the default covers the most common
* header cart selectors. Override this filter in your child theme if
* needed.
*
* @param string $selector A comma-separated CSS selector string.
* 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 to false to disable auto-opening the minicart after add to cart.
*
* @param bool $auto_open
*/
/** 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
*
* Milliseconds to wait before opening the minicart after add to cart,
* to allow WooCommerce fragments to refresh first.
*
* @param int $delay
*/
/** 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', [
@@ -168,6 +137,8 @@ class CommerceKit_Floating_Cart {
'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%%' ),
] );
}
@@ -187,15 +158,11 @@ class CommerceKit_Floating_Cart {
}
/**
* Provide a WooCommerce fragment so the badge count updates via AJAX
* (e.g. after mini-cart quantity changes or AJAX add-to-cart).
*
* @param array $fragments
* @return array
* WooCommerce fragment — keeps the badge count live after AJAX operations.
*/
public function cart_count_fragment( array $fragments ): array {
$count = WC()->cart ? WC()->cart->get_cart_contents_count() : 0;
$fragments['#cgkit-fc-count'] = $this->count_badge_html( $count );
$count = WC()->cart ? WC()->cart->get_cart_contents_count() : 0;
$fragments['#cgkit-fc-count'] = $this->count_badge_html( $count );
return $fragments;
}
@@ -203,9 +170,6 @@ class CommerceKit_Floating_Cart {
// Private helpers
// -------------------------------------------------------------------------
/**
* Return the count badge HTML (targeted by fragments).
*/
private function count_badge_html( int $count ): string {
$hidden = 0 === $count ? ' cgkit-floating-cart__count--hidden' : '';
return sprintf(
@@ -215,9 +179,6 @@ class CommerceKit_Floating_Cart {
);
}
/**
* Shopping-cart SVG icon (Feather icons style, matches CommerceKit aesthetic).
*/
private function cart_icon_svg(): string {
return '<svg class="cgkit-floating-cart__icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"
width="24" height="24" aria-hidden="true" focusable="false"