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' );

 

check-a-tag-has-an-image-or-not1

WORDPRESS投稿内のリンクに独自クラスを付与し、cssでのテキスト装飾やライトボックス系のJSプラグインを扱いやすくする

cssでテキストを装飾したり、ライトボックス系のJSプラグインの設定する方法はいくつかあります。ブログの管理者がある程度ウェブ馴れしているなら、image_send_to_editorフックを用いて記事作成時にクラスを与える(※外部リンク)方法がシンプルでよいと思います。しかし、CMSなどの納品したブログでは、どこからかコピペしてきた謎のHTMLを挿入されてまんまとレイアウト崩れを起こす…なんてこともしばしば。だったらもういっそ、すべての投稿に含まれる画像やリンクにまとめてクラスをつけてしまおうと、そういうわけです。具体的には、the_contentで投稿にフックし、正規表現でその中からリンクを拾い、画像リンクかテキストリンクのどちらなのか、さらにリンク先が画像かどうかを明示するクラスを挿入しています。これならwordpressのエディタを使って書いていない記事にも対応できます。

出力した感じはざっくりとこんなですね。

<p>テキストテキストテキスト。</p>
<p><a class="text-link" href="http://example.com">おすすめのウェブサイトです。</a></p>
<p><a class="image-link" href="http://example.com"><img src="/screenshot.jpg"></a></p>
<p>テキストテキストテキスト。</p>
<p><a class="text-link lightbox" href="/image.jpg">画像を拡大表示します。</a></p>
<p><a class="image-link lightbox" href="/image2.jpg"><img src="/image2s.jpg"></a></p>
<p>テキストテキストテキスト。</p>

 

CODE: Link item CSS class filtering

<?php
//Check the content has an <a> tag with an image or not, and add class to it. 
function linkclass_filter( $content ) {
    global $post;
    preg_match_all('/<a.*?</a>/i', $content, $matches, PREG_SET_ORDER);
    foreach ($matches as $def_link) {
        $link = $def_link[0];
        if ( preg_match('/<a.*?>.*?<img.*?>.*?</a>/i', $link) ) { //画像を含むとき
            $link = preg_replace('/<img(.*?)src=['"]([^'"]+).(bmp|gif|jpeg|jpg|png)['"](.*?)>/i', '<img$1src="$2#p#$3"$4>', $link); //画像srcのエスケープ
            $link = preg_replace('/<img(.*?)class=(.*?)>/i', '<img$1cl#a#ss=$2>', $link); //画像classのエスケープ
            $linkclass = 'image-link'; //画像リンクにつけるクラス
        } else {
            $linkclass = 'text-link'; //テキストリンクにつけるクラス
        }
        if ( preg_match('/<a.*?href=['"]([^'"]+).(bmp|gif|jpeg|jpg|png)['"].*?>/i', $link) ) { //リンク先が画像のとき
            $linkclass .= ' lightbox'; //ライトボックスなどのjs用のクラス
            $linkrel = ' data-group="lightbox-'.$post->ID.'"'; //任意のデータやrelを持たせる処理
        } else {
            $linkrel = '';
        }
        if ( preg_match('/<a.*?class=.*?href=.*?>.*?</a>/i', $link) || preg_match('/<a.*?href=.*?class=.*?>.*?</a>/i', $link) ) {//aタグにclassがあるとき
            $link = preg_replace('/<a(.*?)class="(.*?)"(.*?)>/i', '<a$1class="'.$linkclass.' $2"$3'.$linkrel.'>', $link);  //aタグにclassを追加
        } else {
            $link = preg_replace('/<a(.*?)>/i', '<a class="'.$linkclass.'"$1'.$linkrel.'>', $link); //aタグにclassを付与
        }
        $link = str_replace( array('#p#','#a#'), array('.','a'), $link); //エスケープ解除
        $content = str_replace( $def_link[0], $link, $content );
    }
    return $content;
}
add_filter('the_content','linkclass_filter');
?>

 

設置方法

上記をfunctions.phpに記載します。もう少しスマートにできそうな気もしますが…まあ機能してるしいいんです…。

PDF image generatorという拙作プラグインをwordpress.orgに公開しているけれど、そこのフォーラムに「フロントエンドからの投稿では添付ファイルの親投稿が指定されないから、プラグインをカスタマイズする方法を希望うんたら」という、質問があった。プラグインとは関係がなさそうだったけども、つい流れで答えてしまったのでコードだけここにメモしておきます。私自身はなかなか会員制サイトとか作る機会はなさそうですが、万が一役に立つかもしれないしね。

たとえばこんな感じで投稿欄をページテンプレートに設置されている場合の話。

<style type="text/css">
    #__wp-uploader-id-0 a.media-button-insert { font-size:0; }/*#__wp-uploader-id-0 == .media-frame*/
    #__wp-uploader-id-0 a.media-button-insert:before { display:block; content:'記事に挿入する'; font-size:15px; }
</style>
<form action="<?php the_permalink();?>" id="<?php echo $post->ID; ?>" method="POST">
<?php

if ( isset( $_POST['editor_id'] ) ) {
    $post_information = array(
        'post_title' => 'test',
        'post_content' => $_POST['editor_id'],
        'post_type' => 'post',
        'post_status' => 'pending'
    );
    wp_insert_post( $post_information );
}

wp_enqueue_media( array( 'post' => $post->ID )); // upload file to submitpage

$editor_id = 'editor_id';
$args = array(
'textarea_rows' => 15,
'quicktags' => false,
'textarea_name' => $editor_id,
'media_buttons' => true,
'drag_drop_upload' => true,
);

wp_editor( '','editor_id', $args );
?>
<button type="submit"><?php _e('Add Post') ?></button>
</form>

ここで大事な箇所は wp_enqueue_media( array( ‘post’ => $post->ID ))というところ。新しく作成される記事にはIDがまだ存在していないので、アップロードしたメディアを投稿フォームの設置されたページのIDに一旦添付します。そして、以下のようにsave_postフックで投稿処理中に、添付先IDを新しい記事に差し替えます。

<?php

function tempo_id_for_frontend( $attachment_id ){ // run after an attachment is added to the DB
    global $post;
    $user_id = $_SERVER["REMOTE_ADDR"]; // get_current_user_id();
    $submit_id = get_page_by_path( 'your_submit_page_slug' )->ID; // set the page slug of the submit page
    $attachment = get_post( $attachment_id );
    if ( $attachment->post_parent == $submit_id && $user_id ){
        $tempo_ids = get_post_meta( $submit_id, '_id_for_frontend', true); // set attachment id in the customfield of the submit page
        if( !$tempo_ids ) $tempo_ids = array();
        $tempo_ids = $tempo_ids += array( $attachment_id => $user_id );
        update_post_meta( $submit_id, '_id_for_frontend', $tempo_ids );
    }
    return $attachment_id;
}
add_filter( 'add_attachment', 'tempo_id_for_frontend', 99, 2 );

function save_post_for_frontend ( $post_id ) {
    if ( !is_admin() ){
        $user_id = $_SERVER["REMOTE_ADDR"]; // wp_get_current_user()->ID;
        $submit_id = get_page_by_path( 'your_submit_page_slug' )->ID; // set the page slug of the submit page
        if( get_post_type( $post_id ) === 'post' && $user_id ){ 
            $tempo_ids = get_post_meta( $submit_id, '_id_for_frontend', true);
            if( $tempo_ids ) : foreach ($tempo_ids as $key => $val ) : // pick attachment ids from the customfield
                if ( $val == $user_id ){
                    $at_post = array();
                    $at_post['ID'] = $key;
                    $at_post['post_parent'] = $post_id;
                    wp_update_post( $at_post );
                }
                unset( $tempo_ids[$key] );
            endforeach; endif;
            if( $tempo_ids ) update_post_meta( $submit_id, '_id_for_frontend', $tempo_ids );
            else delete_post_meta( $submit_id, '_id_for_frontend' );
        }
    }
}
add_action( 'save_post', 'save_post_for_frontend' );

?>

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 .'-->';