I would like to delete an existing WooComerce item and add x new WooComerce items from it. It depends on how high the $ item-> quantity is.
My code looks like this:
add_action( 'woocommerce_thankyou', 'my_change_status_function', 10, 1 );
function my_change_status_function( $order_id ) {
$order = new WC_Order( $order_id );
foreach ( $order->get_items() as $item_id => $item ) {
if ( $item->get_quantity() > 1 ) {
wc_delete_order_item( $item_id );
for ( $i = 1; $i <= $item->get_quantity(); $i++ ) {
$new_item_ids = woocommerce_add_order_item(
$order_id,
array(
'order_item_name' => $item->get_name() . '_' . $i,
'order_item_type' => 'line_item',
)
);
if ( $new_item_ids ) {
foreach ( $metavalues as $key => $value ) {
wc_add_order_item_meta( $new_item_ids, $key, $value );
}
}
}
}
}
}
The point where I'm a little bit desperate is here:
if ( $new_item_ids ) {
foreach ( $metavalues as $key => $value ) { <-- I dont know where i get the $metavalues
wc_add_order_item_meta( $new_item_ids, $key, $value );
}
}
I've already tried this post from Stackoverflow:
WooCommerce Add item to Order
Could someone help me?
Thank you in advance
Since WooCommerce 3 your code is outdated and can be simplified using some WC_Abstract_Order methods. Also you should better use woocommerce_checkout_order_created hook instead, that is triggered after order is created:
add_action( 'woocommerce_checkout_order_created', 'separate_order_items_by_quantity' );
function separate_order_items_by_quantity( $order ) {
$items_processed = false; // Initializing
// Loop through order items
foreach ( $order->get_items() as $item_id => $item ) {
$quantity = $item->get_quantity(); // Get quantity
$product = $item->get_product(); // Get the WC_Product Object
// When item has quatity more than 1, separete items by quantity
if ( $item->get_quantity() > 1 ) {
$order->remove_item( $item_id ); // Remove item
$items_processed = true; // Flag it
// Loop through quantity
for ( $i = 1; $i <= $quantity; $i++ ) {
// Add the same productb (separated) n times the quantity
$new_item_id = $order->add_product( $product, 1, array(
'_custom_index' => $i, // (required) to have separated products
'name' => $product->get_name() . ' ' . $i, // (optional) change product name appending the index
'Some meta key' => 'some meta value', // (optional) Here you can set a custom meta key and its value (custom order item meta data) … To hide it from customer the metakey should start with an underscore.
) );
}
}
}
if( $items_processed ) {
$order->apply_changes(); // Merge changes with data and clear
$order->save(); // Save data
}
}
Code goes in functions.php file of the active child theme (or active theme). Tested and works.
Related
I am trying to build a WooCommerce cart rule functionality. It should work like this:
When the user's role is "Wholesaler", they must add to the cart at least 2 or 3 items (depending on the category) of the same product category. Once that condition is met, they could add any product to the cart no matter the rules set before.
E.g:
Minimum order on socks: 3
Minimum order on hats: 3
Minimum order on shirts: 2
Scenarios:
If the customer adds at least 3 hats, the other two minimum rules should be avoided.
If the customer adds 1 sock and 2 hats, they will not be able to complete the order unless they adds 2 more socks or 1 hat.
Based on Prevent WooCommerce checkout if minimum quantity for a category is not reached unless another category is added answer code, this is my code attempt:
function action_woocommerce_check_cart_items() {
// Only run on the cart or checkout pages
if ( is_cart() || is_checkout() ) {
// Minimum
$minimum = 3;
// Category
$category1 = 'socks';
$category2 = 'hats';
// Initialize
$total_socks = 0;
$total_hats = 0;
// Loop through cart items
foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) {
// Product id
$product_id = $cart_item['product_id'];
// Has certain category
if ( has_term( $category1, 'product_cat', $product_id ) ) {
// Add to total
$total_socks += $cart_item['quantity'];
}
elseif ( has_term( $category2, 'product_cat', $product_id ) ) {
// Add to total
$total_hats += $cart_item['quantity'];
}
}
// When total is greater than 0 but less than the minimum
if ( ($total_socks > 0 && $total_socks < $minimum) && ( $total_hats > 0 && $total_hats < $minimum ) ) {
// Notice
wc_add_notice( sprintf( __( 'A minimum of %s products are required from the %s category before checking out.', 'woocommerce' ), $minimum, $category1 ), 'error' );
// Optional: remove proceed to checkout button
remove_action( 'woocommerce_proceed_to_checkout', 'woocommerce_button_proceed_to_checkout', 20 );
}
}
}
add_action( 'woocommerce_check_cart_items' , 'action_woocommerce_check_cart_items', 10, 0 );
But I couldn’t add a user role check and mixed with the other categories missing on the script (socks and shirts), any advice?
First of all, you can use the wp_get_current_user() function to check if the current user has the specified capability.
Then you better use a $settings array that you will loop through against multiple if/else conditions because this is more effective and much shorter to use.
Via the $settings array you can set the "category" and the "minimun", "total" should not be adjusted!
Generating the error message can be done in different ways, but a loop is used again to automatically create the message.
So you get:
function action_woocommerce_check_cart_items() {
// Logged in
if ( is_user_logged_in() ) {
// Get user role(s)
$user = wp_get_current_user();
$roles = ( array ) $user->roles;
// Compare, set the desired user role(s)
$compare = array_diff( $roles, array( 'wholesaler', 'wholesale_customer', 'administrator' ) );
// When empty
if ( empty ( $compare ) ) {
// Settings (multiple settings arrays can be added/removed if desired)
$settings = array(
array(
'category' => 'socks',
'minimum' => 3,
'total' => 0
),
array(
'category' => 'hats',
'minimum' => 3,
'total' => 0
),
array(
'category' => 'shirts',
'minimum' => 2,
'total' => 0
)
);
// Initialize
$flag = false;
// Collect data - loop through cart items
foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) {
// Get product ID
$product_id = $cart_item['product_id'];
// Get quantity
$product_quantity = $cart_item['quantity'];
// Loop trough settings array
foreach ( $settings as $key => $setting ) {
// Checks if the current product has any of given terms
if ( has_term( $setting['category'], 'product_cat', $product_id ) ) {
// Add to the total
$settings[$key]['total'] += $product_quantity;
}
// Checks if the current total is equal to or greater than the minimum
if ( $setting['total'] >= $setting['minimum'] ) {
// Make true, break loop
$flag = true;
break;
}
}
}
// NOT true
if ( ! $flag ) {
// Initialize
$message = '';
$first_letter = __( 'A ', 'woocommerce' );
// Generate error messages - loop trough settings array
foreach ( $settings as $key => $setting ) {
// NOT the first iteration in a foreach loop, convert to capital letter
if ( $key !== array_key_first( $settings ) ) {
// Make a string lowercase
$first_letter = strtolower( $first_letter );
}
// Generate message, append
$message .= sprintf( __( '%s minimum of %s products are required from the "%s" category', 'woocommerce' ), $first_letter, $setting['minimum'], $setting['category'] );
// NOT the last iteration in a foreach loop, append 'OR'
if ( $key !== array_key_last( $settings ) ) {
$message .= '<strong>' . __( ' OR ', 'woocommerce' ) . '</strong>';
}
}
// Append to message
$message .= __( ' before checking out.', 'woocommerce' );
// Notice
wc_add_notice( $message, 'error' );
// Removing the proceed button, until the condition is met
remove_action( 'woocommerce_proceed_to_checkout','woocommerce_button_proceed_to_checkout', 20 );
}
}
}
}
add_action( 'woocommerce_check_cart_items', 'action_woocommerce_check_cart_items', 10 );
I have 3 ticket products in WooCommerce and want to add a name field per ticket product purchased - so if a customer buys three tickets 3 name fields are displayed to fill out in the checkout.
The code below works:
// Custom WooCommerce Checkout Fields based on Quantity
add_action( 'woocommerce_before_order_notes', 'person_details' );
function person_details($checkout) {
global $woocommerce;
$count = WC()->cart->get_cart_contents_count();
$i = 1;
for($k=2; $k<= $count; $k++) {
$i++;
print ('<h3>Please enter details of attendee '.$i.'</h3>');
woocommerce_form_field( 'cstm_full_name'.$i, array(
'type' => 'text',
'class' => array('my-field-class form-row-wide'),
'label' => __('Full name'),
'placeholder' => __('Enter full name'),
),
$checkout->get_value( 'cstm_full_name'.$i ));
echo '<div class="clear"></div>';
}
}
But this includes all products added to cart - how can I target specific Product IDs to be only be part of the cart count?
$count = WC()->cart->get_cart_contents_count();
As an alternative and most recommended way, you can use WC_Cart::get_cart_item_quantities() and in_array() in your callback function to get the cart item quantity from specific product IDs in cart
So you get:
function action_woocommerce_before_order_notes( $checkout ) {
// True
if ( WC()->cart ) {
// Specific product IDs
$product_ids = array( 30, 823, 53, 57 );
// Initialize
$count = 0;
// Loop trough cart items quantities
foreach ( WC()->cart->get_cart_item_quantities() as $product_id => $cart_item_quantity ) {
// Checks if a value exists in an array
if ( in_array( $product_id, $product_ids ) ) {
$count += $cart_item_quantity;
}
}
// Result
echo '<p style="color: red; font-size: 25px;">Count = ' . $count . '</p>';
}
}
add_action( 'woocommerce_before_order_notes', 'action_woocommerce_before_order_notes', 10, 1 );
OR
You could use the woocommerce_cart_contents_count filter hook,
which allows you to customize the get_cart_contents_count() function to your needs:
function filter_woocommerce_cart_contents_count( $array_sum ) {
// True
if ( WC()->cart ) {
// Specific product IDs
$product_ids = array( 30, 823, 53, 57 );
// Loop trough cart items quantities
foreach ( WC()->cart->get_cart_item_quantities() as $product_id => $cart_item_quantity ) {
// Checks if a value NOT exists in an array
if ( ! in_array( $product_id, $product_ids ) ) {
$array_sum -= $cart_item_quantity;
}
}
}
return $array_sum;
}
add_filter( 'woocommerce_cart_contents_count', 'filter_woocommerce_cart_contents_count', 10, 1 );
function action_woocommerce_before_order_notes( $checkout ) {
// True
if ( WC()->cart ) {
// Get number of items in the cart.
$count = WC()->cart->get_cart_contents_count();
// Result
echo '<p style="color: red; font-size: 25px;">Count = ' . $count . '</p>';
}
}
add_action( 'woocommerce_before_order_notes', 'action_woocommerce_before_order_notes', 10, 1 );
Note: although the advantage here is that you can continue to use the get_cart_contents_count() function in this way, the disadvantage is that this function would also display the modified result when used for other purposes
I need to sort fees by name rather than the default by price in WooCommerce orders.
Using Reordering multiple fees differently in Woocommerce cart and checkout pages answer code, I have managed to sort fees by name in cart and checkout pages. But it doesn't work for the order confirmation and emails that get sent out.
Thank you for your help.
Updated
To sort fees by name on customer orders and email notifications, you will use the following:
// Custom function that sort displayed fees by name on order items
function wc_get_sorted_order_item_totals_fee_rows( &$total_rows, $tax_display, $order ) {
$fees = $order->get_fees();
if ( $fees ) {
$fee_names = []; // initializing
// First Loop
foreach ( $fees as $fee_id => $fee ) {
$fee_names[$fee_id] = $fee->get_name();
}
asort($fee_names); // Sorting by name
// 2nd Loop
foreach ( $fee_names as $fee_id => $fee_name ) {
$fee = $fees[$fee_id];
if ( apply_filters( 'woocommerce_get_order_item_totals_excl_free_fees', empty( $fee['line_total'] ) && empty( $fee['line_tax'] ), $fee_id ) ) {
continue;
}
$total_rows[ 'fee_' . $fee->get_id() ] = array(
'label' => $fee->get_name() . ':',
'value' => wc_price( 'excl' === $tax_display ? $fee->get_total() : $fee->get_total() + $fee->get_total_tax(), array( 'currency' => $order->get_currency() ) ),
);
}
}
}
// Display sorted fees by name everywhere on orders and emails
add_filter( 'woocommerce_get_order_item_totals', 'display_date_custom_field_value_on_order_item_totals', 10, 3 );
function display_date_custom_field_value_on_order_item_totals( $total_rows, $order, $tax_display ){
// Initializing variables
$new_total_rows = $item_fees = [];
// 1st Loop - Check for fees
foreach ( $total_rows as $key => $values ) {
if( strpos($key, 'fee_') !== false ) {
$item_fees[] = $key;
}
}
if( count($item_fees) > 1 ) {
// 2nd Loop - Remove item fees total lines
foreach ( $item_fees as $key ) {
unset($total_rows[$key]);
}
$key_start = isset($total_rows['shipping']) ? 'shipping' : ( isset($total_rows['discount']) ? 'discount' : 'cart_subtotal' );
// 3rd Loop - loop through order total rows
foreach( $total_rows as $key => $values ) {
$new_total_rows[$key] = $values;
// Re-inserting sorted fees
if( $key === $key_start ) {
wc_get_sorted_order_item_totals_fee_rows( $new_total_rows, $tax_display, $order );
}
}
return $new_total_rows;
}
return $total_rows;
}
Code goes in functions.php file of your active child theme (or active theme). Tested and works.
This question already has an answer here:
Woocomerce Remove a specific cart items when adding to cart another specific items
(1 answer)
Closed 2 years ago.
I have setup a WooCommerce shop for sell Ticket for a Dancing Workshop. Everthing is ok, but i will delete Tickets from cart if somebody puts 2 Tickets for Workshops that are at the same time.
Example: I have made 10 Tickets for the courses.
5 for Trainer A with Name A1 - A5
5 for Trainer B with Name B1 - B5.
Now when somebody add A1 to cart he cannot use the same Time Course B1.
I use the following code, this only works for 1 product
add_action( 'woocommerce_add_to_cart', 'check_product_added_to_cart', 10, 6 );
function check_product_added_to_cart($cart_item_key, $product_id, $quantity, $variation_id, $variation, $cart_item_data) {
// Set HERE your targeted product ID
$target_product_id = 31;
// Set HERE the product ID to remove
$item_id_to_remove = 37;
// Initialising some variables
$has_item = false;
$is_product_id = false;
foreach( WC()->cart->get_cart() as $key => $item ){
// Check if the item to remove is in cart
if( $item['product_id'] == $item_id_to_remove ){
$has_item = true;
$key_to_remove = $key;
}
// Check if we add to cart the targeted product ID
if( $product_id == $target_product_id ){
$is_product_id = true;
}
}
if( $has_item && $is_product_id ){
WC()->cart->remove_cart_item($key_to_remove);
// Optionaly displaying a notice for the removed item:
wc_add_notice( __( 'The product "blab bla" has been removed from cart.', 'theme_domain' ), 'notice' );
}
}
Someone who can explain how this code should be modified?
Following code works with corresponding products, if one product id is present, then the corresponding product id is removed from cart.
UPDATE: 12/20 - improved code
function action_woocommerce_add_to_cart( $cart_item_key, $product_id, $quantity, $variation_id, $variation, $cart_item_data ) {
// (USE: PRODUCT_IDs) - the corresponding product id's
// Example: if product with ID 30 is added and product with ID 32 is in cart, product with ID 32 will be removed from cart, this also works the opposite
$associated_product_ids_arrays = array(
array(
'product_id_1' => '30',
'product_id_2' => '32',
),
array(
'product_id_1' => '817',
'product_id_2' => '819',
),
array(
'product_id_1' => '827',
'product_id_2' => '843',
),
);
// check
$found = false;
// Loop trough arrays
foreach ( $associated_product_ids_arrays as $key_1 => $associated_product_ids_array ) {
foreach ( $associated_product_ids_array as $key_2 => $associated_product_ids ) {
// Compare
if ( $associated_product_ids == $product_id ) {
// Unset
unset( $associated_product_ids_array[$key_2] );
// Convert last value in array to integer
$associated_ticket_id = (int) implode('', $associated_product_ids_array );
// Found = true, break 2 loops
$found = true;
break 2;
}
}
}
// True
if ( $found ) {
// Generate a unique ID for the cart item
$product_cart_id = WC()->cart->generate_cart_id( $associated_ticket_id );
// Check if product is in the cart and return cart item key
$item_key = WC()->cart->find_product_in_cart( $product_cart_id );
// if item_key true
if ( $item_key ) {
// remove product from cart
WC()->cart->remove_cart_item( $item_key );
// Optionaly displaying a notice
wc_add_notice( sprintf( __( 'The product %d has been removed from cart because product %d has been added.', 'woocommerce' ), $associated_ticket_id, $product_id ), 'notice' );
}
}
}
add_action( 'woocommerce_add_to_cart', 'action_woocommerce_add_to_cart', 10, 6 );
Answer from 03/20
function product_to_remove( $cart_item_key, $product_id, $quantity, $variation_id, $variation, $cart_item_data ) {
// (USE: PRODUCT_IDs) - the corresponding ticket id's
$associated_ticket_ids_1 = array( 30, 32 );
$associated_ticket_ids_2 = array( 817, 819 );
$associated_ticket_ids_3 = array( 827, 843 );
// etc...
// total of associated ticket ids arrays, total = number of different $associated_ticket_ids arrays, in this example: 3
$total = 3;
// check
$found = false;
// loop through the arrays
for ($i = 1; $i <= $total; $i++) {
$array_name = ${'associated_ticket_ids_' . $i};
foreach ($array_name as $ticket_id ) {
if ( $ticket_id == $product_id ) {
/* Get the associated ticket id */
// Search value and unset
unset( $array_name[array_search( $ticket_id, $array_name )] );
// Convert last value in array to integer
$associated_ticket_id = (int) implode('', $array_name);
// if found, break loops
$found = true;
break 2;
/* uncomment line below for debug purposes */
//echo 'Product id: ' . $product_id . ' | Ticket id: ' . $ticket_id . ' | Associated ticket id: ' . $associated_ticket_id;
}
}
}
// if found true
if ( $found ) {
// Generate a unique ID for the cart item
$product_cart_id = WC()->cart->generate_cart_id( $associated_ticket_id );
// Check if product is in the cart and return cart item key
$item_key = WC()->cart->find_product_in_cart( $product_cart_id );
// if item_key true
if ( $item_key ) {
// remove product from cart
WC()->cart->remove_cart_item( $item_key );
// Optionaly displaying a notice
wc_add_notice( __( 'The product ' . $associated_ticket_id . ' has been removed from cart because product ' . $product_id . ' has been added.', 'woocommerce' ), 'notice' );
}
}
}
add_action( 'woocommerce_add_to_cart', 'product_to_remove', 10, 6 );
When I try to get my order items for an order via my functions.php I'm getting this issue here:
Uncaught Error: Call to undefined method WC_Order::get_order_items()
This is my code (I can't find the problem):
add_filter( 'wp_nav_menu_objects', 'set_navigation_user_name' );
function set_navigation_user_name( $menu_items ) {
//Get current user
$current_user = wp_get_current_user();
foreach ( $menu_items as $menu_item ) {
if ( '{user_name}' === $menu_item->title ) {
//Get first and lastname from current user
$user_firstname = $current_user->user_firstname;
$user_lastname = $current_user->user_lastname;
$menu_item->title = $user_firstname . ' ' . $user_lastname;
} elseif ( '{available_pay}' === $menu_item->title ) {
$available_pay = 0;
$order_states = array(
'wc-completed',
'wc-pending'
);
$orders = wc_get_orders( array(
'numberposts' => - 1,
'meta_key' => '_customer_user',
'meta_value' => get_current_user_id(),
'post_status' => $order_states
) );
foreach ( $orders as $order ) {
if ( count( $order->get_order_items() ) > 0 ) {
foreach ( $order->get_order_items() as $item_id => $item ) {
//Order pay
$order_pay = wc_get_order_item_meta( $item_id, '_line_total', true );
//Add order pay to available pay
$available_pay += $order_pay;
}
}
}
$menu_item->title = 'Order pay sum: ' . wc_price( $available_pay );
}
}
return $menu_items;
}
You need to use instead the WC_Order method get_items()…
as WC_Order get_order_items() method doesn't exist for Woocommerce…
Also since Woocommerce 3 you can use WC_Order_Item_Product get_total() method instead of wc_get_order_item_meta( $item_id, '_line_total', true );
So inside your code you will change the following:
foreach ( $orders as $order ) {
if ( count( $order->get_items() ) > 0 ) {
foreach ( $order->get_items() as $item_id => $item ) {
// Add order pay to available pay
$available_pay += $item->get_total();
}
}
}
Related threads:
Get Order items and WC_Order_Item_Product in Woocommerce 3
How to get WooCommerce order details