Events grouped by months within years based on event date custom field value

Updated on 17 Apr 2024

This Pro tutorial provides the steps to output posts of an event custom post type grouped by years and inside the years within months based on the event’s event_date custom field value using three nested query loops in Bricks.

This is an advanced usage showcase of Bricks’ querying capabilities and PHP arrays.

We are going to set up a custom “Event Years” query type loop, output the year, inside that a custom “Event Months” query type loop and inside that a Posts query loop that pulls in the current looping year and month’s posts.

Step 1

Create an event CPT.

Create a field group for this CPT having a date-type of field called event_date.

Step 2

Add some events and populate the event date field.

Step 3

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

<?php

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

	return $control_options;
} );

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

	// if option is selected, run our new query
	if ( $query_obj->object_type === 'event_years' ) {
		$results = bl_get_event_year_data()['years'];
	}

    if ( $query_obj->object_type === 'event_months' ) {
        // Current looping year
        $year = BricksQuery::get_loop_object( itchy_get_bricks_looping_parent_query_id_by_level() );

        // Associative array with years as keys and array of months (during which there are events) as values
        $year_months = bl_get_event_year_data()['year_months'];

        // Return the current looping year's months
        $results = $year_months[ $year ] ?? [];
	}

	return $results;
}, 10, 2 );

// Form an array of event years and months data, and return it.
function bl_get_event_year_data(): array {
    $year_month_post_ids = [];
    $year_months = [];

    $posts = get_posts([
        'post_type' => 'event',
        'posts_per_page' => 100, // Set this to a large number
        'meta_key' => 'event_date',
        'orderby' => 'meta_value_num',
        'order' => 'DESC',
        'meta_query' => [
            [
                'key' => 'event_date',
                'compare' => 'EXISTS',
                'type' => 'DATE',
            ],
            [
                'key' => 'event_date',
                'value' => '',
                'compare' => '!=',
            ],
        ],
    ]);

    foreach ( $posts as $post ) {
        // Store the event_date post meta value
        $event_date = get_post_meta( $post->ID, 'event_date', true );

        // Store current event's year in this format: 2024
        $year = wp_date( 'Y', strtotime( $event_date ) );

        // Store current event's month in this format: March
        $month = wp_date( 'F', strtotime( $event_date ) );

        // If the $year_month_post_ids array does not have the year of event being looped over as one of its keys, set it as the key with an empty array as the value
        if ( ! isset( $year_month_post_ids[ $year ] ) ) {
            $year_month_post_ids[ $year ] = [];
        }

        // If the $year_month_post_ids array's $year key does not have the month being looped over as one of its keys, set it as the key with an empty array as the value
        if ( ! isset( $year_month_post_ids[ $year ][ $month ] ) ) {
            $year_month_post_ids[ $year ][ $month ] = [];
        }

        // If the $year_months array does not have the year of event being looped over as one of its keys, set it as the key with an empty array as the value
        if ( ! isset( $year_months[ $year ] ) ) {
            $year_months[ $year ] = [];
        }

        // If the current event's month is not in the $year_months array, add it
        if ( ! in_array( $month, $year_months[ $year ] ) ) {
            $year_months[ $year ][] = $month;
        }

        $year_month_post_ids[ $year ][ $month ][] = $post->ID;
    }

    return [
        'years' => array_keys( $year_month_post_ids ), // https://d.pr/i/uLYvxe
        'year_months' => $year_months, // https://d.pr/i/8T4DgH
        // 'months' => $months,
        'data' => $year_month_post_ids, // https://d.pr/i/LPz0Ma
    ];
}

// Function to get the year of the current looping query.
function bl_get_event_year(): string {
	$looping_query_id = BricksQuery::is_any_looping();
	
	if ( $looping_query_id && BricksQuery::get_query_object_type( $looping_query_id ) === 'event_years' ) {
		return BricksQuery::get_loop_object( $looping_query_id );
	}
	
	// return current year in 4 digit format - this is only a fallback and won't be the one that's output
	return date( 'Y' );
}

// Function to get the current looping object.
if ( ! function_exists( 'bl_get_loop_object_string' ) ) {
	function bl_get_loop_object_string(): string {
		$looping_query_id = BricksQuery::is_any_looping();

		if ( $looping_query_id ) {
			return BricksQuery::get_loop_object( $looping_query_id );
		}

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

// Helper function to get looping parent query Id by level. Default is parent of the current loop.
// https://itchycode.com/bricks-builder-useful-functions-and-tips/
function itchy_get_bricks_looping_parent_query_id_by_level( $level = 1 ) {
	global $bricks_loop_query;

	if ( empty( $bricks_loop_query ) || $level < 1 ) {
		return false;
	}

	$current_query_id = BricksQuery::is_any_looping();
	
	if ( ! $current_query_id ) { 
		return false;
	}
	
	if ( ! isset( $bricks_loop_query[ $current_query_id ] ) ) {
		return false;
	}

	$query_ids = array_reverse( array_keys( $bricks_loop_query ) );

	if ( ! isset( $query_ids[ $level ] )) {
		return false;
	}

	if ( $bricks_loop_query[ $query_ids[ $level ] ]->is_looping ) {
		return $query_ids[ $level ];
	}

	return false;
}

Step 4

Edit the Page where you’d like to output the events grid with Bricks.

Copy and paste the Section’s JSON from our dev site.

Year Loop Block:

Year heading:

{echo:bl_get_event_year}

Month Loop Block:

Month heading:

{echo:bl_get_loop_object_string}

Post Loop Block (PHP query editor):

$month = BricksQuery::get_loop_object( itchy_get_bricks_looping_parent_query_id_by_level() );

$year = BricksQuery::get_loop_object( itchy_get_bricks_looping_parent_query_id_by_level(2) );

$arr = bl_get_event_year_data()['data'][ $year ];

if ( array_key_exists( $month, $arr ) ) {
  return [
    'post_type' => 'event',
    'posts_per_page' => 100, // a large number
    'no_found_rows' => true,
    'post__in' => bl_get_event_year_data()['data'][ $year ][ $month ],
    'orderby' => 'post__in',
    'bricks_force_run' => true
];
} else {
	return [
    'post__in' => [ 0 ],
    'bricks_force_run' => true
  ];
}

Post Title:


{post_title:link} – <span class=»text–xs»>{acf_event_date:d M Y}</span>

view raw

gistfile1.txt

hosted with ❤ by GitHub

Step 5

Whitelist bl_get_event_year and bl_get_loop_object_string functions.