Breadcrumbs for WordPress

You are probably already familiar with the concept of "Breadcrumbs" in web development and may have even had to implement these "breadcrumbs" in WordPress.

Breadcrumbs - is a site navigation element that looks like a path from the main page to the current one where the user is located. A more logical name is navigation chain. Breadcrumbs are so named due to an ironic analogy with the fairy tale in which children, when they were taken into the forest for the second time, could not find their way back because this time instead of leaving small stones behind, they left breadcrumbs, which were subsequently eaten by forest birds.

I no longer support this code, in some places it does not work as needed. For a stable and extended version of this code, go to the link:

View the paid version of Kama Breadcrumbs

Breadcrumbs look like this:

Home page » Section » Subsection » Current page

"Breadcrumbs" are most recommended for sites with a complex structure of sections (categories), as they make it much easier and clearer for visitors to understand which section of the site they are in, and if necessary, they can easily go up one level and view the entire branch.

Now, after a brief introduction, I will share another function for WordPress that implements the entire chain from the main page to the current one on all types of pages, including taxonomies and custom post types.

The function will display "breadcrumbs" for the following types of pages:
  • Home page;
  • Static page;
  • Page of any hierarchical post type;
  • Post page;
  • Attachment page (whether the attachment is attached to the post or not);
  • Any non-hierarchical post type (attached to any taxonomy, for example, to standard "categories");
  • Category page;
  • Tag page;
  • Taxonomy page (both hierarchical and flat (tags));
  • Archive pages by dates, authors;
  • Pagination page for all types where pagination is provided
    (displayed as: Home » Category » Page 2,3,4).
  • Supports microdata. Tools for checking: for Yandex and for Google.

Among the features that I have not found in similar functions available online, it is worth noting the correct display of "breadcrumbs" for custom post types and custom taxonomies. Also, in other analogs, the pagination page was displayed as, for example, "Category (page 2)" instead of "Category > Page 2", which, in my opinion, is incorrect.

For visual perception, take a look at how "breadcrumbs" of different types look:

I also tried to write the function in the most performance way possible.

As for the Breadcrumb NavXT plugin, which is widely recommended for displaying "breadcrumbs" - I did not like it because of its complexity. My function is no worse, and in some aspects, even better: due to functionality, compactness, and in some cases, speed!

The function also supports microdata: schema.org or RDF, see the parameter 'markup'.

Function "Breadcrumbs" for WordPress

GitHub
<?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.
 */
 

This code should be inserted into the functions.php template file or directly into the file where the function is called.

The function should be called in the template, where the breadcrumbs should be displayed, like this:

<?php if( function_exists('kama_breadcrumbs') ) kama_breadcrumbs(); ?>

If you need to change the separator between the links, specify the first parameter:

<?php if( function_exists('kama_breadcrumbs') ) kama_breadcrumbs(' » '); ?>

Examples of using filters

#1 Setting parameters through a filter

Change the default parameters through a filter

add_filter( 'kama_breadcrumbs_default_args', function( $args ){

	$args['on_front_page']   = 0;
	$args['show_post_title'] = '';
	$args['priority_tax']    = [ 'mytax' ];

	return $args;
} );

If you set the parameters when calling the function in the third argument of the function, they will override the parameters specified in the filter...

#3 Example of translating breadcrumbs into English

These examples show how to translate breadcrumbs into the desired language or simply change the default values:

Option 1

When calling the function, you need to specify localization strings like this:

// Localization
if( function_exists( 'kama_breadcrumbs' ) ){

	$myl10n = array(
		'home'       => 'Front page',
		'paged'      => 'Page %d',
		'_404'       => 'Error 404',
		'search'     => 'Search results by query - <b>%s</b>',
		'author'     => 'Author archive: <b>%s</b>',
		'year'       => 'Archive by <b>%d</b> year',
		'month'      => 'Archive by: <b>%s</b>',
		'day'        => '',
		'attachment' => 'Media: %s',
		'tag'        => 'Posts by tag: <b>%s</b>',
		'tax_tag'    => '%1$s from "%2$s" by tag: <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'
	);

	kama_breadcrumbs( ' » ', $myl10n );

}
Option 2

Starting from version 1.9. You can use the hook kama_breadcrumbs_default_loc to avoid specifying the same thing for each call. To do this, add this hook next to the source code of the breadcrumbs:

add_filter( 'kama_breadcrumbs_default_loc', function( $l10n ){

	// Localization
	return array(
		'home'       => 'Front page',
		'paged'      => 'Page %d',
		'_404'       => 'Error 404',
		'search'     => 'Search results by query - <b>%s</b>',
		'author'     => 'Author archive: <b>%s</b>',
		'year'       => 'Archive by <b>%d</b> year',
		'month'      => 'Archive by: <b>%s</b>',
		'day'        => '',
		'attachment' => 'Media: %s',
		'tag'        => 'Posts by tag: <b>%s</b>',
		'tax_tag'    => '%1$s from "%2$s" by tag: <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'
	);

} );

Then, use the call in the template as usual, and the breadcrumbs will be translated into English:

function_exists( 'kama_breadcrumbs' ) && kama_breadcrumbs();

Settings through the hook have lower priority than through the function call in option 1. This means that if you specify localization through the hook, then later through the call, you can override the localization of individual strings.

#3 Adding a custom link to the beginning of the breadcrumbs

Suppose we need to add a link to page 7 after the "Home" item if we are currently in category 5 or its child category (one level of nesting is considered).

To do this, add this hook next to the breadcrumbs code:

add_action( 'kama_breadcrumbs_home_after', 'my_breadcrumbs_home_after', 10, 4 );

function my_breadcrumbs_home_after( $false, $linkpatt, $sep, $ptype ){

	// if we are in category with ID 5 or in a child category,
	// then add a link to page with ID 7 to the beginning of the breadcrumbs
	$qo = get_queried_object();

	if( is_category() && ( $qo->term_id == 5 || $qo->parent == 5 ) ){
		$page = get_post( 7 );

		return sprintf( $linkpatt, get_permalink($page), $page->post_title ) . $sep;
	}

	return $false;
}

#4 Adding additional taxonomies to the breadcrumbs

By default, only one taxonomy is processed in the breadcrumbs. But sometimes you need more than one, for this, in version 3.3, I inserted hooks: 'attachment_tax_crumbs', 'post_tax_crumbs', 'term_tax_crumbs.

Suppose we have a post type realty and 3 taxonomies for it: country, type_deal, type_realty. We need the breadcrumbs on the post page to display all the taxonomies in the specified order. Also, on each page, the taxonomies should display all the previous taxonomies and the current one in the specified order: country > type_deal > type_realty...

add_filter( 'term_tax_crumbs', 'more_tax_crumbs', 10, 3 );
add_filter( 'post_tax_crumbs', 'more_tax_crumbs', 10, 3 );

function more_tax_crumbs( $empty, $term, $that ){

	$is_post_filter = doing_filter('post_tax_crumbs'); // else 'term_tax_crumbs'

	if( ( $is_post_filter && is_singular('realty') ) || is_tax('country') ){
		global $post;

		$out = '';

		$out = $that->_tax_crumbs( $term, 'self' ) . $that->arg->sep; // base tax - country

		// deal type
		$term = get_query_var('type_deal');
		if( $term && ( $term = get_term_by('slug', $term, 'type_deal') ) )
			$out .= $that->_tax_crumbs( $term, 'self' ) . $that->arg->sep; // deal type

		// realty type
		$term = get_query_var('type_realty');
		if( $term && ( $term = get_term_by('slug', $term, 'type_realty') ) ){
			// post
			if( $is_post_filter ){
				$_crumbs = $that->_tax_crumbs( $term, 'self' );
				$out .= $that->_add_title( $_crumbs, $post );
			}
			// tax
			else {
				$_crumbs = $that->_tax_crumbs( $term, 'parent' );
				$out .= $that->_add_title( $_crumbs, $term, esc_html($term->name) );
			}

		}

		return $out;
	}

	return $empty;
}

Another option for breadcrumbs

This code is suitable not only for WordPress, but for any engine in general.

For WordPress, it will work:

  • firstly, if permalinks are enabled;
  • secondly, if the links contain category names;
  • thirdly, if the names of articles and categories in the URL are written in Cyrillic or it is an English blog in general.

In other cases, it will work, but I think it will not be very good. These conditions are because this option parses the page link (URL) and creates breadcrumbs based on its elements. The link is split by the delimiter /.

Suppose the URL of an article looks like this:
http://example.com/recipes/cake/making-napoleon
then we will get a breadcrumb chain like this:
Home » Recipes » Cake » Making Napoleon

function breadcrumbs( $separator = ' » ', $home = 'Home' ) {

	$path = array_filter( explode( '/', parse_url( $_SERVER['REQUEST_URI'], PHP_URL_PATH ) ) );
	$base_url = ( $_SERVER['HTTPS'] ? 'https' : 'http' ) . '://' . $_SERVER['HTTP_HOST'] . '/';
	$breadcrumbs = array("<a href=\"$base_url\">$home</a>");

	$last = end( array_keys( $path ) );

	foreach( $path as $x => $crumb ){

		$title = ucwords( str_replace( [ '.php', '_' ], [ '', ' ' ], $crumb ) );

		if( $x != $last ){
			$breadcrumbs[] = '<a href="'. $base_url . $crumb .'">'. $title .'</a>';
		}
		else {
			$breadcrumbs[] = $title;
		}
	}

	return implode( $separator, $breadcrumbs );
}

It is used similarly to my function, but it should be displayed on the screen through echo:

<?php echo breadcrumbs(' » '); ?>