WordPressでホームやアーカイブ毎に表示条件を変える(is_main_query と pre_get_posts フック)

ここ最近のWordPress日本語フォーラムでの投稿で

といったものが多いです。
まとめると「ホームや特定のアーカイブページにおいて、表示条件(件数や並び順など)を変えたい」という内容です。

毎回、大曲さん(@jim0912)のブログ「3.3の新しい関数 is_main_query を使おう」を紹介しつつ、具体例を回答していたのですが、あまりにも似たような投稿(質問)が多いので、少し、具体例を書いておきます。

is_main_query と pre_get_posts フックを使おう

「ホームや特定のアーカイブページにおいて、表示条件(件数や並び順など)を変えたい」というの、WordPressの処理的に言い換えると「メインクエリーを特定の条件であらかじめ抽出し直す」です。

今まで多くのブログで紹介されてきたのがテンプレートファイルに query_posts を使って条件を再定義する方法でした。
もちろんこの方法でも可能ですが、WordPress 3.3から登場した is_main_query 関数と pre_get_posts フックを使う事で、テンプレートファイルにアクセスする前にメインクエリを改変することが可能です。つまり、二重にクエリの呼び出しを行わなくて済むというメリットがあります。
また、query_posts を使う方法だと、変更したいテンプレートファイルすべてに条件変更の記述をしなければなりませんが、is_main_query と pre_get_posts フックを使う方法は functions.php 一箇所に書けば大丈夫というメリットがあります。

なお、本家Codexの query_posts のページでも冒頭で

query_posts() is the easiest, but not preferred or most efficient, way to alter the default query that WordPress uses to display posts. Use query_posts() to display different posts than those that would normally show up at a specific URL. (The preferred way is hooking into ‘pre_get_posts’ and altering the main query that way using is_main_query)

最後の()内で「pre_get_posts フックとis_main_queryを使って書き換える事を推奨します(意訳)」と言っています。

基本的な書き方

基本的な書き方はCodexの is_main_query で紹介されています。

add_action( 'pre_get_posts', 'foo_modify_query_exclude_category' ); // pre_get_postsにフック
// フック時に使う関数
function foo_modify_query_exclude_category( $query ) {
    if ( ! is_admin() && $query->is_main_query() && ! $query->get( 'cat' ) )
        $query->set( 'cat', '-5' );
}

これを functions.phpに記述すると、月別やタグアーカイブ、ホームページ(フロントページ)を表示する際、メインクエリーは特定のカテゴリー(例ではカテゴリーID5)を除外してを取得します。

なお、3.2以前でも可能な方法は先に紹介した大曲さんのブログをご覧ください。

「どんなとき」という条件を変える

日本語で書くとややこしい見出しですが、「ホーム(フロントページ)」「月別アーカイブ」「カテゴリーアーカイブ」といった条件を変更したいときは、先のコードの4行目の if 文の条件を書き換えます。

if ( ! is_admin() && $query->is_main_query() && $query->is_home() ) // 管理画面以外 かつ メインクエリー かつ ホームの時
if ( ! is_admin() && $query->is_main_query() && $query->is_month() ) // 管理画面以外 かつ メインクエリー かつ 月別アーカイブの時
if ( ! is_admin() && $query->is_main_query() && $query->is_category( '9' ) ) // 管理画面以外 かつ メインクエリー かつ カテゴリーID 9のアーカイブページ

お気づきかも知れませんが、$query-> のあとに条件分岐タグを加えるだけです。

クエリーの抽出条件を変える

クエリーの抽出条件は先のコードの5行目(4行目から始まる if 文の { } 内)を書き換えます。

$query->set( 'posts_per_page', '5'); // 5件ずつ表示
$query->set( 'cat', '-5'); // カテゴリーID5を除く

ここで $query->set( 'パラメーター名', '内容'); の形で抽出条件を指定していきます。パラメーターは query_posts のパラメーターと同じです。また、複数ある場合は  $query->set( 'パラメーター名', '内容'); の行を増やしていけばOKです。

ちょっと複雑な条件の例

  • ホームページでは通常の投稿の他に、カスタム投稿タイプ(news)の投稿も含める
  • 月別アーカイブではその月の投稿をすべて含める
  • カテゴリーアーカイブでは10件ずつ表示する(管理画面の表示件数は20件)

https://gist.github.com/gatespace/3688912

2013.8.27 追記
Hissy が「WordPressでページ送りが動かないのはどう考えてもquery_postsが悪い!【pre_get_posts、WordPressループまとめ】」というまとめ記事を書かれてました。こちらにたくさんサンプルが載っていますし、かつ予期せぬエラーが起きにくいコードになってましたので、私のサンプルコードも訂正します。

このように条件分岐タグとパラメーターを組み合わせていけば、functions.php のみでも完結します(query_postsの場合はテンプレートファイル毎に記述が必要)。

例えば、通常ではフィード(RSS)に含まれないカスタム投稿を含めることも、上記コードを改変すれば可能です。

すごく便利ですので、ぜひお試しください。

今回の参照先