WooCommerce Code Reference

class-product-price.php

Source code

<?php
/**
 * This file is part of the WooCommerce Email Editor package.
 *
 * @package Automattic\WooCommerce\EmailEditor
 */

declare( strict_types = 1 );
namespace Automattic\WooCommerce\EmailEditor\Integrations\WooCommerce\Renderer\Blocks;

use Automattic\WooCommerce\EmailEditor\Engine\Renderer\ContentRenderer\Rendering_Context;
use Automattic\WooCommerce\EmailEditor\Integrations\Utils\Styles_Helper;
use Automattic\WooCommerce\EmailEditor\Integrations\Utils\Table_Wrapper_Helper;

/**
 * Renders a WooCommerce product price block for email.
 */
class Product_Price extends Abstract_Product_Block_Renderer {
	/**
	 * Render the product price block content for email.
	 *
	 * @param string            $block_content Block content.
	 * @param array             $parsed_block Parsed block.
	 * @param Rendering_Context $rendering_context Rendering context.
	 * @return string
	 */
	protected function render_content( string $block_content, array $parsed_block, Rendering_Context $rendering_context ): string {
		$product = $this->get_product_from_context( $parsed_block );
		if ( ! $product ) {
			return '';
		}

		$attributes = $parsed_block['attrs'] ?? array();

		$price_content = $this->generate_price_html( $product, $attributes, $rendering_context );

		return $this->apply_email_wrapper( $price_content, $parsed_block );
	}

	/**
	 * Generate clean price HTML from product data.
	 *
	 * @param \WC_Product       $product Product object.
	 * @param array             $attributes Block attributes.
	 * @param Rendering_Context $rendering_context Rendering context.
	 * @return string
	 */
	private function generate_price_html( \WC_Product $product, array $attributes, Rendering_Context $rendering_context ): string {
		$price_html = $this->build_price_from_scratch( $product );

		if ( empty( $price_html ) ) {
			return '';
		}

		$price_styles = array(
			'display'         => 'block',
			'margin'          => '0',
			'padding'         => '0',
			'font-family'     => 'inherit',
			'color'           => 'inherit',
			'text-decoration' => 'none',
		);

		$custom_styles = Styles_Helper::get_block_styles( $attributes, $rendering_context, array( 'border', 'background-color', 'color', 'typography', 'spacing' ) );
		$price_styles  = array_merge( $price_styles, $custom_styles['declarations'] );

		$style_attr = \WP_Style_Engine::compile_css( $price_styles, '' );

		return sprintf(
			'<div class="wc-block-components-product-price" style="%s">%s</div>',
			esc_attr( $style_attr ),
			$price_html
		);
	}

	/**
	 * Build price HTML completely from scratch based on product type.
	 *
	 * @param \WC_Product $product Product object.
	 * @return string
	 */
	private function build_price_from_scratch( \WC_Product $product ): string {
		$product_type = $product->get_type();

		switch ( $product_type ) {
			case 'simple':
			case 'external':
				return $this->build_simple_product_price( $product );

			case 'variable':
				// When the product does not have a correct type, the default will be used.
				if ( $product instanceof \WC_Product_Variable ) {
					return $this->build_variable_product_price( $product );
				}
				return $this->build_simple_product_price( $product );

			case 'grouped':
				// When the product does not have a correct type, the default will be used.
				if ( $product instanceof \WC_Product_Grouped ) {
					return $this->build_grouped_product_price( $product );
				}
				return $this->build_simple_product_price( $product );

			default:
				return $this->build_simple_product_price( $product );
		}
	}

	/**
	 * Build price HTML for simple products.
	 *
	 * @param \WC_Product $product Product object.
	 * @return string
	 */
	private function build_simple_product_price( \WC_Product $product ): string {
		$regular_price = wc_get_price_to_display( $product, array( 'price' => (float) $product->get_regular_price() ) );
		$sale_price    = $product->get_sale_price() !== '' ? wc_get_price_to_display( $product, array( 'price' => (float) $product->get_sale_price() ) ) : '';

		if ( empty( $regular_price ) ) {
			return '';
		}

		if ( $product->is_on_sale() && '' !== $sale_price ) {
			return sprintf(
				'<del style="text-decoration: line-through; font-size: 0.9em; margin-right: 0.5em;">%s</del><span>%s</span>',
				wc_price( $regular_price, array( 'in_span' => false ) ),
				wc_price( $sale_price, array( 'in_span' => false ) )
			);
		} else {
			return sprintf(
				'<span>%s</span>',
				wc_price( $regular_price, array( 'in_span' => false ) )
			);
		}
	}

	/**
	 * Build price HTML for variable products.
	 * Uses the same logic as the editor: get_variation_price() methods.
	 *
	 * @param \WC_Product_Variable $product Variable product object.
	 * @return string
	 */
	private function build_variable_product_price( \WC_Product_Variable $product ): string {
		$min_price = $product->get_variation_price( 'min', true );
		$max_price = $product->get_variation_price( 'max', true );

		return sprintf(
			'<span>%s — %s</span>',
			wc_price( (float) $min_price, array( 'in_span' => false ) ),
			wc_price( (float) $max_price, array( 'in_span' => false ) )
		);
	}

	/**
	 * Build price HTML for grouped products.
	 *
	 * @param \WC_Product_Grouped $product Grouped product object.
	 * @return string
	 */
	private function build_grouped_product_price( \WC_Product_Grouped $product ): string {
		$children = $product->get_children();

		if ( empty( $children ) ) {
			return '';
		}

		$prices = array();
		foreach ( $children as $child_id ) {
			$child = wc_get_product( $child_id );
			if ( $child && $child->get_price() !== '' ) {
				$prices[] = wc_get_price_to_display( $child, array( 'price' => (float) $child->get_price() ) );
			}
		}

		if ( empty( $prices ) ) {
			return '';
		}

		$min_price = min( $prices );
		$max_price = max( $prices );

		return sprintf(
			'<span>%s — %s</span>',
			wc_price( (float) $min_price, array( 'in_span' => false ) ),
			wc_price( (float) $max_price, array( 'in_span' => false ) )
		);
	}

	/**
	 * Apply email-compatible table wrapper.
	 *
	 * @param string $price_html Price HTML.
	 * @param array  $parsed_block Parsed block.
	 * @return string
	 */
	private function apply_email_wrapper( string $price_html, array $parsed_block ): string {
		$align = $parsed_block['attrs']['textAlign'] ?? 'left';

		$wrapper_styles = array(
			'border-collapse' => 'collapse',
			'width'           => '100%',
		);

		$cell_styles = array(
			'padding'     => '5px 0',
			'text-align'  => $align,
			'font-family' => 'inherit',
		);

		$table_attrs = array(
			'style' => \WP_Style_Engine::compile_css( $wrapper_styles, '' ),
			'width' => '100%',
		);

		$cell_attrs = array(
			'class' => 'email-product-price-cell',
			'style' => \WP_Style_Engine::compile_css( $cell_styles, '' ),
			'align' => $align,
		);

		return Table_Wrapper_Helper::render_table_wrapper( $price_html, $table_attrs, $cell_attrs );
	}
}