/** * Validates that all composited items chosen can be added-to-cart before actually starting to add items. * * @param bool $add * @param int $product_id * @param int $product_quantity * @return bool */ public function wc_cp_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 composited items from getting validated - they will be added by the container item. if (isset($cart_item_data['is_composited']) && $order_again) { return false; } if ($product_type == 'composite') { // Get composite data $composite = wc_get_product($product_id); $composite_data = $composite->get_composite_data(); $component_ids = array_keys($composite_data); // Check request and prepare validation data for stock and scenarios. $validate_scenarios = array(); // 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 WC_CP_Stock_Manager class does exactly that $composited_stock = new WC_CP_Stock_Manager($composite); foreach ($component_ids as $component_id) { // Check that a product has been selected if (isset($_REQUEST['wccp_component_selection'][$component_id]) && $_REQUEST['wccp_component_selection'][$component_id] !== '') { $composited_product_id = $_REQUEST['wccp_component_selection'][$component_id]; } elseif (isset($cart_item_data['composite_data'][$component_id]['product_id']) && $order_again) { $composited_product_id = $cart_item_data['composite_data'][$component_id]['product_id']; } else { $_REQUEST['wccp_component_selection'][$component_id] = ''; // Save for later $validate_scenarios[$component_id] = array(); $validate_scenarios[$component_id]['product_id'] = '0'; $validate_scenarios[$component_id]['product_type'] = 'none'; continue; } // Prevent people from f*****g around - only valid component options can be added to the cart. if (!in_array($composited_product_id, WC_CP()->api->get_component_options($composite_data[$component_id]))) { return false; } $item_quantity_min = absint($composite_data[$component_id]['quantity_min']); $item_quantity_max = $composite_data[$component_id]['quantity_max'] !== '' ? absint($composite_data[$component_id]['quantity_max']) : ''; // Check quantity if (isset($_REQUEST['wccp_component_quantity'][$component_id]) && is_numeric($_REQUEST['wccp_component_quantity'][$component_id])) { $item_quantity = absint($_REQUEST['wccp_component_quantity'][$component_id]); } elseif (isset($cart_item_data['composite_data'][$component_id]['quantity']) && $order_again) { $item_quantity = absint($cart_item_data['composite_data'][$component_id]['quantity']); } else { $item_quantity = $item_quantity_min; } $quantity = $item_quantity * $product_quantity; $item_sold_individually = get_post_meta($composited_product_id, '_sold_individually', true); if ($item_sold_individually === 'yes' && $quantity > 1) { $quantity = 1; } // Save for later. $validate_scenarios[$component_id] = array(); $validate_scenarios[$component_id]['quantity'] = $item_quantity; $validate_scenarios[$component_id]['quantity_min'] = $item_quantity_min; $validate_scenarios[$component_id]['quantity_max'] = $item_quantity_max; $validate_scenarios[$component_id]['sold_individually'] = $item_sold_individually; // Get composited product type. $composited_product_wrapper = $composite->get_composited_product($component_id, $composited_product_id); if (!$composited_product_wrapper) { wc_add_notice(sprintf(__('This "%1$s" configuration cannot be added to the cart. Please choose another "%2$s" option…', 'woocommerce-composite-products'), get_the_title($product_id), apply_filters('woocommerce_composite_component_title', $composite_data[$component_id]['title'], $component_id, $product_id)), 'error'); return false; } $composited_product = $composited_product_wrapper->get_product(); $composited_product_type = $composited_product->product_type; // Save for later. $validate_scenarios[$component_id]['product_type'] = $composited_product_type; $validate_scenarios[$component_id]['product_id'] = $composited_product_id; // Validate attributes. if ($composited_product_type === 'variable') { $variation_id = ''; if (isset($cart_item_data['composite_data'][$component_id]['variation_id']) && $order_again) { $variation_id = $cart_item_data['composite_data'][$component_id]['variation_id']; } elseif (isset($_REQUEST['wccp_variation_id'][$component_id])) { $variation_id = $_REQUEST['wccp_variation_id'][$component_id]; } if ($variation_id && is_numeric($variation_id) && $variation_id > 1) { // Add item for validation. $composited_stock->add_item($composited_product_id, $variation_id, $quantity); // Save for later. $validate_scenarios[$component_id]['variation_id'] = $variation_id; } // Verify all attributes for the variable product were set. $composited_variation = wc_get_product($variation_id); $attributes = $composited_product->get_attributes(); $variation_data = array(); $missing_attributes = array(); $all_set = true; if ($composited_variation) { $variation_data = $composited_variation->variation_data; } foreach ($attributes as $attribute) { if (!$attribute['is_variation']) { continue; } $taxonomy = 'attribute_' . sanitize_title($attribute['name']); if (isset($_REQUEST['wccp_' . $taxonomy][$component_id])) { if (WC_CP_Core_Compatibility::is_wc_version_gte_2_4()) { // Get value from post data. if ($attribute['is_taxonomy']) { $value = sanitize_title(stripslashes($_REQUEST['wccp_' . $taxonomy][$component_id])); } else { $value = wc_clean(stripslashes($_REQUEST['wccp_' . $taxonomy][$component_id])); } } else { // Get value from post data. $value = sanitize_title(trim(stripslashes($_REQUEST['wccp_' . $taxonomy][$component_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['composite_data'][$component_id]['attributes'][$taxonomy]) && isset($cart_item_data['composite_data'][$component_id]['variation_id']) && $order_again) { if (WC_CP_Core_Compatibility::is_wc_version_gte_2_4()) { // Get value from post data. if ($attribute['is_taxonomy']) { $value = sanitize_title(stripslashes($cart_item_data['composite_data'][$component_id]['attributes'][$taxonomy])); } else { $value = wc_clean(stripslashes($cart_item_data['composite_data'][$component_id]['attributes'][$taxonomy])); } } else { // Get value from post data. $value = sanitize_title(trim(stripslashes($cart_item_data['composite_data'][$component_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; } if (!$all_set) { if ($missing_attributes && WC_CP_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-composite-products'), wc_format_list_of_items($missing_attributes), apply_filters('woocommerce_composite_component_title', $composite_data[$component_id]['title'], $component_id, $product_id)); wc_add_notice(sprintf(__('This "%1$s" configuration cannot be added to the cart. %2$s.', 'woocommerce-composite-products'), get_the_title($product_id), $required_fields_notice), 'error'); return false; } else { wc_add_notice(sprintf(__('This "%1$s" configuration cannot be added to the cart. Please choose "%2$s" options…', 'woocommerce-composite-products'), get_the_title($product_id), apply_filters('woocommerce_composite_component_title', $composite_data[$component_id]['title'], $component_id, $product_id)), 'error'); return false; } } } elseif ($composited_product_type === 'simple') { // Add item for validation. $composited_stock->add_item($composited_product_id, false, $quantity); } else { // Add item for validation. $composited_stock->add_item($composited_product_id, false, $quantity); } if (!apply_filters('woocommerce_composite_component_add_to_cart_validation', true, $product_id, $component_id, $composited_product_id, $quantity, $cart_item_data)) { return false; } // Allow composited products to add extra items to the stock manager. $composited_stock->add_stock(apply_filters('woocommerce_composite_component_associated_stock', '', $product_id, $component_id, $composited_product_id, $quantity)); } /* * Stock Validation. */ if (false === $composited_stock->validate_stock()) { return false; } /* * Scenarios Validation. */ $scenario_data = get_post_meta($product_id, '_bto_scenario_data', true); $scenario_data = apply_filters('woocommerce_composite_scenario_meta', $scenario_data, $composite); $posted_scenarios = !empty($_POST['wccp_active_scenarios']) ? array_map('wc_clean', explode(',', $_POST['wccp_active_scenarios'])) : array(); if (!empty($posted_scenarios)) { $scenario_data = array_intersect_key($scenario_data, array_flip($posted_scenarios)); } // Build scenarios for the selected combination of options foreach ($composite_data as $component_id => &$modified_component_data) { if (isset($validate_scenarios[$component_id]) && $validate_scenarios[$component_id]['product_type'] !== 'none') { $modified_component_data['current_component_options'] = array($validate_scenarios[$component_id]['product_id']); } else { $modified_component_data['current_component_options'] = array(''); } } $scenarios_for_products = apply_filters('woocommerce_composite_validation_scenario_data', WC_CP_Scenarios::build_scenarios($scenario_data, $composite_data), $composite_data, $scenario_data, $product_id); $common_scenarios = array_values($scenarios_for_products['scenarios']); $common_compat_group_scenarios = WC_CP_Scenarios::filter_scenarios_by_type($common_scenarios, 'compat_group', $scenarios_for_products); // Validate Selections. foreach ($composite_data as $component_id => $component_data) { if (isset($validate_scenarios[$component_id])) { $validate_product_id = isset($validate_scenarios[$component_id]['variation_id']) ? $validate_scenarios[$component_id]['variation_id'] : $validate_scenarios[$component_id]['product_id']; $scenarios_for_product = array(); $mandatory_override_check = false; if ($validate_product_id === '0' && $component_data['optional'] === 'no') { $mandatory_override_check = true; } if (!empty($scenarios_for_products['scenario_data'][$component_id][$validate_product_id])) { $scenarios_for_product = $scenarios_for_products['scenario_data'][$component_id][$validate_product_id]; $compat_group_scenarios_for_product = WC_CP_Scenarios::filter_scenarios_by_type($scenarios_for_product, 'compat_group', $scenarios_for_products); } if (empty($compat_group_scenarios_for_product) || $mandatory_override_check) { if ($validate_product_id === '0') { // Allow 3rd parties to override notices for empty selections in non-optional components conditionally through scenarios. if (apply_filters('woocommerce_composite_validation_component_is_mandatory', true, $component_id, $validate_scenarios[$component_id], $common_scenarios, $scenarios_for_products, $product_id)) { wc_add_notice(sprintf(__('Please select a "%s" option.', 'woocommerce-composite-products'), apply_filters('woocommerce_composite_component_title', $component_data['title'], $component_id, $product_id)), 'error'); return false; } } else { wc_add_notice(sprintf(__('Please select a different "%s" option — the selected product cannot be purchased at the moment.', 'woocommerce-composite-products'), apply_filters('woocommerce_composite_component_title', $component_data['title'], $component_id, $product_id)), 'error'); return false; } } else { $common_scenarios = array_intersect($common_scenarios, $scenarios_for_product); $common_compat_group_scenarios = array_intersect($common_compat_group_scenarios, $compat_group_scenarios_for_product); } } } if (empty($common_compat_group_scenarios)) { wc_add_notice(__('The selected options cannot be purchased together. Please select a different configuration and try again.', 'woocommerce-composite-products'), 'error'); return false; } // Validate Quantities. foreach ($composite_data as $component_id => $component_data) { if (!isset($validate_scenarios[$component_id]) || $validate_scenarios[$component_id]['product_type'] === 'none') { continue; } $qty = $validate_scenarios[$component_id]['quantity']; // Allow 3rd parties to modify the min/max qty settings of a component conditionally through scenarios. $qty_min = absint(apply_filters('woocommerce_composite_validation_component_quantity_min', $component_data['quantity_min'], $component_id, $validate_scenarios[$component_id], $common_scenarios, $scenarios_for_products, $product_id)); $qty_max = absint(apply_filters('woocommerce_composite_validation_component_quantity_max', $component_data['quantity_max'], $component_id, $validate_scenarios[$component_id], $common_scenarios, $scenarios_for_products, $product_id)); $sold_individually = $validate_scenarios[$component_id]['sold_individually']; if ($qty < $qty_min && $sold_individually !== 'yes') { wc_add_notice(sprintf(__('This "%1$s" configuration cannot be added to the cart. The quantity of "%2$s" cannot be lower than %3$d.', 'woocommerce-composite-products'), get_the_title($product_id), apply_filters('woocommerce_composite_component_title', $composite_data[$component_id]['title'], $component_id, $product_id), $qty_min), 'error'); return false; } elseif ($qty_max && $qty > $qty_max) { wc_add_notice(sprintf(__('This "%1$s" configuration cannot be added to the cart. The quantity of "%2$s" cannot be higher than %3$d.', 'woocommerce-composite-products'), get_the_title($product_id), apply_filters('woocommerce_composite_component_title', $composite_data[$component_id]['title'], $component_id, $product_id), $qty_max), 'error'); return false; } } $add = apply_filters('woocommerce_add_to_cart_composite_validation', $add, $product_id, $composited_stock); } return $add; }
/** * Validates that all composited items chosen can be added-to-cart before actually starting to add items. * * @param bool $add * @param int $product_id * @param int $product_quantity * @return bool */ function wc_cp_validation($add, $product_id, $product_quantity, $variation_id = '', $variations = array(), $cart_item_data = array()) { global $woocommerce_composite_products; // 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 composited items from getting validated - they will be added by the container item if (isset($cart_item_data['is_composited']) && $order_again) { return false; } if ($product_type == 'composite') { // Get composite data $composite = WC_CP_Core_Compatibility::wc_get_product($product_id); $composite_data = $composite->get_composite_data(); $component_ids = array_keys($composite_data); // Check request and prepare validation data for stock and scenarios $validate_scenarios = array(); // 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 WC_CP_Stock_Manager class does exactly that $composited_stock = new WC_CP_Stock_Manager(); foreach ($component_ids as $component_id) { // Check that a product has been selected if (isset($_REQUEST['wccp_component_selection'][$component_id]) && $_REQUEST['wccp_component_selection'][$component_id] !== '') { $bundled_product_id = $_REQUEST['wccp_component_selection'][$component_id]; } elseif (isset($cart_item_data['composite_data'][$component_id]['product_id']) && $order_again) { $bundled_product_id = $cart_item_data['composite_data'][$component_id]['product_id']; } else { // Verify that the component is indeed optional if ($composite_data[$component_id]['optional'] == 'yes') { $_REQUEST['wccp_component_selection'][$component_id] = ''; // Save for later $validate_scenarios[$component_id] = array(); $validate_scenarios[$component_id]['product_id'] = '0'; $validate_scenarios[$component_id]['product_type'] = 'none'; continue; } else { wc_add_notice(sprintf(__('Please choose "%s" options…', 'woocommerce-composite-products'), apply_filters('woocommerce_composite_component_title', $composite_data[$component_id]['title'], $component_id, $product_id)), 'error'); return false; } } // Prevent people from f*****g around - only valid component options can be added to the cart if (!in_array($bundled_product_id, $woocommerce_composite_products->api->get_component_options($composite_data[$component_id]))) { return false; } $item_quantity_min = absint($composite_data[$component_id]['quantity_min']); $item_quantity_max = absint($composite_data[$component_id]['quantity_max']); // Check quantity if (isset($_REQUEST['wccp_component_quantity'][$component_id]) && is_numeric($_REQUEST['wccp_component_quantity'][$component_id])) { $item_quantity = absint($_REQUEST['wccp_component_quantity'][$component_id]); } elseif (isset($cart_item_data['composite_data'][$component_id]['quantity']) && $order_again) { $item_quantity = absint($cart_item_data['composite_data'][$component_id]['quantity']); } else { $item_quantity = $item_quantity_min; } $item_sold_individually = get_post_meta($bundled_product_id, '_sold_individually', true); if ($item_sold_individually === 'yes' && $item_quantity > 1) { $item_quantity = 1; } if ($item_quantity < $item_quantity_min && $item_sold_individually !== 'yes') { wc_add_notice(sprintf(__('This "%1$s" configuration cannot be added to the cart. The quantity of "%2$s" cannot be lower than %3$d.', 'woocommerce-composite-products'), get_the_title($product_id), apply_filters('woocommerce_composite_component_title', $composite_data[$component_id]['title'], $component_id, $product_id), $item_quantity_min), 'error'); return false; } elseif ($item_quantity > $item_quantity_max) { wc_add_notice(sprintf(__('This "%1$s" configuration cannot be added to the cart. The quantity of "%2$s" cannot be higher than %3$d.', 'woocommerce-composite-products'), get_the_title($product_id), apply_filters('woocommerce_composite_component_title', $composite_data[$component_id]['title'], $component_id, $product_id), $item_quantity_max), 'error'); return false; } $quantity = $item_quantity * $product_quantity; // If quantity is zero, continue if ($quantity == 0) { continue; } // Get bundled product type $terms = get_the_terms($bundled_product_id, 'product_type'); $bundled_product_type = !empty($terms) && isset(current($terms)->name) ? sanitize_title(current($terms)->name) : 'simple'; // Save for later $validate_scenarios[$component_id] = array(); $validate_scenarios[$component_id]['product_type'] = $bundled_product_type; $validate_scenarios[$component_id]['product_id'] = $bundled_product_id; if ($bundled_product_type == 'variable') { if (isset($cart_item_data['composite_data'][$component_id]['variation_id']) && $order_again) { $variation_id = $cart_item_data['composite_data'][$component_id]['variation_id']; } elseif (isset($_REQUEST['wccp_variation_id'][$component_id])) { $variation_id = $_REQUEST['wccp_variation_id'][$component_id]; } if (isset($variation_id) && is_numeric($variation_id) && $variation_id > 1) { // Add item for validation $composited_stock->add_item($bundled_product_id, $variation_id, $quantity); // Save for later $validate_scenarios[$component_id]['variation_id'] = $variation_id; } else { wc_add_notice(sprintf(__('This "%1$s" configuration cannot be added to the cart. Please choose "%2$s" options…', 'woocommerce-composite-products'), get_the_title($product_id), apply_filters('woocommerce_composite_component_title', $composite_data[$component_id]['title'], $component_id, $product_id)), 'error'); return false; } // Verify all attributes for the variable product were set $attributes = (array) maybe_unserialize(get_post_meta($bundled_product_id, '_product_attributes', true)); $variations = array(); $all_set = true; $variation_data = array(); $custom_fields = get_post_meta($variation_id); // Get the variation attributes from meta foreach ($custom_fields as $name => $value) { if (!strstr($name, 'attribute_')) { continue; } $variation_data[$name] = sanitize_title($value[0]); } // Verify all attributes are set and valid foreach ($attributes as $attribute) { if (!$attribute['is_variation']) { continue; } $taxonomy = 'attribute_' . sanitize_title($attribute['name']); if (!empty($_REQUEST['wccp_' . $taxonomy][$component_id])) { // Get value from post data // Don't use woocommerce_clean as it destroys sanitized characters $value = sanitize_title(trim(stripslashes($_REQUEST['wccp_' . $taxonomy][$component_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['composite_data'][$component_id]['attributes']) && isset($cart_item_data['composite_data'][$component_id]['variation_id']) && $order_again) { $value = sanitize_title(trim(stripslashes($cart_item_data['composite_data'][$component_id]['attributes'][$taxonomy]))); $valid_value = $variation_data[$taxonomy]; if ($valid_value == '' || $valid_value == $value) { continue; } } $all_set = false; } if (!$all_set) { wc_add_notice(sprintf(__('This "%1$s" configuration cannot be added to the cart. Please choose "%2$s" options…', 'woocommerce-composite-products'), get_the_title($product_id), apply_filters('woocommerce_composite_component_title', $composite_data[$component_id]['title'], $component_id, $product_id)), 'error'); return false; } } elseif ($bundled_product_type == 'simple') { // Add item for validation $composited_stock->add_item($bundled_product_id, false, $quantity); } else { // Add item for validation $composited_stock->add_item($bundled_product_id, false, $quantity); } if (!apply_filters('woocommerce_composite_component_add_to_cart_validation', true, $product_id, $component_id, $bundled_product_id, $quantity, $cart_item_data)) { return false; } // Allow composited products to add extra items to the stock manager $composited_stock->add_stock(apply_filters('woocommerce_composite_component_associated_stock', '', $product_id, $component_id, $bundled_product_id, $quantity)); } // Check stock for stock-managed composited items // If out of stock, don't proceed if (false === $composited_stock->validate_stock($product_id)) { return false; } // Scenarios Validation $scenario_data = get_post_meta($product_id, '_bto_scenario_data', true); // Build scenarios for the selected combination of options only foreach ($composite_data as $component_id => &$modified_component_data) { if (isset($validate_scenarios[$component_id]) && $validate_scenarios[$component_id]['product_type'] !== 'none') { $modified_component_data['current_component_options'] = array($validate_scenarios[$component_id]['product_id']); } else { $modified_component_data['current_component_options'] = array(''); } } $scenarios_for_products = $woocommerce_composite_products->api->build_scenarios($scenario_data, $composite_data); $common_scenarios = array_values($scenarios_for_products['scenarios']); foreach ($composite_data as $component_id => $component_data) { if (isset($validate_scenarios[$component_id])) { $validate_product_id = isset($validate_scenarios[$component_id]['variation_id']) ? $validate_scenarios[$component_id]['variation_id'] : $validate_scenarios[$component_id]['product_id']; if (empty($scenarios_for_products['scenario_data'][$component_id][$validate_product_id])) { wc_add_notice(sprintf(__('Please select a different "%s" option — the selected product cannot be purchased at the moment.', 'woocommerce-composite-products'), apply_filters('woocommerce_composite_component_title', $component_data['title'], $component_id, $product_id)), 'error'); return false; } else { $common_scenarios = array_intersect($common_scenarios, $scenarios_for_products['scenario_data'][$component_id][$validate_product_id]); } } } if (empty($common_scenarios)) { wc_add_notice(__('The selected options cannot be purchased together. Please select a different configuration and try again.', 'woocommerce-composite-products'), 'error'); return false; } $add = apply_filters('woocommerce_add_to_cart_composite_validation', $add, $product_id, $composited_stock); } return $add; }