Extending WordPress custom menus

Extending WordPress custom menus

Created:29 May 2017 13:23:02 , in  Web development

Suppose you have built a custom menu using WordPress GUI for your website. The menu contains a subset of links to posts and pages that your website has to offer. For example, it might not, and in fact it shouldn't, contain a link to a confirmation page, user gets redirected to after sending email from the website. Nonetheless, once user is on that "email sent" page you want an item in the menu that reflects the new location.

This sort of behavior is difficult to achieve in WordPress now unless perhaps another menu is loaded conditionally.

At times, similar problem emerges with custom menus for single posts. Suppose, each such post contains description of one of thousands of products. Building custom menus for all these products and then loading them conditionally is out of question here. An acceptable solution might be an extra menu item containing a common label or post title for the post currently being displayed.

Again, this sort of functionality is not feasible with the graphical custom menus interface WordPress provides now.

sWWWCustomMenuExtension class

For exactly the sort of problems describe above I wrote sWWWCustomMenuExtension class.

It makes adding an extra custom menu top level item for a page or single post simple, quick and configurable.

Technically, the class relies on two filters, wp_nav_menu_objects and wp_nav_menu_items. They enable modifying output of wp_nav_menu WordPress function.

sWWWCustomMenuExtension depends on my ArrayInsert class. The dependency faciliates inserting new items in a chosen place of custom menu. (A detailed article on ArrayInsert is available under this link.

Here is the sWWWCustomMenuExtension class listing:


Class: sWWWCustomMenuExtension
Description: add extra custom menu top level item for a single post or page  
Author: Sylwester Wojnowski
WWW: wojnowski.net.pl

Dependencies: 
  ArrayInsert::itemAt
   
Parameters:
  [$a_text] - String - label for the new item in the menu, if empty, post/page title will be used
  [$current] - Bool - specifies whether the newly inserted item should be highlighted or not. 
  [$page_id] - Integer|Bool - page id to modify custom menu for (for pages only). 
    menu modification will be visible only on that page. 
    Set it to false for single post.
  [$position] - String|Integer - Position to insert new item in the list of custom menu items.
    Accepted values are :
      prepend - new item will be displayed first
      append - new item will be displayed last
      int - items array index to insert new item at (e.g. 0, 5). 

class sWWWCustomMenuExtension{
   
  public static function forSingle($a_text = '',$current = true,$page_id = 0,$position='append'){
    add_filter( 'wp_nav_menu_objects', function($items,$args) use ($a_text,$current,$page_id,$position){

    # current single post / page is being targeted
    if($page_id){
      if(!is_page($page_id)){ return $items; } 
    }else{
      if(!is_single()){ return $items; } 
    }
      
    $post = get_post();

    if(is_null($post)){ return $items; }

    $post -> title = empty($a_text) ? get_the_title() : $a_text; 
    $post -> classes = array (
      'menu-item'
    );
     
    # highlight the extra item
    if($current){
      $post -> classes[] = 'current-menu-item';
      add_filter('wp_nav_menu_items',function($items,$args) use ($a_text){
        $a_text = esc_attr($a_text);
        $items = str_replace('<a>'.$a_text.'</a>','<span>'.$a_text.'</span>',$items);
        return $items;
      },10,2);
    # for cases when extra menu item must stay a link (receives no highlight)    
    }else{
        # for cases when the new menu item has included
        $post -> url = get_the_permalink($page_id);
    }
      
    # re-index $items
    $items = array_values($items);
    #insert
    switch($position){
      case 'prepend':
        $items = ArrayInsert::itemAt($items,0,$post);
        break;   
      case (int)$position > 0  && (int)$position < count($items):
        $items = ArrayInsert::itemAt($items,(int)$position,$post);
        break;
        # by default new menu item is appended   
      default:
        $items[] = $post;       
      }  
      return $items;
    },10,2);
  }
}  

A note on Installation

As mentioned briefly earlier in this article, sWWWCustomMenuExtension uses ArrayInsert internally. This means both classes have to be fully loaded before they can be used. PHP provides spl_autoload_register function for this sort of purposes. Here is a very basic example how the function can be used for loading any PHP class from includes directory in the active WordPress theme.


spl_autoload_register(function($class){
  include get_template_directory().'/includes/'.$class.'.php';
});  

The piece of code should go in functions.php file.

Use examples

Here are some use examples of sWWWCustomMenuExtension

In order to add label "Record" as the last item to custom menu for single post:


sWWWCustomMenuExtension::forSingle('Record');

This can be put in a conditional tag: e.g. is_category(), is_tag() etc. if more fine-grained control of which posts will be affected is required.

To add highlighted label 'Confirmation' as the last item of a custom menu visible only on page with id 12:


sWWWCustomMenuExtension::forSingle('Confirmation',true,12,'append');

To add highlighted label with page title as the last item of a custom menu for page with id 12:


sWWWCustomMenuExtension::forSingle('',true,12,'append');

To insert link with text "Help" as the second top item of custom menu for page with id 23:


sWWWCustomMenuExtension::forSingle('Help',false,23,1);

Final remarks

Similar functionality to described in this article can be achieved using JavaScript, AJAX and PHP. I went for PHP-only solution mostly to avoid loading extra files.

Often WordPress installations, especially those relying on plugins heavily, are overburdened with JavaScript even before secondary pieces of functionality like the one presented here are added.

At the moment sWWWCustomMenuExtension does not allow for inserting items in sub-menus. Hence, the class might not be a complete solution for you. Still it shows how to extend / modify a custom menu. If you need more insight on the subject I would advise taking a closer look at wp_nav_menu WordPress function.

This post was updated on 29 May 2017 15:35:35

Tags:  php ,  wordpress 


Post navigation

Previous:
  Non-destructive array extending in PHP