分析 WordPress `get_adjacent_post()` 函数源码:如何查询相邻文章的 ID。

各位同学,早上好!今天给大家带来一场关于 WordPress get_adjacent_post() 函数的深度剖析,咱们一起扒一扒它查询相邻文章 ID 的秘密。别担心,我会尽量用大白话,配合代码示例,保证大家听得懂、学得会,还能举一反三。准备好了吗?Let’s dive in!

一、开场白:相邻文章的重要性

在开始之前,先聊聊为什么要研究相邻文章。想想看,当用户浏览一篇博客文章时,如果页面底部有“上一篇”和“下一篇”的链接,是不是能极大地提升用户体验?这能引导用户继续浏览网站上的其他内容,增加页面停留时间,降低跳出率。甚至,相邻文章的关联性,还能提升网站的 SEO 表现。

而 WordPress 的 get_adjacent_post() 函数,正是实现这一功能的利器。它能根据当前文章,查询到前一篇或后一篇的文章 ID。

二、get_adjacent_post() 函数的基本用法

首先,我们来回顾一下 get_adjacent_post() 函数的基本用法。它的函数原型如下:

<?php
get_adjacent_post( bool $in_same_term = false, string $excluded_terms = '', bool $previous = true, string $taxonomy = 'category' );
?>
  • $in_same_term (bool, 可选): 是否只在同一分类/标签下的文章中查找。默认为 false,即不限制分类/标签。
  • $excluded_terms (string, 可选): 排除的分类/标签 ID 列表,多个 ID 用逗号分隔。
  • $previous (bool, 可选): true 表示查找前一篇,false 表示查找后一篇。默认为 true
  • $taxonomy (string, 可选): 当 $in_same_termtrue 时,指定分类法(taxonomy),默认为 category(分类)。

返回值:返回一个 WP_Post 对象,如果找不到相邻文章,则返回 null

简单示例:

<?php
$previous_post = get_adjacent_post( false, '', true ); // 获取前一篇
$next_post = get_adjacent_post( false, '', false ); // 获取后一篇

if ( $previous_post ) {
    echo '<a href="' . get_permalink( $previous_post->ID ) . '">上一篇:' . esc_html( $previous_post->post_title ) . '</a>';
}

if ( $next_post ) {
    echo '<a href="' . get_permalink( $next_post->ID ) . '">下一篇:' . esc_html( $next_post->post_title ) . '</a>';
}
?>

三、源码分析:get_adjacent_post() 的核心逻辑

get_adjacent_post() 函数的源码位于 wp-includes/link-template.php 文件中。接下来,我们逐行剖析它的核心逻辑。

1. 准备工作:参数处理和全局变量

首先,函数会处理传入的参数,并获取一些全局变量,例如 $post(当前文章对象)和 $wpdb(数据库连接对象)。

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

    if ( empty( $post ) ) {
        return null;
    }

这段代码很简单,如果 $post 对象为空,说明没有当前文章,直接返回 null

2. 构建 SQL 查询语句

这是最关键的部分。函数会根据传入的参数,动态构建 SQL 查询语句,用于从数据库中查询相邻文章的 ID。

    $current_post_date = $post->post_date;

    if ( $previous ) {
        $op = '>';
        $order = 'ASC';
    } else {
        $op = '<';
        $order = 'DESC';
    }

    $join = '';
    $where = '';

    if ( $in_same_term && ! empty( $taxonomy ) ) {
        if ( ! is_object( $taxonomy ) ) {
            $taxonomy_obj = get_taxonomy( $taxonomy );
        }

        if ( ! empty( $taxonomy_obj ) && ! is_wp_error( $taxonomy_obj ) ) {
            $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 AND tt.term_id IN ( SELECT term_id FROM {$wpdb->term_relationships} WHERE object_id = %d )", $taxonomy, $post->ID );

            if ( ! empty( $excluded_terms ) ) {
                $excluded_terms = wp_parse_id_list( $excluded_terms );
                $where .= ' AND tt.term_id NOT IN (' . implode( ',', array_map( 'absint', $excluded_terms ) ) . ')';
            }
        }
    }

    $where .= $wpdb->prepare( " AND p.post_date $op %s AND p.post_type = %s AND p.post_status = 'publish'", $current_post_date, $post->post_type );

    $query = "SELECT p.ID FROM {$wpdb->posts} AS p $join WHERE 1=1 $where ORDER BY p.post_date $order LIMIT 1";

这段代码逻辑稍微复杂一些,我们分步解析:

  • 确定比较运算符和排序方式: 根据 $previous 的值,确定 SQL 语句中使用的比较运算符 (><) 和排序方式 (ASCDESC)。
  • 构建 JOINWHERE 子句(当 $in_same_termtrue 时): 如果需要限制在同一分类/标签下查找,则需要添加 JOIN 子句,将 wp_posts 表与 wp_term_relationshipswp_term_taxonomy 表连接起来。WHERE 子句用于筛选出与当前文章属于同一分类/标签的文章。如果指定了 $excluded_terms,则还需要排除指定的分类/标签。
  • 构建 WHERE 子句(通用条件): 无论是否限制在同一分类/标签下查找,都需要添加通用的 WHERE 子句,用于筛选出 post_date 大于/小于当前文章的 post_date,并且 post_typepost_status 符合要求的文章。
  • 构建完整的 SQL 查询语句: 将以上各个部分组合起来,构建完整的 SQL 查询语句。

示例 SQL 查询语句:

假设当前文章 ID 为 123,发布时间为 2023-10-27 10:00:00,要查找前一篇同分类的文章,且排除 ID 为 456 的分类,则生成的 SQL 查询语句可能如下:

SELECT p.ID
FROM wp_posts AS p
INNER JOIN wp_term_relationships AS tr ON p.ID = tr.object_id
INNER JOIN wp_term_taxonomy AS tt ON tr.term_taxonomy_id = tt.term_taxonomy_id
WHERE 1=1
AND tt.taxonomy = 'category'
AND tt.term_id IN (
    SELECT term_id
    FROM wp_term_relationships
    WHERE object_id = 123
)
AND tt.term_id NOT IN (456)
AND p.post_date < '2023-10-27 10:00:00'
AND p.post_type = 'post'
AND p.post_status = 'publish'
ORDER BY p.post_date DESC
LIMIT 1

3. 执行 SQL 查询并获取结果

接下来,函数会使用 $wpdb 对象执行 SQL 查询,并获取查询结果。

    $adjacent_id = $wpdb->get_var( $query );

$wpdb->get_var() 函数用于执行 SQL 查询,并返回查询结果的第一行第一列的值,也就是相邻文章的 ID。

4. 返回 WP_Post 对象或 null

最后,函数会根据查询结果,返回 WP_Post 对象或 null

    if ( $adjacent_id ) {
        return get_post( $adjacent_id );
    } else {
        return null;
    }
}

如果查询到了相邻文章的 ID,则使用 get_post() 函数获取该文章的 WP_Post 对象并返回。否则,返回 null

四、代码示例:自定义相邻文章查询

了解了 get_adjacent_post() 函数的源码之后,我们就可以根据自己的需求,自定义相邻文章的查询逻辑。

示例 1:查找同一作者的文章

假设我们要查找与当前文章属于同一作者的前一篇或后一篇文章。我们可以通过修改 SQL 查询语句来实现。

<?php
function get_adjacent_post_by_author( $previous = true ) {
    global $post, $wpdb;

    if ( empty( $post ) ) {
        return null;
    }

    $current_post_date = $post->post_date;
    $current_post_author = $post->post_author;

    if ( $previous ) {
        $op = '>';
        $order = 'ASC';
    } else {
        $op = '<';
        $order = 'DESC';
    }

    $where = $wpdb->prepare( " AND p.post_date $op %s AND p.post_type = %s AND p.post_status = 'publish' AND p.post_author = %d", $current_post_date, $post->post_type, $current_post_author );

    $query = "SELECT p.ID FROM {$wpdb->posts} AS p WHERE 1=1 $where ORDER BY p.post_date $order LIMIT 1";

    $adjacent_id = $wpdb->get_var( $query );

    if ( $adjacent_id ) {
        return get_post( $adjacent_id );
    } else {
        return null;
    }
}

// 使用示例
$previous_post = get_adjacent_post_by_author( true ); // 获取同一作者的前一篇
$next_post = get_adjacent_post_by_author( false ); // 获取同一作者的后一篇

if ( $previous_post ) {
    echo '<a href="' . get_permalink( $previous_post->ID ) . '">上一篇(同一作者):' . esc_html( $previous_post->post_title ) . '</a>';
}

if ( $next_post ) {
    echo '<a href="' . get_permalink( $next_post->ID ) . '">下一篇(同一作者):' . esc_html( $next_post->post_title ) . '</a>';
}
?>

在这个示例中,我们在 WHERE 子句中添加了 p.post_author = %d 的条件,用于筛选出与当前文章属于同一作者的文章。

示例 2:查找指定时间范围内的文章

假设我们要查找与当前文章发布时间相差不超过 7 天的前一篇或后一篇文章。

<?php
function get_adjacent_post_within_days( $days = 7, $previous = true ) {
    global $post, $wpdb;

    if ( empty( $post ) ) {
        return null;
    }

    $current_post_date = strtotime( $post->post_date );
    $seconds = $days * 24 * 60 * 60;

    if ( $previous ) {
        $op = '<';
        $order = 'DESC';
        $date_limit = date( 'Y-m-d H:i:s', $current_post_date - $seconds );
    } else {
        $op = '>';
        $order = 'ASC';
        $date_limit = date( 'Y-m-d H:i:s', $current_post_date + $seconds );
    }

    $where = $wpdb->prepare( " AND p.post_date $op %s AND p.post_date %s %s AND p.post_type = %s AND p.post_status = 'publish'", $date_limit, $previous ? '>' : '<', $post->post_date, $post->post_type );

    $query = "SELECT p.ID FROM {$wpdb->posts} AS p WHERE 1=1 $where ORDER BY p.post_date $order LIMIT 1";

    $adjacent_id = $wpdb->get_var( $query );

    if ( $adjacent_id ) {
        return get_post( $adjacent_id );
    } else {
        return null;
    }
}

// 使用示例
$previous_post = get_adjacent_post_within_days( 7, true ); // 获取 7 天内的前一篇
$next_post = get_adjacent_post_within_days( 7, false ); // 获取 7 天内的后一篇

if ( $previous_post ) {
    echo '<a href="' . get_permalink( $previous_post->ID ) . '">上一篇(7天内):' . esc_html( $previous_post->post_title ) . '</a>';
}

if ( $next_post ) {
    echo '<a href="' . get_permalink( $next_post->ID ) . '">下一篇(7天内):' . esc_html( $next_post->post_title ) . '</a>';
}
?>

在这个示例中,我们计算了时间范围的上下限,并在 WHERE 子句中添加了 p.post_date > %sp.post_date < %s 的条件,用于筛选出指定时间范围内的文章。

五、注意事项

  • 性能优化: 如果网站的文章数量非常庞大,频繁调用 get_adjacent_post() 函数可能会影响性能。可以考虑使用缓存机制,将查询结果缓存起来,避免重复查询数据库。
  • 索引优化: 为了提高查询效率,可以为 wp_posts 表的 post_datepost_typepost_status 字段添加索引。
  • 安全问题: 在使用自定义 SQL 查询时,要注意防止 SQL 注入攻击。使用 $wpdb->prepare() 函数可以有效地防止 SQL 注入。

六、总结

get_adjacent_post() 函数是 WordPress 中一个非常实用的小工具,可以方便地实现相邻文章的链接。通过分析其源码,我们可以深入了解其内部的查询逻辑,并根据自己的需求,自定义相邻文章的查询方式。希望今天的讲座能帮助大家更好地理解和使用 get_adjacent_post() 函数。

表格总结:get_adjacent_post() 函数参数详解

参数名 类型 可选 描述
$in_same_term bool 是否只在同一分类/标签下的文章中查找。
$excluded_terms string 排除的分类/标签 ID 列表,多个 ID 用逗号分隔。
$previous bool true 表示查找前一篇,false 表示查找后一篇。
$taxonomy string $in_same_termtrue 时,指定分类法(taxonomy),例如 category(分类)、post_tag(标签)等。

表格总结:SQL 查询语句的关键组成部分

部分 描述
SELECT 指定要查询的字段,通常是 p.ID(文章 ID)。
FROM 指定要查询的表,通常是 wp_posts 表。
JOIN 用于连接多个表,例如当 $in_same_termtrue 时,需要将 wp_posts 表与 wp_term_relationshipswp_term_taxonomy 表连接起来。
WHERE 指定查询条件,用于筛选出符合要求的文章。WHERE 子句可以包含多个条件,例如 p.post_date > %s(文章发布时间大于指定时间)、p.post_type = %s(文章类型等于指定类型)、p.post_status = 'publish'(文章状态为已发布)等。
ORDER BY 指定排序方式,用于按照 post_date 字段对查询结果进行排序。ASC 表示升序,DESC 表示降序。
LIMIT 指定返回结果的数量,通常是 LIMIT 1,表示只返回一条结果。

好了,今天的讲座就到这里。希望大家有所收获!下课!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注