php – Get (and display) other posts that match ACF relationship field-ThrowExceptions

Exception or error:

I have an ACF relationship field called products. This field is present on the custom post type called resources.

In resources, I have three blogs, titled:

  • Blog 1
  • Blog 2
  • Blog 3

Blog 1 has the products field set to “Premium”. Blog 2 also has this field set to “Premium”.

Blog 3 has the products field set to “Common”.

I’ve made a custom module that will showcase “related products” (blogs that have matching products). For example, if I’m on Blog 1, I’m expecting to see the title of Blog 2 in this custom module because of the products field match (they’re both set to “Premium”).

If I’m on Blog 3, I expect to see nothing, because no other post exists with the products value.

Currently, in my custom module (called “related products” for reference),I have the following code:

$posts = get_field('products');

if( $posts ):

    foreach( $posts as $post): 
        the_title();
    endforeach;

endif;

Now, I’m on Blog 1 and in the “related products” module, I see: Premium printed once.

It’s clearly pulling the data but I think this is only for the current post (it’s showing the product data for blog 1). I’ve tested this by changing products on Blog 3 to “Premium” and the results were still the same on-page, just “Premium” printed once on the post.

What I’m trying to achieve:

  • Get other posts that are the same product type.
  • Extract data from these other posts (I want to get those post titles and display them).
How to solve:

ACF relation field type stores its values as serialized array to database.
Finding fast through serialized array in database as meta field is not possible for this type of field.

I recommend to rebuild and add your own handler for updating relation field into speed oriented structure for getting values.

I suggest the solution based on rebuilding this type of field with custom structure for fast getting related_resources

(By using your own meta fields)

Codes snippets

1. Building custom structure for existing posts

//run this hook for build existing acf to speed search by meta structure
add_action( 'wp', 'theme_prefix_build_resources_products_meta' );
function theme_prefix_build_resources_products_meta() {
    /*  Visit the web site with theme-prefix-rebuild-resources-products GET  param for
        building existing resources to structure for searching*/
    // if needed - you could add permission checking and wp_nonce checking
    if ( ! isset( $_GET['theme-prefix-rebuild-resources-products'] ) ) {
        return;
    }

    wp_suspend_cache_addition( true ); // Prevent Fatal Error by Memory overflow if there is a lot of resources posts

    $args     = [
        'post_type'      => 'resources',
        'post_status'    => [ 'publish', 'pending', 'draft', 'auto-draft', 'future', 'private', 'inherit', 'trash' ],
        'paged'          => 1,
        'posts_per_page' => 20,
    ];
    $meta_key = 'your-prefix-resource-product-id';
    while ( ! empty( $posts = get_posts( $args ) ) ) {
        foreach ( $posts as $post ) {
            $products = get_field( 'products', $post->ID );
            if ( empty( $products ) ) {
                continue;
            }

            delete_post_meta( $post->ID, $meta_key );

            foreach ( $products as $product ) {
                add_post_meta( $post->ID, $meta_key, $product->ID );
            }
        }

        $args['paged'] ++;
    }
}

2. Update field to speed structure

// store the relation field for searching structure
add_filter( 'acf/update_value', 'theme_prefix_acf_update_field', 10, 3 );

function theme_prefix_acf_update_field( $value, $post_id, $field ) {
    if ( ( 'products' !== $field['name'] ) || ( 'resources' !== get_post_type( $post_id ) ) ) {
        return $value;
    }

    $meta_key = 'your-prefix-resource-product-id';
    delete_post_meta( $post_id, $meta_key );

    $products_ids = $value;

    if ( empty( $products_ids ) ) {
        return $value;
    }

    foreach ( $products_ids as $product_id ) {
        add_post_meta( $post_id, $meta_key, $product_id );
    }

    return $value;
}

3. Get related resources

//You could get related_products in loop by calling theme_prefix_get_related_products( get_the_ID() ) ON Resource page
function theme_prefix_get_related_products( $resource_id ) {
    $resource_products    = get_field( 'products' );
    $related_products_ids = wp_list_pluck( $resource_products, 'ID' );

    $meta_key     = 'your-prefix-resource-product-id';
    $meta_queries = array_map(
        function ( $related_product_id ) use ( $meta_key ) {
            return [
                'key'     => $meta_key,
                'value'   => $related_product_id,
                'compare' => '=',
            ];
        },
        $related_products_ids
    );
    $args         = [
        'post_type'      => 'resources',
        'post_status'    => 'publish',
        'posts_per_page' => 5,
        'paged'          => 1,
        'orderby'        => 'rand',
        'meta_query'     => [ $meta_queries ]
    ];

    return get_posts( $args );
}

Answer:

get_field('products') will fetch the products field from the current post, so what you’re getting is expected output.

If you want to show all other posts that have the same value in their products field you’ll need to fetch all posts and compare their products fields;

$thisPostsProductsField = get_field('products');
$allPosts = get_posts([
    'post_type' => 'resources',
    'numberposts' => -1,
    'post__not_in' => [get_the_ID()] # NOTE: Ignore the post we're on
]);
$relatedPosts = [];

foreach ($allPosts as $aPost) {
    $aPostsProductField = get_field('products', $aField->ID); # NOTE: The second argument to get_field() specifies from where to fetch the field, it defaults to the current post ID

    if ($aPostsProductField === $thisPostsProductsField) {
        $relatedPosts[] = $aPost;
    }
}

# $relatedPosts should now contain other posts of type "resources" that have the same "products" field
foreach ($relatedPosts as $post) {
    setup_postdata($post);

    the_title();
    the_content();
    the_post_thumbnail();
}

wp_reset_postdata();

I assume here that you’ve limited your products relationship field to one post? Otherwise you’ll need to loop the field value too, and you also need to decide whether it’s enough that a post has one of the original post’s products or if it needs to have all of the original post’s products.

Edit: You could possibly use a meta_query instead to avoid having to fetch every single post and comparing their fields;

$relatedPosts = get_posts([
    'post_type' => 'resources',
    'meta_query' => [
        [
            'key' => 'products',
            'value' => $thisPostsProductsField->ID,
            'compare' => '='
        ]
    ]
]);

But it depends on how ACF stores the products field.

Answer:

What you’re describing sounds straightforward.
However the line you wrote seems incorrect :

$posts = get_field('products');

Indeed the get_field() function returns a single value, not a collection of posts.

Here below is a snippet that retrieves all posts having the products field set to the same value as the current post:

$value = get_field('products');

$posts = get_posts(array(
    'numberposts'   => -1,
    'post_type'     => 'post',
    'meta_key'      => 'products',
    'meta_value'    => $value
));

Hope this helps …

Leave a Reply

Your email address will not be published. Required fields are marked *