Update a custom field on "Update Cart" click in WooCommerce Cart - php

I added a custom <select> field to each product in cart page to be able to update variation, but when I change it's value and click "Update Cart", nothing updates unless quantity field has also been changed.
Is there a way to avoid this?
My code:
functions.php file:
add_filter( 'woocommerce_update_cart_action_cart_updated', 'on_action_cart_updated', 20, 1 );
function on_action_cart_updated( $cart_updated ){
if ($cart_updated) {
$cart_content = WC()->cart->get_cart_contents();
$update_cart = false;
$cart_totals = isset( $_POST['cart'] ) ? wp_unslash( $_POST['cart'] ) : '';
if ( ! empty( $cart_content ) && is_array( $cart_totals ) ) {
foreach ($cart_content as $key => $item) {
$lease_period = $cart_totals[$key]['lease'];
if ( ! empty( $lease_period )) {
$cart_content[$key]['variation']['attribute_pa_lease-period'] = $lease_period;
$update_cart = true;
}
}
if ($update_cart) {
WC()->cart->set_cart_contents($cart_content);
}
}
}
}
cart.php file:
<td class="product-lease-period" data-title="<?php esc_attr_e( 'Lease Period', 'woocommerce' ); ?>">
<div class="product-lease-period-select">
<select name="cart[<?php echo $cart_item_key ?>][lease]">
<?php
$lease_periods = ['6-months'=> '6 Months',
'12-months' => '12 Months',
'18-months' => '18 Months',
'24-months' => '24 Months'];
foreach ($lease_periods as $key => $period) {
$selected = '';
if ($cart_item['variation']['attribute_pa_lease-period'] == $key) {
$selected = 'selected="selected"';
}
echo "<option value=" . $key . " $selected>" . $period . "</option>";
}
?>
</select>
</div>
</td>
My conclusions so far:
I believe it's because of these pieces of code inside the class-wc-form-handler.php :
// Skip product if no updated quantity was posted.
if ( ! isset( $cart_totals[ $cart_item_key ] ) || ! isset( $cart_totals[ $cart_item_key ]['qty'] ) ) {
continue;
}
and a bit below:
if ( '' === $quantity || $quantity === $values['quantity'] ) {
continue;
}

I extended the WC_Form_Handler class in my functions.php file, copied a method I needed and edited it, and gave it higher hook priority in my extended class than it's in the original class:
class WC_Form_Handler_Ext extends WC_Form_Handler {
/**
* Hook in method.
*/
public static function init() {
add_action( 'wp_loaded', array( __CLASS__, 'update_cart_action' ), 30 );
}
/**
* Remove from cart/update.
*/
public static function update_cart_action() {
// method content edited
}
}
WC_Form_Handler_Ext::init();
UPDATE:
To make price change after variation value in cart is updated, this function needs to be added to the functions.php
function find_matching_product_variation_id($product_id, $attributes)
{
return (new WC_Product_Data_Store_CPT())->find_matching_product_variation(
new WC_Product($product_id),
$attributes
);
}
And it should be called from the loop in functions.php I mentioned in my question this way:
$attributes = $cart_content[$key]['variation'];
$variation_id = find_matching_product_variation_id($product_id, $attributes);
$price = get_post_meta($variation_id, '_price', true);
$item['data']->set_price($price);

Related

Dynamically added custom fields not displayed on WooCommerce email notifications

I have a different kind of scenario then the typical custom fields (I suppose). I am not getting custom values (fields) from user in the form rather I have an implementation which adds:
ColorName
Size
City
These are from a custom product flow which adds custom attributes to the cart, here is how I am doing that:
add_action('wp_ajax_wdm_add_user_custom_data_options', 'wdm_add_user_custom_data_options_callback');
add_action('wp_ajax_nopriv_wdm_add_user_custom_data_options', 'wdm_add_user_custom_data_options_callback');
function wdm_add_user_custom_data_options_callback()
{
// print_r($_POST);
$productIDM = $_POST['product_id'];
// case swith
switch ($productIDM) {
case "Ace Dura Silk":
$productID = 3254;
break;
case "Ace Matt Finish":
$productID = 3232;
break;
case "Ace Plastic Emulsion":
$productID = 3276;
break;
case "Ace Weather Defender":
$productID = 2991;
break;
case "Overall Plasticoat":
$productID = 3112;
break;
}
$colorname = $_POST['colorname'];
$cityname = $_POST['cityname'];
$size = $_POST['size'];
$price = $_POST['price'];
global $woocommerce;
$woocommerce->cart->add_to_cart( $productID, 1 );
// die();
// echo 'I am in...';
$result = array(
'status' => true
);
echo json_encode($result);
}
add_filter('woocommerce_add_cart_item_data','wdm_add_item_data',1,10);
function wdm_add_item_data($cart_item_data, $product_id) {
global $woocommerce;
$new_value = array();
$new_value['_custom_options'] = $_POST['custom_options'];
if(empty($cart_item_data)) {
return $new_value;
} else {
return array_merge($cart_item_data, $new_value);
}
}
add_filter('woocommerce_get_cart_item_from_session', 'wdm_get_cart_items_from_session', 1, 3 );
function wdm_get_cart_items_from_session($item,$values,$key) {
if (array_key_exists( '_custom_options', $values ) ) {
$item['_custom_options'] = $values['_custom_options'];
}
return $item;
}
add_filter('woocommerce_cart_item_name','add_usr_custom_session',1,3);
function add_usr_custom_session($product_name, $values, $cart_item_key ) {
$return_string = $product_name . "<div class='cart_subitems_custom'><br /><small>".$values['_custom_options']['colorname']."</small><br /><small>".$values['_custom_options']['cityname']."</small><br /><small>".$values['_custom_options']['size']."</small></div>" ; //. "<br />" . print_r($values['_custom_options']);
return $return_string;
}
add_action('woocommerce_add_order_item_meta','wdm_add_values_to_order_item_meta',1,2);
function wdm_add_values_to_order_item_meta($item_id, $values) {
global $woocommerce,$wpdb;
wc_add_order_item_meta($item_id,'_colorname',$values['_custom_options']['colorname']);
wc_add_order_item_meta($item_id,'_cityname',$values['_custom_options']['cityname']);
wc_add_order_item_meta($item_id,'_size',$values['_custom_options']['size']);
}
add_action( 'woocommerce_before_calculate_totals', 'update_custom_price', 1, 1 );
function update_custom_price( $cart_object ) {
foreach ( $cart_object->cart_contents as $cart_item_key => $value ) {
// Version 2.x
//$value['data']->price = $value['_custom_options']['custom_price'];
// Version 3.x / 4.x
if($value['_custom_options']['price'] == null){
echo"";
}else{
$value['data']->set_price($value['_custom_options']['price']);
}
}
}
I am getting these custom values almost everywhere except Email Notification.
Here is what normal product order edit shows:
Here is how I am getting the custom product in order edit page:
I have tried all the solution I can possibly find (filter & action hooks) but nothing works for me.
I have tried first answer from this:
add_action( 'woocommerce_checkout_create_order_line_item', 'custom_checkout_create_order_line_item', 20, 4 );
function custom_checkout_create_order_line_item( $item, $cart_item_key, $values, $order ) {
if( isset( $values['colorname'] ) )
$item->add_meta_data( __('DCM Shade'), $values['_colorname'] );
}
Also the common method I found everywhere:
function custom_woocommerce_email_order_meta_fields( $fields, $sent_to_admin, $order ) {
// Get meta
$color = $order->get_meta( 'colorname', true );
// NOT empty
if( ! empty( $color ) ) {
$fields['colorname'] = array(
'label' => __( 'Shade' ),
'value' => $color,
);
}
// Get (other) meta
$shipping_email = $order->get_meta( '_cityname', true );
// NOT empty
if ( ! empty( $shipping_email ) ) {
$fields['_cityname'] = array(
'label' => __( 'City' ),
'value' => $shipping_email,
);
}
return $fields;
}
add_filter( 'woocommerce_email_order_meta_fields', 'custom_woocommerce_email_order_meta_fields', 10, 3 );
But I can't get the custom fields.
What am I doing wrong can please anyone please guide me.

Display SKU in Woocommerce single product page with OceanWP theme

I want to display the SKU field of each product in its signle product page. The woocommerce plugin's settings does not have that option in wordpress.
I already followed the instructions here:
https://wordpress.stackexchange.com/questions/219410/how-to-show-product-sku-on-product-page/219427#219427
I tried adding to my functions.php file this code:
add_action( 'woocommerce_single_product_summary', 'dev_designs_show_sku', 5 );
function dev_designs_show_sku(){
global $product;
echo 'SKU: ' . $product->get_sku();
}
And also tried to add this code to the theme's single product content file:
if ( 'sku' === $element ) {
woocommerce_template_single_sku();
}
file name is owp-single-product.php:
<?php
/**
* Single product template.
*
* #package OceanWP WordPress theme
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
// Get price conditional display state.
$ocean_woo_single_cond = get_theme_mod( 'ocean_woo_single_conditional', false );
// Conditional vars.
$show_woo_single = '';
$show_woo_single = ( is_user_logged_in() && $ocean_woo_single_cond === true );
/**
* Display Single Product template
*
*/
// Get elements.
$elements = oceanwp_woo_summary_elements_positioning();
// Loop through elements.
foreach ( $elements as $element ) {
do_action( 'ocean_before_single_product_' . $element );
// Title.
if ( 'title' === $element ) {
woocommerce_template_single_title();
}
// Sku.
if ( 'sku' === $element ) {
woocommerce_template_single_sku();
}
// Rating.
if ( 'rating' === $element ) {
woocommerce_template_single_rating();
}
// Price.
if ( 'price' === $element ) {
if ( false === $ocean_woo_single_cond || $show_woo_single ) {
woocommerce_template_single_price();
}
}
// Excerpt.
if ( 'excerpt' === $element ) {
woocommerce_template_single_excerpt();
}
// Quantity & Add to cart button.
if ( 'quantity-button' === $element ) {
if ( false === $ocean_woo_single_cond || $show_woo_single ) {
woocommerce_template_single_add_to_cart();
} else {
// Get Add to Cart button message display state.
$ocean_woo_single_msg = get_theme_mod( 'ocean_woo_single_cond_msg', 'yes' );
if ( 'yes' === $ocean_woo_single_msg ) {
// Get Add to Cart button replacement message.
$ocean_woo_single_msg_txt = get_theme_mod( 'ocean_woo_single_cond_msg_text' );
$ocean_woo_single_msg_txt = $ocean_woo_single_msg_txt ? $ocean_woo_single_msg_txt : esc_html__( 'Log in to view price and purchase', 'oceanwp' );
$woo_single_myaccunt_link = get_theme_mod( 'ocean_single_add_myaccount_link', false );
echo '<div class="owp-woo-single-cond-notice">';
if ( false === $woo_single_myaccunt_link ) {
echo '<span>'. $ocean_woo_single_msg_txt .'</span>';
} else {
echo '' . $ocean_woo_single_msg_txt . '';
}
echo '</div>';
}
}
}
// Meta.
if ( 'meta' === $element ) {
woocommerce_template_single_meta();
}
do_action( 'ocean_after_single_product_' . $element );
}
Did not work! What do I do?
You can try this to show the sku after product title
add_action( 'ocean_after_single_product_title', 'show_product_sku', 5 );
function show_product_sku(){
global $product;
echo 'SKU: ' . $product->get_sku();
}

Using ACF number field as price for WooCommerce custom product type

I've seen online that you can add all Custom Post Types to a WooCommerce cart as long as the CPT has a price field. The only issue is that you have to tell WooCommerce what CPT field contains the price.
Afterwords you can easily create an add-to-cart url like this:
https://yourdomain.com/?add-to-cart=XXX (XXX being the Custom Post Type post ID)
Why is this handy?
I have an online menu (just to inform my guest what we serve) but because of Corona we have to close our doors so I want these dishes to be ordered online.
The code should be something like this but the CPT is not added to cart:
add_filter('woocommerce_get_price', 'yl_get_dish_price', 20,2);
function yl_get_dish_price($price,$post) {
if ($post->post->post_type === 'dish') {
$price = get_field('price', $post->ID);
}
return $price;
}
UPDATE
I took this answer from here https://stackoverflow.com/a/60320662/10291365
class YL_Dish_Product extends WC_Product {
protected $post_type = 'dish';
public function get_type() {
return 'dish';
}
public function __construct( $product = 0 ) {
$this->supports[] = 'ajax_add_to_cart';
parent::__construct( $product );
}
// maybe overwrite other functions from WC_Product
}
class YL_Data_Store_CPT extends WC_Product_Data_Store_CPT {
public function read( &$product ) { // this is required
$product->set_defaults();
$post_object = get_post( $product->get_id() );
if ( ! $product->get_id() || ! $post_object || 'dish' !== $post_object->post_type ) {
throw new Exception( __( 'Invalid product.', 'woocommerce' ) );
}
$product->set_props(
array(
'name' => $post_object->post_title,
'slug' => $post_object->post_name,
'date_created' => 0 < $post_object->post_date_gmt ? wc_string_to_timestamp( $post_object->post_date_gmt ) : null,
'date_modified' => 0 < $post_object->post_modified_gmt ? wc_string_to_timestamp( $post_object->post_modified_gmt ) : null,
'status' => $post_object->post_status,
'description' => $post_object->post_content,
'short_description' => $post_object->post_excerpt,
'parent_id' => $post_object->post_parent,
'menu_order' => $post_object->menu_order,
'reviews_allowed' => 'open' === $post_object->comment_status,
)
);
$this->read_attributes( $product );
$this->read_downloads( $product );
$this->read_visibility( $product );
$this->read_product_data( $product );
$this->read_extra_data( $product );
$product->set_object_read( true );
}
// maybe overwrite other functions from WC_Product_Data_Store_CPT
}
class YL_WC_Order_Item_Product extends WC_Order_Item_Product {
public function set_product_id( $value ) {
if ( $value > 0 && 'dish' !== get_post_type( absint( $value ) ) ) {
$this->error( 'order_item_product_invalid_product_id', __( 'Invalid product ID', 'woocommerce' ) );
}
$this->set_prop( 'product_id', absint( $value ) );
}
}
function YL_woocommerce_data_stores( $stores ) {
// the search is made for product-$post_type so note the required 'product-' in key name
$stores['product-dish'] = 'YL_Data_Store_CPT';
return $stores;
}
add_filter( 'woocommerce_data_stores', 'YL_woocommerce_data_stores' , 11, 1 );
function YL_woo_product_class( $class_name , $product_type , $product_id ) {
if ($product_type == 'dish')
$class_name = 'YL_Dish_Product';
return $class_name;
}
add_filter('woocommerce_product_class','YL_woo_product_class',25,3 );
function my_woocommerce_product_get_price( $price, $product ) {
if ($product->get_type() == 'dish' ) {
$price = 10; // or get price how ever you see fit
}
return $price;
}
add_filter('woocommerce_get_price','my_woocommerce_product_get_price',20,2);
add_filter('woocommerce_product_get_price', 'my_woocommerce_product_get_price', 10, 2 );
// required function for allowing posty_type to be added; maybe not the best but it works
function YL_woo_product_type($false,$product_id) {
if ($false === false) { // don't know why, but this is how woo does it
global $post;
// maybe redo it someday?!
if (is_object($post) && !empty($post)) { // post is set
if ($post->post_type == 'dish' && $post->ID == $product_id)
return 'dish';
else {
$product = get_post( $product_id );
if (is_object($product) && !is_wp_error($product)) { // post not set but it's a dish
if ($product->post_type == 'dish')
return 'dish';
} // end if
}
} else if(wp_doing_ajax()) { // has post set (usefull when adding using ajax)
$product_post = get_post( $product_id );
if ($product_post->post_type == 'dish')
return 'dish';
} else {
$product = get_post( $product_id );
if (is_object($product) && !is_wp_error($product)) { // post not set but it's a dish
if ($product->post_type == 'dish')
return 'dish';
} // end if
} // end if // end if
} // end if
return false;
}
add_filter('woocommerce_product_type_query','YL_woo_product_type',12,2 );
function YL_woocommerce_checkout_create_order_line_item_object($item, $cart_item_key, $values, $order) {
$product = $values['data'];
if ($product->get_type() == 'dish') {
return new YL_WC_Order_Item_Product();
} // end if
return $item ;
}
add_filter( 'woocommerce_checkout_create_order_line_item_object', 'YL_woocommerce_checkout_create_order_line_item_object', 20, 4 );
function cod_woocommerce_checkout_create_order_line_item($item,$cart_item_key,$values,$order) {
if ($values['data']->get_type() == 'dish') {
$item->update_meta_data( '_dish', 'yes' ); // add a way to recognize custom post type in ordered items
return;
} // end if
}
add_action( 'woocommerce_checkout_create_order_line_item', 'cod_woocommerce_checkout_create_order_line_item', 20, 4 );
function YL_woocommerce_get_order_item_classname($classname, $item_type, $id) {
global $wpdb;
$is_IA = $wpdb->get_var("SELECT meta_value FROM {$wpdb->prefix}woocommerce_order_itemmeta WHERE order_item_id = {$id} AND meta_key = '_dish'");
if ('yes' === $is_IA) { // load the new class if the item is our custom post
$classname = 'YL_WC_Order_Item_Product';
} // end if
return $classname;
}
add_filter( 'woocommerce_get_order_item_classname', 'YL_woocommerce_get_order_item_classname', 20, 3 );
The above code does add a CPT to your cart (GREAT!!) but the price is always set to 10,00
So the code below doesn't give the right price :(
add_filter('woocommerce_get_price', 'yl_get_dish_price', 20,2);
function yl_get_dish_price($price,$post) {
if ($post->post->post_type === 'dish') {
$price = get_field('price', $post->ID);
}
return $price;
}
Any idea?
Since WooCommerce 3, the hook woocommerce_get_price is obsolete and deprecated… It's replaced by the following composite hook:
add_filter( 'woocommerce_product_get_price', 'yl_get_dish_price', 20, 2 );
add_filter( 'woocommerce_product_get_regular_price', 'yl_get_dish_price', 20, 2 );
function yl_get_dish_price( $price, $product ) {
if ( $product->is_type('dish') ) {
$price = get_field( 'price', $product->get_id() );
}
return $price;
}
It could and should better work.

Filter orders by specific meta fields in WooCommerce admin orders list

Can anyone let me know, how can i add / set filter by company name in woo-commerce order page.
and please share functions or show my error so i can solved it.
i tried it but not working. you help is much appreciated.
add_action( 'restrict_manage_posts', 'admin_shop_order_by_product_type_filter' );
function admin_shop_order_by_product_type_filter(){
global $pagenow, $post_type;
if( 'shop_order' === $post_type && 'edit.php' === $pagenow ) {
$domain = 'woocommerce';
$filter_id = 'filter_billing_company';
$current = isset($_GET[$filter_id])? $_GET[$filter_id] : '';
$query_args = ['fields' => '_billing_company', 'orderby' => 'order'];
echo "<pre>";print_r(get_terms($query_args)); echo "</pre>";
echo '<select name="'.$filter_id.'">
<option value="">' . __('Filter by Company', $domain) . '</option>';
foreach ( get_terms($query_args) as $term_name ) {
printf( '<option value="%s"%s>%s</option>', $term_name,
$term_name === $current ? '" selected="selected"' : '', ucfirst($term_name) );
}
echo '</select>';
}
}
Thanks
To filter orders by meta fields on admin orders list, you will use the following (where you will define in the 1st function below the metakey / label pairs that will filter orders):
// Custom function where metakeys / labels pairs are defined
function get_filter_shop_order_meta( $domain = 'woocommerce' ){
// Add below the metakey / label pairs to filter orders
return [
'_billing_company' => __('Billing company', $domain),
'_order_total' => __('Gran total', $domain),
];
}
// Add a dropdown to filter orders by meta
add_action( 'restrict_manage_posts', 'display_admin_shop_order_by_meta_filter' );
function display_admin_shop_order_by_meta_filter(){
global $pagenow, $typenow;
if( 'shop_order' === $typenow && 'edit.php' === $pagenow ) {
$domain = 'woocommerce';
$filter_id = 'filter_shop_order_by_meta';
$current = isset($_GET[$filter_id])? $_GET[$filter_id] : '';
echo '<select name="'.$filter_id.'">
<option value="">' . __('Filter by meta…', $domain) . '</option>';
$options = get_filter_shop_order_meta( $domain );
foreach ( $options as $key => $label ) {
printf( '<option value="%s"%s>%s</option>', $key,
$key === $current ? '" selected="selected"' : '', $label );
}
echo '</select>';
}
}
// Process the filter dropdown for orders by Marketing optin
add_filter( 'request', 'process_admin_shop_order_marketing_by_meta', 99 );
function process_admin_shop_order_marketing_by_meta( $vars ) {
global $pagenow, $typenow;
$filter_id = 'filter_shop_order_by_meta';
if ( $pagenow == 'edit.php' && 'shop_order' === $typenow
&& isset( $_GET[$filter_id] ) && ! empty($_GET[$filter_id]) ) {
$vars['meta_key'] = $_GET[$filter_id];
$vars['orderby'] = 'meta_value';
}
return $vars;
}
// (Optional) Make a custom meta field searchable from the admin order list search field
add_filter( 'woocommerce_shop_order_search_fields', 'shop_order_meta_search_fields', 10, 1 );
function shop_order_meta_search_fields( $meta_keys ){
foreach ( get_filter_shop_order_meta() as $meta_key => $label ) {
$meta_keys[] = $meta_key;
}
return $meta_keys;
}
Code goes in functions.php file of your active child theme (or active theme). Tested and works.

Woocommerce Is_Purchasable cause ajax error

I'm using the code below to modify the WooCommerce Is_Purchasable option so that, item Y is purchasable if item X is added to the cart.
But it gives ajax error when trying to add item Y to the cart.
Here's the code:
function aelia_get_cart_contents() {
$cart_contents = array();
/**
* Load the cart object. This defaults to the persistant cart if null.
*/
$cart = WC()->session->get( 'cart', null );
if ( is_null( $cart ) && ( $saved_cart = get_user_meta( get_current_user_id(), '_woocommerce_persistent_cart', true ) ) ) {
$cart = $saved_cart['cart'];
} elseif ( is_null( $cart ) ) {
$cart = array();
}
if ( is_array( $cart ) ) {
foreach ( $cart as $key => $values ) {
$_product = wc_get_product( $values['variation_id'] ? $values['variation_id'] : $values['product_id'] );
if ( ! empty( $_product ) && $_product->exists() && $values['quantity'] > 0 ) {
if ( $_product->is_purchasable() ) {
// Put session data into array. Run through filter so other plugins can load their own session data
$session_data = array_merge( $values, array( 'data' => $_product ) );
$cart_contents[ $key ] = apply_filters( 'woocommerce_get_cart_item_from_session', $session_data, $values, $key );
}
}
}
}
return $cart_contents;
}
// Step 1 - Keep track of cart contents
add_action('wp_loaded', function() {
// If there is no session, then we don't have a cart and we should not take
// any action
if(!is_object(WC()->session)) {
return;
}
// Product Y
global $y_cart_items;
$y_cart_items = 2986;
//Product X
global $x_cart_items;
$x_cart_items = array(
'297'
);
// Step 2
add_filter('woocommerce_is_purchasable', function($is_purchasable, $product) {
global $y_cart_items;
global $x_cart_items;
if( $product->id == $y_cart_items ) {
// make it false
$is_purchasable = false;
// get the cart items object
foreach ( aelia_get_cart_contents() as $key => $item ) {
// do your condition
if( in_array( $item['product_id'], $x_cart_items ) ) {
// Eligible product found on the cart
$is_purchasable = true;
break;
}
}
}
return $is_purchasable;
}, 10, 2);
}, 10);
// Step 3 - Explain customers why they can't add some products to the cart
add_filter('woocommerce_get_price_html', function($price_html, $product) {
if(!$product->is_purchasable() && is_product()) {
$price_html .= '<p>' . __('Add Product X to be able to purchase Product Y.', 'woocommerce') . '</p>';
}
return $price_html;
}, 10, 2);
How do I fix this? Thank you.

Categories