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;
}
}