Skip to content

Instantly share code, notes, and snippets.

@filipecsweb
Last active December 9, 2024 21:59
Show Gist options
  • Save filipecsweb/32467a20f2eeb39b0cbf to your computer and use it in GitHub Desktop.
Save filipecsweb/32467a20f2eeb39b0cbf to your computer and use it in GitHub Desktop.
WooCommerce shortcode to output product categories randomly
<?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' );
@filipecsweb
Copy link
Author

@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.

@Hebhansen
Copy link

Hebhansen commented Dec 9, 2024

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>';
}

}
`

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment