I have several menus (5 in all) each of which has a fixed number of items added by our content management team. One menu in particular, needs to have one item added programmatically because it's only under specific circumstances in which this is needed.

Also, I need to change the link path of an existing menu item, based on whether a user is logged in or not. Basically the last link in a menu is for a customer portal. When a user is not logged in, my manager wishes to have a user shown a marketing page/form, whereas once they are logged-in, they should be shown the actual customer portal page, which is a module itself.

What Drupal hooks do I need to essentially 'intercept' pre-rendering of menus, so I can change the URI right before output??? Most hooks for menus seem to allow interception of reading and saving of menu items, which is not what I need is it???

Cheers,
Alex

Comments

jaypan’s picture

Rather than changing the URL, you can set the access of the menu item in hook_menu() in order to determine whether or not the person should be able to see the item or not. For example:


function my_module_menu()
{
  $menu['my_path'] = array
  (
    'title' => 'Some title',
    'page callback' => 'my_page',
    'access callback' => 'my_access_callback',
  );
  return $menu;
}

// The access callback function:
function my_access_callback()
{
  return user_is_anonymous();
}

With the above configuration, the module will return true if the user is anonymous, and the menu link will show. It will return false if the user is logged in, and the menu link will not be rendered.

Contact me to contract me for D7 -> D10/11 migrations.

GarbageXAY1979’s picture

Greetings, thank you for the quick reply. :)

EDIT | When I say menu, I mean a visual navigation menu, not the URI router - I can't stand that about Drupal - inconsistent nomenclature causes so much confusion for newbies to teh CMS such as me. :p LOL

I am not sure if this solution will work, I need the menu item shown at ALL times, but the link needs to differ, based on whether the user is logged in or not - in fact - ideally the conditions would be a little more verbose - like whether they have active workorders with us or not as well would best.

That being said, how do I apply this hook to work on only one menu? Our site consists of 5 sub-groups (Aerospace, Distribution, Coatings, etc, etc). Each division has it's own menu, this hook should only apply to aerospace. Other divisions will require their own modules, etc.

The menu item is already in place (I can remove it from the DB and just add the item to the end of the menu array stack pre-rendering - although what if I wanted the menu item to appear first or second not last???). Ideally we keep it that way, and simply use the module hooks to change the URI which the menu points based on some criteria above???

P.S-Perhaps this is the function I want to post-hook if possible??? (menu_navigation_links) it returns an array of menu items for each menu in our system PERFECT but I don't want to code the filter in the template where this function is called - ideally I write a custom module which hooks this function at post, checks the ID to ensure it's only done for Aerospace and voila!!! Possible???

Cheers,
Alex

jaypan’s picture

The solution I gave above will do what you want. Links are only rendered if the user has permission to see them. As such, you don't change the URL, you create two links, one for each state, and then set the access conditions. For example:

function my_module_menu()
{
  $menu['logged_in'] = array
  (
    'title' => 'Logged In',
    'page callback' => 'my_logged_in_page',
    'access callback' => 'my_logged_in_access_callback',
  );
  $menu['logged_out] = array
  (
    'title' => 'Logged Out',
    'page callback' => 'my_logged_out_page',
    'access callback' => 'my_logged_out_access_callback',
  );
  return $menu;
}

// The access callback function:
function my_logged_in_access_callback()
{
  return !user_is_anonymous();
}
function my_logged_out_access_callback()
{
  return user_is_anonymous();
}

With the above set up, only one of the links will ever be rendered. The link to /logged_in will be rendered when the user is logged in, and the link to /logged_out will be rendered if the user is logged out.

Contact me to contract me for D7 -> D10/11 migrations.

GarbageXAY1979’s picture

Ok cool, that should work, but help me understand, how do these functions know which menu to apply these conditions to?

Thanks for your help, Drupal can be very frustrating for a newbie like me :)

Cheers,
Alex

jaypan’s picture

I'm not too sure how to respond, as your question doesn't fully make sense.

How have you created the menu links in the first place?

Contact me to contract me for D7 -> D10/11 migrations.

GarbageXAY1979’s picture

I'll try and explain. I apologize I know it's frustrating working with a newbie :)

The site has 5 menus, each with about a dozen entries. The menu of interest in this case, is a menu called aerospace (division of the company).

Aerospace has 10 items and the last item (already in place) is one called "Customer Portal". In case it wasn't clear, by menu, I mean visible navigation menu, like those typically found on the left hand side of a web site.

This is the code I use in my template to echo out the navigation menus, according on the division that is selected (pages are assigned a single taxonomy term which set the menu to load)

echo theme('links', menu_navigation_links('menu-'.strtolower($division), 0));

I just passed the menu_navigation_links() results to a customer function and dumped to log file and the result is almost exactly what I need, with two issues:

1. The array structure looks like this

Array
(
    [menu-990] => Array
        (
            [attributes] => Array
                (
                    [title] => Aerospace - Customer Login
                )

            [href] => node/63
            [title] => Customer Portal
        )

)

Which is awesome but the nodes are not yet translated so I assume if I set the menu href to the proper URI based on whether user is logged in, will lause errors with Drupal when it attempts to translate the node ID into a SEF URI.

2. Secondly, I have to manually call each of the custom functions passing them the result the menu_navigation_links() not a biggie just not as elegant as I would have liked.

So to answer your question directly, yes the menu items are already in place, they offer great default behavior (which is why the content editors did it that way). Now however, they want a more dynamic solution, so the 'Portal' link points to the actual data they seek not a marketing page for anonymous users.

Cheers,
Alex

graysadler’s picture

Sounds like you need to apply the 'menu_name' attribute in hook_menu: http://api.drupal.org/api/drupal/developer--hooks--core.php/function/hoo...

<?php
function my_module_menu()
{
  $menu['logged_in'] = array
  (
    'title' => 'Logged In',
    'page callback' => 'my_logged_in_page',
    'access callback' => 'my_logged_in_access_callback',
    'menu_name' => 'menu-990',
  );
  $menu['logged_out'] = array
  (
    'title' => 'Logged Out',
    'page callback' => 'my_logged_out_page',
    'access callback' => 'my_logged_out_access_callback',
    'menu_name' => 'menu-990',
  );
  return $menu;
}

// The access callback function:
function my_logged_in_access_callback()
{
  return !user_is_anonymous();
}
function my_logged_out_access_callback()
{
  return user_is_anonymous();
}
?>

Lead Developer and Founder of StreamRiot.com

jaypan’s picture

Ok. While the method you are using to create links works, it's really not the best method as any changes require changes to the template. Drupal's system is set up so that menus can be changed through the admin interface, which means that files don't need to be changed.

What you should do is go to admin -> site building -> menus, and create a menu there (admin interface), then add links to the pages in question. Create a region in your template for the menu to be rendered (template), and then put your menu block in that region (admin interface).

Next. To set the access callback of the menus, you have two options:
1) Change the menu definition using hook_menu_alter(). This allows you to alter the defined menu items. In this you can replace the defined access callback with your own, adding whatever conditions you want (as I showed in the previous posts).
Advantages: The items are not accessible to users who shouldn't be able to access them, even if they directly go to the link.
Disadvantages: Has to be done through code (though only once).
2) Use the menu_per_role module. This module allows you to be able to set visibility of menu items based on role (including anonymous and authorized users).
Advantages: Can be done through the admin interface
Disadvantages: Doesn't actually prevent access to the pages, rather just prevents the links from being rendered in the menu. May also not have enough granularity depending on the conditions under which you want the menu items to be rendered (or not).

To go back to your earlier question:

how do these functions know which menu to apply these conditions to?

By setting the access callback, the links will not be rendered in any menu that is created through the admin interface (see the top of this post). Drupal automatically checks whether users have permission to view the destination page before rendering the item, and if they don't, it won't register the link. This itself is one of the main reasons to use Drupal's menu system, rather than bypassing it as you have by directly outputting the menu in your template.

I hope this all makes things a little clearer!

Contact me to contract me for D7 -> D10/11 migrations.

GarbageXAY1979’s picture

What you should do is go to admin -> site building -> menus, and create a menu there (admin interface), then add links to the pages in question. Create a region in your template for the menu to be rendered (template), and then put your menu block in that region (admin interface).

I'm not sure how I gave the the impression otherwise, but this is how we manage menus. The only thing done in the template is to determine which menu should be shown based on category of a given page (categories map to company divisions; Aerospace, Distribution, Flyting, etc). Menu items are entirely managed by content editors through the admin interface.

Setting up another module or two sounded waaaay to complex and introduced yet another learning curve when I know how to solve the problem programmatically, so I opted for a simple solution:

function workorder_menu_navigation_links($items)
{
foreach($items as &$field){
if($field['href'] == 'node/63'){
if(!user_is_anonymous()){
$field['href'] = 'node/2';
}
}
}

return $items;
}

Inside my template I pass the results of menu_navigation_links() to the method above which then simply modifies the the node reference accordingly.

The downside to this approach, is when a new division needs a dynamic menu like aerospace I will need to add the code to the above function or conditionally test which filter needs be applied based on category/division and call the function accordingly. Minimal really, and more explicit to new developers than going a more Drupal way, IMO.

Thanks for all the input though much appreciated :)

Cheers,
Alex

dianacastillo’s picture

this is how to change the url link on an existing menu item , it may be useful to someone since i couldnt find it documented. for D9

$url = "http://www.mynewlink.com";

$menu_link_storage = \Drupal::entityTypeManager()->getStorage('menu_link_content');

$menu_link = $menu_link_storage->loadByProperties(['menu_name' => 'main','title' => 'My Item Title']);

$first_link = $menu_link[1];

if ($first_link) {

  $first_link->link = array('uri' => $url);

  $first_link->save();

}

Diana Castillo