Index: author_facet.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/faceted_search/author_facet.module,v
retrieving revision 1.16
diff -u -p -r1.16 author_facet.module
--- author_facet.module	4 Jan 2009 19:36:25 -0000	1.16
+++ author_facet.module	3 Aug 2009 11:51:02 -0000
@@ -6,8 +6,6 @@
  * Provides a facet for content authors.
  */
 
-require_once('./'. drupal_get_path('module', 'faceted_search') .'/faceted_search.inc');
-
 /**
  * Implementation of hook_form_alter().
  */
@@ -44,12 +42,14 @@ function author_facet_form_faceted_searc
  * Implementation of hook_faceted_search_collect().
  */
 function author_facet_faceted_search_collect(&$facets, $domain, $env, $selection, $arg = NULL) {
+  faceted_search_require('search_engine', $env);
   switch ($domain) {
     case 'facets':
       // If the author facet is allowed.
       if (!isset($selection) || isset($selection['author'][1])) {
         $excluded_roles = author_facet_excluded_roles($env);
-        $facets[] = new author_facet($excluded_roles);
+        $class_name = $env->env_id == 0 ? 'author_facet' : 'author_'. $env->engine .'_facet';
+        $facets[] = new $class_name($excluded_roles);
       }
       break;
 
@@ -65,8 +65,8 @@ function author_facet_faceted_search_col
           if ($name = author_facet_get_user_name($env, $uid, $excluded_roles)) {
             // Create a facet with the user found in the search text as the
             // active category.
-            $facets[] = new author_facet($excluded_roles, $uid, $name);
-            
+            $class_name = $env->env_id == 0 ? 'author_facet' : 'author_'. $env->engine .'_facet';
+            $facets[] = new $class_name($excluded_roles, $uid, $name);            
           }
         }
         // Remove the parsed token from the search text, based
@@ -83,7 +83,8 @@ function author_facet_faceted_search_col
         $excluded_roles = author_facet_excluded_roles($env);
         if ($name = author_facet_get_user_name($env, $arg->uid, $excluded_roles)) {
           // Create a facet with the node's author as the active category.
-          $facets[] = new author_facet($excluded_roles, $arg->uid, $name);
+          $class_name = $env->env_id == 0 ? 'author_facet' : 'author_'. $env->engine .'_facet';
+          $facets[] = new $class_name($excluded_roles, $arg->uid, $name);
         }
       }
       break;
@@ -129,145 +130,3 @@ function author_facet_excluded_roles($en
 function _author_facet_filter_role($role) {
   return $role != 'authenticated user';
 }
-
-/**
- * A facet for node authors.
- */
-class author_facet extends faceted_search_facet {
-
-  var $_excluded_roles;
-
-  /**
-   * Constructor. Optionally assigns the active user of the facet.
-   */
-  function author_facet($excluded_roles = array(), $uid = 0, $name = '') {
-    $active_path = array();
-    if (is_numeric($uid) && $name) {
-      $active_path[] = new author_facet_category($uid, $name);
-    }
-    parent::faceted_search_facet('author', $active_path);
-    $this->_excluded_roles = $excluded_roles;
-  }
-
-  function get_id() {
-    return 1; // This module provides only one facet.
-  }
-
-  function get_label() {
-    return t('Author');
-  }
-
-  /**
-   * Returns the available sort options for this facet.
-   */
-  function get_sort_options() {
-    $options = parent::get_sort_options();
-    $options['name'] = t('Name');
-    return $options;
-  }
-
-  /**
-   * Handler for the 'count' sort criteria.
-   */
-  function build_sort_query_count(&$query) {
-    $query->add_orderby('count', 'DESC');
-    $query->add_orderby('users_name', 'ASC');
-  }
-
-  /**
-   * Handler for the 'name' sort criteria.
-   */
-  function build_sort_query_name(&$query) {
-    $query->add_orderby('users_name', 'ASC');
-  }
-
-  /**
-   * Returns the search text for this facet, taking into account this facet's
-   * active path.
-   */
-  function get_text() {
-    if ($category = $this->get_active_category()) {
-      return $category->_uid;
-    }
-    return '';
-  }
-
-  /**
-   * Updates a query for retrieving the root categories of this facet and their
-   * associated nodes within the current search results.
-   *
-   * @param $query
-   *   The query object to update.
-   *
-   * @return
-   *   FALSE if this facet can't have root categories.
-   */
-  function build_root_categories_query(&$query) {
-    $query->add_table('users', 'uid', 'n', 'uid');
-    $query->add_field('users', 'uid');
-    $query->add_field('users', 'name');
-    $query->add_groupby('users_uid');
-    if (count($this->_excluded_roles)) {
-      $query->add_subquery('NOT EXISTS (SELECT users_roles.rid FROM {users_roles} users_roles WHERE users.uid = users_roles.uid AND users_roles.rid IN ('. implode(', ', $this->_excluded_roles) .'))');
-    }
-    return TRUE;
-  }
-
-  /**
-   * This factory method creates categories given query results that include the
-   * fields selected in get_root_categories_query() or get_subcategories_query().
-   *
-   * @param $results
-   *   $results A database query result resource.
-   *
-   * @return
-   *   Array of categories.
-   */
-  function build_categories($results) {
-    $categories = array();
-    while ($result = db_fetch_object($results)) {
-      if ($result->users_uid == 0) {
-      	$result->users_name = t('Anonymous');
-      }
-      $categories[] = new author_facet_category($result->users_uid, $result->users_name, $result->count);
-    }
-    return $categories;
-  }
-}
-
-/**
- * A node-type based facet category.
- */
-class author_facet_category extends faceted_search_category {
-  var $_uid = 0;
-  var $_name = '';
-
-  function author_facet_category($uid, $name, $count = NULL) {
-    parent::faceted_search_category($count);
-    $this->_uid = $uid;
-    $this->_name = $name;
-  }
-
-  /**
-   * Return the label of this category.
-   *
-   * @param $html
-   *   TRUE when HTML is allowed in the label, FALSE otherwise. Checking this
-   *   flag allows implementors to provide a rich-text label if desired, and an
-   *   alternate plain text version for cases where HTML cannot be used. The
-   *   implementor is responsible to ensure adequate security filtering.
-   */
-  function get_label($html = FALSE) {
-    return check_plain($this->_name);
-  }
-
-  /**
-   * Updates a query for selecting nodes matching this category.
-   *
-   * @param $query
-   *   The query object to update.
-   */
-  function build_results_query(&$query) {
-    $query->add_where('n.uid = %d', $this->_uid);
-  }
-}
Index: content_type_facet.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/faceted_search/content_type_facet.module,v
retrieving revision 1.21
diff -u -p -r1.21 content_type_facet.module
--- content_type_facet.module	3 Mar 2009 05:13:12 -0000	1.21
+++ content_type_facet.module	3 Aug 2009 11:51:02 -0000
@@ -6,8 +6,6 @@
  * Provides a facet for content types.
  */
 
-require_once('./'. drupal_get_path('module', 'faceted_search') .'/faceted_search.inc');
-
 /**
  * Implementation of hook_form_alter().
  */
@@ -49,12 +47,14 @@ function content_type_facet_form_faceted
  * Implementation of hook_faceted_search_collect().
  */
 function content_type_facet_faceted_search_collect(&$facets, $domain, $env, $selection, $arg = NULL) {
+  faceted_search_require('search_engine', $env);
   switch ($domain) {
     case 'facets':
       // If the content type facet is allowed.
       if (!isset($selection) || isset($selection['content_type'][1])) {
         $types = content_type_facet_get_types($env);
-        $facets[] = new content_type_facet($types);
+        $class_name = $env->env_id == 0 ? 'content_type_facet' : 'content_type_'. $env->engine .'_facet';  
+        $facets[] = new $class_name($types);
       }
       break;
 
@@ -80,7 +80,8 @@ function content_type_facet_faceted_sear
           // Create a facet with the type found in search text as the active
           // category.
           if (isset($allowed_types[$type])) {
-            $facets[] = new content_type_facet($types, $type);
+            $class_name = $env->env_id == 0 ? 'content_type_facet' : 'content_type_'. $env->engine .'_facet';
+            $facets[] = new $class_name($types, $type);
           }
           // Remove the parsed token from the search text.
           $arg = search_query_insert($arg, 'content_type');
@@ -94,7 +95,8 @@ function content_type_facet_faceted_sear
         $types = content_type_facet_get_types($env);
         if (empty($types) || isset($types[$arg->type])) {
           // Create a facet with the node's type as the active category.
-          $facets[] = new content_type_facet($types, $arg->type);
+          $class_name = $env->env_id == 0 ? 'content_type_facet' : 'content_type_'. $env->engine .'_facet';
+          $facets[] = new $class_name($types, $arg->type);
         }
       }
       break;
@@ -134,152 +136,3 @@ function content_type_facet_get_types($e
   }
 }
 
-/**
- * A node-type based facet.
- */
-class content_type_facet extends faceted_search_facet {
-
-  var $_types = array();
-
-  /**
-   * Constructor. Optionally assigns the active type of the facet.
-   *
-   * @param $types
-   *   Array of possible type names for this facet, or empty array if all types
-   *   are allowed.
-   * @param $type
-   *   Optional. Type to set as this facet's active category.
-   */
-  function content_type_facet($types, $type = NULL) {
-    $active_path = array();
-    if ($type) {
-      $active_path[] = new content_type_facet_category($type, node_get_types('name', $type));
-    }
-    parent::faceted_search_facet('content_type', $active_path);
-    $this->_types = $types;
-  }
-
-  function get_id() {
-    return 1; // This module provides only one facet
-  }
-
-  function get_label() {
-    return t('Content type');
-  }
-
-  /**
-   * Returns the available sort options for this facet.
-   */
-  function get_sort_options() {
-    $options = parent::get_sort_options();
-    $options['type'] = t('Type');
-    return $options;
-  }
-
-  /**
-   * Handler for the 'count' sort criteria.
-   */
-  function build_sort_query_count(&$query) {
-    $query->add_orderby('count', 'DESC');
-    $query->add_orderby('node_type_name', 'ASC');
-  }
-
-  /**
-   * Handler for the 'type' sort criteria.
-   */
-  function build_sort_query_type(&$query) {
-    $query->add_orderby('node_type_name', 'ASC');
-  }
-
-  /**
-   * Return the search text for this facet, taking into account this facet's
-   * active path.
-   */
-  function get_text() {
-    if ($category = $this->get_active_category()) {
-      return $category->_type;
-    }
-    return '';
-  }
-
-  /**
-   * Updates a query for retrieving the root categories of this facet and their
-   * associated nodes within the current search results.
-   *
-   * @param $query
-   *   The query object to update.
-   *
-   * @return
-   *   FALSE if this facet can't have root categories.
-   */
-  function build_root_categories_query(&$query) {
-    $query->add_table('node_type', 'type', 'n', 'type');
-    $query->add_field('node_type', 'type');
-    $query->add_field('node_type', 'name');
-    if (!empty($this->_types)) {
-      $query->add_where("node_type.type IN ('". implode("', '", $this->_types) ."')");
-    }
-    $query->add_groupby('node_type_type');
-    return TRUE;
-  }
-
-  /**
-   * This factory method creates categories given query results that include the
-   * fields selected in get_root_categories_query() or get_subcategories_query().
-   *
-   * @param $results
-   *   $results A database query result resource.
-   *
-   * @return
-   *   Array of categories.
-   */
-  function build_categories($results) {
-    $categories = array();
-    while ($result = db_fetch_object($results)) {
-      $categories[] = new content_type_facet_category($result->node_type_type, $result->node_type_name, $result->count);
-    }
-    return $categories;
-  }
-}
-
-/**
- * A node-type based facet category.
- */
-class content_type_facet_category extends faceted_search_category {
-  var $_type = NULL;
-  var $_name = '';
-
-  /**
-   * Constructor.
-   */
-  function content_type_facet_category($type, $name, $count = NULL) {
-    parent::faceted_search_category($count);
-    $this->_type = $type;
-    $this->_name = $name;
-    faceted_search_localize_type($this->_type, $this->_name);
-  }
-
-  /**
-   * Return the label of this category.
-   *
-   * @param $html
-   *   TRUE when HTML is allowed in the label, FALSE otherwise. Checking this
-   *   flag allows implementors to provide a rich-text label if desired, and an
-   *   alternate plain text version for cases where HTML cannot be used. The
-   *   implementor is responsible to ensure adequate security filtering.
-   */
-  function get_label($html = FALSE) {
-    return check_plain($this->_name);
-  }
-
-  /**
-   * Updates a query for selecting nodes matching this category.
-   *
-   * @param $query
-   *   The query object to update.
-   */
-  function build_results_query(&$query) {
-    $query->add_where("n.type = '%s'", $this->_type);
-  }
-}
-
Index: date_authored_facet.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/faceted_search/date_authored_facet.module,v
retrieving revision 1.13
diff -u -p -r1.13 date_authored_facet.module
--- date_authored_facet.module	4 Jan 2009 19:36:25 -0000	1.13
+++ date_authored_facet.module	3 Aug 2009 11:51:02 -0000
@@ -6,19 +6,19 @@
  * Provides a facet for searching content by date of creation.
  */
 
-require_once('./'. drupal_get_path('module', 'faceted_search') .'/faceted_search.inc');
-
 // TODO: add setting for granularity.
 
 /**
  * Implementation of hook_faceted_search_collect().
  */
 function date_authored_facet_faceted_search_collect(&$facets, $domain, $env, $selection, $arg = NULL) {
+  faceted_search_require('search_engine', $env);
   switch ($domain) {
     case 'facets':
       // If the date authored facet is allowed.
       if (!isset($selection) || isset($selection['date_authored'][1])) {
-        $facets[] = new date_authored_facet();
+        $class_name = $env->env_id == 0 ? 'date_authored_facet' : 'date_authored_'. $env->engine .'_facet';
+        $facets[] = new $class_name();
       }
       break;
 
@@ -36,20 +36,24 @@ function date_authored_facet_faceted_sea
               $path = array(); // Array to build the path of categories.
               if (isset($matches[1])) {
                 // Found year.
-                $path[] = new date_authored_facet_category($matches[1]);
+                $class_name = $env->env_id == 0 ? 'date_authored_facet_category' : 'date_authored_'. $env->engine .'_facet_category';
+                $path[] = new $class_name($matches[1]);
               }
               if (isset($matches[3])) {
                 // Found month.
-                $path[] = new date_authored_facet_category($matches[1], $matches[3]);
+                $class_name = $env->env_id == 0 ? 'date_authored_facet_category' : 'date_authored_'. $env->engine .'_facet_category';
+                $path[] = new $class_name($matches[1], $matches[3]);
               }
               if (isset($matches[5])) {
                 // Found day.
-                $path[] = new date_authored_facet_category($matches[1], $matches[3], $matches[5]);
+                $class_name = $env->env_id == 0 ? 'date_authored_facet_category' : 'date_authored_'. $env->engine .'_facet_category';
+                $path[] = new $class_name($matches[1], $matches[3], $matches[5]);
               }
               if (!empty($path)) {
                 // Create a facet with the date found in the search text as the
                 // active category.
-                $facets[] = new date_authored_facet($path);
+                $class_name = $env->env_id == 0 ? 'date_authored_facet' : 'date_authored_'. $env->engine .'_facet';
+                $facets[] = new $class_name($path);
               }
             }
           }
@@ -69,257 +73,21 @@ function date_authored_facet_faceted_sea
         $day = gmdate('j', $timestamp);
 
         // Build the path of categories.
+        $class_name = $env->env_id == 0 ? 'date_authored_facet_category' : 'date_authored_'. $env->engine .'_facet_category';
         $path = array(
-          new date_authored_facet_category($year),
-          new date_authored_facet_category($year, $month),
-          new date_authored_facet_category($year, $month, $day),
+          new $class_name($year),
+          new $class_name($year, $month),
+          new $class_name($year, $month, $day),
         );
 
         // Create a facet with the node's creation date as the active category.
-        $facets[] = new date_authored_facet($path);
+        $class_name = $env->env_id == 0 ? 'date_authored_facet' : 'date_authored_'. $env->engine .'_facet';
+        $facets[] = new $class_name($path);
       }
       break;
   }
 }
 
-/**
- * A facet for searching content by date of creation.
- */
-class date_authored_facet extends faceted_search_facet {
-
-  /**
-   * Constructor.
-   */
-  function date_authored_facet($active_path = array()) {
-    parent::faceted_search_facet('date_authored', $active_path);
-  }
-
-  function get_id() {
-    return 1; // This module provides only one facet.
-  }
-
-  function get_label() {
-    return t('Date authored');
-  }
-
-  /**
-   * Returns the available sort options for this facet.
-   */
-  function get_sort_options() {
-    $options = parent::get_sort_options();
-    $options['oldest'] = t('Oldest first');
-    $options['latest'] = t('Latest first');
-    return $options;
-  }
-
-  /**
-   * Handler for the 'count' sort criteria.
-   */
-  function build_sort_query_count(&$query) {
-    $query->add_orderby('count', 'DESC');
-    $query->add_orderby('n.created', 'ASC');
-  }
-
-  /**
-   * Handler for the 'oldest' sort criteria.
-   */
-  function build_sort_query_oldest(&$query) {
-    $query->add_orderby('n.created', 'ASC');
-  }
-
-  /**
-   * Handler for the 'latest' sort criteria.
-   */
-  function build_sort_query_latest(&$query) {
-    $query->add_orderby('n.created', 'DESC');
-  }
-
-  /**
-   * Returns the search text for this facet, taking into account this facet's
-   * active path.
-   */
-  function get_text() {
-    if ($category = $this->get_active_category()) {
-      return $category->get_text();
-    }
-    return '';
-  }
-
-  /**
-   * Updates a query for retrieving the root categories of this facet and their
-   * associated nodes within the current search results.
-   *
-   * @param $query
-   *   The query object to update.
-   *
-   * @return
-   *   FALSE if this facet can't have root categories.
-   */
-  function build_root_categories_query(&$query) {
-    $timezone = _date_authored_facet_get_timezone();
-    $query->add_field(NULL, "YEAR(FROM_UNIXTIME(n.created + $timezone))", 'node_created_year');
-    $query->add_groupby('node_created_year');
-    return TRUE;
-  }
-
-  /**
-   * This factory method creates categories given query results that include the
-   * fields selected in get_root_categories_query() or get_subcategories_query().
-   *
-   * @param $results
-   *   $results A database query result resource.
-   *
-   * @return
-   *   Array of categories.
-   */
-  function build_categories($results) {
-    $categories = array();
-    while ($result = db_fetch_object($results)) {
-      $categories[] = new date_authored_facet_category($result->node_created_year, $result->node_created_month, $result->node_created_day, $result->count);
-    }
-    return $categories;
-  }
-}
-
-/**
- * A category for node creation date.
- */
-class date_authored_facet_category extends faceted_search_category {
-  var $_year = NULL;
-  var $_month = NULL;
-  var $_day = NULL;
-
-  /**
-   * Constructs a category for the specified date.
-   *
-   * @param $year
-   *   Year corresponding to this category.
-   *
-   * @param $month
-   *   Month corresponding to this category. Optional, but must be specified if
-   *   $day is specified.
-   *
-   * @param $day
-   *   Day corresponding to this category. Optional.
-   *
-   * @param $count
-   *   The number of nodes associated to this category within the current
-   *   search. Optional.
-   *
-   * Note: We consider the specified date as within the user's timezone.
-   */
-  function date_authored_facet_category($year, $month = NULL, $day = NULL, $count = NULL) {
-    parent::faceted_search_category($count);
-    $this->_year = $year;
-    $this->_month = $month;
-    $this->_day = $day;
-  }
-
-  /**
-   * Return the label of this category.
-   *
-   * @param $html
-   *   TRUE when HTML is allowed in the label, FALSE otherwise. Checking this
-   *   flag allows implementors to provide a rich-text label if desired, and an
-   *   alternate plain text version for cases where HTML cannot be used. The
-   *   implementor is responsible to ensure adequate security filtering.
-   */
-  function get_label($html = FALSE) {
-    if (isset($this->_day)) {
-      // Format date with YYYY-MM-DD.
-      $timestamp = gmmktime(0, 0, 0, $this->_month, $this->_day, $this->_year);
-      $format = variable_get('date_facets_format_ymd', 'm/d/Y');
-    }
-    elseif (isset($this->_month)) {
-      // Format date with YYYY-MM.
-      $timestamp = gmmktime(0, 0, 0, $this->_month, 1, $this->_year);
-      $format = variable_get('date_facets_format_ym', 'm/Y');
-    }
-    elseif (isset($this->_year)) {
-      // Format date with YYYY.
-      $timestamp = gmmktime(0, 0, 0, 1, 1, $this->_year);
-      $format = variable_get('date_facets_format_y', 'Y');
-    }
-    if ($timestamp) {
-      return format_date($timestamp, 'custom', $format, 0);
-    }
-  }
-
-  function get_text() {
-    $text = sprintf('%04d', $this->_year);
-    if (isset($this->_month)) {
-      $text .= sprintf('-%02d', $this->_month);
-      if (isset($this->_day)) {
-        $text .= sprintf('-%02d', $this->_day);
-      }
-    }
-    return $text;
-  }
-
-  /**
-   * Updates a query for retrieving the subcategories of this category and their
-   * associated nodes within the current search results.
-   *
-   * This only needs to be overridden for hierarchical facets.
-   *
-   * @param $query
-   *   The query object to update.
-   *
-   * @return
-   *   FALSE if this facet can't have subcategories.
-   */
-  function build_subcategories_query(&$query) {
-    $timezone = _date_authored_facet_get_timezone();
-    if (isset($this->_day)) {
-      return FALSE; // No subcategories.
-    }
-    if (isset($this->_month)) {
-      $from = sprintf("'%04d-%02d-01 00:00:00'", $this->_year, $this->_month);
-      $query->add_field(NULL, "YEAR(FROM_UNIXTIME(n.created + $timezone))", 'node_created_year');
-      $query->add_field(NULL, "MONTH(FROM_UNIXTIME(n.created + $timezone))", 'node_created_month');
-      $query->add_field(NULL, "DAY(FROM_UNIXTIME(n.created + $timezone))", 'node_created_day');
-      $query->add_where("(n.created + $timezone >= UNIX_TIMESTAMP($from))");
-      $query->add_where("(n.created + $timezone < UNIX_TIMESTAMP($from + INTERVAL 1 MONTH))");
-      $query->add_groupby('node_created_day'); // Needed for counting matching nodes.
-      return TRUE;
-    }
-    if (isset($this->_year)) {
-      $from = sprintf("'%04d-01-01 00:00:00'", $this->_year);
-      $query->add_field(NULL, "YEAR(FROM_UNIXTIME(n.created + $timezone))", 'node_created_year');
-      $query->add_field(NULL, "MONTH(FROM_UNIXTIME(n.created + $timezone))", 'node_created_month');
-      $query->add_where("(n.created + $timezone >= UNIX_TIMESTAMP($from))");
-      $query->add_where("(n.created + $timezone < UNIX_TIMESTAMP($from + INTERVAL 1 YEAR))");
-      $query->add_groupby('node_created_month'); // Needed for counting matching nodes.
-      return TRUE;
-    }
-    return FALSE; // Unreachable, unless something is wrong...
-  }
-
-  /**
-   * Updates a query for selecting nodes matching this category.
-   *
-   * @param $query
-   *   The query object to update.
-   */
-  function build_results_query(&$query) {
-    $timezone = _date_authored_facet_get_timezone();
-    if (isset($this->_day)) {
-      $from = sprintf("'%04d-%02d-%02d 00:00:00'", $this->_year, $this->_month, $this->_day);
-      $query->add_where("(n.created + $timezone >= UNIX_TIMESTAMP($from))");
-      $query->add_where("(n.created + $timezone < UNIX_TIMESTAMP($from + INTERVAL 1 DAY))");
-    }
-    elseif (isset($this->_month)) {
-      $from = sprintf("'%04d-%02d-01 00:00:00'", $this->_year, $this->_month);
-      $query->add_where("(n.created + $timezone >= UNIX_TIMESTAMP($from))");
-      $query->add_where("(n.created + $timezone < UNIX_TIMESTAMP($from + INTERVAL 1 MONTH))");
-    }
-    elseif (isset($this->_year)) {
-      $from = sprintf("'%04d-01-01 00:00:00'", $this->_year);
-      $query->add_where("(n.created + $timezone >= UNIX_TIMESTAMP($from))");
-      $query->add_where("(n.created + $timezone < UNIX_TIMESTAMP($from + INTERVAL 1 YEAR))");
-    }
-  }
-}
 
 /**
  * Returns the applicable timezone offset (in seconds) for date comparisons in
Index: faceted_search.admin.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/faceted_search/faceted_search.admin.inc,v
retrieving revision 1.4
diff -u -p -r1.4 faceted_search.admin.inc
--- faceted_search.admin.inc	3 Mar 2009 05:13:12 -0000	1.4
+++ faceted_search.admin.inc	3 Aug 2009 11:51:03 -0000
@@ -81,6 +81,7 @@ function faceted_search_edit_form($form_
     drupal_set_title(t('Faceted search environment: @name', array('@name' => $env->name)));
   }
   else {
+    faceted_search_require('search_engine');
     drupal_set_title(t('Add a faceted search environment'));
     $env = new faceted_search();
   }
@@ -142,6 +143,19 @@ function faceted_search_edit_form($form_
     '#description' => t("When this is enabled, unpublished nodes are allowed to appear in search results to users with the 'administer nodes' permission. Note that unpublished nodes are never indexed by Drupal core, so they will never appear in keyword search results although they <em>will</em> appear in guided search results."),
     '#weight' => 1,
   );
+  $form['search'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Search'),
+    '#collapsible' => TRUE,
+    '#collapsed' => FALSE,
+    '#description' => '<p>'. t('Faceted search utilizes Drupal\'s built-in search but can be configured to use other search engines. Select a search engine for this environment.') .'</p>',
+  );
+  $form['search']['engine'] = array(
+    '#type' => 'select',
+    '#title' => t('Search engines'),
+    '#default_value' => $env->engine,
+    '#options' => _faceted_search_get_search_engine_options(),
+  );
 
   // Facets section.
   $form['facets'] = array(
@@ -253,11 +267,12 @@ function faceted_search_edit_form_valida
  */
 function faceted_search_edit_form_submit($form, &$form_state) {
   $env = $form_state['values']['env'];
-  
+
   // Save search environment.
   $update = $env->env_id ? 'env_id' : NULL;
   $env->name = $form_state['values']['name'];
   $env->description = $form_state['values']['description'];
+  $env->engine = $form_state['values']['engine'];
   // Settings are only saved if they have a value in the original environment
   // (we can rely on this since any setting always has at least a default
   // value).
@@ -289,3 +304,16 @@ function faceted_search_edit_form_submit
   }
 }
 
+/**
+ * Helper to collect search engine implementations
+ */
+function _faceted_search_get_search_engine_options() {
+  $options = array();
+  $engines = array();
+  foreach (module_implements('faceted_search_engine') as $module) {
+    $function = $module .'_faceted_search_engine';
+    $engines[$module] = $function();
+    $options[$module] = $engines[$module]['description'];
+  }
+  return $options;
+}
Index: faceted_search.inc
===================================================================
RCS file: faceted_search.inc
diff -N faceted_search.inc
--- faceted_search.inc	4 Jan 2009 19:36:25 -0000	1.57
+++ /dev/null	1 Jan 1970 00:00:00 -0000
@@ -1,1822 +0,0 @@
-<?php
-// $Id: faceted_search.inc,v 1.57 2009/01/04 19:36:25 davidlesieur Exp $
-
-/**
- * @file
- * Provides base classes for implementing filters and facets, and classes needed
- * by other modules.
- */
-
-/**
- * The base class for filters.
- *
- * Filters actually impact results only when they have an active category (a
- * "category" is a filtering value). The filtering is delegated to the active
- * category.
- */
-class faceted_search_filter {
-
-  /**
-   * The key identifying this class of filter. Keys are used in the form of
-   * 'key:text' tokens in the search text.
-   */
-  var $_key = '';
-
-  /**
-   * The status of this filter.
-   */
-  var $_status = FALSE;
-
-  /**
-   * The weight of this filter, for sorting purposes.
-   */
-  var $_weight = 0;
-
-  /**
-   * An array representing the path of categories leading to the active category
-   * of this facet. This path includes the active category itself.
-   */
-  var $_path = array();
-
-  /**
-   * Constructor.
-   *
-   * @param $key
-   *   Key corresponding to this class of filter. This should be the same string
-   *   as used to construct the filter from the search text in the module's
-   *   implementation of hook_faceted_search_parse().
-   * @param $active_path
-   *   Array representing the path leading to the active category, including the
-   *   active category itself. Defaults to an empty array, meaning no active
-   *   category.
-   */
-  function faceted_search_filter($key, $active_path = array()) {
-    $this->_key = $key;
-    $this->_path = $active_path;
-  }
-
-  /**
-   * Return TRUE if this filter offers browsable categories, or FALSE otherwise.
-   */
-  function is_browsable() {
-    return FALSE;
-  }
-
-  /**
-   * Assign settings to this filter.
-   *
-   * @param $settings
-   *   Array of settings.
-   */
-  function set($settings) {
-    if (isset($settings['status'])) {
-      $this->_status = $settings['status'];
-    }
-    if (isset($settings['weight'])) {
-      $this->_weight = $settings['weight'];
-    }
-  }
-
-  /**
-   * Return the key for this class of filter.
-   */
-  function get_key() {
-    return $this->_key;
-  }
-
-  /**
-   * Return a help text for site administrators.
-   */
-  function get_help() {
-    return '';
-  }
-
-  /**
-   * Return the status of this filter.
-   *
-   * @return
-   *   TRUE when the filter is enabled, FALSE otherwise.
-   */
-  function get_status() {
-    return $this->_status;
-  }
-
-  /**
-   * Change the status of this filter.
-   *
-   * @param $status
-   *   TRUE to enable the filter, FALSE to disable it.
-   */
-  function set_status($status) {
-    $this->_status = $status;
-  }
-
-  /**
-   * Return the configured weight of this filter, for sorting purposes.
-   */
-  function get_weight() {
-    return $this->_weight;
-  }
-
-  /**
-   * Assign the weight of this filter.
-   */
-  function set_weight($weight) {
-    $this->_weight = $weight;
-  }
-
-  /**
-   * Return TRUE if this facet has an active category. If a facet is active, it
-   * normally means that it is used in the current search.
-   */
-  function is_active() {
-    return count($this->_path) > 0;
-  }
-
-  /**
-   * Return an array representing the path to the active category, including the
-   * active category itself. Return an empty array if there is no active
-   * category.
-   */
-  function get_active_path() {
-    return $this->_path;
-  }
-
-  /**
-   * Set the path of the active category, including the active category itself.
-   *
-   * @param $path
-   *   The path of the category (array of categories). Defaults to no active
-   *   path.
-   */
-  function set_active_path($path = array()) {
-    $this->_path = $path;
-  }
-
-  /**
-   * Return the active category, or NULL if there is no active category.
-   */
-  function get_active_category() {
-    return end($this->_path);
-  }
-
-  /**
-   * Append keywords used by this filter into the specified array.
-   */
-  function get_keywords(&$keywords) {
-    // Does nothing by default.
-  }
-}
-
-/**
- * Base class for facet categories.
- */
-class faceted_search_category {
-  /**
-   * The number of nodes associated to this category.
-   */
-  var $_count = NULL;
-
-  /**
-   * Constructor.
-   *
-   * @param $count
-   *   The number of nodes associated to this category within the current
-   *   search.
-   */
-  function faceted_search_category($count = NULL) {
-    $this->_count = $count;
-  }
-
-  /**
-   * Return the number of nodes associated to this category within the current
-   * search.
-   *
-   * @return The number of matching nodes, or NULL is count is unknown.
-   */
-  function get_count() {
-    return $this->_count;
-  }
-
-  /**
-   * Return weight of this category, for sorting purposes.
-   */
-  function get_weight() {
-    return 0;
-  }
-
-  /**
-   * Updates a query for retrieving the subcategories of this category and their
-   * associated nodes within the current search results.
-   *
-   * This only needs to be overridden for hierarchical facets.
-   *
-   * @param $query
-   *   The query object to update.
-   * @return
-   *   FALSE if this facet can't have subcategories.
-   */
-  function build_subcategories_query(&$query) {
-    return FALSE;
-  }
-}
-
-/**
- * The parent class for facets.
- *
- * A facet is a filter with browsable categories.
- */
-class faceted_search_facet extends faceted_search_filter {
-
-  /**
-   * The current sort criteria to use for this facet. This determines how to
-   * sort the facet's categories.
-   */
-  var $_sort = 'count';
-
-  /**
-   * The maximum number of categories to show in this facet.
-   */
-  var $_max_categories = 10;
-
-  /**
-   * Constructor.
-   *
-   * @param $key
-   *   Key corresponding to this class of facet. This should be the same string
-   *   as used to construct the facet from the search text in the module's
-   *   implementation of hook_faceted_search_parse().
-   */
-  function faceted_search_facet($key, $active_path = array()) {
-    parent::faceted_search_filter($key, $active_path);
-  }
-
-  /**
-   * Return TRUE if this filter offers browsable categories, or FALSE otherwise.
-   *
-   * A browsable filter implies that categories retrieval and sorting methods
-   * are available.
-   */
-  function is_browsable() {
-    return TRUE;
-  }
-
-  /**
-   * Assign settings to this facet.
-   *
-   * @param $settings
-   *   Array of settings.
-   */
-  function set($settings) {
-    parent::set($settings);
-    if (isset($settings['sort'])) {
-      $this->_sort = $settings['sort'];
-    }
-    if (isset($settings['max_categories'])) {
-      $this->_max_categories = $settings['max_categories'];
-    }
-  }
-
-  /**
-   * Return the available sort options for this facet. Each option is a key =>
-   * label pair.
-   *
-   * Each key must have a corresponding handler method in the form
-   * 'build_sort_query_key'.
-   */
-  function get_sort_options() {
-    return array('count' => t('Count'));
-  }
-
-  /**
-   * Return the current sort criteria for this facet.
-   */
-  function get_sort() {
-    return $this->_sort;
-  }
-
-  /**
-   * Assigns the current sort criteria for this facet.
-   */
-  function set_sort($sort) {
-    // Assign value only if a corresponding handler exists.
-    if (method_exists($this, 'build_sort_query_'. $sort)) {
-      $this->_sort = $sort;
-    }
-  }
-
-  /**
-   * Handler for the 'count' sort criteria.
-   */
-  function build_sort_query_count(&$query) {
-    $query->add_orderby('count', 'DESC');
-  }
-
-  /**
-   * Applies the facet's current sort option to the given query.
-   */
-  function build_sort_query(&$query) {
-    $method = 'build_sort_query_'. $this->_sort;
-    if (method_exists($this, $method)) {
-      $this->$method($query);
-    }
-  }
-
-  /**
-   * Return the configured maximum number of categories to show in this facet.
-   *
-   * @return
-   *   The maximum number of categories, or 0 for no limit.
-   */
-  function get_max_categories() {
-    return $this->_max_categories;
-  }
-
-  /**
-   * Assign the maximum number of categories to show in this facet.
-   *
-   * @param $max_categories
-   *   The maximum number of categories, or 0 for no limit.
-   */
-  function set_max_categories($max_categories) {
-    $this->_max_categories = $max_categories;
-  }
-
-  /**
-   * Updates a query for retrieving the root categories of this filter and their
-   * associated nodes within the current search results.
-   *
-   * @param $query
-   *   The query object to update.
-   * @return
-   *   FALSE if this filter can't have root categories.
-   */
-  function build_root_categories_query() {
-    return FALSE;
-  }
-
-  /**
-   * This factory method creates categories given query results that include the
-   * fields selected in get_root_categories_query() or get_subcategories_query().
-   *
-   * @param $results
-   *   $results A database query result resource.
-   * @return
-   *   Array of categories.
-   */
-  function build_categories($results) {
-    return array();
-  }
-
-  /**
-   * Inject components into the query for selecting nodes matching this facet's
-   * active category.
-   *
-   * @param $query
-   *   Query to inject the components into.
-   * @param $words
-   *   Array keyed by search index type, each element being an array of positive
-   *   words to lookup for that index type. This method should insert any words
-   *   it cares about.
-   * @param $matches
-   *   Minimum number of words that should match in query results for each index type.
-   */
-  function build_results_query(&$query, &$words, &$matches) {
-    // Note: Facets ignore $words and $matches.
-    if ($category = $this->get_active_category()) {
-      $category->build_results_query($query);
-    }
-  }
-}
-
-/**
- * The base class of keyword categories.
- */
-class faceted_search_keyword_category {
-
-  /**
-   * Append keywords used by this category into the specified array.
-   */
-  function get_keywords(&$keywords) {
-    // Does nothing by default.
-  }
-
-  /**
-   * Check whether a given word is allowed for searching.
-   *
-   * @return
-   *   The allowed word, or NULL if it is not allowed.
-   */
-  function check_word($word) {
-    if (is_numeric($word)) {
-      return (int)ltrim($word, '-0');
-    }
-    return $word;
-  }
-
-  /**
-   * Prepare a label for output.
-   */
-  function check_label($label, $html = FALSE) {
-    if (!$html) {
-      return strip_tags($label);
-    }
-    return $label;
-  }
-}
-
-/**
- * The keyword AND category.
- */
-class faceted_search_keyword_and_category extends faceted_search_keyword_category {
-  var $_word = '';
-
-  /**
-   * Constructor.
-   *
-   * @param $phrase
-   *   String containing the word to search.
-   */
-  function faceted_search_keyword_and_category($word) {
-    $this->_word = $word;
-  }
-
-  /**
-   * Return the label for this category.
-   *
-   * @param $html
-   *   TRUE when HTML is allowed in the label, FALSE otherwise.
-   */
-  function get_label($html = FALSE) {
-    return $this->check_label(theme('faceted_search_keyword_and_label', $this->_word), $html);
-  }
-
-  /**
-   * Return the search text for this category.
-   */
-  function get_text() {
-    return $this->_word;
-  }
-
-  /**
-   * Append keywords used by this category into the specified array.
-   */
-  function get_keywords(&$keywords) {
-    $keywords[] = $this->_word;
-  }
-
-  /**
-   * Return the weight of this category, for sorting purposes.
-   */
-  function get_weight() {
-    return 0;
-  }
-
-  /**
-   * Inject components into the query for selecting nodes matching this category.
-   *
-   * @param $query
-   *   Query to inject the components into.
-   * @param $words
-   *   Array keyed by search index type, each element being an array of positive
-   *   words to lookup for that index type. This method should insert any words
-   *   it cares about.
-   * @param $matches
-   *   Minimum number of words that should match in query results for each index type.
-   * @param $type
-   *   Type of search index entry to be searched.
-   */
-  function build_results_query(&$query, &$words, &$matches, $type) {
-    if (($word = $this->check_word($this->_word)) && !isset($words[$type][$word])) {
-      if (strlen($word) >= variable_get('minimum_word_size', 3)) {
-        $words[$type][$word] = $word;
-        $matches[$type]++;
-      }
-      else {
-        // Short words are only searched against the dataset.
-        $query->enable_part("{$type}_search_dataset");
-        // Ensure this type will be searched even though it has no "long" word.
-        if (!isset($words[$type])) {
-          $words[$type] = array();
-        }
-      }
-
-      // The dataset will have to be looked up as well if the query becomes more
-      // complex because of other keyword search operators.
-      $query->set_current_part("{$type}_search_dataset");
-      $query->add_where("{$type}_search_dataset.data LIKE '%% %s %%'", $word);
-      $query->set_current_part(); // Back to default part.
-    }
-  }
-}
-
-/**
- * The keyword phrase category.
- */
-class faceted_search_keyword_phrase_category extends faceted_search_keyword_category {
-  var $_phrase = '';
-
-  /**
-   * Constructor.
-   *
-   * @param $phrase
-   *   String containing the phrase to search.
-   */
-  function faceted_search_keyword_phrase_category($phrase) {
-    $this->_phrase = $phrase;
-  }
-
-  /**
-   * Return the label for this category.
-   *
-   * @param $html
-   *   TRUE when HTML is allowed in the label, FALSE otherwise.
-   */
-  function get_label($html = FALSE) {
-    return $this->check_label(theme('faceted_search_keyword_phrase_label', $this->_phrase), $html);
-  }
-
-  /**
-   * Return the search text for this operator.
-   */
-  function get_text() {
-    return '"'. $this->_phrase .'"';
-  }
-
-  /**
-   * Append keywords used by this category into the specified array.
-   */
-  function get_keywords(&$keywords) {
-    $keywords[] = $this->_phrase;
-  }
-
-  /**
-   * Return the weight of this category, for sorting purposes.
-   */
-  function get_weight() {
-    return 1;
-  }
-
-  /**
-   * Inject components into the query for selecting nodes matching this category.
-   *
-   * @param $query
-   *   Query to inject the components into.
-   * @param $words
-   *   Array keyed by search index type, each element being an array of positive
-   *   words to lookup for that index type. This method should insert any words
-   *   it cares about.
-   * @param $matches
-   *   Minimum number of words that should match in query results for each index type.
-   * @param $type
-   *   Type of search index entry to be searched.
-   */
-  function build_results_query(&$query, &$words, &$matches, $type) {
-    $split = explode(' ', $this->_phrase);
-    foreach ($split as $word) {
-      if ($word = $this->check_word($word)) {
-        $words[$type][$word] = $word;
-      }
-    }
-    if (count($split) > 0) {
-      $matches[$type]++; // A phrase counts as one match.
-
-      if (count($split) > 1) {
-        // Real phrase. We'll have to verify it against the dataset.
-        $query->enable_part("{$type}_search_dataset");
-      }
-
-      // Add phrase match conditions.
-      $query->set_current_part("{$type}_search_dataset");
-      $query->add_where("{$type}_search_dataset.data LIKE '%% %s %%'", $this->_phrase);
-      $query->set_current_part(); // Back to default part.
-    }
-  }
-}
-
-/**
- * The keyword OR category.
- */
-class faceted_search_keyword_or_category extends faceted_search_keyword_category {
-  var $_words = array();
-
-  /**
-   * Constructor.
-   *
-   * @param $words
-   *   Array containing the words to search.
-   */
-  function faceted_search_keyword_or_category($words) {
-    $this->_words = $words;
-  }
-
-  /**
-   * Return the label for this category.
-   *
-   * @param $html
-   *   TRUE when HTML is allowed in the label, FALSE otherwise.
-   */
-  function get_label($html = FALSE) {
-    return $this->check_label(theme('faceted_search_keyword_or_label', $this->_words), $html);
-  }
-
-  /**
-   * Return the search text for this category.
-   */
-  function get_text() {
-    return implode(' OR ', $this->_words);
-  }
-
-  /**
-   * Append keywords used by this category into the specified array.
-   */
-  function get_keywords(&$keywords) {
-    $keywords = array_merge($keywords, $this->_words);
-  }
-
-  /**
-   * Return the weight of this category, for sorting purposes.
-   */
-  function get_weight() {
-    return 2;
-  }
-
-  /**
-   * Inject components into the query for selecting nodes matching this category.
-   *
-   * @param $query
-   *   Query to inject the components into.
-   * @param $words
-   *   Array keyed by search index type, each element being an array of positive
-   *   words to lookup for that index type. This method should insert any words
-   *   it cares about.
-   * @param $matches
-   *   Minimum number of words that should match in query results for each index type.
-   * @param $type
-   *   Type of search index entry to be searched.
-   */
-  function build_results_query(&$query, &$words, &$matches, $type) {
-    $where = '';
-    $where_args = array();
-    foreach ($this->_words as $word) {
-      if (($word = $this->check_word($word)) && !isset($words[$type][$word])) {
-        $words[$type][$word] = $word;
-        if (!empty($where)) {
-          $where .= ' OR ';
-        }
-        $where .= "{$type}_search_dataset.data LIKE '%% %s %%'";
-        $where_args[] = $word;
-      }
-    }
-    if (!empty($where)) {
-      $matches[$type]++;
-
-      // Matches will have to be checked against the dataset.
-      $query->enable_part("{$type}_search_dataset");
-      $query->set_current_part("{$type}_search_dataset");
-      array_unshift($where_args, $where);
-      call_user_func_array(array(&$query, 'add_where'), $where_args);
-      $query->set_current_part(); // Back to default part.
-    }
-  }
-}
-
-/**
- * The keyword NOT category.
- */
-class faceted_search_keyword_not_category extends faceted_search_keyword_category {
-  var $_word = '';
-
-  /**
-   * Constructor.
-   *
-   * @param $word
-   *   String containing the word to exclude from the search.
-   */
-  function faceted_search_keyword_not_category($word) {
-    $this->_word = $word;
-  }
-
-  /**
-   * Return the label for this category.
-   *
-   * @param $html
-   *   TRUE when HTML is allowed in the label, FALSE otherwise.
-   */
-  function get_label($html = FALSE) {
-    return $this->check_label(theme('faceted_search_keyword_not_label', $this->_word), $html);
-  }
-
-  /**
-   * Return the search text for this operator.
-   */
-  function get_text() {
-    return '-'. $this->_word;
-  }
-
-  /**
-   * Return the weight of this category, for sorting purposes.
-   */
-  function get_weight() {
-    return 3;
-  }
-
-  /**
-   * Inject components into the query for selecting nodes matching this category.
-   *
-   * @param $query
-   *   Query to inject the components into.
-   * @param $words
-   *   Array keyed by search index type, each element being an array of positive
-   *   words to lookup for that index type. This method should insert any words
-   *   it cares about.
-   * @param $matches
-   *   Minimum number of words that should match in query results for each index type.
-   * @param $type
-   *   Type of search index entry to be searched.
-   */
-  function build_results_query(&$query, &$words, &$matches, $type) {
-    if ($word = $this->check_word($this->_word)) {
-      // This is a negative word; do not insert it, but mark the type as used.
-      if (!isset($words[$type])) {
-        $words[$type] = array();
-      }
-
-      // Negative words are checked against the dataset.
-      $query->enable_part("{$type}_search_dataset");
-      $query->set_current_part("{$type}_search_dataset");
-      $query->add_where("{$type}_search_dataset.data NOT LIKE '%% %s %%'", $word);
-      $query->set_current_part(); // Back to default part.
-    }
-  }
-}
-
-/**
- * The filter for keyword search.
- *
- * Note: For keyword filters, the key corresponds to the type of search index
- * entry, and the id is always 'keyword'.
- */
-class faceted_search_keyword_filter extends faceted_search_filter {
-  var $_label = ''; // Label of the field.
-
-  /**
-   * Constructor.
-   *
-   * @param $type
-   *   Type of the search index entries corresponding to the field.
-   * @param $label
-   *   Label of the field.
-   * @param $category
-   *   Active category of the field.
-   */
-  function faceted_search_keyword_filter($type, $label, $category = NULL) {
-    parent::faceted_search_filter($type, isset($category) ? array($category) : array());
-    $this->_label = $label;
-  }
-
-  /**
-   * Returns the id of this filter.
-   */
-  function get_id() {
-    return 'keyword';
-  }
-
-  /**
-   * Return the search text corresponding to this filter.
-   */
-  function get_text() {
-    if ($category = $this->get_active_category()) {
-      return $category->get_text();
-    }
-    return '';
-  }
-
-  /**
-   * Return the label of this filter. This method is responsible for ensuring
-   * adequate security filtering.
-   */
-  function get_label() {
-    return check_plain($this->_label);
-  }
-
-  /**
-   * Append keywords used by this filter into the specified array.
-   */
-  function get_keywords(&$keywords) {
-    if ($category = $this->get_active_category()) {
-      $category->get_keywords($keywords);
-    }
-  }
-
-  /**
-   * Inject components into the query for selecting nodes matching this filter.
-   *
-   * @param $query
-   *   Query to inject the components into.
-   * @param $words
-   *   Array keyed by search index type, each element being an array of positive
-   *   words to lookup for that index type. This method should insert any words
-   *   it cares about.
-   * @param $matches
-   *   Minimum number of words that should match in query results for each index type.
-   */
-  function build_results_query(&$query, &$words, &$matches) {
-    if ($category = $this->get_active_category()) {
-      $category->build_results_query($query, $words, $matches, $this->get_key());
-    }
-  }
-}
-
-/**
- * This class stores and processes data related to a search.
- */
-class faceted_search {
-  // TODO: Remove the '_' prefix from data members. These are not so convenient
-  // for working with the schema.
-  
-  /**
-   * The environment id for this search. Each search environment has its own
-   * settings which make it possible to use multiple distinct search
-   * interfaces. It is this id that allows to select the proper settings.
-   */
-  var $env_id = 0;
-
-  /**
-   * The full, unprocessed search text.
-   */
-  var $_text = '';
-
-  /**
-   * An array with all keywords found in the search text.
-   */
-  var $_keywords = array();
-
-  /**
-   * Name of the temporary results table. While it exists, this table can be
-   * queried for various purposes, such as building the search interface.
-   */
-  var $_results_table = '';
-
-  /**
-   * Number of results in the results table. May be used only after a call to
-   * execute().
-   */
-  var $_results_count = 0;
-
-  /**
-   * Flag to indicate whether the search has been executed.
-   */
-  var $_ready = FALSE;
-
-  /**
-   * Collection of filters currently used by this search.
-   */
-  var $_filters = array();
-
-  /**
-   * Constructor. Initialize the search environment.
-   *
-   * @param $record
-   *   Optional for this environment, as fetched from the database. Defaults to
-   *   NULL (for new environment).
-   */
-  function faceted_search($record = NULL) {
-    // Assign default settings, ensuring that all "blanks" are properly filled.
-    $this->init();
-
-    if (isset($record)) {
-      $this->init_from_record($record);
-    }
-  }
-
-  /**
-   * Initialize this search environment with default settings.
-   */
-  function init() {
-    $this->name = '';
-    $this->description = '';
-    $this->settings['title'] = t('Search');
-    $this->settings['ignore_status'] = FALSE;
-    $this->settings['types'] = array();
-
-    // Provide other modules an opportunity to add their own default settings.
-    module_invoke_all('faceted_search_init', $this);
-  }
-
-  /**
-   * Assign this search environment's settings from a record fetched from the
-   * database. Existing settings will be overwritten only if they are present in
-   * the record.
-   *
-   * @param $record
-   *   Optional for this environment, as fetched from the database.
-   */
-  function init_from_record($record) {
-    if (isset($record->settings)) {
-      // The schema has this field serialized.
-      $settings = unserialize($record->settings);
-
-      if (is_array($settings)) {
-        // Load the settings from the record while preserving any default
-        // settings that are not present in the record.
-        $this->settings = $settings + $this->settings;
-      }
-        
-      unset($record->settings);
-    }
-      
-    // Load the remaining data from the record.
-    foreach ($record as $key => $value) {
-      $this->$key = $value;
-    }
-  }
-  
-  /**
-   * Return the original search text of this search (i.e. the text that was
-   * passed to the constructor).
-   */
-  function get_text() {
-    return $this->_text;
-  }
-
-  /**
-   * Return an array with keywords used in the search.
-   */
-  function get_keywords() {
-    return $this->_keywords;
-  }
-
-  /**
-   * Return the filters used by this search.
-   */
-  function get_filters() {
-    return $this->_filters;
-  }
-
-  /**
-   * Return the specified filter.
-   */
-  function get_filter($index) {
-    return $this->_filters[$index];
-  }
-
-  /**
-   * Return the index of a filter given its key and id.
-   */
-  function get_filter_by_id($key, $id) {
-    foreach ($this->_filters as $index => $filter) {
-      if ($filter->get_key() == $key && $filter->get_id() == $id) {
-        return array($index, $filter);
-      }
-    }
-  }
-
-  /**
-   * Prepare the complete search environment (with its filters), parsing the
-   * given search text. Requires that an env_id has been assigned previously.
-   *
-   * @param $text
-   *   Optional search text. Defaults to the empty string.
-   * @return
-   *   TRUE is the search environment could be successfully built.
-   */
-  function prepare($text = '') {
-    if (!$this->env_id) {
-      return FALSE;
-    }
-    
-    $this->_text = $text;
-    $this->_results_table = 'temp_faceted_search_results_'. $this->env_id;
-    
-    // Load settings for all enabled filters in this search environment.
-    $all_filter_settings = faceted_search_load_filter_settings($this);
-
-    // Make a selection with all enabled filters.
-    $selection = faceted_search_get_filter_selection($all_filter_settings);
-
-    // Collect all filters relevant to this search.
-    foreach (module_implements('faceted_search_collect') as $module) {
-      $module_filters = array();
-      $hook = $module .'_faceted_search_collect';
-
-      // Parse the search text and obtain corresponding filters. Text is eaten as
-      // it gets parsed.
-      $text = $hook($module_filters, 'text', $this, $selection, $text);
-
-      // Disallow filters that already have been collected from the search text.
-      foreach ($module_filters as $filter) {
-        unset($selection[$filter->get_key()][$filter->get_id()]);
-      }
-
-      // Collect any remaining allowed facets.
-      if (!empty($selection)) {
-        $hook($module_filters, 'facets', $this, $selection);
-      }
-
-      // Merge the filters listed by the current module.
-      $this->_filters = array_merge($this->_filters, $module_filters);
-
-      if (empty($selection)) {
-        break; // No more filters allowed.
-      }
-    }
-
-    // After filters have been collected, any remaining text is passed to the
-    // node filters.
-    faceted_search_collect_node_keyword_filters($this->_filters, 'text', $this, $text);
-
-    // Prepare filters for use, assigning them their settings are sorting them.
-    faceted_search_prepare_filters($this->_filters, $all_filter_settings);
-
-    // Assign the keywords found.
-    foreach ($this->_filters as $filter) {
-      $filter->get_keywords($this->_keywords);
-    }
-
-    return TRUE;
-  }
-
-  /**
-   * Return TRUE when the search has been executed.
-   */
-  function ready() {
-    return $this->_ready;
-  }
-
-  /**
-   * Perform the search and store the results in a temporary table.
-   *
-   * The prepare() method must have been called previously.
-   *
-   * Results are retrieved in two logical "passes". However, the two passes are
-   * joined together into a single query.  And in the case of most simple
-   * queries the second pass is not even used.
-   *
-   * The first pass selects a set of all possible matches (individual words
-   * looked up in the search_index table), which has the benefit of also
-   * providing the exact result set for simple "AND" or "OR" searches.
-   *
-   * The second portion of the query further refines this set by verifying
-   * advanced text conditions, such negative or phrase matches (search text
-   * checked against the search_dataset table).
-   */
-  function execute() {
-    if (!$this->_filters) {
-      return; // Nothing to search
-    }
-
-    $query = new faceted_search_query;
-    if (!$this->settings['ignore_status'] || !user_access('administer nodes')) {
-      // Restrict the search to published nodes only.
-      $query->add_where('n.status = 1');
-    }
-    $query->add_groupby('n.nid');
-
-    // Apply node type filter
-    $types = faceted_search_types($this);
-    if (!empty($types)) {
-      $query->add_where("n.type IN ('". implode("','", $types) ."')");
-    }
-
-    // Inject keyword search conditions if applicable.
-    $words = array(); // Positive words to include in the query.
-    $matches = array();
-    $word_score_expr = '';
-    $word_score_arg = 0;
-    foreach ($this->_filters as $filter) { // TODO: All filters are iterated; We should avoid iterating through those that are disabled.
-      $filter->build_results_query($query, $words, $matches);
-    }
-
-    if (count($matches) > 0) {
-      $query->add_having('COUNT(*) >= %d', max($matches));
-    }
-
-    // Some positive words were specified (and maybe some negatives as well).
-    $words_where = array();
-    $words_args = array();
-    $words_scores = array();
-    foreach ($words as $type => $type_words) {
-      if (empty($type_words)) {
-        // Negative words and/or short words were specified, but no positive
-        // "long" words. Negative words and short words are looked up in
-        // search_dataset, but since there are no positive "long" words, in this
-        // particular case it is joined directly with the node table and we can
-        // avoid joining search_index.
-        $query->set_current_part("{$type}_search_dataset");
-        $query->add_table('search_dataset', 'sid', 'n', 'nid', "{$type}_search_dataset");
-        $query->add_where("{$type}_search_dataset.type = '%s'", $type);
-        $query->set_current_part(); // Back to default part.
-      }
-      else {
-        // Join the search index for the current index type.
-        $query->add_table('search_index', 'sid', 'n', 'nid', "{$type}_search_index");
-
-        // Join the search dataset for the current index type, in case we're
-        // dealing with a complex query.
-        $query->set_current_part("{$type}_search_dataset");
-        $query->add_table('search_dataset', array('sid', 'type'), "{$type}_search_index", array('sid', 'type'), "{$type}_search_dataset");
-        $query->set_current_part(); // Back to default part.
-
-        $words_where[] = '('. substr(str_repeat("{$type}_search_index.word = '%s' OR ", count($type_words)), 0, -4) .") AND {$type}_search_index.type = '%s'";
-        $words_args = array_merge($words_args, array_values($type_words));
-        $words_args[] = $type;
-
-        $query->add_table('search_total', 'word', "{$type}_search_index", 'word', "{$type}_search_total");
-
-        $words_scores[] = "{$type}_search_index.score * {$type}_search_total.count";
-      }
-    }
-
-    if (!empty($words_where)) {
-      array_unshift($words_args, implode(' AND ', $words_where));
-      call_user_func_array(array(&$query, 'add_where'), $words_args);
-    }
-
-    if (!empty($words_scores)) {
-      // Add word score expression to the query.
-      $score = 'SUM('. implode(' + ', $words_scores) .')';
-      $query->set_current_part('normalize');
-      $query->add_field(NULL, $score, 'score');
-      $query->set_current_part();
-
-      // Perform the word score normalization query.
-      $query->enable_part('normalize');
-      $normalize = db_result(db_query_range($query->query(), $query->args(), 0, 1));
-      $query->disable_part('normalize');
-
-      if (!$normalize) {
-        $this->_ready = TRUE;
-        return; // Return with no results.
-      }
-
-      $word_score_expr = '(%f * '. $score .')';
-      $word_score_arg = 1.0 / $normalize;
-    }
-
-    // Add field needed for results.
-    $query->add_field('n', 'nid', 'nid');
-
-    // Add scoring expression to the query.
-    $this->_add_scoring($query, $word_score_expr, $word_score_arg);
-
-    // Give other modules an opportunity at altering the final query (e.g. for
-    // additional filtering).
-    module_invoke_all('faceted_search_query_alter', $this, $query);
-
-    // Perform the search results query and store results in a temporary table.
-    //
-    // This is MySQL-specific. db_query_temporary() is not used because of the
-    // need to specify the primary key. The index provides a huge performance
-    // improvement.
-    //
-    // See http://drupal.org/node/109513 regarding the use of HEAP engine.
-    db_query('CREATE TEMPORARY TABLE '. $this->_results_table .' (nid int unsigned NOT NULL, PRIMARY KEY (nid)) Engine=HEAP '. $query->query(), $query->args(), $this->_results_table);
-    $this->_results_count = db_result(db_query('SELECT COUNT(*) FROM '. $this->_results_table));
-    $this->_ready = TRUE;
-  }
-
-  /**
-   * Fetch the items from the current search results, or from all available
-   * nodes if no search text has been given.
-   *
-   * execute() must have been called beforehand.
-   *
-   * @return
-   *   Array of objects with nid and score members.
-   */
-  function load_results($limit = 10) {
-    $found_items = array();
-    if ($this->_results_count) {
-      $result = pager_query("SELECT * FROM ". $this->_results_table, $limit, 0, 'SELECT '. $this->_results_count);
-      while ($item = db_fetch_object($result)) {
-        $found_items[] = $item;
-      }
-    }
-    return $found_items;
-  }
-
-  /**
-   * Return the number of results for this search.
-   *
-   * execute() must have been called beforehand.
-   */
-  function get_results_count() {
-    return $this->_results_count;
-  }
-
-  /**
-   * Return the name of this search's (temporary) results table.
-   */
-  function get_results_table() {
-    return $this->_results_table;
-  }
-
-  /**
-   * Return the categories for the given facet and count matching nodes within
-   * results.
-   *
-   * @param $facet
-   *   The facet whose categories are to be loaded.
-   * @param $from
-   *   Ordinal number of the first category to load. Numbering starts at 0.
-   * @param $max_count
-   *   Number of categories to load.
-   * @return
-   *   Array of categories (objects having the faceted_search_category
-   *   interface).
-   */
-  function load_categories($facet, $from = NULL, $max_count = NULL) {
-    // Prepare the base query components to include the current search results
-    // and to count nodes.
-    $query = new faceted_search_query;
-    $query->add_field(NULL, 'COUNT(DISTINCT(n.nid))', 'count');
-    if (!$this->_ready) {
-      // No temporary table available, search within all nodes.
-      if (!$this->settings['ignore_status'] || !user_access('administer nodes')) {
-        // Restrict the search to published nodes only.
-        $query->add_where('n.status = 1');
-      }
-
-      // There is no results table at this point, so we can't rely on the
-      // results table having been filtered already. Therefore, we ask modules
-      // to alter the categories query instead.
-      module_invoke_all('faceted_search_query_alter', $this, $query);
-    }
-    elseif ($this->_results_count > 0) {
-      // Search within results.
-      $query->add_table($this->_results_table, 'nid', 'n', 'nid', 'results', 'INNER', FALSE);
-    }
-    else {
-      // Current search yields no results, thus no categories are possible.
-      return array();
-    }
-
-    // Gather the query components that will retrieve the categories.
-    if ($active_category = $facet->get_active_category()) {
-      $has_categories = $active_category->build_subcategories_query($query);
-    }
-    else {
-      $has_categories = $facet->build_root_categories_query($query);
-    }
-    if (!$has_categories) {
-      return array();
-    }
-
-    // Apply sort criteria.
-    $facet->build_sort_query($query);
-
-    // Apply node type filter.
-    $types = faceted_search_types($this);
-    if (!empty($types)) {
-      $query->add_where("n.type IN ('". implode("','", $types) ."')");
-    }
-
-    // Run the query and return the categories.
-    if (isset($from) && isset($max_count)) {
-      $results = db_query_range($query->query(), $query->args(), $from, $max_count);
-    }
-    else {
-      $results = db_query($query->query(), $query->args());
-    }
-    return $facet->build_categories($results);
-  }
-
-  /**
-   * Add scoring expression to the search query.
-   */
-  function _add_scoring(&$query, $word_score_expr = '', $word_score_arg = 0) {
-    // Based on node_search() -- START
-
-    $score_field = array();
-    $score_arguments = array();
-    if (!empty($word_score_expr) && $weight = (int)variable_get('node_rank_relevance', 5)) {
-      $score_field[] = "%d * $word_score_expr";
-      $score_arguments[] = $weight;
-      $score_arguments[] = $word_score_arg;
-    }
-    if ($weight = (int)variable_get('node_rank_recent', 5)) {
-      // Exponential decay with half-life of 6 months, starting at last indexed node
-      $score_field[] = '%d * POW(2, (GREATEST(MAX(n.created), MAX(n.changed), MAX(c.last_comment_timestamp)) - %d) * 6.43e-8)';
-      $score_arguments[] = $weight;
-      $score_arguments[] = (int)variable_get('node_cron_last', 0);
-      $query->add_table('node_comment_statistics', 'nid', 'n', 'nid', 'c', 'LEFT');
-    }
-    if (module_exists('comment') && $weight = (int)variable_get('node_rank_comments', 5)) {
-      // Inverse law that maps the highest reply count on the site to 1 and 0 to 0.
-      $scale = variable_get('node_cron_comments_scale', 0.0);
-      $score_field[] = '%d * (2.0 - 2.0 / (1.0 + MAX(c.comment_count) * %f))';
-      $score_arguments[] = $weight;
-      $score_arguments[] = $scale;
-      if (!$query->has_table('c')) {
-        $query->add_table('node_comment_statistics', 'nid', 'n', 'nid', 'c', 'LEFT');
-      }
-    }
-    // Based on node_search() -- END
-
-    // Add the formulas and their arguments into the query.
-    if (count($score_field)) {
-      // Prepend the first three arguments for add_field().
-      $score_arguments = array_merge(array(NULL, implode(' + ', $score_field), 'score'), $score_arguments);
-      // Call $query->add_field() with all arguments.
-      call_user_func_array(array(&$query, 'add_field'), $score_arguments);
-
-      $query->add_orderby('score', 'DESC');
-    }
-  }
-}
-
-/**
- * This class allows to build SQL queries piece by piece.
- *
- * Query elements are assigned to parts. These parts may selectively enabled or
- * disabled to control the final assembled the SQL statements. This is useful
- * when some context is still unknown at the time the elements are gathered -
- * those elements can still be injected to the query object and later filtered
- * in or out depending on context.
- */
-class faceted_search_query {
-  var $primary_table_alias = '';
-  var $table_queue = array(); // Ordered array of tables aliases to join.
-  var $tables = array(); // Tables to join, keyed by their alias.
-  var $fields = array(); // Fields, keyed by their alias.
-  var $field_args = array();
-  var $groupby = array();
-  var $having = array();
-  var $having_args = array();
-  var $orderby = array();
-  var $where = array();
-  var $where_args = array();
-  var $subqueries = array();
-  var $subqueries_args = array();
-  // Part to which query elements will be added to.
-  var $current_part = 'default';
-  // Parts enabled for use in the final assembled the query.
-  var $parts = array('default' => 'default');
-
-  /**
-   * Constructor. Specifies the primary table and field for this query.
-   *
-   * The primary table and field are always assigned to the default part.
-   */
-  function faceted_search_query($primary_table = 'node', $primary_table_alias = 'n', $prefixing = TRUE) {
-    $this->primary_table_alias = $primary_table_alias;
-    $this->tables['default'][$primary_table_alias] = array(
-      'table' => $primary_table,
-      'field' => NULL,
-      'left_table_alias' => NULL,
-      'left_field' => NULL,
-      'join' => NULL,
-      'prefixing' => $prefixing,
-    );
-  }
-
-  /**
-   * Set the current part. This determines the part to which any query element
-   * will be added to, until this method is called to select another part as
-   * the current part.
-   *
-   * The current part cannot be "unset", but it can be reset back to the
-   * default part.
-   *
-   * @param $part
-   *   Name of the part. Defaults to 'default'.
-   */
-  function set_current_part($part = 'default') {
-    $this->current_part = $part;
-  }
-
-  /**
-   * Return the current part.
-   */
-  function get_current_part() {
-    return $this->current_part;
-  }
-
-  /**
-   * Mark a part as enabled for use in query assembling. The query() and args()
-   * methods will only return query elements that belong to parts that have
-   * been enabled.
-   *
-   * The default part is always enabled.
-   *
-   * @see query()
-   * @see args()
-   * @see disable_part()
-   */
-  function enable_part($part) {
-    $this->parts[$part] = $part;
-  }
-
-  /**
-   * Disallow a part for use in query assembling.
-   *
-   * The default part cannot be disabled.
-   *
-   * @see enable_part()
-   */
-  function disable_part($part) {
-    if ($part != 'default') {
-      unset($this->parts[$part]);
-    }
-  }
-
-  /**
-   * Indicate whether the specified part is enabled for use in query assembling.
-   */
-  function is_part_enabled($part) {
-    return isset($this->parts[$part]);
-  }
-
-  /**
-   * Add a table to join.
-   *
-   * @param $table
-   *   Name of the table to join.
-   * @param $field
-   *   Field to use in the ON condition of the join clause. This can be an array
-   *   if the condition involves multiple fields (multiple fields will be glued
-   *   together with the AND operator).
-   * @param $left_table_alias
-   *   Alias of the table to use on the left part of the join. That table must
-   *   be the query's primary table or another table added through
-   *   add_table(). This must be an alias as returned by add_table().
-   * @param $left_field
-   *   Field from the left table to use in the ON condition of the join
-   *   clause. If $field is array, then $left_field must be an array of the same
-   *   length.
-   * @param $alias
-   *   Alias to use for the table being added. If unspecified, the alias will be
-   *   the same as the table's name. A unique alias must be given if the table
-   *   is to be joined multiple times.
-   * @param $join
-   *   Type of join clause to use. Default is 'INNER'.
-   * @param $prefixing
-   *   TRUE when the table should be prefixed via db_prefix_tables(). This
-   *   should usually be FALSE when joining a temporary table.
-   * @return
-   *   The alias assigned to the table in this query.
-   */
-  function add_table($table, $field, $left_table_alias, $left_field, $alias = NULL, $join = 'INNER', $prefixing = TRUE) {
-    $alias = $alias ? $alias : $table;
-    $this->table_queue[$this->current_part][] = $alias;
-    $this->tables[$this->current_part][$alias] = array(
-      'table' => $table,
-      'field' => $field,
-      'left_table_alias' => $left_table_alias,
-      'left_field' => $left_field,
-      'join' => $join,
-      'prefixing' => $prefixing,
-    );
-    return $alias;
-  }
-
-  /**
-   * Indicate whether a table alias is present in this query.
-   *
-   * @param $alias
-   *   Alias expected to have been assigned to a table in this query.
-   * @param $part
-   *   Optional. Part in which to look for the table. When not specified, the
-   *   current part is used.
-   * @return
-   *   TRUE if the alias is present in the query, FALSE otherwise.
-   */
-  function has_table($alias, $part = NULL) {
-    $part = isset($part) ? $part : $this->current_part;
-    return isset($this->tables[$part][$alias]);
-  }
-
-  /**
-   * Add a field.
-   *
-   * @param $table_alias
-   *   Alias of the table containing the field, either the primary table or an
-   *   alias returned by add_table(). Use NULL for a formula.
-   * @param $field
-   *   The name of the field, or the formula defining the field.
-   * @param $alias
-   *   Alias to use to identify the field. If omitted, the alias will be
-   *   $table_alias .'_'. $field. Must be specified if the field is a formula.
-   * @param ...
-   *   A variable number of arguments which are substituted into the query using
-   *   printf) syntax. The query arguments can be enclosed in one array
-   *   instead. Valid %-modifiers are: %s, %d, %f, %b (binary data, do not
-   *   enclose in '') and %%. This is useful when the field is defined by a
-   *   formula.
-   * @return
-   *   The alias assigned to the field in this query.
-   */
-  function add_field($table_alias, $field, $alias = NULL) {
-    $alias = $alias ? $alias : $table_alias .'_'. $field;
-    $this->fields[$this->current_part][$alias] = array(
-      'field' => $field,
-      'table_alias' => $table_alias,
-    );
-    $args = func_get_args();
-    array_shift($args); // Skip $table_alias.
-    array_shift($args); // Skip $field.
-    if (count($args)) {
-      array_shift($args); // Skip $alias.
-      if (count($args)) {
-        // Add extra arguments.
-        if (isset($this->field_args[$this->current_part])) {
-          $this->field_args[$this->current_part] = array_merge($this->field_args[$this->current_part], $args);
-        }
-        else {
-          $this->field_args[$this->current_part] = $args;
-        }
-      }
-    }
-    return $alias;
-  }
-
-  /**
-   * Indicate whether a field alias is present in this query.
-   *
-   * @param $alias
-   *   Alias expected to have been assigned to a field in this query.
-   * @param $part
-   *   Optional. Part in which to look for the field. When not specified, the
-   *   current part is used.
-   * @return
-   *   TRUE if the alias is present in the query, FALSE otherwise.
-   */
-  function has_field($alias, $part = NULL) {
-    $part = isset($part) ? $part : $this->current_part;
-    return isset($this->fields[$part][$alias]);
-  }
-
-  /**
-   * Add a WHERE condition. When the query is later assembled, all WHERE
-   * conditions are glued together with the AND operator.
-   *
-   * @param $clause
-   *   The condition to add. The caller must ensure that any field is fully
-   *   qualified using its table's alias as returned by add_table().
-   * @param ...
-   *   A variable number of arguments which are substituted into the query using
-   *   printf) syntax. The query arguments can be enclosed in one array
-   *   instead. Valid %-modifiers are: %s, %d, %f, %b (binary data, do not
-   *   enclose in '') and %%.
-   */
-  function add_where($clause) {
-    $this->where[$this->current_part][] = $clause;
-    $args = func_get_args();
-    array_shift($args); // Skip $clause.
-    if (count($args)) {
-      if (isset($this->where_args[$this->current_part])) {
-        $this->where_args[$this->current_part] = array_merge($this->where_args[$this->current_part], $args);
-      }
-      else {
-        $this->where_args[$this->current_part] = $args;
-      }
-    }
-  }
-
-  /**
-   * Add a subquery as a WHERE condition.
-   *
-   * @param $clause
-   *   The condition to add. The caller must ensure that any field is fully
-   *   qualified using its table's alias as returned by add_table(). The caller
-   *   is responsible for calling db_rewrite_sql() on the subquery.
-   * @param ...
-   *   A variable number of arguments which are substituted into the query using
-   *   printf) syntax. The query arguments can be enclosed in one array
-   *   instead. Valid %-modifiers are: %s, %d, %f, %b (binary data, do not
-   *   enclose in '') and %%.
-   */
-  function add_subquery($clause) {
-    $this->subqueries[$this->current_part][] = $clause;
-    $args = func_get_args();
-    array_shift($args); // Skip $clause.
-    if (isset($args[0]) && is_array($args[0])) {
-      // Using the "all arguments in one array" syntax.
-      $args = $args[0];
-    }
-    if (count($args)) {
-      if (isset($this->subqueries_args[$this->current_part])) {
-        $this->subqueries_args[$this->current_part] = array_merge($this->subqueries_args[$this->current_part], $args);
-      }
-      else {
-        $this->subqueries_args[$this->current_part] = $args;
-      }
-    }
-  }
-
-  /**
-   * Add a GROUP BY clause.
-   *
-   * @param $clause
-   *   The clause to add. The caller must use field aliases as returned by
-   *   add_field().
-   * @param $order
-   *   Either 'ASC' or 'DESC'.
-   */
-  function add_groupby($clause, $order = 'ASC') {
-    $this->groupby[$this->current_part][] = $clause .' '. $order;
-  }
-
-  /**
-   * Add a HAVING clause.
-   *
-   * @param $clause
-   *   The clause to add. The caller must ensure that any field is fully
-   *   qualified using its table's alias as returned by add_table().
-   * @param ...
-   *   A variable number of arguments which are substituted into the query using
-   *   printf) syntax. The query arguments can be enclosed in one array
-   *   instead. Valid %-modifiers are: %s, %d, %f, %b (binary data, do not
-   *   enclose in '') and %%.
-   */
-  function add_having($clause) {
-    $this->having[$this->current_part][] = $clause;
-    $args = func_get_args();
-    array_shift($args); // Skip $clause.
-    if (count($args)) {
-      if (isset($this->having_args[$this->current_part])) {
-        $this->having_args[$this->current_part] = array_merge($this->having_args[$this->current_part], $args);
-      }
-      else {
-        $this->having_args[$this->current_part] = $args;
-      }
-    }
-  }
-
-  /**
-   * Add an ORDER BY clause.
-   *
-   * @param $clause
-   *   The clause to add. The caller must use field aliases as returned by
-   *   add_field().
-   * @param $order
-   *   Either 'ASC' or 'DESC'.
-   */
-  function add_orderby($clause, $order = 'ASC') {
-    $this->orderby[$this->current_part][] = $clause .' '. $order;
-  }
-
-  /**
-   * Return all arguments that need to be substituted into the query. Only
-   * arguments associated to enabled parts are returned.
-   *
-   * @return
-   *   Array of argument values to pass to the query.
-   * @see query()
-   * @see enable_part()
-   */
-  function args() {
-    $field_args = array();
-    $where_args = array();
-    $subqueries_args = array();
-    $having_args = array();
-    // Where arguments.
-    foreach ($this->parts as $part) {
-      if (isset($this->field_args[$part])) {
-        $field_args = array_merge($field_args, $this->field_args[$part]);
-      }
-      if (isset($this->where_args[$part])) {
-        $where_args = array_merge($where_args, $this->where_args[$part]);
-      }
-      if (isset($this->subqueries_args[$part])) {
-        $subqueries_args = array_merge($subqueries_args, $this->subqueries_args[$part]);
-      }
-      if (isset($this->having_args[$part])) {
-        $having_args = array_merge($having_args, $this->having_args[$part]);
-      }
-    }
-    return array_merge($field_args, $where_args, $subqueries_args, $having_args);
-  }
-
-  /**
-   * Return the assembled SQL query (with unsubstituted arguments, if
-   * any). Only query elements associated to enabled parts are used.
-   *
-   * @see args()
-   * @see enable_part()
-   */
-  function query() {
-    // Primary field.
-    if ($this->tables['default'][$this->primary_table_alias]['prefixing']) {
-      $wrapper_begin = '{';
-      $wrapper_end = '}';
-    }
-    else {
-      $wrapper_begin = $wrapper_end = '';
-    }
-    $primary = $wrapper_begin . $this->tables['default'][$this->primary_table_alias]['table'] . $wrapper_end .' AS '. $this->primary_table_alias;
-
-    // Collect elements from all enabled parts.
-    $fields = array();
-    $joins = array();
-    $where = array();
-    $subqueries = array();
-    $groupby = array();
-    $having = array();
-    $orderby = array();
-    foreach ($this->parts as $part) {
-      // Fields.
-      if (isset($this->fields[$part])) {
-        foreach ($this->fields[$part] as $field_alias => $field) {
-          if ($field['table_alias']) {
-            $table = $field['table_alias'];
-            $fields[] = "$table.$field[field] AS $field_alias";
-          }
-          else {
-            $fields[] = "$field[field] AS $field_alias";
-          }
-        }
-      }
-
-      // Joins.
-      if (isset($this->table_queue[$part])) {
-        foreach ($this->table_queue[$part] as $table_alias) {
-          $table = $this->tables[$part][$table_alias];
-          if ($table['prefixing']) {
-            $wrapper_begin = '{';
-            $wrapper_end = '}';
-          }
-          else {
-            $wrapper_begin = $wrapper_end = '';
-          }
-
-          // Build the join condition.
-          if (is_array($table['left_field'])) {
-            // There are multiple fields to use in the join condition.
-            $join_condition = array();
-            foreach ($table['left_field'] as $index => $table_left_field) {
-              $join_condition[] = "{$table['left_table_alias']}.{$table_left_field} = {$table_alias}.{$table['field'][$index]}";
-            }
-            $join_condition = implode(' AND ', $join_condition);
-          }
-          else {
-            $join_condition = "{$table['left_table_alias']}.{$table['left_field']} = {$table_alias}.{$table['field']}";
-          }
-
-          // Add the table join clause.
-          $joins[] = $table['join'] ." JOIN $wrapper_begin". $table['table'] ."$wrapper_end AS $table_alias ON $join_condition";
-        }
-      }
-
-      // Where clauses.
-      if (isset($this->where[$part])) {
-        $where = array_merge($where, $this->where[$part]);
-      }
-
-      // Subqueries.
-      if (isset($this->subqueries[$part])) {
-        $subqueries = array_merge($subqueries, $this->subqueries[$part]);
-      }
-
-      // Group by clauses.
-      if (isset($this->groupby[$part])) {
-        $groupby = array_merge($groupby, $this->groupby[$part]);
-      }
-
-      // Having clauses.
-      if (isset($this->having[$part])) {
-        $having = array_merge($having, $this->having[$part]);
-      }
-
-      // Order by clauses.
-      if (isset($this->orderby[$part])) {
-        $orderby = array_merge($orderby, $this->orderby[$part]);
-      }
-    }
-
-    $fields = implode(', ', $fields);
-    $joins = count($joins) ? ' '. implode(' ', $joins) : '';
-    $where = count($where) ? ' WHERE (('. implode(') AND (', $where) .'))' : '';
-
-    // Where subqueries (added as $SUBQUERY$n tokens, which are replaced after the call
-    // to db_rewrite_sql(). See related issue: http://drupal.org/node/151910).
-    $subqueries_tokens = '';
-    if (count($subqueries)) {
-      $subqueries_tokens = (empty($where) ? ' WHERE ' : ' AND ') .'$SUBQUERY$'. implode(' AND $SUBQUERY$', array_keys($subqueries));
-    }
-
-    // Group by clauses.
-    $groupby = count($groupby) ? ' GROUP BY '. implode(', ', $groupby) : '';
-
-    // Having clauses.
-    $having = count($having) ? ' HAVING ('. implode(') AND (', $having) .')' : '';
-
-    // Order by clauses.
-    $orderby = count($orderby) ? ' ORDER BY '. implode(', ', $orderby) : '';
-
-    // Create the query string.
-    $query = db_rewrite_sql("SELECT $fields FROM $primary$joins$where$subqueries_tokens$groupby$having$orderby");
-    if (count($subqueries)) {
-      foreach ($subqueries as $key => $subquery) {
-        $search[] = '$SUBQUERY$'. $key;
-        $replace[] = $subquery;
-      }
-      // Replace subquery tokens.
-      $query = str_replace($search, $replace, $query);
-    }
-
-    return $query;
-  }
-}
-
Index: faceted_search.install
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/faceted_search/faceted_search.install,v
retrieving revision 1.12
diff -u -p -r1.12 faceted_search.install
--- faceted_search.install	4 Jan 2009 19:36:25 -0000	1.12
+++ faceted_search.install	3 Aug 2009 11:51:03 -0000
@@ -2,6 +2,12 @@
 // $Id: faceted_search.install,v 1.12 2009/01/04 19:36:25 davidlesieur Exp $
 
 /**
+ * @file
+ * Installation file for building tables for faceted search
+ *
+ */
+
+/**
  * Implementation of hook_schema().
  */
 function faceted_search_schema() {
@@ -26,6 +32,13 @@ function faceted_search_schema() {
         'default' => '',
         'description' => t('The description of the faceted search environment.'),
       ),
+      'engine' => array(
+        'type' => 'varchar',
+        'length' => '32',
+        'default' => '',
+        'not null' => TRUE,
+        'description' => t('The faceted search engine used.'),
+      ),
       'settings' => array(
         'type' => 'text',
         'size' => 'big',
@@ -184,3 +197,19 @@ function faceted_search_update_6000() {
   return $ret;
 }
 
+/**
+ * Add engine column to store the search engine selected. 
+ */
+function faceted_search_update_6100() {
+  $ret = array();
+    db_add_field($ret, 'faceted_search_env', 'engine',
+      array(
+        'type' => 'varchar',
+        'length' => '32',
+        'default' => '',
+        'not null' => TRUE,
+        'description' => t('The faceted search engine used.'),
+      )
+    );
+  return $ret;
+}
\ No newline at end of file
Index: faceted_search.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/faceted_search/faceted_search.module,v
retrieving revision 1.53
diff -u -p -r1.53 faceted_search.module
--- faceted_search.module	3 Mar 2009 05:13:12 -0000	1.53
+++ faceted_search.module	3 Aug 2009 11:51:04 -0000
@@ -6,8 +6,6 @@
  * An API for performing faceted searches.
  */
 
-require_once('./'. drupal_get_path('module', 'faceted_search') .'/faceted_search.inc');
-
 /**
  * Implementation of hook_help().
  */
@@ -94,6 +92,7 @@ function faceted_search_menu() {
  *   The search text. Only used when the domain is 'text'.
  */
 function faceted_search_collect_node_keyword_filters(&$filters, $domain, $env, $text = '') {
+  faceted_search_require('search_engine', $env);
   switch ($domain) {
     case 'keyword filters':
       $filter = new faceted_search_keyword_filter('node', t('Anywhere'));
@@ -108,20 +107,24 @@ function faceted_search_collect_node_key
       // Create the filters.
       foreach ($keys['positive'] as $keyword) {
         if (is_array($keyword)) {
-          $filter = new faceted_search_keyword_filter('node', '', new faceted_search_keyword_or_category($keyword));
+          $class_name = $env->env_id == 0 ? 'faceted_search_keyword_or_category' : 'faceted_search_'. $env->engine .'_keyword_or_category';
+          $filter = new faceted_search_keyword_filter('node', '', new $class_name($keyword));
         }
         elseif (strpos($keyword, ' ')) {
-          $filter = new faceted_search_keyword_filter('node', '', new faceted_search_keyword_phrase_category($keyword));
+          $class_name = $env->env_id == 0 ? 'faceted_search_keyword_phrase_category' : 'faceted_search_'. $env->engine .'_keyword_phrase_category';
+          $filter = new faceted_search_keyword_filter('node', '', new $class_name($keyword));
         }
         else {
-          $filter = new faceted_search_keyword_filter('node', '', new faceted_search_keyword_and_category($keyword));
+          $class_name = $env->env_id == 0 ? 'faceted_search_keyword_and_category' : 'faceted_search_'. $env->engine .'_keyword_and_category';
+          $filter = new faceted_search_keyword_filter('node', '', new $class_name($keyword));
         }
         $filter->set_weight(-999); // Default weight.
         $filter->set_status(TRUE); // Default status.
         $filters[] = $filter;
       }
       foreach ($keys['negative'] as $keyword) {
-        $filter = new faceted_search_keyword_filter('node', '', new faceted_search_keyword_not_category($keyword));
+        $class_name = 'faceted_search_'. $env->engine .'_keyword_not_category';
+        $filter = new faceted_search_keyword_filter('node', '', new $class_name($keyword));
         $filter->set_weight(-999); // Default weight.
         $filter->set_status(TRUE); // Default status.
         $filters[] = $filter;
@@ -129,6 +132,52 @@ function faceted_search_collect_node_key
   }
 }
 
+
+/**
+ * Implementation of hook_faceted_search_engine().
+ */
+function faceted_search_faceted_search_engine() {
+  return array(
+    'description' => t('Standard faceted search'),
+    'path' => drupal_get_path('module', 'faceted_search') .'/includes/faceted_search.inc',
+  );
+}
+
+/**
+ * Require once both the base search classes and the search engine
+ * implementation for the current environment
+ */
+function faceted_search_require($require, $env = NULL, $reset = FALSE, $env_id = 0) {
+  switch ($require) {
+    case 'search_engine':
+      require_once('./'. drupal_get_path('module', 'faceted_search') .'/includes/search.inc');
+      static $search_engine_path;
+      if (!$search_engine_path) {
+        if ($env_id) {
+          $engine = faceted_search_get_engine($env_id);
+        }
+        else if ($env) {
+          // default to Drupal search if no engine is set
+          $engine = isset($env->engine) ? $env->engine : 'faceted_search';
+        }
+        else {
+          // include default Drupal search base class
+          return require_once('./'. drupal_get_path('module', 'faceted_search') .'/includes/search.inc');
+        }
+        $info = array();
+        $info = module_invoke($engine, 'faceted_search_engine');
+        $search_engine_path = $info['path'];
+      }
+      else if ($reset) {
+        $info = array();
+        $info = module_invoke($env->engine, 'faceted_search_engine');
+        $search_engine_path = $info['path'];        
+      }
+      require_once($search_engine_path);
+      break;
+  }
+}
+
 /**
  * Return the collection of node types handled by a given environment.
  *
@@ -186,13 +235,16 @@ function faceted_search_env_load($env_id
   }
   if (!isset($env[$env_id])) {
     $results = db_query('SELECT * FROM {faceted_search_env} WHERE env_id = %d', $env_id);
-    if ($record = db_fetch_object($results)) {
-      $env[$env_id] = new faceted_search($record);
+    if ($record = db_fetch_object($results)) {   
+      faceted_search_require('search_engine', $record, TRUE);  
+      $class_name = 'faceted_search_'. $record->engine;       
+      $env[$env_id] = new $class_name($record);
     }
-  }
+  }  
   return isset($env[$env_id]) ? $env[$env_id] : FALSE;
 }
 
+
 /**
  * Return the ids of all existing environments.
  */
@@ -276,8 +328,7 @@ function faceted_search_get_filter_selec
 function faceted_search_build_text($filters) {
   $texts_per_key = array();
   foreach ($filters as $filter) {
-  	
-  	$text = $filter->get_text();
+    $text = $filter->get_text();
     if ($text != '') {
       $texts_per_key[$filter->get_key()][] = $text;
     }
@@ -416,6 +467,17 @@ function faceted_search_prepare_filters(
 }
 
 /**
+ * Get the search engine for the current environment
+ */
+function faceted_search_get_engine($env_id) {
+  $results = db_query('SELECT engine FROM {faceted_search_env} WHERE env_id = %d', $env_id);
+  if ($record = db_fetch_object($results)) {
+    $engine = $record->engine;
+  }  
+  return $engine;
+}
+
+/**
  * Implementation of hook_theme().
  */
 function faceted_search_theme() {
@@ -675,4 +737,4 @@ function _faceted_search_element_sort($a
     return 0;
   }
   return ($a_weight < $b_weight) ? -1 : 1;
-}
+}
\ No newline at end of file
Index: faceted_search_ui.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/faceted_search/faceted_search_ui.module,v
retrieving revision 1.85
diff -u -p -r1.85 faceted_search_ui.module
--- faceted_search_ui.module	3 Mar 2009 00:17:09 -0000	1.85
+++ faceted_search_ui.module	3 Aug 2009 11:51:06 -0000
@@ -6,7 +6,6 @@
  * A user interface for searching and browsing through multiple facets.
  */
 
-require_once('./'. drupal_get_path('module', 'faceted_search') .'/faceted_search.inc');
 require_once('./'. drupal_get_path('module', 'faceted_search') .'/faceted_search_ui.inc');
 
 /**
@@ -365,7 +364,7 @@ function faceted_search_ui_stage_results
   }
 
   $env = faceted_search_env_load($env_id);
-  
+    
   // Initialize the current search.
   $env->prepare($text);
   $env->ui_state['stage'] = 'results';
Index: field_keyword_filter.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/faceted_search/field_keyword_filter.module,v
retrieving revision 1.4
diff -u -p -r1.4 field_keyword_filter.module
--- field_keyword_filter.module	4 Jan 2009 23:00:01 -0000	1.4
+++ field_keyword_filter.module	3 Aug 2009 11:51:06 -0000
@@ -6,12 +6,11 @@
  * Allows users to perform keyword searches restricted by field.
  */
 
-require_once('./'. drupal_get_path('module', 'faceted_search') .'/faceted_search.inc');
-
 /**
  * Implementation of hook_faceted_search_collect().
  */
 function field_keyword_filter_faceted_search_collect(&$filters, $domain, $env, $selection, $arg = NULL) {
+  faceted_search_require('search_engine', $env);
   switch ($domain) {
     case 'keyword filters':
       $fields = field_indexer_load_fields(TRUE);
@@ -38,18 +37,22 @@ function field_keyword_filter_faceted_se
             // Create the filters.
             foreach ($keys['positive'] as $keyword) {
               if (is_array($keyword)) {
-                $filter = new field_keyword_filter($filter_key, $field['label'], new faceted_search_keyword_or_category($keyword));
+                $class_name = $env->env_id == 0 ? 'faceted_search_keyword_or_category' : 'faceted_search_'. $env->engine .'_keyword_or_category';
+                $filter = new field_keyword_filter($filter_key, $field['label'], new $class_name($keyword));
               }
               elseif (strpos($keyword, ' ')) {
-                $filter = new field_keyword_filter($filter_key, $field['label'], new faceted_search_keyword_phrase_category($keyword));
+                $class_name = $env->env_id == 0 ? 'faceted_search_keyword_phrase_category' : 'faceted_search_'. $env->engine .'_keyword_phrase_category';
+                $filter = new field_keyword_filter($filter_key, $field['label'], new $class_name($keyword));
               }
               else {
-                $filter = new field_keyword_filter($filter_key, $field['label'], new faceted_search_keyword_and_category($keyword));
+                $class_name = $env->env_id == 0 ? 'faceted_search_keyword_and_category' : 'faceted_search_'. $env->engine .'_keyword_and_category';
+                $filter = new field_keyword_filter($filter_key, $field['label'], new $class_name($keyword));
               }
               $filters[] = $filter;
             }
             foreach ($keys['negative'] as $keyword) {
-              $filter = new field_keyword_filter($filter_key, $field['label'], new faceted_search_keyword_not_category($keyword));
+              $class_name = $env->env_id == 0 ? 'faceted_search_keyword_not_category' : 'faceted_search_'. $env->engine .'_keyword_not_category';
+              $filter = new field_keyword_filter($filter_key, $field['label'], new $class_name($keyword));
               $filters[] = $filter;
             }
             // Remove the parsed text
@@ -59,35 +62,4 @@ function field_keyword_filter_faceted_se
       }
       return $arg;
   }
-}
-
-/**
- * A filter for restricting a keyword search to a specific field.
- */
-class field_keyword_filter extends faceted_search_keyword_filter {
-  /**
-   * Constructor.
-   *
-   * @param $type
-   *   Type of the search index entries corresponding to the field.
-   * @param $label
-   *   Label of the field.
-   * @param $category
-   *   Active category of the field.
-   */
-  function field_keyword_filter($type, $label, $category = NULL) {
-    parent::faceted_search_keyword_filter($type, $label, $category);
-  }
-
-  /**
-   * Return the search text corresponding to this filter.
-   */
-  function get_text() {
-    if ($category = $this->get_active_category()) {
-      // Quote and escape the value.
-      return '"'. faceted_search_quoted_query_escape(parent::get_text()) .'"';
-    }
-    return '';
-  }
-
 }
\ No newline at end of file
Index: taxonomy_facets.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/faceted_search/taxonomy_facets.module,v
retrieving revision 1.38
diff -u -p -r1.38 taxonomy_facets.module
--- taxonomy_facets.module	3 Mar 2009 04:55:34 -0000	1.38
+++ taxonomy_facets.module	3 Aug 2009 11:51:07 -0000
@@ -6,8 +6,6 @@
  * Provides facets based on taxonomy (categories).
  */
 
-require_once('./'. drupal_get_path('module', 'faceted_search') .'/faceted_search.inc');
-
 /**
  * Implementation of hook_cron().
  */
@@ -127,6 +125,7 @@ function taxonomy_facets_taxonomy($op, $
  * Implementation of hook_faceted_search_collect().
  */
 function taxonomy_facets_faceted_search_collect(&$facets, $domain, $env, $selection, $arg = NULL) {
+  faceted_search_require('search_engine', $env);
   switch ($domain) {
     case 'facets':
       $vocabularies = taxonomy_get_vocabularies();
@@ -134,7 +133,8 @@ function taxonomy_facets_faceted_search_
         _taxonomy_facets_localize_vocabulary($vocabulary);
         // If the vocabulary's corresponding facet is allowed.
         if (!isset($selection) || isset($selection['taxonomy'][$vocabulary->vid])) {
-          $facets[] = new taxonomy_facet($vocabulary);
+          $class_name = $env->env_id == 0 ? 'taxonomy_facet' : 'taxonomy_'. $env->engine .'_facet';
+          $facets[] = new $class_name($vocabulary);
         }
       }
       break;
@@ -171,17 +171,20 @@ function taxonomy_facets_faceted_search_
             // Add category to current path.
             if ($vocabularies[$term->vid]->hierarchy) {
               // TODO: Fix potential problem if parents of the first tid have been omitted from $tids
-              $path[] = new taxonomy_facet_hierarchical_category($term->tid, $term->name);
+              $class_name = $env->env_id == 0 ? 'taxonomy_facet_hierarchical_category' : 'taxonomy_'. $env->engine .'_facet_hierarchical_category';
+              $path[] = new $class_name($term->tid, $term->name);
             }
             else {
-              $path[] = new taxonomy_facet_category($term->tid, $term->name);
+              $class_name = $env->env_id == 0 ? 'taxonomy_facet_category' : 'taxonomy_'. $env->engine .'_facet_category';
+              $path[] = new $class_name($term->tid, $term->name);
             }
             $previous_tid = $tid;
           }
           // If found some categories in the current path of tids, build a facet
           if (count($path) > 0) {
             _taxonomy_facets_localize_vocabulary($vocabularies[$term->vid]);
-            $facets[] = new taxonomy_facet($vocabularies[$term->vid], $path);
+            $class_name = $env->env_id == 0 ? 'taxonomy_facet' : 'taxonomy_'. $env->engine .'_facet';
+            $facets[] = new $class_name($vocabularies[$term->vid], $path);
           }
         }
 
@@ -202,10 +205,12 @@ function taxonomy_facets_faceted_search_
             while ($term) {
               _taxonomy_facets_localize_term($term);
               if ($vocabularies[$vid]->hierarchy) {
-                $category = new taxonomy_facet_hierarchical_category($term->tid, $term->name);
+                $class_name = $env->env_id == 0 ? 'taxonomy_facet_hierarchical_category' : 'taxonomy_'. $env->engine .'_facet_hierarchical_category';           
+                $category = new $class_name($term->tid, $term->name);
               }
               else {
-                $category = new taxonomy_facet_category($term->tid, $term->name);
+                $class_name = $env->env_id == 0 ? 'taxonomy_facet_category' : 'taxonomy_'. $env->engine .'_facet_category';
+                $category = new $class_name($term->tid, $term->name);
               }
               array_unshift($path, $category);
               $parents = taxonomy_get_parents($term->tid);
@@ -214,7 +219,8 @@ function taxonomy_facets_faceted_search_
             if ($path) {
               _taxonomy_facets_localize_vocabulary($vocabularies[$vid]);
               // Create a facet with the found term as the active category.
-              $facets[] = new taxonomy_facet($vocabularies[$vid], $path);
+              $class_name = $env->env_id == 0 ? 'taxonomy_facet' : 'taxonomy_'. $env->engine .'_facet';
+              $facets[] = new $class_name($vocabularies[$vid], $path);
             }
           }
         }
@@ -277,225 +283,6 @@ function taxonomy_facets_edit_form_submi
   }
 }
 
-/**
- * A taxonomy-based facet.
- *
- * @see taxonomy_facet_category()
- */
-class taxonomy_facet extends faceted_search_facet {
-  /**
-   * The vocabulary used by this facet.
-   */
-  var $_vocabulary = NULL;
-
-  /**
-   * Constructor.
-   */
-  function taxonomy_facet($vocabulary, $active_path = array()) {
-    parent::faceted_search_facet('taxonomy', $active_path);
-    $this->_vocabulary = $vocabulary;
-    parent::set_weight($vocabulary->weight); // Assign default weight.
-  }
-
-  /**
-   * Returns the id of this facet.
-   */
-  function get_id() {
-    return $this->_vocabulary->vid;
-  }
-
-  /**
-   * Return the label of this facet. The implementor is responsible to ensure
-   * adequate security filtering.
-   */
-  function get_label() {
-    return check_plain($this->_vocabulary->name);
-  }
-
-  /**
-   * Returns the available sort options for this facet.
-   */
-  function get_sort_options() {
-    $options = parent::get_sort_options();
-    $options['term'] = t('Term'); // Term weight & name.
-    return $options;
-  }
-
-  /**
-   * Handler for the 'count' sort criteria.
-   */
-  function build_sort_query_count(&$query) {
-    $query->add_orderby('count', 'DESC');
-    $query->add_orderby('term_data.weight', 'ASC');
-    $query->add_orderby('term_data.name', 'ASC');
-  }
-
-  /**
-   * Handler for the 'term' sort criteria.
-   */
-  function build_sort_query_term(&$query) {
-    $query->add_orderby('term_data.weight', 'ASC');
-    $query->add_orderby('term_data.name', 'ASC');
-  }
-
-  /**
-   * Returns the search text for this facet, taking into account this facet's
-   * active path.
-   */
-  function get_text() {
-    return implode('.', array_map('_taxonomy_facets_get_category_tid', $this->get_active_path()));
-  }
-
-  /**
-   * Updates a query for retrieving the root categories of this facet and their
-   * associated nodes within the current search results.
-   *
-   * @param $query
-   *   The query object to update.
-   *
-   * @return
-   *   FALSE if this facet can't have root categories.
-   */
-  function build_root_categories_query(&$query) {
-    if ($this->_vocabulary->hierarchy) {
-      $query->add_table('taxonomy_facets_term_node', 'nid', 'n', 'nid', 'term_node');
-      $query->add_table('term_hierarchy', 'tid', 'term_node', 'tid');
-      $query->add_where('term_hierarchy.parent = 0');
-    }
-    else {
-      $query->add_table('term_node', 'vid', 'n', 'vid');
-    }
-    $query->add_table('term_data', 'tid', 'term_node', 'tid');
-    $query->add_field('term_data', 'tid', 'tid');
-    $query->add_field('term_data', 'name', 'name');
-    $query->add_where('term_data.vid = %d', $this->_vocabulary->vid);
-    $query->add_groupby('tid'); // Needed for counting matching nodes.
-    return TRUE;
-  }
-
-  /**
-   * This factory method creates categories given query results that include the
-   * fields selected in get_root_categories_query() or get_subcategories_query().
-   *
-   * @param $results
-   *   $results A database query result resource.
-   *
-   * @return
-   *   Array of categories.
-   */
-  function build_categories($results) {
-    $categories = array();
-    while ($result = db_fetch_object($results)) {
-      $result->vid = $this->_vocabulary->vid; // Fill missing term data.
-      _taxonomy_facets_localize_term($result);
-      if ($this->_vocabulary->hierarchy) {
-        $categories[] = new taxonomy_facet_hierarchical_category($result->tid, $result->name, $result->count);
-      }
-      else {
-        $categories[] = new taxonomy_facet_category($result->tid, $result->name, $result->count);
-      }
-    }
-    return $categories;
-  }
-}
-
-/**
- * A category for non-hierarchical taxonomy-based facets.
- *
- * @see taxonomy_facet()
- */
-class taxonomy_facet_category extends faceted_search_category {
-  var $_tid = NULL;
-  var $_name = '';
-
-  /**
-   * Constructor.
-   */
-  function taxonomy_facet_category($tid, $name, $count = NULL) {
-    parent::faceted_search_category($count);
-    $this->_tid = $tid;
-    $this->_name = $name;
-  }
-
-  /**
-   * Return the label of this category.
-   *
-   * @param $html
-   *   TRUE when HTML is allowed in the label, FALSE otherwise. Checking this
-   *   flag allows implementors to provide a rich-text label if desired, and an
-   *   alternate plain text version for cases where HTML cannot be used. The
-   *   implementor is responsible to ensure adequate security filtering.
-   */
-  function get_label($html = FALSE) {
-    return check_plain($this->_name);
-  }
-
-  /**
-   * Updates a query for selecting nodes matching this category.
-   *
-   * @param $query
-   *   The query object to update.
-   */
-  function build_results_query(&$query) {
-    // Since multiple terms might be used and cause multiple joins of
-    // taxonomy_facets_term_node, we add the tid into the table alias to ensure
-    // a unique alias.
-    $query->add_table('term_node', 'nid', 'n', 'nid', "term_node_{$this->_tid}");
-    $query->add_where("term_node_{$this->_tid}.tid = %d", $this->_tid);
-  }
-}
-
-/**
- * A category for hierarchical taxonomy-based facets.
- *
- * @see taxonomy_facet()
- */
-class taxonomy_facet_hierarchical_category extends taxonomy_facet_category {
-
-  /**
-   * Constructor.
-   */
-  function taxonomy_facet_hierarchical_category($tid, $name, $count = NULL) {
-    parent::taxonomy_facet_category($tid, $name, $count);
-  }
-
-  /**
-   * Updates a query for retrieving the subcategories of this category and their
-   * associated nodes within the current search results.
-   *
-   * This only needs to be overridden for hierarchical facets.
-   *
-   * @param $query
-   *   The query object to update.
-   *
-   * @return
-   *   FALSE if this facet can't have subcategories.
-   */
-  function build_subcategories_query(&$query) {
-    $query->add_table('taxonomy_facets_term_node', 'nid', 'n', 'nid', 'term_node');
-    $query->add_table('term_data', 'tid', 'term_node', 'tid');
-    $query->add_table('term_hierarchy', 'tid', 'term_node', 'tid');
-    $query->add_field('term_data', 'tid', 'tid');
-    $query->add_field('term_data', 'name', 'name');
-    $query->add_where('term_hierarchy.parent = %d', $this->_tid);
-    $query->add_groupby('tid'); // Needed for counting matching nodes.
-    return TRUE;
-  }
-
-  /**
-   * Updates a query for selecting nodes matching this category.
-   *
-   * @param $query
-   *   The query object to update.
-   */
-  function build_results_query(&$query) {
-    // Since multiple terms might be used and cause multiple joins of
-    // taxonomy_facets_term_node, we add the tid into the table alias to ensure
-    // a unique alias.
-    $query->add_table('taxonomy_facets_term_node', 'nid', 'n', 'nid', "term_node_{$this->_tid}");
-    $query->add_where("term_node_{$this->_tid}.tid = %d", $this->_tid);
-  }
-}
 
 // --------------------------------------------------------------------------
 // Internal stuff
Index: includes/faceted_search.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/faceted_search/faceted_search.inc,v
retrieving revision 1.40
diff -u -p -r1.40 faceted_search.inc
--- includes/faceted_search.inc	15 Apr 2008 16:46:15 -0000	1.40
+++ includes/faceted_search.inc	3 Aug 2009 11:51:09 -0000
@@ -1,369 +1,62 @@
 <?php
-// $Id: faceted_search.inc,v 1.40 2008/04/15 16:46:15 davidlesieur Exp $
+// $Id$
 
 /**
  * @file
- * Provides base classes for implementing filters and facets, and classes needed
- * by other modules.
+ * The Drupal search integration, extends search.inc
  */
 
 /**
- * The base class for filters.
- *
- * Filters actually impact results only when they have an active category (a
- * "category" is a filtering value). The filtering is delegated to the active
- * category.
+ * The keyword AND category.
  */
-class faceted_search_filter {
-  
-  /**
-   * The key identifying this class of filter. Keys are used in the form of
-   * 'key:text' tokens in the search text.
-   */
-  var $_key = '';
-
-  /**
-   * The status of this filter.
-   */
-  var $_status = FALSE;
-  
-  /**
-   * The weight of this filter, for sorting purposes.
-   */
-  var $_weight = 0;
+class faceted_search_faceted_search_keyword_and_category extends faceted_search_keyword_and_category {
 
   /**
-   * An array representing the path of categories leading to the active category
-   * of this facet. This path includes the active category itself.
-   */
-  var $_path = array();
-  
-  /**
-   * Constructor.
+   * Inject components into the query for selecting nodes matching this category.
    *
-   * @param $key
-   *   Key corresponding to this class of filter. This should be the same string
-   *   as used to construct the filter from the search text in the module's
-   *   implementation of hook_faceted_search_parse(). 
-   * @param $active_path
-   *   Array representing the path leading to the active category, including the
-   *   active category itself. Defaults to an empty array, meaning no active
-   *   category.
+   * @param $query
+   *   Query to inject the components into.
+   * @param $words
+   *   Array keyed by search index type, each element being an array of positive
+   *   words to lookup for that index type. This method should insert any words
+   *   it cares about.
+   * @param $matches
+   *   Minimum number of words that should match in query results for each index type.
+   * @param $type
+   *   Type of search index entry to be searched.
    */
-  function faceted_search_filter($key, $active_path = array()) {
-    $this->_key = $key;
-    $this->_path = $active_path;
-  }
+  function build_results_query(&$query, &$words, &$matches, $type) {
+    if (($word = $this->check_word($this->_word)) && !isset($words[$type][$word])) {
+      if (strlen($word) >= variable_get('minimum_word_size', 3)) {
+        $words[$type][$word] = $word;
+        $matches[$type]++;
+      }
+      else {
+        // Short words are only searched against the dataset.
+        $query->enable_part("{$type}_search_dataset");
+        // Ensure this type will be searched even though it has no "long" word.
+        if (!isset($words[$type])) {
+          $words[$type] = array();
+        }
+      }
 
-  /**
-   * Return TRUE if this filter offers browsable categories, or FALSE otherwise.
-   */
-  function is_browsable() {
-    return FALSE;
-  }
-  
-  /**
-   * Assign settings to this filter.
-   *
-   * @param $settings
-   *   Array of settings.
-   */
-  function set($settings) {
-    if (isset($settings['status'])) {
-      $this->_status = $settings['status'];
-    }
-    if (isset($settings['weight'])) {
-      $this->_weight = $settings['weight'];
+      // The dataset will have to be looked up as well if the query becomes more
+      // complex because of other keyword search operators.
+      $query->set_current_part("{$type}_search_dataset");
+      $query->add_where("{$type}_search_dataset.data LIKE '%% %s %%'", $word);
+      $query->set_current_part(); // Back to default part.
     }
   }
-  
-  /**
-   * Return the key for this class of filter.
-   */
-  function get_key() {
-    return $this->_key;
-  }
-
-  /**
-   * Return a help text for site administrators.
-   */
-  function get_help() {
-    return '';
-  }
-  
-  /**
-   * Return the status of this filter.
-   *
-   * @return
-   *   TRUE when the filter is enabled, FALSE otherwise.
-   */
-  function get_status() {
-    return $this->_status;
-  }
-
-  /**
-   * Change the status of this filter.
-   *
-   * @param $status
-   *   TRUE to enable the filter, FALSE to disable it.
-   */
-  function set_status($status) {
-    $this->_status = $status;
-  }
-  
-  /**
-   * Return the configured weight of this filter, for sorting purposes.
-   */
-  function get_weight() {
-    return $this->_weight;
-  }
-
-  /**
-   * Assign the weight of this filter.
-   */
-  function set_weight($weight) {
-    $this->_weight = $weight;
-  }
-
-  /**
-   * Return TRUE if this facet has an active category. If a facet is active, it
-   * normally means that it is used in the current search.
-   */
-  function is_active() {
-    return count($this->_path) > 0;
-  }
-
-  /**
-   * Return an array representing the path to the active category, including the
-   * active category itself. Return an empty array if there is no active
-   * category.
-   */
-  function get_active_path() {
-    return $this->_path;
-  }
-
-  /**
-   * Set the path of the active category, including the active category itself.
-   *
-   * @param $path
-   *   The path of the category (array of categories). Defaults to no active
-   *   path.
-   */
-  function set_active_path($path = array()) {
-    $this->_path = $path;
-  }
-
-  /**
-   * Return the active category, or NULL if there is no active category.
-   */
-  function get_active_category() {
-    return end($this->_path);
-  }
 }
 
-/**
- * Base class for facet categories.
- */
-class faceted_search_category {
-  /**
-   * The number of nodes associated to this category.
-   */
-  var $_count = NULL;
-
-  /**
-   * Constructor.
-   *
-   * @param $count
-   *   The number of nodes associated to this category within the current
-   *   search.
-   */
-  function faceted_search_category($count = NULL) {
-    $this->_count = $count;
-  }
-
-  /**
-   * Return the number of nodes associated to this category within the current
-   * search.
-   *
-   * @return The number of matching nodes, or NULL is count is unknown.
-   */
-  function get_count() {
-    return $this->_count;
-  }
-
-  /**
-   * Return weight of this category, for sorting purposes.
-   */
-  function get_weight() {
-    return 0;
-  }
-
-  /**
-   * Updates a query for retrieving the subcategories of this category and their
-   * associated nodes within the current search results. 
-   *
-   * This only needs to be overridden for hierarchical facets.
-   *
-   * @param $query
-   *   The query object to update.
-   * @return
-   *   FALSE if this facet can't have subcategories.
-   */
-  function build_subcategories_query(&$query) {
-    return FALSE;
-  }
-}
 
 /**
- * The parent class for facets.
- *
- * A facet is a filter with browsable categories.
+ * The keyword phrase category.
  */
-class faceted_search_facet extends faceted_search_filter {
-  
-  /**
-   * The current sort criteria to use for this facet. This determines how to
-   * sort the facet's categories.
-   */
-  var $_sort = 'count';
-
-  /**
-   * The maximum number of categories to show in this facet.
-   */
-  var $_max_categories = 10;
-
-  /**
-   * Constructor.
-   *
-   * @param $key
-   *   Key corresponding to this class of facet. This should be the same string
-   *   as used to construct the facet from the search text in the module's
-   *   implementation of hook_faceted_search_parse(). 
-   */
-  function faceted_search_facet($key, $active_path = array()) {
-    parent::faceted_search_filter($key, $active_path);
-  }
-
-  /**
-   * Return TRUE if this filter offers browsable categories, or FALSE otherwise.
-   *
-   * A browsable filter implies that categories retrieval and sorting methods
-   * are available.
-   */
-  function is_browsable() {
-    return TRUE;
-  }
-  
-  /**
-   * Assign settings to this facet.
-   *
-   * @param $settings
-   *   Array of settings.
-   */
-  function set($settings) {
-    parent::set($settings);
-    if (isset($settings['sort'])) {
-      $this->_sort = $settings['sort'];
-    }
-    if (isset($settings['max_categories'])) {
-      $this->_max_categories = $settings['max_categories'];
-    }
-  }
-  
-  /**
-   * Return the available sort options for this facet. Each option is a key =>
-   * label pair.
-   *
-   * Each key must have a corresponding handler method in the form
-   * 'build_sort_query_key'.
-   */
-  function get_sort_options() {
-    return array('count' => t('Count'));
-  }
-
-  /**
-   * Return the current sort criteria for this facet.
-   */
-  function get_sort() {
-    return $this->_sort;
-  }
-  
-  /**
-   * Assigns the current sort criteria for this facet.
-   */
-  function set_sort($sort) {
-    // Assign value only if a corresponding handler exists.
-    if (method_exists($this, 'build_sort_query_'. $sort)) {
-      $this->_sort = $sort;
-    }
-  }
-
-  /**
-   * Handler for the 'count' sort criteria.
-   */
-  function build_sort_query_count(&$query) {
-    $query->add_orderby('count', 'DESC');
-  }
-  
-  /**
-   * Applies the facet's current sort option to the given query.
-   */
-  function build_sort_query(&$query) {
-    $method = 'build_sort_query_'. $this->_sort;
-    if (method_exists($this, $method)) {
-      $this->$method($query);
-    }
-  }
-  
-  /**
-   * Return the configured maximum number of categories to show in this facet.
-   *
-   * @return
-   *   The maximum number of categories, or 0 for no limit.
-   */
-  function get_max_categories() {
-    return $this->_max_categories;
-  }
-
-  /**
-   * Assign the maximum number of categories to show in this facet.
-   *
-   * @param $max_categories
-   *   The maximum number of categories, or 0 for no limit.
-   */
-  function set_max_categories($max_categories) {
-    $this->_max_categories = $max_categories;
-  }
-  
-  /**
-   * Updates a query for retrieving the root categories of this filter and their
-   * associated nodes within the current search results.
-   *
-   * @param $query
-   *   The query object to update.
-   * @return
-   *   FALSE if this filter can't have root categories.
-   */
-  function build_root_categories_query() {
-    return FALSE;
-  }
+class faceted_search_faceted_search_keyword_phrase_category extends faceted_search_keyword_phrase_category {
 
   /**
-   * This factory method creates categories given query results that include the
-   * fields selected in get_root_categories_query() or get_subcategories_query().
-   *
-   * @param $results
-   *   $results A database query result resource.
-   * @return
-   *   Array of categories.
-   */
-  function build_categories($results) {
-    return array();
-  }
-  
-  /**
-   * Inject components into the query for selecting nodes matching this facet's
-   * active category.
+   * Inject components into the query for selecting nodes matching this category.
    *
    * @param $query
    *   Query to inject the components into.
@@ -372,85 +65,44 @@ class faceted_search_facet extends facet
    *   words to lookup for that index type. This method should insert any words
    *   it cares about.
    * @param $matches
-   *   Minimum number of words that should match in query results.
+   *   Minimum number of words that should match in query results for each index type.
+   * @param $type
+   *   Type of search index entry to be searched.
    */
-  function build_results_query(&$query, &$words, &$matches) {
-    // Note: Facets ignore $words and $matches.
-    if ($category = $this->get_active_category()) {
-      $category->build_results_query($query);
+  function build_results_query(&$query, &$words, &$matches, $type) {
+    $split = explode(' ', $this->_phrase);
+    foreach ($split as $word) {
+      if ($word = $this->check_word($word)) {
+        $words[$type][$word] = $word;
+      }
     }
-  }
-}
-
-/**
- * The base class of keyword categories.
- */
-class faceted_search_keyword_category {
+    if (count($split) > 0) {
+      $matches[$type]++; // A phrase counts as one match.
 
-  /**
-   * Check whether a given word is allowed for searching.
-   *
-   * @return
-   *   The allowed word, or NULL if it is not allowed.
-   */
-  function check_word($word) {
-    if (is_numeric($word)) {
-      return (int)ltrim($word, '-0');
-    }
-    elseif (drupal_strlen($word) >= variable_get('minimum_word_size', 3)) {
-      return $word;
-    }
-  }
+      if (count($split) > 1) {
+        // Real phrase. We'll have to verify it against the dataset.
+        $query->enable_part("{$type}_search_dataset");
+      }
 
-  /**
-   * Prepare a label for output.
-   */
-  function check_label($label, $html = FALSE) {
-    if (!$html) {
-      return strip_tags($label);
+      // Add phrase match conditions.
+      $query->set_current_part("{$type}_search_dataset");
+      $query->add_where("{$type}_search_dataset.data LIKE '%% %s %%'", $this->_phrase);
+      $query->set_current_part(); // Back to default part.
     }
-    return $label;
   }
 }
 
+
 /**
- * The keyword AND category.
+ * The keyword OR category.
  */
-class faceted_search_keyword_and_category extends faceted_search_keyword_category {
-  var $_word = '';
-  
-  /**
-   * Constructor.
-   *
-   * @param $phrase
-   *   String containing the word to search.
-   */
-  function faceted_search_keyword_and_category($word) {
-    $this->_word = $word;
-  }
-
-  /**
-   * Return the label for this category.
-   *
-   * @param $html
-   *   TRUE when HTML is allowed in the label, FALSE otherwise.
-   */
-  function get_label($html = FALSE) {
-    return $this->check_label(theme('faceted_search_keyword_and_label', $this->_word), $html);
-  }
+class faceted_search_faceted_search_keyword_or_category extends faceted_search_keyword_or_category {
 
   /**
    * Return the search text for this category.
    */
   function get_text() {
-    return $this->_word;
-  }
-
-  /**
-   * Return the weight of this category, for sorting purposes.
-   */
-  function get_weight() {
-    return 0;
+    return implode(' OR ', $this->_words);
   }
 
   /**
@@ -463,152 +115,7 @@ class faceted_search_keyword_and_categor
    *   words to lookup for that index type. This method should insert any words
    *   it cares about.
    * @param $matches
-   *   Minimum number of words that should match in query results.
-   * @param $type
-   *   Type of search index entry to be searched.
-   */
-  function build_results_query(&$query, &$words, &$matches, $type) {
-    if (($word = $this->check_word($this->_word)) && !isset($words[$type][$word])) {
-      $words[$type][$word] = $word;
-      $matches++;
-
-      // The dataset will have to be looked up as well if the query becomes more
-      // complex because of other keyword search operators.
-      $query->set_current_part("{$type}_search_dataset");
-      $query->add_where("{$type}_search_dataset.data LIKE '%% %s %%'", $word);
-      $query->set_current_part(); // Back to default part.
-    }
-  }
-}
-
-/**
- * The keyword phrase category.
- */
-class faceted_search_keyword_phrase_category extends faceted_search_keyword_category {
-  var $_phrase = '';
-
-  /**
-   * Constructor.
-   *
-   * @param $phrase
-   *   String containing the phrase to search.
-   */
-  function faceted_search_keyword_phrase_category($phrase) {
-    $this->_phrase = $phrase;
-  }
-
-  /**
-   * Return the label for this category.
-   *
-   * @param $html
-   *   TRUE when HTML is allowed in the label, FALSE otherwise.
-   */
-  function get_label($html = FALSE) {
-    return $this->check_label(theme('faceted_search_keyword_phrase_label', $this->_phrase), $html);
-  }
-
-  /**
-   * Return the search text for this operator.
-   */
-  function get_text() {
-    return '"'. $this->_phrase .'"';
-  }
-
-  /**
-   * Return the weight of this category, for sorting purposes.
-   */
-  function get_weight() {
-    return 1;
-  }
-
-  /**
-   * Inject components into the query for selecting nodes matching this category.
-   *
-   * @param $query
-   *   Query to inject the components into.
-   * @param $words
-   *   Array keyed by search index type, each element being an array of positive
-   *   words to lookup for that index type. This method should insert any words
-   *   it cares about.
-   * @param $matches
-   *   Minimum number of words that should match in query results.
-   * @param $type
-   *   Type of search index entry to be searched.
-   */
-  function build_results_query(&$query, &$words, &$matches, $type) {
-    $split = explode(' ', $this->_phrase);
-    foreach ($split as $word) {
-      if ($word = $this->check_word($word)) {
-        $words[$type][$word] = $word;
-      }
-    }
-    if (count($split) > 0) {
-      $matches++; // A phrase counts as one match.
-
-      if (count($split) > 1) {
-        // Real phrase. We'll have to verify it against the dataset.
-        $query->enable_part("{$type}_search_dataset");
-      }
-        
-      // Add phrase match conditions.
-      $query->set_current_part("{$type}_search_dataset");
-      $query->add_where("{$type}_search_dataset.data LIKE '%% %s %%'", $this->_phrase);
-      $query->set_current_part(); // Back to default part.
-    }
-  }
-}
-
-/**
- * The keyword OR category.
- */
-class faceted_search_keyword_or_category extends faceted_search_keyword_category {
-  var $_words = array();
-
-  /**
-   * Constructor.
-   *
-   * @param $words
-   *   Array containing the words to search.
-   */
-  function faceted_search_keyword_or_category($words) {
-    $this->_words = $words;
-  }
-  
-  /**
-   * Return the label for this category.
-   *
-   * @param $html
-   *   TRUE when HTML is allowed in the label, FALSE otherwise.
-   */
-  function get_label($html = FALSE) {
-    return $this->check_label(theme('faceted_search_keyword_or_label', $this->_words), $html);
-  }
-
-  /**
-   * Return the search text for this category.
-   */
-  function get_text() {
-    return implode(' OR ', $this->_words);
-  }
-
-  /**
-   * Return the weight of this category, for sorting purposes.
-   */
-  function get_weight() {
-    return 2;
-  }
-
-  /**
-   * Inject components into the query for selecting nodes matching this category.
-   *
-   * @param $query
-   *   Query to inject the components into.
-   * @param $words
-   *   Array keyed by search index type, each element being an array of positive
-   *   words to lookup for that index type. This method should insert any words
-   *   it cares about.
-   * @param $matches
-   *   Minimum number of words that should match in query results.
+   *   Minimum number of words that should match in query results for each index type.
    * @param $type
    *   Type of search index entry to be searched.
    */
@@ -626,7 +133,7 @@ class faceted_search_keyword_or_category
       }
     }
     if (!empty($where)) {
-      $matches++;
+      $matches[$type]++;
 
       // Matches will have to be checked against the dataset.
       $query->enable_part("{$type}_search_dataset");
@@ -638,45 +145,11 @@ class faceted_search_keyword_or_category
   }
 }
 
+
 /**
  * The keyword NOT category.
  */
-class faceted_search_keyword_not_category extends faceted_search_keyword_category {
-  var $_word = '';
-  
-  /**
-   * Constructor.
-   *
-   * @param $word
-   *   String containing the word to exclude from the search.
-   */
-  function faceted_search_keyword_not_category($word) {
-    $this->_word = $word;
-  }
-  
-  /**
-   * Return the weight of this category, for sorting purposes.
-   */
-  function get_weight() {
-    return 3;
-  }
-
-  /**
-   * Return the label for this category.
-   *
-   * @param $html
-   *   TRUE when HTML is allowed in the label, FALSE otherwise.
-   */
-  function get_label($html = FALSE) {
-    return $this->check_label(theme('faceted_search_keyword_not_label', $this->_word), $html);
-  }
-
-  /**
-   * Return the search text for this operator.
-   */
-  function get_text() {
-    return '-'. $this->_word;
-  }
+class faceted_search_faceted_search_keyword_not_category extends faceted_search_keyword_not_category {
 
   /**
    * Inject components into the query for selecting nodes matching this category.
@@ -688,7 +161,7 @@ class faceted_search_keyword_not_categor
    *   words to lookup for that index type. This method should insert any words
    *   it cares about.
    * @param $matches
-   *   Minimum number of words that should match in query results.
+   *   Minimum number of words that should match in query results for each index type.
    * @param $type
    *   Type of search index entry to be searched.
    */
@@ -698,7 +171,7 @@ class faceted_search_keyword_not_categor
       if (!isset($words[$type])) {
         $words[$type] = array();
       }
-        
+
       // Negative words are checked against the dataset.
       $query->enable_part("{$type}_search_dataset");
       $query->set_current_part("{$type}_search_dataset");
@@ -708,231 +181,17 @@ class faceted_search_keyword_not_categor
   }
 }
 
-/**
- * The filter for keyword search.
- */
-class faceted_search_keyword_filter extends faceted_search_filter {
-  var $_type = ''; // Type of search index entry this facet is interested in.
-  var $_label = ''; // Label of the field.
-
-  /**
-   * Constructor.
-   *
-   * @param $type
-   *   Type of the search index entries corresponding to the field.
-   * @param $label
-   *   Label of the field.
-   * @param $category
-   *   Active category of the field.
-   */
-  function faceted_search_keyword_filter($type, $label, $category = NULL) {
-    parent::faceted_search_filter('keyword', isset($category) ? array($category) : array());
-    $this->_type = $type;
-    $this->_label = $label;
-  }
-
-  /**
-   * Returns the id of this filter.
-   */
-  function get_id() { 
-    return $this->_type;
-  }
-
-  /**
-   * Return the search text corresponding to this filter.
-   */
-  function get_text() {
-    if ($category = $this->get_active_category()) {
-      return $category->get_text();
-    }
-    return '';
-  }
-
-  /**
-   * Return the label of this filter. This method is responsible for ensuring
-   * adequate security filtering.
-   */
-  function get_label() {
-    return check_plain($this->_label);
-  }
-  
-  /**
-   * Inject components into the query for selecting nodes matching this filter.
-   *
-   * @param $query
-   *   Query to inject the components into.
-   * @param $words
-   *   Array keyed by search index type, each element being an array of positive
-   *   words to lookup for that index type. This method should insert any words
-   *   it cares about.
-   * @param $matches
-   *   Minimum number of words that should match in query results.
-   */
-  function build_results_query(&$query, &$words, &$matches) {
-    if ($category = $this->get_active_category()) {
-      $category->build_results_query($query, $words, $matches, $this->_type);
-    }
-  }
-}
 
 /**
  * This class stores and processes data related to a search.
  */
-class faceted_search {
-  /**
-   * The environment id for this search. Each search environment has its own
-   * settings which make it possible to use multiple distinct search
-   * interfaces. It is this id that allows to select the proper settings.
-   */
-  var $_env_id = 0;
-  
-  /**
-   * The full, unprocessed search text.
-   */
-  var $_text = '';
-
-  /**
-   * The search keywords text.
-   */
-  var $_keywords = '';
-
-  /**
-   * Name of the temporary results table. While it exists, this table can be
-   * queried for various purposes, such as building the search interface.
-   */
-  var $_results_table = '';
-
-  /**
-   * Number of results in the results table. May be used only after a call to
-   * execute().
-   */
-  var $_results_count = 0;
-
-  /**
-   * Flag to indicate whether the search has been executed.
-   */
-  var $_ready = FALSE;
-  
-  /**
-   * Collection of filters currently used by this search.
-   */
-  var $_filters = array();
-
-  /**
-   * Constructor. Initialize the search data and parses the given search text.
-   *
-   * @param $env_id
-   *   Id of the environment to use for this search.
-   * @param $text
-   *   Search text.
-   */
-  function faceted_search($env_id, $text = '') {
-    $this->_env_id = $env_id;
-    $this->_text = $text;
-    $this->_results_table = 'temp_faceted_search_results_'. $env_id;
-
-    // Load settings for all enabled filters in this search environment.
-    $all_settings = faceted_search_load_filter_settings($env_id);
-    
-    // Make a selection with all enabled filters.
-    $selection = faceted_search_get_filter_selection($all_settings);
-
-    // Collect all filters relevant to this search.
-    foreach (module_implements('faceted_search_collect') as $module) {
-      $module_filters = array();
-      $hook = $module .'_faceted_search_collect';
-
-      // Parse the search text and obtain corresponding filters. Text is eaten as
-      // it gets parsed.
-      $text = $hook($module_filters, 'text', $env_id, $selection, $text);
-
-      // Disallow filters that have been collected from the search text.
-      foreach ($module_filters as $filter) {
-        unset($selection[$filter->get_key()][$filter->get_id()]);
-      }
-      
-      // Obtain remaining allowed filters.
-      if (!empty($selection)) {
-        $hook($module_filters, 'all', $env_id, $selection);
-      }
-      
-      // Merge the filters listed by the current module.
-      $this->_filters = array_merge($this->_filters, $module_filters);
-
-      if (empty($selection)) {
-        break; // No more filters allowed.
-      }
-    }
-    
-    // Assign the remaining text as keywords.
-    $this->_keywords = $text;
-
-    // After filters have been collected, any remaining text is passed to the
-    // default filters.
-    faceted_search_collect_default_filters($this->_filters, 'text', $env_id, $selection, $text);
-    
-    // Prepare filters for use, assigning them their settings are sorting them.
-    faceted_search_prepare_filters($this->_filters, $all_settings);
-  }
-
-  /**
-   * Return the environment id of this search.
-   */
-  function get_env_id() {
-    return $this->_env_id;
-  }
-  
-  /**
-   * Return the original search text of this search (i.e. the text that was
-   * passed to the constructor).
-   */
-  function get_text() {
-    return $this->_text;
-  }
-
-  /**
-   * Return the search keywords (the search text after having extracted the
-   * facets).
-   */
-  function get_keywords() {
-    return $this->_keywords;
-  }
-  
-  /**
-   * Return the filters used by this search.
-   */
-  function get_filters() {
-    return $this->_filters;
-  }
-
-  /**
-   * Return the specified filter.
-   */
-  function get_filter($index) {
-    return $this->_filters[$index];
-  }
-
-  /** 
-   * Return the index of a filter given its key and id.
-   */
-  function get_filter_by_id($key, $id) {
-    foreach ($this->_filters as $index => $filter) {
-      if ($filter->get_key() == $key && $filter->get_id() == $id) {
-        return array($index, $filter);
-      }
-    }
-  }
-
-  /**
-   * Return TRUE when the search has been executed.
-   */
-  function ready() {
-    return $this->_ready;
-  }
-  
+class faceted_search_faceted_search extends faceted_search {
+ 
   /**
    * Perform the search and store the results in a temporary table.
    *
+   * The prepare() method must have been called previously.
+   *
    * Results are retrieved in two logical "passes". However, the two passes are
    * joined together into a single query.  And in the case of most simple
    * queries the second pass is not even used.
@@ -946,29 +205,34 @@ class faceted_search {
    * checked against the search_dataset table).
    */
   function execute() {
-    if (!$this->_keywords && !$this->_filters) {
+    if (!$this->_filters) {
       return; // Nothing to search
     }
 
-    $query = new faceted_search_query;
-    $query->add_where('n.status = 1');
+    $query = new faceted_search_faceted_search_query;
+    if (!$this->settings['ignore_status'] || !user_access('administer nodes')) {
+      // Restrict the search to published nodes only.
+      $query->add_where('n.status = 1');
+    }
     $query->add_groupby('n.nid');
 
     // Apply node type filter
-    if ($types = faceted_search_types($this->_env_id)) {
+    $types = faceted_search_types($this);
+    if (!empty($types)) {
       $query->add_where("n.type IN ('". implode("','", $types) ."')");
     }
-    
+
     // Inject keyword search conditions if applicable.
     $words = array(); // Positive words to include in the query.
-    $matches = 0;
+    $matches = array();
     $word_score_expr = '';
-    foreach ($this->_filters as $filter) {
+    $word_score_arg = 0;
+    foreach ($this->_filters as $filter) { // TODO: All filters are iterated; We should avoid iterating through those that are disabled.
       $filter->build_results_query($query, $words, $matches);
     }
 
-    if ($matches > 0) {
-      $query->add_having('COUNT(*) >= %d', $matches);
+    if (count($matches) > 0) {
+      $query->add_having('COUNT(*) >= %d', max($matches));
     }
 
     // Some positive words were specified (and maybe some negatives as well).
@@ -977,10 +241,11 @@ class faceted_search {
     $words_scores = array();
     foreach ($words as $type => $type_words) {
       if (empty($type_words)) {
-        // Negative words were specified, but no positive words. Negative words
-        // are looked up in search_dataset, but since there are no positive
-        // words it is joined directly with the node table, and search_index is
-        // not needed.
+        // Negative words and/or short words were specified, but no positive
+        // "long" words. Negative words and short words are looked up in
+        // search_dataset, but since there are no positive "long" words, in this
+        // particular case it is joined directly with the node table and we can
+        // avoid joining search_index.
         $query->set_current_part("{$type}_search_dataset");
         $query->add_table('search_dataset', 'sid', 'n', 'nid', "{$type}_search_dataset");
         $query->add_where("{$type}_search_dataset.type = '%s'", $type);
@@ -989,25 +254,25 @@ class faceted_search {
       else {
         // Join the search index for the current index type.
         $query->add_table('search_index', 'sid', 'n', 'nid', "{$type}_search_index");
-        
+
         // Join the search dataset for the current index type, in case we're
         // dealing with a complex query.
         $query->set_current_part("{$type}_search_dataset");
         $query->add_table('search_dataset', array('sid', 'type'), "{$type}_search_index", array('sid', 'type'), "{$type}_search_dataset");
         $query->set_current_part(); // Back to default part.
 
-        $words_where[] = '(('. substr(str_repeat("{$type}_search_index.word = '%s' OR ", count($type_words)), 0, -4) .") AND {$type}_search_index.type = '%s')";
-        $words_args = array_merge($words_args, $type_words);
+        $words_where[] = '('. substr(str_repeat("{$type}_search_index.word = '%s' OR ", count($type_words)), 0, -4) .") AND {$type}_search_index.type = '%s'";
+        $words_args = array_merge($words_args, array_values($type_words));
         $words_args[] = $type;
-      
+
         $query->add_table('search_total', 'word', "{$type}_search_index", 'word', "{$type}_search_total");
 
         $words_scores[] = "{$type}_search_index.score * {$type}_search_total.count";
       }
     }
-    
+
     if (!empty($words_where)) {
-      array_unshift($words_args, implode(' OR ', $words_where));
+      array_unshift($words_args, implode(' AND ', $words_where));
       call_user_func_array(array(&$query, 'add_where'), $words_args);
     }
 
@@ -1015,7 +280,7 @@ class faceted_search {
       // Add word score expression to the query.
       $score = 'SUM('. implode(' + ', $words_scores) .')';
       $query->set_current_part('normalize');
-      $query->add_field(NULL, $score, 'score'); 
+      $query->add_field(NULL, $score, 'score');
       $query->set_current_part();
 
       // Perform the word score normalization query.
@@ -1024,26 +289,34 @@ class faceted_search {
       $query->disable_part('normalize');
 
       if (!$normalize) {
-        return;
+        $this->_ready = TRUE;
+        return; // Return with no results.
       }
 
-      $word_score_expr = '('. (1.0 / $normalize) .' * '. $score .')';
+      $word_score_expr = '(%f * '. $score .')';
+      $word_score_arg = 1.0 / $normalize;
     }
 
     // Add field needed for results.
-    $query->add_field('n', 'nid', 'nid'); 
+    $query->add_field('n', 'nid', 'nid');
 
     // Add scoring expression to the query.
-    $this->_add_scoring($query, $word_score_expr);
+    $this->_add_scoring($query, $word_score_expr, $word_score_arg);
 
     // Give other modules an opportunity at altering the final query (e.g. for
     // additional filtering).
     module_invoke_all('faceted_search_query_alter', $this, $query);
 
-    // Perform the search results query.
-    db_query_temporary($query->query(), $query->args(), $this->_results_table);
+    // Perform the search results query and store results in a temporary table.
+    //
+    // This is MySQL-specific. db_query_temporary() is not used because of the
+    // need to specify the primary key. The index provides a huge performance
+    // improvement.
+    //
+    // See http://drupal.org/node/109513 regarding the use of HEAP engine.
+    db_query('CREATE TEMPORARY TABLE '. $this->_results_table .' (nid int unsigned NOT NULL, PRIMARY KEY (nid)) Engine=HEAP '. $query->query(), $query->args(), $this->_results_table);
     $this->_results_count = db_result(db_query('SELECT COUNT(*) FROM '. $this->_results_table));
-    $this->_ready = TRUE; 
+    $this->_ready = TRUE;
   }
 
   /**
@@ -1053,7 +326,7 @@ class faceted_search {
    * execute() must have been called beforehand.
    *
    * @return
-   *   Array of objects with nid and score members. 
+   *   Array of objects with nid and score members.
    */
   function load_results($limit = 10) {
     $found_items = array();
@@ -1065,15 +338,6 @@ class faceted_search {
     }
     return $found_items;
   }
-  
-  /**
-   * Return the number of results for this search.
-   *
-   * execute() must have been called beforehand.
-   */
-  function get_results_count() {
-    return $this->_results_count;
-  }
 
   /**
    * Return the name of this search's (temporary) results table.
@@ -1081,7 +345,7 @@ class faceted_search {
   function get_results_table() {
     return $this->_results_table;
   }
-  
+
   /**
    * Return the categories for the given facet and count matching nodes within
    * results.
@@ -1099,11 +363,14 @@ class faceted_search {
   function load_categories($facet, $from = NULL, $max_count = NULL) {
     // Prepare the base query components to include the current search results
     // and to count nodes.
-    $query = new faceted_search_query;
+    $query = new faceted_search_faceted_search_query;
     $query->add_field(NULL, 'COUNT(DISTINCT(n.nid))', 'count');
     if (!$this->_ready) {
       // No temporary table available, search within all nodes.
-      $query->add_where('n.status = 1');
+      if (!$this->settings['ignore_status'] || !user_access('administer nodes')) {
+        // Restrict the search to published nodes only.
+        $query->add_where('n.status = 1');
+      }
 
       // There is no results table at this point, so we can't rely on the
       // results table having been filtered already. Therefore, we ask modules
@@ -1116,7 +383,7 @@ class faceted_search {
     }
     else {
       // Current search yields no results, thus no categories are possible.
-      return array(); 
+      return array();
     }
 
     // Gather the query components that will retrieve the categories.
@@ -1129,15 +396,16 @@ class faceted_search {
     if (!$has_categories) {
       return array();
     }
-    
+
     // Apply sort criteria.
     $facet->build_sort_query($query);
-    
+
     // Apply node type filter.
-    if ($types = faceted_search_types($this->_env_id)) {
+    $types = faceted_search_types($this);
+    if (!empty($types)) {
       $query->add_where("n.type IN ('". implode("','", $types) ."')");
     }
-    
+
     // Run the query and return the categories.
     if (isset($from) && isset($max_count)) {
       $results = db_query_range($query->query(), $query->args(), $from, $max_count);
@@ -1151,7 +419,7 @@ class faceted_search {
   /**
    * Add scoring expression to the search query.
    */
-  function _add_scoring(&$query, $word_score_expr = '') {
+  function _add_scoring(&$query, $word_score_expr = '', $word_score_arg = 0) {
     // Based on node_search() -- START
 
     $score_field = array();
@@ -1159,6 +427,7 @@ class faceted_search {
     if (!empty($word_score_expr) && $weight = (int)variable_get('node_rank_relevance', 5)) {
       $score_field[] = "%d * $word_score_expr";
       $score_arguments[] = $weight;
+      $score_arguments[] = $word_score_arg;
     }
     if ($weight = (int)variable_get('node_rank_recent', 5)) {
       // Exponential decay with half-life of 6 months, starting at last indexed node
@@ -1187,10 +456,11 @@ class faceted_search {
       call_user_func_array(array(&$query, 'add_field'), $score_arguments);
 
       $query->add_orderby('score', 'DESC');
-    }      
+    }
   }
 }
 
+
 /**
  * This class allows to build SQL queries piece by piece.
  *
@@ -1200,7 +470,7 @@ class faceted_search {
  * those elements can still be injected to the query object and later filtered
  * in or out depending on context.
  */
-class faceted_search_query {
+class faceted_search_faceted_search_query extends faceted_search_query {
   var $primary_table_alias = '';
   var $table_queue = array(); // Ordered array of tables aliases to join.
   var $tables = array(); // Tables to join, keyed by their alias.
@@ -1217,14 +487,14 @@ class faceted_search_query {
   // Part to which query elements will be added to.
   var $current_part = 'default';
   // Parts enabled for use in the final assembled the query.
-  var $parts = array('default' => 'default'); 
+  var $parts = array('default' => 'default');
 
   /**
    * Constructor. Specifies the primary table and field for this query.
    *
    * The primary table and field are always assigned to the default part.
    */
-  function faceted_search_query($primary_table = 'node', $primary_table_alias = 'n', $prefixing = TRUE) {
+  function faceted_search_faceted_search_query($primary_table = 'node', $primary_table_alias = 'n', $prefixing = TRUE) {
     $this->primary_table_alias = $primary_table_alias;
     $this->tables['default'][$primary_table_alias] = array(
       'table' => $primary_table,
@@ -1265,9 +535,9 @@ class faceted_search_query {
    *
    * The default part is always enabled.
    *
-   * @see query
-   * @see args
-   * @see disable_part
+   * @see query()
+   * @see args()
+   * @see disable_part()
    */
   function enable_part($part) {
     $this->parts[$part] = $part;
@@ -1278,21 +548,21 @@ class faceted_search_query {
    *
    * The default part cannot be disabled.
    *
-   * @see enable_part
+   * @see enable_part()
    */
   function disable_part($part) {
     if ($part != 'default') {
       unset($this->parts[$part]);
     }
   }
-  
+
   /**
    * Indicate whether the specified part is enabled for use in query assembling.
    */
   function is_part_enabled($part) {
     return isset($this->parts[$part]);
   }
-  
+
   /**
    * Add a table to join.
    *
@@ -1411,7 +681,7 @@ class faceted_search_query {
     $part = isset($part) ? $part : $this->current_part;
     return isset($this->fields[$part][$alias]);
   }
-  
+
   /**
    * Add a WHERE condition. When the query is later assembled, all WHERE
    * conditions are glued together with the AND operator.
@@ -1456,6 +726,10 @@ class faceted_search_query {
     $this->subqueries[$this->current_part][] = $clause;
     $args = func_get_args();
     array_shift($args); // Skip $clause.
+    if (isset($args[0]) && is_array($args[0])) {
+      // Using the "all arguments in one array" syntax.
+      $args = $args[0];
+    }
     if (count($args)) {
       if (isset($this->subqueries_args[$this->current_part])) {
         $this->subqueries_args[$this->current_part] = array_merge($this->subqueries_args[$this->current_part], $args);
@@ -1465,7 +739,7 @@ class faceted_search_query {
       }
     }
   }
-  
+
   /**
    * Add a GROUP BY clause.
    *
@@ -1524,8 +798,8 @@ class faceted_search_query {
    *
    * @return
    *   Array of argument values to pass to the query.
-   * @see query
-   * @see enable_part
+   * @see query()
+   * @see enable_part()
    */
   function args() {
     $field_args = array();
@@ -1554,8 +828,8 @@ class faceted_search_query {
    * Return the assembled SQL query (with unsubstituted arguments, if
    * any). Only query elements associated to enabled parts are used.
    *
-   * @see args
-   * @see enable_part
+   * @see args()
+   * @see enable_part()
    */
   function query() {
     // Primary field.
@@ -1589,7 +863,7 @@ class faceted_search_query {
           }
         }
       }
-      
+
       // Joins.
       if (isset($this->table_queue[$part])) {
         foreach ($this->table_queue[$part] as $table_alias) {
@@ -1607,19 +881,19 @@ class faceted_search_query {
             // There are multiple fields to use in the join condition.
             $join_condition = array();
             foreach ($table['left_field'] as $index => $table_left_field) {
-              $join_condition[] = "{$table[left_table_alias]}.{$table_left_field} = {$table_alias}.{$table[field][$index]}";
+              $join_condition[] = "{$table['left_table_alias']}.{$table_left_field} = {$table_alias}.{$table['field'][$index]}";
             }
             $join_condition = implode(' AND ', $join_condition);
           }
           else {
-            $join_condition = "{$table[left_table_alias]}.{$table[left_field]} = {$table_alias}.{$table[field]}";
+            $join_condition = "{$table['left_table_alias']}.{$table['left_field']} = {$table_alias}.{$table['field']}";
           }
 
           // Add the table join clause.
-          $joins[] = "$table[join] JOIN $wrapper_begin". $table['table'] ."$wrapper_end AS $table_alias ON $join_condition";
+          $joins[] = $table['join'] ." JOIN $wrapper_begin". $table['table'] ."$wrapper_end AS $table_alias ON $join_condition";
         }
       }
-      
+
       // Where clauses.
       if (isset($this->where[$part])) {
         $where = array_merge($where, $this->where[$part]);
@@ -1634,7 +908,7 @@ class faceted_search_query {
       if (isset($this->groupby[$part])) {
         $groupby = array_merge($groupby, $this->groupby[$part]);
       }
-        
+
       // Having clauses.
       if (isset($this->having[$part])) {
         $having = array_merge($having, $this->having[$part]);
@@ -1645,17 +919,18 @@ class faceted_search_query {
         $orderby = array_merge($orderby, $this->orderby[$part]);
       }
     }
-    
+
     $fields = implode(', ', $fields);
     $joins = count($joins) ? ' '. implode(' ', $joins) : '';
     $where = count($where) ? ' WHERE (('. implode(') AND (', $where) .'))' : '';
 
     // Where subqueries (added as $SUBQUERY$n tokens, which are replaced after the call
     // to db_rewrite_sql(). See related issue: http://drupal.org/node/151910).
+    $subqueries_tokens = '';
     if (count($subqueries)) {
       $subqueries_tokens = (empty($where) ? ' WHERE ' : ' AND ') .'$SUBQUERY$'. implode(' AND $SUBQUERY$', array_keys($subqueries));
     }
-      
+
     // Group by clauses.
     $groupby = count($groupby) ? ' GROUP BY '. implode(', ', $groupby) : '';
 
@@ -1664,7 +939,7 @@ class faceted_search_query {
 
     // Order by clauses.
     $orderby = count($orderby) ? ' ORDER BY '. implode(', ', $orderby) : '';
-    
+
     // Create the query string.
     $query = db_rewrite_sql("SELECT $fields FROM $primary$joins$where$subqueries_tokens$groupby$having$orderby");
     if (count($subqueries)) {
@@ -1675,8 +950,444 @@ class faceted_search_query {
       // Replace subquery tokens.
       $query = str_replace($search, $replace, $query);
     }
-    
+
     return $query;
   }
 }
 
+
+/**
+ * A facet for node authors.
+ */
+class author_faceted_search_facet extends author_facet {
+
+  /**
+   * Handler for the 'count' sort criteria.
+   */
+  function build_sort_query_count(&$query) {
+    $query->add_orderby('count', 'DESC');
+    $query->add_orderby('users_name', 'ASC');
+  }
+
+  /**
+   * Handler for the 'name' sort criteria.
+   */
+  function build_sort_query_name(&$query) {
+    $query->add_orderby('users_name', 'ASC');
+  }
+
+  /**
+   * Updates a query for retrieving the root categories of this facet and their
+   * associated nodes within the current search results.
+   *
+   * @param $query
+   *   The query object to update.
+   *
+   * @return
+   *   FALSE if this facet can't have root categories.
+   */
+  function build_root_categories_query(&$query) {
+    $query->add_table('users', 'uid', 'n', 'uid');
+    $query->add_field('users', 'uid');
+    $query->add_field('users', 'name');
+    $query->add_groupby('users_uid');
+    if (count($this->_excluded_roles)) {
+      $query->add_subquery('NOT EXISTS (SELECT users_roles.rid FROM {users_roles} users_roles WHERE users.uid = users_roles.uid AND users_roles.rid IN ('. implode(', ', $this->_excluded_roles) .'))');
+    }
+    return TRUE;
+  }
+
+  /**
+   * This factory method creates categories given query results that include the
+   * fields selected in get_root_categories_query() or get_subcategories_query().
+   *
+   * @param $results
+   *   $results A database query result resource.
+   *
+   * @return
+   *   Array of categories.
+   */
+  function build_categories($results) {
+    $categories = array();
+    while ($result = db_fetch_object($results)) {
+      if ($result->users_uid == 0) {
+        $result->users_name = t('Anonymous');
+      }
+      $categories[] = new author_faceted_search_facet_category($result->users_uid, $result->users_name, $result->count);
+    }
+    return $categories;
+  }
+}
+
+/**
+ * A node-type based facet category.
+ */
+class author_faceted_search_facet_category extends author_facet_category {
+ 
+  /**
+   * Updates a query for selecting nodes matching this category.
+   *
+   * @param $query
+   *   The query object to update.
+   */
+  function build_results_query(&$query) {
+    $query->add_where('n.uid = %d', $this->_uid);
+  }
+}
+
+
+/**
+ * A node-type based facet.
+ */
+class content_type_faceted_search_facet extends content_type_facet {
+
+  /**
+   * Handler for the 'count' sort criteria.
+   */
+  function build_sort_query_count(&$query) {
+    $query->add_orderby('count', 'DESC');
+    $query->add_orderby('node_type_name', 'ASC');
+  }
+
+  /**
+   * Handler for the 'type' sort criteria.
+   */
+  function build_sort_query_type(&$query) {
+    $query->add_orderby('node_type_name', 'ASC');
+  }
+
+  /**
+   * Updates a query for retrieving the root categories of this facet and their
+   * associated nodes within the current search results.
+   *
+   * @param $query
+   *   The query object to update.
+   *
+   * @return
+   *   FALSE if this facet can't have root categories.
+   */
+  function build_root_categories_query(&$query) {
+    $query->add_table('node_type', 'type', 'n', 'type');
+    $query->add_field('node_type', 'type');
+    $query->add_field('node_type', 'name');
+    if (!empty($this->_types)) {
+      $query->add_where("node_type.type IN ('". implode("', '", $this->_types) ."')");
+    }
+    $query->add_groupby('node_type_type');
+    return TRUE;
+  }
+
+  /**
+   * This factory method creates categories given query results that include the
+   * fields selected in get_root_categories_query() or get_subcategories_query().
+   *
+   * @param $results
+   *   $results A database query result resource.
+   *
+   * @return
+   *   Array of categories.
+   */
+  function build_categories($results) {
+    $categories = array();
+    while ($result = db_fetch_object($results)) {
+      $categories[] = new content_type_faceted_search_facet_category($result->node_type_type, $result->node_type_name, $result->count);
+    }
+    return $categories;
+  }
+}
+
+
+/**
+ * A node-type based facet category.
+ */
+class content_type_faceted_search_facet_category extends content_type_facet_category {
+  
+  /**
+   * Updates a query for selecting nodes matching this category.
+   *
+   * @param $query
+   *   The query object to update.
+   */
+  function build_results_query(&$query) {
+    $query->add_where("n.type = '%s'", $this->_type);
+  }
+}
+
+
+/**
+ * A facet for searching content by date of creation.
+ */
+class date_authored_faceted_search_facet extends date_authored_facet {
+
+  /**
+   * Handler for the 'count' sort criteria.
+   */
+  function build_sort_query_count(&$query) {
+    $query->add_orderby('count', 'DESC');
+    $query->add_orderby('n.created', 'ASC');
+  }
+
+  /**
+   * Handler for the 'oldest' sort criteria.
+   */
+  function build_sort_query_oldest(&$query) {
+    $query->add_orderby('n.created', 'ASC');
+  }
+
+  /**
+   * Handler for the 'latest' sort criteria.
+   */
+  function build_sort_query_latest(&$query) {
+    $query->add_orderby('n.created', 'DESC');
+  }
+
+  /**
+   * Updates a query for retrieving the root categories of this facet and their
+   * associated nodes within the current search results.
+   *
+   * @param $query
+   *   The query object to update.
+   *
+   * @return
+   *   FALSE if this facet can't have root categories.
+   */
+  function build_root_categories_query(&$query) {
+    $timezone = _date_authored_facet_get_timezone();
+    $query->add_field(NULL, "YEAR(FROM_UNIXTIME(n.created + $timezone))", 'node_created_year');
+    $query->add_groupby('node_created_year');
+    return TRUE;
+  }
+
+  /**
+   * This factory method creates categories given query results that include the
+   * fields selected in get_root_categories_query() or get_subcategories_query().
+   *
+   * @param $results
+   *   $results A database query result resource.
+   *
+   * @return
+   *   Array of categories.
+   */
+  function build_categories($results) {
+    $categories = array();
+    while ($result = db_fetch_object($results)) {
+      $categories[] = new date_authored_faceted_search_facet_category($result->node_created_year, $result->node_created_month, $result->node_created_day, $result->count);
+    }
+    return $categories;
+  }
+}
+
+/**
+ * A category for node creation date.
+ */
+class date_authored_faceted_search_facet_category extends date_authored_facet_category {
+  
+  /**
+   * Updates a query for retrieving the subcategories of this category and their
+   * associated nodes within the current search results.
+   *
+   * This only needs to be overridden for hierarchical facets.
+   *
+   * @param $query
+   *   The query object to update.
+   *
+   * @return
+   *   FALSE if this facet can't have subcategories.
+   */
+  function build_subcategories_query(&$query) {
+    $timezone = _date_authored_facet_get_timezone();
+    if (isset($this->_day)) {
+      return FALSE; // No subcategories.
+    }
+    if (isset($this->_month)) {
+      $from = sprintf("'%04d-%02d-01 00:00:00'", $this->_year, $this->_month);
+      $query->add_field(NULL, "YEAR(FROM_UNIXTIME(n.created + $timezone))", 'node_created_year');
+      $query->add_field(NULL, "MONTH(FROM_UNIXTIME(n.created + $timezone))", 'node_created_month');
+      $query->add_field(NULL, "DAY(FROM_UNIXTIME(n.created + $timezone))", 'node_created_day');
+      $query->add_where("(n.created + $timezone >= UNIX_TIMESTAMP($from))");
+      $query->add_where("(n.created + $timezone < UNIX_TIMESTAMP($from + INTERVAL 1 MONTH))");
+      $query->add_groupby('node_created_day'); // Needed for counting matching nodes.
+      return TRUE;
+    }
+    if (isset($this->_year)) {
+      $from = sprintf("'%04d-01-01 00:00:00'", $this->_year);
+      $query->add_field(NULL, "YEAR(FROM_UNIXTIME(n.created + $timezone))", 'node_created_year');
+      $query->add_field(NULL, "MONTH(FROM_UNIXTIME(n.created + $timezone))", 'node_created_month');
+      $query->add_where("(n.created + $timezone >= UNIX_TIMESTAMP($from))");
+      $query->add_where("(n.created + $timezone < UNIX_TIMESTAMP($from + INTERVAL 1 YEAR))");
+      $query->add_groupby('node_created_month'); // Needed for counting matching nodes.
+      return TRUE;
+    }
+    return FALSE; // Unreachable, unless something is wrong...
+  }
+
+  /**
+   * Updates a query for selecting nodes matching this category.
+   *
+   * @param $query
+   *   The query object to update.
+   */
+  function build_results_query(&$query) {
+    $timezone = _date_authored_facet_get_timezone();
+    if (isset($this->_day)) {
+      $from = sprintf("'%04d-%02d-%02d 00:00:00'", $this->_year, $this->_month, $this->_day);
+      $query->add_where("(n.created + $timezone >= UNIX_TIMESTAMP($from))");
+      $query->add_where("(n.created + $timezone < UNIX_TIMESTAMP($from + INTERVAL 1 DAY))");
+    }
+    elseif (isset($this->_month)) {
+      $from = sprintf("'%04d-%02d-01 00:00:00'", $this->_year, $this->_month);
+      $query->add_where("(n.created + $timezone >= UNIX_TIMESTAMP($from))");
+      $query->add_where("(n.created + $timezone < UNIX_TIMESTAMP($from + INTERVAL 1 MONTH))");
+    }
+    elseif (isset($this->_year)) {
+      $from = sprintf("'%04d-01-01 00:00:00'", $this->_year);
+      $query->add_where("(n.created + $timezone >= UNIX_TIMESTAMP($from))");
+      $query->add_where("(n.created + $timezone < UNIX_TIMESTAMP($from + INTERVAL 1 YEAR))");
+    }
+  }
+}
+
+
+/**
+ * A taxonomy-based facet.
+ *
+ * @see taxonomy_facet_category()
+ */
+class taxonomy_faceted_search_facet extends taxonomy_facet {
+  
+  /**
+   * Handler for the 'count' sort criteria.
+   */
+  function build_sort_query_count(&$query) {
+    $query->add_orderby('count', 'DESC');
+    $query->add_orderby('term_data.weight', 'ASC');
+    $query->add_orderby('term_data.name', 'ASC');
+  }
+
+  /**
+   * Handler for the 'term' sort criteria.
+   */
+  function build_sort_query_term(&$query) {
+    $query->add_orderby('term_data.weight', 'ASC');
+    $query->add_orderby('term_data.name', 'ASC');
+  }
+
+  /**
+   * Updates a query for retrieving the root categories of this facet and their
+   * associated nodes within the current search results.
+   *
+   * @param $query
+   *   The query object to update.
+   *
+   * @return
+   *   FALSE if this facet can't have root categories.
+   */
+  function build_root_categories_query(&$query) {
+    if ($this->_vocabulary->hierarchy) {
+      $query->add_table('taxonomy_facets_term_node', 'nid', 'n', 'nid', 'term_node');
+      $query->add_table('term_hierarchy', 'tid', 'term_node', 'tid');
+      $query->add_where('term_hierarchy.parent = 0');
+    }
+    else {
+      $query->add_table('term_node', 'vid', 'n', 'vid');
+    }
+    $query->add_table('term_data', 'tid', 'term_node', 'tid');
+    $query->add_field('term_data', 'tid', 'tid');
+    $query->add_field('term_data', 'name', 'name');
+    $query->add_where('term_data.vid = %d', $this->_vocabulary->vid);
+    $query->add_groupby('tid'); // Needed for counting matching nodes.
+    return TRUE;
+  }
+
+  /**
+   * This factory method creates categories given query results that include the
+   * fields selected in get_root_categories_query() or get_subcategories_query().
+   *
+   * @param $results
+   *   $results A database query result resource.
+   *
+   * @return
+   *   Array of categories.
+   */
+  function build_categories($results) {
+    $categories = array();
+    while ($result = db_fetch_object($results)) {
+      $result->vid = $this->_vocabulary->vid; // Fill missing term data.
+      _taxonomy_facets_localize_term($result);
+      if ($this->_vocabulary->hierarchy) {
+        $categories[] = new taxonomy_faceted_search_facet_hierarchical_category($result->tid, $result->name, $result->count);
+      }
+      else {
+        $categories[] = new taxonomy_faceted_search_facet_category($result->tid, $result->name, $result->count);
+      }
+    }
+    return $categories;
+  }
+}
+
+/**
+ * A category for non-hierarchical taxonomy-based facets.
+ *
+ * @see taxonomy_facet()
+ */
+class taxonomy_faceted_search_facet_category extends taxonomy_facet_category {
+
+  /**
+   * Updates a query for selecting nodes matching this category.
+   *
+   * @param $query
+   *   The query object to update.
+   */
+  function build_results_query(&$query) {
+    // Since multiple terms might be used and cause multiple joins of
+    // taxonomy_facets_term_node, we add the tid into the table alias to ensure
+    // a unique alias.
+    $query->add_table('term_node', 'nid', 'n', 'nid', "term_node_{$this->_tid}");
+    $query->add_where("term_node_{$this->_tid}.tid = %d", $this->_tid);
+  }
+}
+
+/**
+ * A category for hierarchical taxonomy-based facets.
+ *
+ * @see taxonomy_facet()
+ */
+class taxonomy_faceted_search_facet_hierarchical_category extends taxonomy_facet_hierarchical_category {
+
+  /**
+   * Updates a query for retrieving the subcategories of this category and their
+   * associated nodes within the current search results.
+   *
+   * This only needs to be overridden for hierarchical facets.
+   *
+   * @param $query
+   *   The query object to update.
+   *
+   * @return
+   *   FALSE if this facet can't have subcategories.
+   */
+  function build_subcategories_query(&$query) {
+    $query->add_table('taxonomy_facets_term_node', 'nid', 'n', 'nid', 'term_node');
+    $query->add_table('term_data', 'tid', 'term_node', 'tid');
+    $query->add_table('term_hierarchy', 'tid', 'term_node', 'tid');
+    $query->add_field('term_data', 'tid', 'tid');
+    $query->add_field('term_data', 'name', 'name');
+    $query->add_where('term_hierarchy.parent = %d', $this->_tid);
+    $query->add_groupby('tid'); // Needed for counting matching nodes.
+    return TRUE;
+  }
+
+  /**
+   * Updates a query for selecting nodes matching this category.
+   *
+   * @param $query
+   *   The query object to update.
+   */
+  function build_results_query(&$query) {
+    // Since multiple terms might be used and cause multiple joins of
+    // taxonomy_facets_term_node, we add the tid into the table alias to ensure
+    // a unique alias.
+    $query->add_table('taxonomy_facets_term_node', 'nid', 'n', 'nid', "term_node_{$this->_tid}");
+    $query->add_where("term_node_{$this->_tid}.tid = %d", $this->_tid);
+  }
+}
Index: includes/search.inc
===================================================================
RCS file: includes/search.inc
diff -N includes/search.inc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ includes/search.inc	3 Aug 2009 11:51:10 -0000
@@ -0,0 +1,1332 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Provides base classes for implementing filters and facets, and classes needed
+ * by other modules.
+ */
+
+/**
+ * The base class for filters.
+ *
+ * Filters actually impact results only when they have an active category (a
+ * "category" is a filtering value). The filtering is delegated to the active
+ * category.
+ */
+class faceted_search_filter {
+
+  /**
+   * The key identifying this class of filter. Keys are used in the form of
+   * 'key:text' tokens in the search text.
+   */
+  var $_key = '';
+
+  /**
+   * The status of this filter.
+   */
+  var $_status = FALSE;
+
+  /**
+   * The weight of this filter, for sorting purposes.
+   */
+  var $_weight = 0;
+
+  /**
+   * An array representing the path of categories leading to the active category
+   * of this facet. This path includes the active category itself.
+   */
+  var $_path = array();
+
+  /**
+   * Constructor.
+   *
+   * @param $key
+   *   Key corresponding to this class of filter. This should be the same string
+   *   as used to construct the filter from the search text in the module's
+   *   implementation of hook_faceted_search_parse().
+   * @param $active_path
+   *   Array representing the path leading to the active category, including the
+   *   active category itself. Defaults to an empty array, meaning no active
+   *   category.
+   */
+  function faceted_search_filter($key, $active_path = array()) {
+    $this->_key = $key;
+    $this->_path = $active_path;
+  }
+
+  /**
+   * Return TRUE if this filter offers browsable categories, or FALSE otherwise.
+   */
+  function is_browsable() {
+    return FALSE;
+  }
+
+  /**
+   * Assign settings to this filter.
+   *
+   * @param $settings
+   *   Array of settings.
+   */
+  function set($settings) {
+    if (isset($settings['status'])) {
+      $this->_status = $settings['status'];
+    }
+    if (isset($settings['weight'])) {
+      $this->_weight = $settings['weight'];
+    }
+  }
+
+  /**
+   * Return the key for this class of filter.
+   */
+  function get_key() {
+    return $this->_key;
+  }
+
+  /**
+   * Return a help text for site administrators.
+   */
+  function get_help() {
+    return '';
+  }
+
+  /**
+   * Return the status of this filter.
+   *
+   * @return
+   *   TRUE when the filter is enabled, FALSE otherwise.
+   */
+  function get_status() {
+    return $this->_status;
+  }
+
+  /**
+   * Change the status of this filter.
+   *
+   * @param $status
+   *   TRUE to enable the filter, FALSE to disable it.
+   */
+  function set_status($status) {
+    $this->_status = $status;
+  }
+
+  /**
+   * Return the configured weight of this filter, for sorting purposes.
+   */
+  function get_weight() {
+    return $this->_weight;
+  }
+
+  /**
+   * Assign the weight of this filter.
+   */
+  function set_weight($weight) {
+    $this->_weight = $weight;
+  }
+
+  /**
+   * Return TRUE if this facet has an active category. If a facet is active, it
+   * normally means that it is used in the current search.
+   */
+  function is_active() {
+    return count($this->_path) > 0;
+  }
+
+  /**
+   * Return an array representing the path to the active category, including the
+   * active category itself. Return an empty array if there is no active
+   * category.
+   */
+  function get_active_path() {
+    return $this->_path;
+  }
+
+  /**
+   * Set the path of the active category, including the active category itself.
+   *
+   * @param $path
+   *   The path of the category (array of categories). Defaults to no active
+   *   path.
+   */
+  function set_active_path($path = array()) {
+    $this->_path = $path;
+  }
+
+  /**
+   * Return the active category, or NULL if there is no active category.
+   */
+  function get_active_category() {
+    return end($this->_path);
+  }
+
+  /**
+   * Append keywords used by this filter into the specified array.
+   */
+  function get_keywords(&$keywords) {
+    // Does nothing by default.
+  }
+}
+
+/**
+ * Base class for facet categories.
+ */
+class faceted_search_category {
+  /**
+   * The number of nodes associated to this category.
+   */
+  var $_count = NULL;
+
+  /**
+   * Constructor.
+   *
+   * @param $count
+   *   The number of nodes associated to this category within the current
+   *   search.
+   */
+  function faceted_search_category($count = NULL) {
+    $this->_count = $count;
+  }
+
+  /**
+   * Return the number of nodes associated to this category within the current
+   * search.
+   *
+   * @return The number of matching nodes, or NULL is count is unknown.
+   */
+  function get_count() {
+    return $this->_count;
+  }
+
+  /**
+   * Return weight of this category, for sorting purposes.
+   */
+  function get_weight() {
+    return 0;
+  }
+
+  /**
+   * Updates a query for retrieving the subcategories of this category and their
+   * associated nodes within the current search results.
+   *
+   * This only needs to be overridden for hierarchical facets.
+   *
+   * @param $query
+   *   The query object to update.
+   * @return
+   *   FALSE if this facet can't have subcategories.
+   */
+  function build_subcategories_query(&$query) {
+    return FALSE;
+  }
+}
+
+/**
+ * The parent class for facets.
+ *
+ * A facet is a filter with browsable categories.
+ */
+class faceted_search_facet extends faceted_search_filter {
+
+  /**
+   * The current sort criteria to use for this facet. This determines how to
+   * sort the facet's categories.
+   */
+  var $_sort = 'count';
+
+  /**
+   * The maximum number of categories to show in this facet.
+   */
+  var $_max_categories = 10;
+
+  /**
+   * Constructor.
+   *
+   * @param $key
+   *   Key corresponding to this class of facet. This should be the same string
+   *   as used to construct the facet from the search text in the module's
+   *   implementation of hook_faceted_search_parse().
+   */
+  function faceted_search_facet($key, $active_path = array()) {
+    parent::faceted_search_filter($key, $active_path);
+  }
+
+  /**
+   * Return TRUE if this filter offers browsable categories, or FALSE otherwise.
+   *
+   * A browsable filter implies that categories retrieval and sorting methods
+   * are available.
+   */
+  function is_browsable() {
+    return TRUE;
+  }
+
+  /**
+   * Assign settings to this facet.
+   *
+   * @param $settings
+   *   Array of settings.
+   */
+  function set($settings) {
+    parent::set($settings);
+    if (isset($settings['sort'])) {
+      $this->_sort = $settings['sort'];
+    }
+    if (isset($settings['max_categories'])) {
+      $this->_max_categories = $settings['max_categories'];
+    }
+  }
+
+  /**
+   * Return the available sort options for this facet. Each option is a key =>
+   * label pair.
+   *
+   * Each key must have a corresponding handler method in the form
+   * 'build_sort_query_key'.
+   */
+  function get_sort_options() {
+    return array('count' => t('Count'));
+  }
+
+  /**
+   * Return the current sort criteria for this facet.
+   */
+  function get_sort() {
+    return $this->_sort;
+  }
+
+  /**
+   * Assigns the current sort criteria for this facet.
+   */
+  function set_sort($sort) {
+    // Assign value only if a corresponding handler exists.
+    if (method_exists($this, 'build_sort_query_'. $sort)) {
+      $this->_sort = $sort;
+    }
+  }
+
+  /**
+   * Handler for the 'count' sort criteria.
+   */
+  function build_sort_query_count(&$query) {
+    $query->add_orderby('count', 'DESC');
+  }
+
+  /**
+   * Applies the facet's current sort option to the given query.
+   */
+  function build_sort_query(&$query) {
+    $method = 'build_sort_query_'. $this->_sort;
+    if (method_exists($this, $method)) {
+      $this->$method($query);
+    }
+  }
+
+  /**
+   * Return the configured maximum number of categories to show in this facet.
+   *
+   * @return
+   *   The maximum number of categories, or 0 for no limit.
+   */
+  function get_max_categories() {
+    return $this->_max_categories;
+  }
+
+  /**
+   * Assign the maximum number of categories to show in this facet.
+   *
+   * @param $max_categories
+   *   The maximum number of categories, or 0 for no limit.
+   */
+  function set_max_categories($max_categories) {
+    $this->_max_categories = $max_categories;
+  }
+
+  /**
+   * Updates a query for retrieving the root categories of this filter and their
+   * associated nodes within the current search results.
+   *
+   * @param $query
+   *   The query object to update.
+   * @return
+   *   FALSE if this filter can't have root categories.
+   */
+  function build_root_categories_query() {
+    return FALSE;
+  }
+
+  /**
+   * This factory method creates categories given query results that include the
+   * fields selected in get_root_categories_query() or get_subcategories_query().
+   *
+   * @param $results
+   *   $results A database query result resource.
+   * @return
+   *   Array of categories.
+   */
+  function build_categories($results) {
+    return array();
+  }
+
+  /**
+   * Inject components into the query for selecting nodes matching this facet's
+   * active category.
+   *
+   * @param $query
+   *   Query to inject the components into.
+   * @param $words
+   *   Array keyed by search index type, each element being an array of positive
+   *   words to lookup for that index type. This method should insert any words
+   *   it cares about.
+   * @param $matches
+   *   Minimum number of words that should match in query results for each index type.
+   */
+  function build_results_query(&$query, &$words, &$matches) {
+    // Note: Facets ignore $words and $matches.
+    if ($category = $this->get_active_category()) {
+      $category->build_results_query($query);
+    }
+  }
+}
+
+/**
+ * The base class of keyword categories.
+ */
+class faceted_search_keyword_category {
+
+  /**
+   * Append keywords used by this category into the specified array.
+   */
+  function get_keywords(&$keywords) {
+    // Does nothing by default.
+  }
+
+  /**
+   * Check whether a given word is allowed for searching.
+   *
+   * @return
+   *   The allowed word, or NULL if it is not allowed.
+   */
+  function check_word($word) {
+    if (is_numeric($word)) {
+      return (int)ltrim($word, '-0');
+    }
+    return $word;
+  }
+
+  /**
+   * Prepare a label for output.
+   */
+  function check_label($label, $html = FALSE) {
+    if (!$html) {
+      return strip_tags($label);
+    }
+    return $label;
+  }
+}
+
+/**
+ * The keyword AND category.
+ */
+class faceted_search_keyword_and_category extends faceted_search_keyword_category {
+  var $_word = '';
+
+  /**
+   * Constructor.
+   *
+   * @param $phrase
+   *   String containing the word to search.
+   */
+  function faceted_search_keyword_and_category($word) {
+    $this->_word = $word;
+  }
+
+  /**
+   * Return the label for this category.
+   *
+   * @param $html
+   *   TRUE when HTML is allowed in the label, FALSE otherwise.
+   */
+  function get_label($html = FALSE) {
+    return $this->check_label(theme('faceted_search_keyword_and_label', $this->_word), $html);
+  }
+
+  /**
+   * Return the search text for this category.
+   */
+  function get_text() {
+    return $this->_word;
+  }
+
+  /**
+   * Append keywords used by this category into the specified array.
+   */
+  function get_keywords(&$keywords) {
+    $keywords[] = $this->_word;
+  }
+
+  /**
+   * Return the weight of this category, for sorting purposes.
+   */
+  function get_weight() {
+    return 0;
+  }
+}
+
+/**
+ * The keyword phrase category.
+ */
+class faceted_search_keyword_phrase_category extends faceted_search_keyword_category {
+  var $_phrase = '';
+
+  /**
+   * Constructor.
+   *
+   * @param $phrase
+   *   String containing the phrase to search.
+   */
+  function faceted_search_keyword_phrase_category($phrase) {
+    $this->_phrase = $phrase;
+  }
+
+  /**
+   * Return the label for this category.
+   *
+   * @param $html
+   *   TRUE when HTML is allowed in the label, FALSE otherwise.
+   */
+  function get_label($html = FALSE) {
+    return $this->check_label(theme('faceted_search_keyword_phrase_label', $this->_phrase), $html);
+  }
+
+  /**
+   * Return the search text for this operator.
+   */
+  function get_text() {
+    return '"'. $this->_phrase .'"';
+  }
+
+  /**
+   * Append keywords used by this category into the specified array.
+   */
+  function get_keywords(&$keywords) {
+    $keywords[] = $this->_phrase;
+  }
+
+  /**
+   * Return the weight of this category, for sorting purposes.
+   */
+  function get_weight() {
+    return 1;
+  }
+}
+
+/**
+ * The keyword OR category.
+ */
+class faceted_search_keyword_or_category extends faceted_search_keyword_category {
+  var $_words = array();
+
+  /**
+   * Constructor.
+   *
+   * @param $words
+   *   Array containing the words to search.
+   */
+  function faceted_search_keyword_or_category($words) {
+    $this->_words = $words;
+  }
+
+  /**
+   * Return the label for this category.
+   *
+   * @param $html
+   *   TRUE when HTML is allowed in the label, FALSE otherwise.
+   */
+  function get_label($html = FALSE) {
+    return $this->check_label(theme('faceted_search_keyword_or_label', $this->_words), $html);
+  }
+
+  /**
+   * Append keywords used by this category into the specified array.
+   */
+  function get_keywords(&$keywords) {
+    $keywords = array_merge($keywords, $this->_words);
+  }
+
+  /**
+   * Return the weight of this category, for sorting purposes.
+   */
+  function get_weight() {
+    return 2;
+  }
+}
+
+/**
+ * The keyword NOT category.
+ */
+class faceted_search_keyword_not_category extends faceted_search_keyword_category {
+  var $_word = '';
+
+  /**
+   * Constructor.
+   *
+   * @param $word
+   *   String containing the word to exclude from the search.
+   */
+  function faceted_search_keyword_not_category($word) {
+    $this->_word = $word;
+  }
+
+  /**
+   * Return the label for this category.
+   *
+   * @param $html
+   *   TRUE when HTML is allowed in the label, FALSE otherwise.
+   */
+  function get_label($html = FALSE) {
+    return $this->check_label(theme('faceted_search_keyword_not_label', $this->_word), $html);
+  }
+
+  /**
+   * Return the search text for this operator.
+   */
+  function get_text() {
+    return '-'. $this->_word;
+  }
+
+  /**
+   * Return the weight of this category, for sorting purposes.
+   */
+  function get_weight() {
+    return 3;
+  }
+}
+
+/**
+ * The filter for keyword search.
+ *
+ * Note: For keyword filters, the key corresponds to the type of search index
+ * entry, and the id is always 'keyword'.
+ */
+class faceted_search_keyword_filter extends faceted_search_filter {
+  var $_label = ''; // Label of the field.
+
+  /**
+   * Constructor.
+   *
+   * @param $type
+   *   Type of the search index entries corresponding to the field.
+   * @param $label
+   *   Label of the field.
+   * @param $category
+   *   Active category of the field.
+   */
+  function faceted_search_keyword_filter($type, $label, $category = NULL) {
+    parent::faceted_search_filter($type, isset($category) ? array($category) : array());
+    $this->_label = $label;
+  }
+
+  /**
+   * Returns the id of this filter.
+   */
+  function get_id() {
+    return 'keyword';
+  }
+
+  /**
+   * Return the search text corresponding to this filter.
+   */
+  function get_text() {
+    if ($category = $this->get_active_category()) {
+      return $category->get_text();
+    }
+    return '';
+  }
+
+  /**
+   * Return the label of this filter. This method is responsible for ensuring
+   * adequate security filtering.
+   */
+  function get_label() {
+    return check_plain($this->_label);
+  }
+
+  /**
+   * Append keywords used by this filter into the specified array.
+   */
+  function get_keywords(&$keywords) {
+    if ($category = $this->get_active_category()) {
+      $category->get_keywords($keywords);
+    }
+  }
+
+  /**
+   * Inject components into the query for selecting nodes matching this filter.
+   *
+   * @param $query
+   *   Query to inject the components into.
+   * @param $words
+   *   Array keyed by search index type, each element being an array of positive
+   *   words to lookup for that index type. This method should insert any words
+   *   it cares about.
+   * @param $matches
+   *   Minimum number of words that should match in query results for each index type.
+   */
+  function build_results_query(&$query, &$words, &$matches) {
+    if ($category = $this->get_active_category()) {
+      $category->build_results_query($query, $words, $matches, $this->get_key());
+    }
+  }
+}
+
+/**
+ * This class stores and processes data related to a search.
+ */
+class faceted_search {
+  // TODO: Remove the '_' prefix from data members. These are not so convenient
+  // for working with the schema.
+  
+  /**
+   * The environment id for this search. Each search environment has its own
+   * settings which make it possible to use multiple distinct search
+   * interfaces. It is this id that allows to select the proper settings.
+   */
+  var $env_id = 0;
+
+  /**
+   * The full, unprocessed search text.
+   */
+  var $_text = '';
+
+  /**
+   * An array with all keywords found in the search text.
+   */
+  var $_keywords = array();
+
+  /**
+   * Name of the temporary results table. While it exists, this table can be
+   * queried for various purposes, such as building the search interface.
+   */
+  var $_results_table = '';
+
+  /**
+   * Number of results in the results table. May be used only after a call to
+   * execute().
+   */
+  var $_results_count = 0;
+
+  /**
+   * Flag to indicate whether the search has been executed.
+   */
+  var $_ready = FALSE;
+
+  /**
+   * Collection of filters currently used by this search.
+   */
+  var $_filters = array();
+
+  /**
+   * Constructor. Initialize the search environment.
+   *
+   * @param $record
+   *   Optional for this environment, as fetched from the database. Defaults to
+   *   NULL (for new environment).
+   */
+  function faceted_search($record = NULL) {
+    // Assign default settings, ensuring that all "blanks" are properly filled.
+    $this->init();
+
+    if (isset($record)) {
+      $this->init_from_record($record);
+    }
+  }
+
+  /**
+   * Initialize this search environment with default settings.
+   */
+  function init() {
+    $this->name = '';
+    $this->description = '';
+    $this->settings['title'] = t('Search');
+    $this->settings['ignore_status'] = FALSE;
+    $this->settings['types'] = array();
+
+    // Provide other modules an opportunity to add their own default settings.
+    module_invoke_all('faceted_search_init', $this);
+  }
+
+  /**
+   * Assign this search environment's settings from a record fetched from the
+   * database. Existing settings will be overwritten only if they are present in
+   * the record.
+   *
+   * @param $record
+   *   Optional for this environment, as fetched from the database.
+   */
+  function init_from_record($record) {
+    if (isset($record->settings)) {
+      // The schema has this field serialized.
+      $settings = unserialize($record->settings);
+
+      if (is_array($settings)) {
+        // Load the settings from the record while preserving any default
+        // settings that are not present in the record.
+        $this->settings = $settings + $this->settings;
+      }
+        
+      unset($record->settings);
+    }
+      
+    // Load the remaining data from the record.
+    foreach ($record as $key => $value) {
+      $this->$key = $value;
+    }
+  }
+  
+  /**
+   * Return the original search text of this search (i.e. the text that was
+   * passed to the constructor).
+   */
+  function get_text() {
+    return $this->_text;
+  }
+
+  /**
+   * Return an array with keywords used in the search.
+   */
+  function get_keywords() {
+    return $this->_keywords;
+  }
+
+  /**
+   * Return the filters used by this search.
+   */
+  function get_filters() {
+    return $this->_filters;
+  }
+
+  /**
+   * Return the specified filter.
+   */
+  function get_filter($index) {
+    return $this->_filters[$index];
+  }
+
+  /**
+   * Return the index of a filter given its key and id.
+   */
+  function get_filter_by_id($key, $id) {
+    foreach ($this->_filters as $index => $filter) {
+      if ($filter->get_key() == $key && $filter->get_id() == $id) {
+        return array($index, $filter);
+      }
+    }
+  }
+
+  /**
+   * Prepare the complete search environment (with its filters), parsing the
+   * given search text. Requires that an env_id has been assigned previously.
+   *
+   * @param $text
+   *   Optional search text. Defaults to the empty string.
+   * @return
+   *   TRUE is the search environment could be successfully built.
+   */
+  function prepare($text = '') {
+    if (!$this->env_id) {
+      return FALSE;
+    }
+    
+    $this->_text = $text;
+    $this->_results_table = 'temp_faceted_search_results_'. $this->env_id;
+    
+    // Load settings for all enabled filters in this search environment.
+    $all_filter_settings = faceted_search_load_filter_settings($this);
+
+    // Make a selection with all enabled filters.
+    $selection = faceted_search_get_filter_selection($all_filter_settings);
+
+    // Collect all filters relevant to this search.
+    foreach (module_implements('faceted_search_collect') as $module) {
+      $module_filters = array();
+      $hook = $module .'_faceted_search_collect';
+
+      // Parse the search text and obtain corresponding filters. Text is eaten as
+      // it gets parsed.
+      $text = $hook($module_filters, 'text', $this, $selection, $text);
+
+      // Disallow filters that already have been collected from the search text.
+      foreach ($module_filters as $filter) {
+        unset($selection[$filter->get_key()][$filter->get_id()]);
+      }
+
+      // Collect any remaining allowed facets.
+      if (!empty($selection)) {
+        $hook($module_filters, 'facets', $this, $selection);
+      }
+
+      // Merge the filters listed by the current module.
+      $this->_filters = array_merge($this->_filters, $module_filters);
+
+      if (empty($selection)) {
+        break; // No more filters allowed.
+      }
+    }
+
+    // After filters have been collected, any remaining text is passed to the
+    // node filters.
+    faceted_search_collect_node_keyword_filters($this->_filters, 'text', $this, $text);
+
+    // Prepare filters for use, assigning them their settings are sorting them.
+    faceted_search_prepare_filters($this->_filters, $all_filter_settings);
+
+    // Assign the keywords found.
+    foreach ($this->_filters as $filter) {
+      $filter->get_keywords($this->_keywords);
+    }
+
+    return TRUE;
+  }
+
+  /**
+   * Return TRUE when the search has been executed.
+   */
+  function ready() {
+    return $this->_ready;
+  }
+
+  /**
+   * Return the number of results for this search.
+   *
+   * execute() must have been called beforehand.
+   */
+  function get_results_count() {
+    return $this->_results_count;
+  }
+}
+
+/**
+ * Search query base class
+ */
+class faceted_search_query {
+}
+
+
+/**
+ * A facet for node authors.
+ */
+class author_facet extends faceted_search_facet {
+
+  var $_excluded_roles;
+
+  /**
+   * Constructor. Optionally assigns the active user of the facet.
+   */
+  function author_facet($excluded_roles = array(), $uid = 0, $name = '') {
+    $active_path = array();
+    if (is_numeric($uid) && $name) {
+      $active_path[] = new author_facet_category($uid, $name);
+    }
+    parent::faceted_search_facet('author', $active_path);
+    $this->_excluded_roles = $excluded_roles;
+  }
+
+  function get_id() {
+    return 1; // This module provides only one facet.
+  }
+
+  function get_label() {
+    return t('Author');
+  }
+
+  /**
+   * Returns the available sort options for this facet.
+   */
+  function get_sort_options() {
+    $options = parent::get_sort_options();
+    $options['name'] = t('Name');
+    return $options;
+  }
+
+  /**
+   * Returns the search text for this facet, taking into account this facet's
+   * active path.
+   */
+  function get_text() {
+    if ($category = $this->get_active_category()) {
+      return $category->_uid;
+    }
+    return '';
+  }
+}
+
+/**
+ * A node-type based facet category.
+ */
+class author_facet_category extends faceted_search_category {
+  var $_uid = 0;
+  var $_name = '';
+
+  function author_facet_category($uid, $name, $count = NULL) {
+    parent::faceted_search_category($count);
+    $this->_uid = $uid;
+    $this->_name = $name;
+  }
+
+  /**
+   * Return the label of this category.
+   *
+   * @param $html
+   *   TRUE when HTML is allowed in the label, FALSE otherwise. Checking this
+   *   flag allows implementors to provide a rich-text label if desired, and an
+   *   alternate plain text version for cases where HTML cannot be used. The
+   *   implementor is responsible to ensure adequate security filtering.
+   */
+  function get_label($html = FALSE) {
+    return check_plain($this->_name);
+  }
+}
+
+
+/**
+ * A node-type based facet.
+ */
+class content_type_facet extends faceted_search_facet {
+
+  var $_types = array();
+
+  /**
+   * Constructor. Optionally assigns the active type of the facet.
+   *
+   * @param $types
+   *   Array of possible type names for this facet, or empty array if all types
+   *   are allowed.
+   * @param $type
+   *   Optional. Type to set as this facet's active category.
+   */
+  function content_type_facet($types, $type = NULL) {
+    $active_path = array();
+    if ($type) {
+      $env_id = current(faceted_search_get_env_ids());
+      faceted_search_require('search_engine', NULL, FALSE, $env_id);
+      $engine = faceted_search_get_engine($env_id);
+      $class_name = 'content_type_'. $engine .'_facet_category';
+
+      $active_path[] = new $class_name($type, node_get_types('name', $type));
+    }
+    parent::faceted_search_facet('content_type', $active_path);
+    $this->_types = $types;
+  }
+
+  function get_id() {
+    return 1; // This module provides only one facet
+  }
+
+  function get_label() {
+    return t('Content type');
+  }
+
+  /**
+   * Returns the available sort options for this facet.
+   */
+  function get_sort_options() {
+    $options = parent::get_sort_options();
+    $options['type'] = t('Type');
+    return $options;
+  }
+
+  /**
+   * Return the search text for this facet, taking into account this facet's
+   * active path.
+   */
+  function get_text() {
+    if ($category = $this->get_active_category()) {
+      return $category->_type;
+    }
+    return '';
+  }
+}
+
+/**
+ * A node-type based facet category.
+ */
+class content_type_facet_category extends faceted_search_category {
+  var $_type = NULL;
+  var $_name = '';
+
+  /**
+   * Constructor.
+   */
+  function content_type_facet_category($type, $name, $count = NULL) {
+    parent::faceted_search_category($count);
+    $this->_type = $type;
+    $this->_name = $name;
+    faceted_search_localize_type($this->_type, $this->_name);
+  }
+
+  /**
+   * Return the label of this category.
+   *
+   * @param $html
+   *   TRUE when HTML is allowed in the label, FALSE otherwise. Checking this
+   *   flag allows implementors to provide a rich-text label if desired, and an
+   *   alternate plain text version for cases where HTML cannot be used. The
+   *   implementor is responsible to ensure adequate security filtering.
+   */
+  function get_label($html = FALSE) {
+    return check_plain($this->_name);
+  }
+}
+
+
+/**
+ * A facet for searching content by date of creation.
+ */
+class date_authored_facet extends faceted_search_facet {
+
+  /**
+   * Constructor.
+   */
+  function date_authored_facet($active_path = array()) {
+    parent::faceted_search_facet('date_authored', $active_path);
+  }
+
+  function get_id() {
+    return 1; // This module provides only one facet.
+  }
+
+  function get_label() {
+    return t('Date authored');
+  }
+
+  /**
+   * Returns the available sort options for this facet.
+   */
+  function get_sort_options() {
+    $options = parent::get_sort_options();
+    $options['oldest'] = t('Oldest first');
+    $options['latest'] = t('Latest first');
+    return $options;
+  }
+
+  /**
+   * Returns the search text for this facet, taking into account this facet's
+   * active path.
+   */
+  function get_text() {
+    if ($category = $this->get_active_category()) {
+      return $category->get_text();
+    }
+    return '';
+  }
+}
+
+/**
+ * A category for node creation date.
+ */
+class date_authored_facet_category extends faceted_search_category {
+  var $_year = NULL;
+  var $_month = NULL;
+  var $_day = NULL;
+
+  /**
+   * Constructs a category for the specified date.
+   *
+   * @param $year
+   *   Year corresponding to this category.
+   *
+   * @param $month
+   *   Month corresponding to this category. Optional, but must be specified if
+   *   $day is specified.
+   *
+   * @param $day
+   *   Day corresponding to this category. Optional.
+   *
+   * @param $count
+   *   The number of nodes associated to this category within the current
+   *   search. Optional.
+   *
+   * Note: We consider the specified date as within the user's timezone.
+   */
+  function date_authored_facet_category($year, $month = NULL, $day = NULL, $count = NULL) {
+    parent::faceted_search_category($count);
+    $this->_year = $year;
+    $this->_month = $month;
+    $this->_day = $day;
+  }
+
+  /**
+   * Return the label of this category.
+   *
+   * @param $html
+   *   TRUE when HTML is allowed in the label, FALSE otherwise. Checking this
+   *   flag allows implementors to provide a rich-text label if desired, and an
+   *   alternate plain text version for cases where HTML cannot be used. The
+   *   implementor is responsible to ensure adequate security filtering.
+   */
+  function get_label($html = FALSE) {
+    if (isset($this->_day)) {
+      // Format date with YYYY-MM-DD.
+      $timestamp = gmmktime(0, 0, 0, $this->_month, $this->_day, $this->_year);
+      $format = variable_get('date_facets_format_ymd', 'm/d/Y');
+    }
+    elseif (isset($this->_month)) {
+      // Format date with YYYY-MM.
+      $timestamp = gmmktime(0, 0, 0, $this->_month, 1, $this->_year);
+      $format = variable_get('date_facets_format_ym', 'm/Y');
+    }
+    elseif (isset($this->_year)) {
+      // Format date with YYYY.
+      $timestamp = gmmktime(0, 0, 0, 1, 1, $this->_year);
+      $format = variable_get('date_facets_format_y', 'Y');
+    }
+    if ($timestamp) {
+      return format_date($timestamp, 'custom', $format, 0);
+    }
+  }
+
+  function get_text() {
+    $text = sprintf('%04d', $this->_year);
+    if (isset($this->_month)) {
+      $text .= sprintf('-%02d', $this->_month);
+      if (isset($this->_day)) {
+        $text .= sprintf('-%02d', $this->_day);
+      }
+    }
+    return $text;
+  }
+}
+
+
+/**
+ * A filter for restricting a keyword search to a specific field.
+ */
+class field_keyword_filter extends faceted_search_keyword_filter {
+  /**
+   * Constructor.
+   *
+   * @param $type
+   *   Type of the search index entries corresponding to the field.
+   * @param $label
+   *   Label of the field.
+   * @param $category
+   *   Active category of the field.
+   */
+  function field_keyword_filter($type, $label, $category = NULL) {
+    parent::faceted_search_keyword_filter($type, $label, $category);
+  }
+
+  /**
+   * Return the search text corresponding to this filter.
+   */
+  function get_text() {
+    if ($category = $this->get_active_category()) {
+      // Quote and escape the value.
+      return '"'. faceted_search_quoted_query_escape(parent::get_text()) .'"';
+    }
+    return '';
+  }
+
+}
+
+
+/**
+ * A taxonomy-based facet.
+ *
+ * @see taxonomy_facet_category()
+ */
+class taxonomy_facet extends faceted_search_facet {
+  /**
+   * The vocabulary used by this facet.
+   */
+  var $_vocabulary = NULL;
+
+  /**
+   * Constructor.
+   */
+  function taxonomy_facet($vocabulary, $active_path = array()) {
+    parent::faceted_search_facet('taxonomy', $active_path);
+    $this->_vocabulary = $vocabulary;
+    parent::set_weight($vocabulary->weight); // Assign default weight.
+  }
+
+  /**
+   * Returns the id of this facet.
+   */
+  function get_id() {
+    return $this->_vocabulary->vid;
+  }
+
+  /**
+   * Return the label of this facet. The implementor is responsible to ensure
+   * adequate security filtering.
+   */
+  function get_label() {
+    return check_plain($this->_vocabulary->name);
+  }
+
+  /**
+   * Returns the available sort options for this facet.
+   */
+  function get_sort_options() {
+    $options = parent::get_sort_options();
+    $options['term'] = t('Term'); // Term weight & name.
+    return $options;
+  }
+
+  /**
+   * Returns the search text for this facet, taking into account this facet's
+   * active path.
+   */
+  function get_text() {
+    return implode('.', array_map('_taxonomy_facets_get_category_tid', $this->get_active_path()));
+  }
+}
+
+/**
+ * A category for non-hierarchical taxonomy-based facets.
+ *
+ * @see taxonomy_facet()
+ */
+class taxonomy_facet_category extends faceted_search_category {
+  var $_tid = NULL;
+  var $_name = '';
+
+  /**
+   * Constructor.
+   */
+  function taxonomy_facet_category($tid, $name, $count = NULL) {
+    parent::faceted_search_category($count);
+    $this->_tid = $tid;
+    $this->_name = $name;
+  }
+
+  /**
+   * Return the label of this category.
+   *
+   * @param $html
+   *   TRUE when HTML is allowed in the label, FALSE otherwise. Checking this
+   *   flag allows implementors to provide a rich-text label if desired, and an
+   *   alternate plain text version for cases where HTML cannot be used. The
+   *   implementor is responsible to ensure adequate security filtering.
+   */
+  function get_label($html = FALSE) {
+    return check_plain($this->_name);
+  }
+}
+
+/**
+ * A category for hierarchical taxonomy-based facets.
+ *
+ * @see taxonomy_facet()
+ */
+class taxonomy_facet_hierarchical_category extends taxonomy_facet_category {
+
+  /**
+   * Constructor.
+   */
+  function taxonomy_facet_hierarchical_category($tid, $name, $count = NULL) {
+    parent::taxonomy_facet_category($tid, $name, $count);
+  }
+}
