Главная WordPress

«Хлебные крошки» для WordPress без использования плагина

«Хлебные крошки» — это важный элемент навигации веб-сайта, который повышает его юзабилити. Особенно это касается сайтов со сложной структурой. Я, к сожалению (а, может, и не к сожалению), не использую их на большинстве своих сайтов, возможно, потому, что у них слишком простая структура (для такого сайта, как этот, они, вроде бы, и не нужны).

Когда я решаю какую-либо задачу на WordPress-сайте, то всегда стараюсь обходится без плагинов, где это бывает возможно. Зачастую одна и та же задача может быть решена с меньшим количеством кода и бывает менее ресурсоемкой по сравнению с применением плагинов.

В Интернете я находил разные варианты реализации «хлебных крошек» без использования плагинов, но ни один из них меня не устроил, поскольку все они не показывали полную цепочку ссылок. Поэтому я создал свою функцию «хлебных крошек» для WordPress. И в данном посте хочу поделиться этой функцией с вами.

Особенности функции

  • Отображается полная цепочка ссылок до текущей страницы. Например, если текущая страница находится в рубрике второго уровня, то цепочка будет выглядеть следующим образом:

    Главная > Рубрика > Подрубрика > Название статьи

    Во всех решениях, которые я встречал (за исключением плагинов), такая цепочка выглядела вот так:

    Главная > Подрубрика > Название статьи

    Т. е. одно звено потеряно.

    Аналогично у меня выглядят и «крошки» для страниц. К примеру, для страницы 3-го уровня вложенности цепочка будет такой:

    Главная > Страница 1-го уровня > Страница 2-го уровня > Страница 3-го уровня

  • «Хлебные крошки» выводятся для следующих типов страниц WordPress-сайта:

    • постраничная навигация с главной страницы (вида site.ru/page/2/);
    • архив рубрики;
    • архив тега;
    • архив за день;
    • архив за месяц;
    • архив за год;
    • архив автора;
    • произвольный тип записи;
    • страница;
    • пост;
    • результаты поиска;
    • страница с ошибкой 404.
  • Добавляется порядковый номер страницы, если это 2-я или больше страница архивов.
  • Можно задать любой символ разделителя между ссылками.
  • Можно задать текст для ссылка «Главная».
  • Интегрирована микроразметка Schema.org.

Функция «Хлебные крошки» для WordPress (обновлено: 03.03.2019)

/*
 * "Хлебные крошки" для WordPress
 * автор: Dimox
 * версия: 2019.03.03
 * лицензия: MIT
*/
function dimox_breadcrumbs() {

	/* === ОПЦИИ === */
	$text['home']     = 'Главная'; // текст ссылки "Главная"
	$text['category'] = '%s'; // текст для страницы рубрики
	$text['search']   = 'Результаты поиска по запросу "%s"'; // текст для страницы с результатами поиска
	$text['tag']      = 'Записи с тегом "%s"'; // текст для страницы тега
	$text['author']   = 'Статьи автора %s'; // текст для страницы автора
	$text['404']      = 'Ошибка 404'; // текст для страницы 404
	$text['page']     = 'Страница %s'; // текст 'Страница N'
	$text['cpage']    = 'Страница комментариев %s'; // текст 'Страница комментариев N'

	$wrap_before    = '<div class="breadcrumbs" itemscope itemtype="http://schema.org/BreadcrumbList">'; // открывающий тег обертки
	$wrap_after     = '</div><!-- .breadcrumbs -->'; // закрывающий тег обертки
	$sep            = '<span class="breadcrumbs__separator"> › </span>'; // разделитель между "крошками"
	$before         = '<span class="breadcrumbs__current">'; // тег перед текущей "крошкой"
	$after          = '</span>'; // тег после текущей "крошки"

	$show_on_home   = 0; // 1 - показывать "хлебные крошки" на главной странице, 0 - не показывать
	$show_home_link = 1; // 1 - показывать ссылку "Главная", 0 - не показывать
	$show_current   = 1; // 1 - показывать название текущей страницы, 0 - не показывать
	$show_last_sep  = 1; // 1 - показывать последний разделитель, когда название текущей страницы не отображается, 0 - не показывать
	/* === КОНЕЦ ОПЦИЙ === */

	global $post;
	$home_url       = home_url('/');
	$link           = '<span itemprop="itemListElement" itemscope itemtype="http://schema.org/ListItem">';
	$link          .= '<a class="breadcrumbs__link" href="%1$s" itemprop="item"><span itemprop="name">%2$s</span></a>';
	$link          .= '<meta itemprop="position" content="%3$s" />';
	$link          .= '</span>';
	$parent_id      = ( $post ) ? $post->post_parent : '';
	$home_link      = sprintf( $link, $home_url, $text['home'], 1 );

	if ( is_home() || is_front_page() ) {

		if ( $show_on_home ) echo $wrap_before . $home_link . $wrap_after;

	} else {

		$position = 0;

		echo $wrap_before;

		if ( $show_home_link ) {
			$position += 1;
			echo $home_link;
		}

		if ( is_category() ) {
			$parents = get_ancestors( get_query_var('cat'), 'category' );
			foreach ( array_reverse( $parents ) as $cat ) {
				$position += 1;
				if ( $position > 1 ) echo $sep;
				echo sprintf( $link, get_category_link( $cat ), get_cat_name( $cat ), $position );
			}
			if ( get_query_var( 'paged' ) ) {
				$position += 1;
				$cat = get_query_var('cat');
				echo $sep . sprintf( $link, get_category_link( $cat ), get_cat_name( $cat ), $position );
				echo $sep . $before . sprintf( $text['page'], get_query_var( 'paged' ) ) . $after;
			} else {
				if ( $show_current ) {
					if ( $position >= 1 ) echo $sep;
					echo $before . sprintf( $text['category'], single_cat_title( '', false ) ) . $after;
				} elseif ( $show_last_sep ) echo $sep;
			}

		} elseif ( is_search() ) {
			if ( get_query_var( 'paged' ) ) {
				$position += 1;
				if ( $show_home_link ) echo $sep;
				echo sprintf( $link, $home_url . '?s=' . get_search_query(), sprintf( $text['search'], get_search_query() ), $position );
				echo $sep . $before . sprintf( $text['page'], get_query_var( 'paged' ) ) . $after;
			} else {
				if ( $show_current ) {
					if ( $position >= 1 ) echo $sep;
					echo $before . sprintf( $text['search'], get_search_query() ) . $after;
				} elseif ( $show_last_sep ) echo $sep;
			}

		} elseif ( is_year() ) {
			if ( $show_home_link && $show_current ) echo $sep;
			if ( $show_current ) echo $before . get_the_time('Y') . $after;
			elseif ( $show_home_link && $show_last_sep ) echo $sep;

		} elseif ( is_month() ) {
			if ( $show_home_link ) echo $sep;
			$position += 1;
			echo sprintf( $link, get_year_link( get_the_time('Y') ), get_the_time('Y'), $position );
			if ( $show_current ) echo $sep . $before . get_the_time('F') . $after;
			elseif ( $show_last_sep ) echo $sep;

		} elseif ( is_day() ) {
			if ( $show_home_link ) echo $sep;
			$position += 1;
			echo sprintf( $link, get_year_link( get_the_time('Y') ), get_the_time('Y'), $position ) . $sep;
			$position += 1;
			echo sprintf( $link, get_month_link( get_the_time('Y'), get_the_time('m') ), get_the_time('F'), $position );
			if ( $show_current ) echo $sep . $before . get_the_time('d') . $after;
			elseif ( $show_last_sep ) echo $sep;

		} elseif ( is_single() && ! is_attachment() ) {
			if ( get_post_type() != 'post' ) {
				$position += 1;
				$post_type = get_post_type_object( get_post_type() );
				if ( $position > 1 ) echo $sep;
				echo sprintf( $link, get_post_type_archive_link( $post_type->name ), $post_type->labels->name, $position );
				if ( $show_current ) echo $sep . $before . get_the_title() . $after;
				elseif ( $show_last_sep ) echo $sep;
			} else {
				$cat = get_the_category(); $catID = $cat[0]->cat_ID;
				$parents = get_ancestors( $catID, 'category' );
				$parents = array_reverse( $parents );
				$parents[] = $catID;
				foreach ( $parents as $cat ) {
					$position += 1;
					if ( $position > 1 ) echo $sep;
					echo sprintf( $link, get_category_link( $cat ), get_cat_name( $cat ), $position );
				}
				if ( get_query_var( 'cpage' ) ) {
					$position += 1;
					echo $sep . sprintf( $link, get_permalink(), get_the_title(), $position );
					echo $sep . $before . sprintf( $text['cpage'], get_query_var( 'cpage' ) ) . $after;
				} else {
					if ( $show_current ) echo $sep . $before . get_the_title() . $after;
					elseif ( $show_last_sep ) echo $sep;
				}
			}

		} elseif ( is_post_type_archive() ) {
			$post_type = get_post_type_object( get_post_type() );
			if ( get_query_var( 'paged' ) ) {
				$position += 1;
				if ( $position > 1 ) echo $sep;
				echo sprintf( $link, get_post_type_archive_link( $post_type->name ), $post_type->label, $position );
				echo $sep . $before . sprintf( $text['page'], get_query_var( 'paged' ) ) . $after;
			} else {
				if ( $show_home_link && $show_current ) echo $sep;
				if ( $show_current ) echo $before . $post_type->label . $after;
				elseif ( $show_home_link && $show_last_sep ) echo $sep;
			}

		} elseif ( is_attachment() ) {
			$parent = get_post( $parent_id );
			$cat = get_the_category( $parent->ID ); $catID = $cat[0]->cat_ID;
			$parents = get_ancestors( $catID, 'category' );
			$parents = array_reverse( $parents );
			$parents[] = $catID;
			foreach ( $parents as $cat ) {
				$position += 1;
				if ( $position > 1 ) echo $sep;
				echo sprintf( $link, get_category_link( $cat ), get_cat_name( $cat ), $position );
			}
			$position += 1;
			echo $sep . sprintf( $link, get_permalink( $parent ), $parent->post_title, $position );
			if ( $show_current ) echo $sep . $before . get_the_title() . $after;
			elseif ( $show_last_sep ) echo $sep;

		} elseif ( is_page() && ! $parent_id ) {
			if ( $show_home_link && $show_current ) echo $sep;
			if ( $show_current ) echo $before . get_the_title() . $after;
			elseif ( $show_home_link && $show_last_sep ) echo $sep;

		} elseif ( is_page() && $parent_id ) {
			$parents = get_post_ancestors( get_the_ID() );
			foreach ( array_reverse( $parents ) as $pageID ) {
				$position += 1;
				if ( $position > 1 ) echo $sep;
				echo sprintf( $link, get_page_link( $pageID ), get_the_title( $pageID ), $position );
			}
			if ( $show_current ) echo $sep . $before . get_the_title() . $after;
			elseif ( $show_last_sep ) echo $sep;

		} elseif ( is_tag() ) {
			if ( get_query_var( 'paged' ) ) {
				$position += 1;
				$tagID = get_query_var( 'tag_id' );
				echo $sep . sprintf( $link, get_tag_link( $tagID ), single_tag_title( '', false ), $position );
				echo $sep . $before . sprintf( $text['page'], get_query_var( 'paged' ) ) . $after;
			} else {
				if ( $show_home_link && $show_current ) echo $sep;
				if ( $show_current ) echo $before . sprintf( $text['tag'], single_tag_title( '', false ) ) . $after;
				elseif ( $show_home_link && $show_last_sep ) echo $sep;
			}

		} elseif ( is_author() ) {
			$author = get_userdata( get_query_var( 'author' ) );
			if ( get_query_var( 'paged' ) ) {
				$position += 1;
				echo $sep . sprintf( $link, get_author_posts_url( $author->ID ), sprintf( $text['author'], $author->display_name ), $position );
				echo $sep . $before . sprintf( $text['page'], get_query_var( 'paged' ) ) . $after;
			} else {
				if ( $show_home_link && $show_current ) echo $sep;
				if ( $show_current ) echo $before . sprintf( $text['author'], $author->display_name ) . $after;
				elseif ( $show_home_link && $show_last_sep ) echo $sep;
			}

		} elseif ( is_404() ) {
			if ( $show_home_link && $show_current ) echo $sep;
			if ( $show_current ) echo $before . $text['404'] . $after;
			elseif ( $show_last_sep ) echo $sep;

		} elseif ( has_post_format() && ! is_singular() ) {
			if ( $show_home_link && $show_current ) echo $sep;
			echo get_post_format_string( get_post_format() );
		}

		echo $wrap_after;

	}
} // end of dimox_breadcrumbs()

Функцию необходимо поместить в файл functions.php вашей WordPress-темы. После этого в то место шаблона, где хотите выводить «хлебные крошки», добавьте следующий код:

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

Единственное, что теперь останется сделать — оформить их с помощью CSS. Для этого к блоку «хлебных крошек» предусмотрен класс .breadcrumbs, для разделителя — .breadcrumbs__separator а для текущей «крошки» — .breadcrumbs__current.

Комментарии (690)

  1. Упс, мой косяк!(все отлично работает, спасибо!)

  2. Отличное рабочее решение! Благодарен автору.

  3. if ($show_current) {
    if ($position ≥ 1)
    echo $sep. $before. get_the_title (). $after; // - здесь не учитывается «position»
    } elseif ($show_last_sep) echo $sep;

    в своих проектах делаю так:

    if ($show_current) {
    $position += 1;
    if ($position ≥ 1)
    echo $sep. $before. get_the_title (). sprintf ($after, $position); // - здесь учитывается «position»
    } elseif ($show_last_sep) echo $sep;

    кусок кода взят для примера, такая проблема присутствует в большинстве условий if ($show_current){ … } (возможно во всех, но не утверждаю, так как все не проверял)

  4. Привет! Классная работа проделана! У меня вопрос:
    дело в том, что у меня нет категорий, идет сразу url/news_post/text_of_post
    Где news_post это рубрика.
    Отображение на сайте верное, только ссылка на новости имеет такой вид url/category/news_post/ и получается ведет на несуществующую страницу

    Вот скрин на всякий случай, чтобы более наглядно i.imgur.com/tLiNFbo.png

    PS Сайт сейчас на локальном компе

  5. Код использую уже много лет, но вот заметил ошибку.
    Есть две испытуемые статьи. Всё обычно: одна главная (корневая) рубрика и одна подрубрика.
    Так вот прикол в том, что из одной определённой главной рубрики код ваших крошек от 2017 года не выводит ни одну подрубрику! С другими категориями работает нормально.

    Поэтому забрёл сюда в поисках новой версии и установил её. Подрубрики стали выводиться!
    Но радость длилась не долго, т.к. я обнаружил статью из другой главной рубрики, в которой не отображаются подрубрики кодом 2019 года, но отображаются старым кодом 2017!!!
    Плагин Breadcrumb NAVXT правильно отображает обе статьи. Не хочется плагин ставить, может подскажете куда хоть копать?

  6. Пока что решил вопрос так: проверяются все подкатегории поста на длину цепочки и выбирается первая самая длинная цепочка. Таким образом получилось ещё выводить больше подкатегорий для постов, которые состояли во многих категориях, включая только корневую.
    $max=-1;
    foreach ($cat as $category){
    $parents1 = get_ancestors ($category→cat_ID, 'category');
    if (count ($parents1)>$max) {
    $max=count ($parents1);
    $catID = $category→cat_ID;
    $parents = $parents1;
    }
    }

  7. Здравствуйте. Могли бы подсказать как сделать? Вот у меня услуги выводятся страницами. Я добавил их в родительские категории, и сейчас выводится — Главная — Род. категория — Страница
    можно сделать — Главная — Страница?

  8. Не выводятся хлебные крошки на странице вывода записей (то есть в файле index. php).
    В чем может быть дело и как это исправить?

  9. Здравствуйте !

    В статье вы пишите:
    «Функцию необходимо поместить в файл functions. php вашей WordPress-темы. После этого в то место шаблона, где хотите выводить „хлебные крошки“, добавьте следующий код:»

    Я начинающий и не совсем понял куда именно в какой файл нужно будет добавить этот код?
    И ещё вопрос: если я захочу вывести хлебные крошки внизу подвала, то куда мне нужно добавить этот код?

  10. Подскажите пожалуйста, это у меня ошибка или этот код не работает на index. php странице? Суть проблемы: есть страница блога она формируется у меня с помощью index.php. При размещении

    <?php echo dimox_breadcrumbs(); ?>

    ничего выводится, хотя на внутренних страницах код работает. Может стоит для блога отдельную tpl-blog.php сделать?

  11. СПАСИБО!

  12. Здравствуйте !
    Подскажите пожалуйста как с помощью вашего кода можно добавить мультиязычность ссылке на главную?
    $text['home'] = pll_e ('fl-breadcrumbshome'); // текст ссылки «Главная»

    С плагином полиланг, выше показал как не работает, подскажите пожалйста что не так ?(

  13. Здравствуйте! Гугл ругается на отсутствие значения в поле position

    Хотя фактически оно там есть. Как это можно исправить?

  14. Здравствуйте. Все круто, всегда пользовался. Но щас на вукомерсе поставил, тема нулевая (своя) ничего особого.
    remove_action ('woocommerce_before_main_content','woocommerce_breadcrumb', 20);
    add_action ('woocommerce_before_main_content','dimox_breadcrumbs', 20);
    так вывожу, в категориях товаров, не выводится, только ссылка на главную, на странице магазина выводится. Подскажите, что можно сделать?

    примерный линк
    site.ru/product-category/sortovoj-prokat/

  15. Добрый день! Очень благодарен за эту крутую разметку. Скажите, а можно ли заставить работать крошки через какой-то шорткод […]?

  16. Здравствуйте!

    Использовал функцию, всё отображается, путь полный, но есть проблема — не работает переход в родительский раздел:

    Находясь по адресу «Главная>Услуги>Услуга 1» не могу перейти на страницу «Услуги» — при клике просто обновляется текущая страница.

    В чем может быть проблема?

    P.S. Нашел в интернете версию функции от 2017 г. — всё работает, но там гугл ругается на position.

  17. А можно ли перевести значение %2 $s если я хочу сделать перевод хлебных крошек. Суть такова, что иерархия получается такая (Главная — Блог — Название статьи), вот соответственно Блог хочу перевести на английский. Может какой-то инной есть способ вывести название ссылки?

  18. Спасибо за код)) очень пригодился))

  19. Спасибо за работу!

    Перечитал все комменты. Близкий ответ к своей проблемме нашел на 13 странице комментариев про произвольное поле с title. Сделал все как вы советовали. Изменил ваш код, создал произвольное поле с title, но эффекта не увидел.

    А можно просто вызывать в хлебных крошках не тайтлы страницы, а заголовки h1?

    • Произвольные поля у меня прописанны в functions. php следующим образом:

      function get_any_fild($field, $pid = NULL) {
      	if (is_category()) {
      		$term_description = $pid->queried_object->description;
      	    if ($term_description) {
      	        preg_match ('![' . $field . '="(.*)"]!iU', $term_description, $match);
      	    }
      		$meta = ($match[1] ? $match[1] : ($field != 'term_description' ? single_cat_title('', false) : NULL));
      		/*if ($paged = get_query_var('paged')) {
      			$meta = $meta . ' стр. ' . $paged;
      		}*/
      	} else {
      		$meta = get_post_meta($pid, $field, true);
      		if (empty($meta)) {
      			$meta = the_title();
      		}
      	}
      	return $meta;
      } 
      
      • Есть еще вариант просто дать ограничение на количество слов в названии каждой хлебной крошки:

        trim_title_words(5, '...')

        C помощью функции:

        function trim_title_words($count, $after) {
         $title = get_the_title();
         $words = split(' ', $title);
         if (count($words) > $count) {
         array_splice($words, $count);
         $title = implode(' ', $words);
         }
         else $after = '';
         echo $title . $after;
        }

        Но пока не понял как это прикрутить к вашему коду.

Ваш комментарий