In WooCommerce I would like to hide Out of Stock products from Related products in single product pages. Is it possible?
Any track is appreciated.
None of the answers given here worked for me (I believe the woocommerce_output_related_products_args filter mentioned does not accept meta_queries), and I wanted a solution that didn't use an SQL query, so I put together the solution below:
add_filter( 'woocommerce_related_products', 'mysite_filter_related_products', 10, 1 );
function mysite_filter_related_products( $related_product_ids ) {
foreach( $related_product_ids as $key => $value ) {
$relatedProduct = wc_get_product( $value );
if( ! $relatedProduct->is_in_stock() ) {
unset( $related_product_ids["$key"] );
}
}
return $related_product_ids;
}
Hope that helps someone looking for a similar solution.
UPDATE 2021
You can use the following:
add_filter( 'woocommerce_product_related_posts_query', 'alter_product_related_posts_query', 10, 3 );
function alter_product_related_posts_query( $query, $product_id, $args ){
global $wpdb;
$query['join'] .= " INNER JOIN {$wpdb->postmeta} as pm ON p.ID = pm.post_id ";
$query['where'] .= " AND pm.meta_key = '_stock_status' AND meta_value = 'instock' ";
return $query;
}
Code goes in functions.php file of your active child theme (or active theme).
Now we need to remove "related products" cached data deleting the related transients to flush this cache (thanks to #Cody Rees).
There is 2 ways to do it:
1). The easiest way:
Go to admin Woocommerce > Status > Tools > WooCommerce transients and press on "Clear transcients".
2). The other way targeting specific related transients to be deleted:
Add the following code and save:
add_action('init', 'delete_related_products_cached_data');
function delete_related_products_cached_data() {
global $wpdb;
$wpdb->query("DELETE FROM {$wpdb->prefix}options WHERE `option_name` LIKE '_transient_wc_related_%'");
}
Code goes in functions.php file of your active child theme (or active theme).
Run it only once by browsing any page of your web site and remove it.
Yes it's possible to hide out of stock products from related products.
Add the below to functions.php – this will hide out of stock products from related products.
add_filter( 'woocommerce_output_related_products_args', function( $args )
{
$args = wp_parse_args( array(
'posts_per_page' => 4,
'meta_query' => array (
'key' => '_stock_status',
'value' => 'instock'
)
), $args );
return $args;
});
The posts per page line can be removed, but its useful as a quick of visualising that this has worked on your related products block.
For those who didn't find solution:
Tested on Woocommerce +6
add_filter( 'woocommerce_related_products', 'vahids_related_products', 10, 3 );
function vahids_related_products( $related_posts, $product_id, $args ){
$in_stock_product_ids = (array) wc_get_products( array(
'status' => 'publish',
'limit' => -1,
'stock_status' => 'instock',
'return' => 'ids',
));
return $in_stock_product_ids;
}
This is working code from here. Add this code to your functions.php and you will see that our of stocks products will not be seen in related product block. Code is from here : https://stackoverflow.com/a/60978253/15213069
add_filter( 'woocommerce_related_products', 'mysite_filter_related_products', 10, 1 );
function mysite_filter_related_products( $related_product_ids ) {
foreach( $related_product_ids as $key => $value ) {
$relatedProduct = wc_get_product( $value );
if( ! $relatedProduct->is_in_stock() ) {
unset( $related_product_ids["$key"] );
}
}
return $related_product_ids;
}
create a function and hook it to related products hook of woocommerce like:
function dont_show_outofstock( $is_visible, $id ) {
$product = new wC_Product( $id );
if ( ! $product->is_in_stock() && ! $product->backorders_allowed() ) {
$is_visible = false;
}
return $is_visible;
}
add_filter( 'woocommerce_output_related_products_args', 'dont_show_outofstock', 10, 2 );
Related
I am adding a custom field to woocommerce product variations to attach additional product categories to product variations. Everything works, during the save process the terms are updated in wp_term_relationships table however, a bit later in the save operation the terms are overwritten again with the parent product ones.
I am using the following code:
add_action( 'woocommerce_save_product_variation', 'save_custom_field_variations', 99, 2 );
function save_custom_field_variations( $variation_id, $i ) {
$custom_categories = $_POST['variation_product_cat'][$variation_id];
if ( isset( $custom_categories ) && !empty( $custom_categories )) {
wp_set_post_terms( $variation_id, $custom_categories, 'product_cat' );
}
}
I have also tried a different hook with the same result:
add_action( 'woocommerce_update_product', 'save_custom_field_variations_2', 99, 1 );
function save_custom_field_variations_2( $post_id ) {
foreach($_POST['variation_product_cat'] as $variation_id => $custom_categories)
if ( isset( $custom_categories ) && !empty( $custom_categories )) {
$response = wp_set_post_terms( $variation_id, $custom_categories, 'product_cat' );
}
}
Any tips on what process is overwriting my changes are very welcome!
Turns out it was one of the plugins that was overwriting the changes (found out by disabling all and then enabling them one by one)
Inspired by the thread Sort Woocommerce products by most viewed using Post View Counter, here is my code below, that changes the product sorting option to show products most viewed products:
/**
* Setting post count on each visit
*
*/
add_action( 'woocommerce_before_single_product', 'prefix_save_product_views' );
function prefix_save_product_views( ) {
$product_id = get_the_ID();
$increment = 1;
$current_visit_count = get_post_meta( $product_id, 'product_visit_count', true );
$total_visit_count = (int)$current_visit_count + $increment;
update_post_meta( $product_id, 'product_visit_count', $total_visit_count );
}
/**
* Change the display order based on visit count only in Catalog
*
*/
add_filter('woocommerce_get_catalog_ordering_args', 'prefix_woocommerce_catalog_orderby');
function prefix_woocommerce_catalog_orderby( $args ) {
$args['meta_key'] = 'product_visit_count';
$args['orderby'] = 'meta_value_num';
$args['order'] = 'desc';
return $args;
}
However it does not show all other products which do not have any page views yet.
Is there a way to prioritize sorting by page views and at the same time, after, to show other products (that haven't page views yet) as well.
Any idea please?
For that all other products (not viewed yet) should need to have product_visit_count meta key with a meta value of 0.
You can do that by adding this simple function, that you will run once (always make a database backup):
// Function that set "product_visit_count" metakey with a zero value on non visited products
function set_zero_product_visit_count_on_not_visited_products() {
// Only for admin user role
if( ! current_user_can('edit_products')) return;
// Get all products that doesn't have "product_visit_count" as metakey (not visited)
$product_ids = wc_get_products( array(
'limit' => -1,
'return' => 'ids',
'meta_key' => 'product_visit_count',
'meta_compare' => 'NOT EXISTS',
) );
// Loop through product ids
foreach( $product_ids as $product_id ) {
// Add 'product_visit_count' meta key with a value of 0 (zero)
update_post_meta( $product_id, 'product_visit_count', 0 );
}
}
// Triggers the function
set_zero_product_visit_count_on_not_visited_products();
Code goes in functions.php file of your active child theme (or active theme).
Once saved, as an administrator user role, browse any page of your web site, this execute the function. Now you can remove it (delete it). Tested and works.
Now for your new upcoming products, when you will add a new product, the following script will add product_visit_count meta key with a meta value of 0:
add_action( 'woocommerce_admin_process_product_object', 'init_product_visit_count_on_new_prodct' );
function init_product_visit_count_on_new_prodct( $product ) {
// Get 'product_visit_count' meta value
$meta_value = $product->get_meta( 'product_visit_count' );
// If meta value doesn't exist
if ( empty($meta_value) ) {
// Set the 'product_visit_count' to zero
$product->update_meta_data( 'product_visit_count', 0 );
}
}
Code goes in functions.php file of your active child theme (or active theme). It should work.
I would like to exclude products from a given city from my shop page but also from my home page where I display products from the flatsome UX Builder's woocommerce shop widget (not sure it's a widget).
The product with the given city doesn't appear in my shop page, but they still appear in my home page.
add_filter( 'pre_get_posts', 'custom_pre_get_posts_query' );
function custom_pre_get_posts_query( $q ) {
if ($q->is_main_query())
{
$meta_query = $q->get('meta_query');
$meta_query[] = array(
'key'=>'city',
'value' => 'Cassis',
'compare'=>'NOT EXISTS',
);
$q->set('meta_query',$meta_query);
remove_filter( 'pre_get_posts', 'custom_pre_get_posts_query' );
}
}
Any idea?
Instead of using pre_get_posts filter hook for a meta_query on products loop, you could use the dedicated woocommerce_product_query_meta_query filter hook.
Now for your problem it could be a widgets or a shortcode that is used, so there is also some dedicated hooks for them.
As the meta_query is going to be similar for the 3 hooked functions, you can set it in a custom function and call it in the 3 hooked functions this way:
// The meta query in a function
function custom_meta_query( $meta_query ){
$meta_query[] = array(
'key'=>'city',
'value' => 'Cassis',
'compare'=>'NOT EXISTS',
);
return $meta_query;
}
// The main shop and archives meta query
add_filter( 'woocommerce_product_query_meta_query', 'custom_product_query_meta_query', 10, 2 );
function custom_product_query_meta_query( $meta_query, $query ) {
if( ! is_admin() )
return custom_meta_query( $meta_query );
}
// The shortcode products query
add_filter( 'woocommerce_shortcode_products_query', 'custom__shortcode_products_query', 10, 3 );
function custom__shortcode_products_query( $query_args, $atts, $loop_name ) {
if( ! is_admin() )
$query_args['meta_query'] = custom_meta_query( $query_args['meta_query'] );
return $query_args;
}
// The widget products query
add_filter( 'woocommerce_products_widget_query_args', 'custom_products_widget_query_arg', 10, 1 );
function custom_products_widget_query_arg( $query_args ) {
if( ! is_admin() )
$query_args['meta_query'] = custom_meta_query( $query_args['meta_query'] );
return $query_args;
}
Code goes in function.php file of the active child theme (or active theme).
This should work…
I know this is not a woocommerce related websites, I tried the official forum, but no reply in 3 days. I spent a day or more with this, so I hope you can help.
I created a custom wp_query for the products. These products has a custom field (custom_price). I would like to override the prices in the query with the values of the custom fields.
I saw questions here about this, but I'm very new in php/wordpress. I really appreciate your help.
My query:
<?php woocommerce_product_loop_start(); ?>
<?php
if(is_front_page()){
$args = array(
'post_type' => 'product',
'posts_per_page' => 6,
'meta_key' => '_featured',
'meta_value' => 'yes'
);
$query = new WP_Query( $args );
if ( $query->have_posts() ) {
while ( $query->have_posts() ) {
$query->the_post();
wc_get_template_part( 'content', 'product' );
}
}
wp_reset_query();
}
?>
<?php woocommerce_product_loop_end(); ?>
If you just want to change the price that is displayed, then you can implement the filters woocommerce_price_html, woocommerce_sale_price_html, woocommerce_cart_item_price_html. This will result in the displayed price being whatever you would like, however the actual price in the cart used for calculating tax, shipping, totals, etc will be based on the "real" price.
// the HTML that is displayed on the product pages
function my_price_html( $html, $_product ) {
// if this is a variation we want the variation ID probably?
$id = isset($_product->variation_id) ? $_product->variation_id : $_product->id;
$custom_price = get_post_meta( $id, 'custom_price', true );
$custom_price_html = "<b>$custom_price</b>"; // just an example of HTML
return $custom_price_html;
}
add_filter( 'woocommerce_price_html', 'my_price_html', 10, 2 );
add_filter( 'woocommerce_sale_price_html', 'my_price_html', 10, 2 );
// the HTML that is displayed on the cart
function my_cart_item_price_html( $html, $cart_item, $cart_item_key ) {
$id = $cart_item['data']->id;
$custom_price = get_post_meta( $id, 'custom_price', true );
$custom_price_html = "<b>$custom_price</b>"; // just an example of HTML
return $custom_price_html;
}
add_filter( 'woocommerce_cart_item_price_html', 'my_cart_item_price_html', 10, 3 );
You will need to implement other filters/actions if you want to actually affect the item price, the above just affects the display. A better option might be to implement an action for save_post or update_post_meta that updates the WooCommerce product price when the custom field value changes (assuming you are using the codex to update this value).
I have 19 items in the "xyz" category currently but I am only able to see 5 of them at a time and shows pagination I don't want pagination.
Is there a way to make it to where more than 5 items show up on your first page of your xyz category ?
i added following code to my function.php of template
add_filter( 'loop_shop_per_page', create_function( '$cols', 'return 10;' ), 19 );
but still its displaying 5 products .
as of php 7.2 create_function is depricated.
following filter is available to change the amount of products in the archive pages (i would only suggest to use that one, if it is different from your wordpress setting under settings>reading)
/**
* WC: change products per page
* #return int
*/
function so22835795_loop_shop_per_page() {
return -1; //return any number, -1 === show all
};
add_filter('loop_shop_per_page', 'so22835795_loop_shop_per_page', 10, 0);
The loop_shop_per_page filter did not work for me as the theme was overriding it. So I used the woocommerce_product_query filter as follows:
<?php
add_action( 'woocommerce_product_query', 'so22835795_woocommerce_products_per_page', 1, 50 );
function so22835795_woocommerce_products_per_page( $query ) {
if ( $query->is_main_query() ) {
$query->set( 'posts_per_page', '5' );
}
}
This can be changed in the Reading Settings section of the Settings tab on the dashboard.
The option Blog pages show at most controls how many products can be seen. Changing that to 19 will allow all 19 products to be shown.
EDIT
add_filter( 'loop_shop_per_page', create_function( '$cols', 'return 19;' ), 20 );
ANOTHER EDIT:
In the woocommerce/includes/class-wc-query.php page there is the following on line ~365
$q->set( 'posts_per_page', $q->get( 'posts_per_page' ) ? $q->get( 'posts_per_page' ) : apply_filters( 'loop_shop_per_page', get_option( 'posts_per_page' ) ) );
Change it to:
$q->set( 'posts_per_page', $q->get( 'posts_per_page' ) ? $q->get( 'posts_per_page' ) : apply_filters( 'loop_shop_per_page', '19') );
add this code to our theme's function.php file
add_filter('loop_shop_per_page', create_function('$cols', 'return 19;'));
it is working for me.
Thakns