0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

WordPress コメント一覧・投稿機能を記事詳細ページから分離する方法

Last updated at Posted at 2025-10-01

はじめに

Wordpressでは記事詳細ページの下のほうに、コメント一覧を表示し、その下にコメント入力欄を表示する、というのが一般的だと思います。
ただ、クライアントの要望により、コメント一覧・投稿機能を記事詳細ページから分離したい、ということもあると思います。
想定してたよりも、だいぶ大変だったので、筆者が忘れないように、やり方を書き記します。
誰かベストプラクティス的な方法教えてけろ ('ω')

実装の背景

  • 課題: 記事詳細ページで全コメントとCAPTCHA認証を毎回読み込むため、パフォーマンスに負荷
  • 解決策: コメント一覧・投稿機能を専用ページに分離
  • 効果: 記事詳細ページの読み込み速度向上

概要

1. 記事詳細ページの変更

  • 記事詳細ページでは、コメントを表示するのみ。書き込みはしない
  • 直近2件のコメントのみ表示する
  • 「もっと見る」ボタンでコメント一覧ページへ遷移
  • 「もっと見る」ボタンに、コメント総件数を括弧内に表示 例: もっと見る(28)

2. コメント一覧ページの新設

  • /comments/?post_id={記事ID} で、コメント一覧ページにアクセス
  • 全コメントをページング表示(20件/ページ)
  • コメント一覧の下のほうに、コメント投稿機能を配置

実装手順

Step 1: 固定ページ「/comments/」の作成

WordPress管理画面で固定ページ「/comments/」を作成して公開します。
記事本文は何も入力しないでOKです。

記事タイトル: コメント一覧 ※このタイトルは使わないので識別できる名前なら何でもOK
URLスラッグ: comments

Step 2: コメント一覧ページテンプレートの作成

page-comments.php を作成し、コメント一覧表示と投稿機能を実装します。
HTMLテンプレートは皆さまの環境に合わせて適宜調整してください。

wp-content/themes/{theme_name}/page-comments.php
<?php
/**
 * Name: page-comments.php
 * Description: コメント一覧ページ
 * URL: /comments/?post_id={記事ID}
 */

get_header();
  
// 記事IDを取得(セキュリティ強化)
$post_id = isset($_GET['post_id']) ? intval($_GET['post_id']) : 0;

// 入力値の妥当性チェック
if ($post_id && (!is_numeric($post_id) || $post_id <= 0)) {
  wp_redirect(home_url());
  exit;
}

// 記事を取得
$post = get_post($post_id);

// 記事が見つからない場合またはセキュリティチェック失敗時はホームページにリダイレクト
if (!$post || !in_array($post->post_status, ['publish', 'future']) || !$post_id) {
  wp_redirect(home_url());
  exit;
}

// コメント機能が有効かチェック
if (!comments_open($post_id)) {
  wp_redirect(get_permalink($post_id));
  exit;
}

// ページング設定
$comments_per_page = 20;
$current_page = get_query_var('cpage') ? max(1, intval(get_query_var('cpage'))) : 1;

// ページ番号の妥当性チェック
if ($current_page > 1000) {
  wp_redirect(home_url());
  exit;
}

$offset = ($current_page - 1) * $comments_per_page;
$total_comments = get_comment_total_count($post_id);
$max_pages = $total_comments > 0 ? (int) ceil($total_comments / $comments_per_page) : 1;

// コメントを取得
$comments = get_comments(array(
  'post_id' => $post_id,
  'status' => 'approve',
  'number' => $comments_per_page,
  'offset' => $offset,
  'order' => 'DESC'
));
?>

<?php
// コメント投稿結果メッセージの表示
$comment_posted = isset($_GET['comment_posted']) ? intval($_GET['comment_posted']) : 0;
$comment_error = isset($_GET['comment_error']) ? intval($_GET['comment_error']) : 0;
?>
<?php if ($comment_posted === 1): ?>
    <div class="comment-message comment-message-success" style="background-color: #d4edda; color: #155724; padding: 15px; margin: 20px 0; border: 1px solid #c3e6cb; border-radius: 4px;">
        <strong>コメントを投稿いただき、ありがとうございます。</strong><br>
        コメントは管理者が承認した後、公開されます。今しばらくお待ちくださいませ。
    </div>
<?php elseif ($comment_error === 1): ?>
    <div class="comment-message comment-message-error" style="background-color: #f8d7da; color: #721c24; padding: 15px; margin: 20px 0; border: 1px solid #f5c6cb; border-radius: 4px;">
        <strong>コメント投稿に失敗しました</strong><br>
        お手数ですが、再度お試しください。
    </div>
<?php endif; ?>

<h2><a href="<?php echo esc_url(get_permalink($post_id)); ?>">
  <?php echo esc_html(get_the_title($post_id)); ?>
</a>のコメント一覧</h2>

<h3>コメント一覧</h3>
<?php if ($comments): ?>
<ul class="comments-list">
<?php $args = array(
  'avatar_size' => 55,
  'reverse_top_level' => false,
  'per_page' => -1,
);
wp_list_comments($args, $comments); ?>
</ul>
<!-- コメントページネーション -->
<?php render_comment_pagination($post_id, $current_page, $max_pages); ?>
<?php else: ?>
<p>まだこの記事にコメントはありません。</p>
<?php endif; ?>


<!-- コメント投稿フォーム -->
<h3>コメントを投稿する</h3>
<?php
$args = array(
  'title_reply_before' => '<div class="respondform_title">',
  'title_reply_after' => '</div>',
  'label_submit' => '送信',
);
comment_form($args, $post_id);
?>

<!-- 記事に戻るリンク -->
<a href="<?php echo esc_url(get_permalink($post_id)); ?>">記事に戻る</a>

<?php get_footer(); ?>

Step 3: 記事詳細ページ用コメント表示の修正

comments.php を修正し、直近2件のコメントのみ表示するようにします。

wp-content/themes/{theme_name}/comments.php
<!-- 記事詳細ページ用のコメント一覧表示(comments.php) -->

<?php
//コメントが有効の場合
if (comments_open()):

$post_id = get_the_ID();
$comments_page_url = add_query_arg('post_id', $post_id, home_url('/comments/'));
$comment_count = get_comments_number(); // 承認済みコメントの総件数を取得
$comments_per_page = 2;


if($comment_count > 0){
  $comment_button_text = 'コメントをもっと見る(' . $comment_count . ')';
} else{
  $comment_button_text = 'コメントを投稿する';
}

// 直近2件のコメントを取得
$comments = get_comments(array(
  'post_id' => $post_id,
  'parent' => 0,
  'status' => 'approve',
  'number' => $comments_per_page,
  'offset' => 0,
  'order' => 'DESC',
  'hierarchical' => false  // 子コメント情報を含まない
));
?>

<h3>最新のコメント</h3>
<?php if ($comments()): ?>

<ul class="comments-list">
<?php $args = array(
  'avatar_size' => 55,
  'reverse_top_level' => false,
  'per_page' => -1,
);
wp_list_comments($args, $comments); ?>
</ul>
<?php else: ?>
<p>まだこの記事にコメントはありません。</p>
<?php endif; ?>
<!-- コメントをもっと見るボタン -->
<a href="<?php echo esc_url($comments_page_url); ?>">
<?php echo $comment_button_text; ?>
</a>
<?php endif; ?>

Step 4: functions.php への機能追加

functions.php にコメント機能に関する関数を追加します。
ページネーションは the_comments_pagination() という、おあつらえ向きな関数が用意されていますが、
なーんか思った通りの挙動をしないので、自前でページネーションを実装しました。

wp-content/themes/{theme_name}/functions.php
/**
 * コメント一覧ページのURL生成
 *
 * @param int $post_id 投稿ID
 * @return string コメント一覧ページのURL
 */
function get_comment_page_url($post_id = null)
{
  if (!$post_id) {
    $post_id = get_the_ID();
  }

  // 投稿IDの妥当性チェック
  if (!$post_id || !is_numeric($post_id) || $post_id <= 0) {
    return home_url();
  }

  // フォールバック: クエリパラメータ形式
  return home_url('/comments/?post_id=' . intval($post_id));
}

/**
 * コメント総件数を取得する関数
 *
 * @param int $post_id 投稿ID
 * @return int コメント総件数
 */
function get_comment_total_count($post_id = null)
{
  // 投稿IDが指定されていない場合は現在の投稿IDを取得
  if (!$post_id) {
    $post_id = get_the_ID();
  }

  // コメント機能が有効かチェック
  if (!comments_open($post_id)) {
    return 0;
  }

  // 承認済みコメントの総件数を取得
  $comment_count = get_comments_number($post_id);

  return (int) $comment_count;
}

/**
 * コメント一覧ページのページング表示
 *
 * @param int $post_id 投稿ID
 * @param int $current_page 現在のページ
 * @param int $max_pages 最大ページ数
 * @return void
 */
function render_comment_pagination($post_id, $current_page, $max_pages)
{
  try {
    // パラメータの妥当性チェック
    if (!is_numeric($post_id) || $post_id <= 0) {
      return;
    }

    if (!is_numeric($current_page) || $current_page <= 0) {
      $current_page = 1;
    }

    if (!is_numeric($max_pages) || $max_pages <= 0) {
      return;
    }

    if ($max_pages <= 1) {
      return; // ページングが不要
    }

    // ページネーションHTML構造に合わせて出力
    echo '<nav class="pagination">';
    echo '<div class="nav-links">';

    // ページ番号(前後2件ずつ表示)
    $range = 2;
    $start = max(1, $current_page - $range);
    $end = min($max_pages, $current_page + $range);

    for ($i = $start; $i <= $end; $i++) {
      if ($i == $current_page) {
        echo '<span class="current page-numbers">' . esc_html($i) . '</span>';
      }
      else {
        $page_url = add_query_arg(array(
          'post_id' => intval($post_id),
          'cpage' => intval($i)
        ), get_comment_page_url($post_id));

        echo '<a class="page-numbers" href="' . esc_url($page_url) . '">' . esc_html($i) . '</a>';
      }
    }

    echo '</div>';

    // ナビゲーション矢印の出力
    if ($current_page > 1) {
      $first_url = add_query_arg(array(
        'post_id' => intval($post_id),
        'cpage' => 1
      ), get_comment_page_url($post_id));

      echo '<a class="first page-arrows" href="' . esc_url($first_url) . '"></a>';
    }

    if ($current_page > 1) {
      $prev_url = add_query_arg(array(
        'post_id' => intval($post_id),
        'cpage' => intval($current_page - 1)
      ), get_comment_page_url($post_id));

      echo '<a class="prev page-arrows" href="' . esc_url($prev_url) . '"></a>';
    }

    if ($current_page < $max_pages) {
      $next_url = add_query_arg(array(
        'post_id' => intval($post_id),
        'cpage' => intval($current_page + 1)
      ), get_comment_page_url($post_id));

      echo '<a class="next page-arrows" href="' . esc_url($next_url) . '"></a>';
    }

    if ($current_page < $max_pages) {
      $last_url = add_query_arg(array(
        'post_id' => intval($post_id),
        'cpage' => intval($max_pages)
      ), get_comment_page_url($post_id));

      echo '<a class="last page-arrows" href="' . esc_url($last_url) . '"></a>';
    }

    echo '</nav>';
  }
  catch (Exception $e) {
    error_log('render_comment_pagination error: ' . $e->getMessage());
    // エラー時は何も表示しない
  }
}

/**
 * コメント返信リンクをコメント一覧ページに変更
 * 
 * @param string $comment_reply_link デフォルトの返信リンクHTML
 * @param array $args 引数配列
 * @param WP_Comment $comment コメントオブジェクト
 * @param WP_Post $post 投稿オブジェクト
 * @return string 変更された返信リンクHTML
 */
function modify_comment_reply_link($comment_reply_link, $args, $comment, $post)
{
    // コメント一覧ページのURLを生成
    $comment_page_url = get_comment_page_url($post->ID);
    
    // 返信先コメントIDを取得
    $reply_to_comment_id = $comment->comment_ID;
    
    // コメント一覧ページのURLに返信先パラメータを追加
    $new_reply_url = add_query_arg('replytocom', $reply_to_comment_id, $comment_page_url);
    $new_reply_url .= '#respond';
    
    // 元のリンクテキストを保持しつつ、URLのみを変更
    $link_text = $args['reply_text'] ?? '返信';
    
    // コメント情報を取得
    $comment_id = $comment->comment_ID;
    $comment_author = $comment->comment_author;
    $post_id = $post->ID;
    
    // 返信先の表示名を生成
    $reply_to_text = '返信';
    
    // 必要な属性値を設定
    $new_link = '<div class="reply"><a rel="nofollow" class="comment-reply-link" href="' . esc_url($new_reply_url) . '" data-commentid="' . esc_attr($comment_id) . '" data-postid="' . esc_attr($post_id) . '" data-belowelement="div-comment-' . esc_attr($comment_id) . '" data-respondelement="respond" data-replyto="' . esc_attr($reply_to_text) . '" aria-label="' . esc_attr($reply_to_text) . '">' . esc_html($link_text) . '</a></div>';
    
    return $new_link;
}
add_filter('comment_reply_link', 'modify_comment_reply_link', 10, 4);

/**
 * コメント投稿完了後のリダイレクト先を制御
 * コメント一覧画面に戻す
 *
 * @param string $location デフォルトのリダイレクト先URL
 * @return string カスタムリダイレクト先URL
 */
function redirect_after_comment($location)
{
  // 投稿IDを取得
  $post_id = isset($_POST['comment_post_ID']) ? intval($_POST['comment_post_ID']) : 0;
  if ($post_id > 0) {
    // コメント一覧ページのURLを生成
    $comment_page_url = get_comment_page_url($post_id);
    // 成功メッセージのクエリパラメータを追加
    $comment_page_url = add_query_arg('comment_posted', '1', $comment_page_url);
    return $comment_page_url;
  }

  return $location;
}
add_filter('comment_post_redirect', 'redirect_after_comment');

Step 5: SEO対策の実装

コメント一覧ページのSEO対策として、メタタグを適切に設定します。
「SEO SIMPLE PACK」というプラグインを使っているため、そのフィルターフックを使って設定します。

wp-content/themes/{theme_name}/functions.php
/**
 * コメント一覧ページの対象記事を取得
 *
 * @return WP_Post|null 対象記事のオブジェクト、取得できない場合はnull
 */
function get_comments_page_target_post()
{
  if (!is_page('comments')) {
    return null;
  }

  $post_slug = get_query_var('post_slug');
  $post_id = isset($_GET['post_id']) ? intval($_GET['post_id']) : 0;

  // 記事を取得
  $target_post = null;

  if ($post_slug) {
    $target_post = get_page_by_path($post_slug, OBJECT, 'post');
  }
  elseif ($post_id) {
    $target_post = get_post($post_id);
  }

  // 記事が存在し、公開状態か公開予約状態の場合のみ返す
  if ($target_post && in_array($target_post->post_status, ['publish', 'future'])) {
    return $target_post;
  }

  return null;
}

/**
 * コメント一覧ページのタイトルを設定
 */
function overwrite_ssp_title_comments($ssp_title)
{
  $target_post = get_comments_page_target_post();

  if ($target_post) {
    $post_title = get_the_title($target_post->ID);
    $site_name = get_bloginfo('name');
    return $post_title . ' のコメント一覧 | ' . $site_name;
  }

  return $ssp_title;
}
add_filter('ssp_output_title', 'overwrite_ssp_title_comments');

/**
 * コメント一覧ページのディスクリプションを設定
 */
function overwrite_ssp_description_comments($ssp_description)
{
  $target_post = get_comments_page_target_post();

  if ($target_post) {
    $post_title = get_the_title($target_post->ID);
    $post_excerpt = get_the_excerpt($target_post->ID);

    // 記事の抜粋があればそれを使用、なければデフォルトメッセージ
    if ($post_excerpt) {
      return $post_excerpt;
    }
    else {
      return $post_title . ' のコメント一覧ページです。読者の感想や意見を確認できます。';
    }
  }

  return $ssp_description;
}
add_filter('ssp_output_description', 'overwrite_ssp_description_comments');

Step 6: コメントのエラーハンドリング

wp-content/themes/{theme_name}/functions.php
/**
 * コメント投稿エラー時のリダイレクト処理
 * 
 * @param WP_Error $error エラーオブジェクト
 * @param array $comment_data コメントデータ
 * @return void
 */
function handle_comment_error($error, $comment_data)
{
    // エラーが発生した場合のみ処理
    if (is_wp_error($error) && $error->get_error_code()) {
        $post_id = isset($comment_data['comment_post_ID']) ? intval($comment_data['comment_post_ID']) : 0;
        
        if ($post_id > 0) {
            // コメント一覧ページのURLを生成
            $comment_page_url = get_comment_page_url($post_id);
            // エラーメッセージのクエリパラメータを追加
            $comment_page_url = add_query_arg('comment_error', '1', $comment_page_url);
            // リダイレクト
            wp_redirect($comment_page_url);
            exit;
        }
    }
}
add_action('comment_duplicate_trigger', 'handle_comment_error', 10, 2);
add_action('comment_flood_trigger', 'handle_comment_error', 10, 2);

/**
 * コメント投稿処理全体のエラーハンドリング
 * 
 * @param int|WP_Error $comment_id コメントIDまたはエラーオブジェクト
 * @param array $comment_data コメントデータ
 * @return void
 */
function handle_comment_submission_error($comment_id, $comment_data)
{
    // エラーが発生した場合
    if (is_wp_error($comment_id)) {
        $post_id = isset($comment_data['comment_post_ID']) ? intval($comment_data['comment_post_ID']) : 0;
        
        if ($post_id > 0) {
            // コメント一覧ページのURLを生成
            $comment_page_url = get_comment_page_url($post_id);
            // エラーメッセージのクエリパラメータを追加
            $comment_page_url = add_query_arg('comment_error', '1', $comment_page_url);
            // リダイレクト
            wp_redirect($comment_page_url);
            exit;
        }
    }
}
add_action('wp_insert_comment', 'handle_comment_submission_error', 10, 2);

Step 7: コメントのタイムスタンプのリンクを削除する

コメント一覧ページを分離した影響か、タイムスタンプのリンクが /entry/{page_slug}/comment-page--2/#comment-2005 みたいな意味不明なリンクになっており、当然ながら正常にリンクしません。

このタイムスタンプのリンクを削除するために、wp_list_comments のコールバックを使用します。

wp-content/themes/{theme_name}/functions.php
/**
 * カスタムコメント表示テンプレート
 * タイムスタンプリンクを削除し、それ以外の部分はデフォルトの表示を維持
 */
function my_custom_comments($comment, $args, $depth)
{
    $GLOBALS['comment'] = $comment;
?>
    <li <?php comment_class(); ?> id="comment-<?php comment_ID(); ?>">
        <div id="div-comment-<?php comment_ID(); ?>" class="comment-body">
            <div class="comment-author vcard">
                <?php
                if ($args['avatar_size'] != 0) {
                    echo get_avatar($comment, $args['avatar_size']);
                }
                ?>
                <cite class="fn"><?php comment_author_link(); ?></cite>
                <span class="says">より:</span>
            </div>
            <div class="comment-meta commentmetadata">
                <a>
                    <?php
                    // リンクなしの日付と時間を表示
                    printf(
                        '%s %s',
                        get_comment_date('', $comment),
                        get_comment_time()
                    );
                    edit_comment_link(__('(Edit)'), ' &nbsp;&nbsp;', '');
                    ?>
                </a>
            </div>
            <?php if ($comment->comment_approved == '0') : ?>
                <em class="comment-awaiting-moderation"><?php _e('Your comment is awaiting moderation.'); ?></em>
                <br />
            <?php endif; ?>
            <div class="comment-content">
                <?php comment_text(); ?>
            </div>
            <div class="reply">
                <?php
                comment_reply_link(array_merge($args, array(
                    'reply_text' => "返信",
                    'depth' => $depth,
                    'max_depth' => $args['max_depth']
                )));
                ?>
            </div>
        </div>
    <?php
}
wp-content/themes/{theme_name}/comments.php

<?php $args = array(
    ...
    'callback' => 'my_custom_comments', // カスタムコメント表示テンプレートを使用 functions.php に定義
);
wp_list_comments($args, $comments); ?>

wp-content/themes/{theme_name}/page-comments.php

<?php $args = array(
    ...
    'callback' => 'my_custom_comments', // カスタムコメント表示テンプレートを使用 functions.php に定義
);
wp_list_comments($args, $comments); ?>

ここまでやれば、コメント投稿機能、一覧表示の分離ができます。

大変すぎワロタ。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?