getEnvironment(); // Add child theme templates directory to the Twig loader so {% include %} // directives in Twig templates can find other child theme templates. $loader = $env->getLoader(); if ( $loader instanceof \Twig\Loader\FilesystemLoader ) { $template_dir = WC_BOOTSTRAP_PATH . 'templates'; if ( is_dir( $template_dir ) ) { $loader->prependPath( $template_dir ); } } // Register WooCommerce functions and filters as Twig extensions. $env->addExtension( new \WcBootstrap\Twig\WooCommerceExtension() ); // Register template interception hooks. $override = new \WcBootstrap\TemplateOverride(); $override->register(); } add_action( 'init', 'wc_bootstrap_init_twig_bridge', 20 ); /** * 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