【WordPress】多階層のカテゴリーを持つ投稿ページのパンくずリストを正しく出力する方法

皆さん、こんにちは~☆ シアです。

前に公開された「【脱プラグイン作戦】WordPressのパンくずリストを自作してみよう」の続きで、

今回は、WordPressで多階層のカテゴリーを持った投稿ページのパンくずリストを正しく出力する方法を紹介します。

1. get_the_category関数の課題点

WordpPressのget_the_category関数は、記事のIDを引数にその記事のカテゴリー一覧を取得するのでしたね。

// 引数には記事のIDを指定します。
$categories = get_the_category( $post->ID );

例えば、「paizaオンラインハッカソン(POH)・Liteで動的計画法を復習する」(「プログラミング」→「C#」に所属)では、カテゴリーは以下のように並んでいます。

Array (
	[0] => WP_Term Object (
		[term_id] => 5,
		[name] => C#,
		[slug] => cs,
		[term_group] => 0,
		[term_taxonomy_id] => 5,
		[taxonomy] => category,
		[description] => プログラミングの内、C#に関するコンテンツです。,
		[parent] => 3,
		[count] => 9,
		[filter] => raw,
		[cat_ID] => 5,
		[category_count] => 9,
		[category_description] => プログラミングの内、C#に関するコンテンツです。,
		[cat_name] => C#,
		[category_nicename] => cs,
		[category_parent] => 3
	),
	[1] => WP_Term Object (
		[term_id] => 3,
		[name] => プログラミング,
		[slug] => programing,
		[term_group] => 0,
		[term_taxonomy_id] => 3,
		[taxonomy] => category,
		[description] => プログラミングに関するコンテンツです。,
		[parent] => 0,
		[count] => 28,
		[filter] => raw,
		[cat_ID] => 3,
		[category_count] => 28,
		[category_description] => プログラミングに関するコンテンツです。,
		[cat_name] => プログラミング,
		[category_nicename] => programing,
		[category_parent] => 0
	)
)

しかし、その配列の順番には特に決まりがないので、必ずしも子孫カテゴリー→祖先カテゴリーの順に並ぶとは限りません。単に配列を反転したものを列挙しただけでは意図しないパンくずリストが生成されてしまいます。

例えば、本サイトでリリースしている「すぱこみっく!」系の記事(「アプリ開発」→「モバイルアプリ」に所属)では、カテゴリーは以下のように並んでいます。

Array (
	[0] => WP_Term Object (
		[term_id] => 46,
		[name] => アプリ開発,
		[slug] => app-development,
		[term_group] => 0,
		[term_taxonomy_id] => 52,
		[taxonomy] => category,
		[description] => アプリ(主にデスクトップ、モバイル)の開発に関するコンテンツです。,
		[parent] => 0,
		[count] => 24,
		[filter] => raw,
		[cat_ID] => 46,
		[category_count] => 24,
		[category_description] => アプリ(主にデスクトップ、モバイル)の開発に関するコンテンツです。,
		[cat_name] => アプリ開発,
		[category_nicename] => app-development,
		[category_parent] => 0
	)
	[1] => WP_Term Object (
		[term_id] => 216,
		[name] => モバイルアプリ,
		[slug] => mobile-app,
		[term_group] => 0,
		[term_taxonomy_id] => 217,
		[taxonomy] => category,
		[description] =>
		[parent] => 46,
		[count] => 7,
		[filter] => raw,
		[cat_ID] => 216,
		[category_count] => 7,
		[category_description] =>,
		[cat_name] => モバイルアプリ,
		[category_nicename] => mobile-app,
		[category_parent] => 46 
	)
)
Xiia08.png
あれ、カテゴリーの並びが先ほどのと逆です!(汗)

本サイトのように、カテゴリーの階層が2層までなら、親と子を判別して並び替えればよいのですが、3層以上のカテゴリーにも対応させるには、さらに工夫が必要となります。

2. 多階層の投稿ページのパンくずリストを正しく生成する

get_the_category関数の戻り値である配列(WP_Term)の各要素には、カテゴリーIDの他に1つ上の階層のカテゴリーIDを格納するcategory_parentプロパティが定義されています。今回はそのプロパティを使って、祖先のカテゴリーを辿っていきます。

Fig-1. 祖先カテゴリーの探索

まずは、array_column関数でWP_Termオブジェクトのcat_IDプロパティを連想配列にキーに設定し、連想配列を作成します。

$categories = get_the_category( $post->ID );
// カテゴリーリストからcat_IDをキーにした連想配列を作成します。
$category_map = array_column(
	$categories, null, 'cat_ID'
);

連想配列を列挙し、各要素のcategory_parentプロパティから1つ上の階層カテゴリーのIDを取得します。そのIDを連想配列のキーに指定して祖先のカテゴリーを取得し、子孫→祖先順に並び替えたカテゴリーリストを作成します。

作成したカテゴリーリストの要素数を比較し、最も多いものをパンくずリストとして使用します。その後にリストを反転して、親子順にソートします。

// 親子順に並び替えたカテゴリーリスト
$bread_list = array();
foreach( $category_map as &$category ) {
	$cur = $category;
	$tmp = array();
	while( true ) {
		// パンくずを追加
		$tmp[] = $cur;
		if( $cur->category_parent == 0 ) {
			break;
		}
		// 1つ上の階層のカテゴリーを取得
		$cur = $category_map[$cur->category_parent];
	}
	// パンくずリストの要素数が生成済みより新たに生成した方が多い時、後者を新たな生成済みのパンくずリストにします。
	if( count( $tmp ) > count( $bread_list ) ) {
		$bread_list = array_reverse( $tmp );
		if( count( $bread_list ) == count( $categories ) ) {
			break;
		}
	}
}
unset( $category );

あとは、ソート済みカテゴリーリストを列挙して、カテゴリーのパンくずリスト生成すればOK。

// カテゴリー部分のパンくずリスト
foreach( $bread_list as $bread ) {
	$bread_crumb.= '<span class="breadcrumb"><i class="fa fa-folder-open" aria-hidden="true"></i> <span itemscope itemtype="http://data-vocabulary.org/Breadcrumb"><a href="'.get_category_link( $bread->cat_ID ).'"  itemprop="url" ><span itemprop="title">'.$bread->cat_name.'</span></a></span></span>>';
}
// 記事のパンくず
$bread_crumb.= '<span class="breadcrumb"><span itemscope itemtype="http://data-vocabulary.org/Breadcrumb"><span itemprop="title">'.$post->post_title.'</span></span></span>';

これで、多階層の投稿ページのパンくずリストを生成するコードの完成です。

$bread_crumb = '';
if( !is_home() && !is_admin() ) {
	$bread_crumb.= '<div class="air-md-breadcrumbs clearfix">';
	$bread_crumb.= '<span class="breadcrumb"><strong><i class="fa fa-home" aria-hidden="true"></i> <span itemscope itemtype="http://data-vocabulary.org/Breadcrumb"><a href="'.home_url('/').'" class="home" itemprop="url"><span itemprop="title">ホーム</span></a></span></strong></span>>';
	
	if( is_single() ) {
		$categories = get_the_category( $post->ID );
		
		$category_map = array_column(
			$categories, null, 'cat_ID'
		);
		
		$bread_list = array();
		foreach( $category_map as &$category ) {
			$cur = $category;
			$tmp = array();
			while( true ) {
				$tmp[] = $cur;
				if( $cur->category_parent == 0 ) {
					break;
				}
				$cur = $category_map[$cur->category_parent];
			}
			if( count( $tmp ) > count( $bread_list ) ) {
				$bread_list = array_reverse( $tmp );
				if( count( $bread_list ) == count( $categories ) ) {
					break;
				}
			}
		}
		unset( $category );
		
		foreach( $bread_list as $bread ) {
			$bread_crumb.= '<span class="breadcrumb"><i class="fa fa-folder-open" aria-hidden="true"></i> <span itemscope itemtype="http://data-vocabulary.org/Breadcrumb"><a href="'.get_category_link( $bread->cat_ID ).'"  itemprop="url" ><span itemprop="title">'.$bread->cat_name.'</span></a></span></span>>';
		}
		
		$bread_crumb.= '<span class="breadcrumb"><span itemscope itemtype="http://data-vocabulary.org/Breadcrumb"><span itemprop="title">'.$post->post_title.'</span></span></span>';
	} 
	
	// ...	
}

こうすることで、get_the_category関数で取得したカテゴリーリストの並び順にかかわらず、パンくずリストを正しく出力できます。

3. おわりに

今回は、WordPressで多階層のカテゴリーを持った投稿ページのパンくずリストを正しく出力する方法を紹介しました。

パンくずリストをプラグインから独自実装に換装する時、投稿ページのカテゴリーの並びからリストを生成するのに苦労しましたが、PHPの実行環境でシミュレーションして試行錯誤し、無事解決することができました。

Xiia12.png
このコードなら、将来カテゴリーが3層以上の記事を書いてもイケます!

それでは、See you~☆

コメント

タイトルとURLをコピーしました