phpwordpresswoocommerceproductcustom-taxonomy

Modify main WordPress query to limit results to a list of possible Post IDs


I am working on a WprdPress/WooCommerce plugin feature where specific Customers are shown only a subset of Products.
The user has a meta data field with an array of post ids corresponding to the allowed Products.

I am trying to hook into the main query:

add_action( 'pre_get_posts', 'customprefix_pre_get_posts' );

function customprefix_pre_get_posts( $query )
{
  $user = wp_get_current_user();

  if ( $query->is_main_query() && is_product_category() )
  {
    /** @var array $allowed Post IDs */
    $allowed = customprefix_get_allowed_products_per_user( $user );

    $query->set( 'post__in', $allowed );
  }
}

I have tried many different combinations of how to add the post__in clause to the query including $query->query_vars['post__in'] = $allowed and others. I have tried adding $query->parse_query_vars() as well with no success.

While diving further into the get_posts() function in WP_Query it appears that my change to the Query is returned correctly (by reference) from the pre_get_posts hook. The next line: $q = $this->fill_query_vars( $q ); somehow looses my custom post__in field.

Most of the documentation for WP_Query and post__in revolve around creating a new query, not modifying the main query.

Edit Per request: $allowed = [1234,1235,1236,1237]


Solution

  • Your approach is almost correct, but the issue likely stems from how WooCommerce modifies queries internally. The is_product_category() condition might not be sufficient because WooCommerce uses custom queries, and pre_get_posts can be overridden or modified by WooCommerce itself.

    Try adding more specific conditions and adjusting the priority of the hook. WooCommerce uses custom post types (product), so we should also check for the post type explicitly.

    Here's a refined version:

    add_action( 'pre_get_posts', 'customprefix_pre_get_posts', 20 );
    
    function customprefix_pre_get_posts( $query ) {
        // Only modify the main WooCommerce product query on the front end
        if ( ! is_admin() && $query->is_main_query() && ( is_product_category() || is_shop() ) ) {
            $user = wp_get_current_user();
    
            // Retrieve allowed product IDs
            $allowed = customprefix_get_allowed_products_per_user( $user );
    
            // If allowed products exist, set the 'post__in' query var
            if ( ! empty( $allowed ) && is_array( $allowed ) ) {
                $query->set( 'post__in', $allowed );
            }
        }
    }
    
    

    What's Changed?

    1. Priority Increased (20): WooCommerce adds its filters later, so this ensures your modification runs after WooCommerce's default behavior.
    2. Check for is_shop(): Added support for the main shop page.
    3. Avoid Admin Queries: Prevents interference with the WordPress admin area.
    4. Safety Check: Ensures $allowed is not empty and is an array before applying the filter.

    Why we increased to 20: The reason I landed on 20 is based on how WordPress and WooCommerce handle query modifications.

    In WordPress, the pre_get_posts hook can have different priorities, which determine the order in which functions attached to it are executed. By default, the priority is 10, but since WooCommerce often hooks into this filter at a later stage (after the main query is set up), increasing the priority helps to ensure that your modifications are applied after WooCommerce’s default behavior.

    WooCommerce filters typically hook into the pre_get_posts action at a priority of 10 or 11, so by setting your function to run at priority 20, you ensure it’s applied afterward. This lets you modify the query after WooCommerce has done its filtering but before the query is actually run, preventing conflicts.

    WooCommerce also adds additional filters for product-related queries, such as adjusting the product visibility or modifying the product category displays. These filters can be found in files like includes/class-wc-query.php, where WooCommerce hooks into pre_get_posts to control the queries for products, product categories, and shop pages.

    By adjusting the priority to 20, you ensure your query filter is applied after WooCommerce's default query modifications but before the page renders. It helps make sure that your query adjustments for the user's allowed products take precedence at the right point in the execution flow.