0; return (bool) apply_filters( 'eppro_theme_builder_use_canvas', $use, self::$header_id, self::$footer_id, self::$content_id ); } // ========================================================================= // Content Template Override (canvas) // ========================================================================= /** * @param string $template Original template file path. * @return string Modified template file path. */ public static function override_content_template( $template ) { if ( ! self::uses_canvas() ) return $template; // Never intercept direct access to the eppro_hf_template CPT itself — // block_direct_access() handles that; we must not fight it. if ( is_singular( EPPRO_Theme_Builder_CPT::POST_TYPE ) ) return $template; $canvas = EPPRO_PATH . 'includes/theme-builder/templates/canvas.php'; return file_exists( $canvas ) ? $canvas : $template; } // ========================================================================= // Header / footer replacement for classic themes // ========================================================================= /** * Fires inside get_header(): print our document head and header, then load * the theme's header.php into a discarded buffer. get_header() loads that * file with require_once right after this action, so it will not run again. * * @param string|null $name Specific header name passed to get_header(). */ public static function replace_theme_header( $name = null ): void { if ( ! self::$header_id || self::uses_canvas() || wp_is_block_theme() ) return; static $done = false; if ( $done ) return; $done = true; require EPPRO_PATH . 'includes/theme-builder/templates/theme-header.php'; $templates = []; $name = (string) $name; if ( '' !== $name ) { $templates[] = "header-{$name}.php"; } $templates[] = 'header.php'; // wp_head() already ran in our header; don't run its callbacks twice. remove_all_actions( 'wp_head' ); ob_start(); locate_template( $templates, true ); ob_end_clean(); } /** * Fires inside get_footer(): print our footer and wp_footer(), then load * the theme's footer.php into a discarded buffer (see replace_theme_header()). * * @param string|null $name Specific footer name passed to get_footer(). */ public static function replace_theme_footer( $name = null ): void { if ( ! self::$footer_id || self::uses_canvas() || wp_is_block_theme() ) return; static $done = false; if ( $done ) return; $done = true; require EPPRO_PATH . 'includes/theme-builder/templates/theme-footer.php'; $templates = []; $name = (string) $name; if ( '' !== $name ) { $templates[] = "footer-{$name}.php"; } $templates[] = 'footer.php'; // wp_footer() already ran in our footer; don't run its callbacks twice. remove_all_actions( 'wp_footer' ); ob_start(); locate_template( $templates, true ); ob_end_clean(); } // ========================================================================= // Header / footer replacement for block themes // ========================================================================= /** * Swap the block theme's header / footer template part for ours. * * @param string $block_content Rendered template part. * @param array $block Parsed block. */ public static function replace_block_template_part( $block_content, $block ) { if ( self::uses_canvas() || ! is_array( $block ) ) return $block_content; $attrs = (array) ( $block['attrs'] ?? [] ); $slug = (string) ( $attrs['slug'] ?? '' ); $area = (string) ( $attrs['area'] ?? ( $attrs['tagName'] ?? '' ) ); if ( self::$header_id && ! self::$header_printed && ( 'header' === $area || str_starts_with( $slug, 'header' ) ) ) { return self::get_header_html(); } if ( self::$footer_id && ! self::$footer_printed && ( 'footer' === $area || str_starts_with( $slug, 'footer' ) ) ) { return self::get_footer_html(); } return $block_content; } // ========================================================================= // Header / Footer markup // ========================================================================= /** Header markup (empty when no header template applies or it was already printed). */ public static function get_header_html(): string { if ( ! self::$header_id || self::$header_printed ) return ''; self::$header_printed = true; $is_sticky = self::get_meta( self::$header_id, '_eppro_hf_sticky' ); $is_transparent = self::get_meta( self::$header_id, '_eppro_hf_transparent' ); $shrink_scroll = self::get_meta( self::$header_id, '_eppro_hf_shrink_scroll' ); $shrink_height = self::get_meta( self::$header_id, '_eppro_hf_shrink_height' ) ?: 60; $classes = [ 'eppro-hf-header' ]; if ( $is_sticky ) $classes[] = 'eppro-hf-header--sticky'; if ( $is_transparent ) $classes[] = 'eppro-hf-header--transparent'; if ( $shrink_scroll ) $classes[] = 'eppro-hf-header--shrink'; $html = '
by enqueue_assets(). $html .= EPPRO_Elementor_Helper::get_template_content( self::$header_id, false ); $html .= '
'; return $html; } /** Footer markup (empty when no footer template applies or it was already printed). */ public static function get_footer_html(): string { if ( ! self::$footer_id || self::$footer_printed ) return ''; self::$footer_printed = true; return ''; } public static function render_header(): void { echo self::get_header_html(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Attributes escaped above; body is Elementor builder output. } public static function render_footer(): void { echo self::get_footer_html(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Elementor builder output. } /** Print the matched content template (used by canvas.php). */ public static function render_content(): void { if ( ! self::$content_id ) return; EPPRO_Elementor_Helper::print_template_content( self::$content_id, false ); } // ========================================================================= // Assets // ========================================================================= public static function enqueue_assets(): void { if ( ! self::$header_id && ! self::$footer_id && ! self::$content_id ) return; wp_enqueue_style( 'eppro-theme-builder', EPPRO_URL . 'includes/theme-builder/assets/css/theme-builder.css', [], eppro_asset_ver( 'includes/theme-builder/assets/css/theme-builder.css' ) ); if ( class_exists( '\Elementor\Plugin' ) ) { // Elementor's own frontend CSS (and the site kit: global colours, // fonts) — needed when the page itself is not built with Elementor. $frontend = \Elementor\Plugin::$instance->frontend ?? null; if ( $frontend && method_exists( $frontend, 'enqueue_styles' ) ) { $frontend->enqueue_styles(); } } // Pre-enqueue Elementor-generated CSS files for each active template // so the tag lands in (we're inside wp_enqueue_scripts). // Templates are then rendered without inline CSS. if ( class_exists( '\Elementor\Core\Files\CSS\Post' ) ) { foreach ( array_filter( [ self::$header_id, self::$footer_id, self::$content_id ] ) as $tid ) { try { $css = \Elementor\Core\Files\CSS\Post::create( $tid ); if ( $css && method_exists( $css, 'enqueue' ) ) { $css->enqueue(); } } catch ( \Throwable $e ) { eppro_log( 'EPP CSS enqueue error for template ' . $tid . ': ' . $e->getMessage() ); } } } if ( self::$header_id ) { $is_sticky = self::get_meta( self::$header_id, '_eppro_hf_sticky' ); $shrink_scroll = self::get_meta( self::$header_id, '_eppro_hf_shrink_scroll' ); if ( $is_sticky || $shrink_scroll ) { wp_enqueue_script( 'eppro-sticky-header', EPPRO_URL . 'includes/theme-builder/assets/js/sticky-header.js', [ 'jquery' ], eppro_asset_ver( 'includes/theme-builder/assets/js/sticky-header.js' ), true ); } } } // ========================================================================= // Body Classes // ========================================================================= public static function body_classes( array $classes ): array { if ( self::$header_id ) { $classes[] = 'eppro-has-custom-header'; if ( self::get_meta( self::$header_id, '_eppro_hf_sticky' ) ) { $classes[] = 'eppro-has-sticky-header'; } if ( self::get_meta( self::$header_id, '_eppro_hf_transparent' ) ) { $classes[] = 'eppro-has-transparent-header'; } } if ( self::$footer_id ) { $classes[] = 'eppro-has-custom-footer'; } if ( self::$content_id ) { $classes[] = 'eppro-has-custom-content'; } return $classes; } // ========================================================================= // Public getters // ========================================================================= public static function get_header_id(): int { return self::$header_id; } public static function get_footer_id(): int { return self::$footer_id; } public static function get_content_id(): int { return self::$content_id; } // ========================================================================= // Helpers // ========================================================================= private static function is_elementor_context(): bool { if ( isset( $_GET['elementor-preview'] ) ) return true; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only context check. if ( class_exists( 'Elementor\Plugin' ) && \Elementor\Plugin::$instance->editor->is_edit_mode() ) return true; if ( class_exists( 'Elementor\Plugin' ) && \Elementor\Plugin::$instance->preview->is_preview_mode() ) return true; return false; } /** * Cache template meta for performance. * Batch loads all meta keys to avoid multiple queries. */ private static function cache_template_meta( int $post_id ): void { if ( isset( self::$meta_cache[ $post_id ] ) ) return; $meta = get_post_meta( $post_id ); self::$meta_cache[ $post_id ] = [ '_eppro_hf_sticky' => ! empty( $meta['_eppro_hf_sticky'][0] ), '_eppro_hf_transparent' => ! empty( $meta['_eppro_hf_transparent'][0] ), '_eppro_hf_shrink_scroll' => ! empty( $meta['_eppro_hf_shrink_scroll'][0] ), '_eppro_hf_shrink_height' => ! empty( $meta['_eppro_hf_shrink_height'][0] ) ? absint( $meta['_eppro_hf_shrink_height'][0] ) : 60, ]; } /** * Get cached meta value. * Falls back to direct get_post_meta if not cached. */ private static function get_meta( int $post_id, string $key ) { if ( isset( self::$meta_cache[ $post_id ][ $key ] ) ) { return self::$meta_cache[ $post_id ][ $key ]; } // For shrink height, return integer if ( '_eppro_hf_shrink_height' === $key ) { return absint( get_post_meta( $post_id, $key, true ) ) ?: 60; } return (bool) get_post_meta( $post_id, $key, true ); } }