Sillyness Spelled Wrong Intentionally

Technology

Stupid WP Tricks

Today we talk about menus, and how to make them more flexible.

Penned 2 months ago.
It was 71°F in San Diego, CA and hazy.
Broken Halos by Chris Stapleton  was spinning on the ol' iTunes.

So I have found myself using WordPress a lot more lately, both previously at Voxox, and now at Filmio. As I've said before WP is great for a lot of things, and terrible for a lot of others.

One of the great things you can do with WordPress, is also one of the most frustrating ones. I am of course talking about custom menus.

On one hand they are great. They allow non-code type people to create menus, add/remove links or pages, etc. Really that's great.

The issue is when you are dealing with a design, in the real world, that doesn't allow menus to fit into the same box. You need more flexibility than the built in system provides.

There are some plugins, but I find most of them overdone, underdone, or just plain scary when you look under the hood. I wanted something straightforward, sanely written and powerful.

Not being able to find one pre-rolled, I wrote my own, and as is my habit, I'm going to share it in hopes that it helps someone else out there.

Let's go over how it works!

The secret sauce to this whole thing is that we are adding a templating engine or sorts to WordPress specifically for menus.

When we reference this on the front end, it will expect the menu we are going to show, the template to use to format each item, and the location of the menu.

Okay, so now let's get into the gory details. First let's talk about the MSRTemplate Class.

It's pretty straightforward, promise.


/**
 * MSR Functions Class
 * Set up the theme and provides some helper functions.
 * @package WordPress
 * @subpackage MSR
 * @since MSR 1.0
 */

class MSRTemplate
{
  protected $_pattern;
  protected $_tags = array();
  protected $_values = array();

  public function __construct($pattern = null) {
    $this->_pattern = $pattern;
  }

  public function set($tags, $values) {
    $this->_tags = $tags;
    $this->_values = $values;
  }

  public function render() {
    $template = str_replace( $this->_tags, $this->_values, $this->_pattern );
    return $template;
  }
}

So we have a simple class here that will handle the formatting of our menus. It has 3 class members that are protected (class members declared as protected can be accessed only within the class itself or by inheriting classes), they are:

  • $_pattern — The pattern to produce with the tags and values provided.
  • $_tags — The template tags to look for when we are doing our replace.
  • $_values — The values to replace the tags with.

Next we have our constructor. You include a constructor in your class to allow you to pass in things like parameters when you are creating an object. In our case we are passing in the pattern.

Our next method is set. This does what it says on the tin. It allows you to tell our object what tags and values we are playing with and set the protected arrays with those items. Render then takes those protected arrays and uses them to process the template.

If you are curious about the use of set here, or getters and setters in general, this is a good, frustrating place to start.

So, render.

Again, this method is as straightforward as they come. We grab the pattern, tags and values and then use good old str_replace to do some replacing magic. We then return that newly replaced and value-ified string for further processing. Easy peasy.

Okay, so now we have a class that can take a bunch of crap and process it to give us some shiny HTML. Now what?

Now we add our theme class, that's what. A line or two below the template class, we throw the following in.


class MYSITEROCKS 
{
  /**
  * Formating function.
  *
  * @param string string that corresponds to the item to format.
  * @param object the WordPress object to parse and format.
  * @param int an integer to use for counting, etc. Can be null.
  * @param array the markup to use for our tag replacement.
  * @return rendered and processed HTML
  */
  private static function format( $type, $object, $pattern ) {
    $template = new MSRTemplate( $pattern );
    
    switch( $type ) {
      case 'headernav':
        $tags = array( '{{url}}', '{{title}}' );
        $values = array( $object->url, $object->title );
        $template->set( $tags, $values );
      break;
    }

    return $template->render();    
  }
}

I tend to be lazy. And I am way more comfortable monkeying around with code than I probably should be, so you can see I am cheating here and using a switch to power my format method.

The right way to do this would be write a much more complicated set of code that allows you to programmatically deal with whatever 'type' you pass in. I find it easier to just add another entry in the switch statement, so that is what I did. YMMV.

Okay, so next we need to create a method to handle our custom menus. It needs to be aware of the menu infrastructure in WordPress, but also support our templating class. It looks something like this:


/**
  * The built in function for showing menus is some what limited.
  * this method allows you to apply arbitrary markup to the core
  * menu system.
  *
  * @param string the slug of the menu in question
  * @param string the theme location this menu is bound to
  * @param string the pattern to use for matching
  * @return string returns a concatenated string suitable for displaying 
  * to the user.
  */
  public static function custom_menus($menu_name, $menu_location, $pattern) {
    $locations = get_nav_menu_locations();
    $menu_id = get_term_by( 'slug', $menu_name, 'nav_menu' );

    if( $locations && isset($locations[$menu_location]) ) {
      $menu = wp_get_nav_menu_object( $locations[$menu_location] );
      $menu_items = wp_get_nav_menu_items( $menu->term_id );

      $menu_list = '<ul>';
            
      foreach( (array)$menu_items as $key => $menu_item ) {
        if( $menu_item->menu_item_parent == 0 ) {
          $menu_list .= '<li>';
          $menu_list .= self::format( $menu_name, $menu_item, $pattern );
          $menu_list .= '</li>';
        }
      }
      
      $menu_list .= '</ul>';
    } else {
      $menu_list = '<!-- Danger Wil Robinson! Danger! -->';
    }
        
    echo $menu_list;
  }

So in the above code we are grabbing all the menus that have been created via the WordPress admin, and looking for one that matches the menu we passed in as $menu_name. If we find that menu, we then grab the items associated with it, pages, posts, offsite links, etc and iterate over them to apply our formatting that was also passed in via this method.

It might look a bit complicated, but it really isn't. So now that we have the full code cycle, we just need to call this on the front end. You would do that like this:


<?php MYSITEROCKS::custom_menus('header-nav', 'header', '<a href="{{url}}">{{title}}</a>'); ?>

Seriously, that's it. Not a lot of code, but a lot of power when it comes to pushing your designs to their limits. Now this isn't fully baked code, there are things that are left to be done, like handling menus with children (that's why there is a check for parent == 0 in that custom_menus method), but this should get you going.

Oh and here is the entire functions file for your perusal.


/**
 * MSR Functions Class
 * Set up the theme and provides some helper functions.
 * @package WordPress
 * @subpackage MSR
 * @since MSR 1.0
 */

class MSRTemplate
{
  protected $_pattern;
  protected $_tags = array();
  protected $_values = array();

  public function __construct($pattern = null) {
    $this->_pattern = $pattern;
  }

  public function set($tags, $values) {
    $this->_tags = $tags;
    $this->_values = $values;
  }

  public function render() {
    $template = str_replace( $this->_tags, $this->_values, $this->_pattern );
    
    return $template;
  }
}

class MYSITEROCKS 
{
  /**
  * Formating function.
  *
  * @param string string that corresponds to the item to format.
  * @param object the WordPress object to parse and format.
  * @param int an integer to use for counting, etc. Can be null.
  * @param array the markup to use for our tag replacement.
  * @return rendered and processed HTML
  */
  private static function format( $type, $object, $pattern ) {
    $template = new MSRTemplate( $pattern );
    
    switch( $type ) {
      case 'headernav':
        $tags = array( '{{count}}', '{{url}}', '{{title}}' );
        $values = array( $count, $object->url, $object->title );
        $template->set( $tags, $values );
      break;
    }

    return $template->render();    
  }
  
  /**
  * The built in function for showing menus is some what limited.
  * this method allows you to apply arbitrary markup to the core
  * menu system.
  *
  * @param string the slug of the menu in question
  * @param string the theme location this menu is bound to
  * @param string the pattern to use for matching
  * @return string returns a concatenated string suitable for displaying 
  * to the user.
  */
  public static function custom_menus($menu_name, $menu_location, $pattern) {
    $locations = get_nav_menu_locations();
    $menu_id = get_term_by( 'slug', $menu_name, 'nav_menu' );

    if( $locations && isset($locations[$menu_location]) ) {
      $menu = wp_get_nav_menu_object( $locations[$menu_location] );
      $menu_items = wp_get_nav_menu_items( $menu->term_id );

      $menu_list = '<ul>';
            
      foreach( (array)$menu_items as $key => $menu_item ) {
        if( $menu_item->menu_item_parent == 0 ) {          
          $menu_list .= '<li>';
          $menu_list .= self::format( $menu_name, $menu_item, $pattern );
          $menu_list .= '</li>';
        }
      }
      
      $menu_list .= '</ul>';
    } else {
      $menu_list = '<!-- Danger Wil Robinson! Danger! -->';
    }
        
    echo $menu_list;
  }
}

If there is interest I can update this to include support for menus with children, as well as a non-lazy method that removes the reliance on the switch statement.

Let me know if you found this helpful, and if there are any other topics you would like to see hashed out when it comes to WP or programming, etc.

I hope you enjoyed reading Stupid WP Tricks
By becoming a Patron of Sillyness you can ensure quality writing, film, photography and music like this continue to be made available.

Say Hello to Filmio

Hey there friend! I have launched Filmio, a new type of entertainment company that brings together fans and creators in a way never before possible! If you want access to great content, click here to jump over and join our team. Together we can change the way great movies and tv shows are made. Thanks!

— Chris J.