Restricted category in Woocommerce [closed]

The function that solves it should do the following :

  1. Loops through each product in the category
  2. For each item, retrieve the list of categories the product is in.
  3. 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.