0

I have created a site (livermorevalleyopera.com) for a client. I made a custom post type for events. In the primary navigation I have a top level menu item "Season Events & Tickets" and the submenu items of that are the custom post type events. Right now I am manually adding and removing them via Appearance > Menus, but is there a way to programmatically add and remove them based on the date of the event? I've found lots of mention of adding custom post types dynamically based on user roles, but not to a submenu or added and removed by date.

EDIT: Below is the image of the menu. The top level item is static (Season Events & Tickets) where as all the submenu items below it will be programmatically populated by the titles of the posts within the Events CPT I created and removed programmatically the day after the event's last occurrence. FYI, Each event can happen as often as 4 times, so I have 4 date meta fields per event and a meta field to denote how many dates are in each event, so I can know which date meta fields to cycle through.

Livermore Valley Opera Events Menu

UPDATE 7/25/2019: I was having a problem getting the wp_update_nav_menu_item call to correctly record the nav menu item into the database. I first had to hardcode the $menu_id to the term_id of the primary menu so it would stop recording the menu item as an orphaned item. Then I had to add 'menu-item-type' as post_type, because it kept defaulting to custom and ignoring my $post variables. Below is my code which is a combination of multiple answers I've found to do checks to make sure it only adds the menu item on a new publish of an event in my custom post type.

add_action( 'save_post', 'prefix_update_menu', 10, 3 );
function prefix_update_menu($post_id, $post, $update) {

  $parent_menu_id = false;

  if ('lvo_event' !== $post->post_type) :
    return;
  endif;

  if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) :
      return;
  endif;

  // Check permissions
  if ( 'page' == $post->post_type ) :
    if ( !current_user_can( 'edit_page', $post_id ) ) :
        return;
    endif;
  else :
    if ( !current_user_can( 'edit_post', $post_id ) ) :
        return;
    endif;
  endif;

  if ( false == $update ) :
    return;
  endif;    

  // New post only
  if ( $post->post_date !== $post->post_modified ) {
    return;
  }


  // Get main menu items
  $main_menu_items = wp_get_nav_menu_items( 'primary' );
  if ( ! $main_menu_items ) :
    return;
  endif;

  foreach ($main_menu_items as $menu_items):
    if ("Season Events & Tickets" == $menu_items->title):
        $parent_menu_id = $menu_items->ID;
    endif;
  endforeach;

  if ( false !== $parent_menu_id ) {
      // If you want to create menu items          
      wp_update_nav_menu_item( 2, 0, array(
        'menu-item-object-id' => $post->ID,
        'menu-item-object'    => $post->post_type,
        'menu-item-type'      => 'post_type',
        'menu-item-parent-id' => $parent_menu_id, // not sure if property is correct, please check it yourself
        'menu-item-status'    => 'publish'
      ));
  }

}
  • To clarify, you want the submenu to automatically show the next event(s) based on their date, and once the date passes, they no longer show in the submenu? Can you post a screenshot of what the menu looks like with your manual edits? – aryxus Apr 19 at 19:58
0

Yes, I think it should be possible to add and remove nav menu items programmatically. You can use either wp_nav_menu_objects or wp_nav_menu_items filter to edit a nav menu. Or if you can edit the menu walker, you could add the event items inside it.

With the first filter you could use wp_update_nav_menu_item to create and save new menu items to database. You should be able to remove menu items with wp_delete_post. With the latter you will be dealing with the html of the nav menu so you can just append string of html to the menu.

If the event dates are stored as post meta, you can use custom field WP_Query to get events with future dates.

Here's a very rough example for using wp_nav_menu_objects that I conjured up based on the Codex and the code reference. Hopefully you can use this as a starting point for creating a working solution. This is by no means a working code example and needs tweaking, fine-tuning and maybe some typo fixing.

add_filter( 'wp_nav_menu_objects', 'prefix_wp_nav_menu_objects', 10, 2 );
function prefix_wp_nav_menu_objects( $menu_items, $args ) {
  // Check if correct menu  
  if ( 'my-theme-location' === $args['theme_location'] ) {
    // Loop items
    $time_now = time();
    foreach ( $menu_items as $key => $menu_item ) {
      // Add new items to correct submenu
      if ( $add_item_logic === $menu_item->property_to_compare ) { // add applicable logic
        $new_items = prefix_add_items_menu( $menu_item, $args );
        $menu_items = $menu_items + $new_items; // add new items to the menu item array
      } else if ( $remove_item_logic === $menu_item->property_to_compare ) { // add applicable logic
        $event_date = get_post_meta( $menu_item->db_id, 'event_date', true );
        if ( $event_date && strtotime( $event_date ) < $time_now ) {
          // Delete menu item permanently
          wp_delete_post( $menu_item->db_id ); // check if property is correct
          // Remove from menu item array
          unset( $menu_items[$key] );
        }
      }      
    }

  }

  return $menu_items;
}

function prefix_add_items_menu( $menu_item, $menu_args ) {
  $new_items = array();

  $query_args = array(
    'post_type'      => 'my_cpt_slug', // add correct cpt slug
    'posts_per_page' => 10, // adjust as needed
    'meta_key'       => 'event_date', // add correct meta key if date saved in post meta
    'meta_value'     => date( "Ymd" ), // change to how "event date" is stored
    'meta_compare'   => '>=' // change comparison methof if needed
  );
  $query = new WP_Query( $query_args );

  if ( $query->posts ) {

    foreach ( $query->posts as $event ) {

      // If you want to create menu items          
      wp_update_nav_menu_item( $menu_args['menu_id'], 0, array(
        'menu-item-title'     => $event->post_title,
        'menu-item-object-id' => $event->ID,
        'menu-item-object'    => $event->post_type,
        'menu-item-parent-id' => $menu_item->ID, // not sure if property is correct, please check it yourself
      ));

      // And / or push posts to menu items
      $new_items[] = array(
        'menu-item-title'     => $event->post_title,
        'menu-item-object-id' => $event->ID,
        'menu-item-object'    => $event->post_type,
        'menu-item-parent-id' => $menu_item->ID, // not sure if property is correct, please check it yourself
      );

    }    
  }

  return $new_items;
}

You can also check the related WPSE Q&A's on the matter,

Add items to a menu dynamically

Programmatically add a Navigation menu and menu items

Add items to a menu dynamically

Remove a menu item in menu

EDIT 1 Actually, you could perhaps update the menu on save_post, too, when a new event is published. This might be better way to update the menu now as I think about it more. Doing the update when a new post is published would mean that the update function is run less frequently thus taxing the site less overall.

For removing past events from the menu you could consider adding a custom wp cront event that would run once a day.

function prefix_update_menu( $post_id, $post ) {
  // New post only
  if ( $post->post_date !== $post->post_modified ) {
    return;
  }
  // Get main menu items
  $main_menu_items = wp_get_nav_menu_items( 'menu-id-slug-name-or-object' );
  if ( ! $main_menu_items ) {
    return;
  }

  // 1. foreach Loop menu items
  // 2. when correct submenu is matched add new menu item with wp_update_nav_menu_item
  // 3. optionally also remove passed events while doing the foreach loop
  // get post id from menu item with get_post_meta( $item->ID, '_menu_item_object_id', true );
  // then get event end date and current date for comparison
  // remove menu item with delete_postw p_delete_post( $item->ID );

}
add_action( 'save_post', 'prefix_update_menu', 10, 2 );
  • Thanks for all this info. I don't have time to get into it right now, but a quick scan looks like it's a good base to build from and I will look at it closer soon. – Ryan Apr 23 at 20:04
  • I looked at the code more in depth and appreciate all the effort put into getting me started. One question as I am not very familiar with hooking into the navigation. Would the filter 'wp_nav_menu_objects' run every time a navigation menu is loaded on the site each time a page is loaded? Assuming the site gets hundreds of page views a day that would be hundreds of executions a day? In that case, the action of checking to add a new menu item on saving a post would make more sense. I think the custom cron event once a day for removing events makes sense as well. Thanks so much for the feedback! – Ryan Apr 25 at 17:52
  • @Ryan Yes, I think the filters run on every nav menu load, so the functions might tax the site quite a lot. So hooking into save_post and using a cron event might make more sense indeed. – Antti Koskinen Apr 25 at 20:02
  • Antti Koskinen, thanks for your start. I have used the info you provided to make progress into making a new menu item, but still seem to be running into problems. I updated my post with the code I've come up with and the issues I think I'm having. If you have any thoughts, I'd appreciate it. – Ryan Jul 25 at 19:06
  • 1
    I am no longer stuck, I updated my question and marked your answer as correct as I finally got to a successful end. – Ryan Jul 25 at 22:37

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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