2

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
) );
11
  • where do you call wp_nav_menu ? this call must be in the header template where the menu will show.
    – Kaperto
    Commented Dec 1, 2019 at 17:19
  • Well, the wp_nav_menu is called in the parent theme, which is twentytwenty by wordpress. I doubt there's any problem with the menu call. Also, the menu is already displaying just fine.
    – Spcaeyob
    Commented Dec 1, 2019 at 18:36
  • I try your class Walker_Add_Myterms and I don't have your problem with get_terms, the walker add the categories as expected. (problems come after that)
    – Kaperto
    Commented Dec 1, 2019 at 20:27
  • @Kaperto : May I ask which theme you tried my class with? And what problems did you encounter while using it?
    – Spcaeyob
    Commented Dec 14, 2019 at 13:43
  • I try it with twentytwenty with calling wp_nav_menu in header.php. the first problem is that get_permalink is for post, you have to use get_term_link
    – Kaperto
    Commented Dec 14, 2019 at 14:20

1 Answer 1

1

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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.