Skip to content

Instantly share code, notes, and snippets.

@tharsheblows
Created January 18, 2020 19:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tharsheblows/47b0c72bff01841d2082d8e55196aa46 to your computer and use it in GitHub Desktop.
Save tharsheblows/47b0c72bff01841d2082d8e55196aa46 to your computer and use it in GitHub Desktop.
Adding conditional menu items to WordPress menus with custom type labels.
<?php
/**
* Conditional menu items and sane ways to add them to menus in the admin area.
*
* @package mjj_menu
*/
class MJJ_Menu_Functions {
/**
* Our new menu types
*
* @var array
*/
public $mjj_menu_types;
/**
* Sets up common variable and adds in hooks.
*
* @return void
*/
public function init() {
// Use mjj-logged-in- as the prefix to check for logged in users.
// Use mjj-subs- as the prefix to check for subscribers.
// The last bit will tell you how to get the permalink from the id.
$this->mjj_menu_types = [
'mjj-logged-in-page' => 'Shows for logged in users',
'mjj-subs-cat' => 'Shows for subscribers',
'mjj-subs-page' => 'Shows for subscribers',
];
// This is what filters the menu items shown on the public facing site.
add_filter( 'wp_nav_menu_objects', array( $this, 'dynamic_menu_items' ), 10, 2 );
// This adds a new metabox in the admin section from which you can choose menu items to add to your menu.
add_action( 'admin_head-nav-menus.php', array( $this, 'add_menu_metabox' ) );
// This handles the new label
add_filter( 'wp_setup_nav_menu_item', array( $this, 'nav_type_labels' ) );
// Re-add the url to the menu item.
add_action( 'wp_update_nav_menu_item', array( $this, 'add_back_url' ), 10, 3 );
}
/**
* Filter out conditional menu items if the user should not see them.
*
* @param array $menu_items The menu items, sorted by each menu item's menu order.
* @param object $menu_args The menu object.
* @return array The final html for the menu.
*/
public function dynamic_menu_items( $menu_items, $menu_args ) {
foreach ( $menu_items as $key => $item ) {
$type = $item->type;
// If a user is not logged in, unset the "mjj-logged-in" type menu items so they don't show in the menu.
if ( strpos( $type, 'mjj-logged-in' ) === 0 ) {
$is_logged_in = is_user_logged_in();
if ( ! $is_logged_in ) {
unset( $menu_items[ $key ] ); // If the user is not logged in, remove the link.
continue;
}
}
// If a user has not bought a subscription, unset the "mjj-subscription-member" type menu items so they don't show in the menu.
if ( strpos( $type, 'mjj-subs-' ) === 0 ) {
$user_id = get_current_user_id();
$is_subscription_user = mjj_user_has_subscription( $user_id ); // Made up function for the purpose of this post.
if ( ! $is_subscription_user ) {
unset( $menu_items[ $key ] );
continue;
}
}
}
return $menu_items;
}
/**
* This takes the object id and object type and returns the correct link.
*
* @param string|int $object_id Will be used as an int. The id of the object the menu item is for.
* @param string $object_type The type of the object the menu item is for.
* @return string The url for the menu item.
*/
private function get_link( $object_id, $object_type ) {
$url = '';
switch ( $object_type ) {
case 'term':
$url = get_term_link( (int) $object_id );
break;
// A note on get_term_link: if you want to use an id, it *must* be an int, otherwise it treats it as the slug.
// See: https://developer.wordpress.org/reference/functions/get_term_link/ .
case 'post':
$url = get_permalink( $object_id );
break;
}
return $url;
}
/**
* Adds the metabox to the nav menu list.
*
* @return void
*/
public function add_menu_metabox() {
add_meta_box( 'mjj_course_conditional_nav_links', __( 'MJJ Conditionals', 'mjjmenu' ), array( $this, 'menu_metabox' ), 'nav-menus', 'side', 'low' );
}
/**
* Output menu metabox.
*
* @return void
*/
public function menu_metabox() {
// A contact page for subscribers only.
$member_contact_page = get_page_by_path( 'member-contact-us', OBJECT );
$one_off_things_to_add = [
'home_on_checkout' => [
'label' => 'Conditional home button for logged in users', // Only logged in people get the home button. Ha.
// Label is used in the checklist.
'title' => 'Home',
'class' => 'mjj-show-logged-in',
'id' => get_option( 'page_on_front' ), // id is the id of the page.
'type' => 'mjj-logged-in-page',
'object-type' => 'post',
// The type. 'mjj-logged-in' tells me it's for logged in users, 'page' tells me how to get the url.
// We'll use id and the type to get the link with get_permalink().
],
'back_to_your_course' => [
'label' => 'Member contact page (shows only for paid subscribers)',
'title' => 'Contact us',
'class' => 'mjj-subscription-members',
'id' => $member_contact_page->ID, // id is the id of the page.
'type' => 'mjj-subs-page',
'object-type' => 'post',
// The type. 'mjj-subs' is the prefix which tells me to limit to subscribers. 'page' tells me how to get the url.
// We'll use id and the type to get the link with get_permalink().
],
];
// This gets a list of categories which are only available to subscribers.
$something = new Something();
$subscription_only_categories = $something->get_protected_categories; // Made up function for illustrative purposes.
// Start with an empty array of them.
$add_sub_cats = [];
foreach ( $subscription_only_categories as $index => $row ) {
$term = get_term( $row->category_id );
$add_sub_cats[ 'cat_id_' . (int) $row->category_id ] = [
'label' => esc_html( $term->name . ' (shows only for paid subscribers)' ),
'title' => esc_html( $term->name ),
'class' => 'mjj-subscription-members',
'id' => $term->term_id, // id is the id of the term.
'type' => 'mjj-subs-cat',
'object-type' => 'term',
// The type. 'mjj-subs' is the prefix which tells me to limit to subscribers. 'cat' tells me how to get the url.
// We'll use id and the type to get the link with get_term_link().
];
}
if ( ! empty( $add_sub_cats ) ) {
// We'll build the menu checklist from the merged arrays.
$things_to_add = array_merge( $one_off_things_to_add, $add_sub_cats );
}
?>
<div id="mjj-conditional-menu-items" class="posttypediv">
<div id="tabs-panel-mjj-conditionals" class="tabs-panel tabs-panel-active">
<ul id="mjj-conditional-checklist" class="categorychecklist form-no-clear">
<?php
$i = -100; // I think this needs to be unique? Fingers crossed no one else is using -100 as a starting point.
foreach ( $things_to_add as $item => $details ) :
?>
<li>
<!-- The label in the checklist *only* -->
<label class="menu-item-title">
<input type="checkbox" class="menu-item-checkbox" name="menu-item[<?php echo esc_attr( $i ); ?>][menu-item-object-id]" value="<?php echo (int) $details['id']; ?>" /> <?php echo esc_html( $details['label'] ); ?>
</label>
<!-- Adds the type to the menu item -->
<input type="hidden" class="item-type" name="menu-item[<?php echo esc_attr( $i ); ?>][menu-item-type]" value="<?php echo esc_attr( $details['type'] ); ?>" />
<!-- Adds the default title to the menu item -->
<input type="hidden" class="menu-item-title" name="menu-item[<?php echo esc_attr( $i ); ?>][menu-item-title]" value="<?php echo esc_html( $details['title'] ); ?>" />
<!-- Adds classes to the menu item -->
<input type="hidden" class="menu-item-classes" name="menu-item[<?php echo esc_attr( $i ); ?>][menu-item-classes]" value="<?php echo esc_html( $details['class'] ); ?>" />
<!-- Adds the object type (*not* custom post type but the type which indicates which table it's in) -->
<input type="hidden" class="menu-item-classes" name="menu-item[<?php echo esc_attr( $i ); ?>][menu-item-object]" value="<?php echo esc_attr( $details['object-type'] ); ?>" />
</li>
<?php
$i--;
endforeach;
?>
</ul>
</div>
<p class="button-controls">
<span class="add-to-menu">
<button type="submit" class="button-secondary submit-add-to-menu right" value="<?php esc_attr_e( 'Add to menu', 'mjjmenu' ); ?>" name="add-post-type-menu-item" id="submit-mjj-conditional-menu-items"><?php esc_html_e( 'Add to menu', 'mjjmenu' ); ?></button>
<span class="spinner"></span>
</span>
</p>
</div>
<?php
}
/**
* This filters the label on the nav item in the list so it says something other than the default "Custom link" for certain item types.
*
* @param object $item The nav menu item object.
* @return object The nav menu item object.
*/
public function nav_type_labels( $item ) {
$types = $this->mjj_menu_types;
if ( ! empty( $types[ $item->type ] ) ) {
$item->type_label = $types[ $item->type ];
}
return $item;
}
/**
* Updates the menu item's url to the correct one.
*
* @param int $menu_id The id of the menu.
* @param int $menu_item_db_id The id of the menu item.
* @param array $args The values which were saved as postmeta and in the menu item's posts entry.
* @return void
*/
public function add_back_url( $menu_id, $menu_item_db_id, $args ) {
$mjj_types = $this->mjj_menu_types;
$item_type = $args['menu-item-type'];
if ( ! empty( $mjj_types[ $item_type ] ) ) {
$object_id = $args['menu-item-object-id'];
$object = $args['menu-item-object'];
$url = $this->get_link( $object_id, $object ); // Private function which gets the correct link depending on the object type and object id.
update_post_meta( $menu_item_db_id, '_menu_item_url', esc_url_raw( $url ) );
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment