プラグインなしで「リアクション」機能を追加する方法

Cocoonカスタマイズ集
この記事は約12分で読めます。
記事内に広告が含まれています。

はじめに

以下の質問に回答します。

プラグイン「Post Reaction」などを使用せずに、以前紹介して「いいね」機能を拡張し、投稿本文の下、コメントに「リアクション」ボタンを追加します。

従来の「いいね」機能は1種類の反応しか扱えませんでしたが、ここでは複数のリアクション(例:いいね・参考になった・すごい など)を扱えるようにします。

この記事では、この方法について説明します。

完成イメージ

実装手順

以下の手順で実装します。

  • ラベル
    PHPを追加(共通)

    以下のコードをfunctions.phpに追加します。

    /**
     * Ajaxコールバック関数
     *
     * @return void
     */
    if (!function_exists('reactions_callback')):
    function reactions_callback() {
      check_ajax_referer('reactions_action', 'reactions_nonce');
    
      // POSTパラメータを取得
      $type     = $_POST['type'] ?? 'post';
      $id       = intval($_POST['id'] ?? 0);
      $reaction = $_POST['reaction'] ?? '';
      $prev     = $_POST['prev_reaction'] ?? '';
      $key      = 'my_' . $type . '_reactions';
    
      $type     = sanitize_text_field($type);
      $reaction = sanitize_text_field($reaction);
      $prev     = sanitize_text_field($prev);
    
      // カスタムフィールドに保存されているデータを取得
      $stored_raw = ($type === 'comment') ? get_comment_meta($id, $key, true) : get_post_meta($id, $key, true);
      
      $stored = json_decode((string) $stored_raw, true);
      if (!is_array($stored)) {
        $stored = [];
      }
    
      // 前回のリアクションを減算
      if ($prev && isset($stored[$prev]) && $stored[$prev] > 0) {
        $stored[$prev]--;
      }
    
      // 新しいリアクションを加算
      if ($reaction) {
        if (!isset($stored[$reaction])) {
          $stored[$reaction] = 0;
        }
        $stored[$reaction]++;
      }
    
      // カスタムフィールドを更新
      $val = wp_json_encode($stored);
      if ($type === 'comment') {
        update_comment_meta($id, $key, $val);
      } else {
        update_post_meta($id, $key, $val);
      }
    
      // 更新情報をJSON形式で返却
      wp_send_json_success($stored);
    }
    endif;
    
    
    // フロント用JSとライブラリを追加
    add_action('wp_enqueue_scripts', function () {
      wp_enqueue_script('jquery-cookie', 'https://cdnjs.cloudflare.com/ajax/libs/jquery-cookie/1.4.1/jquery.cookie.min.js', ['jquery'], '1.4.1', true);
    
      $ajax_url = esc_url(admin_url('admin-ajax.php'));
      $nonce    = wp_create_nonce('reactions_action');
    
      ob_start();
      ?>
      jQuery(function ($) {
        /**
         * クッキーから保存済みのIDリストを取得
         *
         * @param {string} type 対象の種別
         * @return {Object} 保存済みIDのオブジェクト
         */
        function getStoredIds(type) {
          var data = $.cookie('my_' + type + '_reactions');
          return data ? JSON.parse(data) : {};
        }
    
        /**
         * ページ内の全ボタンの状態をクッキーと同期
         *
         * @return {void}
         */
        function syncAllButtonStates() {
          $('.reactions').each(function () {
            var $con  = $(this),
                type  = $con.data('type'),
                id    = String($con.data('id')),
                stored = getStoredIds(type),
                active = stored[id];
    
            $con.find('.reactions__button').removeClass('active');
    
            if (active) {
              $con.find('.reactions__button[data-reaction="' + active + '"]').addClass('active');
            }
          });
        }
    
        // ページ読み込み時に実行
        syncAllButtonStates();
    
        $(document).on('click', '.reactions__button', function (e) {
          e.preventDefault();
          var $btn  = $(this), $con = $btn.closest('.reactions'),
              type  = $con.data('type'),
              id    = String($con.data('id')),
              reaction  = $btn.data('reaction'),
              stored    = getStoredIds(type),
              prev      = stored[id] || '', 
              mode      = (prev === reaction) ? 'remove' : 'add';
    
          // IDリストを更新
          if (mode === 'remove') {
            delete stored[id];
            reaction = '';
          } else {
            stored[id] = reaction;
          }
    
          // クッキーに保存(7日間有効)
          $.cookie('my_' + type + '_reactions', JSON.stringify(stored), { expires: 7, path: '/' });
    
          // Ajaxでサーバーに送信して情報を更新
          $.post('<?= $ajax_url; ?>', {
            action: 'reactions',
            type: type,
            id: id,
            reaction: reaction,
            prev_reaction: prev,
            reactions_nonce: '<?= $nonce; ?>'
          }, function (res) {
            if (res.success) {
              // カウント数を更新
              $.each(res.data, function (k, v) {
                $con.find('.reactions__button[data-reaction="' + k + '"] .reactions__count').text(v);
              });
              syncAllButtonStates();
            }
          }, 'json');
        });
      });
      <?php
      wp_add_inline_script('jquery-cookie', ob_get_clean());
    });
    
    
    // Ajaxアクションを追加
    add_action('wp_ajax_reactions', 'reactions_callback');
    add_action('wp_ajax_nopriv_reactions', 'reactions_callback');
    
    
    /**
     * リアクションボタンを表示
     *
     * @param int|null $post_id 対象のID
     * @param string $type 対象の種別
     * @return void
     */
    if (!function_exists('render_reactions')):
    function render_reactions($post_id = null, $type = 'post') {
      if (!$post_id) {
        $post_id = get_the_ID();
      }
    
      $reactions = [
        'like'  => ['icon' => '<i class="far fa-grin-beam-sweat"></i>', 'label' => '汗'],
        'smile' => ['icon' => '<i class="far fa-smile"></i>',           'label' => 'スマイル'],
        'wow'   => ['icon' => '<i class="far fa-grin-squint"></i>',     'label' => 'すごい'],
        'heart' => ['icon' => '<i class="far fa-kiss-beam"></i>',       'label' => 'ラブ'],
      ];
    
      $key = 'my_' . $type . '_reactions';
      $stored_raw = ($type === 'comment') ? get_comment_meta($post_id, $key, true) : get_post_meta($post_id, $key, true);
    
      $stored = json_decode((string) $stored_raw, true);
      if (!is_array($stored)) {
        $stored = [];
      }
    
      $output = '<div class="reactions" data-id="' . esc_attr($post_id) . '" data-type="' . esc_attr($type) . '">';
      foreach ($reactions as $rk => $rv) {
        $count  = intval($stored[$rk] ?? 0);
        $output .= '<span class="reactions__button" data-reaction="' . esc_attr($rk) . '" title="' . esc_attr($rv['label']) . '">';
        $output .= '<span class="reactions__icon">' . $rv['icon'] . '</span>';
        $output .= '<span class="reactions__count">' . $count . '</span>';
        $output .= '</span>';
      }
      $output .= '</div>';
      echo $output;
    }
    endif;
  • ラベル
    PHPを追加(投稿本文の下)

    以下のコードをfunctions.phpに追加します。

    add_action('singular_entry_content_after', function () {
      if (is_single()) {
        render_reactions();
      }
    });
  • ラベル
    PHPを追加(コメント)

    以下のコードをfunctions.phpに追加します。

    add_filter('comment_text', function ($text, $comment) {
      if (is_admin()) {
        return $text;
      }
    
      ob_start();
      render_reactions($comment->comment_ID, 'comment');
    
      return $text . ob_get_clean();
    }, 10, 2);
  • ラベル
    CSSを追加

    以下のコードをstyle.cssに追加します。

    .reactions {
      display: flex;
      gap: 10px;
    }
    
    .reactions__button {
      cursor: pointer;
    }
    
    .reactions__icon {
      color: #999;
      margin-right: 3px;
    }
    
    .reactions__button.active .reactions__icon {
      color: #e589a2;
    }

補足

$reactionsは、各リアクションに対応するアイコンとラベルを定義しています。
リアクションのカウントは、カスタムフィールドmy_post_reactions(投稿用)またはmy_comment_reactions(コメント用)に、以下のようなJSON 形式で保存されます。

{"smile":3,"sad":1,"wow":2,"angry":0}

従来の「いいねボタン」機能では、カスタムフィールド post_likeに1種類のリアクション(いいね)のカウント数のみを保存していました。

本コードでは、1つのリアクションだけを使用すれば、既存の「いいねボタン」と同じ仕組みとして利用することも可能です。

さいごに

今回紹介した方法を使えば、「リアクション」機能を追加できます。
プラグインを使わずカスタマイズできるため、自分のサイトに合わせたデザインや機能の調整が可能です。
さらに、従来の「いいねボタン」機能の代わりとしても利用でき、選択したリアクションはカスタムフィールドにJSON形式で保存されるため、柔軟に集計や表示をカスタマイズできます。

タイトルとURLをコピーしました