Posts Grouped by Post Dates in Bricks

This Pro tutorial provides the steps to set up nested query loops in Bricks by registering a custom query type called “Post Dates” for the outer query loop and, in the inner loop, accessing the outer date to get posts (can be of any post type) published on that day.

We shall also add caching to store the results for an hour (changeable, of course) in addition to being able to limit the number of dates and specify start and end dates.

Step 1

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

<?php

/**
 * Get an array of all published post dates in Y-m-d format.
 *
 * This function retrieves unique publication dates for a specified post type,
 * with options to limit the number of results and filter by date range.
 * It uses caching to improve performance on repeated calls.
 *
 * @param string $post_type  The post type to query. Default is 'post'.
 * @param int    $limit      The maximum number of dates to return. Default is -1 (no limit).
 * @param string $start_date Optional start date for the query (Y-m-d format).
 * @param string $end_date   Optional end date for the query (Y-m-d format).
 * @return array An array of unique published post dates in Y-m-d format.
 */
function bl_get_all_published_post_dates( string $post_type = 'post', int $limit = -1, string $start_date = '', string $end_date = '' ) : array {
    // Access the WordPress database object
    global $wpdb;

    // Create a unique cache key based on function parameters
    $cache_key = "bl_published_dates_{$post_type}_{$limit}_{$start_date}_{$end_date}";
    
    // Try to retrieve cached results
    $cached_results = wp_cache_get( $cache_key );

    // If cached results exist, return them immediately
    if ( false !== $cached_results ) {
        return $cached_results;
    }

    try {
        // Start building the SQL query
        // Use DATE() to extract only the date part from post_date
        $query = $wpdb->prepare(
            "SELECT DISTINCT DATE(post_date) as post_date
            FROM {$wpdb->posts}
            WHERE post_type = %s
            AND post_status = 'publish'",
            $post_type
        );

        // Add start date filter if provided
        if ( $start_date ) {
            $query .= $wpdb->prepare( " AND post_date >= %s", $start_date );
        }

        // Add end date filter if provided
        if ( $end_date ) {
            $query .= $wpdb->prepare( " AND post_date <= %s", $end_date );
        }

        // Order results by date, most recent first
        $query .= " ORDER BY post_date DESC";

        // Add LIMIT clause if a limit is specified
        if ( $limit > 0 ) {
            $query .= $wpdb->prepare( " LIMIT %d", $limit );
        }

        // Execute the query and get results
        $results = $wpdb->get_col( $query );

        // Ensure all dates are in 'Y-m-d' format
        // This step is necessary because MySQL's DATE() function output
        // might not always match PHP's Y-m-d format exactly
        $formatted_results = array_map( function( $date ) {
            return date( 'Y-m-d', strtotime( $date ) );
        }, $results );

        // Cache the formatted results for 1 hour (3600 seconds)
        wp_cache_set( $cache_key, $formatted_results, '', 3600 );

        return $formatted_results;

    } catch ( Exception $e ) {
        // Log any errors that occur during query execution
        error_log( 'Error in bl_get_all_published_post_dates: ' . $e->getMessage() );
        
        // Return an empty array if an error occurs
        return [];
    }
}

/**
 * Add new query type control called "Post Dates" to Bricks builder.
 *
 * This filter adds a new query type option in the Bricks builder interface,
 * allowing users to query for post dates.
 */
add_filter( 'bricks/setup/control_options', function ( array $control_options ): array {
    $control_options['queryTypes']['bl_post_dates'] = esc_html__( 'Post Dates', 'your-text-domain' );
    return $control_options;
} );

/**
 * Handle the custom 'bl_post_dates' query type in Bricks builder.
 *
 * This filter intercepts queries of type 'bl_post_dates' and returns
 * an array of unique post dates using our custom function.
 */
add_filter( 'bricks/query/run', function( $results, $query_obj ) {
    // Only process queries of type 'bl_post_dates'
    if ( $query_obj->object_type !== 'bl_post_dates' ) {
        return $results;
    }

    // Get post type from query, default to 'post' if not specified
    $post_type = 'post';
    
    // Get limit from query, default to -1 (no limit) if not specified
    $limit = -1;

    // Return the array of post dates
    return bl_get_all_published_post_dates( $post_type, $limit );
}, 10, 2 );

/**
 * Format the current loop object date string.
 *
 * This function is designed to work with the Bricks builder and our custom 'bl_post_dates' query.
 * It retrieves the current date in the loop and formats it according to the provided format string.
 *
 * @param string $format The desired output format for the date. Default is 'j F Y' (e.g., '21 August 2024').
 *                       See PHP date() function for format options: https://www.php.net/manual/en/datetime.format.php
 * @return string The formatted date string or an empty string if not in a 'bl_post_dates' loop or if the date is invalid.
 */
function bl_get_formatted_loop_date( string $format = 'j F Y' ): string {
    // Check if there's any active query looping (for nested queries)
    $looping_query_id = BricksQuery::is_any_looping();

    // If we're in a loop and it's a 'bl_post_dates' query...
    if ( $looping_query_id && BricksQuery::get_query_object_type( $looping_query_id ) === 'bl_post_dates' ) {
        // Get the current date in the loop (expected to be in 'Y-m-d' format)
        $date_string = BricksQuery::get_loop_object( $looping_query_id );

        // Parse the date string into a DateTime object
        $date = DateTime::createFromFormat( 'Y-m-d', $date_string );

        // If the date is valid, format it as requested
        if ( $date instanceof DateTime ) {
            return $date->format( $format );
        }
    }

    // If not in a 'bl_post_dates' loop or date is invalid, return an empty string
    return '';
}

Step 2

Whitelist the bl_get_formatted_loop_date function.

Ex.:

<?php 

add_filter( 'bricks/code/echo_function_names', function() {
  return [
    'bl_get_formatted_loop_date'
  ];
} );

You should also add other functions (native or custom) being used in your Bricks instance besides bl_get_formatted_loop_date. This can be checked at Bricks → Settings → Custom code by clicking the Code review button.

More info on whitelisting can be found here.

Step 3

Edit with Bricks your Page/template in which you would like to show the posts output.

Copy this Section’s JSON and paste.

The outer query loop type should be set to Post Dates.

The H3 heading for outputting the date string:

{echo:bl_get_formatted_loop_date}

Inner query loop’s PHP query:

<?php

$date_parts = explode( '-', bl_get_formatted_loop_date( 'Y-m-d' ) );

return [
	'post_type'      => 'post',
	'posts_per_page' => -1,
	'ignore_sticky_posts' => 1,
	'date_query'     => [
		[
			'year'  => $date_parts[0],
			'month' => $date_parts[1],
			'day'   => $date_parts[2],
		],
	],
];