WPでカテゴリを投稿の新しい順に並べ替える

昨日、Facebookで以下のような質問を見かけました。

Wordpress、というかプログラミングに長けたお優しい方、たすけてー!
・カテゴリごとの「一番古い記事」の情報だけを表示したい
・新しく更新されたカテゴリ順に表示したい
場合、どうすればいいのでしょうか...

この質問に答えます。

1.具体的な出力の例

カテゴリと投稿が、以下のような構成であるとします。(各カテゴリの一番上にある投稿が最も新しく、一番下にある投稿が古い)。
この場合、投稿を「b2 c3 a3」の順に出力したいとのことです。

Aカテゴリ
┣ a1(更新日:2日前)
┣ a2
┗ a3
Bカテゴリ
┣ b1(更新日:今日)
┗ b2
Cカテゴリ
┣ c1(更新日:1日前)
┣ c2
┗ c3

2.考え方

質問の事例は、以下のようにして出力することができます。

2-1.カテゴリの読み込み

まず、WordPressのget_categories関数を使って、投稿があるカテゴリをすべて読み込みます。

2-2.各カテゴリの最も新しい投稿/最も古い投稿を読み込む

次に、WordPressのget_posts関数を使って、各カテゴリの最も新しい投稿と、最も古い投稿を読み込みます。
最も新しい投稿は、カテゴリを並べ替えるために使います。
一方、最も古い投稿は、出力したい情報です。

2-3.カテゴリを並べ替える

次に、カテゴリを、それぞれのカテゴリの最も新しい投稿の順に並べ替えます。
並べ替え方法がやや複雑なので、PHPのusort関数を使って、順序の判断方法を関数で指定するようにします。

2-4.結果を出力する

2-1~2-3で得られた結果をもとに、並べ替え後の各カテゴリの最も古い投稿を出力します。

3.functions.phpに処理を追加

2-1~2-4のうち、2-1~2-3でロジックだけで出力を伴わない部分です。
ロジックの部分をindex.php等のテンプレートに長々と書くと、HTMLとPHPが混在してコードが読みにくくなります。

このように、ロジックのみの部分は、functions.phpに関数として記述し、ロジック(PHP)とHTMLを極力分離することをお勧めします。
今回の例では、以下のコードをfunctions.phpに追加します。

function get_cats_and_sort() {
    // 投稿があるカテゴリをすべて読み込む
    $cats = get_categories();
    // カテゴリの数を得る
    $count = count($cats);
    // カテゴリの数だけ繰り返す
    for ($i = 0; $i < $count; $i++) {
        // 各カテゴリの最も新しい投稿を読み込む
        $where = array('category' => $cats[$i]->term_id, 'orderby' => 'post_date', 'order' => 'desc', 'numberposts' => 1);
        $newest_posts = get_posts($where);
        // カテゴリのオブジェクトに「newest_post」というメンバを追加して、最新の投稿を代入する
        $cats[$i]->newest_post = $newest_posts[0];
        // 各カテゴリの最も古い投稿を読み込む
        $where['order'] = 'asc';
        $oldest_posts = get_posts($where);
        // カテゴリのオブジェクトに「olddest_post」というメンバを追加して、最新の投稿を代入する
        $cats[$i]->oldest_post = $oldest_posts[0];
    }
    // カテゴリを、最新の投稿の日付が新しい順に並べ替える
    usort($cats, 'sort_by_postdate');
    // 結果を返す
    return $cats;
}

function sort_by_postdate($a, $b) {
    // 2つのカテゴリの最新の投稿(newest_postメンバ)の日付の大小関係を返す
    return ($a->newest_post->post_date == $b->newest_post->post_date) ? 0 :
           ($a->newest_post->post_date < $b->newest_post->post_date) ? 1 : -1;
}

各カテゴリの最も古い/新しい投稿を読み込み、カテゴリのオブジェクトのメンバにします(7~18行目)。
そして、usort関数を使って、最も新しい投稿の日付の降順に並べ替えます(20行目)。

usort関数では、並び順を判断するために、sort_by_postdate関数を呼び出すようにします(25~29行目)。
この関数では、各カテゴリの最新の投稿(newestメンバ)の日付(post_dateメンバ)で、カテゴリの大小を判断します。

4.テンプレートの書き換え

次に、index.php等、情報を出力したいテンプレートを書き換え、以下の部分を追加します。

<?php
// カテゴリと各カテゴリの最も古い投稿を読み込む
$cats = get_cats_and_sort();
// カテゴリの数を得る
$count = count($cats);
// カテゴリの数だけ繰り返す
for ($i = 0; $i < $count; $i++) :
?>
    <?php // 最初のカテゴリの前に<ul>タグを出力する ?>
    <?php if ($i == 0) : ?><ul><?php endif; ?>
    <li>
        <?php // カテゴリの最も新しい投稿の情報を出力できるように準備する ?>
        <?php $post = $cats[$i]->newest_post; setup_postdata($post);  ?>
        <?php // カテゴリ名を出力し、カテゴリアーカイブページにリンクする ?>
        <a href="<?php echo get_category_link($cats[$i]->term_id); ?>"><?php echo $cats[$i]->name ?></a>
        <?php // カテゴリの最も新しい投稿の日付を出力する ?>
        (<?php echo get_the_date(); ?>)<br />
        <?php // カテゴリの最も古い投稿の情報を出力できるように準備する ?>
        <?php $post = $cats[$i]->oldest_post; setup_postdata($post); ?>
        <?php // 投稿のタイトルを出力し、投稿のページにリンクして、投稿の日付も出力する ?>
        <a href="<?php the_permalink(); ?>"><?php the_title(); ?></a>(<?php echo get_the_date(); ?>)
    </li>
    <?php // 最後のカテゴリの後に</ul>タグを出力する ?>
    <?php if ($i == $count - 1) : ?></ul><?php endif; ?>
<?php endfor; ?>

手順3で定義したget_cats_and_sort関数を使って、並べ替え後のカテゴリを読み込みます(3行目)。
そして、それらのカテゴリを順に繰り返し(7行目)、各カテゴリの情報と(12~17行目)、カテゴリの最も古い投稿の情報を出力します(18~21行目)。

なお、上のリストでは、カテゴリと投稿の関係が分かるように、カテゴリの情報も出力しています。
しかし、元の質問では、投稿の情報だけを出力すれば良いようです。
その場合、カテゴリの情報を出力する部分(12~17行目)を削除します。

5.MTならどうする?

以前に、Movable Typeで同様の質問を受けて、記事を書いたことがあります。
Movable Typeユーザーの方は、以下の記事をご参照ください。

また、現在開発中のWPConnectorプラグインを使うと、WordPressの情報を、Movable Typeのテンプレートで出力することができます。
今回の質問のようなことも、Movable Typeのテンプレートで書くことができます。
その方法は後日解説します。

なお、WPConnectorプラグインについては、6月29日(土)に開催する「MTCafe Saitama 2013」で紹介します。
ご興味がある方はぜひお越しください。