Динамическое исключение пунктов меню из wp_nav_menu

21 окт. 2011 г., 20:14:45
Просмотры: 29.5K
Голосов: 22

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

1. Предыстория:

Я создал меню в стиле Dock, используя пользовательские меню WordPress (wp_nav_menu) и jqDock на моем сайте. Поскольку jqDock требует непрерывных изображений или ссылок на изображения для своей работы, я использую пользовательский walker, чтобы HTML-вывод навигационного меню выглядел примерно так:

<div id="menu-first" class="nav">
<a><img src="http://path/to/image-1.png"/></a>
<a><img src="http://path/to/image-2.png"/></a>
<a><img src="http://path/to/image-3.png"/></a>
и т.д...
</div>

Код для моего пользовательского walker:

class custom_nav_walker extends Walker_Nav_Menu 
{
    var $tree_type = array( 'post_type', 'taxonomy', 'custom' );
    var $db_fields = array( 'parent' => 'menu_item_parent', 'id' => 'db_id' );

    function start_lvl(&$output, $depth) {
        $indent = str_repeat("\t", $depth);
        $output .= "\n$indent<ul class=\"sub-menu\">\n";
    }

    function end_lvl(&$output, $depth) {
        $indent = str_repeat("\t", $depth);
        $output .= "$indent</ul>\n";
    }

    function start_el(&$output, $item, $depth, $args) {
        global $wp_query;
        $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, $args ) );
        $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        ) .'"' : '';

        $description  = ! empty( $item->description ) ? esc_attr( strtolower( $item->description )) : '';
        $item_title   = ! empty( $item->attr_title )  ? esc_attr( $item->attr_title ) : '';

        if ( strpos($description, ';') !== false ) {
        $description_array = explode (';', $description);
            $image_name = $description_array[0];
            $image_alt = $description_array[1];
        } else {
            $image_name = $description;
            $image_alt = $item_title;
        }

        $item_output = $args->before;
        $item_output .= '<a'. $attributes .'>';
        $item_output .= $args->link_before .'<img src="'.get_bloginfo('template_url').'/images/skin1/'.$image_name.'" alt="'.$image_alt.'" title="'.$item_title.'" />'.$args->link_after;
        $item_output .= '</a>';
        $item_output .= $args->after;

        $output .= apply_filters( 'walker_nav_menu_start_el', $item_output, $item, $depth, $args );
    }

    function end_el(&$output, $item, $depth) {
        $output .= "";
    }
}

Затем скрипт jqDock перехватывает ID меню ('menu-first') и заменяет вывод wp_nav_menu на меню Dock. HTML-вывод меню Dock изменяется в зависимости от параметров, указанных при загрузке jqDock.

2. Вопрос:

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

3. Отвергнутые решения:

a. Множественные меню: Регистрация и создание нескольких меню с последующим условным вызовом могли бы сработать, однако я не думаю, что это идеальное или чистое решение по многим причинам. Кроме того, множественные меню сложно поддерживать и обновлять.

b. Регулярные выражения для поиска и замены: Это может заставить меня менять параметр needle каждый раз при изменении параметров jqDock, поскольку HTML-вывод модифицируется.

c. CSS свойство 'display': Скрытие элементов через CSS свойство display работает, но поскольку оно должно применяться к выводу меню jqDock, это влияет на визуальный рендеринг меню.

4. Неудачные решения:

a. Фильтр для wp_nav_menu_items: Я попытался перехватить переменную '$items' (строка) и присвоить ей разные значения через условные теги следующим кодом:

function userf_dynamic_nav_menu ($items) {
    $items_array_home = explode('<a', $items);
    $items_array_nothome = $items_array_home;

    unset($items_array_home[1]);
    unset($items_array_nothome[2]);

    $items_home = implode('<a', $items_array_home);
    $items_nothome = implode('<a', $items_array_nothome);

    if ( is_home() ) {
        $items = $items_home;
    } else {
        $items = $items_nothome;
    }
    return $items;
}
add_filter('wp_nav_menu_first_items', 'userf_dynamic_nav_menu');

Это работает только частично, потому что пункты меню действительно меняются, но условные теги игнорируются. Полагаю, это имеет смысл из-за момента, в который применяется фильтр.

b. Пользовательская функция навигационного меню: Я попытался создать собственную пользовательскую функцию навигационного меню, чтобы добавить аргумент exclude в массив $defaults и использовать этот слегка модифицированный код из wp_list_pages для заполнения дополнительного аргумента:

$exclude_array = ( $args->exclude ) ? explode(',', $args->exclude) : array();
$args->exclude = implode( ',', apply_filters('wp_nav_menu_excludes', $exclude_array) );

Есть идеи?

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

Можете показать ваш пользовательский класс-наследник Walker?

soulseekah soulseekah
21 окт. 2011 г. 20:42:56

Привет Souleseekah, я только что добавил его в свой исходный пост. Спасибо!

Marventus Marventus
21 окт. 2011 г. 20:52:42

Я тоже думал о передаче аргумента exclude, но, в отличие от wp_list_pages и многих других функций WP, wp_nav_menu не включает его. Так что даже если я укажу его при вызове меню или в Walker'е, он не будет учтен внутри wp_nav_menu, верно?

Marventus Marventus
21 окт. 2011 г. 21:08:15

Извините, писал не подумав, сразу удалил.

soulseekah soulseekah
21 окт. 2011 г. 21:11:02

Не переживайте!

Marventus Marventus
21 окт. 2011 г. 21:16:34

Я подтвердил, что условные теги не работают с фильтрами, применёнными к wp_nav_menu_items. Этот код выводит одно и то же изображение: function bla ($items) { $image_number = ( is_user_logged_in ) ? '2' : '1'; $items .= ( is_user_logged_in ) ? '<a href="'.wp_logout_url( $_SERVER['REQUEST_URI'] ).'"><img src="'.get_bloginfo('template_url').'/images/skin1/login_'.$image_number.'_normal.png" alt="Log Out" title="Log Out" /></a>' : '<a href="'.wp_login_url( $_SERVER['REQUEST_URI'] ).'"><img src="'.get_bloginfo('template_url').'/images/skin1/login_'.$image_number.'_normal.png" alt="Log in" title="Log in"/></a>';}

Marventus Marventus
22 окт. 2011 г. 06:29:59

Тебе нужно ВЫЗВАТЬ функцию is_user_logged_in() со скобками... у тебя там уже поздно?

soulseekah soulseekah
22 окт. 2011 г. 08:21:39

Добавил метод фильтра wp_nav_menu_items в мой ответ.

soulseekah soulseekah
22 окт. 2011 г. 08:41:14

Чёрт... Хахаха. Ну, в своё оправдание должен сказать, что было уже довольно поздно. Но всё же...

Marventus Marventus
22 окт. 2011 г. 15:28:55
Показать остальные 4 комментариев
Все ответы на вопрос 2
12
41

Метод 1

Вы можете добавить конструктор в ваш пользовательский Walker для хранения дополнительных аргументов исключения, например:

class custom_nav_walker extends Walker_Nav_Menu {
    function __construct( $exclude = null ) {
        $this->exclude = $exclude;
    }

    function skip( $item ) {
        return in_array($item->ID, (array)$this->exclude);
        // или
        return in_array($item->title, (array)$this->exclude);
        // и т.д.
    }

    // ...внутри start_el, end_el
    if ( $this->skip( $item ) ) return;
}

Или отказаться от конструктора и установить свойство $exclude перед передачей в качестве walker в wp_nav_menu() следующим образом:

$my_custom_nav_walker = new custom_nav_walker;
$my_custom_nav_walker->exclude = array( ... );

В зависимости от того, что вы исключаете, укажите правильную форму для исключения.

Метод 2

Вот как можно это сделать, используя фильтр wp_get_nav_menu_items.

function wpse31748_exclude_menu_items( $items, $menu, $args ) {
    // Перебираем элементы для поиска и удаления
    foreach ( $items as $key => $item ) {
        if ( $item->object_id == 168 ) unset( $items[$key] );
    }

    return $items;
}

add_filter( 'wp_get_nav_menu_items', 'wpse31748_exclude_menu_items', null, 3 );

Примечание: object_id - это ID объекта, на который указывает меню, а ID - это ID самого меню, они отличаются.

Дайте знать ваши мысли.

21 окт. 2011 г. 21:08:38
Комментарии

Спасибо! Это может сработать. Я попробую и дам вам знать.

Marventus Marventus
21 окт. 2011 г. 21:14:18

Я попробовал подход с конструктором, но, что бы я ни делал, постоянно получаю ошибку "Неверный тип данных для второго аргумента" в функции in_array. Я что-то делаю не так?

Marventus Marventus
21 окт. 2011 г. 21:41:28

Свойство $exclude должно быть массивом. Убедитесь, что вы передаёте массив в конструктор, или посмотрите обновлённый код в моём ответе. Особенно обратите внимание на приведение типа для $this->exclude, на случай, если массив не был передан.

soulseekah soulseekah
21 окт. 2011 г. 21:43:56

Извините за это: у меня была опечатка в функции. Я только что попробовал $exclude = array ('4', '7'); и использование слагов тоже, но это не оказывает никакого влияния на вывод walker. Я попробую второй подход и дам вам знать.

Marventus Marventus
22 окт. 2011 г. 02:20:54

Нет, это тоже не сработало. Думаю, мой мозг уже перегружен попытками разобраться в этом, и это может влиять на мою... "производительность", :-)

Marventus Marventus
22 окт. 2011 г. 02:40:03

Что ж, возможно, вы не сравниваете фактическое значение $item, помните, что $item — это стандартный класс со свойствами, поэтому вам нужно сравнить одно из его свойств с тем, что вы передаёте. Вы можете использовать var_dump для $item, чтобы увидеть, какие свойства доступны для сравнения при исключении. Отредактировал свой код, чтобы сделать его более понятным.

soulseekah soulseekah
22 окт. 2011 г. 08:20:08

Логично. Попробую новую версию и дам знать.

Marventus Marventus
22 окт. 2011 г. 16:25:09

Souseekah, ты просто волшебник: это сработало идеально. Большое спасибо!

Marventus Marventus
22 окт. 2011 г. 20:31:48

Отлично, рад, что смог помочь.

soulseekah soulseekah
23 окт. 2011 г. 10:30:50

При использовании метода 2 пункты меню также исчезают во вкладке Внешний вид > Меню в админке. Как предотвратить это?

Naweed Chougle Naweed Chougle
18 нояб. 2015 г. 14:25:12

........Ответ заключается в том, что вам нужно использовать условную проверку is_admin(), чтобы определить, загружается ли админ-панель.

Naweed Chougle Naweed Chougle
18 нояб. 2015 г. 14:32:29

Метод 2 работал отлично, включая проверку is_admin(). Я был немного удивлен, что скрытие ссылки "Главная" на главной странице не является стандартной опцией, но этот код с использованием is_front_page() дал мне то, что я хотел.

Ron Burk Ron Burk
13 окт. 2016 г. 22:28:44
Показать остальные 7 комментариев
4

это помогает

$exclude_array = ( $args->exclude ) ? explode(',', $args->exclude) : array();
$args->exclude = implode( ',', apply_filters('wp_nav_menu_excludes', $exclude_array) );

в качестве примера

<?php wp_nav_menu( array( 'container_class' => 'menu-header', 'theme_location' => 'primary', 'exclude' => '66' ) ); ?>
22 окт. 2011 г. 03:11:35
Комментарии

Привет, Saq, я забыл упомянуть, что одно из решений, которое не сработало, — это создание пользовательской функции nav_menu и добавление этого кода в качестве дополнительного аргумента к параметрам функции по умолчанию. К сожалению, это не помогло. Я не пробовал включать его в walker, но думаю, что это тоже не сработает по той же причине, которую я упомянул выше — главным образом потому, что у wp_nav_menu нет аргумента "exclude", но я могу ошибаться.

Marventus Marventus
22 окт. 2011 г. 04:35:24

Я обновил свой первоначальный пост, чтобы включить это для ясности.

Marventus Marventus
22 окт. 2011 г. 05:05:48

Что если не использовать пользовательский walker, а вместо этого взять обычный nav_menu и извлечь элементы с помощью wp_get_nav_menu_items(), добавив своё пользовательское изображение?

saq saq
22 окт. 2011 г. 06:45:59

Это было бы хорошим обходным решением в целом, но в данном конкретном случае wp_get_nav_menu_items не получит изображения, потому что теги img не хранятся в самом пользовательском меню (только их имена файлов находятся в поле описания, например, "image1.png"). Пользовательский walker - это то, что позволяет мне вставлять теги img в вывод меню.

Marventus Marventus
22 окт. 2011 г. 14:53:04