東京都府中市、九段下のWeb制作会社Maromaroのブログです

2026.01.19

# コーディング・システム開発

【WordPress】カスタム投稿タイプ・タクソノミー・詳細ページURLを変更してみる

こんにちは、Maromaroの松橋です。

カスタム投稿タイプで、 https://example.com/ad/movie というURLにしたいのに、例えば、「ad-movie」というカスタム投稿タイプだと、通常、 https://example.com/ad-movie になってしまいます。

階層を深くしたURLにしたい場合、頭を抱えてしまいますが、テーマファイル内でカスタマイズで対応可能です。

編集するファイルは下記になります。


# テーマファイル内のfunctions.php
/wp-content/themes/xxxx/functions.php

# 今回の変更処理用に新規作成。
/wp-content/themes/xxxx/includes/Routing_Base.php
/wp-content/themes/xxxx/includes/Ad_Movies_Routing.php

各ファイルの役割・実装

1. functions.php


require_once(__DIR__ . '/includes/routing/Routing_Base.php');
require_once(__DIR__ . '/includes/routing/Ad_Movies_Routing.php');
if (class_exists('Ad_Movies_Routing')) {
  new Ad_Movies_Routing();
}

処理を記述している、後述2ファイルをインクルードします。
Ad_Movies_Routingクラスの、即時実行関数を使用してクラスを読み込み、インスタンス化を行います。
また、クラス存在チェック後にインスタンス化することで、エラー防止でき、より丁寧かと思います。

2. Routing_Base.php – ベースクラス


/**
 * URLルーティングのベースクラス
 * リライトルール、パーマリンク生成、テンプレート選択などの共通処理提供
 */
abstract class Routing_Base {

  /**
   * 指定したパスパターンに対するカノニカルリダイレクト処理を無効化
   *
   * WordPressにはURL正規化機能があり、必要に応じてリダイレクトされる。
   * これが意図しないリダイレクトを起こすことがあるため、カスタマイズしたパスは無効化する。
   *
   * @param string $path_pattern チェック対象のパスパターン
   * @return void
   */
  protected function disable_canonical_redirect_for_pattern($path_pattern) {
    add_filter('redirect_canonical', function($redirect_url, $requested_url) use ($path_pattern) {
      $pattern = preg_quote($path_pattern, '#');
      if (preg_match('#' . $pattern . '?$#', $requested_url)) {
        return false;
      }
      return $redirect_url;
    }, 10, 2);
  }
}

抽象クラスとして、ルーティング処理に共通する機能を提供します。
このクラスでは、カノニカルリダイレクトの無効化の関数を入れてます。

disable_canonical_redirect_for_pattern()メソッドは、WordPressのURL正規化による自動リダイレクトを無効化し、カスタムパスに対して意図しないリダイレクトが発生するのを防ぎます。

3. Ad_Movies_Routing.php – ルーティング実装クラス


/**
 * ad-movie のカスタムルーティング
 */
class Ad_Movies_Routing extends Routing_Base {

  /**
   * カスタムルーティング設定を保持する配列
   *
   * @var array
   */
  private $config = [
    'path' => '/ad/movie/',
    'post_type' => 'ad-movie',
    'taxonomy' => 'ad-movie-category',
  ];

  /**
   * コンストラクタ
   */
  public function __construct() {
    $this->init_routing();
  }


  /**
   * カスタムルーティングを初期化
   *
   * @return void
   */
  private function init_routing() {
    $config = $this->config;

    // パスの正規化(先頭と末尾のスラッシュを統一)
    $config['path'] = '/' . trim($config['path'], '/') . '/';
    // リライトルール用(先頭の^と末尾の/を除く)
    $config['path_pattern'] = trim($config['path'], '/');
    $this->config = $config;

    $config_path = $config['path'];

    // リライトルールを追加
    add_action('init', [$this, 'add_rewrite_rules'], 1);

    // リライトルール配列を修正(ページルールからカスタムパスを除外)
    add_filter('rewrite_rules_array', [$this, 'modify_rewrite_rules'], 10, 1);

    // カノニカルリダイレクトを防ぐ
    $this->disable_canonical_redirect_for_pattern($config_path);

    // 投稿詳細ページのURLを変更
    add_filter('post_type_link', [$this, 'generate_post_link'], 10, 2);

    // アーカイブページのURLを変更
    add_filter('post_type_archive_link', [$this, 'generate_archive_link'], 10, 2);

    // タクソノミーのURLを変更
    add_filter('term_link', [$this, 'generate_term_link'], 10, 3);

    // 条件によりテンプレートファイルを指定
    add_filter('template_include', [$this, 'select_template'], 99);
  }

  /**
   * カスタム投稿タイプのパーマリンクにカスタムパスを追加
   *
   * より具体的なルールを先に配置します(ページネーションなど)。
   *
   * @return void
   */
  public function add_rewrite_rules() {
    $config = $this->config;
    $path_pattern = $config['path_pattern'];
    $post_type = $config['post_type'];
    $taxonomy = $config['taxonomy'] ?? null;

    // タクソノミーアーカイブページのページネーション
    if ($taxonomy) {
      add_rewrite_rule(
        '^' . $path_pattern . '/' . $taxonomy . '/([^/]+)/page/([0-9]+)/?$',
        'index.php?' . $taxonomy . '=$matches[1]&paged=$matches[2]',
        'top'
      );
    }

    // アーカイブページのページネーション
    add_rewrite_rule(
      '^' . $path_pattern . '/page/([0-9]+)/?$',
      'index.php?post_type=' . $post_type . '&paged=$matches[1]',
      'top'
    );

    // 投稿詳細ページのリライトルール
    add_rewrite_rule(
      '^' . $path_pattern . '/([0-9]+)/?$',
      'index.php?post_type=' . $post_type . '&p=$matches[1]',
      'top'
    );

    // タクソノミーアーカイブページのリライトルール
    if ($taxonomy) {
      add_rewrite_rule(
        '^' . $path_pattern . '/' . $taxonomy . '/([^/]+)/?$',
        'index.php?' . $taxonomy . '=$matches[1]',
        'top'
      );
    }

    // アーカイブページのリライトルール(最後に配置)
    add_rewrite_rule(
      '^' . $path_pattern . '/?$',
      'index.php?post_type=' . $post_type,
      'top'
    );
  }

  /**
   * 投稿詳細ページのURLを変更
   *
   * @param string $post_link 現在のパーマリンク
   * @param WP_Post $post 投稿オブジェクト
   * @return string 変更後のパーマリンク
   */
  public function generate_post_link($post_link, $post) {
    $config = $this->config;

    if ($post->post_type === $config['post_type']) {
      $post_link = home_url($config['path'] . $post->ID . '/');
    }

    return $post_link;
  }

  /**
   * アーカイブページのURLを変更
   *
   * @param string $link 現在のアーカイブリンク
   * @param string $post_type 投稿タイプ名
   * @return string 変更後のアーカイブリンク
   */
  public function generate_archive_link($link, $post_type) {
    $config = $this->config;

    if ($post_type === $config['post_type']) {
      $link = home_url($config['path']);
    }

    return $link;
  }

  /**
   * タクソノミーのURLを変更
   *
   * @param string $termlink 現在のタームリンク
   * @param WP_Term $term タームオブジェクト
   * @param string $taxonomy タクソノミー名
   * @return string 変更後のタームリンク
   */
  public function generate_term_link($termlink, $term, $taxonomy) {
    $config = $this->config;

    if (!empty($config['taxonomy']) && $taxonomy === $config['taxonomy']) {
      $termlink = home_url($config['path'] . $config['taxonomy'] . '/' . $term->slug . '/');
    }

    return $termlink;
  }

  /**
   * リライトルール配列を修正
   *
   * ページルールからカスタムパスを除外して、カスタム投稿タイプのルールが優先されるようにします。
   *
   * @param array $rules リライトルール配列
   * @return array 修正後のリライトルール配列
   */
  public function modify_rewrite_rules($rules) {
    $config = $this->config;
    $path_pattern = $config['path_pattern'];

    foreach ($rules as $pattern => $rewrite) {
      // ページルール(pagenameを含む)で、カスタムパスにマッチする可能性があるものを除外
      if (strpos($rewrite, 'pagename=') !== false) {
        // パターンを正規表現として使用(^と$を追加)
        $regex = '#^' . preg_quote($pattern, '#') . '$#';
        if (preg_match($regex, $path_pattern)) {
          // このルールがカスタムパスにマッチする場合、削除
          unset($rules[$pattern]);
        }
      }
    }

    return $rules;
  }


  /**
   * 条件によりテンプレートファイルを指定
   *
   * 投稿詳細ページとタクソノミーページで適切なテンプレートを選択します。
   *
   * @param string $template 現在のテンプレートファイルのパス
   * @return string テンプレートファイルのパス
   */
  public function select_template($template) {
    $config = $this->config;

    // 投稿詳細ページのテンプレート選択
    if (get_query_var('post_type') === $config['post_type'] && is_single()) {
      global $post;

      if ($post && $post->ID) {
        setup_postdata($post);

        $new_template = locate_template(['single-ad-movie.php']);
        if (!empty($new_template)) {
          return $new_template;
        }
      }
    }

    // タクソノミーページのテンプレート選択
    if (is_tax($config['taxonomy'])) {
      $post_type = get_query_var('post_type');

      if ($post_type && $post_type === $config['post_type']) {
        $taxonomy_template = locate_template(['taxonomy-ad-movie-category.php']);

        if (!empty($taxonomy_template)) {
          return $taxonomy_template;
        }
      }
    }

    return $template;
  }
}

Ad_Movies_Routingは、Routing_Baseを継承させ、ad-movieカスタム投稿タイプに対するルーティングを実装します。
$configにカスタム投稿タイプとタクソノミーのスラッグ、設定したいパスを入れていきます。

add_rewrite_rules()メソッドで、投稿詳細ページ(/ad/movie/{post_id}/)、アーカイブページ(/ad/movie/)、タクソノミーアーカイブページ(/ad/movie/ad-movie-category/{slug}/)、およびそれぞれのページネーションに対応するリライトルールを追加します。より具体的なルールを先に配置し、`’top’`優先度で追加することで、他のルールより優先されるようにしています。

post_type_linkpost_type_archive_linkterm_linkの3つのフィルターを使用して、WordPressが生成するURLをカスタムパス形式に変更します。
また、rewrite_rules_arrayフィルターでページルールからカスタムパスにマッチする可能性があるものを除外し、カスタム投稿タイプのルールが優先されるようにしています。
さらに、template_includeフィルターで投稿詳細ページには、single-ad-movie.php、タクソノミーページには、taxonomy-ad-movie-category.phpを選択します。

注意点

リライトルールを追加・変更した後は、パーマリンク設定を保存する必要があります。
また、single-ad-movie.phptaxonomy-ad-movie-category.phpのテンプレートファイルが存在する必要があり、存在しない場合はデフォルトのテンプレートが使用されます。

さいごに

このルーティングのカスタマイズにより、WordPressの標準的なURL構造を変更し、カスタム投稿タイプに対して直感的なURLを提供できます。ベースクラスと具象クラスの分離により、他のカスタム投稿タイプに拡張していけます!

以上、Maromaroの松橋でした。