Отображение части/ветви дерева меню с помощью wp_nav_menu()

12 окт. 2010 г., 00:50:29
Просмотры: 48.3K
Голосов: 113

У меня есть меню, определенное в админке WordPress, которое выглядит так:

структура меню WordPress

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

Я просмотрел документацию wp_nav_menu(), и похоже, что в ней нет встроенного способа указать конкретный узел данного меню в качестве начальной точки при генерации ссылок.

Я создал решение для похожей ситуации, которое опиралось на отношения, создаваемые родительской страницей, но я ищу решение, которое использует именно систему меню. Буду благодарен за любую помощь.

4
Комментарии

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

goldenapples goldenapples
12 окт. 2010 г. 04:51:36

@goldenapples, это именно то, что мне нужно. Если вы не против опубликовать ваш код в качестве ответа, я буду очень признателен.

jessegavin jessegavin
12 окт. 2010 г. 17:26:41

Удивительно, что такая очевидно полезная функциональность ещё не встроена по умолчанию. Это в целом очень полезно для любого сайта, работающего как "CMS".

hakre hakre
9 февр. 2011 г. 17:54:53

Я пытаюсь решить указанную выше проблему или что-то подобное. В качестве альтернативы я предложил CSS-решение здесь: http://stackoverflow.com/q/7640837/518169

hyperknot hyperknot
4 окт. 2011 г. 02:52:14
Все ответы на вопрос 11
16
78

Это все еще не давало мне покоя, поэтому я вернулся к проблеме и собрал это решение, которое не так сильно зависит от контекста:

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 );
12 окт. 2010 г. 12:00:11
Комментарии

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

daniel.tosaba daniel.tosaba
22 авг. 2012 г. 08:09:15

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

Rarst Rarst
22 авг. 2012 г. 14:18:19

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

dotty dotty
11 сент. 2012 г. 14:19:34

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

dotty dotty
11 сент. 2012 г. 19:04:34

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

Rarst Rarst
11 сент. 2012 г. 19:33:12

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

dotty dotty
13 сент. 2012 г. 14:08:50

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

Rarst Rarst
13 сент. 2012 г. 14:12:35

Похоже, это больше не работает в 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 gdaniel
5 сент. 2014 г. 18:13:58

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

Rarst Rarst
5 сент. 2014 г. 18:26:46

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

gdaniel gdaniel
5 сент. 2014 г. 18:36:02

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

gdaniel gdaniel
5 сент. 2014 г. 19:25:04

Отличное решение, @Rarst! Как всегда.

Eric Holmes Eric Holmes
24 февр. 2015 г. 17:03:30

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

Imperative Ideas Imperative Ideas
10 мар. 2015 г. 03:26:00

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

Rarst Rarst
10 мар. 2015 г. 08:47:20

Очень аккуратно. Если кому-то интересно, чтобы сделать то же самое, но по ID страницы, измените строку с wp_filter_object_list на wp_filter_object_list( $items, array( 'object_id' => $args->submenu ), 'and', 'ID' );

Ben Ben
30 апр. 2015 г. 13:25:17
Показать остальные 11 комментариев
0
14

@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-парсер.

7 февр. 2011 г. 16:15:28
3
13

Привет, @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 мог бы реализовать эту идею.

Конечно, ты понимаешь, что если ты всё-таки реализуешь это, то обязан выложить код здесь, чтобы все могли воспользоваться твоим великодушием! :-)

12 окт. 2010 г. 10:50:41
Комментарии

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

jessegavin jessegavin
14 окт. 2010 г. 04:24:47

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

MikeSchinkel MikeSchinkel
14 окт. 2010 г. 05:34:14

Это именно тот ответ, который я искал весь день, большое спасибо!

Chris Haas Chris Haas
13 дек. 2019 г. 22:39:44
4
10

Это расширение 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() ) 
   ); ?>
15 окт. 2010 г. 23:06:30
Комментарии

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

goldenapples goldenapples
16 окт. 2010 г. 22:46:34

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

Zolomon Zolomon
20 февр. 2011 г. 22:10:29

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

goldenapples goldenapples
21 февр. 2011 г. 22:39:20

Возможно, вы хотите передать параметр depth в вызов wp_nav_menu, на случай если ваша тема каким-то образом переопределяет значение по умолчанию 0 (показывать все уровни)?

goldenapples goldenapples
21 февр. 2011 г. 22:41:09
3

Обновление: Я превратил это в плагин. Скачать здесь.


Мне нужно было решить эту задачу самостоятельно, и в итоге я написал фильтр для результатов поиска меню. Это позволяет использовать 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('О нас', 'Совет директоров')
));

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

21 апр. 2011 г. 14:41:12
Комментарии

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

Digerkam Digerkam
28 февр. 2013 г. 18:28:45

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

Floris Floris
4 сент. 2018 г. 13:29:51

Я также предлагаю сделать $submenu опциональным. Так вы все еще сможете получить все меню, когда это необходимо. Добавьте это в начало фильтра: if ( ! isset( $args->submenu ) ) { return $items; }

Floris Floris
4 сент. 2018 г. 13:43:48
0

Я собрал следующий класс для себя. Он найдет родительский элемент верхнего меню для текущей страницы, или вы можете передать ему целевой 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
));
1 февр. 2012 г. 01:19:49
1

@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;
        }
    }
15 мар. 2011 г. 20:07:17
Комментарии

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

TechRemarker TechRemarker
24 мая 2011 г. 04:30:45
0

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

8 февр. 2011 г. 23:39:06
0

Я создал модифицированный 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;
}

}

20 апр. 2011 г. 21:46:33
0

Ознакомьтесь с кодом моего плагина или используйте его для своих целей ;)

Этот плагин добавляет улучшенный виджет "Навигационное меню". Он предлагает множество настроек для кастомизации вывода пользовательского меню через виджет.

Основные возможности:

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

http://wordpress.org/extend/plugins/advanced-menu-widget/

10 янв. 2012 г. 02:59:05
0

Принятый ответ признает, что требует ввода данных и не основан на контексте. Вот версия, которая поддерживает передачу контекста или может использовать встроенные классы 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 записи
]);
21 окт. 2021 г. 00:59:53