
本文介绍如何通过 pre_get_posts 钩子动态过滤 woocommerce 商店主查询,为已购用户自动隐藏其已下单的课程/商品,确保 shop 页面仅展示可购买新品,兼顾 learndash lms 场景下的用户体验与业务逻辑。
在构建基于 LearnDash + WooCommerce 的在线学习平台时,一个常见且关键的用户体验需求是:已购买课程的学员不应再次看到该课程出现在商店首页(Shop 页面)中。这不仅避免重复购买干扰,还能提升页面信息密度与转化聚焦度。单纯依赖 woocommerce_add_to_cart_validation(如问题中提供的代码)仅能拦截加购行为,无法真正“隐藏”商品——用户仍能看到、点击、甚至误操作。
真正的解决方案在于从数据源头干预商品列表的 WordPress 查询。我们使用 pre_get_posts 这一高性能、低开销的钩子,在 WooCommerce 主循环(main query)执行前,动态排除当前用户已购商品的 ID。
citySHOP是一款集CMS、网店、商品、系统,管理更加科学快速;全新Jquery前端引擎;智能缓存、图表化的数据分析,手机短信营销;各种礼包设置、搭配购买、关联等进一步加强用户体验;任何功能及设置都高度自定义;MVC架构模式,代码严禁、规范;商品推荐、促销、礼包、折扣、换购等多种设置模式;商品五级分类,可自由设置分类属性;商品展示页简介大方,清晰,图片自动放大,无需重开页面;商品评价、咨询分开
✅ 实现原理简述
- 检测是否处于前台 Shop 页面(非后台、非其他归档页);
- 获取当前登录用户的订单(仅含 wc-processing 和 wc-completed 状态);
- 遍历每笔有效订单,提取其中所有商品 ID;
- 去重后,通过 $query->set('post__not_in', $product_ids) 从主查询中排除这些商品;
- 整个过程不修改数据库、不增加额外 SQL 查询(复用 WC 原生订单结构),性能友好。
✅ 完整实现代码(推荐添加至主题 functions.php 或子主题中)
add_action( 'pre_get_posts', 'hide_product_from_shop_page_if_user_already_purchased', 20 );
function hide_product_from_shop_page_if_user_already_purchased( $query ) {
// 仅作用于前台主查询,跳过后台及非主循环
if ( ! $query->is_main_query() || is_admin() || ! is_shop() ) {
return;
}
$current_user = wp_get_current_user();
if ( 0 === $current_user->ID ) {
return; // 未登录用户不处理
}
// 查询该用户所有已完成/处理中的订单
$customer_orders = get_posts( array(
'numberposts' => -1,
'meta_key' => '_customer_user',
'meta_value' => $current_user->ID,
'post_type' => 'shop_order',
'post_status' => array( 'wc-processing', 'wc-completed' ),
'fields' => 'ids', // 仅获取 ID,减少内存占用
) );
if ( empty( $customer_orders ) ) {
return;
}
$product_ids = array();
foreach ( $customer_orders as $order_id ) {
$order = wc_get_order( $order_id );
if ( ! $order ) {
continue;
}
foreach ( $order->get_items() as $item ) {
$product_id = $item->get_product_id();
if ( $product_id ) {
$product_ids[] = $product_id;
}
}
}
$product_ids = array_unique( $product_ids );
if ( ! empty( $product_ids ) ) {
$query->set( 'post__not_in', $product_ids );
}
}⚠️ 注意事项与增强建议
- 缓存兼容性:若使用对象缓存(如 Redis)或页面缓存插件(如 WP Rocket),需确保 Shop 页面对登录用户不被静态缓存,否则动态过滤将失效。建议配置缓存插件排除 is_user_logged_in() 用户的 Shop 页面缓存。
-
变体商品支持:上述代码默认按 product_id 过滤。若需隐藏整个父级可变产品(即使只买了某个变体),可在获取 $product_id 后追加其父级 ID:
$parent_id = wp_get_post_parent_id( $product_id ); if ( $parent_id ) { $product_ids[] = $parent_id; } - 性能优化:对于高订单量用户,可考虑引入 transient 缓存已购商品 ID(例如 transient_wc_user_purchased_{$user_id}),设置 15–30 分钟过期,平衡实时性与性能。
- 与 LearnDash 集成:若课程同时通过 LearnDash 直接注册(非 WooCommerce 购买),需额外检查 ld_course_access_list 或 wp_usermeta 中的课程访问记录,并合并到 $product_ids 数组中。
✅ 总结
本方案以轻量、原生、可维护的方式,从根本上解决了“已购商品不展示”的核心诉求。它不依赖 JavaScript 渲染拦截(易被绕过),也不修改模板文件(便于主题升级),而是精准作用于 WordPress 查询层,符合 WooCommerce 最佳实践。搭配合理的缓存策略与边界条件处理,可稳定支撑数千学员规模的 LMS 商店场景。









