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' => '← 古いコメント',
'next_text' => '新しいコメント →',
) );
?>
</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_approvedを0にして承認待ちにする2つの方法があります - ウォーカーのHTML:
Walker_Commentを継承する場合、start_el()とend_el()の両方をオーバーライドして正しい開閉タグを出力してください - 返信機能: ネストコメントを有効にするには「設定 > ディスカッション > スレッドコメント」をオンにする必要があります
まとめ
comment_form()の引数カスタマイズ、Walker_Comment継承による独自HTML、そしてpreprocess_commentフィルターのスパム対策を組み合わせることで、デザイン・機能・セキュリティを兼ね備えたコメントシステムが構築できます。関連記事:WordPressのユーザープロフィールにカスタムフィールドを追加する方法