You've already forked wc-composable-product
Upgrade to PHPUnit 10, add PHPCS with WPCS compliance, add phpcs CI job
All checks were successful
All checks were successful
- Upgrade PHPUnit 9.6 → 10, update phpunit.xml.dist schema - Add PHPCS 3.13 with WordPress-Extra + PHPCompatibilityWP standards - PHPCBF auto-fix + manual fixes for full WPCS compliance - Add phpcs job to release workflow (parallel with lint) - Pin composer platform to PHP 8.3 to prevent incompatible dep locks Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -7,7 +7,7 @@
|
||||
|
||||
namespace Magdev\WcComposableProduct;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Cart Handler Class
|
||||
@@ -15,207 +15,214 @@ defined('ABSPATH') || exit;
|
||||
* Handles adding composable products to cart and calculating prices
|
||||
*/
|
||||
class CartHandler {
|
||||
/**
|
||||
* Stock manager instance
|
||||
*
|
||||
* @var StockManager
|
||||
*/
|
||||
private $stock_manager;
|
||||
/**
|
||||
* Stock manager instance
|
||||
*
|
||||
* @var StockManager
|
||||
*/
|
||||
private $stock_manager;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->stock_manager = new StockManager();
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->stock_manager = new StockManager();
|
||||
|
||||
add_filter('woocommerce_add_to_cart_validation', [$this, 'validate_add_to_cart'], 10, 3);
|
||||
add_filter('woocommerce_add_cart_item_data', [$this, 'add_cart_item_data'], 10, 2);
|
||||
add_filter('woocommerce_get_cart_item_from_session', [$this, 'get_cart_item_from_session'], 10, 2);
|
||||
add_filter('woocommerce_get_item_data', [$this, 'display_cart_item_data'], 10, 2);
|
||||
add_action('woocommerce_before_calculate_totals', [$this, 'calculate_cart_item_price']);
|
||||
add_action('woocommerce_single_product_summary', [$this, 'render_product_selector'], 25);
|
||||
add_action('woocommerce_checkout_create_order_line_item', [$this->stock_manager, 'store_selected_products_in_order'], 10, 3);
|
||||
add_filter('woocommerce_is_purchasable', [$this, 'hide_default_add_to_cart'], 10, 2);
|
||||
}
|
||||
add_filter( 'woocommerce_add_to_cart_validation', array( $this, 'validate_add_to_cart' ), 10, 3 );
|
||||
add_filter( 'woocommerce_add_cart_item_data', array( $this, 'add_cart_item_data' ), 10, 2 );
|
||||
add_filter( 'woocommerce_get_cart_item_from_session', array( $this, 'get_cart_item_from_session' ), 10, 2 );
|
||||
add_filter( 'woocommerce_get_item_data', array( $this, 'display_cart_item_data' ), 10, 2 );
|
||||
add_action( 'woocommerce_before_calculate_totals', array( $this, 'calculate_cart_item_price' ) );
|
||||
add_action( 'woocommerce_single_product_summary', array( $this, 'render_product_selector' ), 25 );
|
||||
add_action( 'woocommerce_checkout_create_order_line_item', array( $this->stock_manager, 'store_selected_products_in_order' ), 10, 3 );
|
||||
add_filter( 'woocommerce_is_purchasable', array( $this, 'hide_default_add_to_cart' ), 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide default WooCommerce add to cart button for composable products
|
||||
*
|
||||
* @param bool $is_purchasable Is purchasable status
|
||||
* @param \WC_Product $product Product object
|
||||
* @return bool
|
||||
*/
|
||||
public function hide_default_add_to_cart($is_purchasable, $product) {
|
||||
if ($product && $product->get_type() === 'composable') {
|
||||
return false;
|
||||
}
|
||||
return $is_purchasable;
|
||||
}
|
||||
/**
|
||||
* Hide default WooCommerce add to cart button for composable products
|
||||
*
|
||||
* @param bool $is_purchasable Is purchasable status
|
||||
* @param \WC_Product $product Product object
|
||||
* @return bool
|
||||
*/
|
||||
public function hide_default_add_to_cart( $is_purchasable, $product ) {
|
||||
if ( $product && $product->get_type() === 'composable' ) {
|
||||
return false;
|
||||
}
|
||||
return $is_purchasable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render product selector on product page
|
||||
*/
|
||||
public function render_product_selector() {
|
||||
global $product;
|
||||
/**
|
||||
* Render product selector on product page
|
||||
*/
|
||||
public function render_product_selector() {
|
||||
global $product;
|
||||
|
||||
if ($product && $product->get_type() === 'composable') {
|
||||
ProductSelector::render($product);
|
||||
}
|
||||
}
|
||||
if ( $product && $product->get_type() === 'composable' ) {
|
||||
ProductSelector::render( $product );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate add to cart
|
||||
*
|
||||
* @param bool $passed Validation status
|
||||
* @param int $product_id Product ID
|
||||
* @param int $quantity Quantity
|
||||
* @return bool
|
||||
*/
|
||||
public function validate_add_to_cart($passed, $product_id, $quantity) {
|
||||
$product = wc_get_product($product_id);
|
||||
/**
|
||||
* Validate add to cart
|
||||
*
|
||||
* @param bool $passed Validation status
|
||||
* @param int $product_id Product ID
|
||||
* @param int $quantity Quantity
|
||||
* @return bool
|
||||
*/
|
||||
public function validate_add_to_cart( $passed, $product_id, $quantity ) {
|
||||
$product = wc_get_product( $product_id );
|
||||
|
||||
if (!$product || $product->get_type() !== 'composable') {
|
||||
return $passed;
|
||||
}
|
||||
if ( ! $product || $product->get_type() !== 'composable' ) {
|
||||
return $passed;
|
||||
}
|
||||
|
||||
// Check if selected products are provided
|
||||
if (!isset($_POST['composable_products']) || empty($_POST['composable_products'])) {
|
||||
wc_add_notice(__('Please select at least one product.', 'wc-composable-product'), 'error');
|
||||
return false;
|
||||
}
|
||||
// Check if selected products are provided.
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce verified by WooCommerce add-to-cart handler.
|
||||
if ( ! isset( $_POST['composable_products'] ) || empty( $_POST['composable_products'] ) ) {
|
||||
wc_add_notice( __( 'Please select at least one product.', 'wc-composable-product' ), 'error' );
|
||||
return false;
|
||||
}
|
||||
|
||||
$selected_products = array_map('absint', $_POST['composable_products']);
|
||||
$selection_limit = $product->get_selection_limit();
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce verified by WooCommerce add-to-cart handler.
|
||||
$selected_products = array_map( 'absint', $_POST['composable_products'] );
|
||||
$selection_limit = $product->get_selection_limit();
|
||||
|
||||
// Validate selection limit
|
||||
if (count($selected_products) > $selection_limit) {
|
||||
/* translators: %d: selection limit */
|
||||
wc_add_notice(sprintf(__('You can select a maximum of %d products.', 'wc-composable-product'), $selection_limit), 'error');
|
||||
return false;
|
||||
}
|
||||
// Validate selection limit
|
||||
if ( count( $selected_products ) > $selection_limit ) {
|
||||
/* translators: %d: selection limit */
|
||||
wc_add_notice( sprintf( __( 'You can select a maximum of %d products.', 'wc-composable-product' ), $selection_limit ), 'error' );
|
||||
return false;
|
||||
}
|
||||
|
||||
if (count($selected_products) === 0) {
|
||||
wc_add_notice(__('Please select at least one product.', 'wc-composable-product'), 'error');
|
||||
return false;
|
||||
}
|
||||
if ( count( $selected_products ) === 0 ) {
|
||||
wc_add_notice( __( 'Please select at least one product.', 'wc-composable-product' ), 'error' );
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate that selected products are valid
|
||||
$available_products = $product->get_available_products();
|
||||
$available_ids = array_map(function($p) {
|
||||
return $p->get_id();
|
||||
}, $available_products);
|
||||
// Validate that selected products are valid
|
||||
$available_products = $product->get_available_products();
|
||||
$available_ids = array_map(
|
||||
function ( $p ) {
|
||||
return $p->get_id();
|
||||
},
|
||||
$available_products
|
||||
);
|
||||
|
||||
foreach ($selected_products as $selected_id) {
|
||||
if (!in_array($selected_id, $available_ids)) {
|
||||
wc_add_notice(__('One or more selected products are not available.', 'wc-composable-product'), 'error');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
foreach ( $selected_products as $selected_id ) {
|
||||
if ( ! in_array( $selected_id, $available_ids, true ) ) {
|
||||
wc_add_notice( __( 'One or more selected products are not available.', 'wc-composable-product' ), 'error' );
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Validate stock availability
|
||||
$stock_validation = $this->stock_manager->validate_stock_availability($selected_products, $quantity);
|
||||
if ($stock_validation !== true) {
|
||||
wc_add_notice($stock_validation, 'error');
|
||||
return false;
|
||||
}
|
||||
// Validate stock availability
|
||||
$stock_validation = $this->stock_manager->validate_stock_availability( $selected_products, $quantity );
|
||||
if ( true !== $stock_validation ) {
|
||||
wc_add_notice( $stock_validation, 'error' );
|
||||
return false;
|
||||
}
|
||||
|
||||
return $passed;
|
||||
}
|
||||
return $passed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add cart item data
|
||||
*
|
||||
* @param array $cart_item_data Cart item data
|
||||
* @param int $product_id Product ID
|
||||
* @return array
|
||||
*/
|
||||
public function add_cart_item_data($cart_item_data, $product_id) {
|
||||
$product = wc_get_product($product_id);
|
||||
/**
|
||||
* Add cart item data
|
||||
*
|
||||
* @param array $cart_item_data Cart item data
|
||||
* @param int $product_id Product ID
|
||||
* @return array
|
||||
*/
|
||||
public function add_cart_item_data( $cart_item_data, $product_id ) {
|
||||
$product = wc_get_product( $product_id );
|
||||
|
||||
if (!$product || $product->get_type() !== 'composable') {
|
||||
return $cart_item_data;
|
||||
}
|
||||
if ( ! $product || $product->get_type() !== 'composable' ) {
|
||||
return $cart_item_data;
|
||||
}
|
||||
|
||||
if (isset($_POST['composable_products']) && !empty($_POST['composable_products'])) {
|
||||
$selected_products = array_map('absint', $_POST['composable_products']);
|
||||
$cart_item_data['composable_products'] = $selected_products;
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce verified by WooCommerce add-to-cart handler.
|
||||
if ( isset( $_POST['composable_products'] ) && ! empty( $_POST['composable_products'] ) ) {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce verified by WooCommerce add-to-cart handler.
|
||||
$selected_products = array_map( 'absint', $_POST['composable_products'] );
|
||||
$cart_item_data['composable_products'] = $selected_products;
|
||||
|
||||
// Make cart item unique
|
||||
$cart_item_data['unique_key'] = md5(json_encode($selected_products) . time());
|
||||
}
|
||||
// Make cart item unique.
|
||||
$cart_item_data['unique_key'] = md5( wp_json_encode( $selected_products ) . time() );
|
||||
}
|
||||
|
||||
return $cart_item_data;
|
||||
}
|
||||
return $cart_item_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cart item from session
|
||||
*
|
||||
* @param array $cart_item Cart item
|
||||
* @param array $values Values from session
|
||||
* @return array
|
||||
*/
|
||||
public function get_cart_item_from_session($cart_item, $values) {
|
||||
if (isset($values['composable_products'])) {
|
||||
$cart_item['composable_products'] = $values['composable_products'];
|
||||
}
|
||||
/**
|
||||
* Get cart item from session
|
||||
*
|
||||
* @param array $cart_item Cart item
|
||||
* @param array $values Values from session
|
||||
* @return array
|
||||
*/
|
||||
public function get_cart_item_from_session( $cart_item, $values ) {
|
||||
if ( isset( $values['composable_products'] ) ) {
|
||||
$cart_item['composable_products'] = $values['composable_products'];
|
||||
}
|
||||
|
||||
return $cart_item;
|
||||
}
|
||||
return $cart_item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display cart item data
|
||||
*
|
||||
* @param array $item_data Item data
|
||||
* @param array $cart_item Cart item
|
||||
* @return array
|
||||
*/
|
||||
public function display_cart_item_data($item_data, $cart_item) {
|
||||
if (isset($cart_item['composable_products']) && !empty($cart_item['composable_products'])) {
|
||||
$product_names = [];
|
||||
foreach ($cart_item['composable_products'] as $product_id) {
|
||||
$product = wc_get_product($product_id);
|
||||
if ($product) {
|
||||
$product_names[] = $product->get_name();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Display cart item data
|
||||
*
|
||||
* @param array $item_data Item data
|
||||
* @param array $cart_item Cart item
|
||||
* @return array
|
||||
*/
|
||||
public function display_cart_item_data( $item_data, $cart_item ) {
|
||||
if ( isset( $cart_item['composable_products'] ) && ! empty( $cart_item['composable_products'] ) ) {
|
||||
$product_names = array();
|
||||
foreach ( $cart_item['composable_products'] as $product_id ) {
|
||||
$product = wc_get_product( $product_id );
|
||||
if ( $product ) {
|
||||
$product_names[] = $product->get_name();
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($product_names)) {
|
||||
$item_data[] = [
|
||||
'key' => __('Selected Products', 'wc-composable-product'),
|
||||
'value' => implode(', ', $product_names),
|
||||
];
|
||||
}
|
||||
}
|
||||
if ( ! empty( $product_names ) ) {
|
||||
$item_data[] = array(
|
||||
'key' => __( 'Selected Products', 'wc-composable-product' ),
|
||||
'value' => implode( ', ', $product_names ),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $item_data;
|
||||
}
|
||||
return $item_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate cart item price
|
||||
*
|
||||
* @param \WC_Cart $cart Cart object
|
||||
*/
|
||||
public function calculate_cart_item_price($cart) {
|
||||
if (is_admin() && !defined('DOING_AJAX')) {
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* Calculate cart item price
|
||||
*
|
||||
* @param \WC_Cart $cart Cart object
|
||||
*/
|
||||
public function calculate_cart_item_price( $cart ) {
|
||||
if ( is_admin() && ! defined( 'DOING_AJAX' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Use static flag to prevent multiple executions within the same request
|
||||
static $already_calculated = false;
|
||||
if ($already_calculated) {
|
||||
return;
|
||||
}
|
||||
// Use static flag to prevent multiple executions within the same request
|
||||
static $already_calculated = false;
|
||||
if ( $already_calculated ) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($cart->get_cart() as $cart_item_key => $cart_item) {
|
||||
if (isset($cart_item['data']) && $cart_item['data']->get_type() === 'composable') {
|
||||
if (isset($cart_item['composable_products'])) {
|
||||
$product = $cart_item['data'];
|
||||
$price = $product->calculate_composed_price($cart_item['composable_products']);
|
||||
$cart_item['data']->set_price($price);
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach ( $cart->get_cart() as $cart_item_key => $cart_item ) {
|
||||
if ( isset( $cart_item['data'] ) && $cart_item['data']->get_type() === 'composable' ) {
|
||||
if ( isset( $cart_item['composable_products'] ) ) {
|
||||
$product = $cart_item['data'];
|
||||
$price = $product->calculate_composed_price( $cart_item['composable_products'] );
|
||||
$cart_item['data']->set_price( $price );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$already_calculated = true;
|
||||
}
|
||||
$already_calculated = true;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user