Web事業部実績紹介_Webマーケティング
Web事業部実績紹介_Webマーケティング
2018.12.21

【2018年WordPress魔改造TOP3】第2位:カスタム投稿アーカイブページのメインクエリに分岐点を作り、前後で順序のルールを変える

リョウタ

TOP3を別記事として出してしまうあたりに、今後のキーワードヒットを狙いたい思惑が丸見えですね。

……どうも。バックエンドエンジニアのリョウタです。年末の予定といえばライブとスタジオくらいのものなので、GutenbergをやっつけるべくReactの独習に励もうと思っています。

さて、本題に入りましょう。前回からスタートした「2018年のWordPress魔改造TOP3」ランキング。

第2位は「内容的には地味なんだけど、中身を伝えようとすると長くなる」このカスタマイズにしました。

第2位:カスタム投稿アーカイブページのメインクエリに分岐点を作り、前後で順序のルールを変える

何のことやら要約では伝わりにくですが、「メインクエリに分岐点を作り、前後でorderbyのルールと、orderの順序を変えたい。」ということになります。

仕様・要件

  • 対象はカスタムフィールドにdateとtimeを持つ、イベント系投稿タイプのアーカイブページ(運用中の追加機能実装でしたので、dateとtimeを統合するのは諦めました……)
  • その投稿タイプにはカスタムタクソノミーもあり、タクソノミーアーカイブページも対象
  • イベントの開催日時に至っていない(未開催)分を先頭に、昇順(現在の日時から未来に向かって)に並べる
  • 未開催のイベントの後は、開催済みのイベントを降順(現在の日時から過去に向かって)に並べる
  • アーカイブページは1ページあたりの表示件数(posts_per_page)があり、ページャーを有する

アプローチ・実践

まず、クエリひとつで要件を満たすことはできないであろうと考えました(もし方法があるようでしたら、コソッと教えて下さい)。そこで、ページャーなどもあるので、メインクエリに集約してしまうことにしました。

当該テーマでは、テンプレートが投稿タイプアーカイブもタクソノミーアーカイブも、他の投稿タイプのアーカイブも共通したarchive.phpを使用しているため、メインクエリにまとめられればテンプレートを新たに作る必要もありません。

以上のことから、

  1. 「ソートがより単純」、「数が多い」を基準として、主体となるクエリは開催後のものを採択する
  2. 開催前の投稿は、先に昇順でIDを取得する関数を用意する
  3. orderbyを変更するフックで1と2をガッチャンコする

というアプローチで解決することにしました。

function custom_pre_get_posts($query)
{
  //管理画面のメインクエリーとメインクエリーじゃないときは処理しない
  if (is_admin() || !$query->is_main_query()) {
    return;
  }

  if (is_post_type_archive('event') || is_tax('event-category')) {
    //開催後の開催日時に対して降順のメインクエリを作る
    $query->set('meta_query', array(
      'event_date' => array(
        'key' => 'date',
        'type' => 'DATE',
      ),
      'event_time' => array(
        'key' => 'time',
        'type' => 'TIME',
      ),
    ));
    $query->set('orderby', array(
      'event_date' => 'DESC', //まずは日付順
      'event_time' => 'DESC', //続いて時間順
      'post_date' => 'DESC', //日時が同じ場合は投稿日時順
    ));
  }
}

add_action('pre_get_posts', 'custom_pre_get_posts');

//未開催のイベントを昇順に取得。
function get_recents_event()
{
  global $wpdb;

  //ログイン時は非公開のpost_statusも含む
  $logged_in = (is_user_logged_in()) ? " OR " . $wpdb->posts . ".post_status = 'private'" : "";

  $posts = $wpdb->posts; //postsのテーブル
  $postmeta = $wpdb->postmeta; //postmetaのテーブル

  //未開催のイベントを昇順に取得するSQL文。ORDER BYは日付>時間>投稿日時の順。(実際のクエリをコピーしてきただけだけども・・・。
  $sql = "
SELECT " . $posts . ".ID
FROM " . $posts . " 
INNER JOIN " . $postmeta . "
ON ( " . $posts . ".ID = " . $postmeta . ".post_id ) 
INNER JOIN " . $postmeta . " AS mt1
ON ( " . $posts . ".ID = mt1.post_id ) 
INNER JOIN " . $postmeta . " AS mt2
ON ( " . $posts . ".ID = mt2.post_id ) 
INNER JOIN " . $postmeta . " AS mt3
ON ( " . $posts . ".ID = mt3.post_id ) 
INNER JOIN " . $postmeta . " AS mt4
ON ( " . $posts . ".ID = mt4.post_id )
WHERE 1=1 
AND ( " . $postmeta . ".meta_key = 'date' 
AND mt1.meta_key = 'time' 
AND ( ( mt2.meta_key = 'date'
AND CAST(mt2.meta_value AS DATE) > '" . date_i18n('Y/m/d') . "' ) 
OR ( ( mt3.meta_key = 'date'
AND CAST(mt3.meta_value AS DATE) = '" . date_i18n('Y/m/d') . "' ) 
AND ( mt4.meta_key = 'time'
AND CAST(mt4.meta_value AS TIME) > '" . date_i18n('H:i:s') . "' ) ) ) )
AND " . $posts . ".post_type = 'event'
AND (" . $posts . ".post_status = 'publish'" . $logged_in . ")
GROUP BY " . $posts . ".ID
ORDER BY CAST(" . $postmeta . ".meta_value AS DATE) DESC, CAST(mt1.meta_value AS TIME) DESC, " . $posts . ".post_date DESC
";

  //未開催のイベントを取得
  $recents = $wpdb->get_results($sql);

  $recent_ids = array();

  //投稿IDを配列に入れる
  if (!empty($recents)) foreach ($recents as $k => $v) $recent_ids[] = $v->ID;

  return $recent_ids;
}

//orderbyを変更する
function custom_orderby($orderby)
{
  if (!is_admin() && is_main_query() && (is_post_type_archive('event') || is_tax('event-category'))) {
    global $wpdb;
    //未開催イベントの投稿ID取得
    $recents = get_recents_event();

    //orderbyに未開催イベントのIDを先頭に結合させる
    $orderby = (!empty($recents)) ? 'FIELD( ' . $wpdb->posts . '.ID, ' . implode(', ', $recents) . ' ) DESC , ' . $orderby : $orderby;
  }
  return $orderby;
}

add_filter('posts_orderby', 'custom_orderby');

「魔」ポイントを解説します

ポイント①

‘pre_get_posts’で投稿タイプ’event’とカスタムタクソノミー’event-category’のクエリを編集します。

メインとなるクエリのルールは「イベント開催日時の降順」です。

ポイント②

‘posts_orderby’でメインクエリのORDER句を編集します。

get_recents_event()で、未開催のイベントの投稿IDを「イベント開催日時の昇順」に取得したものを$recentsに代入します。

未開催の記事があった場合、FIELD ( ‘投稿データテーブル,{投稿IDを半角カンマ区切り}’)を、既存のORDERBY(開催日時の降順)の前に結合させ、明示的に未開催の投稿を結果の先頭に持ってきます。

あとがき

いかがでしょうか? メインクエリに分岐点を持たせることは滅多にないかと思いますが、いつかどこかで誰かのお役にたてたらいいなと思い記事にまとめました。

次はいよいよ第一位。限定的なテーマ過ぎて誰の役にもたてない気しかしません……お楽しみに。

LIGにWordPressの魔改造を依頼する!