When I create nested menus using the menu API, if expanded is set to 'no' then parent items don't expand when I click on them.
I wrote this detailed report originally for this post, but on finishing it felt it was more suitable as a bug report.
Code that results in the issue:
function mymod_menu($may_cache) {
if($may_cache) {
$items[] = array(
'title' => 'outer',
'path' => 'outer',
'access' => true,
);
$items[] = array(
'title' => 'inner',
'path' => 'outer/inner',
'access' => true,
);
}
return $items[];
}
A few key points that I have been able to establish:
1. Menu data is stored in the 'menu' table. Each menu item has a unique 'mid'. The way Drupal decides which menu items appear under which is not through the value of the 'path' field, but from the 'pid' (parent id) field. This simply contains the mid of the parent menu item.
2. I don't think you can set the pid directly via the menu API. Rather, Drupal infers the pid from the path you provide the API. So, in the example above, for the item 'Inner' Drupal sees the path set to 'outer/inner', looks for a menu item with the path 'outer', makes a note of the mid of that item, and sets it as the pid for the new item. Clearly this causes problems if there are two menu items with the same path.
3. If, as in the example above, you do not specify a type, Drupal appears to set the types as 22 (which is a MENU_NORMAL_ITEM). However, if you do the same thing using the Menu User Interface, it sets both items as 118. More about this later.
4. Using the 'empty cache' link on the Devel module does not refresh the menu table. For example, creating a menu using the API, then emptying the cache, will cause the menu to appear on the site, but not yet in the database. It won't be put into the menu table until you navigate to the admin/build/menu page. So when testing you should always navigate to admin/build/menu (since that empties the cache too, I think).
5. The menu database is linked to the menu admin page and the module in a slightly strange way. If you edit your module code then it does not necessarily update correctly in the menu table. For instance, if you were to rename or change one of the fields it either ignores it or just creates a new menu item on top of the previous one (depending on the field I think). This can cause problems, for example if you rename an API-created parent id then it will create a new item, but all the children will still link to the original mid. Equally, if you delete the code for creating a menu item then it still remains in the database.
So, bearing this in mind here is a little test case to demonstrate what seems to break the expanded menu:
1. Create Page for Upper Menu
Just make a page called 'Outer' with the URL set to 'outer', so we can test clicking on the parent menu item.
2. Create Module
Create a module with the following code in hook_menu():
if($may_cache) {
$items[] = array(
'title' => 'outer',
'path' => 'outer',
'access' => true,
'type' => 118,
);
$items[] = array(
'title' => 'inner',
'path' => 'outer/inner',
'access' => true,
'type' => 118,
);
}
return $items[];
}
You will notice I have set the 'type' to be 118; this is important. I will explain what happens if you set it to 22 throughout these steps.
3. Rebuild Menus
Go to 'admin/build/menu'. This updates everything in the database.
You will see the Outer menu item appear in the navigation menu, with an expanding bullet. However, if you click on it then it doesn't expand to reveal the child item. Inner is, however, shown as the child item in the Menu UI.
This behaviour is the same whether or not you set the 'type' to 118.
4.Remove the module code
This is the key thing that got it working and hopefully will provide more clues. If you now comment out the module code and empty the cache, suddenly everything works! When you click on 'Outer', it expands to reveal 'Inner'. Uncomment the code, empty cache again, and it stops working.
Note that if instead of setting the type to 118, you set it to 22, when you comment out the code the menu vanishes altogether. Uncomment it and it appears again.
Comments
That's as far as I've got at the moment. I don't understand everything but there certainly seems to be some buggy behaviour here (unless I'm abusing the API in some way!) The direction I'm going to go in now is probably to forget about the API and add dynamic items in using db_query().
I may also need to set up the way I hook into it slightly differently. I want to have a menu system based on the existence of certain content types (it's types of furniture, so I want to have menu items with 'Chairs', 'Beds', etc.).
Here's what I currently do:
function mymod_menu($may_cache) {
if ($may_cache) {
//figure out new menu items here
//blah blah blah...
}
}
function mymod_nodeapi(&$node, $op) {
//If there's a change of content, clear the menu cache
if ($op == 'insert' || $op == 'delete' || $op == 'update') {
db_query('TRUNCATE TABLE cache_menu');
}
}
Maybe I'll have to locate my db_query() in the nodeapi hook instead.
Once I've got it working properly I'll post back to show you what works.
Anyway I hope this is helpful - does this make sense to anyone more in the know or are we looking at a bug?
Comments
Comment #1
blakehall commentedpt 5 above is due to menu caching...
I've been able to replicate the lack of expanding menu item as you describe above, but I'd call this misuse of the API.
Since your code about doesn't define a callback a node has to be aliased to the same path (you can see the two menu entries if you unserialize cache_menu after commenting out your code).
As far as I can tell the menu entries defined by your code don't get the "Expanded" flag set (in fact when I tried to set MENU_VISIBLE_IF_HAS_CHILDREN it didn't seem to make a difference either)
I'm marking this "by design" since I think this is a misuse of the API (since the code doesn't define a valid callback).