Темизация меню в Wordpress #2: Как кастомизировать вывод каждого элемента меню в Wordpress

Подробно разбираем доступные хуки и что такое WP Walker и как он может помочь.

В предыдущей статье мы рассмотрели базовые функции для работы с меню в Wordpress. Они позволяют управлять общим выводом меню, но не дают возможности как-то работать с отдельными элементами меню.

В Wordpress предусмотрено несколько способов для работы с элементами меню - правильный и неправильный.

Начнём с неправильного метода, который всё ещё можно встретить в некоторых ответах на stackoverflow и подобных ресурсах.

 

НЕПРАВИЛЬНО!

Обработка готового HTML меню с помощью регулярных выражений.

 

Этот метод популярен в виду своей лаконичности и простоты использования, однако, я не рекомендую вам использовать его. Он заточен на использовании функций preg_replace или str_replace, которые чреваты ошибками, чувствительны к тому контенту, который даёт им Wordpress на входе, да и в принципе работают уже после того, как WP сгенерирует вёрстку элементов меню через WP_Nav_Walker, т.е. являются лишней тратой ресурсов.

 

<?php
// ПОЖАЛУЙСТА! Не делайте так!
add_filter( 'wp_nav_menu_items', 'my_theme_nav_menu_items' );
// удаляет все <ul> и <li>
function my_theme_nav_menu_items ($html) {
  $html = preg_replace('<\/?li(?!>)*>', '', $html);
  $html = preg_replace('<\/?ul(?!>)*>', '', $html);
  return $html;
}

 

Да, этот метод, безусловно, уберёт из вывода ненужные элементы вёрстки, но не используйте его! Этот фильтр будет обрабатывать ВСЕ ваши меню на сайте, для того, чтобы применить его к конкретной меню, есть более специализированный фильтр wp_nav_menu_$slug_items, где slug - это идентификатор меню.

 

*   *   *

 

А как правильно-то??!

— Использовать Walker

 

В Wordpress существует универсальный класс Walker (wp-includes/class-wp-walker.php), который был создан для работы с различными древовидными и списковыми структурами данных.
Для обработки элементов меню существует более специализированный класс - WP_Nav_Walker (wp-includes/class-walker-nav-menu.php), который отвечает за способ вывода элементов меню.

 

Walker, это довольно простая концепция, так что написание собственных классов, наследующих Walker, не составит труда (если вы уже знаете азы ООП). Посмотрим на структуру типичного Walker:

 

<?php
class My_Nav_Walker extends WP_Nav_Walker {
  public function walk ( $elements, $max_depth, $page_num, $per_page ) {}
  public function start_lvl (&$output, $depth = 0, $args = array()) {}
  public function end_lvl (&$output, $depth = 0, $args = array()) {}

  public function start_el (&$output, $item, $depth, $args = array()) {}
  public function end_el (&$output, $item, $depth) {}
}

 

Как видите, класс элементарный по структуре. Вот какие методы он нам предоставляет:

 

  • walk — метод вызываемый при начале генерации списка элементов. В самом WP_Nav_Walker он не переопределяется. Принимает массив элементов меню ($elements), максимальный уровень вложенности (разбирали в прошлой статье), номер страницы и количество элементов на странице (это используется для wordpress пагинации, а не меню, поэтому в этой статье рассматривать их не будем).

 

На заметку

Как правило, этот метод вам не понадобится, однако бывают случаи, когда нужно, например, отсортировать список элементов меню по жестко заданному сценарию (как в случае с WPGlobus, пришлось добавить жесткую сортировку, из-за того что плагин ставит текущий язык первым в списке).

 

  • start_lvl - метод вызываемый для построения нового уровня вложенности (по сути, просто выводит вложенный <ul>...)
  • end_lvl - метод для вывода элемента закрывающего уровень вложенности (</ul>)
  • start_el - метод для вывода открывающего тега элемента (<li><a>...</a>)
  • end_el - метод для вывода закрывающего тега элемента (</li>)
     


У всех методов имеется параметр &$output – это ссылка на строку, в которую записывается HTML вывод.

У методов start_el и end_el имеется также аргумент $item – в нём содержится информация о конкретном элементе списка в виде стандартного объекта PHP (stdclass). В случае с меню, $item обладает целой кучей различных свойств, посмотреть их все, вы можете в функции wp_setup_nav_menu_item (wp-includes/nav-menu.php).

 

Вот урезанный пример того, как можно реализовать кастомизацию меню с помощью Walker (чтобы не выводить элементы <li>):

 

<?php
  /*
    Этот Walker убирает из вывода элементы <li>
  */

  class MyWalker extends WP_Nav_Walker {
    // я опустил использование стандартных фильтров из оригинального класса, чтобы донести суть. 
    // Пожалуйста ознакомьтесь с wp-includes/class-walker-nav-menu.php !!!
    public function start_el (&$output, $item, $depth, $args = array()) {

      // генерация css классов
      $class_names = join( ' ', apply_filters( 'nav_menu_css_class', array_filter( $classes ), $item, $args, $depth ) );
      $class_names = $class_names ? ' class="' . esc_attr( $class_names ) . '"' : '';

      // генерация html ID
      $id = apply_filters( 'nav_menu_item_id', 'menu-item-'. $item->ID, $item, $args, $depth );
      $id = $id ? ' id="' . esc_attr( $id ) . '"' : '';

      // набор HTML атрибутов для ссылок
      $atts = array();
      $atts['title']  = ! empty( $item->attr_title ) ? $item->attr_title : '';
      $atts['target'] = ! empty( $item->target )     ? $item->target     : '';
      $atts['rel']    = ! empty( $item->xfn )        ? $item->xfn        : '';
      $atts['href']   = ! empty( $item->url )        ? $item->url        : '';

      $atts = apply_filters( 'nav_menu_link_attributes', $atts, $item, $args, $depth );

      // превращаем набор атрибутов в строку
      $attributes = '';
      foreach ( $atts as $attr => $value ) {
          if ( ! empty( $value ) ) {
              $value = ( 'href' === $attr ) ? esc_url( $value ) : esc_attr( $value );
              $attributes .= ' ' . $attr . '="' . $value . '"';
          }
      }

      // текст ссылки
      $title = apply_filters( 'the_title', $item->title, $item->ID );

      // те самые before, link_before, link_after и after, о которых мы говорили в
      // прошлой статье.
      $item_output = $args->before;
      $item_output .= '<a' . $id . $class_names . $attributes .'>';
      $item_output .= $args->link_before . $title . $args->link_after;
      $item_output .= '</a>';
      $item_output .= $args->after;

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

    // в нашем случае пустая функция, чтобы запретить вывод </li>
    public function end_el (&$output, $item, $depth, $args = array()) {}
  }

 

Теперь нужно разобраться, как использовать наш MyWalker при выводе меню. Самый простой способ это сделать - сделать require файла с определением этого класса в functions.php. Тем самым, класс MyWalker станет доступным в любом месте темы и вы сможете спокойно подключить его к выводу меню следующим образом:

 

<?php
$args = array(
  'theme_location'  => 'main-menu',
  'container'       => 'nav',
  'container_class' => 'my-menu-{menu-slug}-container',
  'menu_class'      => 'my-menu',
  'items_wrap'      => '%3$s',
  'walker'          => new MyWalker(),
);

wp_nav_menu( $args );

 

На этом наше путешествие по управлению меню в Wordpress завершается — используя описанные в этих статьях средства, вы сможете заставить выводить любую вёрстку меню в Wordpress без особых страданий.

 


От себя добавлю, что, лично я, сделал бы эти вещи в Wordpress параметрами для wp_nav_menu – было бы намного удобнее использовать шаблоны для элементов и уровней аналогично шаблону items_wrap и заданию классов как menu_class. Это позволило бы на простых меню сэкономить кучу времени на разработке собственных Walker'ов, и оставить их реализацию только для сложных и экзотических ситуаций.


 

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