WillSquire
7/14/2016 - 12:15 PM

Navigation Walker - Allows greater customisation of Wordpress's Walker_Nav_Menu class through improved configuration at a level by level bas

Navigation Walker - Allows greater customisation of Wordpress's Walker_Nav_Menu class through improved configuration at a level by level basis (configuring the depth of child menus).

/**
 * Class NavigationWalker
 *
 * Allows customisation of navigation items and containers on depth level basis.
 * I.e. element type or attributes can be set with 'element' key with a value of
 * array, where the index of the array corresponds to the current depth of the
 * navigation.
 *
 * Use sprintf() formatting to insert WP-Post variables (i.e. use %1$d to insert
 * ID field).
 *
 * @author Will Squire
 * @example Here is usage example using Bootstrap v4:
  wp_nav_menu([
    'theme_location' => 'primary_navigation',
    'menu_class' => 'nav navbar-nav',
    'element' => [
      // Configure element values for first set of elements
      0 => [
        'type' => 'li',
        'attributes' => [
          'class' => 'nav-item'
        ],
        'link' => [
          'attributes' => [
            'class' => 'nav-link'
          ]
        ],
        // Override about values with these if element has children
        'has_children' => [
          'attributes' => [
            'class' => 'nav-item dropdown'
          ],
          'link' => [
            'attributes' => [
              'class' => 'nav-link dropdown-toggle',
              'data-target' => '#',
              'data-toggle' => 'dropdown',
              'aria-haspopup' => 'true',
              'aria-expanded' => 'false'
            ]
          ]
        ]
      ],
      1 => [
        'attributes' => [
          'class' => 'dropdown-item'
        ],
        'link' => [
          'attributes' => [
            'class' => 'dropdown-item'
          ]
        ]
      ]
    ],
    'elements_container' => [
      // Change elements container to div at first child level
      0 => [
        'type' => 'div',
        'attributes' => [
          'class' => 'dropdown-menu'
        ]
      ]
    ],
    'walker' => new NavigationWalker()
  ]);
 */
class NavigationWalker extends \Walker_Nav_Menu {

  public function start_lvl(&$output, $depth=0, $args=[]) {
    // Container overrides
    $container_type = (!empty($args->elements_container[$depth]['type'])) ? $args->elements_container[$depth]['type'] : 'ul';
    $container_attributes = (!empty($args->elements_container[$depth]['attributes'])) ? $args->elements_container[$depth]['attributes'] : [];

    // Convert container attributes to string
    $container_attributes = $this->attributes_to_string($container_attributes);

    if(!empty($args->parent)) $output .= vsprintf("<{$container_type}{$container_attributes}>", (array)$args->parent);
    else $output .= "<{$container_type}{$container_attributes}>";
  }

  public function end_lvl(&$output, $depth=0, $args=[]) {
    // Container overrides
    $container_type = (!empty($args->elements_container[$depth]['type'])) ? $args->elements_container[$depth]['type'] : 'ul';

    // Remove parent as level is exiting
    if(!empty($args->parent)) unset($args->parent);

    $output .= "</{$container_type}>";
  }

  public function start_el(&$output, $item, $depth=0, $args=[], $id=0) {
    // Element overrides (don't wrap link in element by default, opt-in instead)
    if(!empty($args->element[$depth]['type'])) {
      $element_type = $args->element[$depth]['type'];
      $element_attributes = (!empty($args->element[$depth]['attributes'])) ? $args->element[$depth]['attributes'] : [];

      // Element has children override
      if($args->walker->has_children) {
        if(!empty($args->element[$depth]['has_children']['type'])) $element_type = $args->element[$depth]['has_children']['type'];
        if(!empty($args->element[$depth]['has_children']['attributes'])) $element_attributes = array_merge($element_attributes, $args->element[$depth]['has_children']['attributes']);
      }

      // Convert element attributes to string
      $element_attributes = $this->attributes_to_string($element_attributes);

      // Output element and run vsprintf() to add in dynamic variables
      $output .= vsprintf("<{$element_type}{$element_attributes}>", (array)$item);
    }

    // Link attributes
    $link_attributes = [];
    $link_attributes['title'] = !empty($item->attr_title) ? $item->attr_title : '';
    $link_attributes['target'] = !empty($item->target) ? $item->target : '';
    $link_attributes['rel'] = !empty($item->xfn) ? $item->xfn : '';
    $link_attributes['href'] = !empty($item->url) ? $item->url : '';

    // Link override
    if(!empty($args->element[$depth]['link']['attributes'])) $link_attributes = array_merge($link_attributes, $args->element[$depth]['link']['attributes']);

    // Link has children override
    if($args->walker->has_children) {
      // Add item to args for start_lvl and end_lvl
      $args->parent = $item;
      if(!empty($args->element[$depth]['has_children']['link']['attributes'])) $link_attributes = array_merge($link_attributes, $args->element[$depth]['has_children']['link']['attributes']);
    }

    // Convert link attributes to string
    $link_attributes = $this->attributes_to_string($link_attributes);

    // Link title
    $title = apply_filters('the_title', $item->title, $item->ID);

    // Output link and run vsprintf() to add in dynamic variables
    $output .= vsprintf("<a{$link_attributes}>{$args->link_before}{$title}{$args->link_after}</a>", (array)$item);
  }

  public function end_el(&$output, $item, $depth=0, $args=[]) {
    // Element overrides (don't wrap link in element by default)
    if (!empty($args->element[$depth]['type'])) {
      $element_type = $args->element[$depth]['type'];

      // Element has children override
      if($args->walker->has_children) {
        if(!empty($args->element[$depth]['has_children']['type'])) $element_type = $args->element[$depth]['has_children']['type'];
      }

      // Output element closing and run vsprintf() to add in dynamic variables
      $output .= vsprintf("</{$element_type}>", (array)$item);
    }
  }

  private function attributes_to_string($atts) {
    $attributes = '';
    foreach ( $atts as $attr => $value ) {
      if ( ! empty( $value ) ) {
        $value = ( 'href' === $attr ) ? esc_url( $value ) : esc_attr( $value );
        $attributes .= ' ' . $attr . '="' . $value . '"';
      }
    }
    return $attributes;
  }
}