<?php
/**
 * Class: CoCart_Product_Validation
 *
 * @author  Sébastien Dumont
 * @package CoCart\Classes
 * @since   2.1.0 Introduced.
 * @version 5.0.0
 * @license GPL-3.0
 */

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Handles product validation.
 *
 * Prevents products that are not purchasable or password protected from being added to the cart.
 * Corrects product names for missing variation attributes.
 *
 * @since 2.1.0 Introduced.
 */
class CoCart_Product_Validation {

	/**
	 * Constructor.
	 *
	 * @access public
	 *
	 * @since 2.1.0 Introduced.
	 * @since 2.7.2 Prevents variations that are not purchasable from being added to the cart.
	 *
	 * @ignore Function ignored when parsed into Code Reference.
	 */
	public function __construct() {
		// Prevent certain product types from being added to the cart.
		add_filter( 'cocart_add_to_cart_handler_external', array( $this, 'product_not_allowed_to_add' ), 0, 1 );

		// Prevent password products being added to the cart.
		add_filter( 'cocart_add_to_cart_validation', array( $this, 'protected_product_add_to_cart' ), 10, 2 );

		/**
		 * Prevents variations that are not purchasable from being added to the cart.
		 *
		 * @since 2.7.2 Introduced.
		 */
		add_filter( 'cocart_add_to_cart_validation', array( $this, 'variation_not_purchasable' ), 10, 5 );

		// Correct product name for missing variation attributes.
		add_filter( 'cocart_product_name', array( $this, 'validate_variation_product_name' ), 0, 3 );
		add_filter( 'cocart_item_added_product_name', array( $this, 'validate_variation_product_name' ), 0, 3 );
	} // END __construct()

	/**
	 * Error response for product types that are not allowed to be added to the cart.
	 *
	 * @throws CoCart_Data_Exception Exception if invalid data is detected.
	 *
	 * @access public
	 *
	 * @since   2.1.0 Introduced.
	 * @version 3.0.0
	 *
	 * @param WC_Product      $product_data Passes the product data we are attempting to add to cart.
	 * @param WP_REST_Request $request      The request object.
	 *
	 * @return WP_Error
	 */
	public function product_not_allowed_to_add( $product_data, $request = array() ) {
		try {
			$message = sprintf(
				/* translators: %1$s: product name, %2$s: product type */
				__( 'You cannot add "%1$s" to your cart as it is an "%2$s" product.', 'cocart-core' ),
				$product_data->get_name(),
				$product_data->get_type()
			);

			/**
			 * Filters message about product type that cannot be added to the cart.
			 *
			 * @param string     $message      Message.
			 * @param WC_Product $product_data The product object.
			 */
			$message = apply_filters( 'cocart_cannot_add_product_type_to_cart_message', $message, $product_data );

			throw new CoCart_Data_Exception( 'cocart_cannot_add_product_type_to_cart', $message, 403 );
		} catch ( CoCart_Data_Exception $e ) {
			return new \WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ), $e->getAdditionalData() );
		}
	} // END product_not_allowed_to_add()

	/**
	 * Validates the product name for a variable product.
	 *
	 * If variation details are missing then return the product title instead.
	 *
	 * @access public
	 *
	 * @since   2.1.0 Introduced.
	 * @version 2.2.0
	 *
	 * @uses wc_get_product()
	 *
	 * @param string     $product_name Product name before change.
	 * @param WC_Product $product      The product object.
	 * @param array      $cart_item    Item details of the product in cart.
	 *
	 * @return string $product_name Product name after change.
	 */
	public function validate_variation_product_name( $product_name, $product, $cart_item ) {
		if ( $product->is_type( 'variation' ) ) {
			$parent_product     = wc_get_product( $product->get_parent_id() );
			$default_attributes = $parent_product->get_default_attributes();

			if ( empty( $cart_item['variation'] ) && empty( $default_attributes ) ) {
				return $product->get_title();
			}
		}

		return $product_name;
	} // END validate_variation_product_name()

	/**
	 * Prevent password protected products being added to the cart.
	 *
	 * @access public
	 *
	 * @since   2.1.2 Introduced.
	 * @version 2.7.2
	 *
	 * @uses CoCart_Logger::log()
	 * @uses wc_get_product()
	 *
	 * @param bool $passed     Result before validating.
	 * @param int  $product_id Product ID.
	 *
	 * @return bool $passed Result after validating.
	 */
	public function protected_product_add_to_cart( $passed, $product_id ) {
		if ( post_password_required( $product_id ) ) {
			$passed = false;

			$product = wc_get_product( $product_id );

			CoCart_Logger::log(
				sprintf(
					/* translators: %s: product name */
					__( 'Product "%s" is protected and cannot be purchased.', 'cocart-core' ),
					$product->get_name()
				),
				'error'
			);
		}
		return $passed;
	} // END protected_product_add_to_cart()

	/**
	 * Prevents variations that are not purchasable from being added to the cart.
	 *
	 * @access public
	 *
	 * @since 2.7.2 Introduced.
	 *
	 * @uses CoCart_Logger::log()
	 * @uses wc_get_product()
	 *
	 * @param bool  $passed       Result before validating.
	 * @param int   $product_id   Product ID.
	 * @param int   $quantity     Quantity of item.
	 * @param int   $variation_id Variation ID.
	 * @param array $variation    Attributes of the variation.
	 *
	 * @return bool $passed Result after validating.
	 */
	public function variation_not_purchasable( $passed, $product_id, $quantity, $variation_id, $variation ) {
		$product = wc_get_product( $product_id );

		if ( ! empty( $variation ) ) {
			$data_store   = \WC_Data_Store::load( 'product' );
			$variation_id = $data_store->find_matching_product_variation( $product, $variation );
			$product      = wc_get_product( $variation_id );

			if ( $variation_id > 0 && ! $product->is_purchasable() ) {
				$passed = false;

				CoCart_Logger::log(
					sprintf(
						/* translators: %s: product name */
						__( 'Variation for "%s" is not purchasable.', 'cocart-core' ),
						$product->get_name()
					),
					'error'
				);
			}
		}

		return $passed;
	} // END variation_not_purchasable()
} // END class.

return new CoCart_Product_Validation();
