Files
wc-composable-product/templates/content-single-product-composable.php

84 lines
3.3 KiB
PHP
Raw Permalink Normal View History

<?php
/**
* Custom single product template for composable products.
*
* Replaces the standard WooCommerce two-column layout (image + summary) with
* a compact header and full-width product selector.
*
* This is a thin PHP loader that captures WooCommerce hook output and passes
* it to the Twig template for rendering.
*
* @package Magdev\WcComposableProduct
*/
defined( 'ABSPATH' ) || exit;
global $product;
if ( ! $product || ! is_a( $product, 'WC_Product' ) ) {
return;
}
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WooCommerce hook.
do_action( 'woocommerce_before_single_product' );
if ( post_password_required() ) {
echo get_the_password_form(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- WP core function.
return;
}
// Temporarily remove standard WooCommerce summary hooks — we render title,
// price, and description in the compact header instead. Our product selector
// (CartHandler::render_product_selector at priority 25) stays attached.
$hooks_to_remove = array(
array( 'woocommerce_template_single_title', 5 ),
array( 'woocommerce_template_single_rating', 10 ),
array( 'woocommerce_template_single_price', 10 ),
array( 'woocommerce_template_single_excerpt', 20 ),
array( 'woocommerce_template_single_add_to_cart', 30 ),
array( 'woocommerce_template_single_meta', 40 ),
array( 'woocommerce_template_single_sharing', 50 ),
);
foreach ( $hooks_to_remove as $hook ) {
remove_action( 'woocommerce_single_product_summary', $hook[0], $hook[1] );
}
// Capture product selector output (our hook at priority 25 + structured data at 60).
ob_start();
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WooCommerce hook.
do_action( 'woocommerce_single_product_summary' );
$selector_html = ob_get_clean();
// Restore removed hooks.
foreach ( $hooks_to_remove as $hook ) {
add_action( 'woocommerce_single_product_summary', $hook[0], $hook[1] );
}
// Capture after-summary output (product tabs, related products, etc.).
ob_start();
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WooCommerce hook.
do_action( 'woocommerce_after_single_product_summary' );
$after_summary_html = ob_get_clean();
// Build template context.
$image_id = $product->get_image_id();
$context = array(
'product_id' => $product->get_id(),
'product_name' => $product->get_name(),
'price_html' => $product->get_price_html(),
'short_description' => $product->get_short_description(),
'image_html' => $image_id ? wp_get_attachment_image( $image_id, array( 100, 100 ), false, array( 'class' => 'composable-header-image' ) ) : '',
'permalink' => get_permalink( $product->get_id() ),
'product_class' => implode( ' ', wc_get_product_class( 'composable-product-layout', $product ) ),
'selector_html' => $selector_html,
'after_summary_html' => $after_summary_html,
);
$plugin = \Magdev\WcComposableProduct\Plugin::instance();
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- output escaped by Twig template.
echo $plugin->render_template( 'single-product-composable.html.twig', $context );
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WooCommerce hook.
do_action( 'woocommerce_after_single_product' );