深入理解 `comments_template()` 函数的源码,它是如何加载评论模板的?

咳咳,各位听众,早上好中午好晚上好!今天咱们来聊聊 WordPress 里面一个挺重要的函数——comments_template()。这哥们儿负责加载评论模板,让咱们的博客文章能热热闹闹地展示评论,跟用户互动。

咱们不绕弯子,直接开始扒它的源码,看看它到底是怎么运作的。

1. comments_template() 的基本用法

首先,comments_template() 的基本用法很简单,通常放在 single.php 或者类似的模板文件中:

<?php
    comments_template();
?>

就这么一行代码,它就能把评论模板加载进来,是不是很神奇?接下来咱们要做的就是揭开这层神秘的面纱。

2. 源码分析:从入口开始

comments_template() 函数定义在 /wp-includes/comment-template.php 文件里。我们打开这个文件,找到 comments_template() 函数的定义:

function comments_template( $template = '', $separate_comments = false ) {
    global $wp_query, $withcomments, $post, $wpdb, $id, $comment, $user_login, $user_identity, $overridden_cpage;

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

    $req = get_option( 'require_name_email' );

    if ( in_array( $wp_query->query_vars['pagename'], get_option( 'close_comments_on_old_posts' ) ) && is_singular() ) {
        $closed = true;
    } else {
        $closed = false;
    }

    $default_comments_page = get_option( 'default_comments_page' );

    if ( get_query_var( 'cpage' ) ) {
        $cpage = get_query_var( 'cpage' );
    } elseif ( 'oldest' === $default_comments_page ) {
        $cpage = 1;
    } else {
        $cpage = $wp_query->max_num_comment_pages;
    }

    $overridden_cpage = $cpage;

    if ( ! defined( 'COMMENTS_TEMPLATE' ) ) {
        define( 'COMMENTS_TEMPLATE', true );
    }

    $include = apply_filters( 'comments_template', './comments.php', $template, $separate_comments );

    if ( empty( $include ) ) {
        return;
    }

    if ( ! locate_template( array( $include ), true, false ) ) {
        // just in case, load it directly if it is in the same
        // directory and readable.
        if ( ! is_readable( ABSPATH . WPINC . '/theme-compat/comments.php' ) ) {
            return;
        }

        require( ABSPATH . WPINC . '/theme-compat/comments.php' );
    }
}

看起来有点长,别怕,咱们一步一步来。

2.1 前置条件检查

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

这段代码是第一个检查点。它确保了以下几点:

  • is_singular(): 当前页面是一个文章、页面或者自定义文章类型页面。 如果不是单页面,那就不需要加载评论了嘛。
  • have_comments() || 'open' == $post->comment_status: 要么已经有评论了,要么文章的评论状态是开放的。 如果既没有评论,也不允许评论,那也没必要加载评论模板。

如果这两个条件都不满足,函数就直接 return 了,啥也不干。

2.2 获取配置信息

$req = get_option( 'require_name_email' );

if ( in_array( $wp_query->query_vars['pagename'], get_option( 'close_comments_on_old_posts' ) ) && is_singular() ) {
    $closed = true;
} else {
    $closed = false;
}

$default_comments_page = get_option( 'default_comments_page' );

这里获取了一些配置信息:

  • require_name_email: 是否要求评论者填写姓名和邮箱。 这个配置在后续模板中会用到。
  • close_comments_on_old_posts: 是否在旧文章上关闭评论。 如果当前文章属于这个配置里的,$closed 就会被设置为 true
  • default_comments_page: 默认评论显示方式(最新在前还是最老在前)。

这些配置信息会在评论模板中使用,控制评论的显示和行为。

2.3 处理分页

if ( get_query_var( 'cpage' ) ) {
    $cpage = get_query_var( 'cpage' );
} elseif ( 'oldest' === $default_comments_page ) {
    $cpage = 1;
} else {
    $cpage = $wp_query->max_num_comment_pages;
}

$overridden_cpage = $cpage;

这段代码处理评论的分页。

  • get_query_var( 'cpage' ): 尝试从 URL 中获取 cpage 参数,这个参数表示当前评论页码。
  • 如果没有 cpage 参数,就根据 default_comments_page 的值来决定 $cpage 的值。 如果是 "oldest",就设置为 1,否则设置为最大评论页码。
  • $overridden_cpage 变量用来保存当前的评论页码。

2.4 定义常量

if ( ! defined( 'COMMENTS_TEMPLATE' ) ) {
    define( 'COMMENTS_TEMPLATE', true );
}

这行代码定义了一个常量 COMMENTS_TEMPLATE,防止评论模板被多次加载。

2.5 关键步骤:寻找评论模板

$include = apply_filters( 'comments_template', './comments.php', $template, $separate_comments );

if ( empty( $include ) ) {
    return;
}

if ( ! locate_template( array( $include ), true, false ) ) {
    // just in case, load it directly if it is in the same
    // directory and readable.
    if ( ! is_readable( ABSPATH . WPINC . '/theme-compat/comments.php' ) ) {
        return;
    }

    require( ABSPATH . WPINC . '/theme-compat/comments.php' );
}

这部分代码是整个函数的核心。

  • apply_filters( 'comments_template', './comments.php', $template, $separate_comments ): 这是一个过滤器钩子。 允许开发者通过 comments_template 过滤器来修改默认的评论模板路径。 默认情况下,$include 的值是 ./comments.php,表示在当前主题目录下寻找 comments.php 文件。 $template$separate_comments 是传递给过滤器的参数,开发者可以根据这些参数来动态地选择评论模板。

  • locate_template( array( $include ), true, false ): 这个函数是 WordPress 查找模板文件的关键。 它会在以下路径中查找 comments.php 文件:

    • 当前主题目录
    • 父主题目录 (如果存在)

    true 参数表示如果找到模板文件,就直接加载它。 false 参数表示不返回模板文件的路径,而是直接加载。

  • 如果 locate_template() 找不到 comments.php 文件,就尝试加载 WordPress 默认的评论模板 /wp-includes/theme-compat/comments.php。 这是一个备用方案,确保即使主题没有提供评论模板,也能显示评论。

3. locate_template() 函数的深入分析

locate_template() 函数定义在 /wp-includes/template.php 文件中。 让我们看看它的源码:

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

        if ( file_exists( STYLESHEETPATH . '/' . $template_name ) ) {
            $located = STYLESHEETPATH . '/' . $template_name;
            break;
        } elseif ( file_exists( TEMPLATEPATH . '/' . $template_name ) ) {
            $located = TEMPLATEPATH . '/' . $template_name;
            break;
        } elseif ( file_exists( ABSPATH . WPINC . '/theme-compat/' . $template_name ) ) {
            $located = ABSPATH . WPINC . '/theme-compat/' . $template_name;
            break;
        }
    }

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

    return $located;
}

这个函数接收一个模板名称数组 $template_names,然后依次在以下路径中查找模板文件:

  1. 当前主题目录 (STYLESHEETPATH): 这是首选路径。 如果当前主题提供了模板文件,就优先使用它。
  2. 父主题目录 (TEMPLATEPATH): 如果当前主题没有提供模板文件,就尝试在父主题中查找。 这允许子主题继承父主题的模板。
  3. WordPress 默认模板目录 (ABSPATH . WPINC . ‘/theme-compat/’): 如果当前主题和父主题都没有提供模板文件,就使用 WordPress 默认的模板。

如果找到模板文件,$located 变量就会被设置为模板文件的路径,并且循环会 break

如果 $load 参数为 trueload_template() 函数就会被调用,加载模板文件。 $require_once 参数控制是否使用 require_once 加载模板文件。

最后,函数返回 $located 变量,表示找到的模板文件的路径。

4. load_template() 函数的简单介绍

load_template() 函数定义在 /wp-includes/template.php 文件中。 它的作用很简单,就是加载模板文件。

function load_template( $_template_file, $require_once = true, $args = array() ) {
    global $posts, $post, $wp_did_template_redirect, $wp_query, $wp_rewrite, $wpdb, $wp_locale, $wp_admin_bar;

    if ( is_array( $args ) ) {
        extract( $args, EXTR_SKIP );
    }

    if ( $require_once ) {
        require_once( $_template_file );
    } else {
        require( $_template_file );
    }
}

这个函数接收一个模板文件路径 $_template_file 和一个 require_once 参数。

  • extract( $args, EXTR_SKIP ): 如果 $args 参数是一个数组,它会将数组中的键值对提取为变量,方便在模板中使用。 EXTR_SKIP 表示如果变量已经存在,就跳过。
  • require_once( $_template_file )require( $_template_file ): 根据 $require_once 参数的值,使用 require_oncerequire 加载模板文件。 require_once 确保模板文件只被加载一次,防止重复定义。

5. 评论模板文件 (comments.php) 的内容

现在我们知道了 comments_template() 函数是如何加载评论模板的,接下来让我们看看评论模板文件 (通常是 comments.php) 的内容。

comments.php 文件通常包含以下几个部分:

  • 评论表单: 允许用户提交评论。
  • 评论列表: 显示已有的评论。
  • 一些辅助函数和逻辑: 例如,检查是否允许评论,显示评论分页等等。

一个简单的 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.
 *
 * @link https://developer.wordpress.org/themes/basics/template-hierarchy/
 *
 * @package WordPress
 * @subpackage Twenty_Twenty_One
 * @since Twenty Twenty-One 1.0
 */

/*
 * 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 default-max-width <?php echo get_option( 'show_avatars' ) ? 'show-avatars' : ''; ?>">

    <?php
    if ( have_comments() ) :
        ?>
        <h2 class="comments-title">
            <?php
            $comments_number = get_comments_number();
            if ( '1' === $comments_number ) {
                /* translators: %s: Post title. */
                printf( esc_html__( 'One thought on &ldquo;%s&rdquo;', 'twentytwentyone' ), esc_html( get_the_title() ) );
            } else {
                printf(
                    /* translators: 1: Number of comments, 2: Post title. */
                    esc_html( _nx(
                        '%1$s thought on &ldquo;%2$s&rdquo;',
                        '%1$s thoughts on &ldquo;%2$s&rdquo;',
                        $comments_number,
                        'comments title',
                        'twentytwentyone'
                    ) ),
                    esc_html( number_format_i18n( $comments_number ) ),
                    esc_html( get_the_title() )
                );
            }
            ?>
        </h2><!-- .comments-title -->

        <?php the_comments_navigation(); ?>

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

        <?php the_comments_navigation(); ?>

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

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

    <?php comment_form(); ?>

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

这个例子使用了 wp_list_comments() 函数来显示评论列表,comment_form() 函数来显示评论表单。 这些函数都是 WordPress 提供的,可以方便地在评论模板中使用。

6. 重要函数总结

为了方便大家理解,我们把上面提到的重要函数总结一下:

函数名 作用
comments_template() 加载评论模板。 它会检查当前页面是否允许评论,然后根据配置信息和主题文件,找到合适的评论模板文件并加载它。
locate_template() 查找模板文件。 它会在当前主题目录、父主题目录和 WordPress 默认模板目录中查找指定的模板文件。
load_template() 加载模板文件。 它会使用 require_oncerequire 加载指定的模板文件,并且可以将一个数组提取为变量,方便在模板中使用。
wp_list_comments() 显示评论列表。 它会根据传入的参数,格式化评论列表的显示方式,例如,显示头像、分页等等。
comment_form() 显示评论表单。 它会根据配置信息,生成一个评论表单,允许用户提交评论。

7. 自定义评论模板

理解了 comments_template() 函数的运作方式,我们就可以自定义评论模板了。

  • 创建一个 comments.php 文件: 在你的主题目录下创建一个 comments.php 文件。
  • 修改 comments.php 文件:comments.php 文件中,你可以使用 HTML、CSS 和 PHP 代码来定制评论的显示方式和行为。 你可以使用 wp_list_comments() 函数来显示评论列表,comment_form() 函数来显示评论表单。
  • 使用过滤器钩子: 你可以使用 comments_template 过滤器钩子来修改默认的评论模板路径。 例如,你可以创建一个名为 comments-custom.php 的评论模板,然后使用以下代码来加载它:

    add_filter( 'comments_template', 'my_custom_comments_template' );
    
    function my_custom_comments_template( $template ) {
        return dirname( __FILE__ ) . '/comments-custom.php';
    }

    这段代码会将默认的评论模板路径修改为 comments-custom.php

8. 总结

comments_template() 函数是 WordPress 中一个非常重要的函数,它负责加载评论模板,让咱们的博客文章能热热闹闹地展示评论,跟用户互动。 通过深入分析它的源码,我们了解了它的运作方式,以及如何自定义评论模板。 希望今天的讲座能帮助大家更好地理解 WordPress 的模板机制。

好了,今天的讲座就到这里。 谢谢大家! 有什么问题,欢迎提问。

发表回复

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