Отображение части/ветви дерева меню с помощью wp_nav_menu()
У меня есть меню, определенное в админке WordPress, которое выглядит так:
Я хочу иметь возможность отображать все дочерние ссылки в сайдбаре, когда пользователь находится на родительской странице. Например, если пользователь находится на странице "О нас", я хочу, чтобы в сайдбаре появился список из 4 ссылок, выделенных зеленым цветом.
Я просмотрел документацию wp_nav_menu(), и похоже, что в ней нет встроенного способа указать конкретный узел данного меню в качестве начальной точки при генерации ссылок.
Я создал решение для похожей ситуации, которое опиралось на отношения, создаваемые родительской страницей, но я ищу решение, которое использует именно систему меню. Буду благодарен за любую помощь.
Это все еще не давало мне покоя, поэтому я вернулся к проблеме и собрал это решение, которое не так сильно зависит от контекста:
add_filter( 'wp_nav_menu_objects', 'submenu_limit', 10, 2 );
function submenu_limit( $items, $args ) {
if ( empty( $args->submenu ) ) {
return $items;
}
$ids = wp_filter_object_list( $items, array( 'title' => $args->submenu ), 'and', 'ID' );
$parent_id = array_pop( $ids );
$children = submenu_get_children_ids( $parent_id, $items );
foreach ( $items as $key => $item ) {
if ( ! in_array( $item->ID, $children ) ) {
unset( $items[$key] );
}
}
return $items;
}
function submenu_get_children_ids( $id, $items ) {
$ids = wp_filter_object_list( $items, array( 'menu_item_parent' => $id ), 'and', 'ID' );
foreach ( $ids as $id ) {
$ids = array_merge( $ids, submenu_get_children_ids( $id, $items ) );
}
return $ids;
}
Использование
$args = array(
'theme_location' => 'slug-of-the-menu', // тот, что используется в register_nav_menus
'submenu' => 'О нас', // можно использовать __() для перевода
);
wp_nav_menu( $args );

Отличный прием! Можно задать вопрос, возможно, связанный с этим: Как отобразить содержимое страниц подменю в шаблоне?

@daniel.tosaba вам потребуется создать подкласс или использовать фильтры в классе Walker_Nav_Menu
. Как и все, что связано с меню, это слишком объемно для комментария - задайте новый вопрос по этой теме?

http://wordpress.stackexchange.com/questions/62758/display-content-of-child-links

Такой замечательный ответ. Большое спасибо. Это действительно должно быть стандартной опцией в WordPress.

Хм, у меня странная проблема. У меня есть страница под названием "Children's". И кажется, что апостроф в слове мешает найти страницу. Есть идеи?

@dotty Я не помню точно, что хранится в поле, по которому фильтрует код. Возможно, это санитизированная или экранированная слэшами версия заголовка.

Извините, я не совсем понимаю, что вы имеете в виду. Не могли бы вы уточнить?

@dotty возможно, это хранится как Children\'s
или что-то подобное, у меня сейчас нет этого кода под рукой для тестирования...

Похоже, это больше не работает в WP 4. Я получаю такую ошибку: Strict Standards: Only variables should be passed by reference in -> on line -> $parent_id = array_pop( wp_filter_object_list( $items, array( 'title' => $args->submenu ), 'and', 'ID' ) );

@gdaniel проверил - всё работает и не связано с обновлением WP, просто понадобилась небольшая правка для более корректного PHP-кода :)

Странно. Все мои подменю, использующие эту функцию, пропали сразу после обновления до WP 4. Но спасибо, что посмотрели. Я проверю, что ещё могло случиться.

Я разобрался с проблемой. Я использовал аргумент menu => "menu-name" в wp_nav_menu... Убрал 'menu' и добавил theme_location, после чего всё вернулось в норму.

Привет @Rarst, твой ответ по-прежнему всплывает во многих местах. Принятую версию стоит переписать с учетом кастомных навигационных Walker'ов, так как можно просто фильтровать по "current-menu-item", "current-menu-parent" и "current-menu-ancestor", а затем выводить подменю. Без фильтров, без хаков.

@ImperativeIdeas Мой ответ показывает любую ветку по имени, а не только текущую.

@goldenapples: Ваш класс Walker не работает. Но идея действительно хорошая. Я создал walker на основе вашей идеи:
class Selective_Walker extends Walker_Nav_Menu
{
function walk( $elements, $max_depth) {
$args = array_slice(func_get_args(), 2);
$output = '';
if ($max_depth < -1) //неверный параметр
return $output;
if (empty($elements)) //нечего обрабатывать
return $output;
$id_field = $this->db_fields['id'];
$parent_field = $this->db_fields['parent'];
// плоский вывод
if ( -1 == $max_depth ) {
$empty_array = array();
foreach ( $elements as $e )
$this->display_element( $e, $empty_array, 1, 0, $args, $output );
return $output;
}
/*
* требуется вывести в иерархическом порядке
* разделяем элементы на два бакета: элементы верхнего уровня и дочерние элементы
* children_elements - двумерный массив, например
* children_elements[10][] содержит все дочерние элементы, чей родитель имеет ID 10.
*/
$top_level_elements = array();
$children_elements = array();
foreach ( $elements as $e) {
if ( 0 == $e->$parent_field )
$top_level_elements[] = $e;
else
$children_elements[ $e->$parent_field ][] = $e;
}
/*
* если ни один из элементов не является верхнеуровневым
* предполагаем, что первый должен быть корнем подэлементов
*/
if ( empty($top_level_elements) ) {
$first = array_slice( $elements, 0, 1 );
$root = $first[0];
$top_level_elements = array();
$children_elements = array();
foreach ( $elements as $e) {
if ( $root->$parent_field == $e->$parent_field )
$top_level_elements[] = $e;
else
$children_elements[ $e->$parent_field ][] = $e;
}
}
$current_element_markers = array( 'current-menu-item', 'current-menu-parent', 'current-menu-ancestor' ); //добавлено continent7
foreach ( $top_level_elements as $e ){ //изменено continent7
// спускаемся только по текущему дереву
$descend_test = array_intersect( $current_element_markers, $e->classes );
if ( !empty( $descend_test ) )
$this->display_element( $e, $children_elements, 2, 0, $args, $output );
}
/*
* если мы отображаем все уровни и остались непустые children_elements,
* значит у нас есть "сироты", которые должны быть отображены в любом случае
*/
/* удалено continent7
if ( ( $max_depth == 0 ) && count( $children_elements ) > 0 ) {
$empty_array = array();
foreach ( $children_elements as $orphans )
foreach( $orphans as $op )
$this->display_element( $op, $empty_array, 1, 0, $args, $output );
}
*/
return $output;
}
}
Теперь вы можете использовать:
<?php wp_nav_menu(
array(
'theme_location'=>'test',
'walker'=>new Selective_Walker() )
); ?>
Результатом будет список, содержащий текущий корневой элемент и его дочерние элементы (но не дочерние элементы дочерних элементов). Определение: Корневой элемент := Элемент меню верхнего уровня, который соответствует текущей странице или является родителем текущей страницы, или родителем родителя и т.д.
Это не совсем отвечает на исходный вопрос, но близко к тому, поскольку остаётся элемент верхнего уровня. Для меня это нормально, потому что я хочу использовать элемент верхнего уровня как заголовок сайдбара. Если вы хотите избавиться от него, вам, возможно, придётся переопределить display_element или использовать HTML-парсер.

Привет, @jessegavin:
Навигационные меню хранятся в комбинации пользовательских типов записей и таксономий. Каждое меню сохраняется как термин (например, "Меню О нас", находится в wp_terms
) пользовательской таксономии (например, nav_menu
, находится в wp_term_taxonomy
).
Каждый элемент навигационного меню сохраняется как запись с post_type=='nav_menu_item'
(например, "О компании", находится в wp_posts
), а его атрибуты хранятся как метаданные записи (в wp_postmeta
) с префиксом meta_key
вида _menu_item_*
, где _menu_item_menu_item_parent
— это ID родительского элемента меню.
Связь между меню и элементами меню хранится в wp_term_relationships
, где object_id
относится к $post->ID
элемента навигационного меню, а $term_relationships->term_taxonomy_id
относится к меню, определённому совместно в wp_term_taxonomy
и wp_terms
.
Я почти уверен, что можно использовать хуки 'wp_update_nav_menu'
и 'wp_update_nav_menu_item'
, чтобы создавать реальные меню в wp_terms
и параллельный набор связей в wp_term_taxonomy
и wp_term_relationships
, где каждый элемент навигационного меню, имеющий подменю, также становится отдельным меню.
Также понадобится хук 'wp_get_nav_menus'
(который я предложил добавить в WP 3.0 на основе похожей работы, которую делал несколько месяцев назад), чтобы гарантировать, что сгенерированные меню не отображаются для редактирования в админке, иначе они быстро рассинхронизируются, и тогда возникнет настоящий хаос с данными.
Звучит как интересный и полезный проект, но требует больше кода и тестирования, чем я могу позволить себе сейчас, отчасти потому, что любая синхронизация данных обычно доставляет немало проблем при отладке (и потому что платёжеспособные клиенты настаивают на завершении других задач. :) Но с предоставленной выше информацией любой мотивированный разработчик плагинов для WordPress мог бы реализовать эту идею.
Конечно, ты понимаешь, что если ты всё-таки реализуешь это, то обязан выложить код здесь, чтобы все могли воспользоваться твоим великодушием! :-)

Я не совсем уверен, что правильно понимаю, о чем вы говорите. Я ищу решение только для чтения, чтобы отображать "подменю", связанные с текущей страницей, на которой находится пользователь. Мы говорим об одном и том же? - Хотя я очень ценю ваше подробное объяснение о схеме базы данных.

@jessegavin - Да, если вы хотите использовать wp_nav_menu()
, то вам придется клонировать меню, потому что wp_nav_menu()
жестко привязан к структуре меню. Другой вариант — скопировать код wp_nav_menu()
и внести необходимые изменения для отображения в качестве подменю.

Это расширение Walker, которое должно делать то, что вам нужно:
class Selective_Walker extends Walker_Nav_Menu
{
function walk( $elements, $max_depth) {
$args = array_slice(func_get_args(), 2);
$output = '';
if ($max_depth < -1) //неверный параметр
return $output;
if (empty($elements)) //нечего обходить
return $output;
$id_field = $this->db_fields['id'];
$parent_field = $this->db_fields['parent'];
// плоский вывод
if ( -1 == $max_depth ) {
$empty_array = array();
foreach ( $elements as $e )
$this->display_element( $e, $empty_array, 1, 0, $args, $output );
return $output;
}
/*
* необходимо вывести в иерархическом порядке
* разделить элементы на два уровня: верхний и дочерние элементы
* children_elements - двумерный массив, например,
* children_elements[10][] содержит все подэлементы, чей родитель 10.
*/
$top_level_elements = array();
$children_elements = array();
foreach ( $elements as $e) {
if ( 0 == $e->$parent_field )
$top_level_elements[] = $e;
else
$children_elements[ $e->$parent_field ][] = $e;
}
/*
* когда ни один из элементов не является верхним уровнем
* предполагаем, что первый должен быть корнем подэлементов
*/
if ( empty($top_level_elements) ) {
$first = array_slice( $elements, 0, 1 );
$root = $first[0];
$top_level_elements = array();
$children_elements = array();
foreach ( $elements as $e) {
if ( $root->$parent_field == $e->$parent_field )
$top_level_elements[] = $e;
else
$children_elements[ $e->$parent_field ][] = $e;
}
}
$current_element_markers = array( 'current-menu-item', 'current-menu-parent', 'current-menu-ancestor' );
foreach ( $top_level_elements as $e ) {
// спускаемся только по текущему дереву
$descend_test = array_intersect( $current_element_markers, $e->classes );
if ( empty( $descend_test ) ) unset ( $children_elements );
$this->display_element( $e, $children_elements, $max_depth, 0, $args, $output );
}
/*
* если мы выводим все уровни, а оставшиеся children_elements не пусты,
* то у нас есть "сироты", которые должны быть выведены в любом случае
*/
if ( ( $max_depth == 0 ) && count( $children_elements ) > 0 ) {
$empty_array = array();
foreach ( $children_elements as $orphans )
foreach( $orphans as $op )
$this->display_element( $op, $empty_array, 1, 0, $args, $output );
}
return $output;
}
}
Основано на коде mfields, который я упоминал ранее в комментарии. Всё, что он делает - проверяет при обходе меню, является ли текущий элемент (1) текущим пунктом меню или (2) предком текущего пункта меню, и раскрывает поддерево под ним только если выполняется одно из этих условий. Надеюсь, это сработает для вас.
Чтобы использовать его, просто добавьте аргумент "walker" при вызове меню, например:
<?php wp_nav_menu(
array(
'theme_location'=>'test',
'walker'=>new Selective_Walker() )
); ?>

О... Я перечитал ваш вопрос и понял, что изначально неправильно его понял. Этот walker будет показывать все другие элементы меню верхнего уровня, но не раскрывать их. Это не совсем то, что вы хотели сделать. Тем не менее, этот код можно модифицировать как угодно. Просто посмотрите на цикл через $top_level_elements
и добавьте свой собственный тест перед вызовом $this->display_element
.

Возможно ли сделать так, чтобы этот класс показывал глубину текущей подстраницы? То есть... Если у меня есть глубина три или более уровней, чтобы третий и последующие уровни отображались для текущей (под)страницы? В данный момент он показывает только A > B, но не > C (где C — третий уровень)

@Zolomon - Не уверен, что правильно понял ваш вопрос. Этот код должен раскрывать всё дерево под любым элементом меню с классами 'current-menu-item', 'current-menu-parent' или 'current-menu-ancestor'. Когда я тестирую, он отображает все уровни подстраниц в меню. Что именно вы хотите сделать?

Обновление: Я превратил это в плагин. Скачать здесь.
Мне нужно было решить эту задачу самостоятельно, и в итоге я написал фильтр для результатов поиска меню. Это позволяет использовать wp_nav_menu
как обычно, но выбирать подраздел меню на основе заголовка родительского элемента. Добавьте параметр submenu
в меню следующим образом:
wp_nav_menu(array(
'menu' => 'header',
'submenu' => 'О нас',
));
Вы даже можете углубиться на несколько уровней, используя слеши:
wp_nav_menu(array(
'menu' => 'header',
'submenu' => 'О нас/Совет директоров'
));
Или, если предпочитаете, через массив:
wp_nav_menu(array(
'menu' => 'header',
'submenu' => array('О нас', 'Совет директоров')
));
Он использует версию заголовка в виде слага, что делает его устойчивым к вещам вроде заглавных букв и пунктуации.

Можно ли получить доступ к подменю по id? Я имею в виду id страницы или записи.

split() устарел, замените $loc = split( "/", $loc );
в плагине на $loc = preg_split( "~/~", $loc );

Я собрал следующий класс для себя. Он найдет родительский элемент верхнего меню для текущей страницы, или вы можете передать ему целевой ID верхнего меню в конструкторе Walker.
class Walker_SubNav_Menu extends Walker_Nav_Menu {
var $target_id = false;
function __construct($target_id = false) {
$this->target_id = $target_id;
}
function walk($items, $depth) {
$args = array_slice(func_get_args(), 2);
$args = $args[0];
$parent_field = $this->db_fields['parent'];
$target_id = $this->target_id;
$filtered_items = array();
// если родитель не установлен, определяем его на основе поста
if (!$target_id) {
global $post;
foreach ($items as $item) {
if ($item->object_id == $post->ID) {
$target_id = $item->ID;
}
}
}
// если родитель не найден, выводим обычное меню
if (!$target_id) return parent::walk($items, $depth, $args);
// получаем ID верхнего уровня меню
$target_id = $this->top_level_id($items, $target_id);
// включаем только элементы под родительским пунктом
foreach ($items as $item) {
if (!$item->$parent_field) continue;
$item_id = $this->top_level_id($items, $item->ID);
if ($item_id == $target_id) {
$filtered_items[] = $item;
}
}
return parent::walk($filtered_items, $depth, $args);
}
// получает ID верхнего уровня для указанного ID элемента
function top_level_id($items, $item_id) {
$parent_field = $this->db_fields['parent'];
$parents = array();
foreach ($items as $item) {
if ($item->$parent_field) {
$parents[$item->ID] = $item->$parent_field;
}
}
// находим элемент верхнего уровня
while (array_key_exists($item_id, $parents)) {
$item_id = $parents[$item_id];
}
return $item_id;
}
}
Вызов меню:
wp_nav_menu(array(
'theme_location' => 'main_menu',
'walker' => new Walker_SubNav_Menu(22), // с указанием ID
));

@davidn @hakre Привет, у меня есть некрасивое решение без HTML-парсера или переопределения display_element.
class Selective_Walker extends Walker_Nav_Menu
{
function walk( $elements, $max_depth) {
$args = array_slice(func_get_args(), 2);
$output = '';
if ($max_depth < -1) //неверный параметр
return $output;
if (empty($elements)) //нечего обходить
return $output;
$id_field = $this->db_fields['id'];
$parent_field = $this->db_fields['parent'];
// плоский вывод
if ( -1 == $max_depth ) {
$empty_array = array();
foreach ( $elements as $e )
$this->display_element( $e, $empty_array, 1, 0, $args, $output );
return $output;
}
/*
* требуется вывод в иерархическом порядке
* разделяем элементы на два блока: элементы верхнего уровня и дочерние элементы
* children_elements - двумерный массив, например:
* children_elements[10][] содержит все подэлементы, родитель которых 10.
*/
$top_level_elements = array();
$children_elements = array();
foreach ( $elements as $e) {
if ( 0 == $e->$parent_field )
$top_level_elements[] = $e;
else
$children_elements[ $e->$parent_field ][] = $e;
}
/*
* если ни один из элементов не является элементом верхнего уровня
* предполагаем, что первый элемент должен быть корнем подэлементов
*/
if ( empty($top_level_elements) ) {
$first = array_slice( $elements, 0, 1 );
$root = $first[0];
$top_level_elements = array();
$children_elements = array();
foreach ( $elements as $e) {
if ( $root->$parent_field == $e->$parent_field )
$top_level_elements[] = $e;
else
$children_elements[ $e->$parent_field ][] = $e;
}
}
$current_element_markers = array( 'current-menu-item', 'current-menu-parent', 'current-menu-ancestor' ); //добавлено continent7
foreach ( $top_level_elements as $e ){ //изменено continent7
// спускаемся только по текущему дереву
$descend_test = array_intersect( $current_element_markers, $e->classes );
if ( !empty( $descend_test ) )
$this->display_element( $e, $children_elements, 2, 0, $args, $output );
}
/*
* если мы выводим все уровни и оставшиеся children_elements не пусты,
* значит у нас есть "сироты", которые должны быть выведены в любом случае
*/
/* удалено continent7
if ( ( $max_depth == 0 ) && count( $children_elements ) > 0 ) {
$empty_array = array();
foreach ( $children_elements as $orphans )
foreach( $orphans as $op )
$this->display_element( $op, $empty_array, 1, 0, $args, $output );
}
*/
/*добавлено alpguneysel */
$pos = strpos($output, '<a');
$pos2 = strpos($output, 'a>');
$topper= substr($output, 0, $pos).substr($output, $pos2+2);
$pos3 = strpos($topper, '>');
$lasst=substr($topper, $pos3+1);
$submenu= substr($lasst, 0, -6);
return $submenu;
}
}

Перепробовав все варианты, решение Alp оказалось единственным, которое сработало у меня. Однако есть одна проблема. Оно показывает только дочерние элементы первого уровня, но не отображает элементы третьего и четвертого уровня. Я уже несколько дней пытаюсь заставить это работать. Кто-нибудь знает, как модифицировать его решение для этого? P.S. Я не могу оставлять комментарии, поэтому приходится писать это как ответ.

Вывод навигационного меню включает множество классов для текущего элемента, родителя текущего элемента и т.д. В некоторых случаях я добивался желаемого результата, позволяя выводить всё дерево навигации, а затем с помощью CSS оставлял только дочерние элементы текущей страницы и т.п.

Я создал модифицированный Walker, который должен помочь! Не идеально - он оставляет несколько пустых элементов, но свою работу выполняет. Модификация в основном касается битов $current_branch. Надеюсь, кому-то пригодится!
class Kanec_Walker_Nav_Menu extends Walker {
/**
* @see Walker::$tree_type
* @since 3.0.0
* @var string
*/
var $tree_type = array( 'post_type', 'taxonomy', 'custom' );
/**
* @see Walker::$db_fields
* @since 3.0.0
* @todo Decouple this.
* @var array
*/
var $db_fields = array( 'parent' => 'menu_item_parent', 'id' => 'db_id' );
/**
* @see Walker::start_lvl()
* @since 3.0.0
*
* @param string $output Передается по ссылке. Используется для добавления дополнительного контента.
* @param int $depth Глубина страницы. Используется для отступов.
*/
function start_lvl(&$output, $depth) {
$indent = str_repeat("\t", $depth);
$output .= "\n$indent<ul class=\"sub-menu\">\n";
}
/**
* @see Walker::end_lvl()
* @since 3.0.0
*
* @param string $output Передается по ссылке. Используется для добавления дополнительного контента.
* @param int $depth Глубина страницы. Используется для отступов.
*/
function end_lvl(&$output, $depth) {
global $current_branch;
if ($depth == 0) $current_branch = false;
$indent = str_repeat("\t", $depth);
$output .= "$indent</ul>\n";
}
/**
* @see Walker::start_el()
* @since 3.0.0
*
* @param string $output Передается по ссылке. Используется для добавления дополнительного контента.
* @param object $item Объект данных пункта меню.
* @param int $depth Глубина пункта меню. Используется для отступов.
* @param int $current_page ID пункта меню.
* @param object $args
*/
function start_el(&$output, $item, $depth, $args) {
global $wp_query;
global $current_branch;
// Находится ли этот пункт меню в текущей ветви?
if(in_array('current-menu-ancestor',$item->classes) ||
in_array('current-menu-parent',$item->classes) ||
in_array('current-menu-item',$item->classes)) {
$current_branch = true;
}
if($current_branch && $depth > 0) {
$indent = ( $depth ) ? str_repeat( "\t", $depth ) : '';
$class_names = $value = '';
$classes = empty( $item->classes ) ? array() : (array) $item->classes;
$classes[] = 'menu-item-' . $item->ID;
$class_names = join( ' ', apply_filters( 'nav_menu_css_class', array_filter( $classes ), $item ) );
$class_names = ' class="' . esc_attr( $class_names ) . '"';
$id = apply_filters( 'nav_menu_item_id', 'menu-item-'. $item->ID, $item, $args );
$id = strlen( $id ) ? ' id="' . esc_attr( $id ) . '"' : '';
$output .= $indent . '<li' . $id . $value . $class_names .'>';
$attributes = ! empty( $item->attr_title ) ? ' title="' . esc_attr( $item->attr_title ) .'"' : '';
$attributes .= ! empty( $item->target ) ? ' target="' . esc_attr( $item->target ) .'"' : '';
$attributes .= ! empty( $item->xfn ) ? ' rel="' . esc_attr( $item->xfn ) .'"' : '';
$attributes .= ! empty( $item->url ) ? ' href="' . esc_attr( $item->url ) .'"' : '';
$item_output = $args->before;
$item_output .= '<a'. $attributes .'>';
$item_output .= $args->link_before . apply_filters( 'the_title', $item->title, $item->ID ) . $args->link_after;
$item_output .= '</a>';
$item_output .= $args->after;
$output .= apply_filters( 'walker_nav_menu_start_el', $item_output, $item, $depth, $args );
}
}
/**
* @see Walker::end_el()
* @since 3.0.0
*
* @param string $output Передается по ссылке. Используется для добавления дополнительного контента.
* @param object $item Объект данных страницы. Не используется.
* @param int $depth Глубина страницы. Не используется.
*/
function end_el(&$output, $item, $depth) {
global $current_branch;
if($current_branch && $depth > 0) $output .= "</li>\n";
if($depth == 0) $current_branch = 0;
}
}

Ознакомьтесь с кодом моего плагина или используйте его для своих целей ;)
Этот плагин добавляет улучшенный виджет "Навигационное меню". Он предлагает множество настроек для кастомизации вывода пользовательского меню через виджет.
Основные возможности:
- Кастомная иерархия - "Только связанные подпункты" или "Только строго связанные подпункты".
- Начальная глубина и максимальный уровень отображения + плоский вид.
- Отображение всех пунктов меню, начиная с выбранного.
- Отображение только прямого пути к текущему элементу или только дочерних элементов выбранного пункта (с опцией включения родительского элемента).
- Кастомный CSS-класс для блока виджета.
- И практически все параметры функции wp_nav_menu.

Принятый ответ признает, что требует ввода данных и не основан на контексте. Вот версия, которая поддерживает передачу контекста или может использовать встроенные классы WordPress current-x
для определения контекста:
add_filter( 'wp_nav_menu_objects', 'limit_tree', 10, 2 );
function limit_tree( $items, $args ) {
if ( empty( $args->context ) ) {
return $items;
}
if('current' == $args->context) {
$current_pages = array_filter($items, function($item) {
return !empty(array_intersect(['current-menu-parent', 'current-menu-ancestor', 'current-menu-item'], $item->classes));
});
$parent_id = array_pop($current_pages)->ID;
} else {
$ids = wp_filter_object_list( $items, array( 'object_id' => $args->context ), 'and', 'ID' );
$parent_id = array_pop( $ids );
}
$children = submenu_get_children_ids( $parent_id, $items );
foreach ( $items as $key => $item ) {
// Это можно изменить, если вы не хотите включать родительский элемент
if ( $item->ID != $parent_id && ! in_array( $item->ID, $children ) ) {
unset( $items[$key] );
}
}
return $items;
}
function submenu_get_children_ids( $id, $items ) {
$ids = wp_filter_object_list( $items, array( 'menu_item_parent' => $id ), 'and', 'ID' );
foreach ( $ids as $id ) {
$ids = array_merge( $ids, submenu_get_children_ids( $id, $items ) );
}
return $ids;
}
Использование:
wp_nav_menu([
'theme_location' => 'foo-bar',
'context' => 'current',
]);
Или...
wp_nav_menu([
'theme_location' => 'foo-bar',
'context' => get_queried_object_id(), // или произвольный ID записи
]);
