2018夏のweb制作無料相談会(名古屋当日)
2018夏のweb制作無料相談会(名古屋当日)
2018.03.23
#33
バックエンドへの道

WordPressのカスタム投稿タイプのURLをIDベースにする方法

エリカ

こんにちは。エリカです。

WordPressのカスタム投稿タイプの詳細ページをIDベースにしたいときありますよね。 /book/book_name じゃなくて、/book/1234 みたいな。

そんな風にURLをコントロールする方法を、カスタム投稿タイプを作るときに使う、register_post_type関数の中身を見ながら、再確認したいと思います。

カスタム投稿タイプの登録

まずは、概要です。こんな関数ですね。

function register_post_type( $post_type, $args = array() ) {
    global $wp_post_types;

    if ( ! is_array( $wp_post_types ) ) {
        $wp_post_types = array();
    }

    // Sanitize post type name
    $post_type = sanitize_key( $post_type );

    if ( empty( $post_type ) || strlen( $post_type ) > 20 ) {
        _doing_it_wrong( __FUNCTION__, __( 'Post type names must be between 1 and 20 characters in length.' ), '4.2.0' );
        return new WP_Error( 'post_type_length_invalid', __( 'Post type names must be between 1 and 20 characters in length.' ) );
    }

    $post_type_object = new WP_Post_Type( $post_type, $args );
    $post_type_object->add_supports();
    $post_type_object->add_rewrite_rules();
    $post_type_object->register_meta_boxes();

    $wp_post_types[ $post_type ] = $post_type_object;

    $post_type_object->add_hooks();
    $post_type_object->register_taxonomies();

    /**
     * Fires after a post type is registered.
     *
     * @since 3.3.0
     * @since 4.6.0 Converted the `$post_type` parameter to accept a WP_Post_Type object.
     *
     * @param string       $post_type        Post type.
     * @param WP_Post_Type $post_type_object Arguments used to register the post type.
     */
    do_action( 'registered_post_type', $post_type, $post_type_object );

    return $post_type_object;
}

一つ目の引数にカスタム投稿タイプ名、二つ目の引数にその内容を配列で指定する形です。

配列のサニタイズや、投稿タイプ名の制限チェックなどを経て、次が実行されます。

$post_type_object = new WP_Post_Type( $post_type, $args );

カスタム投稿タイプ登録用のインスタンスを作成します。

コンストラクタでは、与えられた引数を、WP_Post_Type::set_props()で整えながらプロパティにセットしていきます。

$post_type_object->add_supports();

投稿画面でどの機能をサポートするかどうかを設定します。 初期値は「title」と「editor」になります。タイトルと本文ですね。なので「title」だけ指定すればタイトルだけの投稿画面にすることができます。

$post_type_object->add_rewrite_rules();

お待ちかねのリライトルールがここで登場です。これは後述します。

$post_type_object->register_meta_boxes();

投稿画面のメタボックス用のアクションフックとして呼び出す関数を登録します。

$wp_post_types[ $post_type ] = $post_type_object;

    $post_type_object->add_hooks();
    $post_type_object->register_taxonomies();

グローバル変数に登録して、予約投稿用のフックに追加し、カスタム分類との関連づけを行います。

では、add_rewrite_rules()の部分を見ます。

リライトルールについて

前提として、WordPressは下記のようなURLが本来の記事詳細ページのURLです。

index.php?p=1234

それをパーマリンク設定によっては、/archives/1234のようなURLで動作させることができるのですが、これはどういうことかというと、/book/1234というリクエストがあったら、それをindex.php?p=1234に置き換えるようになっているからです。

これがリライトルールです。

このリライトルールをコントロールすることによって自由なURLを設定することができるようになり、カスタム投稿タイプが作成するURLも、この仕組みを利用しています。

それでは、見てみましょう。

public function add_rewrite_rules() {
        global $wp_rewrite, $wp;

        if ( false !== $this->query_var && $wp && is_post_type_viewable( $this ) ) {
            $wp->add_query_var( $this->query_var );
        }

        if ( false !== $this->rewrite && ( is_admin() || '' != get_option( 'permalink_structure' ) ) ) {
            if ( $this->hierarchical ) {
                add_rewrite_tag( "%$this->name%", '(.+?)', $this->query_var ? "{$this->query_var}=" : "post_type=$this->name&pagename=" );
            } else {
                add_rewrite_tag( "%$this->name%", '([^/]+)', $this->query_var ? "{$this->query_var}=" : "post_type=$this->name&name=" );
            }

            if ( $this->has_archive ) {
                $archive_slug = true === $this->has_archive ? $this->rewrite['slug'] : $this->has_archive;
                if ( $this->rewrite['with_front'] ) {
                    $archive_slug = substr( $wp_rewrite->front, 1 ) . $archive_slug;
                } else {
                    $archive_slug = $wp_rewrite->root . $archive_slug;
                }

                add_rewrite_rule( "{$archive_slug}/?$", "index.php?post_type=$this->name", 'top' );
                if ( $this->rewrite['feeds'] && $wp_rewrite->feeds ) {
                    $feeds = '(' . trim( implode( '|', $wp_rewrite->feeds ) ) . ')';
                    add_rewrite_rule( "{$archive_slug}/feed/$feeds/?$", "index.php?post_type=$this->name" . '&feed=$matches[1]', 'top' );
                    add_rewrite_rule( "{$archive_slug}/$feeds/?$", "index.php?post_type=$this->name" . '&feed=$matches[1]', 'top' );
                }
                if ( $this->rewrite['pages'] ) {
                    add_rewrite_rule( "{$archive_slug}/{$wp_rewrite->pagination_base}/([0-9]{1,})/?$", "index.php?post_type=$this->name" . '&paged=$matches[1]', 'top' );
                }
            }

            $permastruct_args         = $this->rewrite;
            $permastruct_args['feed'] = $permastruct_args['feeds'];
            add_permastruct( $this->name, "{$this->rewrite['slug']}/%$this->name%", $permastruct_args );
        }
    }

まずは「%book%」のようなカスタム投稿名を冠するリライトタグを登録しています。これはパーマリンク設定画面で見ることのできる「%post_id%」や「%postname%」のようなものです。これは、それを示すURLがどのような構成かを指定します。

add_rewrite_tag( "%$this->name%", '([^/]+)', $this->query_var ? "{$this->query_var}=" : "post_type=$this->name&name=" );

bookというカスタム投稿だとすると、「%book%」は、「([^/]+)」という正規表現に合致し、それは、「book=」もしくは「post_type=book&pagename=」に置き換えられるということになります。

そして、下記により、URLの構造を登録しています。

add_permastruct( $this->name, "{$this->rewrite['slug']}/%$this->name%", $permastruct_args );

これは「/book/%book%」というURL構造になります。つまり「/book/([^/]+)」 という正規表現にマッチすればと、index.php?post_type=book&name=$matches[1] というように置き換えられることになります。

たとえば「/book/foo」というURLがリクエストされた場合は、index.php?post_type=book&name=fooになり、投稿タイプ「book」の中からfooというslugを持つ記事を抽出するというメインクエリーが発行されることになります。

なお、真ん中の部分は一覧ページのURLのコントロールについてなので今回は割愛します。

これが基本です。オプションを指定することにより、IDベースに置き換えることは、今現在はできません。

なので、他の方法でIDベースのURLにできるようにします。

IDベースのURLへの変更

register_post_type()関数実行時の最後に設定されているregistered_post_typeというアクションフックがあります。

なので、ここで、add_rewrite_tagを変更してみてはどうでしょうか。

add_action('registered_post_type', function($post_type, $post_type_object) {

if('book' === $post_type) {
add_rewrite_tag('%book_id%', '([0-9]+)', "post_type=book&p=");
add_permastruct('book', 'book/%book_id%', [])
}

}, 10 ,2);

ここでは新たに、/book/%book_id% というURL構造を作成しました。これは「/book/([0-9]+)」という正規表現になり、「/book/1234」のようなリクエストをindex.php?post_type=book&p=1234 に置き換えてくれるようになります。

これだけでIDベースのURLでアクセスできるようになります。ただ、もう一つ、記事のパーマリンクはまだ作成されていませんので、これも上記のルールになるようにします。

post_type_linkというフィルターフックを使います。

add_filter('post_type_link', function($post_link, $post, $leavename, $sample ){
if('book' === $post->post_type) {
$post_link = str_replace('%book_id%, $post->ID, $post_link);
}

return $post_link;
}, 10, 4);

これで、get_permalink()のURLもIDベースのURLになります。

URLのコントロールも仕組みを理解しておくといろいろできますね。

それでは。