taxonomy_menu lets you create nice menus from taxonomy but the top level of the menu is the vocabulary name and that entry has no content. I wrote vocabulary_node to connect a node to the top level but then found I could not use that vocabulary level node in other ways without writing more modules. Perhaps it is easier to take the vocabulary out of the menu and just use terms. Will this create problems in other modules?

I have vocabulary aa with terms bb and cc to create menu entries aa, aa/bb, and aa/cc. I made a slight change to the taxonomy_menu code and added aa as the parent term in vocabulary aa. The vocabulary aa no longer creates a menu entry. term aa makes entry aa. Term bb has term aa as parent and creates menu entry aa/bb. Term cc has term aa as parent and creates menu entry aa/cc.

With this change I should be able to use distant parent and other modules on term aa. If I have vocabulary xx with term yy, I should be able to make aa the distant child of yy and get the menu entry yy/aa.

Are distant parent or any other modules dependent on having the vocabulary id in the URL?

Here are the approximate code changes. The line numbers are irrelevant as they no longer match 4.6.3. There was a previous change to make taxonomy_menu build URL aliases which makes my taxonomy_menu a little different to the base module. The building of URL aliases could also be controlled by a variable wich I may look at next century.

In taxonomy_menu_settings() after:

	$form .= form_checkbox(t('Display descendants'),
		'taxonomy_menu_display_descendants', 1,
		variable_get('taxonomy_menu_display_descendants', 1),
		t('If checked, then when a term is selected all nodes belonging to subterms are also displayed.'));

Add:

	$form .= form_checkbox(t('Display vocabulary as top level of menu'),
		'taxonomy_menu_display_vocabulary', 1,
		variable_get('taxonomy_menu_display_vocabulary', 1),
		t('If checked, then the vocabulary name is used as the top level of the menu.'));

In taxonomy_menu_menu($may_cache) after:

 	if ($may_cache)
		{

Add:

 		if(variable_get('taxonomy_menu_display_vocabulary', 1))
			{
			$use_vocabulary_name = true;
			}
		else
			{
			$use_vocabulary_name = false;
			}

In taxonomy_menu_menu($may_cache) after:

 			if (variable_get('taxonomy_menu_show_'. $vocabulary->vid, 1))
				{

Replace some code with:

 				if($use_vocabulary_name)
					{
					$path = 'taxonomy_menu/' . $vocabulary->vid;
					$title = t($vocabulary->name);
					$items[] = array('path' => $path, 'title' => $title,
						'callback' => 'taxonomy_menu_page', 'access' => $access,
						'weight' => $vocabulary->weight);
					if(taxonomy_menu_update_url_alias($path, $title))
						{
						$rebuild = true;
						}
					}
				else
					{
					$path = 'taxonomy_menu';
					$title = '';
					}

In taxonomy_menu_menu($may_cache) between:
$title = t($term->name);
and:
$old_title = $title;
use:

 					if($use_vocabulary_name or $term->depth > 0)
						{
						$items[] = array('path' => $path, 'title' => $title,
							'weight' => $term->weight);
						$title = $old_title .'/'. $title;
						}
					else
						{
						$items[] = array('path' => $path, 'title' => $title,
							'callback' => 'taxonomy_menu_page',
							'access' => $access,
							'weight' => $vocabulary->weight);
						}

In taxonomy_menu_menu($may_cache) after:
$old_title = $title;
I have:

 					if(taxonomy_menu_update_url_alias($path, $title))
						{
						$rebuild = true;
						}

Comments

peterx’s picture

The code is working so far. I deleted vocabulary_node, http://drupal.org/node/30829, because this change to taxonomy_menu is as easy and should increase compatibility with other modules.

petermoulding.com/web_architect

venkat-rk’s picture

This could be a stroke of luck as I grappling with a major problem just a few days before turning over a site for review. Please consider these two requests:

1.) Could the parent term page automaticallly pull in all nodes tagged with the child terms and sub terms of that vocabulary? Currently, the only option is to manually build the url for that parent term page as taxonomy/term/tid1+tid1+tid3 etc. And, doing this makes taxonomy_context and pathauto go bonkers.

2.) Any chance that the entire code of your changed module could be posted?

Thanks.

peterx’s picture

There is no attache option so here is the raw code. I made a couple of other changes to the code and carefully documented those changes on a piece of paper that was eaten by a creature from http://userfriendly.org/. :-)

petermoulding.com/web_architect

// $Id: taxonomy_menu.module,v 1.10.2.1 2005/03/19 15:49:05 JonBob Exp $

/**
 * Implementation of hook_help().
 */
function taxonomy_menu_help($section) {
  switch ($section) {
    case 'admin/modules#description':
      return t('Adds links to taxonomy terms to the global navigation menu.');
  }
}

/**
 * Implementation of hook_menu().
 *
 * Most of the heavy lifting of the module is done here.
 */
/*
PeterMoulding.com 2005 09 09
Fix formatting.
*/
function taxonomy_menu_menu($may_cache)
	{
	$items = array();
	$rebuild = false;
	if ($may_cache)
		{
		$access = user_access('access content');
		
		/*
		PeterMoulding.com 2005 09 09
		Fix formatting.
		*/
		if(variable_get('taxonomy_menu_display_vocabulary', 1))
			{
			$use_vocabulary_name = true;
			}
		else
			{
			$use_vocabulary_name = false;
			}
		
		foreach (taxonomy_get_vocabularies() as $vocabulary)
			{
			if (variable_get('taxonomy_menu_show_'. $vocabulary->vid, 1))
				{
				/* PeterMoulding.com
				*/
				if($use_vocabulary_name)
					{
					$path = 'taxonomy_menu/' . $vocabulary->vid;
					$title = t($vocabulary->name);
					$items[] = array('path' => $path, 'title' => $title,
						'callback' => 'taxonomy_menu_page', 'access' => $access,
						'weight' => $vocabulary->weight);
					if(taxonomy_menu_update_url_alias($path, $title))
						{
						$rebuild = true;
						}
					}
				else
					{
					$path = 'taxonomy_menu';
					$title = '';
					}
				
				$tree = taxonomy_get_tree($vocabulary->vid);
				$old_depth = -1;
				$old_path = $path;
				$old_title = $title;
				
				foreach ($tree as $term)
					{
					if ($term->depth <= $old_depth)
						{
						$slashes_to_remove = $old_depth - $term->depth + 1;

						for ($i = 0; $i < $slashes_to_remove; $i++)
							{
							$old_path = substr($old_path, 0, strrpos($old_path, '/'));
							$old_title = substr($old_title, 0, strrpos($old_title, '/'));
							}
						}
					$path = $old_path .'/'. $term->tid;
					$old_depth = $term->depth;
					$old_path = $path;
					$title = t($term->name);
					
					/* PeterMoulding.com
					*/
					if($use_vocabulary_name or $term->depth > 0)
						{
						$items[] = array('path' => $path, 'title' => $title,
							'weight' => $term->weight);
						$title = $old_title .'/'. $title;
						}
					else
						{
						$items[] = array('path' => $path, 'title' => $title,
							'callback' => 'taxonomy_menu_page',
							'access' => $access,
							'weight' => $vocabulary->weight);
						}
					
					$old_title = $title;
					if(taxonomy_menu_update_url_alias($path, $title))
						{
						$rebuild = true;
						}
					}
				}
			}
		}
	if($rebuild === true)
		{
		drupal_rebuild_path_map();
		}
	return $items;
	}
/*
PeterMoulding.com 2005 09 09
		}
		else {
		db_query("INSERT INTO {url_alias} (src, dst) VALUES ('%s', '%s')", $path, $alias);
		}
*/
function taxonomy_menu_update_url_alias($path, $alias)
	{
	$alias = str_replace(' ', '_', strtolower($alias));
	$result = db_query("select pid, dst from {url_alias} where src = '%s'", $path);
	if($result === false)
		{
		return false;
		}
	$url_alias = db_fetch_object($result);
	if($url_alias === false)
		{
		// Insert
		$result = db_query("insert into {url_alias} set dst = '%s', src = '%s'",
			$alias, $path);
		}
	else
		{
		// Update
		if($alias == $url_alias->dst)
			{
			return false;
			}
		$result = db_query("update {url_alias} set dst = '%s' where pid = %d",
			$alias, $url_alias->pid);
		}
	if($result === false)
		{
		return false;
		}
	return true;
	}

/**
 * Implementation of hook_taxonomy().
 *
 * Invalidates the menu cache on taxonomy changes.
 */
function taxonomy_menu_taxonomy() {
  menu_rebuild();
}

/**
 * Implementation of hook_settings().
 */
function taxonomy_menu_settings()
	{
	$form .= form_checkbox(t('Display descendants'),
		'taxonomy_menu_display_descendants', 1,
		variable_get('taxonomy_menu_display_descendants', 1),
		t('If checked, then when a term is selected all nodes belonging to subterms are also displayed.'));
	
	/* PeterMoulding.com 2005 09 09
	*/
	$form .= form_checkbox(t('Display vocabulary as top level of menu'),
		'taxonomy_menu_display_vocabulary', 1,
		variable_get('taxonomy_menu_display_vocabulary', 1),
		t('If checked, then the vocabulary name is used as the top level of the menu.'));
	
	foreach (taxonomy_get_vocabularies() as $vocabulary)
		{
		$form .= form_checkbox(t('Show "%vocab" in menu', array('%vocab' => t($vocabulary->name))),
			'taxonomy_menu_show_' . $vocabulary->vid, 1,
			variable_get('taxonomy_menu_show_' . $vocabulary->vid, 1));
		}
	return $form;
	}

/**
 * Page callback that renders a node listing for the selected term.
 */
function taxonomy_menu_page()
	{
	$descendants = variable_get('taxonomy_menu_display_descendants', 1);
	$vocabulary = arg(1);
	if (arg(2))
		{
		// If arg(2) then we are looking at a term
		$arguments = explode('/', $_GET['q']);
		$main_tid = db_escape_string(array_pop($arguments));
		variable_set('taxonomy_menu_display_node', true);
		$display_node = variable_get('taxonomy_menu_display_node', false);
		if($display_node)
			{
			$tids = array();
			$tree = taxonomy_get_tree($vocabulary ,$main_tid);
			foreach ($tree as $term)
				{
				if ($descendants || $term->depth == 0)
					{
					$tids[] = $term->tid;
					}
				}
			$result = taxonomy_select_nodes($tids, 'or', 0);
			}
		else
			{
			$descend = 0;
			if($descendants)
				{
				$descend = 'all';
				}
			drupal_set_html_head('<link rel="alternate" type="application/rss+xml" title="RSS" href="'
				. url('taxonomy/term/'. $main_tid .'/'. $descend .'/feed') .'" />');
			$result = taxonomy_select_nodes(array($main_tid), 'or', $descend);
			}
		}
	else
		{
		// If no arg(2), we're looking at just the vid. If display_descendants
		// is on, grab all terms regardless of depth. If off, grab depth 0 terms.
		$tree = taxonomy_get_tree($vocabulary);
		foreach ($tree as $term)
			{
			if ($descendants || $term->depth == 0)
				{
				$tids[] = $term->tid;
				}
			if ($descendants)
				{
				}
			if ($term->depth == 0)
				{
				}
			}
		// The requested terms have already been determined, so don't request
		// descendants here.
		$result = taxonomy_select_nodes($tids, 'or', 0);
		}
	print theme('page', taxonomy_render_nodes($result));
	}

/**
 * Implementation of hook_nodeapi().
 *
 * This hook enables the menu to be displayed in context during node views.
 */
function taxonomy_menu_nodeapi(&$node, $op, $a3, $a4) {
  switch ($op) {
    case 'view':
      if ($a4 == TRUE) {
        // The node is being displayed on its own page.
        foreach (taxonomy_get_vocabularies() as $vocabulary) {
          if (variable_get('taxonomy_menu_show_'. $vocabulary->vid, 1)) {
            $path = 'taxonomy_menu/' . $vocabulary->vid;

            $tree = taxonomy_get_tree($vocabulary->vid);
            $old_depth = -1;
            $old_path = $path;

            foreach ($tree as $term) {
              if ($term->depth <= $old_depth) {
                $slashes_to_remove = $old_depth - $term->depth + 1;
                for ($i = 0; $i < $slashes_to_remove; $i++) {
                  $old_path = substr($old_path, 0, strrpos($old_path, "/"));
                }
              }
              $path = $old_path .'/'. $term->tid;
              $old_depth = $term->depth;
              $old_path = $path;
              if (in_array($term->tid, array_keys(taxonomy_node_get_terms($node->nid)))) {
                menu_set_location(array(array('path' => $path, 'title' => t($term->name)), array('path' => 'node/'. $node->nid, 'title' => $node->title)));
                // Quit after the first match.
                return;
              }
            }
          }
        }
      }
      break;
  }
}

/*
Copyright PeterMoulding.com
*/
function taxonomy_menu_path_from_node_id($nid)
	{
	foreach(taxonomy_get_vocabularies() as $vocabulary)
		{
		if(variable_get('taxonomy_menu_show_'. $vocabulary->vid, 1))
			{
			$path = 'taxonomy_menu/' . $vocabulary->vid;
			$tree = taxonomy_get_tree($vocabulary->vid);
			$old_depth = -1;
			$old_path = $path;
			foreach ($tree as $term)
				{
				if ($term->depth <= $old_depth)
					{
					$slashes_to_remove = $old_depth - $term->depth + 1;
					for ($i = 0; $i < $slashes_to_remove; $i++)
						{
						$old_path = substr($old_path, 0, strrpos($old_path, "/"));
						}
					}
				$path = $old_path . '/' . $term->tid;
				$old_depth = $term->depth;
				$old_path = $path;
				$terms = taxonomy_node_get_terms($nid);
				if (in_array($term->tid, array_keys(taxonomy_node_get_terms($nid))))
					{
					// Quit after the first match.
					return $path;
					}
				}
			}
		}
	return false;
	}

peterx’s picture

I changed taxonomy menu to work using the aliases from taxonomy+pathauto, the aliases of the form taxonomy/term/99. See Allow manual specification of parent. If you feel comfortable modifying includes/menu.inc then let me know and I will post the related changes for taxonomy_menu.

petermoulding.com/web_architect

Jaza’s picture

Hmm.. I don't really understand what you're trying to do here. But anyway, to answer your question (one of them, at least), distant parent does not need any vocabulary id's in a URL in order to function. Distant parent works purely with terms, since taxonomy by itself (i.e. without modules like taxonomy_vocabulary) doesn't have pages for vocabularies.

Jeremy Epstein - GreenAsh

Jeremy Epstein - GreenAsh

venkat-rk’s picture

Speaking for myself, I found that, without coding, the only way (for me personally) to replicate a traditional hierarchical site structure was to use a parent term for each vocabulary. This could have child terms and sub-terms, but, without this parent term, taxonomy_context did not give breadcrumbs and I did not get an index page for a hierarchical section.

Currently, taxonomy_menu takes the vocab name as the parent term and does not give breadcrumbs (AFAIK).

peterx’s picture

I had vocabulary books containing terms linux and windows. taxonomy menu gave me the following menu structure.
books
books/linux
books/windows
The first entry, books, did not have a node attached to provide content so I wrote vocabulary_node to attach a node to books.

Now I want to use distant parent and some other modules. Distant parent works with terms but not nodes. I started to write distant_parent_vocabulary but realised I would need one for term-vocabulary plus one for vocabulary-term plus one for vocabulary-vocabulary.

I changed direction, threw out vocabulary_node and changed taxonomy_menu to optionally not use vocabulary as the top level of the menu. I now have vocabulary books containing the following terms.
books
-linux
-windows

I get the same menu but books is now a term and can be the child of a distant parent without extra coding. Books is duplicated as both a vocabulary name and as a term but it gives me less code to maintain when I have to upgrade.

What I do not know is how this change will work with other modules. If everything uses terms and term ids then I should be able to use a ll the other modules without problems. If something breaks because the vocabulary and a term have the same name then I may have to try something else.

petermoulding.com/web_architect

profix898’s picture

I really like the idea of assigning nodes to vocabs using your code. And I already tried your vocabulary_node module. Great! :) But its hard to follow your patching instructions above.

Could you please provide a patch (diff-file, ...) or your ready-patched module?

Thx Thilo

peterx’s picture

I tried to create a patch on a previous occasion but it did not work because the patch creation utility was faulty. The patch would also be meaningless as I reformatted the code to improve readability.

When I upgrade to 4.7 then I will rework the changes and add documentation. Yeah, that stuff. I teach people to write documentation first then change the code. Ten year olds and 92 year olds write better documented code than me.

petermoulding.com/web_architect

profix898’s picture

Your modified code seems not to work for me!
When looking through the modified module I found something like:

$form .= form_checkbox(t('Display vocabulary as top level of menu'),
		'taxonomy_menu_display_vocabulary', 1,
		variable_get('taxonomy_menu_display_vocabulary', 1),
		t('If checked, then the vocabulary name is used as the top level of the menu.'));

But this option does not appear on my configuration pages. ("Display descendants" does.)

Are there any changes to be made to the database?
Do I have to make any further modifications to my setup?

Just to explain what I did so far:
1) I replaced my existing taxonomy_menu.module with yours
2) I modified taxonomy from

AA
- BB
- CC
- etc.

to

AA
- AA
- - BB
- - CC
- - etc.

3) I searched through the settings for the option mentioned above.
4) I gave up and posted this comment ;)

Maybe its my fault! But I hope you got an idea whats wrong!?

Regards Thilo

peterx’s picture

* "But this option does not appear on my configuration pages. ("Display descendants" does.)"
It appears on mine. The variable handling is standard Drupal so should always work. Drupal might be caching the Web page. If I make a change and the change does not appear, I empty table drupal_cache.

* "Are there any changes to be made to the database?"
No, the variables are automatically stored by Drupal in table drupal_variable.

* "Do I have to make any further modifications to my setup?"
That module contains a couple of other changes but none should stop you seeing the option in the settings page.

* "Just to explain what I did so far:
1) I replaced my existing taxonomy_menu.module with yours
2) I modified taxonomy from"
The vocabulary modifications should not affect whether the option appears in settings.

petermoulding.com/web_architect

peterx’s picture