本文介绍如何通过 pre_get_posts 钩子动态过滤 woocommerce 商店主查询,为已购用户自动隐藏其已下单的课程/商品,确保 shop 页面仅展示可购买新品,兼顾 learndash lms 场景下的用户体验与业务逻辑。
在构建
基于 LearnDash + WooCommerce 的在线学习平台时,一个常见且关键的用户体验需求是:已购买课程的学员不应再次看到该课程出现在商店首页(Shop 页面)中。这不仅避免重复购买干扰,还能提升页面信息密度与转化聚焦度。单纯依赖 woocommerce_add_to_cart_validation(如问题中提供的代码)仅能拦截加购行为,无法真正“隐藏”商品——用户仍能看到、点击、甚至误操作。
真正的解决方案在于从数据源头干预商品列表的 WordPress 查询。我们使用 pre_get_posts 这一高性能、低开销的钩子,在 WooCommerce 主循环(main query)执行前,动态排除当前用户已购商品的 ID。
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 );
}
}$parent_id = wp_get_post_parent_id( $product_id );
if ( $parent_id ) {
$product_ids[] = $parent_id;
}本方案以轻量、原生、可维护的方式,从根本上解决了“已购商品不展示”的核心诉求。它不依赖 JavaScript 渲染拦截(易被绕过),也不修改模板文件(便于主题升级),而是精准作用于 WordPress 查询层,符合 WooCommerce 最佳实践。搭配合理的缓存策略与边界条件处理,可稳定支撑数千学员规模的 LMS 商店场景。