【WordPress】add_rewrite_ruleでリライトルールを追加する

【WordPress】add_rewrite_ruleでリライトルールを追加する

リライトルールとは

WordPressは内部でURLをパラメータに読み換えて、それにより該当するページを表示させています。その「URLをパラメータに読み換える規則」をリライトルールと呼び、WP内に管理しています。

例えば、下記のURLの場合

https://notes.sharesl.net/about/

WordPressはリライトルールに従い、下記のように読み換えて処理します。

https://notes.sharesl.net/index.php?pagename=about

パラメータのpagenameというのが固定ページのスラッグ名を意味しています。そのため、このURLからは固定ページのスラッグが「about」のページを表示させます。このように、WordPressはルートディレクトリに配置されたindex.phpに付いたパラメータ(?以降の文字列)に基づいて、表示させるページを決定しています。

このindex.phpに付くパラメータは「パブリッククエリ変数」と呼ばれ、pagenameの他にも固定ページIDを意味するpage_idや投稿タイプを意味するpost_typeなど、WP内部で定義されています。

つまり、リライトルールは、URLを「パラメータ付きのindex.phpのURL」に変換して適切なページを表示するためのルールのことです。

add_rewrite_ruleとは

リライトルールを独自で追加するWordPressの関数です。
主にfunctions.phpinitアクションフックを使って定義します。

function custom_rewrite_rule() {
   add_rewrite_rule($regex, $redirect, $after);
}
add_action('init', 'custom_rewrite_rule');

引数は下記。

$regex
(文字列) (必須) リクエストされたURLにマッチする正規表現。オプションで1つ以上のグループを使用できる。
初期値: なし
$redirect
(文字列) (必須) $regexがマッチした場合に、実際にフェッチしたいURL。マッチしたキャプチャグループを挿入するには $matches[] を使用する。
初期値: なし
$after
(文字列) (オプション) 'top' または 'bottom'。'top' の場合、ルールは WordPressのすべての既存ルールに優先する。'bottom' の場合、ルールはすべての既存ルールがマッチしない場合に検査される。
初期値: "bottom"

Rewrite API/add rewrite rule - WordPress Codex 日本語版

この関数でどういうことができるか例を示します。
まずhttps://example.com/sample/というURLにアクセスした時に、WordPressでは基本的には固定ページのスラッグ名「sample」のページを表示します。このルールを、新たなルールを追加することで変えることができます。

function custom_rewrite_rule() {
   add_rewrite_rule('sample/?$', 'index.php?pagename=rewrite', 'top');
}
add_action('init', 'custom_rewrite_rule');

上記のコードを追加し、リライトルールを反映するためにWordPressの管理画面から、「設定」->「パーマリンク設定」を選択し、何も変更せずに「変更を保存」をクリックします。その後、https://example.com/sample/にアクセスすると、固定ページのスラッグ名「rewrite」のページの情報が表示されるようになります。

ポイントは、コードを追記したあとの「WordPressの管理画面から、「設定」->「パーマリンク設定」を選択し、何も変更せずに「変更を保存」をクリック」の作業です。これをしないと設定が反映されません。

リライトルールを反映させる手順は、コード上でflush_rewrite_rules()関数を書くことでも実現できますが、高負荷な処理のためfunctions.phpinitフックなどに書いて毎回実行されるようにするのはやめておきましょう。

この記事でやること

今回はリライトルールを追加して問題解決に至った内容をいくつかメモします。

やりたいことがWordPressの仕様でできないかも・・・と思っていたら、リライトルールですんなり解決することがあります。そういう場面に遭遇した時にリライトルールをどのように追加したかを備忘録として残しておきます。

具体的には下記のような内容になります。

  • 固定ページのページネーションで2ページ目以降が404になってしまう時の解決方法
  • カスタム投稿の親ページをなくしてドメイン直下にする(固定ページと同じ階層で表示したいが、管理画面上では固定ページとカスタム投稿で入力を完全に分けたい)
  • ampページを作るときにURLの末尾を/amp/にして表示させる

上記のような不具合の解決やクライアントからの要望は、すべてリライトルールを追加することで解決します。リライトルールの概念や基本的な内容については他にまとめている方がいらっしゃいますのでこの記事ではこれ以上細かくは書きません。より具体的な、実際にリライトルールを追加した場面と内容について簡単にまとめてメモします。

  • 固定ページのページ送りで2ページ目以降が404になってしまう不具合を解決する方法
  • カスタム投稿のアーカイブページを作らずに、固定ページにWP_Queryでサブクエリを使って一覧ページを作る場合に、ページ送りの2ページ目以降が404エラーになってしまう場合があります。

    カスタム投稿は下記のような条件。

    $args = [
      'label'               => 'News',
      'has_archive'         => false,
      'public'              => true,
      'exclude_from_search' => false,
      'publicly_queryable'  => true,
      'show_ui'             => true,
      'query_var'           => true,
      'rewrite'             => [
        'slug'       => 'news',
        'with_front' => false
      ],
      'hierarchical'        => false,
      'menu_position'       => null,
      'show_in_rest'        => true,
      'supports'            => [
        'title',
        'slug',
        'thumbnail',
        'editor'
      ]
    ];
    register_post_type('news', $args);
    • has_archivefalseにしてアーカイブページを作らない。
    • rewrite設定をwith_frontfalseslugを任意の固定ページのスラッグ名に合わせる。

    ここではnewsのアーカイブページは作らず、固定ページnewsを作り、その下層ページにカスタム投稿を配置するという構造を例にします。アーカイブページに他の固定ページと同じくカスタムフィールドやエディターなどを使いたい時は、オプションページを作って別枠で用意しないといけないので、作る方も使う方も面倒です。初めてWPを触るようなクライアントに「固定ページはここ、一覧ページはこっちで設定してねー」とか説明してもなんで分けてるのかよくわからないので、使いづら!って思われるだけです。できるだけ固定ページをそのまま使った方が作る方も圧倒的に作りやすいし、使う方もまとまっている方がわかりやすいので筆者はよくこういった構造にします。

    ただ、こういう時に表題のような「ページネーションの2ページ目以降が404になってしまう」といった予期しない不具合が起こる場合があります。

    ページ送りは、下記のようなURLになります。

    1ページ目https://example.com/news/ → 表示される
    2ページ目https://example.com/news/page/2/ → 404
    3ページ目https://example.com/news/page/3/ → 404

    アーカイブページから固定ページにしたことで、/page/というディレクトリが固定ページの子ページのスラッグとして認識されてしまうためか、うまくテンプレートを表示できなくなってしまいます。

    こういった場合は、下記のようなリライトルールを追加すれば解決します。

    function custom_rewrite_rule() {
      add_rewrite_rule('(news)/page/?([0-9]{1,})/?$', 'index.php?pagename=$matches[1]&paged=$matches[2]', 'top');
    }
    add_action('init', 'custom_rewrite_rule');

    このリライトルールを追記すると、ページスラッグnewsの後ろに/page/ページ数/が入るURLに一致する場合は、パブリッククエリ変数pagenameに一致したスラッグ名($matches[1])、pagedに一致したページ数($matches[2])のページが表示されます。

    具体的には下記のようになります。

    1ページ目https://example.com/news/https://example.com/index.php?pagename=news
    2ページ目https://example.com/news/page/2/https://example.com/index.php?pagename=news&paged=2
    3ページ目https://example.com/news/page/3/https://example.com/index.php?pagename=news&paged=3

    /page/ページ数/が付くとpagedがパラメータに追加されるようになります。
    これでアーカイブページを固定ページにした場合でも期待通りに表示できるようになります。

  • カスタム投稿の親ページをなくしてドメイン直下にする
  • これはけっこう特殊な内容にはなりますが、特殊な時ほどリライトルールの追加が必要になります。例えば、「一覧ページはいらないけど管理画面上では固定ページとは別にしておきたいページ」の場合などです。

    例えば、「商品」のカスタム投稿タイプitemを作ると、WordPressでは通常下記のようなURLになります。

    アーカイブページhttps://example.com/item/
    「商品」投稿ページhttps://example.com/item/{商品ページのスラッグ名やIDなど}/

    このitemという階層そのものは必要ないのでなくしたい。

    アーカイブページなし
    投稿ページhttps://example.com/{商品ページのスラッグ名やIDなど}/

    この場合、普通は固定ページに全部突っ込んでしまわないといけないのですが、「商品」などの場合は他の固定ページと違って商品画像を複数カスタムフィールドに登録したり、コンテンツも他の固定ページと全然違うものになったりするので、ページの編集画面が複雑になってしまいます。そうなると「商品」は固定ページの中に置くのではなく別の投稿として管理して編集画面も分けたい、といった要望が出てくる場合があります。

    その際は下記のようなリライトルールを書いて対応します。

    function custom_rewrite_rule() {
      //リダイレクトフラグ
      add_rewrite_tag('%redirect_from%', '([^&]+)');
      //item一覧
      $args = [
        'post_type'              => 'item',
        'posts_per_page'         => -1,
        'post_status'            => 'publish',
        'no_found_rows'          => true,
        'update_post_meta_cache' => false,
        'update_post_term_cache' => false
      ];
      $query = get_posts( $args );
      if ( !empty($query) ) :
        foreach ( $query as $q ) :
          $slug = $q->post_name;
          add_rewrite_rule($slug.'/?$', 'index.php?post_type=item&name='.$slug.'&redirect_from=item', 'top');
          add_rewrite_rule($slug.'/([^/]+)/?', 'index.php?post_type=item&name=$matches[1]&redirect_from=item', 'top');
        endforeach;
      endif;
    }
    add_action('init', 'custom_rewrite_rule');

    まずadd_rewrite_tag関数を使って独自のパブリッククエリ変数を登録します。add_rewrite_tagの使い方は下記。

    add_rewrite_tag( string $tag, string $regex, string $query = '' )

    Parameters

    $tag

    (string) (Required) Name of the new rewrite tag.

    $regex

    (string) (Required) Regular expression to substitute the tag for in rewrite rules.

    $query

    (string) (Optional) String to append to the rewritten query. Must end in '='.

    Default value: ''

    https://developer.wordpress.org/reference/functions/add_rewrite_tag/

    ↑英語でわかりにくかったので要約すると、

    第一引数$tagはリライトタグの名前。これがそのままパブリッククエリ変数の名前になります。
    第二引数$regexにリライトルールでリライトタグを置き換える正規表現。
    第三引数$queryにリライトされたURLに追加するパラメータの文字列。最後は=で終わる必要があります。指定がない場合は{リライトタグ}=になります。

    ということです。

    これを使って

    add_rewrite_tag('%redirect_from%', '([^&]+)');

    redirect_fromというパブリッククエリ変数を登録します。
    この変数は後ほど使います。

    次にカスタム投稿itemの一覧を取得します。

    //item一覧
    $args = [
      'post_type'              => 'item',
      'posts_per_page'         => -1,
      'post_status'            => 'publish',
      'no_found_rows'          => true,
      'update_post_meta_cache' => false,
      'update_post_term_cache' => false
    ];
    $query = get_posts( $args );

    取得したitem一覧をループで回して、各投稿ごとにリライトルールを追加します。

    $query = get_posts( $args );
    if ( !empty($query) ) :
      foreach ( $query as $q ) :
        //スラッグ名
        $slug = $q->post_name;
        //リライトルールを追加
        add_rewrite_rule($slug.'/?$', 'index.php?post_type=item&name='.$slug.'&redirect_from=item', 'top');
        add_rewrite_rule($slug.'/([^/]+)/?', 'index.php?post_type=item&name=$matches[1]&redirect_from=item', 'top');
      endforeach;
    endif;

    具体的には下記のような内容になります。

    商品カスタム投稿に「shoes」「socks」ページが公開されている場合

    商品カスタム投稿「shoes」ページhttps://example.com/shoes/https://example.com/index.php?post_type=item&name=shoes&redirect_from=item
    商品カスタム投稿「socks」ページhttps://example.com/socks/https://example.com/index.php?post_type=item&name=socks&redirect_from=item
    固定ページ「contact」ページhttps://example.com/contact/https://example.com/index.php?pagename=contact

    わかりやすいように固定ページの場合もcontactページを書いておきました。
    このようにカスタム投稿itemに投稿されたページは固定ページを上書きするような形にできます。

    あとはもとから存在する方の投稿ページをリライトした方にリダイレクトしてあげればOKです。

    まずはパーマリンクを変更します。

    //パーマリンクの変更
    function custom_permalink( $url, $post ) {
      if('item' == get_post_type($post)) {
        $url = str_replace('/item/', '/', $url);
        return $url;
      }
    
      return $url;
    }
    add_filter('pre_post_link', 'custom_permalink', 10, 2);
    add_filter('post_link', 'custom_permalink', 10, 2);
    add_filter('post_type_link', 'custom_permalink', 10, 2);

    これでitem投稿のループでthe_permalink()など投稿のURLを取得する関数を使った時に、リライトされた方のURL(/item/が削除されたURL)が表示されるようになります。

    次に元URLからリライトされたURLにリダイレクトします。
    ここで先ほどadd_rewrite_tag関数で追加したredirect_from変数を使います。

    function canonical_item_url(){
      $redirect_from = get_query_var('redirect_from');
      if(is_singular('item') && empty($redirect_from)){
        global $post;
        $url = get_the_permalink($post->ID);
        wp_safe_redirect($url);
        exit;
      }
    }
    add_action('template_redirect', 'canonical_item_url');

    パブリッククエリ変数はget_query_var('redirect_from')のようにして取得できるので、redirect_fromがない場合はリライト前のページが表示されているので、リライト後のページへリダイレクトします。

    これで固定ページとカスタム投稿をドメイン直下のディレクトリで表示できるようになりました。
    注意点としては、固定ページとカスタム投稿で同じスラッグ名を使わないことです。

  • ampページを作るときにURLの末尾を/amp/にして表示させる
  • これは過去記事でやり方について書いていますのでそちらを参考に。

    add_rewrite_ruleを実行するフックについて

    initアクションフックを使います。
    そして個人的にはこれ以外使わない方が無難だと思っています。

    WordPressにはいくつかフックがあるので、ちょっと詳しい方はinitなんか毎回必要ない時まで最初に読み込まれちゃって邪魔になるんじゃないか?と思うかもしれません。たしかに毎回読み込まれてしまいますし気になる方は気になるかと。管理画面側だけで実行されるような記述に変えた方が効率良いかも、と考えて下記のように書いたりしがちです。

    //admin_initフックを使う
    function custom_rewrite_rule() {
      add_rewrite_rule($regex, $redirect, $after);
    }
    add_action('admin_init', 'custom_rewrite_rule');
    
    //管理画面だけで読み込むようにする
    function custom_rewrite_rule() {
      if(is_admin()){
       add_rewrite_rule($regex, $redirect, $after);
      }
    }
    add_action('init', 'custom_rewrite_rule');

    これらはどちらも動くのですが、確実に動作する保証がないです。
    admin_initはどうやらフックの順番としては遅すぎるようで、その前のフックでflush_rewrite_rules()が実行されてしまう場合があり、動作が不安定になります。

    initフック内でif文を使って管理画面だけ読み込ませるようにした場合についても、前述したようにflush_rewrite_rules()という関数があるのが厄介なところで、もしも利用しているプラグインのフロント側にflush_rewrite_rules()が書かれていた場合は追加したリライトルールは読み込まれません。

    そのため多少は負荷になるかもしれませんが、リライトルールを独自で追加する場合はその負荷込みでinitアクションフック内に書いた方が確実です。

    もしその負荷がどうしても受け入れられないということであれば、使用しているプラグイン・テーマを全て調べて問題ないことを確認してから別のフックを使えば良いと思います。

    一応、他に使えるフックとしてroot_rewrite_rulesフィルターフックがあります。

    function custom_rewrite_rule($rules) {
      add_rewrite_rule($regex, $redirect, $after);
      return $rules;
    }
    add_filter( 'root_rewrite_rules', 'custom_rewrite_rule', 9999);

    「ブログのルート用に生成されたリライトルールをフィルターするフック」なのでアクションフックと違って毎回動作するものではないので負荷は少ないと思います。ただしあまり使っているのを見たことがなく情報が少ないのでどうなのかなーって感じです。やはり確実にいくならinitアクションフックです。

    root_rewrite_rulesの参考記事は下記。

    WordPressのrewrite ruleいじるときのhook

    https://qiita.com/zuya/items/e4b9afcec84befe98ae2

    さいごに

    WordPressのデフォルトのリンク構造でキレイに作れるのが1番良いと思いますが、独自でテーマを作成する場合はそうならないことがよくあります。リライトルールの追加方法を知っていれば、WordPressの構造に捉われずにより自由にサイト構造をカスタマイズできるようになり、仕様でできないと思っていたことも「WordPressの仕様でして・・・」とか言い訳しなくて済むようになります。リライトルールを追加する手順やそういった場面はWordPress特有のものになるので、ニッチすぎて少しとっつきにくい内容ですが、WordPressでサイトを作成することが多いなら覚えておいて損はないです。