Sometimes you need code that messes with Drupal's menus. It helps if you know how Drupal stores menu information in the database.
Let's start by looking at a simple, one-level main menu. Suppose I have a Drupal installation, and create a Welcome page. I add the page to the main menu as I am creating it, leaving the weight set to 0, its default:
I do the same for two more pages: Unicorns and Rainbows. Each of them is a node, stored in the database. They're in the
node table. Here's what they look like:
The primary key of the
node table is
nid (node id).
Here is the menu I end up with:
How does Drupal remember what the menu entries are?
Let's look at the HTML Drupal makes for the menu. It's something like this:
Drupal stores the data needed to make the menu in the table
menu_links. Here it is so far:
Each row represents one menu entry. The primary key of the table is
mlid, the menu item's id. The
menu_name field tells Drupal which menu the entry is part of.
Each menu item is rendered as an
<a> tag, as in
link_path field shows the path. The
link_title field shows the link's text.
Remember that the menu looks like this:
The page I created first is at the end of the menu. The page I created next is at the start of the menu. What gives?
The items are in alphabetical order. Unless you tell Drupal otherwise, it shows menu items alphabetically.
Ordering the menu items
Suppose I go to Admin | Structure | Menus | Main menu. I see:
The items are in alpha order, just as they appear in the menu. I drag them around to get:
The main menu looks like this:
Let's see how Drupal stores this ordering information. Here's the
menu_links table again:
Look at the
weight field, over on the right. The new order is shown there.
Items are sorted alphabetically within weight. So, if several items have weights of 0, and several have weights of 1, the ones with the higher weights (1) come later. Within the 0s, Drupal shows the items alphabetically. Within the 1s, Drupal shows the items alphabetically.
The drag-and-drop code I used to rearrange the menu gives each item its own weight. There is only one item with a weight of -48. So there is no need to sort alphabetically within items of the same weight.
So far, we have a table called
menu_links, with one row per menu item. Some of the table's fields are:
mlid: the primary key.
menu_name: the menu an item is in.
link_path: the menu item's path.
link_title: the text of the item.
weight: the order of items in the menu. Items with the same weight are sorted alphabetically.
How can we use our new knowledge? Suppose some nodes have the title "Zombies." We want to make sure that, if there is such a node in the main menu, it is always the first item in the menu.
Let's use a hook, so that the following pseudocode is run just before a node is saved:
if current_node.title = "zombies" then
if current_node.menu_name = "main_menu" then
//Find the lowest weight in the main menu.
lowest_weight = select min(weight) from menu_links where menu_name = "main_menu"
current_node.menu_weight = lowest_weight - 1
That should do it.
Things get more complex when there is a submenu.
Creating the pages
Suppose I add two pages: Good Unicorns, and Evil Unicorns. I add them under the Unicorns item on the main menu:
Rainbows would be, too, if they could.
Notice that I gave a weight for the second one. It should come after the first in the menu.
A block for the submenu
To see the submenu, I'll use the Submenu Tree module. It can put a submenu in a block. Here is what I get when Unicorns is selected:
So, it worked! What does the database look like?
Here it is:
All of the menu items are in
main-menu, as you can see from the first column. The important field is
plid, which shows the
mlid of each item's parent item. Recall the
mlid is the unique id of each menu item.
The first three items are at the top level of the main menu. They have no parent, so their
plid is 0.
The two new items are children of the Unicorns item. Unicorns has an
mlid of 330. So, the
plid of the new items is 330.
Now look at the
weight field on the right. You can see the values I used when creating the two new nodes.
Now for some weird coding. We want a list of all menu items that have a child item that has the word "evil" in it. Don't ask me why.
Here's some pseudocode:
for each row in menu_links
if current_row.link_title contains "evil" then
parent_mlid = current_row.plid
parent_title = select link_title from menu_links where mlid = parent_mlid
That should work.
Drupal keeps most of the data about a site in a database. That includes data about menus.
menu_links table has the text and path of each link. The table's primary key is
mlid. The order of a menu's links depends on the links' weights, and their alphabetical order.
For submenu items, the
plid field stores the parent's