剖析 WordPress `comments_template()` 函数源码:`comments_template` 过滤器如何加载自定义模板。

各位观众老爷们,大家好!今天咱们就来扒一扒 WordPress 里一个看似简单,实则暗藏玄机的函数:comments_template()。别看它只是用来加载评论模板的,但它可是 WordPress 评论系统的重要组成部分,也是自定义评论模板的关键入口。准备好,咱们要开始“解剖”这个函数了!

一、comments_template() 的基本用途和源码结构

首先,咱们得知道 comments_template() 是干嘛的。简单来说,它就是用来加载评论模板文件的。通常情况下,它会在你的 single.php 或其他页面模板文件中被调用,以便显示文章的评论。

让我们先来看看 comments_template() 的源码(基于 WordPress 6.x):

function comments_template( $template = '', $separate_comments = false ) {
    global $wp_query, $withcomments, $post, $wpdb, $id, $commenter, $comment_author_domain;

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

    $req = get_option( 'require_name_email' );
    if ( $req ) {
        $aria_req = " aria-required='true'";
    } else {
        $aria_req = '';
    }

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

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

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

    // Backwards compatibility.
    if ( '' === $include ) {
        $req = get_option( 'require_name_email' );
        if ( $req ) {
            $aria_req = " aria-required='true'";
        } else {
            $aria_req = '';
        }

        global $id;
        if ( null === $id ) {
            $id = get_the_ID();
        }

        $comment_author       = ! empty( $_COOKIE['comment_author_' . COOKIEHASH] ) ? trim( stripslashes( $_COOKIE['comment_author_' . COOKIEHASH] ) ) : '';
        $comment_author_email = ! empty( $_COOKIE['comment_author_email_' . COOKIEHASH] ) ? trim( stripslashes( $_COOKIE['comment_author_email_' . COOKIEHASH] ) ) : '';
        $comment_author_url   = ! empty( $_COOKIE['comment_author_url_' . COOKIEHASH] ) ? trim( stripslashes( $_COOKIE['comment_author_url_' . COOKIEHASH] ) ) : '';

        require( ABSPATH . WPINC . '/comment-template.php' );
    }
}

别被这一堆代码吓到,咱们一点一点来分析。

二、源码分解与关键步骤

  1. 初始检查:

    • if ( ! ( is_singular() && ( have_comments() || 'open' == $post->comment_status ) ) ) { return; }
      • 这段代码是用来判断是否应该加载评论模板的。它做了以下检查:
        • is_singular(): 确保我们正在访问一个单篇文章或页面。
        • have_comments() || 'open' == $post->comment_status: 确保文章有评论,或者评论是开启状态。如果这些条件都不满足,函数会直接返回,不加载任何评论模板。
  2. 获取是否需要填写姓名和邮箱:

    • $req = get_option( 'require_name_email' );
      • 从 WordPress 选项中获取是否需要填写姓名和邮箱。
      • if ( $req ) { $aria_req = " aria-required='true'"; } else { $aria_req = ''; }
      • 设置 HTML 属性 aria-required,提高可访问性,提示用户是否必须填写姓名和邮箱。
  3. 确定模板文件:

    • if ( empty( $template ) ) { $template = '/comments.php'; }
      • 如果调用 comments_template() 时没有指定模板文件名,默认使用 comments.php。这个 comments.php 应该位于你的主题目录下。
  4. 应用 comments_template 过滤器(重点来了!):

    • $template = apply_filters( 'comments_template', $template, $separate_comments );
      • 这就是我们今天要重点讨论的地方!apply_filters() 函数会触发 comments_template 过滤器。这个过滤器允许我们修改要加载的评论模板文件。
      • $template 是原始的模板文件名 (例如: ‘comments.php’)。
      • $separate_comments 是一个布尔值,指示是否应该将评论分成不同的类型(例如,评论、引用、pingback)。
  5. 定位模板文件:

    • $include = locate_template( $template, true, $separate_comments );
      • locate_template() 函数会在主题目录及其父主题目录中查找指定的模板文件。
      • $template 是经过 comments_template 过滤器修改后的模板文件名。
      • true 表示如果找到模板文件,则直接加载它。
      • $separate_comments 同上。
      • locate_template() 返回找到的模板文件的完整路径,或者返回一个空字符串 ('') 如果没有找到。
  6. 向后兼容处理:

    • if ( '' === $include ) { ... }
      • 如果 locate_template() 没有找到指定的模板文件(即 $include 为空字符串),则执行这段代码。
      • 这段代码是为了向后兼容,它会加载 WordPress 核心文件 wp-includes/comment-template.php。这个文件包含默认的评论显示逻辑。

三、comments_template 过滤器:自定义评论模板的核心

现在,让我们把注意力集中在 comments_template 过滤器上。正如前面提到的,这个过滤器允许我们修改 comments_template() 函数要加载的评论模板文件。

如何使用 comments_template 过滤器?

你需要在你的主题的 functions.php 文件或者插件中添加一个函数,并将其挂载到 comments_template 过滤器上。

/**
 * 自定义评论模板.
 *
 * @param string $template  默认的评论模板文件路径.
 * @param bool   $separate_comments 是否将评论分成不同的类型.
 *
 * @return string 修改后的评论模板文件路径.
 */
function my_custom_comments_template( $template, $separate_comments ) {
    // 1. 检查当前文章是否属于某个特定的分类.
    if ( in_category( 'my-special-category' ) ) {
        // 2. 如果是,则加载自定义的评论模板.
        $template = get_stylesheet_directory() . '/comments-special.php'; // 或者 get_template_directory()
    }

    // 3. 始终返回一个模板文件路径. 即使不修改也要返回原值.
    return $template;
}
add_filter( 'comments_template', 'my_custom_comments_template' );

代码解释:

  1. 定义过滤器函数: 我们定义了一个名为 my_custom_comments_template 的函数,它接受两个参数:$template (原始的模板文件名) 和 $separate_comments
  2. 条件判断: 在这个例子中,我们使用 in_category( 'my-special-category' ) 函数来检查当前文章是否属于 my-special-category 这个分类。你可以根据自己的需求修改这个条件。
  3. 修改模板路径: 如果条件满足(即文章属于 my-special-category 分类),我们将 $template 变量修改为自定义的评论模板文件路径。 get_stylesheet_directory() 返回当前主题的路径,然后我们拼接上自定义模板文件的文件名 comments-special.php。 注意:你的自定义评论模板文件需要放在你的主题目录下。
  4. 返回模板路径: 最重要的一点是,一定要返回 $template 变量。即使你没有修改它,也要返回原始值。这是 apply_filters() 函数的要求。

$separate_comments 参数的用途:

$separate_comments 参数告诉我们是否应该将评论分成不同的类型。例如,普通评论、引用 (trackbacks) 和 pingback。通常,你不需要关心这个参数,直接传递给 locate_template() 函数即可。但如果你需要根据评论类型显示不同的内容,你可以在你的自定义评论模板中使用 wp_list_comments() 函数,并设置 'type' 参数来筛选评论类型。

自定义评论模板文件的内容 (comments-special.php):

<?php
/**
 * 自定义评论模板文件.
 */

if ( post_password_required() ) {
    return;
}
?>

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

    <?php if ( have_comments() ) : ?>
        <h2 class="comments-title">
            <?php
            printf( _nx( 'One thought on &ldquo;%2$s&rdquo;', '%1$s thoughts on &ldquo;%2$s&rdquo;', get_comments_number(), 'comments title', 'your-text-domain' ),
                number_format_i18n( get_comments_number() ),
                '<span>' . get_the_title() . '</span>'
            );
            ?>
        </h2>

        <ol class="comment-list">
            <?php
                wp_list_comments( array(
                    'style'       => 'ol',
                    'short_ping'  => true,
                    'callback' => 'my_custom_comment_callback' // 可以使用自定义的回调函数渲染单个评论
                ) );
            ?>
        </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="comment-navigation" role="navigation">
                <h1 class="screen-reader-text"><?php _e( 'Comment navigation', 'your-text-domain' ); ?></h1>
                <div class="nav-previous"><?php previous_comments_link( __( '&larr; Older Comments', 'your-text-domain' ) ); ?></div>
                <div class="nav-next"><?php next_comments_link( __( 'Newer Comments &rarr;', 'your-text-domain' ) ); ?></div>
            </nav><!-- #comment-nav-below -->
        <?php endif; // Check for comment navigation. ?>

    <?php endif; // 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 _e( 'Comments are closed.', 'your-text-domain' ); ?></p>
    <?php endif; ?>

    <?php comment_form(); ?>

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

这个自定义模板文件基本上就是 comments.php 的一个副本,你可以根据自己的需求修改 HTML 结构、CSS 样式和 PHP 代码。

四、更高级的用法:基于文章类型、作者、甚至用户角色自定义评论模板

comments_template 过滤器的强大之处在于它的灵活性。你可以根据各种条件来加载不同的评论模板。

1. 基于文章类型自定义:

function my_custom_comments_template_post_type( $template, $separate_comments ) {
    if ( get_post_type() === 'my_custom_post_type' ) {
        $template = get_stylesheet_directory() . '/comments-custom-post-type.php';
    }
    return $template;
}
add_filter( 'comments_template', 'my_custom_comments_template_post_type' );

2. 基于作者自定义(需要更复杂的逻辑,因为作者信息通常不在 comments_template 调用的上下文中直接可用):

需要一种方法将作者信息传递到过滤器中。 这通常涉及使用全局变量或会话,但这可能导致问题。 一个更清洁的解决方案是使用一个动作钩子来设置全局变量,然后在评论模板过滤器中使用它。

// 在文章循环之前设置作者 ID
function set_author_id_before_comments() {
  global $author_id_for_comments;
  $author_id_for_comments = get_the_author_meta('ID');
}
add_action('loop_start', 'set_author_id_before_comments');

// 评论模板过滤器
function my_custom_comments_template_by_author( $template, $separate_comments ) {
  global $author_id_for_comments;
  if ( isset($author_id_for_comments) && $author_id_for_comments == 123 ) { // 假设作者 ID 为 123
    $template = get_stylesheet_directory() . '/comments-author-123.php';
  }
  return $template;
}
add_filter( 'comments_template', 'my_custom_comments_template_by_author' );

// 清除作者 ID (可选,避免全局变量污染)
function clear_author_id_after_comments() {
  global $author_id_for_comments;
  unset($author_id_for_comments);
}
add_action('loop_end', 'clear_author_id_after_comments');

3. 基于用户角色自定义(需要一些额外的逻辑来检查当前用户是否登录并具有特定的角色):

function my_custom_comments_template_by_user_role( $template, $separate_comments ) {
    if ( is_user_logged_in() ) {
        $user = wp_get_current_user();
        if ( in_array( 'administrator', (array) $user->roles ) ) {
            $template = get_stylesheet_directory() . '/comments-admin.php';
        }
    }
    return $template;
}
add_filter( 'comments_template', 'my_custom_comments_template_by_user_role' );

五、重要提示与常见问题

  • 缓存问题: 当你修改了评论模板文件或者过滤器函数后,请务必清除 WordPress 缓存(如果使用了缓存插件)或者浏览器缓存,以确保你的修改生效。
  • 模板文件的位置: 自定义评论模板文件应该放在你的主题目录下,或者子主题目录下。使用 get_stylesheet_directory()get_template_directory() 函数来获取正确的路径。
  • locate_template() 函数: locate_template() 函数会按照一定的顺序查找模板文件。它首先会在子主题目录下查找,如果没有找到,则会在父主题目录下查找。
  • comment_form() 函数: comment_form() 函数用于显示评论表单。你可以在自定义评论模板中使用这个函数来显示评论表单。如果你想自定义评论表单的样式和字段,可以使用 comment_form_defaults 过滤器。
  • wp_list_comments() 函数: wp_list_comments() 函数用于显示评论列表。你可以在自定义评论模板中使用这个函数来显示评论列表。如果你想自定义评论列表的样式和结构,可以使用 'callback' 参数,并提供一个自定义的回调函数来渲染单个评论。
  • 调试技巧: 如果你的自定义评论模板没有生效,可以使用 var_dump()error_log() 函数来调试你的代码。例如,你可以使用 var_dump( $template ) 来查看 $template 变量的值,以确保它指向正确的模板文件。

六、总结

comments_template() 函数是 WordPress 评论系统的一个重要组成部分。通过 comments_template 过滤器,我们可以轻松地自定义评论模板,从而实现各种各样的效果。希望今天的讲解能够帮助你更好地理解和使用这个函数。 记住,灵活运用过滤器,你就能掌控 WordPress 的方方面面!

好了,今天的讲座就到这里。如果大家还有什么问题,欢迎提问!下次有机会再和大家分享 WordPress 的其他知识点。 祝大家编程愉快!

发表回复

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