函数文档

get_adjacent_post()

💡 云策文档标注

概述

get_adjacent_post() 函数用于检索当前文章的相邻文章(上一篇或下一篇)。它支持基于分类法术语的筛选、排除特定术语,并考虑用户权限以处理私有文章。

关键要点

  • 函数返回 WP_Post 对象、null 或空字符串,具体取决于查询结果和全局 $post 的设置。
  • 参数包括 $in_same_term(是否在同一分类法术语中)、$excluded_terms(排除的术语ID)、$previous(是否检索前一篇,默认为 true)和 $taxonomy(分类法名称,默认为 'category')。
  • 函数内部使用 SQL 查询,并应用多个动态过滤器(如 get_{$adjacent}_post_join、get_{$adjacent}_post_where)以允许开发者自定义查询逻辑。
  • 缓存机制通过 wp_cache_get_salted 和 wp_cache_set_salted 提高性能,缓存键基于查询字符串和最后更改时间。
  • 用户权限处理:对于已登录用户,查询可能包含其私有文章或具有 read_private_posts 权限的文章;未登录用户仅检索已发布文章。

代码示例

// 获取同一分类法术语中的前一篇文章
$previous_post = get_adjacent_post(true, '', true, 'category');

// 获取同一分类法术语中的后一篇文章
$next_post = get_adjacent_post(true, '', false, 'category');

注意事项

  • 如果全局 $post 未设置或分类法不存在,函数返回 null。
  • 排除术语参数 $excluded_terms 支持数组或逗号分隔的字符串,旧版本中使用 'and' 分隔已弃用。
  • 函数依赖于当前文章日期和ID进行排序,确保相邻文章的确定性顺序。

📄 原文内容

Retrieves the adjacent post.

Description

Can either be next or previous post.

Parameters

$in_same_termbooloptional
Whether post should be in the same taxonomy term.

Default:false

$excluded_termsint[]|stringoptional
Array or comma-separated list of excluded term IDs.
Default empty string.
$previousbooloptional
Whether to retrieve previous post.

Default:true

$taxonomystringoptional
Taxonomy, if $in_same_term is true. Default 'category'.

Return

WP_Post|null|string Post object if successful. Null if global $post is not set.
Empty string if no corresponding post exists.

Source

function get_adjacent_post( $in_same_term = false, $excluded_terms = '', $previous = true, $taxonomy = 'category' ) {
	global $wpdb;

	$post = get_post();

	if ( ! $post || ! taxonomy_exists( $taxonomy ) ) {
		return null;
	}

	$current_post_date = $post->post_date;

	$join     = '';
	$where    = '';
	$adjacent = $previous ? 'previous' : 'next';

	if ( ! empty( $excluded_terms ) && ! is_array( $excluded_terms ) ) {
		// Back-compat, $excluded_terms used to be $excluded_categories with IDs separated by " and ".
		if ( str_contains( $excluded_terms, ' and ' ) ) {
			_deprecated_argument(
				__FUNCTION__,
				'3.3.0',
				sprintf(
					/* translators: %s: The word 'and'. */
					__( 'Use commas instead of %s to separate excluded terms.' ),
					"'and'"
				)
			);
			$excluded_terms = explode( ' and ', $excluded_terms );
		} else {
			$excluded_terms = explode( ',', $excluded_terms );
		}

		$excluded_terms = array_map( 'intval', $excluded_terms );
	}

	/**
	 * Filters the IDs of terms excluded from adjacent post queries.
	 *
	 * The dynamic portion of the hook name, `$adjacent`, refers to the type
	 * of adjacency, 'next' or 'previous'.
	 *
	 * Possible hook names include:
	 *
	 *  - `get_next_post_excluded_terms`
	 *  - `get_previous_post_excluded_terms`
	 *
	 * @since 4.4.0
	 *
	 * @param int[]|string $excluded_terms Array of excluded term IDs. Empty string if none were provided.
	 */
	$excluded_terms = apply_filters( "get_{$adjacent}_post_excluded_terms", $excluded_terms );

	if ( $in_same_term || ! empty( $excluded_terms ) ) {
		if ( $in_same_term ) {
			$join  .= " INNER JOIN $wpdb->term_relationships AS tr ON p.ID = tr.object_id INNER JOIN $wpdb->term_taxonomy AS tt ON tr.term_taxonomy_id = tt.term_taxonomy_id";
			$where .= $wpdb->prepare( 'AND tt.taxonomy = %s', $taxonomy );

			if ( ! is_object_in_taxonomy( $post->post_type, $taxonomy ) ) {
				return '';
			}
			$term_array = wp_get_object_terms( $post->ID, $taxonomy, array( 'fields' => 'ids' ) );
			if ( is_wp_error( $term_array ) ) {
				return '';
			}

			// Remove any exclusions from the term array to include.
			$term_array = array_diff( $term_array, (array) $excluded_terms );

			if ( ! $term_array ) {
				return '';
			}

			$term_array = array_map( 'intval', $term_array );

			$where .= ' AND tt.term_id IN (' . implode( ',', $term_array ) . ')';
		}

		if ( ! empty( $excluded_terms ) ) {
			$where .= " AND p.ID NOT IN ( SELECT tr.object_id FROM $wpdb->term_relationships tr LEFT JOIN $wpdb->term_taxonomy tt ON (tr.term_taxonomy_id = tt.term_taxonomy_id) WHERE tt.term_id IN (" . implode( ',', array_map( 'intval', $excluded_terms ) ) . ') )';
		}
	}

	// 'post_status' clause depends on the current user.
	if ( is_user_logged_in() ) {
		$user_id = get_current_user_id();

		$post_type_object = get_post_type_object( $post->post_type );
		if ( empty( $post_type_object ) ) {
			$post_type_cap    = $post->post_type;
			$read_private_cap = 'read_private_' . $post_type_cap . 's';
		} else {
			$read_private_cap = $post_type_object->cap->read_private_posts;
		}

		/*
		 * Results should include private posts belonging to the current user, or private posts where the
		 * current user has the 'read_private_posts' cap.
		 */
		$private_states = get_post_stati( array( 'private' => true ) );
		$where         .= " AND ( p.post_status = 'publish'";
		foreach ( $private_states as $state ) {
			if ( current_user_can( $read_private_cap ) ) {
				$where .= $wpdb->prepare( ' OR p.post_status = %s', $state );
			} else {
				$where .= $wpdb->prepare( ' OR (p.post_author = %d AND p.post_status = %s)', $user_id, $state );
			}
		}
		$where .= ' )';
	} else {
		$where .= " AND p.post_status = 'publish'";
	}

	$comparison_operator = $previous ? '<' : '>';
	$order               = $previous ? 'DESC' : 'ASC';

	/**
	 * Filters the JOIN clause in the SQL for an adjacent post query.
	 *
	 * The dynamic portion of the hook name, `$adjacent`, refers to the type
	 * of adjacency, 'next' or 'previous'.
	 *
	 * Possible hook names include:
	 *
	 *  - `get_next_post_join`
	 *  - `get_previous_post_join`
	 *
	 * @since 2.5.0
	 * @since 4.4.0 Added the `$taxonomy` and `$post` parameters.
	 *
	 * @param string       $join           The JOIN clause in the SQL.
	 * @param bool         $in_same_term   Whether post should be in the same taxonomy term.
	 * @param int[]|string $excluded_terms Array of excluded term IDs. Empty string if none were provided.
	 * @param string       $taxonomy       Taxonomy. Used to identify the term used when `$in_same_term` is true.
	 * @param WP_Post      $post           WP_Post object.
	 */
	$join = apply_filters( "get_{$adjacent}_post_join", $join, $in_same_term, $excluded_terms, $taxonomy, $post );

	// Prepare the where clause for the adjacent post query.
	$where_prepared = $wpdb->prepare( "WHERE (p.post_date $comparison_operator %s OR (p.post_date = %s AND p.ID $comparison_operator %d)) AND p.post_type = %s $where", $current_post_date, $current_post_date, $post->ID, $post->post_type ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $comparison_operator is a string literal, either '<' or '>'.

	/**
	 * Filters the WHERE clause in the SQL for an adjacent post query.
	 *
	 * The dynamic portion of the hook name, `$adjacent`, refers to the type
	 * of adjacency, 'next' or 'previous'.
	 *
	 * Possible hook names include:
	 *
	 *  - `get_next_post_where`
	 *  - `get_previous_post_where`
	 *
	 * @since 2.5.0
	 * @since 4.4.0 Added the `$taxonomy` and `$post` parameters.
	 * @since 6.9.0 Adds ID-based fallback for posts with identical dates in adjacent post queries.
	 *
	 * @param string       $where          The `WHERE` clause in the SQL.
	 * @param bool         $in_same_term   Whether post should be in the same taxonomy term.
	 * @param int[]|string $excluded_terms Array of excluded term IDs. Empty string if none were provided.
	 * @param string       $taxonomy       Taxonomy. Used to identify the term used when `$in_same_term` is true.
	 * @param WP_Post      $post           WP_Post object.
	 */
	$where = apply_filters( "get_{$adjacent}_post_where", $where_prepared, $in_same_term, $excluded_terms, $taxonomy, $post );

	/**
	 * Filters the ORDER BY clause in the SQL for an adjacent post query.
	 *
	 * The dynamic portion of the hook name, `$adjacent`, refers to the type
	 * of adjacency, 'next' or 'previous'.
	 *
	 * Possible hook names include:
	 *
	 *  - `get_next_post_sort`
	 *  - `get_previous_post_sort`
	 *
	 * @since 2.5.0
	 * @since 4.4.0 Added the `$post` parameter.
	 * @since 4.9.0 Added the `$order` parameter.
	 * @since 6.9.0 Adds ID sort to ensure deterministic ordering for posts with identical dates.
	 *
	 * @param string $order_by The `ORDER BY` clause in the SQL.
	 * @param WP_Post $post    WP_Post object.
	 * @param string  $order   Sort order. 'DESC' for previous post, 'ASC' for next.
	 */
	$sort = apply_filters( "get_{$adjacent}_post_sort", "ORDER BY p.post_date $order, p.ID $order LIMIT 1", $post, $order );

	$query        = "SELECT p.ID FROM $wpdb->posts AS p $join $where $sort";
	$key          = md5( $query );
	$last_changed = (array) wp_cache_get_last_changed( 'posts' );
	if ( $in_same_term || ! empty( $excluded_terms ) ) {
		$last_changed[] = wp_cache_get_last_changed( 'terms' );
	}
	$cache_key = "adjacent_post:$key";

	$result = wp_cache_get_salted( $cache_key, 'post-queries', $last_changed );
	if ( false !== $result ) {
		if ( $result ) {
			$result = get_post( $result );
		}
		return $result;
	}

	$result = $wpdb->get_var( $query );
	if ( null === $result ) {
		$result = '';
	}

	wp_cache_set_salted( $cache_key, $result, 'post-queries', $last_changed );

	if ( $result ) {
		$result = get_post( $result );
	}

	return $result;
}

Hooks

apply_filters( “get_{$adjacent}_post_excluded_terms”, int[]|string $excluded_terms )

Filters the IDs of terms excluded from adjacent post queries.

apply_filters( “get_{$adjacent}_post_join”, string $join, bool $in_same_term, int[]|string $excluded_terms, string $taxonomy, WP_Post $post )

Filters the JOIN clause in the SQL for an adjacent post query.

apply_filters( “get_{$adjacent}_post_sort”, string $order_by, WP_Post $post, string $order )

Filters the ORDER BY clause in the SQL for an adjacent post query.

apply_filters( “get_{$adjacent}_post_where”, string $where, bool $in_same_term, int[]|string $excluded_terms, string $taxonomy, WP_Post $post )

Filters the WHERE clause in the SQL for an adjacent post query.

Changelog

Version Description
2.5.0 Introduced.

User Contributed Notes

  1. Skip to note 4 content

    I am not sure if there’s a function to get the next post of a post that maybe was retrieved from a custom loop or that isn’t the current global $post;. But sometimes, for design purposes, I have found the need to get the next post but in two levels. I searched for a function that works like this function but passing a custom $post as parameter.

    For example: get_adjacent_post( $my_post, true, '', false, 'taxonomy_slug' );. Where $my_post is a custom post where I would like to get the next post of that specific post.

    I am not sure if currently exists that function, but could be great to have it accessible.