DNotes LLC

DNotes LLC

Drupal development, hosting, and consulting

How to change, theme or alter Drupal 7 primary links

Doing some review of D7 code today, detailing how the primary links are made and how they can be altered in modules or themes.

How Drupal 7 primary links are made : code trace

  1. template_preprocess_page(&$variables)
    Sets up the variables that are passed to the theme, generally in page.tpl.php. The primary_links variable is set up by calling...
  2. menu_main_menu()
    ...which is just a wrapper function for...
  3. menu_navigation_links($menu_name, $level = 0)
    ...which gets the tree of menu items at the specified depth by calling...
  4. menu_tree_page_data($menu_name, $max_depth = NULL, $only_active_trail = FALSE)
    ...which checks the current language, depth, etc. and prepares a query for...
  5. menu_build_tree($menu_name, $parameters)
    ...which mainly checks the access on the data returned from...
  6. _menu_build_tree($menu_name, $parameters)
    ...which gets the menu data directly from the menu_links and menu_router tables in the database. These links are passed back all the way through menu_build_tree and menu_tree_page_data, to...
  7. menu_navigation_links($menu_name, $level = 0)
    ...which takes the array of menu items that it got from the functions in 4, 5, and 6, and sets up a renderable array of $links that is returned to...
  8. template_preprocess_page(&$variables)
    ...and passed as the variable $main_menu to the tpl.php file.
  9. page.tpl.php (file)
    The default page.tpl.php, as well as the preprocess function for core themes like Bartik, renders the $main_menu by calling...
  10. theme('links__system_main_menu', array('links' => $main_menu, ...))
    ...which passes links on to theme hook functions, along with a renderable array of variables. If there is an implementation of theme_links__system_main_menu, then that function will be called; if not, it will be the function for theme_links. It is always helpful to review the documentation of Drupal's theme function.

How to alter Drupal 7 primary links : some options

There are several options for changing or altering the main menu, and which one you want depends on what you want to accomplish and whether you are writing a module or a theme.

Register a theme function (module)

A module may register a theme function for theme_links__system_main_menu($variables). This is the approach taken by modules like responsive dropdown menus, which is intended to alter the main menu for any enabled theme. It accomplishes the task by implementing hook_theme() and registering a theme hook for links__system_main_menu, along with a function that will be called in order to theme the menu:

<?php
function responsive_dropdown_menus_theme($existing, $type, $theme, $path) {
  return array(
    // Override the main menu theme.
    'links__system_main_menu' => array(
      'variables' => array('inline' => FALSE),
      'function' => 'responsive_dropdown_menus_main_menu',
    ),
  );
}
?>

The function responsive_dropdown_menus_main_menu($vars) then returns a renderable array for the main menu.

Override a theme function (theme)

A theme may override a the theme function by implementing {themename}_links__system_main_menu($variables). I couldn't quickly find an example of any theme that does this, but it is simple enough, at least in theory. In the theme's template.php file, you create a function to theme the links, which will be passed to your function in the $variables parameter. Here's a hypothetical implementation from a stackexchange question:

<?php
function MYTHEME_links__system_main_menu($variables) {
  $html = "<div>\n";
  $html .= "  <ul>\n";

  foreach ($variables['links'] as $link) {
    $html .= "<li>" . l($link['title'], $link['path'], $link) . "</li>";
  }

  $html .= "  </ul>\n";
  $html .= "</div>\n";

  return $html;
}
?>

Implement hook_preprocess_page to provide variables to the tpl.php files (theme)

A theme may implement hook_preprocess_page(&$vars) to override or create new variables for the page.tpl.php file. This is the approach I've decided to take at with my present theme project: I wanted to implement a drop-down menu in the theme, but despite much discussion Drupal still doesn't do this by default. Working from an example by James Morrish, I used a helper function to clean up the array of all links returned from menu_tree_page_data:

<?php
function incubator_prograde_alpha_preprocess_page(&$vars) {
  $menu = variable_get('menu_main_links_source', 'main-menu');
  $vars['main_menu'] = incubator_prograde_clean_links(menu_tree_page_data($menu, 3));
}

function incubator_prograde_clean_links($links, $level = 1) {
  $result = array();
  foreach($links as $id => $item) {
    if (!$item['link']['hidden']) {

      $new_item = array(
        'title' => '<span>' . check_plain($item['link']['link_title']) . '</span>',
        'link_path' => $item['link']['link_path'],
        'href' => $item['link']['href'],
        'html' => TRUE,
      );
      $new_item += $item['link']['options'];
      $new_item['attributes']['class'][] = "level-$level";

      if (!empty($item['below'])) {
        $new_item['below'] = incubator_prograde_clean_links($item['below'], $level + 1);
      }
      $result[] = $new_item;
    }
  }
  return $result;
}
?>

Conclusion and Unresolved Issues

We've taken a look at how the main menu is determined in Drupal 7, and we've examined three methods of altering the data. Still missing from the final method is a means of determining the active trail, which does not happen with this code, although since Drupal 7.9 it can be set using menu_tree_set_path.

Technologies: 
Drupal Modules: