Take the 2-minute tour ×
WordPress Development Stack Exchange is a question and answer site for WordPress developers and administrators. It's 100% free, no registration required.

I have a series of posts that are ordered by a meta_key value. They could also be arranged by menu order, if necessary.

The next/prev post links (generated by next_post_link, previous_post_link, or posts_nav_link all navigate by chronology. While I understand this default behaviour, I don't understand how to change it. I found that it maps through to adjacent_post_link in link-template.php, but then it starts to seem fairly hard-coded. Is it recommended to re-write this from scratch to replace it, or is there a better solution.

share|improve this question
    
+1 Kool question! –  kaiser Nov 19 '12 at 17:27
1  
Here is the perfect plugin for your problem : wordpress.org/support/topic/… wordpress.org/extend/plugins/… Thank you Ambrosite ! :) –  miguelb Mar 16 '13 at 12:33
add comment

4 Answers

up vote 10 down vote accepted

Understanding the internals

The "sort" order of adjacent (next/prev) posts is not really a sort "order". It's a separate query on each request/page, but it sorts the query by the post_date - or the post parent if you have a hierarchical post as currently displayed object.

When you take a look at the internals of next_post_link(), then you see that it's basically an API wrapper for adjacent_post_link(). The later function calls get_adjacent_post() internally with the $previous argument/flag set to bool(true|false) to grab the next or previous post link.

What to filter?

After digging deeper into it, you'll see that get_adjacent_post() Source link has some nice filters for its output (a.k.a. query result): (Filter Name/Arguments)

  • "get_{$adjacent}_post_join"

    $join
    // Only if `$in_same_cat`
    // or: ! empty( $excluded_categories` 
    // and then: 
    // " INNER JOIN $wpdb->term_relationships AS tr 
    //     ON p.ID = tr.object_id 
    // INNER JOIN $wpdb->term_taxonomy tt 
    //     ON tr.term_taxonomy_id = tt.term_taxonomy_id"; 
    // and if $in_same_cat then it APPENDS: 
    // " AND tt.taxonomy = 'category' 
    // AND tt.term_id IN (" . implode(',', $cat_array) . ")";
    $in_same_cat
    $excluded_categories
    
  • "get_{$adjacent}_post_where"

    $wpdb->prepare(
          // $op = $previous ? '<' : '>'; | $current_post_date
           "WHERE p.post_date $op %s "
          // $post->post_type
          ."AND p.post_type = %s "
          // $posts_in_ex_cats_sql = " AND tt.taxonomy = 'category' 
          // AND tt.term_id NOT IN (" . implode($excluded_categories, ',') . ')'; 
          // OR empty string if $in_same_cat || ! empty( $excluded_categories
          ."AND p.post_status = 'publish' $posts_in_ex_cats_sql "
        ",
        $current_post_date,
        $post->post_type
    )
    $in_same_cat
    $excluded_categories
    
  • "get_{$adjacent}_post_sort"

    "ORDER BY p.post_date $order LIMIT 1"`
    

So you can do alot with it. That starts with filtering the WHERE clause, as well as the JOINed table and the ORDER BY statement.

The result gets cached in memory for the current request, so it doesn't add additional queries if you call that function multiple times on a single page.

Automatic query building

As @StephenHarris pointed out in the comments, there's a core function that might come in handy when building the SQL Query: get_meta_sql() - Examples in Codex. Basically this function is just used to build the meta SQL statement that gets used in WP_Query, but you can use it in this case (or others) as well. The argument that you throw into it is an array, the exact same that would add to a WP_Query.

$meta_sql = get_meta_sql(
    $meta_query,
    'post',
    $wpdb->posts,
    'ID'
);

The return value is an array:

$sql => (array) 'join' => array(),
        (array) 'where' => array()

So you can use $sql['join'] and $sql['where'] in your callback.

Dependencies to keep in mind

In your case the easiest thing would be to intercept it in a small (mu)plugin or in your themes functions.php file and alter it depending on the $adjacent = $previous ? 'previous' : 'next'; variable and the $order = $previous ? 'DESC' : 'ASC'; variable:

The actual filter names

So the filter names are:

  • get_previous_post_join, get_next_post_join
  • get_previous_post_where, get_next_post_where
  • get_previous_post_sort, get_next_post_sort

Wrapped up as a plugin

...and the filter callback would be (for example) something like the following:

<?php
/** Plugin Name: (#73190) Alter adjacent post link sort order */
function wpse73190_adjacent_post_sort( $orderby )
{
    return "ORDER BY p.menu_order DESC LIMIT 1";
}
add_filter( 'get_previous_post_sort', 'wpse73190_adjacent_post_sort' );
add_filter( 'get_next_post_sort', 'wpse73190_adjacent_post_sort' );
share|improve this answer
2  
+1. Just for information, (@magnakai) if doing something like this for meta queries, check out get_meta_sql() –  Stephen Harris Nov 19 '12 at 18:30
    
+1 to you @StephenHarris ! Haven't seen this one before. Short question: As I read from the source that you have to pass a fully qualified query object, how would you do this with the above mentioned filters? As far as I can see there're only query strings passed, as the query is executed after the filters. –  kaiser Nov 19 '12 at 18:58
2  
nope, $meta_query is just the array you would pass to WP_Query for the meta_query, argument: In this example: $meta_sql = get_meta_sql( $meta_query, 'post', $wpdb->posts, 'ID'); - this generates the JOIN and WHERE part of the query that would need to be added. –  Stephen Harris Nov 19 '12 at 19:41
1  
WOW. Thank you so much @kaiser, that's an amazing answer. Educational, well written, and extremely handy. Thank you so much. –  Magnakai Nov 20 '12 at 8:41
1  
Thanks to you too, @StephenHarris, that's the cherry on this delicious code pie. –  Magnakai Nov 20 '12 at 8:41
show 3 more comments

Kaiser's answer is awesome and thorough, however just changing the ORDER BY clause isn't enough unless your menu_order matches your chronological order.

I can't take credit for this, but I found the following code in this gist:

<?php
/**
 * Customize Adjacent Post Link Order
 */
function wpse73190_gist_adjacent_post_where($sql) {
  if ( !is_main_query() || !is_singular() )
    return $sql;

  $the_post = get_post( get_the_ID() );
  $patterns = array();
  $patterns[] = '/post_date/';
  $patterns[] = '/\'[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}\'/';
  $replacements = array();
  $replacements[] = 'menu_order';
  $replacements[] = $the_post->menu_order;
  return preg_replace( $patterns, $replacements, $sql );
}
add_filter( 'get_next_post_where', 'wpse73190_gist_adjacent_post_where' );
add_filter( 'get_previous_post_where', 'wpse73190_gist_adjacent_post_where' );

function wpse73190_gist_adjacent_post_sort($sql) {
  if ( !is_main_query() || !is_singular() )
    return $sql;

  $pattern = '/post_date/';
  $replacement = 'menu_order';
  return preg_replace( $pattern, $replacement, $sql );
}
add_filter( 'get_next_post_sort', 'wpse73190_gist_adjacent_post_sort' );
add_filter( 'get_previous_post_sort', 'wpse73190_gist_adjacent_post_sort' );

I've modified the function names for WP.SE.

If you only change the ORDER BY clause, the query still looks for posts greater than or less than the current post date. If your posts aren't in chronological order, you won't get the right post.

This changes the where clause to look for posts where the menu_order is greater than or less than the current post's menu_order, in addition to modifying the orderby clause.

The orderby clause also shouldn't be hardcoded to use DESC as it will need to switch based on whether you are getting the next or previous post link.

share|improve this answer
    
One note: The WHERE clause looks for 'YYYY-mm-dd HH:mm:ss'. If that isn't met, it won't work. As the value isn't set by the DB, but by the Application, you will have to check for that format first when building the Regular expression. –  kaiser Apr 6 at 12:11
add comment
function wpse73190_gist_adjacent_post_sort( $sql ) {
    $pattern = '/post_date/';
    $replacement = 'menu_order';

    return preg_replace( $pattern, $replacement, $sql );
}

add_filter( 'get_next_post_sort', 'wpse73190_gist_adjacent_post_sort' );
add_filter( 'get_previous_post_sort', 'wpse73190_gist_adjacent_post_sort' );
share|improve this answer
add comment

I find this small plugin really handy: http://wordpress.org/plugins/wp-query-powered-adjacent-post-link/

WP_Query Powered Adjacent Post Link is a plugin for developers. It adds the function wpqpapl(); to WordPress which can return information on the previous and next post to the current. It accepts arguments for use in the WP_Query class.

share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.