Expanding Menus don't work with Menu API

seddonym - November 23, 2007 - 18:10
Project:Drupal
Version:5.3
Component:menu system
Category:bug report
Priority:normal
Assigned:Unassigned
Status:active
Description

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:

<?php
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():

<?php
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:

<?php
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?

 
 

Drupal is a registered trademark of Dries Buytaert.