wp_get_archives()でカテゴリ別アーカイブを取得する方法

2014年、wordpressのバージョンも3.8.1を数える今、なお「カテゴリ別のアーカイブ一覧を取得する方法」においては公式の関数を持たず、最適解も与えられぬままと聞きます。今回は、案件で使用するために作られたので、ある程度使用範囲は限定されるという前提ではありますが、実装してみましたので参考までに共有いたしたく思う次第です。  
以下は全てfunctions.phpにでも書いてください。

こちらのエントリを参考にしたよ！
http://wpxtreme.jp/%E3%82%AB%E3%83%86%E3%82%B4%E3%83%AA%E3%83%BC%E6%AF%8E%E3%81%AE%E6%97%A5%E4%BB%98%E5%88%A5%E3%82%A2%E3%83%BC%E3%82%AB%E3%82%A4%E3%83%96%E3%82%92%E8%A1%A8%E7%A4%BA%E3%81%99%E3%82%8B

## wp_get_archives()のフックを探して、タクソノミのテーブルをjoinする

通常、```wp_get_archives()```は、全エントリを対象にしているので、タクソノミのテーブルは見に行っていません。  
幸い、JOIN句にfilterが当たっていますので、書き換えてやります。ここの処理は上記エントリのまま。

```php
function cat_archives_join ($join, $r)
{
  global $wpdb;
  return
    " LEFT JOIN $wpdb->term_relationships as r ON $wpdb->posts.ID = r.object_ID
      LEFT JOIN $wpdb->term_taxonomy as t ON r.term_taxonomy_id = t.term_taxonomy_id
      LEFT JOIN $wpdb->terms as terms ON t.term_id = terms.term_id";
}
```

## 同じく、where文も書き換える

今回は、該当のカテゴリアーカイブと、記事詳細ページで使用するつもりで書いています。  
```$cat_id```は、WHERE IN句の中で使用するので、カンマ区切りのterm_idが入る想定です。  
```$cat```にはカテゴリオブジェクトが単数あるいは複数入ります。それをimplodeしてやって、$cat_idに代入してやるんですね。

※term_idを取得する前に、該当カテゴリ及び記事ページの主カテゴリを取得したいので、予め```get_primary_category()```という名前で関数化してあります。この関数は、該当のカテゴリアーカイブ及び記事の持つ一番最初のカテゴリオブジェクトを返します。

```php
function cat_archives_where ($where, $r)
{
  
  $cat_id = null;
  
  if (is_single())
  {
    $cat = get_primary_category();
    $cat_id = $cat->term_id;
  }
  
  if (is_category())
  {
    
    $cat_ids[] = get_query_var('cat');
    
    $child_cats = get_categories(array(
      'parent' => $cat_ids[0],
    ));
    
    if ($child_cats)
    {
      foreach ($child_cats as $child_cat) $cat_ids[] = $child_cat->term_id;
    }
    
    $cat_id = implode(',', $cat_ids);
    
  }
  
  return $cat_id ? $where . " AND t.taxonomy = 'category' AND terms.term_id IN (" . $cat_id .")" : $where;
  
}
```

get_primary_category()。  
単に、一番最初に出てくるカテゴリを取得するだけ。  
（条件分岐タグをここでも使用するのが若干うっとおしいので、何か策を講じたい）

```php
function get_primary_category()
{
  
  $cat = null;
  
  if (is_single())
  {
    $cats = get_the_category();
    
    $cat = $cats[0];
  }
  
  if (is_category())
  {
    $cat = get_category(get_query_var('cat'));
  }
  
  return $cat;
  
}
```

## URLを変更する

このままだと、```/date/{YYYY}/{MM}```って形のURLを吐いているので、```/{CATEGORY}?date={YYYY}/{MM}```という形に変換してやります。  
僕の場合、```wp-no-category-base```っていうプラグインを使っているので、リンク先が```/{CATEGORY}```から始まっていますが、ここは適宜```/category/{CATEGORY}?date={YYYY}/{MM}```などに直してやってください。  
```get_parent_cat_slug()```というのは、親のカテゴリを取得してslugの頭に付けていく再帰関数です。  
$_GETで取得するのはパーマリンク弄りたくないので、勘弁ね。

```php
function cat_archives_link($url)
{
  
  $slug = null;
  
  if ($cat = get_primary_category()) $slug = get_parent_cat_slug($cat->slug, $cat);
  
  return $slug ? preg_replace("/\/date\//", "/{$slug}?date=", $url) : $url;
  
}
```

```php
function get_parent_cat_slug($slug, $cat = null)
{
  
  if (!$cat || $cat->parent == 0) return $slug;
  
  $parent = get_category($cat->parent);
  
  return get_parent_cat_slug($parent->slug.'/'.$slug, $parent);
  
}
```

## pre_get_posts()を使って、日付フィルタを有効にします。

query_posts()を使ってもいいんですけど、functions.phpをコントローラーみたいにしてやりたくて（実際やってることはヘルパー以下なんですけど）、こんな関数を書いています。

```if ($q->is_category() || $q->is_single())```では、上記関数を```wp_get_archives()```関連のフックのコールバックに設定しています。なので、記事ページには当てたくないとかってばあいは、```$q->is_single()```を外せばいい。

その下の```if ($q->is_category())```は、カテゴリアーカイブページ内で、日付フィルタを有効にしています。
```?date={YYYY}/{MM}/{DD}```の書式でクエリを送ってやれば、きちんとフィルタリングされるはず。

```php
function controller($q)
{
  
  if (is_admin() || !$q->is_main_query()) return null;
  
  if ($q->is_category() || $q->is_single())
  {
    
    add_filter('getarchives_join', 'cat_archives_join', 10, 2);
    add_filter('getarchives_where', 'cat_archives_where', 10, 2);
    add_filter('month_link', 'cat_archives_link', 10, 2);
    
  }
  
  if ($q->is_category())
  {
    
    if ($_GET['date'])
    {
      
      $date = explode('/', $_GET['date']);
      
      if ($date[0]) $q->set('year', intval($date[0]));
      if ($date[1]) $q->set('monthnum', intval($date[1]));
      if ($date[2]) $q->set('day', intval($date[2]));
      
      return;
      
    }
    
  }

}

add_action('pre_get_posts', 'controller');
```

以上になります。
問題あったら、すぐ、オレに言え。