HUSKY - WooCommerce Products Filter Professional

How to Fix Category Hierarchy Filtering in woocommerce shop (normalize db)

If you’re experiencing issues where filtering by parent categories returns no results, even though products are assigned to child categories, this guide will help you fix it.

The Problem

Symptom: When you filter by a parent category, no products are shown, even though products exist in its child categories.

Example:

  • Category structure: Federal Actions > SEC > Enforcement
  • Product is assigned to: Enforcement (child category only)
  • Filtering by Federal Actions or SEC returns: No results
  • Filtering by Enforcement returns: Product shown

Why This Happens

WordPress/WooCommerce requires products to be explicitly marked with all parent categories in the hierarchy, not just the child category.

When you assign a product to a child category through the WordPress admin interface, WooCommerce doesn’t automatically mark the product with all parent categories in the term relationships table. This is a data structure requirement, not a HUSKY limitation.

Correct category assignment should include:

  • Child category: Enforcement
  • Parent category: SEC
  • Grandparent category: Federal Actions

How to Check If You Have This Issue

  1. Go to a product edit page: wp-admin/post.php?post=YOUR_PRODUCT_ID&action=edit
  2. Look at the Categories metabox on the right side
  3. Check if all parent categories in the hierarchy are checked
  4. If only the child category is checked → You have this issue

Solution: Automatic Category Hierarchy Normalization

This solution consists of two parts:

  1. Future-proof fix: Automatically mark parent categories when saving products
  2. Immediate fix: Normalize all existing products

Part 1: Auto-Mark Parent Categories (Future Products)

Add this code to your child theme’s functions.php:

/**
 * Automatically mark all parent categories when product is saved
 * This ensures hierarchy filtering works correctly
 */
add_action('save_post_product', 'auto_mark_parent_categories', 10, 1);

function auto_mark_parent_categories($product_id) {
    // Clear cache
    clean_object_term_cache($product_id, 'product_cat');

    // Get currently assigned categories
    $terms = wp_get_post_terms($product_id, 'product_cat', ['fields' => 'ids']);

    if (!is_wp_error($terms) AND !empty($terms)) {
        $all_terms = [];

        // For each assigned category, get all its parents
        foreach ($terms as $term_id) {
            $all_terms[] = $term_id;

            // Get all ancestor categories
            $ancestors = get_ancestors($term_id, 'product_cat', 'taxonomy');
            if (!empty($ancestors)) {
                $all_terms = array_merge($all_terms, $ancestors);
            }
        }

        // Assign product to all categories including parents
        wp_set_object_terms($product_id, array_unique($all_terms), 'product_cat');

        // Clear caches
        clean_post_cache($product_id);
        clean_object_term_cache($product_id, 'product_cat');

        if (function_exists('wc_delete_product_transients')) {
            wc_delete_product_transients($product_id);
        }
    }
}

What this does:

  • Triggers every time a product is saved
  • Gets all assigned categories
  • Finds all parent categories for each assigned category
  • Marks the product with the complete hierarchy
  • Clears all relevant caches

Result: From now on, any product saved will automatically include all parent categories.

Part 2: Normalize Existing Products (One-Time Fix)

This script will fix all existing products in your database. Add this temporary code to your functions.php:

/**
 * One-time script to normalize all existing products
 * Access: yourdomain.com/?normalize_cats=1 (must be logged in as admin)
 */
add_action('init', function () {
    if (isset($_GET['normalize_cats']) AND current_user_can('manage_options')) {
        $result = normalize_all_products_categories();
        echo '<h2>Category Normalization Results</h2>';
        echo '<pre>';
        print_r($result);
        echo '</pre>';
        die();
    }
});

function normalize_all_products_categories() {
    global $wpdb;

    // Get all product IDs
    $args = [
        'post_type' => 'product',
        'post_status' => 'any',
        'posts_per_page' => -1,
        'fields' => 'ids'
    ];

    $product_ids = get_posts($args);
    $log = [];
    $normalized_count = 0;

    foreach ($product_ids as $product_id) {
        // Get current categories
        $terms = wp_get_post_terms($product_id, 'product_cat', ['fields' => 'ids']);

        if (!is_wp_error($terms) AND !empty($terms)) {
            $all_terms = [];

            // Collect all parent categories
            foreach ($terms as $term_id) {
                $all_terms[] = $term_id;

                // Get ancestors
                $ancestors = get_ancestors($term_id, 'product_cat', 'taxonomy');

                if (!empty($ancestors)) {
                    $all_terms = array_merge($all_terms, $ancestors);
                }
            }

            $all_terms = array_unique($all_terms);

            // Only update if we're adding new parent categories
            if (count($all_terms) > count($terms)) {
                wp_set_object_terms($product_id, $all_terms, 'product_cat');
                $normalized_count++;
                $log[] = "Product #{$product_id}: added " . (count($all_terms) - count($terms)) . " parent categories";
            }
        }
    }

    // Log to WordPress error log
    error_log("=== Category Normalization Complete ===");
    error_log(implode("\n", $log));
    error_log("Total normalized: {$normalized_count} products");

    return [
        'success' => true,
        'normalized' => $normalized_count,
        'total' => count($product_ids),
        'details' => $log
    ];
}

How to use:

  1. Add the code to your functions.php
  2. Go to: https://yoursite.com/?normalize_cats=1 (must be logged in as admin)
  3. Wait for the script to complete
  4. You’ll see results showing how many products were updated
  5. Remove this code after running it once

Expected output:

Category Normalization Results
Array
(
    [success] => 1
    [normalized] => 156
    [total] => 919
)

This means 156 products out of 919 were missing parent categories and have been fixed.

Testing the Fix

After implementing both parts:

  1. Go to your shop page with HUSKY filters
  2. Try filtering by a parent category
  3. Products from child categories should now appear
  4. Try drilling down through the hierarchy – all levels should work

Why Not Make This Default in HUSKY?

This is a legitimate question. Here’s why:

  1. Data Structure Change: This modifies core WooCommerce data relationships
  2. Not Universal: Some sites intentionally assign only child categories for specific business logic
  3. Performance: On very large catalogs, this adds overhead to every product save
  4. Conflicts: May interfere with other plugins that manage category relationships

Future Enhancement: We may add this as an optional setting in HUSKY that users can enable if needed, but it won’t be default behavior.

Alternative: Manual Category Assignment

If you prefer manual control, you can:

  1. Edit each product
  2. In the Categories metabox, check all relevant parent categories manually
  3. Save the product

This gives you granular control but is time-consuming for large catalogs.

Troubleshooting

Script shows 0 normalized products

  • Your products might already have correct parent categories assigned
  • Check a few products manually to verify

Filtering still doesn’t work after normalization

  • Clear all caches (WordPress, server, browser)
  • Make sure HUSKY is using correct taxonomy (product_cat)
  • Check for conflicts with other filter plugins
  • Verify products are published (not draft)

Products disappear from child category filter

This shouldn’t happen, but if it does:

  • The script preserves all existing category assignments
  • Check if another plugin is interfering
  • Restore from backup and contact support

Custom Taxonomies

This solution also works for custom taxonomies created with CPT UI or similar plugins. Simply change 'product_cat' to your custom taxonomy slug:

// Example for custom taxonomy 'modality'
$terms = wp_get_post_terms($product_id, 'modality', ['fields' => 'ids']);
wp_set_object_terms($product_id, array_unique($all_terms), 'modality');

Important Notes

  • Child Theme Required: Always use a child theme to prevent code loss during theme updates
  • Backup First: Backup your database before running the normalization script
  • Test on Staging: Test on a staging site first if possible
  • One-Time Script: Remove the normalization script after running it
  • Keep Part 1 Code: The save_post_product hook should stay permanently

Summary

Add to functions.php permanently:

  • Auto-mark parent categories hook (Part 1)

Run once and remove:

  • Normalization script (Part 2)

Expected results:

  • Parent category filtering works correctly
  • Child category filtering continues to work
  • Hierarchy drill-down functions properly
  • Future products automatically include parent categories

Troubles? Ask for Support!