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

 

画像を背景指定せずにCSSだけで正方形に切り抜く。

CMS案件だと、必然的に縦横比が異なる画像を扱うことが多い。wordpressの場合サムネイルがあるからあまり必要ないのだが、レスポンシブデザインで中サイズの画像を無理やり四角く切り抜きたいことがある。WP4.4からのsrcsetの画像切り替えも普通は比率が異なるものには適用されない。必要とする条件はこんな感じか。

・画像のサイズは不明
・縦横比はバラバラ
・背景画像に指定できない
・htmlはなるべく自然に

他所を調べてみてみると、これがまたいろんなやり方のバリエーションがあって面白いのだが、意外にもこれらの前提をクリアする選択肢がない印象。画像のサイズがわからないとダメだったり、画像の向きが指定できないとダメだったりする。(これとか、これとか)

その中で目からウロコだったのはline-heightとtext-alignを使用した方法で、完璧なセンタリングが可能になっている。でも画像をラップする要素が増えるので、ケースによっては使いづらそうだ。

で、今日打ち合わせの帰り道、ふと思いついたので、やってみたのが以下。シンプルな作りになったのでそれなりに使い出があるかも。

 

写真をdivの中央からカバーするサイズで配置する。

まずは写真の一般的な比率3:2を前提して考えてみる。
横向きの画像を中央で正方形にくりぬこうとすると、こんな感じになる。

crop_1

画像を中央に配置するには中央の正方形のDIVにoverflow:hiddenとposition:relativeを与えて、そこから外に対してtop,left,right,bottomを絶対指定し、画像のマージンをautoにする。こんな感じ。

crop_2

写真をdivに対して中央いっぱいに配置できればあとはoverflow:hiddenでクロップするだけ。

div { 
  width:160px;
  height:160px;
  overflow:hidden;
  position:relative;
}
div img { 
  max-width:140%;
  min-width:100%;
  width:auto;
  min-height:100%;
  max-height:140%;
  height:auto;
  position:absolute;
  top:-40%;
  right:-40%;
  bottom:-40%;
  left:-40%;
  margin:auto;
}

本当は上の説明通りmax幅は133.3333%ですが、表記の簡便さと余裕をみて140%にしてあります。マイナスポジションは50%とかある程度大きければナンボでもいいです。ここではなんとなく-40%。divの幅をパーセンテージ指定したい場合は、heightをautoにして、before要素を設定してwidth:100%とpadding-top:100%を指定してくださいね。

DEMO

実際にやってみるとこうなります。

crop_item1

crop_item2

縦画像だとこんな感じになります。ついでにradiusも効かせてみるともっとリアルになります。ついでにあり得るケースとしてクロップする正方形は元画像よりも大きくしてみました。(画像の横幅は200pxに対してdivは240px。)

crop_item2

crop_item2

上手く引き延ばされていますね。chromeとfirefoxとsafariでは。・・・件のブラウザは確かめてません。嫌な予感しかしませんね。
残念ながら、元画像が正方形の場合、無駄に大きく余白を取る形でクロップされてしまいます。まあ、応急処置的なcssということですね。

crop_item2

crop_item2
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に記載します。もう少しスマートにできそうな気もしますが…まあ機能してるしいいんです…。

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