wordpressでカスタム投稿タイプとタクソノミーを作った時のURL設計

Qiita WordPress Advent Calendar 2017の記事です。
公開がすごく遅くなって申し訳ない。

wordpressってイマイチURL設計に自由度がないのよね〜などと言われた時に「そんなことないもん」って言い返したいための、カスタム投稿タイプとカスタムタクソノミーを混ぜたURL設計のやり方です。

実際、カスタム投稿タイプやカスタムタクソノミーなどを作成した時に「あれ、このパーマリンクどうやって実現するんだっけ」ってなることがおおいにあることでしょう。
たとえば
カスタム投稿タイプ:animals
カスタム投稿タイプanimalのカスタムタクソノミー:animal-families
を作成したとしてお話を進めていきますよ。

カスタム投稿タイプとタクソノミーを作る

functions.phpに以下のように記載し、カスタム投稿タイプとカスタムタクソノミーを作成します。

register_post_type(
    'animals',
    array(
        'label' => 'どうぶつ',
        'description' => '',
        'public' => true,
        'hierarchical' => false,
        'rewrite' => true,
        'query_var' => true,
        'has_archive' => true,
        'menu_position' => 5,
        'supports' => array(
            'title',
            'editor',
            'custom-fields'
        ),
        'labels' => array(
            'name' => 'どうぶつ',
            'all_items' =>'項目一覧',
            'add_new' => '新しいどうぶつ',
            'add_new_item' => '新しいどうぶつを追加',
            'edit' => '編集',
            'edit_item' => 'どうぶつを編集',
            'new_item' => '新しいどうぶつ',
            'view' => '表示',
            'view_item' => 'どうぶつを表示',
            'search_items' => 'どうぶつを検索',
            'not_found' => '見つかりませんでした。',
            'not_found_in_trash' => 'ゴミ箱内に見つかりませんでした。'
        )
    )
);

register_taxonomy(
    'animal_families', //タクソノミー名
    'animals', //投稿タイプ
    array(
        'hierarchical' => true,
        'label' => 'どうぶつの種類',
        'query_var' => true,
        'rewrite' => array('slug' => 'animals/family'),
        'labels' => array(
            'name' => 'どうぶつの種類',
            'singular_name' => 'どうぶつの種類',
            'search_items' => 'どうぶつの種類を検索',
            'all_items' => 'すべてのどうぶつの種類',
            'edit_item' => 'どうぶつの種類を編集',
            'update_item' => 'どうぶつの種類を更新',
            'add_new_item' => 'どうぶつの種類を追加',
            'new_item_name' => '新しいどうぶつの種類'
        )
    )
);

animal-familiesは以下のようになっているとしよう。

  • felidae(ネコ科)
    • panthera(ヒョウ属)
    • felis(ネコ属)
  • canidae(イヌ科)
    • vulpes(キツネ属)
    • nyctereutes(タヌキ属)

(亜目とかのことはわすれてください)

期待するURL

カスタム投稿タイプanimalの記事は
/animals/%postname%
カスタム投稿タイプanimalsに紐づくカスタムタクソノミーanimal-familiesのアーカイブは
/animals/family/%term%
(なので、taxonomyのrewriteではslugにanimals/familyと記載)

このままではアクセスできない!

しかしこのままではanimal-familiesのアーカイブにアクセスはできないのです。
/animals/以下はカスタム投稿タイプの記事を探されてしまうから。

add_rewrite_ruleを追記して解決しよう

ここでfunctions.phpに以下の通り追記します。

function myUrlRewrite(){
    add_rewrite_rule('animals/family/([^/]+)/?$', 'index.php?animal_families=$matches[1]', 'top');
    }
    add_action( 'init', 'myUrlRewrite' );

add_rewrite_ruleを追記した後のお約束

add_rewrite_ruleを追記した後はこちらの関数を呼ぶ必要があります。
やり方としては2通り。
1. functions.php内でflush_rulesを実行する
2. 「設定」の「パーマリンク設定」で何も変更せずに「保存」をクリックする(内部でflush_rulesが呼ばれている)

1番は

global $wp_rewrite;
$wp_rewrite->flush_rules(true);

みたいな使い方をします。しかし、flush_rulesは結構負担が大きい処理なので、前出の例ではinitという毎回呼ばれているfunctionに記載するのはやだなーということで今回は2番で書き換えしてます。
えっ、じゃあそもそもadd_rewrite_ruleってinitに書く必要あるの?いいえ、ないです。
本来はこんな感じで書くといいですね。前出は検証ってことでinitに書いちゃったっていうだけです。

function myUrlRewrite($rules){
    $myRule = array();
    $myRule['animals/family/([^/]+)/?$'] = 'index.php?animal_families=$matches[1]';
    return array_merge( $myRule, $rules );
}
add_action('rewrite_rules_array', 'myUrlRewrite' );

flush後、アクセスして確認。

なんということでしょう。期待した通りのURLでアクセスできるではありませんか。

/animals/family/%term%

/animals/%postname%

カスタムタクソノミーをURLに入れ込みたい

この例の場合、キツネ属のアカギツネだからURLも
/animals/family/vulpes(%term%)/red_fox(%postname%)/
みたいな感じにしたいな〜という要望もあるかもしれない。

これらは、まあ実現できないこともないのです。
まずはrewrite_ruleをちょこっと追記します。

function myUrlRewrite($rules){
    $myRule = array();
    $myRule['animals/family/([^/]+)/?$'] = 'index.php?animal_families=$matches[1]';
    $myRule['animals/family/([^/]+)/([^/]+)?$'] = 'index.php?animals=$matches[2]';
    return array_merge( $myRule, $rules );
}
add_action('rewrite_rules_array', 'myUrlRewrite' );

これで/animals/family/vulpes/red_foxにアクセスされた際のルーティングはOK

お次にリンク生成部分の処理に手を加えるべく、post_type_linkとpost_linkに以下の処理をadd_filterします。

function myPostTypeLink($link, $post ) {
    if ( $post->post_type == 'animals' ) {
        if ( $cats = get_the_terms( $post->ID, 'animal_families' ) ) {
            $link = str_replace( '%animal_families%', current( $cats )->slug, $link );
        }
    }
    return $link;
}
add_filter('post_type_link', 'myPostTypeLink', 10, 2 );
add_filter('post_link', 'myPostTypeLink', 10, 2 );

これで/animals/family/vulpes/のアーカイブページにアクセスした際、ちゃんと期待したURLがリンクとして生成されています。
/animals/family/vulpes/red_fox/にアクセスすると

/animals/family/%term%/%postname%/

このように実現します。

もし複数のtermが紐づいていたら・・・とか考えると、あんまり実装するべきではないのかなー、と思いますが。