I'm trying to create a shortcode for a single product page that shows the average sale price of the current product. So the shortcode will be in a description of a product page.
This value should work both with simple and variable products.
I'm able to get average value for simple product but only when there is one item in order and I'm not able to solve this with variable products.
function avg_sales_price () {
global $product;
if ( is_a($product, 'WC_Product') ) {
$product_id = $product->get_id();
$orders = wc_get_orders( array(
'numberposts' => -1,
'post_type' => 'shop_order',
) );
foreach ( $orders as $order ) {
if ( count( $order->get_items() ) > 0 ) {
foreach ( $order->get_items() as $item_id => $item )
$productid = $item->get_variation_id() ? $item->get_variation_id() : $item->get_product_id();
if ( $productid == $product_id) {
$product_price_in_orders += $item->get_total();
}
}
}
$count_orders = 0;
foreach ( $orders as $order ) {
$has_product = false;
foreach ( $order->get_items() as $item_values )
if ( $item_values['product_id'] == $product_id )
$has_product = true;
if ( $has_product )
$count_orders++;
}
$avg_sales_price = round( $product_price_in_orders / $count_orders );
echo '<div class="sales-price-number">
<span class="sales-price-text">Average Sale Price </span>
<span class="sales-price-value">' . esc_html( get_woocommerce_currency_symbol() . $avg_sales_price ). ' </span>
</div>';
}
}
Any ideas how to get this work?
Thanks.
To get orders based on current product you shouldn't have to query all orders every time. It takes up too many resources especially if the orders are many.
From Get all Orders IDs from a product ID in Woocommerce answer code you can use the function get_orders_ids_by_product_id() to get all orders from a specific product Id (where you will have to set the desired order statuses).
Then you can use this other function to calculate the average price of each product (simple or variation) based on the product id.
// calculate the average price based on the product id using the query within the "get_orders_ids_by_product_id()" function
function calculates_average_price_based_on_product_id( $product_id ) {
$orders = get_orders_ids_by_product_id( $product_id );
$count = count( $orders );
if ( $count <= 0 ) {
return; // set the return value in case there are no orders for this product
}
$prices = 0;
foreach ( $orders as $order_id ) {
$order = wc_get_order( $order_id );
if ( $order ) {
foreach ( $order->get_items() as $item ) {
$product = $item->get_product();
if ( $product_id == $product->get_id() ) {
$qty = $item->get_quantity();
$total = $item->get_total();
$prices += $total / $qty;
}
}
}
}
return $prices / $count;
}
HOW DOES IT WORK
If the product is variable:
Calculate the average price ONLY between the product variations that have been purchased
If no variation has been purchased, it calculates the average price between the net prices currently set
If the product is simple:
Calculate the average price based on the selling prices across all orders containing this product
If the product has never been purchased, it displays the currently set net price
SHORTCODE
Finally here is the shortcode which will show the average price based on the product of the current page. If the product is variable, the average price between all variations will be calculated and shown.
The [average_price_product] shortcode will only work on the product page.
// create a shortcode showing the average price of the product
add_shortcode( 'average_price_product', 'shortcode_average_price_product' );
function shortcode_average_price_product() {
global $product;
// initialize prices
$avg_price = 0;
$current_avg_price = 0;
// initializes the number of variations that have been purchased at least once
$count = 0;
// if the product is variable it calculates the average price of all the variations
if ( $product->is_type( 'variable' ) ) {
$variation_ids = $product->get_children();
// gets the total number of variations
$total_variations = count( $variation_ids );
foreach ( $variation_ids as $variation_id ) {
$variation = wc_get_product( $variation_id );
// gets the average price of each single variation
$avg_price_variation = calculates_average_price_based_on_product_id( $variation_id );
// if the average price of the current product variation is greater than zero, it updates the price and count
if ( $avg_price_variation > 0 ) {
$avg_price += $avg_price_variation;
$count++;
}
// sum the current price of each single variation (in case no variation has ever been purchased)
$current_avg_price += $variation->get_price();
}
// if no variation has been purchased, I display the average price between the current net prices
if ( $avg_price == 0 ) {
$avg_price = $current_avg_price / $total_variations;
} else {
$avg_price /= $count;
}
// if the product is simple
} else {
// gets the average price of the product
$avg_price = calculates_average_price_based_on_product_id( $product->get_id() );
// if the simple product has never been ordered, I will display the current price
if ( $avg_price == 0 ) {
$avg_price = $product->get_price();
}
}
// creates the HTML content to display on the product page
$html = '<div class="sales-price-number"><span class="sales-price-text">Average Sale Price </span><span class="sales-price-value">' . wc_price( $avg_price ) . ' </span></div>';
return $html;
}
HOOK
As an alternative to the shortcode you can add the average price on the product page using the woocommerce_before_add_to_cart_form hook, with the following function:
// shows the average price on the product page
add_action( 'woocommerce_before_add_to_cart_form', 'shows_average_price_on_the_product_page', 10 );
function shows_average_price_on_the_product_page() {
global $product;
// initialize prices
$avg_price = 0;
$current_avg_price = 0;
// initializes the number of variations that have been purchased at least once
$count = 0;
// if the product is variable it calculates the average price of all the variations
if ( $product->is_type( 'variable' ) ) {
$variation_ids = $product->get_children();
// gets the total number of variations
$total_variations = count( $variation_ids );
foreach ( $variation_ids as $variation_id ) {
$variation = wc_get_product( $variation_id );
// gets the average price of each single variation
$avg_price_variation = calculates_average_price_based_on_product_id( $variation_id );
// if the average price of the current product variation is greater than zero, it updates the price and count
if ( $avg_price_variation > 0 ) {
$avg_price += $avg_price_variation;
$count++;
}
// sum the current price of each single variation (in case no variation has ever been purchased)
$current_avg_price += $variation->get_price();
}
// if no variation has been purchased, I display the average price between the current net prices
if ( $avg_price == 0 ) {
$avg_price = $current_avg_price / $total_variations;
} else {
$avg_price /= $count;
}
// if the product is simple
} else {
// gets the average price of the product
$avg_price = calculates_average_price_based_on_product_id( $product->get_id() );
// if the simple product has never been ordered, I will display the current price
if ( $avg_price == 0 ) {
$avg_price = $product->get_price();
}
}
// creates the HTML content to display on the product page
$html = '<div class="sales-price-number"><span class="sales-price-text">Average Sale Price </span><span class="sales-price-value">' . wc_price( $avg_price ) . ' </span></div>';
echo $html;
}
The code has been tested and works. Add it to your active theme's functions.php.
Advice
Although the code above works correctly this is not the best way to get the average price of the product.
You could create and update a custom meta for each product whenever the status of each order changes, for example, to "wc-completed".
You will only need a counter of the number of orders for that product and the average price calculated based on the last completed order.
When a new order goes to the completed status you will update the respective fields.
Related
I already found this code here and it works for like 80/90% for me (see below).
This code adds 60 euro to my cart when there is a product from category ID 349 in the cart. When I add a product from that category to my cart when the cart is empty it works fine. But when there is already a product in my cart from a different category and then I add the product with category 349 it doesn't add the 60 euro extra fee. How is this possible?
function woo_add_cart_fee() {
$category_ID = '349';
global $woocommerce;
foreach ($woocommerce->cart->cart_contents as $key => $values ) {
// Get the terms, i.e. category list using the ID of the product
$terms = get_the_terms( $values['product_id'], 'product_cat' );
// Because a product can have multiple categories, we need to iterate through the list of the products category for a match
foreach ($terms as $term) {
// 349 is the ID of the category for which we want to remove the payment gateway
if($term->term_id == $category_ID){
$excost = 60;
}
}
$woocommerce->cart->add_fee('Extra bezorgkosten kunstgras', $excost, $taxable = false, $tax_class = '');
}
}
add_action( 'woocommerce_cart_calculate_fees', 'woo_add_cart_fee' );
The code you are using is a bit outdated and you should use has_term() Wordpress conditional function to target a product category this way:
add_action( 'woocommerce_cart_calculate_fees','custom_pcat_fee', 20, 1 );
function custom_pcat_fee( $cart ) {
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return;
// Set HERE your categories (can be term IDs, slugs or names) in a coma separated array
$categories = array('349');
$fee_amount = 0;
// Loop through cart items
foreach( $cart->get_cart() as $cart_item ){
if( has_term( $categories, 'product_cat', $cart_item['product_id']) )
$fee_amount = 60;
}
// Adding the fee
if ( $fee_amount > 0 ){
// Last argument is related to enable tax (true or false)
WC()->cart->add_fee( __( "Extra bezorgkosten kunstgras", "woocommerce" ), $fee_amount, false );
}
}
Code goes in function.php file of your active child theme (or active theme). Tested and works.
A fee of 60 will always be added if there is in cart an item from 349 product category ID.
I found a solution like the following. Note that I kept adding a fee, because I started from a previous snippet. You can apply an existing discount or calculate a different total according to your needs.
In this case, I apply a negative fee - like a discount - according to a >= 300 total value of the cart, whose values would be -25% only for local_pickup shipping method, calculating taxes in addition.
function discount_to_cat(){
// thanks to LoicTheAztec's snippet
$cat_in_cart = false;
// Loop through all products in the Cart
foreach ( WC()->cart->get_cart() as $cart_item ) {
if ( has_term( 'category_to_pick', 'product_cat', $cart_item['product_id'] ) ) {
$cat_in_cart = true;
$tot_category_price = $cart_item['data']->get_price();
$tot_category_qty = $cart_item['quantity'];
$tot_category = $tot_category_price * $tot_category_qty;
break;
}
} global $product;
$total = WC()->cart->subtotal;
$discount_label = "";
if($total >= 300){
$discount_label=15;
}
$chosen_methods = WC()->session->get( 'chosen_shipping_methods' );
$chosen_shipping = explode(':',$chosen_methods[0]);
if($chosen_shipping[0]=='local_pickup'){
$discount_label=25;
}
$discount_applied = ($total-$tot_category)*$discount_label/100;
if($discount_label!=""){
$discount_applied_net = ($discount_applied/1.1); //1.1 according taxes for shipping
WC()->cart->add_fee( "Discount applied: ($discount_label%)", -$discount_applied_net, false );
}
}
add_action( 'woocommerce_cart_calculate_fees','discount_to_cat' );
I'm building a website where i'll sell some fine art prints and i would like to set an automated discount when a customer buy the first print of a serie. Each of my prints are limited to 15 pieces and i would like to set a 30% discount for the first sell (#1/15) of each series. Then, for the next prints (#2 to #15), price goes back to normal.
Edit :
I did progress on my problem ! I did some research and tried a lot of different things and i found how to set the custom price i wanted on my product page (regular price + discount price) with the rule to only apply it if the related item available stock is 15.
regular price + discount price
And here's the code is used :
// Generating the product "regular price"
add_filter( 'woocommerce_product_get_regular_price', 'dynamic_regular_price', 10, 2 );
add_filter( 'woocommerce_product_variation_get_regular_price', 'dynamic_regular_price', 10, 2 );
function dynamic_regular_price( $regular_price, $product ) {
$stock_qte = $product->get_stock_quantity();
if( empty($regular_price) || $regular_price == 0 )
return $product->get_price();
else
return $regular_price;
}
// Generating the product "sale price"
add_filter( 'woocommerce_product_get_sale_price', 'dynamic_sale_price', 10, 2 );
add_filter( 'woocommerce_product_variation_get_sale_price', 'dynamic_sale_price', 10, 2 );
function dynamic_sale_price( $sale_price, $product ) {
$stock_qte = $product->get_stock_quantity();
if( $stock_qte == '15')
return $product-> get_regular_price() * 0.7;
else
//return $product->get_regular_price();
return $regular_price;
};
// Displayed formatted regular price + sale price
add_filter( 'woocommerce_get_price_html', 'dynamic_sale_price_html', 20, 2 );
function dynamic_sale_price_html( $price_html, $product ) {
$stock_qte = $product->get_stock_quantity();
if( $stock_qte == '15')
$price_html = wc_format_sale_price( wc_get_price_to_display( $product, array( 'price' => $product->get_regular_price() ) ), wc_get_price_to_display( $product, array( 'price' => $product->get_sale_price() ) ) ) . $product->get_price_suffix();
else
$price_html = wc_price(wc_get_price_to_display( $product, array( 'price' => $product->get_regular_price() ) ). $product->get_price_suffix());
return $price_html;
}
It's almost working but the code related to the cart values updates is not fully working. The cart sub-total is good and take count of the discount but the individual price of each product is not updated and shown as without discount. It's the same on the cart page.
cart (from mini cart)
cart (cart page)
add_action( 'woocommerce_before_calculate_totals', 'alter_price_cart', 9999 );
function alter_price_cart( $cart ) {
if ( is_admin() && ! defined( 'DOING_AJAX' ) ) return;
if ( did_action( 'woocommerce_before_calculate_totals' ) >= 2 ) return;
// IF CUSTOMER NOT LOGGED IN, DONT APPLY DISCOUNT
//if ( ! wc_current_user_has_role( 'customer' ) ) return;
// LOOP THROUGH CART ITEMS & APPLY 30% DISCOUNT
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.7 );
}
}
I'll also have to add the stock availability logic for the mini-cart / cart / checkout page but i'll do that in a second time.
I must have made some mistakes in the loop throught the cart but i cannot catch the issue :( Any idea about how to hook the price of each product and change the displayed value ?
Have a nice day,
Quentin
I want to split cart items of same product in individual lines. When I increase quantity on single product page and add to cart, it shows as separate cart items.
Im using WooCommerce - Treat cart items separate if quantity is more than 1 answer code.
I want when quantity is updated on cart page and clicked on 'Update cart', then items must split on individual lines.
How can I do that?
The code below will split items on individual lines when quantity is updated on the cart page.
Comment with explanation added to the code.
function on_action_cart_updated( $cart_updated ) {
if ( $cart_updated ) {
// Get cart
$cart_items = WC()->cart->get_cart();
foreach ( $cart_items as $cart_item_key => $cart_item ) {
$quantity = $cart_item['quantity'];
// If product has more than 1 quantity
if ( $quantity > 1 ) {
// Keep the product but set its quantity to 1
WC()->cart->set_quantity( $cart_item_key, 1 );
// Run a loop 1 less than the total quantity
for ( $j = 1; $j <= $quantity -1; $j++ ) {
// Set a unique key.
$cart_item['unique_key'] = md5( microtime() . rand() . "Hi Mom!" );
// Get vars
$product_id = $cart_item['product_id'];
$variation_id = $cart_item['variation_id'];
$variation = $cart_item['variation'];
// Add the product as a new line item with the same variations that were passed
WC()->cart->add_to_cart( $product_id, 1, $variation_id, $variation, $cart_item );
}
}
}
}
}
add_action( 'woocommerce_update_cart_action_cart_updated', 'on_action_cart_updated', 20, 1 );
I used this code to filter the products added to the cart of a certain category (id = 70).
My goal is to count the number of step quantities added to the cart of a product belonging to the '70' category.
Example:
Minimum quantity: 0
Step: 100
I added the product to the cart with 300 quantities.
So the step number is 3.
Is there a way to get the number of step quantities of a product added to the cart?
// Utility function that count specific product category cart items
add_shortcode('palletcompleto', 'cat_cart_count');
function cat_cart_count( $term_ids ) {
$quantity = 0; // Initializing
// Loop through cart items
foreach ( WC()->cart->get_cart() as $cart_item ) {
if ( has_product_category( $cart_item['product_id'], array( 70 ) ) ) {
$quantity += $cart_item['step_quantity'];
}
}
// Returning category count
return $quantity == 0 ? false : $quantity;
}
I am using some code like has_product_category() function from this thread:
Set item quantity to multiples of “x” for products in a specific category in Woocommerce
// Custom conditional function that checks also for parent product categories
function has_product_category( $product_id, $category_ids ) {
$term_ids = array(); // Initializing
// Loop through the current product category terms to get only parent main category term
foreach( get_the_terms( $product_id, 'product_cat' ) as $term ){
if( $term->parent > 0 ){
$term_ids[] = $term->parent; // Set the parent product category
$term_ids[] = $term->term_id;
} else {
$term_ids[] = $term->term_id;
}
}
return array_intersect( $category_ids, array_unique($term_ids) );
}
I have seen several posts relating to adding a service fee to a WooCommerce item based on Category. Tickets in my case. I have had success with several of the codes presented. However In all cases Sales Tax was not applied to the new sub total. I'm stuck trying to make that happen. So with the code below I am not taxing the up charge resulting in under collection of taxes.
Here is the code I'm currently using:
/* Service fee for tickets */
function df_add_ticket_surcharge( $cart_object ) {
global $woocommerce;
$specialfeecat = 23; // category id for the special fee
$spfee = 0.00; // initialize special fee
$spfeeperprod = 0.214; //special fee per product
foreach ( $cart_object->cart_contents as $key => $value ) {
$proid = $value['product_id']; //get the product id from cart
$quantiy = $value['quantity']; //get quantity from cart
$itmprice = $value['data']->price; //get product price
$terms = get_the_terms( $proid, 'product_cat' ); //get taxonamy of the prducts
if ( $terms && ! is_wp_error( $terms ) ) :
foreach ( $terms as $term ) {
$catid = $term->term_id;
if($specialfeecat == $catid ) {
$spfee = $spfee + $itmprice * $quantiy * $spfeeperprod;
}
}
endif;
}
if($spfee > 0 ) {
$woocommerce->cart->add_fee( 'Service Fee', $spfee, true, $taxable = true, $tax_class = 'standard' );
}