お気に入り一覧ページを追加する方法

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

はじめに

以前の記事では、お気に入り機能を追加する方法を紹介しました。

ウィジェットでは表示には数が限られてしまうという制限があります。
また、ウィジェットエリア自体が狭いため、登録記事が増えるにつれて全体が見にくくなってしまうという課題がありました。

そこで、ウィジェット内での部分的な表示にとどまらず、カテゴリー一覧ページと同じように、/favorites/という専用のURL(アーカイブページ)を用意して、ページネーション(ページ送り)付きで一覧表示します。

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

完成イメージ

実装手順

お気に入り一覧は、ウィジェットエリアのような非同期(Ajax)による部分的な書き換えとは異なり、メインクエリやページネーションの仕組み上、非同期で部分的に更新することができません。

そのため、お気に入りボタンの操作に応じ画面をリロードさせ、サーバー側で最新のクッキーデータを確実に読み込ませることで、記事の追加・削除やページ数の変動が正しく反映された最新の状態を表示します。

「お気に入り」機能(クッキー版)

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

// リライトルールを追加
add_action('init', function () {
  add_rewrite_rule('^favorites/?$', 'index.php?fav_archive=1', 'top');
  add_rewrite_rule('^favorites/page/([0-9]+)/?$', 'index.php?fav_archive=1&paged=$matches[1]', 'top');

  // パーマリンクを更新
  if (get_option('my_fav_archive_rewrite_version') !== '1') {
    flush_rewrite_rules();
    update_option('my_fav_archive_rewrite_version', '1');
  }
});


// クエリ変数の変更
add_filter('query_vars', function ($vars) {
  $vars[] = 'fav_archive';
  return $vars;
});


// メインクエリの変更
add_action('pre_get_posts', function ($q) {
  if (is_admin() || !$q->is_main_query() || !$q->get('fav_archive')) {
    return;
  }

  // クッキーからのデータ取得
  $ids = [];
  if (!empty($_COOKIE['my_fav_page_list'])) {
    $ids = json_decode(stripslashes($_COOKIE['my_fav_page_list']), true);
  }
  $ids = is_array($ids) ? array_values(array_unique(array_map('intval', $ids))) : [];

  // クエリパラメータを設定
  $q->set('post_type', 'post');
  $q->set('post_status', 'publish');
  $q->set('post__in', $ids ? array_reverse($ids) : [0]);
  $q->set('orderby', 'post__in');
  $q->set('ignore_sticky_posts', true);

  // クエリフラグを変更
  $q->is_archive  = true;
  $q->is_home     = false;
  $q->is_page     = false;
  $q->is_singular = false;
  $q->is_404      = false;
});


// テンプレートファイルの置換
add_filter('template_include', function ($template) {
  if (get_query_var('fav_archive')) {
    $archive = locate_template('index.php');
    if ($archive) {
      return $archive;
    }
  }

  return $template;
});


// キャッシュ無効化と存在しないページのリダイレクト
add_action('template_redirect', function () {
  if (!get_query_var('fav_archive')) {
    return;
  }

  // キャッシュを無効化
  nocache_headers();

  global $wp_query;
  $paged    = get_query_var('paged') ? absint(get_query_var('paged')) : 1;
  $max_page = $wp_query->max_num_pages;

  // 最大ページ超過または投稿なしの場合は最終ページへリダイレクト
  if ($paged > 1 && ($paged > $max_page || !have_posts())) {
    $redirect_url = ($max_page > 1) ? home_url('/favorites/page/' . $max_page . '/') : home_url('/favorites/');
    wp_safe_redirect($redirect_url);
    exit;
  }
});


// クッキーが0件の場合でも404にせず正常表示
add_filter('pre_handle_404', function ($preempt, $wp_query) {
  if ($wp_query->get('fav_archive')) {
    status_header(200);

    return true;
  }

  return $preempt;
}, 10, 2);


// お気に入りページでボタンが押されたときリロード
add_action('wp_enqueue_scripts', function () {
  ob_start();
  ?>
  jQuery(function ($) {
    // お気に入り更新イベントを監視
    $(document).on('favListUpdated', function () {
      // お気に入り一覧ページをリロード
      if (/^\/favorites(\/|$)/.test(location.pathname)) {
        location.reload();
      }
    });
  });
  <?php
  wp_add_inline_script('jquery', ob_get_clean(), 'after');
});


// アーカイブタイトルを変更
add_filter('get_archive_chapter_title', function ($title) {
  if (get_query_var('fav_archive')) {
    return '<span class="fas fa-paperclip" aria-hidden="true"></span>お気に入り';
  }

  return $title;
});

「お気に入り」機能(ローカルストレージ版)

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

// リライトルールを追加
add_action('init', function () {
  add_rewrite_rule('^favorites/?$', 'index.php?fav_archive=1', 'top');
  add_rewrite_rule('^favorites/page/([0-9]+)/?$', 'index.php?fav_archive=1&paged=$matches[1]', 'top');

  // パーマリンクを更新
  if (get_option('my_fav_archive_rewrite_version') !== '1') {
    flush_rewrite_rules();
    update_option('my_fav_archive_rewrite_version', '1');
  }
});


// クエリ変数の変更
add_filter('query_vars', function ($vars) {
  $vars[] = 'fav_archive';
  return $vars;
});


// メインクエリの変更
add_action('pre_get_posts', function ($q) {
  if (is_admin() || !$q->is_main_query() || !$q->get('fav_archive')) {
    return;
  }

  // クッキーからのデータ取得
  $ids = [];
  if (!empty($_COOKIE['my_fav_ids'])) {
    $raw = sanitize_text_field(wp_unslash($_COOKIE['my_fav_ids']));
    $parsed_ids = array_map('intval', explode(',', $raw));
    $ids = array_values(array_unique(array_filter($parsed_ids)));
  }

  // クエリパラメータを設定
  $q->set('post_type', 'post');
  $q->set('post_status', 'publish');
  $q->set('post__in', $ids ? array_reverse($ids) : [0]);
  $q->set('orderby', 'post__in');
  $q->set('ignore_sticky_posts', true);

  // クエリフラグを変更
  $q->is_archive  = true;
  $q->is_home     = false;
  $q->is_page     = false;
  $q->is_singular = false;
  $q->is_404      = false;
});


// テンプレートファイルの置換
add_filter('template_include', function ($template) {
  if (get_query_var('fav_archive')) {
    $archive = locate_template('index.php');
    if ($archive) {
      return $archive;
    }
  }

  return $template;
});


// キャッシュ無効化と存在しないページのリダイレクト
add_action('template_redirect', function () {
  if (!get_query_var('fav_archive')) {
    return;
  }

  // キャッシュを無効化
  nocache_headers();

  global $wp_query;
  $paged    = get_query_var('paged') ? absint(get_query_var('paged')) : 1;
  $max_page = $wp_query->max_num_pages;

  // 最大ページ超過または投稿なしの場合は最終ページへリダイレクト
  if ($paged > 1 && ($paged > $max_page || !have_posts())) {
    $redirect_url = ($max_page > 1) ? home_url('/favorites/page/' . $max_page . '/') : home_url('/favorites/');
    wp_safe_redirect($redirect_url);
    exit;
  }
});


// クッキーが0件の場合でも404にせず正常表示
add_filter('pre_handle_404', function ($preempt, $wp_query) {
  if ($wp_query->get('fav_archive')) {
    status_header(200);

    return true;
  }

  return $preempt;
}, 10, 2);


// お気に入りページでボタンが押されたときリロード
add_action('wp_enqueue_scripts', function () {
  ob_start();
  ?>
  jQuery(function ($) {

    /**
     * ローカルストレージとクッキーを同期
     * 
     * @return {boolean} 変更有無の判定
     */
    function syncCookie() {
      // ローカルストレージからのデータ取得を登録
      let ids = [];
      try {
        ids = JSON.parse(localStorage.getItem('my_fav_page_list') || '[]');
      } catch (e) {}

      const value = ids.join(',');

      // クッキーからのデータ取得を登録
      const current = (document.cookie.match(/(?:^|; )my_fav_ids=([^;]*)/) || [])[1] || '';

      // クッキーの設定を登録
      document.cookie = 'my_fav_ids=' + value + ';path=/;max-age=31536000;SameSite=Lax';

      return current !== encodeURIComponent(value).replace(/%2C/g, ',');
    }

    // クッキー同期の実行と変更状態の取得を登録
    const changed = syncCookie();

    // お気に入りアーカイブページかつ未リロードの場合の判定を登録
    if (changed && /^\/favorites(\/|$)/.test(location.pathname) && !sessionStorage.getItem('fav_reloaded')) {
      // セッションストレージの設定を登録
      sessionStorage.setItem('fav_reloaded', '1');

      location.reload();
      return;
    }

    // セッションストレージを削除
    sessionStorage.removeItem('fav_reloaded');

    // お気に入り更新イベントを監視
    $(document).on('favListUpdated', function () {
      syncCookie();
      if (/^\/favorites(\/|$)/.test(location.pathname)) {
        location.reload();
      }
    });
  });
  <?php
  wp_add_inline_script('jquery', ob_get_clean(), 'after');
});


// アーカイブタイトルを変更
add_filter('get_archive_chapter_title', function ($title) {
  if (get_query_var('fav_archive')) {
    return '<span class="fas fa-paperclip" aria-hidden="true"></span>お気に入り';
  }

  return $title;
});

さいごに

今回紹介した方法を使えば、ウィジェット内における表示制限やスペースの狭さを気にすることなく、お気に入り記事をカテゴリーページと同じような専用のアーカイブページ(/favorites/)として美しく一覧表示できるようになります。
サイトの利便性が大幅に向上するため、ぜひご自身の環境に合わせて導入してみてください。

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