開発

Blog

更新日: 開発

【WordPress】カスタムタクソノミーの初期タームと並び順をプラグインなしでコード管理する

WordPress でカスタムタクソノミーを作ったとき、「最初から用意しておきたいカテゴリー」や「カテゴリーの並び順」をどう管理するかで迷う人もいると思います。この記事では、プラグインを使わず、テーマのコードだけで初期タームの登録と並び順を管理する方法を紹介します。

この記事でわかること
  • 初期タームと並び順を、プラグインなしでコード管理するメリット
  • 設定の配列から、タクソノミーと初期タームをまとめて登録する方法
  • 設定が変わったときだけ登録を走らせて、DB への問い合わせを減らす工夫
  • タームの並び順を、設定に書いた順にそろえる方法

なぜプラグインを使わずにコードで管理するのか

オレオレナリでは、WordPress サイトを作るときに「プラグインを入れなくても実装できるものは、プラグインを使わずに実装する」という方針にしています。

オレオレナリの方針

依存するプラグインは、少ないほどセキュリティ的にも運用的にも良い。テーマのコードで無理なく書けるものは、テーマで書く。

タームの並び替えや初期データの投入は、プラグインを入れれば管理画面からできます。ただ、この程度の処理ならテーマの PHP で十分に書けます。コードで管理すると、こんなメリットがあります。

観点コードで管理するメリット
セキュリティプラグインが減るぶん、脆弱性が見つかったときに影響を受ける入り口が減る
運用プラグインの更新作業や、WordPress 本体の更新に合わせた相性の確認が減る
環境差ローカル・本番で同じコードが動くので、「本番でカテゴリーを作り忘れた」「スラッグを打ち間違えた」が起きない
履歴Git で管理できるので、いつ・なぜカテゴリーを変えたかが残る

もちろん、サイトを運用する人がコードを触らず、管理画面で自由にカテゴリーを増やしたり並べ替えたりしたい場合は、管理画面やプラグインで管理するほうが向いています。どちらが良いかは、誰がサイトを触るかで決めるのがおすすめです。

設定はひとつの配列にまとめる

まず、タクソノミーの設定を配列にまとめて返す関数を用意します。タクソノミーを増やしたいときは、ここに足すだけで済むようにしておきます。

function ore2nari_taxonomies(): array {
	return array(
		// ブログ
		array(
			'slug'         => ORE_TAXONOMY_SLUG_BLOG_CAT,
			'label'        => 'カテゴリー',
			'post_types'   => array( ORE_POST_TYPE_SLUG_BLOG ),
			'hierarchical' => true,
			'terms'        => array(
				array( 'slug' => 'dev', 'name' => '開発' ),
				array( 'slug' => 'making', 'name' => '制作の裏側' ),
				array( 'slug' => 'tips', 'name' => '制作のヒント' ),
				array( 'slug' => 'talk', 'name' => '雑談' ),
			),
		),
		// …ほかのタクソノミーも同じ形で並べる
	);
}

それぞれのキーの意味はこんな感じです。

slug

タクソノミーのスラッグ。URL にも使います。

label

管理画面での名前。

post_types

紐付ける投稿タイプ。

hierarchical

true ならカテゴリー型、false ならタグ型。

terms

初期ターム。ここに書いた順が、そのまま並び順になります。

タクソノミーを登録する

設定の配列をループして、register_taxonomy() を呼びます。管理画面の文言(ラベル)を作る関数は、長くなるので省略しています。

add_action( 'init', function () {
	foreach ( ore2nari_taxonomies() as $taxonomy ) {
		$args = array(
			'labels'            => ore2nari_taxonomy_labels( $taxonomy['label'], $taxonomy['hierarchical'] ),
			'hierarchical'      => $taxonomy['hierarchical'],
			'public'            => true,
			'show_in_rest'      => true, // ブロックエディターで選べるようにする
			'show_admin_column' => true, // 投稿一覧に列を出す
			'rewrite'           => array(
				'slug'         => $taxonomy['slug'],
				'with_front'   => false,
				'hierarchical' => $taxonomy['hierarchical'],
			),
		);

		register_taxonomy( $taxonomy['slug'], $taxonomy['post_types'], array_merge( $args, $taxonomy['args'] ?? array() ) );
	}
} );

show_in_rest を true にしないと、ブロックエディターのサイドバーにタクソノミーが出てこないので注意です。

初期タームを登録する

ここがこの記事の本題です。設定の terms に書いたタームを、まだなければ登録します。

ポイントは、設定が変わったときだけ登録処理を走らせることです。毎回のページ表示で「このタームはある?」と DB に問い合わせるのはもったいないので、設定のハッシュを保存しておき、変わったときだけ登録します。

設定から初期タームを集める

すべてのタクソノミーの terms を、タクソノミーごとの配列にまとめます。slug か name が空の行は除きます。

ハッシュを比べる

まとめた配列のハッシュ(md5)を作り、前回保存したハッシュと同じなら、何もせずに終わります。

ないタームだけ登録する

同じスラッグのタームがなければ wp_insert_term() で登録します。すでにあるタームには触りません。

ハッシュを保存する

最後に今回のハッシュをオプションに保存します。次からは設定を変えるまで、登録処理は走りません。

add_action( 'init', function () {
	$definitions = array();
	foreach ( ore2nari_taxonomies() as $taxonomy ) {
		// slug・name が空の行(まだ埋めていない枠)は除く
		$terms = array_values( array_filter( $taxonomy['terms'] ?? array(), function ( $term ) {
			return '' !== trim( $term['slug'] ?? '' ) && '' !== trim( $term['name'] ?? '' );
		} ) );
		if ( $terms ) {
			$definitions[ $taxonomy['slug'] ] = $terms;
		}
	}

	$hash = md5( wp_json_encode( $definitions ) );
	if ( get_option( 'ore2nari_default_terms_hash' ) === $hash ) {
		return;
	}

	foreach ( $definitions as $taxonomy => $terms ) {
		foreach ( $terms as $term ) {
			if ( ! term_exists( $term['slug'], $taxonomy ) ) {
				wp_insert_term( $term['name'], $taxonomy, array( 'slug' => $term['slug'] ) );
			}
		}
	}

	update_option( 'ore2nari_default_terms_hash', $hash, true );
}, 20 );

タクソノミーの登録(優先度 10)より後に動くように、優先度を 20 にしています。登録前に wp_insert_term() を呼ぶと、タクソノミーがまだないので失敗します。

設定から消しても、タームは消えません

この仕組みは「ないものを足す」だけです。設定の terms から行を消しても、すでに登録されたタームは残ります。不要になったタームは、管理画面か WP-CLI で削除してください。

また、管理画面で削除したタームは、設定の terms を変えるまで復活しません(ハッシュが同じなので登録処理が走らないため)。

実際にこのブログでも、カテゴリーを見直して「お知らせ」を設定から外したとき、開発環境にはタームが残ったままになりました。消すときは手で消す、と覚えておくと迷いません。

タームの並び順を設定どおりにする

WordPress のタームは、標準では名前順に並びます。「開発」「制作の裏側」…と決めた順に並べたいので、設定の terms に書いた順に並べ替えるフィルターを付けます。

設定の順番を取り出す

タクソノミーごとに「スラッグ → 何番目か」の配列を作ります。何度も呼ばれるので、static で一度だけ作るようにしています。

function ore2nari_term_order( string $taxonomy ): array {
	static $orders = null;
	if ( null === $orders ) {
		$orders = array();
		foreach ( ore2nari_taxonomies() as $setting ) {
			if ( ! empty( $setting['terms'] ) ) {
				$orders[ $setting['slug'] ] = array_flip( array_column( $setting['terms'], 'slug' ) );
			}
		}
	}
	return $orders[ $taxonomy ] ?? array();
}

function ore2nari_sort_terms( array $terms ): array {
	// usort は同じ順位のものの順番を変えないので、terms に無いものはもとの順のまま後ろに並ぶ
	usort( $terms, function ( $a, $b ) {
		$order_a = ore2nari_term_order( $a->taxonomy )[ $a->slug ] ?? PHP_INT_MAX;
		$order_b = ore2nari_term_order( $b->taxonomy )[ $b->slug ] ?? PHP_INT_MAX;
		return $order_a <=> $order_b;
	} );
	return $terms;
}

設定にないターム(管理画面で追加したもの)は PHP_INT_MAX 扱いになるので、後ろにまとまります。

usort の並びについて

PHP 8.0 から、usort などのソートは「同じ順位の要素の順番を変えない」(安定ソート)ようになりました。設定にないターム同士は、もとの名前順のまま後ろに並びます。

get_the_terms と get_terms にフィルターを付ける

記事に付いたタームと、タームの一覧の両方に並べ替えをかけます。

// 投稿に付いたターム(get_the_terms()・get_the_term_list() など)
add_filter( 'get_the_terms', function ( $terms, $post_id, $taxonomy ) {
	if ( ! is_array( $terms ) || ! ore2nari_term_order( $taxonomy ) ) {
		return $terms;
	}
	return ore2nari_sort_terms( $terms );
}, 10, 3 );

// タームの一覧(get_terms()。管理画面のチェックボックスなども)
add_filter( 'get_terms', function ( $terms, $taxonomies, $args ) {
	// 並べ方を指定しているとき、WP_Term 以外を取るときは何もしない
	if ( 'name' !== $args['orderby'] || 'all' !== $args['fields'] ) {
		return $terms;
	}

	// 件数を区切って取るときは、全部が1ページに収まっているときだけ並べ替える
	if ( ! empty( $args['offset'] ) || ( ! empty( $args['number'] ) && count( $terms ) >= $args['number'] ) ) {
		return $terms;
	}

	foreach ( (array) $taxonomies as $taxonomy ) {
		if ( ore2nari_term_order( $taxonomy ) ) {
			return ore2nari_sort_terms( $terms );
		}
	}
	return $terms;
}, 10, 3 );

get_terms のほうは、件数を区切って取っているときに並べ替えないのがポイントです。ページをまたいで取るときに1ページの中だけ並べ替えると、ページ間で順番が崩れてしまいます。

ブロックエディターのチェックボックスはどう取っている?

編集画面のカテゴリーのチェックボックスは、REST API で件数(100件)を区切ってタームを取っています。そのため、タームが100件未満で1ページに収まっているときだけ並べ替える、という条件にしています。カテゴリーが100件を超えることはまずないので、実際にはほぼ並べ替えが効きます。

同じ仕組みはパーマリンクの更新にも使える

「設定のハッシュを保存して、変わったときだけ処理する」という形は、ほかにも使えます。たとえば、カスタム投稿タイプやタクソノミーの定義が変わったときだけ、パーマリンク(リライトルール)を作り直す処理です。

add_action( 'init', function () {
	$hash = md5( wp_json_encode( array( ore2nari_post_types(), ore2nari_taxonomies(), ore2nari_use_default_post() ) ) );
	if ( get_option( 'ore2nari_rewrite_hash' ) === $hash ) {
		return;
	}

	flush_rewrite_rules( false );
	update_option( 'ore2nari_rewrite_hash', $hash, true );
}, 99 );

管理画面の「パーマリンク設定」を保存し直すのと同じことが、本番にデプロイしたときにも自動で行われます。「本番でタクソノミーのページが 404 になる」というよくあるつまずきも防げます。

よくある疑問

設定のタームの名前を変えたら、登録済みのタームの名前も変わりますか?

変わりません。スラッグで「すでにあるか」を確かめて、あるものには触らないからです。名前を変えたいときは、管理画面で変更してください。

管理画面で追加したタームはどうなりますか?

普通に使えます。並び順は、設定に書いたタームの後ろに、名前順で並びます。

プラグインで並べ替えているサイトに後から入れても大丈夫ですか?

並べ替えのプラグインと同時に使うと、どちらの並び順が効くかわかりにくくなります。入れるなら、どちらか一方にするのがおすすめです。

おわりに

初期タームや並び順は、一度作ってしまえば頻繁に変えるものではありません。だからこそ、管理画面の手作業やプラグインに任せるより、コードに書いて Git で管理しておくと安心だなと感じています。設定の配列をひとつ直すだけで、ローカルでも本番でも同じ状態になるのは、地味ですがかなり楽です。

プラグインを入れる前に「これ、テーマで書けないかな?」と一度考えてみると、サイトがすっきりしますよ。

お仕事のご相談

WordPress のカスタマイズのような制作・改修のご相談も受け付けています。お気軽にどうぞ。

//サイト内を検索

制作実績・ブログ・お知らせから探します。キーワードはスペースで区切って複数入れられます。