カスタム投稿タイプとカスタムタクソノミーに関して調べたことをメモ的に。だいぶ散らかっている上に、調査やスキル不足のため内容が間違っている可能性もあり、読まれる方は留意ください。
[追記:2021.10.1]
大幅に内容を修正。
[追記:2021.10.4]
パーマリンク構造からカスタムタクソノミーのスラッグを取り除く方法を追加修正。
前提と実現したいこと
- WordPressでカスタム投稿タイプを設定し専用でカスタムタクソノミーも設定
- カスタム投稿タイプとカスタムタクソノミーは1対1の対応関係(複数のカスタムタクソノミーを設定しない)
上記の前提で以下の実現を模索。
- パーマリンク構造からカスタムタクソノミーのスラッグ($taxonomy)を取り除きたい
- 表示中の記事がカスタム投稿タイプか否かなどを判定したい
- カスタム投稿タイプ名を記述するようなハードコーディングは避けたい
- 表示中の記事のカスタム投稿タイプのスラッグを出力したい
対応策
パーマリンク構造からカスタムタクソノミーのスラッグを取り除きたい
- カスタム投稿タイプ: products
- カスタムタクソノミー: product_cat
- 追加するカテゴリー: item1item1-child
- タクソノミーは階層構造あり: item1/item1-child
上記前提の場合、Custom Post Type Permalinksを使っても通常であれば以下のようになってしまいます。
https://example.com/products/product_cat/item1/item1-child/記事のスラッグ
上記を以下のような構造にしたいと仮定します。
https://example.com/products/item1/item1-child/記事のスラッグ
結論
試した範囲では、2種類の方法である程度は実現できました。ただしいずれも問題が残る状態です。
- カスタムタクソノミーの階層構造に対応できる方法
(※ただし一部ページの扱いが微妙に変わってしまう可能性がある) - カスタムタクソノミーの階層構造に対応できない方法
(※確認できていないが他にも不具合をもつ可能性がある)
カスタムタクソノミーの階層構造に対応できる方法
一部のWPテンプレートタグで妙な判定になりますが、カスタムタクソノミーが階層構造を持っていても対応できる方法です。ただしバッドノウハウの可能性が高いのでお勧めはしません。
- カスタム投稿タイプを設定し、taxonomiesにカスタムタクソノミーのスラッグを追加(自力でアプリでも可)
- カスタムタクソノミーを設定し、任意のカスタム投稿タイプに紐付けし、hierarchicalとrewriteのhierarchicalをtrueに設定
- Custom Post Type Permalinksでカテゴリーベースでパーマリンクを設定
functions.phpやプラグイン用phpファイルなどに上記を設定後、続いて以下を記載。
なお、試作のためおかしな部分があると思われますので、その点は予め了解ください。
function my_custom_post_type_permalinks_set($termlink, $term, $taxonomy)
{
return str_replace('/' . $taxonomy . '/', '/', $termlink);
}
add_filter('term_link', 'my_custom_post_type_permalinks_set', 11, 3);
class CustomWalker extends Walker_Category
{
public function start_el(&$output, $category, $depth = 0, $args = [], $id = 0)
{
$homeUrl = home_url('/');
$ulr = get_term_link($category);
$path = str_replace($homeUrl, '', $ulr);
$output .= rtrim($path, '/') . '<br>';
}
}
function custom_rewrite_rule()
{
$post_slug = 'カスタム投稿タイプのスラッグ';
$tax_slug = 'カスタムタクソノミーのスラッグ';
$terms = get_terms($tax_slug, ['hide_empty' => false]);
$pathArray = [];
if (!empty($terms) && !is_wp_error($terms)) {
$paths = walk_category_tree($terms, 0, [
'use_desc_for_title' => false,
'style' => '',
'walker' => new CustomWalker(),
]);
$pathArray = explode('<br>', $paths);
}
if (!empty($pathArray)) {
$pathArray = array_filter($pathArray, 'strlen');
foreach ($pathArray as $path) {
$array = explode('/', $path);
$cat = end($array);
add_rewrite_rule($post_slug . '/' . $path . '/?$', 'index.php?post_type=' . $post_slug . '&' . $tax_slug . '=' . $cat, 'top');
add_rewrite_rule($post_slug . '/' . $path . '/page/([0-9]+)/?$', 'index.php?' . $tax_slug . '=' . $cat . '&paged=$matches[1]', 'top');
}
}
}
add_action('init', 'custom_rewrite_rule', 0);
補足
2つの関数の役割はそれぞれ以下の通りで、両方が必要です。
- タームのリンク先からカスタムタクソノミーのスラッグを削除する
- リライトルールを設定して、パーマリンク からカスタムタクソノミーのスラッグを取り除く
リライトルールの方の流れは以下の通りです。
- get_terms()とwalk_category_tree()を利用して、親子関係を持った状態でのURLを取得し、add_rewrite_rule()の正規表現に代入可能な値の配列を作成する
- 作成した配列を回してadd_rewrite_rule()を実行し登録する
検索で出てくる例を用いると、add_rewrite_rule()のみでは、階層構造を持つURLで、かつ、URLの末尾が/(スラッシュ)の場合に、カスタム投稿タイプのページまで置換対象になってしまいます。
検索すると以下のような例が出てきますが、この記述はタクソノミーが階層を持つ場合には使えないと思います。
add_rewrite_rule('カスタム投稿タイプのスラッグ/([^/]+)/?$', 'index.php?post_type=カスタム投稿タイプのスラッグ&カスタムタクソノミーのスラッグ=$matches[1]', 'top');
以下の場合は2階層に対応していますが、1階層目に属する投稿や、3階層以上には使えません。
正規表現の([^/]+)/を増やすことで多層化に対応できるかもしれませんが、タクソノミーの最大階層数と記事のスラッグのある階層が一致してしまうとエラーになります。
add_rewrite_rule('カスタム投稿タイプのスラッグ/([^/]+)/([^/]+)/?$', 'index.php?post_type=カスタム投稿タイプのスラッグ&カスタムタクソノミーのスラッグ=$matches[2]', 'top');
正規表現のみで「URLがアーカイブなのか投稿ページなのか」を判別することはできないと思われたので、視点を変えて、必要なパターンを全て列記してadd_rewrite_rule()で登録する方法を検討した次第です。
親子関係を持った状態でカスタムタクソノミーのURLの一部を取得し配列にする方法
かなり無理やりなのですが以下の部分が該当します。
class CustomWalker extends Walker_Category
{
public function start_el(&$output, $category, $depth = 0, $args = [], $id = 0)
{
$homeUrl = home_url('/');
$ulr = get_term_link($category);
$path = str_replace($homeUrl, '', $ulr);
$output .= rtrim($path, '/') . '<br>';
}
}
function custom_rewrite_rule()
{
$post_slug = 'カスタム投稿タイプのスラッグ';
$tax_slug = 'カスタムタクソノミーのスラッグ';
$terms = get_terms($tax_slug, ['hide_empty' => false]);
$pathArray = [];
if (!empty($terms) && !is_wp_error($terms)) {
$paths = walk_category_tree($terms, 0, [
'use_desc_for_title' => false,
'style' => '',
'walker' => new CustomWalker(),
]);
$pathArray = explode('<br>', $paths);
}
// 省略
}
流れは以下の通り。
- get_terms()で指定のカスタムタクソノミーの情報を取得
- walk_category_tree()に入れて、不要なHTMLタグを消す
- さらにwalkerを利用して、カスタム投稿タイプの親子関係を持つURLから不要な部分を削った状態で取得し、brタグを加えた文字列を作成
- 作成した文字列をexplode()で配列に変換する
親子関係を持つ状態で必要なパスを習得できる方法が見当たらず、本来はHTMLとして出力するwalk_category_tree()を無理やり利用している状態です。
もっとスマートな方法があると思うのですが…。
問題点
ここまでの作業である程度の動作は確認できますが、以下の問題が発生します。
- カスタムタクソノミーを追加するたびに、パーマリンクの更新が必要になる
- カスタムタクソノミーのアーカイブで、カスタム投稿タイプのアーカイブを判定するためのis_post_type_archive()がtrueとして判定されるようになる
カスタムタクソノミーのアーカイブで、is_post_type_archive()がtrueになると、分岐の状態によっては意図しない動作をする可能性があります。
状況にもよりますが、is_post_type_archive()でカスタム投稿タイプのアーカイブのみを判定できる必要があります。
試したところ以下の書き方で、is_post_type_archive()を使ったカスタム投稿タイプのアーカイブの判定が可能でした。
if( is_tax() === false && is_post_type_archive() ){
//カスタム投稿タイプのアーカイブ
}
しかしながら上記のように個別に対応できたとしても、下記のような懸念点があるので現時点では案件には使えないと判断しています。
理解が進めば案件に利用可能だと判断できるとは思いますが…。
- is_post_type_archive()がなぜtrueになるのか不明
- is_post_type_archive()がtrueになるのが正常な動作なのか不明
- is_tax() === false && is_post_type_archive()で判定できることが正常なのか不明
- 他にも把握できていない問題が発生している可能性がある
参考サイト
- get_terms()でタクソノミーの親子関係の階層を維持したリストを出力する方法 | WEMO
- WordPress のタクソノミーを親子順に並べる | Pimp My Site
walk_category_tree()について - WordPress で get_terms を使ったターム一覧の表示方法 – by Takumi Hirashima
- WP: get_termsを親子関係にソートする
- walk_category_tree() | Function | WordPress Developer Resources
- Walker::walk() | Method | WordPress Developer Resources
- class-walker-category.php in tags/5.3/src/wp-includes – WordPress Trac
- test/classes.php at 42aef63992d0a309d0476744a983dc6e4d9c84b0 · ankitb458/test
walkerのカスタマイズの例として参考になった - 【php】空文字の要素を配列から取り除きたい at softelメモ
- WordPressで種類が多くて混乱するタクソノミーやタームを表示するときの関数のまとめ | それからデザイン スタッフブログ
- WordPress のURL URI パスを取得・出力する関数/ Web Design Leaves
- 関数リファレンス/is post type archive – WordPress Codex 日本語版
- get_query_var()で値が取得できない?ページ種別ごとに有効な変数キーまとめ! | WEMO
- Rewrite API/add rewrite rule – WordPress Codex 日本語版
カスタムタクソノミーの階層構造に対応できない方法
「カスタムタクソノミーに階層構造を持たせないか、持たせても1階層しか使わない」という制限が許容できるのであれば以下で確認できる情報で実現できました。
- How to add custom taxonomy in custom post type permalink? | WordPress.org
コメント部分も併せて初めて動作する - Permalinks: custom post type -> custom taxonomy -> post – WordPress Development Stack Exchange
1つ目で参照として書かれていたスレッド - How to add Custom Taxonomy in WordPress Custom Post Type (CPT) permalink? • Crunchify
記載はないようだが、上記2ページを参考にしたと思われる内容なので、まとまってはいる - Modifying The Custom Taxonomy Permalink Structure In A Custom Post Type
post_type_linkフィルターの記述に関連する可能性のある記述あり
確認したところポイントは以下の4つかなと思います。なおプラグインは使いません。
- カスタム投稿タイプのhas_archiveにカスタム投稿タイプのスラッグを設定する
例:has_archive: 'product' - カスタム投稿タイプのrewriteのスラッグでカスタムタクソノミーを含む値を設定する
例:'rewrite' => ['slug' => 'product/%product_cat%'] - カスタムタクソノミーのrewriteでカスタム投稿タイプのスラッグを設定
例:'rewrite' => ['slug' => 'product'] - post_type_linkフィルターを用いて、カスタムタクソノミーのスラッグを置換する
問題点
- カスタムタクソノミーで追加したカテゴリーの子カテゴリーのアーカイブにアクセスすると404になる
例:/products/item1/は表示されるが、/products/item1/item1-child/は404 - rewriteのスラッグにproduct/%product_cat%のような設定ができるというドキュメントはない模様で、たまたま動いているだけの可能性があるかもしれない
上記はさらに調査や試作を重ねれば解決できるかもといろいろ試していたのですが、現状私のスキルでは無理でした。
参考サイト
- カスタム投稿タイプ、カスタム分類(タクソノミー)をつくる(そもそもそれって何なのか?も含めて) | WordPressカスタマイズ事例【100ウェブ】
- 【WordPress】コラムに階層構造(3階層)を持たせてパーマリンクを整える – Qiita
- wordpressでカスタム投稿タイプとタクソノミーを作った時のURL設計 | Harder, Better, Faster, Stronger
- カスタムタクソノミーのパーマリンクで絶望的な404から抜け出した方法 – codaMac.jp
- WordPressのカスタム投稿タイプとタクソノミーのパーマリンクをカスタマイズする方法|ブログ|ウェブスタジオTANI
- CPTUIとCustom Post Type Permalinks での設定メモ – 未来的ライフスタイル
- 【SEOを考慮したWordPressサイト構築】3/3 STEP3 カスタム投稿タイプURLの設定方法|BLOG|株式会社エムハンド|M-HAND Inc.
- カスタム投稿タイプの記事のURLにカスタム分類のタームを含める – ナデズダ・ゴボリン🖥
- WordPressでタクソノミー(カスタム分類)を省略したパーマリンクに変更する方法! | それからデザイン スタッフブログ
- 新?パーマリンクからタクソノミーを消す方法 @餅。
- Custom Permalinks for hierarchical taxonomies / Moon Watch
試すとページが表示されなかったり複数の記事コンテンツが表示されたりと不具合が続出したが、リライトルールだけではなく出力される各種リンクの値まで考慮されている点が参考になる。 - wordpress – Custom permalink structure: /%custom-post-type%/%custom-taxonomy%/%post-name%/ – Stack Overflow
slugの設定にて対応するタイプ。階層なしのタクソノミーならいけると思われる。 - Add Custom Taxonomy Tags to Your WordPress Permalinks
query_varにスラッグを設定した場合についての説明と例あり。 - Custom Permalink Structure for Taxonomies Added to Custom Post Types
上記の補足になると思われる。 - WordPress – How to create a permalink structure with custom taxonomies and custom post types like base-name/parent-tax/child-tax/custom-post-type-name | Newbedev
カスタム投稿タイプのパーマリンクにタクソノミーの階層を追加するコードだがエラーになる。これが動けばCustom Post Type Permalinksの重要性が下がるが、それならCustom Post Type Permalinksの中を解析して仕組みを理解した方がよいかも。 - query.php in tags/5.8/src/wp-includes – WordPress Trac
実際に上記の内容を試しますと、いずれも効果が確認できないかエラーや404となって意図した状態にはなりませんでした。
おそらく主な原因は、カスタムタクソノミーに階層構造を持たせ、それがパーマリンクに反映された状態を求めていたからかもしれません。
1つのカスタム投稿タイプに複数のカスタムタクソノミー
1つのカスタム投稿タイプに複数のカスタムタクソノミーを設定したい場合、Custom Post Type Permalinksでは対応できません。
何か一つのタクソノミーを選んで記述するしかなく、選ばなかった方のタクソノミーでエラーが生じます。
これはslugにタクソノミースラッグを記載するパターンの方法でも同様です。
おそらくリライトルールなどを駆使すればできるのではと考えていますが、理解不足だからか実現できませんでした。
表示されているページに関する判定と値の出力
カスタム投稿タイプとカスタムタクソノミーに関する値を取得したり判別したりする部分を検討してみました。
限られた範囲と用途内での動作(つまり動作前に条件を絞っている)を目的としているので、参考にされる方はその点留意ください。
値が取れない時の返り値はまだ検討中ですが、概ね以下のようなコードで目的は達成できそうです。
class MyClass
{
// 以降のコメントにあるように、この変数にはカスタム投稿タイプのスラッグのみが入る想定
public $post_slug = '';
// まずカスタム投稿タイプのスラッグを取得
function __construct()
{
if (is_singular() && get_post_type() !== 'post' && get_post_type() !== 'page' && get_post_type() !== 'attachment') {
// is_singular()が真でpost_typeがpost,page,attachmentでなければ、カスタム投稿のページと判定できる
$this->post_slug = get_post_type();
} elseif (is_tax()) {
// is_tax()はカスタム タクソノミーのアーカイブページの時のみtrueを返す
// 参考: https://wpdocs.osdn.jp/%E9%96%A2%E6%95%B0%E3%83%AA%E3%83%95%E3%82%A1%E3%83%AC%E3%83%B3%E3%82%B9/is_tax
$taxonomy = get_query_var('taxonomy');
$this->post_slug = get_taxonomy($taxonomy)->object_type[0];
} elseif (is_archive()) {
// get_query_var('post_type')で値が取れるのは、表示しているページが「カスタム投稿」「カスタム投稿のアーカイブ」「検索結果のページ」の時のみ
// つまりis_tax()が偽でis_archive()が真であり、get_query_var('post_type')に空文字以外が入っていれば、それは「カスタム投稿タイプのスラッグ」となる
// 参考: https://wemo.tech/2043
$this->post_slug = get_query_var('post_type');
}
}
// カスタム投稿タイプのスラッグをそのまま返す
public function get_custom_post_type_slug()
{
if($this->post_slug !== ''){
return $this->post_slug;
}
return $this->post_slug;
}
// 投稿タイプのスラッグからlabelを取得して返す
public function get_custom_post_type_name()
{
$post_obj = get_post_type_object($this->post_slug);
if($post_obj){
return $post_obj->label;
}
return null;
}
// カスタムタクソノミーのタクソノミー名を取得する
// 参考: https://into-the-program.com/get-taxonomy-name/
public function get_taxonomy_name()
{
$taxonomy_slug = array_keys(get_the_taxonomies());
if ($taxonomy_slug) {
$taxonomy = get_taxonomy($taxonomy_slug[0]);
$taxonomy_name = $taxonomy->name;
return $taxonomy_name;
}
return null;
}
// 表示されているページがカスタム投稿タイプに属しているかどうかを判定する
public function belong_custom_post_type()
{
return (is_singular() && $this->post_slug !== '') || is_tax() || is_post_type_archive();
}
// 表示されているページがカスタム投稿タイプかどうかを判定する
public function is_custom_post_type()
{
return is_singular() && $this->post_slug !== '';
}
}
その他の参考サイト
- 関数リファレンス/register post type – WordPress Codex 日本語版
- 関数リファレンス/register taxonomy – WordPress Codex 日本語版
- Rewrite API/add rewrite rule – WordPress Codex 日本語版
- WordPress パーマリンク リライトルール/ Web Design Leaves
- Custom Post Type Permalinks – WordPress プラグイン | WordPress.org 日本語
- カスタム投稿タイプ&タクソノミー・タグの追加 – Sensitivitiy
- WordPressのカスタム投稿タイプ:作成と使用のオールインワンガイド
- WordPress カスタム投稿タイプ カスタム分類/ Web Design Leaves
- 新エディタ「gutenberg」にカスタムタクソノミーの選択項目を表示する方法 – Offise Kondo
- 関数リファレンス/is tax – WordPress Codex 日本語版
- get_query_var()で値が取得できない?ページ種別ごとに有効な変数キーまとめ! | WEMO
- 【WordPress】カスタム投稿タイプのタクソノミー名を取得する|Into the Program
WordPress Codex 日本語版のページには「今後更新されない」という趣旨の一文が書かれていますので、現状ではあまり参照するべきではありません。
しかしながら、こちらを見よと示されたサイトは非常に使いづらく感じます。
勘違いかもしれませんが、サイト内検索はフォーラム用しかなく、ドキュメント検索目的には使えないように見えました。
結び
カスタム投稿タイプとカスタムタクソノミーの組み合わせは、かなり面倒に思います。
求める条件次第では楽に設定できるものの、そこから一歩外れると難度が跳ね上がるという印象です。
パーマリンクのカスタマイズに関して
パーマリンクからカスタムタクソノミーのスラッグを取り除くことを断念しましたが、おそらく以下あたりが重要では思います。
- カスタム投稿タイプとカスタムタクソノミーの設定
- add_rewrite_rule()などによるリライトルールの書き換え
- post_type_linkやterm_linkリンクなど、テーマが出力する各種リンクの出力内容の書き換え
リライトルールがうまく動いていれば想定しているURLの直打ちで表示されると思いますが、テーマによって出力されたリンク先が想定している状態でなければ、結果として404になってしまいます。
もちろん逆もまた然り。
関連する事柄を全て理解している方であればかなり自由にカスタマイズできるかもしれませんが、そうではないなら、パーマリンクからカスタムタクソノミーのスラッグを削除することは諦めた方が安全ではと思います。
0人がこの記事を評価
役に立ったよという方は上の「記事を評価する」ボタンをクリックしてもらえると嬉しいです。
連投防止のためにCookie使用。SNSへの投稿など他サービスとの連動は一切ありません。