HUSKY - Product Filter for WooCommerce

按分类筛选 WooCommerce 返回空结果:分类层级修复

如果WooCommerce按产品分类筛选时,父级分类没有返回结果,即使产品已分配给子分类,本指南将帮助您解决此问题。

问题描述

症状:当您按父级分类筛选时,没有显示任何产品,即使其子分类中存在产品。

示例:

  • 分类结构:Federal Actions > SEC > Enforcement
  • 产品分配至:Enforcement(仅子分类)
  • Federal ActionsSEC 筛选返回:无结果
  • Enforcement 筛选返回:显示产品

原因分析

WordPress/WooCommerce 要求产品必须显式标记层级结构中的所有父级分类,而不仅仅是子分类。

当您在WordPress管理界面将产品分配给子分类时,WooCommerce不会自动在术语关系表中为产品标记所有父级分类。这是一个数据结构要求,而非HUSKY的限制。

正确的分类分配应包含:

  • 子分类:Enforcement
  • 父级分类:SEC
  • 祖父级分类:Federal Actions

如何检查是否存在此问题

  1. 前往产品编辑页面:wp-admin/post.php?post=YOUR_PRODUCT_ID&action=edit
  2. 查看右侧的“分类”元数据框
  3. 检查层级结构中的所有父级分类是否已勾选
  4. 如果仅勾选了子分类 → 存在此问题

解决方案:自动分类层级标准化

此解决方案包含两部分:

  1. 未来防护修复:保存产品时自动标记父级分类
  2. 即时修复:标准化所有现有产品

第1部分:自动标记父级分类(未来产品)

将以下代码添加到子主题的 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);
        }
    }
}

作用说明:

  • 每次保存产品时触发
  • 获取所有已分配的分类
  • 查找每个已分配分类的所有父级分类
  • 使用完整层级结构标记产品
  • 清除所有相关缓存

结果:从此以后,任何保存的产品将自动包含所有父级分类。

第2部分:标准化现有产品(一次性修复)

此脚本将修复数据库中所有现有产品。将以下临时代码添加到您的 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
    ];
}

使用方法:

  1. 将代码添加到您的 functions.php
  2. 访问:https://yoursite.com/?normalize_cats=1(必须以管理员身份登录)
  3. 等待脚本完成
  4. 您将看到显示已更新产品数量的结果
  5. 运行一次后删除此代码

预期输出:

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

这意味着919个产品中有156个缺少父级分类,现已修复。

测试修复效果

实施上述两部分后:

  1. 前往带有HUSKY筛选器的商店页面
  2. 尝试按父级分类筛选
  3. 子分类中的产品现在应会显示
  4. 尝试沿层级结构向下钻取——所有层级均应正常

为什么不将此作为HUSKY默认功能?

这是一个合理的问题。原因如下:

  1. 数据结构变更:这会修改核心WooCommerce数据关系
  2. 并非通用需求:某些站点因特定业务逻辑而仅分配子分类
  3. 性能影响:在非常大的目录中,每次产品保存都会增加开销
  4. 冲突风险:可能干扰其他管理分类关系的插件

未来增强:我们可能会在HUSKY中添加此功能作为可选设置,供用户按需启用,但不会设为默认行为。

替代方案:手动分类分配

如果您更倾向于手动控制,可以:

  1. 编辑每个产品
  2. 在“分类”元数据框中,手动勾选所有相关的父级分类
  3. 保存产品

这样可以实现精细控制,但对于大型目录而言耗时较长。

故障排除

脚本显示0个标准化产品

  • 您的产品可能已经正确分配了父级分类
  • 手动检查几个产品以确认

标准化后筛选仍然无效

  • 清除所有缓存(WordPress、服务器、浏览器)
  • 确保HUSKY使用了正确的分类法(product_cat
  • 检查与其他筛选插件的冲突
  • 确认产品已发布(非草稿)

产品从子分类筛选中消失

这不应发生,但如果出现:

  • 脚本保留了所有现有的分类分配
  • 检查是否有其他插件干扰
  • 从备份恢复并联系支持

自定义分类法

此解决方案同样适用于使用CPT UI或类似插件创建的自定义分类法。只需将 'product_cat' 改为您的自定义分类法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');

重要说明

  • 需要子主题:始终使用子主题,以防止主题更新时代码丢失
  • 先备份:在运行标准化脚本之前备份数据库
  • 在测试站上测试:尽可能先在测试站点上测试
  • 一次性脚本:运行后删除标准化脚本
  • 保留第1部分代码save_post_product 钩子应永久保留

总结

永久添加至 functions.php:

  • 自动标记父级分类钩子(第1部分)

运行一次后删除:

  • 标准化脚本(第2部分)

预期结果:

  • 父级分类筛选正常工作
  • 子分类筛选继续正常工作
  • 层级下钻功能正常
  • 未来产品自动包含父级分类