Drupal Menu Hell(p)

I recently had to implement what seemed like a very simple feature for a client, moving several of the local tasks located on a user's profile page into the site's primary menu. The menu paths in question are dynamic, E.g, /user/%/edit, /user/%/orders, /user/%/notifications, etc., which at first seemed like slight complication. So how to tackle this? At first blush, one might think that you can just use the Menu module to add a dynamic menu item though the GUI or menu API. Well, that won't work. You can only create a menu item that way for an existing path that you have access to. Luckily, Drupal provides a hook that seems like the perfect solution,

<?php
function hook_menu_alter(&$items) {
 
// Example - disable the page at node/add
 
$items['node/add']['access callback'] = FALSE;
}
?>

Great, so I can change the type of the menu items I want to alter by doing something like the code below and problem solved!

<?php
function mymodule_menu_alter(&$items) {
 
$items['user/%user/edit']['type'] = MENU_NORMAL_ITEM;
}
?>

WRONG! At least that's what I realized after much trial and error. Turns out that menu items with wildcards will NOT now show up in the menu tree, and there are no warnings or explanations to that effect. I never found that documented anywhere, only came across it in my trusty copy of Pro Drupal Development, along with following some trails in the code and on another blog post. But that's not where the confusion ends, because menu items with wildcards can appear in the menu tree if the wildcard is a function name that ends with to_arg, e.g., user/%user_uid_optional, and user_uid_optional_to_arg() can be found in user.module. So I'm getting closer, but how do I change those menu items since they don't have one of those nifty to_arg() wildcards? Well, I couldn't think of a way, so in the end, I created my own menu items using user_uid_optional_to_arg() as the placeholder. The code looks something like this,

<?php
function mymodule_menu() {
 
$items['mymodule/orders/%user_uid_optional'] = array(
   
'title' => 'My Orders',
   
'page callback' => '_mymodule_reroute',
   
'page arguments' => array(2, 1),
   
'access callback' => '_mymodule_access_account',
   
'access arguments' => array(2),
   
'type' => MENU_NORMAL_ITEM,
   
'menu_name' => 'primary-links'
 
);
 
$items['mymodule/notifications/%user_uid_optional'] = array(
   
'title' => 'Notifications',
   
'page callback' => '_mymodule_reroute',
   
'page arguments' => array(2, 1),
   
'access callback' => '_mymodule_access_account',
   
'access arguments' => array(2),
   
'type' => MENU_NORMAL_ITEM,
   
'menu_name' => 'primary-links'
 
);
 
$items['mymodule/recurring-fees/%user_uid_optional'] = array(
   
'title' => 'Recurring Fees',
   
'page callback' => '_mymodule_reroute',
   
'page arguments' => array(2, 1),
   
'access callback' => '_mymodule_access_account',
   
'access arguments' => array(2),
   
'type' => MENU_NORMAL_ITEM,
   
'menu_name' => 'primary-links'
 
); 
}

function
_mymodule_util_reroute($user, $tab) {
 
drupal_goto('user/'. $user->uid .'/'. $tab, NULL, NULL);
}
?>

Notice the simple and, in my opinion, hack-ish reroute function that actually directs users to the correct destination. In many ways, this is duplicate code, not resilient in the face of changes in other modules, and the href on the new links doesn't match the final destination. Since in this case these links are only available for authenticated users, I'm not worried about SEO implications. So that's my solution to the simple problem of adding some account related links to the primary menu, and I don't like it one bit (even though I burned way too much time on it!). Are there better approaches? I hope so, feedback welcome!

Same exact problem... Same exact time

We must be working on the same project. I had the same problem yesterday. I am using the Content Profile" module and the client wanted a "My Profile" link in the primary links. Absolutely beat head against the wall yesterday. Ended up using a similar hack, it feels dirty but it functions properly. I created a menu item in the GUI that points to /my-profile with the code below. I wanted the menu item to change text based on whether the user had a profile or not. Your solution may work for that. Can you use if statements inside the menu hook?

function mymodule_menu() {
  $items["my-profile"] = array(
    'title'            => 'My Profile',
    'description'      => 'Builds profile link and send user to it.',
    'page callback'    => 'mymodule_my_link',
    'access arguments' => array('access content'),
    'type'             => MENU_CALLBACK,
  );
  return $items;
}

function mymodule_my_link(){
  global $user;
  $profile_node = content_profile_load('profile', $user->uid);
 
  if ($profile_node->nid) { 
    drupal_goto("node/". $profile_node->nid);
  }
  else {
    drupal_goto("node/add/profile");
  }
}

Can you use if statements

Can you use if statements inside the menu hook?

No, but you can use a callback for the menu title and handle your conditional logic in there.

This helped me out

Thanks, this really helped me out!

would me aliases work?

Perhaps I'm misunderstanding your problem, but would the "Me Aliases" module work? http://drupal.org/project/me

It creates an alias for the current user's UID and lets you use that alias in menu paths. The default is "me" but you can change it to whatever you like.

You understand correctly, and

You understand correctly, and it looks like that module would have worked for my needs, thanks for pointing it out. It still would have been difficult to customize to the project needs, though, and I'm not sure how that module handles permissions. Good learning experience in any case!

I used the Me Aliases module

I used the Me Aliases module as well. Worked perfectly.

Hack the menu in the theme layer

I usually hack this in at the theme layer, but this is a much tidier and more scalable solution. Nice! And thanks! :)

Happy you found it useful, my

Happy you found it useful, my pleasure! It is possible to hack apart the menu arrays in the theme layer, but that's obviously not a resilient solution, E..g, your solution might not work if the order, hierarchy, or path of any of the menu items change.

I think I've done it this

I think I've done it this way:

implement hook_menu() in a custom module and create the exact same menu items, just change the 'type' key (or whatever else you're trying to overwrite). As long as your module runs after the one you want to overwrite (use weights in the system table if necessary) your item will overwrite as expected.

Thanks Dalin, that would work

Thanks Dalin, that would work as well, although you'd have adjust the weights in hook_install() and would run into problems if original module changed it's menu implementation at all. Of course, even this approach depends on the original menu paths not changing.