Take the 2-minute tour ×
WordPress Development Stack Exchange is a question and answer site for WordPress developers and administrators. It's 100% free, no registration required.

I'll try to be brief....

I have a theme that only supports one navigation menu. At this point I'd like to add 2 or 3 more menus. I have successfully registered my menus in functions.php with register_nav_menus and I am able to switch the header.php code manually between my different menus.

My problem... Since my theme uses a single header (header.php) I can't assign my pages or posts the navigation menu I want without changing all the other pages navigation menus.

Can anyone help me out on this? Thanks in advance. Greg

share|improve this question
    
What criteria are you using (or want to use) to change the menus? –  s_ha_dum Feb 10 '13 at 0:14
    
I don't think I understand the question. Ideally I want to have a meta box with a menu select drop down. But, I'd be willing to settle for pasting the wp_nav_menu code into the files if it would overide the 'header.php' file –  user27251 Feb 10 '13 at 1:44

1 Answer 1

up vote 1 down vote accepted

First off, some things you should know about nav menus:

  1. Menus are simply terms in a taxonomy called nav_menu.
  2. Menu items are a special post type

In short: menus are just like nearly every other piece of content in WordPress, they just have a custom UI.

With that out of the way, the task is fairly simple: put a custom meta box on your edit screen that shows options to choose nav menus. On the front end, switch the nav menu out to whatever you set in the admin area. The only thing to worry about is what the theme_location value is for your theme's call to wp_nav_menu. Where do you want to switch the menu out?

This is example will use Twenty Twelve, which uses the theme location primary.

A call to wrap everything up in:

<?php
class PerPostNavMenu
{
    const NONCE = 'wpse85243_nav_nonce';
    const FIELD = '_wpse85243_per_post_menu';
    const LOC   = 'primary'; // the location for twenty twelve

    private static $ins = null;

    public static function instance()
    {
        if (is_null(self::$ins)) {
            self::$ins = new self;
        }

        return self::$ins;
    }

    public static function init()
    {
        add_action('plugins_loaded', array(self::instance(), '_setup'));
    }

    public function _setup()
    {
        // we'll add actions/filters here later
    }
}

Now let's add our meta box. Since this is covered in detail a great many places, I'll give the cliff notes explanation: hook into add_meta_boxes, call add_meta_box. In the meta box callback function, output a nonce and your field(s).

To save the values, hook into save_post, check to make sure we are where we want to be (no autosave, nonce validates) and that the current user can edit the post, save (or delete) the value with (update|delete)_post_meta.

<?php
class PerPostNavMenu
{
    const NONCE = 'wpse85243_nav_nonce';
    const FIELD = '_wpse85243_per_post_menu';
    const LOC   = 'primary'; // the location for twenty twelve

    // snip snip

    public function _setup()
    {
        add_action('add_meta_boxes', array($this, 'addBox'));
        add_action('save_post', array($this, 'save'), 10, 2);
    }

    public function addBox($post_type)
    {
        if (!post_type_exists($post_type)) {
            return;
        }

        add_meta_box(
            'wpse85243_nav_menu',
            __('Nav Menu', 'wpse'),
            array($this, 'boxCallback'),
            $post_type,
            'side',
            'low'
        );
    }

    public function boxCallback($post)
    {
        $menu = get_post_meta($post->ID, static::FIELD, true);

        wp_nonce_field(static::NONCE . $post->ID, static::NONCE, false);

        printf(
            '<label for="%s">%s</label>',
            esc_attr(static::FIELD),
            esc_html__('Nav Menu', 'wpse')
        );

        echo '<br />';

        printf('<select name="%1$s" id="%1$s">', esc_attr(static::FIELD));

        foreach ($this->getNavMenus() as $id => $name) {
            printf(
                '<option value="%s" %s>%s</option>',
                esc_attr($id),
                selected($menu, $id, false),
                esc_html($name)
            );
        }

        echo '</select>';
    }

    public function save($post_id, $post)
    {
        if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
            return;
        }

        if (
            !isset($_POST[static::NONCE]) ||
            !wp_verify_nonce($_POST[static::NONCE], static::NONCE . $post_id)
        ) {
            return;
        }

        $type = get_post_type_object($post->post_type);
        if (!current_user_can($type->cap->edit_post, $post_id)) {
            return;
        }

        $menu = isset($_POST[static::FIELD]) ? $_POST[static::FIELD] : false;

        if ($menu && '-' !== $menu) {
            update_post_meta($post_id, static::FIELD, absint($menu));
        } else {
            delete_post_meta($post_id, static::FIELD);
        }
    }

    private function getNavMenus()
    {
        $terms = get_terms('nav_menu');

        $menus = array('-' => __('Default', 'wpse'));
        if ($terms && !is_wp_error($terms)) {
            foreach($terms as $t) {
                $menus[$t->term_id] = $t->name;
            }
        }

        return apply_filters('per_post_nav_menus_list', $menus);
    }
}

Notice the helper method to retrieve the nav menus. Not much, just a convenient helper that runs the result through a filter.

Finally, we just need to switch out the menu. To do that, hook into wp_nav_menu_args and, if we're on a singular page and have the correct theme location, switch out the menu as appropriate. Some extra filters are included to make this a bit more extensible.

<?php
class PerPostNavMenu
{
    const NONCE = 'wpse85243_nav_nonce';
    const FIELD = '_wpse85243_per_post_menu';
    const LOC   = 'primary'; // the location for twenty twelve

    // snip snip

    public function _setup()
    {
        add_action('add_meta_boxes', array($this, 'addBox'));
        add_action('save_post', array($this, 'save'), 10, 2);
        add_filter('wp_nav_menu_args', array($this, 'switchMenu'));
    }

    // snip snip

    public function switchMenu($args)
    {
        // we can only deal with singular pages
        if (!is_singular()) {
            return;
        }

        $switch = apply_filters(
            'per_post_nav_menus_switch',
            isset($args['theme_location']) && static::LOC === $args['theme_location'],
            $args
        );

        // if we're allowed to switch, the the `menu` argument to
        // the correct menu ID.
        if ($switch) {
            $menu = get_post_meta(get_queried_object_id(), static::FIELD, true);

            if ('-' !== $menu) {
                $args['menu'] = absint($menu);
            }
        }

        return $args;
    }

    // snip snip
}

All of the above as a plugin.

share|improve this answer
    
Wow Chris, thank you so much for taking the time to help! –  user27251 Feb 11 '13 at 16:21

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.