) 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' ) );
}
}