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	14 Apr 2009 14:29:47 -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.127
diff -u -p -r1.1.2.12.2.127 apachesolr.module
--- apachesolr.module	14 Apr 2009 00:12:18 -0000	1.1.2.12.2.127
+++ apachesolr.module	14 Apr 2009 14:29:47 -0000
@@ -138,6 +138,14 @@ function apachesolr_document_id($id, $ty
 }
 
 /**
+ * Convert date from timestamp into ISO 8601 format.
+ * http://lucene.apache.org/solr/api/org/apache/solr/schema/DateField.html
+ */
+function apachesolr_date_iso($date_timestamp) {
+  return gmdate('Y-m-d\TH:i:s\Z', $date_timestamp);
+}
+
+/**
  * Implementation of hook_user().
  *
  * Mark nodes as needing re-indexing if the author name changes.
@@ -262,7 +270,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;
@@ -299,238 +307,6 @@ function apachesolr_index_nodes($result,
   return $position;
 }
 
-/**
- * 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
- */
-function apachesolr_date_iso($date_timestamp) {
-  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;
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	14 Apr 2009 14:29:47 -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 = trim(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,17 +112,18 @@ 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));
   }
 
@@ -183,11 +194,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 +209,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 +291,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 +314,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 +322,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 +356,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);
