How to dynamically add custom taxonomy terms as a sub-menu of an existing menu item, using custom walker class
-
18-04-2021 - |
Pregunta
Desired outcome
I have a custom post type and belonging to it, a custom taxonomy. I'm trying to get my child theme dynamically add the terms belonging to that taxonomy as a sub-menu under a certain list item in one of my theme's menus. The taxonomy is hierarchical, and I would like to add all terms of depth = 1, i.e. the first and second-level terms with proper indentation and the same styles as my current menu.
My strategy
So I first thought of using the wp_nav_menu_items filter, but since there can be many terms and they are hierarchical, I instead thought it's best to go with a custom walker to extend the Walker_Nav_Menu class.
The issue
The issue I've encountered is that when I try to call the get_terms() function inside the custom walker, it returns "Invalid taxonomy". I know for a fact that the taxonomy is registered and that there are terms in the database. It seems like the get_terms() is getting called before init when it's invoked in the walker class. Since the taxonomy gets registered on init, the taxonomy is invalid when the walker class is calling get_terms();
What I've tried
So I tried to lift out the get_terms() function from the class and do the following:
function get_mytax_terms() {
$myterms= get_terms( array(
'taxonomy' => 'mytax',
'hide_empty' => true
) );
return $myterms;
}
add_action( 'init' , get_mytax_terms() , 10 );
I even tried making the variable global, but it doesn't return any terms, and throws an error for the taxonomy being invalid. A var_dump still gives me "Invalid taxonomy" error. I read on, and saw in a comment at the codex (on the Walker_Nav_menu class page) that I need to use static functions, and did this inside the custom walker class:
/**
* Get all terms of tax mytax
*/
public static function init() {
add_action( 'init', array( get_called_class(), 'get_mytax_terms' ), 10 );
}
public static function get_mytax_terms() {
$myterms = get_terms( array(
'taxonomy' => 'mytax',
'hide_empty' => true
) );
return $myterms ;
var_dump($myterms );
}
That var_dump runs but still returns the "Invalid taxonomy" error.
Edit
I tried changing my custom taxonomy to the built-in taxonomy 'category' and then my function works. Using 'mytax' it still gives me invalid taxonomy.
The custom taxonomy is registered with
add_action( 'init', 'custom_register_taxonomies');
I am now fairly certain that this issue is due to the wp_nav_menu
call coming earlier than the registration of my custom taxonomy.
The questions
Is it safe to register the custom taxonomies earlier than on init
?
Am I right in using static functions in the custom walker class, as described above?
Would it improve anything to delay the wp_nav_menu
call, by calling it from a custom location in my child theme. Rather than letting the parent theme call it, and adapting my walker class and/or taxonomy registration function?
Am I on the right track here or am I missing something else?
Current situation
I'm including my custom walker below. Any tips are greatly appreciated.
/* ---- Add mytax taxonomy terms to menu ---- */
class Walker_Add_Myterms extends Walker_Nav_Menu {
// Insert a submenu
function end_el( &$output, $item, $depth=1, $args=array() ) {
// if the current menu item being output is parentmenuitem
if( 'parentmenuitem' == $item->title ){
// get all terms
$myterms = get_terms( array(
'taxonomy' => 'mytax',
'hide_empty' => true,
) );
if ( ! empty( $myterms ) && ! is_wp_error( $myterms ) ) {
// start a new list
$output .= '<ul>';
// iterate over each type and add an li
foreach( $myterms $myterm){
$term_url = get_term_link( $myterm->term_id , 'mytax');
$name = $myterm->name;
$format = '<li><a href="%s">%s</a></li>';
$output .= sprintf( $format, $term_url, $name );
}
// close the list
$output .= '</ul>';
}
}
// close the parent li
$output .= "</li>\n";
}
}
wp_nav_menu( array(
'theme_location' => 'expanded',
'container' => 'ul',
'menu_class' => 'expanded-menu sub-menu active',
'walker' => new Walker_Add_Myterms
) );
Solución
Okay, I figured it out. It's not very smooth, but at least it works as I want it to. Will have to do some refactoring.
The issue was that wp_nav_menu() got called earlier than the taxonomy registration. I had accidentally placed it in my theme-functions.php and forgotten about it, then called it again in my header.php . That's why it seemed to work, but I still got the invalid taxonomy error and a broken menu.
Here's the final walker class:
class Walker_Add_Myterms extends Walker_Nav_Menu {
public function end_el( &$output, $item, $depth=1, $args=array() ) {
// if the current menu item being output has class "uthyrning"
if( in_array( 'parentliitemclass' , $item->classes ) ){
// get all terms
$myterms = get_terms( array(
'taxonomy' => 'mytax',
'hide_empty' => true,
'parent' => 0,
) );
if ( ! empty( $myterms ) && ! is_wp_error( $myterms ) ) {
// Insert a submenu
$output .= '<ul class="sub-menu">';
// iterate over each type and add an li
foreach( $myterms as $myterm){
$term_url = get_term_link( $myterm->term_id , 'mytax' );
$name = $myterm->name;
$li_id = 'id="term-item-' . $myterm->term_id . '"';
$li_classes = 'menu-item menu-item-type-taxonomy menu-item-object-mytax term-item-' . $myterm->term_id;
$wrap_class = 'class="ancestor-wrapper"';
$format = '<li %s class="%s"><div %s><a href="%s">%s</a></div></li>';
// Get child terms ,and if they exist, create sub-menu with term children
$children = get_term_children( $myterm->term_id, 'mytax' );
if( isset($children) && ! is_wp_error( $children ) && sizeof($children) > 0) {
$li_classes .= ' menu-item-has-children';
$child_items = '';
foreach ( $children as $child ) {
$child_term = get_term( $child, 'mytax' );
$child_item = '<li id="term-item-' . $child_term->term_id . '" class="menu-item menu-item-type-taxonomy menu-item-object-mytax term-item-' . $child_term->term_id . '"><div class="ancestor-wrapper">';
$child_item .= '<a href="' . get_term_link( $child_term, 'mytax' ) . '">' . $child_term->name . '</a>';
$child_item .= '</div></li>';
$child_items .= $child_item;
}
$format = '<li %s class="%s"><div %s><a href="%s">%s</a>';
$format .= '<button class="toggle sub-menu-toggle fill-children-current-color" data-toggle-target=".menu-modal .term-item-' . $myterm->term_id . ' > .sub-menu" data-toggle-type="slidetoggle" data-toggle-duration="250" aria-expanded="false"><span class="screen-reader-text">Visa undermeny</span><svg class="svg-icon" aria-hidden="true" role="img" focusable="false" xmlns="http://www.w3.org/2000/svg" width="20" height="12" viewBox="0 0 20 12"><polygon fill="" fill-rule="evenodd" points="1319.899 365.778 1327.678 358 1329.799 360.121 1319.899 370.021 1310 360.121 1312.121 358" transform="translate(-1310 -358)"></polygon></svg></button>';
$format .= '</div><ul class="sub-menu">';
$format .= $child_items . '</ul></li>';
}
// END Child terms
$output .= sprintf( $format, $li_id, $li_classes, $wrap_class, $term_url, $name );
}
// close the list
$output .= '</ul>';
}
}
// close the parent li
$output .= "</li>\n";
}
}
And I also ended up copying the template-part where the wp_nav_menu() is called, from the parent theme, and replacing the default walker_nav_menu class with my own extended class above.