The function that solves it should do the following :
- Loops through each product in the category
- For each item, retrieve the list of categories the product is in.
- If there is only one category and it’s the ‘restricted’ category, then the product is excluded from the query.
Here’s an example :
function exclude_category_archive_products( $q ) {
$current_cat = get_queried_object();
if( isset($current_cat->slug) && $current_cat->slug != "restricted" ) {
$tax_query = (array) $q->get( 'tax_query' );
$tax_query[] = array(
'taxonomy' => 'product_cat',
'field' => 'slug',
'terms' => 'restricted',
'operator' => 'NOT IN'
);
$product_id_array = $q->get('post__in'); //get array of product IDs
// loop through each product ID
foreach ($product_id_array as $product_id) {
$product_cat_ids = wp_get_post_terms( $product_id, 'product_cat', array('fields' => 'ids')); //get array of category IDs for this product
//if there's only one category and it's the 'restricted' category
if(count($product_cat_ids) == 1 && has_term('restricted', 'product_cat', $product_id)){
$q->set('post__not_in', array($product_id)); //exclude this product
}
}
$q->set( 'tax_query', $tax_query );
}
}
add_action( 'woocommerce_product_query', 'exclude_category_archive_products' );
EDIT FOR THE SEARCH ISSUE YOU MENTIONED BELOW
This won’t also work for search, so we’ll need to extend it. We have two options.
Option 1 – We remove all products that have the “restricted” category from search.
This is a simple function that will just exclude all products included in the “restricted” category.
function exclude_category_search_products( $query ) {
// We only care if it's the search query
if ( ! is_admin() && $query->is_search() && $query->is_main_query() ) {
$tax_query = (array) $query->get( 'tax_query' );
$tax_query[] = array(
'taxonomy' => 'product_cat',
'field' => 'slug',
'terms' => 'restricted',
'operator' => 'NOT IN'
);
$query->set( 'tax_query', $tax_query );
}
}
add_action( 'pre_get_posts', 'exclude_category_search_products' );
Option 2 – Hide the product only if it has just the “restricted” category. If they have multiple categories.
This is a bit more complex, but it will only exclude the products that ONLY have the “restricted” category. If they have the “restricted” category and another one, it will show :
function exclude_category_search_products( $query ) {
if ( ! is_admin() && $query->is_search() && $query->is_main_query() ) {
$restricted_ids = array();
// Get products that belong to the 'restricted' category
$args = array(
'post_type' => 'product',
'posts_per_page' => -1,
'fields' => 'ids',
'tax_query' => array(
array(
'taxonomy' => 'product_cat',
'field' => 'slug',
'terms' => 'restricted',
),
),
);
$restricted_products = get_posts( $args );
// Loop through each product and check the categories
foreach ($restricted_products as $product_id) {
$product_cat_ids = wp_get_post_terms( $product_id, 'product_cat', array('fields' => 'ids'));
// If the product belongs exclusively to the 'restricted' category
if(count($product_cat_ids) == 1){
$restricted_ids[] = $product_id; // add the product id to the array
}
}
$query->set( 'post__not_in', $restricted_ids );
}
}
add_action( 'pre_get_posts', 'exclude_category_search_products' );
I think Option 2 is the one you’re after.
But the new hook is super similar to the old one, so to keep things DRY we can just combine them and do this :
function exclude_category_products( $query ) {
// Check if it's the main WooCommerce product query but not for the restricted category
if( $query->is_main_query() && !is_admin() && is_post_type_archive( 'product' ) && !is_tax( 'product_cat', 'restricted' ) ) {
$tax_query = (array) $query->get( 'tax_query' );
$tax_query[] = array(
'taxonomy' => 'product_cat',
'field' => 'slug',
'terms' => 'restricted',
'operator' => 'NOT IN'
);
$product_id_array = $query->get('post__in'); //get array of product IDs
foreach ($product_id_array as $product_id) {
$product_cat_ids = wp_get_post_terms( $product_id, 'product_cat', array('fields' => 'ids')); //get array of category IDs for this product
//if there's only one category and it's the 'restricted' category
if(count($product_cat_ids) == 1 && has_term('restricted', 'product_cat', $product_id)){
$query->set('post__not_in', array($product_id)); //exclude this product
}
}
$query->set( 'tax_query', $tax_query );
} else if ( $query->is_search() && !is_admin() ) { // Check if it's the search query
$restricted_ids = array();
$args = array(
'post_type' => 'product',
'posts_per_page' => -1,
'fields' => 'ids',
'tax_query' => array(
array(
'taxonomy' => 'product_cat',
'field' => 'slug',
'terms' => 'restricted',
),
),
);
$restricted_products = get_posts( $args );
foreach ($restricted_products as $product_id) {
$product_cat_ids = wp_get_post_terms( $product_id, 'product_cat', array('fields' => 'ids'));
if(count($product_cat_ids) == 1){
$restricted_ids[] = $product_id;
}
}
$query->set( 'post__not_in', $restricted_ids );
}
}
add_action( 'pre_get_posts', 'exclude_category_products' );
We hook into pre_get_posts (because this gets triggered during both the main query and search) and do a conditional statement to check what type of query we’re dealing with. This will work both in the main query and in the search.