) to inject header BEFORE the theme's header. // Fallback to wp_head with late priority if theme doesn't support wp_body_open. add_action( 'wp_body_open', [ $this, 'inject_header' ], 1 ); add_action( 'wp_footer', [ $this, 'inject_footer' ] ); // AAFE FIX: Suppress the theme's native header/footer when AAFE templates are active. add_action( 'init', [ $this, 'suppress_theme_header_footer' ] ); // AAFE FIX: no longer registers Elementor Pro theme locations named "header"/"footer". // Nothing used them and they overrode Elementor Pro's own core locations. add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_frontend_css' ] ); add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_admin_assets' ] ); // add_action( 'elementor/documents/register_controls', [ $this, 'register_document_controls' ] ); } /** * Suppress the active theme's native header/footer by hooking into theme-specific actions. * Supports Astra, OceanWP, GeneratePress, Hello Elementor, Storefront, and generic themes. */ public function suppress_theme_header_footer() { $theme = get_template(); // Only suppress on the frontend. add_action( 'template_redirect', function() use ( $theme ) { $has_header = $this->get_active_template( 'header' ); $has_footer = $this->get_active_template( 'footer' ); if ( ! $has_header && ! $has_footer ) { return; } // --- Astra Theme --- if ( 'astra' === $theme ) { if ( $has_header ) { // Remove ALL Astra header-related actions so nothing from the theme header renders. remove_all_actions( 'astra_header' ); remove_all_actions( 'astra_masthead' ); remove_all_actions( 'astra_masthead_top' ); remove_all_actions( 'astra_header_before' ); remove_all_actions( 'astra_header_after' ); // For Astra Builder (Customizer-based header): add_filter( 'astra_header_enabled', '__return_false', 99 ); add_filter( 'astra_masthead_enabled', '__return_false', 99 ); } if ( $has_footer ) { remove_all_actions( 'astra_footer' ); remove_all_actions( 'astra_footer_before' ); remove_all_actions( 'astra_footer_after' ); add_filter( 'astra_footer_enabled', '__return_false', 99 ); } } // --- OceanWP Theme --- if ( 'oceanwp' === $theme ) { if ( $has_header ) { remove_action( 'ocean_header', 'ocean_get_header' ); } if ( $has_footer ) { remove_action( 'ocean_footer', 'ocean_get_footer' ); } } // --- GeneratePress Theme --- if ( 'generatepress' === $theme ) { if ( $has_header ) { remove_action( 'generate_header', 'generate_construct_header' ); } if ( $has_footer ) { remove_action( 'generate_footer', 'generate_construct_footer' ); } } // --- Hello Elementor / Elementor Hello --- if ( in_array( $theme, [ 'hello-elementor', 'hello-theme' ], true ) ) { if ( $has_header ) { remove_action( 'hello_elementor_header', 'hello_elementor_header_template' ); } if ( $has_footer ) { remove_action( 'hello_elementor_footer', 'hello_elementor_footer_template' ); } } // --- Storefront Theme --- if ( 'storefront' === $theme ) { if ( $has_header ) { remove_action( 'storefront_header', 'storefront_header_container' ); remove_action( 'storefront_header', 'storefront_skip_links', 5 ); remove_action( 'storefront_header', 'storefront_site_branding', 20 ); remove_action( 'storefront_header', 'storefront_secondary_navigation', 30 ); remove_action( 'storefront_header', 'storefront_primary_navigation_wrapper', 42 ); remove_action( 'storefront_header', 'storefront_primary_navigation', 50 ); remove_action( 'storefront_header', 'storefront_header_cart', 60 ); remove_action( 'storefront_header', 'storefront_primary_navigation_wrapper_close', 68 ); remove_action( 'storefront_header', 'storefront_header_container_close', 72 ); } } // --- Generic CSS fallback: hide common theme header/footer selectors --- // --- Generic CSS fallback: hide common theme header/footer selectors --- // This acts as a safety net if PHP hook removal is not enough. add_action( 'wp_enqueue_scripts', function() use ( $has_header, $has_footer ) { $css = ''; if ( $has_header ) { $css .= ' /* Hide theme headers universally */ #masthead:not(.aafe-hf-wrap), .site-header:not(.aafe-hf-wrap), header.site-header, #header:not(.aafe-hf-wrap), .header-area:not(.aafe-hf-wrap), /* Astra specific */ .ast-desktop-header-content, #ast-fixed-header, /* Astra info bar */ .ast-above-header-section, .ast-above-header, /* Block themes (Twenty Twenty-Four / Twenty Twenty-Five ...) */ .wp-site-blocks > header.wp-block-template-part { display:none !important; } '; } if ( $has_footer ) { $css .= ' /* Hide theme footers universally */ #colophon:not(.aafe-hf-wrap), .site-footer:not(.aafe-hf-wrap), footer.site-footer, #footer:not(.aafe-hf-wrap), .footer-area:not(.aafe-hf-wrap), /* Block themes */ .wp-site-blocks > footer.wp-block-template-part { display:none !important; } '; } if ( ! empty( $css ) ) { wp_register_style( 'aafe-suppress-theme-hf', false ); wp_enqueue_style( 'aafe-suppress-theme-hf' ); wp_add_inline_style( 'aafe-suppress-theme-hf', trim( $css ) ); } }, 99 ); } ); } /** * Add meta box to set template display conditions — only for Header/Footer templates. */ public function add_meta_boxes() { // Only show Header/Footer settings on header or footer type templates. $post_id = isset( $_GET['post'] ) ? absint( $_GET['post'] ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only template type check. if ( $post_id ) { $type = get_post_meta( $post_id, \AddronixAddons\AAFE_Template::META_TYPE, true ); // If template type is set but it's not header/footer, don't add the box. if ( ! empty( $type ) && ! in_array( $type, [ 'header', 'footer' ], true ) ) { return; } } add_meta_box( 'aafe_hf_settings', esc_html__( 'AAFE Template Settings', 'addronix-addons-for-elementor' ), [ $this, 'render_meta_box' ], self::CPT, 'side', 'high' ); } /** * Render the meta box HTML with advanced conditional logic (Pro Level). * * @param \WP_Post $post */ public function render_meta_box( $post ) { wp_nonce_field( 'aafe_hf_meta_nonce', 'aafe_hf_nonce' ); // Prefer the unified template type so both meta boxes always agree. $unified = get_post_meta( $post->ID, \AddronixAddons\AAFE_Template::META_TYPE, true ); $type = in_array( $unified, [ 'header', 'footer' ], true ) ? $unified : ( get_post_meta( $post->ID, '_aafe_hf_type', true ) ?: 'header' ); $display_on = get_post_meta( $post->ID, '_aafe_hf_display_on', true ) ?: 'all'; $singular_type = get_post_meta( $post->ID, '_aafe_hf_singular_type', true ) ?: 'all'; $sticky = get_post_meta( $post->ID, '_aafe_hf_sticky', true ); $transparent = get_post_meta( $post->ID, '_aafe_hf_transparent', true ); ?>
post_type !== self::CPT ) { return; } $css = '.aafe-meta-row{margin-bottom:16px;}.aafe-meta-row label{font-weight:600;display:block;margin-bottom:6px;color:#1d2327;}.aafe-meta-row select{width:100%;max-width:100%;border-radius:4px;border:1px solid #8c8f94;}.aafe-meta-checkbox label{font-weight:400;display:flex;align-items:center;gap:8px;cursor:pointer;}.aafe-meta-checkbox{margin-bottom:10px;}.aafe-hidden{display:none !important;}'; wp_add_inline_style( 'wp-admin', $css ); $js = 'jQuery(document).ready(function($){function aafeToggleFields(){var type=$("#aafe_hf_type").val();var displayOn=$("#aafe_hf_display_on").val();if(type==="header"){$("#aafe_hf_header_options").slideDown(200);}else{$("#aafe_hf_header_options").slideUp(200);}if(displayOn==="singular"){$("#aafe_hf_singular_wrap").slideDown(200);}else{$("#aafe_hf_singular_wrap").slideUp(200);}}$("#aafe_hf_type,#aafe_hf_display_on").on("change",aafeToggleFields);aafeToggleFields();});'; wp_add_inline_script( 'jquery', $js ); } /** * Save meta box data. * * @param int $post_id */ public function save_meta( $post_id ) { if ( ! isset( $_POST['aafe_hf_nonce'] ) ) { return; } if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['aafe_hf_nonce'] ) ), 'aafe_hf_meta_nonce' ) ) { return; } if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) { return; } if ( ! current_user_can( 'edit_post', $post_id ) ) { return; } $allowed_types = [ 'header', 'footer' ]; $allowed_display_on = [ 'all', 'singular', 'archive', '404' ]; $type = isset( $_POST['aafe_hf_type'] ) ? sanitize_text_field( wp_unslash( $_POST['aafe_hf_type'] ) ) : 'header'; if ( ! in_array( $type, $allowed_types, true ) ) { $type = 'header'; } $display_on = isset( $_POST['aafe_hf_display_on'] ) ? sanitize_text_field( wp_unslash( $_POST['aafe_hf_display_on'] ) ) : 'all'; if ( ! in_array( $display_on, $allowed_display_on, true ) ) { $display_on = 'all'; } $singular_type = isset( $_POST['aafe_hf_singular_type'] ) ? sanitize_key( wp_unslash( $_POST['aafe_hf_singular_type'] ) ) : 'all'; update_post_meta( $post_id, '_aafe_hf_type', $type ); // Keep the unified type in sync (it takes precedence when the template is resolved). update_post_meta( $post_id, \AddronixAddons\AAFE_Template::META_TYPE, $type ); update_post_meta( $post_id, '_aafe_hf_display_on', $display_on ); update_post_meta( $post_id, '_aafe_hf_singular_type', $singular_type ); update_post_meta( $post_id, '_aafe_hf_sticky', isset( $_POST['aafe_hf_sticky'] ) ? '1' : '' ); update_post_meta( $post_id, '_aafe_hf_transparent', isset( $_POST['aafe_hf_transparent'] ) ? '1' : '' ); } /** * Register settings inside the Elementor Editor (Gear Icon > Settings) * * @param \Elementor\Core\DocumentTypes\DocumentBase $document */ public function register_document_controls( $document ) { if ( ! $document instanceof \Elementor\Core\DocumentTypes\Post || self::CPT !== $document->get_post()->post_type ) { return; } $document->start_controls_section( 'aafe_hf_settings_section', [ 'label' => esc_html__( '⚙️ Header & Footer Conditions', 'addronix-addons-for-elementor' ), 'tab' => \Elementor\Controls_Manager::TAB_SETTINGS, ] ); $document->add_control( 'aafe_hf_type', [ 'label' => esc_html__( 'Template Type', 'addronix-addons-for-elementor' ), 'type' => \Elementor\Controls_Manager::SELECT, 'options' => [ 'header' => esc_html__( 'Header', 'addronix-addons-for-elementor' ), 'footer' => esc_html__( 'Footer', 'addronix-addons-for-elementor' ), ], 'default' => 'header', ] ); $document->add_control( 'aafe_hf_display_on', [ 'label' => esc_html__( 'Display Conditions', 'addronix-addons-for-elementor' ), 'type' => \Elementor\Controls_Manager::SELECT, 'options' => [ 'all' => esc_html__( 'Entire Site', 'addronix-addons-for-elementor' ), 'singular' => esc_html__( 'Singular (Pages/Posts)', 'addronix-addons-for-elementor' ), 'archive' => esc_html__( 'Archive (Categories/Tags)', 'addronix-addons-for-elementor' ), '404' => esc_html__( '404 Error Page', 'addronix-addons-for-elementor' ), ], 'default' => 'all', ] ); $document->add_control( 'aafe_hf_singular_type', [ 'label' => esc_html__( 'Singular Type', 'addronix-addons-for-elementor' ), 'type' => \Elementor\Controls_Manager::SELECT, 'options' => [ 'all' => esc_html__( 'All Singulars', 'addronix-addons-for-elementor' ), 'front_page' => esc_html__( 'Front Page', 'addronix-addons-for-elementor' ), 'post' => esc_html__( 'All Posts', 'addronix-addons-for-elementor' ), 'page' => esc_html__( 'All Pages', 'addronix-addons-for-elementor' ), ], 'default' => 'all', 'condition' => [ 'aafe_hf_display_on' => 'singular', ], ] ); $document->add_control( 'aafe_hf_sticky', [ 'label' => esc_html__( 'Enable Sticky Header', 'addronix-addons-for-elementor' ), 'type' => \Elementor\Controls_Manager::SWITCHER, 'return_value' => 'yes', 'default' => '', 'condition' => [ 'aafe_hf_type' => 'header', ], ] ); $document->add_control( 'aafe_hf_transparent', [ 'label' => esc_html__( 'Transparent on Top', 'addronix-addons-for-elementor' ), 'type' => \Elementor\Controls_Manager::SWITCHER, 'return_value' => 'yes', 'default' => '', 'condition' => [ 'aafe_hf_type' => 'header', ], ] ); $document->end_controls_section(); } /** * Register Elementor theme locations (for Elementor Theme Builder compatibility). * * @param \ElementorPro\Modules\ThemeBuilder\Classes\Locations_Manager $manager */ public function register_elementor_locations( $manager ) { $manager->register_location( 'header', [ 'hook' => 'aafe_header', 'label' => esc_html__( 'AAFE Header', 'addronix-addons-for-elementor' ), 'multiple' => false, 'edit_in_content' => false, ] ); $manager->register_location( 'footer', [ 'hook' => 'aafe_footer', 'label' => esc_html__( 'AAFE Footer', 'addronix-addons-for-elementor' ), 'multiple' => false, ] ); } /** * Force the Elementor Canvas template for our CPTs. * This prevents the theme's header/footer from loading in the editor. */ public function force_canvas_template( $value, $object_id, $meta_key, $single ) { if ( '_wp_page_template' === $meta_key ) { if ( self::CPT === get_post_type( $object_id ) ) { return $single ? 'elementor_canvas' : [ 'elementor_canvas' ]; } } return $value; } /** * Get template settings properly from Elementor or fallback to meta. */ private function get_hf_setting( $post_id, $key, $default = '' ) { if ( class_exists( '\Elementor\Plugin' ) ) { $document = \Elementor\Plugin::$instance->documents->get( $post_id ); if ( $document ) { $val = $document->get_settings( $key ); if ( ! empty( $val ) ) { return $val; } } } // Fallback to legacy meta return get_post_meta( $post_id, '_aafe_hf_' . str_replace( 'aafe_hf_', '', $key ), true ) ?: $default; } /** * Get all templates of a specific type that match the current page. * * @param string $type 'header' or 'footer' * @return \WP_Post|null */ private function check_condition_match( $condition ) { $display_on = isset( $condition['display_on'] ) ? $condition['display_on'] : 'all'; $singular_type = isset( $condition['singular_type'] ) ? $condition['singular_type'] : 'all'; if ( 'all' === $display_on ) { return true; } if ( 'logged_in' === $display_on ) { return is_user_logged_in(); } if ( 'logged_out' === $display_on ) { return ! is_user_logged_in(); } if ( '404' === $display_on ) { return is_404(); } if ( 'search' === $display_on ) { return is_search(); } if ( strpos( $display_on, 'specific__' ) === 0 ) { $post_id = (int) str_replace( 'specific__', '', $display_on ); if ( ( is_singular() || is_front_page() || is_home() ) && get_queried_object_id() === $post_id ) { return true; } return false; } if ( 'singular' === $display_on ) { if ( ! is_singular() && ! is_front_page() && ! is_home() ) { return false; } if ( 'singular_front_page' === $singular_type || 'front_page' === $singular_type ) { return ( is_front_page() || is_home() ); } if ( 'singular_post' === $singular_type || 'post' === $singular_type ) { return is_singular( 'post' ); } if ( 'singular_page' === $singular_type || 'page' === $singular_type ) { return is_singular( 'page' ); } if ( 'singular_media' === $singular_type || 'singular_media_author' === $singular_type ) { return is_attachment(); } // Legacy options saved by older versions (they had no target selector in the UI). // AAFE FIX: they used to fall through to "return true" and matched EVERY singular page. if ( in_array( $singular_type, [ 'singular_post_cat', 'singular_post_childcat', 'singular_post_tag', 'singular_post_format', 'singular_post_author' ], true ) ) { return is_singular( 'post' ); } if ( 'singular_page_author' === $singular_type ) { return is_singular( 'page' ); } if ( 'singular_child_of' === $singular_type || 'singular_any_child_of' === $singular_type ) { return is_singular() && wp_get_post_parent_id( get_queried_object_id() ) > 0; } // Catch-alls if ( 'singular_all' === $singular_type || 'all' === $singular_type || 'singular_by_author' === $singular_type ) { return true; } // Custom Post Types if ( strpos( $singular_type, 'singular_' ) === 0 ) { $cpt = substr( $singular_type, 9 ); return post_type_exists( $cpt ) && is_singular( $cpt ); } return false; } if ( 'archive' === $display_on ) { if ( ! is_archive() && ! is_search() && ! is_home() ) { return false; } if ( 'archive_search' === $singular_type ) { return is_search(); } if ( 'archive_date' === $singular_type ) { return is_date(); } if ( 'archive_author' === $singular_type ) { return is_author(); } if ( 'archive_post' === $singular_type ) { return is_post_type_archive( 'post' ) || is_home(); } if ( 'archive_category' === $singular_type ) { return is_category(); } if ( 'archive_tag' === $singular_type ) { return is_tag(); } if ( 'archive_all' === $singular_type || 'all' === $singular_type ) { return true; } // Custom Post Type Archives if ( strpos( $singular_type, 'archive_' ) === 0 ) { $cpt = substr( $singular_type, 8 ); return post_type_exists( $cpt ) && is_post_type_archive( $cpt ); } return false; } if ( 'woo' === $display_on ) { if ( ! class_exists( 'WooCommerce' ) ) { return false; } if ( 'woo_shop' === $singular_type ) { return is_shop(); } if ( 'woo_product' === $singular_type ) { return is_product(); } if ( 'woo_category' === $singular_type ) { return is_product_category(); } if ( 'woo_tag' === $singular_type ) { return is_product_tag(); } if ( 'woo_cart' === $singular_type ) { return is_cart(); } if ( 'woo_checkout' === $singular_type ) { return is_checkout(); } if ( 'woo_account' === $singular_type ) { return is_account_page(); } if ( 'woo_thankyou' === $singular_type ) { return is_order_received_page(); } return true; } return false; } private function get_active_template( $type ) { // AAFE FIX BUG#21: Return cached result to avoid duplicate DB queries per request. if ( array_key_exists( $type, $this->template_cache ) ) { return $this->template_cache[ $type ]; } // AAFE FIX: never inject a header/footer into the template screens themselves // (editing or previewing a Header, Footer, Loop Item or Mega Menu template showed the // active header a second time above the canvas), nor into admin / feeds. if ( is_admin() || is_feed() || is_singular( \AddronixAddons\AAFE_Template::CPT ) ) { return null; } // Query using _aafe_template_type (new) with fallback to _aafe_hf_type (legacy). $posts = get_posts( [ 'post_type' => self::CPT, 'posts_per_page' => -1, 'post_status' => 'publish', 'meta_query' => [ // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query 'relation' => 'OR', [ 'key' => \AddronixAddons\AAFE_Template::META_TYPE, 'value' => $type, ], [ 'key' => '_aafe_hf_type', 'value' => $type, ], ], ] ); $result = null; foreach ( $posts as $post ) { // Ensure type actually matches (since OR query may bring in wrong results) $saved_type = get_post_meta( $post->ID, \AddronixAddons\AAFE_Template::META_TYPE, true ); $saved_hf_type = get_post_meta( $post->ID, '_aafe_hf_type', true ); $effective_type = ! empty( $saved_type ) ? $saved_type : $saved_hf_type; if ( $effective_type !== $type ) { continue; } // Read saved advanced conditions array $conditions = get_post_meta( $post->ID, '_aafe_hf_conditions', true ); if ( ! is_array( $conditions ) || empty( $conditions ) ) { // No advanced conditions — fall back to simple display_on meta (always default to 'all') $display_on = get_post_meta( $post->ID, '_aafe_hf_display_on', true ); $singular = get_post_meta( $post->ID, '_aafe_hf_singular_type', true ); if ( empty( $display_on ) ) { $display_on = 'all'; // New templates with no conditions default to showing everywhere } if ( empty( $singular ) ) { $singular = 'all'; } $conditions = [ [ 'type' => 'include', 'display_on' => $display_on, 'singular_type' => $singular ] ]; } $is_included = false; $is_excluded = false; foreach ( $conditions as $cond ) { $match = $this->check_condition_match( $cond ); if ( $match ) { if ( isset( $cond['type'] ) && 'exclude' === $cond['type'] ) { $is_excluded = true; } else { $is_included = true; } } } // Exclude rules override include rules if ( $is_included && ! $is_excluded ) { $result = $post; break; } } $this->template_cache[ $type ] = $result; return $result; } /** * Render an Elementor template by post ID. * * @param int $post_id * @param bool $is_sticky * @param bool $is_transparent * @param string $type 'header' or 'footer' */ private function render_template( $post_id, $is_sticky, $is_transparent, $type ) { $classes = [ 'aafe-hf-wrap', 'aafe-hf-' . $type ]; if ( $is_sticky ) { $classes[] = 'aafe-hf-sticky'; } if ( $is_transparent ) { $classes[] = 'aafe-hf-transparent'; } echo '
'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo \Elementor\Plugin::$instance->frontend->get_builder_content_for_display( $post_id, true ); echo '
'; } /** * Inject header template. */ public function inject_header() { $template = $this->get_active_template( 'header' ); if ( ! $template ) { return; } $sticky = $this->get_hf_setting( $template->ID, 'aafe_hf_sticky', '' ); $transparent = $this->get_hf_setting( $template->ID, 'aafe_hf_transparent', '' ); // AAFE FIX BUG#1: Accept both '1' (meta box checkbox) and 'yes' (Elementor Switcher control). $this->render_template( $template->ID, ! empty( $sticky ) && in_array( $sticky, [ '1', 'yes', true ], true ), ! empty( $transparent ) && in_array( $transparent, [ '1', 'yes', true ], true ), 'header' ); } /** * Inject footer template. */ public function inject_footer() { $template = $this->get_active_template( 'footer' ); if ( ! $template ) { return; } $this->render_template( $template->ID, false, false, 'footer' ); } /** * Enqueue sticky header JS and base CSS. */ public function enqueue_frontend_css() { // AAFE FIX BUG#12: Only enqueue assets if there's an active header or footer template. $has_header = $this->get_active_template( 'header' ); $has_footer = $this->get_active_template( 'footer' ); if ( ! $has_header && ! $has_footer ) { return; } wp_enqueue_style( 'aafe-header-footer', AAFE_URL . 'assets/css/header-footer.css', [], AAFE_VERSION ); wp_enqueue_script( 'aafe-header-footer', AAFE_URL . 'assets/js/header-footer.js', [ 'jquery' ], AAFE_VERSION, array( 'in_footer' => true, 'strategy' => 'defer' ) ); } }