-
-
Save filipecsweb/32467a20f2eeb39b0cbf to your computer and use it in GitHub Desktop.
| <?php | |
| /** | |
| * Create the new WooCommerce shortcode [random_product_categories] to output product categories randomly. | |
| * Based on [product_categories] (/plugins/woocommerce/includes/class-wc-shortcodes.php). | |
| * | |
| * This new shortcode accepts all attributes used by [product_categories], except 'orderby' and 'order', of course. | |
| * See more at @link https://woocommerce.com/document/woocommerce-shortcodes/. | |
| * | |
| * @param array $atts User defined attributes in shortcode tag. | |
| */ | |
| function random_product_categories_shortcode( $atts ) { | |
| global $woocommerce_loop; | |
| $atts = shortcode_atts( [ | |
| 'number' => null, | |
| 'columns' => '4', | |
| 'hide_empty' => 1, | |
| 'parent' => '', | |
| 'ids' => '', | |
| 'hide_title' => 0, | |
| ], $atts ); | |
| if ( isset( $atts['ids'] ) ) { | |
| $ids = explode( ',', $atts['ids'] ); | |
| $ids = array_map( 'trim', $ids ); | |
| } else { | |
| $ids = []; | |
| } | |
| $hide_empty = (bool) $atts['hide_empty']; | |
| // Get terms and workaround WP bug with parents/pad counts. | |
| $args = [ | |
| 'hide_empty' => $hide_empty, | |
| 'include' => $ids, | |
| 'pad_counts' => true, | |
| 'child_of' => $atts['parent'] | |
| ]; | |
| $product_categories = get_terms( 'product_cat', $args ); | |
| // Shuffle the array of objects `$product_categories`. | |
| shuffle( $product_categories ); | |
| if ( '' !== $atts['parent'] ) { | |
| $product_categories = wp_list_filter( $product_categories, [ 'parent' => $atts['parent'] ] ); | |
| } | |
| if ( $hide_empty ) { | |
| foreach ( $product_categories as $key => $category ) { | |
| if ( $category->count == 0 ) { | |
| unset( $product_categories[ $key ] ); | |
| } | |
| } | |
| } | |
| if ( $atts['number'] ) { | |
| $product_categories = array_slice( $product_categories, 0, $atts['number'] ); | |
| } | |
| $columns = absint( $atts['columns'] ); | |
| $woocommerce_loop['columns'] = $columns; | |
| ob_start(); | |
| // Reset loop/columns globals when starting a new loop. | |
| $woocommerce_loop['loop'] = $woocommerce_loop['column'] = ''; | |
| if ( $product_categories ) { | |
| woocommerce_product_loop_start(); | |
| if ( ! empty( $atts['hide_title'] ) ) { | |
| remove_action( 'woocommerce_shop_loop_subcategory_title', 'woocommerce_template_loop_category_title' ); | |
| } | |
| foreach ( $product_categories as $category ) { | |
| wc_get_template( 'content-product-cat.php', [ | |
| 'category' => $category | |
| ] ); | |
| } | |
| if ( ! empty( $atts['hide_title'] ) ) { | |
| add_action( 'woocommerce_shop_loop_subcategory_title', 'woocommerce_template_loop_category_title' ); | |
| } | |
| woocommerce_product_loop_end(); | |
| } | |
| wc_reset_loop(); | |
| return '<div class="woocommerce columns-' . $columns . '">' . ob_get_clean() . '</div>'; | |
| } | |
| add_shortcode( 'random_product_categories', 'random_product_categories_shortcode' ); |
@Hebhansen this code shows all categories because this is the default.
See, number is set to null which means all categories are output. If you want to show less then write something like [random_product_categories number=8].
Regarding your question about the columns, it does respect the variable columns on e.g. [random_product_categories columns=2].
About removing title, for sure you can. You could for example copy the template content-product-cat.php to your theme and then edit the way you want.
@filipecsweb Thx for your reply. This code is just awesome. For real! I don't get why it is not included in Woo core!
I was using limit attribute for showing only 3. The one used for products. number attribute does the job thx. As you can probably tell, I am not a coder myself. I do css and xml, but php is too wild for me.
content-product_cat is that available here at Github?
Again thx really
@Hebhansen I updated the code so you can hide the title.
You'd have to use the following shortcode: [random_product_categories hide_title=1].
Regarding the file "content-product-cat.php", in other words if you want more control over the output you'd have to copy the file "/wp-content/plugins/woocommerce/templates/content-product-cat.php" to "yourtheme/woocommerce/content-product-cat.php". More details at https://woocommerce.com/document/template-structure-overriding-templates-via-a-theme/#how-to-edit-files.
This is called "template override". It might seem a bit confusing in the beginning if you never did it, but it's something very easy to do.
@filipecsweb Thx man. That is just incredibly awesome. It works perfectly. Just a question, you may know the answer to. When I am log'ed into my site everything Random works perfect. When I am not, nothing Random works. Du you have any idea what plugin or function causes this?
@Hebhansen the first thing that came to me is cache. Do you have any cache plugins active? Wp fastest cache, wp rocket, etc...
Those cache plugins normally don't cache your page if you're logged in, but the page is cached if you're not, which means the random magic wouldn't work on every page request.
@Hebhansen the first thing that came to me is cache. Do you have any cache plugins active? Wp fastest cache, wp rocket, etc... Those cache plugins normally don't cache your page if you're logged in, but the page is cached if you're not, which means the random magic wouldn't work on every page request.
Hey Filip
So two years later I am still a fan. 2 years ago I remember changing a setting in my cache that sorted random. I do not remember what setting!
I have now made a new site, and I am back at square one. Something blocks random from working for all users, also admin. I have Litespeed cache now. Do you happen to know this fix? You may see this on my frontpage at svalinnart.com where only categories under themes and genres are supposed to be random (where 4 cats shows)
Finally, do you know how to hide overflow on mouse over? I have a zoom effect on the cat image, but I cannot get it to clip the overflow.
All the best
@Hebhansen It seems the cache comes from Cloudflare. You'd have to correctly setup your cache settings under the Cloudflare platform and/or under any WP plugin that integrates your website with Cloudflare.
About the overflow I couldn't reproduce what you described.
Hey Filip - I hope you are well. I have prompted ChatGPT to make Query Loop Block output product categories. After endless no results, I thew in your code above, hoping Chat would be able de decipher and combine the two. So the idea is to abandon the shortcode approach and use the flexibility of query loop to chose an image block for cat image include excerpt etc. If you have the time and don't mind, maybe you could review these lines from chat to get a better prompt and move in the right direction. I can select prod categories in loop as post type. This yields no results. Then I need filters based on a parent cat, tags etc. This is the code I have so far:
`// 1. Register Product Categories as a Custom Post Type for Query Block
function draupnir_register_product_cat_as_post_type() {
// Register product categories as a custom post type for the query block
register_post_type( 'product_cat', array(
'label' => __( 'Product Categories', 'draupnir-9' ),
'public' => true,
'show_in_rest' => true,
'rest_base' => 'product_cat',
'show_in_graphql' => true,
'supports' => array( 'title', 'thumbnail' ), // Title and thumbnail support
'has_archive' => false,
'show_ui' => true,
'hierarchical' => true, // Hierarchical (parent-child structure)
'rewrite' => array( 'slug' => 'product-category' ),
) );
// Ensure product categories are available as taxonomy for products
register_taxonomy_for_object_type('product_cat', 'product');
}
add_action( 'init', 'draupnir_register_product_cat_as_post_type' );
// 2. Modify the Query to Loop Through Product Categories
function draupnir_set_default_product_cat_query( $query_args, $block_instance ) {
if ( isset( $block_instance['post_type'] ) ) {
$post_type = $block_instance['post_type'];
// Only modify the query if it's the 'product_cat' post type
if ( 'product_cat' === $post_type ) {
// Get filters if available
$filters = isset( $block_instance['filters'] ) ? $block_instance['filters'] : [];
// Default behavior: show top-level product categories (parent = 0)
if ( empty( $filters ) || !isset( $filters['product_cat'] ) ) {
// Show top-level product categories (parent = 0)
$query_args['tax_query'] = array(
array(
'taxonomy' => 'product_cat',
'field' => 'id',
'terms' => 0, // Parent category ID 0 to fetch top-level categories
'operator' => 'IN',
),
);
} else {
// Filter categories by parent ID (if selected in the filters)
if ( isset( $filters['product_cat'] ) ) {
$parent_category = $filters['product_cat']; // Get the selected parent category
$query_args['tax_query'] = array(
array(
'taxonomy' => 'product_cat',
'field' => 'id', // Use category ID for filtering
'terms' => $parent_category,
'operator' => 'IN',
),
);
}
}
// Ensure we're querying product categories (not products)
$query_args['post_type'] = 'product_cat';
$query_args['posts_per_page'] = isset($block_instance['number']) ? $block_instance['number'] : -1; // Show all categories or the number set
}
}
return $query_args;
}
add_filter( 'block_query_args', 'draupnir_set_default_product_cat_query', 10, 2 );
// 3. Add Product Categories to the Query Block filters
function draupnir_add_product_categories_to_query_block( $settings ) {
if ( isset( $settings['core/query'] ) ) {
// Add product categories as a filterable taxonomy in the query block
$settings['core/query']['supports']['filters'] = array(
'taxonomy' => array(
'product_cat' => __( 'Product Categories', 'draupnir-9' ),
),
);
}
return $settings;
}
add_filter( 'block_editor_settings_all', 'draupnir_add_product_categories_to_query_block' );
// 4. Modify the Parent Category Filter to Autofill Category Names
function draupnir_autofill_parent_category_filter( $filters, $block_instance ) {
if ( isset( $filters['product_cat'] ) ) {
// Autofill category names in filter dropdown
$categories = get_terms( array(
'taxonomy' => 'product_cat',
'parent' => 0, // Only top-level categories
'orderby' => 'name',
'order' => 'ASC',
'hide_empty' => false, // Show even categories with no products
) );
if ( !is_wp_error( $categories ) && !empty( $categories ) ) {
$options = array();
foreach ( $categories as $category ) {
$options[] = array(
'label' => $category->name, // Display the category name
'value' => $category->term_id, // Use the category ID for filtering
);
}
// Update the filter options
if ( isset( $filters['product_cat'] ) ) {
$filters['product_cat']['options'] = $options;
}
}
}
return $filters;
}
add_filter( 'block_editor_settings_all', 'draupnir_autofill_parent_category_filter', 10, 2 );
// 5. Query Block Output: Properly Output Categories without Manual HTML Structure
function draupnir_category_loop_output( $block_instance ) {
// Get categories for display (loop through them)
$categories = get_terms( array(
'taxonomy' => 'product_cat',
'parent' => 0, // Parent category ID 0 to fetch top-level categories
'orderby' => 'name',
'order' => 'ASC',
'hide_empty' => true, // Show categories with products
) );
// If there are categories, output them through the Query Block's loop structure
if ( !empty( $categories ) ) {
foreach ( $categories as $category ) {
?>
<div class="product-category">
<a href="<?php echo get_term_link( $category ); ?>">
<?php echo get_the_post_thumbnail( $category->term_id, 'medium' ); ?>
<h2><?php echo esc_html( $category->name ); ?></h2>
</a>
</div>
<?php
}
} else {
echo '<p>' . __( 'No categories found.', 'draupnir-9' ) . '</p>';
}
}
`
This is cool code :) However, it does not respect limit fx set to 3 but displays all categories in the particular parent.
You have columns set to 4. Will it respect the column="" variable or go for the hardcoded 4?
Also is there a way to remove the title for the category? and do it as a variable, fx set to title="none" or hide...?