WordPressでプラグインを使わずにパンくずリストを自動生成する関数を自作しました。

全ページ種別に対応しており、カスタム投稿タイプに紐づいたタクソノミーも表示するように設計しています。

もちろん、プラグインを使ってもお手軽に実装できますが、カスタム投稿などを使用すると結局コードをいじることになりませんか?
だったら自分でコードを記述しておく方が 柔軟な対応が可能で便利かなと思います。

【2018 / 12 / 28 更新】
コードの内容を大幅に改良いたしました。

今回は、通常ののパンくずリストの他、スキーマ(schema.org)に対応させたタイプのパンくずリストのコードも紹介しておきます。
※ 改良されたコードのschema.org対応版はありません(今では JSON-LD対応の方が推奨されているようなので、追加する予定もないです)

目次
自分の価値を生み出すWordPressテーマ、SWELL

【限定セール中】WEMOの筆者が開発したブログ向けWordPressテーマです!

パンくずリストの生成関数を定義する

functions.phpに以下のコードをコピペしてください。

【旧版】パンくずリスト生成関数

function breadcrumb(){
  global $post;
  $str = '';
  $pNum = 2;
  $str.= '<div id="breadcrumb">';
  $str.= '<ul>';
  $str.= '<li ><a href="'.home_url('/').'" class="home"><span>HOME</span></a></li>';

  /* 通常の投稿ページ  */
  if(is_singular('post')){
    $categories = get_the_category($post->ID);
    $cat = $categories[0];

    if($cat->parent != 0){
      $ancestors = array_reverse(get_ancestors($cat->cat_ID, 'category'));
      foreach($ancestors as $ancestor){
        $str.= '<li><a href="'. get_category_link($ancestor).'"><span>'.get_cat_name($ancestor).'</span></a></li>';
      }
    }
    $str.= '<li><a href="'. get_category_link($cat-> term_id). '"><span>'.$cat->cat_name.'</span></a></li>';
    $str.= '<li><span>'.$post->post_title.'</span></li>';
  }

  /* カスタムポスト */
  elseif(is_single() && !is_singular('post')){
    $cp_name = get_post_type_object(get_post_type())->label;
    $cp_url = home_url('/').get_post_type_object(get_post_type())->name;
  
    $str.= '<li><a href="'.$cp_url.'"><span>'.$cp_name.'</span></a></li>';
    $str.= '<li><span>'.$post->post_title.'</span></li>';
  }

  /* 固定ページ */
  elseif(is_page()){
    $pNum = 2;
    if($post->post_parent != 0 ){
      $ancestors = array_reverse(get_post_ancestors($post->ID));
      foreach($ancestors as $ancestor){
        $str.= '<li><a href="'. get_permalink($ancestor).'"><span>'.get_the_title($ancestor).'</span></a></li>';
      }
    }
    $str.= '<li><span>'. $post->post_title.'</span></li>';
  }

  /* カテゴリページ */
  elseif(is_category()) {
    $cat = get_queried_object();
    $pNum = 2;
    if($cat->parent != 0){
      $ancestors = array_reverse(get_ancestors($cat->cat_ID, 'category'));
      foreach($ancestors as $ancestor){
        $str.= '<li><a href="'. get_category_link($ancestor) .'"><span>'.get_cat_name($ancestor).'</span></a></li>';
      }
    }
    $str.= '<li><span>'.$cat->name.'</span></li>';
  }

  /* タグページ */
  elseif(is_tag()){
    $str.= '<li><span>'. single_tag_title('', false). '</span></li>';
  }

  /* 時系列アーカイブページ */
  elseif(is_date()){
    if(get_query_var('day') != 0){
      $str.= '<li><a href="'. get_year_link(get_query_var('year')).'"><span>'.get_query_var('year').'年</span></a></li>';
      $str.= '<li><a  href="'.get_month_link(get_query_var('year'), get_query_var('monthnum')).'"><span>'.get_query_var('monthnum').'月</span></a></li>';
      $str.= '<li><span>'.get_query_var('day'). '</span>日</li>';
    } elseif(get_query_var('monthnum') != 0){
      $str.= '<li><a href="'. get_year_link(get_query_var('year')).'"><span>'.get_query_var('year').'年</span></a></li>';
      $str.= '<li><span>'.get_query_var('monthnum'). '</span>月</li>';
    } else {
      $str.= '<li><span>'.get_query_var('year').'年</span></li>';
    }
  }

  /* 投稿者ページ */
  elseif(is_author()){
    $str.= '<li><span>投稿者 : '.get_the_author_meta('display_name', get_query_var('author')).'</span></li>';
  }

  /* 添付ファイルページ */
  elseif(is_attachment()){
    $pNum = 2;
    if($post -> post_parent != 0 ){
      $str.= '<li><a href="'.get_permalink($post-> post_parent).'"><span>'.get_the_title($post->post_parent).'</span></a></li>';
    }
    $str.= '<li><span>'.$post->post_title.'</span></li>';
  }

  /* 検索結果ページ */
  elseif(is_search()){
    $str.= '<li><span>「'.get_search_query().'」で検索した結果</span></li>';
  }

  /* 404 Not Found ページ */
  elseif(is_404()){
    $str.= '<li><span>お探しの記事は見つかりませんでした。</span></li>';
  }

  /* その他のページ */
  else{
    $str.= '<li><span>'.wp_title('', false).'</span></li>';
  }
  $str.= '</ul>';
  $str.= '</div>';

  echo $str;
}

※上記コードは古いバージョンですので、以下をお使いください。(どのように改良したのかを比較できるように、一応コードは残しています)

【改良版】パンくずリスト生成関数

if ( ! function_exists( 'custom_breadcrumb' ) ) {
    function custom_breadcrumb( $wp_obj = null ) {

        // トップページでは何も出力しない
        if ( is_home() || is_front_page() ) return false;

        //そのページのWPオブジェクトを取得
        $wp_obj = $wp_obj ?: get_queried_object();

        echo '<div id="breadcrumb">'.  //id名などは任意で
                '<ul>'.
                    '<li>'.
                        '<a href="'. home_url() .'"><span>ホーム</span></a>'.
                    '</li>';

        if ( is_attachment() ) {

            /**
             * 添付ファイルページ ( $wp_obj : WP_Post )
             * ※ 添付ファイルページでは is_single() も true になるので先に分岐
             */
            echo '<li><span>'. $wp_obj->post_title .'</span></li>';

        } elseif ( is_single() ) {

            /**
             * 投稿ページ ( $wp_obj : WP_Post )
             */
            $post_id    = $wp_obj->ID;
            $post_type  = $wp_obj->post_type;
            $post_title = $wp_obj->post_title;

            // カスタム投稿タイプかどうか
            if ( $post_type !== 'post' ) {

                $the_tax = "";  //そのサイトに合わせ、投稿タイプごとに分岐させて明示的に指定してもよい

                // 投稿タイプに紐づいたタクソノミーを取得 (投稿フォーマットは除く)
                $tax_array = get_object_taxonomies( $post_type, 'names');
                foreach ($tax_array as $tax_name) {
                    if ( $tax_name !== 'post_format' ) {
                        $the_tax = $tax_name;
                        break;
                    }
                }

                //カスタム投稿タイプ名の表示
                echo '<li>'.
                        '<a href="'. get_post_type_archive_link( $post_type ) .'">'.
                            '<span>'. get_post_type_object( $post_type )->label .'</span>'.
                        '</a>'.
                     '</li>';

            } else {
                $the_tax = 'category';  //通常の投稿の場合、カテゴリーを表示
            }

            // タクソノミーが紐づいていれば表示
            if ( $the_tax !== "" ) {

                $child_terms = array();   // 子を持たないタームだけを集める配列
                $parents_list = array();  // 子を持つタームだけを集める配列

                // 投稿に紐づくタームを全て取得
                $terms = get_the_terms( $post_id, $the_tax );

                if ( !empty( $terms ) ) {

                    //全タームの親IDを取得
                    foreach ( $terms as $term ) {
                        if ( $term->parent !== 0 ) $parents_list[] = $term->parent;
                    }

                    //親リストに含まれないタームのみ取得
                    foreach ( $terms as $term ) {
                        if ( ! in_array( $term->term_id, $parents_list ) ) $child_terms[] = $term;
                    }

                    // 最下層のターム配列から一つだけ取得
                    $term = $child_terms[0];

                    if ( $term->parent !== 0 ) {

                        // 親タームのIDリストを取得
                        $parent_array = array_reverse( get_ancestors( $term->term_id, $the_tax ) );

                        foreach ( $parent_array as $parent_id ) {
                            $parent_term = get_term( $parent_id, $the_tax );
                            echo '<li>'.
                                    '<a href="'. get_term_link( $parent_id, $the_tax ) .'">'.
                                      '<span>'. $parent_term->name .'</span>'.
                                    '</a>'.
                                 '</li>';
                        }
                    }

                    // 最下層のタームを表示
                    echo '<li>'.
                            '<a href="'. get_term_link( $term->term_id, $the_tax ). '">'.
                              '<span>'. $term->name .'</span>'.
                            '</a>'.
                         '</li>';
                }
            }

            // 投稿自身の表示
            echo '<li><span>'. $post_title .'</span></li>';

        } elseif ( is_page() ) {

            /**
             * 固定ページ ( $wp_obj : WP_Post )
             */
            $page_id    = $wp_obj->ID;
            $page_title = $wp_obj->post_title;

            // 親ページがあれば順番に表示
            if ( $wp_obj->post_parent !== 0 ) {
                $parent_array = array_reverse( get_post_ancestors( $page_id ) );
                foreach( $parent_array as $parent_id ) {
                    echo '<li>'.
                            '<a href="'. get_permalink( $parent_id ).'">'.
                                '<span>'.get_the_title( $parent_id ).'</span>'.
                            '</a>'.
                         '</li>';
                }
            }
            // 投稿自身の表示
            echo '<li><span>'. $page_title .'</span></li>';

        } elseif ( is_post_type_archive() ) {

            /**
             * 投稿タイプアーカイブページ ( $wp_obj : WP_Post_Type )
             */
            echo '<li><span>'. $wp_obj->label .'</span></li>';

        } elseif ( is_date() ) {

            /**
             * 日付アーカイブ ( $wp_obj : null )
             */
            $year  = get_query_var('year');
            $month = get_query_var('monthnum');
            $day   = get_query_var('day');

            if ( $day !== 0 ) {
                //日別アーカイブ
                echo '<li><a href="'. get_year_link( $year ).'"><span>'. $year .'年</span></a></li>'.
                     '<li><a href="'. get_month_link( $year, $month ). '"><span>'. $month .'月</span></a></li>'.
                     '<li><span>'. $day .'日</span></li>';

            } elseif ( $month !== 0 ) {
                //月別アーカイブ
                echo '<li><a href="'. get_year_link( $year ).'"><span>'.$year.'年</span></a></li>'.
                     '<li><span>'.$month . '月</span></li>';

            } else {
                //年別アーカイブ
                echo '<li><span>'.$year.'年</span></li>';

            }

        } elseif ( is_author() ) {

            /**
             * 投稿者アーカイブ ( $wp_obj : WP_User )
             */
            echo '<li><span>'. $wp_obj->display_name .' の執筆記事</span></li>';

        } elseif ( is_archive() ) {

            /**
             * タームアーカイブ ( $wp_obj : WP_Term )
             */
            $term_id   = $wp_obj->term_id;
            $term_name = $wp_obj->name;
            $tax_name  = $wp_obj->taxonomy;

            /* ここでタクソノミーに紐づくカスタム投稿タイプを出力しても良いでしょう。 */

            // 親ページがあれば順番に表示
            if ( $wp_obj->parent !== 0 ) {

                $parent_array = array_reverse( get_ancestors( $term_id, $tax_name ) );
                foreach( $parent_array as $parent_id ) {
                    $parent_term = get_term( $parent_id, $tax_name );
                    echo '<li>'.
                            '<a href="'. get_term_link( $parent_id, $tax_name ) .'">'.
                                '<span>'. $parent_term->name .'</span>'.
                            '</a>'.
                         '</li>';
                }
            }

            // ターム自身の表示
            echo '<li>'.
                    '<span>'. $term_name .'</span>'.
                '</li>';


        } elseif ( is_search() ) {

            /**
             * 検索結果ページ
             */
            echo '<li><span>「'. get_search_query() .'」で検索した結果</span></li>';

        
        } elseif ( is_404() ) {

            /**
             * 404ページ
             */
            echo '<li><span>お探しの記事は見つかりませんでした。</span></li>';

        } else {

            /**
             * その他のページ(無いと思うが一応)
             */
            echo '<li><span>'. get_the_title() .'</span></li>';
        }

        echo '</ul></div>';  // 冒頭に合わせて閉じタグ

    }
}

全ページ種別に対応させているため かなり長いコードですが、その分、様々な知識が詰まったコードになっています。(コメントもブログ用に細かく記述しています。)
WordPress初心者の方は、ひとつひとつ中身を見ていただければ色々な発見もあるかと思いますので ぜひ参考にしていただければと思います。


サイトによって、パンくずリストを設置しないページに関する記述は削除するなりしてスッキリさせてください。

改良した点について

以前と比べ、何を改良したのかを軽く記述しておきます

  1. if (function_exists())で二重登録の防止
  2. グローバル変数$postの呼び出しをやめ、処理の冒頭でget_queried_object()を取得
  3. 上項に合わせ、引数に予めWPオブジェクトを渡しておくことを可能に
  4. ページ種別の分岐方法・順番を改良
  5. 各ページにてソースの出力コードを改良(特に投稿ページ)
  6. カスタム投稿に紐づいたカスタムタクソノミーも表示
  7. ソースは変数に代入していくのではなく、その場でechoさせる

サイトに合わせて改良すべき部分

改良点6およびその逆パターンについてです。

カスタム投稿ページの場合、それに紐づくタクソノミーを自動で取得して表示するようにはしていますが、この部分はサイトに合わせて明示的に指定した方が良いでしょう。

 

また、タクソノミーアーカイブページで、そのタクソノミーが紐づいた投稿タイプが1つのみの構造のサイトの場合は、その投稿タイプを出力してもいいかと思います。

自動で投稿タイプを取得する場合は、アーカイブページ内でget_post()すると1記事目の情報が得られるので、そこから投稿タイプを取得するという方法があります。
ただし、複数の投稿タイプと紐づいている場合は、その時の最新記事でパンくずが変動してしまいます。(この理由により、上記の自作関数内では実装しておりません)

定義したパンくずリスト生成関数を呼び出す

さて、あとはお好きな場所で関数を呼び出すだけでパンくずリストが設置されます。

パンくずリストを設置したい場所に以下のコードを記述

<?php custom_breadcrumb(); ?>

header.phpなどの共通パーツで呼び出すと、一箇所の記述だけでいいので便利です。

すでにそのテンプレートファイル内でget_queried_object()や、そのページのWPオブジェクトを変数に代入などをしている場合は、以下のようにしていただくと、無駄な処理を省略できます。

get_queried_object()などすでに使っている場合

<?php 
  $obj = get_queried_object();  // もしくは single.php での get_post() など

  /* ...他のコード...*/

  // パンくず表示
  custom_breadcrumb( $obj );

schema.orgで構造化マークアップしたパンくずの自動生成コード

先ほどのソースに、schema.orgでのマークアップを付け加えたものです。

こちらは旧版のパンくずリスト生成関数をschema.orgで構造化マークアップしたものです。

もし、改良版にもschema.orgで構造化マークアップしたい場合に参考にしてみてください。
(JSON-LDで構造化マークアップする記事をそのうち書きます)

【旧版】スキーマ対応のパンくずリスト

function breadcrumb(){
  global $post;
  $str ='';
  $pNum = 2;

  $str.= '<div id="breadcrumb">';
  $str.= '<ul itemprop="Breadcrumb" itemscope itemtype="http://data-vocabulary.org/BreadcrumbList">';
  $str.= '<li itemprop="itemListElement" itemscope itemtype="http://schema.org/ListItem"><a itemprop="item" href="'.home_url('/').'" class="home"><span itemprop="name">HOME</span></a><meta itemprop="position" content="1"></li>';

  /* 通常の投稿ページ */
  if(is_singular('post')){
    $categories = get_the_category($post->ID);
    $cat = $categories[0];

    if($cat->parent != 0){
      $ancestors = array_reverse(get_ancestors($cat->cat_ID, 'category'));
      foreach($ancestors as $ancestor){
        $str.= '<li itemprop="itemListElement" itemscope itemtype="http://schema.org/ListItem"><a itemprop="item" href="'. get_category_link($ancestor).'"><span itemprop="name">'.get_cat_name($ancestor).'</span></a><meta itemprop="position" content="'.$pNum.'"></li>';
        $pNum++;
      }
    }
    $str.='<li itemprop="itemListElement" itemscope itemtype="http://schema.org/ListItem"><a itemprop="item" href="'. get_category_link($cat-> term_id). '"><span itemprop="name">'. $cat-> cat_name . '</span></a><meta itemprop="position" content="'.$pNum.'"></li>';
    $pNum++;
    $str.= '<li itemprop="itemListElement" itemscope itemtype="http://schema.org/ListItem"><span itemprop="name">'.$post->post_title.'</span><meta itemprop="position" content="'.$pNum.'"></li>';
  } 

  /* カスタムポスト */
  elseif(is_single() && !is_singular('post')){
    $cp_name = get_post_type_object(get_post_type())->label;
    $cp_url = home_url('/').get_post_type_object(get_post_type())->name;
    $str.= '<li itemprop="itemListElement" itemscope itemtype="http://schema.org/ListItem"><a itemprop="item" href="'.$cp_url.'"><span itemprop="name">'.$cp_name.'</span></a><meta itemprop="position" content="2"></li>';
    $str.= '<li itemprop="itemListElement" itemscope itemtype="http://schema.org/ListItem"><span itemprop="name">'.$post->post_title.'</span><meta itemprop="position" content="3"></li>'; 
  }

  /* 固定ページ */
  elseif(is_page()){
    $pNum = 2;
    if($post->post_parent != 0){
      $ancestors = array_reverse(get_post_ancestors($post->ID));
      foreach($ancestors as $ancestor){
        $str.='<li itemprop="itemListElement" itemscope itemtype="http://schema.org/ListItem"><a itemprop="item" href="'. get_permalink($ancestor).'"><span itemprop="name">'.get_the_title($ancestor).'</span></a><meta itemprop="position" content="'.$pNum.'"></li>';
        $pNum++;
      }
    }
    $str.= '<li itemprop="itemListElement" itemscope itemtype="http://schema.org/ListItem"><span itemprop="name">'. $post->post_title.'</span><meta itemprop="position" content="'.$pNum.'"></li>';
  }

  /* カテゴリページ */ 
  elseif(is_category()) {
    $cat = get_queried_object();
    $pNum = 2;
    if($cat->parent != 0){
      $ancestors = array_reverse(get_ancestors($cat->cat_ID, 'category'));
      foreach($ancestors as $ancestor){
        $str.= '<li itemprop="itemListElement" itemscope itemtype="http://schema.org/ListItem"><a itemprop="item" href="'. get_category_link($ancestor) .'"><span itemprop="name">'.get_cat_name($ancestor).'</span></a><meta itemprop="position" content="'.$pNum.'"></li>';
      }
    }
    $str.= '<li itemprop="itemListElement" itemscope itemtype="http://schema.org/ListItem"><span itemprop="name">'.$cat->name.'</span><meta itemprop="position" content="'.$pNum.'"></li>';
  }

  /* タグページ */
  elseif(is_tag()){
    $str.= '<li itemprop="itemListElement" itemscope itemtype="http://schema.org/ListItem"><span itemprop="name">'. single_tag_title( '' , false ). '</span><meta itemprop="position" content="2"></li>';
  } 

  /* 時系列アーカイブページ */
  elseif(is_date()){

    if(get_query_var('day') != 0){
      $str.= '<li itemprop="itemListElement" itemscope itemtype="http://schema.org/ListItem"><a itemprop="item" href="'. get_year_link(get_query_var('year')).'"><span itemprop="name">'.get_query_var('year').'年</span></a><meta itemprop="position" content="2"></li>';
      $str.= '<li itemprop="itemListElement" itemscope itemtype="http://schema.org/ListItem"><a itemprop="item" href="'. get_month_link(get_query_var('year'), get_query_var('monthnum')).'"><span itemprop="name">'.get_query_var('monthnum').'月</span></a><meta itemprop="position" content="3"></li>';
      $str.= '<li itemprop="itemListElement" itemscope itemtype="http://schema.org/ListItem"><span itemprop="name">'.get_query_var('day'). '</span>日<meta itemprop="position" content="4"></li>';
    } elseif(get_query_var('monthnum') != 0){
      $str.= '<li itemprop="itemListElement" itemscope itemtype="http://schema.org/ListItem"><a itemprop="item" href="'. get_year_link(get_query_var('year')).'"><span itemprop="name">'.get_query_var('year').'年</span></a><meta itemprop="position" content="2"></li>';
      $str.= '<li itemprop="itemListElement" itemscope itemtype="http://schema.org/ListItem"><span itemprop="name">'.get_query_var('monthnum'). '</span>月<meta itemprop="position" content="3"></li>';
    } else {
      $str.= '<li itemprop="itemListElement" itemscope itemtype="http://schema.org/ListItem"><span itemprop="name">'.get_query_var('year').'年</span><meta itemprop="position" content="2"></li>';
    }
  }

  /* 投稿者ページ */
  elseif(is_author()){
    $str .= '<li itemprop="itemListElement" itemscope itemtype="http://schema.org/ListItem"><span itemprop="name">投稿者 : '. get_the_author_meta('display_name', get_query_var('author')).'</span><meta itemprop="position" content="2"></li>';
  } 

  /* 添付ファイルページ */
  elseif(is_attachment()){
    $pNum = 2;
    if($post -> post_parent != 0 ){
      $str.= '<li itemprop="itemListElement" itemscope itemtype="http://schema.org/ListItem"><a itemprop="item" href="'.get_permalink($post-> post_parent).'"><span itemprop="name">'.get_the_title($post->post_parent).'</span></a><meta itemprop="position" content="'.$pNum.'"></li>';
      $pNum++;
    }
    $str.= '<li itemprop="itemListElement" itemscope itemtype="http://schema.org/ListItem"><span itemprop="name">'.$post->post_title.'</span><meta itemprop="position" content="'.$pNum.'"></li>';
  }

  /* 検索結果ページ */
  elseif(is_search()){
    $str.= '<li itemprop="itemListElement" itemscope itemtype="http://schema.org/ListItem"><span itemprop="name">「'.get_search_query().'」で検索した結果</span><meta itemprop="position" content="2"></li>';
  } 

  /* 404 Not Found ページ */
  elseif(is_404()){
    $str.= '<li itemprop="itemListElement" itemscope itemtype="http://schema.org/ListItem"><span itemprop="name">お探しの記事は見つかりませんでした。</span><meta itemprop="position" content="2"></li>';
  } 

  /* その他のページ */
  else{
    $str.= '<li itemprop="itemListElement" itemscope itemtype="http://schema.org/ListItem"><span itemprop="name">'.wp_title('', false).'</span><meta itemprop="position" content="2"></li>';
  }
  
  $str.= '</ul>';
  $str.= '</div>';
  echo $str;
}

スキーマ(schema.org)を使用したmicrodataマークアップについては以前メモしていますのでこちらを参考にしてください。

[リッチスニペット]スキーマ(schema.org)でパンくずリストのmicrodataをマークアップしてみた[リッチスニペット]スキーマ(schema.org)でパンくずリストのmicrodataをマークアップしてみた

おわりに

パンくずリストは、ぜひ一度、その手で自作してみてください。

WordPressの構造をある程度把握する必要があるので、かなり勉強になります。

そして、定期的にコードのメンテナンスをしてみましょう。
見直した時に「なんだよこのコード!めちゃくちゃ非効率じゃねぇか」って思うことができれば、その分プログラミング知識がついた、ということが実感できるはずです。

- Thank you for reading. -

コメント

  • never_kinks より:

    こんばんは
    とても参考になりました!
    ありがとうございます。

    一個指摘でスキーマの方のパンくずリストでspanの閉じタグが誤っている場所がありました。
    年</span.

  • kozikozi より:

    こちらのソースそのまま使えました!とても助かりました。ありがとうございます!

コメントする