diff --git a/og.js b/og.js
index 1ffe812..834a7ea 100644
--- a/og.js
+++ b/og.js
@@ -42,6 +42,10 @@ Drupal.verticalTabs.og = function() {
       return Drupal.t('May not be posted into a group.');
       break;
       
+    case 'group_post_group':
+      return Drupal.t('Group node and/or post');
+      break;
+      
     case 'group_post_standard':
       return Drupal.t('Standard group post');
       break;
diff --git a/og.module b/og.module
index 9d7a089..79d7972 100644
--- a/og.module
+++ b/og.module
@@ -397,6 +397,8 @@ function og_theme() {
     'opml_icon' => array('arguments' => array('url')),
     'og_format_subscriber_status' => array('arguments' => array('group')),
     'og_mission' => array('template' => 'og-mission', 'arguments' => array('form' => NULL), 'path' => drupal_get_path('module', 'og'). '/theme'),
+    'og_tree' => array('arguments' => array('tree' => NULL, 'group' => NULL, 'skipSingle' => NULL)),
+    'og_tree_branch' => array('arguments' => array('branch' => NULL, 'group' => NULL)),
   );
 }
 
@@ -480,6 +482,147 @@ function og_init() {
 }
 
 /**
+ * Create a family tree for a specific group
+ *
+ * @param $group
+ *   The group object
+ */
+function og_get_group_tree($group) {
+  // Avoid running this more than once on runtime
+  static $group_tree;
+  if (isset($group_tree[$group->nid])) {
+    return $group_tree[$group->nid];
+  }
+  // See if we have the tree in the cache
+  else if ($cache = cache_get("og_tree:{$group->nid}")) {
+    if (isset($cache->data) && !empty($cache->data)) {
+      return $cache->data;
+    }
+  }
+  
+  // Fetch a complete group tree
+  $tree = og_get_tree();
+  
+  // Iterate a tree of all available groups
+  foreach ($tree as $nid => $branch) {
+    // If this group is a branch root, then take the entire branch
+    if ($nid == $group->nid) {
+      $group_tree[$group->nid][$nid] = $tree[$nid]; 
+    }
+    // Iterate the entire branch to see if this group is in it
+    else {
+      if (og_group_in_branch($group, $branch)) {
+        $group_tree[$group->nid][$nid] = $tree[$nid];
+      }
+    }
+  }
+  
+  // Cache this tree
+  cache_set("og_tree:{$group->nid}", $group_tree[$group->nid]);
+  
+  return $group_tree[$group->nid]; 
+}
+
+/**
+ * Recursive callback to determine if a group is inside 
+ * a tree branch
+ * 
+ * @param $group
+ *   The group object
+ * @param $branch
+ *   A branch of a group tree
+ * @return
+ *   TRUE if the group is inside the branch, otherwise FALSE
+ */
+function og_group_in_branch($group, $branch) {
+  foreach ($branch->children as $child_nid => $child) {
+    if ($child_nid == $group->nid) {
+      return TRUE;
+    }
+    else if (!empty($child->children)) {
+      if (og_group_in_branch($group, $child)) {
+        return TRUE;
+      }
+    }
+  }
+  
+  return FALSE;
+}
+
+/**
+ * Create a family tree of all groups
+ */
+function og_get_tree() {
+  // Avoid running this more than once on runtime
+  static $tree;
+  if ($tree) {
+    return $tree;
+  }
+  // See if we have the tree in the cache
+  else if ($tree = cache_get('og_tree')) {
+    if (isset($tree->data) && !empty($tree->data)) {
+      return $tree->data;
+    }
+  }
+
+  // Query to fetch all groups and their parents
+  $sql = "SELECT og.nid, oga.group_nid, n.title FROM {og} og LEFT JOIN {og_ancestry} oga ON og.nid = oga.nid INNER JOIN {node} n on og.nid = n.nid ORDER BY og.nid ASC";
+
+  // Execute the query
+  $results = db_query($sql);
+
+  // Move all results into an array
+  $groups = array();
+  while ($result = db_fetch_object($results)) {
+    $result->children = array();
+    $groups[] = $result;
+  }
+
+  // Create the tree
+  $tree = array();
+  foreach ($groups as $key => $group) {
+    // If no group_nid, this group is only a parent
+    if ($group->group_nid == NULL) {
+      $tree[$group->nid] = $group;
+      $tree[$group->nid]->children = og_tree_find_children($group, $groups);
+    }
+  }
+  
+  // Cache the tree
+  cache_set('og_tree', $tree);
+
+  return $tree;
+}
+
+/**
+ * Recursive callback to extract all children 
+ * 
+ * @param $parent
+ *   The parent group
+ * @param $groups
+ *   All available groups
+ * @return
+ *   An array of group children
+ */
+function og_tree_find_children($parent, $groups) {
+  $children = array();
+  
+  // Iterate through the groups
+  foreach ($groups as $key => $group) {
+    // If no group_nid, this group is only a parent
+    if ($parent->nid == $group->group_nid) {
+      // First find any children of this group
+      $group->children = og_tree_find_children($group, $groups);
+      
+      // Add the child
+      $children[$group->nid] = $group;
+    }
+  }
+  
+  return $children;
+}
+
+/**
  * Set session variable thats used to determine group context when node is in multiple groups.
  * @see og_determine_context().
  */
@@ -1145,7 +1288,7 @@ function og_group_form($node, $form_state) {
 // Returns all the group affiliations for a given node.
 function og_get_node_groups($node) {
   $groups = array();
-  if (!og_is_group_type($node->type)) {
+  if (og_is_group_post_type($node->type)) {
     $result = og_get_node_groups_result($node->nid);
     while ($row = db_fetch_object($result)) {
       $groups[$row->group_nid] = $row->title;
@@ -1214,7 +1357,7 @@ function og_devel_generate(&$node) {
       }
     }
   }
-  elseif (og_is_group_post_type($node->type)) {
+  else if (og_is_group_post_type($node->type)) {
     $types = og_get_types('group');
     $placeholders = db_placeholders($types, 'varchar');
     $sql = "SELECT nid FROM {node} WHERE type IN ($placeholders) AND status = 1 ORDER BY RAND()";
@@ -1258,9 +1401,8 @@ function og_get_breadcrumb($group_node) {
 }
 
 /**
- * Implementation of hook_nodeapi().
- *
-*/
+ * Implementation of hook_nodeapi()
+ */
 function og_nodeapi(&$node, $op, $teaser = NULL, $page = NULL) {
   global $user;
 
@@ -1280,7 +1422,8 @@ function og_nodeapi(&$node, $op, $teaser = NULL, $page = NULL) {
       if (og_is_group_type($node->type)) {
         og_load_group($node);
       }
-      elseif ($grps = og_get_node_groups($node)) {
+      
+      if ($grps = og_get_node_groups($node)) {
         // TODO: Refactor so we don't need 2 arrays.
         $node->og_groups = drupal_map_assoc(array_keys($grps));
         $node->og_groups_both = $grps;
@@ -1292,7 +1435,7 @@ function og_nodeapi(&$node, $op, $teaser = NULL, $page = NULL) {
     case 'validate':
       // Ensure that a group is selected if groups are required. needed when
       // author has no groups. In other cases, fapi does the validation.
-      if (og_is_group_post_type($node->type) && variable_get('og_audience_required', FALSE) && !user_access('administer nodes')) {
+      if (!og_is_group_type($node->type) && variable_get('og_audience_required', FALSE) && !user_access('administer nodes')) {
         if (!isset($node->og_groups)) {
           form_set_error('title', t('You must <a href="@join">join a group</a> before posting on this web site.', array('@join' => url('og'))));
         }
@@ -1349,7 +1492,8 @@ function og_nodeapi(&$node, $op, $teaser = NULL, $page = NULL) {
           module_invoke_all('og', 'admin new', $node->nid, $account->uid, $message);
         }
       }
-      else {
+      
+      if (og_is_group_post_type($node->type)) {
         og_save_ancestry($node);
       }
       break;
@@ -1365,7 +1509,8 @@ function og_nodeapi(&$node, $op, $teaser = NULL, $page = NULL) {
           }
         }
       }
-      else {
+      
+      if (og_is_group_post_type($node->type)) {
         og_save_ancestry($node);
       }
       break;
@@ -1405,6 +1550,16 @@ function og_nodeapi(&$node, $op, $teaser = NULL, $page = NULL) {
       }
       break;
   }
+  
+  // Switch again just to clear hierarchy cache
+  switch ($op) {
+    case 'insert':
+    case 'update':
+    case 'delete':
+      if (og_is_group_type($node->type)) {
+        cache_clear_all('og_', 'cache', TRUE);
+      }
+  }
 }
 
 function og_msgid_server() {
@@ -1434,7 +1589,8 @@ function og_form_alter(&$form, &$form_state, $form_id) {
       $form['author']['name']['#title'] = t('Group manager');
       $form['options']['sticky']['#title'] = t('Sticky at top of group home page and other lists.');
     }
-    elseif (og_is_group_post_type($node->type)) {
+    
+    if (og_is_group_post_type($node->type)) {
       if ($group_node = og_get_group_context()) {
         $bc = og_get_breadcrumb($group_node);
         if (isset($node->nid)) {
@@ -1508,6 +1664,7 @@ function og_form_node_type_form_alter(&$form, &$form_state) {
   else {
     $usage = variable_get('og_content_type_usage_'. $form['identity']['type']['#value'], 'omitted');
   }
+  
   // Persist $usage so that we can rebuild node access as needed.
   $form['old_og_content_type_usage'] = array(
     '#type' => 'value',
@@ -2055,9 +2212,10 @@ function og_node_type($op, $info) {
 
 function og_types_map() {
   $usages = array(
-    'group' => t('Group node'),
     'omitted' => t('May not be posted into a group.'),
-    'group_post_standard' => t('Standard group post (typically only author may edit).')
+    'group' => t('Group node'),
+    'group_post_group' => t('Group node which can also be posted into an existing group'),
+    'group_post_standard' => t('Standard group post (typically only author may edit).'),
   );
 
   if (module_exists('og_access')) {
@@ -2072,7 +2230,12 @@ function og_get_types($usage) {
   $types = node_get_types();
   foreach ($types as $type) {
     if ($usage == 'group_post') {
-      if (!og_is_omitted_type($type->type) && !og_is_group_type($type->type)) {
+      if (og_is_group_post_type($type->type)) {
+        $return[$usage][] = $type->type;
+      }
+    }
+    else if ($usage == 'group') {
+      if (og_is_group_type($type->type)) {
         $return[$usage][] = $type->type;
       }
     }
@@ -2108,7 +2271,20 @@ function og_is_omitted_type($type) {
  * @return boolean
  */
 function og_is_group_type($type) {
-  return variable_get('og_content_type_usage_'. $type, 'omitted') == 'group';
+  $mode = variable_get('og_content_type_usage_'. $type, 'omitted');
+  return $mode == 'group' || og_is_group_post_group_type($type);
+}
+
+/**
+ * API function for determining whether a given node type has been designated
+ * by admin to behave as a group node and a group post
+ *
+ * @param string $type
+ * @return boolean
+ */
+function og_is_group_post_group_type($type) {
+  $mode = variable_get('og_content_type_usage_'. $type, 'omitted');
+  return $mode == 'group_post_group';
 }
 
 /**
@@ -2134,7 +2310,9 @@ function og_block($op = 'list', $delta = 0, $edit = array()) {
 
     // Now provided by og_views. Please don't reuse this number 6
     // $blocks[6]['info'] = t('Group network');
-
+    
+    $blocks[7]['info'] = t('Group hierarchy');
+    $blocks[7]['cache'] = BLOCK_NO_CACHE;
 
     // Auto-enable the group blocks for fresh installations.
     // TODOL: In order for this to work, you must rehash blocks during install which has been problematic.
@@ -2153,6 +2331,8 @@ function og_block($op = 'list', $delta = 0, $edit = array()) {
         return og_block_details();
       case 3:
        return og_block_new();
+      case 7:
+       return og_block_hierarchy();
     }
   }
   elseif ($op == 'configure') {
@@ -2200,6 +2380,70 @@ function og_block($op = 'list', $delta = 0, $edit = array()) {
 }
 
 /**
+ * Theme a group hierachy tree
+ * 
+ * @param $tree
+ *   An array of tree branches
+ * @param $group
+ *   Optionally supply a group to focus
+ * @param $skipSingle
+ *   If set to TRUE, skip trees that only have one parent with
+ *   no children
+ * @return
+ *   An HTML-rendered tree of group links
+ */
+function theme_og_tree($tree, $group = NULL, $skipSingle = TRUE) {
+  $content = '';
+  
+  foreach ($tree as $branch) {
+    // If we're skipping single-item trees
+    if ($skipSingle) {
+      // Check the branch count
+      if (count($tree) < 2) {
+        // Only one branch available
+        if (empty($branch->children)) {
+          // And no children so end here
+          return NULL; 
+        }
+      }
+      
+      // Avoid this check again
+      $skipSingle = FALSE;
+    }
+    
+    $content .= '<div class="og-tree">';
+    $content .= '<h4>' . l($branch->title, "node/{$branch->nid}") . '</h4>';
+    if (!empty($branch->children)) {
+      $content .= '<ul>' . theme('og_tree_branch', $branch->children, $group) . '</ul>';
+    }
+    $content .= '</div>';
+  }
+  
+  return $content;
+}
+
+/**
+ * Theme a group hierachy tree branch
+ * 
+ * @param $branch
+ *   An array of tree branch stems
+ * @param $group
+ *   Optionally supply a group to focus
+ * @return
+ *   An HTML-rendered tree of group links
+ */
+function theme_og_tree_branch($branch, $group = NULL) {
+  $content = '';
+  foreach ($branch as $stem) {
+    $content .= '<li>' . l($stem->title, "node/{$stem->nid}") . '</li>';
+    if (!empty($stem->children)) {
+      $content .= '<ul>' . theme('og_tree_branch', $stem->children, $group) . '</ul>';
+    }
+  }
+  return $content;
+}
+
+/**
  * Return code that emits an XML icon. TODOL: this opml icon belongs in theme.inc
  */
 function theme_opml_icon($url) {
@@ -2226,6 +2470,23 @@ function og_block_new() {
   }
 }
 
+/**
+ * Provide a block that displays a groups hierachy
+ */
+function og_block_hierarchy() {
+  if ($group = og_get_group_context()) {
+    if (og_is_group_type($group->type)) {
+      if ($tree = og_get_group_tree($group)) {
+        $block = array(
+          'subject' => t('Group hierarchy'),
+          'content' => theme('og_tree', $tree, $group),
+        );
+        return $block;
+      }
+    }
+  }
+}
+
 function og_block_details() {
   global $user;
 
@@ -2452,4 +2713,4 @@ function og_check_token($token, $seed) {
  */
 function og_broadcast_access($node) {
   return og_is_group_admin($node) && module_exists('og_notifications');
-}
\ No newline at end of file
+}
