<?php
/**
* Handles the adding of products to WooCommerce cart.
*
* @package WooCommerce\PayPalCommerce\Button\Helper
*/
declare (strict_types=1);
namespace WooCommerce\PayPalCommerce\Button\Helper;
use Exception;
use WC_Cart;
use WC_Data_Store;
/**
* Class CartProductsHelper
*/
class CartProductsHelper
{
/**
* The cart
*
* @var ?WC_Cart
*/
private $cart;
/**
* The product data store.
*
* @var WC_Data_Store
*/
protected $product_data_store;
/**
* The added cart item IDs
*
* @var array
*/
private $cart_item_keys = array();
/**
* CheckoutFormSaver constructor.
*
* @param WC_Data_Store $product_data_store The data store for products.
*/
public function __construct(WC_Data_Store $product_data_store)
{
$this->product_data_store = $product_data_store;
}
/**
* Sets a new cart instance.
*
* @param WC_Cart $cart The cart.
* @return void
*/
public function set_cart(WC_Cart $cart): void
{
$this->cart = $cart;
}
/**
* Returns products information from a data array.
*
* @param array $data The data array.
*
* @return array|null
*/
public function products_from_data(array $data): ?array
{
$products = array();
if (!isset($data['products']) || !is_array($data['products'])) {
return null;
}
foreach ($data['products'] as $product) {
$product = $this->product_from_data($product);
if ($product) {
$products[] = $product;
}
}
return $products;
}
/**
* Returns product information from a data array.
*
* @param array $product The product data array, usually provided by the product page form.
* @return array|null
*/
public function product_from_data(array $product): ?array
{
if (!isset($product['quantity']) || !isset($product['id'])) {
return null;
}
$wc_product = wc_get_product((int) $product['id']);
if (!$wc_product) {
return null;
}
return array('product' => $wc_product, 'quantity' => (int) $product['quantity'], 'variations' => $product['variations'] ?? array(), 'booking' => $product['booking'] ?? null, 'extra' => $product['extra'] ?? null);
}
/**
* Adds products to cart.
*
* @param array $products Array of products to be added to cart.
* @return bool
* @throws Exception Add to cart methods throw an exception on fail.
*/
public function add_products(array $products): bool
{
$success = \true;
foreach ($products as $product) {
// Add extras to POST, they are usually added by custom plugins.
if ($product['extra'] && is_array($product['extra'])) {
// Handle cases like field[].
$query = http_build_query($product['extra']);
parse_str($query, $extra);
foreach ($extra as $key => $value) {
$_POST[$key] = $value;
}
}
if ($product['product']->is_type('booking')) {
$success = $success && $this->add_booking_product($product['product'], $product['booking']);
} elseif ($product['product']->is_type('variable')) {
if (empty($product['variations']) || !is_array($product['variations'])) {
$product['variations'] = array();
}
$success = $success && $this->add_variable_product($product['product'], $product['quantity'], $product['variations']);
} else {
$success = $success && $this->add_product($product['product'], $product['quantity']);
}
}
if (!$success) {
throw new Exception('Error adding products to cart.');
}
return \true;
}
/**
* Adds a product to the cart.
*
* @param \WC_Product $product The Product.
* @param int $quantity The Quantity.
*
* @return bool
* @throws Exception When product could not be added.
*/
public function add_product(\WC_Product $product, int $quantity): bool
{
if (!$this->cart) {
throw new Exception('Cart not set.');
}
$cart_item_key = $this->cart->add_to_cart($product->get_id(), $quantity);
if ($cart_item_key) {
$this->cart_item_keys[] = $cart_item_key;
}
return \false !== $cart_item_key;
}
/**
* Adds variations to the cart.
*
* @param \WC_Product $product The Product.
* @param int $quantity The Quantity.
* @param array $post_variations The variations.
*
* @return bool
* @throws Exception When product could not be added.
*/
public function add_variable_product(\WC_Product $product, int $quantity, array $post_variations): bool
{
if (!$this->cart) {
throw new Exception('Cart not set.');
}
$variations = array();
foreach ($post_variations as $key => $value) {
if (!isset($value['name'], $value['value'])) {
continue;
}
$variations[$value['name']] = $value['value'];
}
// @phpstan-ignore method.notFound
$variation_id = $this->product_data_store->find_matching_product_variation($product, $variations);
// ToDo: Check stock status for variation.
$cart_item_key = $this->cart->add_to_cart($product->get_id(), $quantity, $variation_id, $variations);
if ($cart_item_key) {
$this->cart_item_keys[] = $cart_item_key;
}
return \false !== $cart_item_key;
}
/**
* Adds booking to the cart.
*
* @param \WC_Product $product The Product.
* @param array $data Data used by the booking plugin.
*
* @return bool
* @throws Exception When product could not be added.
*/
public function add_booking_product(\WC_Product $product, array $data): bool
{
if (!$this->cart) {
throw new Exception('Cart not set.');
}
if (!is_callable('wc_bookings_get_posted_data')) {
// @phpstan-ignore function.impossibleType
return \false;
}
$cart_item_data = array('booking' => wc_bookings_get_posted_data($data, $product));
$cart_item_key = $this->cart->add_to_cart($product->get_id(), 1, 0, array(), $cart_item_data);
if ($cart_item_key) {
$this->cart_item_keys[] = $cart_item_key;
}
return \false !== $cart_item_key;
}
/**
* Removes stored cart items from WooCommerce cart.
*
* @return void
* @throws Exception Throws if there's a failure removing the cart items.
*/
public function remove_cart_items(): void
{
if (!$this->cart) {
throw new Exception('Cart not set.');
}
foreach ($this->cart_item_keys as $cart_item_key) {
if (!$cart_item_key) {
continue;
}
$this->cart->remove_cart_item($cart_item_key);
}
}
/**
* Returns the cart item keys of the items added to cart.
*
* @return array
*/
public function cart_item_keys(): array
{
return $this->cart_item_keys;
}
}