so i've been reading what feels like most of the day on drupal forums and sites around the web on just how to solve that most complex of beasts - the 3 level menu, with drupal. menus are web 101 in my book and i've implemented complex menus in just about all languages whether it's java, php or .net

so it appears to me at least that menus in drupal ought to be addressed, or at least some more explanation around them needs to be documented if so-called pros like myself can even understand them. i appear to not be alone at all.

i have seen posts suggesting the menu module, i have seen posts citing the categoyr module, the taxonomy module, the book module.

I mean, which is it? I have setup a 4-level menu system using the out of box menu module and it seems fine, but the issue I face is displaying level 3 when I am on a level 2 node. What's the practice there? Are we supposed to just create a block php snippet of our own to render level 3 and 4 for ourselves ... i would have thought a sensible thing would be to be able to expose the levels like the primary_links and secondary_links so i can pick them up in the page template but nope.

i am guessing somewhere out there has implemented a 3/4 level menu system and are able to show level 3 when on level 2 and show level 4 when on level 3 etc.. it's just hidden knowledge as far as I can tell?

Comments

halfer’s picture

Hi - I tend to agree, the menu system is a bit puzzling. Perhaps I didn't look hard enough, but the material I found on the menu didn't seem to tell me how to pull it apart, so that I could write my own menu block. Well, I did just that, and what I found is documented below (Drupal version 4.7). Apologies if there are proper accessor functions for this stuff - but the below stuff should get you started.

Create a new block, and add in the opening and closing php tags. Declare $_menu as global. Then you can dissect this value and do what you will with it. At the topmost level, it is an associative array containing a number of keys. The one you will be most interested in is called 'items'.

$_menu['items'] then reveals the whole of your menu structure, with each 'mid' (ie menu primary key) as a key, and with a menu item as the value. Each menu item is an associative array containing the following keys: path, title, description, pid, weight, type, children, access, callback.

'pid' is the 'parent id', which can be looked up in $_menu['items'] in the same way; weight is the relative position value, and it should be noted that this array does NOT come pre-sorted; children is an array of child mids, I believe; title and description are self-explanatory. The 'type' is an integer and comes from the menu table, I think it describes whether a menu appears as a standard menu, or a tab etc.

Hope that helps. You will need to implement a sort to ensure items are listed in the right order, and obviously you'll have to work out how to show only the hierarchies you want. You may find menu_get_active_nontask_item() useful, which (I believe) returns the mid of the currently active menu.

I am working on a custom menu myself, so if any experienced Drupal users know of a better approach, or an API, then please post here!

nowsearch’s picture

Really useful advice thanks halfer. With your starting tips I have been able to implement a tertiary menu system into my layout and all using the inbuilt menu module for primary, secondary and breadcrumb.

To do the tertiary menu, I had to write a function that enabled me to test the depth of a node in the menu. This gives me an int that I can use to determine whether to show the tertiary menu block or not on a given page.

I hope the code comes out below ..


<?
function menu_depth_to_parent($from, $parent) {
	$n = 0;
	$mid = $from;
	do {
		$mi = menu_get_item($mid);
		$mid = $mi['pid'];
		$n++;
	} while ($mid != $parent);
	return $n;
}

$cur = menu_get_item(menu_get_active_nontask_item());
?>

						<div id="tertiary">
							<ol>
							<?
							if ($cur && $depth = menu_depth_to_parent($cur['pid'], 0)) {
								if ($depth > 1) {
									foreach ($cur['children'] as $l1mid) { 
										$l1cm = menu_get_item($l1mid);
										if ($l1cm['type'] != 118) continue;
										?>
										<li><a href="/<?= $l1cm['path'] ?>"><?= $l1cm['title'] ?></a></li>
										<?
									}
								}
							}
							?>
							</ol>		
						</div>

When my project is done I will certainly be posting some help on this and contributing this block for those it might help.

halfer’s picture

By the way, if you need to get further information about the structure, use print_r() on the array or array segment. It's what I used to reverse-engineer the structure info I provided above.

9802008’s picture

The following taxonomy_context code has been modified so as to return a menu tree based on a term id. The first two tiers of the menu have been removed.

Eg if you have a menu/category structure like this:
Item 1
-- item 1.1
----item 1.1.1
------item 1.1.1.1
------item 1.1.1.2
----item 1.1.2

then item 1 and item 1.1 will not be generated in the tree.

<?php

/**
 * Builds a tree from the current term 'upwards'
 *
 * @param int $tid
 * @return array
 */
function _context_build_tree_no_parents($tid)
  {
  // First, get path from root and own children
  $parents = taxonomy_get_parents_all($tid);
  for ($i = 1; $i < sizeof($parents) - (sizeof($parents) -3); $i++) {
    // We don't display the first two parents in our menu
    array_pop ($parents);
  }
  $tree = taxonomy_get_children($tid);
  foreach ($parents as $parent) 
    {
  	$newtree = array ($parent->tid => $parent) ;
  	$newtree[$parent->tid]->children = $tree;
  	$tree = $newtree;
    }

    
  if (sizeof ($tree) > 0) {
    // Then decorate with brethren along the path, we only do this if the term has children
    $current = &$tree;
    do {
      $base_term = current($current);
      foreach (_taxonomy_context_term_get_phratry($base_term->tid) as $current_tid => $current_term)
        {
        if ($current_tid != $base_term->tid)
          {
          $current[$current_tid] = $current_term;
          }
        }
      $current = &$current[$base_term->tid]->children;
      } while (count($current) > 0);
  }
  // sort according to weight
  sort_by_weight($tree);
  return $tree ;
}

/**
 * Recursively sorts the menu tree according to weight
 *
 * @param unknown_type $tree
 */
function sort_by_weight (&$tree) {
  foreach ($tree as $t) {
    if (is_array($t->children) && sizeof ($t->children) > 0 ){
      sort_by_weight($t->children);
    }
  }
  usort($tree, 'phpsnippet_menu_sort'); 
}
  
/**
 * Comparator routine for use in sorting menu items.
 */
function phpsnippet_menu_sort($a, $b) {
  $menu = menu_get_menu();

  $a = $a->weight;
  $b = $b->weight;

  if ($a < $b) {
    return -1;
  }
  elseif ($a > $b) {
    return 1;
  }
  elseif (isset($a['title']) && isset($b['title'])) {
    return strnatcasecmp($a['title'], $b['title']);
  }
  else {
    return 1;
  }
}
?>

www.jws.co.za/we_install_modify_and_maintain_drupal_web_sites

socialnicheguru’s picture

subscribing

http://SocialNicheGuru.com
Delivering inSITE(TM), we empower you to deliver the right product and the right message to the right NICHE at the right time across all product, marketing, and sales channels.