Index: modules/taxonomy.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/taxonomy.module,v
retrieving revision 1.123
diff -u -F^function -r1.123 taxonomy.module
--- modules/taxonomy.module	21 Jun 2004 08:26:20 -0000	1.123
+++ modules/taxonomy.module	25 Jun 2004 15:12:43 -0000
@@ -1,22 +1,6 @@
 <?php
 // $Id: taxonomy.module,v 1.123 2004/06/21 08:26:20 dries Exp $
 
-function taxonomy_feed($taxonomy) {
-  global $id, $type;
-
-  if ($type == 'voc') {
-    //TODO - vocabulary feed.
-  }
-  else {
-    $result = taxonomy_select_nodes($taxonomy, 0);
-    $term = taxonomy_get_term($taxonomy->tids[0]);
-    $channel['link'] = url("taxonomy/view/$taxonomy->operator/$taxonomy->str_tids", NULL, NULL, TRUE);
-    $channel['title'] = variable_get('site_name', 'drupal') .' - '. $term->name;
-    $channel['description'] = $term->description;
-    node_feed($result, $channel);
-  }
-}
-
 /**
  * Implementation of hook_perm().
  */
@@ -67,8 +51,12 @@ function taxonomy_menu() {
     'callback' => 'taxonomy_admin',
     'access' => user_access('administer taxonomy'),
     'type' => MENU_LOCAL_TASK);
-  $items[] = array('path' => 'taxonomy', 'title' => t('taxonomy'),
-    'callback' => 'taxonomy_page',
+  $items[] = array('path' => 'taxonomy/vocabulary', 'title' => t('taxonomy vocabulary'),
+    'callback' => 'taxonomy_vocabulary_page',
+    'access' => user_access('access content'),
+    'type' => MENU_CALLBACK);
+  $items[] = array('path' => 'taxonomy/term', 'title' => t('taxonomy term'),
+    'callback' => 'taxonomy_term_page',
     'access' => user_access('access content'),
     'type' => MENU_CALLBACK);
   return $items;
@@ -105,7 +93,7 @@ function taxonomy_save_vocabulary($edit)
 
   $data = array('name' => $edit['name'], 'nodes' => implode(',', $edit['nodes']), 'description' => $edit['description'], 'help' => $edit['help'], 'multiple' => $edit['multiple'], 'required' => $edit['required'], 'hierarchy' => $edit['hierarchy'], 'relations' => $edit['relations'], 'weight' => $edit['weight']);
   if ($edit['vid'] && $edit['name']) {
-    db_query('UPDATE {vocabulary} SET '. _prepare_update($data) .' WHERE vid = %d', $edit['vid']);
+    db_query('UPDATE {vocabulary} SET '. _taxonomy_prepare_update($data) .' WHERE vid = %d', $edit['vid']);
     module_invoke_all('taxonomy', 'update', 'vocabulary', $edit);
     $message = t('updated vocabulary "%name".', array('%name' => $edit['name']));
   }
@@ -114,7 +102,7 @@ function taxonomy_save_vocabulary($edit)
   }
   else {
     $data['vid'] = $edit['vid'] = db_next_id('{vocabulary}_vid');
-    db_query('INSERT INTO {vocabulary} '. _prepare_insert($data, 1) .' VALUES '. _prepare_insert($data, 2));
+    db_query('INSERT INTO {vocabulary} '. _taxonomy_prepare_insert($data, 1) .' VALUES '. _taxonomy_prepare_insert($data, 2));
     module_invoke_all('taxonomy', 'insert', 'vocabulary', $edit);
     $message = t('created new vocabulary "%name".', array('%name' => $edit['name']));
   }
@@ -199,7 +187,7 @@ function taxonomy_save_term($edit) {
   if ($edit['tid'] && $edit['name']) {
     $data = array('name' => $edit['name'], 'description' => $edit['description'], 'weight' => $edit['weight']);
 
-    db_query('UPDATE {term_data} SET '. _prepare_update($data) .' WHERE tid = %d', $edit['tid']);
+    db_query('UPDATE {term_data} SET '. _taxonomy_prepare_update($data) .' WHERE tid = %d', $edit['tid']);
     module_invoke_all('taxonomy', 'update', 'term', $edit);
     $message = t('the term "%a" has been updated.', array('%a' => $edit['name']));
   }
@@ -209,7 +197,7 @@ function taxonomy_save_term($edit) {
   else {
     $edit['tid'] = db_next_id('{term_data}_tid');
     $data = array('tid' => $edit['tid'], 'name' => $edit['name'], 'description' => $edit['description'], 'vid' => $edit['vid'], 'weight' => $edit['weight']);
-    db_query('INSERT INTO {term_data} '. _prepare_insert($data, 1) .' VALUES '. _prepare_insert($data, 2));
+    db_query('INSERT INTO {term_data} '. _taxonomy_prepare_insert($data, 1) .' VALUES '. _taxonomy_prepare_insert($data, 2));
     module_invoke_all('taxonomy', 'insert', 'term', $edit);
     $message = t('created new term "%name".', array('%name' => $edit['name']));
   }
@@ -509,6 +497,9 @@ function taxonomy_get_children($tid, $vi
  * @param $depth
  *   Internal use only.
  *
+ * @param $max_depth
+ *   The number of levels of the tree to return. Leave NULL to return all levels.
+ *
  * @return
  *   An array of all term objects in the tree. Each term object is extended
  *   to have "depth" and "parents" attributes in addition to its normal ones.
@@ -531,7 +522,7 @@ function taxonomy_get_tree($vid, $parent
     }
   }
 
-  $max_depth = ($max_depth == '') ? count($children[$vid]) : $max_depth;
+  $max_depth = (is_null($max_depth)) ? count($children[$vid]) : $max_depth;
   if ($children[$vid][$parent]) {
     foreach ($children[$vid][$parent] as $child) {
       if ($max_depth > $depth) {
@@ -703,7 +694,7 @@ function _taxonomy_depth($depth, $graphi
   return $result;
 }
 
-function _prepare_update($data) {
+function _taxonomy_prepare_update($data) {
   foreach ($data as $key => $value) {
     $q[] = "$key = '". check_query($value) ."'";
   }
@@ -711,7 +702,7 @@ function _prepare_update($data) {
   return $result;
 }
 
-function _prepare_insert($data, $stage) {
+function _taxonomy_prepare_insert($data, $stage) {
   if ($stage == 1) {
     $result = implode(', ', array_keys($data));
   }
@@ -734,6 +725,8 @@ function _prepare_insert($data, $stage) 
  *   - "str_tids": A comma-separated list of the same IDs.
  *   - "operator": How to interpret multiple IDs in the array. Can be
  *     "or" or "and".
+ *   - "depth": How many levels deep to traverse the taxonomy tree. Can be a
+ *     nonnegative integer or "all".
  *
  * @param $pager
  *   Whether the nodes are to be used with a pager (the case on most Drupal
@@ -744,15 +737,28 @@ function _prepare_insert($data, $stage) 
  */
 function taxonomy_select_nodes($taxonomy, $pager = TRUE) {
   if ($taxonomy->str_tids) {
+    // Traverse hierarchies as required.
+    if ($taxonomy->depth === 'all') {
+      $taxonomy->depth = NULL;
+    }
+    $tids = $taxonomy->tids;
+    foreach ($taxonomy->tids as $tid) {
+      $term = taxonomy_get_term($tid);
+      $tree = taxonomy_get_tree($term->vid, $tid, -1, $taxonomy->depth);
+      $tids = array_merge($tids, array_map('_taxonomy_get_tid_from_term', $tree));
+    }
+    $taxonomy->tids = $tids;
+    $taxonomy->str_tids = implode(',', $taxonomy->tids);
+    
     if ($taxonomy->operator == 'or') {
-      $sql = "SELECT DISTINCT(n.nid), n.title, n.type, n.created, n.changed, n.uid, n.sticky, n.created, u.name FROM {node} n INNER JOIN {term_node} r ON n.nid = r.nid INNER JOIN {users} u ON n.uid = u.uid WHERE r.tid IN ($taxonomy->str_tids) AND n.status = 1 ORDER BY sticky DESC, created DESC";
-      $sql_count = "SELECT COUNT(DISTINCT(n.nid)) FROM {node} n INNER JOIN {term_node} r ON n.nid = r.nid INNER JOIN {users} u ON n.uid = u.uid WHERE r.tid IN ($taxonomy->str_tids) AND n.status = 1";
+      $sql = 'SELECT DISTINCT(n.nid), n.title, n.type, n.created, n.changed, n.uid, n.sticky, n.created, u.name FROM {node} n INNER JOIN {term_node} r ON n.nid = r.nid INNER JOIN {users} u ON n.uid = u.uid WHERE r.tid IN ('. $taxonomy->str_tids .') AND n.status = 1 ORDER BY sticky DESC, created DESC';
+      $sql_count = 'SELECT COUNT(DISTINCT(n.nid)) FROM {node} n INNER JOIN {term_node} r ON n.nid = r.nid INNER JOIN {users} u ON n.uid = u.uid WHERE r.tid IN ('. $taxonomy->str_tids .') AND n.status = 1';
     }
     else {
-      $sql = "SELECT n.nid, n.title, n.type, n.created, n.changed, n.uid, u.name FROM {node} n INNER JOIN {term_node} r ON n.nid = r.nid INNER JOIN {users} u ON n.uid = u.uid WHERE r.tid IN ($taxonomy->str_tids) AND n.status = 1 GROUP BY n.nid, n.title, n.type, n.created, n.changed, n.uid, u.name HAVING COUNT(n.nid) = ". count($taxonomy->tids) ." ORDER BY sticky DESC, created DESC";
+      $sql = 'SELECT n.nid, n.title, n.type, n.created, n.changed, n.uid, u.name FROM {node} n INNER JOIN {term_node} r ON n.nid = r.nid INNER JOIN {users} u ON n.uid = u.uid WHERE r.tid IN ('. $taxonomy->str_tids .') AND n.status = 1 GROUP BY n.nid, n.title, n.type, n.created, n.changed, n.uid, u.name HAVING COUNT(n.nid) = '. count($taxonomy->tids) .' ORDER BY sticky DESC, created DESC';
 
       // Special trick as we could not find anything better:
-      $count = db_num_rows(db_query("SELECT n.nid FROM {node} n INNER JOIN {term_node} r ON n.nid = r.nid WHERE r.tid IN ($taxonomy->str_tids) AND n.status = 1 GROUP BY n.nid HAVING COUNT(n.nid) = ". count($taxonomy->tids)));
+      $count = db_num_rows(db_query('SELECT n.nid FROM {node} n INNER JOIN {term_node} r ON n.nid = r.nid WHERE r.tid IN ('. $taxonomy->str_tids .') AND n.status = 1 GROUP BY n.nid HAVING COUNT(n.nid) = '. count($taxonomy->tids)));
       $sql_count = "SELECT $count";
     }
 
@@ -797,46 +803,128 @@ function taxonomy_nodeapi($node, $op, $a
   }
 }
 
-function taxonomy_page() {
+/**
+ * Menu callback; displays all nodes associated with a term.
+ */
+function taxonomy_term_page($str_tids = '', $depth = 0, $op = 'page') {
+  $taxonomy->depth = $depth;
+  
+  if (preg_match('/^([0-9]+[+ ])+[0-9]+$/', $str_tids)) {
+    $taxonomy->operator = 'or';
+    // The '+' character in a query string may be parsed as ' '.
+    $taxonomy->tids = preg_split('/[+ ]/', $str_tids);
+  }
+  else if (preg_match('/^([0-9]+,)*[0-9]+$/', $str_tids)) {
+    $taxonomy->operator = 'and';
+    $taxonomy->tids = explode(',', $str_tids);
+  }
+  else {
+    drupal_not_found();
+  }
 
-  $taxonomy->operator = arg(2);
-  $taxonomy->str_tids = check_query(arg(3));
-  $taxonomy->tids = explode(',', $taxonomy->str_tids);
-
-  if (ereg('^([0-9]+,){0,}[0-9]+$', $taxonomy->str_tids)) {
-    switch (arg(1)) {
-      case 'feed':
-        taxonomy_feed($taxonomy);
-      break;
-      default:
-      // Build title:
-      $sql = 'SELECT name FROM {term_data} WHERE tid IN (%s)';
-      $result = db_query($sql, $taxonomy->str_tids);
-      $names = array();
-      while ($term = db_fetch_object($result)) {
-        $names[] = $term->name;
-      }
+  $taxonomy->str_tids = implode(',', $taxonomy->tids);
+  
+  // Build title:
+  $result = db_query('SELECT name FROM {term_data} WHERE tid IN (%s)', $taxonomy->str_tids);
+  $names = array();
+  while ($term = db_fetch_object($result)) {
+    $names[] = $term->name;
+  }
+  $title = implode(', ', $names);
 
+  switch ($op) {
+    case 'page':
       // Build breadcrumb based on first hierarchy of first term:
       $current->tid = $taxonomy->tids[0];
       $breadcrumbs = array(array('path' => $_GET['q']));
       while ($parents = taxonomy_get_parents($current->tid)) {
         $current = array_shift($parents);
-        $breadcrumbs[] = array('path' => 'taxonomy/view/or/'. $current->tid, 'title' => $current->name);
+        $breadcrumbs[] = array('path' => 'taxonomy/term/'. $current->tid, 'title' => $current->name);
       }
       $breadcrumbs = array_reverse($breadcrumbs);
       menu_set_location($breadcrumbs);
-
-      drupal_set_html_head('<link rel="alternate" type="application/rss+xml" title="RSS - '. implode(' : ', $names) .'" href="'. url("taxonomy/feed/or/$taxonomy->str_tids") .'" />');
+      
+      drupal_set_html_head('<link rel="alternate" type="application/rss+xml" title="RSS - '. $title .'" href="'. url('taxonomy/term/'. $str_tids .'/'. $depth .'/feed') .'" />');
 
       $output = taxonomy_render_nodes(taxonomy_select_nodes($taxonomy));
-      print theme('page', $output, implode(', ', $names));
+      print theme('page', $output, $title);
+      break;
+    
+    case 'feed':
+      $term = taxonomy_get_term($taxonomy->tids[0]);
+      $channel['link'] = url('taxonomy/term/'. $str_tids .'/'. $depth, NULL, NULL, TRUE);
+      $channel['title'] = variable_get('site_name', 'drupal') .' - '. $title;
+      $channel['description'] = $term->description;
+      
+      $result = taxonomy_select_nodes($taxonomy, 0);
+      node_feed($result, $channel);
       break;
+    
+    default:
+      drupal_not_found();
+  }
+}
+
+/**
+ * Menu callback; displays all terms within a vocabulary.
+ */
+function taxonomy_vocabulary_page($str_vids = 'all', $depth = 0, $op = 'page') {
+  $taxonomy->depth = $depth;
+  
+  if (preg_match('/^([0-9]+[+ ])+[0-9]+$/', $str_vids)) {
+    $taxonomy->operator = 'or';
+    // The '+' character in a query string may be parsed as ' '.
+    $taxonomy->vids = preg_split('/[+ ]/', $str_vids);
+  }
+  else if (preg_match('/^([0-9]+,)*[0-9]+$/', $str_vids)) {
+    $taxonomy->operator = 'and';
+    $taxonomy->vids = explode(',', $str_vids);
+  }
+  else if ($str_vids == 'all') {
+    $taxonomy->operator = 'or';
+    $result = db_query('SELECT vid FROM {vocabulary}');
+    while ($voc = db_fetch_object($result)) {
+      $taxonomy->vids[] = $voc->vid;
     }
+    $title = t('all vocabularies');
   }
   else {
     drupal_not_found();
   }
+
+  $taxonomy->str_vids = implode(',', $taxonomy->vids);
+
+  // Build title:
+  if (!isset($title)) {
+    $result = db_query('SELECT name FROM {vocabulary} WHERE vid IN (%s)', $taxonomy->str_vids);
+    $names = array();
+    while ($term = db_fetch_object($result)) {
+      $names[] = $term->name;
+    }
+    $title = implode(', ', $names);
+  }
+
+  switch ($op) {
+    case 'page':      
+      drupal_set_html_head('<link rel="alternate" type="application/rss+xml" title="RSS - '. $title .'" href="'. url('taxonomy/vocabulary/'. $str_vids .'/'. $depth .'/feed') .'" />');
+
+      //$output = taxonomy_render_nodes(taxonomy_select_nodes($taxonomy));
+      print theme('page', $output, $title);
+      break;
+    
+    case 'feed':
+      $voc = taxonomy_get_vocabulary($taxonomy->vids[0]);
+      $channel['link'] = url('taxonomy/vocabulary/'. $str_tids .'/'. $depth, NULL, NULL, TRUE);
+      $channel['title'] = variable_get('site_name', 'drupal') .' - '. $title;
+      $channel['description'] = $voc->description;
+      
+      //$result = taxonomy_select_nodes($taxonomy, 0);
+      //node_feed($result, $channel);
+      break;
+    
+    default:
+      drupal_not_found();
+  }
 }
 
 /**
@@ -955,4 +1043,12 @@ function taxonomy_help($section = 'admin
       <p>Every term, or collection of terms, provides an <a href=\"%userland-rss\">RSS</a> feed to which interested users may subscribe. The URL format for a sample RSS feed is <a href=\"%sample-rss\">node/feed/or/1,2</a>. Built like a Taxonomy URL, <a href=\"%taxo-help\">see above</a> it starts with \"node/feed\", then has the querystring parameter, and finally the Term IDs.</p>", array('%classification-types' => 'http://www.eleganthack.com/archives/002165.html#002165', '%drupal-dis' => 'http://www.drupal.org/node/55', '%slashdot' => 'http://www.slashdot.com/', '%taxo-example' => url('taxonomy/page/or/1,2'), '%taxo-overview' => url('admin/taxonomy'), '%userland-rss' => 'http://backend.userland.com/stories/rss', '%sample-rss' => url('node/feed/or/1,2'), '%taxo-help' => url('admin/taxonomy/help', NULL, 'taxonomy-url')));
   }
 }
+
+/**
+ * Helper function for array_map purposes.
+ */
+function _taxonomy_get_tid_from_term($term) {
+  return $term->tid;
+}
+
 ?>
