Synchronize a menu with your page hierarchy

WordPress’s menu feature is a fantastic way of creating a hierarchical menu, but unfortunately you still have to keep your pages in their own, separate hierarchy, using a horribly cumbersome interface, or a special plugin. The code snippet below will adjust your page hierarchy to match your menu, every time you edit your menu (so long as javascript is turned on). It does this by changing post_parent and menu_order for any pages that are no longer in sync with the menu. Pages that are not in the menu are not changed. To use the function in your own plugin, on the first line, you’ll need to enter the id of the menu you want to keep in sync.

The function gets the details of the menu by calling get_term_by, whilst wp_get_nav_menu_items returns all of the items in the menu. Finally, wp_update_post modifies any pages that need to be changed.

<?php
add_action ('wp_update_nav_menu', 'emw_create_hierarchy_from_menu', 10, 2);

function emw_create_hierarchy_from_menu($menu_id, $menu_data = NULL) {
	if ($menu_id != 1)  // You should update this integer to the id of the menu you want to keep in sync
		return;
	if ($menu_data !== NULL) // If $menu_date !== NULL, this means the action was fired in nav-menu.php, BEFORE the menu items have been updated, and we should ignore it.
		return;
	$menu_details = get_term_by('id', $menu_id, 'nav_menu');
	if ($items = wp_get_nav_menu_items ($menu_details->term_id)) {
	    // Create an index of menu item IDs, so we can find parents easily
		foreach ($items as $key => $item)
    		$item_index[$item->ID] = $key;
    	// Loop through each menu item
		foreach ($items as $item)
			// Only proceed if we're dealing with a page
			if ($item->object == 'page') {
				// Get the details of the page
				$post = get_post($item->object_id, ARRAY_A);
				if ($item->menu_item_parent != 0)
					// This is not top-level menu items, so we need to find the parent page
					if ($items[$item_index[$item->menu_item_parent]]->object != 'page') {
						// The parent isn't a page. Queue an error message.
						global $messages;
						$messages[] = '<div id="message" class="error"><p>' . sprintf( __("The parent of <strong>%1s</strong> is <strong>%2s</strong>, which is not a page, which means that this part of the menu cannot sync with your page hierarchy.", ETTD), $item->title, $items[$item_index[$item->menu_item_parent]]->title) . '</p></div>';
						$new_post['post_parent'] = new WP_Error;
					} else
						// Get the new parent page from the index
						$new_post['post_parent'] = $items[$item_index[$item->menu_item_parent]]->object_id;
				else
					$new_post['post_parent'] = 0; // Top-level menu item, so the new parent page is 0
				if (!is_wp_error ($new_post['post_parent'])) {
					$new_post['ID'] = $post['ID'];
					$new_post['menu_order'] = $item->menu_order;
					if ($new_post['menu_order'] !== $post['menu_order'] || $new_post['post_parent'] !== $post['post_parent'])
						// Only update the page if something has changed
						wp_update_post ($new_post);
				}
			}
	}
}
?>

Comments

  1. Well, I was searching for forum posts of people having problems with keeping their pages in sync to shamelessly promote a new plugin I wrote. Turns out we came to a similar conclusion for keeping the pages in sync, and the menu editor is by far a more superior method of editing those relationships. Anyway, I wrote a plugin to enhance the built-in menu editor that you might want to check out. And if you’re interested in extending the functionality I’d love to collaborate with you on it. I’ve been thinking it would also be nice to reverse the sync process in case people make changes to the page hierarchy via the page editor.

    Anyway the plugin is being approved still but here’s the link if you want to check it out in a couple days – http://wordpress.org/extend/plugins/enhanced-menu-editor/

    Thanks,
    Marcus

    • Mark Barnes says:

      I’ll be interested in checking the plugin out. My own code works fine, but is incompatible with the WPML multilingual plugin which I also use, and which can synchronise menus between languages. Before I discovered the incompatibility I did begin to write some code that would sync in the opposite direction, and was triggered by the ‘save_post’ action. It’s much harder in that direction!

  2. You are an absolute god send, thank you for this. Spent ages messing around with half baked crap page management plugins, but which all still left Pages and Appearance>Menus out of sync. This fixes everything. Thank you for sharing 🙂

  3. I am desperately trying to find a way to use the menu hierarchy to link parent pages to submenu (child) POSTS (not pages!). I feel this is on the right track as far as some of the vars I might need. Is there anyway you can help with this? I can see NO way to relate pages and posts at this point other than using the menu tree. Thank you for any advice!

    • Mark Barnes says:

      By definition, in WordPress, posts cannot be part of a hierarchy, only pages can. You might be able to use a plugin such as “Types” to make a link between your posts and your page.

    • @Sarah ~ if you’re still looking, there’s a new plugin called Cornerstone which intends to provide this function, only its still in beta and there’s a ways to go yet. For example, when you’ve altered your loop to put your posts in sections (pages) what do you do with the categories and tags? There not quite there yet but its promising. You can check it out at http://wordpress.org/extend/plugins/cornerstone/ ~ be sure and read the beta notes carefully!

      Terence.

  4. Your script is a good solution for my needs, however is there some way to target a specific menu with your script, the first level of my menu in frontend comes with pages from another menu !!! wich i find it strange.
    Thx for your help

    • i’ve updated the integer of the menu ID with my menu ID, but doesn’t work anyway, it still shows my menu synchronized as well, but comes with items from other branches !!

Speak Your Mind

*