剖析 WordPress comment_form 函数如何注入前端验证脚本

WordPress comment_form 函数:前端验证脚本注入剖析

大家好,今天我们来深入剖析 WordPress 的 comment_form 函数如何巧妙地注入前端验证脚本,提升评论体验。我们会从 comment_form 函数的基本用法出发,逐步分析其内部逻辑,重点关注前端验证脚本的注入机制,并提供一些实际的代码示例。

1. comment_form 函数:基础与定制

comment_form 函数是 WordPress 提供的一个核心函数,用于生成评论表单。它接受一个可选的参数 $args,允许我们自定义表单的各个方面。

最简单的用法:

<?php comment_form(); ?>

这将生成一个包含默认字段(姓名、邮箱、网址、评论)的评论表单。

更进一步,我们可以通过 $args 数组自定义表单的各个部分,例如:

<?php
$args = array(
    'title_reply'          => __( 'Leave a Reply', 'your-theme' ),
    'comment_notes_before' => '<p class="comment-notes">' . __( 'Your email address will not be published.', 'your-theme' ) . '</p>',
    'comment_field'        => '<p class="comment-form-comment"><label for="comment">' . _x( 'Comment', 'noun' ) .
                              ' <span class="required">*</span></label><textarea id="comment" name="comment" cols="45" rows="8" aria-required="true"></textarea></p>',
    'submit_button'        => '<input name="%1$s" type="submit" id="%2$s" class="%3$s" value="%4$s" />',
    'submit_field'         => '<p class="form-submit">%1$s %2$s</p>',
    'class_submit'         => 'submit',
);

comment_form( $args );
?>

这个例子展示了如何修改表单标题、评论前置说明、评论字段的 HTML 结构、提交按钮的 HTML 结构以及提交按钮的 CSS 类。

2. comment_form 函数的内部机制

为了理解前端验证脚本的注入,我们需要了解 comment_form 函数的内部工作原理。 comment_form 函数主要做了以下几件事:

  1. 合并默认参数和用户提供的参数: wp_parse_args 函数用于将用户提供的 $args 数组与默认参数进行合并,保证所有必要的参数都存在。
  2. 构建表单字段: 根据合并后的参数,函数会生成 HTML 形式的表单字段,例如姓名、邮箱、网址和评论文本框。
  3. 处理用户登录状态: 如果用户已登录,则会显示已登录用户的相关信息,并隐藏姓名和邮箱字段。
  4. 处理评论 Cookie: 如果用户之前发表过评论,则会读取 Cookie 中的姓名和邮箱信息,并自动填充到相应的字段中。
  5. 添加 comment_form 钩子: 这是最重要的部分,comment_form 函数会触发 comment_form 钩子。这个钩子允许开发者在评论表单的 HTML 代码中插入自定义内容,包括 JavaScript 代码。
  6. 输出表单 HTML: 最后,函数会将生成的 HTML 代码输出到页面上。

3. comment_form 钩子:注入前端验证脚本的关键

comment_form 钩子是 comment_form 函数注入前端验证脚本的关键。我们可以通过 add_action 函数将自定义函数绑定到 comment_form 钩子上。这个自定义函数会在 comment_form 函数执行期间被调用,并允许我们在评论表单的 HTML 代码中插入任意内容,包括 JavaScript 代码。

add_action( 'comment_form', 'my_comment_form_validation' );

function my_comment_form_validation() {
    // 在这里插入 JavaScript 代码
    echo '<script type="text/javascript">
        // JavaScript 验证代码
        jQuery(document).ready(function($) {
            $("#commentform").submit(function(event) {
                var comment = $("#comment").val();
                if (comment == "") {
                    alert("请填写评论内容!");
                    event.preventDefault(); // 阻止表单提交
                }
            });
        });
    </script>';
}

这段代码会将一个简单的 JavaScript 验证脚本插入到评论表单的 HTML 代码中。这个脚本会在表单提交时检查评论内容是否为空,如果为空,则会弹出一个警告框,并阻止表单提交。

4. 前端验证脚本的注入方式

有多种方式可以将前端验证脚本注入到评论表单中。以下是一些常用的方法:

  • 直接在 comment_form 钩子中输出 JavaScript 代码: 这是最简单的方法,但不太灵活,不方便维护。

    add_action( 'comment_form', 'my_comment_form_validation' );
    
    function my_comment_form_validation() {
        echo '<script type="text/javascript">
            // JavaScript 验证代码
            jQuery(document).ready(function($) {
                // ... 验证逻辑 ...
            });
        </script>';
    }
  • 使用 wp_enqueue_scripts 钩子注册和加载 JavaScript 文件: 这是更推荐的方法,可以将 JavaScript 代码放在单独的文件中,方便维护和管理。

    add_action( 'wp_enqueue_scripts', 'my_enqueue_comment_validation_script' );
    
    function my_enqueue_comment_validation_script() {
        if ( is_singular() && comments_open() ) {
            wp_enqueue_script( 'comment-validation', get_template_directory_uri() . '/js/comment-validation.js', array( 'jquery' ), '1.0', true );
        }
    }
    
    // js/comment-validation.js
    jQuery(document).ready(function($) {
        $("#commentform").submit(function(event) {
            var comment = $("#comment").val();
            if (comment == "") {
                alert("请填写评论内容!");
                event.preventDefault(); // 阻止表单提交
            }
        });
    });

    解释:

    • wp_enqueue_scripts 钩子用于在页面加载时注册和加载脚本。
    • is_singular() && comments_open() 条件判断当前页面是否为单篇文章页面且评论已开启,确保脚本只在需要时加载。
    • wp_enqueue_script() 函数用于注册和加载脚本。
      • 'comment-validation' 是脚本的唯一标识符。
      • get_template_directory_uri() . '/js/comment-validation.js' 是脚本文件的 URL。
      • array( 'jquery' ) 是脚本的依赖项,表示该脚本依赖 jQuery。
      • '1.0' 是脚本的版本号。
      • true 表示脚本将在页面底部加载。
  • 结合 comment_form_beforecomment_form_after 钩子: 这允许你在评论表单的前后插入 HTML 元素,例如用于显示错误信息的容器。

    add_action( 'comment_form_before', 'my_comment_form_before' );
    add_action( 'comment_form_after', 'my_comment_form_after' );
    add_action( 'wp_enqueue_scripts', 'my_enqueue_comment_validation_script' );
    
    function my_comment_form_before() {
        echo '<div id="comment-errors" style="color: red;"></div>';
    }
    
    function my_comment_form_after() {
        // 可以添加一些清理工作
    }
    
    function my_enqueue_comment_validation_script() {
        if ( is_singular() && comments_open() ) {
            wp_enqueue_script( 'comment-validation', get_template_directory_uri() . '/js/comment-validation.js', array( 'jquery' ), '1.0', true );
        }
    }
    
    // js/comment-validation.js
    jQuery(document).ready(function($) {
        $("#commentform").submit(function(event) {
            var comment = $("#comment").val();
            if (comment == "") {
                $("#comment-errors").text("请填写评论内容!");
                event.preventDefault(); // 阻止表单提交
            } else {
                $("#comment-errors").text(""); // 清除错误信息
            }
        });
    });

    解释:

    • comment_form_before 钩子在评论表单之前执行,用于插入错误信息容器。
    • comment_form_after 钩子在评论表单之后执行,用于进行清理工作。
    • JavaScript 代码修改为将错误信息显示在 #comment-errors 容器中,而不是使用 alert()

5. 前端验证脚本的实现

前端验证脚本可以使用 JavaScript 或 jQuery 来实现。以下是一些常见的验证需求:

  • 必填字段验证: 检查必填字段是否已填写。
  • 邮箱格式验证: 检查邮箱地址是否符合正确的格式。
  • 网址格式验证: 检查网址是否符合正确的格式。
  • 字符长度验证: 检查字段的字符长度是否符合要求。
  • 防止重复提交: 禁用提交按钮,防止用户重复提交评论。

以下是一个使用 jQuery 实现的验证脚本示例:

jQuery(document).ready(function($) {
    $("#commentform").submit(function(event) {
        var comment = $("#comment").val();
        var author = $("#author").val();
        var email = $("#email").val();

        var errors = [];

        if (comment == "") {
            errors.push("请填写评论内容!");
        }

        if (author == "") {
            errors.push("请填写您的姓名!");
        }

        if (email == "") {
            errors.push("请填写您的邮箱!");
        } else if (!isValidEmail(email)) {
            errors.push("请填写有效的邮箱地址!");
        }

        if (errors.length > 0) {
            $("#comment-errors").html(errors.map(function(error) {
                return "<p>" + error + "</p>";
            }).join(""));
            event.preventDefault(); // 阻止表单提交
        } else {
            $("#comment-errors").html(""); // 清除错误信息
        }

        function isValidEmail(email) {
            var emailRegex = /^([a-zA-Z0-9_.+-])+@(([a-zA-Z0-9-])+.)+([a-zA-Z0-9]{2,4})+$/;
            return emailRegex.test(email);
        }
    });
});

6. 实际案例:一个更完善的前端验证

下面是一个更完善的例子,包含了更多的验证规则,并且使用了 AJAX 提交评论,提升用户体验。

functions.php:

add_action( 'wp_enqueue_scripts', 'my_enqueue_comment_validation_script' );
add_action( 'wp_ajax_my_submit_comment', 'my_submit_comment_callback' );
add_action( 'wp_ajax_nopriv_my_submit_comment', 'my_submit_comment_callback' );

function my_enqueue_comment_validation_script() {
    if ( is_singular() && comments_open() ) {
        wp_enqueue_script( 'comment-validation', get_template_directory_uri() . '/js/comment-validation.js', array( 'jquery' ), '1.0', true );
        wp_localize_script( 'comment-validation', 'my_ajax_object',
            array( 'ajax_url' => admin_url( 'admin-ajax.php' ) ) );
    }
}

function my_submit_comment_callback() {
    // 验证 nonce
    check_ajax_referer( 'my_comment_nonce', 'security' );

    $comment_post_ID = isset($_POST['comment_post_ID']) ? absint($_POST['comment_post_ID']) : 0;
    $comment_author  = ! empty( $_POST['author'] ) ? trim( strip_tags( $_POST['author'] ) ) : null;
    $comment_author_email = ! empty( $_POST['email'] ) ? trim( $_POST['email'] ) : null;
    $comment_author_url = ! empty( $_POST['url'] ) ? trim( $_POST['url'] ) : null;
    $comment_content = ! empty( $_POST['comment'] ) ? trim( $_POST['comment'] ) : null;

    // 简单的服务器端验证
    if ( empty( $comment_content ) ) {
        wp_send_json_error( array( 'message' => '评论内容不能为空' ) );
    }

    if ( empty( $comment_author ) ) {
        wp_send_json_error( array( 'message' => '姓名不能为空' ) );
    }

    if ( empty( $comment_author_email ) || ! is_email( $comment_author_email ) ) {
        wp_send_json_error( array( 'message' => '邮箱格式不正确' ) );
    }

    $data = array(
        'comment_post_ID'      => $comment_post_ID,
        'comment_author'       => $comment_author,
        'comment_author_email' => $comment_author_email,
        'comment_author_url'   => $comment_author_url,
        'comment_content'      => $comment_content,
        'comment_agent'        => 'WordPress AJAX Comment',
        'comment_date'         => current_time('mysql'),
        'comment_date_gmt'     => gmdate('Y-m-d H:i:s'),
        'comment_approved'     => 1, // 审核状态,可以设置为 0  需要审核
    );

    $comment_id = wp_insert_comment( $data );

    if ( ! is_wp_error( $comment_id ) ) {
        // 成功插入评论
        $comment = get_comment( $comment_id );
        $comment_html = '<li>' . get_comment_text( $comment_id ) . '</li>'; // 简单地显示评论内容,实际应用中需要更复杂的处理

        wp_send_json_success( array( 'message' => '评论成功', 'comment_html' => $comment_html ) );

    } else {
        // 插入评论失败
        wp_send_json_error( array( 'message' => '评论失败' ) );
    }

    wp_die(); // 必须要有,结束 AJAX 请求
}

function my_comment_form_fields( $fields ) {
    $commenter = wp_get_current_commenter();
    $req      = get_option( 'require_name_email' );
    $aria_req = ( $req ? " aria-required='true'" : '' );

    $fields['author'] = '<p class="comment-form-author">'.
        '<label for="author">' . __( 'Name', 'domainreference' ) . ( $req ? ' <span class="required">*</span>' : '' ) .
        '</label> '.
        '<input id="author" name="author" type="text" value="' . esc_attr( $commenter['comment_author'] ) .
        '" size="30"' . $aria_req . ' /></p>';

    $fields['email'] = '<p class="comment-form-email">'.
        '<label for="email">' . __( 'Email', 'domainreference' ) . ( $req ? ' <span class="required">*</span>' : '' ) .
        '</label> '.
        '<input id="email" name="email" type="text" value="' . esc_attr(  $commenter['comment_author_email'] ) .
        '" size="30"' . $aria_req . ' /></p>';

    $fields['url'] = '<p class="comment-form-url">'.
        '<label for="url">' . __( 'Website', 'domainreference' ) . '</label>'.
        '<input id="url" name="url" type="text" value="' . esc_attr( $commenter['comment_author_url'] ) .
        '" size="30" /></p>';

    return $fields;
}
add_filter( 'comment_form_default_fields', 'my_comment_form_fields' );

function my_comment_form( $args = array() ) {
    $commenter = wp_get_current_commenter();
    $req = get_option( 'require_name_email' );
    $aria_req = ( $req ? " aria-required='true'" : '' );

    $fields =  array(
        'author' => '<p class="comment-form-author">' . '<label for="author">' . __( 'Name', 'domainreference' ) . ( $req ? ' <span class="required">*</span>' : '' ) . '</label> ' .
            '<input id="author" name="author" type="text" value="' . esc_attr( $commenter['comment_author'] ) . '" size="30"' . $aria_req . ' /></p>',
        'email'  => '<p class="comment-form-email">' . '<label for="email">' . __( 'Email', 'domainreference' ) . ( $req ? ' <span class="required">*</span>' : '' ) . '</label> ' .
            '<input id="email" name="email" type="text" value="' . esc_attr(  $commenter['comment_author_email'] ) . '" size="30"' . $aria_req . ' /></p>',
        'url'   => '<p class="comment-form-url">' . '<label for="url">' . __( 'Website', 'domainreference' ) . '</label>' .
            '<input id="url" name="url" type="text" value="' . esc_attr( $commenter['comment_author_url'] ) . '" size="30" /></p>',
    );

    $defaults = array(
        'fields'               => apply_filters( 'comment_form_default_fields', $fields ),
        'comment_field'        => '<p class="comment-form-comment"><label for="comment">' . _x( 'Comment', 'noun' ) . ' <span class="required">*</span></label><textarea id="comment" name="comment" cols="45" rows="8" aria-required="true"></textarea></p>',
        'must_log_in'          => '<p class="must-log-in">' . sprintf( __( 'You must be <a href="%s">logged in</a> to post a comment.' ), wp_login_url( apply_filters( 'the_permalink', get_permalink() ) ) ) . '</p>',
        'logged_in_as'         => '<p class="logged-in-as">' . sprintf( __( 'Logged in as <a href="%1$s">%2$s</a>. <a href="%3$s" title="Log out of this account">Log out?</a>' ), admin_url( 'profile.php' ), wp_get_current_user()->display_name, wp_logout_url( apply_filters( 'the_permalink', get_permalink() ) ) ) . '</p>',
        'comment_notes_before' => '<p class="comment-notes">' . __( 'Your email address will not be published.' ) . ( $req ? $required_text : '' ) . '</p>',
        'comment_notes_after'  => '<p class="form-allowed-tags">' . sprintf( __( 'You may use these <abbr title="HyperText Markup Language">HTML</abbr> tags and attributes: %s' ), ' <code>' . allowed_tags() . '</code>' ) . '</p>',
        'id_form'              => 'commentform',
        'id_submit'            => 'submit',
        'class_submit'         => 'submit',
        'name_submit'          => 'submit',
        'title_reply'          => __( 'Leave a Reply' ),
        'title_reply_to'       => __( 'Leave a Reply to %s' ),
        'cancel_reply_link'    => __( 'Cancel reply' ),
        'label_submit'         => __( 'Post Comment' ),
        'format'               => 'xhtml',
    );

    $args = wp_parse_args( $args, $defaults );

    ?>
    <div id="comment-errors" style="color: red;"></div>
    <form action="<?php echo esc_url( admin_url( 'admin-ajax.php' ) ); ?>" method="post" id="<?php echo esc_attr( $args['id_form'] ); ?>" class="comment-form">
        <?php
        if ( have_comments() && ! comments_open() && get_comments_number() ) {
            echo '<p class="no-comments">' . __( 'Comments are closed.' ) . '</p>';
        } else {

            if ( get_option( 'comment_registration' ) && ! is_user_logged_in() ) {
                echo $args['must_log_in'];
            } else {

                if ( is_user_logged_in() ) {
                    echo $args['logged_in_as'];
                } else {
                    echo $args['comment_notes_before'];

                    do_action( 'comment_form_top' );

                    echo '<div class="comment-form-fields">';
                    foreach ( $args['fields'] as $name => $field ) {
                        echo apply_filters( "comment_form_field_{$name}", $field ) . "n";
                    }
                    echo '</div><!-- .comment-form-fields -->';
                }

                echo $args['comment_field'];

                echo $args['comment_notes_after'];

                ?>
                <p class="form-submit">
                    <?php wp_nonce_field( 'my_comment_nonce', 'security' ); ?>
                    <input type="hidden" name="action" value="my_submit_comment">
                    <input type="hidden" name="comment_post_ID" value="<?php echo get_the_ID(); ?>">
                    <input type="hidden" name="comment_parent" id="comment_parent" value="0">
                    <input name="submit" type="submit" id="<?php echo esc_attr( $args['id_submit'] ); ?>" class="<?php echo esc_attr( $args['class_submit'] ); ?>" value="<?php echo esc_attr( $args['label_submit'] ); ?>" />
                </p>
                <?php

                do_action( 'comment_form_after' );
            }
        }
        ?>
    </form><!-- #commentform -->
    <?php

    // 清理全局变量
    unset( $commenter, $req, $aria_req, $fields );
}

js/comment-validation.js:

jQuery(document).ready(function($) {
    $("#commentform").submit(function(event) {
        event.preventDefault(); // 阻止默认提交

        var comment = $("#comment").val();
        var author = $("#author").val();
        var email = $("#email").val();
        var url = $("#url").val();
        var comment_post_ID = $("input[name='comment_post_ID']").val();
        var comment_parent = $("input[name='comment_parent']").val();
        var security = $("#security").val();

        var errors = [];

        if (comment == "") {
            errors.push("请填写评论内容!");
        }

        if (author == "") {
            errors.push("请填写您的姓名!");
        }

        if (email == "") {
            errors.push("请填写您的邮箱!");
        } else if (!isValidEmail(email)) {
            errors.push("请填写有效的邮箱地址!");
        }

        if (url != "" && !isValidURL(url)) {
            errors.push("请填写有效的网址!");
        }

        if (errors.length > 0) {
            $("#comment-errors").html(errors.map(function(error) {
                return "<p>" + error + "</p>";
            }).join(""));
        } else {
            $("#comment-errors").html("").hide();
            // AJAX 提交评论
            $.ajax({
                url: my_ajax_object.ajax_url,
                type: 'POST',
                data: {
                    action: 'my_submit_comment',
                    comment: comment,
                    author: author,
                    email: email,
                    url: url,
                    comment_post_ID: comment_post_ID,
                    comment_parent: comment_parent,
                    security: security
                },
                dataType: 'json',
                success: function(response) {
                    if (response.success) {
                         $("#comment").val('');
                        // 评论成功,将评论添加到评论列表中
                        $("ol.commentlist").append(response.data.comment_html);
                        $("#comment-errors").html("<p style='color:green'>评论提交成功!</p>").show(); // 显示成功消息
                        setTimeout(function(){ $("#comment-errors").hide(); }, 3000); // 3秒后隐藏
                    } else {
                        // 评论失败,显示错误信息
                         $("#comment-errors").html("<p>" + response.data.message + "</p>").show();
                    }
                },
                error: function(jqXHR, textStatus, errorThrown) {
                    console.log(jqXHR);
                    $("#comment-errors").html("<p>评论提交失败,请稍后再试!</p>").show();
                }
            });
        }

        function isValidEmail(email) {
            var emailRegex = /^([a-zA-Z0-9_.+-])+@(([a-zA-Z0-9-])+.)+([a-zA-Z0-9]{2,4})+$/;
            return emailRegex.test(email);
        }

        function isValidURL(url) {
            var urlRegex = /^(ftp|http|https)://[^ "]+$/;
            return urlRegex.test(url);
        }
    });
});

7. 表格总结:关键钩子与函数

钩子/函数 描述 用途
comment_form 用于生成评论表单。 生成评论表单的 HTML 代码。
comment_form_before 在评论表单之前执行的钩子。 插入评论表单之前的 HTML 元素,例如错误信息容器。
comment_form_after 在评论表单之后执行的钩子。 插入评论表单之后的 HTML 元素,例如用于清理工作的代码。
wp_enqueue_scripts 用于注册和加载 JavaScript 和 CSS 文件的钩子。 注册和加载用于前端验证的 JavaScript 文件。
wp_localize_script 用于将 PHP 变量传递给 JavaScript 文件的函数。 将 AJAX URL 等 PHP 变量传递给 JavaScript 文件,使其能够发起 AJAX 请求。
comment_form_default_fields 用于修改评论表单默认字段的过滤器 可以修改默认的name,email,url字段的html代码,添加class,id等等。
wp_ajax_my_submit_commentwp_ajax_nopriv_my_submit_comment 处理AJAX请求的钩子 在functions.php中处理前端提交的AJAX请求,验证数据,插入评论并返回结果。

8. 应该注意的点

  • 安全性: 前端验证只能提供基本的验证,不能完全保证数据的安全性。仍然需要在服务器端进行验证,防止恶意攻击。使用 wp_nonce_field 生成 nonce 值,并在服务器端进行验证,防止 CSRF 攻击。
  • 用户体验: 前端验证应该提供清晰友好的错误提示信息,帮助用户快速纠正错误。
  • 可访问性: 确保前端验证脚本不会影响网站的可访问性,例如使用 ARIA 属性提供辅助信息。
  • 性能: 尽量减少前端验证脚本的大小,避免影响网站的加载速度。将 JavaScript 代码放在单独的文件中,并进行压缩。
  • 服务器端验证 永远不要信任来自客户端的数据. 始终在服务器端进行验证, 防止恶意用户绕过前端验证。
  • 使用 wp_kses 清理数据: 在将用户输入的数据插入到数据库之前, 使用 wp_kses 函数清理数据,防止 XSS 攻击。

总结:前端验证,提升体验,安全为重

通过 comment_form 函数和相关的钩子,我们可以轻松地将前端验证脚本注入到 WordPress 评论表单中,提升用户体验,减少服务器压力。但务必注意,前端验证只是第一道防线,服务器端验证和安全措施才是关键。

希望今天的分享对大家有所帮助!

发表回复

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