In my functions.php, I am trying to add a sold out message in the drop down menu for my product variants. So for example if I have shirt that has variants of small, medium and large and the large is out of stock, in the drop down menu the user should see the large option is disabled and contains a sold out message next to 'Large'. The other variants should remain active.
The issue I have with my code below is the following:
The code that is commented, this disables the correct product variant that is out of stock, but doesn't add the sold out message.
The active code does add the sold out message but it disables all product variants even though it's only one variant that is out of stock.
How can I fix the code to do what I need it to do?
/**
* Disable out of stock variations
* https://github.com/woocommerce/woocommerce/blob/826af31e1e3b6e8e5fc3c1004cc517c5c5ec25b1/includes/class-wc-product-variation.php
* #return Boolean
*/
// function wcbv_variation_is_active( $active, $variation ) {
// if( ! $variation->is_in_stock() ) {
// return false;
// }
// return $active;
// }
// add_filter( 'woocommerce_variation_is_active', 'wcbv_variation_is_active', 10, 2 );
add_action( 'woocommerce_variation_is_active', 'woocommerce_sold_out_dropdown' );
function woocommerce_sold_out_dropdown() {
?>
<script type="text/javascript">
jQuery( document ).bind( 'woocommerce_update_variation_values', function() {
jQuery( '.variations select option' ).each( function( index, el ) {
var sold_out = '<?php _e( 'sold out', 'woocommerce' ); ?>';
var re = new RegExp( ' - ' + sold_out + '$' );
el = jQuery( el );
if ( el.is( ':disabled' ) ) {
if ( ! el.html().match( re ) ) el.html( el.html() + ' - ' + sold_out );
} else {
if ( el.html().match( re ) ) el.html( el.html().replace( re,'' ) );
}
} );
} );
</script>
<?php
}
You can get what you need with this code (your code):
// disable options for unavailable variants
add_filter( 'woocommerce_variation_is_active', 'wcbv_variation_is_active', 10, 2 );
function wcbv_variation_is_active( $active, $variation ) {
if ( ! $variation->is_in_stock() ) {
return false;
}
return $active;
}
And now, to change the name of each individual option based on stock status you can use the woocommerce_variation_option_name hook like so:
add_filter( 'woocommerce_variation_option_name','add_stock_status_after_option_name', 10, 1 );
function add_stock_status_after_option_name( $option ) {
// only in frontend
if ( is_admin() ) {
return $option;
}
// get variable product object
global $product;
$variation_ids = $product->get_children();
foreach ( $variation_ids as $variation_id ) {
$variation = wc_get_product( $variation_id );
$variation_attributes = $variation->get_variation_attributes();
foreach ( $variation_attributes as $key => $value ) {
// slugify option name
$option_slug = sanitize_title( $option );
// check if the current option is equal to the variation slug
if ( $value == $option_slug ) {
// check if it is out of stock
if ( ! $variation->is_in_stock() ) {
return $option . ' (Out of stock)';
}
}
}
}
return $option;
}
The code has been tested and works correctly. It needs to be added to your theme's functions.php.
So i first created 2 fields, the custom field to display the calculated price and a hidden field that has the percentage value
/*Custom price field - to create custom field for simple product*/
add_action( 'woocommerce_product_options_pricing', 'wc_cost_product_field' );
function wc_cost_product_field() {
woocommerce_wp_text_input(
array(
'id' => 'cost_price',
'name' =>'cost_price',
'class' => 'wc_input_price short',
'desc_tip' => 'true',
'description' => __( 'Your Profit price is calculated as ((Vendor price x 15%) + 500 + vendor price )', 'woocommerce' ),
'label' => __( 'Total Price with Profit', 'woocommerce' ) . ' (' . get_woocommerce_currency_symbol() . ')'
)
);
woocommerce_wp_hidden_input(
array(
'id' => '_hidden_field[' . $post->ID . ']',
'name' => 'per',
'value' => 0.15
)
);
}
So i saved the custom field to always display
/*Custom price field - to save custom field for simple product*/
add_action( 'save_post', 'wc_cost_save_product' );
function wc_cost_save_product( $product_id ) {
// stop the quick edit interferring as this will stop it saving properly, when a user uses quick edit feature
if (wp_verify_nonce($_POST['_inline_edit'], 'inlineeditnonce'))
return;
// If this is a auto save do nothing, we only save when update button is clicked
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE )
return;
if ( isset( $_POST['cost_price'] ) ) {
if ( is_numeric( $_POST['cost_price'] ) )
update_post_meta( $product_id, 'cost_price', $_POST['cost_price'] );
} else delete_post_meta( $product_id, 'cost_price' );
$hidden = $_POST['_hidden_field'][ $post_id ];
if( ! empty( $hidden ) ) {
update_post_meta( $post_id, '_hidden_field', esc_attr( $hidden ) );
}
}
?>
Then i added the script that will call and trigger the value then render it to the custom field
<script>
const calculateprofit_price = (profitPrice, percent) => {
profitPrice = parseFloat(profitPrice);
percent = parseFloat(pecent);
return ((profitPrice * percent) + 500 + profitPrice).toFixed(2); // profit price
}
const $price = $('input[name="_regular_price"]'),
$per = $('input[name="per"]'),
$profit_price = $('input[name="cost_price"]');
$price.add( $per ).on('input', () => { // price and percent inputs events
let profit_price = $price.val(); // Default to profit price
if ( $per.val().length ) { // if value is entered- calculate profit_price
profit_price = calculateprofit_price($price.val(), $per.val());
}
$profit_price.val( profit_price );
});
$price.trigger('input');
</script>
I have checked online to see available codes to help but to no avail, pls help.
This is an image of what I'm trying to achieve
displaying calculated regular price in custom field
Good afternoon devs, I developed a favorite system using Wordpress and php, it works as follows, clicking on the add favorites icon I send an ajax request to php with the favorite id and the user id logged in. php processes this by saving in user_meta the information with number of favorite users and in the logged user an array with the ids of the favorites. Anyway, the question is, the system is working, but whenever I develop something I keep wondering if there is a way to improve the code, if there is a way to do it in a better way, I will leave my code here.
All this for learning ok?
HTML
<div class="stats -favorites">
<a class="icon -click <?php echo is_favorite(get_current_user_id(), $user_ID) ? 'fa' : 'far' ?> fa-heart" data-js="favorite" data-favorite="<?php echo $user_ID; ?>" data-user="<?php echo get_current_user_id(); ?>"></a>
<span class="label" data-js="favorite_label">
<?php echo get_favorites_num( $user_ID ); ?>
</span>
<span class="value">favoritos</span>
</div>
JS
function setFavorite(userid, favoriteid) {
var favorite_field = $('[data-js="favorite"]');
var favorite_label = $('[data-js="favorite_label"]');
$.ajax({
url : appmeninas_ajax_params.ajaxurl,
data : {
'action' : 'setfavorite',
'userid' : userid,
'favoriteid' : favoriteid,
},
dataType : 'json',
type : 'POST',
cache : false,
beforeSend : function( xhr ) {
favorite_field.removeClass('far fa fa-heart').addClass('fas fa-circle-notch fa-spin');
},
success : function( data ) {
var icon = (data.is_favorite) ? 'fa fa-heart' : 'far fa-heart';
favorite_field.removeClass('fas fa-circle-notch fa-spin');
favorite_field.addClass(icon);
favorite_label.html('');
favorite_label.append(data.favorites_num);
}
});
};
$('[data-js=favorite]').click(function() {
var favoriteid = $(this).data('favorite');
var userid = $(this).data('user');
setFavorite(userid, favoriteid);
});
PHP
function setfavorite() {
$userid = $_POST['userid'];
$favoriteid = $_POST['favoriteid'];
// user require favorite
$favorites_list = get_field( 'favorites_list', 'user_' .$userid );
$favorites_num = get_field( 'favorites', 'user_' .$favoriteid );
if ( !$favorites_list ) {
$favorites_list = [];
}
// profile favorite
if ( in_array( $favoriteid, $favorites_list ) ) {
$favorites_num--;
$tmp = array_search( $userid, $favorites_list );
array_splice( $favorites_list, $tmp, 1 );
$is_favorite = false;
} else {
$favorites_num++;
$favorites_list[] = $favoriteid;
$is_favorite = true;
}
// set favorite counter
update_user_meta( $favoriteid, 'favorites', $favorites_num );
// set favorite list
update_user_meta( $userid, 'favorites_list', $favorites_list );
echo json_encode( array(
'favorites_num' => $favorites_num,
'favorites_list' => $favorites_list,
'is_favorite' => $is_favorite,
) );
die();
}
function is_favorite($userid, $favoriteid) {
$favorites_list = get_field( 'favorites_list', 'user_' .$userid );
return in_array( $favoriteid, $favorites_list );
}
function get_favorites_num( $userid ) {
if ( get_field( 'favorites', 'user_' .$userid ) ) {
return get_field( 'favorites', 'user_' .$userid );
} else {
return '0';
}
}
add_action('wp_ajax_setfavorite', 'setfavorite');
add_action('wp_ajax_nopriv_setfavorite', 'setfavorite');
i maybe will implement a same function in the next 2 months. i allready started researching plugins but as your question states, it does not seems to be very hard.
I try to come back then but first i need a frontend profile page where i can list the favorites.
The first impression looks good so far but first i would give attention to the $_POST array and sanitize and maybe validate the values because sometimes not only your ajax will call.
if ( isset($_POST['userid']) && isset($_POST['favoriteid']) ) { // Both $_POST values exist
$userid = filter_var($_POST['userid'], FILTER_SANITIZE_NUMBER_INT);
$favoriteid = filter_var($_POST['favoriteid'], FILTER_SANITIZE_NUMBER_INT);
if ( filter_var($userid, FILTER_VALIDATE_INT) && filter_var($favoriteid, FILTER_VALIDATE_INT) ) {
// $_POST was save, both values are Integers
}
}
The second one is related to get_field which is a function provided by the ACF Plugin. therefor when deactivating it or it gets replaced with JCF, it may cause errors.
You can avoid this by using if ( function_exists('get_field') ) {. Then your code only stops working when ACF gets deactivated.
Otherwise it seems not neccessary to use the ACF function and you can use the WP native function get_user_meta instead:
function is_favorite($userid, $favoriteid){
$favorites_list = get_user_meta($userid, 'favorites_list', true);
// check the new output instead of get_field
return in_array( $favoriteid, $favorites_list );
}
and also all calls to get_field('favorites', 'user_' .$favoriteid) seem to be wrong then. The ACF Docs say the the second parameter of get_field ist a post ID so i don't know what 'user_' means then. I would call:
function get_favorites_num($favoriteid){
return get_user_meta($favoriteid, 'favorites', true) || 0;
}
i now have my own favorite system ready where users can favorite posts from a specific post_type
HTML
<?php while ( have_posts() ) : the_post() ?>
<?php
$customPostsMeta = get_post_custom();
$favorite_class = 'favorite-me disabled';
$fav_count = isset($customPostsMeta['_favorites']) ? intval($customPostsMeta['_favorites'][0]) : 0;
$fav_count_text = $fav_count > 0 ? '(' . $fav_count . ')' : '';
$fav_count = ' <span class="fav-count clearfix">' . $fav_count_text . '</span>';
$favorite_title = '';
if ( is_user_logged_in() ) {
$user = wp_get_current_user();
$favorites = get_user_meta($user->ID, '_favorite_posts', true);
$fav_key = array_search($post_id, $favorites);
$is_favorite = ( $fav_key !== false );
if ( $is_favorite ) {
$favorite_class .= ' is-favorite';
$favorite_title = ' title="' . get_the_title() . ' ' . __('favorisieren', 'myTheme') . '"';
} else {
$favorite_title = ' title="' . get_the_title() . ' ' . __('nicht mehr favorisieren', 'myTheme') . '"';
}
}
?>
<a class="<?php echo $favorite_class; ?>" href="#post-<?php the_ID() ?>"<?php echo $favorite_title; ?>><?php echo __('Favorit', 'myTheme')?><?php echo $fav_count; ?></a>
<?php endwhile; ?>
JS
// i use a small self written JS module frame where this is included as module
// favorite.setup() is fired imediatly, favorite.ready() fires on document ready
// you can see a full version here: https://dev.alphabetisierung.at/wp-content/themes/sandbox_2017/js/actions.js
// line 732
/**
* Favorites ajax system
* =====================
* https://stackoverflow.com/questions/60468237
*/
favorite: {
options: {
selectors: {
link: '.favorite-me',
fav_count: '.fav-count'
},
classNames: {
disabled: 'disabled',
is_favorite: 'is-favorite',
}
},
events: function(){
var options = this.options,
selectors = options.selectors,
classNames = options.classNames,
info = this.info;
this.$favorites.on('click', function(event){
var post_id = this.hash.replace('#post-', ''),
$favorite_link = $(this).addClass(classNames.disabled),
$fav_count = $favorite_link.children(selectors.fav_count),
$favorite = $.ajax({
url: myTheme.page.urls.ajax,
type: 'post',
data: {
action: info.name, // derived from the module name "favorite"
verify: myTheme.page.verify, // https://developer.wordpress.org/reference/functions/check_ajax_referer/
post_id: post_id
// user_id of user who takes the action is not necessary
}
});
$favorite.done(function(data){
var fav_count = data.hasOwnProperty('fav_count') ? parseInt(data.fav_count) : 0,
fav_count_text = '',
is_favorite = data.hasOwnProperty('is_favorite') ? data.is_favorite : false;
if ( fav_count > 0 ) {
fav_count_text = '(' + fav_count + ')';
}
$fav_count.html(fav_count_text);
if ( is_favorite && !$favorite_link.is('.' + classNames.is_favorite) ) {
$favorite_link.addClass(classNames.is_favorite);
} else {
$favorite_link.removeClass(classNames.is_favorite);
}
$favorite_link.removeClass(classNames.disabled);
});
event.preventDefault();
});
},
ready: function ready(){
var selectors = this.options.selectors,
classNames = this.options.classNames;
this.$favorites = $(selectors.link).removeClass(classNames.disabled);
this.events();
},
setup: function setup(){
var setup = myTheme.info.is_user_logged_in && myTheme.info.post_type === 'my_custom_post_type';
return setup; // only for my post_type
}
PHP
add_action('wp_enqueue_scripts', 'myTheme_enqueue_scripts');
add_action('wp_ajax_favorite', 'myTheme_set_favorite');
// wp_ajax_nopriv_{action} is not necessary when feature is only for logged in users
function myTheme_enqueue_scripts(){
$data = array(
'id' => get_the_ID(),
'urls' => array(
'ajax' => admin_url('admin-ajax.php'),
'template' => get_stylesheet_directory_uri(),
),
'verify' => wp_create_nonce('myThemeOrAction_ajax_call'), // used for check_ajax_referer()
// ...
'info' => array(
// ...
'is_user_logged_in' => is_user_logged_in(),
'post_type' => get_post_type(),
),
);
// ...
wp_localize_script('actions', 'myTheme_data', $data );
}
function myTheme_set_favorite(){
check_ajax_referer('myThemeOrAction_ajax_call', 'verify');
if ( isset($_POST['post_id']) ) {
$user = wp_get_current_user(); // here we get the user ID of the current user
$post_id = filter_var($_POST['post_id'], FILTER_SANITIZE_NUMBER_INT);
$post = get_post($post_id);
// $fav_id = filter_var($_POST['fav_id'], FILTER_SANITIZE_NUMBER_INT);
// $fav_user = get_userdata($fav_id); // WP_User
$is_favorite = false;
if ( $post instanceof WP_Post ) { // post ID is valid
// for user favorites it would be
// if ( $fav_user instanceof WP_User ) {
$fav_count = intval(get_post_meta($post->ID, '_favorites', true));
$favorites = get_user_meta($user->ID, '_favorite_posts', true);
if ( !filter_var($fav_count, FILTER_VALIDATE_INT) ) {
$fav_count = 0;
}
if ( !is_array($favorites) || empty($favorites) ) {
$favorites = array();
}
$fav_key = array_search($post->ID, $favorites);
if ( $fav_key !== false ) { // is favorite, remove it
$fav_count--;
unset($favorites[$fav_key]);
} else { // is no favorite, add it
$fav_count++;
$favorites[] = $post->ID;
$is_favorite = true;
}
// set favorite counter
update_post_meta($post->ID, '_favorites', $fav_count);
// set favorite list
update_user_meta($user->ID, '_favorite_posts', $favorites);
// Output
$json = array(
'fav_count' => $fav_count,
'favorites' => $favorites,
'error' => false,
'is_favorite' => $is_favorite,
);
} else {
$json = array('is_favorite' => $is_favorite, 'error' => true, 'message' => 'Invalid Post ID');
}
} else {
$json = array('is_favorite' => $is_favorite, 'error' => true, 'message' => 'No post_id Post ID sent');
}
// wp_send_json sets the http header and ends the request
wp_send_json($json);
}
this are the things i noticed on the way:
verify referer with wp_create_nonce() and check_ajax_referer()
check JSON result data keys for existance with data.hasOwnProperty('key') to avoid possible JS errors
check valid WP_User or WP_Post object
current users ID with is not necessary to send with POST
wp_ajax_nopriv_{action} is not necessary when feature is only for logged in users
use wp_send_json() for ending the response
kind regards
tom
I'm building a site within WordPress which will display two rows of images at the start, then on button click, two more, dynamically.
I have four files involved in this process:
case-study-card.php: Which holds the markup for the each card.
home.php: Where the load more button is created.
load-more.js
ajax-loaders.php
However, I'm stumped on where my AJAX is falling apart? I feel as if
/*************/
// load-more.js
/*************/
jQuery(function($){
$('#loadmore').click(function(){
var column_width = $(this).data('column-width');
var max_num_pages = $(this).data('max-num-pages');
var ignore = $(this).data('featured');
var post_type = $(this).data('type');
var button = $(this),
data = {
action:'loadmore',
query: loadmore_params.posts,
page : loadmore_params.current_page,
security : loadmore_params.security
};
$.ajax({
url : loadmore_params.ajaxurl,
data : data,
type : 'POST',
beforeSend : function ( xhr ) {
button.text('Loading...');
console.log ("before sending ajax"); // works
},
success : function( data ){
if( data ) {
console.log("test")
button.text( 'Load More' ).prev().before(data); // insert new posts
loadmore_params.current_page++;
// where to insert new posts...
console.log("now inserting new posts");
$('.case-studies-container').find('.case-card').last().after( data );
console.log("found card");
// if it's the last page in the query, remove the button
if ( loadmore_params.current_page == max_num_pages )
button.remove();
console.log (data);
} else {
// if no data, remove the button as well
button.remove();
}
},
error : function(error){
console.log(error);
}
});
});
});
<!-- In case-study-card.php -->
<div class="case-card" >
<div class="case-card__wrapper">
<h4 class="case-card__title">
Title
</h4>
</div>
</div>
<!-- in home.php
* After looping through and displaying the initial 5 cards, display the button at the bottom
-->
<?php
if ($the_query->found_posts > 5 ){
echo "<div class='case-study__loadmore'><a id='loadmore'>Load more</a>";
}
?>
<!-- ajax-loaders.php -->
<?php
function ajax_handler(){
check_ajax_referer('load_more', 'security');
//prepare arguments for query
$args = json_decode( stripslashes( $_POST['query'] ), true );
$args['paged'] = $_POST['page'] + 1; // we need next page to be loaded
$args['post_status'] = 'publish';
$args['post__not_in'] = explode(',',$_POST['exclude']);
$args['max_num_pages'] = $_POST['max_num_pages'];
$cols = $_POST['columns'];
$type = $_POST['post_type'];
query_posts( $args );
if( have_posts() ) :
// run the loop
while( have_posts() ): the_post();
endwhile;
endif;
die;
}
add_action('wp_ajax_loadmore', 'ajax_handler'); // wp_ajax_{action}
add_action('wp_ajax_nopriv_loadmore', 'ajax_handler'); // wp_ajax_nopriv_{action}
?>
I believe everything after success : function( data ) in load-more.js is not working and I'm not sure why? I also get a admin-ajax.php 400 (Bad Request) error, a file that I have not seen or worked on in this project ...
Edit:
asset-loader.php
wp_localize_script( 'theme', 'loadmore_params', array(
'ajaxurl' => admin_url( 'admin-ajax.php' ),
'posts' => json_encode( $wp_query->query_vars ),
'current_page' => get_query_var( 'paged' ) ? get_query_var('paged') : 1,
'max_page' => $wp_query->max_num_pages,
'security' => wp_create_nonce("load_more")
) );
wp_localize_script( 'theme', 'repeater_loadmore_params', array(
'ajaxurl' => admin_url( 'admin-ajax.php' ),
'current_page' => get_query_var( 'paged' ) ? get_query_var('paged') : 1,
'max_page' => $wp_query->max_num_pages,
'security' => wp_create_nonce("repeater_field_nonce")
) );
Edit 2:
I believe the ajax handler url : loadmore_params.ajaxurl is working.
When I have url : loadmore_params.ajaxurl the button text changes from "Load more" to "Loading".
When I have url : ajaxurl the button doesn't change beforeSend.
Which makes me think the handler is working fine, but anything within success : function( data ){ isn't working and I'm not sure why.
Place your localize script also so it may be debug properly.
I am working on gravity forms add-on. I am saving all the values of gform fields in my database successfully after form submission using this hook gform_after_submission. Now I want to save the total pricing value but I don't get it I also see that the total pricing value coming from span.
So i search a lot but nothing found except some java script code:
<script type="text/javascript">
gform.addFilter( 'gform_product_total', function(total, formId) {
return total;
} );
</script>
But i don't get that how can i get this value in gform_after_submission hook
So please give me some suggestions
check bellow code . it's meet your requirement .
function saiful_gf_remove_money_symbol( $s ) {
return GFCommon::to_number( $s );
}
add_filter( 'saiful_gf_remove_money_symbol', 'saiful_gf_remove_money_symbol' );
add_filter( 'gform_product_info', 'add_fee', 10, 3 );
function add_fee( $product_info, $form, $lead ) {
$loop_price = 0;
if( isset( $product_info['products'] ) && count( $product_info['products'] ) > 0 ){
foreach ( $product_info['products'] as $data ){
if( isset( $data['options'] ) && count( $data['options'] ) > 0 ){
foreach ( $data['options'] as $key => $option ) {
$loop_price += floatval( $option['price'] );
}
}
// Save From Here total amount
$new_price = apply_filters( 'saiful_gf_remove_money_symbol',$data['price']) + $loop_price ;
}
}
return $product_info;
}