How add custom column to woocommerce shopping cart and then add info of some input from this column to order, checkout page and to email?
Actually i need add friends list from buddypress to each product row(price must depends on how many friends checked).
Here i found suggestion, but it`s partly helpfull WooCommerce: Add input field to every item in cart
wp community also keep silence
http://wordpress.org/support/topic/woocommerce-custom-column-in-cart?replies=1
what i do - its just add list of avalaible friends and no idea how can I save data on update cart or proceed.
if ( bp_has_members( 'user_id=' . bp_loggedin_user_id() ) ){
function ggUserFrom(){
$arrUsers = array();
while ( bp_members() ){
bp_the_member();
$arrUsers[ bp_get_member_user_nicename() ] = bp_get_member_user_nicename();
}
return $arrUsers;
}
echo "<div class='friends-holder'>";
foreach ( ggUserFrom() as $friend ){
echo '<p><input type="checkbox" name="cart['.$cart_item_key.'][friendsfromcart]" value="'.$friend.'">
<span>'.$friend.'</span></p>';
}
echo "</div>";
}
Im seek ANY info about this question.
Here, since user may choose multiple checkboxes, it may contain multiple values. Therefore, we have used "serialize" function.
for example,
add_filter( 'woocommerce_get_cart_item_from_session', 'wdm_get_cart_items_from_session', 1, 3 );
if(!function_exists('wdm_get_cart_items_from_session'))
{
function wdm_get_cart_items_from_session($item,$values,$key)
{
$item['custom_field_name'] = isset( $values['friendsfromcart'] )? serialize($values['friendsfromcart']) : '';
return $item;
}
}
While you are adding Order meta data, you can fetch individual values and add corresponding keys as follows,
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)
{
$user_custom_values = unserialize($values['friendsfromcart']);
if(count($user_custom_values) > 0)
{
foreach($user_custom_values as $single_value)
{
wc_add_order_item_meta($item_id,ucfirst($single_value),single_value);
}
}
}
since order meta will be sent in E - mail.
Thank for reply and - yes, variable isset in checkout page, but his empty in
var_dump($_POST)...
...[custom_field_name] =>
...
and ofc is empty in email.
Perhaps i incorrect send it ?
name="friendsfromcart"
or send values its a array and must send:
name="friendsfromcart[]"
or
name="[friendsfromcart]"
or need session key
name="cart['.$cart_item_key.'][friendsfromcart]" ?
It works, array in cart but something wrong with unserialize:
add_action( 'init', 'update_cart_action', 9);
function update_cart_action() {
global $woocommerce;
if ( ( ! empty( $_POST['update_cart'] ) || ! empty( $_POST['proceed'] ) ) && $woocommerce->verify_nonce('cart')) {
$cart_totals = isset( $_POST['cart'] ) ? $_POST['cart'] : '';
if ( sizeof( $woocommerce->cart->get_cart() ) > 0 ) {
foreach ( $woocommerce->cart->get_cart() as $cart_item_key => $values ) {
if ( isset( $cart_totals[ $cart_item_key ]['friendsfromcart'] ) ) {
$woocommerce->cart->cart_contents[ $cart_item_key ]['friendsfromcart'] = $cart_totals[ $cart_item_key ]['friendsfromcart'];
}
}
}
}
}
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){
$item['friendsfromcart'] = isset( $values['friendsfromcart'] )? serialize($values['friendsfromcart']) : '';
return $item;
}
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){
$user_custom_values = unserialize($values['friendsfromcart']);
if(count($user_custom_values) > 0){
foreach($user_custom_values as $single_value){
wc_add_order_item_meta($item_id,ucfirst($single_value),single_value);
}
}
}
Related
As you can tell from the title I try to "automatically pre select the first in stock value in WooCommerce variable product dropdown".
Currently, I'm using the code below to pre-select the first variant, but I need this code to select the first IN STOCK variant. Any advice?
function fun_select_default_option( $args ) {
// Check the count of available options in dropdown
if ( count($args['options']) > 0 ) {
$args['selected'] = $args['options'][0];
}
return $args;
}
add_filter( 'woocommerce_dropdown_variation_attribute_options_args', 'fun_select_default_option', 10, 1 );
The callback function only contains $args, via $args['product'] however, you have access to the product variable object.
Based on that you can loop through the visible children. By the variation ID's you can get the product variation object(s).
With the use of get_stock_status() you can then determine the status.
So you get:
function filter_woocommerce_dropdown_variation_attribute_options_args( $args ) {
// Check the count of available options in dropdown
if ( count( $args['options'] ) > 0 ) {
// Initialize
$option_key = '';
// Get WC_Product_Variable Object
$product = $args['product'];
// Is a WC Product Variable
if ( is_a( $product, 'WC_Product_Variable' ) ) {
// Loop through children
foreach ( $product->get_visible_children() as $key => $variation_id ) {
// Get product variation object
$variation = wc_get_product( $variation_id );
// Is a WC Product Variation
if ( is_a( $variation, 'WC_Product_Variation' ) ) {
// Get stock status
$product_stock_status = $variation->get_stock_status();
// In stock
if ( $product_stock_status == 'instock' ) {
// Set key
$option_key = $key;
// Break
break;
}
}
}
}
// Finds whether a variable is a number
if ( is_numeric( $option_key ) ) {
// Selected
$args['selected'] = $args['options'][$option_key];
}
}
return $args;
}
add_filter( 'woocommerce_dropdown_variation_attribute_options_args', 'filter_woocommerce_dropdown_variation_attribute_options_args', 10, 1 );
Alternatively you can also use get_available_variations() and $variation['is_in_stock'].
The big difference with the above answer is that this answer will also see backorders, where backorder is allowed as instock because it does not take the specific stock status into account.
So then you get:
function filter_woocommerce_dropdown_variation_attribute_options_args( $args ) {
// Check the count of available options in dropdown
if ( count( $args['options'] ) > 0 ) {
// Initialize
$option_key = '';
// Get WC_Product_Variable Object
$product = $args['product'];
// Is a WC Product Variable
if ( is_a( $product, 'WC_Product_Variable' ) ) {
// Get an array of available variations for the current product
foreach ( $product->get_available_variations() as $key => $variation ) {
// Is in stock
$is_in_stock = $variation['is_in_stock'];
// True
if ( $is_in_stock ) {
// Set key
$option_key = $key;
// Break
break;
}
}
}
// Finds whether a variable is a number
if ( is_numeric( $option_key ) ) {
// Selected
$args['selected'] = $args['options'][$option_key];
}
}
return $args;
}
add_filter( 'woocommerce_dropdown_variation_attribute_options_args', 'filter_woocommerce_dropdown_variation_attribute_options_args', 10, 1 );
I am using WooCommerce with WPML plugin. I want to implement a feature on checkout when a customer under certain conditions can have an upgrade of his product but keeping the old product price.
The products are variable with many variable attributes.
So, more specifically, what I want is if a customer has selected a specific product variation with x price on checkout (under a certain condition) I could change his cart item with another product's variation but keep the x price.
What I tried first is to change only the name of the product using woocommerce_order_item_name hook but the change doesn't follow on the order. This is important because some order data are then sent to an API.
Afterwards I used "Changing WooCommerce cart item names" answer code, which worked perfectly for my purpose until I installed WPML. For some reason the WC_Cart method set_name() doesn't work with WPML. I opened a support thread but they still can't find a solution.
Can anyone suggest any other solution?
Update
I have tried an approach where I remove the product item on cart and then I add the one I need. After I use set_price() to change the price of the newly added item. The removal/addition seems to be working but the price is not changed on one language and it is not applied on both languages after placing order.
This is the code I use:
function berrytaxiplon_change_product_name( $cart ) {
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return;
if ( did_action( 'woocommerce_before_calculate_totals' ) >= 2 )
return;
// Loop through cart items
foreach ( $cart->get_cart() as $cart_item_key => $cart_item ) {
// Get an instance of the WC_Product object
$product = $cart_item['data'];
// Get the product name (Added Woocommerce 3+ compatibility)
$product_id = method_exists( $product, 'get_parent_id' ) ? $product->get_parent_id() : $product->post->post_parent;
if ( ICL_LANGUAGE_CODE == 'en') {
if (isset($cart_item['s-member-level']) && $cart_item['s-member-level'] == 3 && $product_id == 12) {
$new_product = wc_get_product( 82 );
$atrributes = $product->get_attributes('view');
foreach ($atrributes as $atrribute_key => $atrribute_value) {
$new_attributes['attribute_' . $atrribute_key] = strtolower($atrribute_value);
}
$new_variation_id = find_matching_product_variation_id(82, $new_attributes);
$cart->remove_cart_item( $cart_item_key );
$cart->add_to_cart( 82, 1, $new_variation_id, $new_attributes, $cart_item );
foreach ( WC()->cart->get_cart() as $new_item ) {
$new_item['data']->set_price( $cart_item['s-fare'] );
}
}
} else {
if (isset($cart_item['s-member-level']) && $cart_item['s-member-level'] == 3 && $product_id == 282) {
$new_product = wc_get_product( 303 );
$atrributes = $product->get_attributes('view');
foreach ($atrributes as $atrribute_key => $atrribute_value) {
$new_attributes['attribute_' . $atrribute_key] = strtolower($atrribute_value);
}
$new_variation_id = find_matching_product_variation_id(303, $new_attributes);
$cart->remove_cart_item( $cart_item_key );
$cart->add_to_cart( 303, 1, $new_variation_id, $new_attributes, $cart_item );
foreach ( WC()->cart->get_cart() as $new_item ) {
$new_item['data']->set_price( $cart_item['s-fare']);
}
}
}
}
}
add_action( 'woocommerce_before_calculate_totals', 'berrytaxiplon_change_product_name', 10, 1 );
Any idea why the set_price() method is not applied?
Update 2
WPMl uses 'woocommerce_before_calculate_totals' and overrides the action added on functions.php
WPML support provided a solution using 3 filters:
https://wpml.org/forums/topic/cant-use-set_name-method-for-the-product-object-on-checkout/#post-3977153
So this is a code that I am using in one of my projects to add a product variation to cart based off of some filters and the selected product:
$product = new WC_Product($product_id); //The main product whose variation has to be added
$product_name = $product->get_name(); //Name of the main product
$quantity = sanitize_text_field($cData['quantity']); //You can set this to 1
$variation_id = sanitize_text_field($cData['variation_id']); //I had the variation ID from filters
$variation = array(
'pa_duration' => sanitize_text_field($cData['duration']) //The variation slug was also available for me.
);
$cart_item_data = array('custom_price' => sanitize_text_field($custom_price));
$cart = WC()->cart->add_to_cart( (int)$product_id, (int)$quantity, (int)$variation_id, $variation, $cart_item_data ); //This will add products to cart but with the actual price of the variation being added and meta data holding the custom price.
WC()->cart->calculate_totals();
WC()->cart->set_session();
WC()->cart->maybe_set_cart_cookies();
Then you need to do a check on before cart totals are calculated and set the price to custom price like this:
function woocommerce_custom_price_to_cart_item( $cart_object ) {
if( !WC()->session->__isset( "reload_checkout" )) {
foreach ( $cart_object->cart_contents as $key => $value ) {
if( isset( $value["custom_price"] ) ) {
$value['data']->set_price($value["custom_price"]);
}
}
}
}
add_action( 'woocommerce_before_calculate_totals', 'woocommerce_custom_price_to_cart_item', 99 );
The code provided from Faham is very helpful but the page-template that leads to checkout is already over-complicated so I focused to use his logic on the 'woocommerce_before_calculate_totals' hook I am trying all along.
So instead of trying to change the name I remove the item and add the new one. Then calling a new loop I set the price to be of the item that was removed.
function berrytaxiplon_change_product_name( $cart ) {
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return;
if ( did_action( 'woocommerce_before_calculate_totals' ) >= 2 )
return;
// Loop through cart items
foreach ( $cart->get_cart() as $cart_item_key => $cart_item ) {
// Get an instance of the WC_Product object
$product = $cart_item['data'];
// Get the product name (Added Woocommerce 3+ compatibility)
$product_id = method_exists( $product, 'get_parent_id' ) ? $product->get_parent_id() : $product->post->post_parent;
if ( ICL_LANGUAGE_CODE == 'en') {
if (isset($cart_item['s-member-level']) && $cart_item['s-member-level'] == 3 && $product_id == 12) {
// SET THE NEW NAME
$new_product = wc_get_product( 82 );
$atrributes = $product->get_attributes('view');
foreach ($atrributes as $atrribute_key => $atrribute_value) {
$new_attributes['attribute_' . $atrribute_key] = strtolower($atrribute_value);
}
$new_variation_id = find_matching_product_variation_id(82, $new_attributes);
$cart->remove_cart_item( $cart_item_key );
$cart->add_to_cart( 82, 1, $new_variation_id, $new_attributes, $cart_item );
foreach ( WC()->cart->get_cart() as $new_item ) {
$new_item['data']->set_price( get_post_meta( $cart_item['variation_id'], '_price', true ) );
}
}
} else {
if (isset($cart_item['s-member-level']) && $cart_item['s-member-level'] == 3 && $product_id == 282) {
// SET THE NEW NAME
$new_product = wc_get_product( 303 );
$atrributes = $product->get_attributes('view');
foreach ($atrributes as $atrribute_key => $atrribute_value) {
$new_attributes['attribute_' . $atrribute_key] = strtolower($atrribute_value);
}
$new_variation_id = find_matching_product_variation_id(303, $new_attributes);
$cart->remove_cart_item( $cart_item_key );
$cart->add_to_cart( 303, 1, $new_variation_id, $new_attributes, $cart_item );
foreach ( WC()->cart->get_cart() as $new_item ) {
$new_item['data']->set_price( get_post_meta( $cart_item['variation_id'], '_price', true ) );
}
}
}
}
}
add_action( 'woocommerce_before_calculate_totals', 'berrytaxiplon_change_product_name', 10, 1 );
I use the function below to match the attributes taken from the question WooCommerce: Get Product Variation ID from Matching Attributes
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
);
}
I am a little skeptical using add_to_cart() and a second foreach() inside the $cart_item. But I tested and it seems to work without errors.
Update
Actually there is an issue with this code (or with WPML again). It seems that set_price() is not applied on the secondary language. Yet if I reload the checkout page an send the data again the new price is applied.
I have a metabox (one checkbox) , in posts, to check if a post will be featured..
My code:
function add_featured_post_checkbox() {
add_meta_box(
'custom_featured_meta',
'Featured post for Sidebar',
'featured_post_checkbox_callback',
'Post',
'side',
'high'
);
} add_action( 'add_meta_boxes', 'add_featured_post_checkbox' );
Callback function:
function featured_post_checkbox_callback( $post ) {
wp_nonce_field( 'custom_save_data' , 'custom_featured_nonce' );
$featured = get_post_meta($post->ID, '_featured_post', true);
echo "<label for='_featured_post'>".__('Is Featured? ', 'foobar')."</label>";
echo "<input type='checkbox' name='_featured_post' id='featured_post' value='1' " . checked(1, $featured) . " />";
}
Save function:
function custom_save_data( $post_id ) {
if( ! isset( $_POST['custom_featured_nonce'] ) ){
return;
}
if( ! wp_verify_nonce( $_POST['custom_featured_nonce'], 'custom_save_data') ) {
return;
}
if( defined('DOING_AUTOSAVE') && DOING_AUTOSAVE ){
return;
}
if( ! current_user_can( 'edit_post', $post_id ) ) {
return;
}
if( ! isset( $_POST['_featured_post'] ) ) {
return;
}
$my_data_featured = sanitize_text_field( $_POST['_featured_post'] );
update_post_meta( $post_id, '_featured_post', $my_data_featured );
} add_action( 'save_post', 'custom_save_data' );
This code works well only when I want to make a post featured..
For example if a post is not checked as featured and I select the choice then database updated perfect (with value 1 in post_meta) and the ckeckbox has the tick on box
But after, If I try to uncheck and save the post then the ckeckbox is again with the tick and nothing changed in database..
I try to find a solution for days on stackoverflow and general in web but I can't find.
Please help me
Thank you
When a checkbox is unchecked, $_POST will not contain the key _featured_post. Therefore, in your save callback, you need to change your strategy. You do not want to bail out if the key is not set. Rather, you either want to save a 0 or delete the post meta. Let's go with the later option.
function custom_save_data( $post_id ) {
if( ! isset( $_POST['custom_featured_nonce'] ) ){
return;
}
if( ! wp_verify_nonce( $_POST['custom_featured_nonce'], 'custom_save_data') ) {
return;
}
if( defined('DOING_AUTOSAVE') && DOING_AUTOSAVE ){
return;
}
if( ! current_user_can( 'edit_post', $post_id ) ) {
return;
}
if ( isset( $_POST['_featured_post'] ) ) {
update_post_meta( $post_id, '_featured_post', 1 );
} else {
delete_post_meta( $post_id, '_featured_post' );
}
}
Notice that you do not need to use sanitize_text_field(). Why? Because the value will only be a 1. That's it. Here we can hardcode that value.
Another Problem I Saw
Running your code I saw that upon being checked, the HTML for checked=checked is being rendered as text. Why? Because running the function checked() will echo it out to the browser unless you tell it not to. You'd have to check your code to:
checked(1, $featured, false);
That's happening because of return after the check if _featured_post is set in $_POST array. It's not set when you do not check the checkbox. Your approach here should be next
if (isset($_POST['_featured_post']))
{
update_post_meta($object_id, '_featured_post', 1); // can only be 1 when checked anyway
}
else
{
delete_post_meta($object_id, '_featured_post');
}
Is there a conditional function or another solution to check if the products are currently filtered?
Something like this would be great:
if( is_filtered() ) echo 'Filters active';
Amazing would be if the function returns the number of active filters (or a array) or false.
Thanks to David Chandra Purnama who pushed me in the right direction here's a very simple function to use:
function active_woocommerce_filters() {
// for older WC versions
// global $_chosen_attributes;
$_chosen_attributes = WC_Query::get_layered_nav_chosen_attributes();
return count( $_chosen_attributes );
}
The function returns the number of active filters so it can be used like this:
if( active_woocommerce_filters() ) {
echo str_replace( '%s', active_woocommerce_filters(), 'There are %s filters active' );
} else {
echo 'There are no filters active';
}
EDIT:
As Artur Czyżewski pointed out the $_chosen_attributes global variable is not available in his installation. This is most likely due to changes to WooCommerce and probably affect all newer versions, so i've updated the active_woocommerce_filters function above.
You can check using global $_chosen_attributes;
WooCommerce "Layered Nav Filters" only displayed if filters is active. you can check the code "includes/widgets/class-wc-widget-layered-nav-filters.php":
global $_chosen_attributes;
if ( ! is_post_type_archive( 'product' ) && ! is_tax( get_object_taxonomies( 'product' ) ) ) {
return;
}
// Price
$min_price = isset( $_GET['min_price'] ) ? esc_attr( $_GET['min_price'] ) : 0;
$max_price = isset( $_GET['max_price'] ) ? esc_attr( $_GET['max_price'] ) : 0;
if ( 0 < count( $_chosen_attributes ) || 0 < $min_price || 0 < $max_price ) {
/* Your Code Here. */
}
is_filtered() is a WooCommerce built-in function.
It returns true when filtering products using layered nav or price sliders.
Use case here
if ( is_filtered() ) {
echo esc_html__('Some filters are active.', 'text-domain');
} else {
echo esc_html__('No filters are active.', 'text-domain');
}
what I am trying to do is creating custom coupon type with custom programmatically discount in woocommerce using hooks. This what my code looks like
//add new coupon type called "custom_discount"
function custom_discount_type( $discount_types ) {
$discount_types['custom_discount'] =__( 'custom discount', 'woocommerce' );
return $discount_types;
}
// add the hooks
add_filter( 'woocommerce_coupon_discount_types', 'custom_discount_type',10, 1);
//function to get coupon amount for "custom_discount"
function woocommerce_coupon_get_discount_amount($discount, $discounting_amount, $cart_item, $single, $coupon) {
if ($coupon->type == 'custom_discount'){ //if $coupon->type == 'fixed_cart' or 'percent' or 'fixed_product' or 'percent_product' The code Works
$discount = 85;
return $discount;
} else {
return $discount;
}
}
//add hook to coupon amount hook
add_filter('woocommerce_coupon_get_discount_amount', 'woocommerce_coupon_get_discount_amount', 10, 5);
the coupon always return with 0(Zero) discount amount. Am I missing something.
Thanks for helping =)
Ok so this topic lead me on the road to making my own coupon type so I thought I should answer it to help others.
I tried out your code and was wondering why any normal coupon showed "-£xx" before the remove button, but the custom coupon type just was lank, no minus, no pound sign jut empty. Turns out it's an issue with how WooCommerce validates the coupon on cart products. It will become active if the coupon is valid regardless if the item in the cart are.
Your coupon needs to be either valid and valid for a product, or valid for the entire cart. However when Woocommerce checks to see if your product is valid for the product it ONLY checks if the coupon type is fixed_product or 'percent_product'. If it's anything else it runs the woocommerce_coupon_is_valid_for_product hook with the existing data but defaulting to false, so your woocommerce_coupon_get_discount_amount hook will not fire.
So you need to hook into woocommerce_coupon_is_valid_for_product and validate the product to make sure it's valid. In my case I just copied the default valid function and changes $this to $coupon.
// Check if coupon is valid for product
add_filter('woocommerce_coupon_is_valid_for_product', 'woocommerce_coupon_is_valid_for_product', 10, 4);
function woocommerce_coupon_is_valid_for_product($valid, $product, $coupon, $values){
if ( ! $coupon->is_type( array( 'bogof' ) ) ) {
return $valid;
}
$product_cats = wp_get_post_terms( $product->id, 'product_cat', array( "fields" => "ids" ) );
var_dump( $coupon->product_ids );
// Specific products get the discount
if ( sizeof( $coupon->product_ids ) > 0 ) {
if ( in_array( $product->id, $coupon->product_ids ) || ( isset( $product->variation_id ) && in_array( $product->variation_id, $coupon->product_ids ) ) || in_array( $product->get_parent(), $coupon->product_ids ) ) {
$valid = true;
}
}
// Category discounts
if ( sizeof( $coupon->product_categories ) > 0 ) {
if ( sizeof( array_intersect( $product_cats, $coupon->product_categories ) ) > 0 ) {
$valid = true;
}
}
if ( ! sizeof( $coupon->product_ids ) && ! sizeof( $coupon->product_categories ) ) {
// No product ids - all items discounted
$valid = true;
}
// Specific product ID's excluded from the discount
if ( sizeof( $coupon->exclude_product_ids ) > 0 ) {
if ( in_array( $product->id, $coupon->exclude_product_ids ) || ( isset( $product->variation_id ) && in_array( $product->variation_id, $coupon->exclude_product_ids ) ) || in_array( $product->get_parent(), $coupon->exclude_product_ids ) ) {
$valid = false;
}
}
// Specific categories excluded from the discount
if ( sizeof( $coupon->exclude_product_categories ) > 0 ) {
if ( sizeof( array_intersect( $product_cats, $coupon->exclude_product_categories ) ) > 0 ) {
$valid = false;
}
}
// Sale Items excluded from discount
if ( $coupon->exclude_sale_items == 'yes' ) {
$product_ids_on_sale = wc_get_product_ids_on_sale();
if ( isset( $product->variation_id ) ) {
if ( in_array( $product->variation_id, $product_ids_on_sale, true ) ) {
$valid = false;
}
} elseif ( in_array( $product->id, $product_ids_on_sale, true ) ) {
$valid = false;
}
}
return $valid;
}
This question may be a little old, but there are two main coupon types, and you should add another filter based on the coupon you're adding. The filter for woocommerce_cart_coupon_types is for cart discounts, while woocommerce_product_coupon_types is for product discounts.
What you want to use is the second one woocommerce_product_coupon_types when you want WooCommerce to automatically validate the product/category_in and product/category_exclude fields etc.
You can still use the answer submitted by Chris for any manual validation, without using the above filters as required, however, please note that there are two validation functions to use/filter: $coupon->is_valid_for_cart() and $coupon->is_valid_for_product().