WordPress Logo WordPress パンくずリストの作成

プラグインを使わずに functions.php に記述してパンくずリストを作成する方法と JSON-LD で構造化マークアップを出力する方法の覚え書きです。

以前ブログに掲載したパンくずリストを出力するコードを更新・修正()したものと、それを基に JSON-LD で構造化マークアップを出力するコードを掲載しています。

[追記] 2019年07月10日 コードの内容を一部変更しました。

更新日:2022年03月13日

作成日:2019年07月04日

JSON-LD で構造化マークアップ

以下は JSON-LD での構造化マークアップの例です。

対象としているページの種類は以下になります。関数を呼び出すフィルターに条件分岐タグを使えば、対象のページを限定することが可能です(条件分岐タグで出力するページを限定)。

表示する項目のオプションはパンくずリストの関数とほぼ同じです。パンくずリストの関数と一緒に使う場合は、オプションを合わせるようにすると良いかと思います。

  • ホーム(オプションで対象外に指定可能)
  • カスタムタクソノミーアーカイブページ
  • カテゴリーアーカイブページ
  • カスタム投稿タイプアーカイブページ
  • カスタム投稿タイプ個別記事ページ
  • 個別投稿ページ
  • 固定ページ
  • タグのアーカイブページ

以下がコードで、functions.php に記述します。

/*** パンくずリスト JSON-LD 構造化マークアップ my_json_ld_breadcrumbs 2019/07/14 updated ***
  * 2019/07/14 カスタム投稿タイプにカスタム分類が登録されていない場合に対応(183行目追加)
  *
  * [注意]この関数は別途 get_deepest_term()   が必要です。
  *
  * カスタムフィールド myterm を使って優先するカテゴリーやタームを指定可能に 2019/07/10
  *
  * パラメータ $args :引数の連想配列(全てオプション)。以下概要。
  * show_home ホームの場合にホームの構造化マークアップを出力するかどうか。初期値 true(出力する)
  * show_cpta カスタム投稿タイプ個別ページでアーカイブページを出力するかどうか。 初期値 true(出力する)2019/07/06 Added
  * home ホームの文字列。初期値:Home
  * blog_home 管理画面のホームページの表示で「固定ページ」→「投稿ページ(メインブログページ)」に
  * 指定したページで出力する文字。初期値:Blog
  * cat_off 個別ページでカテゴリーを出力しない。初期値: false(出力する)
  * cat_parents_off 個別ページで親のカテゴリーを表示しない。初期値: false(表示する)
  * tax_off 個別ページでタクソノミー(ターム)を出力しない。初期値:false(出力する)
  * tax_parents_off カスタム投稿タイプ個別ページで親のタームを表示しない。初期値:false(表示する)
  * extra_top_id 先頭(ホームの前)に更に階層を追加する場合に"name"に出力する名前を指定。 初期値:''(追加しない)
  * extra_top_url 先頭(ホームの前)に更に階層を追加する場合に"@id"に出力する URL を指定。 初期値:''(追加しない)
  * show_cat_tag_for_cpt カスタム投稿タイプ個別ページでカテゴリーを表示する(その場合は、カスタムタクソノミーは表示されない)。初期値:false(表示しない)
  * show_terms カスタム投稿タイプでタームを表示
  * 記事が複数のカテゴリーやタームに属する場合、カスタムフィールド(myterm)を使って優先するカテゴリーやタームを指定可能
  * カスタム投稿タイプに複数のカスタムタクソノミーが登録されている場合、カスタムフィールド(my_pref_tax)を使ってタクソノミーを指定可能 
  
*/
function my_json_ld_breadcrumbs($args = array()){
  global $post;
  // デフォルトの値
  $defaults = array(
    'show_home' => true, 
    'show_cpta' => true,
    'home' => 'Home',
    'blog_home' => 'Blog',
    'cat_off' => false,
    'cat_parents_off' => false,
    'tax_off' => false,
    'tax_parents_off' => false, 
    'extra_top_id' => '',
    'extra_top_url' => '',
    'show_cat_tag_for_cpt' => false,
    'show_terms' => false,
  );
  //引数の値とデフォルトをマージ
  $args = wp_parse_args( $args, $defaults );
  extract( $args, EXTR_SKIP );
 
  //出力文字列の初期化
  $str ='{
  "@context": "http://schema.org",
  "@type": "BreadcrumbList",
  "itemListElement":
  ['."\n"; 
  
  //position カウンター
  $count = 1;
  
  //先頭(ホームの前)に追加するマークアップ
  $extra_top_str = '';
  
  //先頭(ホームの前)に追加する場合
  if($extra_top_id && $extra_top_url) {
    $extra_top_str = '{"@type": "ListItem",
    "position": '.$count.',
    "item":{
      "@id":"'. $extra_top_url. '",
      "name":"'.$extra_top_id. '"}},' ."\n";
    $str.= $extra_top_str;
    $count ++;
  }
 
  //Home 文字列
  $home_str = '{"@type": "ListItem",
  "position": '.$count.',
  "item":{
    "@id":"'. home_url(). '",
    "name":"'.$home. '"}},' ."\n";
  
  //ホーム・フロントページの場合  
  if(is_front_page() || is_home()) {
    if($show_home) {
      $label = is_front_page() ? $home: $blog_home;
      $str.= '{"@type": "ListItem",
      "position": '.$count.',
      "item":{';
      $str.= '"@id":"'. home_url(). '",
      "name":"'.$label. '"}}'."\n";
    }else{
      return '';
    }
  }
  //ホーム・フロントページでない場合(且つ管理ページでない場合)
  if(!is_front_page() && !is_home() && !is_admin()){
    
    $str.= $home_str; 
    
    //タクソノミー名を取得(タクソノミーアーカイブの場合のみ取得可能)
    $my_taxonomy = get_query_var('taxonomy');  
    //投稿タイプ名を取得(カスタム投稿タイプ個別ページの場合のみ取得可能)
    $cpt = get_query_var('post_type'); 
    
    //カスタムタクソノミーアーカイブページ
    if($my_taxonomy &&  is_tax($my_taxonomy)) {
      //タームオブジェクト(現在のページのオブジェクト)を取得
      $my_term = get_queried_object(); 
      //タクソノミーオブジェクトの object_type プロパティは配列
      $post_types = get_taxonomy( $my_taxonomy )->object_type;
      //配列の0番目からカスタム投稿タイプのスラッグ(カスタム投稿タイプ名)を取得
      $cpt = $post_types[0]; 
      //カスタム投稿タイプのアーカイブページを追加
      $count++;
      $str.=  '{"@type": "ListItem",
      "position": '.$count.',
      "item":{
        "@id":"'. esc_url(get_post_type_archive_link($cpt)). '",
        "name":"'.get_post_type_object($cpt)->label. '"}},'."\n";
      //タームオブジェクトに親があればそれらを取得して追加
      if($my_term->parent != 0) { 
        //祖先タームオブジェクトの ID の配列を取得し逆順に(取得される配列の並びは階層の下から上)
        $ancestors = array_reverse(get_ancestors( $my_term->term_id, $my_term->taxonomy ));
        //全ての祖先タームオブジェクトのアーカイブページを追加
        foreach($ancestors as $ancestor){
          $count++;
          $str.=  '{"@type": "ListItem",
          "position": '.$count.',
          "item":{
            "@id":"'. esc_url(get_term_link($ancestor, $my_term->taxonomy)). '",
            "name":"'. get_term($ancestor, $my_term->taxonomy)->name. '"}},'."\n";
        }
      }
      //タームを追加 
      $count++;
      $str.=  '{"@type": "ListItem",
      "position": '.$count.',
      "item":{
        "@id":"'. esc_url(get_term_link($my_term, $my_term->taxonomy)). '",
        "name":"'. get_term($my_term, $my_term->taxonomy)->name. '"}}'."\n";
    //カテゴリーのアーカイブページ
    }elseif(is_category()) { 
      //カテゴリーオブジェクトを取得
      $cat = get_queried_object();
      //取得したカテゴリーオブジェクトに親があればそれらを取得して追加
      if($cat->parent != 0){
        $ancestors = array_reverse(get_ancestors( $cat->term_id, 'category' ));
        foreach($ancestors as $ancestor){
          $count++;
          $str.=  '{"@type": "ListItem",
          "position": '.$count.',
          "item":{
            "@id":"'. esc_url(get_category_link($ancestor)). '",
            "name":"'. get_cat_name($ancestor). '"}},'."\n";
        }
      }
      //テゴリー名を追加
      $count++;
      $str.=  '{"@type": "ListItem",
      "position": '.$count.',
      "item":{
      "@id":"'. esc_url(get_category_link($cat)). '",
      "name":"'. $cat->name. '"}}'."\n";
    //カスタム投稿のアーカイブページ
    } elseif(is_post_type_archive()) { 
      //カスタム投稿タイプ名を取得
      $cpt = get_query_var('post_type');
      //カスタム投稿タイプを追加
      $count++;
      $str.=  '{"@type": "ListItem",
      "position": '.$count.',
      "item":{
      "@id":"'. esc_url(get_post_type_archive_link($cpt)). '",
      "name":"'. get_post_type_object($cpt)->label. '"}}'."\n";
    //カスタム投稿タイプの個別記事ページ
    } elseif($cpt && is_singular($cpt)){ 
      if($show_cpta) {
        //カスタム投稿タイプアーカイブページを追加
        $count++;
        $str.=  '{"@type": "ListItem",
        "position": '.$count.',
        "item":{
          "@id":"'. esc_url(get_post_type_archive_link($cpt)). '",
          "name":"'.get_post_type_object($cpt)->label. '"}},'."\n";
      }
      //このカスタム投稿タイプに登録されている全てのタクソノミーオブジェクトの名前を取得
      $taxes = get_object_taxonomies( $cpt );
      //タクソノミーオブジェクトの名前が取得できれば
      if(count($taxes) !== 0) {
        //タクソノミーを表示する場合
        if(!$tax_off) {
          //配列の先頭のタクソノミーオブジェクトの名前(複数ある可能性があるので先頭のものを使う)
          //デフォルトでは標準のカテゴリーやタグが追加されている場合はインデックスを変更 
          //但し、show_cat_tag_for_cpt が true の場合はカテゴリーを取得可能に
          $tax_index = 0;
          if(!$show_cat_tag_for_cpt) {
            for ($i = 0; $i < count($taxes); $i++) {
             if($taxes[$i] !== 'category' && $taxes[$i] !== 'post_tag' && $taxes[$i] !== 'post_format') {
               $tax_index = $i;
               break;
             }
            }
          }
          $mytax = $taxes[$tax_index];
          //カスタムフィールドに優先するタクソノミーのラベルが記載されていればそのタクソノミーを選択
          //タクソノミーのラベルを取得
          $my_pref_tax_label = get_post_meta( get_the_ID(), 'my_pref_tax', true) ? esc_attr(get_post_meta( get_the_ID(), 'my_pref_tax', true)) : null;
          //ラベルからタクソノミーを取得(戻り値はタクソノミーの名前の配列)
          $my_pref_tax_name = get_taxonomies(array('label'=> $my_pref_tax_label));
          //タクソノミー名の初期化
          $my_pref_tax = '';
          //取得した配列が1つの場合、その値が優先されるタクソノミーの名前
          if(count($my_pref_tax_name) == 1 ){
            $my_pref_tax = $my_pref_tax_name[key($my_pref_tax_name)];
          }
          //タクソノミーの名前が取得できて且つそのタクソノミーが現在の投稿タイプに属している場合は、そのタクソノミーを使用
          if($my_pref_tax && is_object_in_taxonomy($post->post_type, $my_pref_tax)) {
            $mytax = $my_pref_tax;
          }
          //投稿に割り当てられたタームオブジェクト(配列)を取得
          $terms = get_the_terms($post->ID, $mytax); 
          //カスタムフィールドに優先するタームが記載されていればその値を取得して $myterm へ
          $myterm = get_post_meta( get_the_ID(), 'myterm', true) ? esc_attr(get_post_meta( get_the_ID(), 'myterm', true)) : null;
          //$terms が取得できていれば一番下の階層のタームを取得(できない場合は null に)  
          $my_term = $terms ? get_deepest_term($terms, $mytax, $myterm) : null;
          //タームが取得できていれば
          if( !empty($my_term) ) { 
            //タームに親があればそれらを取得してリンクを生成してリストに追加
            if($my_term->parent != 0 && !$tax_parents_off){
              $ancestors = array_reverse(get_ancestors( $my_term->term_id, $mytax ));
              foreach($ancestors as $ancestor){
                $count++;
                $str.=  '{"@type": "ListItem",
                "position": '.$count.',
                "item":{
                  "@id":"'. esc_url(get_term_link($ancestor, $mytax)). '",
                  "name":"'.get_term($ancestor, $mytax)->name. '"}},'."\n"; 
              }
            }
            if($show_terms) {
            //タームを追加(不要?)
            $count++;
            $str.=  '{"@type": "ListItem",
            "position": '.$count.',
            "item":{
              "@id":"'. esc_url(get_term_link($my_term, $mytax)). '",
              "name":"'.$my_term->name. '"}},'."\n";   
            }
          }
          $count++;
          $str.=  '{"@type": "ListItem",
          "position": '.$count.',
          "item":{
            "@id":"'. esc_url(get_permalink($post->post_id)). '",
            "name":"'.wp_strip_all_tags($post->post_title). '"}}'."\n"; 
        }
      }else{
        //タクソノミーを表示しない場合
        $count++;
        $str.=  '{"@type": "ListItem",
        "position": '.$count.',
        "item":{
          "@id":"'. esc_url(get_permalink($post->post_id)). '",
          "name":"'.wp_strip_all_tags($post->post_title). '"}}'."\n"; 
      }
    //個別投稿ページ(添付ファイルも true と判定されるので除外)
    }elseif(is_single() && !is_attachment()){
      //投稿が属するカテゴリーオブジェクトの配列を取得
      $categories = get_the_category($post->ID);
      //カテゴリーを表示する場合
      if(!$cat_off) {
        //カスタムフィールドに優先するカテゴリーが記載されていればその値を取得して $myterm へ
        $myterm = get_post_meta( get_the_ID(), 'myterm', true) ? esc_attr(get_post_meta( get_the_ID(), 'myterm', true)) : null;
        //一番下の階層のカテゴリーを取得
        $cat = get_deepest_term($categories, 'category', $myterm);
        //カテゴリーに親があればそれらを取得して追加
        if($cat->parent != 0 && !$cat_parents_off){
          $ancestors = array_reverse(get_ancestors( $cat->term_id, 'category' ));
          foreach($ancestors as $ancestor){
            $count++;
            $str.=  '{"@type": "ListItem",
            "position": '.$count.',
            "item":{
              "@id":"'. esc_url(get_category_link($ancestor)). '",
              "name":"'.get_cat_name($ancestor). '"}},'."\n"; 
          }
        }
        //カテゴリーを追加
        $count++;
        $str.=  '{"@type": "ListItem",
        "position": '.$count.',
        "item":{
          "@id":"'. esc_url(get_category_link($cat->term_id)). '",
          "name":"'.$cat->name. '"}},'."\n"; 
      }
      $count++;
      $str.=  '{"@type": "ListItem",
      "position": '.$count.',
      "item":{
        "@id":"'. esc_url(get_permalink($post->post_id)). '",
        "name":"'.wp_strip_all_tags($post->post_title). '"}}'."\n";
    //固定ページ
    } elseif(is_page()){
      //固定ページに親があればそれらを取得してリンクを生成してリストに追加
      if($post -> post_parent != 0 ){
        $ancestors = array_reverse(get_post_ancestors( $post->ID ));
        foreach($ancestors as $ancestor){
          $count++;
          $str.=  '{"@type": "ListItem",
          "position": '.$count.',
          "item":{
            "@id":"'. esc_url(get_permalink($ancestor)). '",
            "name":"'. wp_strip_all_tags(get_the_title($ancestor)). '"}},'."\n";
        }
      }
      //固定ページを追加
      $count++;
      $str.=  '{"@type": "ListItem",
      "position": '.$count.',
      "item":{
        "@id":"'. esc_url(get_permalink($post->post_id)). '",
        "name":"'. wp_strip_all_tags($post->post_title). '"}}'."\n";
    //タグのアーカイブページ
    } elseif(is_tag()){
      //タームオブジェクトを取得
      $tag = get_queried_object(); 
      $count++;
      $str.=  '{"@type": "ListItem",
      "position": '.$count.',
      "item":{
        "@id":"'. esc_url( get_tag_link($tag->term_id) ). '",
        "name":"'. single_tag_title( '' , false ). '"}}'."\n";
    } else {
      return '';
    }
  }
  $str.= "\n".']'."\n".'}'."\n";
  return  '<script type="application/ld+json">' ."\n". $str . '</script>'."\n";
}

上記コードはパンくずリストのコードを基に、対象のページを限定してリンクの代わりに構造化マークアップを出力していて内容的にはほぼ同じです。

使い方

my_json_ld_breadcrumbs() を使用するには get_deepest_term() も functions.php にコピーしておく必要があります。

以下のように記述すると構造化マークアップを </body> 閉じタグの直前に出力します。

add_action( 'wp_footer', 'add_my_json_ld_breadcrumbs', 999999 );
function add_my_json_ld_breadcrumbs() {
  if ( function_exists( 'my_json_ld_breadcrumbs' ) ) {
    echo my_json_ld_breadcrumbs(); 
  }
}

以下はパラメータの show_home と cat_off を指定する例です。

以下の場合、ホームでは構造化マークアップを出力せず、投稿個別ページではカテゴリーを出力しません。

add_action( 'wp_footer', 'add_my_json_ld_breadcrumbs', 100 );
function add_my_json_ld_breadcrumbs() {
  if ( function_exists( 'my_json_ld_breadcrumbs' ) ) {
    echo my_json_ld_breadcrumbs(array(
      'show_home'=>false,
      'cat_off'=>true
    )); 
  }
}

マークアップの出力場所

add_action() の第1パラメータに wp_footer を指定するとフッター( </body> 閉じタグの前)に、wp_head を指定するとヘッダー(<head> 要素内)にマークアップを出力します。

フッターやヘッダー内での出力位置は add_action() の第3パラメータの優先度(整数値)で調整できます。

ヘッダー内に出力する場合は、以下のように add_action() の第1パラメータのアクションに wp_head を指定します。

add_action( 'wp_head', 'add_my_json_ld_breadcrumbs', 10 );
function add_my_json_ld_breadcrumbs() {
  if ( function_exists( 'my_json_ld_breadcrumbs' ) ) {
    echo my_json_ld_breadcrumbs(); 
  }
}

条件分岐タグで出力するページを限定

以下はカテゴリーやカスタムタクソノミータームのアーカイブページでは構造化マークアップを出力しない例です。

必要であれば、条件分岐を使ってページ種類によって異なるパラメータを指定することができます。

add_action( 'wp_head', 'add_my_json_ld_breadcrumbs', 10 );
function add_my_json_ld_breadcrumbs() {
  if ( function_exists( 'my_json_ld_breadcrumbs' ) ) {
    //条件分岐タグで関数の実行を限定(カテゴリーやタームのアーカイブページでなければ実行)
    if( !(is_category() || is_tax()) ) {
      echo my_json_ld_breadcrumbs(); 
    } 
  }
}

トップの階層に追加

以下は WordPress をサブディレクトリにインストールしている場合などで、トップの階層に更に上の階層のマークアップを追加する例です。

add_action( 'wp_footer', 'add_my_json_ld_breadcrumbs', 999999 );
function add_my_json_ld_breadcrumbs() {
  if ( function_exists( 'my_json_ld_breadcrumbs' ) ) {
    echo my_json_ld_breadcrumbs(array(
      'extra_top_id'=>'Home', 
      'extra_top_url' => 'https://www.webdesignleaves.com', 
      'home'=> 'Web制作メモ'
    )); 
  }
} 

上記は WordPress を wp と言うディレクトリにインストールしていて、追加したトップのマークアップの name を Home に、@id を https://www.webdesignleaves.com に、home の文字列を「Web制作メモ」にする例です。

構造化データテストツール」で確認すると以下のような結果になります。

構造化データテストツールでの結果のスクリーンショット

関連ページ:SEO /パンくずリスト/構造化マークアップ