Instantly share code, notes, and snippets.
Last active
July 12, 2019 00:50
-
Save DaveyJake/a4055a9bdf13d7c54821f629cd6dab79 to your computer and use it in GitHub Desktop.
Add custom ID attribute to all WordPress submenus. Great for building mega menus.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* Nav Menu API: DJ_NavWalker class. | |
* | |
* I cloned & modified this class to help improve compatibility for | |
* WordPress and the Foundation 5.5.3 `dropdown` module. | |
* | |
* @author Davey Jacobson <djacobson@usarugby.org> | |
* | |
* @package Davey_Jacobson | |
* @subpackage NavWalker | |
* @since 2.5.0 | |
*/ | |
/** | |
* Core class used to implement an HTML list of nav menu items. | |
* | |
* @since 3.0.0 | |
* | |
* @see Walker | |
*/ | |
class DJ_NavWalker extends Walker_Nav_Menu { | |
/** | |
* Change this according to your website's main menu slug. | |
* | |
* @access private | |
* @var string | |
*/ | |
private static $main_menu = 'main-menu'; | |
/** | |
* Custom postfix value to be appended to the `ID`. | |
* | |
* @access private | |
* @var string | |
*/ | |
private static $postfix = '-menu'; | |
/** | |
* The main sub-menu list container. | |
* | |
* @access private | |
* @var array | |
*/ | |
private static $sub_menus = array(); | |
/** | |
* Items to be renamed; adjust according to your website. | |
* | |
* @access private | |
* @var array { | |
* | |
* @type string `$item->ID` => @type string `$custom_slug` | |
* | |
*} | |
*/ | |
private static $renamed = array( | |
//'21464' => 'about' | |
); | |
/** | |
* The most important piece of this whole puzzle. | |
* | |
* @access private | |
* @var int | |
*/ | |
private static $index = -1; | |
/** | |
* Starts the list before the elements are added. | |
* | |
* @since 3.0.0 | |
* | |
* @see Walker::start_lvl() | |
* | |
* @param string $output Used to append additional content (passed by reference). | |
* @param int $depth Depth of menu item. Used for padding. | |
* @param stdClass $args An object of wp_nav_menu() arguments. | |
*/ | |
public function start_lvl( &$output, $depth = 0, $args = array() ) { | |
self::$index++; | |
if ( isset( $args->item_spacing ) && 'discard' === $args->item_spacing ) { | |
$t = ''; | |
$n = ''; | |
} else { | |
$t = "\t"; | |
$n = "\n"; | |
} | |
$indent = str_repeat( $t, $depth ); | |
// Default class. | |
$classes = array( 'sub-menu' ); | |
/** | |
* Filters the CSS class(es) applied to a menu list element. | |
* | |
* @since 4.8.0 | |
* | |
* @param string[] $classes Array of the CSS classes that are applied to the menu `<ul>` element. | |
* @param stdClass $args An object of `wp_nav_menu()` arguments. | |
* @param int $depth Depth of menu item. Used for padding. | |
*/ | |
$class_names = join( ' ', apply_filters( 'nav_menu_submenu_css_class', $classes, $args, $depth ) ); | |
$class_names = $class_names ? ' class="' . esc_attr( $class_names ) . '"' : ''; | |
/** | |
* Add a specified `ID` attribute to all main menu dropdowns. | |
* | |
* @link {@see 'wp_get_nav_menu_items'} | |
*/ | |
if ( self::$main_menu === $args->menu->slug ) { /** MAKE SURE YOU RENAME THE `$main_menu` PROPERTY FOR YOUR WEBSITE. */ | |
/** | |
* The final list of available dropdowns. | |
* | |
* @see self::$sub_menus | |
* | |
* @var array | |
*/ | |
$sub_menus = array(); | |
/** | |
* Grab only `$items` whose parent's object ID is `0`. | |
* | |
* @var array | |
*/ | |
$items = wp_get_nav_menu_items( self::$main_menu ); | |
$items = array_map( array( $this, 'remove_none_dropdown_menus' ), $items ); | |
// Ensure there are no duplicates or `null` values. | |
array_filter( $items ); | |
// If we have dropdown menus... | |
if ( $items ) { | |
foreach ( $items as $i => $item ) { | |
if ( isset( $item->url ) ) { | |
$hash = wp_parse_url( $item->url, PHP_URL_FRAGMENT ); | |
$path = wp_parse_url( $item->url, PHP_URL_PATH ); | |
$_menu = empty( $path ) ? $hash : ltrim( $path, '/' ); | |
$sub_menu = untrailingslashit( $_menu ); | |
// In case we've specifed `$items` that need to be renamed... | |
if ( isset( self::$renamed ) && ! empty( self::$renamed ) ) { | |
foreach ( self::$renamed as $id => $slug ) { | |
if ( $id === @$item->ID ) { | |
$sub_menu = $slug; | |
} | |
} | |
} | |
} | |
// Ensure we only have clean slugs. | |
$sub_menus[] = stripslashes( $sub_menu ); | |
} | |
} | |
// Final, cleaned sub-menu list. | |
self::$sub_menus = array_values( array_unique( $sub_menus ) ); | |
// Begin building the final sub-menu output. | |
$output .= "{$n}{$indent}<ul{$class_names} id='"; | |
// The actual `ID` attribute value. | |
$output .= esc_attr( @array_slice( self::$sub_menus, self::$index )[0] . self::$postfix ); | |
// Added compatibility for Foundation 5.5.3. | |
$output .= "' data-dropdown-content>{$n}"; | |
} | |
else { | |
$output .= "{$n}{$indent}<ul{$class_names}>{$n}"; | |
} | |
} | |
/** | |
* Starts the element output. | |
* | |
* @since 3.0.0 | |
* @since 4.4.0 The {@see 'nav_menu_item_args'} filter was added. | |
* | |
* @see Walker::start_el() | |
* | |
* @param string $output Used to append additional content (passed by reference). | |
* @param WP_Post $item Menu item data object. | |
* @param int $depth Depth of menu item. Used for padding. | |
* @param stdClass $args An object of wp_nav_menu() arguments. | |
* @param int $id Current item ID. | |
*/ | |
public function start_el( &$output, $item, $depth = 0, $args = array(), $id = 0 ) { | |
if ( isset( $args->item_spacing ) && 'discard' === $args->item_spacing ) { | |
$t = ''; | |
$n = ''; | |
} else { | |
$t = "\t"; | |
$n = "\n"; | |
} | |
$indent = ( $depth ) ? str_repeat( $t, $depth ) : ''; | |
$classes = empty( $item->classes ) ? array() : (array) $item->classes; | |
$classes[] = 'menu-item-' . $item->ID; | |
/** | |
* Filters the arguments for a single nav menu item. | |
* | |
* @since 4.4.0 | |
* | |
* @param stdClass $args An object of wp_nav_menu() arguments. | |
* @param WP_Post $item Menu item data object. | |
* @param int $depth Depth of menu item. Used for padding. | |
*/ | |
$args = apply_filters( 'nav_menu_item_args', $args, $item, $depth ); | |
/** | |
* Filters the CSS classes applied to a menu item's list item element. | |
* | |
* @since 3.0.0 | |
* @since 4.1.0 The `$depth` parameter was added. | |
* | |
* @param string[] $classes Array of the CSS classes that are applied to the menu item's `<li>` element. | |
* @param WP_Post $item The current menu item. | |
* @param stdClass $args An object of wp_nav_menu() arguments. | |
* @param int $depth Depth of menu item. Used for padding. | |
*/ | |
$class_names = join( ' ', apply_filters( 'nav_menu_css_class', array_filter( $classes ), $item, $args, $depth ) ); | |
$class_names = $class_names ? ' class="' . esc_attr( $class_names ) . '"' : ''; | |
/** | |
* Filters the ID applied to a menu item's list item element. | |
* | |
* @since 3.0.1 | |
* @since 4.1.0 The `$depth` parameter was added. | |
* | |
* @param string $menu_id The ID that is applied to the menu item's `<li>` element. | |
* @param WP_Post $item The current menu item. | |
* @param stdClass $args An object of wp_nav_menu() arguments. | |
* @param int $depth Depth of menu item. Used for padding. | |
*/ | |
$id = apply_filters( 'nav_menu_item_id', 'menu-item-' . $item->ID, $item, $args, $depth ); | |
$id = $id ? ' id="' . esc_attr( $id ) . '"' : ''; | |
/** | |
* Add a custom `HTML5` attribute to list item tag if linked directly any `post_type`. | |
* | |
* @var string | |
*/ | |
if ( '#' !== $item->url ) { | |
$url_path = wp_parse_url( $item->url, PHP_URL_PATH ); | |
// Get the actual WP_Post object. | |
$post = get_page_by_path( $url_path ); | |
// Default to `post_id` of 0 if `$post` not found. | |
$post_id = !is_null( $post ) ? $post->ID : 0; | |
} | |
// Custom HTML5 data attribute on list item tag. | |
$dataID = ' data-id="' . ( 0 !== $post_id ? esc_attr( $post_id ) : 'external' ) . '"'; | |
// Generate the output. | |
$output .= "{$indent}<li{$id}{$class_names}{$dataID}>"; | |
$atts = array(); | |
$atts['title'] = ! empty( $item->attr_title ) ? $item->attr_title : ''; | |
$atts['target'] = ! empty( $item->target ) ? $item->target : ''; | |
// Automatic handling of URL targets and relationships (for SEO). | |
if ( '_blank' === $item->target && empty( $item->xfn ) ) { | |
$atts['rel'] = 'noopener noreferrer'; | |
} else { | |
$atts['rel'] = $item->xfn; | |
} | |
$atts['href'] = ! empty( $item->url ) ? $item->url : ''; | |
$atts['aria-current'] = $item->current ? 'page' : ''; | |
/** | |
* Filters the HTML attributes applied to a menu item's anchor element. | |
* | |
* @since 3.6.0 | |
* @since 4.1.0 The `$depth` parameter was added. | |
* | |
* @param array $atts { | |
* The HTML attributes applied to the menu item's `<a>` element, empty strings are ignored. | |
* | |
* @type string $title Title attribute. | |
* @type string $target Target attribute. | |
* @type string $rel The rel attribute. | |
* @type string $href The href attribute. | |
* @type string $aria_current The aria-current attribute. | |
* } | |
* @param WP_Post $item The current menu item. | |
* @param stdClass $args An object of wp_nav_menu() arguments. | |
* @param int $depth Depth of menu item. Used for padding. | |
*/ | |
$atts = apply_filters( 'nav_menu_link_attributes', $atts, $item, $args, $depth ); | |
$attributes = ''; | |
foreach ( (array) $atts as $attr => $value ) { | |
if ( ! empty( $value ) ) { | |
$value = ( 'href' === $attr ) ? esc_url( $value ) : esc_attr( $value ); | |
$attributes .= ' ' . $attr . '="' . $value . '"'; | |
} | |
} | |
/** This filter is documented in wp-includes/post-template.php */ | |
$title = apply_filters( 'the_title', $item->title, $item->ID ); | |
/** | |
* Filters a menu item's title. | |
* | |
* @since 4.4.0 | |
* | |
* @param string $title The menu item's title. | |
* @param WP_Post $item The current menu item. | |
* @param stdClass $args An object of wp_nav_menu() arguments. | |
* @param int $depth Depth of menu item. Used for padding. | |
*/ | |
$title = apply_filters( 'nav_menu_item_title', $title, $item, $args, $depth ); | |
$item_output = $args->before; | |
$item_output .= '<a' . $attributes . '>'; | |
$item_output .= $args->link_before . $title . $args->link_after; | |
$item_output .= '</a>'; | |
$item_output .= $args->after; | |
/** | |
* Filters a menu item's starting output. | |
* | |
* The menu item's starting output only includes `$args->before`, the opening `<a>`, | |
* the menu item's title, the closing `</a>`, and `$args->after`. Currently, there is | |
* no filter for modifying the opening and closing `<li>` for a menu item. | |
* | |
* @since 3.0.0 | |
* | |
* @param string $item_output The menu item's starting HTML output. | |
* @param WP_Post $item Menu item data object. | |
* @param int $depth Depth of menu item. Used for padding. | |
* @param stdClass $args An object of wp_nav_menu() arguments. | |
*/ | |
$output .= apply_filters( 'walker_nav_menu_start_el', $item_output, $item, $depth, $args ); | |
} | |
/** | |
* Ensure we're only retrieving the dropdown menus. | |
* | |
* @access private | |
* | |
* @return object Dropdown menus. | |
*/ | |
private function remove_non_dropdown_menus( $item ) { | |
if ( '0' === $item->menu_item_parent ) { | |
return $item; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment