bodyとpostクラスに所属カテゴリのスラッグを付与したい。

だいぶ前に「投稿が所属するカテゴリの先祖や子孫を取得するあれこれ。」という記事を書きましたがそこそこアクセスがあったので、実用レベルでよく使うアレンジも紹介しておきます。functions.phpに入れるだけで動きます。正確には、使用テーマのテンプレートでbody_classとpost_classを使用していれば動きます。

実際の出力例は以下のような感じです。

アーカイブページを表示中の場合

<body class=”archive category category-child category-99 logged-in admin-bar no-customize-support category-ancestor category-parent“>

投稿ページを表示中の場合

<body class=”single single-post postid-999 single-format-standard logged-in admin-bar no-customize-support category-child category-ancestor category-parent taxonomy-term“>

category-childが表示中のカテゴリだとすると、category-ancestor(先祖カテゴリ)やcategory-parent(親カテゴリ)も付与してくれるのがミソです。すべてのタクソノミに対応するようにしています。cssによるカスタマイズがしやすくなるのではないでしょうか。もちろんカスタムタクソノミにも対応です。

※ちなみに以下の関数はpage slugにも対応していますが、不要な場合は削除してください。すでに同じ機能の関数を設置している場合はそっちを削除するか、こっちに変更を加えてください。

CODE


/////////////////////// 投稿及びカテゴリページで所属カテゴリ・親カテゴリクラスを付与 ///////////////////////

function add_term_name_to_body_and_post_class( $classes ) {

if ( is_admin() ) return $classes; //管理画面を除く

if ( is_singular() ){ //シングル投稿タイプ

global $post;
$capability = get_post_type_object( $post->post_type )->capability_type;

if ( $capability == 'page' ){ // 固定ページ及びカスタム固定ページ // ページスラッグ付与が不要なら以下の分岐を削除

  $classes[] = $post->post_type.'-' . $post->post_name;
  if ($post->post_parent) $classes[] = $post->post_type.'-' . get_page_uri($post->post_parent) . '-child';

} elseif ( $capability == 'post' ){ // 投稿及びカスタム投稿タイプ

  $taxes = get_object_taxonomies( $post->post_type, 'names' );
  if ($taxes) : unset( $taxes['post_format'] );
  foreach ( $taxes as $taxname ) :
    $tax = get_taxonomy( $taxname );
    if ( !$tax || !$tax->hierarchical ) continue;
    $terms = get_the_terms( $post->ID, $taxname );
    if ( $terms ): foreach ( $terms as $term ) :
      $classes[] = $taxname. '-' .$term->slug;
      $parent = get_term_parent_slug_by_slug( $term->slug, $taxname );
      while( $parent ){ //loop
        $classes[] = $taxname. '-' .$parent;
        $parent = get_term_parent_slug_by_slug( $parent, $taxname );
      }
    endforeach; endif;
  endforeach; endif;

}

} elseif ( is_archive() ) {

  $term_slug = $taxname = '';
  if ( get_query_var( 'category_name' ) ){

    $term_slug = get_query_var( 'category_name' );
    $taxname = 'category';

  } elseif ( get_query_var( 'taxonomy' ) && get_query_var( 'term' ) ) {

    $term_slug = get_query_var( 'term' );
    $taxname = get_query_var( 'taxonomy' );

  }

  if ( $term_slug && $taxname ){

    $parent = get_term_parent_slug_by_slug( $term_slug, $taxname );
    while( $parent ){ //loop
    $classes[] = $taxname. '-' .$parent;
    $parent = get_term_parent_slug_by_slug( $parent, $taxname );
  }

}

}
return $classes;

}
add_filter( 'post_class', 'add_term_name_to_body_and_post_class' );
add_filter( 'body_class', 'add_term_name_to_body_and_post_class' );

 

sc-2015-01-25-13.23.51

投稿画面のカテゴリやタグのメタボックスを使いやすくするあれこれ

先日、wordpressの管理画面でのタグクラウドをデフォルトで表示する方法を書きましたが、同じくカテゴリーやカスタムタクソノミノーの表示をあれこれ変更する方法を書いておきます。CMSで納品するときなどには大事な心遣いだと思います。カスタムタクソノミに適用するときは、「category」となっているところをすべてタクソノミ名に変換します。

 

「よく使うもの」リンクとタブを消す。

よく使う項目を表示とか余計なお世話、というときは消してしまう。

div#category-pop, ul#category-tabs { display:none; }

「+ 新規カテゴリーを追加」リンクを消す

同じく、カテゴリを追加ボタンも投稿画面から触ってほしくないときは消してしまう。

div#category-adder { display:none; }

カテゴリメタボックスのmax-heightを解除

デフォルトのカテゴリメタボックスが高さが200pxを超えるとスクロール表示される。視認性を確保したいときはこの設定を消してしまう。

div#category-all { max-height:none; }

カテゴリのチェックボックスをラジオボタンに変換

var checklist = $( '#categorychecklist' );
checklist.each( function(){
    $( this ).find( 'input' ).attr( 'type', 'radio' );
    if ( $( this ).find( 'input' ).is(':checked') === false ) $( this ).children( 'li:first' ).find( 'input' ).prop('checked', true);
});

カテゴリをひとつしか選んでほしくないときは、jQueryでラジオボタンに変換します。

カテゴリのチェックボックスをセレクトボックスに変換

カテゴリの項目が多いときは、jQueryでセレクトボックスに変換して省スペース化を計るととても使いやすいです。

var options=[]; 
$( "#categorychecklist label" ).each(function(){ 
    options.push( '<option value="' + $(this).children("input").val()+ '">' + $(this).html().replace(/<input[^>]+>/g, '') + '</option>' ); 
});
$( "#categorychecklist" ).replaceWith( '<select id="categorychecklist" name="tax_input[category][]" style="width:100%; margin:10px 0; ">' + options.join( '' ) + '</select>' );

チェックボックスを並び替えないようにする

ついでに並び順のデフォルトが、チェックが入っているボックスが一番上にくるようになっていますので、それも外してみます。これはフックで。

add_filter( 'wp_terms_checklist_args', 'stop_checked_ontop' ,10,2); 
function stop_checked_ontop( $args, $post_id ){ 
    $args['checked_ontop'] = false; 
    return $args; //チェックボックスの並び替え停止
}

 

CODE : Hook to admin_head

タグクラウドのデフォルト表示もまとめたコードです。以下の関数をお好みでカスタマイズしてadmin_headフックしたものをfunctions.phpに記載します。当然ですが、このまま有効にすると、ラジオボタンに変換する関数とセレクトボックスに変換する関数とがコンフリクトします。

<?php
function my_admin_head(){ 
?>
<style type="text/css">
    /*  カテゴリ欄のタブ非表示  */ 
    /*  remove tabs from category metabox  */ 
    div#category-pop, ul#category-tabs { display:none; }

    /*  カテゴリ追加ボタン非表示  */ 
    /*  remove category add button from category metabox  */
    div#category-adder { display:none; }

    /*  カテゴリ欄を最大サイズに変更  */ 
    /*  unset max-height of category metabox  */
    div#category-all { max-height:none; }
</style>
<script type="text/javascript">
jQuery(function( $ ){ 

    /*  チェックボックスをラジオボタンに変換  */
    /*  change category check box into radio button  */
    var checklist = $( '#categorychecklist' );
    checklist.each( function(){
        $( this ).find( 'input' ).attr( 'type', 'radio' );
        if ( $( this ).find( 'input' ).is(':checked') === false ) $( this ).children( 'li:first' ).find( 'input' ).prop('checked', true);
    });

    /*  チェックボックスをセレクトボックスに変換  */
    /*  change category check box into select box  */
    var options=[]; 
    $( "#categorychecklist label" ).each(function(){ 
        options.push( '<option value="' + $(this).children("input").val()+ '">' + $(this).html().replace(/<input[^>]+>/g, '') + '</option>' ); 
    });
    $( "#categorychecklist" ).replaceWith( '<select id="categorychecklist" name="tax_input[category][]" style="width:100%; margin:10px 0; ">' + options.join( '' ) + '</select>' );

    /*  タグクラウドをデフォルトで表示に変更  */
    /*  automatically display the popular tags  */
    $( 'a.tagcloud-link' ).each( function(){ 
        tagBox.get( $(this).attr( 'id' ) ); 
        $(this).html('').unbind().click(function(){ 
            $(this).siblings( '.the-tagcloud' ).toggle(); return false; 
        }); 
        return false; 
    });
});
</script>
<?php 
}
add_filter( 'admin_head', 'my_admin_head' );

function stop_checked_ontop( $args, $post_id ){ 
    if ( $args['checked_ontop'] !== false ) $args['checked_ontop'] = false; 
    return $args; //チェックボックスの並び替え停止
} 
add_filter( 'wp_terms_checklist_args', 'stop_checked_ontop' ,10,2); 
?>

参考

ラジオボタンとチェックボックスのソート解除は以下を参考にしました。あとは見当たらなかったので自作。

Category checkbox list tree changes when editing a post

WordPressのカスタムタクソノミーのチェックボックスをラジオボタンにする方法(クイック編集でもチェックボックスの制限をしたい場合なども参考になります。)

投稿編集画面のタグクラウドを初期状態から開いたままにする。

要するにページが読み込まれると同時に「よく使われているタグから選択」をクリックした状態にするだけ。探したけどなかったので自作。けっこう重宝します。ちょっと前はsimple tagsプラグインで拡張するのが一般的だったけども、もはやクイックタグボタンいらない気がしますしね。

CODE : Automatically display the popular tags when the admin post-php loads.

function my_admin_head(){
echo
'<script type="text/javascript">
    jQuery(function( $ ){ 
        $("a.tagcloud-link").each( function(){
            tagBox.get( $(this).attr("id") );
            $(this).html("").unbind().click(function(){
                $(this).siblings(".the-tagcloud").toggle();
                return false;
            });
            return false;
        });
    });
</script>';
}
add_action('admin_head', 'my_admin_head');

唐突にtagBoxってなんやねん?って思うでしょうが、tags-box.jsで指定されているタグクラウドをajaxする際のコンテナ名です。当初jQueryでトリガーを試みたものの動かないので、明示的にtags-box.js内のファンクションを動作させるために上記のような不可解なコードになったわけです。
ちなみに、a.tagcloud-linkのところをa#link-(post_tag名)にすることで特定のタグ(カスタムタクソノミ)だけに適用できます。

その他

カテゴリのメタボックスの表示をラジオボタンにしたりセレクトボックスにする方法も併せて書いたのでよければ参考に。クライアントワークの場合、不要なメニューやら入力欄を整理しておくことは大事ですね、ユーザーに熟練してもらう前提で設計できないケースが多いですから。

wordpressで複数タクソノミによるPOSTの絞り込みの方法はそちこちで紹介されています。(個人的にはここが参照しやすいと思う。)こいつを、従来のカテゴリページのようにページングができる複数タクソノミのアーカイブページとして設置したい、というわけです。

multiple_tax

 

パーマリンクがないからパラメータでURLをカスタマイズする

例えばポストに次のような2種類のカスタムタクソノミを割り当てているとします。

taxonomy: color   terms: red / blue / green

taxonomy: form   terms: square / circle / triangle

普通にタクソノミーのテンプレートでは
//example.com/color/red
…とか
//example.com/form/square/page/2
…とかは表示できても、colorはredかつformはsquareみたいにできない。そもそもそんなパーマリンクが存在しないのだから、ページングできるわけがない。この問題を回避するためにURLクエリパラメータを与えることにします。
最終的なURLは
//example.com/items?color=red&form=square
…とか
//example.com/items/page/2?color=blue&form=circle
…のような感じになります。googleさんの検索結果ページと一緒ですね。

 


 

設置の順序

① まずはカスタムタクソノミを作ります。

カスタムタクソノミを$postかカスタム投稿タイプに割り当てます。(まずそこからという方はこちらとかを参照して下さい。)

② アーカイブページの代わりにするための固定ページを作成

自動的に作られるアーカイブページの代わりに適当な名前の固定ページを作成し、適当なページ用のテンプレートphpを用意します。(今ひとつ意味が分からない方はこちらとかが参考になるかもしれません。)WP_Queryを使ってカスタム投稿タイプのアーカイブを表示します。(※以下ではカスタム投稿タイプは「item」、固定ページのslugは「items」と設定したものとして表記していきます。)

<?php
	$args = array(
		'post_type' => 'item',
	);
	$wp_query = new WP_Query();
	$wp_query->query( $args );
	$li = '';
	while ( $wp_query->have_posts() ) : $wp_query->the_post(); 
?>
<div class="post">
	<a href="<?php echo get_permalink( $post->ID );?>" title="<?php echo get_the_title( $post->ID );?">
		<h2><?php the_title( ); ?></h2>
		<div class="excerpt"><?php the_excerpt( ); ?></div>
	</a>
</div>
<?php endwhile; ?>
<div class="page-navigation">
    <div class="older"><?php previous_posts_link( '&laquo; 前へ' ); ?></div>
    <div class="newer"><?php next_posts_link( '次へ &raquo;' ); ?></div>
</div>
<?php $wp_reset_postdata(); ?>

 

③ カテゴリのリンクを作成する

ここから本題です。②の前にカテゴリのリンクを作成します。

<?php 
	$this_url = get_permalink( $post->ID );
	$tax1 = 'color'; //TAX1に自由なタクソノミ名を設定
	$tax2 = 'form'; //TAX2に自由なタクソノミ名を設定
	if ( isset( $_GET[$tax1] ) ) $tax1_get = $_GET[$tax1]; else $tax1_get = 'all'; //URLパラメータを取得
	if ( isset( $_GET[$tax2] ) ) $tax2_get = $_GET[$tax2]; else $tax2_get = 'all'; //URLパラメータを取得
	$tax1_terms = get_terms( $tax1, '&hide_empty=true' );
	if ( $tax1_terms ){ 
		$current = '';
		if ( $tax1_get == 'all' ) $current = 'current';
		$tax1_term_items = "t" .'<li><a href="'.$this_url.'?'.$tax1.'=all&'.$tax2.'='.$tax2_get.'" title="View all '.$tax1.'" class="'.$current.'">All '.$tax1.'</a></li>'. "n";
		foreach( $tax1_terms as $tax1_term ){
			$current = '';
			if ( $tax1_get == $tax1_term->slug ) $current = 'current';
			$tax1_term_items .= "t" .'<li><a href="'.$this_url.'?'.$tax1.'='.$tax1_term->slug.'&'.$tax2.'='.$tax2_get.'" title="'.$tax1_term->name.'" class="'.$current.'">' .esc_html( $tax1_term->name ). '</a></li>'. "n";
		}
	}
	$tax1_term_items = '<ul id="tax1">' ."n". $tax1_term_items. '</ul>' ."n";
	echo $tax1_term_items;

	$tax2_terms = get_terms( $tax2, '&hide_empty=true' );
	if ( $tax2_terms ){ 
		$current = '';
		if ( $tax2_get == 'all' ) $current = 'current';
		$tax2_term_items = "t" .'<li><a href="'.$this_url.'?'.$tax1.'='.$tax1_get.'&'.$tax2.'=all" title="View all '.$tax2.'" class="'.$current.'">All '.$tax2.'</a></li>'. "n";
		foreach( $tax2_terms as $tax2_term ){
			$current = '';
			if ( $tax2_get == $tax2_term->slug ) $current = 'current';
			$tax2_term_items .= "t" .'<li><a href="'.$this_url.'?'.$tax1.'='.$tax1_get.'&'.$tax2.'='.$tax2_term->slug.'" title="'.$tax2_term->name.'" class="'.$current.'">' .esc_html( $tax2_term->name ). '</a></li>'. "n";
		}
	}
	$tax2_term_items = '<ul id="tax2">' ."n". $tax2_term_items. '</ul>' ."n";
	echo $tax2_term_items;
?>

※特定のタクソノミに絞られている環境で、好みのデザインを適用するイメージで書いてますので、ループさせずに似たような処理を繰り返していますが、不特定多数のタクソノミで絞り込んでいくようなカタログサイトだったらadd_query_argなどの関数を使ってループさせた方が吉です。

出力したカテゴリはたぶんこんな感じ↓になるはず。

<ul id="tax1">
	<li><a href="http://example.com/items?color=all&form=all" title="View all color" class="current">color</a></li>
	<li><a href="http://example.com/items?color=red&form=all" title="レッド" class="">red</a></li>
	<li><a href="http://example.com/items?color=blue&form=all" title="ブルー" class="">blue</a></li>
	<li><a href="http://example.com/items?color=green&form=all" title="グリーン" class="">green</a></li>
</ul>
<ul id="tax2">
	<li><a href="http://example.com/items?color=all&form=all" title="View all form" class="current">form</a></li>
	<li><a href="http://example.com/items?color=all&form=square" title="square" class="">square</a></li>
	<li><a href="http://example.com/items?color=all&form=circle" title="circle" class="">circle</a></li>
	<li><a href="http://example.com/items?color=all&form=triangle" title="triangle" class="">triangle</a></li>
</ul>

$_GET[ $tax ]でパラメータを取得し、表示されているカテゴリのaタグのclassにcurrentを付与してます。上はパラメータが何も付いていないか、allが選択されている場合です。もし現在選択されているcurrentがblueとtriangleだとすると、以下のように代入されます。
#tax1↓
href=”~/items?color=all&form=triangle” class=””
href=”~/items?color=red&form=triangle” class=””
href=”~/items?color=blue&form=triangle” class=”current
href=”~/items?color=green&form=triangle” class=””

#tax2↓
href=”~/items?color=blue&form=all” class=””
href=”~/items?color=blue&form=square” class=””
href=”~/items?color=blue&form=circle” class=””
href=”~/items?color=blue&form=triangle” class=”current

④ tax_Queryにパラメータを代入してやる

最後にWP_Queryのtax_queryにカスタムタクソノミのパラメータを放り込んでやる必要があります。②の$argsを下記のように補完してやります。

<?php
	$paged = get_query_var( 'paged' ) ? get_query_var( 'paged' ) : 1;
	$args = array(
		'post_type' => 'item', //自由なポストタイプを設定
		'paged' => $paged,
	);
	if ( $tax1_get !== 'all' ) { 
		$tax1_arg = array(
			'taxonomy' => $tax1,
			'field' => 'slug',
			'terms' => $tax1_get,
		);
	} else {
		$tax1_arg = '';
	}
	if ( $tax2_get !== 'all' ) { 
		$tax2_arg = array(
			'taxonomy' => $tax2,
			'field' => 'slug',
			'terms' => $tax2_get,
		);
	} else {
		$tax2_arg = '';
	}
	if ( !empty($tax1_arg) || !empty($tax2_arg) ){
		$args['tax_query'] = array(
			$tax1_arg, $tax2_arg
		);
	}
?>

 


 

PHPの記述まとめ

phpの記述をまとめると次のようになります。適当にカスタマイズして下さい。post_typeをpostにタクソノミ名をcategoryとpost_tagにすれば、従来の投稿でも使えます。

<?php 
	//カテゴリのリンクを作成する
	$this_url = get_permalink( $post->ID );
	$tax1 = 'color'; //TAX1に自由なタクソノミ名を設定
	$tax2 = 'form'; //TAX2に自由なタクソノミ名を設定
	if ( isset( $_GET[$tax1] ) ) $tax1_get = $_GET[$tax1]; else $tax1_get = 'all'; //URLパラメータを取得
	if ( isset( $_GET[$tax2] ) ) $tax2_get = $_GET[$tax2]; else $tax2_get = 'all'; //URLパラメータを取得
	$tax1_terms = get_terms( $tax1, '&hide_empty=true' );
	if ( $tax1_terms ){ 
		$current = '';
		if ( $tax1_get == 'all' ) $current = 'current';
		$tax1_term_items = "t" .'<li><a href="'.$this_url.'?'.$tax1.'=all&'.$tax2.'='.$tax2_get.'" title="View all '.$tax1.'" class="'.$current.'">All '.$tax1.'</a></li>'. "n";
		foreach( $tax1_terms as $tax1_term ){
			$current = '';
			if ( $tax1_get == $tax1_term->slug ) $current = 'current';
			$tax1_term_items .= "t" .'<li><a href="'.$this_url.'?'.$tax1.'='.$tax1_term->slug.'&'.$tax2.'='.$tax2_get.'" title="'.$tax1_term->name.'" class="'.$current.'">' .esc_html( $tax1_term->name ). '</a></li>'. "n";
		}
	}
	$tax1_term_items = '<ul id="tax1">' ."n". $tax1_term_items. '</ul>' ."n";
	echo $tax1_term_items;
	$tax2_terms = get_terms( $tax2, '&hide_empty=true' );
	if ( $tax2_terms ){ 
		$current = '';
		if ( $tax2_get == 'all' ) $current = 'current';
		$tax2_term_items = "t" .'<li><a href="'.$this_url.'?'.$tax1.'='.$tax1_get.'&'.$tax2.'=all" title="View all '.$tax2.'" class="'.$current.'">All '.$tax2.'</a></li>'. "n";
		foreach( $tax2_terms as $tax2_term ){
			$current = '';
			if ( $tax2_get == $tax2_term->slug ) $current = 'current';
			$tax2_term_items .= "t" .'<li><a href="'.$this_url.'?'.$tax1.'='.$tax1_get.'&'.$tax2.'='.$tax2_term->slug.'" title="'.$tax2_term->name.'" class="'.$current.'">' .esc_html( $tax2_term->name ). '</a></li>'. "n";
		}
	}
	$tax2_term_items = '<ul id="tax2">' ."n". $tax2_term_items. '</ul>' ."n";
	echo $tax2_term_items;
	//tax_Queryにパラメータを代入
	$paged = get_query_var( 'paged' ) ? get_query_var( 'paged' ) : 1;
	$args = array(
		'post_type' => 'item', //自由なポストタイプを設定
		'paged' => $paged,
	);
	if ( $tax1_get !== 'all' ) { 
		$tax1_arg = array(
			'taxonomy' => $tax1,
			'field' => 'slug',
			'terms' => $tax1_get,
		);
	} else {
		$tax1_arg = '';
	}
	if ( $tax2_get !== 'all' ) { 
		$tax2_arg = array(
			'taxonomy' => $tax2,
			'field' => 'slug',
			'terms' => $tax2_get,
		);
	} else {
		$tax2_arg = '';
	}
	if ( !empty($tax1_arg) || !empty($tax2_arg) ){
		$args['tax_query'] = array(
			$tax1_arg, $tax2_arg
		);
	}
	//クエリを出力
	$wp_query = new WP_Query();
	$wp_query->query( $args );
	while ( $wp_query->have_posts() ) : $wp_query->the_post();
?>
<div class="post">
	<a href="<?php echo get_permalink( $post->ID );?>" title="<?php echo get_the_title( $post->ID );?">
		<h2><?php the_title( ); ?></h2>
		<div class="excerpt"><?php the_excerpt( ); ?></div>
	</a>
</div>
<?php endwhile; ?>
<div class="page-navigation">
    <div class="older"><?php previous_posts_link( '&laquo; 前へ' ); ?></div>
    <div class="newer"><?php next_posts_link( '次へ &raquo;' ); ?></div>
</div>
<?php wp_reset_postdata(); ?>

wordpressの先祖&子孫タクソノミを取得する関数

wordpressを使い始めたころはカスタム投稿というものがなく、ブログもニュースも商品もカテゴリだけで分岐させていたから、カテゴリツリーは複雑な構成になりがちでした。カテゴリスラッグでCSSを充てたくて、子孫カテゴリや先祖カテゴリを調べる関数を見よう見まねで作りました。カスタムタクソノミに対応するように書き直したりしたものの、そもそも階層の深いカテゴリを使わなくなってしまい、お蔵入りに。カテゴリを複雑にするよりも、カスタム投稿タイプのが分かりよいからです。

で、welcart関連のカスタマイズを調べていたら【プラグインなしで直近のカテゴリーだけから関連商品(記事)を表示させる方法】という記事で、最下層カテゴリを取得するために子孫カテゴリーを持たないカテゴリーだけを抽出するくだりに行き当たりました。ネットショップだとカテゴリが複雑になりがちだから、まだ使えるかもしれない。(そんな大きな規模のネットショップとか作りたくないですけどね。)というわけで、何かの役に立つこともあるかと思いなおし、備忘録も兼ねてまとめておくことにしました。

(実用的な応用として、bodyクラスに投稿/カテゴリの所属スラッグをすべて付与する方法も書きましたのでどうぞ。)

 


 

子孫カテゴリを取得する

記事が所属するカテゴリのうち最下層のものだけを取得する

よくあるケースだと、同一カテゴリツリーの階層を複数選択している場合があります。丁寧なユーザーは中間カテゴリや最上位カテゴリにもチェックを入れてしまうことがあるからです。この場合、記事内でget_categoryしてもどのカテゴリが取得されるか分かりません。なので、記事が所属するカテゴリのうち最下層のものだけを取得するようにしてやります。

ちなみに複数ツリーに分岐してセレクトされているときは…

複数ツリーに分岐してカテゴリがセレクトされている場合は、最下層のカテゴリを配列で取得します。

cat_sample2_2

下記関数群をfunctions.phpに記載します

function get_term_descendants ( $post_id ='', $tax_name = 'category' ) {
  $terms = get_the_terms( $post_id, $tax_name );
  if ( empty( $terms )) return false; 
  $candidate = $terms;
  $count = count( $terms );
  if ( $count > 1 ): foreach( $terms as $key => $term ):
    foreach( $terms as $term2 ):
      if ( term_is_ancestor_of( $term->term_id, $term2->term_id, $tax_name ) ) { // 比較対象IDが親系の場合
        unset( $candidate[$key] ); // 比較対象IDを配列から削除
        break;
     }
    endforeach;
  endforeach; endif;
  return $candidate;
}

 

出力の仕方

get_term_descendants( $post->ID, ‘カスタムタクソノミ名’ )とすると、該当カテゴリの配列が返ってきます。カスタムタクソノミ名をなしにすると、通常のカテゴリが出力されます。

$descends = get_term_descendants(); 
if ( $descends ): foreach ( $descends as $descend ):
echo '<!-- タームID:'. $descend->term_id .' ターム名:'. esc_html( $descend->name ) .' タームスラッグ:'. $descend->slug .'-->'. "n";
endforeach; endif;

 

先祖カテゴリを取得する

最上層のカテゴリIDやスラッグを取得する場合

今度は逆に、カテゴリページを表示しているときに、その一番上の階層のカテゴリIDやスラッグを取得したいときがあります。カテゴリ毎にCSSでデザインを変更するときなどですね。(2015/02/04 … get_ancestorsという大変便利な関数の存在をスルーしていたので、シンプルに書き直しました。)

cat_sample3

関数は二組セットでget_term_ancestor_by_term()はカテゴリから祖先カテゴリを求める関数、get_term_ancestor()は投稿ループの中から所属カテゴリを取得するところからスタートし、関数内でget_term_ancestor_by_term()を呼び出しています。

下記関数群をfunctions.phpに記載します

function get_term_ancestor_by_term ( $sbjcat = '', $tax_name = 'category' ) { 
// [$sbjcat = カテゴリIDまたはスラッグ],
// [$tax_name = タクソノミ名]
    if ( empty( $sbjcat ) && is_category() ) {
        $sbjcat = get_query_var( 'cat' ); //カテゴリページ
    } elseif ( empty( $sbjcat ) && is_tax() ) {
        $sbjcat = get_query_var( 'term_id' );
        $tax_name = get_query_var('taxonomy'); //カスタムタクソノミページ
    } elseif ( !is_numeric( $sbjcat ) ) {
        $sbjcat = get_term_by( 'slug', $sbjcat, $tax_name )->term_id; //スラッグからIDを取得
    }
    if ( !$sbjcat ) return false; //カテゴリ取得不可

    $ancestors = array_reverse( get_ancestors( $sbjcat , $tax_name ) ); 
    if( isset( $ancestors[0] ) ) $ancestor_id = $ancestors[0]; else $ancestor_id = $sbjcat;
    return get_term( $ancestor_id , $tax_name );
}

function get_term_ancestor ( $post_id = '', $extree = '', $tax_name = 'category' ) { 
// [$post_id = 投稿ID],
// [$extree = 含めないカテゴリツリーのIDまたはスラッグ],
// [$tax_name = タクソノミ名]
    if ( empty ( $post_id ) ) $post_id = get_post()->ID;
    if ( $extree && !is_numeric( $extree ) ) $extree = get_term_by( 'slug', $extree, $tax_name )->term_id; 
    $terms = get_the_terms( $post_id, $tax_name );
    if ($terms) : foreach ( $terms as $current_term ) : 
        if ( $current_term->term_id !== $extree && !isset( $tia ) ) { // $extreeが親ではない場合
            $req_id = $current_term->term_id;
            break;
        }
    endforeach; endif;
    if ( $req_id ) return get_term_ancestor_by_term( $req_id, $tax_name );
    else return false;
}

 

 

カテゴリから先祖カテゴリを取得する場合

祖先は一つしか返さない仕様になっている(複数の祖先を求めるケースは考えにくいため)のでループする必要はありません。第二引数にカスタムタクソノミ名(省略時はカテゴリ)を指定できます。

$ancestor = get_term_ancestor_by_term ( get_query_var('cat') );
echo '<!-- タームID:'. $ancestor->term_id .' ターム名:'. esc_html( $ancestor->name ) .' タームスラッグ:'. $ancestor->slug .'-->';

 

投稿から先祖カテゴリを取得する場合

ほぼ同様です。ループの必要はありません。

$ancestor = get_term_ancestor( $post->ID );
echo '<!-- タームID:'. $ancestor->term_id .' ターム名:'. esc_html( $ancestor->name ) .' タームスラッグ:'. $ancestor->slug .'-->';

 

複数の先祖に属するカテゴリが設定されている投稿から特定の先祖カテゴリを取得する場合

投稿から先祖カテゴリを取得する場合、複数の先祖に跨がることがあります。

除外したいカテゴリツリーがあることも。たとえば、セール商品や更新情報などのサブカテゴリなどのケースが考えられます。

cat_sample3_2

この場合、「食べ物」か「飲み物」のどちらが取得されるかは、投稿IDの若さに左右されます。それでもいいのですが、特定のカテゴリを除外したい場合もあります。

出力の仕方

たとえば[nomimono]というカテゴリスラッグをもつカテゴリツリーを除外し、かつ先祖カテゴリを取得したい場合は以下の通り。第二引数に除外カテゴリ、第三引数にカスタムタクソノミ名(省略時はカテゴリ)を指定します。

$ancestor = get_term_ancestor( $post->ID, 'nomimono' );
echo '<!-- タームID:'. $ancestor->term_id .' ターム名:'. esc_html( $ancestor->name ) .' タームスラッグ:'. $ancestor->slug .'-->';

ちなみにこの関数でexcludeできるカテゴリID/slugはひとつだけです。複数のツリーを除外したければ適宜改変してください。

 


おまけ:第二階層の先祖カテゴリを取得する

ウェルカートの場合、第二階層のカテゴリが取得したい

サイトの構成によっては、第一階層のカテゴリよりも第二層の方が取得したいということもあります。たとえばウェルカートプラグインの場合、先祖カテゴリが自動的にitemになってしまいますので、分岐させたい場合、第二階層が欲しいです。

cat_sample4

下記関数群をfunctions.phpに記載します

この場合、[item]が取得されてはまずいので、[飲み物]カテゴリが取得されるようにする必要があります。で、やはり除外したいカテゴリツリーがある場合が想定されますすので、その処理も含めるようにします。

function get_welcart_ancestor_by_cat ( $sbjcat = '' ) { 
// [$sbjcat = カテゴリIDまたはスラッグ]
    if ( empty( $sbjcat ) ) 
        $sbjcat = get_query_var('cat'); //カテゴリページ
    elseif ( !is_numeric( $sbjcat ) ) 
        $sbjcat = get_term_by( 'slug', $sbjcat, $tax_name )->term_id;
    if ( !$sbjcat ) return false; //カテゴリ取得不可
    $ancestors = array_reverse( get_ancestors( $sbjcat , 'category' ) ); 
    if ( !$ancestors || get_term_by( 'term_id', $ancestors[0], 'category' )->slug !== 'item' ) return false; //非welcart
    if( isset( $ancestors[1] ) ) $ancestor_id = $ancestors[1]; else $ancestor_id = $sbjcat;
    return get_term( $ancestor_id , 'category' );
}

function get_welcart_ancestor ( $post_id = '', $extree = '' ) { 
// [$post_id = 投稿ID],
// [$extree = 除外するカテゴリツリーのIDまたはスラッグ]
    if ( !$post_id ) $post_id = get_post()->ID;
    $item_ancestor = get_term_by( 'slug', 'item', 'category' );
    $item_ancestor_id = $item_ancestor->term_id; //「アイテム」カテゴリのID
    if ( $extree && !is_numeric( $extree ) ) { 
        $get_extree = get_term_by( 'slug', $extree, 'category' );
        if ( $get_extree ) $extree = $get_extree->term_id; 
    }
    $cats = get_the_terms( $post_id, 'category' );
    if ($cats): foreach ( $cats as $current_cat ) :
        if ( $current_cat->term_id !== $extree && !term_is_ancestor_of ( $extree, $current_cat->term_id, 'category' ) && $current_cat->term_id !== $item_ancestor_id ) {
        // $extreeではないカテゴリで、$extreeが親ではないカテゴリ、かつitemではないカテゴリ、かつuncategorizedではないカテゴリ
            $sbjcat = $current_cat->term_id;
            break;
        }
    endforeach; endif;
    if ( empty( $sbjcat ) ) return $item_ancestor; // 該当カテゴリがない場合、「アイテム」カテゴリを返す
    return get_welcart_ancestor_by_cat( $sbjcat );
}

 

商品シングルテンプレートから呼び出す場合

特定の[sale]というスラッグを持つ商品カテゴリとその子孫を除いて、先祖カテゴリを取得したい場合の出力方法は以下のようになります。

$ancestor = get_welcart_ancestor( $post->ID, 'sale' ); 
echo '<!-- タームID:'. $ancestor->term_id .' ターム名:'. esc_html( $ancestor->name ) .' タームスラッグ:'. $ancestor->slug .'-->';

 

商品カテゴリテンプレートから呼び出す場合

$ancestor = get_welcart_ancestor_by_cat( get_query_var('cat') ); 
echo '<!-- タームID:'. $ancestor->term_id .' ターム名:'. esc_html( $ancestor->name ) .' タームスラッグ:'. $ancestor->slug .'-->';

 

abstract13

関連度に応じた関連記事を表示する

お題の通り、wordpress用の自作の関連記事表示ファンクションです。精度の高さを求めるなら記事内容に踏み込んだプラグイン(YARPPとか)を使うのが基本です。しかしそういったプラグインを回避したいこともある。その場合、タグで記事を表示したり、カテゴリ毎の新着記事を出力したりで充分なケースもあるが、やはり関連性という点でもの足らず、どうしても古い記事が埋もれてしまいがちに。で、ここで紹介する方法は、既存のプラグイン未満だけどもタグとタクソノミの重複度で関連度合いの評価を加えたものです。このページの下で使っています。

 

主な特徴

関数で処理するメリットとしてはやっぱりアレンジが容易いことです。主な特徴としては以下の通り。

カスタム投稿タイプやカスタムタクソノミに対応

タグ・カテゴリ・カスタムタクソノミの重複回数に応じて関連度を計測

カスタムフィールドに関連度の高い記事のIDを登録

ちなみに、デフォルトではタグが他のタクソノミよりも優先的に点数付けされる設定にしていますが、カスタムタクソノミなどケースバイケースで設定してください。

 

CODE : Related post IDs in a custom field

以下の関数に任意にアレンジを加えてfunctions.phpに記述します。

 

/*
* plugin name: Related post IDs in a custom field
* author: Mizuho Ogino
* author URI: //web.contempo.jp/
* license: //www.gnu.org/licenses/gpl.html GPL v2 or later
*/
/////////////////////// タグとカテゴリによる関連記事の表示 /////////////////////// 

function get_related_posts( $show_post = 5, $target_posttype = '', $post_id = ''){
    if ( empty( $post_id ) ) $post_id = get_post()->ID;
    if ( empty( $post_id ) ) return;
    if ( empty( $target_posttype ) ) $target_posttype = get_post( $post_id )->post_type;
    $ex_show_post = get_post_meta( $post_id, '_my_relatedposts_shownum', true ); // 関連記事の表示数
    $rel_date = get_post_meta( $post_id, '_my_relatedposts_update', true ); // 関連記事の登録日
    $modified_date = mysql2date( 'Ymd', get_lastpostmodified(), false ); // ブログの最終更新日
    $posts_per_page = -1; // 検索するpost数  [-1] で全て
    //以下、 関連記事が登録されていない場合 || 関連記事登録日よりもあとに記事が更新されている場合 || 表示関連記事数の設定が変わった場合、のみ新規で関連記事を設定する
    if( empty($rel_date) || $modified_date > $rel_date || $show_post != $ex_show_post ){

        $rel_ids = array();
        $taxes = get_object_taxonomies( get_post( $post_id )->post_type, 'names' ); // 使用タクソノミを取得(カテゴリ+カスタムタクソノミ)
        unset( $taxes['post_format'] ); // post_formatを除外
        $tax_count = 0;
        $tax_count = count($taxes);
        $tax_array = array_fill( 0, $tax_count, '' );
        if ($taxes) { 
            foreach ( $taxes as $taxname ) {
                 $terms = get_the_terms( $post_id, $taxname );
                if ( $terms ){
                    foreach ( $terms as $term ) {
                        $rel_ids[ $taxname ][] = $term->term_id; // タームIDを配列に入れる
                    }
                    $tax_array[] = array(
                        'taxonomy' => $taxname, // categoryとかpost_tagとか
                        'field' => 'id', // 'id' または 'slug'
                        'terms' => $rel_ids[ $taxname ], // int または string または array
                        'include_children' => false, // 子カテゴリを含まない
                        'operator' => 'IN',
                    );
                } 
            }
        }
        $args = array(
            'post__not_in' => array( $post_id ), // このpostを除外
            'posts_per_page' => $posts_per_page, // 検索するpost数
            'post_status' => 'publish', // 公開postに限定
            'post_type' => $target_posttype, // ターゲットポストタイプ
            'tax_query' => array_merge( array('relation' => 'OR'), $tax_array )
        );
        $rel_query = get_posts( $args );
        if ( $rel_query ) : foreach ($rel_query as $rel ):
            $rel_point = 0;
            $set_id = $rel->ID;
            foreach ( $taxes as $taxname ) {
                 $terms = get_the_terms( $set_id, $taxname );
                if ( is_array($terms)) : foreach ( $terms as $term ):
                    if ( isset( $rel_ids[ $taxname ]) && in_array( $term->term_id , (array)$rel_ids[ $taxname ] ) ){ // 関連IDを含むかチェック
                        $rel_point++; 
                        if( $taxname == 'post_tag' ){ $rel_point++; } // ポストタグを他タクソノミより優先するようにポイントを補正 必要に応じてコメントアウトや変更をして下さい。
                    }
                endforeach; endif; // end $terms foreach
            } // end $taxes foreach
             $rel_with[ $set_id ] = intval($rel_point); // 関連度数
        endforeach; endif; 
        if ( !empty( $rel_with ) ) {
            arsort( $rel_with ); // 関連度数でソート
            $i = 0;
            foreach ( $rel_with as $key => $val ) { 
                if( $i >= $show_post ) { 
                    break;
                } else {
                    $rel_posts[] = $key;
                    echo '<!-- POST ID:'.$key. ' / RELATION POINT:' .$val. ' -->' ."n";
                    $i++;
                }
            }
            update_post_meta( $post_id, '_my_relatedposts', $rel_posts );
            echo '<!-- RELATED POSTS FOR POST-'.$post_id. ' ARE UPLOADED!! -->' ."n";
        } else {
            $no_related = false;
            update_post_meta( $post_id, '_my_relatedposts', $no_related );
        }
        $timestamp = date( 'Ymd' );
        update_post_meta( $post_id, '_my_relatedposts_update', $timestamp );
        update_post_meta( $post_id, '_my_relatedposts_shownum', $show_post );
    }
    $return_array = get_post_meta( $post_id, '_my_relatedposts', true );
    return $return_array;
}

関連記事IDをカスタムフィールドに登録しているので、通常表示には処理時間はかかりません。記事の新規作成があったときに関連記事が再設定されるため、そのページを訪れた最初のユーザーにだけ処理が発生します。関数の冒頭にある[ $modified_date ]は[ 20140430 ]のような日付型になっていますので、ブログの更新頻度が高い場合、更新後一ヶ月以上経っていたら関連記事も更新する[ $modified_date = mysql2date( ‘Ymd’, get_lastpostmodified(), false ) – 30; ]とすればかなりの程度負担が調整できます。また[ $posts_per_page = -1; ]を[ $posts_per_page = 200; ]としたりして新しい記事限定にすれば、検索にかかる時間が短縮できます。(wp_cronの使用に支障のある環境だったので上記のような手法で設計していますが、問題なく使える環境であればcronですべての投稿を処理してしまっても良いと思います。)

 

テーマ内で呼び出す方法

get_related_posts()という関数で呼び出すと、IDが配列で得られます。このサイトでは以下のように呼び出しています。

<?php
$related_posts = get_related_posts(5);
   $related_links = "t". '<ul>' ."n"; 
if ( $related_posts ):
  foreach( $related_posts as $rid ):
    $related_links .= "tt". '<li id="related-post-'.$rid.'"><a href="'.get_permalink( $rid ).'">' .get_the_title( $rid ). '</a></li>'. "n";
  endforeach; 
    $related_links .= "t". '</ul>' ."n";
?>
<!--関連記事リスト START-->
<div id="related-posts">
  <strong>Related posts</strong>
<?php echo $related_links; ?>
</div><!--関連記事リスト END-->
<?php else: ?>	
関連記事がない場合の処理
<?php endif; ?>