Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save xlplugins/f2be7958f513b2f628182c576bc52da6 to your computer and use it in GitHub Desktop.

Select an option

Save xlplugins/f2be7958f513b2f628182c576bc52da6 to your computer and use it in GitHub Desktop.
Funnelkit Load Elementor widget CSS files in header instead of footer
class WFACP_Elementor_Widget_CSS_Header_Loader {
/**
* Singleton instance
*
* @var WFACP_Elementor_Widget_CSS_Header_Loader|null
*/
private static $instance = null;
/**
* Get singleton instance
*
* @return WFACP_Elementor_Widget_CSS_Header_Loader
*/
public static function get_instance() {
if ( null === self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Store styles that need to be moved to header
*
* @var array
*/
private static $styles_to_move = [];
/**
* Track if styles were already in header
*
* @var bool
*/
private $styles_already_in_header = false;
/**
* All Elementor widget styles (dynamically detected)
*
* @var array
*/
private $widget_styles = [];
/**
* Constructor
*/
private function __construct() {
// Only initialize hooks when WFACP checkout page is found
// This ensures the fix only runs on FunnelKit checkout pages
add_action( 'wfacp_after_checkout_page_found', [ $this, 'init_hooks' ], 5 );
}
/**
* Initialize all hooks when WFACP checkout page is found
*
* This method runs when wfacp_after_checkout_page_found fires,
* ensuring all hooks are set up only for FunnelKit checkout pages
*/
public function init_hooks() {
// Check if Elementor is active
if ( ! class_exists( '\Elementor\Plugin' ) ) {
return;
}
// Get all Elementor widget styles dynamically
$this->get_all_elementor_widget_styles();
// If no widget styles found, don't proceed
if ( empty( $this->widget_styles ) ) {
return;
}
// Check if styles are already in header (after wp_head)
add_action( 'wp_head', [ $this, 'check_styles_in_header' ], 999 );
// Hook VERY early (priority 1) to enqueue before Elementor (priority 20)
// This ensures styles are registered and enqueued in header
add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_widget_css_in_header' ], 1 );
// Also hook into wp_head as backup to catch any missed styles
add_action( 'wp_head', [ $this, 'ensure_styles_in_header' ], 1 );
// Prevent styles from being output in footer (only if needed)
add_action( 'wp_footer', [ $this, 'prevent_footer_styles' ], 1 );
// Intercept and capture footer style tags (only if needed)
add_filter( 'style_loader_tag', [ $this, 'capture_footer_styles' ], 999, 2 );
// Also enqueue immediately when checkout page is found (in case wp_enqueue_scripts already fired)
$this->enqueue_widget_css_in_header();
}
/**
* Get all Elementor widget styles dynamically
*
* This detects all widget CSS files that Elementor registers
*/
private function get_all_elementor_widget_styles() {
if ( ! class_exists( '\Elementor\Plugin' ) ) {
return;
}
$widget_styles = [];
// Get widgets with styles
if ( method_exists( \Elementor\Plugin::$instance->widgets_manager, 'widgets_with_styles' ) ) {
$widgets_with_styles = \Elementor\Plugin::$instance->widgets_manager->widgets_with_styles();
foreach ( $widgets_with_styles as $widget_name ) {
$widget_styles[] = 'widget-' . $widget_name;
}
}
// Get widgets with responsive styles
if ( method_exists( \Elementor\Plugin::$instance->widgets_manager, 'widgets_with_responsive_styles' ) ) {
$widgets_with_responsive_styles = \Elementor\Plugin::$instance->widgets_manager->widgets_with_responsive_styles();
foreach ( $widgets_with_responsive_styles as $widget_name ) {
$handle = 'widget-' . $widget_name;
// Avoid duplicates
if ( ! in_array( $handle, $widget_styles, true ) ) {
$widget_styles[] = $handle;
}
}
}
// Also check registered styles that start with 'widget-'
global $wp_styles;
if ( isset( $wp_styles->registered ) ) {
foreach ( $wp_styles->registered as $handle => $style ) {
if ( strpos( $handle, 'widget-' ) === 0 && strpos( $style->src, 'elementor' ) !== false ) {
if ( ! in_array( $handle, $widget_styles, true ) ) {
$widget_styles[] = $handle;
}
}
}
}
$this->widget_styles = $widget_styles;
}
/**
* Check if styles are already in header
*
* This runs at the end of wp_head to detect if styles were already output
*/
public function check_styles_in_header() {
global $wp_styles;
if ( ! isset( $wp_styles->done ) ) {
return;
}
// Check if any of our widget styles are already in the done array
// (meaning they were already output in header)
$found_in_header = false;
foreach ( $this->widget_styles as $handle ) {
if ( in_array( $handle, $wp_styles->done, true ) ) {
$found_in_header = true;
break;
}
}
$this->styles_already_in_header = $found_in_header;
}
/**
* Enqueue Elementor widget CSS files in header
*
* This runs at priority 1 on wp_enqueue_scripts, which is BEFORE Elementor's
* enqueue_styles (priority 20). This ensures our styles are enqueued first.
*
* Only runs if styles are NOT already in header (detected in check_styles_in_header)
*/
public function enqueue_widget_css_in_header() {
// Only run if Elementor is active
if ( ! class_exists( '\Elementor\Plugin' ) ) {
return;
}
// Skip if styles are already in header (no fix needed)
if ( $this->styles_already_in_header ) {
return;
}
// Get all widget styles if not already loaded
if ( empty( $this->widget_styles ) ) {
$this->get_all_elementor_widget_styles();
}
// Get Elementor version
$version = defined( 'ELEMENTOR_VERSION' ) ? ELEMENTOR_VERSION : '3.34.0';
// Get Elementor CSS base URL
$base_url = defined( 'ELEMENTOR_URL' ) ? ELEMENTOR_URL . 'assets/css/' : plugins_url( 'assets/css/', WP_PLUGIN_DIR . '/elementor/elementor.php' );
// Register and enqueue each widget CSS file
foreach ( $this->widget_styles as $handle ) {
// Skip if already enqueued (to avoid duplicates)
if ( wp_style_is( $handle, 'enqueued' ) ) {
continue;
}
// Register if not already registered
if ( ! wp_style_is( $handle, 'registered' ) ) {
// Extract widget name from handle (e.g., 'widget-image' -> 'image')
$widget_name = str_replace( 'widget-', '', $handle );
// Try to get the correct URL from Elementor's frontend class
$style_url = $this->get_elementor_widget_style_url( $widget_name );
if ( $style_url ) {
wp_register_style(
$handle,
$style_url,
[ 'elementor-frontend' ],
$version
);
}
}
// Force enqueue it (this ensures it's in header)
if ( wp_style_is( $handle, 'registered' ) ) {
wp_enqueue_style( $handle );
}
}
}
/**
* Get Elementor widget style URL
*
* @param string $widget_name Widget name (e.g., 'image', 'icon-list')
* @return string|false Style URL or false on failure
*/
private function get_elementor_widget_style_url( $widget_name ) {
// Construct URL manually (Elementor's method is protected)
$base_url = defined( 'ELEMENTOR_URL' ) ? ELEMENTOR_URL . 'assets/css/' : plugins_url( 'assets/css/', WP_PLUGIN_DIR . '/elementor/elementor.php' );
return $base_url . 'widget-' . $widget_name . '.min.css';
}
/**
* Ensure styles are in header (backup method)
*
* Runs in wp_head to catch any styles that weren't enqueued early
* Only runs if styles are NOT already in header
*/
public function ensure_styles_in_header() {
if ( ! class_exists( '\Elementor\Plugin' ) ) {
return;
}
// Skip if styles are already in header (no fix needed)
if ( $this->styles_already_in_header ) {
return;
}
foreach ( $this->widget_styles as $handle ) {
// If registered but not enqueued, enqueue it now
if ( wp_style_is( $handle, 'registered' ) && ! wp_style_is( $handle, 'enqueued' ) ) {
wp_enqueue_style( $handle );
}
}
}
/**
* Prevent widget styles from being output in footer
*
* This runs early in wp_footer to dequeue any widget styles
* that Elementor might have enqueued for footer output
* Only runs if styles were NOT already in header (meaning they need fixing)
*/
public function prevent_footer_styles() {
if ( ! class_exists( '\Elementor\Plugin' ) ) {
return;
}
// Skip if styles are already in header (no fix needed)
if ( $this->styles_already_in_header ) {
return;
}
global $wp_styles;
if ( ! isset( $wp_styles->queue ) ) {
return;
}
// Remove widget styles from footer queue
foreach ( $this->widget_styles as $handle ) {
$key = array_search( $handle, $wp_styles->queue, true );
if ( false !== $key ) {
unset( $wp_styles->queue[ $key ] );
}
// Also remove from done array if present
if ( isset( $wp_styles->done ) ) {
$key = array_search( $handle, $wp_styles->done, true );
if ( false !== $key ) {
unset( $wp_styles->done[ $key ] );
}
}
}
}
/**
* Capture footer styles and prevent their output
*
* @param string $tag The link tag for the enqueued style
* @param string $handle The style's registered handle
* @return string Modified tag
*/
public function capture_footer_styles( $tag, $handle ) {
// Only process our target widget styles
if ( ! in_array( $handle, $this->widget_styles, true ) ) {
return $tag;
}
// Skip if styles are already in header (no fix needed)
if ( $this->styles_already_in_header ) {
return $tag;
}
// If we're in wp_footer, prevent output (styles should already be in header)
if ( doing_action( 'wp_footer' ) ) {
// Return empty string to prevent footer output
return '';
}
return $tag;
}
}
// Initialize the class
WFACP_Elementor_Widget_CSS_Header_Loader::get_instance();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment