Quick Tip: How to add a Skype URI Link to your WordPress Menu

Overview

Skype URIs provide the ability to insert links into your website which will launch the Skype app when clicked.

You might think that adding a Skype link to your WordPress site’s menu would be a nice idea, but when you go to save your custom URL, you’ll notice that the URL is stripped by the WordPress menu system and you’re left with an anchor tag with no href attribute.

This happens because WordPress rejects URLs whose protocols aren’t whitelisted. By default, the following protocols are whitelisted and can therefore be added to custom URLS in your menu:

  • http
  • https
  • ftp
  • ftps
  • mailto
  • news
  • irc
  • gopher
  • nntp
  • feed
  • telnet

The solution is therefore to add the skype protocol to the whitelist.

Tracking the issue

There isn’t much documentation for custom menu item (Link) URLs, so when you try to save it silently fails and there is no indication as to why the URL won’t save properly.

By digging through the core code, I found that the menu item settings are validated and saved in the wp_update_nav_menu_item function located in wp-includes/nav-menu.php. This function calls esc_url_raw() on the submitted menu item URL prior to saving the updated value.

The esc_url_raw function can take a list of protocols as a parameter, however at this point there is no hook with which we could add our custom protocol without editing the core file – therefore we continue to dig for a customization point. The esc_url_raw function calls the esc_url function in wp-includes/formatting.php, which checks that the protocol of the URL is present in the array of whitelisted protocols returned by wp_allowed_protocols.

Thankfully, the wp_allowed_protocols function, defined in wp-includes/functions.php, provides a filter kses_allowed_protocols, which is the key to adding our Skype protocol to the whitelist.

The solution

Once we track down the appropriate filter, the solution is actually quite simple. We filter the $protocol array and add the Skype protocol:

function ss_allow_skype_protocol( $protocols ){
	$protocols[] = 'skype';
	return $protocols;
}
add_filter( 'kses_allowed_protocols' , 'ss_allow_skype_protocol' );

This code would be well placed in a child theme’s functions.php

After adding this code to your installation, you should be able to save Skype URIs like skype:username?call by creating a new Custom Menu Item (Link) in your menu and adding the Skype URI as your custom URL:

The same method should work with any other custom protocols.

UberMenu 2.4 Update Announcement

UberMenu 2.4 is here! It offers a few new features and tweaks.

Common Questions

Is it backwards compatible?

Yes, it’s backwards compatible. Installing this update won’t change any of your settings or require you to redo everything.

How do I update?

In short, back up any custom files and overwrite the old files on your server with the new ones in the download package. There are no special update instructions for this version.

For a complete walkthrough, see Knowledgebase: Installing Updates

Where do I get the latest version?

The latest version of UberMenu is always available through your CodeCanyon Downloads Page.

New Features

Here are some new features in UberMenu 2.4

Improved Android and Windows 8 Touch handling

UberMenu 2.4 takes a big step toward unifying the touch interface for all devices. Android and Windows 8 touch devices now work more like iOS devices – the first tap on a menu item opens its submenu, and the second will follow the link.

New Media Uploader

UberMenu 2.4 integrates the latest WordPress media uploader so that selecting images is even easier than before. Drag and Drop your images to upload, and just click “Select” to set your image.

HTML now allowed in Descriptions

You can now place HTML markup right in your descriptions box, increasing the flexibility of your menu styles.

Search the Knowledgebase from the Control Panel

You can now search the Knowledgebase straight from the Control Panel, making it even easier to find the article relevant to your question without leaving your admin screen.

WordPress 3.8 Compatibility

UberMenu 2.3 already ran properly on WordPress 3.8, but UberMenu 2.4 adds a few extra tweaks to make it even more seamless.


Various other tweaks and minor browser bug fixes also come with this release, so I recommend upgrading to take advantage of this new version.

Enjoy UberMenu 2.4!

Displaying Categories Dynamically with UberMenu: Writing an auto-population shortcode

Please note this functionality is now built in to UberMenu 3, with the new Dynamic Content feature.

One thing the WordPress 3 Menu System lacks is the ability to dynamically insert/auto-populate content such as Categories. However, with UberMenu – Responsive WordPress Mega Menu Plugin you can use the menu’s Content Override / Shortcode capability to insert any content you want to into the menu.

One use case is the need to place several columns of categories, stacked top to bottom and then left to right. We could do this with the standard WordPress Menu System and UberMenu by creating third level menu items for each category. The main issue with this is ease of maintenance, which can become tedious when dealing with large numbers of menu items that may change frequently.

Our goal is to:

  1. create a shortcode that will dynamically generate several columns of categories
  2. automatically optimize the number of categories per column to keep them as level as possible
  3. make the shortcode flexible to be able to return specific terms from any taxonomy

The strategy will be to create a wrapper shortcode for the WordPress core get_terms() function, and then output the results in several lists, which we will turn into a column layout with some simple CSS.

Download the PHP & CSS

Part 1: The Shortcode

Shortcode Skeleton

First, we’re going to define a shortcode, menu-terms, in our child theme’s functions.php, along with its accompanying function.

function ss_menu_terms( $atts ){

}
add_shortcode( 'menu-terms' , 'ss_menu_terms' );

The rest of the PHP will go inside the ss_menu_terms function, which will take the arguments passed to the shortcode and return the requested terms in the proper number of columns.

Define Arguments

Next we’re going to define the arguments for the shortcode, along with intelligent defaults, to minimize the amount of arguments that need to be passed to the shortcode. We’re going to define a few output-specific arguments, plus the arguments taken by the get_terms function, to make our shortcode as flexible as possible.

After that we’ll also convert our arguments into proper data types: strings into booleans and arrays.

	extract( shortcode_atts( array(
		'tax' 			=> 'category',
		'display_count'	=> 'false',
		'columns'		=> 5,

		'orderby'		=> 'name', 
		'order'			=> 'ASC',
		'hide_empty'	=> 'false', 
		'exclude'		=> array(), 
		'exclude_tree'	=> array(), 
		'include'		=> array(),
		'number'		=> '', 
		//'fields'		=> 'all', 
		'slug'			=> '', 
		'parent'		=> '',
		'hierarchical'	=> 'true', 
		'child_of'		=> 0, 
		'get'			=> '', 
		'name__like'	=> '',
		'pad_counts'	=> 'false', 
		'offset'		=> '', 
		'search'		=> '', 
		'cache_domain'	=> 'core'
	), $atts ) );

	$display_count = $display_count == 'true' ? true : false;
	$hide_empty = $hide_empty == 'true' ? true : false;
	$hierarchical = $hierarchical == 'false' ? false : true;
	$pad_counts = $pad_counts == 'true' ? true : false;

	if( strpos( $tax , ',' ) ) $tax = explode( ',' , $tax );
	if( !is_array( $exclude ) ) $exclude = explode( ',' , $exclude );
	if( !is_array( $exclude_tree ) ) $exclude_tree = explode( ',' , $exclude_tree );
	if( !is_array( $include ) ) $include = explode( ',' , $include );

The tax argument will define our target taxonomy and allow the plugin to work categories, tags, or any custom taxonomy

The display_count argument will allow us to display a count of the number of posts associated with a term, if we choose.

The columns argument will define the number of columns to evenly divide the terms between.

The rest of the arguments are specific to the get_terms function, and are all defined here. Since we can’t pass arrays or booleans via a shortcode, we’ll use comma-delimited strings and boolean text strings instead.

Get the Terms

Now that we have all of our arguments, we’ll compress them into an array and pass them to the get_terms function to get our list of $term objects.

	$term_args = compact( 
			'orderby' , 'order' , 'hide_empty' , 'exclude' , 
			'exclude_tree' , 'include' , 'number' , 'slug' , 
			'parent' , 'hierarchical' , 'child_of' , 'get' ,
			'name__like' ,  'pad_counts' , 'offset' , 
			'search' , 'cache_domain' );

	$terms = get_terms( $tax , $term_args );

Calculate Columns

Next, we figure out how many columns we need by dividing the number of total terms by the number of desired columns and rounding up.

$n = count( $terms );
$per_column = ceil( $n / $columns );

Now that we know how many terms per column we need, we just start a new list every $per_column items.

Output the terms

Finally, we loop through the terms, creating out lists and list items, and wrapping the term names in links to the term archive pages. We create an output string and return that, because shortcodes must always return their values and not print them directly.

$html = '<div class="menu-terms menu-terms-col-'.$columns.'">';

$html.= '<ul class="menu-terms-column">';

foreach( $terms as $i => $term ){
	$html.= '<li class="term-'.$term->term_id.' term-slug-'.$term->slug.' term-taxonomy-'.$term->taxonomy.'">';
	$html.= '<a href="'.get_term_link($term).'">';
	$html.= $term->name;
	if( $display_count ) $html.= ' ('.$term->count. ')';
	$html.= '</a></li>';

	if( ($i+1) % $per_column == 0 ){
		$html.= '</ul><ul class="menu-terms-column">';
	}
}

$html.= '</ul></div>';

return $html;

Our final shortcode definition looks like this:

function ss_menu_terms( $atts ){

	extract( shortcode_atts( array(
		'tax' 			=> 'category',
		'display_count'	=> 'false',
		'columns'		=> 5,

		'orderby'		=> 'name', 
		'order'			=> 'ASC',
		'hide_empty'	=> 'false', 
		'exclude'		=> array(), 
		'exclude_tree'	=> array(), 
		'include'		=> array(),
		'number'		=> '', 
		//'fields'		=> 'all', 
		'slug'			=> '', 
		'parent'		=> '',
		'hierarchical'	=> 'true', 
		'child_of'		=> 0, 
		'get'			=> '', 
		'name__like'	=> '',
		'pad_counts'	=> 'false', 
		'offset'		=> '', 
		'search'		=> '', 
		'cache_domain'	=> 'core'
	), $atts ) );

	$display_count = $display_count == 'true' ? true : false;
	$hide_empty = $hide_empty == 'true' ? true : false;
	$hierarchical = $hierarchical == 'false' ? false : true;
	$pad_counts = $pad_counts == 'true' ? true : false;

	if( strpos( $tax , ',' ) ) $tax = explode( ',' , $tax );
	if( !is_array( $exclude ) ) $exclude = explode( ',' , $exclude );
	if( !is_array( $exclude_tree ) ) $exclude_tree = explode( ',' , $exclude_tree );
	if( !is_array( $include ) ) $include = explode( ',' , $include );

	$term_args = compact( 
			'orderby' , 'order' , 'hide_empty' , 'exclude' , 
			'exclude_tree' , 'include' , 'number' , 'slug' , 
			'parent' , 'hierarchical' , 'child_of' , 'get' ,
			'name__like' ,  'pad_counts' , 'offset' , 
			'search' , 'cache_domain' );

	$terms = get_terms( $tax , $term_args );

	$n = count( $terms );
	$per_column = ceil( $n / $columns );

	$html = '<div class="menu-terms menu-terms-col-'.$columns.'">';

	$html.= '<ul class="menu-terms-column">';

	foreach( $terms as $i => $term ){
		$html.= '<li class="term-'.$term->term_id.' term-slug-'.$term->slug.' term-taxonomy-'.$term->taxonomy.'">';
		$html.= '<a href="'.get_term_link($term).'">';
		$html.= $term->name;
		if( $display_count ) $html.= ' ('.$term->count. ')';
		$html.= '</a></li>';

		if( ($i+1) % $per_column == 0 ){
			$html.= '</ul><ul class="menu-terms-column">';
		}
	}

	$html.= '</ul></div>';

	return $html;
}
add_shortcode( 'menu-terms' , 'ss_menu_terms' );

Part 2: The CSS

Now we have our markup – by default, 5 lists with the terms evenly distributed among them. But right now they’re just unstyled lists. Adding CSS will turn them into the columns we want.

First, we’re going to define a class, menu-terms-item which we will add to our menu item to ensure it takes up the whole with of the submenu; otherwise our percentage-based columns won’t spread out properly.

#megaMenu ul.megaMenu .menu-terms-item{
	width:100%;
	-webkit-box-sizing:border-box;
	-mox-box-sizing:border-box;
	-o-box-sizing:border-box;
	box-sizing:border-box;
}

Next, we’ll define the layout of the columns themselves, as well as a simple grid to define the percentage widths of the columns from 1 to 8.

#megaMenu ul.megaMenu .menu-terms{
	margin-top:10px;
}
#megaMenu ul.megaMenu .menu-terms a{
	text-decoration: none;
}
#megaMenu ul.megaMenu .menu-terms ul.menu-terms-column{
	-webkit-box-sizing:border-box;
	-mox-box-sizing:border-box;
	-o-box-sizing:border-box;
	box-sizing:border-box;
	float:left;
	margin:0;
	padding-right:20px;
	margin-bottom:20px;
	list-style:none;
}
#megaMenu ul.megaMenu .menu-terms.menu-terms-col-1 ul.menu-terms-column{ width: 100%; }
#megaMenu ul.megaMenu .menu-terms.menu-terms-col-2 ul.menu-terms-column{ width: 50%; }
#megaMenu ul.megaMenu .menu-terms.menu-terms-col-3 ul.menu-terms-column{ width: 33%; }
#megaMenu ul.megaMenu .menu-terms.menu-terms-col-4 ul.menu-terms-column{ width: 25%; }
#megaMenu ul.megaMenu .menu-terms.menu-terms-col-5 ul.menu-terms-column{ width: 20%; }
#megaMenu ul.megaMenu .menu-terms.menu-terms-col-6 ul.menu-terms-column{ width: 16%; }
#megaMenu ul.megaMenu .menu-terms.menu-terms-col-7 ul.menu-terms-column{ width: 14%; }
#megaMenu ul.megaMenu .menu-terms.menu-terms-col-8 ul.menu-terms-column{ width: 12%; }

Finally, below 768 pixels we will collapse the terms into two columns, and below 480px we will collapse them into a single column:

@media only screen and (max-width:767px) {
	#megaMenu ul.megaMenu .menu-terms-item{ width:100% !important; }
	#megaMenu ul.megaMenu .menu-terms.menu-terms-col-1 ul.menu-terms-column,
	#megaMenu ul.megaMenu .menu-terms.menu-terms-col-2 ul.menu-terms-column,
	#megaMenu ul.megaMenu .menu-terms.menu-terms-col-3 ul.menu-terms-column,
	#megaMenu ul.megaMenu .menu-terms.menu-terms-col-4 ul.menu-terms-column,
	#megaMenu ul.megaMenu .menu-terms.menu-terms-col-5 ul.menu-terms-column,
	#megaMenu ul.megaMenu .menu-terms.menu-terms-col-6 ul.menu-terms-column,
	#megaMenu ul.megaMenu .menu-terms.menu-terms-col-7 ul.menu-terms-column,
	#megaMenu ul.megaMenu .menu-terms.menu-terms-col-8 ul.menu-terms-column{ width: 50%; }
}
@media only screen and (max-width:479px) {
	#megaMenu ul.megaMenu .menu-terms.menu-terms-col-1 ul.menu-terms-column,
	#megaMenu ul.megaMenu .menu-terms.menu-terms-col-2 ul.menu-terms-column,
	#megaMenu ul.megaMenu .menu-terms.menu-terms-col-3 ul.menu-terms-column,
	#megaMenu ul.megaMenu .menu-terms.menu-terms-col-4 ul.menu-terms-column,
	#megaMenu ul.megaMenu .menu-terms.menu-terms-col-5 ul.menu-terms-column,
	#megaMenu ul.megaMenu .menu-terms.menu-terms-col-6 ul.menu-terms-column,
	#megaMenu ul.megaMenu .menu-terms.menu-terms-col-7 ul.menu-terms-column,
	#megaMenu ul.megaMenu .menu-terms.menu-terms-col-8 ul.menu-terms-column{ width: 100%; }
}

See also Adding Custom CSS

Part 3: Adding the Shortcode to the menu

Now that we have all our code in place, we’ll add it to our menu. The intention is to auto-populate a full row of leveled columns with a single menu item / shortcode.

First, make sure you have Content Overrides Enabled

Next, create a new second level menu item. I recommend using a “Link” menu item and setting the URL to # and checking the “Disable Link” option. However, you can use any type you like.

If you don’t want to display the link’s Navigation Title above the columns of categories, check the “Disable Text” option.

Next, add the menu-terms-item class to your menu item

Finally, insert the shortcode itself into the Custom Content box.

[menu-terms]

Here’s the result:

The categories are sorted alphabetically by default.

Part 4: Using the shortcode arguments

We can refine the terms returned and their layout by passing arguments to the shortcode. Here are some examples:

Columns

By default, 5 columns to be displayed. To display 6, we would use

[menu-terms columns="6"]

Sorting

By default, the terms will be ordered alphabetically. To order them by ID, you would use

[menu-terms orderby="id"]

Hierarchies

With no arguments passed, the shortcode will return all terms in the taxonomy (by default, this means all Categories). If we want to restrict the set to children of a specific menu item, we can do this:

[menu-terms child_of="5"]

Where 5 is the ID of the parent item.

Different taxonomies

If you have a custom taxonomy you want to display, you can pass the tax argument.

[menu-terms tax="product-categories"]

For example, maybe you have a product categories taxonomy for an e-commerce site, and you have subcategories under “Men’s”, “Women’s”, etc. If your “Men’s” term ID is 10 and your “Women’s” term ID is 20, you could display their subcategories in two different submenus using these two different instances of the shortcode:

[menu-terms tax="product-categories" child_of="10"]
[menu-terms tax="product-categories" child_of="20"]

The first would display only Men’s categories, and the second only Women’s

Exclude items

If you want to exclude specific items, you can do so by ID. List multiple IDs in a comma-separated string

[menu-terms exclude="15,20,76"]

The full set of get_terms parameters and what they do can be found in the WordPress Codex.

Full Code

Download the PHP & CSS

Quick Tip: Displaying the current user’s username in UberMenu

UberMenu allows you to place any shortcode content inside your menu, so adding custom content becomes as simple as writing a shortcode to return that content.

In this example, we’ll grab the current user’s username and display it in the menu.

Write the shortcode

First we want to create a simple shortcode which we will call like this in the menu: [current-username]. We’ll use WordPress’s core wp_get_current_user() function to retrieve the current user object and then return the user’s display name.

add_shortcode( 'current-username' , 'ss_get_current_username' );
function ss_get_current_username(){
	$user = wp_get_current_user();
	return $user->display_name;
}

This PHP can be added to the theme’s functions.php, preferably in a child theme to preserve changes more easily.

For a more detailed explanation of writing custom shortcodes, see How to write a custom shortcode for UberMenu.

Add the shortcode to the menu

You can create the username menu item with whichever type of menu item you prefer. In this example, we’ll create a new custom menu item ( “Link” ) to hold our username menu item. Set the URL to # and the link text to “Username” (temporarily) and add it to the menu.

Next, replace the Navigation Label with our new shortcode, [current-username] and save the menu

Finally, tell the menu to allow shortcodes in Navigation Labels via the UberMenu Control Panel

UberMenu 3

Main UberMenu Configuration > Miscellaneous > Allow Shortcodes in Navigation Label & Description

UberMenu 2

Descriptions, Shortcodes, Widgets > Advanced Content Capabilities > Allow Shortcodes in Navigation Label & Description

Now your menu will display the current user’s username inside the menu

Next Steps

The above strategy will display the username of the currently logged in user. When the user is logged out, the menu item will be blank. You could adjust the shortcode to return something else when logged out. Alternatively, you could use the UberMenu Conditionals Extension to hide the menu item entirely when the user is logged out, and replace it with a different item, perhaps a login link. To do so, you would create two menu items: one for the logged in user (displaying the username), and one for the logged out user (displaying a login link). You would apply the “If user is logged in” condition to the first, and “If user is not logged in” condition to the second.

Learn more about the UberMenu Conditionals Extension

Speeding up the Appearance > Menus Screen in WordPress 3.6 / 3.7 / 3.8 / 3.9 / 4.0 / 4.1

Quick Synopsis

WordPress 3.6 introduced new accessibility functionality in the Appearance > Menus admin screen. This extra javascript processing creates inefficiencies when handling menus with large numbers of menu items, resulting in very slow and sometimes unresponsive interactions when managing menus in WordPress. By deferring this processing using “lazy” techniques, we can eliminate the bottleneck and have the menu management system run much faster for large numbers of menu items. I have provided a plugin which does just that.

Skip to Video Skip to plugin

Update April 2015

The patch submitted has finally been incorporated into the core in WordPress 4.2! See: core trac ticket. The Faster Appearance – Menus plugin should no longer be necessary with this update.

Update October 25, 2013

I have submitted this plugin/solution as a core trac ticket, and the patch is currently awaiting review to be included in a future version of WordPress core. If you’d like to test, head over to the track ticket and report your feedback 🙂

Background

As the author of several WordPress menu plugins which handle large numbers of menu items, I’ve recently been fielding a lot of support requests from concerned customers who have experienced a dramatic slow-down in their Appearance > Menus admin screen since they have upgraded to WordPress 3.6. For one user who was using about 300 menu items (note: definitely not recommended in any event), the Appearance > Menus page completely freezes and crashes. As I was worried that my code might be causing the issue, I began to investigate.

Stripping my installation down to just TwentyTwelve running on WordPress 3.6, without any plugins, I was surprise to find that the Appearance > Menus page “freezes” for about 5 seconds upon loading with a menu of about 100 menu items. That means no scrolling, no clicking – the average user might even think that the browser has crashed, as the only indication that anything is going on is that the progress spinner continues to spin indicating that the page has not completely loaded.

Testing with a much smaller menu, say 5 menu items, the page loads immediately.

When profiling the javascript, it became apparent that the majority of the processing was occurring in wp-admin/js/nav-menu.js (or the minified equivalent). Digging deeper into this file, I found that the bottleneck occurs with the refreshAdvancedAccessibility function. This function is responsible for adding buttons that allow the menu items’ positions to be moved without having to drag and drop.

Searching the issue in trac, I came across this ticket: Give the menus page an accessibility mode option, like the widgets screen, which confirms that this code was added in WordPress 3.6. The core team has done a nice job adding some useful accessibility features to the Appearance > Menus screen, which is great. See the relevant commit.

The code looks like this:

		refreshAdvancedAccessibility : function() {

			// Hide all links by default
			$( '.menu-item-settings .field-move a' ).hide();

			$( '.item-edit' ).each( function() {
				var $this = $(this),
					movement = [],
					availableMovement = '',
					menuItem = $this.parents( 'li.menu-item' ).first(),
					depth = menuItem.menuItemDepth(),
					isPrimaryMenuItem = ( 0 === depth ),
					itemName = $this.parents( '.menu-item-handle' ).find( '.menu-item-title' ).text(),
					position = parseInt( menuItem.index() ),
					prevItemDepth = ( isPrimaryMenuItem ) ? depth : parseInt( depth - 1 ),
					prevItemNameLeft = menuItem.prevAll('.menu-item-depth-' + prevItemDepth).first().find( '.menu-item-title' ).text(),
					prevItemNameRight = menuItem.prevAll('.menu-item-depth-' + depth).first().find( '.menu-item-title' ).text(),
					totalMenuItems = $('#menu-to-edit li').length,
					hasSameDepthSibling = menuItem.nextAll( '.menu-item-depth-' + depth ).length;

				// Where can they move this menu item?
				if ( 0 !== position ) {
					var thisLink = menuItem.find( '.menus-move-up' );
					thisLink.prop( 'title', menus.moveUp ).show();
				}

				if ( 0 !== position && isPrimaryMenuItem ) {
					var thisLink = menuItem.find( '.menus-move-top' );
					thisLink.prop( 'title', menus.moveToTop ).show();
				}

				if ( position + 1 !== totalMenuItems && 0 !== position ) {
					var thisLink = menuItem.find( '.menus-move-down' );
					thisLink.prop( 'title', menus.moveDown ).show();
				}

				if ( 0 === position && 0 !== hasSameDepthSibling ) {
					var thisLink = menuItem.find( '.menus-move-down' );
					thisLink.prop( 'title', menus.moveDown ).show();
				}

				if ( ! isPrimaryMenuItem ) {
					var thisLink = menuItem.find( '.menus-move-left' ),
						thisLinkText = menus.outFrom.replace( '%s', prevItemNameLeft );
					thisLink.prop( 'title', menus.moveOutFrom.replace( '%s', prevItemNameLeft ) ).html( thisLinkText ).show();
				}

				if ( 0 !== position ) {
					if ( menuItem.find( '.menu-item-data-parent-id' ).val() !== menuItem.prev().find( '.menu-item-data-db-id' ).val() ) {
						var thisLink = menuItem.find( '.menus-move-right' ),
							thisLinkText = menus.under.replace( '%s', prevItemNameRight );
						thisLink.prop( 'title', menus.moveUnder.replace( '%s', prevItemNameRight ) ).html( thisLinkText ).show();
					}
				}

				if ( isPrimaryMenuItem ) {
					var primaryItems = $( '.menu-item-depth-0' ),
						itemPosition = primaryItems.index( menuItem ) + 1,
						totalMenuItems = primaryItems.length,

						// String together help text for primary menu items
						title = menus.menuFocus.replace( '%1$s', itemName ).replace( '%2$d', itemPosition ).replace( '%3$d', totalMenuItems );
				} else {
					var parentItem = menuItem.prevAll( '.menu-item-depth-' + parseInt( depth - 1 ) ).first(),
						parentItemId = parentItem.find( '.menu-item-data-db-id' ).val(),
						parentItemName = parentItem.find( '.menu-item-title' ).text(),
						subItems = $( '.menu-item .menu-item-data-parent-id[value="' + parentItemId + '"]' ),
						itemPosition = $( subItems.parents('.menu-item').get().reverse() ).index( menuItem ) + 1;

						// String together help text for sub menu items
						title = menus.subMenuFocus.replace( '%1$s', itemName ).replace( '%2$d', itemPosition ).replace( '%3$s', parentItemName );
				}

				$this.prop('title', title).html( title );
			});
		},

It uses jQuery’s .each() function to loop through every menu item and add in accessibility functionality. This is some relatively intensive processing, but for only a few menu items it’s not a big deal.

Unfortunately, for menus with a large number of menu items, this code creates a significant bottleneck as the amount of processing compounds with each menu item. Every item in the menu needs to be processed at page load, as well as when the menu item order changes. As a result, we get long pauses while the browser executes this javascript over and over. This creates user experience issues as the browser seems to seize up every time a change is made.

Demonstration

Here is a video demonstration of the issue. Best viewed in 720p.

A Potential Solution

The main problem here is that when all the items must be processed at once, this creates a severe bottleneck when working with large numbers of menu items. Processing the items one at a time, only when necessary, would alleviate this issue.

Before this accessibility functionality can be used, the items will need to be either hovered, focused, or touched. Therefore we can defer processing until those events occur on a per-item basis. Here’s what I’ve done:

First, we move all of the processing to a function called refreshAdvancedAccessibilityLazy. This function will be passed an individual menu item for processing, and will only be processed if it hasn’t already been. The rest of the processing is identical, however, we’ll bind this function to certain events as a callback, rather than looping over everything at once.

		/* This function takes all code from refreshAdvancedAccessibility() and executes it only on a single 
			menu item, to be used as a callback */
		refreshAdvancedAccessibilityLazy : function( $itemEdit ){

			//don't reprocess
			if( $itemEdit.data( 'accessibility_refreshed' ) ) return;

			//mark as processed
			$itemEdit.data( 'accessibility_refreshed' , true );

			//do the accessibility processing
			var $this = $itemEdit,
				movement = [],
				availableMovement = '',
				menuItem = $this.parents( 'li.menu-item' ).first(),
				depth = menuItem.menuItemDepth(),
				isPrimaryMenuItem = ( 0 === depth ),
				itemName = $this.parents( '.menu-item-handle' ).find( '.menu-item-title' ).text(),
				position = parseInt( menuItem.index() ),
				prevItemDepth = ( isPrimaryMenuItem ) ? depth : parseInt( depth - 1 ),
				prevItemNameLeft = menuItem.prevAll('.menu-item-depth-' + prevItemDepth).first().find( '.menu-item-title' ).text(),
				prevItemNameRight = menuItem.prevAll('.menu-item-depth-' + depth).first().find( '.menu-item-title' ).text(),
				totalMenuItems = $('#menu-to-edit li').length,
				hasSameDepthSibling = menuItem.nextAll( '.menu-item-depth-' + depth ).length;

			// Where can they move this menu item?
			if ( 0 !== position ) {
				var thisLink = menuItem.find( '.menus-move-up' );
				thisLink.prop( 'title', menus.moveUp ).show();
			}

			if ( 0 !== position && isPrimaryMenuItem ) {
				var thisLink = menuItem.find( '.menus-move-top' );
				thisLink.prop( 'title', menus.moveToTop ).show();
			}

			if ( position + 1 !== totalMenuItems && 0 !== position ) {
				var thisLink = menuItem.find( '.menus-move-down' );
				thisLink.prop( 'title', menus.moveDown ).show();
			}

			if ( 0 === position && 0 !== hasSameDepthSibling ) {
				var thisLink = menuItem.find( '.menus-move-down' );
				thisLink.prop( 'title', menus.moveDown ).show();
			}

			if ( ! isPrimaryMenuItem ) {
				var thisLink = menuItem.find( '.menus-move-left' ),
					thisLinkText = menus.outFrom.replace( '%s', prevItemNameLeft );
				thisLink.prop( 'title', menus.moveOutFrom.replace( '%s', prevItemNameLeft ) ).html( thisLinkText ).show();
			}

			if ( 0 !== position ) {
				if ( menuItem.find( '.menu-item-data-parent-id' ).val() !== menuItem.prev().find( '.menu-item-data-db-id' ).val() ) {
					var thisLink = menuItem.find( '.menus-move-right' ),
						thisLinkText = menus.under.replace( '%s', prevItemNameRight );
					thisLink.prop( 'title', menus.moveUnder.replace( '%s', prevItemNameRight ) ).html( thisLinkText ).show();
				}
			}

			if ( isPrimaryMenuItem ) {
				var primaryItems = $( '.menu-item-depth-0' ),
					itemPosition = primaryItems.index( menuItem ) + 1,
					totalMenuItems = primaryItems.length,

					// String together help text for primary menu items
					title = menus.menuFocus.replace( '%1$s', itemName ).replace( '%2$d', itemPosition ).replace( '%3$d', totalMenuItems );
			} else {
				var parentItem = menuItem.prevAll( '.menu-item-depth-' + parseInt( depth - 1 ) ).first(),
					parentItemId = parentItem.find( '.menu-item-data-db-id' ).val(),
					parentItemName = parentItem.find( '.menu-item-title' ).text(),
					subItems = $( '.menu-item .menu-item-data-parent-id[value="' + parentItemId + '"]' ),
					itemPosition = $( subItems.parents('.menu-item').get().reverse() ).index( menuItem ) + 1;

					// String together help text for sub menu items
					title = menus.subMenuFocus.replace( '%1$s', itemName ).replace( '%2$d', itemPosition ).replace( '%3$s', parentItemName );
			}

			$this.prop('title', title).html( title );

			//console.log( 'refresh ' + title );
		},

Next, we’ll strip everything from the original function. Rather than reprocessing everything every time something changes, we’ll simply reset all states to unprocessed, and then they will be reprocessed next time those items are interacted with

		/* Functionality stripped from this function and deferred to callback 
			function refreshAdvancedAccessibilityLazy() */
		refreshAdvancedAccessibility : function() {

			// Hide all links by default
			$( '.menu-item-settings .field-move a' ).hide();

			//Mark all items as unprocessed
			$( '.item-edit' ).data( 'accessibility_refreshed' , false );

			return;
		},

Finally, we’ll rewrite the accessibility initialization function to trigger our deferred processing on hover/focus/touch, and use on() with event delegation in order to ensure newly added items also work.

		initAccessibility : function() {
			api.refreshKeyboardAccessibility();
			api.refreshAdvancedAccessibility();

			//Setup the refresh on hover/focus/touch event
			$( '#menu-management' ).on( 'mouseenter.refreshAccessibility focus.refreshAccessibility touchstart.refreshAccessibility' , '.menu-item' , function(){
				api.refreshAdvancedAccessibilityLazy( $( this ).find( '.item-edit' ) );
			});

			//Modified events to use on() with event delegation so that newly added menu items work as well
			
			// Events
			$( '#menu-management' ).on( 'click', '.menus-move-up' , function ( e ) {
				api.moveMenuItem( $( this ).parents( 'li.menu-item' ).find( 'a.item-edit' ), 'up' );
				e.preventDefault();
			});
			$( '#menu-management' ).on( 'click', '.menus-move-down', function ( e ) {
				api.moveMenuItem( $( this ).parents( 'li.menu-item' ).find( 'a.item-edit' ), 'down' );
				e.preventDefault();
			});
			$( '#menu-management' ).on( 'click', '.menus-move-top', function ( e ) {
				api.moveMenuItem( $( this ).parents( 'li.menu-item' ).find( 'a.item-edit' ), 'top' );
				e.preventDefault();
			});
			$( '#menu-management' ).on( 'click', '.menus-move-left' , function ( e ) {
				api.moveMenuItem( $( this ).parents( 'li.menu-item' ).find( 'a.item-edit' ), 'left' );
				e.preventDefault();
			});
			$( '#menu-management' ).on( 'click', '.menus-move-right', function ( e ) {
				api.moveMenuItem( $( this ).parents( 'li.menu-item' ).find( 'a.item-edit' ), 'right' );
				e.preventDefault();
			});

		},

The Plugin

In order to make this new code work as a plugin rather than edit the Core, I’ve deregistered the standard nav-menu.js and re-registered this newly edited version from the plugin. Activating the plugin will immediately switch to the revised script. The code and plugin are available for download on GitHub

View Plugin on GitHub Download from WordPress Plugin Repository

How to write a custom shortcode for UberMenu

Please note that this tutorial is no longer necessary, as this functionality is built into UberMenu 3 with Dynamic Posts

One of the really flexible pieces of UberMenu – Responsive WordPress Mega Menu Plugin is the Content Overrides system, which allows users to insert any type of shortcode content into their menu. UberMenu ships with a variety of included shortcodes, but any shortcode can be placed in the menu. This leaves customers with the ability to write their own custom shortcodes as an easy way to add totally custom content to the menu.

In this tutorial, we’ll build a custom shortcode that will allow you to add a grid of recent posts to your submenu.

Skip to Result Skip to Solution View the demo

Writing the Shortcode

Shortcode skeleton

Creating a custom shortcode in WordPress is really very simple. There are two parts to the process:

1. Register the shortcode

First, we tell WordPress that there’s a new shortcode it should be on the lookout for, and we tell it what function to run when it encounters the shortcode.

add_shortcode( 'ubermenu-post-grid' , 'ubermenu_post_grid' );

This means that when WordPress sees this shortcode: [ubermenu-post-grid] it will execute the ubermenu_post_grid() function and print the returned value.

2. Define the shortcode handler function

Next, we need to define a function that will actually produce and return the markup that we want the shortcode to be replaced with. We’ve already created the function name above. The basic function skeleton looks like this:

function ubermenu_post_grid( $atts ){

  $html = '';

  //add markup to $html based on the values in $atts

  return $html;

}

We will add both of these pieces of PHP to our theme’s functions.php, preferably in a child theme in order to best preserve our customizations.

Shortcode attributes

Shortcodes accept specific attributes. We will define the attributes that our shortcode will accept, along with default values, in our shortcode function. For example, if the shortcode instance looks like this

[ubermenu-post-grid num="3" category="kittens"]

Then the $atts array that is passed to ubermenu_post_grid( $atts ) will look like this:

$atts = array(
  'num' => 3,
  'category' => 'kittens'
);

The rest of the attribute values will then be applied as defaults.

WordPress has a handy function called shortcode_atts that automatically merges the default array of attribute values with the values passed by the user in the shortcode instance. In order to define our attributes and their default values, we add this to our function:

//Merge user-provided attributes with default values 
//and extract into individual variables
extract(shortcode_atts(array(

	'num'		=>	4,			//maximum number of posts to retrieve
	'grid'		=> 	4,			//number of columns per row
	'category'	=>	'',			//optional category to retrieve posts from

	'img'		=>	'on',		//'on' or 'off' to display the image or not
	'img_width'	=> 	220,		//image width in pixels
	'img_height'=>	120,		//image height in pixels
		
	'excerpt'	=>	'off',		//'on' or 'off' to display the excerpt
	'default_img' => false,		//URL of a default image to display when no featured image is available
	'offset'	=>	0,			//offset for get_posts() (number of posts to skip)

), $atts));

The extract() function turns each of the array keys into a variable, so now we have variables like $num, $grid, and $img that we can work with in our function.

Query Posts

Next, we want to retrieve the matching posts from the database. To do this, we will set up an array of query arguments and pass them to the get_posts() function. We will also test the category value so that users can pass either a numeric or text value and still have the shortcode work.

//Setup default query arguments
$query_args = array(
	'numberposts'	=>	$num,
	'offset'		=>	$offset,
	'suppress_filters' => false
);

//If the user has provided a category, parse it into the query args
if( !empty( $category ) ){
	//Handle numeric category values
	if(is_numeric($category)){
		$query_args['category'] = $category;
	}
	//Handle category names as well
	else $query_args['category_name'] = $category;		
}

//Retrieve matching posts from the database
$posts = get_posts( $query_args );

Produce the output markup

Now that we have retrieved the appropriate post data from the database, we need build our HTML. We’re going to loop through the array of posts the get_posts() function returned and create an unordered list of our posts, each with a link wrapping the post’s featured image and title. We’ll utilize UberMenu’s image generator function (which will use the same settings as the other images in your menu and can be set in the Control Panel) to make things easy.

//Wrap our grid in an unordered list container
$html = '<ul class="uber-post-grid uber-post-grid-'.$grid.'">';

//Loop through each post and output the image, title, and excerpt
foreach( $posts as $post ){

	//Each post becomes a list item
	$html.= '<li class="uber-post-grid-item post-'.$post->ID.'">';

	//We will wrap the entire item contents with the post's permalink (note anchors can be block level elements in HTML5)
	$html.= '<a href="'.get_permalink( $post->ID ).'">';

	//Get the image at the right size and append it
	$image = '';
	if($img == 'on'){
		$image = $uberMenu->getPostImage($post->ID, $img_width, $img_height, $default_img );
		$html.= $image;
	}
	
	//Add the post title
	$html.= '<h5>'.$post->post_title.'</h5>';

	//Add the excerpt
	if($excerpt == 'on')
		$html.= '<span class="uber-post-grid-excerpt">'.apply_filters( 'get_the_excerpt', $post->post_excerpt ).'</span>';

	$html.= '</a>';

	$html.= '</li>';
}

$html.= '</ul>';

And finally, we just return our markup string, as shortcodes need to return rather than print their output;

//Return the entire list 
return $html;

Here’s how the entire function will look:

function ubermenu_post_grid( $atts ){

	//we're going to need access to the UberMenu object for image functionality
	global $uberMenu;
	
	//Merge user-provided attributes with default values 
	//and extract into individual variables
	extract(shortcode_atts(array(

		'num'		=>	4,			//maximum number of posts to retrieve
		'grid'		=> 	4,			//number of columns per row
		'category'	=>	'',			//optional category to retrieve posts from

		'img'		=>	'on',		//'on' or 'off' to display the image or not
		'img_width'	=> 	220,		//image width in pixels
		'img_height'=>	120,		//image height in pixels
		
		'excerpt'	=>	'off',		//'on' or 'off' to display the excerpt
		'default_img' => false,		//URL of a default image to display when no featured image is available
		'offset'	=>	0,			//offset for get_posts() (number of posts to skip)

	), $atts));
	
	//Setup default query arguments
	$query_args = array(
		'numberposts'	=>	$num,
		'offset'		=>	$offset,
		'suppress_filters' => false
	);

	//If the user has provided a query, parse it into the query args
	if( !empty( $category ) ){
		//Handle numeric category values
		if(is_numeric($category)){
			$query_args['category'] = $category;
		}
		//Handle category names as well
		else $query_args['category_name'] = $category;		
	}

	//Retrieve matching posts from the database
	$posts = get_posts( $query_args );

	//Wrap our grid in an unordered list container
	$html = '<ul class="uber-post-grid uber-post-grid-'.$grid.'">';

	//Loop through each post and output the image, title, and excerpt
	foreach( $posts as $post ){

		//Each post becomes a list item
		$html.= '<li class="uber-post-grid-item post-'.$post->ID.'">';

		//We will wrap the entire item contents with the post's permalink (note anchors can be block level elements in HTML5)
		$html.= '<a href="'.get_permalink( $post->ID ).'">';

		//Get the image at the right size and append it
		$image = '';
		if($img == 'on'){
			$image = $uberMenu->getPostImage($post->ID, $img_width, $img_height, $default_img );
			$html.= $image;
		}
		
		//Add the post title
		$html.= '<h5>'.$post->post_title.'</h5>';

		//Add the excerpt
		if($excerpt == 'on')
			$html.= '<span class="uber-post-grid-excerpt">'.apply_filters( 'get_the_excerpt', $post->post_excerpt ).'</span>';

		$html.= '</a>';

		$html.= '</li>';
	}

	$html.= '</ul>';

	//Return the entire list 
	return $html;
}
add_shortcode( 'ubermenu-post-grid' , 'ubermenu_post_grid' );

Adding Styling (CSS)

At this point, we have our markup, but it’s not styled, and it doesn’t look too pretty.

unstyled grid

Our final goal is to create a simple, flexible and responsive grid system for our menu items.

First, we’re going to use `border-box` box sizing to make things easy.

/* Setup Grid container and items to make sizing easy */
#megaMenu ul.megaMenu .uber-post-grid,
#megaMenu ul.megaMenu .uber-post-grid-item{
	-webkit-box-sizing:border-box;
	-o-box-sizing:border-box;
	-moz-box-sizing:border-box;
	box-sizing:border-box;
}
/* Align grid container properly */
#megaMenu ul.megaMenu .uber-post-grid{
	display:block;
	margin:10px 0 10px 0;
	float:left;
	padding-left:27px;
}

Next, we want to turn our list items into grid boxes, so we’ll give them some padding and margins and float them left. Then we’ll set up a simple grid system to divide the boxes into even columns

/* Space grid items */
#megaMenu ul.megaMenu .uber-post-grid-item{
	float:left;
	padding:0 27px 10px 0;
}
/* Setup simple grid widths */
#megaMenu ul.megaMenu .uber-post-grid-1 .uber-post-grid-item{ width: 100%; }
#megaMenu ul.megaMenu .uber-post-grid-2 .uber-post-grid-item{ width: 50%; }
#megaMenu ul.megaMenu .uber-post-grid-3 .uber-post-grid-item{ width: 33%; }
#megaMenu ul.megaMenu .uber-post-grid-4 .uber-post-grid-item{ width: 25%; }
#megaMenu ul.megaMenu .uber-post-grid-5 .uber-post-grid-item{ width: 20%; }

After that, we’ll style our anchors, titles, and excerpts to look a bit nicer

/* Post titles */
#megaMenu ul.megaMenu .uber-post-grid h5,
#megaMenu ul.megaMenu .uber-post-grid a{
	font-size:12px;
	text-transform:none;
	text-decoration: none
}
#megaMenu ul.megaMenu .uber-post-grid h5{
	font-weight:bold;
	line-height:18px;
}
/* Post Excerpts */
#megaMenu ul.megaMenu .uber-post-grid-excerpt{
	font-size:90%;
}

Finally, we’ll add a special class to adjust the normal submenu item padding in order to properly space out our grid evenly.

#megaMenu ul.megaMenu ul.sub-menu-1 > li.menu-item.ss-override.no-padding-shortcode,
#megaMenu ul.megaMenu ul.sub-menu-1 > li.menu-item.ss-sidebar.menu-item.no-padding-widget > .wpmega-widgetarea > ul.um-sidebar > li.widget{
	padding-left:0;
	padding-right:0;
	width:100%;
}
/* Align nav labels */
#megaMenu ul.megaMenu ul.sub-menu-1 > li.menu-item.ss-override.no-padding-shortcode > a,
#megaMenu ul.megaMenu ul.sub-menu-1 > li.menu-item.ss-override.no-padding-shortcode > span.um-anchoremulator,
#megaMenu ul.megaMenu ul.sub-menu-1 > li.ss-sidebar.no-padding-widget > .wpmega-widgetarea > ul.um-sidebar > li.widget h2.widgettitle{
	margin:0 27px;
}

The special classes no-padding-shortcode and no-padding-widget will be added to second level menu items to remove the padding and allow the grid to be laid out appropriately.

Adding Responsiveness

Finally, we’ll make the grid responsive by implementing two breakpoints. Below 767px, we’ll collapse all grids to two columns, and below 480px we’ll collapse it further to a single column.

First, we’ll make sure all of our images respond to the grid at all sizes

/* Make images responsive */
#megaMenu ul.megaMenu .uber-post-grid .uber-post-grid-item img{
	max-width:100%;
}

Then we’ll force all grids into 1 or two column layouts depending on the current viewport size

/* #Mobile (Landscape) - 480px - 767px
================================================== */
@media only screen and (min-width: 480px) and (max-width: 767px) {
	#megaMenu ul.megaMenu .uber-post-grid-2 .uber-post-grid-item,
	#megaMenu ul.megaMenu .uber-post-grid-3 .uber-post-grid-item,
	#megaMenu ul.megaMenu .uber-post-grid-4 .uber-post-grid-item,
	#megaMenu ul.megaMenu .uber-post-grid-5 .uber-post-grid-item{ width: 50%; }
}
/* #Mobile (Portrait) - < 480px 
================================================== */
@media only screen and (max-width: 479px) {
	#megaMenu ul.megaMenu .uber-post-grid-2 .uber-post-grid-item,
	#megaMenu ul.megaMenu .uber-post-grid-3 .uber-post-grid-item,
	#megaMenu ul.megaMenu .uber-post-grid-4 .uber-post-grid-item,
	#megaMenu ul.megaMenu .uber-post-grid-5 .uber-post-grid-item{ width: 100%; }
}

And we’ll make sure that all our containers are 100% width of the submenus

/* Stretch everything to 100% for no-padding items */
@media only screen and (max-width: 767px) {
	#megaMenu.megaResponsive ul.megaMenu li.menu-item.ss-nav-menu-mega ul.sub-menu.sub-menu-1 > li.menu-item.ss-override.no-padding-shortcode,
	#megaMenu.megaResponsive ul.megaMenu li.menu-item.ss-nav-menu-mega ul.sub-menu.sub-menu-1 > li.menu-item.ss-sidebar.menu-item.no-padding-widget > .wpmega-widgetarea > ul.um-sidebar > li.widget{
		padding:0;
		width:100%;
	}
}

The Result

View the Demo

After all that, we end up with a simple responsive grid that looks like this:

Full width

UberMenu Custom Shortcode Post Grid

< 767px

responsive menu 767px

< 480px

responsive menu 480px

Putting it all together

Step 1: Add the shortcode PHP & CSS to your site

We’ve now completed our two-part process: (1) create a shortcode in PHP, (2) style the shortcode output with CSS. I’ve provided final versions of the code as gists, which can be downloaded from github.

1. Add the shortcode PHP to your theme’s functions.php, preferably in a child theme in order make preserving your changes as straightforward as possible.

2. Add the UberMenu Post Grid CSS to any stylesheet. Not sure where to put it? See Adding Custom CSS

Step 2: Create your menu item

Finally, actually create your menu item. Add a second level menu item, and add a content override using your newly created shortcode!

[ubermenu-post-grid grid="4"]

Then add the special class to your menu item, no-padding-shortcode (if you’re using a widget, use no-padding-widget) to ensure your grid aligns properly.

menu item configuration

Enjoy your new post grid layout!

Like the UberMenu skin used in this tutorial? It’s available in the Flat Skins Pack

How to search a single forum with bbPress

Out of the box, bbPress search will search across all forums. However, there are use cases where this doesn’t make sense, one being running a support forum. For example, if you’re having an issue with Product A and are searching for a solution, you only want to see results from Product A’s forum, not results from Product B’s forum.

A specific case from my support forums: if an UberMenu customer searches the forum for “responsive”, they should get results from the UberMenu – WordPress Mega Menu Plugin forum but not from the Agility Responsive WordPress Theme forum.

Since I couldn’t find a setting or plugin to accomplish this, I made a few customizations to accomplish the following:

  1. Display a search bar on the individual product forums (/forum/ubermenu).
  2. Automatically restrict the search form on each forum to return results only from that forum’s topics.

To implement this we need to make three customizations:

  1. Move the search form to the top of the single forum template
  2. Add an extra field to the search form to identify the current forum
  3. Filter the search query to only search a specific forum

Before proceeding, we’ll be overriding bbPress templates in our theme, so you’ll want to set up a child theme with a /bbpress subfolder to house these templates. bbPress’ default templates are located in the wp-content/plugins/bbpress/templates/defaults/bbpress directory, and we’ll copy the templates we need from here to our wp-content/themes/{mychildtheme}/bbpress directory in order to override them.

Step 1: Move the search form to the individual forums

By default, the bbPress search form will appear at the top of the main /forums page, produced by the content-archive-forum.php template. There is no hook to remove the search form, so we’ll do this by overriding the template. Copy bbPress’ content-archive-forum.php template into your child theme’s /bbpress directory, then remove the search box from the top of the file. Here’s the code you’ll remove:

<div class="bbp-search-form">

	<?php bbp_get_template_part( 'form', 'search' ); ?>

</div>

To move the search form into the content-single-forum.php template, however, we can just use an action hook rather than needing to override the template (which could lead to extra maintenance down the road). Just add this action to your child theme’s functions.php

function my_bbp_search_form(){
	?>
	<div class="bbp-search-form">

		<?php bbp_get_template_part( 'form', 'search' ); ?>

	</div>
	<?php
}
add_action( 'bbp_template_before_single_forum', 'my_bbp_search_form' );

The search form should now appear on your individual forums rather than on the main forums page.

Step 2: Add a field to the search form to identify the current forum

In order to restrict search results to a particular forum, we’ll need that forum’s ID when the form is submitted to alter the query. In this case we need to override form-search.php, so copy bbPress’s template into the child theme’s /bbpress directory, then make the following alterations:

1. Get the forum ID

$forum_id = bbp_get_forum_id();

2. After the search field, add a hidden field to store the forum ID

<?php if( $forum_id ): ?>
	<input class="button" type="hidden" name="bbp_search_forum_id" value="<?php echo $forum_id; ?>" />
<?php endif; ?>

3. (Optional) Edit the placeholder text on the search field to display “Search {Forum Title} Topics”

<input placeholder="Search <?php the_title(); ?> Topics" tabindex="<?php bbp_tab_index(); ?>" type="text" value="<?php echo esc_attr( bbp_get_search_terms() ); ?>" name="bbp_search" id="bbp_search" />

The final file looks like this:

/**
 * Search 
 *
 * @package bbPress
 * @subpackage Theme
 */
$forum_id = bbp_get_forum_id();
?>

<form role="search" method="get" id="bbp-search-form" action="<?php bbp_search_url(); ?>">
	<div>
		<label class="screen-reader-text hidden" for="bbp_search"><?php _e( 'Search for:', 'bbpress' ); ?></label>
		<input placeholder="Search <?php the_title(); ?> Topics" tabindex="<?php bbp_tab_index(); ?>" type="text" value="<?php echo esc_attr( bbp_get_search_terms() ); ?>" name="bbp_search" id="bbp_search" />
		<?php if( $forum_id ): ?>
		<input class="button" type="hidden" name="bbp_search_forum_id" value="<?php echo $forum_id; ?>" />
		<?php endif; ?>
		<input tabindex="<?php bbp_tab_index(); ?>" class="button" type="submit" id="bbp_search_submit" value="<?php esc_attr_e( 'Search', 'bbpress' ); ?>" />
	</div>
</form>

Nothing will change visibly on your site after making these changes, but now the forum ID value will be submitted when the search button is clicked.

Step 3: Filter the search query to only search a specific forum

Here’s where the real meat of the customization occurs. Using the bbp_after_has_search_results_parse_args filter we’ll alter the search query arguments and add a meta query to restrict the results to topics/replies in a specific forum. Just add this code to your functions.php

/*
 * Search only a specific forum
 */
function my_bbp_filter_search_results( $r ){

	//Get the submitted forum ID (from the hidden field added in step 2)
	$forum_id = sanitize_title_for_query( $_GET['bbp_search_forum_id'] );

	//If the forum ID exits, filter the query
	if( $forum_id && is_numeric( $forum_id ) ){

		$r['meta_query'] = array(
			array(
				'key' => '_bbp_forum_id',
				'value' => $forum_id,
				'compare' => '=',
			)
		);
		
	}

	return $r;
}
add_filter( 'bbp_after_has_search_results_parse_args' , 'my_bbp_filter_search_results' );

That’s it! Now when you search from a search form within a specific forum, you’ll only get results back from that forum.

How to change the menu on a custom post type with the Menu Swapper plugin

For those who are unfamiliar, the Menu Swapper plugin allows you to switch the menu you’re using on any individual Post or Page on your site. But what happens if you need to swap the menu on a custom post type?

Out of the box, the Menu Swapper will add a meta box to Posts and Pages (two standard post types) which allow you to choose which menu to display on the single post page. If you want to have the same on a custom post type, like a Portfolio Item for example, you’ll need to add that post type to the list of post types that the Menu Swapper will target.

The Menu Swapper makes this easy with the mswp_post_types filter. You’ll just need to add a few lines of code to your child theme’s functions.php. The main thing you’ll need is the custom post type ID – the easiest way to determine that if you’re not sure is to click on the “All {Post Type}” panel in your WordPress Admin Panel. The URL will contain the string ?post_type=portfolio; in this case the custom post type ID would be portfolio.

For the portfolio example, here’s what the PHP that you’d add to functions.php would look like:

function my_add_mswp_post_type( /* array */ $post_types ){

	$post_types[] = 'portfolio'; // 'portfolio' is the custom post type ID
	//$post_types[] = 'download'; // this is how you would add a second custom post type

	return $post_types;
}
add_filter( 'mswp_post_types', 'my_add_mswp_post_type' );

Add that code to your functions.php and you’ll get the Menu Swapper meta box on your custom post type, allowing you to change out the menu on that post type’s single page.

Enjoy!

The WordPress Menu Item Limit or: Help! Half my menu items just disappeared!

So, you just saved your WordPress 3 menu and found to your horror that you lost menu items – maybe a LOT of menu items. And now you can’t add any more – when you add a new one, the one last drops off the end of the list.

Is this a WordPress bug? Did one of those darn plugins do it?! Nope, it’s a server configuration issue that silently cuts your menu items down before WordPress even knows about them, like wolves picking off sheep in the night. Or you know, the HTTP/PHP equivalent of that.

Shake your fist at the sky and take a deep breath, and I’ll lay it out for you.

High Level Overview

Let’s start at the beginning. There are three important things to understand:

1. To save a WordPress menu, you have to send data from the client (your web browser) to the server, where it is processed and stored by the WordPress Menu System. This form data is submitted to the server via an HTTP POST. PHP parses this data as a nice array that we know and love – $_POST.

2. The WordPress Menu System bases the menu items it saves on the data it receives from the client side via the $_POST array. It assumes that data that is not present should be removed – that is, WordPress deletes menu items not present in the $_POST array upon save.

3. Depending on your server configuration, not all of the POSTed variables sent by the client may be received by the WordPress, as the $_POST array can be truncated. And when that data is lost, we lose menu items. Dang.

Hold on, I’ve had enough of the techno-babble. Skip to the Solution

Here’s how it works

Every menu item on the Appearance > Menus screen has about 11-12 fields associated with it (Title, Classes, Description, ID, etc). There are also a few meta values for the menu overall (like the menu name, menu ID, and a few nonce-related fields for security). All this data gets sent to the server as POST data. Each field is a separate POST variable. So if you have 10 menu items, you’re sending around 120 POST variables (11 per menu item + various menu meta fields). If you have 100 menu items, you’re sending around 1200 POST variables.

Here’s the problem

Unfortunately, PHP’s desire to be secure and WordPress’s desire to be tidy conspire to stab you in the back. Two wrongs might not make a right, but in this case, two rights make a big ol’ headache.

PHP’s Security

For security reasons, PHP likes to limit the maximum numbers of POST vars you can submit. This is to mitigate the effects of DoS attacks. We’ll get into details below. The important bit from the PHP manual:

If there are more input variables than specified by this directive, an E_WARNING is issued, and further input variables are truncated from the request.

This is good, but in practice, if you’re unaware of this setting (and aren’t paying attention to Warnings in the logs – and the average user isn’t), it can produce stress-induced manual hair extraction.

WordPress’s housekeeping

The WordPress Menu System save mechanism works by iterating over the $_POST array, and then discarding any menu items that weren’t present in the array.

//nav-menus.php line 341
// Remove menu items from the menu that weren't in $_POST
if ( ! empty( $menu_items ) ) {
	foreach ( array_keys( $menu_items ) as $menu_item_id ) {
		if ( is_nav_menu_item( $menu_item_id ) ) {
			wp_delete_post( $menu_item_id );
		}
	}
}

That’s good, as it prevents us from having all sorts of orphaned menu items in the posts table. But it also means that if the menu system is sent incomplete data, WordPress assumes the menu items corresponding to that data should be deleted, rather than that they should be ignored.

The Upshot: The road to confounding data loss is paved with good intentions

In the end, if the client submits more menu item variables than the PHP-configured limit, any menu items over the limit will be lost. So if you submit 1200 variables, and the limit is 1000, the last 200 are simply discarded – WordPress never even knows they were sent. These 200 variables will correspond to the last 18 items or so in your menu.

And that is why the menu items are lost. PHP prevents the menu items that you saved on the client side from reaching WordPress’s processing on the server side, so WordPress decides to delete the menu items. Who’s at fault? (I demand blood!) No one, really. It’s just a situation where you need to configure your server properly.

Sidenote: If this happens, it’s all over. We haven’t just lost pointers, we’ve lost the actual data. The only way to recover the menu items would be to restore a backup of the database prior to the menu save. Gotta catch it before it happens to avoid the repercussions.

Menu Item Specifics

Here’s how the POST variables that are submitted on menu save break down, for those who want specifics:

Key

AbbreviationExample Value
n100Number of Menu Items
c10Number of Custom Menu Items
m2Number of Registered Theme Locations

POST Variables (Example values using 100 menu items)

KeyVariable CountExample CountExplanation
menu-name11User-defined Menu Name
save_menu11Submit button
closedpostboxesnonce11nonce
meta-box-order-nonce11nonce
update-nav-menu-nonce11nonce
_wp_http_referer11nonce
action11Form meta
menu11Menu ID
menu-item-urlc10Link URL for custom menu items
menu-item-titlen100Item Title
menu-item-attr-titlen100Item Title Attribute
menu-item-classesn100Item Custom Classes
menu-item-xfnn100Item XFN Attribute (rel=)
menu-item-descriptionn100Item Description
menu-item-db-idn100Menu Item ID
menu-item-object-idn100Linked object ID
menu-item-objectn100Linked object type (post/page/custom)
menu-item-parent-idn100Item Parent ID
menu-item-positionn100Item Position
menu-item-typen100Item Type
menu-locationsm2Theme Location
Total1120

You can estimate about 12 variables per menu item to be safe. So if you want to save a menu with 150 items, you’d want a POST variable limit of no less than 150*12 + 10 = 1810.

Sidenote: the actual number varies because some menu item types have more fields than others. For example, custom menu items have an extra field to set a URL as they are not linked to a post object.

So why does this happen suddenly and without warning?

Sometimes this issue manifests as a user reaching a simple limit: they’ve added 50 menu items. They try to add the 51st, and nothing happens if they’re adding it at the end. Or if they add it in the middle, the last menu item is dropped. The limit is 50, so every time they add X more items, the last X are deleted.

Much more distressing is the case where a user has happily had 120 menu items for the last month. Then, one fateful day, they go to manage their menu, as they have new content they wish to add. Upon adding their 121st menu item and saving, they find that suddenly, to their horror, the last 40 menu items have been deleted. How could that happen? The POST vars limit should have prevented them from adding 120 menu items in the first place, right?

The most likely scenario leading to this massive frustration is that the server’s PHP version has been updated, either by the user or the web host (or possibly added Suhosin, but let’s focus on one thing at a time). And here’s why:

The PHP directive max_input_vars was introduced in PHP 5.3.9 (a relatively recent addition from January 10, 2012). The real issue is that this directive has a default value of 1000. That’s a limit of around 80 menu items. The problem comes from this sequence of events:

  1. User adds 120 menu items while host is at PHP version < 5.3.9 (no menu item limit, because max_post_vars does not exist)
  2. Host upgrades server PHP to 5.3.9+
  3. User adds 121st menu item. Only the first 80 are sent to the server due to new POST variable limit. User loses 40ish menu items
  4. User rips out hair and smashes computer against wall. Vows to quit WordPress once and for all
Sidenote: I don’t actually recommend approaching 100 menu items, even in a mega menu. The general rule of thumb is that having more than 100 links on a page can have a negative impact on SEO. Plus, presenting a user with 100 options is generally overkill, and you should probably be rethinking the navigation on your site to make it more user-friendly

How UberMenu factors in (for those who care)

UPDATE for UberMenu 3

As of UberMenu 3.0, UberMenu’s settings have been extracted from the standard menu system save process and are now saved via AJAX in a single serialized string, so UberMenu’s settings no longer contribute to this limit. The limit will be the same whether or not UberMenu is present.

UberMenu 2 (this section does not apply to UberMenu 3)

I’d like to quickly address the impact my plugin, UberMenu – WordPress Mega Menu Plugin, has on this scenario. While UberMenu does not cause this limit, it does cause the user to reach their menu item limit faster, because it adds one extra variable to each menu item.

Note that UberMenu 2 actually adds around 10 extra settings to each menu item. However, in an effort to minimize this limitation effect, UberMenu serializes all of its settings into a single active form field (via javascript), so that it only contributes one additional POST variable per menu item, rather than 10 per item. This optimization was added in UberMenu 2.0.

So here’s how it breaks down with some quick examples.

max_input_varsNormal Menu Item Limit (approx)UberMenu 2 Item Limit (approx)
10008276
2000165153
3000249230

Adding UberMenu 2 to the mix means the limit decreases by about 6 menu items per 1000 POST vars.

Unfortunately, this has the effect of customers believing UberMenu is the cause of the issue, because with UberMenu enabled they can only add 76 menu items; with UberMenu disabled, they can add 77, 78, 79… and as such it is natural to confuse symptom for cause and conclude that UberMenu is the problem. It’s not; it just makes the problem apparent sooner, unfortunately.

Server Configuration Solution

As with most problems, once you understand the issue, it’s easy to solve it. The server configuration is limiting the number of POST variables the can be submitted to the server; we need to increase that.

There are two ways this limit may be imposed

1. PHP’s max_input_vars

The increasingly common issue is the max_input_vars PHP directive. By default, it is set to 1000. To increase this, simply set it in php.ini. I’d suggest 3000 to be safe, but 2000 should be sufficient for most sites.

max_input_vars = 3000

How do you edit php.ini? That depends on your host. If you have access to the php.ini file, simply add or edit the directive, save, and restart Apache. If you’re with a web host that doesn’t give you access (common with shared hosting), you may have to contact your host and have them make the change for you.

2. Suhosin

Prior to PHP’s max_input_vars, the Suhosin PHP Security module introduced a similar pair of max_vars directives that are identical in purpose. Not all servers run Suhosin, but those that do will need to increase the following directives in php.ini:

suhosin.post.max_vars = 3000
suhosin.request.max_vars = 3000

Again, you may need to contact your host to get them to make these changes if you don’t know how or can’t do it yourself.

Restarting Apache with the new maximum POST variable values should allow you to submit all of your menu item variables and therefore save new menu items. Hoorah!

Keep in mind this won’t actually recover your lost menu items – that data is gone. The only way to get it back would be to restore from a backup copy of your database.

How to edit php.ini on various hosts:

Is there a better solution?

Wouldn’t it be nice if this wasn’t an issue? Theoretically, there may be a few options. In practice, most are more easily said than done.

Option 1: Condense all of the WordPress Menu Item fields into a single input before submitting

This is what I did with UberMenu – just serialize all the settings and decode the string on the server side. It works well, with two caveats. First, we still have the potential to reach a menu item limit eventually. Second, it means we don’t have a non-javascript fallback, and for core WordPress functionality, that’s a no-go. So that solution is probably out.

Option 2: Save menu items individually

We could save menu items via AJAX, individually, so we’d only ever submit data for one menu item at a time. Again, this requires javascript, and I suspect it would also require a major reworking of how the menu system works. It would also have to be implemented carefully in order to be intuitive and convenient; I think there would still need to be a Save-All button that would sequence all of the menu items for processing.

I don’t have enough expertise in the nitty gritty of the menu system to say whether this is truly a viable/practical option.

Option 3: Use a “checksum” to protect from unintended deletions

I’m using the term loosely here, but we could submit an extra form field that acts as a “checksum“, indicating how many menu items are being sent to the server for processing. WordPress’s menu save process could then check this value to make sure the number of menu items that it has received in the $_POST array is equivalent to the number of items it expects to receive. If it finds a discrepancy, it could avoid deleting missing menu items; however, it may be too late to save newly submitted menu items that were previously unknown.

Option 4: Native Nested Menus

Another thought would be to provide a native nesting/import functionality in WordPress. Essentially, each menu item would have an option to nest a separate menu as its submenu. This would allow users to divide up their menu items among multiple menu trees, to be dynamically assembled when the menu is printed. There are some existing plugin solutions out there that already do this, I believe. This would also enhance the reusability of the menu system, as users could potentially use the same menu subtree fragments in multiple menus.

Note that UberMenu 3 has a feature called “Menu Segments” that allows you to do this

Option 5: Provide a Warning

I think this is really the best option, at least in the short term. Simply detect the potential issue and alert the user to it. Providing explanations and solutions (or links to solutions) within the interface is generally the most efficient way to inform a user of what they need to be aware of and resolve.

And that brings us to…

The Menu Item Limit Detector Plugin

As a first step toward this, I’ve written a rudimentary plugin that attempts to detect impending menu item limits by roughly (and conservatively) estimating the remaining POST variables based on the prior save and the current variable limits. It adds an alert in the Appearance > Menus screen when the user approaches the limit, and explains the issue.

Menu Item Limit Warning

Note that I’ve already included a very similar piece of code within my UberMenu plugin to detect this scenario for UberMenu customers. Available in UberMenu 2.3+

The obvious downside to the plugin is that users need to be aware of the issue in order to install it in the first place… and the whole point of the plugin is to provide that awareness. Chicken and egg much? This’d be far more useful in WordPress Core.

Download the Plugin Contribute on Github

Please try out the plugin and let me know if it helps! If it’s useful, I’ll add it to the WordPress plugin repository.

And let me know if you have any creative suggestions for solving this problem! I highly doubt the options I’ve presented are comprehensive, but I hope they start the conversation going in the right direction.

UberMenu 2.3 Released & Important Upgrade Notes

Hit a snag? Head to the Support Forum

UberMenu 2.3.0.2 is now available, and upgrade is recommended.

UberMenu 2.3 was released today, and includes a variety of updates and feature enhancements:

  • Updated Search Box Styling
  • Ability to name custom widget areas
  • Improved localization
  • Added ability to include custom javascript
  • Ability to disable updates
  • Separated basic.css into LESS stylesheets for easier customization
  • Improved compatibility with nested plugin styles
  • Improved Windows 8 Mobile touch compatibility
  • Fixed minor skin bugs

Important upgrade notes

For those upgrading from a previous version of UberMenu, there are a few changes to the plugin to be aware of.

General upgrade

UberMenu 2.3 has a lot of refactoring and refining to the core code. It’s best to delete the previous plugin files and replace them with the new version. Don’t forget to back up any changes!

Style Generator Skins

In the latest version of UberMenu, LIs are now qualified with the .menu-item class in stylesheets in an effort to improve widget and shortcode style compatibility. As a result, you may need to re-save your style generator styles in order to update the skin to be compatible with the latest version.

custom.css Skins

If you are using a custom.css file, you will need to move it into the new /custom folder for it to be picked up by the new version of UberMenu. That is, save your custom.css before upgrading, then move it to the /custom folder.

Also, in the latest version of UberMenu, LIs are now qualified with the .menu-item class in stylesheets in an effort to improve widget and shortcode style compatibility. You will likely need to qualify all of the appropriate LIs in your custom stylesheet with the .menu-item class as well in order to make sure your styles have a high enough specificity to be applied. Generally, a simple find-and-replace from ” li” to ” li.menu-item” will resolve the issue – just be careful not to duplicate the class, and not to replace any LIs that aren’t actually menu items in your style selectors.

You may also need to add the class .megaMenu to any top level ULs in your skins to make sure your styles are specific enough.

Sorry for the inconvenience! But these changes will help improve future compatibility with other content you might like to place in your menu 🙂

Search Bar

UberMenu 2.3 introduces a new search bar style and functionality, and will replace your old UberMenu search short code automatically. If you wish to revert to the old style, please use the short code [ubermenu-search-v1]

Enjoy UberMenu 2.3!

Update April 13, 2013

UberMenu 2.3 bugs that have now been resolved in 2.3.0.1

Unfortunately, a few bugs were introduced in UberMenu 2.3, and have now been resolved. They are noted below for your reference:

Easy Integration Shortcode
Please note, if you are using the Easy Integration shortcode – [uberMenu_easyIntegrate] (normal PHP Easy Integration should not be affected), do not update yet.

TimThumb
I introduced a bug with timthumb in 2.3. If you are using Timthumb, please do not upgrade yet.

I have a fix and have already submitted version 2.3.0.1 (April 12 4PM EDT), and I will update this post when it is approved by CodeCanyon and ready for download. Thanks for your understanding and sorry for the inconvenience!

Update April 13: Version 2.3.0.1 is now available and safe to upgrade – thanks!