WordPressにコピペで実装するパンくずリストのPHPコード
ウェブサイトのパーツとして欠かせない「パンくずリスト」ですが、WordPressで実装するのは割と面倒だったりします。
そこで今回は、Microdataで構造化されたパンくずリストを、WordPressテーマのfunctions.phpにコピペするだけで使用できるPHPコードを作ってみたので紹介します。
早速ですが、次のコードをfunctions.phpにコピペしてみてください。
if( !function_exists( 'my_breadcrumb_list' ) ):
function my_breadcrumb_list( $format = '%s' ) {
if( is_front_page() || is_404() || false === strpos( $format, '%s' ) ) {
return;
}
$items = [
[
'name' => get_bloginfo( 'name', 'display' ),
'url' => get_bloginfo( 'url', 'display' ),
],
];
if( is_search() ) {
$items[] = [
'name' => wp_title( '', false ),
'url' => get_pagenum_link(),
];
} else {
$object = get_queried_object();
if( is_attachment() ) {
$items[] = [
'name' => get_the_title( $object->ID ),
'url' => get_permalink( $object->ID ),
];
} else {
$post_type = get_post_type();
if( empty( $post_type ) ) {
$post_type = get_query_var( 'post_type' );
if( is_array( $post_type ) ) {
list( $post_type ) = $post_type;
}
}
$get_pages_args = [];
if( 'page' === get_option( 'show_on_front' ) ) {
$get_pages_args[ 'exclude' ] = [
get_option( 'page_on_front' ),
];
}
$pages = get_pages( $get_pages_args );
if( !empty( $pages ) ) {
$link = is_page() ? get_permalink( $object->ID ) : get_post_type_archive_link( $post_type );
$ancestors = [];
foreach( $pages as $page ) {
$permalink = get_permalink( $page->ID );
if( $link === $permalink || 0 === strpos( $link, trailingslashit( $permalink ) ) ) {
$ancestors[ $permalink ] = [
'name' => get_the_title( $page->ID ),
'url' => $permalink,
];
}
}#endforeach
if( [] !== $ancestors && ksort( $ancestors ) ) {
$items = array_merge( $items, array_values( $ancestors ) );
}
}
if( 'post' !== $post_type && ( is_single() || is_archive() ) ) {
$items[] = [
'name' => apply_filters( 'post_type_archive_title', get_post_type_object( $post_type )->labels->name, $post_type ),
'url' => get_post_type_archive_link( $post_type ),
];
}
if( is_single() ) {
if( 'post' === $post_type ) {
$dirs = explode( '/', get_option( 'permalink_structure' ) );
foreach( $dirs as $dir ) {
if( '%category%' === $dir ) {
list( $cat ) = get_the_category( $object->ID );
if( 0 !== $cat->category_parent ) {
$ancestors = array_reverse( get_ancestors( $cat->cat_ID, 'category', 'taxonomy' ) );
foreach( $ancestors as $ancestor ) {
$items[] = [
'name' => get_cat_name( $ancestor ),
'url' => get_category_link( $ancestor ),
];
}#endforeach
}
$items[] = [
'name' => get_cat_name( $cat->cat_ID ),
'url' => get_category_link( $cat->cat_ID ),
];
} elseif( '%author%' === $dir ) {
$items[] = [
'name' => get_the_author_meta( 'display_name', $object->post_author ),
'url' => get_author_posts_url( $object->post_author ),
];
} elseif( '%year%' === $dir ) {
$items[] = [
'name' => get_the_date( _x( 'Y', 'yearly archives date format', 'my_domain' ), $object->ID ),
'url' => get_year_link( get_query_var( 'year' ) ),
];
} elseif( '%monthnum%' === $dir ) {
$items[] = [
'name' => get_the_date( _x( 'm', 'monthly archives date format', 'my_domain' ), $object->ID ),
'url' => get_month_link( get_query_var( 'year' ), get_query_var( 'monthnum' ) ),
];
} elseif( '%day%' === $dir ) {
$items[] = [
'name' => get_the_date( _x( 'd', 'daily archives date format', 'my_domain' ), $object->ID ),
'url' => get_day_link( get_query_var( 'year' ), get_query_var( 'monthnum' ), get_query_var( 'day' ) ),
];
}
}#endforeach
}
$items[] = [
'name' => get_the_title( $object->ID ),
'url' => get_permalink( $object->ID ),
];
} elseif( is_category() || is_tag() || is_tax() ) {
$filter = 'single_term_title';
if( is_category() ) {
$filter = 'single_cat_title';
} elseif( is_tag() ) {
$filter = 'single_tag_title';
}
if( 0 !== $object->parent ) {
$ancestors = array_reverse( get_ancestors( $object->term_id, $object->taxonomy, 'taxonomy' ) );
foreach( $ancestors as $ancestor ) {
$term = get_term( $ancestor, $object->taxonomy );
$items[] = [
'name' => apply_filters( $filter, $term->name ),
'url' => get_term_link( $term, $object->taxonomy ),
];
}#endforeach
}
$items[] = [
'name' => apply_filters( $filter, $object->name ),
'url' => get_term_link( $object, $object->taxonomy ),
];
} elseif( is_date() ) [
if( is_year() || is_month() || is_day() ) {
$items[] = [
'name' => get_the_date( _x( 'Y', 'yearly archives date format', 'my_domain' ) ),
'url' => get_year_link( get_query_var( 'year' ) ),
];
if( is_month() || is_day() ) {
$items[] = [
'name' => get_the_date( _x( 'm', 'monthly archives date format', 'my_domain' ) ),
'url' => get_month_link( get_query_var( 'year' ), get_query_var( 'monthnum' ) ),
];
if( is_day() ) {
$items[] = [
'name' => get_the_date( _x( 'd', 'daily archives date format', 'my_domain' ) ),
'url' => get_day_link( get_query_var( 'year' ), get_query_var( 'monthnum' ), get_query_var( 'day' ) ),
];
}
}
}
} elseif( is_author() ) {
$items[] = [
'name' => $object->display_name,
'url' => get_author_posts_url( $object->ID, $object->user_nicename ),
];
}
}
}
if( is_singular() ) {
$page = (int) get_query_var( 'page' );
if( 1 < $page ) {
$link = '#';
if( !is_preview() ) {
$post = get_post();
if( !get_option( 'permalink_structure' ) || false !== strpos( '[draft][pending]', "[{$post->post_status}]" ) ) {
$link = add_query_arg( 'page', $page, get_permalink() );
} elseif( 'page' === get_option( 'show_on_front' ) && get_option( 'page_on_front' ) == $post->ID ) {
$link = trailingslashit( get_permalink() ). user_trailingslashit( "{$GLOBALS[ 'wp_rewrite' ]->pagination_base}/{$page}", 'single_paged' );
} else {
$link = trailingslashit( get_permalink() ). user_trailingslashit( $page, 'single_paged' );
}
}
$items[] = [
'name' => sprintf( __( 'Page %s', 'my_domain' ), $page ),
'url' => $link,
];
}
} elseif( is_home() || is_archive() || is_search() ) {
$paged = (int) get_query_var( 'paged' );
if( 1 < $paged ) {
$items[] = [
'name' => sprintf( __( 'Page %s', 'my_domain' ), $paged ),
'url' => get_pagenum_link( $paged ),
];
}
}
$output = '<ol itemscope="itemscope" itemtype="https://schema.org/BreadcrumbList">';
foreach( $items as $key => $item ) {
$output .= sprintf(
'<li itemprop="itemListElement" itemscope="itemscope" itemtype="https://schema.org/ListItem"%1$s>'.
'<span itemprop="name"><a href="%2$s" itemprop="item">%3$s</a></span>'.
'<meta itemprop="position" content="%4$s" />'.
'</li>',
( !isset( $items[ $key + 1 ] ) ? ' aria-current="page"' : '' ),
esc_url( $item[ 'url' ] ),
$item[ 'name' ],
( $key + 1 )
);
}
$output .= '</ol>';
printf( $format, $output );
}
endif;
あとは、パンくずリストを表示したいテンプレート部分に次のようなPHPコードを追加するだけです。
<?php my_breadcrumb_list( '<nav class="breadcrumb-list">%s</nav>' ); ?>
どうでしょう?意図した通りに表示されたでしょうか?
翻訳対象箇所を意味する __( '〇〇', 'my_domain' )
または _x( '〇〇', '〇〇', 'my_domain' )
の部分は英語のまま表示されると思うので、テーマの翻訳ファイルから編集できるよう 'my_domain'
の部分を適宜変更するか、表示したい文字列に直接置き換えてみてください。
それでは、このPHPコードを上から順に解説していきます。
特定の状況では表示させない処理
function my_breadcrumb_list( $format = '%s' ) {
if( is_front_page() || is_404() || false === strpos( $format, '%s' ) ) {
return;
}
…
}
パンくずリストを表示させる必要のない「フロントページ (is_front_page()
)」と「404ページ (is_404()
)」で my_breadcrumb_list()
が使用された場合と、$format
引数に設定できる文字列の中に %s
がない場合は、処理を終了します。
配列$itemsの作成と検索結果ページと添付ファイルページの処理
…
$items = [
[
'name' => get_bloginfo( 'name', 'display' ),
'url' => get_bloginfo( 'url', 'display' ),
],
];
if( is_search() ) {
$items[] = [
'name' => wp_title( '', false ),
'url' => get_pagenum_link(),
];
} else {
$object = get_queried_object();
if( is_attachment() ) {
$items[] = [
'name' => get_the_title( $object->ID ),
'url' => get_permalink( $object->ID ),
];
} else {
…
}
}
…
ウェブサイト情報 (get_bloginfo(…)
) を入れた配列 $items
を作成します。この $items
に順次ページ情報 (nameとurl) を加えていきます。
まずは、get_queried_object()
で取得できるクエリ情報が必要ない「検索結果ページ (is_search()
)」のページ情報を加え、次にクエリ情報が必要となる「添付ファイルページ (is_attachment()
)」のページ情報を加えます。
検索結果ページと添付ファイルページの2つを先に処理する理由は、階層的に祖先となるページがフロントページ以外にないためです。
- 検索結果ページの表示例
- URL例: https://example.com/?s=パンくずリスト
- ウェブサイト名
- 「パンくずリスト」の検索結果
- 添付ファイルページの表示例
- URL例: https://example.com/breadcrumb-list-jpg
- ウェブサイト名
- breadcrumb-list.jpg
固定ページの処理
…
$post_type = get_post_type();
if( empty( $post_type ) ) {
$post_type = get_query_var( 'post_type' );
if( is_array( $post_type ) ) {
list( $post_type ) = $post_type;
}
}
$get_pages_args = [];
if( 'page' === get_option( 'show_on_front' ) ) {
$get_pages_args[ 'exclude' ] = [
get_option( 'page_on_front' ),
];
}
$pages = get_pages( $get_pages_args );
if( !empty( $pages ) ) {
$link = is_page() ? get_permalink( $object->ID ) : get_post_type_archive_link( $post_type );
$ancestors = [];
foreach( $pages as $page ) {
$permalink = get_permalink( $page->ID );
if( $link === $permalink || 0 === strpos( $link, trailingslashit( $permalink ) ) ) {
$ancestors[ $permalink ] = [
'name' => get_the_title( $page->ID ),
'url' => $permalink,
];
}
}#endforeach
if( [] !== $ancestors && ksort( $ancestors ) ) {
$items = array_merge( $items, array_values( $ancestors ) );
}
}
…
まず、以降の処理で使用される投稿タイプ ($post_type
) を取得します。
次に「表示設定」で「ホームページ」に設定されていない、ほぼ全ての「固定ページ (is_page()
)」を get_pages(…)
で取得し、それらのURL ($permalink
) と、固定ページあるいは投稿一覧ページのURL ($link
) とを比較して、全体一致もしくは前方一致する固定ページ ($link === $permalink || 0 === strpos( $link, trailingslashit( $permalink ) )
) のページ情報を配列 $ancestors
に加えます。
$ancestors
に加えた固定ページを ksort(…)
でURLの短い順に並べ替え、array_merge(…)
で $items
に加えれば固定ページの処理は終了です。
「表示設定」で「投稿ページ」を設定している場合は「投稿一覧ページ (is_home()
)」の処理もここで終了です。
- 固定ページの表示例
- URL例: https://example.com/parent-page/child-page
- ウェブサイト名
- 親ページ名
- 子ページ名
- 親ページがない固定ページを「投稿ページ」に設定した投稿一覧ページの表示例
- URL例: https://example.com/posts
- ウェブサイト名
- 投稿一覧のページ名
- 親ページがある固定ページを「投稿ページ」に設定した投稿一覧ページの表示例
- URL例: https://example.com/parent-page/posts
- ウェブサイト名
- 親ページ名
- 投稿一覧のページ名
各種投稿ページと各種アーカイブページの処理
…
if( 'post' !== $post_type && ( is_single() || is_archive() ) ) {
$items[] = [
'name' => apply_filters( 'post_type_archive_title', get_post_type_object( $post_type )->labels->name, $post_type ),
'url' => get_post_type_archive_link( $post_type ),
];
}
…
まず最初に、カスタム投稿タイプが設定してある場合を想定して、「カスタム投稿一覧ページ (is_post_type_archive()
)」のページ情報を加えます。
カスタム投稿タイプは register_post_type(…)
で設定しますが、URLに関する設定がかなり柔軟で、既存の固定ページの子孫ページであるかのようなURLも設定できます。それも念頭に入れたPHPコードとなっています。
- 固定ページのURLを含まないカスタム投稿一覧ページの表示例
- URL例: https://example.com/custom-posts
- ウェブサイト名
- カスタム投稿一覧のページ名
- 固定ページのURLを含むカスタム投稿一覧ページの表示例
- URL例: https://example.com/parent-page/custom-posts
- ウェブサイト名
- 親ページ名
- カスタム投稿一覧のページ名
投稿ページ・カスタム投稿ページの処理
…
if( 'post' === $post_type ) {
$dirs = explode( '/', get_option( 'permalink_structure' ) );
foreach( $dirs as $dir ) {
if( '%category%' === $dir ) {
list( $cat ) = get_the_category( $object->ID );
if( 0 !== $cat->category_parent ) {
$ancestors = array_reverse( get_ancestors( $cat->cat_ID, 'category', 'taxonomy' ) );
foreach( $ancestors as $ancestor ) {
$items[] = [
'name' => get_cat_name( $ancestor ),
'url' => get_category_link( $ancestor ),
];
}#endforeach
}
$items[] = [
'name' => get_cat_name( $cat->cat_ID ),
'url' => get_category_link( $cat->cat_ID ),
];
} elseif( '%author%' === $dir ) {
$items[] = [
'name' => get_the_author_meta( 'display_name', $object->post_author ),
'url' => get_author_posts_url( $object->post_author ),
];
} elseif( '%year%' === $dir ) {
$items[] = [
'name' => get_the_date( _x( 'Y', 'yearly archives date format', 'my_domain' ), $object->ID ),
'url' => get_year_link( get_query_var( 'year' ) ),
];
} elseif( '%monthnum%' === $dir ) {
$items[] = [
'name' => get_the_date( _x( 'm', 'monthly archives date format', 'my_domain' ), $object->ID ),
'url' => get_month_link( get_query_var( 'year' ), get_query_var( 'monthnum' ) ),
];
} elseif( '%day%' === $dir ) {
$items[] = [
'name' => get_the_date( _x( 'd', 'daily archives date format', 'my_domain' ), $object->ID ),
'url' => get_day_link( get_query_var( 'year' ), get_query_var( 'monthnum' ), get_query_var( 'day' ) ),
];
}
}#endforeach
}
$items[] = [
'name' => get_the_title( $object->ID ),
'url' => get_permalink( $object->ID ),
];
…
「投稿ページ」と「カスタム投稿ページ」(is_single()
) の情報を加えます。投稿ページの場合、「パーマリンク設定」で「年」「月」「日」「カテゴリー」などを含んだURLが生成される可能性があるので、これらアーカイブのページ情報も加えます。
- 「表示設定」の「ホームページの表示」が「最新の投稿」の場合の投稿ページの表示例
- URL例: https://example.com/%postname%
- ウェブサイト名
- 投稿のページ名
- 「表示設定」の「ホームページの表示」が「固定ページ」の場合の投稿ページの表示例
- URL例: https://example.com/%postname%
- ウェブサイト名
- 親ページ名
- 投稿一覧のページ名
- 投稿のページ名
- 「パーマリンク設定」でカテゴリーを含む場合の投稿ページの表示例
- URL例: https://example.com/%category%/%postname%
- ウェブサイト名
- 親ページ名
- 投稿一覧のページ名
- 親カテゴリー名
- 子カテゴリー名
- 投稿のページ名
- 「パーマリンク設定」で年と月を含む場合の投稿ページの表示例
- URL例: https://example.com/%year%/%monthnum%/%postname%
- ウェブサイト名
- 親ページ名
- 投稿一覧のページ名
- 年別アーカイブ名
- 月別アーカイブ名
- 投稿のページ名
- 固定ページのURLを含まないカスタム投稿ページの表示例
- URL例: https://example.com/custom-posts/%postname%
- ウェブサイト名
- カスタム投稿一覧のページ名
- カスタム投稿のページ名
- 固定ページのURLを含むカスタム投稿ページの表示例
- URL例: https://example.com/parent-page/custom-posts/%postname%
- ウェブサイト名
- 親ページ名
- カスタム投稿一覧のページ名
- カスタム投稿のページ名
カテゴリーアーカイブ・タグアーカイブ・タクソノミーアーカイブの処理
…
$filter = 'single_term_title';
if( is_category() ) {
$filter = 'single_cat_title';
} elseif( is_tag() ) {
$filter = 'single_tag_title';
}
if( 0 !== $object->parent ) {
$ancestors = array_reverse( get_ancestors( $object->term_id, $object->taxonomy, 'taxonomy' ) );
foreach( $ancestors as $ancestor ) {
$term = get_term( $ancestor, $object->taxonomy );
$items[] = [
'name' => apply_filters( $filter, $term->name ),
'url' => get_term_link( $term, $object->taxonomy ),
];
}#endforeach
}
$items[] = [
'name' => apply_filters( $filter, $object->name ),
'url' => get_term_link( $object, $object->taxonomy ),
];
…
「カテゴリーアーカイブ (is_category()
)」と「階層型タクソノミーアーカイブ (is_tax() && is_taxonomy_hierarchical(…)
)」、「タグアーカイブ (is_tag()
)」と「非階層型タクソノミーアーカイブ (is_tax() && !is_taxonomy_hierarchical(…)
)」の2種類が存在するので、それを念頭に置いてページ情報を加えます。
- カテゴリーアーカイブの表示例
- URL例: https://example.com/category/parent-category-term/child-category-term
- ウェブサイト名
- 親ページ名
- 投稿一覧のページ名
- 親カテゴリー名
- 子カテゴリー名
- カスタム投稿ページに関連付けられた階層型タクソノミーアーカイブの表示例
- URL例: https://example.com/custom-category/parent-term/child-term
- ウェブサイト名
- 親ページ名
- カスタム投稿一覧のページ名
- 親ターム名
- 子ターム名
- タグアーカイブの表示例
- URL例: https://example.com/tag/tag-term
- ウェブサイト名
- 親ページ名
- 投稿一覧のページ名
- タグ名
- カスタム投稿ページに関連付けられた非階層型タクソノミーアーカイブの表示例
- URL例: https://example.com/custom-tag/term
- ウェブサイト名
- 親ページ名
- カスタム投稿一覧のページ名
- ターム名
日付アーカイブの処理
…
if( is_year() || is_month() || is_day() ) {
$items[] = [
'name' => get_the_date( _x( 'Y', 'yearly archives date format', 'my_domain' ) ),
'url' => get_year_link( get_query_var( 'year' ) ),
];
if( is_month() || is_day() ) {
$items[] = [
'name' => get_the_date( _x( 'm', 'monthly archives date format', 'my_domain' ) ),
'url' => get_month_link( get_query_var( 'year' ), get_query_var( 'monthnum' ) ),
];
if( is_day() ) {
$items[] = [
'name' => get_the_date( _x( 'd', 'daily archives date format', 'my_domain' ) ),
'url' => get_day_link( get_query_var( 'year' ), get_query_var( 'monthnum' ), get_query_var( 'day' ) ),
];
}
}
}
…
「年別アーカイブ」「月別アーカイブ」「日別アーカイブ」のページ情報を加えます。名前 (name) の部分は翻訳対象箇所となっているので、適宜変更してください。
- 年別アーカイブページの表示例
- URL例: https://example.com/2021
- ウェブサイト名
- 親ページ名
- 投稿一覧のページ名
- 2021年
- 日別アーカイブページの表示例
- URL例: https://example.com/2021/01/01
- ウェブサイト名
- 親ページ名
- 投稿一覧のページ名
- 2021年
- 1月
- 1日
投稿者アーカイブの処理
…
$items[] = [
'name' => $object->display_name,
'url' => get_author_posts_url( $object->ID, $object->user_nicename ),
];
…
- 投稿者アーカイブページの表示例
- URL例: https://example.com/author/author-name
- ウェブサイト名
- 親ページ名
- 投稿一覧のページ名
- 投稿者名
分割ページの処理
…
if( is_singular() ) {
$page = (int) get_query_var( 'page' );
if( 1 < $page ) {
$link = '#';
if( !is_preview() ) {
$post = get_post();
if( !get_option( 'permalink_structure' ) || false !== strpos( '[draft][pending]', "[{$post->post_status}]" ) ) {
$link = add_query_arg( 'page', $page, get_permalink() );
} elseif( 'page' === get_option( 'show_on_front' ) && get_option( 'page_on_front' ) == $post->ID ) {
$link = trailingslashit( get_permalink() ). user_trailingslashit( "{$GLOBALS[ 'wp_rewrite' ]->pagination_base}/{$page}", 'single_paged' );
} else {
$link = trailingslashit( get_permalink() ). user_trailingslashit( $page, 'single_paged' );
}
}
$items[] = [
'name' => sprintf( __( 'Page %s', 'my_domain' ), $page ),
'url' => $link,
];
}
} elseif( is_home() || is_archive() || is_search() ) {
$paged = (int) get_query_var( 'paged' );
if( 1 < $paged ) {
$items[] = [
'name' => sprintf( __( 'Page %s', 'my_domain' ), $paged ),
'url' => get_pagenum_link( $paged ),
];
}
}
…
配列 $items
に加える最後の部分は、分割ページのページ情報です。各種投稿ページ用と各種アーカイブ・検索結果ページ用の2種類です。
各種アーカイブ・検索結果ページ用のURLは get_pagenum_link()
で取得できますが、各種投稿ページ用のURLを取得できるテンプレート関数がWordPressにはありません。そこで、_wp_link_page()
のPHPコードを流用します。
- ページ分割された投稿一覧ページの表示例
- URL例: https://example.com/parent-page/posts/page/2
- ウェブサイト名
- 親ページ名
- 投稿一覧のページ名
- ページ2
- ページ分割された投稿ページの表示例
- URL例: https://example.com/%postname%/2
- ウェブサイト名
- 親ページ名
- 投稿一覧のページ名
- 投稿のページ名
- ページ2
出力処理
…
$output = '<ol itemscope="itemscope" itemtype="https://schema.org/BreadcrumbList">';
foreach( $items as $key => $item ) {
$output .= sprintf(
'<li itemprop="itemListElement" itemscope="itemscope" itemtype="https://schema.org/ListItem"%1$s>'.
'<span itemprop="name"><a href="%2$s" itemprop="item">%3$s</a></span>'.
'<meta itemprop="position" content="%4$s" />'.
'</li>',
( !isset( $items[ $key + 1 ] ) ? ' aria-current="page"' : '' ),
esc_url( $item[ 'url' ] ),
$item[ 'name' ],
( $key + 1 )
);
}
$output .= '</ol>';
printf( $format, $output );
…
これまで配列 $items
に加えてきたページ情報を foreach()
でループ処理して、HTMLにしていきます。最後に my_breadcrumb_list()
の引数である $format
で整形して出力します。
〆
今回のPHPコードは、大抵のWordPressサイトで機能するよう作りましたが、「パーマリンク設定」やカスタム投稿タイプ (カスタムポスト)・カスタム分類 (カスタムタクソノミー) の設定の仕方によっては意図した通りに表示されないかもしれません (それ以前になにかミスしている可能性もあるかもしれません)。
なので、ご利用は自己責任のうえ、適宜カスタマイズしてご利用ください。
「ココこうしたらイイんじゃね」的なものがありましたら、コメントなどから教えてください。