WordPress でカテゴリとタグを使った入れ子メニューを表示するメモ書き。
以前に、カテゴリとタグで絞り込める検索フォームを作ったのはいいですが、ぶっちゃけドロップダウンメニューって見難いよねってことでコレを改造して入れ子型のメニューリストを作ってみたいと思います。
完成はこんな感じです。

では早速コードから。
functions.php にベタ書きするか、このコードを nestmenu.php みたな別ファイルに切り出して functions.php に include してやる。今回は見通しの良さを考慮して後者を採用。
<?php
function nestmenu( $args ) {
$nestmenu = '';
if ( ! isset( $args['exclude'] ) ) {
$args['exclude'] = '';
}
$opt = array(
'hide_empty' => true,
'exclude_tree' => $args['exclude']
);
$cat_array = get_categories( $opt );
$nestmenu = "<div id='nestmenu'>\n";
$nestmenu .= "<ul class='nestmenu-category'>\n";
foreach ( $cat_array as $key => $cat ) {
$cat_name = $cat->name;
$cat_num = $cat->count;
$cat_link = get_category_link( $cat->term_id );
$tag_array = nestmenu_relation($cat->term_id);
$nestmenu .= "<li><h2><a href='$cat_link' title='$cat_name'>$cat_name ($cat_num)</a></h2></li>\n";
$nestmenu .= "<ul class='nestmenu-tag'>\n";
foreach ( $tag_array as $tags ) {
foreach ( $tags as $tag ) {
$tag_link = esc_url( home_url( '/' ) . '?cat=' . $cat->term_id . '&tag=' . $tag['slug'] );
$nestmenu .= "<li><a href='$tag_link' title='$tag[name]'>$tag[name] ($tag[count])</a></li>";
}
}
$nestmenu .= "</ul>\n";
}
$nestmenu .= "</ul>\n";
$nestmenu .= "</div>\n";
return $nestmenu;
}
add_shortcode( 'nestmenu', 'nestmenu' );
add_filter( 'widget_text', 'do_shortcode' );
function nestmenu_relation( $term_id = false ) {
if ( $term_id === false ) {
return $term_ids;
}
$taxonomy = get_terms(
array(
'category',
'post_tag'
),
array(
'hide_empty' => true,
'include' => $term_id
)
);
$taxonomy = $taxonomy[0]->taxonomy;
if ( ! is_null( $taxonomy ) ) {
$args = array(
'nopaging' => 1,
'ignore_sticky_posts' => 1,
'order' => 'ASK',
'tax_query' => array(
array(
'taxonomy' => $taxonomy,
'terms' => $term_id,
'field' => 'id',
'operator' => 'AND'
)
)
);
$my_query = new WP_Query( $args );
if( $my_query->have_posts() ) {
if ( $taxonomy === 'category' ) {
$taxonomy = 'post_tag';
} else {
$taxonomy = 'category';
}
while ( $my_query->have_posts() ) : $my_query->the_post();
$term = get_the_terms( get_the_ID(), $taxonomy );
if ( $term ) {
foreach ( $term as $value ) {
if ( ! isset( $term_ids['term'][$value->term_id] ) ) {
$term_info = array(
'id' => $value->term_id,
'name' => $value->name,
'slug' => $value->slug,
);
$term_ids['term'][$value->term_id] = $term_info;
}
$term_ids['term'][$value->term_id]['count'] += 1;
}
}
endwhile;
wp_reset_query();
}
if ( $term_ids['term'] ) {
$term_ids['term'] = array_merge( $term_ids['term'] );
foreach ( $term_ids['term'] as $key => $value ) {
$key_id[$key] = $value['count'];
}
array_multisort( $key_id, SORT_DESC, $term_ids['term'] );
}
}
return $term_ids;
}
?>
※ 関数名 nestmenu, nestmenu_relation が他とぶつかる場合は適時修正する。
※ 子カテゴリ等を使ってる場合等は動作未検証。
この入れ子メニューは下記のショートコードで呼び出せるので、テキストウィジェットに貼り付けて表示させます。
[[nestmenu]]
除外したいカテゴリがある場合は下記のように exclude にカテゴリ ID を指定。
[[nestmenu exclude=”1,10,100″]]
こんなイメージで入れ子メニューが表示されます。
- 全カテゴリの一覧を取得しループ (除外は除く)
- カテゴリに含まれるタグを取得 (投稿が 1 以上あるもの)
- アンカータグをつけてリスト表示
css は #nestmenu (全体を囲む div), .nestmenu-category (カテゴリの ul), .nestmenu-tag (タグの ul) 辺りを利用して調整する。
このブログはこんな感じの css でサイドウィジェットに表示しています。
/* nestmenu */
#nestmenu h2 {
font-size: 14px;
margin-top: 8px;
margin-bottom: 8px;
border-bottom: 1px dotted #dcdcdc;
}
#nestmenu ul {
list-style: none;
}
#nestmenu .nestmenu-category {
margin-left: 0;
}
#nestmenu .nestmenu-tag {
margin-left: 13px;
}
#nestmenu .nestmenu-tag li {
font-size: 13px;
}
カテゴリ、タグが沢山ある場合は、アコーディオン式のメニューにした方が見やすいかもしれないです。
簡単ですが、カテゴリとタグを使った入れ子メニューの表示はこれで以上です。
こちらとっても参考になりました。
ありがとうございます。
1点
Warning: Invalid argument supplied for foreach() in nestmenu.php on line 31
というエラーがでてしまったのですが、
こちらの対処方法はわかりますでしょうか?
何から何まで質問で申し訳ございません。
みぃ様
ご質問いただいた件、以下に回答します。
1. エラーについて
空の配列をループさせようとした時に出るエラーですね。
このプログラムは投稿記事に必ず、カテゴリとタグが設定されている事を想定して作った物なので、細かいエラー処理を入れていないのですがもしかするとそこが原因かもですね。
だいぶ前に作った物なので自分でも色々と失念していますが、恐らく、WordPress の使い方によってはエラーが起きてしまうのかなとも思います。
申し訳ありませんがこれはサンプルプログラムとしていただいて、実動作については個別で修正等の対応をしていただければと思います。
2. 任意の件数分だけタグを表示
別スレッドで頂いた件も合わせて回答します。
「任意の件数分」というのは、例えば、記事に設定された回数が多い順にタグを10個表示とかでしょうか。
勿論可能ですけど、これはカスタマイズ次第だと思います。
サンプルプログラムを改変するなりして、この要件を満たすように別途プログラミングする必要があると思います。
色々とありがとうございました!
こちらのコードを色々カスタマイズしてみようと思います。