/** * Hook into 'woocommerce_composite_component_associated_stock' to append bundled items to the composite stock data object. * * @param WC_PB_Stock_Manager $items * @param int $composite_id * @param string $component_id * @param int $bundled_product_id * @param int $quantity * @return WC_PB_Stock_Manager */ public static function associated_bundle_stock($items, $composite_id, $component_id, $bundled_product_id, $quantity) { if (!empty(WC_PB_Compatibility::$stock_data)) { $items = WC_PB_Compatibility::$stock_data; WC_PB_Compatibility::$stock_data = ''; remove_filter('woocommerce_composite_component_associated_stock', array(__CLASS__, 'associated_bundle_stock'), 10, 5); } return $items; }
/** * Validates add-to-cart for bundles. * Basically ensures that stock for all bundled products exists before attempting to add them to cart. * * @param boolean $add core validation add to cart flag * @param int $product_id the product id * @param int $product_quantity quantity * @param mixed $variation_id variation id * @param array $variations variation data * @param array $cart_item_data cart item data * @return boolean modified add to cart validation flag */ public function woo_bundles_validation($add, $product_id, $product_quantity, $variation_id = '', $variations = array(), $cart_item_data = array()) { // Get product type. $terms = get_the_terms($product_id, 'product_type'); $product_type = !empty($terms) && isset(current($terms)->name) ? sanitize_title(current($terms)->name) : 'simple'; // Ordering again? $order_again = isset($_GET['order_again']) && isset($_GET['_wpnonce']) && wp_verify_nonce($_GET['_wpnonce'], 'woocommerce-order_again'); // prevent bundled items from getting validated - they will be added by the container item. if (isset($cart_item_data['is_bundled']) && $order_again) { return false; } if ($product_type === 'bundle') { $product = wc_get_product($product_id); if (!$product) { return false; } if (!apply_filters('woocommerce_bundle_before_validation', true, $product)) { return false; } // If a stock-managed product / variation exists in the bundle multiple times, its stock will be checked only once for the sum of all bundled quantities. // The stock manager class keeps a record of stock-managed product / variation ids. $bundled_stock = new WC_PB_Stock_Manager($product); // Grab bundled items. $bundled_items = $product->get_bundled_items(); if (!$bundled_items) { return $add; } foreach ($bundled_items as $bundled_item_id => $bundled_item) { $id = $bundled_item->product_id; $bundled_product_type = $bundled_item->product->product_type; // Optional. $is_optional = $bundled_item->is_optional(); if ($is_optional) { if (isset($cart_item_data['stamp'][$bundled_item_id]['optional_selected']) && $order_again) { $is_optional_selected = 'yes' === $cart_item_data['stamp'][$bundled_item_id]['optional_selected'] ? true : false; } elseif (isset($_REQUEST[apply_filters('woocommerce_product_bundle_field_prefix', '', $product_id) . 'bundle_selected_optional_' . $bundled_item_id])) { $is_optional_selected = isset($_REQUEST[apply_filters('woocommerce_product_bundle_field_prefix', '', $product_id) . 'bundle_selected_optional_' . $bundled_item_id]); } else { $is_optional_selected = false; } } if ($is_optional && !$is_optional_selected) { continue; } // Check quantity. $item_quantity_min = $bundled_item->get_quantity(); $item_quantity_max = $bundled_item->get_quantity('max'); if (isset($_REQUEST[apply_filters('woocommerce_product_bundle_field_prefix', '', $product_id) . 'bundle_quantity_' . $bundled_item_id]) && is_numeric($_REQUEST[apply_filters('woocommerce_product_bundle_field_prefix', '', $product_id) . 'bundle_quantity_' . $bundled_item_id])) { $item_quantity = absint($_REQUEST[apply_filters('woocommerce_product_bundle_field_prefix', '', $product_id) . 'bundle_quantity_' . $bundled_item_id]); } elseif (isset($cart_item_data['stamp'][$bundled_item_id]['quantity']) && $order_again) { $item_quantity = $cart_item_data['stamp'][$bundled_item_id]['quantity']; } else { $item_quantity = $item_quantity_min; } if ($item_quantity < $item_quantity_min) { wc_add_notice(sprintf(__('"%1$s" cannot be added to the cart. The quantity of "%2$s" cannot be lower than %3$d.', 'woocommerce-product-bundles'), get_the_title($product_id), $bundled_item->get_raw_title(), $item_quantity_min), 'error'); return false; } elseif ($item_quantity_max && $item_quantity > $item_quantity_max) { wc_add_notice(sprintf(__('"%1$s" cannot be added to the cart. The quantity of "%2$s" cannot be higher than %3$d.', 'woocommerce-product-bundles'), get_the_title($product_id), $bundled_item->get_raw_title(), $item_quantity_max), 'error'); return false; } $quantity = $item_quantity * $product_quantity; // If quantity is zero, continue. if ($quantity == 0) { continue; } // Purchasable if (!$bundled_item->is_purchasable()) { wc_add_notice(sprintf(__('"%1$s" cannot be added to the cart because "%2$s" cannot be purchased at the moment.', 'woocommerce-product-bundles'), get_the_title($product_id), $bundled_item->get_raw_title()), 'error'); return false; } // Validate variation id. if ($bundled_product_type === 'variable' || $bundled_product_type === 'variable-subscription') { $variation_id = ''; if (isset($cart_item_data['stamp'][$bundled_item_id]['variation_id']) && $order_again) { $variation_id = $cart_item_data['stamp'][$bundled_item_id]['variation_id']; } elseif (isset($_REQUEST[apply_filters('woocommerce_product_bundle_field_prefix', '', $product_id) . 'bundle_variation_id_' . $bundled_item_id])) { $variation_id = $_REQUEST[apply_filters('woocommerce_product_bundle_field_prefix', '', $product_id) . 'bundle_variation_id_' . $bundled_item_id]; } if ($variation_id && is_numeric($variation_id) && $variation_id > 1) { if (get_post_meta($variation_id, '_price', true) === '') { wc_add_notice(sprintf(__('"%1$s" cannot be added to the cart. The selected variation of "%2$s" cannot be purchased.', 'woocommerce-product-bundles'), get_the_title($product_id), $bundled_item->product->get_title()), 'error'); return false; } // Add item for validation. $bundled_stock->add_item($id, $variation_id, $quantity); } // Verify all attributes for the variable product were set. $bundled_variation = wc_get_product($variation_id); $attributes = (array) maybe_unserialize(get_post_meta($id, '_product_attributes', true)); $variation_data = array(); $missing_attributes = array(); $all_set = true; if ($bundled_variation) { $variation_data = $bundled_variation->variation_data; // Verify all attributes. foreach ($attributes as $attribute) { if (!$attribute['is_variation']) { continue; } $taxonomy = 'attribute_' . sanitize_title($attribute['name']); if (!empty($_REQUEST[apply_filters('woocommerce_product_bundle_field_prefix', '', $product_id) . 'bundle_' . $taxonomy . '_' . $bundled_item_id])) { if (WC_PB_Core_Compatibility::is_wc_version_gte_2_4()) { // Get value from post data. if ($attribute['is_taxonomy']) { $value = sanitize_title(stripslashes($_REQUEST[apply_filters('woocommerce_product_bundle_field_prefix', '', $product_id) . 'bundle_' . $taxonomy . '_' . $bundled_item_id])); } else { $value = wc_clean(stripslashes($_REQUEST[apply_filters('woocommerce_product_bundle_field_prefix', '', $product_id) . 'bundle_' . $taxonomy . '_' . $bundled_item_id])); } } else { // Get value from post data. $value = sanitize_title(trim(stripslashes($_REQUEST[apply_filters('woocommerce_product_bundle_field_prefix', '', $product_id) . 'bundle_' . $taxonomy . '_' . $bundled_item_id]))); } // Get valid value from variation. $valid_value = $variation_data[$taxonomy]; // Allow if valid if ($valid_value === '' || $valid_value === $value) { continue; } } elseif (isset($cart_item_data['stamp'][$bundled_item_id]['attributes'][$taxonomy]) && isset($cart_item_data['stamp'][$bundled_item_id]['variation_id']) && $order_again) { if (WC_PB_Core_Compatibility::is_wc_version_gte_2_4()) { // Get value from post data if ($attribute['is_taxonomy']) { $value = sanitize_title(stripslashes($cart_item_data['stamp'][$bundled_item_id]['attributes'][$taxonomy])); } else { $value = wc_clean(stripslashes($cart_item_data['stamp'][$bundled_item_id]['attributes'][$taxonomy])); } } else { // Get value from post data $value = sanitize_title(trim(stripslashes($cart_item_data['stamp'][$bundled_item_id]['attributes'][$taxonomy]))); } $valid_value = $variation_data[$taxonomy]; if ($valid_value === '' || $valid_value === $value) { continue; } } else { $missing_attributes[] = wc_attribute_label($attribute['name']); } $all_set = false; } } else { $all_set = false; } if (!$all_set) { if ($missing_attributes && WC_PB_Core_Compatibility::is_wc_version_gte_2_3()) { $required_fields_notice = sprintf(_n('%1$s is a required "%2$s" field', '%1$s are required "%2$s" fields', sizeof($missing_attributes), 'woocommerce-product-bundles'), wc_format_list_of_items($missing_attributes), $bundled_item->product->get_title()); wc_add_notice(sprintf(__('"%1$s" cannot be added to the cart. %2$s.', 'woocommerce-product-bundles'), get_the_title($product_id), $required_fields_notice), 'error'); return false; } else { wc_add_notice(sprintf(__('"%1$s" cannot be added to the cart. Please choose "%2$s" options…', 'woocommerce-product-bundles'), get_the_title($product_id), $bundled_item->product->get_title()), 'error'); return false; } } } elseif ($bundled_product_type === 'simple' || $bundled_product_type === 'subscription') { // Add item for validation. $bundled_stock->add_item($id, false, $quantity); } if (!apply_filters('woocommerce_bundled_item_add_to_cart_validation', true, $product, $bundled_item, $quantity, $variation_id)) { return false; } } // Check stock for stock-managed bundled items. // If out of stock, don't proceed. if (false === apply_filters('woocommerce_add_to_cart_bundle_validation', $bundled_stock->validate_stock(), $product_id, $bundled_stock)) { return false; } // Composite Products compatibility. WC_PB_Compatibility::$stock_data = $bundled_stock; } return $add; }