Search code examples
phpwordpresswoocommercecategoriesproduct

Show cross-sells before same category on related products in WooCommerce


I'm writing some code to modify the related products section as follows:

  • If a product has cross sell products, show those first, and fill up to 4 total products with others from the same category*

Or

  • If a product has no cross sell products, show 4 products from the same category*

Here's my function to filter the related products so far:

add_filter( 'woocommerce_related_products', 'fivem_add_linked_to_related_products', 9999, 3 );
function fivem_add_linked_to_related_products( $related_posts, $product_id, $args ) {

    $product = wc_get_product( $product_id );
    $cross_sell_ids = $product->get_cross_sell_ids();
    $product_categories = $product->get_category_ids();

    // Get cross sell products
    $cross_sell_products = get_posts( array(
        'post_type' => 'product',
        'post_status' => 'publish',
        'fields' => 'ids',
        'post__in' => $cross_sell_ids,
        'posts_per_page' => 4,
        'exclude' => array( $product_id ),
    ));

    // Calculate how many filler products are needed
    $category_product_count = 4 - count( $cross_sell_products );

    // Exclude main product and cross sell products
    $excluded_products = array_push( $cross_sell_ids, $product_id );

    // Get filler products from same category
    $category_products = get_posts( array(
        'post_type' => 'product',
        'post_status' => 'publish',
        'orderby' => 'rand',
        'fields' => 'ids',
        'post__not_in' => $excluded_products,
        'posts_per_page' => $category_product_count,
        'tax_query' => array(
            array(
                'taxonomy' => 'product_cat',
                'field' => 'id',
                'terms' => $product_categories,
                'operator' => 'IN',
            )
        )
    ));

    // Merge cross sell products with filler products
    $related_products = array_merge( $cross_sell_products, $category_products );

    // Return related products
    return $related_products;

}

Currently, the above code mostly works.

  • If cross-sells are set, it only displays those cross-sell products- ie. does not fill out to 4 total
  • If no cross-sells are set, it displays products from the same category as expected.

There are two problems I'm trying to solve:

  1. Code above doesn't fill with category products. If I remove the post__not_in and tax_query arguments, it fills out, but obviously not with products from the same category.
  2. I want to show cross-sell products first, then the category-related products. There appears to be another randomization somewhere that mixes the order up, and I can't work out where that comes from.

Any ideas how I can fix this? Thanks in advance.


Solution

  • Code contains

    • If a product has cross sell products, show those first, and fill up to 4 total products with others from the same category
    • If a product has no cross sell products, show 4 products from the same category
    function filter_woocommerce_related_products( $related_posts, $product_id, $args ) {    
        // Taxonomy
        $taxonomy = 'product_cat';
    
        // Show products
        $show_products = 4;
    
        // Get product
        $product = wc_get_product( $product_id );
    
        // Get cross sell IDs
        $cross_sell_ids = $product->get_cross_sell_ids();
    
        // Calculate how many filler products are needed
        $category_product_needed_count = $show_products - count( $cross_sell_ids );
    
        // If category product needed 
        if ( $category_product_needed_count >= 1 ) {
            // Retrieves product term ids for a taxonomy.
            $product_cats_ids = wc_get_product_term_ids( $product_id, $taxonomy );
    
            // Get product id(s) from a certain category, by category-id
            $product_ids_from_cats_ids = get_posts( array(
                'post_type'   => 'product',
                'numberposts' => $category_product_needed_count,
                'post_status' => 'publish',
                'fields'      => 'ids',
                'tax_query'   => array(
                    array(
                        'taxonomy' => $taxonomy,
                        'field'    => 'id',
                        'terms'    => $product_cats_ids,
                        'operator' => 'IN',
                    )
                ),
            )); 
    
            // Merge array
            $related_posts = array_merge( $cross_sell_ids, $product_ids_from_cats_ids );
        } else {
            // Slice array until show products
            $related_posts = array_slice( $cross_sell_ids, 0, $show_products );
        }   
    
        // Return
        return $related_posts;
    
    }
    add_filter( 'woocommerce_related_products', 'filter_woocommerce_related_products', 10, 3 );
    
    // Order by
    function filter_woocommerce_output_related_products_args( $args ) { 
        $args['orderby'] = 'id';
        $args['order'] = 'ASC';
    
        return $args;
    }
    add_filter( 'woocommerce_output_related_products_args', 'filter_woocommerce_output_related_products_args', 10, 1 );