Cross-Sell Grid Using ACF Field On Cart Page

Hi,

I am trying to build a new Grid to manage cross-sells on my Cart page. However, rather than using the standard Woocommerce cross-sells functionality, I am trying to something a little different. I am pretty sure it is achievable, but am unable to get the Looper to display the results.

I have so far set up a basic grid which will eventually display the product image, title and price for each product.

The page structure is:

Section
    Row
        Column
            Text Element containing [woocomerce_cart]
            Grid Element (to display the cross-sells)
                 Cell 1 (Looper Provider & Looper Consumer - details below)
                      Headline (cross-sell title)

At Woocommerce product level, every product has an ACF field called “bird_animal_name” which is a mandatory field. (All products are bird-related).

What I would like is my Grid to look at the product(s) in the cart, “see” their ACF field data and to then display a grid of products from different categories, which have a product using the same ACF “bird_animal_name” (there will only ever be one of each “bird_animal_name” in each product category.

For example:

IN CART

  • Pheasant Notebook
  • Curlew Mug

CROSS_SELL COMPONENT DISPLAYS

  • Pheasant Print
  • Pheasant Mug
  • Pheasant Placemats
  • Pheasant … (any other products in other categories with products having the Pheasant “bird_animal_name”)
  • Curlew Notebook
  • Curlew Print
  • Curlew … (any other products in other categories with products having the Curlew “bird_animal_name”)

I have the following functions in place:
The first one should be providing the data I need:

function display_related_products_based_on_cart() {
    // Get cart items
    $cart_items = WC()->cart->get_cart();
    $bird_names = array();

    foreach ($cart_items as $cart_item_key => $cart_item) {
        $product_id = $cart_item['product_id'];
        $bird_name = get_field('bird_animal_name', $product_id);
        if ($bird_name && !in_array($bird_name, $bird_names)) {
            $bird_names[] = $bird_name;
        }
    }

    // If there are no bird names in the cart, exit the function early
    if (empty($bird_names)) return;

    // Use collected bird names in WP_Query
    $args = array(
        'post_type'  => 'product',
        'meta_query' => array(
            array(
                'key'     => 'bird_animal_name',
                'value'   => $bird_names,
                'compare' => 'IN'
            )
        )
    );

    $query = new WP_Query($args);

    // Store results of WP_Query in a global variable
    $related_products = array();
    if ($query->have_posts()) {
        while ($query->have_posts()) {
            $query->the_post();
            // Store the post (or product) data in an array
            $related_products[] = array(
                'title' => get_the_title(),
                'id' => get_the_ID(),
                // Add other data as needed
            );
        }
        wp_reset_postdata();
    }

    // Store the related products array in a global variable
    $GLOBALS['related_products_based_on_cart'] = $related_products;
}

add_action('woocommerce_before_cart', 'display_related_products_based_on_cart');

The second one should be giving a Looper Provider output:

function custom_looper_provider($data, $args) {
    if (isset($GLOBALS['related_products_based_on_cart']) && !empty($GLOBALS['related_products_based_on_cart'])) {
        return $GLOBALS['related_products_based_on_cart'];
    }
    return array();
}

In my Grid Cell I have enabled a Looper provider type “Custom” with the entry related_products
The Cell also has a Looper Consumer enabled.

Within the Cell is a Headline, which uses dynamic data {{dc:post:title}}. However, all I get the the cart’s title, not the data from the Looper provider.

I am pretty certain that the above function is working, as it is a modification of the below one, which definitely outputs the data I want to see in the Looper (though the below function displays it on-screen, whereas I want the data to be available in a Looper for use elsewhere):

function display_related_products_based_on_cart() {
    // Get cart items
    $cart_items = WC()->cart->get_cart();
    $bird_names = array();

    foreach ($cart_items as $cart_item_key => $cart_item) {
        $product_id = $cart_item['product_id'];
        $bird_name = get_field('bird_animal_name', $product_id);
        if ($bird_name && !in_array($bird_name, $bird_names)) {
            $bird_names[] = $bird_name;
        }
    }

    // If there are no bird names in the cart, exit the function early
    if (empty($bird_names)) return;

    // Use collected bird names in WP_Query
    $args = array(
        'post_type'  => 'product',
        'meta_query' => array(
            array(
                'key'     => 'bird_animal_name',
                'value'   => $bird_names,
                'compare' => 'IN'
            )
        )
    );

    $query = new WP_Query($args);

    // Display results of WP_Query
    if ($query->have_posts()) {
        echo '<div class="related-products-based-on-cart">';
        echo '<h2>Related Products</h2>';  // You can change the title if needed
        while ($query->have_posts()) {
            $query->the_post();
            // Display the post (or product) data here
            the_title();  // As an example
            echo '<br>';  // Line break for clarity, adjust as needed
        }
        wp_reset_postdata();
        echo '</div>';
    }
}
add_action('woocommerce_before_cart', 'display_related_products_based_on_cart');

Do you have any ideas why the content is not displaying as expected?

Thanks,
Christopher

Hello @Whitemedia,

Thanks for writing in!

The custom PHP code cannot be used in a Looper Provider Custom. You cannot use related_products as your Looper Provider Custom as well. You will have to create your own. Kindly check out this documentation first:

The custom PHP code must return a WP Object or at least an array so that it can be consumed within the loop.

Be advised that custom PHP coding is beyond the scope of our support under our Support Policy. If you are unfamiliar with code and resolving potential conflicts, you may select our One service for further assistance.

Best Regards.

Thanks @ruenel,

I now have a working solution to dynamically show related products on the Cart/Basket page:

The following filter goes in my child theme’s functions.php file. It looks at the products in the cart and identifies the content of the ACF field bird_animal_name, then looks for all products which contain the same content in that ACF field and outputs them from most expensive to cheapest.

There is an AJAX check in the first block of code, as without it, the filter was giving a Cornerstone “Failed to save. The response is not a valid JSON response” error when saving any changes.

add_filter('cs_looper_custom_related_products', function($result, $params) {
    // Check if in an AJAX request or if WooCommerce is not active or cart isn't available
    if (defined('DOING_AJAX') && DOING_AJAX || !class_exists('WooCommerce') || !WC()->cart) {
        return $result;  // Return the default result
    }

    // Get cart items
    $cart_items = WC()->cart->get_cart();
    $bird_names = array();
    $product_ids_in_cart = array();

    foreach ($cart_items as $cart_item_key => $cart_item) {
        $product_id = $cart_item['product_id'];
        $product_ids_in_cart[] = $product_id; // Collect product IDs in the cart
        $bird_name = get_field('bird_animal_name', $product_id);
        if ($bird_name && !in_array($bird_name, $bird_names)) {
            $bird_names[] = $bird_name;
        }
    }

    // If there are no bird names in the cart, exit the function early
    if (empty($bird_names)) return array();

    // Use collected bird names in WP_Query
    $args = array(
        'post_type'      => 'product',
        'post__not_in'   => $product_ids_in_cart, // Exclude products in the cart
        'meta_query'     => array(
            array(
                'key'     => 'bird_animal_name',
                'value'   => $bird_names,
                'compare' => 'IN'
            )
        ),
        'orderby'        => 'meta_value_num', // Order by numeric meta value
        'meta_key'       => '_price',         // Specify the meta key (price in this case)
        'order'          => 'DESC'            // Order in descending order
    );

    $query = new WP_Query($args);
    $related_products = array();

    // Extract results of WP_Query
    if ($query->have_posts()) {
        while ($query->have_posts()) {
            $query->the_post();
            $product_id = get_the_ID();
            $product = wc_get_product($product_id);  // Get the WC product object

            // Get the product image URL
            $image_url = wp_get_attachment_image_src(get_post_thumbnail_id($product_id), 'full')[0];

            $related_products[] = array(
                'title'           => get_the_title(),
                'image'           => $image_url,
                'permalink'       => get_permalink(),
                'regular_price'   => wc_price($product->get_regular_price()),
                'sale_price'      => $product->is_on_sale() ? wc_price($product->get_sale_price()) : null,
                'current_price'   => wc_price($product->get_price()),
                'product_type'    => $product->get_type(),   // Get the product type
                'product_price_html' => $product->get_price_html()  // Get the price in HTML format
            );
        }
        wp_reset_postdata();
    }

    return $related_products;
}, 10, 2);

In Cornerstone I have added what is effectively a product category grid section below the cart. In the Section I have Looper Provider and in the Coumn a Looper Consumer. The Looper Provider is custom with the hook related_products.

There is a condition on the Section to not display the section if the Looper Provider is empty, which is why the Looper Provider is at Section level.

The product’s dynamic data is altered in the appropriate places from the product category defaults, which were copied from my product category layout, to:

Product name: {{dc:looper:field key="title"}}
Product Image: {{dc:looper:field key="image"}}
Image Alt: {{dc:looper:field key="title"}}
Product Link: {{dc:looper:field key="permalink"}}
Original Price: {{dc:looper:field key="regular_price"}}
Current Price : {{dc:looper:field key="product_price_html"}}

I hope this helps anyone else who ever wants to try something slightly different to the default Woocommerce Cross Sells.

Christopher

1 Like

Hi @whitemedia,

Glad that you shared the solution with others.

Thanks

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.