Completely custom wordpress menus

One can say a lot of bad things about PHP and Wordpress. But to be honest, I’m mostly grateful to these technologies for introducing me to coding. And grateful to the community around it for making it more or less easy to build stuff without actually knowing any code.

And Wordpress, for all the slack it gets, is still the most established CMS. Which is still why, for one off projects (like the future customer-facing site for rainforest) I’ll take to wordpress.

One thing that I run into a lot is how little control you have over printing your menu, so I wrote a bunch of functions (omg functional php) to gain full control over my menus.

Like many others, I want to render child menu items in a dropdown extending from its parent item, so my html would look something like this:

<nav>
  <ul>
    <li><a href="...">Home</a></li>
    <li><a href="...">About</a></li>
    <li>
      <a href="...">Features</a>
      <ul>
        <li><a href="...">Feature 1</a></li>
        <li><a href="...">Feature 2</a></li>
        <li><a href="...">Feature 3</a></li>
      </ul>
    </li>
  </ul>
</nav>

To render this within Wordpress, first, we need to get all the menu items for a specific menu, we can do that with the gloriously named wordpress method: wp_get_nav_menu_items, like this:

<?php

$menu_items = wp_get_nav_menu_items('my-menu');

This will give us a list of all the menu items in it, including child menu items. That’s not terribly useful, because as you can see above, the structure of the menu is a tree, not a flat list. It’ll be easier if thd datastructure reflects the way it is represented, in a tree. So to enable us to render this menu, we need to take those menu items and build a tree.

And here’s the first time that I write a recursive function in php. This function will take a bunch of items, loop over them and attach any children of this menu item to the ->children attribute.

<?php

function build_tree_menu($items, $tree = null) {
  if(!isset($tree)) {
    $tree = (object) [ 'children' => [] ];
  } else if (!isset($tree->children)) {
    $tree->children = [];
  }
  foreach ($items as $index => $item) {
    if(isset($tree->ID) == false && $item->menu_item_parent == 0 ||
      (isset($tree->ID) && $item->menu_item_parent == $tree->ID)) {
      array_splice($items, $index, 1);
      build_tree_menu($items, $item);
      array_push($tree->children, $item);
    }
  }
  return $tree;
}

When you run a menu item list through this function you end up with a datastructure which looks like this:

menu_item
menu_item
menu_item
  children
    menu_item
    menu_item

Now that we have a tree structure, we can render the menu, recursively. Because I love small functions I have three functions for this, although one could totally have two instead:

One to render the menu

<?php

function print_menu($tree) {
  if(isset($tree->children) && count($tree->children) > 0) {
    return '<ul>' . print_menu_nodes($tree->children) . '</ul>';
  }
}

One to render the menu nodes

<?php

function print_menu_nodes($items) {
  return join(array_map('print_menu_node', $items), '');
}

And one to render an individual menu node, which then calls print_menu again if it has children. As you can see I’m using the do_shortcode method so I can use shortcodes in menu descriptions.

<?php

function print_menu_node($item) {
  return '<li>' . 
    '<a ' .  'href="' .  $item->url .  '">' . 
    do_shortcode($item->title) . 
    '</a>' .  
    print_menu($item) . 
    '</li>';
}

Then I call my functions like this wherever I want to print a custom menu:

 <?php echo print_menu(build_tree_menu(wp_get_nav_menu_items('left-header-menu')));?>

So just to wrap this up, all you need to stick these functions into your functions.php:

<?php

function build_tree_menu($items, $tree = null) {
  if(!isset($tree)) {
    $tree = (object) [ 'children' => [] ];
  } else if (!isset($tree->children)) {
    $tree->children = [];
  }
  foreach ($items as $index => $item) {
    if(isset($tree->ID) == false && $item->menu_item_parent == 0 ||
      (isset($tree->ID) && $item->menu_item_parent == $tree->ID)) {
      array_splice($items, $index, 1);
      build_tree_menu($items, $item);
      array_push($tree->children, $item);
    }
  }
  return $tree;
}

function print_menu($tree) {
  if(isset($tree->children) && count($tree->children) > 0) {
    return '<ul>' . print_menu_nodes($tree->children) . '</ul>';
  }
}

function print_menu_node($item) {
  return '<li>' . 
    '<a ' .  'href="' .  $item->url .  '">' . 
    do_shortcode($item->title) . 
    '</a>' .  
    print_menu($item) . 
    '</li>';
}

function print_menu_nodes($items) {
  return join(array_map('print_menu_node', $items), '');
}

and then print your menu like this:

 <?php echo print_menu(build_tree_menu(wp_get_nav_menu_items('left-header-menu')));?>