Подсветка класса предка wp_nav_menu() без дочерних элементов в структуре навигации
(Примечание модератора: Изначально называлось "Класс предка wp_nav_menu без дочерних элементов в структуре навигации")
У меня есть wp_nav_menu
в шапке с тремя страницами. Когда я нахожусь на одной из этих страниц, элемент li
, содержащий эту страницу в меню, получает класс .current_page_item
. Эти три страницы имеют шаблоны, и эти шаблоны содержат пользовательские запросы для получения всех записей определенного типа контента. По сути, воспринимаемые "дочерние элементы" этой страницы верхнего уровня на самом деле не являются дочерними, они просто относятся к типу контента, который я связал с этой страницей верхнего уровня, используя шаблон.
Я хотел бы, чтобы пункты меню верхнего уровня получали класс 'current-ancestor'
, когда пользователь просматривает отдельную страницу определенного типа записи, опять же, связанную с этой страницей только через пользовательский запрос в файле шаблона.
Надеюсь, это имеет смысл - если нет, дайте мне знать, где я вас потерял! Очень признателен за любую помощь.
--Отредактировано для уточнения: Например, у меня есть статическая страница под названием Workshops (Мастерские), использующая шаблон. Её ЧПУ - workshops. Шаблон содержит пользовательскую функцию get_posts и цикл, который извлекает и отображает все записи пользовательского типа контента workshops. Если я нажму на заголовок одной из этих мастерских, я перейду к полному содержанию этой записи. Структура постоянных ссылок для пользовательского типа записей настроена как workshops/postname, поэтому с точки зрения пользователя эти записи являются дочерними элементами страницы Workshops, хотя на самом деле они все относятся к одному типу контента, но не связаны со страницей. Именно этот разрыв мне нужно эффективно закрыть в меню, подсвечивая пункт меню 'Workshops' при просмотре контента типа 'workshop'.
Снова надеюсь, что это имеет смысл. Думаю, я сказал 'workshop' более 20 раз в одном абзаце!

Есть более простое решение. Забудьте о создании отдельных страниц для каждого типа записей только для того, чтобы добавить пункты меню, потому что, как вы уже поняли, WordPress не умеет распознавать, что просматриваемые вами пользовательские типы связаны с этой страницей.
Вместо этого создайте пользовательскую ссылку в разделе Внешний вид → Меню. Просто укажите URL, который будет возвращать ваш пользовательский тип записей, задайте метку и нажмите "Добавить в меню".
http://example.com/workshops/
Или для некрасивых постоянных ссылок:
http://example.com/?post_type=workshops
Это создаст пункт меню, который отображает все записи данного типа, а также добавит класс current-menu-item при клике на этот пункт — но пока что класс не будет добавляться на других URL, кроме этого
После создания пункта зайдите в его настройки и введите слаг типа записи в поле "Атрибут title" (можно также использовать поле описания, но оно по умолчанию скрыто в настройках экрана админки).
Теперь нужно подключить фильтр nav_menu_css_class
(который срабатывает для каждого пункта меню) и проверить, соответствует ли просматриваемый контент типу записи, указанному в вашем пользовательском пункте меню:
add_filter('nav_menu_css_class', 'current_type_nav_class', 10, 2 );
function current_type_nav_class($classes, $item) {
$post_type = get_query_var('post_type');
if ($item->attr_title != '' && $item->attr_title == $post_type) {
array_push($classes, 'current-menu-item');
};
return $classes;
}
В этом случае мы проверяем, что поле "Атрибут title" не пустое и соответствует текущему типу записей в запросе. Если условие выполняется, мы добавляем класс current-menu-item в массив классов и возвращаем изменённый массив.
Вы можете модифицировать код для сравнения с заголовком пункта меню, но если вам нужно назвать пункт меню иначе, чем слаг типа записи, использование полей "Атрибут title" или описания даёт такую гибкость.
Теперь при просмотре одиночной записи (или, вероятно, даже архивных списков) типа, соответствующего пункту меню, этому пункту будет присвоен CSS-класс current-menu-item, и ваше выделение будет работать.
Никаких страниц или шаблонов страниц не требуется ;-) URL-запрос отвечает за выборку нужных записей. Ваш шаблон цикла отвечает за отображение вывода запроса. Эта функция отвечает за распознавание отображаемого контента и добавление CSS-класса.
БОНУС
Вы можете даже автоматизировать процесс с помощью wp_update_nav_menu_item
, автоматически создавая пункты меню для всех ваших типов записей. Для этого примера сначала нужно получить $menu_id
меню, в которое вы хотите добавить эти пункты.
$types = get_post_types( array( 'exclude_from_search' => false, '_builtin' => false ), 'objects' );
foreach ($types as $type) {
wp_update_nav_menu_item( $menu_id, 0, array(
'menu-item-type' => 'custom',
'menu-item-title' => $type->labels->name,
'menu-item-url' => get_bloginfo('url') . '/?post_type=' . $type->rewrite['slug'],
'menu-item-attr-title' => $type->rewrite['slug'],
'menu-item-status' => 'publish'
)
);
}

Вот это да! Я использую шаблоны страниц только потому, что макеты для этих страниц довольно сложные и не просто выводят список страниц, но я все равно могу использовать тот фильтр, который вы предоставили, чтобы проверять ID страницы. Особенность этой темы в том, что настройки темы позволяют сопоставлять страницы ('главная' - это эта страница, 'о нас' - эта страница и т.д.), так что это должно отлично сработать. Спасибо за (невероятно подробную) помощь!

мне пришлось убрать current_page_parent
из элемента навигации, который был моим блогом - но в остальном все сработало. спасибо

у меня это не работало, так как $item->attr_title
вытаскивал ЗАГОЛОВОК, а я писал заголовок заглавными буквами. поэтому я изменил атрибут на $item->post_name
, и теперь все работает как надо.

вместо использования
$post_type = get_query_var('post_type');
Вы можете попробовать:
$post_type = get_post_type();
Поскольку иногда тип записи не устанавливается в переменной запроса. Это относится к стандартному типу записи "post", поэтому если вы хотите выделить запись, которая была выведена на странице списка, вам нужно использовать эту функцию. get_query_var() просто возвращает пустую строку для типов записей, которые не являются пользовательскими.
add_filter('nav_menu_css_class', 'current_type_nav_class', 10, 2 );
function current_type_nav_class($classes, $item) {
$post_type = get_post_type();
if ($item->attr_title != '' && $item->attr_title == $post_type) {
array_push($classes, 'current-menu-item');
};
return $classes;
}

@Somatic - это фантастика! Я немного изменил ваш код, чтобы он также работал с конкретной таксономией (которую я использую только для связанного post_type). Идея в том, чтобы использовать атрибут Title пункта меню для хранения как имени post_type, так и имени таксономии, разделённых точкой с запятой, а затем разделять их с помощью функции.
add_filter('nav_menu_css_class', 'current_type_nav_class', 10, 2 );
function current_type_nav_class($classes, $item) {
# Получаем Query Vars
$post_type = get_query_var('post_type');
$taxonomy = get_query_var('taxonomy');
# Получаем и разбираем атрибут Title пункта меню
$title = $item->attr_title; // атрибут Title пункта меню в формате post_type;taxonomy
$title_array = explode(";", $title);
$title_posttype = $title_array[0];
$title_taxonomy = $title_array[1];
# Добавляем класс, если нужно
if ($title != '' && ($title_posttype == $post_type || $title_taxonomy == $taxonomy)) {
array_push($classes, 'current-menu-item');
};
return $classes;
}

Вот мое решение, если вы хотите работать с wp_list_pages.
Добавьте это в functions.php
add_filter('page_css_class', 'my_page_css_class', 10, 2);
function my_page_css_class($css_class, $page){
$post_type = get_post_type();
if($post_type != "page"){
$parent_page = get_option('page_for_custom_post_type-'.$post_type);
if($page->ID == $parent_page)
$css_class[] = 'current_page_parent';
}
return $css_class;
}
Теперь просто добавьте в таблицу wp_options новую строку с option_name равным page_for_custom_post_type-xxxx и option_value с ID страницы, которую вы хотите связать.
Возможно, вы заметили, что уже существует опция под названием page_for_posts. Если у вас только один пользовательский тип записи, вы можете установить свою страницу в выпадающем списке на странице /wp-admin/options-reading.php, и навигация будет правильно устанавливать current_page.
Я думаю, что ядро WordPress должно расширить этот раздел выпадающим списком для каждого зарегистрированного типа записи.

Я решил использовать страницы и применять имя шаблона страницы в качестве класса для пункта меню. Это позволяет избежать загромождения атрибута title, что мне не нравилось в других решениях.
add_filter('nav_menu_css_class', 'mbudm_add_page_type_to_menu', 10, 2 );
// Если пункт меню является страницей, добавляем имя шаблона как CSS-класс
function mbudm_add_page_type_to_menu($classes, $item) {
if($item->object == 'page'){
$template_name = get_post_meta( $item->object_id, '_wp_page_template', true );
$new_class =str_replace(".php","",$template_name);
array_push($classes, $new_class);
return $classes;
}
}
Также я добавил классы body в header.php
<body <?php body_class(); ?>>
Для завершения решения требуется дополнительный CSS для применения состояния selected/active к пунктам меню. Я использую это для отображения архивов таксономий и пользовательских типов записей, связанных со страницей, как дочерних элементов:
/* состояния выбора - включаем подстраницы для всего, связанного с продуктами */
#nav-main li.current-menu-item a,
body.single-mbudm_product #nav-main li.lp_products a,
body.tax-mbudm_product_category #nav-main li.lp_products a,
#nav-main li.current_page_parent a{color:#c00;}

Это вызвало у меня следующую ошибку:
Warning: join() [function.join]: Invalid arguments passed in /home/path/to/wp-includes/nav-menu-template.php on line 76
Есть идеи, что здесь произошло?

@Somatic - Отличный код! Я внес одно изменение самостоятельно. Я хотел сохранить атрибут Title для его основного назначения, поэтому вместо этого разместил слаг типа записи (Custom Post Type) в поле Link Relationship (XFN) расширенных свойств меню, которые можно включить в настройках экрана. Я изменил
if ($item->attr_title != '' && $item->attr_title == $post_type) {
и заменил на
if ($item->xfn != '' && $item->xfn == $post_type) {

Отличная работа, Somatic.
К сожалению, я не понимаю, как можно вывести произвольные типы записей на странице так, как вы объясняете. Если я не использую page-portfolio.php и добавляю это на страницу, я получаю только ошибку 404.
Если делать, как предлагает Gavin, я немного изменил вашу функцию, чтобы также убрать класс "current_page_parent" со страницы блога, вот так:
add_filter('nav_menu_css_class', 'current_type_nav_class', 10, 2);
function current_type_nav_class($css_class, $item) {
$post_type = get_query_var('post_type');
if (get_post_type()=='portfolio') {
$current_value = "current_page_parent";
$css_class = array_filter($css_class, function ($element) use ($current_value) { return ($element != $current_value); } );
}
if ($item->attr_title != '' && $item->attr_title == $post_type) {
array_push($css_class, 'current_page_parent');
};
return $css_class;
}
