--- Z:/environment/www_acquia/sites/all/modules/apachesolr_custom_sort/Drupal_Apache_Solr_Service.php	Fri Feb 26 11:48:14 2010
+++ Z:/environment/www_acquia/sites/all/modules/apachesolr_custom_sort/ApacheSolr_Custom_Query.php	Thu Apr 15 13:21:13 2010
@@ -1,344 +1,475 @@
 <?php
-require_once 'SolrPhpClient/Apache/Solr/Service.php';
+// $Id: Solr_Base_Query.php 4748 2010-02-23 17:15:12Z simondawson1- $
 
-/**
- * PHP 5.1 compatability code.
- */
-if (!function_exists('json_decode')) {
-  // Zend files include other files.
-  set_include_path(dirname(__FILE__) . PATH_SEPARATOR . get_include_path());
-  require_once 'Zend/Json/Decoder.php';
+class ApacheSolr_Custom_Query implements Drupal_Solr_Query_Interface {
 
   /**
-   * Substitute for missing PHP built-in function.
+   * Extract all uses of one named field from a filter string e.g. 'type:book'
    */
-  function json_decode($string) {
-    return Zend_Json_Decoder::decode($string, 0);
+  public function filter_extract(&$filterstring, $name) {
+    $extracted = array();
+    // Range queries.  The "TO" is case-sensitive.
+    $patterns[] = '/(^| |-)'. $name .':([\[\{](\S+) TO (\S+)[\]\}])/';
+    // Match quoted values.
+    $patterns[] = '/(^| |-)'. $name .':"([^"]*)"/';
+    // Match unquoted values.
+    $patterns[] = '/(^| |-)'. $name .':([^ ]*)/';
+    foreach ($patterns as $p) {
+      if (preg_match_all($p, $filterstring, $matches, PREG_SET_ORDER)) {
+        foreach($matches as $match) {
+          $filter = array();
+          $filter['#query'] = $match[0];
+          $filter['#exclude'] = ($match[1] == '-');
+          $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);
+        }
+      }
+    }
+    return $extracted;
   }
-}
 
-class Drupal_Apache_Solr_Service extends Apache_Solr_Service {
+  /**
+   * Takes an array $field and combines the #name and #value in a way
+   * suitable for use in a Solr query.
+   */
+  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']) && !isset($filter['#start']) && !preg_match('/[\[\{]\S+ TO \S+[\]\}]/', $filter['#value'])) {
+      $filter['#value'] = '"'. $filter['#value']. '"';
+    }
+    $prefix = empty($filter['#exclude']) ? '' : '-';
+    return $prefix . $filter['#name'] . ':' . $filter['#value'];
+  }
 
-  protected $luke;
-  protected $luke_cid;
-  protected $stats;
-  const LUKE_SERVLET = 'admin/luke';
-  const STATS_SERVLET = 'admin/stats.jsp';
+  /**
+   * Static shared by all instances, used to increment ID numbers.
+   */
+  protected static $idCount = 0;
 
   /**
-   * Call the /admin/ping servlet, to test the connection to the server.
-   *
-   * @param $timeout
-   *   maximum time to wait for ping in seconds, -1 for unlimited (default 2).
-   * @return
-   *   (float) seconds taken to ping the server, FALSE if timeout occurs.
+   * Each query/subquery will have a unique ID
    */
-  public function ping($timeout = 2) {
-    $start = microtime(TRUE);
+  public $id;
 
-    if ($timeout <= 0.0) {
-      $timeout = -1;
-    }
-    // Attempt a HEAD request to the solr ping url.
-    list($data, $headers) = $this->_makeHttpRequest($this->_pingUrl, 'HEAD', array(), null, $timeout);
-    $response = new Apache_Solr_Response($data, $headers);
+  /**
+   * A keyed array where the key is a position integer and the value
+   * is an array with #name and #value properties.  Each value is a
+   * used for filter queries, e.g. array('#name' => 'uid', '#value' => 0)
+   * for anonymous content.
+   */
+  protected $fields;
 
-    if ($response->getHttpStatus() == 200) {
-      return microtime(TRUE) - $start;
-    }
-    else {
-      return FALSE; 
-    }
-  }
+  /**
+   * The complete filter string for a query.  Usually from $_GET['filters']
+   * Contains name:value pairs for filter queries.  For example,
+   * "type:book" for book nodes.
+   */
+  protected $filterstring;
 
   /**
-   * Sets $this->luke with the meta-data about the index from admin/luke.
+   * A mapping of field names from the URL to real index field names.
    */
-  protected function setLuke($num_terms = 0) {
-    if (empty($this->luke[$num_terms])) {
-      $url = $this->_constructUrl(self::LUKE_SERVLET, array('numTerms' => "$num_terms", 'wt' => self::SOLR_WRITER));
-      $this->luke[$num_terms] = $this->_sendRawGet($url);
-      cache_set($this->luke_cid, $this->luke, 'cache_apachesolr');
-    }
-  }
+  protected $field_map = array();
 
   /**
-   * Get just the field meta-data about the index.
+   * An array of subqueries.
    */
-  public function getFields($num_terms = 0) {
-    return $this->getLuke($num_terms)->fields;
-  }
+  protected $subqueries = array();
 
   /**
-   * Get meta-data about the index.
+   * The search keywords.
    */
-  public function getLuke($num_terms = 0) {
-    if (!isset($this->luke[$num_terms])) {
-      $this->setLuke($num_terms);
-    }
-    return $this->luke[$num_terms];
-  }
-  
+  protected $keys;
+
   /**
-   * Sets $this->stats with the information about the Solr Core form /admin/stats.jsp
+   * The search base path.
    */
-  protected function setStats() {
-    $data = $this->getLuke();
-    // Only try to get stats if we have connected to the index.
-    if (empty($this->stats) && isset($data->index->numDocs)) {
-      $url = $this->_constructUrl(self::STATS_SERVLET);
-      $this->stats_cid = "apachesolr:stats:" . md5($url);
-      $cache = cache_get($this->stats_cid, 'cache_apachesolr');
-      if (isset($cache->data)) {
-        $this->stats = simplexml_load_string($cache->data);
-      }
-      else {
-        $response = $this->_sendRawGet($url);
-        $this->stats = simplexml_load_string($response->getRawResponse());
-        cache_set($this->stats_cid, $response->getRawResponse(), 'cache_apachesolr');
-      }
-    }
-  }
-  
+  protected $base_path;
+
+  /**
+   * Apache_Solr_Service object
+   */
+  protected $solr;
+
+  protected $available_sorts;
+
+  // Makes sure we always have a valid sort.
+  protected $solrsort = array('#name' => 'score', '#direction' => 'asc');
+
   /**
-   * Get information about the Solr Core.
+   * @param $solr
+   *   An instantiated Apache_Solr_Service Object.
+   *   Can be instantiated from apachesolr_get_solr().
+   *
+   * @param $keys
+   *   The string that a user would type into the search box. Suitable input
+   *   may come from search_get_keys().
    *
-   * Returns a Simple XMl document
+   * @param $filterstring
+   *   Key and value pairs that are applied as filter queries.
+   *
+   * @param $sortstring
+   *   Visible string telling solr how to sort - added to GET query params.
+   *
+   * @param $base_path
+   *   The search base path (without the keywords) for this query.
    */
-  public function getStats() {
-    if (!isset($this->stats)) {
-      $this->setStats();
-    }
-    return $this->stats;
+  function __construct($solr, $keys, $filterstring, $sortstring, $base_path) {
+    $this->solr = $solr;
+    $this->keys = trim($keys);
+    $this->filterstring = trim($filterstring);
+    $this->parse_filters();
+    $this->available_sorts = $this->get_available_sorts();// $this->default_sorts();
+    $this->sortstring = trim($sortstring);
+    $this->parse_sortstring($sortstring);
+    $this->base_path = $base_path;
+    $this->id = ++self::$idCount;
   }
 
-  /**
-   * Get summary information about the Solr Core.
-   */
-  public function getStatsSummary() {
-    $stats = $this->getStats();
-    $summary = array(
-     '@pending_docs' => '',
-     '@autocommit_time_seconds' => '',
-     '@autocommit_time' => '',
-     '@deletes_by_id' => '',
-     '@deletes_by_query' => '',
-     '@deletes_total' => '',
-     '@schema_version' => '',
-     '@core_name' => '',
-    );
-
-    if (!empty($stats)) {
-      $docs_pending_xpath = $stats->xpath('//stat[@name="docsPending"]');
-      $summary['@pending_docs'] = (int) trim($docs_pending_xpath[0]);
-      $max_time_xpath = $stats->xpath('//stat[@name="autocommit maxTime"]');
-      $max_time = (int) trim(current($max_time_xpath));
-      // Convert to seconds.
-      $summary['@autocommit_time_seconds'] = $max_time / 1000;
-      $summary['@autocommit_time'] = format_interval($max_time / 1000);
-      $deletes_id_xpath = $stats->xpath('//stat[@name="deletesById"]');
-      $summary['@deletes_by_id'] = (int) trim($deletes_id_xpath[0]);
-      $deletes_query_xpath = $stats->xpath('//stat[@name="deletesByQuery"]');
-      $summary['@deletes_by_query'] = (int) trim($deletes_query_xpath[0]);
-      $summary['@deletes_total'] = $summary['@deletes_by_id'] + $summary['@deletes_by_query'];
-      $schema = $stats->xpath('/solr/schema[1]');
-      $summary['@schema_version'] = trim($schema[0]);;
-      $core = $stats->xpath('/solr/core[1]');
-      $summary['@core_name'] = trim($core[0]);
-    }
-
-    return $summary;
+  function __clone() {
+    $this->id = ++self::$idCount;
+  }
+
+  public function add_filter($field, $value, $exclude = FALSE, $callbacks = array()) {
+    $this->fields[] = array('#exclude' => $exclude, '#name' => $field, '#value' => trim($value), '#callbacks' => $callbacks);
   }
 
   /**
-   * Clear cached Solr data.
-   */
-  public function clearCache() {
-    // Don't clear cached data if the server is unavailable.
-    if (@$this->ping()) {
-      $this->_clearCache();
+   * Get all filters, or the subset of filters for one field.
+   *
+   * @param $name
+   *   Optional name of a Solr field.
+   */
+  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) {
+    // We can only remove named fields.
+    if (empty($name)) {
+      return;
+    }
+    if (!isset($value)) {
+      foreach ($this->fields as $pos => $values) {
+        if ($values['#name'] == $name) {
+          unset($this->fields[$pos]);
+        }
+      }
     }
     else {
-      throw new Exception('No Solr instance available when trying to clear the cache.');
+      foreach ($this->fields as $pos => $values) {
+        if ($values['#name'] == $name && $values['#value'] == $value) {
+          unset($this->fields[$pos]);
+        }
+      }
     }
   }
 
-  protected function _clearCache() {
-    cache_clear_all("apachesolr:luke:", 'cache_apachesolr', TRUE);
-    cache_clear_all("apachesolr:stats:", 'cache_apachesolr', TRUE);
-    $this->luke = array();
-    $this->stats = NULL;
+  public function has_filter($name, $value) {
+    foreach ($this->fields as $pos => $values) {
+      if (isset($values['#name']) && isset($values['#value']) && $values['#name'] == $name && $values['#value'] == $value) {
+        return TRUE;
+      }
+    }
+    return FALSE;
   }
 
   /**
-   * Clear the cache whenever we commit changes.
+   * Handle aliases for field to make nicer URLs
    *
-   * @see Apache_Solr_Service::commit()
+   * @param $field_map
+   *   An array keyed with real Solr index field names, with value being the alias.
    */
-  public function commit($optimize = TRUE, $waitFlush = TRUE, $waitSearcher = TRUE, $timeout = 3600) {
-    parent::commit($optimize, $waitFlush, $waitSearcher, $timeout);
-    $this->_clearCache();
+  function add_field_aliases($field_map) {
+    $this->field_map = array_merge($this->field_map, $field_map);
+    // We have to re-parse the filters.
+    $this->parse_filters();
+  }
+
+  function get_field_aliases() {
+    return $this->field_map;
+  }
+
+  function clear_field_aliases() {
+    $this->field_map = array();
+    // We have to re-parse the filters.
+    $this->parse_filters();
   }
 
   /**
-   * Construct the Full URLs for the three servlets we reference
+   * Set keywords in this query.
    *
-   * @see Apache_Solr_Service::_initUrls()
+   * @param $keys
+   *   New keywords
    */
-  protected function _initUrls() {
-    parent::_initUrls();
-    $this->_lukeUrl = $this->_constructUrl(self::LUKE_SERVLET, array('numTerms' => '0', 'wt' => self::SOLR_WRITER));
+  function set_keys($keys) {
+    $this->keys = $keys;
   }
 
   /**
-   * Make a request to a servlet (a path) that's not a standard path.
-   *
-   * @param string $servlet
-   *   A path to be added to the base Solr path. e.g. 'extract/tika'
-   *
-   * @param array $params
-   *   Any request parameters when constructing the URL.
-   *
-   * @param string $method
-   *   'GET', 'POST', 'PUT', or 'HEAD'.
-   *
-   * @param array $request_headers
-   *   Keyed array of header names and values.  Should include 'Content-Type'
-   *   for POST or PUT.
-   *
-   * @param string $rawPost
-   *   Must be an empty string unless method is POST or PUT.
+   * Get this query's keywords.
+   */
+  function get_keys() {
+    return $this->keys;
+  }
+
+  /**
+   * A subquery is another instance of a Solr_Base_Query that should be joined
+   * to the query. The operator determines whether it will be joined with AND or
+   * OR.
    *
-   * @param float $timeout
-   *   Read timeout in seconds or FALSE.
+   * @param $query
+   *   An instance of Drupal_Solr_Query_Interface.
    *
-   * @return 
-   *  Apache_Solr_Response object
+   * @param $operator
+   *   'AND' or 'OR'
    */
-  public function makeServletRequest($servlet, $params = array(), $method = 'GET', $request_headers = array(), $rawPost = '', $timeout = FALSE) {
-    if ($method == 'GET' || $method == 'HEAD') {
-      // Make sure we are not sending a request body.
-      $rawPost = '';
-    }
-    // Add default params.
-    $params += array(
-      'wt' => self::SOLR_WRITER,
-    );
+  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);
+  }
+
+  public function remove_subquery(Drupal_Solr_Query_Interface $query) {
+    unset($this->subqueries[$query->id]);
+  }
 
-    $url = $this->_constructUrl($servlet, $params);
-    list ($data, $headers) = $this->_makeHttpRequest($url, $method, $request_headers, $rawPost, $timeout);
-    $response = new Apache_Solr_Response($data, $headers, $this->_createDocuments, $this->_collapseSingleValueArrays);
-    $code = (int) $response->getHttpStatus();
-    if ($code != 200) {
-      $message = $response->getHttpStatusMessage();
-      if ($code >= 400 && $code != 403 && $code != 404) {
-        // Add details, like Solr's exception message.
-        $message .= $response->getRawResponse();
+  public function remove_subqueries() {
+    $this->subqueries = array();
+  }
+
+  protected function parse_sortstring() {
+    // Substitute any field aliases with real field names.
+    $sortstring = strtr($this->sortstring, array_flip($this->field_map));
+    // Score is a special case - it's the default sort for Solr.
+    if ('' == $sortstring) {
+      $this->set_solrsort('score', 'asc');
+    }
+    else {
+      // Validate and set sort parameter
+      $fields = implode('|', array_keys($this->available_sorts));
+      if (preg_match('/^(?:('. $fields .') (asc|desc),?)+$/', $sortstring, $matches)) {
+        // We only use the last match.
+        $this->set_solrsort($matches[1], $matches[2]);
       }
-      throw new Exception('"' . $code . '" Status: ' . $message);
     }
-    return $response;
+  }
+
+  public function set_solrsort($name, $direction) {
+    if (isset($this->available_sorts[$name])) {
+      $this->solrsort = array('#name' => $name, '#direction' => $direction);
+    }
+  }
+
+  public function get_solrsort() {
+    return $this->solrsort;
+  }
+
+  public function get_available_sorts() {
+    
+    return apachesolr_custom_sort_get_available_sorts();
+  }
+  
+  public function set_available_sort($name, $sort) {
+    // We expect non-aliased sorts to be added.
+    $this->available_sorts[$name] = $sort;
+    $this->available_sorts[$name]['standard'] = FALSE;
+    $this->available_sorts[$name]['status'] = TRUE;
+  }
+
+  public function remove_available_sort($name) {
+    unset($this->available_sorts[$name]);
+    // Re-parse the sortstring.
+    $this->parse_sortstring();
   }
 
   /**
-   * Put Luke meta-data from the cache into $this->luke when we instantiate.
-   *
-   * @see Apache_Solr_Service::__construct()
+   * Returns a default list of sorts.
    */
-  public function __construct($host = 'localhost', $port = 8180, $path = '/solr/') {
-    parent::__construct($host, $port, $path);
-    $this->luke_cid = "apachesolr:luke:" . md5($this->_lukeUrl);
-    $cache = cache_get($this->luke_cid, 'cache_apachesolr');
-    if (isset($cache->data)) {
-      $this->luke = $cache->data;
-    }
+  protected function default_sorts() {
+   return variable_get('sort_field_settings', array());
   }
 
   /**
-   * Central method for making a get operation against this Solr Server
-   *
-   * @see Apache_Solr_Service::_sendRawGet()
+   * Return filters and sort in a form suitable for a query param to url().
    */
-  protected function _sendRawGet($url, $timeout = FALSE) {
-    list ($data, $headers) = $this->_makeHttpRequest($url, 'GET', array(), '', $timeout);
-    $response = new Apache_Solr_Response($data, $headers, $this->_createDocuments, $this->_collapseSingleValueArrays);
-    $code = (int) $response->getHttpStatus();
-    if ($code != 200) {
-      $message = $response->getHttpStatusMessage();
-      if ($code >= 400 && $code != 403 && $code != 404) {
-        // Add details, like Solr's exception message.
-        $message .= $response->getRawResponse();
+   public function get_url_queryvalues() {
+    $queryvalues = array();
+    $queryvalues['filters'] = '';
+    if ($fq = $this->rebuild_fq(TRUE)) {
+      foreach ($fq as $delta => $values) {
+        $queryvalues['filters'] .= ' ' . implode(' ', $values);
+      }
+    }
+    
+    if (isset($queryvalues['filters'])) {
+      $queryvalues['filters'] = trim($queryvalues['filters']);
+    }
+    
+    $solrsort = $this->solrsort;
+    if ($solrsort && ($solrsort['#name'] != 'score' || $solrsort['#direction'] != 'asc')) {
+      if (isset($this->field_map[$solrsort['#name']])) {
+        $solrsort['#name'] = $this->field_map[$solrsort['#name']];
       }
-      throw new Exception('"' . $code . '" Status: ' . $message);
+      $queryvalues['solrsort'] = $solrsort['#name'] .' '. $solrsort['#direction'];
     }
-    return $response;
+    return $queryvalues;
+  }
+
+  public function get_fq() {
+    return $this->rebuild_fq();
   }
 
   /**
-   * Central method for making a post operation against this Solr Server
+   * A function to get just the keyword components of the query,
+   * omitting any field:value portions.
+   */
+  public function get_query_basic() {
+    return $this->rebuild_query();
+  }
+
+  /**
+   * Return the search path.
    *
-   * @see Apache_Solr_Service::_sendRawPost()
+   * @param string $new_keywords
+   *   Optional. When set, this string overrides the query's current keywords.
    */
-  protected function _sendRawPost($url, $rawPost, $timeout = FALSE, $contentType = 'text/xml; charset=UTF-8') {
-    $request_headers = array('Content-Type' => $contentType);
-    list ($data, $headers) = $this->_makeHttpRequest($url, 'POST', $request_headers, $rawPost, $timeout);
-    $response = new Apache_Solr_Response($data, $headers, $this->_createDocuments, $this->_collapseSingleValueArrays);
-    $code = (int) $response->getHttpStatus();
-    if ($code != 200) {
-      $message = $response->getHttpStatusMessage();
-      if ($code >= 400 && $code != 403 && $code != 404) {
-        // Add details, like Solr's exception message.
-        $message .= $response->getRawResponse();
-      }
-      throw new Exception('"' . $code . '" Status: ' . $message);
+  public function get_path($new_keywords = NULL) {
+    if (isset($new_keywords)) {
+      return $this->base_path . '/' . $new_keywords;
     }
-    return $response;
+    return $this->base_path . '/' . $this->get_query_basic();
   }
 
-  protected function _makeHttpRequest($url, $method = 'GET', $headers = array(), $content = '', $timeout = FALSE) {
-    // Set a response timeout
-    if ($timeout) {
-      $default_socket_timeout = ini_set('default_socket_timeout', $timeout);
-    }
-    $result = drupal_http_request($url, $headers, $method, $content);
-    // Restore the response timeout
-    if ($timeout) {
-      ini_set('default_socket_timeout', $default_socket_timeout);
+  /**
+   * Build additional breadcrumb elements relative to $base.
+   */
+  public function get_breadcrumb($base = NULL) {
+    $progressive_crumb = array();
+    if (!isset($base)) {
+      $base = $this->get_path();
     }
 
-    // This will no longer be needed after http://drupal.org/node/345591 is committed
-    $responses = array(
-      0 => 'Request failed',
-      100 => 'Continue', 101 => 'Switching Protocols',
-      200 => 'OK', 201 => 'Created', 202 => 'Accepted', 203 => 'Non-Authoritative Information', 204 => 'No Content', 205 => 'Reset Content', 206 => 'Partial Content',
-      300 => 'Multiple Choices', 301 => 'Moved Permanently', 302 => 'Found', 303 => 'See Other', 304 => 'Not Modified', 305 => 'Use Proxy', 307 => 'Temporary Redirect',
-      400 => 'Bad Request', 401 => 'Unauthorized', 402 => 'Payment Required', 403 => 'Forbidden', 404 => 'Not Found', 405 => 'Method Not Allowed', 406 => 'Not Acceptable', 407 => 'Proxy Authentication Required', 408 => 'Request Time-out', 409 => 'Conflict', 410 => 'Gone', 411 => 'Length Required', 412 => 'Precondition Failed', 413 => 'Request Entity Too Large', 414 => 'Request-URI Too Large', 415 => 'Unsupported Media Type', 416 => 'Requested range not satisfiable', 417 => 'Expectation Failed',
-      500 => 'Internal Server Error', 501 => 'Not Implemented', 502 => 'Bad Gateway', 503 => 'Service Unavailable', 504 => 'Gateway Time-out', 505 => 'HTTP Version not supported'
-    );
-
-    if (!isset($result->code) || $result->code < 0) {
-      $result->code = 0;
+    $search_keys = $this->get_query_basic();
+    if ($search_keys) {
+      $breadcrumb[] = l($search_keys, $base);
     }
 
-    if (isset($result->error)) {
-      $responses[0] .= ': ' . check_plain($result->error);
+    foreach ($this->fields as $field) {
+      $name = $field['#name'];
+      // Look for a field alias.
+      if (isset($this->field_map[$name])) {
+        $field['#name'] = $this->field_map[$name];
+      }
+      $progressive_crumb[] = $this->make_filter($field);
+      $options = array('query' => 'filters=' . rawurlencode(implode(' ', $progressive_crumb)));
+      $breadcrumb_name = "apachesolr_breadcrumb_" . $name;
+      // Modules utilize this alter to consolidate several fields into one
+      // theme function. This is how CCK breadcrumbs are handled.
+      drupal_alter('apachesolr_theme_breadcrumb', $breadcrumb_name);
+      if ($themed = theme($breadcrumb_name, $field)) {
+        $breadcrumb[] = l($themed, $base, $options);
+      }
+      else {
+        $breadcrumb[] = l($field['#value'], $base, $options);
+      }
     }
+    // The last breadcrumb is the current page, so it shouldn't be a link.
+    $last = count($breadcrumb) - 1;
+    $breadcrumb[$last] = strip_tags($breadcrumb[$last]);
+
+    return $breadcrumb;
+  }
 
-    if (!isset($result->data)) {
-      $result->data = '';
+  /**
+   * Parse the filter string in $this->filters into $this->fields.
+   *
+   * Builds an array of field name/value pairs.
+   */
+  protected function parse_filters() {
+    $this->fields = array();
+    $filterstring = $this->filterstring;
+
+    // Gets information about the fields already in solr index.
+    $index_fields = $this->solr->getFields();
+
+    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 = $this->filter_extract($filterstring, $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'] = $name;
+          $this->fields[$pos] = $filter;
+        }
+      }
     }
+    // Even though the array has the right keys they are likely in the wrong
+    // order. ksort() sorts the array by key while maintaining the key.
+    ksort($this->fields);
+  }
 
-    if (!isset($responses[$result->code])) {
-      $result->code = floor($result->code / 100) * 100;
+  /**
+   * Builds a set of filter queries from $this->fields and all subqueries.
+   *
+   * Returns an array of strings that can be combined into
+   * a URL query parameter or passed to Solr as fq paramters.
+   */
+  protected function rebuild_fq($aliases = FALSE) {
+    $fq = array();
+    $fields = array();
+    foreach ($this->fields as $pos => $field) {
+      // Look for a field alias.
+      if ($aliases && isset($this->field_map[$field['#name']])) {
+        $field['#name'] = $this->field_map[$field['#name']];
+      }
+      $fq[$field['#name']][] = $this->make_filter($field);
     }
+    foreach ($this->subqueries as $id => $data) {
+      $subfq = $data['#query']->rebuild_fq($aliases);
+      if ($subfq) {
+        $operator = $data['#fq_operator'];
+        $subqueries = array();
+        foreach ($subfq as $key => $values) {
+          foreach ($values as $value) {
+            $subqueries[] = $value;
+          }
+        }
+        $fq['subqueries'][$key] =  " {$data['#q_operataor']} (" . implode(" $operator " , $subqueries) . ")";
+      }
+    }
+    return $fq;
+  }
 
-    $protocol = "HTTP/1.1";
-    $headers[] = "{$protocol} {$result->code} {$responses[$result->code]}";
-    if (isset($result->headers)) {
-      foreach ($result->headers as $name => $value) {
-        $headers[] = "$name: $value";
+  protected function rebuild_query() {
+    $query = $this->keys;
+    foreach ($this->subqueries as $id => $data) {
+      $operator = $data['#q_operator'];
+      $subquery = $data['#query']->get_query_basic();
+      if ($subquery) {
+        $query .= " {$operator} ({$subquery})";
       }
     }
-    return array($result->data, $headers);
+    return $query;
   }
 }
