「アーカイブ」ウィジェットでカテゴリーごとの投稿を表示する方法

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

はじめに

「アーカイブ」ウィジェットは、通常その年月に投稿された全ての記事を一覧表示します。
しかし、標準機能では特定のカテゴリーに絞って表示することはできません。

そこで、「アーカイブ」ウィジェットにカテゴリー選択機能を追加し、特定のカテゴリーに限定して投稿を表示します。

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

完成イメージ

実装手順

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

// ウィジェットを追加
add_action('widgets_init', function () {
  unregister_widget('WP_Widget_Archives');
  register_widget('WP_Widget_Archives_By_Category');
});


/**
 * ウィジェットクラスを定義
 */
class WP_Widget_Archives_By_Category extends WP_Widget_Archives {

  /**
   * ウィジェットを表示
   *
   * @param array $args ウィジェット引数
   * @param array $instance 保存された値
   * @return void
   */
  public function widget($args, $instance) {
    // タイトル取得
    $title = apply_filters(
      'widget_title',
      empty($instance['title']) ? __('Archives') : $instance['title'],
      $instance,
      $this->id_base
    );

    // 選択されたカテゴリーと表示設定を取得
    $cat_id    = !empty($instance['cat']) ? (int) $instance['cat'] : 0;
    $year_only = !empty($instance['year_only']);

    // 投稿数とドロップダウンの表示設定を取得
    $show_post_count = !empty($instance['count']);
    $use_dropdown    = !empty($instance['dropdown']);

    // カテゴリー未選択で年表示も無効の場合は親クラスの処理を実行
    if ($cat_id <= 0 && !$year_only) {
      parent::widget($args, $instance);
      return;
    }

    // 投稿取得用のクエリパラメータを設定
    $query_args = [
      'posts_per_page' => -1,
      'post_status'    => 'publish',
      'fields'         => 'ids',
      'no_found_rows'  => true,
    ];

    if ($cat_id > 0) {
      $query_args['cat'] = $cat_id;
    }

    // 条件に該当する投稿を取得
    $query = new WP_Query($query_args);

    // 投稿がない場合は処理対象外
    if (!$query->have_posts()) {
      echo $args['before_widget'];
      if ($title) {
        echo $args['before_title'] . $title . $args['after_title'];
      }
      echo '<p>該当する投稿はありません。</p>';
      echo $args['after_widget'];
      return;
    }

    // 年または年月ごとに投稿数を集計
    $months = [];
    foreach ($query->posts as $post_id) {
      $ym = get_the_date($year_only ? 'Y' : 'Y-m', $post_id);
      if (!isset($months[$ym])) {
        $months[$ym] = 0;
      }
      $months[$ym]++;
    }
    krsort($months);

    // 現在のアーカイブ年月を取得
    $current_year  = get_query_var('year');
    $current_month = get_query_var('monthnum');

    // ウィジェットを出力
    echo $args['before_widget'];
    if ($title) {
      echo $args['before_title'] . $title . $args['after_title'];
    }

    // 設定に応じてドロップダウンまたはリストを出力
    if ($use_dropdown) {
      $this->render_dropdown($months, $cat_id, $show_post_count, $title, $current_year, $current_month, $year_only);
    } else {
      $this->render_list($months, $cat_id, $show_post_count, $current_year, $current_month, $year_only);
    }

    echo $args['after_widget'];
  }


  /**
   * ドロップダウン表示を出力
   *
   * @param array $months
   * @param int $cat_id
   * @param bool $show_post_count
   * @param string $title
   * @param int $current_year
   * @param int $current_month
   * @param bool $year_only
   * @return void
   */
  private function render_dropdown($months, $cat_id, $show_post_count, $title, $current_year, $current_month, $year_only) {
    ob_start();
    ?>
    <label class="screen-reader-text"><?= esc_html($title); ?></label>
    <select name="archive-dropdown" onchange="document.location.href=this.options[this.selectedIndex].value;">
      <option value=""><?= esc_html__('Select Month'); ?></option>
      <?php foreach ($months as $ym => $count) :
        if ($year_only) {
          $year  = $ym;
          $month = 0;
          $url   = get_year_link($year);
          $label = $year . '年';
        } else {
          list($year, $month) = explode('-', $ym);
          $month = (int) $month;
          $url   = get_month_link($year, $month);
          $label = $year . '年' . $month . '月';
        }

        // カテゴリーが指定されている場合のみクエリパラメータを付与
        if ($cat_id > 0) {
          $url = add_query_arg('cat', $cat_id, $url);
        }

        if ($show_post_count) {
          $label .= ' &nbsp;(' . (int) $count . ')';
        }

        $selected_attr = ($year == $current_year && (!$year_only && $month == $current_month)) ? ' selected' : '';
        ?>
        <option value="<?= esc_url($url); ?>" <?= $selected_attr; ?>><?= esc_html($label); ?></option>
      <?php endforeach; ?>
    </select>
    <?php
    echo ob_get_clean();
  }


  /**
   * リスト表示を出力
   *
   * @param array $months
   * @param int $cat_id
   * @param bool $show_post_count
   * @param int $current_year
   * @param int $current_month
   * @param bool $year_only
   * @return void
   */
  private function render_list($months, $cat_id, $show_post_count, $current_year, $current_month, $year_only) {
    ob_start();
    ?>
    <ul>
      <?php foreach ($months as $ym => $count) :
        if ($year_only) {
          $year  = $ym;
          $url   = get_year_link($year);
          $label = $year . '年';
        } else {
          list($year, $month) = explode('-', $ym);
          $month = (int) $month;
          $url   = get_month_link($year, $month);
          $label = $year . '年' . $month . '月';
        }

        // カテゴリーが指定されている場合のみクエリパラメータを付与
        if ($cat_id > 0) {
          $url = add_query_arg('cat', $cat_id, $url);
        }

        ?>
        <li>
          <a href="<?= esc_url($url); ?>">
            <span class="list-item-caption"><?= esc_html($label); ?></span>
            <?php if ($show_post_count) : ?>
              <span class="post-count"><?= (int) $count; ?></span>
            <?php endif; ?>
          </a>
        </li>
      <?php endforeach; ?>
    </ul>
    <?php
    echo ob_get_clean();
  }


  /**
   * ウィジェットの設定フォーム
   *
   * @param array $instance 保存された値
   * @return void
   */
  public function form($instance) {
    $cat_id    = !empty($instance['cat']) ? (int) $instance['cat'] : 0;
    $year_only = !empty($instance['year_only']);

    // 親クラスのフォームを出力
    parent::form($instance);

    // カテゴリ取得(非表示も含む)
    $categories = get_categories([
      'hide_empty' => false,
      'orderby'    => 'name',
      'order'      => 'ASC',
    ]);
    $cat_tree = $this->build_category_tree($categories);

    ob_start();
    ?>
    <p>
      <label for="<?= esc_attr($this->get_field_id('cat')); ?>">カテゴリー:</label><br>
      <select class="widefat" id="<?= esc_attr($this->get_field_id('cat')); ?>" name="<?= esc_attr($this->get_field_name('cat')); ?>">
        <option value="0">全て</option>
        <?php $this->print_category_options($cat_tree, $cat_id); ?>
      </select>
    </p>
    <p>
      <input class="checkbox" type="checkbox" <?php checked($year_only); ?> id="<?= esc_attr($this->get_field_id('year_only')); ?>" name="<?= esc_attr($this->get_field_name('year_only')); ?>" />
      <label for="<?= esc_attr($this->get_field_id('year_only')); ?>">年で表示</label>
    </p>
    <?php
    echo ob_get_clean();
  }


  /**
   * ウィジェットの設定を保存
   *
   * @param array $new_instance 新しい値
   * @param array $old_instance 古い値
   * @return array 保存する値
   */
  public function update($new_instance, $old_instance) {
    $instance              = parent::update($new_instance, $old_instance);
    $instance['cat']       = !empty($new_instance['cat']) ? (int) $new_instance['cat'] : 0;
    $instance['year_only'] = !empty($new_instance['year_only']);

    return $instance;
  }


  /**
   * カテゴリーの階層構造を構築
   *
   * @param array $categories カテゴリーオブジェクトの配列
   * @return array 階層化されたカテゴリー配列
   */
  private function build_category_tree($categories) {
    $tree = [];
    $refs = [];

    foreach ($categories as $cat) {
      $cat->children       = [];
      $refs[$cat->term_id] = $cat;
    }

    foreach ($refs as $cat_id => $cat) {
      if ($cat->parent && isset($refs[$cat->parent])) {
        $refs[$cat->parent]->children[$cat_id] = $cat;
      } else {
        $tree[$cat_id] = $cat;
      }
    }

    return $tree;
  }


  /**
   * 階層構造のカテゴリーから<option>要素を再帰的に出力
   *
   * @param array $categories 階層化されたカテゴリー配列
   * @param int $selected 選択中のカテゴリーID
   * @param int $depth 階層の深さ
   * @return void
   */
  private function print_category_options($categories, $selected = 0, $depth = 0) {
    foreach ($categories as $cat) {
      $indent = str_repeat('&nbsp;&nbsp;&nbsp;', $depth);
      ob_start();
      ?>
      <option value="<?= esc_attr($cat->term_id); ?>" <?= selected($selected, $cat->term_id, false); ?>>
        <?= $indent . esc_html($cat->name); ?>
      </option>
      <?php
      echo ob_get_clean();

      if (!empty($cat->children)) {
        $this->print_category_options($cat->children, $selected, $depth + 1);
      }
    }
  }

}

解説

WordPressの動作仕様で、例えば、2025年の投稿一覧のURLは以下の形式になります。

https://example.com/2025/

このURLに?cat=1を追加すると、特定のカテゴリー(ID=1)の投稿に絞り込まれた一覧が表示されます。

https://example.com/2025/?cat=1

拡張:アーカイブタイトルに年月を追加する方法

デフォルトではアーカイブタイトルにカテゴリー名のみが表示されます。
そこで、選択した年月をタイトルに追加し、より詳細な情報を表示できるように変更します。

完成イメージ

実装手順

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

add_filter('get_archive_chapter_title', function ($chapter_title) {
  $cat = get_query_var('cat');
  if ($cat) {

    // 年月を取得
    $year  = get_query_var('year');
    $month = get_query_var('monthnum');

    if ($year) {
      if ($month) {
        // 月を2桁に統一
        $month = str_pad($month, 2, '0', STR_PAD_LEFT);
        $chapter_title .= " ({$year}-{$month})";
      } else {
        $chapter_title .= " ({$year})";
      }
    }
  }

  return $chapter_title;
});

さいごに

今回紹介した方法を使えば、「アーカイブ」ウィジェットにカテゴリー選択機能を追加し、特定のカテゴリーに絞った投稿一覧を簡単に表示できます。
これにより、読者にとってより目的に沿った情報提供が可能となり、サイトの利便性と使いやすさを向上させることができます。

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