Woocommerce product restriction by attributes - php

I have a trying to make a pice of code that i can put in my functions.php
It needs to check what the selected attributes of the first product in the cart is. After that it needs to block all other product with an OTHER attributes than the selected one in the cart.
In my case:
Product 1 has attribute: color, attribute: height.
Product 2 has attribute: color, attribute: height.
etc..
IF
product 1 is added in the cart with color black.
THEN
all the other products in the shop can only be added with color black.
I have several try's with editing existing code but none of them work :S
function check_if_cart_has_product( $valid, $product_id, $quantity ) {
if(!empty(WC()->cart->get_cart()) && $valid){
foreach (WC()->cart->get_cart() as $cart_item_key => $values) {
$_product = $values['data'];
if( $product_id == $_product->id ) {
wc_add_notice( 'The product is already in cart', 'error' );
return false;
}
}
}
return $valid;
}
add_filter( 'woocommerce_add_to_cart_validation', 'check_if_cart_has_product', 10, 3 );
This will allow only one product in cart and blocks the rest, it only needs the check for the right conditons...
I try to combine it with this pice of code without success
function get_variation_data_from_variation_id( $item_id ) {
$_product = new WC_Product_Variation( $item_id );
$variation_data = $_product->get_variation_attributes();
$variation_detail = woocommerce_get_formatted_variation( $variation_data, true ); // this will give all variation detail in one line
// $variation_detail = woocommerce_get_formatted_variation( $variation_data, false); // this will give all variation detail one by one
return $variation_detail; // $variation_detail will return string containing variation detail which can be used to print on website
// return $variation_data; // $variation_data will return only the data which can be used to store variation data
}

Related

Adjust WooCommerce price if product is picked from specific archive page

I'm not sure if this is possible, but it feels like it should be.
I have a list of products that I wish to display via two different archives (set by another plugin) where I can control the products displayed by listing categories with the shortcode. The different archives need to have different prices showing for the same products and sometimes to not list specific products within a category.
So what I've done is setup categories (which are filterable on the front end and as such display their name) with the same name but different slugs for each archive page and then added each product to both categories with the same name so they show up in each archive.
The problem is I need to increase the base price of almost all of the products in one archive by 0.50 and I can't figure out a way of doing it. I can achieve the correct result visually like this:
function bbloomer_alter_price_display( $price_html, $product ) {
global $wp_query;
$slug = basename(get_permalink($wp_query->post->ID));
// Only if not null
if ( '' === $product->get_price() ) return $price_html;
// If on specifc archive page
if ( $slug == "eathere" ) {
$orig_price = wc_get_price_to_display( $product );
$price_html = wc_price( $orig_price + 0.50 );
}
return $price_html;
}
add_filter( 'woocommerce_get_price_html', 'bbloomer_alter_price_display', 9999, 2 );
But obviously, this only updates the visual price, and as soon as it's added to the cart and checkout it just shows the base price, unaltered.
I know that you're supposed to update the cart separately with a hook like woocommerce_before_calculate_totals but I can't figure out how to apply the logic that it has to have come from this archive to have the modified price.
This code works for the category they're in:
add_filter( 'woocommerce_product_get_price', 'custom_sale_price_for_category', 10, 2 );
function custom_sale_price_for_category( $price, $product ) {
//Get all product categories for the current product
$terms = wp_get_post_terms( $product->get_id(), 'product_cat' );
foreach ( $terms as $term ) {
$categories[] = $term->slug;
}
if ( ! empty( $categories ) && in_array( 'eathere-burgers', $categories, true ) ) {
$price *= ( 1 + 0.50 );
}
return $price;
}
But this will then affect the prices across the site regardless of which archive page they're on. If I add that it should only affect the archive page via the slug as in the first example then they appear correctly in the archive but again don't change in the cart of checkout.
I'm really stuck on how to get this working without duplicating the entire product list and adjusting the price. It feels like this should be possible with a simple bit of code but WooCommerce's hooks and filters are so dense in their functionality I'm finding it hard to figure out.
I don't know if the logic is correct or not but you can try with Session Variable in 'woocommerce_product_get_price' filter to store value of archive
session_start();
$_SESSION["archive"] = "eathere";
Now get the same session variable value on cart/checkout Hook 'woocommerce_before_calculate_totals' & don't forgot to
session_start(); & get value of $_SESSION["archive"] in this hook to check archive slug & update the price according to that.
In your code assign slug value to session variable in if & elseif statement based on which archive page you are on
function bbloomer_alter_price_display( $price_html, $product ) {
global $wp_query;
$slug = basename(get_permalink($wp_query->post->ID));
// Only if not null
if ( '' === $product->get_price() ) return $price_html;
// If on specifc archive page
if ( $slug == "eathere" ) {
$_SESSION["archive"] = "eathere";
$orig_price = wc_get_price_to_display( $product );
$price_html = wc_price( $orig_price + 0.50 );
}
elseif ( $slug == some_other_archive_slug ) {
$_SESSION["archive"] = some_other_archive_slug;
$orig_price = wc_get_price_to_display( $product );
$price_html = wc_price( $orig_price + 2 );
}
return $price_html;
}
add_filter( 'woocommerce_get_price_html', 'bbloomer_alter_price_display', 9999, 2 );
on Cart/Checkout page compare the value of archive page slug with Session variable slug value & based on that update the price of the product
add_action( 'woocommerce_before_calculate_totals', 'update_price_cart', 9999 );
function update_price_cart( $cart ) {
// LOOP THROUGH CART ITEMS & ADD Price
if ($_SESSION["archive"] == "eathere"){
foreach ( $cart->get_cart() as $cart_item_key => $cart_item ) {
$product = $cart_item['data'];
$price = $product->get_price();
$cart_item['data']->set_price( $price + 0.50 );
}
}
elseif ($_SESSION["archive"] == some_other_archive_slug) {
$product = $cart_item['data'];
$price = $product->get_price();
$cart_item['data']->set_price( $price + 2 );
}
}
Sometimes it may give error in that case you need to start_session(); before assigning value to session

How can I create a cart for each brand in Woocommerce

So I'm using Woo Brand plugin to display all brands that i'm using in my store.
What I want to do is limit customers to buy from several brands at once. For example, if I have Brand A and Brand B, the customer can add products to the cart only from brand A or only from brand B.
If they have products in the basket only from brand A and they go to the page from brand B, on the page from brand B I want to have a new basket, in which they can only add products from brand B. If they return to the page from brand A, they will still have in their basket the products added only from brand A. How can I achieve this?
Currently I have this piece of code which can add in cart only from a specific brand e.g (If i'm on brand A page, I can add only from they)
add_filter( 'woocommerce_add_to_cart_validation', 'only_one_product_brand_allowed', 20, 3 );
function only_one_product_brand_allowed( $passed, $product_id, $quantity) {
// Getting the product brand term slugs in an array for the current product
$brand_slugs = wp_get_post_terms( $product_id, 'product_brand', array( 'fields' => 'slugs' ) );
$cart_contents = WC()->cart->get_cart();
$cart_item_keys = array_keys ( $cart_contents );
// Get the brand name for first item from cart
$first_item = $cart_item_keys[0];
$first_item_id = $cart_contents[$first_item]['product_id'];
$brand_name = get_the_terms($first_item_id, 'product_brand');
$current_product_brand = get_the_terms($product_id, 'product_brand');
// Loop through cart items
foreach ($cart_contents as $cart_item_key => $cart_item ){
// Check if the product category of the current product don't match with a cart item
if( ! has_term( $brand_slugs, 'product_brand', $cart_item['product_id'] ) ){
// phpAlert('Trebuie golit cosul');
// Displaying a custom notice
wc_add_notice( __('You can add products to the cart only from the same brand. In your cart are products from <strong>'.$brand_name[0]->name. '.' ), 'error' );
// Avoid add to cart
return false; // exit
}
}
return $passed;
}
You can't have in WooCommerce different baskets (cart) for each product brand, you can only avoid customer to have combined items from different brands and optimize your code like:
add_filter( 'woocommerce_add_to_cart_validation', 'only_one_product_brand_allowed', 20, 3 );
function only_one_product_brand_allowed( $passed, $product_id, $quantity) {
$taxonomy = 'product_brand';
$field_names = array( 'fields' => 'names');
// Getting the product brand term name for the current product
if( $term_name = wp_get_post_terms( $product_id, $taxonomy, $field_names ) ) {
$term_name = reset($term_name);
} else return $passed;
// Loop through cart items
foreach (WC()->cart->get_cart() as $cart_item ){
// Get the cart item brand term name
if( $item_term_name = wp_get_post_terms( $cart_item['product_id'], $taxonomy, $field_names ) ) {
$item_term_name = reset($item_term_name);
} else continue;
// Check if the product brand of the current product exist already in cart items
if( isset($term_name) && isset($item_term_name) && $item_term_name !== $term_name ){
// Displaying a custom notice
wc_add_notice( sprintf( __("You are not allowed to combine products from different brands. There is already a cart item from <strong>%s</strong> brand.", "woocommerce" ), $item_term_name ), 'error' );
// Avoid add to cart and display message
return false;
}
}
return $passed;
}
Code goes in function.php file of the active child theme (or active theme). Tested and works.
Now what you could do is to spit an order in multiple sub-orders, one for each brand (or merchant), keeping your original Order.

Disable Woocommerce add to cart button if the product is already in cart

I am trying to set the functionality in Woocommerce on the Add to Cart button to only allow a particular product to be added once to the Cart.
Once a particular product has been added to cart the first time, the Add to Cart needs to be hidden.
In the cart I can have any number of products - just a max of quantity 1 for each product.
I was doing research and saw that I can use woocommerce_add_to_cart_validation hook. But have no idea how to start off.
How can I allow a particular product to be added once to the Cart?
Disable add to cart button if product is in cart using woocommerce_is_purchasable hook:
add_filter( 'woocommerce_is_purchasable', 'disable_add_to_cart_if_product_is_in_cart', 10, 2 );
function disable_add_to_cart_if_product_is_in_cart ( $is_purchasable, $product ){
// Loop through cart items checking if the product is already in cart
foreach ( WC()->cart->get_cart() as $cart_item ){
if( $cart_item['data']->get_id() == $product->get_id() ) {
return false;
}
}
return $is_purchasable;
}
Code goes in function.php file of your active child theme (or active theme). Tested and works (even for product variations in variable products).
Original answer:
Here is an example using woocommerce_add_to_cart_validation hook and that will do the trick (preventing add to cart action and displaying a custom notice when needed), and using a custom utility function that will remove quantity field for your specific defined product ID:
add_filter( 'woocommerce_add_to_cart_validation', 'limit_cart_items_from_category', 10, 3 );
function limit_cart_items_from_category ( $passed, $product_id, $quantity ){
// HERE define your product ID
$targeted_product_id = 37;
// Check quantity and display notice
if( $quantity > 1 && $targeted_product_id == $product_id ){
wc_add_notice( __('Only one item quantity allowed for this product', 'woocommerce' ), 'error' );
return false;
}
// Loop through cart items checking if the product is already in cart
foreach ( WC()->cart->get_cart() as $cart_item ){
if( $targeted_product_id == $product_id && $cart_item['data']->get_id() == $targeted_product_id ) {
wc_add_notice( __('This product is already in cart (only one item is allowed).', 'woocommerce' ), 'error' );
return false;
}
}
return $passed;
}
// Checking and removing quantity field for a specific product
add_filter( 'woocommerce_quantity_input_args', 'custom_quantity_input_args', 10, 2 );
function custom_quantity_input_args( $args, $product ) {
// HERE define your product ID
$targeted_product_id = 37;
if( $targeted_product_id == $product->get_id() )
$args['min_value'] = $args['max_value'] = $args['input_value'] = 1;
return $args;
}
Code goes in function.php file of your active child theme (or active theme). Tested and works.

Show Message in Woocommerce Cart only for specific product variation Attribute

I am adding a custom message to the Cart page, but so that it only appears if an added product has a custom variation atribute selected.
After researching I came up to:
add_action( 'woocommerce_after_cart', 'wnd_after_cart' );
function wnd_after_cart() {
if($attribute_slug == 'no_review'){
echo '<div class="wnd_after_cart"><h4>There will be no item manual review</h4><br /> </div>';
}
}
But its not working. Does anyone know what am I doing wrong?
add_action( 'woocommerce_after_cart', 'wnd_after_cart' );
function wnd_after_cart() {
echo '<div class="wnd_after_cart"><h4>There will be no item manual review</h4><br /> </div>';
}
Works very well, but I can't get the code to display the message only IF my custom attribute is selected.
Any help is appreciated…
Edit:
I set my product Atributes (example:Shirt size, Slug:'Shirt_Size') , and their variations within this atribute (Example: S (Slug:'Size_S'), M(Slug:'Size_M'), XL(Slug:'Size_XL') )
I'm trying to display the message when a specific Attribute Variation is selected (Example: The slug for S, 'Size_S')
I'm using clothes/shirt sizes since its a more common example to help illustrate.
In case I didn't explain very well, basically the code is searching for the attribute slug that you can see here in this video at 0:23
https://www.youtube.com/watch?v=QyMuq-WkV0o
But I'm trying to make it search for the slugs of the attribute variations that can be seen at 0:35
(the attribute attributes, or attribute variations, or attribute childs, I'm not sure what to name them)
#LoicTheAztec code seems to be working very well, but it's searching for the slug of the attribute (Shirt_Size), and not for the attribute variations shown previosly.
When I set the code to find 'Shirt_size', it will disply the message, but when I set it to find 'Size_S', it stops working.
Did this make sense?
Thank you for the attention and advice once again.
There is some missing code like getting your $attribute_slug from variations in cart items:
add_action( 'woocommerce_after_cart', 'checking_variation_attributes_message' );
function checking_variation_attributes_message() {
$found = false;
// Loop through cart items
foreach ( WC()->cart->get_cart() as $cart_item ){
$product = $cart_item['data'];
if( ! $product->is_type('variation')){
continue; // Jump to next cart item
}
$variation_attributes = $product->get_variation_attributes();
foreach ( $variation_attributes as $variation_attribute => $term_slug ){
$attribute_slug = str_replace('attribute_pa_', '', $variation_attribute);
if( $attribute_slug == 'no_review' ){
$found = true;
break;
}
}
}
if($found){
echo '<div class="wnd_after_cart"><h4>There will be no item manual review</h4><br /> </div>';
}
}
Update: Or if you are looking for a product attribute term slug instead, use it this way:
add_action( 'woocommerce_after_cart', 'checking_variation_attributes_message' );
function checking_variation_attributes_message() {
$found = false;
// Loop through cart items
foreach ( WC()->cart->get_cart() as $cart_item ){
$product = $cart_item['data'];
if( ! $product->is_type('variation')){
continue; // Jump to next cart item
}
$variation_attributes = $product->get_variation_attributes();
foreach ( $variation_attributes as $variation_attribute => $term_slug ){
$attribute_slug = str_replace('attribute_pa_', '', $variation_attribute);
if( $term_slug == 'no_review' ){
$found = true;
break;
}
}
}
if($found){
echo '<div class="wnd_after_cart"><h4>There will be no item manual review</h4><br /> </div>';
}
}
Code goes in function.php file of your active child theme (or active theme). Tested and work.

Deny checkout for specific cart items in Woocommerce based on product categories

Based on Allow checkout only when a product of a mandatory category is in cart I tried to make my own code sample that Renders a notice and prevents checkout if the cart
only contains products in specific categories. It works on prevention and error notice however on adding other products, it still denies checkout.
/**
* Renders a notice and prevents checkout if the cart
* only contains products in a specific category
*/
//add_action( 'woocommerce_check_cart_items', 'brown_wc_prevent_checkout_for_category' );
function brown_wc_prevent_checkout_for_category() {
// set the slug of the category for which we disallow checkout
$categories = array('drinks','extra-accompaniments');
foreach ($categories as $category => $value) {
// get the product category
$product_cat = get_term_by( 'slug', $category, 'product_cat' );
// sanity check to prevent fatals if the term doesn't exist
if ( is_wp_error( $product_cat ) ) {
return;
}
// check if this category is the only thing in the cart
if ( brown_wc_is_category_alone_in_cart( $category ) ) {
// render a notice to explain why checkout is blocked
wc_add_notice( sprintf( 'Please choose atleast one bento.', $category ), 'error' );
}
}
}
/**
* Checks if a cart contains exclusively products in a given category
*
* #param string $category the slug of the product category
* #return bool - true if the cart only contains the given category
*/
function brown_wc_is_category_alone_in_cart( $category ) {
//When the cart is empty, remove warning message that I have set for when the snippet takes effect
if (!WC()->cart->is_empty()) {
// All the code
// check each cart item for our category
foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) {
// if a product is not in our category, bail out since we know the category is not alone
if ( ! has_term( $category, 'product_cat', $cart_item['data']->id ) ) {
return false;
}
}
// if we're here, all items in the cart are in our category
return true;
} else {
return false; // Assume you'd want false here, since the cart is empty
}
}
There is some mistakes in your code that avoid it to work successfully. Your code and requirements are quiet different, than my previous answer.
You will have to set your needed button URL and text for the notice, like the "bento" product category link and button name.
Here is the code:
// Conditional function that check if the non mandatory product categories are in all cart items
function has_not_mandatory_category(){
// DEFINE HERE the your non mandatory product categories (Ids, slugs or names)
$categories = array('drinks','extra-accompaniments');
// Iterrating each item in cart and detecting…
foreach ( WC()->cart->get_cart() as $cart_item ) {
$product_id = $cart_item['product_id']; // <== This is the right way (working for product variations too)
if ( ! has_term( $categories, 'product_cat', $product_id ) )
return false;
}
return true;
}
// Display a message and prevent checkout if the non mandatory product categories are not in all cart items
add_action( 'woocommerce_check_cart_items', 'prevent_checkout_display_notice' ); // for cart and checkout
function prevent_checkout_display_notice() {
// DEFINE HERE the return button URL and title
$button_url = '#';
$button_title = 'Go there';
// Display message if the non mandatory product categories are not in all cart items
if ( has_not_mandatory_category() )
wc_add_notice(
sprintf( __( 'Please choose at least one bento. <a class="button" href="%s">%s</a>', 'your_theme_domain'),
$button_url,
$button_title
), 'error'
);
}
Code goes in function.php file of your active child theme (or active theme).
Tested and works…
Instead of targeting non mandatory product categories you should do the contrary…

Categories