年別・年月別アーカイブを階層構造でカードで表示する方法

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

はじめに

ブログを運営していると、過去の記事を年別・年月別に整理して、サイドバーや固定ページに見やすく表示したい場面があります。

通常、Cocoonの navi_list ショートコードを使ってアーカイブ一覧をナビカード形式で表示するには、外観→メニューから年別・月別のリンクを手動で登録する必要があります。
しかし、年や月が増えるたびにメニューを更新する手間が発生してしまいます。

そこで、Cocoonの navi_list の仕組みを活用し、投稿が存在する年や年月を自動取得して、ナビカード形式で表示する仕組みを追加します。

年別アーカイブは各年を独立したカードとして表示し、年月別アーカイブは年を親カード、月を子カードとして階層構造で表示します。

また、最新投稿のアイキャッチ画像をカードに使用し、現在表示しているアーカイブを強調表示できるようにします。投稿が増えても自動で反映されるため、アーカイブのカードを手動で管理する必要はありません。

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

完成イメージ

使用方法

以下にショートコードを示します。

  • 年別アーカイブ
[navi_list source="year"]
  • 年月別アーカイブ
[navi_list source="month"]

実装方法

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

  • ラベル
  • ラベル
    PHPを追加(年別アーカイブ一覧)

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

    add_filter('pre_do_shortcode_tag', function($output, $tag, $attr, $m) {
      if ($tag !== 'navi_list') {
        return $output;
      }
    
      // source=year以外は処理対象外
      if (!is_array($attr) || ($attr['source'] ?? '') !== 'year') {
        return $output;
      }
    
      global $wpdb;
    
      // 投稿が存在する年を新しい順で取得
      $years = $wpdb->get_col($wpdb->prepare(
        "SELECT DISTINCT YEAR(post_date)
        FROM {$wpdb->posts}
        WHERE post_type = %s
        AND post_status = %s
        AND post_date <= %s
        ORDER BY YEAR(post_date) DESC",
        'post',
        'publish',
        current_time('mysql')
      ));
    
      // 年別アーカイブが存在しない場合は終了
      if (empty($years)) {
        return '';
      }
    
      // カードHTMLを初期化
      $cards = '';
    
      // 年ごとにカードを生成
      foreach ($years as $year) {
        // その年の最新投稿を取得
        $posts = get_posts([
          'post_type'           => 'post',
          'post_status'         => 'publish',
          'posts_per_page'      => 1,
          'orderby'             => 'date',
          'order'               => 'DESC',
          'date_query'          => [
            [
              'year' => (int) $year,
            ],
          ],
          'ignore_sticky_posts' => true,
        ]);
    
        // 投稿がない場合は処理対象外
        if (empty($posts)) {
          continue;
        }
    
        $post = $posts[0];
    
        // 年別アーカイブを擬似メニュー項目形式に変換
        $menu = (object) [
          'object'      => 'post',
          'object_id'   => $post->ID,
          'url'         => get_year_link((int) $year),
          'title'       => $year . '年',
          'description' => '',
          'classes'     => [],
          'target'      => '',
          'attr_title'  => get_the_post_thumbnail_url($post->ID, 'full') ?: '',
        ];
    
        // カード表示用の属性を設定
        $card_atts = [
          'prefix'           => WIDGET_NAVI_ENTRY_CARD_PREFIX,
          'url'              => $menu->url,
          'title'            => escape_shortcodes($menu->title),
          'snippet'          => '',
          'image_attributes' => get_navi_card_image_attributes($menu, 0),
          'ribbon_no'        => null,
          'type'             => 0,
          'classes'          => [],
          'object'           => 'post',
          'object_id'        => $post->ID,
          'horizontal'       => 0,
          'target'           => '',
        ];
    
        // Cocoon標準のナビカードを取得
        $card = get_widget_entry_card_link_tag($card_atts);
    
        // 階層情報とカレント情報をカードリンクに追加
        $card = str_replace(
          '<a ',
          '<a data-menu-id="' . esc_attr($year) . '" data-menu-parent="0" data-menu-current="' . (is_year() && (int) get_query_var('year') === (int) $year ? '1' : '0') . '" ',
          $card
        );
    
        // カードを追加
        $cards .= $card;
      }
    
      // カードがない場合は終了
      if ($cards === '') {
        return '';
      }
    
      // Cocoon標準のナビカード外枠を設定
      $wrap_atts = [
        'tag'        => $cards,
        'type'       => 0,
        'bold'       => 1,
        'arrow'      => 0,
        'class'      => '',
        'horizontal' => 0,
      ];
    
      // Cocoon標準のナビカード外枠を取得
      $html = get_navi_card_wrap_tag($wrap_atts);
    
      // 階層用クラスを追加
      return str_replace(
        'navi-entry-cards',
        'navi-entry-cards my-navi-hierarchy',
        $html
      );
    }, 10, 4);
  • ラベル
    PHPを追加(年月アーカイブ一覧)

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

    add_filter('pre_do_shortcode_tag', function($output, $tag, $attr, $m) {
      if ($tag !== 'navi_list') {
        return $output;
      }
    
      // source=month以外は処理対象外
      if (!is_array($attr) || ($attr['source'] ?? '') !== 'month') {
        return $output;
      }
    
      global $wpdb;
    
      // 投稿が存在する年月を取得
      $archives = $wpdb->get_results($wpdb->prepare(
        "SELECT YEAR(post_date) AS year, MONTH(post_date) AS month
        FROM {$wpdb->posts}
        WHERE post_type = %s
        AND post_status = %s
        AND post_date <= %s
        GROUP BY YEAR(post_date), MONTH(post_date)
        ORDER BY year DESC, month DESC",
        'post',
        'publish',
        current_time('mysql')
      ));
    
      // アーカイブが存在しない場合は終了
      if (empty($archives)) {
        return '';
      }
    
      // 年ごとの月を整理
      $months_by_year = [];
    
      foreach ($archives as $archive) {
        $months_by_year[$archive->year][] = $archive->month;
      }
    
      // カードHTMLを初期化
      $cards = '';
    
      // 年ごとに親カードを生成
      foreach ($months_by_year as $year => $months) {
        // その年の最新投稿を取得
        $year_posts = get_posts([
          'post_type'           => 'post',
          'post_status'         => 'publish',
          'posts_per_page'      => 1,
          'orderby'             => 'date',
          'order'               => 'DESC',
          'date_query'          => [
            [
              'year' => (int) $year,
            ],
          ],
          'ignore_sticky_posts' => true,
        ]);
    
        // 投稿がない場合は処理対象外
        if (empty($year_posts)) {
          continue;
        }
    
        $year_post = $year_posts[0];
    
        // 年別アーカイブを擬似メニュー項目形式に変換
        $year_menu = (object) [
          'object'      => 'post',
          'object_id'   => $year_post->ID,
          'url'         => get_year_link((int) $year),
          'title'       => $year . '年',
          'description' => '',
          'classes'     => [],
          'target'      => '',
          'attr_title'  => get_the_post_thumbnail_url($year_post->ID, 'full') ?: '',
        ];
    
        // 年別カードの表示用属性を設定
        $year_card_atts = [
          'prefix'           => WIDGET_NAVI_ENTRY_CARD_PREFIX,
          'url'              => $year_menu->url,
          'title'            => escape_shortcodes($year_menu->title),
          'snippet'          => '',
          'image_attributes' => get_navi_card_image_attributes($year_menu, 0),
          'ribbon_no'        => null,
          'type'             => 0,
          'classes'          => [],
          'object'           => 'post',
          'object_id'        => $year_post->ID,
          'horizontal'       => 0,
          'target'           => '',
        ];
    
        // Cocoon標準のナビカードを取得
        $year_card = get_widget_entry_card_link_tag($year_card_atts);
    
        // 階層情報とカレント情報をカードリンクに追加
        $year_card = str_replace(
          '<a ',
          '<a data-menu-id="' . esc_attr($year) . '" data-menu-parent="0" data-menu-current="' . (is_year() && (int) get_query_var('year') === (int) $year ? '1' : '0') . '" ',
          $year_card
        );
    
        // 年別カードを追加
        $cards .= $year_card;
    
        // 月ごとに子カードを生成
        foreach ($months as $month) {
          // その月の最新投稿を取得
          $month_posts = get_posts([
            'post_type'           => 'post',
            'post_status'         => 'publish',
            'posts_per_page'      => 1,
            'orderby'             => 'date',
            'order'               => 'DESC',
            'date_query'          => [
              [
                'year'  => (int) $year,
                'month' => (int) $month,
              ],
            ],
            'ignore_sticky_posts' => true,
          ]);
    
          // 投稿がない場合は処理対象外
          if (empty($month_posts)) {
            continue;
          }
    
          $month_post = $month_posts[0];
    
          // 月別アーカイブを擬似メニュー項目形式に変換
          $month_menu = (object) [
            'object'      => 'post',
            'object_id'   => $month_post->ID,
            'url'         => get_month_link((int) $year, (int) $month),
            'title'       => $year . '年' . sprintf('%02d', $month) . '月',
            'description' => '',
            'classes'     => [],
            'target'      => '',
            'attr_title'  => get_the_post_thumbnail_url($month_post->ID, 'full') ?: '',
          ];
    
          // 月別カードの表示用属性を設定
          $month_card_atts = [
            'prefix'           => WIDGET_NAVI_ENTRY_CARD_PREFIX,
            'url'              => $month_menu->url,
            'title'            => escape_shortcodes($month_menu->title),
            'snippet'          => '',
            'image_attributes' => get_navi_card_image_attributes($month_menu, 0),
            'ribbon_no'        => null,
            'type'             => 0,
            'classes'          => [],
            'object'           => 'post',
            'object_id'        => $month_post->ID,
            'horizontal'       => 0,
            'target'           => '',
          ];
    
          // Cocoon標準のナビカードを取得
          $month_card = get_widget_entry_card_link_tag($month_card_atts);
    
          // 階層情報とカレント情報をカードリンクに追加
          $month_card = str_replace(
            '<a ',
            '<a data-menu-id="' . esc_attr($year . '-' . $month) . '" data-menu-parent="' . esc_attr($year) . '" data-menu-current="' . (is_month() && (int) get_query_var('year') === (int) $year && (int) get_query_var('monthnum') === (int) $month ? '1' : '0') . '" ',
            $month_card
          );
    
          // 月別カードを追加
          $cards .= $month_card;
        }
      }
    
      // カードがない場合は終了
      if ($cards === '') {
        return '';
      }
    
      // Cocoon標準のナビカード外枠を設定
      $wrap_atts = [
        'tag'        => $cards,
        'type'       => 0,
        'bold'       => 1,
        'arrow'      => 0,
        'class'      => '',
        'horizontal' => 0,
      ];
    
      // Cocoon標準のナビカード外枠を取得
      $html = get_navi_card_wrap_tag($wrap_atts);
    
      // 階層用クラスを追加
      return str_replace(
        'navi-entry-cards',
        'navi-entry-cards my-navi-hierarchy',
        $html
      );
    }, 10, 4);

さいごに

今回紹介した方法を使えば、投稿が存在する年や年月を自動取得し、Cocoonのナビカード形式で階層表示できます。
年別アーカイブは各年を独立したカードとして表示し、年月別アーカイブは年を親カード、月を子カードとして表示できます。また、現在表示しているアーカイブを強調表示することも可能です。
カードには各年・月の最新投稿のアイキャッチ画像を使用でき、投稿が追加されても自動的に反映されます。
既存の navi_list ショートコードでは実現できない年別・年月別アーカイブの階層表示を、ナビカード形式で実現できます。

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