I am looking for a way to not have to manually exclude products from coupons based upon the product ID that the coupon should apply to. Thus, build an array of all product IDs in the shop that will then populate the excluded products field minus the product ID (or IDs) that the coupon does apply to.
I was thinking to use "Disable coupons & discounts from applying to defined Woocommerce products in cart" answer making some changes to it, but as I am new to WP/WC queries and functions, I didn't get something functional yet.
Try the following, where:
you will define in the first function the array of product ids to be excluded from coupons.
the 2nd function will remove included products ids from the array of excluded product ids and will set that in the coupon when saved.
The code:
function my_coupons_excl_product_ids() {
// HERE set in the array your product IDs to be excluded
return array(17, 37, 52, 123, 124, 152, 154);
}
// On coupon save
add_action('woocommerce_coupon_options_save', 'action_coupon_options_save_callback', 10, 2);
function action_coupon_options_save_callback( $post_id, $coupon ) {
$included_ids = (array) $coupon->get_product_ids();
if( size_of($included_ids) > 0 ) {
$excl_product_ids = array_diff( my_coupons_excl_product_ids(), $included_ids ); // Get the difference
$coupon->set_excluded_product_ids( array_filter( array_map( 'intval', (array) $excl_product_ids ) ) );
$coupon->save();
}
}
Code goes in function.php file of your active child theme (or active theme). It should works.
Related
Having all the single products that are assigned to grouped products available and visible on the archive / category pages is not an ideal solution and I'm wondering how to resolve this.
I know there's a "visibility" option in WooCommerce, but that's even less ideal.
As far as I understand it, WooCommerce now uses meta data for this and not post_parent and because of that, I am asking for help on how to update this query to cover that.
The code from here that I tried and doesn't work anymore:
add_action( 'woocommerce_product_query', 'hide_single_products_assigned_to_grouped_product_from_archive' );
function hide_single_products_assigned_to_grouped_product_from_archive( $q ){
$q->set( 'post_parent', 0 );
}
You cant really target the children products from a grouped product on the product query as the data is stored under _children meta_key on wp_post_meta table as a serialized array.
But what you can do is first to add to all children products from your grouped products a custom field. Then you will be able to use that custom field to change the product query.
The following function will do that job and you will run it only once:
function add_a_custom_field_to_grouped_children_products() {
// get all grouped products Ids
$grouped_ids = wc_get_products( array( 'limit' => -1, 'type' => 'grouped', 'return' =>'ids' ) );
// Loop through grouped products
foreach( $grouped_ids as $grouped_id ){
// Get the children products ids
$children_ids = (array) get_post_meta( $grouped_id, '_children', true );
// Loop through children product Ids
foreach( $children_ids as $child_id ) {
// add a specific custom field to each child with the parent grouped product id
update_post_meta( $child_id, '_child_of', $grouped_id );
}
}
}
add_a_custom_field_to_grouped_children_products(); // Run the function
Code goes in functions.php file of your active child theme (or active theme).
Once saved, browse any page of your web site. Then remove that code and save.
Now all your grouped children products will have a custom field. If you add/create more grouped products, you will need the following function that will add that custom field to the children products:
// Add on the children products from a grouped product a custom field
add_action( 'woocommerce_process_product_meta_grouped', 'wc_action_process_children_product_meta' );
function wc_action_process_children_product_meta( $post_id ) {
// Get the children products ids
$children_ids = (array) get_post_meta( $post_id, '_children', true );
// Loop through children product Ids
foreach( $children_ids as $child_id ) {
// add a specific custom field to each child with the parent grouped product id
update_post_meta( $child_id, '_child_of', $post_id );
}
}
Code goes in functions.php file of your active child theme (or active theme). Tested and works.
Now to finish, the function that will hide from on all product loops the children products from your grouped products:
add_filter( 'woocommerce_product_query_meta_query', 'hide_children_from_grouped_products' );
function hide_children_from_grouped_products( $meta_query ) {
if( ! is_admin() ) {
$meta_query[] = array(
'key' => '_child_of',
'compare' => 'NOT EXISTS'
);
}
return $meta_query;
}
Code goes in functions.php file of your active child theme (or active theme). Tested and works.
Related: Filter products from a specific custom meta data in Woocommerce shop page
I have created product addons for products in my cart. I am associating each product with an image saved to the filesystem, so when you go to view an order you can see the image that was created with the product.
When an image is saved, the cart item gets a custom key. This custom key is used in a cookie to carry the path to the image through the checkout process
if (!function_exists('force_individual_cart_items')) {
function force_individual_cart_items( $cart_item_data, $product_id ){
$unique_cart_item_key = md5( microtime().rand() );
$cart_item_data['unique_key'] = $unique_cart_item_key;
if (isset($_COOKIE['CustomImagePath'])) {// if image path exists that needs to be saved
$imagePath = $_COOKIE['CustomImagePath']; // get image path
$imagePaths = (isset($_COOKIE['ImagePaths']) ? json_decode(stripslashes(html_entity_decode($_COOKIE['ImagePaths'])), true) : array());
$imagePaths[$unique_cart_item_key] = $imagePath; //asscoiate image path with product cart key
setcookie('ImagePaths', json_encode($imagePaths),0,'/'); // save association in image paths cookie
unset($_COOKIE['CustomImagePath']);
setcookie('CustomImagePath', null, -1, '/');
}
return $cart_item_data;
}
}
add_filter( 'woocommerce_add_cart_item_data','force_individual_cart_items', 10, 2 );
When the order is created I then add a new row into the woocommerce_item_meta table with the meta_key of "Custom Image". The issue I am running into is associating the order item with the cart_item_key.
(in the woocommerce_thankyou hook)
global $wpdb, $wp;
$query = "SELECT order_item_id FROM wp_woocommerce_order_items WHERE order_id = $order_id";
$result = $wpdb->get_results($query, true);
$imagePaths = json_decode(stripslashes(html_entity_decode($_COOKIE['ImagePaths'])), true);
foreach ($result as $order_item) {
$item_id = $order_item->order_item_id;
}
$cart_item_custom_key = NOT AVAILABLE IN THE ORDER ITEM
$filePath = $_COOKIE[$cart_item_custom_key];
$wpdb->insert('wp_woocommerce_order_itemmeta', array('order_item_id'=>$post->ID, "meta_key"=>'Custom Image', 'meta_value'=>$filePath))
For example let's say a user selects 3 images. In the cart the first product will have a custom key of 1, the second will have a custom key of 2, and the third a custom key of 3. Using
$woocommerce->cart->get_cart_contents()[cart_item_key]['unique_key'];
I can get that unique key while I have access to the cart. Once the order is created however, order_item does not have that key. Order one, two, and three no longer have custom keys. So I cannot get the image from the cookie with the associated key.
$filePath = $_COOKIE[ ? KEY NO LONGER EXISTS ? ];
$wpdb->insert('wp_woocommerce_order_itemmeta', array('order_item_id'=>$post->ID, "meta_key"=>'Custom Image', 'meta_value'=>$filePath));
is there a way to retrieve the key that that cart item had, because the order item does not seem to have it?
If you just need to get cart item key, save it as custom hidden order item meta data using:
add_action('woocommerce_checkout_create_order_line_item', 'save_cart_item_key_as_custom_order_item_metadata', 10, 4 );
function save_cart_item_key_as_custom_order_item_metadata( $item, $cart_item_key, $values, $order ) {
// Save the cart item key as hidden order item meta data
$item->update_meta_data( '_cart_item_key', $cart_item_key );
}
Code goes in functions.php file of your active child theme (or active theme). Tested and works.
To get this cart item key from the order item you will use something like:
// Get the WC_Order Object from order ID (if needed)
$order = wc_get_order( $order_id );
// Loop though order items
foreach ( $order->get_items() as $item ){
// Get the corresponding cart item key
$cart_item_key = $item->get_meta( '_cart_item_key' );
}
My opinion: You are making things much more complicated than they should be:
Instead of using cookies, you should better use WC_Sessions (if this is really needed). But it should be better to add all required data in hidden input fields on the product page…
You should better set all required data as custom cart item data when the product is added to cart with woocommerce_add_cart_item_data hook (so no need of cookie or something else).
Also you should not use woocommerce_thankyou action hook to add order item custom metadata for many reasons as there is specifically dedicated hooks for that: Once the order is created you can use woocommerce_checkout_create_order_line_item action hook, to save your custom cart item data as order item meta data.
With your provided code I can't help more than that for instance.
How would we go about disabling the single product page for specific products?
For example, we have some products that have product variations. In this case we are using the single product page. But for the products that do not have variations we simply use an add to cart link on the landing pages and skip the single product page that just adds an extra step for the customer.
I found this post which outlines how to disable ALL single product pages. But I would like to target the pages that are disabled. Either by product number or perhaps product listing type ie. variable, non variable.
What is the best way to go about doing this, without breaking WooCommerce or causing SEO issues?
To clarify: by disabled I mean remove the link to the page from areas like the shopping cart etc.
In the following functions, you will have to define one or many product IDs inside the code.
This first hooked function will remove the product from product catalog:
add_filter( 'woocommerce_product_is_visible', 'filter_product_is_visible', 20, 2 );
function filter_product_is_visible( $is_visible, $product_id ){
// HERE define your products IDs (or variation IDs) to be set as not visible in the array
$targeted_ids = array(37, 43, 51);
if( in_array( $product_id, $targeted_ids ) )
$is_visible = false;
return $is_visible;
}
To remove the link from cart items in cart page, you can use the following
add_filter( 'woocommerce_cart_item_name', 'filter_cart_item_name', 20, 3 );
function filter_cart_item_name( $product_name, $cart_item, $cart_item_key ) {
// HERE define your products IDs (or variation IDs) to be set as not visible in the array
$targeted_ids = array(37, 43, 51);
if( in_array( $cart_item['data']->get_id(), $targeted_ids ) && is_cart() )
return $cart_item['data']->get_name();
return $product_name;
}
Code goes in function.php file of your active child theme (or active theme). Tested and works.
Is also possible to redirect the target products pages to the main shop.
You can add if statement around return false. You can either check for product (page) id or I would add tag (or category or custom field) to these and then in if statement you can check for this tag and if its there you return false;
I have found similar solutions to this, for example: "How to limit orders to one category". I have tried to modify the code but it's not specific enough.
In my case, each Vendor is defined by a product attribute value, 8 Terms at all. If the cart contains products from more than 3 different Terms, I need to set up a message that says "Sorry, you may only order from 3 different Vendors at a time".
This is what I'm using as a starting point:
add_action( 'woocommerce_add_to_cart', 'three_vendors' );
function three_vendors() {
if ATTRIBUTE = SELECT A VENDOR; NUMBER OF TERMS > 3 {
echo "Sorry! You can only order from 3 Vendors at a time.”;
}
}
The middle line is me filling in the blanks using non-php language.
I am looking for a way to define the amount of variations in the cart. If this is not possible to do with Attributes, I am open to use Categories instead.
Does anyone know how to do this?
Update: Is not possible to manage variations with woocommerce_add_to_cart_valisation hook
Instead we can use a custom function hooked in woocommerce_add_to_cart filter hook, removing the last added cart item when more than 3 vendors (items) are in cart:
// Remove the cart item and display a notice when more than 3 values for "pa_vendor" attibute.
add_action( 'woocommerce_add_to_cart', 'no_more_than_three_vendors', 10, 6 );
function no_more_than_three_vendors( $cart_item_key, $product_id, $quantity, $variation_id, $variation, $cart_item_data ) {
// Check only when there is more than 3 cart items
if ( WC()->cart->get_cart_contents_count() < 4 ) return;
// SET BELOW your attribute slug… always begins by "pa_"
$attribute_slug = 'pa_vendor'; // (like for "Color" attribute the slug is "pa_color")
// The current product object
$product = wc_get_product( $variation_id );
// the current attribute value of the product
$curr_attr_value = $product->get_attribute( $attribute_slug );
// We store that value in an indexed array (as key /value)
$attribute_values[ $curr_attr_value ] = $curr_attr_value;
//Iterating through each cart item
foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) {
// The attribute value for the current cart item
$attr_value = $cart_item[ 'data' ]->get_attribute( $attribute_slug );
// We store the values in an array: Each different value will be stored only one time
$attribute_values[ $attr_value ] = $attr_value;
}
// We count the "different" values stored
$count = count($attribute_values);
// if there is more than 3 different values
if( $count > 3 ){
// We remove last cart item
WC()->cart->remove_cart_item( $cart_item_key );
// We display an error message
wc_clear_notices();
wc_add_notice( __( "Sorry, you may only order from 3 different Vendors at a time. This item has been removed", "woocommerce" ), 'error' );
}
}
Code goes in function.php file of your active child theme (or theme) or also in any plugin file.
This code is tested and should works for you. It will check for the attribute values regarding your specific attribute and will remove last added cart item for more than 3 attribute values.
The only annoying thing is that I can't remove the classic added to cart notice for the moment. I will try to find another way…
On woocommerce, I am using the code below to render some product custom fields, on cart and on checkout:
add_filter( 'woocommerce_get_item_data', 'rendering_meta_field_on_cart_and_checkout', 10, 2 );
function rendering_meta_field_on_cart_and_checkout( $cart_data, $cart_item ) {
$custom_items = array();
if( !empty( $cart_data ) ) {
$custom_items = $cart_data;
}
if( isset( $cart_item['wccpf_enter_product_id'] ) ) {
$diamond = $cart_item['wccpf_enter_product_id'];
$pacolor = get_the_terms($diamond, 'pa_color');
foreach ( $pacolor as $pacolor ) {
$color= $pacolor ->name;
}
$custom_items[] = array( "name" => "color", "value" => $color);
}
return $custom_items;
}
How can I display that custom product fields wccpf_enter_product_id' value in orders?
Thanks.
You can use a custom function hooked in woocommerce_add_order_item_meta action hook to achieve this.
You will need first to add an attribute in your products to get a "readable clean label" for your custom field value that is going to appear as order items meta data.
For that you have to create an attribute first and then set it in your product with any value (as you will replace this value in the code below).
See HERE some more explanations about that process…
You will have to replace in my code the 'custom_field_key' by your specific custom key that you will find on wp_woocommerce_order_itemmeta MySQL table for the corresponding item ID for your specific Order ID.
To find the corresponding item ID for the order, you can search in wp_woocommerce_order_items MySQL table with the Order ID…
You will have also to set your correct attribute slug instead of 'pa_your_attribute' to display in your orders the correct label text for this custom field value.
(see below other similar answers references).
So your code will be something like this:
// ADD THE INFORMATION AS META DATA SO THAT IT CAN BE SEEN AS PART OF THE ORDER
add_action('woocommerce_add_order_item_meta','add_and_update_values_to_order_item_meta', 1, 3 );
function add_and_update_values_to_order_item_meta( $item_id, $item_values, $item_key ) {
// Getting your custom product ID value from order item meta
$custom_value = wc_get_order_item_meta( $item_id, 'custom_field_key', true );
// Here you update the attribute value set in your simple product
wc_update_order_item_meta( $item_id, 'pa_your_attribute', $custom_value );
}
}
Code goes in function.php file of your active child theme (or theme) or also in any plugin file.
This should work
Related answers:
Adding user custom field value to order items details
Add custom Product data dynamically as item meta data on the Order
Displaying custom product data in Order items view