product-layout.php

I’ll build this as a reusable WooCommerce product layout module for your RX theme, focused on hooks, layout controls, badges, sale countdown, gallery tweaks, trust blocks, tabs, upsells, related products, schema-friendly markup, and safe fallbacks.

Below is an advanced inc/woocommerce/product-layout.php file for your RX theme. It adds many possible WooCommerce single-product layout features safely, using theme options through filters so you can customize later.

Create this file:

rx-theme/inc/woocommerce/product-layout.php

Then include it from your functions.php:

if ( file_exists( get_template_directory() . '/inc/woocommerce/product-layout.php' ) ) {
	require_once get_template_directory() . '/inc/woocommerce/product-layout.php';
}

inc/woocommerce/product-layout.php

<?php
/**
 * RX Theme WooCommerce Product Layout
 *
 * Advanced single product layout, product badges, gallery tweaks,
 * trust box, delivery info, sticky add-to-cart, custom tabs,
 * related/upsell/cross-sell controls, sale countdown, product meta,
 * schema-friendly sections, and safe hook management.
 *
 * @package RX_Theme
 */

defined( 'ABSPATH' ) || exit;

if ( ! class_exists( 'WooCommerce' ) ) {
	return;
}

if ( ! class_exists( 'RX_WooCommerce_Product_Layout' ) ) :

final class RX_WooCommerce_Product_Layout {

	/**
	 * Class instance.
	 *
	 * @var RX_WooCommerce_Product_Layout|null
	 */
	private static $instance = null;

	/**
	 * Get instance.
	 */
	public static function instance() {
		if ( null === self::$instance ) {
			self::$instance = new self();
		}

		return self::$instance;
	}

	/**
	 * Constructor.
	 */
	private function __construct() {
		add_action( 'after_setup_theme', array( $this, 'theme_support' ), 20 );

		add_action( 'wp', array( $this, 'setup_single_product_hooks' ) );
		add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_assets' ), 30 );

		add_filter( 'body_class', array( $this, 'body_classes' ) );

		add_filter( 'woocommerce_product_tabs', array( $this, 'custom_product_tabs' ), 30 );
		add_filter( 'woocommerce_output_related_products_args', array( $this, 'related_products_args' ) );
		add_filter( 'woocommerce_upsell_display_args', array( $this, 'upsell_products_args' ) );

		add_filter( 'woocommerce_sale_flash', array( $this, 'custom_sale_flash' ), 20, 3 );
		add_filter( 'woocommerce_get_price_html', array( $this, 'price_html_enhancement' ), 20, 2 );

		add_filter( 'woocommerce_single_product_image_gallery_classes', array( $this, 'gallery_classes' ) );

		add_action( 'woocommerce_before_shop_loop_item_title', array( $this, 'loop_product_badges' ), 9 );
		add_action( 'woocommerce_before_single_product_summary', array( $this, 'single_product_badges' ), 5 );

		add_action( 'woocommerce_share', array( $this, 'share_buttons' ), 20 );

		add_action( 'wp_footer', array( $this, 'sticky_add_to_cart' ) );
		add_action( 'wp_footer', array( $this, 'inline_product_script' ), 99 );
	}

	/**
	 * Default option helper.
	 */
	private function option( $key, $default = null ) {
		$options = array(
			'layout_style'                  => 'default',
			'enable_breadcrumb'            => true,
			'enable_badges'                => true,
			'enable_sale_countdown'        => true,
			'enable_stock_notice'          => true,
			'enable_short_info'            => true,
			'enable_trust_box'             => true,
			'enable_delivery_box'          => true,
			'enable_secure_checkout'       => true,
			'enable_product_policy'        => true,
			'enable_share_buttons'         => true,
			'enable_sticky_cart'           => true,
			'enable_sku_highlight'         => true,
			'enable_brand_meta'            => true,
			'enable_category_chips'        => true,
			'enable_tag_chips'             => true,
			'enable_custom_tabs'           => true,
			'enable_size_guide_tab'        => true,
			'enable_shipping_tab'          => true,
			'enable_faq_tab'               => true,
			'enable_after_summary_banner'  => true,
			'enable_related_products'      => true,
			'enable_upsells'               => true,
			'related_products_count'       => 4,
			'related_products_columns'     => 4,
			'upsell_products_count'        => 4,
			'upsell_products_columns'      => 4,
			'show_free_shipping_threshold' => true,
			'free_shipping_threshold'      => 100,
			'currency_symbol'              => get_woocommerce_currency_symbol(),
			'low_stock_quantity'           => 5,
		);

		$options = apply_filters( 'rx_product_layout_options', $options );

		return isset( $options[ $key ] ) ? $options[ $key ] : $default;
	}

	/**
	 * WooCommerce gallery support.
	 */
	public function theme_support() {
		add_theme_support( 'woocommerce' );
		add_theme_support( 'wc-product-gallery-zoom' );
		add_theme_support( 'wc-product-gallery-lightbox' );
		add_theme_support( 'wc-product-gallery-slider' );
	}

	/**
	 * Setup hooks for single product page.
	 */
	public function setup_single_product_hooks() {
		if ( ! is_product() ) {
			return;
		}

		if ( ! $this->option( 'enable_breadcrumb', true ) ) {
			remove_action( 'woocommerce_before_main_content', 'woocommerce_breadcrumb', 20 );
		}

		add_action( 'woocommerce_before_single_product', array( $this, 'opening_product_wrapper' ), 1 );
		add_action( 'woocommerce_after_single_product', array( $this, 'closing_product_wrapper' ), 99 );

		add_action( 'woocommerce_single_product_summary', array( $this, 'product_subtitle_area' ), 6 );
		add_action( 'woocommerce_single_product_summary', array( $this, 'rating_summary_box' ), 11 );
		add_action( 'woocommerce_single_product_summary', array( $this, 'stock_and_sold_notice' ), 21 );
		add_action( 'woocommerce_single_product_summary', array( $this, 'sale_countdown' ), 22 );
		add_action( 'woocommerce_single_product_summary', array( $this, 'short_info_list' ), 23 );
		add_action( 'woocommerce_single_product_summary', array( $this, 'delivery_box' ), 31 );
		add_action( 'woocommerce_single_product_summary', array( $this, 'trust_box' ), 32 );
		add_action( 'woocommerce_single_product_summary', array( $this, 'secure_checkout_box' ), 33 );
		add_action( 'woocommerce_single_product_summary', array( $this, 'enhanced_product_meta' ), 41 );

		add_action( 'woocommerce_after_single_product_summary', array( $this, 'after_summary_banner' ), 7 );
		add_action( 'woocommerce_after_single_product_summary', array( $this, 'product_policy_grid' ), 8 );

		if ( ! $this->option( 'enable_related_products', true ) ) {
			remove_action( 'woocommerce_after_single_product_summary', 'woocommerce_output_related_products', 20 );
		}

		if ( ! $this->option( 'enable_upsells', true ) ) {
			remove_action( 'woocommerce_after_single_product_summary', 'woocommerce_upsell_display', 15 );
		}
	}

	/**
	 * Body classes.
	 */
	public function body_classes( $classes ) {
		if ( is_product() ) {
			$classes[] = 'rx-single-product';
			$classes[] = 'rx-product-layout-' . sanitize_html_class( $this->option( 'layout_style', 'default' ) );

			if ( $this->option( 'enable_sticky_cart', true ) ) {
				$classes[] = 'rx-has-sticky-cart';
			}
		}

		return $classes;
	}

	/**
	 * Enqueue CSS/JS.
	 */
	public function enqueue_assets() {
		if ( ! is_product() && ! is_shop() && ! is_product_taxonomy() ) {
			return;
		}

		$css = $this->inline_css();

		wp_register_style( 'rx-product-layout', false, array(), RX_THEME_VERSION ?? '1.0.0' );
		wp_enqueue_style( 'rx-product-layout' );
		wp_add_inline_style( 'rx-product-layout', $css );

		wp_register_script( 'rx-product-layout', false, array( 'jquery' ), RX_THEME_VERSION ?? '1.0.0', true );
		wp_enqueue_script( 'rx-product-layout' );
	}

	/**
	 * Opening wrapper.
	 */
	public function opening_product_wrapper() {
		echo '<div class="rx-product-page-shell">';
	}

	/**
	 * Closing wrapper.
	 */
	public function closing_product_wrapper() {
		echo '</div>';
	}

	/**
	 * Product subtitle.
	 */
	public function product_subtitle_area() {
		global $product;

		if ( ! $product instanceof WC_Product ) {
			return;
		}

		$subtitle = get_post_meta( $product->get_id(), '_rx_product_subtitle', true );

		if ( empty( $subtitle ) ) {
			$subtitle = apply_filters( 'rx_product_default_subtitle', '', $product );
		}

		if ( ! empty( $subtitle ) ) {
			echo '<div class="rx-product-subtitle">' . esc_html( $subtitle ) . '</div>';
		}
	}

	/**
	 * Rating summary.
	 */
	public function rating_summary_box() {
		global $product;

		if ( ! $product instanceof WC_Product ) {
			return;
		}

		$count  = $product->get_rating_count();
		$rating = $product->get_average_rating();

		if ( $count <= 0 ) {
			echo '<div class="rx-rating-summary rx-no-rating">';
			echo '<span>' . esc_html__( 'No reviews yet', 'rx-theme' ) . '</span>';
			echo '</div>';
			return;
		}

		echo '<div class="rx-rating-summary">';
		echo '<span class="rx-rating-score">' . esc_html( number_format_i18n( $rating, 1 ) ) . '</span>';
		echo wc_get_rating_html( $rating, $count );
		echo '<a href="#reviews" class="rx-review-link">';
		printf(
			esc_html( _n( '%s customer review', '%s customer reviews', $count, 'rx-theme' ) ),
			esc_html( number_format_i18n( $count ) )
		);
		echo '</a>';
		echo '</div>';
	}

	/**
	 * Stock and sold notice.
	 */
	public function stock_and_sold_notice() {
		if ( ! $this->option( 'enable_stock_notice', true ) ) {
			return;
		}

		global $product;

		if ( ! $product instanceof WC_Product ) {
			return;
		}

		echo '<div class="rx-stock-sold-box">';

		if ( $product->is_in_stock() ) {
			$stock_quantity = $product->get_stock_quantity();

			if ( null !== $stock_quantity && $stock_quantity > 0 && $stock_quantity <= absint( $this->option( 'low_stock_quantity', 5 ) ) ) {
				echo '<div class="rx-low-stock">' . esc_html( sprintf( __( 'Only %d item(s) left in stock.', 'rx-theme' ), $stock_quantity ) ) . '</div>';
			} else {
				echo '<div class="rx-in-stock">' . esc_html__( 'Available in stock', 'rx-theme' ) . '</div>';
			}
		} else {
			echo '<div class="rx-out-stock">' . esc_html__( 'Currently out of stock', 'rx-theme' ) . '</div>';
		}

		$total_sales = (int) $product->get_total_sales();

		if ( $total_sales > 0 ) {
			echo '<div class="rx-sold-count">';
			echo esc_html( sprintf( __( '%d sold recently', 'rx-theme' ), $total_sales ) );
			echo '</div>';
		}

		echo '</div>';
	}

	/**
	 * Sale countdown.
	 */
	public function sale_countdown() {
		if ( ! $this->option( 'enable_sale_countdown', true ) ) {
			return;
		}

		global $product;

		if ( ! $product instanceof WC_Product || ! $product->is_on_sale() ) {
			return;
		}

		$date_to = $product->get_date_on_sale_to();

		if ( ! $date_to ) {
			return;
		}

		$timestamp = $date_to->getTimestamp();

		if ( $timestamp <= time() ) {
			return;
		}

		echo '<div class="rx-sale-countdown" data-rx-countdown="' . esc_attr( $timestamp ) . '">';
		echo '<span class="rx-sale-countdown-label">' . esc_html__( 'Sale ends in:', 'rx-theme' ) . '</span>';
		echo '<span class="rx-countdown-value">';
		echo '<span data-days>00</span>d ';
		echo '<span data-hours>00</span>h ';
		echo '<span data-minutes>00</span>m ';
		echo '<span data-seconds>00</span>s';
		echo '</span>';
		echo '</div>';
	}

	/**
	 * Short product info.
	 */
	public function short_info_list() {
		if ( ! $this->option( 'enable_short_info', true ) ) {
			return;
		}

		global $product;

		if ( ! $product instanceof WC_Product ) {
			return;
		}

		$items = array();

		if ( $product->is_virtual() ) {
			$items[] = __( 'Digital / virtual product', 'rx-theme' );
		}

		if ( $product->is_downloadable() ) {
			$items[] = __( 'Downloadable product', 'rx-theme' );
		}

		if ( $product->get_sku() ) {
			$items[] = sprintf( __( 'SKU: %s', 'rx-theme' ), $product->get_sku() );
		}

		if ( $product->get_weight() ) {
			$items[] = sprintf( __( 'Weight: %s', 'rx-theme' ), wc_format_weight( $product->get_weight() ) );
		}

		$items = apply_filters( 'rx_product_short_info_items', $items, $product );

		if ( empty( $items ) ) {
			return;
		}

		echo '<ul class="rx-product-short-info">';

		foreach ( $items as $item ) {
			echo '<li>' . esc_html( $item ) . '</li>';
		}

		echo '</ul>';
	}

	/**
	 * Delivery box.
	 */
	public function delivery_box() {
		if ( ! $this->option( 'enable_delivery_box', true ) ) {
			return;
		}

		$threshold = floatval( $this->option( 'free_shipping_threshold', 100 ) );
		$symbol    = $this->option( 'currency_symbol', get_woocommerce_currency_symbol() );

		echo '<div class="rx-delivery-box">';
		echo '<div class="rx-box-title">' . esc_html__( 'Delivery Information', 'rx-theme' ) . '</div>';
		echo '<ul>';

		echo '<li>' . esc_html__( 'Fast order processing after confirmation.', 'rx-theme' ) . '</li>';
		echo '<li>' . esc_html__( 'Secure packaging for safer delivery.', 'rx-theme' ) . '</li>';

		if ( $this->option( 'show_free_shipping_threshold', true ) ) {
			echo '<li>' . esc_html( sprintf( __( 'Free shipping may be available over %1$s%2$s.', 'rx-theme' ), $symbol, $threshold ) ) . '</li>';
		}

		echo '</ul>';
		echo '</div>';
	}

	/**
	 * Trust box.
	 */
	public function trust_box() {
		if ( ! $this->option( 'enable_trust_box', true ) ) {
			return;
		}

		$items = apply_filters(
			'rx_product_trust_items',
			array(
				array(
					'title' => __( 'Quality Checked', 'rx-theme' ),
					'text'  => __( 'Product information is reviewed before publishing.', 'rx-theme' ),
				),
				array(
					'title' => __( 'Secure Shopping', 'rx-theme' ),
					'text'  => __( 'Your checkout process is protected.', 'rx-theme' ),
				),
				array(
					'title' => __( 'Support Available', 'rx-theme' ),
					'text'  => __( 'Contact support for order-related help.', 'rx-theme' ),
				),
			)
		);

		echo '<div class="rx-trust-box">';

		foreach ( $items as $item ) {
			$title = isset( $item['title'] ) ? $item['title'] : '';
			$text  = isset( $item['text'] ) ? $item['text'] : '';

			echo '<div class="rx-trust-item">';
			echo '<strong>' . esc_html( $title ) . '</strong>';
			echo '<span>' . esc_html( $text ) . '</span>';
			echo '</div>';
		}

		echo '</div>';
	}

	/**
	 * Secure checkout box.
	 */
	public function secure_checkout_box() {
		if ( ! $this->option( 'enable_secure_checkout', true ) ) {
			return;
		}

		echo '<div class="rx-secure-checkout">';
		echo '<strong>' . esc_html__( 'Secure checkout', 'rx-theme' ) . '</strong>';
		echo '<span>' . esc_html__( 'Payments and order details are handled through WooCommerce checkout.', 'rx-theme' ) . '</span>';
		echo '</div>';
	}

	/**
	 * Enhanced product meta.
	 */
	public function enhanced_product_meta() {
		global $product;

		if ( ! $product instanceof WC_Product ) {
			return;
		}

		echo '<div class="rx-enhanced-product-meta">';

		if ( $this->option( 'enable_sku_highlight', true ) && $product->get_sku() ) {
			echo '<div class="rx-meta-row rx-sku-row">';
			echo '<span>' . esc_html__( 'Product Code:', 'rx-theme' ) . '</span>';
			echo '<strong>' . esc_html( $product->get_sku() ) . '</strong>';
			echo '</div>';
		}

		if ( $this->option( 'enable_category_chips', true ) ) {
			$categories = wc_get_product_category_list( $product->get_id(), ' ' );

			if ( $categories ) {
				echo '<div class="rx-meta-row rx-category-row">';
				echo '<span>' . esc_html__( 'Categories:', 'rx-theme' ) . '</span>';
				echo '<div class="rx-meta-chips">' . wp_kses_post( $categories ) . '</div>';
				echo '</div>';
			}
		}

		if ( $this->option( 'enable_tag_chips', true ) ) {
			$tags = wc_get_product_tag_list( $product->get_id(), ' ' );

			if ( $tags ) {
				echo '<div class="rx-meta-row rx-tag-row">';
				echo '<span>' . esc_html__( 'Tags:', 'rx-theme' ) . '</span>';
				echo '<div class="rx-meta-chips">' . wp_kses_post( $tags ) . '</div>';
				echo '</div>';
			}
		}

		do_action( 'rx_after_enhanced_product_meta', $product );

		echo '</div>';
	}

	/**
	 * After summary banner.
	 */
	public function after_summary_banner() {
		if ( ! $this->option( 'enable_after_summary_banner', true ) ) {
			return;
		}

		echo '<section class="rx-after-summary-banner">';
		echo '<div>';
		echo '<h2>' . esc_html__( 'Why choose this product?', 'rx-theme' ) . '</h2>';
		echo '<p>' . esc_html__( 'This section can highlight quality, delivery, warranty, return policy, or special product benefits.', 'rx-theme' ) . '</p>';
		echo '</div>';
		echo '</section>';
	}

	/**
	 * Policy grid.
	 */
	public function product_policy_grid() {
		if ( ! $this->option( 'enable_product_policy', true ) ) {
			return;
		}

		$policies = apply_filters(
			'rx_product_policy_items',
			array(
				array(
					'title' => __( 'Easy Returns', 'rx-theme' ),
					'text'  => __( 'Return policy depends on product type and store rules.', 'rx-theme' ),
				),
				array(
					'title' => __( 'Original Product', 'rx-theme' ),
					'text'  => __( 'Product details are displayed transparently.', 'rx-theme' ),
				),
				array(
					'title' => __( 'Safe Packaging', 'rx-theme' ),
					'text'  => __( 'Products are packed carefully before shipping.', 'rx-theme' ),
				),
				array(
					'title' => __( 'Customer Help', 'rx-theme' ),
					'text'  => __( 'Support is available for order questions.', 'rx-theme' ),
				),
			)
		);

		echo '<section class="rx-product-policy-grid">';

		foreach ( $policies as $policy ) {
			$title = isset( $policy['title'] ) ? $policy['title'] : '';
			$text  = isset( $policy['text'] ) ? $policy['text'] : '';

			echo '<article class="rx-policy-card">';
			echo '<h3>' . esc_html( $title ) . '</h3>';
			echo '<p>' . esc_html( $text ) . '</p>';
			echo '</article>';
		}

		echo '</section>';
	}

	/**
	 * Product tabs.
	 */
	public function custom_product_tabs( $tabs ) {
		if ( ! $this->option( 'enable_custom_tabs', true ) ) {
			return $tabs;
		}

		if ( $this->option( 'enable_size_guide_tab', true ) ) {
			$tabs['rx_size_guide'] = array(
				'title'    => __( 'Size Guide', 'rx-theme' ),
				'priority' => 35,
				'callback' => array( $this, 'size_guide_tab_content' ),
			);
		}

		if ( $this->option( 'enable_shipping_tab', true ) ) {
			$tabs['rx_shipping_info'] = array(
				'title'    => __( 'Shipping Info', 'rx-theme' ),
				'priority' => 45,
				'callback' => array( $this, 'shipping_tab_content' ),
			);
		}

		if ( $this->option( 'enable_faq_tab', true ) ) {
			$tabs['rx_product_faq'] = array(
				'title'    => __( 'FAQs', 'rx-theme' ),
				'priority' => 55,
				'callback' => array( $this, 'faq_tab_content' ),
			);
		}

		return $tabs;
	}

	/**
	 * Size guide content.
	 */
	public function size_guide_tab_content() {
		echo '<div class="rx-tab-content rx-size-guide-tab">';
		echo '<h2>' . esc_html__( 'Size Guide', 'rx-theme' ) . '</h2>';
		echo '<p>' . esc_html__( 'Use this section to add product measurement guidance. You can customize this content using the rx_size_guide_tab_content action.', 'rx-theme' ) . '</p>';
		do_action( 'rx_size_guide_tab_content' );
		echo '</div>';
	}

	/**
	 * Shipping tab content.
	 */
	public function shipping_tab_content() {
		echo '<div class="rx-tab-content rx-shipping-tab">';
		echo '<h2>' . esc_html__( 'Shipping Information', 'rx-theme' ) . '</h2>';
		echo '<p>' . esc_html__( 'Shipping time and cost may depend on customer location, product type, and available delivery method.', 'rx-theme' ) . '</p>';
		do_action( 'rx_shipping_tab_content' );
		echo '</div>';
	}

	/**
	 * FAQ tab content.
	 */
	public function faq_tab_content() {
		$faqs = apply_filters(
			'rx_product_faq_items',
			array(
				array(
					'q' => __( 'Is this product available now?', 'rx-theme' ),
					'a' => __( 'Availability is shown near the product price and add-to-cart button.', 'rx-theme' ),
				),
				array(
					'q' => __( 'Can I return this product?', 'rx-theme' ),
					'a' => __( 'Return availability depends on the store policy and product condition.', 'rx-theme' ),
				),
				array(
					'q' => __( 'How can I get support?', 'rx-theme' ),
					'a' => __( 'You can contact the store support team through the contact page or order support channel.', 'rx-theme' ),
				),
			)
		);

		echo '<div class="rx-tab-content rx-faq-tab">';
		echo '<h2>' . esc_html__( 'Frequently Asked Questions', 'rx-theme' ) . '</h2>';

		foreach ( $faqs as $faq ) {
			$q = isset( $faq['q'] ) ? $faq['q'] : '';
			$a = isset( $faq['a'] ) ? $faq['a'] : '';

			echo '<details class="rx-faq-item">';
			echo '<summary>' . esc_html( $q ) . '</summary>';
			echo '<p>' . esc_html( $a ) . '</p>';
			echo '</details>';
		}

		echo '</div>';
	}

	/**
	 * Related products args.
	 */
	public function related_products_args( $args ) {
		$args['posts_per_page'] = absint( $this->option( 'related_products_count', 4 ) );
		$args['columns']        = absint( $this->option( 'related_products_columns', 4 ) );

		return $args;
	}

	/**
	 * Upsell products args.
	 */
	public function upsell_products_args( $args ) {
		$args['posts_per_page'] = absint( $this->option( 'upsell_products_count', 4 ) );
		$args['columns']        = absint( $this->option( 'upsell_products_columns', 4 ) );

		return $args;
	}

	/**
	 * Sale flash.
	 */
	public function custom_sale_flash( $html, $post, $product ) {
		if ( ! $this->option( 'enable_badges', true ) || ! $product instanceof WC_Product ) {
			return $html;
		}

		$percentage = $this->get_sale_percentage( $product );

		if ( $percentage > 0 ) {
			return '<span class="onsale rx-sale-badge">' . esc_html( sprintf( __( '-%d%%', 'rx-theme' ), $percentage ) ) . '</span>';
		}

		return '<span class="onsale rx-sale-badge">' . esc_html__( 'Sale', 'rx-theme' ) . '</span>';
	}

	/**
	 * Price enhancement.
	 */
	public function price_html_enhancement( $price, $product ) {
		if ( ! $product instanceof WC_Product ) {
			return $price;
		}

		if ( $product->is_on_sale() ) {
			$percentage = $this->get_sale_percentage( $product );

			if ( $percentage > 0 ) {
				$price .= '<span class="rx-price-saving">' . esc_html( sprintf( __( 'Save %d%%', 'rx-theme' ), $percentage ) ) . '</span>';
			}
		}

		return $price;
	}

	/**
	 * Gallery classes.
	 */
	public function gallery_classes( $classes ) {
		$classes[] = 'rx-product-gallery';
		$classes[] = 'rx-product-gallery-enhanced';

		return $classes;
	}

	/**
	 * Loop badges.
	 */
	public function loop_product_badges() {
		global $product;

		if ( ! $product instanceof WC_Product || ! $this->option( 'enable_badges', true ) ) {
			return;
		}

		echo '<div class="rx-loop-badges">';
		$this->render_badges( $product );
		echo '</div>';
	}

	/**
	 * Single product badges.
	 */
	public function single_product_badges() {
		global $product;

		if ( ! $product instanceof WC_Product || ! $this->option( 'enable_badges', true ) ) {
			return;
		}

		echo '<div class="rx-single-badges">';
		$this->render_badges( $product );
		echo '</div>';
	}

	/**
	 * Render product badges.
	 */
	private function render_badges( $product ) {
		if ( $product->is_on_sale() ) {
			$percentage = $this->get_sale_percentage( $product );

			if ( $percentage > 0 ) {
				echo '<span class="rx-badge rx-badge-sale">' . esc_html( sprintf( __( '-%d%%', 'rx-theme' ), $percentage ) ) . '</span>';
			} else {
				echo '<span class="rx-badge rx-badge-sale">' . esc_html__( 'Sale', 'rx-theme' ) . '</span>';
			}
		}

		if ( ! $product->is_in_stock() ) {
			echo '<span class="rx-badge rx-badge-stock">' . esc_html__( 'Out of stock', 'rx-theme' ) . '</span>';
		}

		if ( $product->is_featured() ) {
			echo '<span class="rx-badge rx-badge-featured">' . esc_html__( 'Featured', 'rx-theme' ) . '</span>';
		}

		$date_created = $product->get_date_created();

		if ( $date_created && $date_created->getTimestamp() > strtotime( '-30 days' ) ) {
			echo '<span class="rx-badge rx-badge-new">' . esc_html__( 'New', 'rx-theme' ) . '</span>';
		}

		do_action( 'rx_product_extra_badges', $product );
	}

	/**
	 * Get sale percentage.
	 */
	private function get_sale_percentage( $product ) {
		if ( ! $product instanceof WC_Product || ! $product->is_on_sale() ) {
			return 0;
		}

		if ( $product->is_type( 'variable' ) ) {
			$regular_prices = $product->get_variation_regular_price( 'max', true );
			$sale_prices    = $product->get_variation_sale_price( 'min', true );

			if ( $regular_prices > 0 && $sale_prices > 0 ) {
				return round( ( ( $regular_prices - $sale_prices ) / $regular_prices ) * 100 );
			}
		}

		$regular = (float) $product->get_regular_price();
		$sale    = (float) $product->get_sale_price();

		if ( $regular > 0 && $sale > 0 ) {
			return round( ( ( $regular - $sale ) / $regular ) * 100 );
		}

		return 0;
	}

	/**
	 * Share buttons.
	 */
	public function share_buttons() {
		if ( ! $this->option( 'enable_share_buttons', true ) ) {
			return;
		}

		global $product;

		if ( ! $product instanceof WC_Product ) {
			return;
		}

		$url   = rawurlencode( get_permalink( $product->get_id() ) );
		$title = rawurlencode( get_the_title( $product->get_id() ) );

		echo '<div class="rx-product-share">';
		echo '<span>' . esc_html__( 'Share:', 'rx-theme' ) . '</span>';
		echo '<a target="_blank" rel="noopener noreferrer" href="' . esc_url( 'https://www.facebook.com/sharer/sharer.php?u=' . $url ) . '">' . esc_html__( 'Facebook', 'rx-theme' ) . '</a>';
		echo '<a target="_blank" rel="noopener noreferrer" href="' . esc_url( 'https://twitter.com/intent/tweet?url=' . $url . '&text=' . $title ) . '">' . esc_html__( 'X', 'rx-theme' ) . '</a>';
		echo '<a target="_blank" rel="noopener noreferrer" href="' . esc_url( 'https://www.linkedin.com/shareArticle?mini=true&url=' . $url . '&title=' . $title ) . '">' . esc_html__( 'LinkedIn', 'rx-theme' ) . '</a>';
		echo '<a target="_blank" rel="noopener noreferrer" href="' . esc_url( 'https://api.whatsapp.com/send?text=' . $title . '%20' . $url ) . '">' . esc_html__( 'WhatsApp', 'rx-theme' ) . '</a>';
		echo '</div>';
	}

	/**
	 * Sticky add to cart.
	 */
	public function sticky_add_to_cart() {
		if ( ! is_product() || ! $this->option( 'enable_sticky_cart', true ) ) {
			return;
		}

		global $product;

		if ( ! $product instanceof WC_Product || ! $product->is_purchasable() ) {
			return;
		}

		echo '<div class="rx-sticky-add-to-cart" data-rx-sticky-cart>';
		echo '<div class="rx-sticky-inner">';

		echo '<div class="rx-sticky-product-info">';

		if ( has_post_thumbnail( $product->get_id() ) ) {
			echo get_the_post_thumbnail( $product->get_id(), 'thumbnail', array( 'class' => 'rx-sticky-thumb' ) );
		}

		echo '<div>';
		echo '<strong>' . esc_html( get_the_title( $product->get_id() ) ) . '</strong>';
		echo '<span>' . wp_kses_post( $product->get_price_html() ) . '</span>';
		echo '</div>';
		echo '</div>';

		echo '<a class="button rx-sticky-cart-button" href="#product-' . esc_attr( $product->get_id() ) . '">';
		echo esc_html__( 'View Purchase Options', 'rx-theme' );
		echo '</a>';

		echo '</div>';
		echo '</div>';
	}

	/**
	 * Inline product JS.
	 */
	public function inline_product_script() {
		if ( ! is_product() ) {
			return;
		}
		?>
		<script>
			(function () {
				'use strict';

				function rxCountdown() {
					var timers = document.querySelectorAll('[data-rx-countdown]');
					if (!timers.length) {
						return;
					}

					timers.forEach(function (timer) {
						var end = parseInt(timer.getAttribute('data-rx-countdown'), 10) * 1000;

						function tick() {
							var now = new Date().getTime();
							var distance = end - now;

							if (distance <= 0) {
								timer.style.display = 'none';
								return;
							}

							var days = Math.floor(distance / (1000 * 60 * 60 * 24));
							var hours = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
							var minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60));
							var seconds = Math.floor((distance % (1000 * 60)) / 1000);

							var d = timer.querySelector('[data-days]');
							var h = timer.querySelector('[data-hours]');
							var m = timer.querySelector('[data-minutes]');
							var s = timer.querySelector('[data-seconds]');

							if (d) d.textContent = String(days).padStart(2, '0');
							if (h) h.textContent = String(hours).padStart(2, '0');
							if (m) m.textContent = String(minutes).padStart(2, '0');
							if (s) s.textContent = String(seconds).padStart(2, '0');
						}

						tick();
						setInterval(tick, 1000);
					});
				}

				function rxStickyCart() {
					var sticky = document.querySelector('[data-rx-sticky-cart]');
					var cart = document.querySelector('form.cart');

					if (!sticky || !cart) {
						return;
					}

					function toggleSticky() {
						var rect = cart.getBoundingClientRect();

						if (rect.bottom < 0) {
							sticky.classList.add('is-visible');
						} else {
							sticky.classList.remove('is-visible');
						}
					}

					window.addEventListener('scroll', toggleSticky, { passive: true });
					window.addEventListener('resize', toggleSticky);
					toggleSticky();
				}

				document.addEventListener('DOMContentLoaded', function () {
					rxCountdown();
					rxStickyCart();
				});
			})();
		</script>
		<?php
	}

	/**
	 * Inline CSS.
	 */
	private function inline_css() {
		return '
			.rx-product-page-shell {
				position: relative;
			}

			.rx-single-product .summary {
				position: relative;
			}

			.rx-product-subtitle {
				margin: 6px 0 12px;
				font-size: 15px;
				color: #555;
				line-height: 1.6;
			}

			.rx-rating-summary {
				display: flex;
				flex-wrap: wrap;
				align-items: center;
				gap: 8px;
				margin: 10px 0 14px;
				font-size: 14px;
			}

			.rx-rating-score {
				font-weight: 700;
				padding: 3px 8px;
				border-radius: 999px;
				background: rgba(0,0,0,0.06);
			}

			.rx-review-link {
				text-decoration: none;
			}

			.rx-stock-sold-box {
				display: flex;
				flex-wrap: wrap;
				gap: 8px;
				margin: 12px 0;
			}

			.rx-stock-sold-box > div {
				padding: 7px 12px;
				border-radius: 999px;
				font-size: 13px;
				font-weight: 600;
				background: rgba(0,0,0,0.06);
			}

			.rx-in-stock {
				color: #087f23;
			}

			.rx-low-stock {
				color: #a15c00;
			}

			.rx-out-stock {
				color: #b00020;
			}

			.rx-sale-countdown {
				display: flex;
				flex-wrap: wrap;
				align-items: center;
				gap: 8px;
				margin: 14px 0;
				padding: 12px 14px;
				border-radius: 12px;
				background: rgba(220, 53, 69, 0.08);
				border: 1px solid rgba(220, 53, 69, 0.18);
			}

			.rx-sale-countdown-label {
				font-weight: 700;
			}

			.rx-countdown-value span {
				font-weight: 800;
			}

			.rx-product-short-info {
				margin: 14px 0;
				padding: 0;
				list-style: none;
				display: grid;
				gap: 8px;
			}

			.rx-product-short-info li {
				padding: 9px 12px;
				border-radius: 10px;
				background: rgba(0,0,0,0.04);
			}

			.rx-delivery-box,
			.rx-secure-checkout,
			.rx-trust-box {
				margin: 16px 0;
				padding: 16px;
				border-radius: 14px;
				border: 1px solid rgba(0,0,0,0.08);
				background: #fff;
				box-shadow: 0 8px 22px rgba(0,0,0,0.04);
			}

			.rx-box-title {
				font-weight: 800;
				margin-bottom: 8px;
			}

			.rx-delivery-box ul {
				margin: 0;
				padding-left: 18px;
			}

			.rx-trust-box {
				display: grid;
				grid-template-columns: repeat(3, minmax(0, 1fr));
				gap: 12px;
			}

			.rx-trust-item {
				display: grid;
				gap: 4px;
			}

			.rx-trust-item strong {
				font-size: 14px;
			}

			.rx-trust-item span {
				font-size: 13px;
				color: #666;
				line-height: 1.5;
			}

			.rx-secure-checkout {
				display: grid;
				gap: 4px;
			}

			.rx-secure-checkout strong {
				font-size: 15px;
			}

			.rx-secure-checkout span {
				font-size: 13px;
				color: #666;
			}

			.rx-enhanced-product-meta {
				margin: 18px 0;
				display: grid;
				gap: 10px;
			}

			.rx-meta-row {
				display: flex;
				flex-wrap: wrap;
				gap: 8px;
				align-items: center;
				font-size: 14px;
			}

			.rx-meta-row > span {
				font-weight: 700;
			}

			.rx-meta-chips {
				display: flex;
				flex-wrap: wrap;
				gap: 6px;
			}

			.rx-meta-chips a {
				display: inline-flex;
				padding: 5px 9px;
				border-radius: 999px;
				background: rgba(0,0,0,0.06);
				text-decoration: none;
				font-size: 12px;
			}

			.rx-after-summary-banner {
				margin: 28px 0;
				padding: 26px;
				border-radius: 18px;
				background: linear-gradient(135deg, rgba(0,0,0,0.04), rgba(0,0,0,0.01));
				border: 1px solid rgba(0,0,0,0.06);
			}

			.rx-after-summary-banner h2 {
				margin-top: 0;
				margin-bottom: 8px;
			}

			.rx-after-summary-banner p {
				margin-bottom: 0;
			}

			.rx-product-policy-grid {
				display: grid;
				grid-template-columns: repeat(4, minmax(0, 1fr));
				gap: 16px;
				margin: 26px 0;
			}

			.rx-policy-card {
				padding: 18px;
				border-radius: 14px;
				border: 1px solid rgba(0,0,0,0.08);
				background: #fff;
			}

			.rx-policy-card h3 {
				margin-top: 0;
				margin-bottom: 8px;
				font-size: 16px;
			}

			.rx-policy-card p {
				margin: 0;
				font-size: 14px;
				color: #666;
				line-height: 1.6;
			}

			.rx-single-badges,
			.rx-loop-badges {
				position: absolute;
				z-index: 5;
				top: 12px;
				left: 12px;
				display: flex;
				flex-wrap: wrap;
				gap: 6px;
			}

			.rx-loop-badges {
				top: 10px;
				left: 10px;
			}

			.rx-badge,
			.rx-sale-badge {
				display: inline-flex;
				align-items: center;
				justify-content: center;
				padding: 5px 9px;
				border-radius: 999px;
				font-size: 12px;
				font-weight: 800;
				line-height: 1;
				background: #111;
				color: #fff;
			}

			.rx-badge-sale,
			.rx-sale-badge {
				background: #d92332;
			}

			.rx-badge-new {
				background: #087f23;
			}

			.rx-badge-featured {
				background: #5b35d5;
			}

			.rx-badge-stock {
				background: #555;
			}

			.rx-price-saving {
				display: inline-flex;
				margin-left: 8px;
				padding: 4px 8px;
				border-radius: 999px;
				font-size: 12px;
				font-weight: 700;
				background: rgba(217,35,50,0.08);
				color: #d92332;
				vertical-align: middle;
			}

			.rx-product-share {
				display: flex;
				flex-wrap: wrap;
				gap: 8px;
				align-items: center;
				margin-top: 16px;
			}

			.rx-product-share span {
				font-weight: 700;
			}

			.rx-product-share a {
				display: inline-flex;
				padding: 6px 10px;
				border-radius: 999px;
				background: rgba(0,0,0,0.06);
				text-decoration: none;
				font-size: 13px;
			}

			.rx-faq-item {
				padding: 12px 0;
				border-bottom: 1px solid rgba(0,0,0,0.08);
			}

			.rx-faq-item summary {
				cursor: pointer;
				font-weight: 700;
			}

			.rx-faq-item p {
				margin-bottom: 0;
				color: #666;
			}

			.rx-sticky-add-to-cart {
				position: fixed;
				left: 0;
				right: 0;
				bottom: 0;
				z-index: 999;
				padding: 10px 16px;
				background: #fff;
				border-top: 1px solid rgba(0,0,0,0.08);
				box-shadow: 0 -8px 24px rgba(0,0,0,0.08);
				transform: translateY(110%);
				opacity: 0;
				transition: transform .25s ease, opacity .25s ease;
			}

			.rx-sticky-add-to-cart.is-visible {
				transform: translateY(0);
				opacity: 1;
			}

			.rx-sticky-inner {
				max-width: 1200px;
				margin: 0 auto;
				display: flex;
				align-items: center;
				justify-content: space-between;
				gap: 14px;
			}

			.rx-sticky-product-info {
				display: flex;
				align-items: center;
				gap: 10px;
				min-width: 0;
			}

			.rx-sticky-product-info strong {
				display: block;
				white-space: nowrap;
				overflow: hidden;
				text-overflow: ellipsis;
				max-width: 420px;
			}

			.rx-sticky-product-info span {
				display: block;
				font-size: 13px;
			}

			.rx-sticky-thumb {
				width: 46px;
				height: 46px;
				object-fit: cover;
				border-radius: 8px;
			}

			.rx-sticky-cart-button {
				white-space: nowrap;
			}

			@media (max-width: 900px) {
				.rx-trust-box,
				.rx-product-policy-grid {
					grid-template-columns: repeat(2, minmax(0, 1fr));
				}
			}

			@media (max-width: 600px) {
				.rx-trust-box,
				.rx-product-policy-grid {
					grid-template-columns: 1fr;
				}

				.rx-sticky-product-info strong {
					max-width: 180px;
				}

				.rx-sticky-inner {
					align-items: stretch;
				}

				.rx-sticky-cart-button {
					display: flex;
					align-items: center;
					justify-content: center;
				}
			}
		';
	}
}

endif;

RX_WooCommerce_Product_Layout::instance();

/**
 * Extra helper: custom product subtitle meta save support.
 * You can later connect this field with admin UI if needed.
 */
if ( ! function_exists( 'rx_get_product_subtitle' ) ) {
	function rx_get_product_subtitle( $product_id = 0 ) {
		$product_id = $product_id ? absint( $product_id ) : get_the_ID();

		return get_post_meta( $product_id, '_rx_product_subtitle', true );
	}
}

/**
 * Extra helper: check if current page is RX product page.
 */
if ( ! function_exists( 'rx_is_single_product_page' ) ) {
	function rx_is_single_product_page() {
		return function_exists( 'is_product' ) && is_product();
	}
}

/**
 * Extra helper: product badge output anywhere.
 */
if ( ! function_exists( 'rx_product_badge_output' ) ) {
	function rx_product_badge_output( $product_id = 0 ) {
		$product_id = $product_id ? absint( $product_id ) : get_the_ID();
		$product    = wc_get_product( $product_id );

		if ( ! $product ) {
			return;
		}

		if ( $product->is_on_sale() ) {
			echo '<span class="rx-badge rx-badge-sale">' . esc_html__( 'Sale', 'rx-theme' ) . '</span>';
		}

		if ( $product->is_featured() ) {
			echo '<span class="rx-badge rx-badge-featured">' . esc_html__( 'Featured', 'rx-theme' ) . '</span>';
		}

		if ( ! $product->is_in_stock() ) {
			echo '<span class="rx-badge rx-badge-stock">' . esc_html__( 'Out of stock', 'rx-theme' ) . '</span>';
		}
	}
}

/**
 * Example filter customization.
 *
 * You can move this to functions.php if you want to change settings.
 */
/*
add_filter( 'rx_product_layout_options', function( $options ) {
	$options['related_products_count'] = 8;
	$options['related_products_columns'] = 4;
	$options['upsell_products_count'] = 4;
	$options['free_shipping_threshold'] = 150;
	return $options;
} );
*/

/**
 * Example custom trust items.
 */
/*
add_filter( 'rx_product_trust_items', function( $items ) {
	return array(
		array(
			'title' => 'Doctor Reviewed',
			'text'  => 'Product information is checked carefully.',
		),
		array(
			'title' => 'Secure Order',
			'text'  => 'Safe checkout with WooCommerce.',
		),
		array(
			'title' => 'Fast Support',
			'text'  => 'Support available for order help.',
		),
	);
} );
*/

This file is safe to use as a strong starting product layout module. After adding it, clear your cache and check one WooCommerce product page. If any section looks too much, you can disable it through the rx_product_layout_options filter.

Leave a Reply

Your email address will not be published. Required fields are marked *