Programmatically creating menu items in Drupal

  • strict warning: Non-static method view::load() should not be called statically in /home/tomkaname/jan.tomka.name/sites/all/modules/views/views.module on line 906.
  • strict warning: Declaration of views_handler_field_comment::init() should be compatible with views_handler_field::init(&$view, $options) in /home/tomkaname/jan.tomka.name/sites/all/modules/views/modules/comment/views_handler_field_comment.inc on line 49.
  • strict warning: Declaration of views_handler_filter::options_validate() should be compatible with views_handler::options_validate($form, &$form_state) in /home/tomkaname/jan.tomka.name/sites/all/modules/views/handlers/views_handler_filter.inc on line 607.
  • strict warning: Declaration of views_handler_filter::options_submit() should be compatible with views_handler::options_submit($form, &$form_state) in /home/tomkaname/jan.tomka.name/sites/all/modules/views/handlers/views_handler_filter.inc on line 607.
  • strict warning: Declaration of views_handler_filter_node_status::operator_form() should be compatible with views_handler_filter::operator_form(&$form, &$form_state) in /home/tomkaname/jan.tomka.name/sites/all/modules/views/modules/node/views_handler_filter_node_status.inc on line 13.
  • strict warning: Declaration of views_plugin_row::options_validate() should be compatible with views_plugin::options_validate(&$form, &$form_state) in /home/tomkaname/jan.tomka.name/sites/all/modules/views/plugins/views_plugin_row.inc on line 134.
  • strict warning: Declaration of views_plugin_row::options_submit() should be compatible with views_plugin::options_submit(&$form, &$form_state) in /home/tomkaname/jan.tomka.name/sites/all/modules/views/plugins/views_plugin_row.inc on line 134.
  • strict warning: Non-static method view::load() should not be called statically in /home/tomkaname/jan.tomka.name/sites/all/modules/views/views.module on line 906.
  • strict warning: Declaration of views_handler_filter_boolean_operator::value_validate() should be compatible with views_handler_filter::value_validate($form, &$form_state) in /home/tomkaname/jan.tomka.name/sites/all/modules/views/handlers/views_handler_filter_boolean_operator.inc on line 159.
  • strict warning: Non-static method view::load() should not be called statically in /home/tomkaname/jan.tomka.name/sites/all/modules/views/views.module on line 906.
  • strict warning: Declaration of calendar_plugin_display_page::options_submit() should be compatible with views_plugin_display_page::options_submit(&$form, &$form_state) in /home/tomkaname/jan.tomka.name/sites/all/modules/calendar/includes/calendar_plugin_display_page.inc on line 297.
  • strict warning: Declaration of calendar_plugin_display_page::options() should be compatible with views_object::options() in /home/tomkaname/jan.tomka.name/sites/all/modules/calendar/includes/calendar_plugin_display_page.inc on line 297.
  • strict warning: Declaration of calendar_plugin_display_block::options() should be compatible with views_object::options() in /home/tomkaname/jan.tomka.name/sites/all/modules/calendar/includes/calendar_plugin_display_block.inc on line 78.
  • strict warning: Declaration of calendar_plugin_display_attachment::options_submit() should be compatible with views_plugin_display_attachment::options_submit(&$form, &$form_state) in /home/tomkaname/jan.tomka.name/sites/all/modules/calendar/includes/calendar_plugin_display_attachment.inc on line 242.
  • strict warning: Declaration of calendar_plugin_display_attachment::options() should be compatible with views_object::options() in /home/tomkaname/jan.tomka.name/sites/all/modules/calendar/includes/calendar_plugin_display_attachment.inc on line 242.
  • strict warning: Declaration of views_handler_argument::init() should be compatible with views_handler::init(&$view, $options) in /home/tomkaname/jan.tomka.name/sites/all/modules/views/handlers/views_handler_argument.inc on line 744.
  • strict warning: Declaration of date_api_argument_handler::init() should be compatible with views_handler_argument::init(&$view, &$options) in /home/tomkaname/jan.tomka.name/sites/all/modules/date/includes/date_api_argument_handler.inc on line 398.
  • strict warning: Declaration of calendar_view_plugin_style::options() should be compatible with views_object::options() in /home/tomkaname/jan.tomka.name/sites/all/modules/calendar/includes/calendar_view_plugin_style.inc on line 173.

Menu breadcrumbs

Drupal breadcrumbs implementation is a never-ending source of frustration for many website developers. A few modules exist which more or less successfully attempt to get Drupal to display meaningful breadcrumbs on every page.

One of these modules is menu_breadcrumb, which displays the breadcrumb trail based on current node's menu path. Very useful and sufficient in most use cases.

Limitations and automatic menu modules

The only problem with menu_breadcrumb is when you're not happy to grant users the administer menu permission to create and assign menu items for the content they create.

Several Drupal modules exist which work around the permission restriction and automatically create menu items for newly created content nodes. To name a few, Auto Menu and Automatic Menu, both of which are either only available for Drupal 5.x, or their Drupal 6.x implementation is still in beta development stage.

Rules and Drupal API

Fortunately, a quick and easy way to create menu items programmatically doesn't even require a standalone module. All we're going to need is the Rules module and a bit of Drupal API knowledge.

The Rules set of modules allows site administrators to define conditionally executed actions, such as arbitrary PHP code execution, based on occurring events, such as node creation. For the Drupal API part, we'll use a single function menu_link_save.

menu_link_save requires an item configuration parameter, whose key names mirror the column names of Drupal's menu_links database table.

The solution

As mentioned above, we're going to use the Rules module. It's installation is pretty straightforward and requires no additional configuration. Let's move right to the setting up our rule.

Say we want to create a menu item under Primary Links menu's Blog item every time a new node of type Story is created.

Navigate your browser to Administer » Rules » Triggered rules page, add a new rule and configure it along the lines of the image:

Screen-shot of Drupal Rules' configuration screen of a rule which would execute custom PHP code at each creation of a node of type Story

Finally, we're getting to the PHP snippet itself, which we'll use to automatically create a new menu item every time this rule is triggered. Click Execute custom PHP code and insert the following fragment of code:

$item = array(
  'link_path' => 'node/' . $node->nid,
  'link_title' => $node->title,
  'plid' => 426,
  'hidden' => 1
);
menu_link_save($item);

Just a few remarks on otherwise pretty simple piece of code.

  • menu_link_save function will only accept normalized path as a link path. When using anything nontrivial for the path, such as the loaded node's path property, always use drupal_get_normal_path() to normalize its value!
  • menu_name is the the machine-readable name of the menu. You can get that from Administer » Site building » Menus page as the last component of the menu customization URL.
  • Value of plid is the menu link id of the item you want your item to be a child of. Again, easy to find out, just navigate your browser to the menu's List items page and figure out the menu link id of each menu item from its edit link URL.
  • It's up to you whether you want the menu item hidden or displayed by default. I'm using the automatically created menu items just to give menu_breadcrumb a when putting together the current page's breadcrumb trail. I don't want the menu items to be visible to the users, that's why i set the hidden value to 1.

Comments

Thanks, This was really

Thanks,

This was really helpful. Is there any way to also generate an automatic url alias?

Cheers
Rod

PathAuto is usually the way

PathAuto is usually the way to go when it comes to automatic aliases.

A more low-level approach is to implement functions custom_url_rewrite_inbound and custom_url_rewrite_outbound. You might want to have a look at Drupal Coder's How to create URL aliases in Drupal without path module.

Module URL Alter creates hooks for the aforementioned functions so that modules can implement custom own URL rewrite rules. This has been integrated into the core in Drupal 7.

well done with this

well done with this example,
clearly written and clever solution
Thanks

Thanks! This helpt me a lot!

Thanks!
This helpt me a lot!

Thanks, it was useful for me

Thanks, it was useful for me too.

I need set as plid the mid of a referenced node (via nodereference), which is different for each menù item I create.

Is there a way to find the mid from the nid?

Any thoughts on this?

I'm glad you liked it. What

I'm glad you liked it.

What you would most probably want to do is to find the menu link id of the item pointing to the node with the given nid.

You'd change the above code to something roughly along these lines:

// Get the referenced node's nid
$nid = $node->field_noderef[0]["nid"];
 
// Get the menu item linking to it
$row = db_fetch_array(db_query("
  SELECT mlid
  FROM {menu_links}
  WHERE link_path = 'node/%d'
  AND menu_name = '%s'
"), array($nid, 'primary-links'));
 
// Set it as the parent for the new menu link
$item = array(
  'link_path' => 'node/' . $node->nid,
  'link_title' => $node->title,
  'plid' => $row["mlid"],
  'hidden' => 1
);
menu_link_save($item);

Hi, I tried to do what you

Hi,
I tried to do what you explained here, but I got stuck when I couldn't find the option 'Execute custom PHP code' for an action. Which version of the Rules module are you using? I got the latest one at this date which is 6.x-1.3

The only options I have that might be similar are
'Execute a VBO programmatically on %', % being term, file, comment, node or user.

Do you have any suggestions how to solve this?

Make sure that you have

Make sure that you have enabled the "PHP filter" module in admin/build/modules. In Drupal 6 it is disabled by default.

Im getting very strange

Im getting very strange results. Ive followed the instructions and set the plid to have a parent.

However my node is added to the top level of the menu. When I go to admin/build/menu-customize/primary-links a menu item called (disabled) has been created.

Check the plid and that the

Check the plid and that the parent actually belongs to the particular menu. Sounds like the menu system creates the menu item like instructed no problem. But it refuses to display it linked to a parent in a different menu.

That's just a guess, though. Check it and provide me with more information if that's actually not the problem.

Ive created a fresh drupal

Ive created a fresh drupal install to test this. I have a triggered rule where the event is 'After saving new content'. Im just trying to get nodes into the primary links menu, so ive left out the plid. This isnt doing anything for me:

$item = array(
'link_path' => 'node/' . $node->nid,
'link_title' => $node->title,
'menu_name' => primary-links,
'hidden' => 1
);
menu_link_save($item);
Thanks

The menu_name is supposed to

The menu_name is supposed to be a string, you're actually lucky if it isn't doing anything. ;-)

Also, if fixing that doesn't help, enable the Debug rule evaluation (admin/rules/settings) to make sure the rule actually gets triggered and executed.

How do I find out what the

How do I find out what the string it? Thanks

It's the Menu name field from

It's the Menu name field from the Add menu admin page. It's part of the URL of each Edit menu page. Or, you can retrieve it from database:

SELECT menu_name FROM menu_custom;

I was searching in Google for

I was searching in Google for a solution I am setting up a kind of shopping cart for construction products at http://www.datacentredesign.co/arch-requests.php I used the Rules Module and this part works just fine thanks for the information.

This was really helpful.

This was really helpful. Module URL Alter creates hooks for the aforementioned functions so that modules can implement custom own URL rewrite rules. This has been integrated into the core in Drupal 7.

Hi, I'm working with Drupal7,

Hi, I'm working with Drupal7, and I've followed your instructions to add an hidden item to Main Menu after creating and updating o node.
I've installed Rules Module, I've created a new rules and activate "Debug rules evaluation", but even in Report Log page I can't see anything.

This is my custom php code

$item = array(
'menu_name' => 'main-menu',
'link_path' => 'news/' . $node->nid,
'link_title' => $node->title,
'mlid' => 0,
'plid' => 452,
'hidden' => 1
);
menu_link_save($item);

menu_cache_clear_all()

but it doesn't work.
What I'm doing wrong? Can you help me?

When using anything

When using anything nontrivial for the path, such as the loaded node's path property, always use drupal_get_normal_path() to normalize its value!

Try

'link_path' => 'node/' . $node->nid,

or
'link_path' => drupal_get_normal_path('news/' . $node->nid),

Thanks!!!! It works!

Thanks!!!! It works!

what would you put for

what would you put for creating a top level main menu parent item, would you include link_path or exclude it. If including it, what would the code be(just [ 'link_path'=> .$node>nid, ] )? And the plid would not be necessary, correct? I am not a coder, so I could use some help. Thank you so much for your post, it's huge help!

This was really helpful.

This was really helpful. Module URL Alter creates hooks for the aforementioned functions so that modules can implement custom own URL rewrite rules. This has been integrated into the core in Drupal 7. Thank you.

I think this is a nice idea.

I think this is a nice idea. I have a triggered rule where the event is 'After saving new content'. Im just trying to get nodes into the primary links menu, so ive left out the plid. This has been integrated into the core in Drupal 7. Thank you.

thx also! in order to also

thx also!

in order to also adjust an existent menu item i came up with the following solution.
of course you need to add the event "After updating existing content".

$tMenu_name = explode( ':', $node->menu['parent']);
$menu_name = $tMenu_name[0];

$mlid = $node->menu['mlid'];

$item = array(
'link_path' => 'node/' . $node->nid,
'link_title' => $node->title,
'mlid' => $mlid,
'menu_name' => $menu_name,
'hidden' => 1
);

menu_link_save($item);

i forgot... the menu_name

i forgot...

the menu_name i'am using is the default menu you can assign a content-type to

Jan, many thanks for an

Jan, many thanks for an elegant solution.

If it helps anyone, here is the code snippet I used in Drupal 7.12:

$item = array(
'menu_name' => 'main-menu',
'link_path' => 'node/'.$node->nid,
'link_title' => $node->title,
'plid' => 486
);
menu_link_save($item);
menu_cache_clear_all();

...where the plid value was obtained by following Jan's instructions above.

I chose the "After saving new content" event, and added a "Content is of type" condition to apply the rule only to my News content type. All worked perfectly, and nodes picked up their pathauto alias as usual... including nodes created by the Feeds module from a RSS feed. Sweet.

Something to watch out for: at first I was trying to use one of the events offered by the Feeds module; but they all run *before* the item is created, so I kept getting "Notice: Undefined property: stdClass::$nid in eval()" in the dblog, before I figured that the node didn't yet exist when the rule was being run - doh :)

Thanks again sir!
DCh

This works great! I've added

This works great!

I've added a field call "Menu Entry Title" so that the user can customize the title value of the link in the menu. How would I call that field's value? I tried:

'link_title' => $node->field_menu_entry_title,

but the link just shows up as "Array"

Thanks!

It. Just. Works!!! I love it,

It. Just. Works!!! I love it, thanks a heap for sharing!!!

Works fine with Drupal 7

Works fine with Drupal 7

Hi, How would you add a #

Hi,
How would you add a # tag, for example, to a menu link?

An old tip, but stil usefull!

An old tip, but stil usefull! Thanks for taking the time to share your knowledge with us, mere mortals ;-)

Paiguebeize <a

Paiguebeize <a href="http://vikaswieier.com">xaikalitag</a> Dessstink http://usillumaror.com - iziananatt SIPHAGANNUAGO http://gussannghor.com SonStypeJen

Paiguebeize <a

Paiguebeize <a href=http://vikaswieier.com>xaikalitag</a> Dessstink http://usillumaror.com - iziananatt SIPHAGANNUAGO http://gussannghor.com SonStypeJen