Index: apachesolr.index.inc
===================================================================
RCS file: apachesolr.index.inc
diff -N apachesolr.index.inc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ apachesolr.index.inc	16 Apr 2009 01:16:19 -0000
@@ -0,0 +1,232 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ *   Functions used when indexing content to Apache Solr.
+ */
+
+/**
+ * Add a document to the $documents array based on a node ID.
+ */
+function apachesolr_add_node_document(&$documents, $nid) {
+  if ($document = apachesolr_node_to_document($nid)) {
+    $documents[] = $document;
+  }
+}
+
+/**
+ * Strip control characters that cause Jetty/Solr to fail.
+ */
+function apachesolr_strip_ctl_chars($text) {
+  // See:  http://w3.org/International/questions/qa-forms-utf-8.html
+  // Printable utf-8 does not include any of these chars below x7F
+  return preg_replace('@[\x00-\x08\x0B\x0C\x0E-\x1F]@', ' ', $text);
+}
+
+/**
+ * Strip html tags and also control characters that cause Jetty/Solr to fail.
+ */
+function apachesolr_clean_text($text) {
+  $text = preg_replace('@[\x00-\x08\x0B\x0C\x0E-\x1F]@', ' ', $text);
+  // Add spaces before stripping tags to avoid running words together.
+  return strip_tags(str_replace(array('<', '>'), array(' <', '> '), $text));
+}
+
+/**
+ * Given a node ID, return a document representing that node.
+ */
+function apachesolr_node_to_document($nid) {
+  // Set reset = TRUE to avoid static caching of all nodes that get indexed.
+  $node = node_load($nid, NULL, TRUE);
+  if (empty($node)) {
+    return FALSE;
+  }
+
+  $document = FALSE;
+  // Let any module exclude this node from the index.
+  $build_document = TRUE;
+  foreach (module_implements('apachesolr_node_exclude') as $module) {
+    $exclude = module_invoke($module, 'apachesolr_node_exclude', $node);
+    if (!empty($exclude)) {
+      $build_document = FALSE;
+    }
+  }
+
+  if ($build_document) {
+    // Build the node body.
+    $node->build_mode = NODE_BUILD_SEARCH_INDEX;
+    $node = node_build_content($node, FALSE, FALSE);
+    $node->body = drupal_render($node->content);
+    $node->title = apachesolr_clean_text($node->title);
+
+    $text = $node->body;
+
+    // Fetch extra data normally not visible, including comments.
+    $extra = node_invoke_nodeapi($node, 'update index');
+    $text .= "\n\n" . implode(' ', $extra);
+    $text = apachesolr_strip_ctl_chars($text);
+
+    $document = new Apache_Solr_Document();
+    $document->id = apachesolr_document_id($node->nid);
+    $document->site = url(NULL, array('absolute' => TRUE));
+    $document->hash = apachesolr_site_hash();
+    $document->nid = $node->nid;
+    $document->uid = $node->uid;
+    $document->title = $node->title;
+    $document->status = $node->status;
+    $document->sticky = $node->sticky;
+    $document->promote = $node->promote;
+    $document->moderate = $node->moderate;
+    $document->tnid = $node->tnid;
+    $document->translate = $node->translate;
+    if (!empty($node->language)) {
+      $document->language = $node->language;
+    }
+    $document->body = strip_tags(str_replace(array('<', '>'), array(' <', '> '), $text));
+    $document->type  = $node->type;
+    $document->type_name = apachesolr_strip_ctl_chars(node_get_types('name', $node));
+    $document->created = apachesolr_date_iso($node->created);
+    $document->changed = apachesolr_date_iso($node->changed);
+    $last_change = (isset($node->last_comment_timestamp) && $node->last_comment_timestamp > $node->changed) ? $node->last_comment_timestamp : $node->changed;
+    $document->last_comment_or_change = apachesolr_date_iso($last_change);
+    $document->comment_count = isset($node->comment_count) ? $node->comment_count : 0;
+    $document->name = apachesolr_strip_ctl_chars($node->name);
+
+    $path = 'node/' . $node->nid;
+    $document->url = url($path, array('absolute' => TRUE));
+    $document->path = $path;
+    // Path aliases can have important information about the content.
+    // Add them to the index as well.
+    if (function_exists('drupal_get_path_alias')) {
+      // Add any path alias to the index, looking first for language specific
+      // aliases but using language neutral aliases otherwise.
+      $language = empty($node->language) ? '' : $node->language;
+      $output = drupal_get_path_alias($path, $language);
+      if ($output && $output != $path) {
+        $document->path_alias = apachesolr_strip_ctl_chars($output);
+      }
+    }
+
+    // Get CCK fields list
+    $cck_fields = apachesolr_cck_fields();
+    foreach ($cck_fields as $key => $cck_info) {
+      if (isset($node->$key)) {
+        // Got a CCK field. See if it is to be indexed.
+        $function = $cck_info['callback'];
+        if ($cck_info['callback'] && function_exists($function)) {
+          $field = call_user_func_array($function, array($node, $key));
+        }
+        else {
+          $field = $node->$key;
+        }
+        $index_key = apachesolr_index_key($cck_info);
+        foreach ($field as $value) {
+          // Don't index NULLs or empty strings
+          if (isset($value['safe']) && strlen($value['safe'])) {
+            if ($cck_info['multiple']) {
+              $document->setMultiValue($index_key, apachesolr_clean_text($value['safe']));
+            }
+            else {
+              $document->$index_key = apachesolr_clean_text($value['safe']);
+            }
+          }
+        }
+      }
+    }
+    // Index book module data.
+    if (!empty($node->book['bid'])) {
+      // Hard-coded - must change if apachesolr_index_key() changes.
+      $document->is_book_bid = (int) $node->book['bid'];
+    }
+    apachesolr_add_tags_to_document($document, $text);
+    apachesolr_add_taxonomy_to_document($document, $node);
+
+    // Let modules add to the document - TODO convert to drupal_alter().
+    foreach (module_implements('apachesolr_update_index') as $module) {
+      $function = $module .'_apachesolr_update_index';
+      $function($document, $node);
+    }
+  }
+  return $document;
+}
+
+/**
+ * Extract taxonomy from $node and add to dynamic fields.
+ */
+function apachesolr_add_taxonomy_to_document(&$document, $node) {
+  if (isset($node->taxonomy) && is_array($node->taxonomy)) {
+    foreach ($node->taxonomy as $term) {
+      // Double indexing of tids lets us do effecient searches (on tid)
+      // and do accurate per-vocabulary faceting.
+
+      // By including the ancestors to a term in the index we make
+      // sure that searches for general categories match specific
+      // categories, e.g. Fruit -> apple, a search for fruit will find
+      // content categorized with apple.
+      $ancestors = taxonomy_get_parents_all($term->tid);
+      foreach ($ancestors as $ancestor) {
+        $document->setMultiValue('tid', $ancestor->tid);
+        $document->setMultiValue('im_vid_'. $ancestor->vid, $ancestor->tid);
+        $name = apachesolr_clean_text($ancestor->name);
+        $document->setMultiValue('vid', $ancestor->vid);
+        $document->{'ts_vid_'. $ancestor->vid .'_names'} .= ' '. $name;
+        // We index each name as a string for cross-site faceting
+        // using the vocab name rather than vid in field construction .
+        $document->setMultiValue('sm_vid_'. apachesolr_vocab_name($ancestor->vid), $name);
+      }
+    }
+  }
+}
+
+/**
+ * Helper function - return a safe (PHP identifier) vocabulary name.
+ */
+function apachesolr_vocab_name($vid) {
+  static $names = array();
+
+  if (!isset($names[$vid])) {
+    $vocab_name = db_result(db_query('SELECT v.name FROM {vocabulary} v WHERE v.vid = %d', $vid));
+    $names[$vid] = preg_replace('/[^a-zA-Z0-9_\x7f-\xff]/', '_', $vocab_name);
+    // Fallback for names ending up all as '_'.
+    $check = rtrim($names[$vid], '_');
+    if (!$check) {
+      $names[$vid] = '_' . $vid . '_';
+    }
+  }
+  return $names[$vid];
+}
+
+/**
+ * Extract HTML tag contents from $text and add to boost fields.
+ *
+ * $text must be stripped of control characters before hand.
+ */
+function apachesolr_add_tags_to_document(&$document, $text) {
+  $tags_to_index = variable_get('apachesolr_tags_to_index', array(
+    'h1' => 'tags_h1',
+    'h2' => 'tags_h2_h3',
+    'h3' => 'tags_h2_h3',
+    'h4' => 'tags_h4_h5_h6',
+    'h5' => 'tags_h4_h5_h6',
+    'h6' => 'tags_h4_h5_h6',
+    'u' => 'tags_inline',
+    'b' => 'tags_inline',
+    'i' => 'tags_inline',
+    'strong' => 'tags_inline',
+    'em' => 'tags_inline',
+    'a' => 'tags_a'
+  ));
+
+  // Strip off all ignored tags.
+  $text = strip_tags($text, '<'. implode('><', array_keys($tags_to_index)) .'>');
+
+  preg_match_all('@<('. implode('|', array_keys($tags_to_index)) .')[^>]*>(.*)</\1>@Ui', $text, $matches);
+  foreach ($matches[1] as $key => $tag) {
+    // We don't want to index links auto-generated by the url filter.
+    if ($tag != 'a' || !preg_match('@(?:http://|https://|ftp://|mailto:|smb://|afp://|file://|gopher://|news://|ssl://|sslv2://|sslv3://|tls://|tcp://|udp://|www\.)[a-zA-Z0-9]+@', $matches[2][$key])) {
+      $document->{$tags_to_index[$tag]} .= ' '. $matches[2][$key];
+    }
+  }
+}
+
Index: apachesolr.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/apachesolr/apachesolr.module,v
retrieving revision 1.1.2.12.2.128
diff -u -p -r1.1.2.12.2.128 apachesolr.module
--- apachesolr.module	15 Apr 2009 23:20:02 -0000	1.1.2.12.2.128
+++ apachesolr.module	16 Apr 2009 01:16:19 -0000
@@ -262,7 +262,7 @@ function apachesolr_index_nodes($result,
     watchdog('Apache Solr', $e->getMessage(), NULL, WATCHDOG_ERROR);
     return FALSE;
   }
-
+  include_once(drupal_get_path('module', 'apachesolr') .'/apachesolr.index.inc') ;
   $documents = array();
   $old_position = apachesolr_get_last_index($namespace);
   $position = $old_position;
@@ -300,151 +300,6 @@ function apachesolr_index_nodes($result,
 }
 
 /**
- * Add a document to the $documents array based on a node ID.
- */
-function apachesolr_add_node_document(&$documents, $nid) {
-  if ($document = apachesolr_node_to_document($nid)) {
-    $documents[] = $document;
-  }
-}
-
-/**
- * Strip control characters that cause Jetty/Solr to fail.
- */
-function apachesolr_strip_ctl_chars($text) {
-  // See:  http://w3.org/International/questions/qa-forms-utf-8.html
-  // Printable utf-8 does not include any of these chars below x7F
-  return preg_replace('@[\x00-\x08\x0B\x0C\x0E-\x1F]@', ' ', $text);
-}
-
-/**
- * Strip html tags and also control characters that cause Jetty/Solr to fail.
- */
-function apachesolr_clean_text($text) {
-  $text = preg_replace('@[\x00-\x08\x0B\x0C\x0E-\x1F]@', ' ', $text);
-  // Add spaces before stripping tags to avoid running words together.
-  return strip_tags(str_replace(array('<', '>'), array(' <', '> '), $text));
-}
-
-/**
- * Given a node ID, return a document representing that node.
- */
-function apachesolr_node_to_document($nid) {
-  // Set reset = TRUE to avoid static caching of all nodes that get indexed.
-  $node = node_load($nid, NULL, TRUE);
-  if (empty($node)) {
-    return FALSE;
-  }
-
-  $document = FALSE;
-  // Let any module exclude this node from the index.
-  $build_document = TRUE;
-  foreach (module_implements('apachesolr_node_exclude') as $module) {
-    $exclude = module_invoke($module, 'apachesolr_node_exclude', $node);
-    if (!empty($exclude)) {
-      $build_document = FALSE;
-    }
-  }
-
-  if ($build_document) {
-    // Build the node body.
-    $node->build_mode = NODE_BUILD_SEARCH_INDEX;
-    $node = node_build_content($node, FALSE, FALSE);
-    $node->body = drupal_render($node->content);
-    $node->title = apachesolr_clean_text($node->title);
-
-    $text = $node->body;
-
-    // Fetch extra data normally not visible, including comments.
-    $extra = node_invoke_nodeapi($node, 'update index');
-    $text .= "\n\n" . implode(' ', $extra);
-    $text = apachesolr_strip_ctl_chars($text);
-
-    $document = new Apache_Solr_Document();
-    $document->id = apachesolr_document_id($node->nid);
-    $document->site = url(NULL, array('absolute' => TRUE));
-    $document->hash = apachesolr_site_hash();
-    $document->nid = $node->nid;
-    $document->uid = $node->uid;
-    $document->title = $node->title;
-    $document->status = $node->status;
-    $document->sticky = $node->sticky;
-    $document->promote = $node->promote;
-    $document->moderate = $node->moderate;
-    $document->tnid = $node->tnid;
-    $document->translate = $node->translate;
-    if (!empty($node->language)) {
-      $document->language = $node->language;
-    }
-    $document->body = strip_tags(str_replace(array('<', '>'), array(' <', '> '), $text));
-    $document->type  = $node->type;
-    $document->type_name = apachesolr_strip_ctl_chars(node_get_types('name', $node));
-    $document->created = apachesolr_date_iso($node->created);
-    $document->changed = apachesolr_date_iso($node->changed);
-    $last_change = (isset($node->last_comment_timestamp) && $node->last_comment_timestamp > $node->changed) ? $node->last_comment_timestamp : $node->changed;
-    $document->last_comment_or_change = apachesolr_date_iso($last_change);
-    $document->comment_count = isset($node->comment_count) ? $node->comment_count : 0;
-    $document->name = apachesolr_strip_ctl_chars($node->name);
-
-    $path = 'node/' . $node->nid;
-    $document->url = url($path, array('absolute' => TRUE));
-    $document->path = $path;
-    // Path aliases can have important information about the content.
-    // Add them to the index as well.
-    if (function_exists('drupal_get_path_alias')) {
-      // Add any path alias to the index, looking first for language specific
-      // aliases but using language neutral aliases otherwise.
-      $language = empty($node->language) ? '' : $node->language;
-      $output = drupal_get_path_alias($path, $language);
-      if ($output && $output != $path) {
-        $document->path_alias = apachesolr_strip_ctl_chars($output);
-      }
-    }
-
-    // Get CCK fields list
-    $cck_fields = apachesolr_cck_fields();
-    foreach ($cck_fields as $key => $cck_info) {
-      if (isset($node->$key)) {
-        // Got a CCK field. See if it is to be indexed.
-        $function = $cck_info['callback'];
-        if ($cck_info['callback'] && function_exists($function)) {
-          $field = call_user_func_array($function, array($node, $key));
-        }
-        else {
-          $field = $node->$key;
-        }
-        $index_key = apachesolr_index_key($cck_info);
-        foreach ($field as $value) {
-          // Don't index NULLs or empty strings
-          if (isset($value['safe']) && strlen($value['safe'])) {
-            if ($cck_info['multiple']) {
-              $document->setMultiValue($index_key, apachesolr_clean_text($value['safe']));
-            }
-            else {
-              $document->$index_key = apachesolr_clean_text($value['safe']);
-            }
-          }
-        }
-      }
-    }
-    // Index book module data.
-    if (!empty($node->book['bid'])) {
-      // Hard-coded - must change if apachesolr_index_key() changes.
-      $document->is_book_bid = (int) $node->book['bid'];
-    }
-    apachesolr_add_tags_to_document($document, $text);
-    apachesolr_add_taxonomy_to_document($document, $node);
-
-    // Let modules add to the document - TODO convert to drupal_alter().
-    foreach (module_implements('apachesolr_update_index') as $module) {
-      $function = $module .'_apachesolr_update_index';
-      $function($document, $node);
-    }
-  }
-  return $document;
-}
-
-/**
  * Convert date from timestamp into ISO 8601 format.
  * http://lucene.apache.org/solr/api/org/apache/solr/schema/DateField.html
  */
@@ -452,85 +307,6 @@ function apachesolr_date_iso($date_times
   return gmdate('Y-m-d\TH:i:s\Z', $date_timestamp);
 }
 
-/**
- * Extract taxonomy from $node and add to dynamic fields.
- */
-function apachesolr_add_taxonomy_to_document(&$document, $node) {
-  if (isset($node->taxonomy) && is_array($node->taxonomy)) {
-    foreach ($node->taxonomy as $term) {
-      // Double indexing of tids lets us do effecient searches (on tid)
-      // and do accurate per-vocabulary faceting.
-
-      // By including the ancestors to a term in the index we make
-      // sure that searches for general categories match specific
-      // categories, e.g. Fruit -> apple, a search for fruit will find
-      // content categorized with apple.
-      $ancestors = taxonomy_get_parents_all($term->tid);
-      foreach ($ancestors as $ancestor) {
-        $document->setMultiValue('tid', $ancestor->tid);
-        $document->setMultiValue('im_vid_'. $ancestor->vid, $ancestor->tid);
-        $name = apachesolr_clean_text($ancestor->name);
-        $document->setMultiValue('vid', $ancestor->vid);
-        $document->{'ts_vid_'. $ancestor->vid .'_names'} .= ' '. $name;
-        // We index each name as a string for cross-site faceting
-        // using the vocab name rather than vid in field construction .
-        $document->setMultiValue('sm_vid_'. apachesolr_vocab_name($ancestor->vid), $name);
-      }
-    }
-  }
-}
-
-/**
- * Helper function - return a safe (PHP identifier) vocabulary name.
- */
-function apachesolr_vocab_name($vid) {
-  static $names = array();
-
-  if (!isset($names[$vid])) {
-    $vocab_name = db_result(db_query('SELECT v.name FROM {vocabulary} v WHERE v.vid = %d', $vid));
-    $names[$vid] = preg_replace('/[^a-zA-Z0-9_\x7f-\xff]/', '_', $vocab_name);
-    // Fallback for names ending up all as '_'.
-    $check = rtrim($names[$vid], '_');
-    if (!$check) {
-      $names[$vid] = '_' . $vid . '_';
-    }
-  }
-  return $names[$vid];
-}
-
-/**
- * Extract HTML tag contents from $text and add to boost fields.
- *
- * $text must be stripped of control characters before hand.
- */
-function apachesolr_add_tags_to_document(&$document, $text) {
-  $tags_to_index = variable_get('apachesolr_tags_to_index', array(
-    'h1' => 'tags_h1',
-    'h2' => 'tags_h2_h3',
-    'h3' => 'tags_h2_h3',
-    'h4' => 'tags_h4_h5_h6',
-    'h5' => 'tags_h4_h5_h6',
-    'h6' => 'tags_h4_h5_h6',
-    'u' => 'tags_inline',
-    'b' => 'tags_inline',
-    'i' => 'tags_inline',
-    'strong' => 'tags_inline',
-    'em' => 'tags_inline',
-    'a' => 'tags_a'
-  ));
-
-  // Strip off all ignored tags.
-  $text = strip_tags($text, '<'. implode('><', array_keys($tags_to_index)) .'>');
-
-  preg_match_all('@<('. implode('|', array_keys($tags_to_index)) .')[^>]*>(.*)</\1>@Ui', $text, $matches);
-  foreach ($matches[1] as $key => $tag) {
-    // We don't want to index links auto-generated by the url filter.
-    if ($tag != 'a' || !preg_match('@(?:http://|https://|ftp://|mailto:|smb://|afp://|file://|gopher://|news://|ssl://|sslv2://|sslv3://|tls://|tcp://|udp://|www\.)[a-zA-Z0-9]+@', $matches[2][$key])) {
-      $document->{$tags_to_index[$tag]} .= ' '. $matches[2][$key];
-    }
-  }
-}
-
 function apachesolr_delete_node_from_index($node) {
   try {
     $solr = apachesolr_get_solr();
@@ -684,13 +460,7 @@ function apachesolr_block($op = 'list', 
 
         switch ($delta) {
           case 'sort':
-            $sorts = array(
-              'relevancy' => array('name' => t('Relevancy'), 'default' => 'asc'),
-              'sort_title' => array('name' => t('Title'), 'default' => 'asc'),
-              'type' => array('name' => t('Type'), 'default' => 'asc'),
-              'sort_name' => array('name' => t('Author'), 'default' => 'asc'),
-              'created' => array('name' => t('Date'), 'default' => 'desc'),
-            );
+            $sorts = $query->get_available_sorts();
 
             $solrsorts = array();
             $sort_parameter = isset($_GET['solrsort']) ? check_plain($_GET['solrsort']) : FALSE;
@@ -801,17 +571,9 @@ function apachesolr_facet_block($respons
  */
 function apachesolr_date_facet_block($response, $query, $module, $delta, $facet_field, $filter_by, $facet_callback = FALSE) {
   $items = array();
-  $active_filters = array();
-  // Add active facets.
-  foreach ($query->get_filters() as $filter) {
-    if (($filter['#name'] == $facet_field) && preg_match('/^\[(\S+) TO (\S+)\]/', $filter['#value'], $matches)) {
-      $filter['#start'] = $matches[1];
-      $filter['#end'] = $matches[2];
-      $active_filters[] = $filter;
-    }
-  }
+
   $new_query = clone $query;
-  foreach (array_reverse($active_filters) as $filter) {
+  foreach (array_reverse($new_query->get_filters($facet_field)) as $filter) {
     // Iteratively remove the date facets.
     $new_query->remove_filter($facet_field, $filter['#value']);
     $path = $new_query->get_path();
Index: apachesolr_search.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/apachesolr/apachesolr_search.module,v
retrieving revision 1.1.2.6.2.83
diff -u -p -r1.1.2.6.2.83 apachesolr_search.module
--- apachesolr_search.module	15 Apr 2009 23:20:02 -0000	1.1.2.6.2.83
+++ apachesolr_search.module	16 Apr 2009 01:16:19 -0000
@@ -97,7 +97,7 @@ function apachesolr_search_search($op = 
           $solrsort = $_GET['solrsort'];
         }
         $query = apachesolr_drupal_query($keys, $filters, $solrsort);
-        if (is_null($query)) {
+        if (empty($query)) {
           throw new Exception(t('Could not construct a Solr query in function apachesolr_search_search()'));
         }
 
@@ -135,22 +135,17 @@ function apachesolr_search_search($op = 
 
         $facet_query_limits = variable_get('apachesolr_facet_query_limits', array());
         $facet_missing = variable_get('apachesolr_facet_missing', array());
-        // Request all enabled facets.  We need $all_facets so we can
-        // check the facet_date property and callback, because
-        // get_enabled_facets() does not (currently) return all of that.
-        $all_facets = module_invoke_all('apachesolr_facets');
+
         foreach (apachesolr_get_enabled_facets() as $module => $module_facets) {
           foreach($module_facets as $delta => $facet_field) {
-            if (isset($all_facets[$delta]['facet_date'])) {
-              $facet_date = $all_facets[$delta]['facet_date'];
-              $params['facet.date'][] = $facet_date;
-
-              $callback = $all_facets[$delta]['facet_date_range_callback'];
-              list($start, $end, $gap) = $callback($query, $params, $delta);
+            // TODO: generalize handling of date and range facets.
+            if ($module == 'apachesolr_search' && ($facet_field == 'created' || $facet_field == 'changed')) {
+              list($start, $end, $gap) = apachesolr_search_date_range($query, $facet_field);
               if ($gap) {
-                $params['f.'. $facet_date .'.facet.date.start'] = $start;
-                $params['f.'. $facet_date .'.facet.date.end'] = $end;
-                $params['f.'. $facet_date .'.facet.date.gap'] = $gap;
+                $params['facet.date'][] = $facet_field;
+                $params['f.'. $facet_field .'.facet.date.start'] = $start;
+                $params['f.'. $facet_field .'.facet.date.end'] = $end;
+                $params['f.'. $facet_field .'.facet.date.gap'] = $gap;
               }
             }
             else {
@@ -252,7 +247,7 @@ function apachesolr_search_search($op = 
           }
         }
         
-        // cache the built query. Since all the built queries go through
+        // Cache the built query. Since all the built queries go through
         // this process, all the hook_invocations will happen later
         apachesolr_current_query($query);
         
@@ -261,7 +256,7 @@ function apachesolr_search_search($op = 
         if (!$query) {
           return array();
         }
-        
+
         $response = $solr->search($query->get_query_basic(), $params['start'], $params['rows'], $params);
         // The response is cached so that it is accessible to the blocks and anything
         // else that needs it beyond the initial search.
@@ -308,63 +303,37 @@ function apachesolr_search_search($op = 
       } // try
       catch (Exception $e) {
         watchdog('Apache Solr', $e->getMessage(), NULL, WATCHDOG_ERROR);
-        apachesolr_failure(t('Solr search'), is_null($query) ? $keys : $query->get_query_basic());
+        apachesolr_failure(t('Solr search'), empty($query) ? $keys : $query->get_query_basic());
       }
       break;
   } // switch
 }
 
-function _hack_parse_date_range($str) {
-  if (preg_match('/\[(\S+) TO (\S+)\]/', $str, $m)) {
-    return array($m[1], strtotime($m[1]), $m[2], strtotime($m[2]));
-  }
-  return array();
-}
-  
-function apachesolr_search_date_range($query, &$params, $delta) {
-  // Find the smallest filter range for $delta in the query.  $query
-  // has no method to retrieve filters for $delta, so we iterate
-  // through them all.
-  $fields = $query->get_filters();
-  foreach ($fields as $info) {
-    if ($info['#name'] == $delta) {
-      // $info['#value'] is the filter value.  Ideally it would also
-      // have #range, #from, and #to properties.  Until it does, we
-      // parse them out ourselves.
-      //
-      // Also, if we had an ISO date library we could use them
-      // directly.  Instead, we convert to Unix timestamps for comparison.
-      list($_start_iso, $_start, $_end_iso, $_end) = _hack_parse_date_range($info['#value']);
-      // Only use dates that we were able to parse into timestamps.
-      if ($_start > 0 && $_end > 0) {
-        if (!isset($start) || $_start > $start) {
-          $start = $_start;
-          $start_iso = $_start_iso;
-        }
-        if (!isset($end) || $_end < $end) {
-          $end = $_end;
-          $end_iso = $_end_iso;
-        }
-        
-        // Determine the drilldown gap for this range.
-        $gap = apachesolr_date_gap_drilldown(apachesolr_date_find_query_gap($start_iso, $end_iso));
-      }
+function apachesolr_search_date_range($query, $facet_field) {
+  foreach ($query->get_filters($facet_field) as $filter) {
+    // If we had an ISO date library we could use ISO dates
+    // directly.  Instead, we convert to Unix timestamps for comparison.
+    // Only use dates if we are able to parse into timestamps.
+    $start = strtotime($filter['#start']);
+    $end = strtotime($filter['#end']);
+    if ($start && $end && ($start < $end)) {
+        $start_iso = $filter['#start'];
+        $end_iso = $filter['#end'];
+      // Determine the drilldown gap for this range.
+      $gap = apachesolr_date_gap_drilldown(apachesolr_date_find_query_gap($start_iso, $end_iso));
     }
   }
-      
   // If there is no $delta field in query object, get initial
   // facet.date.* params from the DB and determine the best search
   // gap to use.  This callback assumes $delta is 'changed' or 'created'.
-  if (!isset($start_iso)) {
-    $start_iso = apachesolr_date_iso(db_result(db_query("SELECT MIN($delta) FROM {node} WHERE status = 1")));
-    $end_iso = apachesolr_date_iso(db_result(db_query("SELECT MAX($delta) FROM {node} WHERE status = 1")));
+  if (!isset($gap)) {
+    $start_iso = apachesolr_date_iso(db_result(db_query("SELECT MIN($facet_field) FROM {node} WHERE status = 1")));
+    $end_iso = apachesolr_date_iso(db_result(db_query("SELECT MAX($facet_field) FROM {node} WHERE status = 1")));
     $gap = apachesolr_date_determine_gap($start_iso, $end_iso);
   }
-
-  // Return a query range from the beginning of a gap period to the
-  // beginning of the next gap period.  We ALWAYS generate query
-  // ranges of this form and the apachesolr_date_*() helper functions
-  // require it.
+  // Return a query range from the beginning of a gap period to the beginning
+  // of the next gap period.  We ALWAYS generate query ranges of this form
+  // and the apachesolr_date_*() helper functions require it.
   return array("$start_iso/$gap", "$end_iso+1$gap/$gap", "+1$gap");
 }
 
@@ -389,19 +358,13 @@ function apachesolr_search_apachesolr_fa
     'facet_field' => 'language',
   );
 
-  // TODO bjaspan: I need to include facet_field or the entry is
-  // ignored, even though facet_date is the key element.
   $facets['changed'] = array(
     'info' => t('Apache Solr Search: Filter by updated date'),
     'facet_field' => 'changed',
-    'facet_date' => 'changed',
-    'facet_date_range_callback' => 'apachesolr_search_date_range',
   );
   $facets['created'] = array(
     'info' => t('Apache Solr Search: Filter by post date'),
     'facet_field' => 'created',
-    'facet_date' => 'created',
-    'facet_date_range_callback' => 'apachesolr_search_date_range',
   );
 
   // A book module facet.
@@ -547,7 +510,7 @@ function apachesolr_search_block($op = '
                 if (! $fielddisplay = theme("apachesolr_breadcrumb_". $field['#name'], $field['#value'])) {
                   $fielddisplay = $field['#value'];
                 }
-                $links[] = theme('apachesolr_facet_item', $fielddisplay, NULL, $path, $querystring, $active, $unclick_link, $response->numFound);
+                $links[] = theme('apachesolr_facet_item', $fielddisplay, NULL, $path, $querystring, $active, $unclick_link, $response->response->numFound);
               }
             }
             $content = theme('apachesolr_currentsearch', $response->response->numFound, $links);
@@ -732,11 +695,8 @@ function apachesolr_search_theme() {
 }
 
 function theme_apachesolr_breadcrumb_date_range($range) {
-  if (preg_match('@[\[\{](\S+) TO (\S+)[\]\}]@', $range, $m)) {
-    list($all, $start_iso, $end_iso) = $m;
-    return apachesolr_date_format_range($m[1], $m[2]);
-    $start_label = apachesolr_date_format_iso_by_gap($gap, $start_iso);
-    return "$start_label";
+  if (preg_match('@[\[\{](\S+) TO (\S+)[\]\}]@', $range, $match)) {
+    return apachesolr_date_format_range($match[1], $match[2]);
   }
   return $range;
 }
@@ -787,7 +747,7 @@ function theme_apachesolr_currentsearch(
 }
 
 /**
- * Returns the snipit text for a search entry
+ * Theme the highlighted snippet text for a search entry.
  *
  * @param object $doc
  * @param array $snippets
Index: Solr_Base_Query.php
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/apachesolr/Solr_Base_Query.php,v
retrieving revision 1.1.4.26
diff -u -p -r1.1.4.26 Solr_Base_Query.php
--- Solr_Base_Query.php	12 Apr 2009 21:10:50 -0000	1.1.4.26
+++ Solr_Base_Query.php	16 Apr 2009 01:16:19 -0000
@@ -6,34 +6,42 @@ class Solr_Base_Query implements Drupal_
   /**
    * Extract all uses of one named field from a filter string e.g. 'type:book'
    */
-  static function filter_extract(&$filters, $name) {
-    $queries = array();
-    $values = array();
+  public function filter_extract(&$filterstring, $name) {
+    $extracted = array();
     // Range queries.  The "TO" is case-sensitive.
-    $patterns[] = '/(^| )'. $name .':([\[\{]\S+ TO \S+[\]\}])/';
+    $patterns[] = '/(^| )'. $name .':([\[\{](\S+) TO (\S+)[\]\}])/';
     // Match quoted values.
     $patterns[] = '/(^| )'. $name .':"([^"]*)"/';
     // Match unquoted values.
     $patterns[] = '/(^| )'. $name .':([^ ]*)/';
     foreach ($patterns as $p) {
-      if (preg_match_all($p, $filters, $matches)) {
-        $queries = array_merge($matches[0], $queries);
-        $values = array_merge($matches[2], $values);
+      if (preg_match_all($p, $filterstring, $matches, PREG_SET_ORDER)) {
+        foreach($matches as $match) {
+          $filter = array();
+          $filter['#query'] = $match[0];
+          $filter['#value'] = trim($match[2]);
+          if (isset($match[3])) {
+            // Extra data for range queries
+            $filter['#start'] = $match[3];
+            $filter['#end'] = $match[4];
+          }
+          $extracted[] = $filter;
+          // Update the local copy of $filters by removing the match.
+          $filterstring = str_replace($match[0], '', $filterstring);
+        }
       }
-      // Update the local copy of $filters by removing all matches.
-      $filters = trim(str_replace($matches[0], '', $filters));
     }
-    return array('queries' => $queries, 'values' => $values);
+    return $extracted;
   }
 
   /**
    * Takes an array $field and combines the #name and #value in a way
    * suitable for use in a Solr query.
    */
-  static function make_filter(array $filter) {
+  public function make_filter(array $filter) {
     // If the field value has spaces, or : in it, wrap it in double quotes.
     // unless it is a range query.
-    if (preg_match('/[ :]/', $filter['#value']) && !preg_match('/[\[\{]\S+ TO \S+[\]\}]/', $filter['#value'])) {
+    if (preg_match('/[ :]/', $filter['#value']) && !isset($filter['#start']) && !preg_match('/[\[\{]\S+ TO \S+[\]\}]/', $filter['#value'])) {
       $filter['#value'] = '"'. $filter['#value']. '"';
     }
     return $filter['#name'] . ':' . $filter['#value'];
@@ -47,7 +55,7 @@ class Solr_Base_Query implements Drupal_
   /**
    * Each query/subquery will have a unique ID
    */
-  public $id;
+  protected $id;
 
   /**
    * A keyed array where the key is a position integer and the value
@@ -62,7 +70,7 @@ class Solr_Base_Query implements Drupal_
    * Contains name:value pairs for filter queries.  For example,
    * "type:book" for book nodes.
    */
-  protected $filters;
+  protected $filterstring;
 
   /**
    * A mapping of field names from the URL to real index field names.
@@ -84,6 +92,8 @@ class Solr_Base_Query implements Drupal_
    */
   protected $solr;
 
+  protected $available_sorts;
+
   /**
    * @param $solr
    *  An instantiated Apache_Solr_Service Object.
@@ -102,22 +112,33 @@ class Solr_Base_Query implements Drupal_
   function __construct($solr, $querypath, $filterstring, $sortstring) {
     $this->solr = $solr;
     $this->querypath = trim($querypath);
-    $this->filters = trim($filterstring);
+    $this->filterstring = trim($filterstring);
     $this->solrsort = trim($sortstring);
     $this->id = ++self::$idCount;
     $this->parse_filters();
+    $this->available_sorts = $this->default_sorts();
   }
 
   function __clone() {
     $this->id = ++self::$idCount;
   }
 
-  function add_filter($field, $value) {
+  public function add_filter($field, $value) {
     $this->fields[] = array('#name' => $field, '#value' => trim($value));
   }
 
-  public function get_filters() {
-    return $this->fields;
+  public function get_filters($name = NULL) {
+    if (empty($name)) {
+      return $this->fields;
+    }
+    reset($this->fields);
+    $matches = array();
+    foreach ($this->fields as $filter) {
+      if ($filter['#name'] == $name) {
+        $matches[] = $filter;
+      }
+    }
+    return $matches;
   }
 
   public function remove_filter($name, $value = NULL) {
@@ -183,11 +204,11 @@ class Solr_Base_Query implements Drupal_
    * @param $operator
    *   'AND' or 'OR'
    */
-  function add_subquery(Drupal_Solr_Query_Interface $query, $fq_operator = 'OR', $q_operator = 'AND') {
+  public function add_subquery(Drupal_Solr_Query_Interface $query, $fq_operator = 'OR', $q_operator = 'AND') {
     $this->subqueries[$query->id] = array('#query' => $query, '#fq_operator' => $fq_operator, '#q_operator' => $q_operator);
   }
 
-  function remove_subquery(Solr_Base_Query $query) {
+  public function remove_subquery(Solr_Base_Query $query) {
     unset($this->subqueries[$query->id]);
   }
 
@@ -198,6 +219,28 @@ class Solr_Base_Query implements Drupal_
   public function set_solrsort($sortstring) {
     $this->solrsort = trim($sortstring);
   }
+
+  public function get_available_sorts() {
+    return $this->available_sorts;
+  }
+
+  public function set_available_sort($field, $sort) {
+    $this->available_sorts[$field] = $sort;
+  }
+
+  /**
+   * Returns a default list of sorts.
+   */
+  protected function default_sorts() {
+    return array(
+      'relevancy' => array('name' => t('Relevancy'), 'default' => 'asc'),
+      'sort_title' => array('name' => t('Title'), 'default' => 'asc'),
+      'type' => array('name' => t('Type'), 'default' => 'asc'),
+      'sort_name' => array('name' => t('Author'), 'default' => 'asc'),
+      'created' => array('name' => t('Date'), 'default' => 'desc'),
+    );
+  }
+
   /**
    * Return filters and sort in a form suitable for a query param to url().
    */
@@ -258,7 +301,7 @@ class Solr_Base_Query implements Drupal_
       if (isset($this->field_map[$name])) {
         $field['#name'] = $this->field_map[$name];
       }
-      $progressive_crumb[] = Solr_Base_Query::make_filter($field);
+      $progressive_crumb[] = $this->make_filter($field);
       $options = array('query' => 'filters=' . implode(' ', $progressive_crumb));
       if ($themed = theme("apachesolr_breadcrumb_{$name}", $field['#value'])) {
         $breadcrumb[] = l($themed, $base, $options);
@@ -281,7 +324,7 @@ class Solr_Base_Query implements Drupal_
    */
   protected function parse_filters() {
     $this->fields = array();
-    $filters = $this->filters;
+    $filterstring = $this->filterstring;
 
     // Gets information about the fields already in solr index.
     $index_fields = $this->solr->getFields();
@@ -289,24 +332,18 @@ class Solr_Base_Query implements Drupal_
     foreach ((array) $index_fields as $name => $data) {
       // Look for a field alias.
       $alias = isset($this->field_map[$name]) ? $this->field_map[$name] : $name;
-      // Get the values for $name
-      $extracted = Solr_Base_Query::filter_extract($filters, $alias);
-      if (count($extracted['values'])) {
-        foreach ($extracted['values'] as $index => $value) {
-          $pos = strpos($this->filters, $extracted['queries'][$index]);
-          // $solr_keys and $solr_crumbs are keyed on $pos so that query order
-          // is maintained. This is important for breadcrumbs.
-          $this->fields[$pos] = array('#name' => $name, '#value' => trim($value));
-        }
-      }
-      // Look for negative queries for the same field.
-      $extracted = Solr_Base_Query::filter_extract($filters, '-'. $alias);
-      if (count($extracted['values'])) {
-        foreach ($extracted['values'] as $index => $value) {
-          $pos = strpos($this->filters, $extracted['queries'][$index]);
-          // $solr_keys and $solr_crumbs are keyed on $pos so that query order
-          // is maintained. This is important for breadcrumbs.
-          $this->fields[$pos] = array('#name' => '-'. $name, '#value' => trim($value));
+      // Look for normal and negative queries for the same field.
+      foreach(array('', '-') as $prefix) {
+        // Get the values for $name
+        $extracted = $this->filter_extract($filterstring, $prefix . $alias);
+        if (count($extracted)) {
+          foreach ($extracted as $filter) {
+            $pos = strpos($this->filterstring, $filter['#query']);
+            // $solr_keys and $solr_crumbs are keyed on $pos so that query order
+            // is maintained. This is important for breadcrumbs.
+            $filter['#name'] = $prefix . $name;
+            $this->fields[$pos] = $filter;
+          }
         }
       }
     }
@@ -329,7 +366,7 @@ class Solr_Base_Query implements Drupal_
       if ($aliases && isset($this->field_map[$field['#name']])) {
         $field['#name'] = $this->field_map[$field['#name']];
       }
-      $fq[] = Solr_Base_Query::make_filter($field);
+      $fq[] = $this->make_filter($field);
     }
     foreach ($this->subqueries as $id => $data) {
       $subfq = $data['#query']->rebuild_fq($aliases);
