Created
January 2, 2026 07:12
-
-
Save xlplugins/f2be7958f513b2f628182c576bc52da6 to your computer and use it in GitHub Desktop.
Funnelkit Load Elementor widget CSS files in header instead of footer
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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