Можно вот таким шорткодом
[products limit="48" category="petli, ruchki, furnitura, zamki" cat_operator="NOT IN" orderby="rand" paginate="true"]
Если я все правильно понял то аргументы для wp_query будут так:
<?php
$current = absint( max( 1, get_query_var( 'paged' ) ? get_query_var( 'paged' ) : get_query_var( 'page' ) ) );
$args = array(
'post_type' => 'product',
'posts_per_page' => 8,
'paged' => $current,
'order' => 'rand',
'tax_query' => array(
array(
'taxonomy' => 'product_cat',
'field' => 'slug', // Or 'name' or 'term_id'
'terms' => array('hoodies'),
'operator' => 'NOT IN', // Excluded
)
)
);
$query = new WP_Query($args);
?>
<?php if( $query->have_posts() ) : ?>
<?php while( $query->have_posts() ) : $query->the_post(); ?>
<div class="product">
<!-- тут вывод карточки продукта -->
</div>
<?php endwhile; ?>
<nav class="navigation pagination">
<div class="nav-links">
<?php
echo wp_kses_post(
paginate_links( [
'total' => $query->max_num_pages, // количество берем из дефолтной опции запроса
'current' => $current, // текущая страница
] )
);
?>
</div>
</nav>
<?php else: ?>
<div class="post">
<h2><?php _e( 'Posts not found', 'basic' ); ?></h2>
<?php get_search_form(); ?>
</div>
<?php endif; ?>
<?php wp_reset_query(); ?>
тут можно вполне убрать вывод пагинации, но вдруг пригодится