The idea
Instead of create a custom walker I thought was easier filter the items using the filter wp_nav_menu_objects
hook.
This hook is defined in /wp-includes/nav-menu-templates.php
and whe is fired, it pass to functions hooking into it an array $sorted_menu_items
that contains all the elements of the menu being printed, if the function alter that array, adding or removing items, the resulting menu will be altered.
The method
If the filter to wp_nav_menu_objects
is applyed direclty, all menus will be filtered, so I thought was better create a function that wrap wp_nav_menu
adding the filter before calling wp_nav_menu
and remove it after: in this way only the wanted menu is filtered.
The filter workflow
- Cycle all items passed by
wp_nav_menu_objects
filter hook - Create 2 helper arrays: one only of parent ids, one with items like
$itemid => $parentid
- While looping items, also check it item url match the current url
- if url not match return only parent items
- if url match, using the helper arrays cretaed, return the wanted elements
The code
The code I wrote for that, make use of 5 functions, so I create a plugin to contains all that, here the code:
<?php
/**
* Plugin Name: Filtered Nav Menus
* Author: Giuseppe Mazzapica
* Plugin URI: http://wordpress.stackexchange.com/questions/118720/
* Author URI: http://wordpress.stackexchange.com/users/35541/
*/
/**
* The API function, just a wrap for wp_nav_menu adding filter first and removing after
*/
function filtered_nav_menu( $args = array() ) {
$echo = isset($args['echo']) ? (bool)$args['echo'] : true;
$args['echo'] = false;
add_filter( 'wp_nav_menu_objects', 'gmfnv_filter', 999 );
$menu = wp_nav_menu( $args );
remove_filter( 'wp_nav_menu_objects', 'gmfnv_filter', 999 );
if ( $echo ) echo $menu;
else return $menu;
}
/**
* The filter callback, return the filtered elements
*/
function gmfnv_filter( $items ) {
$found = false;
$parents = $items_tree = $allowed = array();
$all_items = $items;
while ( ! empty( $items ) ) {
$item = array_shift( $items );
$items_tree[$item->ID] = $item->menu_item_parent;
if ( (int) $item->menu_item_parent == 0 ) $parents[] = $item->ID;
if ( isset($item->current) && $item->current ) $found = $item->ID;
}
if ( ! $found ) {
$ids = $parents;
} else {
$tree = gmfnv_get_tree( $found, $all_items, $items_tree );
$ids = array_merge( $parents, $tree );
}
foreach ( $all_items as $item ) {
if ( in_array( $item->ID, $ids ) ) $allowed[] = $item;
}
return $allowed;
}
/**
* Helper function: take the matched element if and the helper array and
* return the item ancestors by gmfnv_get_parents,
* and the children of these ancestors returned by gmfnv_get_parents
* using gmfnv_get_parents
*/
function gmfnv_get_tree( $test, $items, $tree ) {
$parents = gmfnv_get_parents( $test, $items );
$parents[] = $test;
$n = array();
foreach ( $parents as $parent ) {
$n = array_merge( $n, gmfnv_get_childrens( $parent, $tree ) );
}
return array_unique( $n );
}
/**
* Helper function: return ancestors of an element using the helper array
*/
function gmfnv_get_parents( $test, $items ) {
$parents = array();
foreach( $items as $item ) {
if (
(isset($item->current_item_ancestor) && $item->current_item_ancestor)
|| (isset($item->current_item_ancestor) && $item->current_item_ancestor)
) $parents[] = $item->ID;
}
return $parents;
}
/**
* Helper function: return children of an element using the helper array
*/
function gmfnv_get_childrens( $test, $tree ) {
$children = array();
foreach ( $tree as $child => $parent ) {
if ( $parent == $test ) $children[] = $child;
}
return $children;
}
How To
Create a file containing this plugin put in plugins folder and activate.
When you want filter a menu as required, instead using wp_nav_menu( $args )
use
filtered_nav_menu( $args );
Disclaimer
Code provided as is, no warranty, but just quickly tested on PHP 5.4, WP 3.7 with twentytherteen theme active, and no other plugins: it worked in this case.