WooCommerce Code Reference

GroupedProductItemSelector.php

Source code

<?php
declare(strict_types=1);

namespace Automattic\WooCommerce\Blocks\BlockTypes\AddToCartWithOptions;

use Automattic\WooCommerce\Blocks\BlockTypes\AbstractBlock;
use Automattic\WooCommerce\Blocks\BlockTypes\EnableBlockJsonAssetsTrait;
use Automattic\WooCommerce\Blocks\BlockTypes\AddToCartWithOptions\Utils as AddToCartWithOptionsUtils;
use WP_Block;

/**
 * Block type for the CTA of grouped product selector items in add to cart with options.
 * It's responsible to render the CTA for each child product, that might be a button,
 * a checkbox, or a link.
 */
class GroupedProductItemSelector extends AbstractBlock {

	use EnableBlockJsonAssetsTrait;

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'add-to-cart-with-options-grouped-product-item-selector';

	/**
	 * Gets the quantity selector markup for a product.
	 *
	 * @param \WC_Product $product The product object.
	 * @return string The HTML markup for the quantity selector.
	 */
	private function get_quantity_selector_markup( $product ) {
		ob_start();

		woocommerce_quantity_input(
			array(
				'input_name'  => 'quantity[' . $product->get_id() . ']',
				'input_id'    => 'quantity_' . $product->get_id(),
				'input_value' => isset( $_POST['quantity'][ $product->get_id() ] ) ? wc_stock_amount( wc_clean( wp_unslash( $_POST['quantity'][ $product->get_id() ] ) ) ) : '', // phpcs:ignore WordPress.Security.NonceVerification.Missing
				/**
				 * Filter the minimum quantity value allowed for the product.
				 *
				 * @since 2.0.0
				 * @param int        $min_value Minimum quantity value.
				 * @param WC_Product $product   Product object.
				 */
				'min_value'   => apply_filters( 'woocommerce_quantity_input_min', 0, $product ),
				/**
				 * Filter the maximum quantity value allowed for the product.
				 *
				 * @since 2.0.0
				 * @param int        $max_value Maximum quantity value.
				 * @param WC_Product $product   Product object.
				 */
				'max_value'   => apply_filters( 'woocommerce_quantity_input_max', $product->get_max_purchase_quantity(), $product ),
				/**
				 * Filter the placeholder value allowed for the product.
				 *
				 * @since 3.10.0
				 * @param int        $max_value Maximum quantity value.
				 * @param WC_Product $product   Product object.
				 */
				'placeholder' => apply_filters( 'woocommerce_quantity_input_placeholder', 0, $product ),
			)
		);

		$quantity_html = ob_get_clean();

		// Remove the label because we are rendering one as a separate block via GroupedProductItemLabel.
		$quantity_html = $this->remove_quantity_label( $quantity_html );

		// Modify the quantity input to add stepper buttons.
		$product_name = $product->get_name();

		$quantity_html = AddToCartWithOptionsUtils::add_quantity_steppers( $quantity_html, $product_name );
		$quantity_html = AddToCartWithOptionsUtils::add_quantity_stepper_classes( $quantity_html );

		// Add interactive data attribute for the stepper functionality.
		$quantity_html = AddToCartWithOptionsUtils::make_quantity_input_interactive( $quantity_html );

		return $quantity_html;
	}

	/**
	 * Removes the label from quantity input HTML.
	 *
	 * @param string $quantity_html The quantity input HTML.
	 * @return string The quantity input HTML without the label.
	 */
	private function remove_quantity_label( $quantity_html ) {
		// Remove the label and aria-label from the quantity input.
		$quantity_html = preg_replace( '/<label[^>]*>.*?<\/label>/s', '', $quantity_html );
		return preg_replace( '/\s*aria-label="[^"]*"/', '', $quantity_html );
	}

	/**
	 * Gets the add to cart button markup for a product.
	 *
	 * @param \WC_Product $product_to_render The product object.
	 * @return string The HTML markup for the add to cart button.
	 */
	private function get_button_markup( $product_to_render ) {
		ob_start();
		woocommerce_template_loop_add_to_cart();
		$button_html = ob_get_clean();

		return $button_html;
	}

	/**
	 * Gets the checkbox markup for a product.
	 *
	 * @param \WC_Product $product The product object.
	 * @return string The HTML markup for the checkbox input and label.
	 */
	private function get_checkbox_markup( $product ) {
		if ( $product->is_on_sale() ) {
			$label = sprintf(
				/* translators: %1$s: Product name. %2$s: Sale price. %3$s: Regular price */
				esc_html__( 'Buy one of %1$s on sale for %2$s, original price was %3$s', 'woocommerce' ),
				esc_html( $product->get_name() ),
				esc_html( wp_strip_all_tags( wc_price( $product->get_price() ) ) ),
				esc_html( wp_strip_all_tags( wc_price( $product->get_regular_price() ) ) )
			);
		} else {
			$label = sprintf(
				/* translators: %1$s: Product name. %2$s: Product price */
				esc_html__( 'Buy one of %1$s for %2$s', 'woocommerce' ),
				esc_html( $product->get_name() ),
				esc_html( wp_strip_all_tags( wc_price( $product->get_price() ) ) )
			);
		}
		return '<input type="checkbox" name="' . esc_attr( 'quantity[' . $product->get_id() . ']' ) . '" value="1" class="wc-grouped-product-add-to-cart-checkbox" id="' . esc_attr( 'quantity_' . $product->get_id() ) . '" data-wp-on--change="actions.handleQuantityCheckboxChange" />';
	}

	/**
	 * Render the block.
	 *
	 * @param array    $attributes Block attributes.
	 * @param string   $content Block content.
	 * @param WP_Block $block Block instance.
	 * @return string Rendered block output.
	 */
	protected function render( $attributes, $content, $block ): string {
		global $product;
		$previous_product = $product;

		$product = AddToCartWithOptionsUtils::get_product_from_context( $block, $previous_product );
		$markup  = '';

		if ( $product ) {
			if ( ! $product->is_purchasable() || $product->has_options() || ! $product->is_in_stock() ) {
				$markup = $this->get_button_markup( $product );
			} elseif ( $product->is_sold_individually() ) {
				$markup = $this->get_checkbox_markup( $product );
			} else {
				$markup = $this->get_quantity_selector_markup( $product );
			}

			if ( $markup ) {
				$markup = '<div class="wp-block-add-to-cart-with-options-grouped-product-item-selector wc-block-add-to-cart-with-options-grouped-product-item-selector">' . $markup . '</div>';
			}
		}

		$product = $previous_product;

		return $markup;
	}
}