阐述 `comments_template()` 函数的源码,它是如何根据主题目录中的文件来加载评论模板的?

各位听众,晚上好!今天咱们来聊聊 WordPress 里一个“默默奉献”的函数:comments_template()。 别看它名字平平无奇,但它可是负责在你的博客文章里“召唤”评论区的关键人物。 咱们要深入它的源码,看看它是如何一步步找到并加载评论模板的。 准备好了吗?Let’s dive in!

1. 故事的开端:comments_template() 的职责

comments_template() 函数的主要职责非常明确:它负责根据 WordPress 主题的结构,加载相应的评论模板文件,从而在文章或页面中显示评论表单和已有的评论列表。 简单来说,就是把评论功能“变”出来。

2. 源码剖析:一层层抽丝剥茧

咱们直接上代码,然后逐行讲解。 这是 comments_template() 函数的核心代码(简化版,去掉了部分兼容性处理和过滤):

function comments_template( $template = '', $separate_comments = false ) {
    global $wp_query, $withcomments, $post, $wpdb, $id, $comment, $user_login, $user_ID, $comments, $comment_alt, $comment_depth, $comment_thread_alt;

    if ( ! ( is_singular() && ( have_comments() || 'open' == $post->comment_status ) ) ) {
        return;
    }

    $req = get_option( 'require_name_email' );

    $id = get_the_ID();

    if ( null === $id ) {
        return;
    }

    $comments = $wp_query->comments;
    $withcomments = true;

    if ( empty( $template ) ) {
        $template = '/comments.php';
    }

    $template = apply_filters( 'comments_template', $template );

    $include = locate_template( $template, true, false );

    return;
}

现在,我们来一行行解读:

  • function comments_template( $template = '', $separate_comments = false ) { ... }: 函数定义,接受两个参数。 $template 指定了评论模板的文件名(默认是 comments.php),$separate_comments 用于控制是否将评论和 trackbacks/pingbacks 分开显示(通常不用管它)。

  • global ...: 声明了一堆全局变量。 WordPress 的全局变量那可是个大杂烩,这里用到的主要是 $wp_query (保存查询结果), $post (当前文章对象), $id (当前文章ID), $comments (评论列表)等等。 这些全局变量为评论模板的渲染提供数据。

  • if ( ! ( is_singular() && ( have_comments() || 'open' == $post->comment_status ) ) ) { return; }: 这是一个关键的条件判断。 只有当以下条件同时满足时,才会继续执行:

    • is_singular(): 当前页面是单篇文章或页面。 也就是说,评论模板只会在文章或页面上显示,不会在首页、分类页等地方显示。
    • have_comments() || 'open' == $post->comment_status: 要么文章已经有评论,要么文章允许评论。 这保证了只有在需要显示评论或允许发表评论的情况下,才会加载评论模板。
  • $req = get_option( 'require_name_email' );: 获取是否要求评论者填写姓名和邮箱的设置。

  • $id = get_the_ID();: 获取当前文章的 ID。

  • if ( null === $id ) { return; }: 确保文章 ID 存在。

  • $comments = $wp_query->comments;: 将评论列表从 $wp_query 赋值给 $comments 变量,方便在模板中使用。

  • $withcomments = true;: 设置 $withcomments 变量为 true,表示当前页面包含评论。

  • if ( empty( $template ) ) { $template = '/comments.php'; }: 如果没有指定模板文件名,则使用默认的 comments.php

  • $template = apply_filters( 'comments_template', $template );: 这是一个非常重要的过滤器。 它允许你通过插件或主题,修改要加载的评论模板文件名。 比如,你可以根据不同的文章类型,加载不同的评论模板。

  • $include = locate_template( $template, true, false );: 这就是“寻宝”的关键一步! locate_template() 函数会在主题目录及其父主题目录中查找指定的模板文件。 它的三个参数分别是:

    • $template: 要查找的模板文件名。
    • true: 找到模板后,立即包含(require_once)它。
    • false: 是否加载子主题的语言文件。

    locate_template() 函数会按照以下顺序查找模板文件:

    1. 子主题目录(如果存在)。
    2. 父主题目录。

    如果找到了模板文件,locate_template() 函数会返回模板文件的完整路径,并立即包含它。 如果没有找到,则返回空字符串。

  • return;: 函数结束。

3. locate_template() 函数:寻宝的罗盘

locate_template() 函数是寻找模板文件的核心。 为了更清楚地理解它的工作原理,咱们可以简单模拟一下它的实现:

function my_locate_template( $template_names, $load = false, $require_once = true ) {
    $located = false;
    foreach ( (array) $template_names as $template_name ) {
        if ( ! $template_name ) {
            continue;
        }

        // 1. 检查子主题目录
        if ( file_exists( get_stylesheet_directory() . '/' . $template_name ) ) {
            $located = get_stylesheet_directory() . '/' . $template_name;
            break;
        }

        // 2. 检查父主题目录
        if ( file_exists( get_template_directory() . '/' . $template_name ) ) {
            $located = get_template_directory() . '/' . $template_name;
            break;
        }
    }

    if ( $load && '' != $located ) {
        load_template( $located, $require_once );
    }

    return $located;
}

function load_template( $_template_file, $require_once = true ) {
    global $posts, $post, $wp_did_header, $wp_did_template_redirect, $wp_query, $wp, $wp_locale, $wp_admin_bar, $content_width;

    ( $require_once ) ? require_once( $_template_file ) : require( $_template_file );
}

这个简化的 my_locate_template() 函数模拟了 locate_template() 的主要逻辑:

  1. 遍历模板文件名数组(虽然 comments_template() 只传递一个文件名,但 locate_template() 可以接受一个文件名数组)。
  2. 首先检查子主题目录是否存在该文件。
  3. 如果子主题目录不存在,则检查父主题目录。
  4. 如果找到了文件,则返回文件的完整路径。
  5. 如果 $load 参数为 true,则使用 load_template() 函数包含该文件。
  6. load_template()函数使用 require_once或者 require函数来包含模板文件。

4. 主题目录的结构:评论模板的“家”

WordPress 主题通常会将评论模板文件放在主题的根目录下,文件名通常是 comments.php。 但是,你也可以根据需要,创建不同的评论模板文件,并使用 comments_template 过滤器来指定要加载的模板。

例如,你可以创建一个名为 comments-special.php 的评论模板,并在你的主题的 functions.php 文件中添加以下代码:

function my_custom_comments_template( $template ) {
    if ( is_single( array( 1, 2, 3 ) ) ) { // 文章ID为 1,2,3 时使用特殊模板
        return 'comments-special.php';
    }
    return $template;
}
add_filter( 'comments_template', 'my_custom_comments_template' );

这段代码会将文章 ID 为 1、2 或 3 的文章的评论模板替换为 comments-special.php

5. 评论模板文件的内容:评论区的“骨架”

评论模板文件(例如 comments.php)通常包含以下内容:

  • 评论列表: 使用 wp_list_comments() 函数显示已有的评论列表。
  • 评论表单: 使用 comment_form() 函数显示评论表单,让用户可以发表评论。
  • 评论导航: 如果评论数量很多,可以使用 paginate_comments_links() 函数显示评论分页导航。
  • 一些判断逻辑: 例如,判断是否允许评论,判断是否需要密码才能查看评论等。

一个典型的 comments.php 文件的结构如下:

<?php
/**
 * The template for displaying comments
 *
 * This is the template that displays the area of the page that contains both the current comments
 * and the comment form.
 */

/*
 * If the current post is protected by a password and
 * the visitor has not yet entered the password we will
 * return early without loading the comments.
 */
if ( post_password_required() ) {
    return;
}
?>

<div id="comments" class="comments-area">

    <?php
    // You can start editing here -- including this comment!
    if ( have_comments() ) :
        ?>
        <h2 class="comments-title">
            <?php
            printf( // WPCS: XSS OK.
                esc_html( _nx( 'One thought on &ldquo;%2$s&rdquo;', '%1$s thoughts on &ldquo;%2$s&rdquo;', get_comments_number(), 'comments title', 'your-theme' ) ),
                number_format_i18n( get_comments_number() ),
                '<span>' . get_the_title() . '</span>'
            );
            ?>
        </h2><!-- .comments-title -->

        <?php if ( get_comment_pages_count() > 1 && get_option( 'page_comments' ) ) : // Are there comments to navigate through? ?>
            <nav id="comment-nav-above" class="navigation comment-navigation" role="navigation">
                <h2 class="screen-reader-text"><?php esc_html_e( 'Comment navigation', 'your-theme' ); ?></h2>
                <div class="nav-links">

                    <div class="nav-previous"><?php previous_comments_link( esc_html__( 'Older Comments', 'your-theme' ) ); ?></div>
                    <div class="nav-next"><?php next_comments_link( esc_html__( 'Newer Comments', 'your-theme' ) ); ?></div>

                </div><!-- .nav-links -->
            </nav><!-- #comment-nav-above -->
        <?php endif; // Check for comment navigation. ?>

        <ol class="comment-list">
            <?php
                wp_list_comments( array(
                    'style'      => 'ol',
                    'short_ping' => true,
                ) );
            ?>
        </ol><!-- .comment-list -->

        <?php if ( get_comment_pages_count() > 1 && get_option( 'page_comments' ) ) : // Are there comments to navigate through? ?>
            <nav id="comment-nav-below" class="navigation comment-navigation" role="navigation">
                <h2 class="screen-reader-text"><?php esc_html_e( 'Comment navigation', 'your-theme' ); ?></h2>
                <div class="nav-links">

                    <div class="nav-previous"><?php previous_comments_link( esc_html__( 'Older Comments', 'your-theme' ) ); ?></div>
                    <div class="nav-next"><?php next_comments_link( esc_html__( 'Newer Comments', 'your-theme' ) ); ?></div>

                </div><!-- .nav-links -->
            </nav><!-- #comment-nav-below -->
        <?php endif; // Check for comment navigation. ?>

    <?php endif; // Check for have_comments(). ?>

    <?php
    // If comments are closed and there are comments, let's leave a little note, shall we?
    if ( ! comments_open() && get_comments_number() && post_type_supports( get_post_type(), 'comments' ) ) :
        ?>
        <p class="no-comments"><?php esc_html_e( 'Comments are closed.', 'your-theme' ); ?></p>
    <?php endif; ?>

    <?php comment_form(); ?>

</div><!-- #comments -->

6. 总结:comments_template() 的寻宝之旅

现在,咱们来总结一下 comments_template() 函数的工作流程:

  1. 检查当前页面是否是单篇文章或页面,并且是否允许评论。
  2. 获取要加载的评论模板文件名(默认是 comments.php)。
  3. 使用 locate_template() 函数在主题目录及其父主题目录中查找模板文件。
  4. 如果找到了模板文件,则包含该文件。
  5. 评论模板文件负责显示评论列表和评论表单。

用一张表格来总结:

步骤 函数/变量 描述
1 comments_template() 函数入口,接收模板文件名参数。
2 is_singular(), have_comments(), $post->comment_status 条件判断:确保当前页面是单篇文章/页面,并且允许评论。
3 apply_filters( 'comments_template', $template ) 使用过滤器,允许修改模板文件名。
4 locate_template( $template, true, false ) 在主题目录和父主题目录中查找模板文件,并加载。
5 comments.php (或其他指定的模板文件) 包含评论列表和评论表单的 HTML 和 PHP 代码。

7. 常见问题与解答

  • Q: 为什么我的评论区不显示?

    • A: 首先,确认你的文章或页面允许评论(在编辑文章/页面时,检查“讨论”选项)。 其次,确认你的主题目录下存在 comments.php 文件。 最后,检查你的主题或插件是否禁用了评论功能。
  • Q: 我想修改评论表单的样式,应该怎么做?

    • A: 你可以复制 comments.php 文件到你的子主题目录,然后修改子主题目录中的 comments.php 文件。 这样可以避免修改父主题的文件,防止主题更新时丢失修改。 另外,你也可以使用 CSS 来修改评论表单的样式。
  • Q: 我想为不同的文章类型使用不同的评论模板,可以实现吗?

    • A: 当然可以! 可以使用 comments_template 过滤器来实现。 在你的主题的 functions.php 文件中,添加一个函数,根据文章类型修改要加载的模板文件名。
  • Q: comments_template() 函数的 $separate_comments 参数有什么用?

    • A: $separate_comments 参数用于控制是否将评论和 trackbacks/pingbacks 分开显示。 在 WordPress 4.5 之后,这个参数已经不再使用了,因为 WordPress 已经默认将评论和 trackbacks/pingbacks 分开显示。

好了,今天的讲座就到这里。 希望通过今天的讲解,大家对 comments_template() 函数的工作原理有了更深入的了解。 记住,理解源码是成为 WordPress 大牛的第一步! 咱们下期再见!

发表回复

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