Woocommerce: use non-variation attributes from frontend

Posted on
5/5 - (413 votes)
Woocommerce: use non-variation attributes from frontend – allow customers to select term (option) from non-variation attributes on variable or simple products frontend pages, add selection to cart item, display it in cart, add it to order item.
/**
* Inspired from: https://gist.github.com/dazecoop/548b2621e86fb030da8e5adb1bfe484f
* 
* How to use:
* - add to your function.php or related
*
* What you can use it for:
* Variable products:
* - you have variable product with some attributes not used for variations
* - you still want to let your users choose some of those attributes values on frontend
* - eg: your have an attribute that don't impact prices, and that is required or not, and don't want to create useless variations for those attributes
*
* Simple product:
* - you have a single product, and want to allow user to select some of its attributes values from frontend
*/

/**
* List available attributes on product page in a drop-down selection
*/
function list_attributes_on_product_page() {
global $product;
$attributes = $product->get_attributes();

if ( ! $attributes ) {
return;
}

//uncomment this if you only want fields to be displayed for non-variable products
/*if ($product->is_type( 'variable' )) {
return;
}*/

echo '<div style="padding-bottom:15px;">';

foreach ( $attributes as $attribute ) {

//If product is variable, and attribute is used for variation: woocommerce already handle this input
if($product->is_type( 'variable' ) && $attribute['variation']) {
continue;
}

//get taxonomy for the attribute - eg: Size
$taxonomy = get_taxonomy($attribute['name']);

//get terms - eg: small
$options = wc_get_product_terms( $product->get_id(), $attribute['name'], array( 'fields' => 'all' ) );
$label = str_replace('Product ', '', $taxonomy->label);
//display select input
?>
<div style="padding-bottom:8px;">
<label for="attribute[<?php echo $attribute['id']; ?>]"><?php echo $label; ?></label>
<br />
<!-- add required attribute or not, handle default with "selected" attribute depending your needs -->
<select name="attribute[<?php echo $attribute['id']; ?>]" id="attribute[<?php echo $attribute['id']; ?>]">
<option value disabled selected>Choose an option</option>
<?php foreach ( $options as $pa ): ?>
<option value="<?php echo $pa->name; ?>"><?php echo $pa->name; ?></option>
<?php endforeach; ?>
</select>
</div>
<?php
}

echo '</div>';
}
//update the "woocommerce_before_add_to_cart_button" to change the position of the HTML output
add_action('woocommerce_before_add_to_cart_button', 'list_attributes_on_product_page');

/**
* Add selected attributes to cart items
*/
add_filter('woocommerce_add_cart_item_data', 'add_attributes_to_cart_item', 10, 3 );
function add_attributes_to_cart_item( $cart_item_data, $product_id, $variation_id ) {
$attributes = $_POST['attribute'] ?? null;

if (empty( $attributes ) ) {
return $cart_item_data;
}

$cart_item_data['attributes'] = serialize($attributes);

return $cart_item_data;
}

/**
* Display attributes in cart
*/
add_filter( 'woocommerce_get_item_data', 'display_attributes_in_cart', 10, 2 );
function display_attributes_in_cart( $item_data, $cart_item ) {
if ( empty( $cart_item['attributes'] ) ) {
return $item_data;
}

foreach (unserialize($cart_item['attributes']) as $attributeID => $value) {
$attribute = wc_get_attribute($attributeID);
$item_data[] = array(
'key' => $attribute->name,
'value' => $value,
'display' => '',
);
}

return $item_data;
}

/**
* Add attribute data to order items
*/
add_action( 'woocommerce_checkout_create_order_line_item', 'add_attributes_to_order_items', 10, 4 );
function add_attributes_to_order_items( $item, $cart_item_key, $values, $order ) {
if ( empty( $values['attributes'] ) ) {
return;
}

foreach (unserialize($values['attributes']) as $attributeID => $value) {
$attribute = wc_get_attribute($attributeID);
$item->add_meta_data( $attribute->name, $value );
}
}