I ended up writing my own "menu_tree_full_data" function that builds the menu, still using all the appropriate cache facilities, etc. Also take note any Drupal Core developers around: this is how you write a function that isn't incomprehensibly nested 9 blocks deep.
function menu_tree_full_data($menu_name) {
$tree = &drupal_static(__FUNCTION__, array());
// Check if the active trail has been overridden for this menu tree.
$active_path = menu_tree_get_path($menu_name);
// Generate a cache ID(cid) specific for this page
$item = menu_get_item($active_path);
$cid = "links:$menu_name:full:{$item['href']}:{$GLOBALS['language']->language}";
// Did we already build this menu during this request?
if(isset($tree[$cid]))
return $tree[$cid];
// If the static variable doesn't have the data, check {cache_menu}.
$cache = cache_get($cid, 'cache_menu');
if($cache && isset($cache->data)) {
$tree_params = $cache->data;
if(isset($tree_params))
return $tree[$cid] = menu_build_tree($menu_name, $tree_params);
}
$tree_params = array(
'min_depth' => 1,
'max_depth' => null,
);
// Parent mlids; used both as key and value to ensure uniqueness.
// We always want all the top-level links with plid == 0.
$active_trail = array(0 => 0);
// Find a menu link corresponding to the current path. If $active_path
// is NULL, let menu_link_get_preferred() determine the path.
$active_link = menu_link_get_preferred($active_path, $menu_name);
// The active link may only be taken into account to build the
// active trail, if it resides in the requested menu. Otherwise,
// we'd needlessly re-run _menu_build_tree() queries for every menu
// on every page.
if(@$active_link['menu_name'] == $menu_name) {
// Use all the coordinates, except the last one because there
// can be no child beyond the last column.
for($i = 1; $i < MENU_MAX_DEPTH; $i++) {
if($active_link['p' . $i])
$active_trail[$active_link['p' . $i]] = $active_link['p' . $i];
}
}
$parents = $active_trail;
do {
$result = db_select('menu_links', NULL, array('fetch' => PDO::FETCH_ASSOC))
->fields('menu_links', array('mlid'))
->condition('menu_name', $menu_name)
//->condition('expanded', 1)
->condition('has_children', 1)
->condition('plid', $parents, 'IN')
->condition('mlid', $parents, 'NOT IN')
->execute();
$num_rows = FALSE;
foreach($result as $item) {
$parents[$item['mlid']] = $item['mlid'];
$num_rows = TRUE;
}
} while($num_rows);
$tree_params['expanded'] = $parents;
$tree_params['active_trail'] = $active_trail;
// Cache the tree building parameters using the page-specific cid.
cache_set($cid, $tree_params, 'cache_menu');
// Build the tree using the parameters; the resulting tree will be cached by _menu_build_tree().
return $tree[$cid] = menu_build_tree($menu_name, $tree_params);
}
Aside from changing the cache key, commenting out the expanded
condition, dropping the no-longer-relevant $max_depth
and $only_active_trail
arguments, and the branching on $item['access']
(whether the user can access current page), this is logically equivalent to menu_tree_page_data
. 4 of the extra 5 levels of nested code blocks could be just as easily removed from the original function as well.
Now I can finally just load the menu I need as I need it with no mess or fuss, like so:
function THEME_preprocess_page(&$vars) {
if(@$vars['main_menu']) {
$menu = variable_get('menu_main_links_source', 'main-menu');
$vars['main_menu'] = menu_tree_output(menu_tree_full_data($menu));
}
}
And use it in my theme like so:
<?= render($main_menu); ?>
Or something like so (this is how a lot of other themes seem to handle it, though it just makes a mess for me):
<?php if ($main_menu): ?>
<nav id="primary-menu" role="navigation">
<?php print theme('links__system_primary_menu', array(
'links' => $main_menu,
'attributes' => array(
'class' => array('links', 'inline', 'clearfix'),
)
)); ?>
</nav>
<?php endif; ?>
Of course you still have to define or include the menu_tree_full_data function somewhere as well. For me, it goes in a "Shorthand" module I maintain with various simplified object-oriented API wrappers, where it'll also get turned into a method in SH_Menu class and get loaded on demand with shorthand_load_menu(); (Note you've gotta switch __FUNCTION__
for 'menu_tree_full_data'
if you do something similar, or the caching won't work correctly.)