Change price from single product input hidden field in Woocommerce - php

I Would like to set a custom price when product is added to cart, based on an input hidden field located in the single product page.
I have tried to use this code to change the product price in cart:
add_action( 'woocommerce_before_calculate_totals', 'add_custom_price' );
function add_custom_price( $cart_object ) {
$custom_price = $_POST['_custom_price'];
foreach ( $cart_object->cart_contents as $key => $value ) {
// for WooCommerce version 3+ use:
$value['data']->set_price($custom_price);
}
}
But I get a zero price.
Here is my test page. Any help is appreciated.
Thanks for help but it dosent work...it give an error
this is my function.php
add_action( 'woocommerce_before_calculate_totals', 'add_custom_price' );
function add_custom_price( $cart_object ) {
if(isset($_POST['_custom_price'] )){
$custom_price = $_POST['_custom_price'];
foreach ( $cart_object->cart_contents as $key => $value ) {
// for WooCommerce version 3+ use:
$value['data']->set_price($custom_price);
$_SESSION['custom_price']=$custom_price;
}
}else{
}
}
// STACK HELP CODE
/*
add_filter( 'woocommerce_add_cart_item_data', 'add_custom_price', 20, 2 );
function add_custom_price( $cart_item_data, $product_id ){
// Only when option is passed through the URL
if( ! isset($_POST['_custom_price']) && empty($_POST['_custom_price']) )
return $cart_item_data;
$cart_item_data['custom_price'] = sanitize_text_field( $_POST['_custom_price'] );
return $cart_item_data;
}
add_action( 'woocommerce_before_calculate_totals', 'set_custom_price' );
function set_custom_price( $cart ) {
foreach ( $cart->get_cart() as $cart_item ) {
if( isset($cart_item['custom_price']) ){
$value['data']->set_price($cart_item['custom_price']);
}
}
}
*/
?>
<?php
/*
* Display input on single product page
* #return html
*/
function kia_custom_option(){
$value = isset( $_POST['_custom_option'] ) ? sanitize_text_field( $_POST['_custom_option'] ) : '';
printf( '<label>%s</label><input id="hotel_chambre_selected" name="_custom_option" value="%s" />', __( '', 'kia-plugin-textdomain' ), esc_attr( $value ) );
}
add_action( 'woocommerce_before_add_to_cart_button', 'kia_custom_option', 9 );
/*
* Validate when adding to cart
* #param bool $passed
* #param int $product_id
* #param int $quantity
* #return bool
*/
function kia_add_to_cart_validation($passed, $product_id, $qty){
if( isset( $_POST['_custom_option'] ) && sanitize_text_field( $_POST['_custom_option'] ) == '' ){
$product = wc_get_product( $product_id );
wc_add_notice( sprintf( __( '%s cannot be added to the cart until you enter some custom text.', 'kia-plugin-textdomain' ), $product->get_title() ), 'error' );
return false;
}
return $passed;
}
add_filter( 'woocommerce_add_to_cart_validation', 'kia_add_to_cart_validation', 10, 3 );
/*
* Add custom data to the cart item
* #param array $cart_item
* #param int $product_id
* #return array
*/
function kia_add_cart_item_data( $cart_item, $product_id ){
if( isset( $_POST['_custom_option'] ) ) {
$cart_item['custom_option'] = sanitize_text_field( $_POST['_custom_option'] );
}
return $cart_item;
}
add_filter( 'woocommerce_add_cart_item_data', 'kia_add_cart_item_data', 10, 2 );
/*
* Load cart data from session
* #param array $cart_item
* #param array $other_data
* #return array
*/
function kia_get_cart_item_from_session( $cart_item, $values ) {
if ( isset( $values['custom_option'] ) ){
$cart_item['custom_option'] = $values['custom_option'];
}
return $cart_item;
}
add_filter( 'woocommerce_get_cart_item_from_session', 'kia_get_cart_item_from_session', 20, 2 );
/*
* Add meta to order item
* #param int $item_id
* #param array $values
* #return void
*/
function kia_add_order_item_meta( $item_id, $values ) {
if ( ! empty( $values['custom_option'] ) ) {
woocommerce_add_order_item_meta( $item_id, 'custom_option', $values['custom_option'] );
}
}
add_action( 'woocommerce_add_order_item_meta', 'kia_add_order_item_meta', 10, 2 );
/*
* Get item data to display in cart
* #param array $other_data
* #param array $cart_item
* #return array
*/
function kia_get_item_data( $other_data, $cart_item ) {
if ( isset( $cart_item['custom_option'] ) ){
$other_data[] = array(
'name' => __( 'Votre chambre ', 'kia-plugin-textdomain' ),
'value' => sanitize_text_field( $cart_item['custom_option'] )
);
}
return $other_data;
}
add_filter( 'woocommerce_get_item_data', 'kia_get_item_data', 10, 2 );
/*
* Show custom field in order overview
* #param array $cart_item
* #param array $order_item
* #return array
*/
function kia_order_item_product( $cart_item, $order_item ){
if( isset( $order_item['custom_option'] ) ){
$cart_item_meta['custom_option'] = $order_item['custom_option'];
}
return $cart_item;
}
add_filter( 'woocommerce_order_item_product', 'kia_order_item_product', 10, 2 );
/*
* Add the field to order emails
* #param array $keys
* #return array
*/
function kia_email_order_meta_fields( $fields ) {
$fields['custom_field'] = __( 'Votre chambre ', 'kia-plugin-textdomain' );
return $fields;
}
add_filter('woocommerce_email_order_meta_fields', 'kia_email_order_meta_fields');
/*
* Order Again
* #param array $cart_item
* #param array $order_item
* #param obj $order
* #return array
*/
function kia_order_again_cart_item_data( $cart_item, $order_item, $order ){
if( isset( $order_item['custom_option'] ) ){
$cart_item_meta['custom_option'] = $order_item['custom_option'];
}
return $cart_item;
}
add_filter( 'woocommerce_order_again_cart_item_data', 'kia_order_again_cart_item_data', 10, 3 );
this is the result of print_r($_POST) in my function

Related

Retrieve Vendor name from Rest API on Add to cart

We are using Woocommerce and we are trying to retrieve the vendor name for a product that has been added to a cart.
We are using the Cocart plugin to make the RestAPI work.
Our marketplace plugin is Dokan.
That is our code:
$cart_contents[ $item_key ]['product_author'] = apply_filters( 'cocart_product_author', $_product->getAuthor(), $_product, $cart_item, $item_key );
We got the below error
<b>Fatalerror</b>: UncaughtError: CalltoundefinedmethodWC_Product_Simple: : getAuthor()
My entire code
* Return cart contents.
*
* #access public
* #since 2.0.0
* #version 3.5.0
* #param array $data
* #param array $cart_contents
* #param string $cart_item_key
* #param bool $from_session
* #return array $cart_contents
*/
public function return_cart_contents( $data = array(), $cart_contents = array(), $cart_item_key = '', $from_session = false ) {
if ( CoCart_Count_Items_Controller::get_cart_contents_count( array( 'return' => 'numeric' ), $cart_contents ) <= 0 || empty( $cart_contents ) ) {
/**
* Filter response for empty cart.
*
* #since 2.0.8
*/
$empty_cart = apply_filters( 'cocart_return_empty_cart', array() );
return $empty_cart;
}
$show_thumb = ! empty( $data['thumb'] ) ? $data['thumb'] : false;
// Find the cart item key in the existing cart.
if ( ! empty( $cart_item_key ) ) {
$cart_item_key = $this->find_product_in_cart( $cart_item_key );
return $cart_contents[ $cart_item_key ];
}
foreach ( $cart_contents as $item_key => $cart_item ) {
// If product data is missing then get product data and apply.
if ( ! isset( $cart_item['data'] ) ) {
$cart_item['data'] = wc_get_product( $cart_item['variation_id'] ? $cart_item['variation_id'] : $cart_item['product_id'] );
$cart_contents[ $item_key ]['data'] = $cart_item['data'];
}
$_product = apply_filters( 'cocart_item_product', $cart_item['data'], $cart_item, $item_key );
// If product is no longer purchasable then don't return it and notify customer.
if ( ! $_product->is_purchasable() ) {
/* translators: %s: product name */
$message = sprintf( __( '%s has been removed from your cart because it can no longer be purchased. Please contact us if you need assistance.', 'cart-rest-api-for-woocommerce' ), $_product->get_name() );
/**
* Filter message about item removed from the cart.
*
* #since 2.1.0
* #param string $message Message.
* #param WC_Product $_product Product data.
*/
$message = apply_filters( 'cocart_cart_item_removed_message', $message, $_product );
WC()->cart->set_quantity( $item_key, 0 ); // Sets item quantity to zero so it's removed from the cart.
wc_add_notice( $message, 'error' );
} else {
// Adds the product name and title as new variables.
$cart_contents[ $item_key ]['product_name'] = apply_filters( 'cocart_product_name', $_product->get_name(), $_product, $cart_item, $item_key );
$cart_contents[ $item_key ]['product_title'] = apply_filters( 'cocart_product_title', $_product->get_title(), $_product, $cart_item, $item_key );
$cart_contents[ $item_key ]['product_author'] = apply_filters( 'cocart_product_author', $_product->getAuthor(), $_product, $cart_item, $item_key );

How to convert a WC_Data_Store to a WC_Product_Variable_Data_Store_CPT or something that has $prices_array property?

I have to do the following reflection code because the WooCommerce version I have available has a bug (v4.9.2).
Please see the comments in the code below:
// checked before the existence of the class with class_exists
$rp = new ReflectionProperty('WC_Product_Variable_Data_Store_CPT', 'prices_array');
$rp->setAccessible(true);
var_dump('start'); // echoes something to the html code
$protected_prices_array = $rp->getValue($ths); // crashes the PHP script instance
var_dump('stop'); // this is not printed anymore
If requested, I can offer more code.
Currently I am attempting to inherit the given class to see if I can walk around the bug.
On staging site I have PHP 7.4.16.
Update 1
I have this code in my own function my_read_price_data( $ths, &$product, $for_display = false ) { ... which does the same as WC's data store's read_price_data public method which accesses the prices_array property which is protected.
Update 2
/**
* Modified function from WC.
*
* #param WC_Product_Variable_Data_Store_CPT $ths
* #param WC_Product_Variable $product
* #param boolean $for_display
* #return void
*/
function my_read_price_data( $ths, &$product, $for_display = false ) {
/**
* Transient name for storing prices for this product (note: Max transient length is 45)
*
* #since 2.5.0 a single transient is used per product for all prices, rather than many transients per product.
*/
$transient_name = 'wc_var_prices_' . $product->get_id();
$transient_version = WC_Cache_Helper::get_transient_version( 'product' );
$price_hash = my_get_price_hash($ths, $product, $for_display); // with this it does not crash (*)
// NOTE: maybe inherit from WC_Product_Variable_Data_Store_CPT to not use reflection.
$rp = new ReflectionProperty('WC_Product_Variable_Data_Store_CPT', 'prices_array'); // the class exists
$rp->setAccessible(true);
var_dump('start');
$protected_prices_array = $rp->getValue($ths); // (*) until this
var_dump('stop');
// Check if prices array is stale.
if ( ! isset( $protected_prices_array['version'] ) || $protected_prices_array['version'] !== $transient_version ) {
$rp->setValue($ths, array(
'version' => $transient_version,
));
}
$protected_prices_array = $rp->getValue($ths);
/**
* $this->prices_array is an array of values which may have been modified from what is stored in transients - this may not match $transient_cached_prices_array.
* If the value has already been generated, we don't need to grab the values again so just return them. They are already filtered.
*/
if ( empty( $protected_prices_array[ $price_hash ] ) ) {
$transient_cached_prices_array = array_filter( (array) json_decode( strval( get_transient( $transient_name ) ), true ) );
// If the product version has changed since the transient was last saved, reset the transient cache.
if ( ! isset( $transient_cached_prices_array['version'] ) || $transient_version !== $transient_cached_prices_array['version'] ) {
$transient_cached_prices_array = array(
'version' => $transient_version,
);
}
// If the prices are not stored for this hash, generate them and add to the transient.
if ( empty( $transient_cached_prices_array[ $price_hash ] ) ) {
$prices_array = array(
'price' => array(),
'regular_price' => array(),
'sale_price' => array(),
);
$variation_ids = $product->get_visible_children();
if ( is_callable( '_prime_post_caches' ) ) {
_prime_post_caches( $variation_ids );
}
foreach ( $variation_ids as $variation_id ) {
$variation = wc_get_product( $variation_id );
if ( $variation ) {
$price = apply_filters( 'woocommerce_variation_prices_price', $variation->get_price( 'edit' ), $variation, $product );
$regular_price = apply_filters( 'woocommerce_variation_prices_regular_price', $variation->get_regular_price( 'edit' ), $variation, $product );
$sale_price = apply_filters( 'woocommerce_variation_prices_sale_price', $variation->get_sale_price( 'edit' ), $variation, $product );
// Skip empty prices.
if ( '' === $price ) {
continue;
}
// If sale price does not equal price, the product is not yet on sale.
if ( $sale_price === $regular_price || $sale_price !== $price ) {
$sale_price = $regular_price;
}
// If we are getting prices for display, we need to account for taxes.
if ( $for_display ) {
if ( 'incl' === get_option( 'woocommerce_tax_display_shop' ) ) {
$price = '' === $price ? '' : wc_get_price_including_tax(
$variation,
array(
'qty' => 1,
'price' => $price,
)
);
$regular_price = '' === $regular_price ? '' : wc_get_price_including_tax(
$variation,
array(
'qty' => 1,
'price' => $regular_price,
)
);
$sale_price = '' === $sale_price ? '' : wc_get_price_including_tax(
$variation,
array(
'qty' => 1,
'price' => $sale_price,
)
);
} else {
$price = '' === $price ? '' : wc_get_price_excluding_tax(
$variation,
array(
'qty' => 1,
'price' => $price,
)
);
$regular_price = '' === $regular_price ? '' : wc_get_price_excluding_tax(
$variation,
array(
'qty' => 1,
'price' => $regular_price,
)
);
$sale_price = '' === $sale_price ? '' : wc_get_price_excluding_tax(
$variation,
array(
'qty' => 1,
'price' => $sale_price,
)
);
}
}
$prices_array['price'][ $variation_id ] = wc_format_decimal( $price, wc_get_price_decimals() );
$prices_array['regular_price'][ $variation_id ] = wc_format_decimal( $regular_price, wc_get_price_decimals() );
$prices_array['sale_price'][ $variation_id ] = wc_format_decimal( $sale_price, wc_get_price_decimals() );
$prices_array = apply_filters( 'woocommerce_variation_prices_array', $prices_array, $variation, $for_display );
}
}
// Add all pricing data to the transient array.
foreach ( $prices_array as $key => $values ) {
$transient_cached_prices_array[ $price_hash ][ $key ] = $values;
}
set_transient( $transient_name, wp_json_encode( $transient_cached_prices_array ), DAY_IN_SECONDS * 30 );
}
/**
* Give plugins one last chance to filter the variation prices array which has been generated and store locally to the class.
* This value may differ from the transient cache. It is filtered once before storing locally.
*/
$protected_prices_array = $rp->getValue($ths);
$protected_prices_array[$price_hash] = apply_filters( 'woocommerce_variation_prices', $transient_cached_prices_array[ $price_hash ], $product, $for_display );
$rp->setValue($ths, $protected_prices_array);
}
return $rp->getValue($ths)[ $price_hash ];
}
Update 3
The function above, my_read_price_data, is called by:
/**
* Function modified from WC.
*
* #param WC_Product_Variable $p
* #param boolean $for_display
* #return void
*/
function my_get_variation_prices( $p, $for_display = false ) {
$ds = $p->get_data_store(); // $p->data_store;
$prices = my_read_price_data($ds, $p, $for_display);
foreach ( $prices as $price_key => $variation_prices ) {
$prices[ $price_key ] = asort( $variation_prices );
}
return $prices;
}
This is called by the following function which is a modified version of a WC function, but this time the modification is done to change the output to something the client wants:
function my_get_price_html( $price = '' ) {
global $product;
$prices = my_get_variation_prices($product, true);
if ( empty( $prices['price'] ) ) {
$price = apply_filters( 'woocommerce_variable_empty_price_html', '', $product );
} else {
$min_price = current( $prices['price'] );
$max_price = end( $prices['price'] );
$min_reg_price = current( $prices['regular_price'] );
$max_reg_price = end( $prices['regular_price'] );
if ( $min_price !== $max_price ) {
$price = wc_format_price_range( $min_price, $max_price );
} elseif ( $product->is_on_sale() && $min_reg_price === $max_reg_price ) {
$price = my_wc_format_sale_price( $prices['regular_price'] , $prices['price'] );
} else {
$price = wc_price( $min_price );
}
$price = apply_filters( 'woocommerce_variable_price_html', $price . $product->get_price_suffix(), $product );
}
return apply_filters( 'woocommerce_get_price_html', $price, $product );
}
As you can see above, I use my_wc_format_sale_price which is here:
/**
* Format a sale price for display.
*
* #since 3.0.0
* #param float $regular_price Regular price.
* #param float $sale_price Sale price.
* #return string
*/
function my_wc_format_sale_price( $regular_price, $sale_price ) {
$price = '<span>' . get_my_percent($regular_price, $sale_price) . '</span> <ins>' . ( is_numeric( $sale_price ) ? wc_price( $sale_price ) : $sale_price ) . '</ins>';
return apply_filters( 'woocommerce_format_sale_price', $price, $regular_price, $sale_price );
}
Here is the last function that matters, I think (it has a doc comment that says it returns a string):
function get_my_percent($regular_price, $sale_price) {
$a = ($regular_price - $sale_price) / $regular_price * 100;
return "$a% reducere";
}
Update 4
I discovered the following through https://stackoverflow.com/a/21429652/258462.
It seems that the object given to the reflection mechanism is of a different type than the expected type.
From the source code of WooCommerce 4.9.2:
/**
* WC Variable Product Data Store: Stored in CPT.
*
* #version 3.0.0
*/
class WC_Product_Variable_Data_Store_CPT extends WC_Product_Data_Store_CPT implements WC_Object_Data_Store_Interface, WC_Product_Variable_Data_Store_Interface {
/**
* Cached & hashed prices array for child variations.
*
* #var array
*/
protected $prices_array = array()
...
So the question is how to convert the WC_Data_Store into something that has the $prices_array property?

Greying out "out-of-stock" product variations with custom stock quantity reduction in WooCommerce

I've used the awesome snippet of https://jeroensormani.com/custom-stock-quantity-reduction/ to add an additional setting to variations that reduces the main inventory stock by the set amount in the variation.
The problem I'm facing now is that it doesn't check if those variations are out of stock (for example main inventory is 10, and the bundle setting is set to 12 bottles).
The code I've used to add the the multiplier for the total stock reduction is:
// For implementation instructions see: https://aceplugins.com/how-to-add-a-code-snippet/
/**
* Simple product setting.
*/
function ace_add_stock_inventory_multiplier_setting() {
?><div class='options_group'><?php
woocommerce_wp_text_input( array(
'id' => '_stock_multiplier',
'label' => __( 'Inventory reduction per quantity sold', 'woocommerce' ),
'desc_tip' => 'true',
'description' => __( 'Enter the quantity multiplier used for reducing stock levels when purchased.', 'woocommerce' ),
'type' => 'number',
'custom_attributes' => array(
'min' => '1',
'step' => '1',
),
) );
?></div><?php
}
add_action( 'woocommerce_product_options_inventory_product_data', 'ace_add_stock_inventory_multiplier_setting' );
/**
* Add variable setting.
*
* #param $loop
* #param $variation_data
* #param $variation
*/
function ace_add_variation_stock_inventory_multiplier_setting( $loop, $variation_data, $variation ) {
$variation = wc_get_product( $variation );
woocommerce_wp_text_input( array(
'id' => "stock_multiplier{$loop}",
'name' => "stock_multiplier[{$loop}]",
'value' => $variation->get_meta( '_stock_multiplier' ),
'label' => __( 'Inventory reduction per quantity sold', 'woocommerce' ),
'desc_tip' => 'true',
'description' => __( 'Enter the quantity multiplier used for reducing stock levels when purchased.', 'woocommerce' ),
'type' => 'number',
'custom_attributes' => array(
'min' => '1',
'step' => '1',
),
) );
}
add_action( 'woocommerce_variation_options_pricing', 'ace_add_variation_stock_inventory_multiplier_setting', 50, 3 );
/**
* Save the custom fields.
*
* #param WC_Product $product
*/
function ace_save_custom_stock_reduction_setting( $product ) {
if ( ! empty( $_POST['_stock_multiplier'] ) ) {
$product->update_meta_data( '_stock_multiplier', absint( $_POST['_stock_multiplier'] ) );
}
}
add_action( 'woocommerce_admin_process_product_object', 'ace_save_custom_stock_reduction_setting' );
/**
* Save custom variable fields.
*
* #param int $variation_id
* #param $i
*/
function ace_save_variable_custom_stock_reduction_setting( $variation_id, $i ) {
$variation = wc_get_product( $variation_id );
if ( ! empty( $_POST['stock_multiplier'] ) && ! empty( $_POST['stock_multiplier'][ $i ] ) ) {
$variation->update_meta_data( '_stock_multiplier', absint( $_POST['stock_multiplier'][ $i ] ) );
$variation->save();
}
}
add_action( 'woocommerce_save_product_variation', 'ace_save_variable_custom_stock_reduction_setting', 10, 2 );
The code that reduces the quantity then is the following:
// For implementation instructions see: https://aceplugins.com/how-to-add-a-code-snippet/
/**
* Reduce with custom stock quantity based on the settings.
*
* #param $quantity
* #param $order
* #param $item
* #return mixed
*/
function ace_custom_stock_reduction( $quantity, $order, $item ) {
/** #var WC_Order_Item_Product $product */
$multiplier = $item->get_product()->get_meta( '_stock_multiplier' );
if ( empty( $multiplier ) && $item->get_product()->is_type( 'variation' ) ) {
$product = wc_get_product( $item->get_product()->get_parent_id() );
$multiplier = $product->get_meta( '_stock_multiplier' );
}
if ( ! empty( $multiplier ) ) {
$quantity = $multiplier * $quantity;
}
return $quantity;
}
add_filter( 'woocommerce_order_item_quantity', 'ace_custom_stock_reduction', 10, 3 );
What I've tried to do is add an "If" Snippet to check the quantity
add_filter( ‘woocommerce_variation_is_active’, ‘my_jazzy_function’, 10, 2 );
function my_jazzy_function( $active, $variation ) {
// Get Multiplier
$multiplier = $item->get_product()->get_meta( '_stock_multiplier' );
$var_stock_count = $variation->get_stock_quantity();
// if there are 5 or less, disable the variant, could always just set to 0.
if( $var_stock_count <= $multiplier ) {
return false;
}
else {
return true;
}
}
But this doesn't work, I think it only checks the variations quantity (if you set the variation to its own quantity instead of global).
How can I compare the total stock count to the newly added setting $multiplier?
Any help would be great.
Compare the total stock quantity to the newly added setting $multiplier
Comment with explanation added to the code
function filter_woocommerce_variation_is_active( $active, $variation ) {
// Get multiplier
$multiplier = get_post_meta( $variation->get_variation_id(), '_stock_multiplier', true );
// NOT empty
if ( ! empty( $multiplier ) ) {
// Get stock quantity
$var_stock_count = $variation->get_stock_quantity();
// Stock quantity < multiplier
if( $var_stock_count < $multiplier ) {
$active = false;
}
}
return $active;
}
add_filter( 'woocommerce_variation_is_active', 'filter_woocommerce_variation_is_active', 10, 2 );
It doesn't work because:
$item variable is not defined in your code.
your custom field is defined in the parent variable product.
So you need to replace:
$multiplier = $item->get_product()->get_meta( '_stock_multiplier' );
by the folling (getting the data from the parent variable product):
$multiplier = get_post_meta( $variation->get_parent_id(), '_stock_multiplier', true );
So in your code:
add_filter( 'woocommerce_variation_is_active', 'my_jazzy_function', 10, 2 );
function my_jazzy_function( $active, $variation ) {
// Get multiplier
if( $multiplier = get_post_meta( $variation->get_parent_id(), '_stock_multiplier', true ) {
// Get stock quantity
$var_stock_count = (int) $variation->get_stock_quantity();
// if there are 5 or less, disable the variant, could always just set to 0
return $var_stock_count <= $multiplier ? false : $active;
}
return $active;
}
It should work now.

Update cart item custom data on quantity change in Woocommerce

I have added a custom field to the woocommerce product named 'Donation', which stores donation of the individual product. Then, I added line item meta named 'line_donation'. Now I need to update 'line_donation' on product quantity change and after clicking update cart Button like product total changes.
function cfwc_add_custom_field_item_data( $cart_item_data, $product_id, $variation_id, $quantity ) {
// Add the item data
$cart_item_data['line_donation'] = get_post_meta($product_id,'donation', true);
return $cart_item_data;
}
add_filter( 'woocommerce_add_cart_item_data', 'cfwc_add_custom_field_item_data', 10, 4 );
/**
* Display the custom field value in the cart
* #since 1.0.0
*/
function cfwc_cart_item_name( $name, $cart_item, $cart_item_key ) {
if( isset( $cart_item['line_donation'] ) ) {
$name .= sprintf(
'<p>%s</p>',
esc_html( $cart_item['line_donation'] )
);
}
return $name;
}
add_filter( 'woocommerce_cart_item_name', 'cfwc_cart_item_name', 10, 3 );
function add_line_donation_to_order( $item, $cart_item_key, $values, $order ) {
foreach( $item as $cart_item_key=>$values ) {
if( isset( $values['line_donation'] ) ) {
$item->add_meta_data( __( 'line_donation', 'woocommerce' ), $values['line_donation'], true );
}
}
}
add_action( 'woocommerce_checkout_create_order_line_item', 'add_line_donation_to_order', 10, 4 );
Any help is welcome.
You just simply need to make a little change in your 2 last functions as follows:
add_filter( 'woocommerce_cart_item_name', 'cfwc_cart_item_name', 10, 3 );
function cfwc_cart_item_name( $name, $cart_item, $cart_item_key ) {
if( isset( $cart_item['line_donation'] ) )
$name .= '<p>' . $cart_item['line_donation'] * $cart_item['quantity'] . '</p>';
return $name;
}
add_action( 'woocommerce_checkout_create_order_line_item', 'add_line_donation_to_order', 10, 4 );
function add_line_donation_to_order( $item, $cart_item_key, $values, $order ) {
foreach( $item as $cart_item_key=>$values ) {
if( isset( $values['line_donation'] ) ) {
$item->add_meta_data( __( 'line_donation', 'woocommerce' ), $values['line_donation'] * $values['quantity'], true );
}
}
}
Code goes in function.php file of your active child theme (or active theme). Tested and works.

Single product custom date fields not validated and saved in Woocommerce

I've added two customer date inputs to the single product page. I need them to be required and validated before adding to the cart, and would also like the dates to be shown on the cart/checkout page and in the order emails.
I found the snippets needed here, however it was only for one custom field so I adjusted to make it for two: https://www.kathyisawesome.com/add-a-custom-field-to-woocommerce-product/
The input fields show up fine, but once you hit the Add to Cart button it doesn't carry throughout the order.
Here is the code used in my functions.php file:
/*
* Display inputs on single product page
*/
function amp_custom_option_1(){
$value = isset( $_POST['_est_delivery'] ) ? sanitize_text_field( $_POST['_est_delivery'] ) : '';
printf( '<div id="dates"><div class="delivery"><label>%s</label><input name="_est_delivery" value="%s" type="date" required /></div>', __( 'Estimated Delivery Date:', 'amp-plugin-textdomain-1' ), esc_attr( $value ) );
}
add_action( 'woocommerce_before_add_to_cart_form', 'amp_custom_option_1', 9 );
function amp_custom_option_2(){
$value = isset( $_POST['_est_pickup'] ) ? sanitize_text_field( $_POST['_est_pickup'] ) : '';
printf( '<div class="pickup"><label>%s</label><input name="_est_pickup" value="%s" type="date" required /></div></div>', __( 'Estimated Pickup Date:', 'amp-plugin-textdomain-2' ), esc_attr( $value ) );
}
add_action( 'woocommerce_before_add_to_cart_form', 'amp_custom_option_2', 9 );
/*
* Validate when adding to cart
*/
function amp_add_to_cart_validation_1($passed, $product_id, $qty){
if( isset( $_POST['_est_delivery'] ) && sanitize_text_field( $_POST['_est_delivery'] ) == '' ){
$product = wc_get_product( $product_id );
wc_add_notice( sprintf( __( '%s cannot be added to the cart until you enter a delivery date.', 'amp-plugin-textdomain-1' ), $product->get_title() ), 'error' );
return false;
}
return $passed;
}
add_filter( 'woocommerce_add_to_cart_validation', 'amp_add_to_cart_validation_1', 10, 3 );
function amp_add_to_cart_validation_2($passed, $product_id, $qty){
if( isset( $_POST['_est_pickup'] ) && sanitize_text_field( $_POST['_est_pickup'] ) == '' ){
$product = wc_get_product( $product_id );
wc_add_notice( sprintf( __( '%s cannot be added to the cart until you enter a pickup date.', 'amp-plugin-textdomain-2' ), $product->get_title() ), 'error' );
return false;
}
return $passed;
}
add_filter( 'woocommerce_add_to_cart_validation', 'amp_add_to_cart_validation_2', 10, 3 );
/*
* Add custom data to the cart item
*/
function amp_add_cart_item_data_1( $cart_item, $product_id ){
if( isset( $_POST['_est_delivery'] ) ) {
$cart_item['est_delivery'] = sanitize_text_field( $_POST['_est_delivery'] );
}
return $cart_item;
}
add_filter( 'woocommerce_add_cart_item_data', 'amp_add_cart_item_data_1', 10, 2 );
function amp_add_cart_item_data_2( $cart_item, $product_id ){
if( isset( $_POST['_est_pickup'] ) ) {
$cart_item['est_pickup'] = sanitize_text_field( $_POST['_est_pickup'] );
}
return $cart_item;
}
add_filter( 'woocommerce_add_cart_item_data', 'amp_add_cart_item_data_2', 10, 2 );
/*
* Load cart data from session
*/
function amp_get_cart_item_from_session_1( $cart_item, $values ) {
if ( isset( $values['est_delivery'] ) ){
$cart_item['est_delivery'] = $values['est_delivery'];
}
return $cart_item;
}
add_filter( 'woocommerce_get_cart_item_from_session', 'amp_get_cart_item_from_session_1', 20, 2 );
function amp_get_cart_item_from_session_2( $cart_item, $values ) {
if ( isset( $values['est_pickup'] ) ){
$cart_item['est_pickup'] = $values['est_pickup'];
}
return $cart_item;
}
add_filter( 'woocommerce_get_cart_item_from_session', 'amp_get_cart_item_from_session_2', 20, 2 );
/*
* Add meta to order item
*/
function amp_add_order_item_meta_1( $item_id, $values ) {
if ( ! empty( $values['est_delivery'] ) ) {
woocommerce_add_order_item_meta( $item_id, 'est_delivery', $values['est_delivery'] );
}
}
add_action( 'woocommerce_add_order_item_meta', 'amp_add_order_item_meta_1', 10, 2 );
function amp_add_order_item_meta_2( $item_id, $values ) {
if ( ! empty( $values['est_pickup'] ) ) {
woocommerce_add_order_item_meta( $item_id, 'est_pickup', $values['est_pickup'] );
}
}
add_action( 'woocommerce_add_order_item_meta', 'amp_add_order_item_meta_2', 10, 2 );
/*
* Get item data to display in cart
*/
function amp_get_item_data_1( $other_data, $cart_item ) {
if ( isset( $cart_item['est_delivery'] ) ){
$other_data[] = array(
'name' => __( 'Estimated Delivery Date:', 'amp-plugin-textdomain-1' ),
'value' => sanitize_text_field( $cart_item['est_delivery'] )
);
}
return $other_data;
}
add_filter( 'woocommerce_get_item_data', 'amp_get_item_data_1', 10, 2 );
function amp_get_item_data_2( $other_data, $cart_item ) {
if ( isset( $cart_item['est_pickup'] ) ){
$other_data[] = array(
'name' => __( 'Estimated Pickup Date', 'amp-plugin-textdomain-2' ),
'value' => sanitize_text_field( $cart_item['est_pickup'] )
);
}
return $other_data;
}
add_filter( 'woocommerce_get_item_data', 'amp_get_item_data_2', 10, 2 );
/*
* Show custom field in order overview
*/
function amp_order_item_product_1( $cart_item, $order_item ){
if( isset( $order_item['est_delivery'] ) ){
$cart_item_meta['est_delivery'] = $order_item['est_delivery'];
}
return $cart_item;
}
add_filter( 'woocommerce_order_item_product', 'amp_order_item_product_1', 10, 2 );
function amp_order_item_product_2( $cart_item, $order_item ){
if( isset( $order_item['est_pickup'] ) ){
$cart_item_meta['est_pickup'] = $order_item['est_pickup'];
}
return $cart_item;
}
add_filter( 'woocommerce_order_item_product', 'amp_order_item_product_2', 10, 2 );
/*
* Add the field to order emails
*/
function amp_email_order_meta_fields_1( $fields ) {
$fields['est_delivery'] = __( 'Estimated Delivery Date:', 'amp-plugin-textdomain-1' );
return $fields;
}
add_filter('woocommerce_email_order_meta_fields', 'amp_email_order_meta_fields_1');
function amp_email_order_meta_fields_2( $fields ) {
$fields['est_delivery'] = __( 'Estimate Pickup Date:', 'amp-plugin-textdomain-2' );
return $fields;
}
add_filter('woocommerce_email_order_meta_fields', 'amp_email_order_meta_fields_2');
I'm not sure what is wrong with my code? Any help is appreciated.
There was some errors and mistakes. I have changed and removed some hooks, remove unnecessary code, merged functions, revisited all your code. As Your 2 dates fields are on single product pages, they will be related to cart items and order items (so order items meta data).
I have set your 2 date fields slugs and labels in the first function, inside an array. Then I call that function everywhere else and I use a foreach loop to process each field. This avoid repetitions, optimize and compact the code.
The code (commented):
// Utility function that contain the 2 field keys and labels pairs used on all other functions
function get_date_label_keys(){
$text_domain = 'woocommerce';
return array( 'est_delivery' => __( 'Estimated Delivery Date', $text_domain ),
'est_pickup' => __( 'Estimated Pickup Date', $text_domain ) );
}
// Display custom fields on single product page (hook replaced)
add_action( 'woocommerce_before_add_to_cart_button', 'amp_display_custom_fields', 20 );
function amp_display_custom_fields(){
echo '<div id="dates">';
// Loop through each custom field
foreach( get_date_label_keys() as $key => $label ){
$class = str_replace('est_', '', $key); // The class
$value = isset($_POST[$key]) ? sanitize_text_field($_POST[$key]) : ''; // Display the value
printf( '<div class="%s"><label>%s:</label> <input type="date" name="%s" value="%s" required /></div>', $class, $label, $key, $value );
}
echo '</div><br clear="all">';
}
// Add to cart fields validation (in case of need)
add_filter( 'woocommerce_add_to_cart_validation', 'amp_add_to_cart_validation', 20, 3 );
function amp_add_to_cart_validation( $passed, $product_id, $qty ){
// Loop through each custom field
foreach( get_date_label_keys() as $key => $label ){
if( isset( $_POST[$key] ) && empty( $_POST[$key] ) ){
wc_add_notice( sprintf( __( '%s cannot be added to the cart until you enter a delivery date.', $domain ), get_the_title() ), 'error' );
$passed = false;
}
}
return $passed;
}
// Add to cart items the custom data
add_filter( 'woocommerce_add_cart_item_data', 'amp_add_cart_item_data', 20, 2 );
function amp_add_cart_item_data( $cart_item, $product_id ){
// Loop through each custom field
foreach( get_date_label_keys() as $key => $label ){
if( isset( $_POST[$key] ) )
$cart_item['dates'][$key] = sanitize_text_field( $_POST[$key] );
}
return $cart_item;
}
// Display the dates in cart items on cart and checkout pages
add_filter( 'woocommerce_get_item_data', 'amp_get_item_data', 20, 2 );
function amp_get_item_data( $item_data, $cart_item = null ) {
// Loop through each custom field
foreach( get_date_label_keys() as $key => $label ){
if ( isset( $cart_item['dates'][$key] ) )
$item_data[] = array(
'name' => $label,
'value' => sanitize_text_field( $cart_item['dates'][$key] )
);
}
return $item_data;
}
// Add order item meta data and Display the data in order items (hook replaced)
add_action( 'woocommerce_checkout_create_order_line_item', 'amp_add_order_item_meta', 20, 4 );
function amp_add_order_item_meta( $item, $cart_item_key, $values, $order ) {
foreach( get_date_label_keys() as $key => $label ){
// Loop through each custom field
if ( ! empty( $values['dates'][$key] ) )
$item->update_meta_data( $label, $values['dates'][$key] );
}
}
Code goes in function.php file of your active child theme (or active theme). Tested and works.
On cart page (and checkout too):
Order received and order view pages (in admin order edit pages and email notifications too):

Categories