I don’t see a way to make the query faster, but we can cache the result.
Unfortunately, there is no hook to circumvent next_post_link()
and previous_post_link()
, so we have to replace those functions with custom functions.
The following sample code uses a post meta field to store the result. There might be side effects – I tried to cover most cases, but might have missed something.
<?php # -*- coding: utf-8 -*-
namespace WPSE;
/* Plugin Name: Fast Next/Prev Post Links
* Plugin URI: https://wordpress.stackexchange.com/q/101435/
*/
add_action(
'fast_prev_post_link',
__NAMESPACE__ . '\fast_prev_post_link',
10,
4
);
add_action(
'fast_next_post_link',
__NAMESPACE__ . '\fast_next_post_link',
10,
4
);
add_action(
'transition_post_status',
__NAMESPACE__ . '\delete_fast_adjacent_meta',
10,
3
);
/**
* Print previous post link.
*
* @param unknown_type $format
* @param unknown_type $link
* @param unknown_type $in_same_cat
* @param unknown_type $excluded_categories
*/
function fast_prev_post_link(
$format = '« %link',
$link = '%title',
$in_same_cat = FALSE,
$excluded_categories = ''
)
{
empty ( $format ) && $format = '%link »';
fast_adjacent_post_link(
$format,
$link,
$in_same_cat,
$excluded_categories,
TRUE
);
}
/**
* Print next post link.
*
* @param string $format
* @param string $link
* @param bool $in_same_cat
* @param array|string $excluded_categories
* @return void
*/
function fast_next_post_link(
$format = '%link »',
$link = '%title',
$in_same_cat = FALSE,
$excluded_categories = ''
)
{
empty ( $format ) && $format = '%link »';
fast_adjacent_post_link(
$format,
$link,
$in_same_cat,
$excluded_categories,
FALSE
);
}
/**
* Display adjacent post link.
*
* Slightly changed copy of adjacent_post_link().
* Unfortunately, WP mixes retrieving data and display. :(
*
* Can be either next post link or previous.
*
* @param string $format Link anchor format.
* @param string $link Link permalink format.
* @param bool $in_same_cat Whether link should be in a same
* category.
* @param array|string $excluded_categories Array or comma-separated list of
* excluded category IDs.
* @param bool $previous Default is true. Whether to display
* link to previous or next post.
* @return void
*/
function fast_adjacent_post_link(
$format,
$link,
$in_same_cat = FALSE,
$excluded_categories = '',
$previous = TRUE
)
{
if ( $previous && is_attachment() )
$post = get_post( get_post()->post_parent );
else // the only real change
$post = get_fast_adjacent_post(
$in_same_cat,
$excluded_categories,
$previous
);
if ( ! $post ) {
$output = '';
} else {
$title = $post->post_title;
if ( empty( $post->post_title ) )
$title = $previous ? __( 'Previous Post' ) : __( 'Next Post' );
$title = apply_filters( 'the_title', $title, $post->ID );
$date = mysql2date( get_option( 'date_format' ), $post->post_date );
$rel = $previous ? 'prev' : 'next';
$string = '<a href="' . get_permalink( $post ) . '" rel="'.$rel.'">';
$inlink = str_replace( '%title', $title, $link );
$inlink = str_replace( '%date', $date, $inlink );
$inlink = $string . $inlink . '</a>';
$output = str_replace( '%link', $inlink, $format );
}
$adjacent = $previous ? 'previous' : 'next';
echo apply_filters( "{$adjacent}_post_link", $output, $format, $link, $post );
}
/**
* Get next or previous post from post meta.
*
* @param bool $in_same_cat
* @param string|array $excluded_categories
* @param bool $previous
* @param object $post
* @return object|NULL Either the found post object or NULL
*/
function get_fast_adjacent_post(
$in_same_cat = FALSE,
$excluded_categories = array(),
$previous = TRUE,
$post = NULL
)
{
if ( ! $post = get_post( $post ) )
return;
$excluded_categories = prepare_excluded_categories( $excluded_categories );
$type = $previous ? 'prev' : 'next';
$cat_hash = empty ( $excluded_categories ) ? 0 : join( '-', $excluded_categories );
$hash = (int) $in_same_cat . "-$cat_hash";
$meta = (array) get_post_meta( $post->ID, "_fast_{$type}_post", TRUE );
if ( isset ( $meta[ $hash ] ) )
return get_post( $meta[ $hash ] );
$ad_post = get_adjacent_post( $in_same_cat, $excluded_categories, $previous );
if ( ! $ad_post )
return;
$meta[ $hash ] = $ad_post->ID;
update_post_meta( $post->ID, "_fast_{$type}_post", $meta );
return $ad_post;
}
/**
* Prepare categories sent as string.
*
* @param string|array $cats
* @return array
*/
function prepare_excluded_categories( $cats )
{
if ( empty ( $cats ) or is_array( $cats ) )
return array();
$cats = explode( ',', $cats );
$cats = array_map( 'trim', $excluded_categories );
$cats = array_map( 'intval', $excluded_categories );
return $cats;
}
/**
* Deletes post meta values for the current post and all posts referencing it.
*
* @wp-hook transition_post_status
* @param string $new_status
* @param string $old_status
* @param object $post
* @return void
*/
function delete_fast_adjacent_meta( $new_status, $old_status, $post )
{
$prev = (array) get_post_meta( $post->ID, '_fast_prev_post', TRUE );
if ( ! empty ( $prev ) )
{
foreach ( $prev as $post_id )
delete_post_meta( $post_id, '_fast_next_post' );
}
$next = (array) get_post_meta( $post->ID, '_fast_next_post', TRUE );
if ( ! empty ( $next ) )
{
foreach ( $next as $post_id )
delete_post_meta( $post_id, '_fast_prev_post' );
}
delete_post_meta( $post->ID, '_fast_prev_post' );
delete_post_meta( $post->ID, '_fast_next_post' );
}
To use these functions, add this to the theme:
do_action( 'fast_prev_post_link' );
do_action( 'fast_next_post_link' );
ascii
table$in_same_cat
argument, right? Or you wouldn't have, or need, theJOIN
.