Dynamic Menus

Disclaimer

Technically menu content isn't a theming issue per se, but this chapter seems like the best fit for the topic.

The Problem

Unfortunately, it's impossible to create a Drupal menu item that doesn't link to a page. Consider a hierarchical menu like ours:

  • Home
  • How to Begin
    • Recreational
    • The Aerobatic Box
    • Aerobatic Schools
    • etc.
  • Programs
    • Local Chapters
      • Chapter Locator
      • Chapter Resources
      • Judges Schools
        • Calendar
        • Courses
        • Exams
      • etc.
    • etc.
  • etc.

What should the user see when they click on How to Begin? We could (and did) create a general page about how to get started in aerobatics. Unfortunately such pages are easily overlooked by users who don't expect a top- or mid-level menu item to be associated with a page. Many other web sites present a page consisting of links to the various sub-pages (RecreationalThe Aerobatic Box, etc.), and that's the approach we've decided to take.

It would be easy enough to manually build a page consisting of the next level of links, but that duplicates information that's present in the menu structure itself. And any future menu changes would require manual edits to the link page, lest it get out of sync with the menu. Therefore this is a job for PHP.

The Solution

I created a "Basic Page" node named menu-links that automatically reads the menu contents and builds a corresponding page of sub-links. It takes a URL parameter in the form m=<Menu Item Title>, where <Menu Item Title> must exactly match the menu item itself, e.g. http://www.iac.org/menu-links?m=How to Begin.  Important: No two menu top- or mid-level menus may have the same title because the PHP code displays the sub-links for the first match it finds.  However it's OK to duplicate titles for menu items that have no children (aka "leaf nodes".)

Here's the PHP code. I'll let the embedded comments speak for themselves.

<?php

// Do nothing unless URL parameter 'm' is supplied, e.g. www.iac.org/menu-links?m=Programs
if (!isset($_GET['m'])) return;

// Get the menu link ID associated with the menu named in the 'm' URL parameter
// Note: We could error-check here, but we don't
$res = db_query("SELECT mlid FROM menu_links WHERE menu_name = 'main-menu' AND link_title = :title LIMIT 1", array(':title' => $_GET['m']))->fetchObject();

// Get the paths to the menu items whose parent is the menu link ID we extracted in the previous query,
// sorted by weight (so the order of the bullet list items will match the menu)
$r2 = db_query("SELECT link_path, link_title FROM menu_links WHERE plid = :mlid ORDER BY weight", array(':mlid' => $res->mlid));

// Modify the page ititle
drupal_set_title("Menu: " . $_GET['m']);

// Start the bullet list
print "<ul>\n";

// Loop through the menu items
foreach ($r2 as $item) {

  // Link paths are in the form "node/nnnn", where nnnn is a node number (nid).
  // Split the path at the slash
  $pes = explode('/', $item->link_path);

  // Find the node title
  if ($pes[0] == "node") {    // internal link, use node title
    $t = db_query("SELECT title FROM node WHERE nid = :nid LIMIT 1", array(':nid' => $pes[1]))->fetchObject();
    $t = $t->title;
  } else {   // external link, use menu title
    $t = $item->link_title;
  }

  // Find the node alias
  $a = db_query("SELECT alias FROM url_alias WHERE source = :path LIMIT 1", array(':path' => $item->link_path))->fetchObject();

  // Generate a bullet item consisting of the node title, which is hyperlinked to the page
  // Note that some links (e.g. external pages) don't have an alias, hence the "ternary if" statement
  print "<li><a href='" . ($a ? $a->alias : $item->link_path) . "'>" . $t . "</a></li>\n";
   
}

// Close the bullet list
print "</ul>\n";

?>