2026年5月17日

2026年5月17日

WordPressのコメント機能をカスタマイズする方法

はじめに

WordPressのコメント機能はデフォルトのままでは見た目が古く、スパムにも脆弱です。comment_form()の引数カスタマイズ、Walker_Commentクラスの継承による独自HTML生成、そしてスパムフィルタリングまで、コメントシステムを一から作り直す方法を詳しく解説します。

症状・原因

  • コメントフォームのデザインがテーマと合わず統一感がない
  • デフォルトのコメントHTMLが古くレスポンシブ対応していない
  • スパムコメントが大量に届きサイト管理が煩雑になっている
  • コメント投稿後の処理(通知・ポイント付与など)を追加したい
  • ネストされたコメント(スレッド表示)が正しく表示されない

解決手順

ステップ1:comment_form()でカスタムフィールドと引数を設定

<?php
/**
 * カスタムコメントフォームを出力する
 * single.php や comments.php で comments_template() の代わりに使用
 */
function render_custom_comment_form() {
    // ログイン状態によってフォームを切り替え
    if ( ! comments_open() ) {
        echo '<p class="comments-closed">このページのコメントは締め切られています。</p>';
        return;
    }

    // コメントフォームのフィールドをカスタマイズ
    $custom_fields = array(
        // 名前フィールド
        'author' => sprintf(
            '<div class="comment-form-author">
                <label for="author">%s <span class="required" aria-hidden="true">*</span></label>
                <input id="author" name="author" type="text" value="%s" size="30"
                    maxlength="245" autocomplete="name" required />
            </div>',
            __( 'お名前', 'your-theme' ),
            esc_attr( $commenter['comment_author'] ?? '' )
        ),
        // メールフィールド
        'email' => sprintf(
            '<div class="comment-form-email">
                <label for="email">%s <span class="required" aria-hidden="true">*</span></label>
                <input id="email" name="email" type="email" value="%s" size="30"
                    maxlength="100" autocomplete="email" required />
                <p class="form-help">メールアドレスは公開されません。</p>
            </div>',
            __( 'メールアドレス', 'your-theme' ),
            esc_attr( $commenter['comment_author_email'] ?? '' )
        ),
        // ウェブサイトフィールド(非表示にする場合は削除)
        'url' => sprintf(
            '<div class="comment-form-url">
                <label for="url">%s</label>
                <input id="url" name="url" type="url" value="%s" size="30"
                    maxlength="200" autocomplete="url" />
            </div>',
            __( 'ウェブサイト(任意)', 'your-theme' ),
            esc_attr( $commenter['comment_author_url'] ?? '' )
        ),
    );

    comment_form( array(
        'fields'             => $custom_fields,
        // コメントテキストエリアのカスタマイズ
        'comment_field'      => '<div class="comment-form-comment">
            <label for="comment">' . __( 'コメント', 'your-theme' ) . ' <span class="required">*</span></label>
            <textarea id="comment" name="comment" cols="45" rows="6" required></textarea>
        </div>',
        'id_form'            => 'comment-form',
        'class_form'         => 'comment-form custom-comment-form',
        'id_submit'          => 'comment-submit',
        'class_submit'       => 'submit btn btn-primary', // Bootstrapクラスなど
        'name_submit'        => 'submit',
        'label_submit'       => __( 'コメントを送信', 'your-theme' ),
        'title_reply'        => __( 'コメントを残す', 'your-theme' ),
        'title_reply_to'     => __( '%s さんへ返信', 'your-theme' ),
        'cancel_reply_link'  => __( '返信をキャンセル', 'your-theme' ),
        'comment_notes_before' => '<p class="comment-notes">コメントは承認後に公開されます。</p>',
        'comment_notes_after'  => '',
    ) );
}
?>

ステップ2:Walker_Commentを継承してカスタムHTMLを生成

<?php
/**
 * カスタムコメントウォーカークラス
 * Walker_Comment を継承してコメントのHTMLをフルカスタマイズ
 */
class Custom_Walker_Comment extends Walker_Comment {

    /**
     * 各コメントの開始タグを出力
     * @param string $output  出力HTML
     * @param object $comment コメントオブジェクト
     * @param int    $depth   ネスト深度
     * @param array  $args    wp_list_comments()に渡された引数
     */
    public function start_el( &$output, $comment, $depth = 0, $args = array(), $id = 0 ) {
        $depth++;
        $GLOBALS['comment_depth'] = $depth;
        $GLOBALS['comment']       = $comment;

        // コメントの種類に応じて処理を分岐
        if ( 'comment' === $comment->comment_type ) {
            ob_start();
            $this->html5_comment( $comment, $depth, $args );
            $output .= ob_get_clean();
        }
    }

    /**
     * カスタムコメントHTMLの生成
     */
    protected function html5_comment( $comment, $depth, $args ) {
        $tag = ( $args['style'] === 'div' ) ? 'div' : 'li';
        $commenter = wp_get_current_commenter();
        $show_pending_links = ! empty( $commenter['comment_author'] );

        // 現在のユーザーのコメントかどうか
        $is_by_post_author = ( $comment->user_id === get_post()->post_author );
        $author_class = $is_by_post_author ? ' comment-by-author' : '';
        ?>

        <<?php echo $tag; ?> id="comment-<?php comment_ID(); ?>"
            <?php comment_class( $this->filled_classes( $comment ) . $author_class, $comment ); ?>>

            <article id="div-comment-<?php comment_ID(); ?>" class="comment-body">

                <!-- コメントヘッダー:アバター + メタ情報 -->
                <header class="comment-header">
                    <div class="comment-author-avatar">
                        <?php
                        // アバターサイズをカスタム(デフォルトは96px)
                        echo get_avatar( $comment, $args['avatar_size'] ?? 60, '', '', array(
                            'class' => 'avatar rounded-circle',
                        ) );
                        ?>
                    </div>
                    <div class="comment-meta">
                        <div class="comment-author-name">
                            <?php
                            // 投稿者名(リンクあり/なしを制御)
                            $comment_author_url = get_comment_author_url( $comment );
                            if ( $comment_author_url && 'http://' !== $comment_author_url ) {
                                echo '<a href="' . esc_url( $comment_author_url ) . '" rel="external nofollow">';
                            }
                            echo get_comment_author( $comment );
                            if ( $comment_author_url && 'http://' !== $comment_author_url ) {
                                echo '</a>';
                            }
                            // 投稿者バッジ
                            if ( $is_by_post_author ) {
                                echo '<span class="author-badge">投稿者</span>';
                            }
                            ?>
                        </div>
                        <time class="comment-date" datetime="<?php comment_time( 'c' ); ?>">
                            <?php printf( '%s 前', human_time_diff( get_comment_time( 'U' ), current_time( 'timestamp' ) ) ); ?>
                        </time>
                    </div>
                </header>

                <!-- コメント本文 -->
                <div class="comment-content">
                    <?php
                    // 承認待ちコメントの表示
                    if ( '0' == $comment->comment_approved ) {
                        echo '<p class="comment-awaiting-moderation">このコメントは承認待ちです。</p>';
                    }
                    comment_text( $comment );
                    ?>
                </div>

                <!-- コメントフッター:返信リンク -->
                <footer class="comment-footer">
                    <?php
                    comment_reply_link( array_merge( $args, array(
                        'add_below' => 'div-comment',
                        'depth'     => $depth,
                        'max_depth' => $args['max_depth'],
                        'before'    => '<div class="reply">',
                        'after'     => '</div>',
                    ) ) );
                    ?>
                </footer>

            </article>
        <?php
    }

    /**
     * コメントの追加クラスを返す
     */
    private function filled_classes( $comment ) {
        $classes = array();
        if ( $comment->user_id > 0 ) {
            $classes[] = 'registered-user';
        }
        return implode( ' ', $classes );
    }
}
?>

ステップ3:wp_list_comments()でカスタムウォーカーを使用

<?php
/**
 * comments.php でカスタムウォーカーを使ってコメント一覧を出力
 */

// コメントが存在する場合
if ( have_comments() ) : ?>

    <section id="comments" class="comments-section">
        <h2 class="comments-title">
            <?php
            $comments_count = get_comments_number();
            printf(
                esc_html( _n(
                    '%1$s件のコメント「%2$s」',
                    '%1$s件のコメント「%2$s」',
                    $comments_count,
                    'your-theme'
                ) ),
                number_format_i18n( $comments_count ),
                '<span>' . get_the_title() . '</span>'
            );
            ?>
        </h2>

        <ol class="comment-list">
            <?php
            wp_list_comments( array(
                'walker'      => new Custom_Walker_Comment(), // カスタムウォーカー指定
                'style'       => 'ol',         // リストスタイル(ol/ul/div)
                'short_ping'  => true,          // ピングバックを短く表示
                'avatar_size' => 60,            // アバターサイズ(ピクセル)
                'max_depth'   => 3,             // ネストの最大深度
                'reply_text'  => '返信する',    // 返信リンクのテキスト
                'callback'    => null,          // カスタムウォーカー使用時はnull
            ) );
            ?>
        </ol>

        <!-- コメントナビゲーション(多数のコメントがある場合) -->
        <?php
        the_comments_navigation( array(
            'prev_text' => '&larr; 古いコメント',
            'next_text' => '新しいコメント &rarr;',
        ) );
        ?>

    </section>

<?php endif; ?>

ステップ4:comment_postフックで投稿後処理を追加

<?php
/**
 * コメント投稿後の処理
 * 通知メール送信、ポイント付与、ログ記録などに使用
 */
function after_comment_post( $comment_id, $comment_approved, $commentdata ) {
    // 承認済みコメントのみ処理
    if ( 1 !== $comment_approved ) {
        return;
    }

    $comment = get_comment( $comment_id );
    $post    = get_post( $comment->comment_post_ID );

    // --- 1. 管理者への通知メール ---
    $admin_email = get_option( 'admin_email' );
    $subject     = sprintf( '新しいコメント:%s', $post->post_title );
    $message     = sprintf(
        "投稿「%s」に新しいコメントが届きました。\n\n投稿者: %s\nコメント:\n%s\n\n確認: %s",
        $post->post_title,
        $comment->comment_author,
        $comment->comment_content,
        admin_url( "comment.php?action=editcomment&c={$comment_id}" )
    );
    wp_mail( $admin_email, $subject, $message );

    // --- 2. ユーザーへのポイント付与(ログインユーザーのみ) ---
    if ( $comment->user_id > 0 ) {
        $current_points = (int) get_user_meta( $comment->user_id, 'comment_points', true );
        update_user_meta( $comment->user_id, 'comment_points', $current_points + 10 );
    }

    // --- 3. コメント統計を投稿メタに記録 ---
    $comment_count = get_post_meta( $comment->comment_post_ID, '_comment_count_custom', true );
    update_post_meta( $comment->comment_post_ID, '_comment_count_custom', (int) $comment_count + 1 );
}
add_action( 'comment_post', 'after_comment_post', 10, 3 );
?>

ステップ5:preprocess_commentフィルターでスパム防止を実装

<?php
/**
 * コメントスパム防止フィルター
 * キーワードフィルタリング + フラッド(連投)チェック
 */
function prevent_comment_spam( $commentdata ) {
    // --- 1. スパムキーワードフィルタリング ---
    $spam_keywords = array(
        'カジノ', 'ギャンブル', 'ローン', '副業', 'FX', 'bitcoin', 'crypto',
        'click here', 'buy now', 'cheap', 'free money',
    );

    $comment_text = strtolower( $commentdata['comment_content'] );

    foreach ( $spam_keywords as $keyword ) {
        if ( mb_strpos( $comment_text, mb_strtolower( $keyword ) ) !== false ) {
            wp_die(
                __( 'スパムとして検出されました。コメントを投稿できません。', 'your-theme' ),
                __( 'スパム検出', 'your-theme' ),
                array( 'response' => 403 )
            );
        }
    }

    // --- 2. フラッドチェック(同一IPからの連続投稿を防ぐ) ---
    $comment_ip = $commentdata['comment_author_IP'];
    $flood_key  = 'comment_flood_' . md5( $comment_ip );
    $last_time  = get_transient( $flood_key );

    if ( false !== $last_time ) {
        $time_diff = time() - $last_time;
        if ( $time_diff < 60 ) { // 60秒以内の連続投稿をブロック
            wp_die(
                sprintf(
                    __( 'コメントは%d秒後に再度お試しください。', 'your-theme' ),
                    60 - $time_diff
                ),
                __( '投稿制限', 'your-theme' ),
                array( 'response' => 429 )
            );
        }
    }

    // 最終投稿時刻を記録(60秒のトランジェント)
    set_transient( $flood_key, time(), 60 );

    // --- 3. URLの数が多すぎるコメントをブロック ---
    $url_count = preg_match_all( '/https?:\/\//i', $commentdata['comment_content'], $matches );
    if ( $url_count > 2 ) {
        wp_die(
            __( 'URLが多すぎます。コメントを投稿できません。', 'your-theme' ),
            __( 'スパム検出', 'your-theme' ),
            array( 'response' => 403 )
        );
    }

    return $commentdata; // 問題なければそのまま返す
}
add_filter( 'preprocess_comment', 'prevent_comment_spam' );
?>

注意事項

  • Akismet連携: 独自スパムフィルターに加えてAkismetプラグインを使うとより効果的にスパムを防げます
  • コメントモデレーション: preprocess_commentは投稿前処理のため、疑わしいコメントはwp_die()で完全ブロックするか、comment_approved0にして承認待ちにする2つの方法があります
  • ウォーカーのHTML: Walker_Commentを継承する場合、start_el()end_el()の両方をオーバーライドして正しい開閉タグを出力してください
  • 返信機能: ネストコメントを有効にするには「設定 > ディスカッション > スレッドコメント」をオンにする必要があります

まとめ

comment_form()の引数カスタマイズ、Walker_Comment継承による独自HTML、そしてpreprocess_commentフィルターのスパム対策を組み合わせることで、デザイン・機能・セキュリティを兼ね備えたコメントシステムが構築できます。関連記事:WordPressのユーザープロフィールにカスタムフィールドを追加する方法

お気軽にご相談ください

お見積りへ お問い合わせへ