WordPress で若干複雑なパーマリンクに変えようとしたらとても大変だったのでその記録です。
ちなみにカスタム投稿タイプとカスタム分類の登録には「Custom Post Type UI」プラグインを使用しています。
概要
今回やりたかったのは次のようなURLの変更です。
カスタム投稿アーカイブ -> /news/ カスタム分類アーカイブ -> /news/カスタム分類ターム名/ カスタム投稿記事ページ -> /news/カスタム分類ターム名/投稿名/
http://example.com/news/店舗a/リニューアルオープン!/ みたいなURLにしたいわけです。
とりあえず作成
とりあえずカスタム投稿タイプを「news」、カスタム分類を「newsstore」として作成するとデフォルトでは以下のようなURLになります。
カスタム投稿アーカイブ -> /news/ カスタム分類アーカイブ -> /newsstore/カスタム分類ターム名/ カスタム投稿記事ページ -> /news/投稿名/
カスタム投稿のアーカイブページはこのままでよいので、カスタム分類のアーカイブページとカスタム投稿記事ページのURLを変更します。
まずはカスタム分類アーカイブページを変える
カスタム分類のアーカイブページのスラッグは rewrite_slug オプションで変更することができます。「Custom Post Type UI」プラグインでは以下の「カスタムリライトスラッグ」から変更が可能です。ここをまず「news」に変更しましょう。
ただこれだけだと投稿タイプ「news」の記事ページと被ってしまいうまく動作しないためリライトルールにルールを追加します。
add_action( 'init', function() {
global $wp_rewrite;
add_rewrite_rule( 'news/([^/]+)(/page/([0-9]+))?/?', 'index.php?newsstore=$matches[1]&paged=$matches[3]', 'top');
$wp_rewrite->flush_rules( false );
} );
このルールによって正規表現 news/([^/]+)(/page/([0-9]+))?/? に該当するURLを index.php?newsstore=$matches[1]&paged=$matches[3] にリライトします。第3引数の 'top' で他のルールより先にチェックされるようになります。
これでカスタム分類のアーカイブページのURLが /news/カスタム分類名/ になりました。
カスタム投稿記事ページも変える
/news/カスタム分類名/ にはアクセスできるようになりましたがそれぞれの記事へのリンクを踏んでも404に飛ばされます。先ほど追加したnews/([^/]+)(/page/([0-9]+))?/? に該当してしまっているからですね。
記事ページ用のリライトルールを追加したいところですが、まずさきにリンクのURLを変更しましょう。post_type_link フィルタを使用することで記事ページへのリンクURLを変更できます。
add_filter( 'post_type_link', function( $permalink, $post, $leavename ) {
if ( $post->post_type == 'news' ) {
$term = wp_get_post_terms( $post->ID, 'newsstore' )[0]->slug;
return "/news/" . $term . "/" . $post->post_name . "/";
}
}, 10, 4 );
投稿タイプが news の時は /news/ターム名/投稿名/ となるようにしています。
リンクURLが変わったところでリライトルールを追加します。上で追加したリライトルールの上部に書き加えます。
add_action( 'init', function() {
global $wp_rewrite;
add_rewrite_rule( 'news/([^/]+)/([^/]+)/?$', 'index.php?news=$matches[2]', 'top' );
add_rewrite_rule( 'news/([^/]+)(/page/([0-9]+))?/?', 'index.php?newsstore=$matches[1]&paged=$matches[3]', 'top');
$wp_rewrite->flush_rules( false );
} );
news/([^/]+)/([^/]+)/?$ に一致するURLを index.php?news=$matches[2] にリライトします。
これで記事ページにもアクセスできるようになりました。
カスタム分類が違う場合はリダイレクト
ただ今のままでは1つ問題があります。
例えばカスタム分類のタームが「店舗a」で投稿名が「リニューアルオープン!」の場合、以下のURLでアクセスできますが、
http://example.com/news/店舗a/リニューアルオープン!/
以下のURLでもアクセスできてしまいます。
http://example.com/news/あああああああ/リニューアルオープン!/
これはリライトルールではカスタム分類の判別まで行えないためです。
これを防ぐためにリダイレクト処理を加えます。WordPressにはカノニカルURLという機能がありますが、それに似た機能を実装します。
add_action( 'template_redirect', function() {
global $post;
if ( ! is_singular( "news" ) ) return;
$term = get_the_terms( $post->ID, "newsstore" )[0];
$term_slug = $term->slug;
$post_slug = $post->post_name;
$request_url = is_ssl() ? 'https://' : 'http://';
$request_url .= $_SERVER['HTTP_HOST'];
$request_url .= $_SERVER['REQUEST_URI'];
$redirect_url = is_ssl() ? 'https://' : 'http://';
$redirect_url .= $_SERVER['HTTP_HOST'];
$redirect_url .= "/news/{$term_slug}/{$post_slug}/";
if ( strcasecmp( $request_url, $redirect_url )) {
wp_redirect( $redirect_url, 301 );
die();
}
} );
カスタム分類が異なる場合は正しいタームに変更したURLにリダイレクトしています。
これで目的達成です。おめでとう!!
おわり
動作が確認できたら以下の行は削除しましょう。
$wp_rewrite->flush_rules( false );
一度でも実行されればデータベースにリライトルールが書き込まれるためこの行は必要なくなります。むしろ負荷が大きいため削除した方が良いでしょう。

はじめまして。
現在自作テーマを作っている最中で、まさに自分がやりたいことがこちらに書かれていました。
あと少しで実装出来そうですが、現在の状況が下記のようになっています。
(〇は正常に表示されるもの。×は404になってしまうもの。)
カスタム投稿アーカイブページ 〇
カスタム分類アーカイブページ 〇
記事(カスタム分類に登録しているもの) 〇
記事(カスタム分類に未登録のもの) ×
カスタム分類に登録していない未分類の記事のページのみ表示されないのですが、どのようにしたらいいのでしょうか。
ご教授頂けると幸いです。
気づくのが遅れました、すみません。
すでに解決済みであれば無視してもらって構いません。
カスタム分類に未登録のもの、というのはカスタム分類のタームを一つも選択してない記事、という認識でいいでしょうか?
その場合であれば、どのようなURLにしたいかによって設定方法が変わってくるかと思います
(/news/uncategorized/投稿名 あるいは /news/投稿名 など)
記事を書いたのが結構前なのでかなり忘れかけてますが…
返答いただければ多少はお力になれるかと思います!