一、函数概述
get_adjacent_post()是WordPress核心中一个功能强大但相对”底层”的函数,它同时支撑着get_previous_post()和get_next_post()两个常用函数。理解这个函数的工作原理,能让你在开发中拥有更大的灵活性和控制力。
二、函数原型与参数详解
2.1 官方函数定义
/**
* 获取相邻的文章对象
*
* @param bool $in_same_term 是否在同一分类/标签中查找
* @param array|string $excluded_terms 要排除的分类/标签ID
* @param bool $previous 是否为上一篇(true)或下一篇(false)
* @param string $taxonomy 使用的分类法
* @return WP_Post|null 相邻文章对象或null
*/
function get_adjacent_post(
$in_same_term = false,
$excluded_terms = '',
$previous = true,
$taxonomy = 'category'
) {
// 函数实现...
}
2.2 参数深度解析
2.2.1 $in_same_term 参数
// 基本使用 - 只在同一分类中查找
$adjacent_post = get_adjacent_post(true);
// 多分类法支持
$adjacent_post = get_adjacent_post(
true, // 在同一分类法中
'', // 不排除任何分类
true, // 查找上一篇
'product_cat' // 使用商品分类
);
2.2.2 $excluded_terms 参数
// 排除单个分类
$adjacent_post = get_adjacent_post(false, 5);
// 排除多个分类(数组形式)
$excluded = array(5, 8, 12);
$adjacent_post = get_adjacent_post(false, $excluded);
// 排除多个分类(字符串形式)
$adjacent_post = get_adjacent_post(false, '5,8,12');
// 排除特定标签
$adjacent_post = get_adjacent_post(
true,
array(3, 7), // 排除标签ID 3和7
true,
'post_tag'
);
2.2.3 $previous 参数
// 获取上一篇
$prev_post = get_adjacent_post(false, '', true);
// 获取下一篇
$next_post = get_adjacent_post(false, '', false);
// 动态获取
$direction = isset($_GET['dir']) && $_GET['dir'] === 'prev' ? true : false;
$adjacent_post = get_adjacent_post(false, '', $direction);
三、核心工作原理
3.1 查询机制分析
<?php
/**
* 模拟get_adjacent_post核心逻辑
*/
function debug_adjacent_post_query($in_same_term, $excluded_terms, $previous, $taxonomy) {
global $wpdb, $post;
$current_post_date = $post->post_date;
$operator = $previous ? '<' : '>';
$order = $previous ? 'DESC' : 'ASC';
// 基础查询
$query = "
SELECT p.ID
FROM $wpdb->posts AS p
WHERE p.post_date $operator %s
AND p.post_type = %s
AND p.post_status = 'publish'
";
$query_params = array($current_post_date, $post->post_type);
// 处理分类限制
if ($in_same_term && !empty($taxonomy)) {
$term_ids = wp_get_object_terms($post->ID, $taxonomy, array('fields' => 'ids'));
if (!empty($term_ids)) {
$term_ids = array_map('intval', $term_ids);
// 排除特定分类
if (!empty($excluded_terms)) {
if (!is_array($excluded_terms)) {
$excluded_terms = explode(',', $excluded_terms);
}
$excluded_terms = array_map('intval', $excluded_terms);
$term_ids = array_diff($term_ids, $excluded_terms);
}
if (!empty($term_ids)) {
$term_ids_in = implode(',', $term_ids);
$query .= " AND (
SELECT COUNT(1)
FROM $wpdb->term_relationships AS tr
INNER JOIN $wpdb->term_taxonomy AS tt ON tr.term_taxonomy_id = tt.term_taxonomy_id
WHERE tr.object_id = p.ID
AND tt.term_id IN ($term_ids_in)
) > 0";
}
}
}
$query .= " ORDER BY p.post_date $order LIMIT 1";
echo "SQL查询: " . $wpdb->prepare($query, $query_params);
return $wpdb->get_var($wpdb->prepare($query, $query_params));
}
?>
四、高级应用实例
4.1 创建自定义相邻文章导航组件
<?php
/**
* 高级相邻文章导航组件
*
* @param array $args 配置参数
* @return string HTML输出
*/
function advanced_adjacent_posts_navigation($args = array()) {
$defaults = array(
'wrapper_class' => 'advanced-post-navigation',
'previous_label' => __('上一篇', 'textdomain'),
'next_label' => __('下一篇', 'textdomain'),
'in_same_term' => false,
'excluded_terms' => '',
'taxonomy' => 'category',
'show_thumbnail' => true,
'thumbnail_size' => 'medium',
'show_excerpt' => false,
'excerpt_length' => 20,
'meta_data' => array('date', 'author', 'comments'),
'show_arrow' => true,
'arrow_prev' => '←',
'arrow_next' => '→',
'no_post_text' => __('没有更多文章', 'textdomain'),
);
$args = wp_parse_args($args, $defaults);
// 获取相邻文章
$prev_post = get_adjacent_post(
$args['in_same_term'],
$args['excluded_terms'],
true,
$args['taxonomy']
);
$next_post = get_adjacent_post(
$args['in_same_term'],
$args['excluded_terms'],
false,
$args['taxonomy']
);
// 如果没有相邻文章,尝试获取循环中的首尾文章
if (!$prev_post || !$next_post) {
$adjacent_posts = get_adjacent_posts_loop_fallback($args);
if ($adjacent_posts) {
if (!$prev_post) $prev_post = $adjacent_posts['prev'];
if (!$next_post) $next_post = $adjacent_posts['next'];
}
}
ob_start();
?>
<nav class="<?php echo esc_attr($args['wrapper_class']); ?>" aria-label="<?php esc_attr_e('文章导航', 'textdomain'); ?>">
<!-- 上一篇 -->
<div class="nav-previous">
<?php if ($prev_post) : ?>
<a href="<?php echo esc_url(get_permalink($prev_post)); ?>" rel="prev">
<?php if ($args['show_arrow']) : ?>
<span class="nav-arrow"><?php echo $args['arrow_prev']; ?></span>
<?php endif; ?>
<div class="nav-content">
<span class="nav-label"><?php echo $args['previous_label']; ?></span>
<?php if ($args['show_thumbnail'] && has_post_thumbnail($prev_post->ID)) : ?>
<div class="nav-thumbnail">
<?php echo get_the_post_thumbnail($prev_post->ID, $args['thumbnail_size']); ?>
</div>
<?php endif; ?>
<h4 class="nav-title"><?php echo get_the_title($prev_post); ?></h4>
<?php if ($args['show_excerpt']) : ?>
<div class="nav-excerpt">
<?php
$excerpt = get_the_excerpt($prev_post);
echo wp_trim_words($excerpt, $args['excerpt_length']);
?>
</div>
<?php endif; ?>
<?php if (!empty($args['meta_data'])) : ?>
<div class="nav-meta">
<?php
foreach ($args['meta_data'] as $meta) {
switch ($meta) {
case 'date':
echo '<time class="nav-date">' . get_the_date('', $prev_post) . '</time>';
break;
case 'author':
echo '<span class="nav-author">' . get_the_author_meta('display_name', $prev_post->post_author) . '</span>';
break;
case 'comments':
$comments = get_comments_number($prev_post->ID);
echo '<span class="nav-comments">' . sprintf(_n('%d 评论', '%d 评论', $comments), $comments) . '</span>';
break;
case 'views':
$views = get_post_meta($prev_post->ID, 'views', true);
echo '<span class="nav-views">' . sprintf(__('%s 阅读', 'textdomain'), $views) . '</span>';
break;
}
}
?>
</div>
<?php endif; ?>
</div>
</a>
<?php else : ?>
<span class="nav-empty"><?php echo $args['no_post_text']; ?></span>
<?php endif; ?>
</div>
<!-- 下一篇 -->
<div class="nav-next">
<?php if ($next_post) : ?>
<a href="<?php echo esc_url(get_permalink($next_post)); ?>" rel="next">
<div class="nav-content">
<span class="nav-label"><?php echo $args['next_label']; ?></span>
<?php if ($args['show_thumbnail'] && has_post_thumbnail($next_post->ID)) : ?>
<div class="nav-thumbnail">
<?php echo get_the_post_thumbnail($next_post->ID, $args['thumbnail_size']); ?>
</div>
<?php endif; ?>
<h4 class="nav-title"><?php echo get_the_title($next_post); ?></h4>
<?php if ($args['show_excerpt']) : ?>
<div class="nav-excerpt">
<?php
$excerpt = get_the_excerpt($next_post);
echo wp_trim_words($excerpt, $args['excerpt_length']);
?>
</div>
<?php endif; ?>
<?php if (!empty($args['meta_data'])) : ?>
<div class="nav-meta">
<?php
foreach ($args['meta_data'] as $meta) {
switch ($meta) {
case 'date':
echo '<time class="nav-date">' . get_the_date('', $next_post) . '</time>';
break;
case 'author':
echo '<span class="nav-author">' . get_the_author_meta('display_name', $next_post->post_author) . '</span>';
break;
case 'comments':
$comments = get_comments_number($next_post->ID);
echo '<span class="nav-comments">' . sprintf(_n('%d 评论', '%d 评论', $comments), $comments) . '</span>';
break;
}
}
?>
</div>
<?php endif; ?>
</div>
<?php if ($args['show_arrow']) : ?>
<span class="nav-arrow"><?php echo $args['arrow_next']; ?></span>
<?php endif; ?>
</a>
<?php else : ?>
<span class="nav-empty"><?php echo $args['no_post_text']; ?></span>
<?php endif; ?>
</div>
</nav>
<?php
return ob_get_clean();
}
/**
* 当没有相邻文章时的回退方案
*/
function get_adjacent_posts_loop_fallback($args) {
$query = new WP_Query(array(
'post_type' => get_post_type(),
'post_status' => 'publish',
'posts_per_page' => 50, // 获取足够多的文章
'orderby' => 'date',
'order' => 'DESC',
'fields' => 'ids',
));
if (!$query->have_posts()) {
return false;
}
$post_ids = $query->posts;
$current_index = array_search(get_the_ID(), $post_ids);
if ($current_index === false) {
return false;
}
$prev_id = isset($post_ids[$current_index - 1]) ? $post_ids[$current_index - 1] : false;
$next_id = isset($post_ids[$current_index + 1]) ? $post_ids[$current_index + 1] : false;
return array(
'prev' => $prev_id ? get_post($prev_id) : false,
'next' => $next_id ? get_post($next_id) : false,
);
}
?>
4.2 多种分类法组合查询
<?php
/**
* 多维度相邻文章查询
* 支持在多个分类法中查找相邻文章
*/
function get_adjacent_post_multi_tax($taxonomies = array(), $relation = 'OR') {
global $post, $wpdb;
if (empty($taxonomies)) {
return get_adjacent_post(false, '', true);
}
$taxonomies = (array) $taxonomies;
$current_post_date = $post->post_date;
// 获取当前文章在所有指定分类法中的术语
$term_ids = array();
foreach ($taxonomies as $taxonomy) {
$terms = wp_get_object_terms($post->ID, $taxonomy, array('fields' => 'ids'));
if (!is_wp_error($terms) && !empty($terms)) {
$term_ids = array_merge($term_ids, $terms);
}
}
if (empty($term_ids)) {
return get_adjacent_post(false, '', true);
}
$term_ids = array_unique(array_map('intval', $term_ids));
$term_ids_in = implode(',', $term_ids);
$operator = '<';
$order = 'DESC';
$query = "
SELECT p.ID
FROM $wpdb->posts AS p
WHERE p.post_date $operator %s
AND p.post_type = %s
AND p.post_status = 'publish'
AND p.ID != %d
";
$query_params = array($current_post_date, $post->post_type, $post->ID);
// 构建分类查询条件
if (count($taxonomies) > 1 && $relation === 'AND') {
// AND关系:文章必须包含所有分类法中的至少一个术语
$sub_queries = array();
foreach ($taxonomies as $taxonomy) {
$terms = wp_get_object_terms($post->ID, $taxonomy, array('fields' => 'ids'));
if (!empty($terms)) {
$terms_in = implode(',', array_map('intval', $terms));
$sub_queries[] = "EXISTS (
SELECT 1 FROM $wpdb->term_relationships AS tr
INNER JOIN $wpdb->term_taxonomy AS tt ON tr.term_taxonomy_id = tt.term_taxonomy_id
WHERE tr.object_id = p.ID
AND tt.taxonomy = '$taxonomy'
AND tt.term_id IN ($terms_in)
)";
}
}
if (!empty($sub_queries)) {
$query .= " AND (" . implode(" $relation ", $sub_queries) . ")";
}
} else {
// OR关系:文章包含任何分类法中的任何术语
$query .= " AND EXISTS (
SELECT 1 FROM $wpdb->term_relationships AS tr
INNER JOIN $wpdb->term_taxonomy AS tt ON tr.term_taxonomy_id = tt.term_taxonomy_id
WHERE tr.object_id = p.ID
AND tt.term_id IN ($term_ids_in)
)";
}
$query .= " ORDER BY p.post_date $order LIMIT 1";
$adjacent_id = $wpdb->get_var($wpdb->prepare($query, $query_params));
return $adjacent_id ? get_post($adjacent_id) : null;
}
?>
4.3 自定义排序方式的相邻文章
<?php
/**
* 支持自定义排序字段的相邻文章函数
*
* @param string $orderby 排序字段
* @param string $order 排序方式
* @return WP_Post|null
*/
function get_adjacent_post_custom_order($orderby = 'post_date', $order = 'DESC', $previous = true) {
global $wpdb, $post;
$current_value = get_post_field($orderby, $post->ID);
$operator = $previous ? '<' : '>';
$order_dir = $previous ? 'DESC' : 'ASC';
$valid_orderby = array(
'ID', 'post_author', 'post_date', 'post_title',
'post_modified', 'menu_order', 'comment_count'
);
if (!in_array($orderby, $valid_orderby)) {
$orderby = 'post_date';
}
$query = $wpdb->prepare("
SELECT p.ID
FROM $wpdb->posts AS p
WHERE p.$orderby $operator %s
AND p.post_type = %s
AND p.post_status = 'publish'
AND p.ID != %d
ORDER BY p.$orderby $order_dir
LIMIT 1
", $current_value, $post->post_type, $post->ID);
$adjacent_id = $wpdb->get_var($query);
return $adjacent_id ? get_post($adjacent_id) : null;
}
/**
* 使用示例:按菜单顺序获取相邻文章
*/
function get_adjacent_post_by_menu_order($previous = true) {
$adjacent_post = get_adjacent_post_custom_order('menu_order', 'ASC', $previous);
if (!$adjacent_post) {
// 回退到日期排序
$adjacent_post = get_adjacent_post(false, '', $previous);
}
return $adjacent_post;
}
?>
五、性能优化技巧
5.1 查询缓存优化
<?php
/**
* 带缓存的相邻文章查询
*/
function get_cached_adjacent_post($in_same_term = false, $excluded_terms = '', $previous = true, $taxonomy = 'category') {
$post_id = get_the_ID();
// 生成缓存键
$cache_key = sprintf(
'adjacent_post_%d_%d_%s_%d_%s',
$post_id,
$in_same_term ? 1 : 0,
is_array($excluded_terms) ? implode(',', $excluded_terms) : $excluded_terms,
$previous ? 1 : 0,
$taxonomy
);
$adjacent_post = wp_cache_get($cache_key, 'posts');
if (false === $adjacent_post) {
$adjacent_post = get_adjacent_post($in_same_term, $excluded_terms, $previous, $taxonomy);
wp_cache_set($cache_key, $adjacent_post, 'posts', 12 * HOUR_IN_SECONDS);
}
return $adjacent_post;
}
/**
* 批量获取相邻文章的缓存
*/
function get_cached_adjacent_posts_batch($post_ids, $args = array()) {
$defaults = array(
'in_same_term' => false,
'taxonomy' => 'category',
);
$args = wp_parse_args($args, $defaults);
$results = array();
foreach ($post_ids as $post_id) {
$cache_key = sprintf(
'adjacent_post_%d_%d_%d_%s',
$post_id,
$args['in_same_term'] ? 1 : 0,
$args['previous'] ? 1 : 0,
$args['taxonomy']
);
$adjacent_post = wp_cache_get($cache_key, 'posts');
if (false === $adjacent_post) {
$original_post = $GLOBALS['post'];
$GLOBALS['post'] = get_post($post_id);
setup_postdata($GLOBALS['post']);
$adjacent_post = get_adjacent_post(
$args['in_same_term'],
$args['excluded_terms'] ?? '',
$args['previous'] ?? true,
$args['taxonomy']
);
wp_cache_set($cache_key, $adjacent_post, 'posts', 12 * HOUR_IN_SECONDS);
wp_reset_postdata();
$GLOBALS['post'] = $original_post;
}
$results[$post_id] = $adjacent_post;
}
return $results;
}
?>
六、实际应用场景
6.1 电子商务网站商品导航
<?php
/**
* 商品详情页导航组件
*/
function woocommerce_product_navigation() {
if (!is_product()) {
return;
}
$prev_product = get_adjacent_post(
true, // 同一分类
'', // 不排除任何分类
true, // 上一篇
'product_cat' // 商品分类
);
$next_product = get_adjacent_post(
true,
'',
false,
'product_cat'
);
if (!$prev_product && !$next_product) {
return;
}
echo '<nav class="product-navigation">';
echo '<div class="product-navigation-inner">';
if ($prev_product) {
$prev_price = wc_price(get_post_meta($prev_product->ID, '_price', true));
echo '<a href="' . get_permalink($prev_product) . '" class="prev-product">';
echo '<span class="nav-label">上一商品</span>';
echo '<h4>' . get_the_title($prev_product) . '</h4>';
echo '<span class="product-price">' . $prev_price . '</span>';
echo '</a>';
}
if ($next_product) {
$next_price = wc_price(get_post_meta($next_product->ID, '_price', true));
echo '<a href="' . get_permalink($next_product) . '" class="next-product">';
echo '<span class="nav-label">下一商品</span>';
echo '<h4>' . get_the_title($next_product) . '</h4>';
echo '<span class="product-price">' . $next_price . '</span>';
echo '</a>';
}
echo '</div>';
echo '</nav>';
}
add_action('woocommerce_after_single_product_summary', 'woocommerce_product_navigation', 5);
?>
6.2 新闻网站文章导航
<?php
/**
* 新闻文章增强导航
*/
function news_article_navigation() {
if (!is_single() || 'post' !== get_post_type()) {
return;
}
// 获取同一栏目下的相邻文章
$prev_article = get_adjacent_post(true, '', true, 'category');
$next_article = get_adjacent_post(true, '', false, 'category');
// 获取热门推荐文章
$related_posts = get_related_popular_posts(get_the_ID());
?>
<div class="news-navigation-wrapper">
<div class="adjacent-articles">
<?php if ($prev_article) : ?>
<a href="<?php echo get_permalink($prev_article); ?>" class="prev-article">
<div class="article-info">
<span class="label">上一篇</span>
<h3><?php echo get_the_title($prev_article); ?></h3>
<time><?php echo get_the_date('Y-m-d', $prev_article); ?></time>
</div>
</a>
<?php endif; ?>
<?php if ($next_article) : ?>
<a href="<?php echo get_permalink($next_article); ?>" class="next-article">
<div class="article-info">
<span class="label">下一篇</span>
<h3><?php echo get_the_title($next_article); ?></h3>
<time><?php echo get_the_date('Y-m-d', $next_article); ?></time>
</div>
</a>
<?php endif; ?>
</div>
<?php if (!empty($related_posts)) : ?>
<div class="related-popular">
<h3>相关热门文章</h3>
<div class="popular-posts">
<?php foreach ($related_posts as $related_post) : ?>
<a href="<?php echo get_permalink($related_post); ?>" class="popular-post">
<?php if (has_post_thumbnail($related_post)) : ?>
<?php echo get_the_post_thumbnail($related_post, 'thumbnail'); ?>
<?php endif; ?>
<span><?php echo get_the_title($related_post); ?></span>
</a>
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
</div>
<?php
}
add_action('after_single_content', 'news_article_navigation');
?>
七、常见问题与解决方案
7.1 函数返回null的调试方法
<?php
/**
* 调试get_adjacent_post函数
*/
function debug_adjacent_post() {
if (!is_single()) {
return;
}
echo '<div class="debug-adjacent">';
echo '<h3>get_adjacent_post调试信息</h3>';
$current_post = get_post();
echo '<p>当前文章ID: ' . $current_post->ID . '</p>';
echo '<p>当前文章日期: ' . $current_post->post_date . '</p>';
echo '<p>文章类型: ' . $current_post->post_type . '</p>';
// 测试不同参数
$test_cases = array(
'基本查询' => array(false, '', true, 'category'),
'同一分类' => array(true, '', true, 'category'),
'排除分类' => array(true, array(1,2), true, 'category'),
'标签查询' => array(true, '', true, 'post_tag'),
);
foreach ($test_cases as $label => $params) {
$adjacent = get_adjacent_post($params[0], $params[1], $params[2], $params[3]);
echo '<p><strong>' . $label . ':</strong> ';
echo $adjacent ? '找到文章 #' . $adjacent->ID . ' - ' . $adjacent->post_title : '无结果';
echo '</p>';
}
// 检查分类信息
$categories = wp_get_post_categories($current_post->ID, array('fields' => 'all'));
if (!empty($categories)) {
echo '<p>文章分类: ';
foreach ($categories as $category) {
echo $category->name . ' (ID: ' . $category->term_id . '), ';
}
echo '</p>';
}
echo '</div>';
}
// 仅在管理员登录时显示
if (current_user_can('manage_options')) {
add_action('wp_footer', 'debug_adjacent_post');
}
?>
7.2 处理自定义文章类型
<?php
/**
* 为自定义文章类型添加相邻文章支持
*/
function custom_post_type_adjacent_support() {
// 假设我们有一个自定义文章类型'portfolio'
global $wpdb;
add_filter('get_previous_post_where', 'custom_post_type_adjacent_where');
add_filter('get_next_post_where', 'custom_post_type_adjacent_where');
add_filter('get_previous_post_sort', 'custom_post_type_adjacent_sort');
add_filter('get_next_post_sort', 'custom_post_type_adjacent_sort');
}
function custom_post_type_adjacent_where($where) {
global $post;
if ($post->post_type == 'portfolio') {
$where = str_replace(
"post_type = 'post'",
"post_type = 'portfolio'",
$where
);
}
return $where;
}
function custom_post_type_adjacent_sort($sort) {
global $post;
if ($post->post_type == 'portfolio') {
// 可以为作品集使用不同的排序方式
$sort = str_replace(
'p.post_date',
'p.menu_order',
$sort
);
}
return $sort;
}
add_action('wp', 'custom_post_type_adjacent_support');
?>
八、总结与最佳实践
8.1 关键要点总结
- 灵活使用参数:
get_adjacent_post()的四个参数提供了强大的筛选能力 - 性能考虑:在高流量网站中使用缓存机制
- 错误处理:始终检查函数返回值是否为null
- SEO友好:正确使用rel=”prev”和rel=”next”属性
8.2 最佳实践建议
<?php
/**
* 安全可靠的相邻文章导航实现
*/
function best_practice_adjacent_navigation() {
// 1. 使用缓存
$prev_post = get_cached_adjacent_post(true, '', true, 'category');
$next_post = get_cached_adjacent_post(true, '', false, 'category');
// 2. 提供回退方案
if (!$prev_post && !$next_post) {
$fallback_posts = get_recent_posts_fallback(2);
if (!empty($fallback_posts)) {
$prev_post = $fallback_posts[0] ?? null;
$next_post = $fallback_posts[1] ?? null;
}
}
// 3. 输出语义化HTML
if ($prev_post || $next_post) {
echo '<nav class="post-navigation" role="navigation" aria-label="文章导航">';
if ($prev_post) {
printf(
'<div class="nav-previous"><a href="%s" rel="prev">%s</a></div>',
esc_url(get_permalink($prev_post)),
esc_html(get_the_title($prev_post))
);
}
if ($next_post) {
printf(
'<div class="nav-next"><a href="%s" rel="next">%s</a></div>',
esc_url(get_permalink($next_post)),
esc_html(get_the_title($next_post))
);
}
echo '</nav>';
}
}
?>
通过深入理解和灵活运用get_adjacent_post()函数,你可以为WordPress网站创建高度定制化的导航体验,无论是简单的上一篇/下一篇链接,还是复杂的多维度相关文章推荐系统,都能得心应手地实现。


湘公网安备43020002000238