register(); } add_action( 'after_setup_theme', 'wc_bootstrap_register_template_override' ); /** * Enqueue child theme styles. * * Loads parent theme stylesheet first, then child theme overrides. * CSS cascade order: * 1. wp-bootstrap (parent) * 2. woocommerce (plugin styles) * 3. wc-bootstrap-style (child theme style.css) * 4. wc-bootstrap-overrides (plugin CSS overrides) */ function wc_bootstrap_enqueue_styles(): void { $theme_version = wp_get_theme()->get( 'Version' ); // Enqueue parent theme stylesheet. wp_enqueue_style( 'wp-bootstrap-style', get_template_directory_uri() . '/assets/css/style.min.css', array(), wp_get_theme( 'wp-bootstrap' )->get( 'Version' ) ); // Enqueue child theme stylesheet. wp_enqueue_style( 'wc-bootstrap-style', get_stylesheet_directory_uri() . '/style.css', array( 'wp-bootstrap-style' ), $theme_version ); // Enqueue plugin Bootstrap override styles. // Depend on plugin stylesheets so overrides always load after plugin CSS. wp_enqueue_style( 'wc-bootstrap-overrides', get_stylesheet_directory_uri() . '/assets/css/wc-bootstrap.css', array( 'wc-bootstrap-style', 'woocommerce' ), $theme_version ); } add_action( 'wp_enqueue_scripts', 'wc_bootstrap_enqueue_styles' ); /** * Enqueue child theme scripts. * * @since 0.1.0 */ function wc_bootstrap_enqueue_scripts(): void { $theme_version = wp_get_theme()->get( 'Version' ); // Quantity +/- button handler for Bootstrap input-group widget. wp_enqueue_script( 'wc-bootstrap-quantity', get_stylesheet_directory_uri() . '/assets/js/quantity.js', array(), $theme_version, true ); } add_action( 'wp_enqueue_scripts', 'wc_bootstrap_enqueue_scripts' ); /** * Handle plugin page rendering via plugin render filter. * * Delegates page rendering to the parent theme's TwigService so that plugin pages * share the same page shell (header, footer, layout) as native WordPress pages. * Falls back to letting the plugin handle rendering if the parent theme is not available. * * @param bool $rendered Whether the page has been rendered. * @param string $content Pre-rendered plugin HTML content. * @param array $context Plugin template context. * @return bool True if rendering was handled, false to let plugin use fallback. * * @since 0.1.0 */ function wc_bootstrap_render_page( bool $rendered, string $content, array $context ): bool { if ( ! class_exists( '\WPBootstrap\Twig\TwigService' ) || ! class_exists( '\WPBootstrap\Template\ContextBuilder' ) ) { return false; // Can't render, let plugin use its own fallback } $context_builder = new \WPBootstrap\Template\ContextBuilder(); $theme_context = $context_builder->build(); $twig = \WPBootstrap\Twig\TwigService::getInstance(); // Inject plugin content as the page post content so page.html.twig renders it // inside the standard content block. Title is empty so the parent theme does not // render its own

— plugin templates handle their own headings. $theme_context['post'] = array_merge( $theme_context['post'] ?? [], [ 'content' => $content, 'title' => '', 'thumbnail' => '', ] ); echo $twig->render( 'pages/page.html.twig', $theme_context ); return true; } add_filter( 'woocommerce_render_page', 'wc_bootstrap_render_page', 10, 3 ); /** * Signal to the plugin that its content will be wrapped by the parent theme. * * When the parent theme's TwigService is available, the plugin templates should skip * their outer wrapper elements (breadcrumbs, sidebar, page shell) to avoid double-wrapping. * * @param bool $wrapped Whether content is theme-wrapped. * @return bool * * @since 0.1.0 */ function wc_bootstrap_is_wrapped( bool $wrapped ): bool { if ( class_exists( '\WPBootstrap\Twig\TwigService' ) ) { return true; } return $wrapped; } add_filter( 'woocommerce_is_theme_wrapped', 'wc_bootstrap_is_wrapped' ); /** * Add sticky header scroll shadow behavior. * * Toggles an 'is-stuck' class on the navbar when the page is scrolled, * adding a subtle box-shadow to visually separate the header from content. * * @since 0.1.0 */ function wc_bootstrap_sticky_header_script(): void { ?>