Related Posts Grouped by Custom Field Value when using ACF Relationships in Bricks

In the Bricks Facebook group a user asks:

Let’s summarize the situation.

  • Location CPT (view in question)
    • Related Boat field (Relationship)
  • Boat CPT
    • Related Location field (Relationship)
    • Cabin field (Select)
  • Relationship

with the CPTs and field groups created using ACF.

Requirement: On single locations, show related boats grouped by cabin field.

Rough outline of what we should do:

  • Get array of boat IDs related to the current location
  • Loop through the above boat IDs, get the Cabin field value for each and construct an associative array of cabins => boat IDs.
  • Outer query loop: Custom cabins query type
    • Inner query loop: boats where post__in => bl_get_boats_by_cabin( ‘outer cabin’ )

Sample data (numbers in brackets are post IDs):

  • Location 1 (199): Boat 1 (202), Boat 3 (204), Boat 4 (205)
  • Location 2: Boat 2 (203), Boat 3 (204)
  • Location 3: Boat 4 (205)
  • Boat 1 (202): V-berth (cabin)
  • Boat 2 (203): Saloon (cabin)
  • Boat 3 (204): Saloon (cabin)
  • Boat 4 (205): V-berth (cabin)

This Pro tutorial shows how related posts of a different CPT can be grouped by post meta when viewing single posts of a CPT in Bricks.

Step 1

Create location and boat CPTs.

Location Fields:

Boat Fields:

Step 2

Edit your Location CPT and Boat CPT entries and populate the custom fields for each.

Step 3

Let’s create a custom function that loops through the boats related to the current location and returns an associative array having the value of cabin field as the array keys and an array of related boat post objects as the array values.

Ex.: For ‘Location 1’ post:

Add the following in child theme‘s functions.php (w/o the opening PHP tag) or a code snippets plugin:

(code snippet title suggestion: Get cabins => boat IDs for current location)

<?php 

// Function to
// Get array of boat CPT IDs related to the current location CPT
// Loop through the above boat IDs, get the Cabin ACF custom field value for each and construct an associative array of cabins => boat IDs.
// Return the associative array of cabins => boat IDs.
function bl_get_cabins_by_boat(): array {
	// Get the current location CPT ID.
	$location_id = get_the_ID();
	
	// Initialize an empty associative array of cabins => boat IDs.
	$cabins_by_boat = [];

	// Get the boat CPT IDs related to the current location CPT.
	$boat_ids = get_posts( [
		'post_type' => 'boat',
		'posts_per_page' => -1,
		'meta_query' => [
			[
				'key' => 'related_location',
				'value' => '"' . $location_id . '"',
				'compare' => 'LIKE'
			]
		]
	] );
	
	// If there are no boat CPT IDs, return the empty associative array.
	if ( empty( $boat_ids ) ) {
		return $cabins_by_boat;
	}

	// Loop through the boat IDs and get the Cabin ACF custom field value for each.
	foreach ( $boat_ids as $boat_id ) {
		$cabin = get_field( 'cabin', $boat_id );

        if ( ! empty( $cabin ) ) {
            $cabins[] = $cabin;

            // Construct an associative array of cabins => boat IDs.
            $cabins_by_boat[$cabin][] = $boat_id;
        }
	}
	
	// Return the associative array of cabins => boat IDs.
    return $cabins_by_boat;
}

Step 4

Add another snippet to register the “Cabins” custom query type.

<?php 

// Add new "Cabins" query loop type option in the dropdown.
add_filter( 'bricks/setup/control_options', function( $control_options ): array {
	$control_options['queryTypes']['boat_cabins'] = esc_html__( 'Cabins' );

	return $control_options;
} );

// Run new query if option selected.
add_filter( 'bricks/query/run', function( $results, $query_obj ): array {
	if ( $query_obj->object_type !== 'boat_cabins' ) {
		return $results;
	}

	// if option is selected, run our new query
	if ( $query_obj->object_type === 'boat_cabins' ) {
		$results = array_keys( bl_get_cabins_by_boat() );
	}

	return $results;
}, 10, 2 );

// Function to get the cabin of the current looping query.
function bl_get_boat_cabin(): string {
	$looping_query_id = BricksQuery::is_any_looping();

	if ( $looping_query_id && BricksQuery::get_query_object_type( $looping_query_id ) === 'boat_cabins' ) {
		return BricksQuery::get_loop_object( $looping_query_id );
	}

	// fallback, this won't be the one that's output
	return 'no cabin set';
}

Step 5

Create a Bricks template of type ‘Single’ named “Location”.

Edit it with Bricks.

Set the template condition.

Add a Section with Post Title and Post Content elements.

Add another Section (JSON export for reference) to show related boats grouped by their cabins.

Set a dynamic condition like this to ensure that it gets output only if there is at least 1 related boat:

Add a h2 heading inside the Container with the text of Related Boat.

Let’s add s at the end of this heading text depending on whether there is 1 or more related boats.

Add this snippet:

<?php

// Add "s" at the end of a specific heading element's text if there is more than 1 related boat (using ACF Relationship).
add_filter( 'bricks/element/settings', function( $settings, $element ) {
	if ( $element->id === 'lyogfy' && count( get_field( 'related_boat' ) ) > 1 ) {
		$settings['text'] .= 's';
	}

	return $settings;
}, 10, 3 );

Replace lyogfy with the Bricks ID of the heading element.

Back in the Bricks editor, add a Container below the heading.

Add a Block inside and enable query loop.

Set the query type to Cabins.

Add a h3 heading with this text:

{echo:bl_get_boat_cabin}

This function was defined in the previous step.

Add a Container and a Block inside it.

Enable query loop on the Block. Set Post Type to Boats.

Enable Query editor (PHP).

return [
  'posts_per_page' => '-1',
  'post__in' => wp_list_pluck( bl_get_cabins_by_boat()[BricksQuery::get_query_for_element_id( 'izirjb' )->loop_object], 'ID' ),
];

Add a Basic Text inside the Block having:

{post_title:link}

Reference

https://www.php.net/manual/en/function.array-keys.php