Index: apachesolr.module
===================================================================
--- apachesolr.module	(revision 28232)
+++ apachesolr.module	(working copy)
@@ -931,6 +931,14 @@
     '#description' => t('A facet can be generated corresponding to all documents entirely missing this field.'),
     '#default_value' => isset($facet_missing[$module][$delta]) ? $facet_missing[$module][$delta] : 0,
   );
+  $reflect_hierarchy = variable_get('apachesolr_facet_reflect_hierarchy', array());
+  $form['apachesolr_facet_reflect_hierarchy'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Links reflect hierarchy'),
+    '#description' => t('Facet will be sorted hierarchically if it bases on a hierarchical taxonomy vocabulary.'),
+    '#default_value' => !empty($reflect_hierarchy[$module][$delta]),
+  );
+
   return $form;
 }
 
@@ -951,6 +959,9 @@
   $facet_missing = variable_get('apachesolr_facet_missing', array());
   $facet_missing[$module][$delta] = (int)$edit['apachesolr_facet_missing'];
   variable_set('apachesolr_facet_missing', $facet_missing);
+  $reflect_hierarchy = variable_get('apachesolr_facet_reflect_hierarchy', array());
+  $reflect_hierarchy[$module][$delta] = $edit['apachesolr_facet_reflect_hierarchy'];
+  variable_set('apachesolr_facet_reflect_hierarchy', $reflect_hierarchy);
 }
 
 /**
@@ -1395,8 +1406,11 @@
     drupal_add_js(drupal_get_path('module', 'apachesolr') . '/apachesolr.js');
     // Split items array into displayed and hidden.
     $hidden_items = array_splice($items, $display_limit);
-    foreach ($hidden_items as $link) {
-      $items[] = array('data' => $link, 'class' => 'apachesolr-hidden-facet');
+    foreach ($hidden_items as $hidden_item) {
+      if (!is_array($hidden_item)) {
+        $hidden_item = array('data' => $hidden_item);
+      }
+      $items[] = $hidden_item + array('class' => 'apachesolr-hidden-facet');
     }
   }
   $admin_link = '';
Index: apachesolr_search.module
===================================================================
--- apachesolr_search.module	(revision 28232)
+++ apachesolr_search.module	(working copy)
@@ -565,51 +565,83 @@
         $initial_limits = variable_get('apachesolr_facet_query_initial_limits', array());
         $limit_default = variable_get('apachesolr_facet_query_initial_limit_default', 10);
 
+        $reflect_hierarchy = variable_get('apachesolr_facet_reflect_hierarchy', array());
+
         // Handle taxonomy vocabulary facets
         if ((strpos($delta, 'im_vid_') === 0) && module_exists('taxonomy')) {
 
           if (is_object($response->facet_counts->facet_fields->$delta)) {
-            $contains_active = FALSE;
-            $terms = array();
+            $vid = substr($delta, 7);
+            if (is_numeric($vid)) {
+              $vocab = taxonomy_vocabulary_load($vid);
+              $terms = array();
 
-            foreach ($response->facet_counts->facet_fields->$delta as $tid => $count) {
-              $options = array();
-              if ($tid == '_empty_') {
-                // TODO - for now we don't handle facet missing.
-                continue;
-              }
-              $unclick_link = '';
-              unset($active);
-              $term = taxonomy_get_term($tid);
-              $new_query = clone $query;
-              if ($active = $query->has_filter('tid', $tid)) {
-                $contains_active = TRUE;
-                $new_query->remove_filter('tid', $term->tid);
-                $options['query'] = $new_query->get_url_querystring();
-                $link = theme('apachesolr_unclick_link', $term->name, $new_query->get_path(), $options);
-              }
-              else {
-                $new_query->add_filter('tid', $term->tid);
-                $options['query'] = $new_query->get_url_querystring();
-                $link = theme('apachesolr_facet_link', $term->name, $new_query->get_path(), $options, $count, $active, $response->response->numFound);
+              foreach ($response->facet_counts->facet_fields->$delta as $tid => $count) {
+                if ($tid == '_empty_') {
+                  // TODO - for now we don't handle facet missing.
+                  continue;
+                }
+                $unclick_link = '';
+                unset($active);
+
+                $parents = array();
+                if ($reflect_hierarchy['apachesolr_search'][$delta]) {
+                  $parents = taxonomy_get_parents_all($tid);
+                  if (!empty($parents[1]) && !$query->has_filter('tid', $parents[1]->tid)) {
+                    continue;
+                  }
+
+                }
+                else if ($term = taxonomy_get_term($tid)) {
+                  $parents[] = $term;
+                }
+
+                $parents = array_reverse($parents);
+
+                foreach ($parents as $key => $term) {
+                  $parents[$key] = apachesolr_search_localize_taxonomy_term($term);
+                }
+
+                $options = array();
+                $active = $query->has_filter('tid', $tid);
+                if ($active) {
+                  $link = apachesolr_search_get_unclick_link($query, $term->name, 'tid', $tid, $reflect_hierarchy['apachesolr_search'][$delta] ? $vid : FALSE);
+                }
+                else {
+                  $new_query = clone $query;
+                  $new_query->add_filter('tid', $tid);
+                  $options = array('query' => $new_query->get_url_querystring());
+                  $link = theme('apachesolr_facet_link', $term->name, $new_query->get_path(), $options, $count, $active, $response->response->numFound);
+                }
+
+                // if numdocs == 1 and !active, don't add.
+                if ($response->response->numFound > 1 || $active) {
+                  $sort_key = '';
+                  $term = NULL;
+                  foreach ($parents as $term) {
+                    $count = $response->facet_counts->facet_fields->$delta->{$term->tid};
+                    $countsort = $count == 0 ? '' : 1 / $count;
+                    $countsort = $query->has_filter('tid', $term->tid) ? $countsort : 1 + $countsort;
+                    $sort_key .= ($sort_key ? '|' : '') . $countsort . ':' . $term->name;
+                  }
+                 $terms[$term->vid][$sort_key] = $link;
+                }
               }
-              $countsort = $count == 0 ? '' : 1 / $count;
-              // if numdocs == 1 and !active, don't add.
-              if ($response->response->numFound > 1 || $active) {
-                $terms[$term->vid][$active ? $countsort . $term->name : 1 + $countsort . $term->name] = $link;
+
+              // Process all terms into an item list
+              if (is_array($terms) && isset($terms[$vid]) && is_array($terms[$vid])) {
+
+                // Create array of nested items
+                $items = _apachesolr_search_nested_facet_items($terms[$vid]);
+
+                $limit = isset($initial_limits['apachesolr_search'][$delta]) ? $initial_limits['apachesolr_search'][$delta] : $limit_default;
+                return array(
+                  'subject' => t('Filter by @name', array('@name' => $vocab->name)),
+                  'content' => theme('apachesolr_facet_list', $items, $limit),
+                );
               }
             }
           }
-          $vid = substr($delta, 7);
-          $vocab = taxonomy_vocabulary_load($vid);
-          if (is_numeric($vid) && is_array($terms) && isset($terms[$vid]) && is_array($terms[$vid])) {
-            ksort($terms[$vid]);
-            $limit = isset($initial_limits['apachesolr_search'][$delta]) ? $initial_limits['apachesolr_search'][$delta] : $limit_default;
-            return array(
-              'subject' => t('Filter by @name', array('@name' => $vocab->name)),
-              'content' => theme('apachesolr_facet_list', $terms[$vid], $limit),
-            );
-          }
           return;
         }
 
@@ -624,14 +656,18 @@
             $links[] = apachesolr_l($query->get_query_basic(), $path, $options);
             foreach($fields as $field) {
               if ($field['#name']) {
-                $new_query = clone $query;
-                $new_query->remove_filter($field['#name'], $field['#value']);
-                $options['query'] = $new_query->get_url_querystring();
-                $fielddisplay = theme("apachesolr_breadcrumb_". $field['#name'], $field['#value']);
-                if (!$fielddisplay) {
+                $vid = FALSE;
+                if (module_exists('taxonomy') && 'tid' == $field['#name']) {
+                  $term = taxonomy_get_term($field['#value']);
+                  if ($reflect_hierarchy['apachesolr_search']['im_vid_'.$term->vid]) {
+                    $vid = $term->vid;
+                  }
+                }
+
+                if (! $fielddisplay = theme("apachesolr_breadcrumb_". $field['#name'], $field['#value'])) {
                   $fielddisplay = $field['#value'];
                 }
-                $links[] = theme('apachesolr_unclick_link', $fielddisplay, $new_query->get_path(), $querystring);
+                $links[] = apachesolr_search_get_unclick_link($query, $fielddisplay, $field['#name'], $field['#value'], $vid);
               }
             }
             $content = theme('apachesolr_currentsearch', $response->response->numFound, $links);
@@ -678,6 +714,47 @@
 }
 
 /**
+ * Creates a link to trigger a new solr search after removing a previously
+ * selected search filter. Therefor you have to pass the filters's field name and value.
+ *
+ * If the filter is about a taxonomy term and a coresponding vocabulary id is given,
+ * the filter won't be simply removed. If this vocabulary is a hierarchical one,
+ * all children of the term which are present in current query will be removed, too.
+ *
+ * (Paramter $vid is a switch to respect hierarchies in vocabularies or not when creating
+ * an unclick link. But in order to increase performance you not simply pass TRUE but
+ * vocabulary's id to not waste time by detecting it from term's id)
+ *
+ * @param $query current apachesolr query
+ * @param $facet_text facet_text
+ * @param $field_name field name
+ * @param $field_value field value to be removed from current query
+ * @param $vid integer vocabulary id
+ * @return string themed unclick link
+ */
+function apachesolr_search_get_unclick_link(&$query, $facet_text, $field_name, $field_value, $vid = FALSE) {
+  static $unclick_links = array();
+
+  if (empty($unclick_links[$field_name][$field_value])) {
+    $new_query = clone $query;
+
+    $new_query->remove_filter($field_name, $field_value);
+
+    if ($vid && 'tid' == $field_name) {
+      $childs = taxonomy_get_tree($vid, $field_value);
+      foreach ($childs as $child) {
+        $new_query->remove_filter('tid', $child->tid);
+      }
+    }
+
+    $options['query'] = $new_query->get_url_querystring();
+    $unclick_links[$field_name][$field_value] = theme('apachesolr_unclick_link', $facet_text, $new_query->get_path(), $options);
+  }
+
+  return $unclick_links[$field_name][$field_value];
+}
+
+/**
  * Callback function for the 'Filter by book' facet block.
  */
 function apachesolr_search_get_book($facet, &$options) {
@@ -996,3 +1073,24 @@
   return implode(' ... ', $snippets) .' ...';
 }
 
+
+/**
+ * Return nested array of nested facet values for use with theme_item_list()
+ */
+function _apachesolr_search_nested_facet_items($input, $parent = '', $level = 0) {
+  if ($level == 0) {
+    ksort($input);
+  }
+  $result = array();
+  foreach ($input as $key => $item) {
+    // For items on current level, and their $key is son of $parent
+    if (substr_count($key, "|") == $level && ($parent == '' || strpos($key, $parent) === 0)) {
+      // Add this item and all items on next level
+      $result[$key] = array(
+        'data' => $item,
+        'children' => _apachesolr_search_nested_facet_items($input, $key, $level+1)
+      );
+    }
+  }
+  return $result;
+}
