Remove particular variation from dropdown in WooCommerce frontend - php

I am using WooCommerce and WooCommerce Subscriptions and its working as per my expectations.
Now I am creating a variable subscription product having multiple attributes like this.
Now I want to remove/hide particular item from dropdown hence I am trying to use below code / hook which I believe might help me to achieve.
add_filter('woocommerce_dropdown_variation_attribute_options_args', 'hide_variations_for_mindesk_users');
function hide_variations_for_mindesk_users( $args ){
print_r($args);
return $args;
}
Now my question is, how can I remove or hide particular variation product from dropdown? Do I need to remove from variation id or from somewhere?
For example:
Here I want to remove/hide 2nd variation from dropdown which has variation id #4171 having "Monthly- Professional". This should work with single attribute as well.
Can anyone point me in the right direction to achieve this?

In add-to-cart/variable.php template file, we find foreach ( $attributes as $attribute_name => $options ). However, the intention is to hide 1 attribute, so let's see where these are passed to the template file.
In includes/wc-template-functions.php, we can see that the template file is called and an array is passed with some options. One of these options is available_variations' => $get_variations ? $product->get_available_variations()
The get_available_variations() function is then found in includes/class-wc-product-variable.php which in turn $variation_ids = $this->get_children(); contains.
The get_children() function can then be found in includes/class-wc-product-variable.php, which contains apply_filters( 'woocommerce_get_children', $this->children, $this, false );
And that filter hook can be used to remove one or more childIDs (variantions)
So you get:
function filter_woocommerce_get_children( $children, $product, $false ) {
// NOT backend
if ( is_admin() ) return $children;
// Variation ID
$variation_id = 4171;
// Delete by value: Searches the array for a given value and returns the first corresponding key if successful
if ( ( $key = array_search( $variation_id, $children ) ) !== false ) {
unset( $children[$key] );
}
return $children;
}
add_filter( 'woocommerce_get_children', 'filter_woocommerce_get_children', 10, 3 );
If you want to apply it for multiple variantion IDs, use:
function filter_woocommerce_get_children( $children, $product, $false ) {
// NOT backend
if ( is_admin() ) return $children;
// Variation IDs, multiple IDs can be entered, separated by a comma
$variation_ids = array( 4171, 36, 38 );
// Loop through variation IDs
foreach ( $variation_ids as $variation_id ) {
// Delete by value: Searches the array for a given value and returns the first corresponding key if successful
if ( ( $key = array_search( $variation_id, $children ) ) !== false ) {
unset( $children[$key] );
}
}
return $children;
}
add_filter( 'woocommerce_get_children', 'filter_woocommerce_get_children', 10, 3 );
Used in this answer: PHP array delete by value (not key)

Related

Why is this conditional removal of WooCommerce billing field not working?

I've got the following code in my functions.php file that is supposed to check if certain products are in the cart and, if they are, remove a field from the billing set of fields at checkout. However, it's not removing the field, or any other field I try. I'm wondering why that is. Other tutorials I'm following are consistent with this code and say it should work, but for me it's not. What did I get wrong?
/**
* Check if a specific product ID is in the cart
*/
function dz_product_is_in_the_cart() {
// Add your special product IDs here
$ids = array( '10771', '10773', '10774', '10943', '10944', '10945', '10946', '10947', '10948', '10949', '10950', '10951', '10952', '10953', '10943', '10943', '10943', '10943');;
// Products currently in the cart
$cart_ids = array();
// Find each product in the cart and add it to the $cart_ids array
foreach( WC()->cart->get_cart() as $cart_item_key => $values ) {
$cart_product = $values['data'];
$cart_ids[] = $cart_product->id;
}
// If one of the special products are in the cart, return true.
if ( ! empty( array_intersect( $ids, $cart_ids ) ) ) {
return true;
} else {
return false;
}
}
/**
* Conditionally remove a checkout field based on products in the cart
*/
function dz_remove_checkout_field( $fields ) {
if ( dz_product_is_in_the_cart() ) {
unset( $fields['billing']['billing_first_name'] );
}
return $fields;
}
add_filter( 'woocommerce_checkout_fields' , 'dz_remove_checkout_field' );
The billing_first_name field is a required field. So do I need to do something to unrequire it before I try to unset it?
The problem was a plugin that was inserting this field was overriding my code that tried to unrequire and remove it. I opted to disable the plugin and add and conditionally control my own field(s) with the code here: https://woocommerce.com/document/tutorial-customising-checkout-fields-using-actions-and-filters/#section-7

Woocommerce Admin Order Details - Show custom data on order details page

I'm searching and trying it for 2 days with no success, please help.
I want to filter woocommerce orders to add additional details from db to order details page based on product attribute but I can't find the right woocommerce action/filter hook for this task.
Here suppose I've variable $is_customized = false;
If $is_customized == true then I need to add custom data from database to orders detail page.
NOTE: I don't want to add additional meta box instead I want to change order detail table for:
Replacing the default Product image with the image stored in database
and,
Adding a div containing custom attributes below product name.
I've all these values in my variables but I can't figure out which action hook should I use.
I've attached an image for clarification.
Just need to know if I can change / filter these order results and how ?
I appreciate for your time and help.
Thanks
Here's a start on how to display some extra data on the woocommerce_before_order_itemmeta hook:
add_action( 'woocommerce_before_order_itemmeta', 'so_32457241_before_order_itemmeta', 10, 3 );
function so_32457241_before_order_itemmeta( $item_id, $item, $_product ){
echo '<p>bacon</p>';
}
I don't know how you are saving your data, so I can't make more a more precise suggestion. Keep in mind that immediately following that hook, anything you've saved as order item meta will automatically display.
Filtering the image is more difficult. I've found this gist as a start, but it requires some custom conditional logic as you don't want to filter the thumbnail everywhere, but only in orders.
Edit: Currently the best I can do for filtering the item thumbnails:
add_filter( 'get_post_metadata', 'so_32457241_order_thumbnail', 10, 4 );
function so_32457241_order_thumbnail( $value, $post_id, $meta_key, $single ) {
// We want to pass the actual _thumbnail_id into the filter, so requires recursion
static $is_recursing = false;
// Only filter if we're not recursing and if it is a post thumbnail ID
if ( ! $is_recursing && $meta_key === '_thumbnail_id' ) {
$is_recursing = true; // prevent this conditional when get_post_thumbnail_id() is called
$value = get_post_thumbnail_id( $post_id );
$is_recursing = false;
$value = apply_filters( 'post_thumbnail_id', $value, $post_id ); // yay!
if ( ! $single ) {
$value = array( $value );
}
}
return $value;
}
add_filter( 'post_thumbnail_id', 'so_custom_order_item_thumbnail', 10, 2 );
function so_custom_order_item_thumbnail( $id, $post_id ){
if( is_admin() ){
$screen = get_current_screen();
if( $screen->base == 'post' && $screen->post_type == 'shop_order' ){
// this gets you the shop_order $post object
global $post;
// no really *good* way to check post item, but could possibly save
// some kind of array in the order meta
$id = 68;
}
}
return $id;
}

Woocommerce Order Again won't copy custom fields

I've managed to code my plugin to add extra custom fields to woocommerce products. This works all the way through from cart to completing an order. Going to my account and viewing past orders, the custom fields are displayed correctly.
However, when I choose to click "order again" on a past order, the new cart doesn't contain the custom fields and their values.
Here is what I currently have to attempt this:
// order again
add_filter( 'woocommerce_order_again_cart_item_data', 'woocommerce_order_again_cart_item_data', 10, 3 );
function woocommerce_order_again_cart_item_data($cart_item_meta, $product, $order){
global $woocommerce;
// Disable validation
remove_filter( 'woocommerce_add_to_cart_validation', array( $this, 'validate_add_cart_item' ), 10, 3 );
if ( ! array_key_exists( 'item_meta', $cart_item_meta ) || ! is_array( $cart_item_meta['item_meta'] ) )
$cart_item_meta['item_meta'] = array();
foreach ( array( 'jhpc_toppings', 'jhpc_sauce', 'jhpc_toppings_half', 'jhpc_sauce_half', 'jhpc_garnish' ) as $key )
$cart_item_meta['item_meta'][$key] = $product['item_meta'][$key];
return $cart_item_meta;
}
replace
$cart_item_meta['item_meta'][$key] = $product['item_meta'][$key];
by
$cart_item_meta[$key] = $product[$key];
Otherwise, why are you removing the validation ?
Here is the code to add all custom field data for order again. Use the given code in your theme's function.php file and replace the custom field keys of $customfields array with your keys.
<?php
add_filter( 'woocommerce_order_again_cart_item_data', 'wpso2523951_order_again_cart_item_data', 10, 3 );
function wpso2523951_order_again_cart_item_data($cart_item_meta, $product, $order){
//Create an array of all the missing custom field keys that needs to be added in cart item.
$customfields = [
'customfield_key1',
'customfield_key2',
'customfield_key3',
'customfield_key4',
];
global $woocommerce;
remove_all_filters( 'woocommerce_add_to_cart_validation' );
if ( ! array_key_exists( 'item_meta', $cart_item_meta ) || ! is_array( $cart_item_meta['item_meta'] ) )
foreach ( $customfields as $key ){
if(!empty($product[$key])){
$cart_item_meta[$key] = $product[$key];
}
}
return $cart_item_meta;
}
?>
Replace the values of array $customfields with the keys of custom fields that are missing or are not being added automatically.

How to retrieve cart_item_data with WooCommerce?

During the add_to_cart function, there is a filter to add "cart item data". The filter is woocommerce_add_cart_item_data. I expected to store my custom plugin data in this, so that the data is stored relative to the item and multiple products can be added with different data.
This all seemed to work, but I am not able to retrieve the data. I can't figure it out. The data is there, I can see it in a serialized string, but I can't pull it out.
echo '<pre>';
var_dump( WC() );
foreach( WC()->cart->get_cart() as $cart_item ) {
var_dump( $cart_item );
var_dump( WC()->cart->get_item_data( $cart_item ) );
}
echo '</pre>';
The first dump of WC() has a property: session->_data->cart->(serialized data). The _data property is protected, though, but I can see my custom field inside the serialized data.
The $cart_item is an array with product_id and some other data, but it does not include my custom data :(
Finally, using the get_item_data() method I thought I had it all figured out. I passed in the cart item object, and... an empty string. Same if I pass the key, rather than the cart item itself.
How am I supposed to access the cart item data?
Here is the "Add cart item data" function, which works (or at least seems to work):
function save_class_menu_selection( $cart_item_data, $product_id, $variation_id ) {
if ( !product_is_class( $product_id ) ) return $cart_item_data;
// Save the date, or give a fatal warning. Date is required.
if ( !empty($_REQUEST['class-date']) ) {
$cart_item_data['class-date'] = stripslashes($_REQUEST['class-date']);
return $cart_item_data;
}else{
wp_die('<h2>Invalid Class Date Selected</h2><p>You tried to add a class to your cart, but the date selected was invalid. Please try again.</p>');
exit;
}
}
add_filter( 'woocommerce_add_cart_item_data', 'save_class_menu_selection', 10, 3 );
I was in the same situation today and stumbled over this question after some research.
After some reverse engineering I found the problem and want to provide a solution for other which may also stumble over this question.
The problem is that the data gets sanitized when the cart items get restored from the session. So the extra cart item data IS stored into the session but on the next request it does not get restored.
There is a filter "woocommerce_get_cart_item_from_session". As first parameter you get the sanitized cart item (without extra data) and as second all data which got stored into the session (including extra data).
The solution is to hook in there and also restore your custom cart item data.
Example Code:
add_filter( 'woocommerce_add_cart_item_data', function ( $cartItemData, $productId, $variationId ) {
$cartItemData['myCustomData'] = 'someCustomValue';
return $cartItemData;
}, 10, 3 );
add_filter( 'woocommerce_get_cart_item_from_session', function ( $cartItemData, $cartItemSessionData, $cartItemKey ) {
if ( isset( $cartItemSessionData['myCustomData'] ) ) {
$cartItemData['myCustomData'] = $cartItemSessionData['myCustomData'];
}
return $cartItemData;
}, 10, 3 );
To also show the data at the cart/checkout page you can use the following code:
add_filter( 'woocommerce_get_item_data', function ( $data, $cartItem ) {
if ( isset( $cartItem['myCustomData'] ) ) {
$data[] = array(
'name' => 'My custom data',
'value' => $cartItem['myCustomData']
);
}
return $data;
}, 10, 2 );
The final thing now is to save the data when the order is made:
add_action( 'woocommerce_add_order_item_meta', function ( $itemId, $values, $key ) {
if ( isset( $values['myCustomData'] ) ) {
wc_add_order_item_meta( $itemId, 'myCustomData', $values['myCustomData'] );
}
}, 10, 3 );
You dont have to do anything else the show the data inside the backend, all order item meta data gets display automatically.
I could not get the default cart item data to work, unfortunately. I feel it may not be properly implemented, or may even be deprecated, as there is a lack of support and documentation.
Instead, I used a cart session variable to accomplish the same thing. It's simply an array where each key is the cart_item_key. The value of each array is yet another array, containing a key-value pair of custom fields. So it's essentially the same thing as the built-in cart item data, except stored as cart session data instead.
Here is a Gist containing some utility functions to make it easy:
https://gist.github.com/RadGH/e3444fc661554a0f8c6f
Or if you want to build it yourself, the magic is in WC()->session. Here are the two key functions for this to work:
WC()->session->get('_my_cart_item_data');
WC()->session->set('_my_cart_item_data', $cart_item_data_array);
These are the action hooks you will need:
<<<EXAMPLES
action: woocommerce_add_to_cart
desc: When an item is added to the cart.
args: $cart_item_key, $product_id, $quantity, $variation_id, $variation, $cart_item_data
action: woocommerce_add_order_item_meta
desc: When a cart item is converted to an order item, save metadata to the order item
using the function "wc_add_order_item_meta( $item_id, $key, $value )"
args: item_id, $values, $cart_item_key

How to Alter Cart Contents in WooCommerce?

I am trying to remove particular product from cart via code. I only see the empty cart option which is clear all the product in cart, but i want to clear particular product in cart page.
For example:
Let say I have added two products to cart but i want cart behavior should be neither or nor mean only one product should be in cart.
If product 1 is in cart then product 2 should not allow to add in cart. If product 2 is in cart then product 1 should not allow.
I tried little set of code but i can't find the exact hook to do this actual behavior. What i am trying is instead of empty entire cart, I load the cart content which is in array of values just unset particular array using cart item key, and to load remaining content to cart. But looks like is not works for me.
function cf_alter_cart_content($value) {
global $woocommerce;
$cart_contents = $woocommerce->cart->get_cart();
foreach ($woocommerce->cart->get_cart() as $cart_item_key => $value) {
if ($value['product_id'] == '77') {
unset($cart_contents[$cart_item_key]);
unset($value['data']);
}
return $value['data'];
}
}
//add_action('wp_head', 'cf_alter_cart_content');
add_filter('woocommerce_cart_item_product', 'cf_alter_cart_content', 10, 1);
May be is there any easy way to achieve this? Not sure any suggestion would be great.
I'm using the woocommerce_before_cart filter for a similar setup where people in certain groups aren't allowed to order specific product skus. I hope this helps. You would likely want to create a custom field in each product that would be something like a comma delineated list of other skus/post_ids that it wouldn't be allowed to be ordered with.
This code checks the first group that the user is associated with (in my site's case they only ever have 1 group). The disallowed_product_skus is the list of skus that the group for the user isn't allowed to purchase.
$disallowed_product_skus = array (
<group_num> => array (
'<sku>',
)
);
add_filter ( 'woocommerce_before_cart' , 'cart_check_disallowed_skus' );
function cart_check_disallowed_skus() {
global $woocommerce;
global $disallowed_product_skus;
$assigned_group = GroupOperations::get_current_user_first_group();
$cart_contents = $woocommerce->cart->get_cart();
$keys = array_keys ( $cart_contents );
if ( array_key_exists ( $assigned_group , $disallowed_product_skus ) ) {
$disallowed_products_in_cart = false;
foreach ( $keys as $key ) {
$cart_item_product_id = $cart_contents[$key]['product_id'];
$cart_product_meta = get_post_meta ( $cart_item_product_id );
$cart_product_sku = $cart_product_meta['_sku'][0];
if ( in_array ( $cart_product_sku , $disallowed_product_skus[$assigned_group] ) ) {
$woocommerce->cart->set_quantity ( $key , 0 , true );
$disallowed_products_in_cart = true;
}
}
if ( $disallowed_products_in_cart ) {
echo '<p class="woocommerce-error">Non-approved products have been automatically removed from cart.</p>';
}
}
}

Categories