広告

WordPressで年度別一覧を作ったらハマった

この記事は約13分で読めます。

WordPressで年別なら、下記の方法でアーカイブリスト(一覧)が作れます。

でも年度別はできません

<?php
$args = array(
  'type'            => 'yearly',
  'limit'           => '',
  'format'          => 'html',
  'before'          => '',
  'after'           => '',
  'show_post_count' => false,
  'echo'            => 1,
  'order'           => 'DESC',
  'post_type'       => 'news',
);
wp_get_archives( $args );

また、カスタム投稿の複数指定はできません

wp_get_archivespost_typeの配列を受け入れないので。

色々とテストしたけど、どれもダメ。。。

結果、カスタム投稿を辞めて、投稿+カテゴリにしました。

結論:固定ページを使う

通常アーカイブで使うテンプレート archive.php date.php などではダメ。

4月切り替えの年度で、2020年4月~12月までの記事がなく、2021年1月~3月までの記事のみある場合、404エラーになるから。

まず、本文なしの固定ページ(スラッグnews)を作って、固定ページ用のテンプレートを作ります。

page.php やら index.php ですね。

そこに、下記をぶち込みます。

// 表示中の年を入れる.
$years = null;

// URLのgetから呼び出す年度をget.
if ( isset( $_GET['ye'] ) ) {
  $years = intval( $_GET['ye'] );
} else {
  // 今日の年を整数にして入れる.
  $years       = intval( date_i18n( 'Y' ) );
  // 今日の月を整数にして入れる.
  $today_month = intval( date_i18n( 'm' ) );
  // 3月までは前年に戻す
  if ( 4 > $today_month ) {
    $years = ( $years - 1);
  }
}

echo '<div>' . esc_html( $years ) . '年度</div>';

// 翌年を定義.
$next_y = ( $years + 1 );

$args = array(
  'post_type'      => 'post',
  // 公開されたものだけ表示.
  'post_status'    => array( 'publish' ),
  // 1ページに含める投稿数(-1を指定すると全投稿を表示)
  'posts_per_page' => -1,
  // 昇順'ASC'、降順'DESC'.
  'order'          => 'DESC',
  // 投稿順に表示.
  'orderby'        => 'date',
  'date_query'     => array(
    array(
      'after'     => array(
        // その年の4/1から.
        'year'  => $years,
        'month' => 4,
        'day'   => 1,
      ),
      'before'    => array(
        // 翌年の3/31まで.
        'year'  => $next_y,
        'month' => 3,
        'day'   => 31,
      ),
      'inclusive' => true,
      'compare'   => 'BETWEEN',
    ),
  ),
);
// クエリ定義.
$the_query = new WP_Query( $args );
// ループ.
if ( $the_query->have_posts() ) {
  while ( $the_query->have_posts() ) {
    $the_query->the_post();
    // ループ内記述 この例では別ファイルを用意して読み込み.
    include __DIR__ . '/card/card-news.php';
  }
} else {
  echo '<p>お知らせはありません。</p>';
}
// ループ終わり.
// 投稿データのリセット.
wp_reset_postdata();

// ここから年度別一覧.
<ul>
<?php
$archives = get_archives_by_fiscal_year();
foreach ( $archives as $archive ) {
  // https://aw1.jp/wordps/news/?ye=表示年 というようなリンクをつくります
  ?>
<li><a href="<?php echo esc_url( get_home_url( null, '/news' ) ) . '/?ye=' . esc_html( $archive->year ); ?>"><img src="<?php echo esc_url( get_home_url( null, '/img/menu_o.svg' ) ); ?>"><?php echo esc_html( $archive->year ); ?>年度</a></li>
  <?php
}
?>

</ul>

注意!

WordPressのGETパラメータに引っかかるので、リンクを~~/?year=2021 のようにすると、エラーになります。

なので~~/?ye=2021にしました。

function.php に入れ込むもの

上のコードにある

get_archives_by_fiscal_year

これの定義をfunction.phpでします。

/**
 * 年度別アーカイブリスト
 *
 *  @param string $args .
 */
function get_archives_by_fiscal_year( $args = '' ) {
  global $wpdb, $wp_locale;
  $defaults = array (
    'post_type'       => 'post',
    'limit'           => '',
    'format'          => 'html',
    'before'          => '',
    'after'           => '',
    'show_post_count' => false,
    'echo'            => 1,
  );
  $rrr      = wp_parse_args( $args, $defaults );
  extract ( $rrr, EXTR_SKIP );
  if ( '' !== $limit ) {
      $limit = absint( $limit );
      $limit = ' LIMIT ' . $limit;
  }
  $arcresults = (array) $wpdb->get_results(
    "SELECT YEAR(ADDDATE(post_date, INTERVAL -3 MONTH)) AS `year`, COUNT(ID) AS `posts`
    FROM $wpdb->posts
    WHERE post_type = '$post_type' AND post_status = 'publish'
    GROUP BY YEAR(ADDDATE(post_date, INTERVAL -3 MONTH))
    ORDER BY post_date DESC
    $limit"
  );
  return $arcresults;
}

これはhttps://www.webopixel.net/wordpress/871.htmlをそのまま使わせていただきました。

ありがとうございます。

2026年3月版

基本的には、上の考え方 「URLのパラメータ(?ye=)を使ってWordPressの標準バグを回避する」という一番重要なコアのアイデアは全く同じですが、改善したコードを記載します。

WordPress標準のフック pre_get_posts を使う

pre_get_postsとは、メインクエリがデータベースに問い合わせる直前にフック(割り込み)して、クエリの条件を書き換えるアクションフックです。

/**
 * URLのパラメータに独自の「ye」を使えるように登録
 */
add_filter(
	'query_vars',
	function ( $vars ) {
		$vars[] = 'ye';
		return $vars;
	}
);

/**
 * カスタムパーマリンクルール
 *
 * @return void
 */
function add_pastnews_archive_rewrite_rules() {
	// URLが /pastnews/2025/ の時、裏側では ye=2025 として処理する
	add_rewrite_rule(
		'^pastnews/([0-9]{4})/?$',
		'index.php?post_type=post&ye=$matches[1]',
		'top'
	);
	// 2ページ目以降用
	add_rewrite_rule(
		'^pastnews/([0-9]{4})/page/([0-9]+)/?$',
		'index.php?post_type=post&ye=$matches[1]&paged=$matches[2]',
		'top'
	);
}
add_action( 'init', 'add_pastnews_archive_rewrite_rules' );

/**
 * データベースから投稿がある年度(4月〜翌3月)を取得し、
 * 年度別アーカイブのリンク一覧(HTML)を生成します。
 * 結果はキャッシュされ、データベースへの負荷を軽減します。
 *
 * @return string 年度別リンクのHTMLリスト(<ul>...</ul>形式)
 */
function get_fiscal_year_archives_links() {
	// 1. キャッシュ(一時保存データ)が存在するか確認
	$html = get_transient( 'fiscal_year_archives_html' );

	// 2. キャッシュがない場合のみ、データベースにアクセスして作成する
	if ( false === $html ) {
		global $wpdb;

		// データベースから取得(キャッシュ警告などは解決済みのため無視)
		// WordPressの標準関数では対応できない独自の計算のため、直接アクセスを許可するコメント
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$years = $wpdb->get_col(
			$wpdb->prepare(
				"SELECT DISTINCT IF( MONTH(post_date) >= 4, YEAR(post_date), YEAR(post_date) - 1 ) AS fiscal_year FROM {$wpdb->posts} WHERE post_type = %s AND post_status = %s ORDER BY fiscal_year DESC",
				'post',
				'publish'
			)
		);

		$html = '<ul>';
		if ( ! empty( $years ) ) {
			foreach ( $years as $year ) {
				$link  = home_url( '/pastnews/' . $year . '/' );
				$html .= '<li><a href="' . esc_url( $link ) . '">' . esc_html( $year ) . '年度</a></li>';
			}
		}
		$html .= '</ul>';

		// 作成したHTMLをキャッシュとして保存(例:1日間 = DAY_IN_SECONDS)
		set_transient( 'fiscal_year_archives_html', $html, DAY_IN_SECONDS );
	}

	return $html;
}

/**
 * 記事が投稿・更新・削除された時に、年度別アーカイブの古いキャッシュを削除します。
 *
 * @param int $post_id 投稿ID
 * @return void
 */
function clear_fiscal_year_archives_cache( $post_id ) {
	// 自動保存などの場合は処理をスキップして負荷を減らす
	if ( wp_is_post_revision( $post_id ) || wp_is_post_autosave( $post_id ) ) {
		return;
	}
	// キャッシュを削除
	delete_transient( 'fiscal_year_archives_html' );
}
// 記事が保存(更新)されたときと、削除されたときにキャッシュクリアの関数を動かす
add_action( 'save_post', 'clear_fiscal_year_archives_cache' );
add_action( 'deleted_post', 'clear_fiscal_year_archives_cache' );

/**
 * メインクエリをフックして、年別アーカイブの対象期間を
 * 「1月1日〜12月31日」から「4月1日〜翌年3月31日」に変更します。
 *
 * @param WP_Query $query WordPressのクエリオブジェクト(参照渡し)
 * @return void 値は返さず、$query オブジェクトを直接操作します
 */
function modify_year_archive_to_fiscal( $query ) {
	// 管理画面やメインループ以外には影響させない
	if ( is_admin() || ! $query->is_main_query() ) {
		return;
	}

	// yeパラメータがセットされている時(=年度アーカイブが表示された時)だけ実行
	if ( $query->get( 'ye' ) ) {
		$year = intval( $query->get( 'ye' ) );

		$date_query = array(
			array(
				'after'     => array(
					'year'  => $year,
					'month' => 4,
					'day'   => 1,
				),
				'before'    => array(
					'year'  => $year + 1,
					'month' => 3,
					'day'   => 31,
				),
				'inclusive' => true,
			),
		);
		$query->set( 'date_query', $date_query );

		// WordPressに「これは正しい年別アーカイブページだ」と強制的に認識させる
		$query->is_archive = true;
		$query->is_year    = true;
		$query->is_home    = false;
	}
}
add_action( 'pre_get_posts', 'modify_year_archive_to_fiscal' );


/**
 * アーカイブページのタイトル出力(the_archive_titleなど)をフックし、
 * 年別アーカイブの場合に「〇〇年度のニュース」という表記に変更します。
 *
 * @param string $title 現在のアーカイブタイトル文字列
 * @return string 変更後のアーカイブタイトル文字列
 */
add_filter(
	'get_the_archive_title',
	function ( $title ) {
		$ye = get_query_var( 'ye' );
		if ( $ye ) {
			$title = $ye . '年度のニュース';
		}
		return $title;
	}
);

archive.php のサイドバーなど、年度別リンクを表示させたい場所に入れる

<section class="archive-year">
    <h3>年度別アーカイブ</h3>
    <?php echo wp_kses_post( get_fiscal_year_archives_links() ); ?>
</section>

コピペは自由ですが、動作確認をしっかりするようにしてください。