Hibiiroでは、お知らせや映画レビューをカスタム投稿で管理していました。
種類ごとにURLを分けられるのは便利だったのですが、投稿先が分かれることで管理画面のメニューが増え、カテゴリーなどの設定も別々に扱う構成になっていました。
カスタム投稿でも共通のタクソノミーを使う設定はできます。ただ、自分の運用では、投稿画面を分けておく必要性があまりありませんでした。
そこで、記事は通常投稿にまとめ、専用URLが必要なものだけを投稿メタで区別する構成に変更しました。
この記事では、特に気になっていた「通常投稿に移しても、以前のURLを維持できるのか」という部分を説明します。
投稿の保存形式とURLを分けて考える
変更後は、お知らせも映画レビューも、WordPress内部では通常投稿の post として保存しています。
違いは、投稿に付ける _hibiiro_kind というメタデータです。
| 記事の種類 | post_type | _hibiiro_kind | URLの例 |
|---|---|---|---|
| 通常の記事 | post | post | /post/123/ |
| お知らせ | post | news | /news/456/ |
| 映画レビュー | post | movie | /movie/789/ |
通常の記事のURLは、サイトのパーマリンク設定を使います。お知らせなどの専用URLは、プラグイン側で生成しています。
ここで必要なのは、次の二つです。
- 記事のリンクを生成するときに、専用URLを返す
- 専用URLへアクセスされたときに、対応する通常投稿を表示する
リンクの見た目を変えるだけでは、アクセス先が404になることがあります。逆に、URLを受け付ける処理だけを作っても、記事一覧などには別のURLが出てしまいます。
1. 投稿の種類に応じてリンクを生成する
実装では、post_link フィルターを使っています。
以下は、プラグイン内の Hibiiro_Unified_Posts クラスから抜き出した処理です。self::kind() は投稿メタから種類を取得するメソッドです。
add_filter('post_link', [__CLASS__, 'link'], 30, 3);
public static function link($url, $post, $leavename = false) {
$kind = self::kind($post->ID);
if ($post->post_type !== 'post' || $kind === 'post') {
return $url;
}
return home_url(
user_trailingslashit($kind . '/' . $post->ID)
);
}例えば、投稿IDが456で、種類が news なら /news/456/ を返します。
通常の記事は $url をそのまま返すため、WordPress側のパーマリンク設定が使われます。
種類の表示名と、URLに使う識別子も分けています。「お知らせ」という表示名を変更しても、識別子の news を変更しなければURLは変わりません。
2. 専用URLをWordPressの検索条件に変換する
次は、アクセスされたURLを受け付ける処理です。
add_rewrite_rule() は、URLのパターンをWordPressのクエリ変数へ変換するために使います。
実装では投稿種類ごとにルールを登録していますが、お知らせだけに絞ると次の形です。
add_action('init', function () {
add_rewrite_rule(
'^news/([0-9]+)/?$',
'index.php?hibiiro_kind=news&hibiiro_id=$matches[1]',
'top'
);
});
add_filter('query_vars', function ($vars) {
$vars[] = 'hibiiro_kind';
$vars[] = 'hibiiro_id';
return $vars;
});([0-9]+) が記事IDの部分です。
/news/456/ へアクセスすると、内部では次の情報として扱われます。
hibiiro_kind = news
hibiiro_id = 456ただし、この時点では「456番の記事を表示してよいか」までは確認していません。
3. IDだけでなく、記事の種類も確認する
IDだけを使って記事を表示すると、映画レビューに対して /news/789/ のようなURLでもアクセスできてしまいます。
そこで、URLに書かれた種類と、保存されている投稿の種類が一致するかを確認します。
次は実装している request フィルターの処理です。
public static function request($vars) {
if (!empty($vars['hibiiro_id'])) {
$id = absint($vars['hibiiro_id']);
$post = get_post($id);
$kind = $vars['hibiiro_kind'] ?? '';
if (
!$post ||
!in_array($kind, self::special_kinds(), true) ||
self::kind($id) !== $kind ||
$post->post_type !== 'post'
) {
return ['error' => '404'];
}
$vars['p'] = $id;
$vars['post_type'] = $post->post_type;
}
return $vars;
}確認しているのは、次の4点です。
- そのIDの記事が存在するか
- URLの種類が登録済みか
- 記事に保存した種類と一致するか
- 通常投稿であるか
条件が合った場合に、WordPressが記事を取得するための p と post_type を設定します。
ここで本文を直接出力しないのもポイントです。公開・非公開やパスワード保護の扱いは、WordPress本体の処理に委ねています。
4. 一覧ページは、通常投稿をメタデータで絞り込む
/news/ の一覧も、別のカスタム投稿を検索する必要はありません。
実装では、URLから hibiiro_archive=news を受け取り、pre_get_posts でメインの検索条件を変更しています。
種類の条件は、次のような meta_query になります。
$clause = [
'key' => '_hibiiro_kind',
'value' => $kind,
];
$old = $q->get('meta_query');
$q->set(
'meta_query',
$old
? ['relation' => 'AND', $old, $clause]
: [$clause]
);既存の条件がある場合は上書きせず、AND で追加しています。
また、この処理は対象の一覧に絞って適用します。管理画面やサイドバーの取得まで変更しないよう、is_admin() と $q->is_main_query() で範囲を限定することが大切です。
今回の実装では、検索条件だけでなく、WordPressが一覧として扱うためのアーカイブ判定も設定しています。
実際の移行で、別に確認する必要があること
ここまでのコードは、移行後のURLと表示を扱う部分です。既存のカスタム投稿を通常投稿へ移す処理そのものではありません。
移行では、少なくとも次の情報を確認する必要があります。
- 投稿IDを維持できているか
- 本文・画像・投稿メタが残っているか
- カテゴリーや独自の分類が引き継がれているか
- 記事だけでなく一覧のURLも開けるか
- 一覧の2ページ目、フィードなども必要に応じて使えるか
Hibiiroでは20件を移行し、旧URLから記事を開けることを確認してから、カスタム投稿側を無効化しました。
数値IDを使ったURLなので、IDの維持は特に重要でした。記事を新規作成し直してIDが変われば、同じ仕組みだけでは元のURLを保てません。
確認には「正しいURL」と「間違ったURL」の両方を使う
表示できることだけでなく、表示されてはいけない組み合わせも確認します。
| 確認するURL | 想定する動作 |
|---|---|
| お知らせの正しいURL | お知らせの記事が開く |
お知らせのIDを使った /movie/ID/ | 404になる |
| 存在しないID | 404になる |
| お知らせ一覧 | お知らせだけが並ぶ |
| 一覧の2ページ目 | 次のページへ進める |
| 非公開の記事 | ログアウト状態では本文を読めない |
リライトルールを追加・変更したときは、ルールの再生成も必要です。手動で確認する場合は、ルールを登録した状態でパーマリンク設定を保存します。毎回のページ表示で再生成する構成にはしません。
移行が終わっても、URLを管理するプラグインは残す
今回の整理では、移行用の処理と、運用中に必要な処理を分けました。
記事を通常投稿へ移す作業は一度で終わりますが、専用URLの生成や振り分けは、その後も必要です。そのため、運用用プラグインは有効にしておきます。
「カスタム投稿をやめる」と「独自のURL処理をなくす」は別の話でした。
投稿画面はまとめつつ、残したいURLや一覧は維持する。そのために、記事の保存形式とURLの処理を分けた構成にしています。


コメント