Instantly share code, notes, and snippets.
Created
January 11, 2024 01:25
-
Star
(0)
0
You must be signed in to star a gist -
Fork
(1)
1
You must be signed in to fork a gist
-
Save doiftrue/7d130deae19e7c480a9c1d79d8caf756 to your computer and use it in GitHub Desktop.
[wpkama embed] https://wp-kama.ru/541 Breadcrumbs for WordPress
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* Breadcrumbs for WordPress | |
* | |
* @param string $sep Separator. Default is ' » '. | |
* @param array $l10n For localization. See the variable `$default_l10n`. | |
* @param array $args Options. See the variable `$def_args`. | |
* | |
* @return void Outputs HTML code to the screen | |
* | |
* version 3.3.3 | |
*/ | |
function kama_breadcrumbs( $sep = ' » ', $l10n = [], $args = [] ) { | |
$kb = new Kama_Breadcrumbs; | |
echo $kb->get_crumbs( $sep, $l10n, $args ); | |
} | |
class Kama_Breadcrumbs { | |
public $arg; | |
// Localisation | |
static $l10n = [ | |
'home' => 'Главная', | |
'paged' => 'Страница %d', | |
'_404' => 'Ошибка 404', | |
'search' => 'Результаты поиска по запросу - <b>%s</b>', | |
'author' => 'Архив автора: <b>%s</b>', | |
'year' => 'Архив за <b>%d</b> год', | |
'month' => 'Архив за: <b>%s</b>', | |
'day' => '', | |
'attachment' => 'Медиа: %s', | |
'tag' => 'Записи по метке: <b>%s</b>', | |
'tax_tag' => '%1$s из "%2$s" по тегу: <b>%3$s</b>', | |
// tax_tag will output: 'post_type from "taxonomy_name" by tag: term_name'. | |
// If separate placeholders are needed, for example, only the term name, write like this: 'posts by tag: %3$s' | |
]; | |
// Default parameters | |
static $args = [ | |
// Display breadcrumbs on the front page | |
'on_front_page' => true, | |
// Show the post title at the end (last element). For posts, pages, attachments | |
'show_post_title' => true, | |
// Show the taxonomy item title at the end (last element). For tags, categories, and other taxonomies | |
'show_term_title' => true, | |
// Template for the last title. If enabled: show_post_title or show_term_title | |
'title_patt' => '<span class="kb_title">%s</span>', | |
// Show the last separator when the title at the end is not displayed | |
'last_sep' => true, | |
// 'markup' - microdata. Can be: 'rdf.data-vocabulary.org', 'schema.org', '' - without microdata | |
// Or you can specify your own markup array: | |
// array( 'wrappatt'=>'<div class="kama_breadcrumbs">%s</div>', 'linkpatt'=>'<a href="%s">%s</a>', 'sep_after'=>'', ) | |
'markup' => 'schema.org', | |
// Priority taxonomies, needed when a post is in multiple taxonomies | |
'priority_tax' => [ 'category' ], | |
// 'priority_terms' - priority items of taxonomies when a post is in multiple items of the same taxonomy at the same time. | |
// For example: array( 'category'=>array(45,'term_name'), 'tax_name'=>array(1,2,'name') ) | |
// 'category' - taxonomy for which priority items are specified: 45 - term ID and 'term_name' - label. | |
// The order of 45 and 'term_name' matters: the earlier, the more important. All specified terms are more important than unspecified ones... | |
'priority_terms' => [], | |
// Add rel=nofollow to links? | |
'nofollow' => false, | |
// Service | |
'sep' => '', | |
'linkpatt' => '', | |
'pg_end' => '', | |
]; | |
function get_crumbs( $sep, $l10n, $args ) { | |
global $post, $wp_post_types; | |
self::$args['sep'] = $sep; | |
// Filters the defaults and merges | |
$loc = (object) array_merge( apply_filters( 'kama_breadcrumbs_default_loc', self::$l10n ), $l10n ); | |
$arg = (object) array_merge( apply_filters( 'kama_breadcrumbs_default_args', self::$args ), $args ); | |
$arg->sep = '<span class="kb_sep">' . $arg->sep . '</span>'; // supplement | |
// simplify | |
$sep = &$arg->sep; | |
$this->arg = &$arg; | |
// micro-markup --- | |
if( 1 ){ | |
$mark = &$arg->markup; | |
// Default markup | |
if( ! $mark ){ | |
$mark = [ | |
'wrappatt' => '<div class="kama_breadcrumbs">%s</div>', | |
'linkpatt' => '<a href="%s">%s</a>', | |
'sep_after' => '', | |
]; | |
} | |
// rdf | |
elseif( $mark === 'rdf.data-vocabulary.org' ){ | |
$mark = [ | |
'wrappatt' => '<div class="kama_breadcrumbs" prefix="v: http://rdf.data-vocabulary.org/#">%s</div>', | |
'linkpatt' => '<span typeof="v:Breadcrumb"><a href="%s" rel="v:url" property="v:title">%s</a>', | |
'sep_after' => '</span>', // close span after the separator! | |
]; | |
} | |
// schema.org | |
elseif( $mark === 'schema.org' ){ | |
$mark = [ | |
'wrappatt' => '<div class="kama_breadcrumbs" itemscope itemtype="http://schema.org/BreadcrumbList">%s</div>', | |
'linkpatt' => '<span itemprop="itemListElement" itemscope itemtype="http://schema.org/ListItem"><a href="%s" itemprop="item"><span itemprop="name">%s</span></a></span>', | |
'sep_after' => '', | |
]; | |
} | |
elseif( ! is_array( $mark ) ){ | |
die( __CLASS__ . ': "markup" parameter must be array...' ); | |
} | |
$wrappatt = $mark['wrappatt']; | |
$arg->linkpatt = $arg->nofollow ? str_replace( '<a ', '<a rel="nofollow"', $mark['linkpatt'] ) : $mark['linkpatt']; | |
$arg->sep .= $mark['sep_after'] . "\n"; | |
} | |
$linkpatt = $arg->linkpatt; // simplify | |
$q_obj = get_queried_object(); | |
// maybe it's an empty archive of the tax | |
$ptype = null; | |
if( ! $post ){ | |
if( isset( $q_obj->taxonomy ) ){ | |
$ptype = $wp_post_types[ get_taxonomy( $q_obj->taxonomy )->object_type[0] ]; | |
} | |
} | |
else{ | |
$ptype = $wp_post_types[ $post->post_type ]; | |
} | |
// paged | |
$arg->pg_end = ''; | |
$paged_num = get_query_var( 'paged' ) ?: get_query_var( 'page' ); | |
if( $paged_num ){ | |
$arg->pg_end = $sep . sprintf( $loc->paged, (int) $paged_num ); | |
} | |
$pg_end = $arg->pg_end; // simplify | |
$out = ''; | |
if( is_front_page() ){ | |
return $arg->on_front_page ? sprintf( $wrappatt, ( $paged_num ? sprintf( $linkpatt, get_home_url(), $loc->home ) . $pg_end : $loc->home ) ) : ''; | |
} | |
// post page when a separate page is set for the main page. | |
elseif( is_home() ){ | |
$out = $paged_num ? ( sprintf( $linkpatt, get_permalink( $q_obj ), esc_html( $q_obj->post_title ) ) . $pg_end ) : esc_html( $q_obj->post_title ); | |
} | |
elseif( is_404() ){ | |
$out = $loc->_404; | |
} | |
elseif( is_search() ){ | |
$out = sprintf( $loc->search, esc_html( $GLOBALS['s'] ) ); | |
} | |
elseif( is_author() ){ | |
$tit = sprintf( $loc->author, esc_html( $q_obj->display_name ) ); | |
$out = ( $paged_num ? sprintf( $linkpatt, get_author_posts_url( $q_obj->ID, $q_obj->user_nicename ) . $pg_end, $tit ) : $tit ); | |
} | |
elseif( is_year() || is_month() || is_day() ){ | |
$y_url = get_year_link( $year = get_the_time( 'Y' ) ); | |
if( is_year() ){ | |
$tit = sprintf( $loc->year, $year ); | |
$out = ( $paged_num ? sprintf( $linkpatt, $y_url, $tit ) . $pg_end : $tit ); | |
} | |
// month day | |
else{ | |
$y_link = sprintf( $linkpatt, $y_url, $year ); | |
$m_url = get_month_link( $year, get_the_time( 'm' ) ); | |
if( is_month() ){ | |
$tit = sprintf( $loc->month, get_the_time( 'F' ) ); | |
$out = $y_link . $sep . ( $paged_num ? sprintf( $linkpatt, $m_url, $tit ) . $pg_end : $tit ); | |
} | |
elseif( is_day() ){ | |
$m_link = sprintf( $linkpatt, $m_url, get_the_time( 'F' ) ); | |
$out = $y_link . $sep . $m_link . $sep . get_the_time( 'l' ); | |
} | |
} | |
} | |
// Hierarchical posts | |
elseif( is_singular() && $ptype->hierarchical ){ | |
$out = $this->_add_title( $this->_page_crumbs( $post ), $post ); | |
} | |
// Taxonomies, flat posts, and attachments | |
else{ | |
$term = $q_obj; // taxonomies | |
// define a term for posts (including attachments) | |
if( is_singular() ){ | |
// modify $post to define the term for the parent of the attachment | |
if( is_attachment() && $post->post_parent ){ | |
$save_post = $post; // сохраним | |
$post = get_post( $post->post_parent ); | |
} | |
// takes into account if attachments are attached to hierarchical taxonomies - it happens :) | |
$taxonomies = get_object_taxonomies( $post->post_type ); | |
// let's leave only hierarchical and public, just in case.... | |
$taxonomies = array_intersect( $taxonomies, get_taxonomies( [ | |
'hierarchical' => true, | |
'public' => true, | |
] ) ); | |
if( $taxonomies ){ | |
// sort by priority | |
if( ! empty( $arg->priority_tax ) ){ | |
usort( $taxonomies, static function( $a, $b ) use ( $arg ) { | |
$a_index = array_search( $a, $arg->priority_tax ); | |
if( $a_index === false ){ | |
$a_index = 9999999; | |
} | |
$b_index = array_search( $b, $arg->priority_tax ); | |
if( $b_index === false ){ | |
$b_index = 9999999; | |
} | |
return ( $b_index === $a_index ) ? 0 : ( $b_index < $a_index ? 1 : -1 ); // меньше индекс - выше | |
} ); | |
} | |
// try to get the terms, in order of tax priority | |
foreach( $taxonomies as $taxname ){ | |
if( $terms = get_the_terms( $post->ID, $taxname ) ){ | |
// проверим приоритетные термины для таксы | |
$prior_terms = &$arg->priority_terms[ $taxname ]; | |
if( $prior_terms && count( $terms ) > 2 ){ | |
foreach( (array) $prior_terms as $term_id ){ | |
$filter_field = is_numeric( $term_id ) ? 'term_id' : 'slug'; | |
$_terms = wp_list_filter( $terms, [ $filter_field => $term_id ] ); | |
if( $_terms ){ | |
$term = array_shift( $_terms ); | |
break; | |
} | |
} | |
} | |
else{ | |
$term = array_shift( $terms ); | |
} | |
break; | |
} | |
} | |
} | |
// revert back (for attachments) | |
if( isset( $save_post ) ){ | |
$post = $save_post; | |
} | |
} | |
// output | |
// terms or post of any type with terms | |
if( $term && isset( $term->term_id ) ){ | |
$term = apply_filters( 'kama_breadcrumbs_term', $term ); | |
// attachment | |
if( is_attachment() ){ | |
if( ! $post->post_parent ){ | |
$out = sprintf( $loc->attachment, esc_html( $post->post_title ) ); | |
} | |
else{ | |
if( ! $out = apply_filters( 'attachment_tax_crumbs', '', $term, $this ) ){ | |
$_crumbs = $this->_tax_crumbs( $term, 'self' ); | |
$parent_tit = sprintf( $linkpatt, get_permalink( $post->post_parent ), get_the_title( $post->post_parent ) ); | |
$_out = implode( $sep, [ $_crumbs, $parent_tit ] ); | |
$out = $this->_add_title( $_out, $post ); | |
} | |
} | |
} | |
// single | |
elseif( is_single() ){ | |
if( ! $out = apply_filters( 'post_tax_crumbs', '', $term, $this ) ){ | |
$_crumbs = $this->_tax_crumbs( $term, 'self' ); | |
$out = $this->_add_title( $_crumbs, $post ); | |
} | |
} | |
// non-hierarchical taxonomy (tags) | |
elseif( ! is_taxonomy_hierarchical( $term->taxonomy ) ){ | |
// метка | |
if( is_tag() ){ | |
$out = $this->_add_title( '', $term, sprintf( $loc->tag, esc_html( $term->name ) ) ); | |
} | |
// taxonomy | |
elseif( is_tax() ){ | |
$post_label = $ptype->labels->name; | |
$tax_label = $GLOBALS['wp_taxonomies'][ $term->taxonomy ]->labels->name; | |
$out = $this->_add_title( '', $term, sprintf( $loc->tax_tag, $post_label, $tax_label, esc_html( $term->name ) ) ); | |
} | |
} | |
// hierarchical taxonomy (category) | |
elseif( ! $out = apply_filters( 'term_tax_crumbs', '', $term, $this ) ){ | |
$_crumbs = $this->_tax_crumbs( $term, 'parent' ); | |
$out = $this->_add_title( $_crumbs, $term, esc_html( $term->name ) ); | |
} | |
} | |
// attachments (of post) without terms | |
elseif( is_attachment() ){ | |
$parent = get_post( $post->post_parent ); | |
$parent_link = sprintf( $linkpatt, get_permalink( $parent ), esc_html( $parent->post_title ) ); | |
$_out = $parent_link; | |
// attachment of post with hierarchical type | |
if( is_post_type_hierarchical( $parent->post_type ) ){ | |
$parent_crumbs = $this->_page_crumbs( $parent ); | |
$_out = implode( $sep, [ $parent_crumbs, $parent_link ] ); | |
} | |
$out = $this->_add_title( $_out, $post ); | |
} | |
// posts without terms | |
elseif( is_singular() ){ | |
$out = $this->_add_title( '', $post ); | |
} | |
} | |
// replacement of link to post type archive page | |
$home_after = apply_filters( 'kama_breadcrumbs_home_after', '', $linkpatt, $sep, $ptype ); | |
if( '' === $home_after ){ | |
// A link to the archive page of a post type for: | |
// individual pages of that type; archives of that type; taxonomies associated with that type. | |
if( $ptype && $ptype->has_archive && ! in_array( $ptype->name, [ 'post', 'page', 'attachment' ] ) | |
&& ( is_post_type_archive() || is_singular() || ( is_tax() && in_array( $term->taxonomy, $ptype->taxonomies ) ) ) | |
){ | |
$pt_title = $ptype->labels->name; | |
// first page of the post type archive | |
if( is_post_type_archive() && ! $paged_num ){ | |
$home_after = sprintf( $this->arg->title_patt, $pt_title ); | |
} | |
// singular, paged post_type_archive, tax | |
else{ | |
$home_after = sprintf( $linkpatt, get_post_type_archive_link( $ptype->name ), $pt_title ); | |
$home_after .= ( ( $paged_num && ! is_tax() ) ? $pg_end : $sep ); // pagination | |
} | |
} | |
} | |
$before_out = sprintf( $linkpatt, home_url(), $loc->home ) . ( $home_after ? $sep . $home_after : ( $out ? $sep : '' ) ); | |
$out = apply_filters( 'kama_breadcrumbs_pre_out', $out, $sep, $loc, $arg ); | |
$out = sprintf( $wrappatt, $before_out . $out ); | |
return apply_filters( 'kama_breadcrumbs', $out, $sep, $loc, $arg ); | |
} | |
function _page_crumbs( $post ) { | |
$parent = $post->post_parent; | |
$crumbs = []; | |
while( $parent ){ | |
$page = get_post( $parent ); | |
$crumbs[] = sprintf( $this->arg->linkpatt, get_permalink( $page ), esc_html( $page->post_title ) ); | |
$parent = $page->post_parent; | |
} | |
return implode( $this->arg->sep, array_reverse( $crumbs ) ); | |
} | |
function _tax_crumbs( $term, $start_from = 'self' ) { | |
$termlinks = []; | |
$term_id = ( $start_from === 'parent' ) ? $term->parent : $term->term_id; | |
while( $term_id ){ | |
$term = get_term( $term_id, $term->taxonomy ); | |
$termlinks[] = sprintf( $this->arg->linkpatt, get_term_link( $term ), esc_html( $term->name ) ); | |
$term_id = $term->parent; | |
} | |
if( $termlinks ){ | |
return implode( $this->arg->sep, array_reverse( $termlinks ) ); | |
} | |
return ''; | |
} | |
/** | |
* Adds a title to the passed text, taking into account all options. | |
* Adds a delimiter at the beginning, if necessary. | |
* | |
* @param string $add_to | |
* @param object $obj | |
* @param string $term_title | |
* | |
* @return string | |
*/ | |
function _add_title( $add_to, $obj, $term_title = '' ) { | |
// simplify. | |
$arg = & $this->arg; | |
// $term_title is cleaned separately, HTML tags can be inside. | |
$title = $term_title ?: esc_html( $obj->post_title ); | |
$show_title = $term_title ? $arg->show_term_title : $arg->show_post_title; | |
// pagination | |
if( $arg->pg_end ){ | |
$link = $term_title ? get_term_link( $obj ) : get_permalink( $obj ); | |
$add_to .= ( $add_to ? $arg->sep : '' ) . sprintf( $arg->linkpatt, $link, $title ) . $arg->pg_end; | |
} | |
// add sep | |
elseif( $add_to ){ | |
if( $show_title ){ | |
$add_to .= $arg->sep . sprintf( $arg->title_patt, $title ); | |
} | |
elseif( $arg->last_sep ){ | |
$add_to .= $arg->sep; | |
} | |
} | |
// sep will be later... | |
elseif( $show_title ){ | |
$add_to = sprintf( $arg->title_patt, $title ); | |
} | |
return $add_to; | |
} | |
} | |
/** | |
* Changelog: | |
* 3.3 - new hooks: attachment_tax_crumbs, post_tax_crumbs, term_tax_crumbs. Allow to extend taxonomy breadcrumbs. | |
* 3.2 - fixed an issue with the separator when 'show_term_title' is disabled. Stabilized the logic. | |
* 3.1 - fixed an issue with esc_html() for term titles - it was rendering tags incorrectly... | |
* 3.0 - Wrapped in a class. Added options: 'title_patt', 'last_sep'. Refactored the code. Added pagination for posts. | |
* 2.5 - ADD: 'show_term_title' option | |
* 2.4 - Minor code adjustments | |
* 2.3 - ADD: Posts page when a separate page is set for the homepage. | |
* 2.2 - ADD: Link to post type archive on taxonomies page | |
* 2.1 - ADD: $sep, $loc, $args params to hooks | |
* 2.0 - ADD: fourth argument $ptype to the 'kama_breadcrumbs_home_after' filter | |
* 1.9 - ADD: 'kama_breadcrumbs_default_loc' filter to change the default localization | |
* 1.8 - FIX: notes when there are no posts in the category | |
* 1.7 - Improved handling of primary taxonomies. | |
*/ | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment