Get next post and previous post by meta key

jean frederic fortier 25858

There are a number of standard WordPress functions to help you add navigation buttons to your posts, notably the_post_navigation which you can use on a single post to print links to the next and previous posts. However, next and previous are defined by publication date – but what if you want to get the next and previous posts according to some other parameter?

I built a site recently for a client’s art gallery. They have regular exhibitions which they promote on the website with information about the exhibitions, notably the start and end dates. Exhibitions are custom post types and the start and end dates are entered through custom metafields.

This all works well enough but because exhibitions are not necessarily entered on the site in chronological order (the order in which they will take place at the gallery), the_post_navigation doesn’t provide a meaningful sequence – users clicking the ‘Next Exhibition’ button would expect to see the next exhibition to take place, not the next exhibition that was entered on to the site.

Instead of retrieving the next published exhibition, it made much more sense to retrieve a link to the exhibition that is chronologically next. If you are browsing an exhibition that takes place in October, you would expect the next button to lead you to an exhibition that will take place in November.

WordPress doesn’t, as far as I can see, offer the option to navigate through posts according to a meta value instead of publication date. So I wrote the following code.

Ordering post navigation by metafield

Firstly, we query the custom post types and order by the meta key / meta value pair, in this case a meta key called _event_start_ts, which is a timestamp value for the date the exhibition starts.

/**
 * Query all the exhibitions and order them by start date
 */
function pf_query_all_exhibitions() {
 // Only return IDs
 $args = array(
  'post_type' => 'exhibition',
  'posts_per_page' => -1,
  'orderby' => 'meta_value',
  'meta_key' => '_event_start_ts',
  'order' => 'ASC',
  'fields' => 'ids'
 );
 $exhibs = new WP_Query( $args );
 $exhibs = $exhibs->posts;
 set_transient( 'exhibition_posts', $exhibs );
}

Note that we use fields => 'ids' to return only the IDs of each exhibition; we don’t need anything else. Through $exhibs->posts, we obtain a simple key=>value array of all our custom posts, e.g.

[0] => 345,
[1] => 1328,
[2] => 57,
[3] => 1284,
etc...

This array is ordered by the meta key, meaning in this instance that it’s ordered by exhibition start date.

Finally, we save the array to a transient so that we don’t need to run it every time we load an exhibition single page.

Updating the transient

function pf_update_exhibition_transient( $post_id, $post, $update ) {
 // Only do this for exhibition post types
 if( 'exhibition' == get_post_type( $post_id ) ) {
  pf_query_all_exhibitions();
 }
}
add_action( 'save_post', 'pf_update_exhibition_transient' );

Of course, new exhibitions are going to get added and dates will change so the transient will need to be updated from time to time. We do that using the function above which runs whenever save a post. It checks that the post is for our custom post type, then runs the first function to re-query the exhibitions and update the transient.

Do the markup for the navigation

So, instead of the_post_navigation which would normally appear at the bottom of the single.php template, we need an alternative for our metafield-based navigation that can be used at the bottom of our single-exhibition.php template (or whatever custom post type you’re using). The code below is what I use:

/**
 * Get navigation markup for exhibitions
 * @see get_the_post_navigation wp-includes/link-template.php
 */
function pf_exhibition_navigation() {
 $navigation = '';
 $previous = pf_get_adjacent_exhibition_link( true );
 $next = pf_get_adjacent_exhibition_link( false );
 // Only add markup if there's somewhere to navigate to.
 if ( $previous || $next ) {
  printf( '<nav class="navigation %1$s" role="navigation"><h2 class="screen-reader-text">%2$s</h2><div class="nav-links">%3$s</div></nav>',
   'post-navigation',
   __( 'Exhibition navigation', 'pf' ),
   $previous . $next
  );
 }
 return $navigation;
}

I add pf_exhibition_navigation() to the bottom of the single template for the custom post type. This uses the following function to grab the links to the next and previous exhibitions, according to their start date not their publication date, then it prints the markup. This is closely based on the_post_navigation markup.

Get the next and previous links

/**
 * Get link for adjacent exhibition
 * @param $previous Boolean - true if previous, false if next
 */
function pf_get_adjacent_exhibition_link( $previous ) {
 $post_id = get_the_ID();
 $exhibs = get_transient( 'exhibition_posts' );
 if( false === $exhibs ) {
  // Set the transient if it's empty
  pf_query_all_exhibitions();
  $exhibs = get_transient( 'exhibition_posts' );
 }
 // Get the position of the current exhibition in the ordered array of all exhibitions
 $pos = array_search( $post_id, $exhibs );
 if( $previous ) {
  $new_pos = $pos - 1;
  $class = 'nav-previous';
 } else {
  $new_pos = $pos + 1;
  $class = 'nav-next';
 }
 if( $exhibs[$new_pos] ) {
  $prev_id = $exhibs[$new_pos];
  $string = sprintf(
   '<div class="%s"><a href="%s" rel="prev">%s</a></div>',
   $class,
   get_permalink( $prev_id ),
   get_the_title( $prev_id )
 );
 return $string;
 }
 return false;
}

Finally, this function uses the array we created of all exhibitions to find the next and previous by start date, not publication date. It returns the markup for the link using the exhibition title as the anchor text.

How do I use this?

Just add pf_exhibition_navigation() at the bottom of your single template for your custom post type, replacing the_post_navigation().

Further reading

I recommend taking a look at the link-template.php file in WordPress core, which contains all the related navigation functions.

Two comments

  1. User image

    Awesome read, thanks very much! This article helped me a lot in achiving what i set out to do and is clear and well explained. I have two suggestions or comments:

    – in the “pf_update_exhibition_transient” function i had to remove the 2nd and 3rd argument in the function call, otherwise creating a new post would throw errors in the backend

    – in the “pf_get_adjacent_exhibition_link” function i changed “if( $exhibs[$new_pos] )” to “if( isset($array[$new_pos]))” to avoid getting a out of bounds

    cheers,
    Dave

  2. User image

    Great article, you really helped me out with this one!
    One little correction for pf_get_adjacent_exhibition_link( $previous ):
    rel should be “next” or “prev”, not always “prev, like this:

    $string = sprintf(
    ‘%s’,
    $class,
    get_permalink( $prev_id ),
    $previous ? “prev” : “next”,
    get_the_title( $prev_id )
    );

Leave a Reply

Your email address will not be published. All fields are required.