Use Transients to speed up WordPress menus

How to Use Transients to speed up WordPress Menus

In my last tutorial How To Use Transients to speed up WordPress Widgets I covered the basics of Transients API and how you can leverage that to speed up your WordPress Site by caching your widgets. Similar to that we can use the Transients API to speed up our WordPress site by caching navigation menus.

The Quick Way

The simplest way to use transients to cache the menu is to look for the wp_nav_menu call inside your theme and wrap it inside a transient get/set block for example if we look at the twenty fourteen header.php file on line 54 we see:

<?php wp_nav_menu( array( 'theme_location' => 'primary', 'menu_class' => 'nav-menu' ) ); ?>

and if we want to wrap it in a transient get/set block we replace it with:

<?php
if (false === ($menu = get_transient('primary_menu'))){
	ob_start();
	wp_nav_menu( array( 'theme_location' => 'primary', 'menu_class' => 'nav-menu' ) );
	$menu = ob_get_clean();
	set_transient( 'primary_menu', $menu, DAY_IN_SECONDS );
}
echo $menu;
?>

This code snippet will cache the menu output for 24 hours so the next page request the menu will be pulled from the transient instead of making multiple database queries to render the menu.

This solution is nice and will work for most cases but it has a few drawbacks:

  • You need to edit your theme.
  • The cached menu will be the same on every single page, so it won’t be context aware.

You need to edit your theme – is a drawback because its not always in the same place, some themes have multiple menus just in general editing your theme requires you to have a child the if you are doing it right but if not then you will need to redo that every time you update your theme.

The cached menu will be the same on every single page, so it won’t be context aware means that your menu will not show the right active page. The active page will always be the one that was the real active page when the transient was created.

The plan

We are going to create a simple plugin which will store the output of the menu in a transient and will use it on the next page load instead of rendering the output all over again. To do that we are going to use the new pre_wp_nav_menu filter hook which was introduced at WordPress 3.9 and allows us to short-circuit the menu generation. We are also going to make sure that the real active/current page is cached on each page request.

Coding the plugin

The whole plugin is available for download at GitHub
To start coding our plugin we create a new file named gwp_menu_cache.php and we load it up with Standard WordPress Plugin Information header.

<?php
/*
Plugin Name: GWP Menu Cache
Plugin URI:
Description: A plugin to cache WordPress menus using the Transients API, based on this tutorial https://generatewp.com/?p=10473
Version: 1.0
Author: Ohad Raz
Author URI: https://generatewp.com
*/

Then we Create a main class for the plugin with the methods we want to implement:

/**
* GWP_menu_cache
*/
class GWP_menu_cache{
	/**
     * $cache_time
     * transient expiration time
     * @var int
     */
    public $cache_time = 43200; // 12 hours in seconds
    /**
     * $timer 
     * simple timer to time the menu generation
     * @var time
     */
    public $timer;
	
	/**
	 * __construct 
	 * class constructor will set the needed filter and action hooks
	 * 
	 */
	function __construct(){}
 	
 	/**
 	 * get_menu_key 
 	 * Simple function to generate a unique id for the menu transient
     * based on the menu arguments and currently requested page.
 	 * @param  object $args 	An object containing wp_nav_menu() arguments.
 	 * @return string 
 	 */
	function get_menu_key($args){}
 	
 	/**
 	 * get_menu_transient
 	 * Simple function to get the menu transient based on menu arguments
 	 * @param  object $args 	An object containing wp_nav_menu() arguments.
 	 * @return mixed 			menu output if exists and valid else false.
 	 */
	function get_menu_transient($args){}
 
	/**
	 * pre_wp_nav_menu 
	 * 
	 * This is the magic filter that lets us short-circit the menu generation 
	 * if we find it in the cache so anything other then null returend will skip the menu generation.
	 * 
	 * @param  string|null $nav_menu 	Nav menu output to short-circuit with.
	 * @param  object      $args     	An object containing wp_nav_menu() arguments
	 * @return string|null
	 */
	function pre_wp_nav_menu($nav_menu, $args){}
 
	/**
	 * wp_nav_menu 
	 * store menu in cache
	 * @param  string $nav  	The HTML content for the navigation menu.
	 * @param  object $args     An object containing wp_nav_menu() arguments
	 * @return string 		  	The HTML content for the navigation menu.
	 */
	function wp_nav_menu( $nav, $args ) {}
 
	/**
	 * wp_update_nav_menu 
	 * refresh time on update to force refresh of cache
	 * @param  int $menu_id 
	 * @return void
	 */
	function wp_update_nav_menu($menu_id) {}
 
}//end class

//instantiate the class
add_action( 'plugins_loaded', 'GWP_Menu_cache_init' );
function GWP_Menu_cache_init() {
	$GLOBALS['wp_menu_cache'] = new GWP_menu_cache();
}

Next we are going to implement the class methods and we are going to starts with the class constructor. Here we are going to check if the WordPress Version is 3.9 or newer to insure that the pre_wp_nav_menu filter is in place. We are So our constructor will look like this:

	 /**
	 * __construct 
	 * class constructor will set the needed filter and action hooks
	 * 
	 */
	function __construct(){
		global $wp_version;
		// only do all of this if WordPress version is 3.9+
		if ( version_compare( $wp_version, '3.9', '>=' ) ) {

			//show the menu from cache
			add_filter( 'pre_wp_nav_menu', array($this,'pre_wp_nav_menu'), 10, 2 );
			//store the menu in cache
			add_filter( 'wp_nav_menu', array($this,'wp_nav_menu'), 10, 2);
			//refresh on update
			add_action( 'wp_update_nav_menu', array($this,'wp_update_nav_menu'), 10, 1);
		}
	}

As you can see I hooked 3 functions in an IF block which checks if the current WordPress version is 3.9 or newer, we will implement each as we advance in this tutorial.
Moving on we are going to implement the helper methods get_menu_key and get_menu_transient:

 	/**
 	 * get_menu_key 
 	 * Simple function to generate a unique id for the menu transient
 	 * based on the menu arguments and currently requested page.
 	 * @param  object $args 	An object containing wp_nav_menu() arguments.
 	 * @return string 
 	 */
	function get_menu_key($args){
		return 'MC-' . md5( serialize( $args ).serialize(get_queried_object()) );
	}
 	
 	/**
 	 * get_menu_transient
 	 * Simple function to get the menu transient based on menu arguments
 	 * @param  object $args 	An object containing wp_nav_menu() arguments.
 	 * @return mixed 		menu output if exists and valid else false.
 	 */
	function get_menu_transient($args){
		$key = $this->get_menu_key($args);
		return get_transient($key);
	}

Now if you look close at get_menu_key you will see that because we’ve added serialize(get_queried_object()) at the end we will get a unique ID for each requested page and this will allow us store/cache a version of the menu per requested page which will be context aware and will know the correct “active” or “current” page/link in the menu.
As for the get_menu_transient you can see that there is nothing to fancy going on there.

Next We implement our 3 hooked functions, but we are going to do it one by one to understand each one better:

	/**
	 * pre_wp_nav_menu 
	 * 
	 * This is the magic filter that lets us short-circit the menu generation 
	 * if we find it in the cache so anything other then null returend will skip the menu generation.
	 * 
	 * @param  string|null $nav_menu 	Nav menu output to short-circuit with.
	 * @param  object      $args     	An object containing wp_nav_menu() arguments
	 * @return string|null
	 */
	function pre_wp_nav_menu($nav_menu, $args){
		$this->timer = microtime(true);
		$in_cache = $this->get_menu_transient($args);
		$last_updated = get_transient('MC-' . $args->theme_location . '-updated');
		if (isset($in_cache['data']) && isset($last_updated) &&  $last_updated < $in_cache['time'] ){
			return $in_cache['data'].'<!-- From menu cache in '.number_format( microtime(true) - $this->timer, 5 ).' seconds -->';
		}
		return $nav_menu;
	}

Breakdown: This is the magic function hooked to the pre_wp_nav_menu filter that lets us short-circit the menu generation if we find it in the cache.
We start on line 12 where we initiate a simple timer to clock the menu generation time. Next on line 13-14 we get the menu transient and the “last updated” transient for that menu (if they exist). On line 15 we check that the cached version is valid, and that it was generated after the last time the menu was saved, if so it simply returns the menu from the cache and by that killing the current menu generation. If not, it simply returns $nav_menu which in most cases will be null and will tell WordPress to keep on with the menu generation.

	 /**
	 * wp_nav_menu 
	 * store menu in cache
	 * @param  string $nav  	The HTML content for the navigation menu.
	 * @param  object $args 	An object containing wp_nav_menu() arguments
	 * @return string 		The HTML content for the navigation menu.
	 */
	function wp_nav_menu( $nav, $args ) {
		$last_updated = get_transient('MC-' . $args->theme_location . '-updated');
		if( ! $last_updated ) {
			set_transient('MC-' . $args->theme_location . '-updated', time());
		}
		$key = $this->get_menu_key($args);
		$data = array('time' => time(), 'data' => $nav);
		set_transient( $key, $data ,$this->cache_time);
		return $nav.'<!-- Not From menu cache in '.number_format( microtime(true) - $this->timer, 5 ).' seconds -->';
	}

Breakdown: – The method is hooked to wp_nav_menu filter hook,In this method we once again on line 9 get the “last updated” transient for that menu. On line 10 we check if the “last updated” transient exists and if not we need to created a new “last updated” transient which we use in the pre_wp_nav_menu. On line 13-15 we create a new transient as an array with the menu as data and current Unix timestamp as time. Last on line 16 we return the menu with an HTML comment saying the menu was loaded not from cache and the time it took to generate.

	 /**
	 * wp_update_nav_menu 
	 * refresh time on update to force refresh of cache
	 * @param  int $menu_id 
	 * @return void
	 */
	function wp_update_nav_menu($menu_id) {
		$locations = array_flip(get_nav_menu_locations());
 
		if( isset($locations[$menu_id]) ) {
			set_transient('MC-' . $locations[$menu_id] . '-updated', time());
		}
	}

Breakdown: – The last method to complete our class implementation, it is hooked to wp_update_nav_menu action hook, So it is executed every time a menu is saved and this will cause the menu cache to be refreshed every time the menu is saved or updated.

And Our plugin file should look like this:

<?php
/*
Plugin Name: GWP Menu Cache
Plugin URI:
Description: A plugin to cache WordPress menus using the Transients API, based on this tutorial https://generatewp.com/?p=10473
Version: 1.0
Author: Ohad Raz
Author URI: https://generatewp.com
*/
/**
* GWP_menu_cache
*/
class GWP_menu_cache{
	/**
     * $cache_time
     * transient expiration time
     * @var int
     */
    public $cache_time = 43200; // 12 hours in seconds
    /**
     * $timer 
     * simple timer to time the menu generation
     * @var time
     */
    public $timer;
	
	/**
	 * __construct 
	 * class constructor will set the needed filter and action hooks
	 * 
	 */
	function __construct(){
		global $wp_version;
		// only do all of this if WordPress version is 3.9+
		if ( version_compare( $wp_version, '3.9', '>=' ) ) {

			//show the menu from cache
			add_filter( 'pre_wp_nav_menu', array($this,'pre_wp_nav_menu'), 10, 2 );
			//store the menu in cache
			add_filter( 'wp_nav_menu', array($this,'wp_nav_menu'), 10, 2);
			//refresh on update
			add_action( 'wp_update_nav_menu', array($this,'wp_update_nav_menu'), 10, 1);
		}
	}
 	
 	/**
 	 * get_menu_key 
 	 * Simple function to generate a unique id for the menu transient
     * based on the menu arguments and currently requested page.
 	 * @param  object $args 	An object containing wp_nav_menu() arguments.
 	 * @return string 
 	 */
	function get_menu_key($args){
		return 'MC-' . md5( serialize( $args ).serialize(get_queried_object()) );
	}
 	
 	/**
 	 * get_menu_transient
 	 * Simple function to get the menu transient based on menu arguments
 	 * @param  object $args 	An object containing wp_nav_menu() arguments.
 	 * @return mixed 			menu output if exists and valid else false.
 	 */
	function get_menu_transient($args){
		$key = $this->get_menu_key($args);
		return get_transient($key);
	}
 

 
	/**
	 * pre_wp_nav_menu 
	 * 
	 * This is the magic filter that lets us short-circit the menu generation 
	 * if we find it in the cache so anything other then null returend will skip the menu generation.
	 * 
	 * @param  string|null $nav_menu 	Nav menu output to short-circuit with.
	 * @param  object      $args     	An object containing wp_nav_menu() arguments
	 * @return string|null
	 */
	function pre_wp_nav_menu($nav_menu, $args){
		$this->timer = microtime(true);
		$in_cache = $this->get_menu_transient($args);
		$last_updated = get_transient('MC-' . $args->theme_location . '-updated');
		if (isset($in_cache['data']) && isset($last_updated) &&  $last_updated < $in_cache['time'] ){
			return $in_cache['data'].'<!-- From menu cache in '.number_format( microtime(true) - $this->timer, 5 ).' seconds -->';
		}
		return $nav_menu;
	}
 
	
	/**
	 * wp_nav_menu 
	 * store menu in cache
	 * @param  string $nav  	The HTML content for the navigation menu.
	 * @param  object $args     An object containing wp_nav_menu() arguments
	 * @return string 		  	The HTML content for the navigation menu.
	 */
	function wp_nav_menu( $nav, $args ) {
		$last_updated = get_transient('MC-' . $args->theme_location . '-updated');
		if( ! $last_updated ) {
			set_transient('MC-' . $args->theme_location . '-updated', time());
		}
		$key = $this->get_menu_key($args);
		$data = array('time' => time(), 'data' => $nav);
		
		set_transient( $key, $data ,$this->cache_time);
		return $nav.'<!-- Not From menu cache in '.number_format( microtime(true) - $this->timer, 5 ).' seconds -->';
	}
 
	/**
	 * wp_update_nav_menu 
	 * refresh time on update to force refresh of cache
	 * @param  int $menu_id 
	 * @return void
	 */
	function wp_update_nav_menu($menu_id) {
		$locations = array_flip(get_nav_menu_locations());
 
		if( isset($locations[$menu_id]) ) {
			set_transient('MC-' . $locations[$menu_id] . '-updated', time());
		}
	}
 
}//end class

//instantiate the class
add_action( 'plugins_loaded', 'GWP_Menu_cache_init' );
function GWP_Menu_cache_init() {
    $GLOBALS['wp_menu_cache'] = new GWP_menu_cache();
}

The results

after activating the plugin I ran a simple test to see how long the menu take to render, The first page load which is when the menu is not yet cached I got:
menu load not from cache
You can see it a simple menu with about 15 links and it takes about 0.36 seconds to render.
And on the second page load I get:
menu loaded from cache
This time the menu is loaded from the transient and as you can see it only takes around 0.00115 seconds which is about 313% faster. Now this is a very simple menu with only 15 links in it, If this was a menu with 50 links or more (like most mega menus today) this would be an even bigger improvement.

Conclusion

Once again without doing anything too complex we get better performance using the simple Transients API. Other the Widgets and menus transients can help improve so many other features of WordPress native or custom. Use them wisely and use them often.

So once again, o you have a better solution or getting better results? Let me know in the comments below.

Share on facebook
Share on twitter
Share on linkedin
Share on reddit
Share on whatsapp
Share on email
Ohad Raz

Ohad Raz

A WordPress Consultant, a plugins and themes developer,WordPress Development moderator and sometimes a WordPress core contributor.

28 Comments:

  • Mark de Scande

    WOW Great Post well done PS Nice Tip to speed up a WP Menue

    Reply

  • Amit

    That’s a great solution! what about using the same solution to actually cache the entire page HTML and load that instead?

    Reply

    • Ohad Raz

      There are a few plugin that do that already, but the whole idea of Transients is data and segments caching and not page caching. A solution that always worked well for me is the Batcache plugin for full page caching which I highly recommend if you do have Memcached installed on your server.

      Reply

  • Serbanov Vostrov

    Have you actually tried it? It’s not working as the generated key at menu creation and the key generated at fetch are different. Spent quite some time trying to fix it…. but nada so far.

    Reply

    • Ohad Raz

      Yes I have and I can think of a few reasons its not working for you but the main one would be, you have not set the menu “theme location”. Make sure you set that and then look at the source code of the menu and look for the comment.

      Reply

  • Nir Goldberg (ניר גולדברג)

    Hi Ohad,

    Thanks for great articles.
    I’ve implemented your solution on a WordPress 3.9.2 installation. I have 2-4 different menus per page and it worked great.
    After upgrade to WordPress 4.0, among other plugins – I found out that only one of the menus is actually displayed from transient even though all menus are cached.
    Nevertheless, each page visit, updates all menus transients but only one is delivered from cache.

    Can you think of a reason for such behavior?

    Thanks,

    Reply

  • Nir Goldberg

    OK, I know where is the problem, I’m not sure I know why:
    2 of the menus are generated with a menu walker – by disable the call to the walker function, the problem is solved.
    The walker function only adds special classes to each menu item.

    Is this info helping you understand the problem?

    Thanks.

    Reply

    • Ohad Raz

      Nope sadly Its not really helping me understand, what version of WordPress you have?

      Reply

      • Nir Goldberg (ניר גולדברג)

        I use WordPress 4.0.
        I managed to handle this issue by adding a custom argument to wp_nav_menu() $args array and including it in MD5 hash string instead of all $args.

        Have you tried to get this method work using a menu walker?

        Thanks.

        Reply

        • Ohad Raz

          The only thing I can think of is that the problem comes from the custom walker since I have this running on a few sites with custom walkers, and even if you don’t use a custom walker the default native Walker_Nav_Menu is used so that’s not the problem.

  • Johana

    @Nir Goldberg: i have the same problem, if you custom the stdClass Object you well naver get the menu cached because the $args of pre_wp_nav_menu is different of $args of wp_nav_menu the solution of this problem is in the get_menu_key you delete the $args param like this:

    function get_menu_key($args){
    return ‘MC-‘ . md5(serialize(get_queried_object()) );
    }

    Reply

    • vale

      Thanks it kind of solved my problem.
      A better way, in case you’ll have a page where there are more than one menu displayed, is to instead serialize the theme_location from $args:

      function get_menu_key($args){
      return ‘MC-‘ . md5(serialize($args->theme_location) . serialize(get_queried_object()) );
      }

      I was wondering why not just using the queried_object_id instead of the whole queried_object, and doing this instead:

      function get_menu_key($args){
      return ‘MC-‘ . md5(serialize($args->theme_location) . serialize(get_queried_object_id()) );
      }

      Reply

    • vale

      In order to make work my last solution, we also need to add another filter for the menus generated in the native custom menu widget of WP. Unfortunately in that widget the parameter theme_location is not passed to wp_nav_menu() function, thus the caching plug-in will create transients named MC–updated for those menus.
      Here’s the fix:

      Add this to the __construct method of the menu cache plugin class:

      add_filter('widget_nav_menu_args', array($this, 'widget_nav_menu_args'), 10, 3);
      

      Then add this method to the plugin cache class:

      	/**
      	* widget_nav_menu_args
      	* Filters the arguments passed to wp_nav_menu() function when fired within the
      	* native WP Custom Menu Widget, by adding the theme_location parameter to them. 
      	* 
      	* This is a method hooked to a filter in the WP Widget Menu class.
      	* Since in that wp_nav_menu() is not given theme_location in args, that results
      	* in breaking this caching system for key generation based on that parameter. 
      	*
      	* @since 4.2.0 
      	* @access public
      	* @see WP_Nav_Menu_Widget in /wp-includes/default-widgets.php
      	* @param array $nav_menu_args {
      	* 		an array of arguments passed to wp_nav_menu() to retreive a custom menu.
      	* 		@type callback|bool $fallback_cb Callback to fire if the menu doesn't exist. Default empty.
      	* 		@type mixed         $menu        Menu ID, slug, or name.
      	* }
      	* @param stdClass $nav_menu      Nav menu object for the current menu.
      	* @param array    $args          Display arguments for the current widget. 
      	*/
      	public function widget_nav_menu_args($nav_menu_args, $nav_menu, $args) {
      		$locations = get_nav_menu_locations(); //retrieve menus theme_locations.
      		$the_location = array_search($nav_menu->term_id, $locations); //get theme_location for this menu.
      		if ($the_location !== false) {
      			$nav_menu_args['theme_location'] = $the_location; //set theme_location in new args passed to wp_nav_menu
      		}
      		return $nav_menu_args;
      	}
      

      Reply

      • Ohad Raz

        Nice Work around.

        Reply

        • vale

          Thank you for the amazing work you are also sharing!

          In facts I ended up making an abstract class out of it, which I extend for things I want to cache the in my theme.
          The only thing I need to implement is a way to get rid of all persistent transients storing the updated timestamp (mc-element-updated) on de-activation of the plugin/theme.
          So not to leave leftovers in the WP database.

  • Hazem

    if some menu items have custom style when its active, the current class will not apply

    Reply

    • Ohad Raz

      You can set the “cache” or transient key to be set by the requested page using something like $_SERVER[‘REQUEST_URI’] and the menu cache will be unique per page.

      Reply

      • Hazem

        Thank you for quick reply I appreciate your help
        I solve it via javascript by adding active class to item that match current URL

        Reply

  • Blaise

    Hi,
    Thanks for coming up with these solutions! I installed your plugin and it did not exactly work as expected.
    My WP site has mostly logged in users. Pages have 2 menus. A sidebar topright one which is personal to the user (my account, my friends…) and a main one which is the same for all.

    Your plugin has cached the personal one and NOT the standard default one: exactly the wrong way round! Despite refreshing the page multiple times this is always what happens.

    How can I change things round?

    thanks!

    Blaise

    Reply

    • Blaise

      Let me more precise:
      The generic menu that is not getting cached is called as follows:

      <?php
      $nav_args = array(
          'theme_location' => 'nav',
          'container' => 'none',
          'menu_class' => 'level-1',
          'depth' => apply_filters( 'yit_main_nav_depth', 3 ),
          //'fallback_fb' => false,
          //'walker' => new YIT_Walker_Nav_Menu()
      );
      

      wp_nav_menu( $nav_args );

      And the custom menu that IS getting cached:

      <?php
      wp_nav_menu( array( 'menu' => 'Logged User', 'menu_class' => 'nav-menu' ) );
      

      Reply

      • Ohad Raz

        It could be because you are using a custom walker that is not using the “pre_wp_nav_menu” filter hook.
        create a snippet of that walker, reply with link and I’ll take a look.

        Reply

    • Blaise

      This is the standard menu “walker” code (no idea what I’m talking about here!)

      class YIT_Walker_Nav_Menu extends Walker_Nav_Menu
      {
      	protected $_customFields = array();
      
      	public function __construct() {
      		$this->_customFields = yit_get_model('nav_menu')->fields;
      	}
      
          function display_element( $element, &$children_elements, $max_depth, $depth=0, $args, &$output )
          {
              $id_field = $this->db_fields['id'];
              if ( is_object( $args[0] ) ) {
                  $args[0]->children_number = !empty( $children_elements[$element->$id_field] ) ? count($children_elements[$element->$id_field]) : 0;
              }
              return parent::display_element( $element, $children_elements, $max_depth, $depth, $args, $output );
          }
      
      	function start_el(&$output, $item, $depth = 0, $args = array(), $current_object_id = 0)
          {
          	global $wp_query;
              $indent = ( $depth ) ? str_repeat( "\t", $depth ) : '';
      
      		$this->_loadCustomFields( $item );
      
              $class_names = $value = '';
      		$children_number = isset($args->children_number) ? $args->children_number : '0';
      
              $classes = empty( $item->classes ) ? array() : (array) $item->classes;
      
              $class_names = join( ' ', apply_filters( 'nav_menu_css_class', array_filter( $classes ), $item ) );
      		$class_names .= ' menu-item-children-' . $children_number;
      		if($depth == 1 && $this->_isCustomItem( $item ) ) {
      			$class_names .= ' menu-item-custom-content';
      		}
              $class_names = ' class="'. esc_attr( $class_names ) . '"';
      
              $output .= $indent . 'ID . '"' . $value . $class_names .'>';
      
              $attributes  = ! empty( $item->attr_title ) ? ' title="'  . esc_attr( $item->attr_title ) .'"' : '';
              $attributes .= ! empty( $item->target )     ? ' target="' . esc_attr( $item->target     ) .'"' : '';
              $attributes .= ! empty( $item->xfn )        ? ' rel="'    . esc_attr( $item->xfn        ) .'"' : '';
              $attributes .= ! empty( $item->url )        ? ' href="'   . esc_attr( $item->url        ) .'"' : '';
      
              $prepend = '';
              $append = '';
              $description  = ''; //! empty( $item->description ) ? ''.esc_attr( $item->description ).'' : '';
      
              $item_output = $args->before;
      
              if($depth == 1 && $this->_isCustomItem( $item ) ) {
      
      	        $item_output .= '<a>';
      	        $item_output .= $args->link_before .$prepend.apply_filters( 'the_title', $item->title, $item->ID ).$append;
      	        $item_output .= $args->link_after;
      	        $item_output .= '</a>';
      
      			foreach( $this->_customFields as $id => $field ) {
      				if( !empty( $item->{$id} ) ) {
      					$item_output .= $this->getOutput( $item, $id, $field );
      				}
      			}
      
      		} else {
      	        $item_output .= '<a>';
      	        $item_output .= $args->link_before .$prepend.apply_filters( 'the_title', $item->title, $item->ID ).$append;
      	        $item_output .= $args->link_after;
      	        $item_output .= '</a>';
      		}
      
              $item_output .= $args->after;
      
              $output .= apply_filters( 'walker_nav_menu_start_el', $item_output, $item, $depth, $args );
      	}
      
      	protected function _loadCustomFields( &$item ) {
      		foreach( $this->_customFields as $field=>$data ) {
      			$item->{$field} = get_post_meta( $item->ID, '_menu_item_' . $field, true ) ? get_post_meta( $item->ID, '_menu_item_' . $field, true ) : '';
      		}
      	}
      
      	protected function _isCustomItem( $item ) {
      		foreach( $this->_customFields as $field=>$data ) {
      			if( !empty( $item->{$field} ) ) {
      				return true;
      			}
      		}
      
      		return false;
      	}
      
      	protected function _parseString( $string ) {
      		$string = str_replace( array( '[', ']' ), array( '', '' ), $string );
      		return nl2br($string);
      	}
      
      	public function getOutput( $item, $fieldId, $field ) {
      		if( $field['type'] == 'text' ) {
      			return "ID} custom-item-{$fieldId} custom-item-text'>" . $item->{$fieldId} . '';
      		} elseif( $field['type'] == 'textarea' ) {
      			return "ID} custom-item-{$fieldId} custom-item-textarea'>" . $this->_parseString($item->{$fieldId}) . '';
      		} elseif( $field['type'] == 'upload' ) {
      			$image_id = yit_get_attachment_id( $item->{$fieldId} );
                  $image = yit_image( "id=" . $image_id . "&size=thumb_portfolio_4cols&output=array" );
      			if ( count( $image ) != 3 ) return;
                  list( $thumbnail_url, $thumbnail_width, $thumbnail_height ) = $image;
      
      			return "<a>ID} custom-item-{$fieldId} custom-item-image' href='". esc_attr( $item->url ) ."'>title, $item->ID ) ."' /></a>";
      		}
      	}
      }
      

      Reply

      • Vale

        Hi Blaise,
        have you already checked in the WP database table Options what transients got stored?

        Reply

        • Blaise

          Hi Vale,
          Thanks for your reply.
          No, I have not checked. Should I be looking for anything specific?
          wp_options only has 2700 rows. But I am also using Redis so most of my transients never hit the database at all.

        • Vale

          Hi Blaise,
          I thought your transients were stored in the DB.
          Thus have you already checked where they are supposed be stored if they have been generated by the cache plugin?

  • jankiiahir

    Thank you very much for posting your ideas for using transients, especially using them to speed up WordPress Navigation Menus, which once they get even slightly large on lower power servers horribly slow down the site. The word needs to be spread about doing this, it’s such a huge page load speed improvement!

    However I also could use some help with your function. I’m finding an issue on a recent site I just started developing locally. The transient function otherwise has worked great on all other sites I’ve retroactively added it to. The issue I am having is the “current-menu-item” class is being cashed on this site, on all menus I’ve tried to create. Any thoughts as so why that might be happening now and what I can do to avoid it?

    Reply

    • Ohad Raz

      Make sure that the get_menu_key method is set to return this:

      return 'MC-' . md5( serialize( $args ).serialize(get_queried_object()) );
      

      Reply

  • isuke01Isu

    Hi, Thanks for code share.
    But I have 1 problem, menu placed in widget wont update after edit this menu :/

    Reply

Leave a Reply

Subscribe to our Blog

Receive email notifications when new blog posts are published.