Prevent date_modified changes when reducing stock - php

I have written a script that, among other things, shows me how many days ago a product was updated/modified. Everything works perfectly, however it has become evident to me that get_date_modified() is returning the date/time of the last time the product was ordered.
I have a custom script that is reducing stock based on an attribute. I believe this is the culprit. I would like date_modified not to update on stock change via purchase, only when the product is manually updated.
How can I bypass the updating of date_modified when this function fires? Is there a filter that can be utilized perhaps?
Stock reduction code:
// ---------------------------------------------------------------------------------------- //
// -------------------------- REDUCE WEIGHT BASED ON WEIGHT FIELD ----------------------- //
// ---------------------------------------------------------------------------------------- //
// reduce stock based on 'pa_weight' attribute
add_filter( 'woocommerce_order_item_quantity', 'filter_order_item_quantity', 10, 3 );
function filter_order_item_quantity( $quantity, $order, $item )
{
$product = $item->get_product();
$term_name = $product->get_attribute('pa_weight');
// 'pa_weight' - keep only the numbers
$quantity = preg_replace('/[^0-9.]+/', '', $term_name);
// new quantity
if( is_numeric ( $quantity ) && $quantity != 0 )
$quantity *= $quantity;
return $quantity;
}
/**
* Order change stock reduction fix.
*
* #param $prevent
* #param $item
* #param $item_quantity
* #return bool|mixed
*/
function custom_stock_reduction_order_change( $prevent, $item, $item_quantity ) {
$stock_reduced = wc_stock_amount( $item->get_meta( '_reduced_stock', true ) );
$stock_reduction = filter_order_item_quantity( $item->get_quantity(), null, $item );
if ( $stock_reduction == $stock_reduced ) {
$prevent = true;
}
return $prevent;
}
add_filter( 'woocommerce_prevent_adjust_line_item_product_stock', 'custom_stock_reduction_order_change', 10, 3 );

Related

How do I display the price of selected bundle products instead of the highest possible price?

I have a bundle product consisting of 6 products. Some of these are variable products with a default value picked. The price is correctly shown on the product page but on the category page (shop loop) the price shows the maximum price available and not the actual price of the default selection.
Since the price is correctly shown on the product page it should be possible to do the same on the shop loop?
I've recorded a short video of the issue: https://www.awesomescreenshot.com/video/10704837?key=291f103a39650becb7f185694534246c
here's the code that makes it show the max price:
/**
* 'woocommerce_bundle_force_old_style_price_html' filter.
*
* Used to suppress the range-style display of bundle price html strings.
*
* #param boolean $force_suppress_range_format
* #param WC_Product_Bundle $this
*/
if ( $suppress_range_price_html || apply_filters( 'woocommerce_bundle_force_old_style_price_html', false, $this ) ) {
$price = wc_price( $price_max );
$regular_price_min = $this->get_bundle_regular_price( 'max', true );
if ( $regular_price_min !== $price_max ) {
$regular_price = wc_price( $regular_price_max );
if ( $price_min !== $price_max ) {
$price = sprintf( _x( '%1$s%2$s', 'Price range: from', 'woocommerce-product-bundles' ), wc_get_price_html_from_text(), wc_format_sale_price( $regular_price, $price ) . $this->get_price_suffix() );
} else {
$price = wc_format_sale_price( $regular_price, $price ) . $this->get_price_suffix();
}

Limit maximum quantity of free products in Woocommerce cart

My client has asked for the ability to offer free samples of their variable products on their Woocommerce online store, with a maximum of 1 of each unique product sample and 5 of any of the samples on their order.
I’ve achieved part of this by adding an extra “Free Sample” variation to each product and then using a free Min/Max quantity plugin to limit the amount of each individual free sample to 1 per order, please see the following screenshots:
https://ibb.co/f0Wd7XC
https://ibb.co/8DrsZZj
So far I haven’t established a way to limit the maximum number of any combination of the “Free Sample” variations to 5 though. The only way I can see is by limiting the total number of free products (i.e. price = £0) per order to 5, or alternatively by assigning a specific shipping class to each variation (i.e.”Free Samples”) and then somehow limiting the amount of products assigned with this shipping class in each order to 5. Is this possible?
Cheers,
M.
Woocommerce have validation hook which you can use.
First one is when we add to our cart
function add_to_cart_free_samples($valid, $product_id, $quantity) {
$max_allowed = 5;
$current_cart_count = WC()->cart->get_cart_contents_count();
foreach (WC()->cart->get_cart() as $cart_item_key=>$cart_item ){
// Here change attribute group if needed - currently assigned to default size attribute
$variation = $cart_item['variation']['attribute_pa_size'];
}
if( ( $current_cart_count > $max_allowed || $current_cart_count + $quantity > $max_allowed ) && $variation === 'free-sample' && $valid ){
wc_add_notice( sprintf( __( 'Whoa hold up. You can only have %d items in your cart', 'your-textdomain' ), $max_allowed ), 'error' );
$valid = false;
}
return $valid;
}
add_filter( 'woocommerce_add_to_cart_validation', 'add_to_cart_free_samples', 10, 3 );
Second one is when we update the cart on cart page for example.
function update_add_to_cart_free_samples( $passed, $cart_item_key, $values, $updated_quantity ) {
$cart_items_count = WC()->cart->get_cart_contents_count();
$original_quantity = $values['quantity'];
$max_allowed = 5;
$total_count = $cart_items_count - $original_quantity + $updated_quantity;
foreach (WC()->cart->get_cart() as $cart_item_key=>$cart_item ){
// Here change attribute group if needed - currently assigned to default size attribute
$variation = $cart_item['variation']['attribute_pa_size'];
}
if( $cart_items_count > $max_allowed && $variation === 'free-sample' ){
$passed = false;
wc_add_notice( sprintf( __( 'Whoa hold up. You can only have %d items in your cart', 'your-textdomain' ), $max_allowed ), 'error' );
}
return $passed;
}
add_filter( 'woocommerce_update_cart_validation', 'update_add_to_cart_free_samples', 10, 4 );
In $cart_item you can debug and see all info for the current product in the cart. From there you can add condition depending on shipping or attribue or price or w/e you want.
This is what you are looking for (NO PLUGIN NEEDED):
In the theme's function.php add the following:
/*----------------------------------------------------------
* Adds custom field to the inventory tab in the product data meta box
* ---------------------------------------------------------- */
function action_woocommerce_product_options_stock_status() {
woocommerce_wp_text_input(
array(
'id' => '_max_qty',
'placeholder' => __( 'Set the bundle maximum to at least 2 items or leave blank', 'woocommerce' ),
'label' => __( 'Maximum Bundle', 'woocommerce' ),
'desc_tip' => true,
'description' => __( 'Limits the maximum amount of product bundles your customer can order', 'woocommerce' ),
'type' => 'number',
'custom_attributes' => array(
'step' => 'any',
),
)
);
}
add_action( 'woocommerce_product_options_stock_status', 'action_woocommerce_product_options_stock_status', 10, 0 );
/* ----------------------------------------------------------
* Save custom field to the inventory tab in the product data meta box
* ---------------------------------------------------------- */
function action_woocommerce_admin_process_product_object( $product ) {
// Isset
if ( isset( $_POST['_max_qty'] ) ) {
// Update
$product->update_meta_data( '_max_qty', sanitize_text_field( $_POST['_max_qty'] ) );
}
}
add_action( 'woocommerce_admin_process_product_object', 'action_woocommerce_admin_process_product_object', 10, 1 );
function get_cart_quantity_variable_product( $child_ids ) {
// Get cart items quantities
$cart_item_quantities = WC()->cart->get_cart_item_quantities();
// Counter
$qty = 0;
// Loop through the childIDs
foreach ( $child_ids as $child_id ) {
// Checks if the given key or index exists in the array
if ( array_key_exists( $child_id, $cart_item_quantities ) ) {
// Addition
$qty += $cart_item_quantities[$child_id];
}
}
return $qty;
}
function action_woocommerce_check_cart_items() {
// Will increment
$i = 0;
// Will hold information about products that have not met the minimum order quantity
$bad_products = array();
// Will hold information about which product ID has already been checked so that this does not happen twice (especially applies to products with variants)
$already_checked = array();
// Loop through cart items
foreach( WC()->cart->get_cart() as $cart_item ) {
// Get IDs
$product_id = $cart_item['product_id'];
$variation_id = $cart_item['variation_id'];
// NOT in array, already checked? Continue
if ( ! in_array( $product_id, $already_checked ) ) {
// Push to array
$already_checked[] = $product_id;
// Get the parent variable product for product variation items
$product = $variation_id > 0 ? wc_get_product( $product_id ) : $cart_item['data'];
// Get meta
$max_qty = $product->get_meta( '_max_qty', true );
// NOT empty & minimum quantity is lesser than or equal to 5 (1 never needs to be checked)
if ( ! empty( $max_qty ) && $max_qty <= 5 ) {
// Get current quantity in cart
$cart_qty = $cart_item['quantity'];
// Product type = variable & cart quantity is less than the maximum quantity
if ( $product->get_type() == 'variable' && ( $cart_qty < $max_qty ) ) {
// Get childIDs in an array
$child_ids = $product->get_children();
// Call function, get total quantity in cart for a variable product
$cart_qty = get_cart_quantity_variable_product( $child_ids );
}
// Cart quantity is less than the maximum quantity
if ( $cart_qty > $max_qty ) {
// The product ID
$bad_products[$i]['product_id'] = $product_id;
// The variation ID (optional)
//$bad_products[$i]['variation_id'] = $variation_id;
// The Product quantity already in the cart for this product
$bad_products[$i]['in_cart'] = $cart_qty;
// Get the maximum required for this product
$bad_products[$i]['max_req'] = $max_qty;
// Increment $i
$i++;
}
}
}
}
// Time to build our error message to inform the customer, about the maximum quantity per order.
if ( is_array( $bad_products) && count( $bad_products ) < 5 ) {
// Clear all other notices
wc_clear_notices();
foreach( $bad_products as $bad_product ) {
// Displaying an error notice
wc_add_notice( sprintf(
__( '%s has a maximum quantity of %d. You currently have %d in cart', 'woocommerce' ),
get_the_title( $bad_product['product_id'] ),
$bad_product['max_req'],
$bad_product['in_cart'],
), '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 );
Credit goes to 7uc1f3r for the solution on: Force minimum order quantity for specific products including variations on WooCommerce cart page
I only added and changed some code to fit it for the maximum bundle. This works for free samples stuff and you can also use it for any product the shop owner wants to limit to a set amount of bundle, as this isn't hardcoding the limit to a certain product.
Here is an example of this working:

Add random string to WooCommerce order items based on product SKU

I have set up variable products within WooCommerce.
Each variation has a unique SKU. My mission is to inspect the SKU within the cart, then according to the SKU, generate a number of random strings (16alphanumeric characters).
If item (1) in the cart is SKU 'ABC', then (x) number of unique strings are generated and stored for the respective order.
If item (2) in the cart is SKU 'DEF', then (y) number of unique strings are generated and stored for the respective order.
I have had a look at woocommerce_add_order_item_meta hook but I can't seem to be able to access the item product data within the function I create associated with this hook?
I am now using
add_action( 'woocommerce_add_order_item_meta', 'experiment_add_order_item_meta', 10, 3 );
function experiment_add_order_item_meta( $item_id, $values, $cart_item_key ) {
// Get a product custom field value
$custom_field_value = 'hithere';
// Update order item meta
if ( ! empty( $custom_field_value ) ){
wc_add_order_item_meta( $item_id, 'meta_key1', $custom_field_value, false );
}
}
But I am lost trying to get the product SKU from this point. I cannot var_dump() within the function to see the $values etc?
Explanation / functions used
woocommerce_add_order_item_meta hook is deprecated since WooCommerce 3. Use woocommerce_checkout_create_order_line_item instead
WC_Product::get_sku( $context ); - Get SKU (Stock-keeping unit) – product unique ID.
See PHP random string generator for different options. I used https://stackoverflow.com/a/13212994/11987538 anser code.
So you get:
function generate_random_string( $length = 16 ) {
return substr( str_shuffle( str_repeat( $x = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', ceil( $length / strlen( $x ) ) ) ), 1, $length );
}
function action_woocommerce_checkout_create_order_line_item( $item, $cart_item_key, $values, $order ) {
// The WC_Product instance Object
$product = $item->get_product();
// Get product SKU
$product_sku = $product->get_sku();
// Generate random string
$random_string = generate_random_string();
// Compare
if ( $product_sku == 'ABC' ) {
$item->update_meta_data( '_random_string', $random_string );
} elseif ( $product_sku == 'DEF' ) {
$item->update_meta_data( '_random_string', $random_string );
} elseif ( $product_sku == 'GHI' ) {
$item->update_meta_data( '_random_string', $random_string );
} else {
$item->update_meta_data( '_random_string', $random_string );
}
}
add_action( 'woocommerce_checkout_create_order_line_item', 'action_woocommerce_checkout_create_order_line_item', 10, 4 );

One coupon with multiple discount percentages in WooCommerce

I am looking for a Woocommerce hook that will help to change the discount percentage based on 2 different product category restrictions when a specific coupon is applied.
For example if customer add a specific coupon I will like to:
If a cart item is from product category A then it will give 10% discount on that item.
if it's in product category B it will give 20% discount on that item
I've found this code but it's still get coupon discount from Coupons in WooCommerce.
add_filter( 'woocommerce_coupon_get_discount_amount', 'alter_shop_coupon_data', 20, 5 );
function alter_shop_coupon_data( $round, $discounting_amount, $cart_item, $single, $coupon ){
## ---- Your settings ---- ##
// Related coupons codes to be defined in this array (you can set many)
$coupon_codes = array('10percent');
// Product categories at 20% (IDs, Slugs or Names) for 20% of discount
$product_category20 = array('hoodies'); // for 20% discount
$second_percentage = 0.2; // 20 %
## ---- The code: Changing the percentage to 20% for specific a product category ---- ##
if ( $coupon->is_type('percent') && in_array( $coupon->get_code(), $coupon_codes ) ) {
if( has_term( $product_category20, 'product_cat', $cart_item['product_id'] ) ){
$original_coupon_amount = (float) $coupon->get_amount();
$discount = $original_coupon_amount * $second_percentage * $discounting_amount;
$round = round( min( $discount, $discounting_amount ), wc_get_rounding_precision() );
}
}
return $round;
}
This hook get's triggered of your cart get's updated. So maybe this is the correct hook. Try to fit your code within the foreach. If you need more help, let me know.
add_action( 'woocommerce_update_cart_action_cart_updated', 'on_action_cart_updated', 20, 1 );
function on_action_cart_updated( $cart_updated ) {
$applied_coupons = WC()->cart->get_applied_coupons();
if ( count( $applied_coupons ) > 0 ) {
foreach ( $applied_coupons as $coupon ) {
$round = .....
}
$cart_subtotal = WC()->cart->get_cart_subtotal();
$new_value = $cart_subtotal - $round;
WC()->cart->set_total( $new_value );
if ( $cart_updated ) {
// Recalc our totals
WC()->cart->calculate_totals();
}
}
}

About update product stock status function in WooCommerce 3

The deal is, I have to fix an error in a custom WooCommerce import plugin, which appeared after updating WC from 2.6 to 3.4.
It uses the 'wc_update_product_stock_status' function, and used to pass post (product) id and it's stock status as it is represented in DB ('instock' and 'outofstock', as a string). But nowadays, as I can see in the WooCommerce docs (https://docs.woocommerce.com/wc-apidocs/function-wc_update_product_stock_status.html) it accepts integer instead of string.
So, the question is - what are those integers for in/out of stock values (1/0 did not fit).
If you look to the source code in wc_update_product_stock_status() function:
/**
* Update a product's stock status.
*
* #param int $product_id
* #param int $status
*/
function wc_update_product_stock_status( $product_id, $status ) {
$product = wc_get_product( $product_id );
if ( $product ) {
$product->set_stock_status( $status );
$product->save();
}
}
It uses the WC_Product set_stock_status() Woocommerce 3 CRUD method which uses strings But not integers values:
/**
* Set stock status.
*
* #param string $status New status.
*/
public function set_stock_status( $status = 'instock' ) {
$valid_statuses = wc_get_product_stock_status_options();
if ( isset( $valid_statuses[ $status ] ) ) {
$this->set_prop( 'stock_status', $status );
} else {
$this->set_prop( 'stock_status', 'instock' );
}
}
So it's an error in the comment usage in wc_update_product_stock_status() function.
It still uses: 'instock' and 'outofstock' status strings. the default value is 'instock'…
The main difference is also that stock status is now handled as outofstock term for the custom taxonomy product_visibility
Before Woocommerce 3, stock status was handled as product meta data.

Categories