Last updated November 13, 2008. Created by jdschroeder on August 4, 2005.
Edited by jbrauer, AjK, Dublin Drupaller, add1sun. Log in to edit this page.

Note: site recipes are opinions of the original author. Book module is a way to create hierarchical content out of the box.

This question is asked almost daily in the forums on drupal.org but there seem to be only partial answers. At best Drupal is not configured for a hierarchical site out of the box. At worst Drupal is not currently a good solution for a hierarchical site. The Taxonomy module helps but doesn't always work as I would like it to (an presumably other too). Yet, I need something like Drupal for its flexibility and extensibility. No CMS commercial or open source does everything you want out of the box. I prefer a CMS that is extensible and flexible even if I have to get my hands dirty to make it work the way I need it to.

So far I have a mostly complete solution that meets many of the requirements yet is not quite perfect either. In fact as I concocted this I kept thinking, there has to be a better way to do this but this is the best I have come up with. I post this in hopes others can suggest ways to improve this or

Assumptions

  1. A hierarchical site should start with one major category (home) and have sub categories and sub sub categories as deeply as needed.
  2. Each major category (and potential sub categories) should be editable by a different user or group (some users may need to edit in more than one category).
  3. Each category should have an "index.html" type page with an
    overview of the category.
  4. Navigation menus should conform to the hierarchy as well
  5. Nodes in the menu should be able to be reordered in custom orders
  6. Breadcrumbs should reflect the hierarchy
  7. Does not require extensive php coding but at least a decent understanding of html, css, basic php variables, and php include statements

Requirements

  • Drupal 4.6 or higher
  • Clean Urls enabled
  • Php template engine
  • Taxonomy module
  • Taxonomy Context or Taxonomy Associations Module

Making it work

  1. Install php template (directions here
    http://drupal.org/node/11810)
    and here
    http://support.bryght.com/articles/converting-css-html-design-phptemplat...,
    turn clean urls on, install the taxonomy context and or taxonomy associations modules.
  2. I am using the Box_Grey theme for this example. Download it here:
    http://drupal.org/project/box_grey or use your own theme.
  3. Set up a vocabulary and some terms in Administer-> Categories . I this example I set up a vocabulary called "Sections" and three terms:
    home, section 1 and section 2. Give each term a description. When setting up the vocaublary be sure to select the types of nodes you want to associate with this vocabulary (I choose book pages, pages, and images)
  4. Create a few new page nodes and assign them to home, section 1 or section 2.
  5. Create a new block in Administer -> Blocks -> Add Block. Choose Input Format "Php Code" and paste in the following code:
    // Not you may need to change the $vocabulary_id variable to the number of you master vocabulary or category ID. If you
    // don't know you can find out by going to administer -> categories -> edit Vocabulary. The url will be something like:
    // http://localhost/admin/taxonomy/edit/vocabulary/1 - the number one is the Vocabulary ID in this case

    <?php
    $vocabulary_id
    = 1; // vocabulary id for the "master" category
    $result = db_query("SELECT d.tid, d.name, d.weight FROM {term_data} d INNER JOIN {term_node} USING (tid) INNER JOIN {node} n USING (nid) WHERE d.vid = $vocabulary_id AND n.status = 1 GROUP BY d.tid, d.name ORDER BY d.weight");
    $items = array();
    $sub_items = array();
    while (
    $category = db_fetch_object($result)) {
    $output .= "<ul>"; // for term list
    $output .= "<li>" . l($category->name, 'taxonomy/term/'. $category->tid) . "</li>";
    $output .= "<ul>"; // for nodes list
    // call function to find all nodes in a term
    $sub_items = get_nodes_in_term($category->tid);
    // Add nodes to items array
    foreach($sub_items as $val) {
    $output .= $val;
    }
    $output .= "</ul></li></ul>";
    }
    print
    $output;
    function
    get_nodes_in_term($tid) {
    $sub_items = array();
    $sql = "SELECT n.title, n.nid FROM {node} n INNER JOIN {term_node} t ON n.nid = t.nid WHERE t.tid = $tid ";
    $result = db_query($sql);
    while (
    $anode = db_fetch_object($result)) {
    $sub_items[] = "<li>" . l($anode->title, "node/$anode->nid") . "</li>";
    }
    return
    $sub_items;
    }
    ?>

    This will give you a hierarchical menu. You may have to adjust your theme's stylesheet to get it to look the way you want it to.

  6. Turn on Taxonomy Context and make sure your top-level category has a description (if not add one under administer->categories-> edit term).

At this point we have a hierarchically organized site with a hierarchically organized navigation menu. Now we need to tweak a few things to get an "index.html" style page for each taxonomy term and sub-term. If I click on the navigation link for the term "home" as things stand now I will see first the taxonomy description, followed by the Section 1 and Section 2 subcategory headers (which are also links), followed by the teaser for the first node created under the "home" section. If you are like me - you just want a description of the category to server as the "index.html" page. To do this we need to change the template and modify the template for the theme.

Tweaking the theme to show an index page for each category

  1. First go to administer->settings-> Taxonomy Context and set "Show Subterm info" to disabled. This will get rid of the subcategory listings in our index page but not the node teasers.
  2. Now we have to chop up the template file and make it show the category term description if we are on a taxonomy term page and the normal node content if we are on a node. How? Here is the original page.tpl.php file for the box grey theme. :
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml" lang="<?php print $language ?>" xml:lang="<?php print $language ?>">
    <head>
    <title><?php print $head_title ?></title>
    <meta http-equiv="Content-Style-Type" content="text/css" />
    <?php print $head ?>
    <?php print $styles ?>
    </head>
    <body <?php print theme("onload_attribute"); ?>>
    <div id="header">
    <?php if ($search_box): ?>
    <form action="<?php print $search_url ?>" method="post">
    <div id="search">
    <input class="form-text" type="text" size="15" value="" name="edit[keys]" /><input class="form-submit" type="submit" value="<?php print $search_button_text ?>" />
    </div>
    </form>
    <?php endif; ?>
    <?php if ($logo) : ?>
    <a href="<?php print url() ?>" title="Index Page"><img src="<?php print($logo) ?>" alt="Logo" /></a>
    <?php endif; ?>
    <?php if ($site_name) : ?>
    <h1 id="site-name"><a href="<?php print url() ?>" title="Index Page"><?php print($site_name) ?></a></h1>
    <?php endif;?>
    <?php if ($site_slogan) : ?>
    <span id="site-slogan"><?php print($site_slogan) ?></span>
    <?php endif;?>
    <br class="clear" />
    </div>
    <div id="top-nav">
    <?php if (count($secondary_links)) : ?>
    <ul id="secondary">
    <?php foreach ($secondary_links as $link): ?>
    <li><?php print $link?></li>
    <?php endforeach; ?>
    </ul>
    <?php endif; ?>
    <?php if (count($primary_links)) : ?>
    <ul id="primary">
    <?php foreach ($primary_links as $link): ?>
    <li><?php print $link?></li>
    <?php endforeach; ?>
    </ul>
    <?php endif; ?>
    </div>
    <table id="content">
    <tr>
    <?php if ($sidebar_left != ""): ?>
    <td class="sidebar" id="sidebar-left">
    <?php print $sidebar_left ?>
    </td>
    <?php endif; ?>
    <td class="main-content" id="content-<?php print $layout ?>">
    <?php if ($title != ""): ?>
    <h2 class="content-title"><?php print $title ?></h2>
    <?php endif; ?>
    <?php if ($tabs != ""): ?>
    <?php print $tabs ?>
    <?php endif; ?>
    <?php if ($mission != ""): ?>
    <div id="mission"><?php print $mission ?></div>
    <?php endif; ?>
    <?php if ($help != ""): ?>
    <p id="help"><?php print $help ?></p>
    <?php endif; ?>
    <?php if ($messages != ""): ?>
    <div id="message"><?php print $messages ?></div>
    <?php endif; ?>
    <!-- start main content -->
    <?php print($content) ?>
    <!-- end main content -->
    </td><!-- mainContent -->
    <?php if ($sidebar_right != ""): ?>
    <td class="sidebar" id="sidebar-right">
    <?php print $sidebar_right ?>
    </td>
    <?php endif; ?>
    </tr>
    </table>
    <?php print $breadcrumb ?>
    <div id="footer">
    <?php if ($footer_message) : ?>
    <p><?php print $footer_message;?></p>
    <?php endif; ?>
    Validate <a href="http://validator.w3.org/check/referer">XHTML</a> or <a href="http://jigsaw.w3.org/css-validator/check/referer">CSS</a>.
    </div><!-- footer -->
    <?php print $closure;?>
    </body>
    </html>

    We want take everything up to the point where it says:
    <?php
    endif;
    ?>
    on line 53 and put it in its own file called header.tpl.php. Then we want to take everything from
    <?php
    if ($sidebar_right != ""):
    ?>
    to the end of the file and put this in a file called footer.tpl.php. In te node.tpl.php replace the existing html and php code with this:
    <td class="main-content" id="content">
    <!-- start main content -->
    <?php print($content) ?>
    <!-- end main content -->
    </td>

    Put this in a file called taxonomy.tpl.php:
    <td class="main-content" id="content-<?php print $layout ?>">
    <?php if ($title != ""): ?>
    <h2 class="content-title"><?php print $title ?></h2>
    <?php endif; ?>
    <?php if ($tabs != ""): ?>
    <?php print $tabs ?>
    <?php endif; ?>
    <?php if ($mission != ""): ?>
    <div id="mission"><?php print $mission ?></div>
    <?php endif; ?>
    <?php if ($help != ""): ?>
    <p id="help"><?php print $help ?></p>
    <?php endif; ?>
    <?php if ($messages != ""): ?>
    <div id="message"><?php print $messages ?></div>
    <?php endif; ?>
    </td><!-- mainContent -->
            Erase anything left in the page.tpl.php file and add this:
    <?php
     
    require('header.tpl.php');
      if (
    arg(0)=="taxonomy") {
      include(
    'taxonomy.tpl.php');
      return;
      }
      else {
      include(
    'node.tpl.php');
      }
      require(
    'footer.tpl.php');
     
    ?>

       </li>
       <li>What's going on here? Well the magic is in the if statement in the page.tpl.php file:
         if (arg(0)=="taxonomy") {}

    Drupal looks at the current url to see if it is a taxonomy page. So a url like this: http://localhost/taxonomy/term/1 would evaluate to true. If it is a taxonomy node it includes the taxonomy.tpl.php file which does not include the normal node content.

    If it is false it includes node.tpl.php which has the normal node content but nothing else.

Drawbacks

  • Main drawback is the term description is not a node so you can't do versioning and all the other good treatments that regular nodes use instead. One way around this is to use Taxonomy Associations which lets you displayed a specified node when a taxonomy term page is displayed.
  • Title of the node prints twice for some reason I have not figured out.

I'll add more as I discover additional methods (hopefully with the help of fellow Drupal users)

Looking for support? Visit the Drupal.org forums, or join #drupal-support in IRC.

Comments

http://drupal.org/project/hierarchical_select

This page is highly ranked by Google for terms pertinent to "hierarchical dropdowns widgets using taxonomy terms in Drupal", and many people might be coming here looking for a quick way to do that.

Well, just install the module in the link the above, you'll probably get at what you need :)