admin管理员组文章数量:1125457
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.
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.
- 2 Here is the perfect plugin for your problem : wordpress.org/support/topic/… wordpress.org/extend/plugins/… Thank you Ambrosite ! :) – miguelb Commented Mar 16, 2013 at 12:33
- 1 Note that the second answer seems to yield the correct result. – Thomas Commented Mar 11, 2016 at 12:34
13 Answers
Reset to default 36Understanding 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 JOIN
ed 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' );
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.
Tried to hook in without success. Might be just a problem of my configuration, but for those who can't make the hook work, here is the simplest solution:
<?php
$all_posts = new WP_Query(array(
'orderby' => 'menu_order',
'order' => 'ASC',
'posts_per_page' => -1
));
foreach($all_posts->posts as $key => $value) {
if($value->ID == $post->ID){
$nextID = $all_posts->posts[$key + 1]->ID;
$prevID = $all_posts->posts[$key - 1]->ID;
break;
}
}
?>
<?php if($prevID): ?>
<span class="prev">
<a href="<?= get_the_permalink($prevID) ?>" rel="prev"><?= get_the_title($prevID) ?></a>
</span>
<?php endif; ?>
<?php if($nextID): ?>
<span class="next">
<a href="<?= get_the_permalink($nextID) ?>" rel="next"><?= get_the_title($nextID) ?></a>
</span>
<?php endif; ?>
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' );
FWIW here’s how you can order by menu_order
for a specific custom post type:
/**
* Customize adjacent post order
*/
add_filter('get_next_post_sort', function($order) {
if (is_singular('my_custom_post_type')) {
return 'ORDER BY p.menu_order ASC LIMIT 1';
}
return $order;
}, 10);
add_filter('get_previous_post_sort', function($order) {
if (is_singular('my_custom_post_type')) {
return 'ORDER BY p.menu_order DESC LIMIT 1';
}
return $order;
}, 10);
add_filter('get_next_post_where', function() {
if (is_singular('my_custom_post_type')) {
global $post, $wpdb;
return $wpdb->prepare("WHERE p.menu_order > %s AND p.post_type = %s AND p.post_status = 'publish'", $post->menu_order, $post->post_type);
}
}, 10);
add_filter('get_previous_post_where', function() {
if (is_singular('my_custom_post_type')) {
global $post, $wpdb;
return $wpdb->prepare("WHERE p.menu_order < %s AND p.post_type = %s AND p.post_status = 'publish'", $post->menu_order, $post->post_type);
}
}, 10);
Hope this helps someone else!
Based on @Szabolcs Páll's answer I've created this utility class with helper methods to be able to get posts of type by menu order and get the next and previous post by menu order as well. I've additionally added conditions to check if the current post is the first or last post to get the last or first post respectively.
For example:
// $currentPost is first by menu order
getPreviousPostByMenuOrder($postType, $$currentPost->ID)
// returns => last post by menu order
// $currentPost is last by menu order
getPreviousPostByMenuOrder($postType, $$currentPost->ID)
// returns => first post by menu order
The full class:
class PostMenuOrderUtils {
public static function getPostsByMenuOrder($postType){
$args =[
'post_type' => $postType,
'orderby' => 'menu_order',
'order' => 'ASC',
'posts_per_page' => -1
];
$posts = get_posts($args);
return $posts;
}
public static function getNextPostByMenuOrder($postType, $postID){
$posts = self::getPostsByMenuOrder($postType);
$nextPost = null;
foreach($posts as $key => $value) {
if($value->ID == $postID){
$nextPost = $posts[$key] !== end($posts) ? $posts[$key + 1] : $posts[0];
break;
}
}
return $nextPost;
}
public static function getPreviousPostByMenuOrder($postType, $postID){
$posts = self::getPostsByMenuOrder($postType);
$prevPost = null;
foreach($posts as $key => $value) {
if($value->ID == $postID){
$prevPost = $key !== 0 ? $posts[$key - 1] : end($posts);
break;
}
}
return $prevPost;
}
}
Based on @Szabolcs Páll's answer and bbloomer's post on adding next/prev buttons in WooCommerce Single Product Page, I created this code.
It sorts all products by meta key and adding prev/next buttons above + below the product.
(The meta key can be an ACF field too!)
/**
* @snippet Add next/prev buttons sorted by meta key or ACF field @ WooCommerce Single Product Page
* @testedwith WooCommerce 4.8.0
* @source Elron : https://wordpress.stackexchange.com/a/365334/98773
* @thanks bbloomer : https://businessbloomer.com/?p=20567
* @thanks Szabolcs Páll : https://wordpress.stackexchange.com/a/284045/98773
*/
add_action('woocommerce_before_single_product', 'elron_prev_next_product');
// and if you also want them at the bottom...
add_action('woocommerce_after_single_product', 'elron_prev_next_product');
function elron_prev_next_product()
{
global $post;
echo '<div class="prev-next-buttons">';
$all_posts = new WP_Query(
array(
'post_type' => 'product',
'meta_key' => 'the_meta_key_or_acf_field', // <-- CHANGE THIS
'orderby' => 'meta_value',
'order' => 'DESC',
'posts_per_page' => -1
)
);
foreach ($all_posts->posts as $key => $value) {
if ($value->ID == $post->ID) {
$nextID = $all_posts->posts[$key + 1]->ID;
$prevID = $all_posts->posts[$key - 1]->ID;
break;
}
}
if ($prevID) : ?>
<a href="<?= get_the_permalink($prevID) ?>" rel="prev" class="prev" title="<?= get_the_title($prevID) ?>"><?= esc_attr__('Previous product') ?></a>
<?php endif; ?>
<?php if ($nextID) : ?>
<a href="<?= get_the_permalink($nextID) ?>" rel="next" class="next" title="<?= get_the_title($nextID) ?>"><?= esc_attr__('Next product') ?></a>
<?php endif; ?>
<?php
echo '</div>';
}
If you want the extra scss file I used: _prev-next-buttons.scss
.prev-next-buttons {
background: $lightpurple;
padding: 2em;
text-align: center;
a {
opacity: 0.7;
border-radius: 0.5em;
border: $white 1px solid;
color: $white;
display: inline-block;
padding: 0.5em 0.8em;
text-decoration: none;
margin: 0 0.1em;
&:hover, &:focus {
opacity: 1;
}
}
.prev {
&:before {
content: "
本文标签:
版权声明:本文标题:Can the NextPrev Post links be ordered by menu order or by a meta key? 内容由网友自发贡献,该文观点仅代表作者本人,
转载请联系作者并注明出处:http://www.betaflare.com/web/1736663948a1946561.html,
本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论