What I need
I want to make a simple implementation of product add-ons without using heavyweight plugins which are overkill for my needs. Also I want to be able to keep track of add-ons inventory which is not offered by any of plugins. To make things a bit clearer I can say that the main product is a pendant and add-on is a chain. The customer should be able to select the chain on pendant's page, when he or she clicks add to cart button both items should be added to cart.
So I thought I could just use hidden single products as add-ons. The idea is to add a drop-down with my add-on products and get selected value via POST and add to cart it along with main product. So far so good.
What I did
Here's my code:
add_action( 'woocommerce_before_add_to_cart_button', 'chain_selection_field' );
function chain_selection_field() {
global $product;
$domain = 'woocommerce';
$args = array(
'sku' => 'SOME_TEXT',
'stock_status' => 'instock',
);
$products = wc_get_products( $args );
foreach ($products as $product) {
$product_id = $product->get_id();
$options[$product_id] = $product->get_name();
}
woocommerce_form_field('chain_type', array(
'type' => 'select',
'label' => __('Chain type selection', $domain),
'required' => true,
'options' => $options,
),'');
}
add_action('woocommerce_add_to_cart', 'product_option_add_to_cart');
function product_option_add_to_cart() {
$product_id = $_POST['chain_type'];
$found = false;
if ( sizeof( WC()->cart->get_cart() ) > 0 ) {
foreach ( WC()->cart->get_cart() as $cart_item_key => $values ) {
$_product = $values['data'];
if ( $_product->id == $product_id )
$found = true;
}
if ( ! $found )
WC()->cart->add_to_cart( $product_id );
} else {
WC()->cart->add_to_cart( $product_id );
}
}
Add to cart implementation is taken from here - How to add filter or hook for "woocommerce_add_to_cart"
What's the problem?
Actually there are a lot of problems:
$_POST = array();
or unset($_POST);
in the end of add to cart function but that didn't work.The problem was that I used global variable $product
in foreach loop. So it was breaking loop logics and using global $product
instead of desired array value. So all it takes to make the code work is changing foreach loop code to something like:
foreach ($products as $single_product) {
$product_id = $single_product->get_id();
$options[$product_id] = $single_product->get_name();
Otherwise the code is working fine, although I have to make a few remarks:
<form> ... </form>
submitted by pressing add to cart button, i.e. woocommerce_before_add_to_cart_button
, woocommerce_before_add_to_cart_quantity
and woocommerce_after_add_to_cart_quantity
(you can check it yourself in the template - https://github.com/woocommerce/woocommerce/blob/4.1.0/templates/single-product/add-to-cart/simple.php) for simple product and also a few hooks called inside variation loop vor variable product which can be found here - https://github.com/woocommerce/woocommerce/blob/4.1.0/templates/single-product/add-to-cart/variable.phpwoocommerce_before_add_to_cart_button
input field is placed inbetween product stock text and add to cart button which is not the best place and it looks like you either have to edit template files or use JS to achieve better placement with current WC (4.1) hooks.wc_get_products ()
function is rather slow. In my test it was 10 times slower than similar query made through $wpdb->get_results
product_option_add_to_cart()
custom function code into if (isset($_POST['chain_type'])) { ... }
to prevent PHP notices in case there's no $_POST value for some reason