Move woocommerce variation price - woocommerce

I'm running my demo store under Woocommerce and I want to move price that shows up when you choose product variations to be right below Qty field and not between it and the last variation.
This is the code I tried inside my functions.php file so far but it didn't work:
// Move WooCommerce price
remove_action( 'woocommerce_single_product_summary', 'woocommerce_template_single_price', 10 );
add_action( 'woocommerce_single_product_summary', 'woocommerce_template_single_price', 25 );
Did I make a mistake somewhere?

The javascript file woocommerce/assets/js/frontend/add-to-cart-variation.js, responsible for displaying variable product prices, basically uses $('form').find('.single_variation'); to update the price, so if the .single_variation is outside of the form, it will not work.
So things like this do not work:
function move_variation_price() {
remove_action( 'woocommerce_single_variation', 'woocommerce_single_variation', 10 );
add_action( 'woocommerce_single_product_summary', 'woocommerce_single_variation', 5 );
}
add_action( 'woocommerce_before_add_to_cart_form', 'move_variation_price' );
That said, your best bet if you want to use it on top of your product, is to use the woocommerce_before_variations_form hook.
woocommerce_single_variation hook will bring up the add to cart button too, so you will have to hide that out with CSS:
form.variations_form.cart .single_variation_wrap:nth-child(1) .quantity {
display: none !important; /* important is necessary */
}
form.variations_form.cart .single_variation_wrap:nth-child(1) button {
display: none;
}
I know it hurts. But it's the "only" way.
Method 2
Ok, I was pissed with Method 1 and came up with this new method, that doesn't require any changes in PHP, only Javascript and CSS.
Javascript that will check if the variable product price changed, and will update the actual price div with the new value. This new price div can be anywhere, not only inside the form.
Javascript:
// Update price according to variable price
if (jQuery('form.variations_form').length !== 0) {
var form = jQuery('form.variations_form');
var variable_product_price = '';
setInterval(function() {
if (jQuery('.single_variation_wrap span.price span.amount').length !== 0) {
if (jQuery('.single_variation_wrap span.price span.amount').text() !== variable_product_price) {
variable_product_price = jQuery('.single_variation_wrap span.price span.amount').text();
jQuery('.single-product-summary p.price span.amount').text(variable_product_price);
}
}
}, 500);
}
CSS:
.single_variation_wrap .price {
display: none;
}
Final result:

This will move the variation price to below the quantity and just above the add to cart button. You may need to apply some CSS styles as needed.
function move_variation_price() {
remove_action( 'woocommerce_single_variation', 'woocommerce_single_variation', 10 );
add_action( 'woocommerce_after_add_to_cart_quantity', 'woocommerce_single_variation', 10 );
}
add_action( 'woocommerce_before_add_to_cart_form', 'move_variation_price' );
Since the woocommerce_after_add_to_cart_quantity hook was added in WooCommerce 3.0, in order to get the above code to be work in WooCommerce 2.6.x, you will need to override the single-product/add-to-cart/variation-add-to-cart-button.php template by saving it to your theme's woocommerce folder and then adding in the action hook. See below:
<?php
/**
* Single variation cart button
*
* #see https://docs.woocommerce.com/document/template-structure/
* #author WooThemes
* #package WooCommerce/Templates
* #version 2.5.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
global $product;
?>
<div class="woocommerce-variation-add-to-cart variations_button">
<?php if ( ! $product->is_sold_individually() ) : ?>
<?php woocommerce_quantity_input( array( 'input_value' => isset( $_POST['quantity'] ) ? wc_stock_amount( $_POST['quantity'] ) : 1 ) ); ?>
<?php endif; ?>
<?php do_action( 'woocommerce_after_add_to_cart_quantity' ); ?>
<button type="submit" class="single_add_to_cart_button button alt"><?php echo esc_html( $product->single_add_to_cart_text() ); ?></button>
<input type="hidden" name="add-to-cart" value="<?php echo absint( $product->id ); ?>" />
<input type="hidden" name="product_id" value="<?php echo absint( $product->id ); ?>" />
<input type="hidden" name="variation_id" class="variation_id" value="0" />
</div>

I think I have found a very simple solution.
Woocommerce launches a javascript event when the selection of variations has changed.
We can listen to that event and copy the .woocommerce-variation-price .woocommerce-Price-amount html wherever we want.
Next I leave you an example of my solution:
js
jQuery(function ($) {
/**
* Change Price Variation to correct position
*/
$('.variations_form').on('woocommerce_variation_has_changed', function () {
$('.wrap-price-variation').empty();
$('.wrap-price-variation').html($('.woocommerce-variation-price .woocommerce-Price-amount').html())
});
});
css
.woocommerce-variation .woocommerce-variation-price {
display: none;
}

You can use CSS:
.woocommerce div.product .summary.entry-summary {
display: flex;
flex-direction: column;
}
And order property to sort elements by the order you want:
.product_title.entry-title {
order: 1;
}
div.woo-short-description {
order: 2;
}
form.cart {
order: 3;
}
p.price {
order: 4;
}
div.quick_buy_container {
order: 5;
}
More information can be found at: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Flexible_Box_Layout/Ordering_Flex_Items

In case a more complete solution is searched for, this is how I solved the task at hand-
// Edit single page variable product pricing
add_filter( 'woocommerce_variable_price_html', 'wrap_variation_price_html', 9999, 2 );
function wrap_variation_price_html( $price, $product ) {
if( ! is_product() ) return;
$price = sprintf( __( '<span class="wrap-top-price">%1$s</span>', 'woocommerce' ), $price );
return $price;
}
add_action('wp_footer', 'update_variable_product_price', 999, 0);
function update_variable_product_price() {
if( ! is_product() ) return;
?>
<script>
jQuery(function ($) {
$(document).ready(function() {
var original_price_html = $('.wrap-top-price').html();
$('.entry-summary').on('click', '.reset_variations', function() {
$('.wrap-top-price').html(original_price_html);
});
$('.variations_form').on('woocommerce_variation_has_changed', function () {
if($('.woocommerce-variation-price .woocommerce-Price-amount').html().length) {
$('.wrap-top-price').empty();
$('.wrap-top-price').html($('.woocommerce-variation-price .woocommerce-Price-amount').html());
}
});
})
});
</script>
<?php
}
The first filter is adding a wrapper span around the price on top, whatever passed into, the min-price or the range.
Then the JS is getting added in the footer of the single product page. It's copying the initial value of the price on top to cache it which is used later. We listen to the woocommerce_variation_has_changed event and copy the dynamically added variation price element into the top price wrapper we added previously. Once the reset_variations button is clicked, we set the top price back to what it was on page load.

I have used this:
jQuery('form.variations_form #size').change(function(){
setTimeout(function() {
jQuery('.price-wrapper .price > .amount').text(jQuery('.woocommerce-variation-price .price > .amount').text());
}, 800);
});
and works perfectly. The code about seems not to work.

I just wrote this using Lucas' answer above as the template, but utilizing the actual select ("drop-down") element's change-state in jQuery to accomplish it. It also takes into account the price-range default state to deliver a more complete-feeling solution.
CSS:
/* Hide variant prices since we have JS to show that in place of the range */
body.woocommerce .product .single_variation_wrap {
display: none !important;
}
JavaScript:
/* Product Detail Page: Variant Pricing Updater */
// Updates price according to variable price
jQuery(function() {
if (jQuery('form.variations_form').length !== 0) {
/* Set this to the ID of your variant select drop-down.
* Example: var $variant_selector = jQuery('#color'); */
var $variant_selector = jQuery('#color');
var variable_product_price = '';
var vpu_timeout;
// Get and save the original price range
var original_price_range = jQuery('.product .summary p.price').html();
$variant_selector.change(function() {
// Clear any previous timeouts to avoid possible stacking
clearTimeout(vpu_timeout);
// setTimeout so we don't get ahead of WooCommerce's JS
vpu_timeout = setTimeout(function(){
// If there's no value, set back to default.
if($variant_selector.val().length < 1) {
jQuery('.product .summary p.price').html(original_price_range);
return(false);
}
// Make sure we have a price to pull from
if (jQuery('.single_variation_wrap span.price').length !== 0) {
if ($variant_selector.val().length > 0) {
// Update the price HTML variable if different
variable_product_price = jQuery('.single_variation_wrap span.price').html();
// Then change the displayed price
jQuery('.product .summary p.price').html(variable_product_price);
return(true);
}
}
}, 5);
});
}
});

I found an easy solution to this.
I copied the woocommerce file woocommerce/single-product/add-to-cart/variable.php into my child theme and made some changes in that file.
Inside, at the start of the form tag, I moved the contents of the "woocommerce_single_variation" hook:
<div class="single_variation_wrap above_form">
<div class="woocommerce-variation single_variation"></div>
</div>
Javascript populates this div if placed inside the form tag (so keep it in there!) I then disabled the hook, as all it does is echo these divs. In my functions.php file I did the following:
function move_variation_price() {
remove_action( 'woocommerce_single_variation', 'woocommerce_single_variation', 10 );
}
add_action( 'woocommerce_before_add_to_cart_form', 'move_variation_price' );

Related

Add custom CSS class to "add to cart" button on WooCommerce shop and archives pages based on certain condition

I am trying to disable "add to cart" button on WooCommerce shop and archives pages if product's quantity stock is zero or less.
I don't want to hide it so I came up with the code below.
My issue is that code doesn't add style to element, it replaces the whole button code inside html tree so button is not displayed at all.
Any ideas on how to overcome the issue?
add_action( 'woocommerce_loop_add_to_cart_link', 'remove_price_from_variable_products', 9 );
function remove_price_from_variable_products() {
global $product;
if( $product->get_stock_quantity() <= 0 ) {
?>
<style>
.add_to_cart_button {
cursor: not-allowed !important;
}
</style>
<?php
add_action( 'woocommerce_after_shop_loop_item', 'custom_content_addtocart_button', 100 );
}
}
function custom_content_addtocart_button() {
echo '<br/><div class="button-notice">Contact Us for more information</div>';
}
To add/edit/remove CSS classes from the existing add to cart button on WooCommerce shop and archives pages you can use the woocommerce_loop_add_to_cart_args filter hook
So you get:
function action_woocommerce_loop_add_to_cart_args( $wp_parse_args, $product ) {
// Initialize
$custom_class = '';
// Certain condition, can be anything, in this specific case for the stock quantity. ADAPT TO YOUR NEEDS
if ( $product->get_stock_quantity() <= 0 ) {
$custom_class = 'button-not-allowed';
}
// NOT empty (from here on, no need to make any changes to my answer)
if ( ! empty ( $custom_class ) ) {
// Class
$wp_parse_args['class'] = implode(
' ',
array_filter(
array(
'button' . ' ' . $custom_class,
'product_type_' . $product->get_type(),
$product->is_purchasable() && $product->is_in_stock() ? 'add_to_cart_button' : '',
$product->supports( 'ajax_add_to_cart' ) && $product->is_purchasable() && $product->is_in_stock() ? 'ajax_add_to_cart' : '',
)
)
);
}
return $wp_parse_args;
}
add_filter( 'woocommerce_loop_add_to_cart_args', 'action_woocommerce_loop_add_to_cart_args', 10, 2 );
Note: the $product->get_stock_quantity() function is best used in combination with $product->managing_stock(), but this depends on your needs
Then apply the following CSS
.button-not-allowed {
cursor: not-allowed !important;
}

Remove 'Move to Trash' under woocommerce order action for shop manager

According to this post, custom post type, hide or disable the trash button in publish meta box, this is specifically removing for post. I couldn't find any post solutions relating to remove 'Move to trash' under woocommerce order action. Does this code apply same to removing this button on woocommerce?
function my_custom_admin_styles() {
?>
<style type="text/css">
.post-type-inhoud form #delete-action{
display:none;
}
</style>
<?php
}
add_action('admin_head', 'my_custom_admin_styles');
How do I apply this to woocommerce removing 'move to trash' button under order action and only specifically for shop manager.
To prevent deleting post/order/custom post is capability per user role. To prevent shop_manager role from deleting orders you must remove that capability. All other solutions will visualy work but someone who knows his stuff still can delete order if he wants. So place the following function in your functions.php
function wp23958_remove_shop_manager_capabilities() {
$shop_manager = get_role( 'shop_manager' ); // Target user role
//List of capabilities which we want to edit
$caps = array(
'delete_shop_orders',
'delete_private_shop_orders',
'delete_published_shop_orders',
'delete_others_shop_orders',
);
// Remove capabilities from our list
foreach ( $caps as $cap ) {
$shop_manager->remove_cap( $cap );
}
}
add_action( 'init', 'wp23958_remove_shop_manager_capabilities' );
Bonus How to know what capabilities current user have
function wp32985_check_user_capabilities() {
$data = get_userdata( get_current_user_id() );
if ( is_object( $data) ) {
$current_user_caps = $data->allcaps;
// print it to the screen
echo '<pre>' . print_r( $current_user_caps, true ) . '</pre>';
}
}
add_action( 'init', 'wp32985_check_user_capabilities' );
The correct CSS class to target is: .order_actions li #delete-action
The following will hide it for all backend users (admins, shop managers, etc.):
add_action( 'admin_head', 'bbloomer_css_wp_admin' );
function bbloomer_css_wp_admin() {
echo '<style>.order_actions li #delete-action { display: none; }</style>';
}
If you only wanted to apply this CSS for shop managers (and therefore not for admins for example), you need conditional logic, and specifically you need to use the inbuilt WooCommerce function wc_current_user_has_role():
add_action( 'admin_head', 'bbloomer_css_wp_admin' );
function bbloomer_css_wp_admin() {
if ( ! wc_current_user_has_role( 'shop_manager' ) ) return;
echo '<style>.order_actions li #delete-action { display: none; }</style>';
}

Showing 'Please choose product options…' on checkout page after clicks custom checkout button

I have created custom checkout button in single product page. It is working fine.But after selected the variation with checkout button,it redirects to the checkout page with this error 'Please choose product options…'.
This is my code
function add_content_after_addtocart() {
global $woocommerce;
// get the current post/product ID
$current_product_id = get_the_ID();
// get the product based on the ID
$product = wc_get_product( $current_product_id );
// get the "Checkout Page" URL
$checkout_url = WC()->cart->get_checkout_url();
// run only on simple products
if( $product->is_type( 'variable' ) ){
?>
<script>
jQuery(function($) {
<?php /* if our custom button is clicked, append the string "&quantity=", and also the quantitiy number to the URL */ ?>
// if our custom button is clicked
$(".custom-checkout-btn").on("click", function() {
// get the value of the "href" attribute
$(this).attr("href", function() {
// return the "href" value + the string "&quantity=" + the current selected quantity number
return this.href + '&quantity=' + $('input.qty').val();
});
});
});
</script>
<?php
echo '<div class="col-sm-6"><div class="buy_now"><a href="'.$checkout_url.'?add-to-cart='.$current_product_id.'" class="single_add_to_cart_button buy_now_button button alt disabled custom-checkout-btn ajax_add_to_cart" >Buy Now</a></div></div><div class="clearfix"></div>';
?>
<?php
}
else if( $product->is_type( 'simple' ) ){
echo '</div><div class="col-sm-6"><div class="p-t-35"></div><div class="buy_now">Buy Now</div></div><div class="clearfix"></div>';
}
}
add_action( 'woocommerce_after_add_to_cart_button', 'add_content_after_addtocart' );
Please help me..
I had the same issue and I had contacted WooThemes support team and they said that
"We limit the amount of variations we show on the front end for speed.
But sometimes you need more than 36 variations, so we offer that
filter to override that limitation."
Please add this code below in the functions.php file.
function custom_wc_ajax_variation_threshold( $qty, $product )
{
return 100;
}
add_filter( 'woocommerce_ajax_variation_threshold', 'custom_wc_ajax_variation_threshold', 100, 2 );

Removing Continue Shopping button from Added to Cart Notice

Currently when someone adds a product to our website it says: "X Product" has been added to your cart | and then has a "Continue Shopping" button in that notice that is on the right.
Since we only sell 2 products we want to remove the continue shopping button completely but still say the rest of the message, and keep "X Product" as a link.
I've been using the following code (but it replaces Continue Shopping with Checkout and I'd prefer to just remove the button completely instead). I just can't figure out how to remove the button but still keep the rest of the message exactly the same:
add_filter( 'woocommerce_continue_shopping_redirect', 'my_changed_woocommerce_continue_shopping_redirect', 10, 1 );
function my_changed_woocommerce_continue_shopping_redirect( $return_to ){
$return_to = wc_get_page_permalink( 'checkout' );
return $return_to;
}
add_filter( 'wc_add_to_cart_message_html', 'my_changed_wc_add_to_cart_message_html', 10, 2 );
function my_changed_wc_add_to_cart_message_html($message, $products){
if (strpos($message, 'Continue shopping') !== false) {
$message = str_replace("Continue shopping", "Checkout", $message);
}
return $message;
}
Use preg_replace to find the string containing the full link and return the new message with all the original HTML minus the link.
add_filter('wc_add_to_cart_message_html','remove_continue_shoppping_button',10,2);
function remove_continue_shoppping_button($message, $products) {
if (strpos($message, 'Continue shopping') !== false) {
return preg_replace('/<a.*<\/a>/m','', $message);
} else {
return $message;
}
}
/* Start Disable Continue Shopping Message after Add to Cart
*/
add_filter( 'wc_add_to_cart_message', function( $string, $product_id = 0 ) {
$start = strpos( $string, '<a href=' ) ?: 0;
$end = strpos( $string, '</a>', $start ) ?: 0;
return substr( $string, $end ) ?: $string;
});
/* End Disable Continue Shopping Message after Add to Cart
*/
If anyone is interested the following code fixed it perfectly, just replace id-407 with whatever page id your Cart page is:
/* Remove Continue Shopping Button Add Cart */
body.page-id-407 .woocommerce-message .button {
display: none
}

Add "Select All" to Post Categories. Option to add new post to all categories in a custom taxonomy?

I have a custom taxonomy of US states for a custom post type. Currently when adding a new post to this custom post type I have to manually check all 50 states (if applicable) is there a way to add a button inside of the custom taxonomy metabox that when pressed would check all of the boxes to assign the post to all 50 states?
So this is how I added it in:
I added this code into functions.php:
//Add Script to CPT Page
add_action( 'admin_print_scripts-post-new.php', 'portfolio_admin_script', 11 );
add_action( 'admin_print_scripts-post.php', 'portfolio_admin_script', 11 );
function portfolio_admin_script() {
global $post_type;
if( 'counselor' == $post_type )
wp_enqueue_script( 'portfolio-admin-script', get_stylesheet_directory_uri() . '/js/counselor.js' );
}
function style_state_button() {
echo '<style type="text/css">
#select-all-states-btn {
margin-top: 15px;
}
#statesserved-adder h4 {
display: none;
}
</style>';
}
add_action('admin_head', 'style_state_button');
This Calls a javascript file called counselor.js. In that file was this:
jQuery(document).ready(function() {
jQuery('div#statesserved-adder ').prepend('<input type="button" class="button" id="select-all-states-btn" value="Select All States" />');
jQuery("#select-all-states-btn").click(function() {
var checkBoxes = jQuery('input[name="tax_input[statesserved][]"]');
checkBoxes.attr("checked", !checkBoxes.attr("checked"));
});
});

Resources