diff --git a/archive-product.php b/archive-product.php new file mode 100644 index 0000000..f2e99c7 --- /dev/null +++ b/archive-product.php @@ -0,0 +1,104 @@ + +
+ + +
+ +
+ + + +
+
+ markup. + * + * WooCommerce's wc_get_template_part('content', 'product') uses locate_template() + * which finds this file in the child theme before falling back to the plugin template. + * Unlike wc_get_template(), wc_get_template_part() does NOT fire the + * woocommerce_before_template_part / woocommerce_after_template_part hooks, + * so the TemplateOverride class cannot intercept it — this bridge file is needed. + * + * @package WcBootstrap + * @since 0.1.0 + */ + +defined( 'ABSPATH' ) || exit; + +global $product; + +// Ensure the product is valid and visible (same guard as WooCommerce's default template). +if ( ! is_a( $product, WC_Product::class ) || ! $product->is_visible() ) { + return; +} + +if ( class_exists( '\WPBootstrap\Twig\TwigService' ) ) { + $twig = \WPBootstrap\Twig\TwigService::getInstance(); + echo $twig->render( 'content-product.html.twig', [] ); +} else { + // Fallback: include WooCommerce's default template directly. + include WC()->plugin_path() . '/templates/content-product.php'; +} diff --git a/functions.php b/functions.php index 07abe74..e94f253 100644 --- a/functions.php +++ b/functions.php @@ -14,6 +14,7 @@ if ( ! defined( 'ABSPATH' ) ) { exit; } + /** * Define theme constants. * @@ -223,3 +224,123 @@ function wc_bootstrap_sticky_header_script(): void { __( 'Shop Sidebar', 'wc-bootstrap' ), + 'id' => 'shop-sidebar', + 'description' => __( 'Add widgets here to appear in the shop sidebar.', 'wc-bootstrap' ), + 'before_widget' => '
', + 'after_widget' => '
', + 'before_title' => '', + ) ); +} +add_action( 'widgets_init', 'wc_bootstrap_register_sidebars' ); + +/** + * Set the number of product columns in the shop loop. + * + * @return int Number of columns. + * @since 0.1.0 + */ +function wc_bootstrap_loop_columns(): int { + return 4; +} +add_filter( 'loop_shop_columns', 'wc_bootstrap_loop_columns' ); + +/** + * Remove WooCommerce's default sidebar hook. + * + * The child theme's archive-product.php renders the sidebar inline within the + * Bootstrap grid layout, so the default woocommerce_sidebar hook must not render + * a second sidebar outside the layout. + * + * @since 0.1.0 + */ +function wc_bootstrap_remove_default_sidebar(): void { + remove_action( 'woocommerce_sidebar', 'woocommerce_get_sidebar', 10 ); +} +add_action( 'init', 'wc_bootstrap_remove_default_sidebar' ); + +/** + * Prevent the parent theme from rendering product archives. + * + * The parent theme's TemplateController hooks template_redirect at priority 10 + * and renders pages/archive.html.twig for all archives (then exits). For product + * archives, we need to render our own WooCommerce-specific Bootstrap layout instead. + * Returning false tells the parent theme to skip rendering for this request. + * + * @param bool $should_render Whether the parent theme should render. + * @return bool + * + * @since 0.1.0 + */ +function wc_bootstrap_skip_parent_archive( bool $should_render ): bool { + if ( is_post_type_archive( 'product' ) || is_page( wc_get_page_id( 'shop' ) ) ) { + return false; + } + if ( function_exists( 'is_product_taxonomy' ) && is_product_taxonomy() ) { + return false; + } + return $should_render; +} +add_filter( 'wp_bootstrap_should_render_template', 'wc_bootstrap_skip_parent_archive' ); + +/** + * Render product archive pages with Bootstrap 5 layout. + * + * Since the parent theme's TemplateController is blocked for product archives + * (via wp_bootstrap_should_render_template filter), we render the page ourselves + * at priority 11 using the parent theme's TwigService and page shell. + * + * The archive-product.php file provides the Bootstrap layout (sidebar + product + * grid) and is captured via output buffering, then injected into the parent + * theme's page template — the same pattern as wc_bootstrap_render_page(). + * + * @since 0.1.0 + */ +function wc_bootstrap_render_product_archive(): void { + $is_shop = is_post_type_archive( 'product' ) || is_page( wc_get_page_id( 'shop' ) ); + $is_tax = function_exists( 'is_product_taxonomy' ) && is_product_taxonomy(); + + if ( ! $is_shop && ! $is_tax ) { + return; + } + + if ( ! class_exists( '\WPBootstrap\Twig\TwigService' ) + || ! class_exists( '\WPBootstrap\Template\ContextBuilder' ) ) { + return; + } + + // Capture WooCommerce archive content via output buffering. + ob_start(); + include get_stylesheet_directory() . '/archive-product.php'; + $content = ob_get_clean(); + + // Build parent theme context and inject archive content into page shell. + $context_builder = new \WPBootstrap\Template\ContextBuilder(); + $theme_context = $context_builder->build(); + $twig = \WPBootstrap\Twig\TwigService::getInstance(); + + $theme_context['post'] = array_merge( + $theme_context['post'] ?? [], + [ + 'content' => $content, + 'title' => '', + 'thumbnail' => '', + ] + ); + + echo $twig->render( 'pages/page.html.twig', $theme_context ); + exit; +} +add_action( 'template_redirect', 'wc_bootstrap_render_product_archive', 11 ); diff --git a/inc/TemplateOverride.php b/inc/TemplateOverride.php index 20ab7ee..5406b34 100644 --- a/inc/TemplateOverride.php +++ b/inc/TemplateOverride.php @@ -78,8 +78,17 @@ class TemplateOverride { } try { - $twig = TwigService::getInstance(); - echo $twig->render( $twigTemplate, $args ); + $twig = TwigService::getInstance(); + $context = $args; + + // Inject the global $product into the Twig context. + // WooCommerce PHP templates access it via `global $product;` but Twig + // templates have isolated variable scopes and need it passed explicitly. + if ( ! isset( $context['product'] ) && ! empty( $GLOBALS['product'] ) ) { + $context['product'] = $GLOBALS['product']; + } + + echo $twig->render( $twigTemplate, $context ); // Buffer the upcoming PHP include so we can discard it. ob_start(); diff --git a/templates/components/pagination.html.twig b/templates/components/pagination.html.twig index bda0663..b004f5c 100644 --- a/templates/components/pagination.html.twig +++ b/templates/components/pagination.html.twig @@ -16,7 +16,7 @@