diff solrsearch.module @ 0:a2b4f67e73dc default tip

initial
author Dirk Wintergruen <dwinter@mpiwg-berlin.mpg.de>
date Mon, 08 Jun 2015 10:21:54 +0200
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/solrsearch.module	Mon Jun 08 10:21:54 2015 +0200
@@ -0,0 +1,2788 @@
+<?php
+
+/**
+ * @file
+ *   Integration with the Solr Search search application.
+ */
+
+define('solrsearch_READ_WRITE', 0);
+define('solrsearch_READ_ONLY', 1);
+define('solrsearch_API_VERSION', '3.0');
+
+/**
+ * Implements hook_init().
+ */
+function solrsearch_init() {
+  if (arg(0) == 'admin') {
+    // Add the CSS for this module
+    drupal_add_css(drupal_get_path('module', 'solrsearch') . '/solrsearch.css');
+  }
+}
+
+/**
+ * Implements hook_menu().
+ */
+function solrsearch_menu() {
+  //$items = array();
+
+  $items['solrsearch-terms-change'] =array(
+      'page callback' => 'solrsearch_term_select_field',
+      'access arguments'   => array('access content'),
+      'file' => 'solrsearch_terms.inc',
+  );
+
+  $items['solrsearch-terms'] =array(
+      'page callback' => 'solrsearch_term_list',
+      'access arguments'   => array('access content'),
+      'file' => 'solrsearch_terms.inc',
+  );
+  $items['solrsearch/node'] = array(
+      'title' => 'Search',
+      'page callback' => 'solrsearch_view',
+      'access arguments'   => array('access content'),
+      //'access callback' => 'solrsearch_is_active',
+      'type' => MENU_SUGGESTED_ITEM,
+      'file' => 'solrsearch.pages.inc',
+  );
+  $items['solrsearch/site'] = array(
+      'title' => 'Search',
+      'page callback' => 'solrsearch_view',
+      'access arguments'   => array('access content'),
+      //'access callback' => 'solrsearch_is_active',
+      'type' => MENU_SUGGESTED_ITEM,
+      'file' => 'solrsearch.pages.inc',
+  );
+  $items['solrsearch'] = array(
+      'title' => 'Search',
+      'page callback' => 'solrsearch_view',
+      'access arguments'   => array('access content'),
+      //'access callback' => 'solrsearch_is_active',
+      'type' => MENU_SUGGESTED_ITEM,
+      'file' => 'solrsearch.pages.inc',
+  );
+
+  $items['solrsearchsimple'] = array(
+      'title' => 'Simple Search',
+      'page callback' => 'solrsearch_view',
+      'access arguments'   => array('access content'),
+      //'access callback' => 'solrsearch_is_active',
+      'type' => MENU_SUGGESTED_ITEM,
+      'file' => 'solrsearch.pages.inc',
+  );
+
+
+  $items['solrsearchsave'] = array(
+      'title' => 'Search',
+      'page callback' => 'solrsearch_save',
+      'access arguments'   => array('access content'),
+      //'access callback' => 'solrsearch_is_active',
+      'type' => MENU_SUGGESTED_ITEM,
+      'file' => 'solrsearch.pages.inc',
+  );
+  $items['admin/config/search/solrsearch'] = array(
+    'title'              => 'Solr Search search',
+    'description'        => 'Administer Solr Search.',
+    'page callback'      => 'solrsearch_status_page',
+    'access arguments'   => array('administer search'),
+    'weight'             => -8,
+    'file'               => 'solrsearch.admin.inc',
+  );
+
+
+  $items['admin/config/search/solrsearch/settings'] = array(
+    'title'              => 'Settings',
+    'weight'             => 10,
+    'page callback'      => 'drupal_get_form',
+    'page arguments'     => array('solrsearch_settings'),
+    'access arguments'   => array('administer search'),
+    'file'               => 'solrsearch.admin.inc',
+    'type'               => MENU_LOCAL_TASK,
+  );
+
+  $settings_path = 'admin/config/search/solrsearch/settings/';
+
+  $items[$settings_path . '%solrsearch_environment/edit'] = array(
+    'title'              => 'Edit',
+    'page callback'      => 'drupal_get_form',
+    'page arguments'     => array('solrsearch_environment_edit_form', 5),
+    'description'        => 'Edit Solr Search search environment.',
+    'access arguments'   => array('administer search'),
+    'weight'             => 10,
+    'file'               => 'solrsearch.admin.inc',
+    'type'               => MENU_LOCAL_TASK,
+  );
+  $items[$settings_path . '%solrsearch_environment/clone'] = array(
+    'title'              => 'Solr Search search environment clone',
+    'page callback'      => 'drupal_get_form',
+    'page arguments'     => array('solrsearch_environment_clone_form', 5),
+    'access arguments'   => array('administer search'),
+    'file'               => 'solrsearch.admin.inc',
+  );
+  $items[$settings_path . '%solrsearch_environment/delete'] = array(
+    'title'              => 'Solr Search search environment delete',
+    'page callback'      => 'drupal_get_form',
+    'page arguments'     => array('solrsearch_environment_delete_form', 5),
+    'access callback'    => 'solrsearch_environment_delete_page_access',
+    'access arguments'   => array('administer search', 5),
+    'file'               => 'solrsearch.admin.inc',
+  );
+  $items[$settings_path . 'add'] = array(
+    'title'              => 'Add search environment',
+    'description'        => 'Add Solr Search environment.',
+    'page callback'      => 'drupal_get_form',
+    'page arguments'     => array('solrsearch_environment_edit_form'),
+    'access arguments'   => array('administer search'),
+    'file'               => 'solrsearch.admin.inc',
+    'type'               => MENU_LOCAL_ACTION,
+  );
+
+
+  $reports_path = 'admin/reports/solrsearch';
+  $items[$reports_path] = array(
+    'title'              => 'Solr Search search index',
+    'description'        => 'Information about the contents of the index on the server',
+    'page callback'      => 'solrsearch_index_report',
+    'access arguments'   => array('access site reports'),
+    'file'               => 'solrsearch.admin.inc',
+  );
+  $items[$reports_path . '/%solrsearch_environment'] = array(
+    'title'              => 'Solr Search search index',
+    'description'        => 'Information about the contents of the index on the server',
+    'page callback'      => 'solrsearch_index_report',
+    'page arguments'     => array(3),
+    'access arguments'   => array('access site reports'),
+    'file'               => 'solrsearch.admin.inc',
+  );
+  $items[$reports_path . '/%solrsearch_environment/index'] = array(
+    'title'              => 'Search index',
+    'file'               => 'solrsearch.admin.inc',
+    'type'               => MENU_DEFAULT_LOCAL_TASK,
+  );
+  $items[$reports_path . '/%solrsearch_environment/conf'] = array(
+    'title'              => 'Configuration files',
+    'page callback'      => 'solrsearch_config_files_overview',
+    'access arguments'   => array('access site reports'),
+    'file'               => 'solrsearch.admin.inc',
+    'weight'             => 5,
+    'type'               => MENU_LOCAL_TASK,
+  );
+  $items[$reports_path . '/%solrsearch_environment/conf/%'] = array(
+    'title'              => 'Configuration file',
+    'page callback'      => 'solrsearch_config_file',
+    'page arguments'     => array(5, 3),
+    'access arguments'   => array('access site reports'),
+    'file'               => 'solrsearch.admin.inc',
+    'type'               => MENU_CALLBACK,
+  );
+  if (module_exists('devel')) {
+    $items['node/%node/devel/solrsearch'] = array(
+      'title'              => 'Solr Search',
+      'page callback'      => 'solrsearch_devel',
+      'page arguments'     => array(1),
+      'access arguments'   => array('access devel information'),
+      'file'               => 'solrsearch.admin.inc',
+      'type'               => MENU_LOCAL_TASK,
+    );
+  }
+
+  // We handle our own menu paths for facets
+  if (module_exists('facetapi')) {
+    $file_path = drupal_get_path('module', 'facetapi');
+    $first = TRUE;
+    foreach (facetapi_get_realm_info() as $realm_name => $realm) {
+      if ($first) {
+        $first = FALSE;
+        $items[$settings_path . '%solrsearch_environment/facets'] = array(
+          'title'            => 'Facets',
+          'page callback'    => 'solrsearch_enabled_facets_page',
+          'page arguments'   =>  array($realm_name, 5),
+          'weight'           => -5,
+          'access arguments' => array('administer search'),
+          'file path'        => $file_path,
+          'file'             => 'facetapi.admin.inc',
+          'type'             => MENU_LOCAL_TASK,
+          'context'          => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
+        );
+      }
+      else {
+        $items[$settings_path . '%solrsearch_environment/facets/' . $realm_name] = array(
+          'title'            => $realm['label'],
+          'page callback'    => 'solrsearch_enabled_facets_page',
+          'page arguments'   => array($realm_name, 5),
+          'weight'           => -5,
+          'access arguments' => array('administer search'),
+          'type'             => MENU_LOCAL_TASK,
+          'context'          => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
+          'file path'        => $file_path,
+          'file'             => 'facetapi.admin.inc',
+        );
+      }
+    }
+  }
+  return $items;
+}
+
+/**
+ * Wrapper for facetapi settings forms.
+ */
+function solrsearch_enabled_facets_page($realm_name, $environment = NULL) {
+  $page = array();
+
+  if (isset($environment['env_id'])) {
+    $env_id = $environment['env_id'];
+  }
+  else {
+    $env_id = solrsearch_default_environment();
+  }
+  $searcher = 'solrsearch@' . $env_id;
+
+  // Initializes output with information about which environment's setting we are
+  // editing, as it is otherwise not transparent to the end user.
+  $page['solrsearch_environment'] = array(
+    '#theme' => 'solrsearch_settings_title',
+    '#env_id' => $env_id,
+  );
+
+  $page['settings'] = drupal_get_form('facetapi_realm_settings_form', $searcher, $realm_name);
+  return $page;
+}
+
+/**
+ * Implements hook_facetapi_searcher_info().
+ */
+function solrsearch_facetapi_searcher_info() {
+  $info = array();
+  // TODO: is it needed to return all of them here?
+  foreach (solrsearch_load_all_environments() as $id => $environment) {
+    $info['solrsearch@' . $id] = array(
+      'label' => t('Solr Search environment: @environment', array('@environment' => $environment['name'])),
+      'adapter' => 'solrsearch',
+      'instance' => $id,
+      'path' => '',
+      'supports facet mincount' => TRUE,
+      'supports facet missing' => TRUE,
+      'include default facets' => FALSE,
+    );
+  }
+  return $info;
+}
+
+/**
+ * Implements hook_facetapi_adapters().
+ */
+function solrsearch_facetapi_adapters() {
+  return array(
+    'solrsearch' => array(
+      'handler' => array(
+        'class' => 'solrsearchFacetapiAdapter',
+      ),
+    ),
+  );
+}
+
+/**
+ * Implements hook_facetapi_query_types().
+ */
+function solrsearch_facetapi_query_types() {
+  return array(
+    'solrsearch_term' => array(
+      'handler' => array(
+        'class' => 'solrsearchFacetapiTerm',
+        'adapter' => 'solrsearch',
+      ),
+    ),
+    'solrsearch_date' => array(
+      'handler' => array(
+        'class' => 'solrsearchFacetapiDate',
+        'adapter' => 'solrsearch',
+      ),
+    ),
+    'solrsearch_numeric_range' => array(
+      'handler' => array(
+        'class' => 'solrsearchFacetapiNumericRange',
+        'adapter' => 'solrsearch',
+      ),
+    ),
+    'solrsearch_integer' => array(
+          'handler' => array(
+              'class' => 'solrsearchFacetapiInteger',
+              'adapter' => 'solrsearch',
+          ),
+      ),
+    'solrsearch_geo' => array(
+      'handler' => array(
+        'class' => 'solrsearchFacetapiGeo',
+        'adapter' => 'solrsearch',
+      ),
+    ),
+  );
+}
+
+/**
+ * Implements hook_facetapi_facet_info().
+ * Currently it only supports the node entity type
+ */
+function solrsearch_facetapi_facet_info($searcher_info) {
+  $facets = array();
+  //dpm("sorlsearch_facetapi");
+  if ('solrsearch' == $searcher_info['adapter']) {
+    $environment = solrsearch_environment_load($searcher_info['instance']);
+
+    if (!empty($environment['conf']['facet callbacks'])) {
+      foreach ($environment['conf']['facet callbacks'] as $callback) {
+        if (is_callable($callback)) {
+          $facets = array_merge($facets, call_user_func($callback, $searcher_info));
+        }
+      }
+    }
+    elseif (isset($searcher_info['types']['node'])) {
+      $facets = solrsearch_default_node_facet_info();
+    }
+  }
+
+  return $facets;
+}
+
+/**
+ * Returns an array of facets for node fields and attributes.
+ *
+ * @return
+ *   An array of node facets.
+ */
+function solrsearch_default_node_facet_info() {
+  //dpm("sorlsearch_default_facetapi");
+  return array_merge(solrsearch_common_node_facets(), solrsearch_entity_field_facets('node'));
+}
+
+/**
+ * Returns an array of facets for the provided entity type's fields.
+ *
+ * @param string $entity_type
+ *   An entity type machine name.
+ * @return
+ *   An array of facets for the fields of the requested entity type.
+ */
+function solrsearch_entity_field_facets($entity_type) {
+  $facets = array();
+
+  foreach (solrsearch_entity_fields($entity_type) as $field_nm => $entity_fields) {
+    foreach ($entity_fields as $field_info) {
+      if (!empty($field_info['facets'])) {
+        $field = solrsearch_index_key($field_info);
+        $facets[$field] = array(
+          'label' => check_plain($field_info['display_name']),
+          'dependency plugins' => $field_info['dependency plugins'],
+          'field api name' => $field_info['field']['field_name'],
+          'description' => t('Filter by field of type @type.', array('@type' => $field_info['field']['type'])),
+          'map callback' => $field_info['map callback'],
+          'map options' => $field_info,
+          'hierarchy callback' => $field_info['hierarchy callback'],
+        );
+        if (!empty($field_info['facet mincount allowed'])) {
+          $facets[$field]['facet mincount allowed'] = $field_info['facet mincount allowed'];
+        }
+        if (!empty($field_info['facet missing allowed'])) {
+          $facets[$field]['facet missing allowed'] = $field_info['facet missing allowed'];
+        }
+        if (!empty($field_info['query types'])) {
+          $facets[$field]['query types'] = $field_info['query types'];
+        }
+        if (!empty($field_info['allowed operators'])) {
+          $facets[$field]['allowed operators'] = $field_info['allowed operators'];
+        }
+        // TODO : This is actually deprecated but we should still support
+        // older versions of facetapi. We should remove once facetapi has RC1
+        // For reference : http://drupal.org/node/1161444
+        if (!empty($field_info['query type'])) {
+          $facets[$field]['query type'] = $field_info['query type'];
+        }
+        if (!empty($field_info['min callback'])) {
+          $facets[$field]['min callback'] = $field_info['min callback'];
+        }
+        if (!empty($field_info['max callback'])) {
+          $facets[$field]['max callback'] = $field_info['max callback'];
+        }
+        if (!empty($field_info['map callback'])) {
+          $facets[$field]['map callback'] = $field_info['map callback'];
+        }
+        if (!empty($field_info['alter callbacks'])) {
+          $facets[$field]['alter callbacks'] = $field_info['alter callbacks'];
+        }
+      }
+    }
+  }
+
+  return $facets;
+}
+
+/**
+ * Helper function returning common facet definitions.
+ */
+function solrsearch_common_node_facets() {
+
+
+
+   $facets['author'] = array(
+    'label' => t('Author'),
+    'description' => t('Filter by author.'),
+    'field' => 'author_c',
+    'facet mincount allowed' => TRUE,
+
+  );
+
+
+  $facets['title'] = array(
+      'label' => t('Title'),
+      'description' => t('Filter by title.'),
+      'field' => 'title_s',
+      'facet mincount allowed' => TRUE,
+  );
+
+
+  $facets['doc-type'] = array(
+      'label' => t('Type of item'),
+      'description' => t('Filter by the type of item (field: doc-type).'),
+      'field' => 'doc-type',
+      'facet mincount allowed' => TRUE,
+      'map callback' => 'solrsearch_map_doc_type',
+  );
+
+  $facets['access-type'] = array(
+      'label' => t('Access restrictions'),
+      'description' => t('Filter by Access Type.'),
+      'field' => 'access-type',
+      'facet mincount allowed' => TRUE,
+  );
+
+
+  $facets['collection'] = array(
+      'label' => t('Filter by collection'),
+      'description' => t('Filter by Collection (field:collection).'),
+      'field' => 'collection',
+      'facet mincount allowed' => TRUE,
+  );
+
+ $facets['provider'] = array(
+      'label' => t('Filter by provider'),
+      'description' => t('Filter by Provider.'),
+      'field' => 'provider',
+      'facet mincount allowed' => TRUE,
+  );
+
+ $facets['data_provider'] = array(
+      'label' => t('Filter by data provider'),
+      'description' => t('Filter by Data provider .'),
+      'field' => 'data_provider',
+      'facet mincount allowed' => TRUE,
+  );
+
+
+ $facets['type'] = array(
+      'label' => t('Filter bytype'),
+      'description' => t('Filter by type .'),
+      'field' => 'type',
+      'facet mincount allowed' => TRUE,
+  );
+
+
+  $facets['year'] = array(
+    'label' => t('Year'),
+    'description' => t('Filter by the year.'),
+    'field' => 'year',
+    'query types' => array('integer'),
+    'allowed operators' => array(FACETAPI_OPERATOR_AND => TRUE),
+      'min callback' => 'solrsearch_get_min_integer',
+      'max callback' => 'solrsearch_get_max_integer',
+      'gap callback' => 'solrsearch_get_gap_integer',
+      'default sorts' => array(
+      array('active', SORT_DESC),
+      array('indexed', SORT_ASC),
+    ),
+  );
+
+
+  //dpm($facets);
+  return $facets;
+}
+
+
+/*
+ * solrsearch_map_doc_type
+ * map doc type to human readable
+ */
+
+function solrsearch_map_doc_type($facets, $options) {
+  $map = array();
+  $allowed_values = array();
+  // @see list_field_formatter_view()
+
+  $mapping = array(
+      'institutesLibrary' => 'Printed Catalog',
+      'indexMeta' => 'Digital Library',
+      'echo2_collection' => 'Collections (ECHO)',
+  );
+  foreach ($facets as $key) {
+
+    if (isset( $mapping[$key])) {
+      $map[$key]['#markup'] = $mapping[$key];
+    } else {
+      $map[$key]['#markup'] = $key;
+    }
+
+    $map[$key]['#html'] = TRUE;
+  }
+  return $map;
+}
+/**
+ * Callback that returns the gap
+ *
+ * @param $facet
+ *   An array containing the facet definition.
+ *
+ * @return
+ *
+ *
+ * @todo Cache this value.
+ */
+function solrsearch_get_gap_integer(array $facet) {
+  //TODO: should be dynamic or configurable
+  return 20;
+}
+
+/**
+ * Callback that returns the minimum integer
+ *
+ * @param $facet
+ *   An array containing the facet definition.
+ *
+ * @return
+ *
+ *
+ * @todo Cache this value.
+ */
+function solrsearch_get_min_integer(array $facet) {
+  //TODO: should be dynamic or configurable
+  return 1;
+}
+
+/**
+ * Callback that returns the maximum integer
+ *
+ * @param $facet
+ *   An array containing the facet definition.
+ *
+ * @return
+ *
+ *
+ * @todo Cache this value.
+ */
+function solrsearch_get_max_integer(array $facet) {
+  //TODO: should be dynamic or configurable
+  return 2020;
+}
+
+
+/**
+ * Determines Solr Search's behavior when searching causes an exception (e.g. Solr isn't available.)
+ * Depending on the admin settings, possibly redirect to Drupal's core search.
+ *
+ * @param $search_name
+ *   The name of the search implementation.
+ *
+ * @param $querystring
+ *   The search query that was issued at the time of failure.
+ */
+function solrsearch_failure($search_name, $querystring) {
+  $fail_rule = variable_get('solrsearch_failure', 'solrsearch:show_error');
+
+  switch ($fail_rule) {
+    case 'solrsearch:show_error':
+      // drupal_set_message(t('Search is temporarily unavailable. If the problem persists, please contact the site administrator.'), 'error');
+      break;
+    case 'solrsearch:show_no_results':
+      // Do nothing.
+      break;
+    default:
+      // If we're failing over to another module make sure the search is available.
+      if (module_exists('search')) {
+        $search_info = search_get_info();
+        if (isset($search_info[$fail_rule])) {
+          $search_info = $search_info[$fail_rule];
+          drupal_set_message(t("%search_name is not available. Your search is being redirected.", array('%search_name' => $search_name)));
+          drupal_goto('search/' . $search_info['path'] . '/' . rawurlencode($querystring));
+        }
+      }
+      // if search is not enabled, break and do nothing
+      break;
+  }
+}
+
+/**
+ * Like $site_key in _update_refresh() - returns a site-specific hash.
+ */
+function solrsearch_site_hash() {
+  if (!($hash = variable_get('solrsearch_site_hash', FALSE))) {
+    global $base_url;
+    // Set a random 6 digit base-36 number as the hash.
+    $hash = substr(base_convert(sha1(uniqid($base_url, TRUE)), 16, 36), 0, 6);
+    variable_set('solrsearch_site_hash', $hash);
+  }
+  return $hash;
+}
+
+/**
+ * Generate a unique ID for an entity being indexed.
+ *
+ * @param $id
+ *   An id number (or string) unique to this site, such as a node ID.
+ * @param $entity
+ *   A string like 'node', 'file', 'user', or some other Drupal object type.
+ *
+ * @return
+ *   A string combining the parameters with the site hash.
+ */
+function solrsearch_document_id($id, $entity_type = 'node') {
+  return solrsearch_site_hash() . "/{$entity_type}/" . $id;
+}
+
+/**
+ * Mark one entity as needing re-indexing.
+ */
+function solrsearch_mark_entity($entity_type, $entity_id) {
+  module_load_include('inc', 'solrsearch', 'solrsearch.index');
+  $table = solrsearch_get_indexer_table($entity_type);
+  if (!empty($table)) {
+    db_update($table)
+      ->condition('entity_id', $entity_id)
+      ->fields(array('changed' => REQUEST_TIME))
+      ->execute();
+  }
+}
+
+/**
+ * Implements hook_user_update().
+ *
+ * Mark nodes as needing re-indexing if the author name changes.
+ *
+ * @see http://drupal.org/node/592522
+ *   Performance issue with Mysql
+ * @see http://api.drupal.org/api/drupal/includes--database--database.inc/function/db_update/7#comment-15459
+ *   To know why PDO in drupal does not support UPDATE and JOIN at once.
+ */
+function solrsearch_user_update(&$edit, $account, $category) {
+  if (isset($account->name) && isset($account->original) && isset($account->original->name) && $account->name != $account->original->name) {
+    $table = solrsearch_get_indexer_table('node');
+    switch (db_driver()) {
+      case 'mysql' :
+        $table = db_escape_table($table);
+        $query = "UPDATE {{$table}} asn
+          INNER JOIN {node} n ON asn.entity_id = n.nid SET asn.changed = :changed
+          WHERE n.uid = :uid";
+        $result = db_query($query, array(':changed' => REQUEST_TIME,
+          ':uid' => $account->uid,
+        ));
+        break;
+      default :
+        $nids = db_select('node')
+          ->fields('node', array('nid'))
+          ->where("uid = :uid", array(':uid' => $account->uid));
+        $update = db_update($table)
+          ->condition('entity_id', $nids, 'IN')
+          ->fields(array('changed' => REQUEST_TIME))
+          ->execute();
+    }
+  }
+}
+
+/**
+ * Implements hook_term_update().
+ *
+ * Mark nodes as needing re-indexing if a term name changes.
+ *
+ * @see http://drupal.org/node/592522
+ *   Performance issue with Mysql
+ * @see http://api.drupal.org/api/drupal/includes--database--database.inc/function/db_update/7#comment-15459
+ *   To know why PDO in drupal does not support UPDATE and JOIN at once.
+ * @todo the rest, such as term deletion.
+ */
+function solrsearch_taxonomy_term_update($term) {
+  $table = solrsearch_get_indexer_table('node');
+  switch (db_driver()) {
+    case 'mysql' :
+      $table = db_escape_table($table);
+      $query = "UPDATE {{$table}} asn
+        INNER JOIN {taxonomy_index} ti ON asn.entity_id = ti.nid SET asn.changed = :changed
+        WHERE ti.tid = :tid";
+      $result = db_query($query, array(':changed' => REQUEST_TIME,
+        ':tid' => $term->tid,
+      ));
+      break;
+    default :
+      $nids = db_select('taxonomy_index')
+        ->fields('taxonomy_index', array('nid'))
+        ->where("tid = :tid", array(':tid' => $term->tid));
+      $update = db_update($table)
+        ->condition('entity_id', $nids, 'IN')
+        ->fields(array('changed' => REQUEST_TIME))
+        ->execute();
+  }
+}
+
+
+
+/**
+ * Convert date from timestamp into ISO 8601 format.
+ * http://lucene.apache.org/solr/api/org/apache/solr/schema/DateField.html
+ */
+function solrsearch_date_iso($date_timestamp) {
+  return gmdate('Y-m-d\TH:i:s\Z', $date_timestamp);
+}
+
+/**
+ * Function to flatten documents array recursively.
+ *
+ * @param array $documents
+ *   The array of documents being indexed.
+ * @param array &$tmp
+ *   A container variable that will contain the flattened array.
+ */
+function solrsearch_flatten_documents_array($documents, &$tmp) {
+  foreach ($documents AS $index => $item) {
+    if (is_array($item)) {
+      solrsearch_flatten_documents_array($item, $tmp);
+    }
+    elseif (is_object($item)) {
+      $tmp[] = $item;
+    }
+  }
+}
+
+/**
+ * Implements hook_flush_caches().
+ */
+function solrsearch_flush_caches() {
+  return array('cache_solrsearch');
+}
+
+/**
+ * A wrapper for cache_clear_all to be used as a submit handler on forms that
+ * require clearing Luke cache etc.
+ */
+function solrsearch_clear_cache($env_id) {
+  // Reset $env_id to NULL if call originates from a form submit handler.
+  if (is_array($env_id)) {
+    $env_id = NULL;
+  }
+  try {
+    $solr = solrsearch_get_solr($env_id);
+    $solr->clearCache();
+  }
+  catch (Exception $e) {
+    watchdog('Solr Search', nl2br(check_plain($e->getMessage())), NULL, WATCHDOG_ERROR);
+    drupal_set_message(nl2br(check_plain($e->getMessage())), 'warning');
+  }
+}
+
+/**
+ * Call drupal_set_message() with the text.
+ *
+ * The text is translated with t() and substituted using Solr stats.
+ * @todo This is not according to drupal code standards
+ */
+function solrsearch_set_stats_message($text, $type = 'status', $repeat = FALSE) {
+  try {
+    $solr = solrsearch_get_solr();
+    $stats_summary = $solr->getStatsSummary();
+    drupal_set_message(check_plain(t($text, $stats_summary)), $type, FALSE);
+  }
+  catch (Exception $e) {
+    watchdog('Solr Search', nl2br(check_plain($e->getMessage())), NULL, WATCHDOG_ERROR);
+  }
+}
+
+/**
+ * Returns last changed and last ID for an environment and entity type.
+ */
+function solrsearch_get_last_index_position($env_id, $entity_type) {
+  $stored = variable_get('solrsearch_index_last', array());
+  return isset($stored[$env_id][$entity_type]) ? $stored[$env_id][$entity_type] : array('last_changed' => 0, 'last_entity_id' => 0);
+}
+
+/**
+ * Sets last changed and last ID for an environment and entity type.
+ */
+function solrsearch_set_last_index_position($env_id, $entity_type, $last_changed, $last_entity_id) {
+  $stored = variable_get('solrsearch_index_last', array());
+  $stored[$env_id][$entity_type] = array('last_changed' => $last_changed, 'last_entity_id' => $last_entity_id);
+  variable_set('solrsearch_index_last', $stored);
+}
+
+/**
+ * Clear a specific environment, or clear all.
+ */
+function solrsearch_clear_last_index_position($env_id = NULL, $entity_type = NULL) {
+  $stored = variable_get('solrsearch_index_last', array());
+  if (empty($env_id)) {
+    $stored = array();
+  }
+  elseif ($entity_type) {
+    unset($stored[$env_id][$entity_type]);
+  }
+  else {
+    unset($stored[$env_id]);
+  }
+  variable_set('solrsearch_index_last', $stored);
+}
+
+/**
+ * Set the timestamp of the last index update
+ * @param $timestamp
+ *   A timestamp or zero. If zero, the variable is deleted.
+ */
+function solrsearch_set_last_index_updated($env_id, $timestamp = 0) {
+  $updated = variable_get('solrsearch_index_updated', array());
+  if ($timestamp > 0) {
+    $updated[$env_id] = $timestamp;
+  }
+  else {
+    unset($updated[$env_id]);
+  }
+  variable_set('solrsearch_index_updated', $updated);
+}
+
+/**
+ * Get the timestamp of the last index update.
+ * @return integer (timestamp)
+ */
+function solrsearch_get_last_index_updated($env_id) {
+  $updated = variable_get('solrsearch_index_updated', array());
+  return isset($updated[$env_id]) ? $updated[$env_id] : 0;
+}
+
+
+
+/**
+ * Implements hook_form_[form_id]_alter().
+ *
+ * Make sure to flush cache when content types are changed.
+ */
+function solrsearch_form_node_type_form_alter(&$form, $form_state) {
+  $form['#submit'][] = 'solrsearch_clear_cache';
+}
+
+/**
+ * Implements hook_form_[form_id]_alter(). (D7)
+ *
+ * Make sure to flush cache when fields are added.
+ */
+function solrsearch_form_field_ui_field_overview_form_alter(&$form, $form_state) {
+  $form['#submit'][] = 'solrsearch_clear_cache';
+}
+
+/**
+ * Implements hook_form_[form_id]_alter(). (D7)
+ *
+ * Make sure to flush cache when fields are updated.
+ */
+function solrsearch_form_field_ui_field_edit_form_alter(&$form, $form_state) {
+  $form['#submit'][] = 'solrsearch_clear_cache';
+}
+
+/**
+ * Sets breadcrumb trails for Facet API settings forms.
+ *
+ * @param FacetapiAdapter $adapter
+ *   The Facet API adapter object.
+ * @param array $realm
+ *   The realm definition.
+ */
+function solrsearch_set_facetapi_breadcrumb(FacetapiAdapter $adapter, array $realm) {
+  if ('solrsearch' == $adapter->getId()) {
+    // Hack here that depnds on our construction of the searcher name in this way.
+    list(, $env_id) = explode('@', $adapter->getSearcher());
+    // Appends additional breadcrumb items.
+    $breadcrumb = drupal_get_breadcrumb();
+    $breadcrumb[] = l(t('Solr Search search environment edit'), 'admin/config/search/solrsearch/settings/' . $env_id);
+    $breadcrumb[] = l($realm['label'], 'admin/config/search/solrsearch/settings/' . $env_id . '/facets/' . $realm['name']);
+    drupal_set_breadcrumb($breadcrumb);
+  }
+}
+
+/**
+ * Implements hook_form_[form_id]_alter(). (D7)
+ */
+function solrsearch_form_facetapi_facet_settings_form_alter(&$form, $form_state) {
+  solrsearch_set_facetapi_breadcrumb($form['#facetapi']['adapter'], $form['#facetapi']['realm']);
+}
+
+/**
+ * Implements hook_form_[form_id]_alter(). (D7)
+ */
+function solrsearch_form_facetapi_facet_dependencies_form_alter(&$form, $form_state) {
+  solrsearch_set_facetapi_breadcrumb($form['#facetapi']['adapter'], $form['#facetapi']['realm']);
+}
+
+/**
+ * Semaphore that indicates whether a search has been done. Blocks use this
+ * later to decide whether they should load or not.
+ *
+ * @param $searched
+ *   A boolean indicating whether a search has been executed.
+ *
+ * @return
+ *   TRUE if a search has been executed.
+ *   FALSE otherwise.
+ */
+function solrsearch_has_searched($env_id, $searched = NULL) {
+  $_searched  = &drupal_static(__FUNCTION__, FALSE);
+  if (is_bool($searched)) {
+    $_searched[$env_id] = $searched;
+  }
+  // Return false if the search environment is not available in our array
+  if (!isset($_searched[$env_id])) {
+    return FALSE;
+  }
+  return $_searched[$env_id];
+}
+
+/**
+ * Semaphore that indicates whether Blocks should be suppressed regardless
+ * of whether a search has run.
+ *
+ * @param $suppress
+ *   A boolean indicating whether to suppress.
+ *
+ * @return
+ *   TRUE if a search has been executed.
+ *   FALSE otherwise.
+ */
+function solrsearch_suppress_blocks($env_id, $suppress = NULL) {
+  $_suppress = &drupal_static(__FUNCTION__, FALSE);
+  if (is_bool($suppress)) {
+    $_suppress[$env_id] = $suppress;
+  }
+  // Return false if the search environment is not available in our array
+  if (!isset($_suppress[$env_id])) {
+    return FALSE;
+  }
+  return $_suppress[$env_id];
+}
+
+/**
+ * Get or set the default environment ID for the current page.
+ */
+function solrsearch_default_environment($env_id = NULL) {
+  $default_env_id = &drupal_static(__FUNCTION__, NULL);
+
+  if (isset($env_id)) {
+    $default_env_id = $env_id;
+  }
+  if (empty($default_env_id)) {
+    $default_env_id = variable_get('solrsearch_default_environment', 'echosearch');
+  }
+  return $default_env_id;
+}
+
+/**
+ * Set the default environment and let other modules know about the change.
+ */
+function solrsearch_set_default_environment($env_id) {
+  $old_env_id = variable_get('solrsearch_default_environment', 'solr');
+  variable_set('solrsearch_default_environment', $env_id);
+  module_invoke_all('solrsearch_default_environment', $env_id, $old_env_id);
+}
+
+/**
+ * Factory method for solr singleton objects. Structure allows for an arbitrary
+ * number of solr objects to be used based on a name whie maps to
+ * the host, port, path combination.
+ * Get an instance like this:
+ *   try {
+ *     $solr = solrsearch_get_solr();
+ *   }
+ *   catch (Exception $e) {
+ *     watchdog('Solr Search', nl2br(check_plain($e->getMessage())), NULL, WATCHDOG_ERROR);
+ *   }
+ *
+ *
+ * @param string $env_id
+ *
+ * @return DrupalApacheSolrServiceInterface $solr
+ *
+ * @throws Exception
+ */
+function solrsearch_get_solr($env_id = NULL) {
+  $solr_cache = &drupal_static(__FUNCTION__);
+  $environments = solrsearch_load_all_environments();
+
+  if (!interface_exists('DrupalApacheSolrServiceInterface')) {
+    require_once(dirname(__FILE__) . '/solrsearch.interface.inc');
+  }
+
+  if (empty($env_id)) {
+    $env_id = solrsearch_default_environment();
+  }
+  elseif (empty($environments[$env_id])) {
+    throw new Exception(t('Invalid Solr Search environment: @env_id.', array('@env_id' => $env_id)));
+  }
+
+  if (isset($environments[$env_id])) {
+    $class = $environments[$env_id]['service_class'];
+
+    if (empty($solr_cache[$env_id])) {
+      // Use the default class if none is specified.
+      if (empty($class)) {
+        $class = variable_get('solrsearch_service_class', 'DrupalsolrsearchService');
+      }
+      // Takes advantage of auto-loading.
+      $solr = new $class($environments[$env_id]['url'], $env_id);
+      $solr_cache[$env_id] = $solr;
+    }
+    return $solr_cache[$env_id];
+  }
+  else {
+    throw new Exception('No default Solr Search environment.');
+  }
+}
+
+/**
+ * Function that loads all the environments
+ *
+ * @return $environments
+ *   The environments in the database
+ */
+function solrsearch_load_all_environments() {
+  $environments = &drupal_static(__FUNCTION__);
+
+  if (isset($environments)) {
+    return $environments;
+  }
+  // Use cache_get to avoid DB when using memcache, etc.
+  $cache = cache_get('solrsearch:environments', 'cache_solrsearch');
+  if (isset($cache->data)) {
+    $environments = $cache->data;
+  }
+  elseif (!db_table_exists('solrsearch_index_bundles') || !db_table_exists('solrsearch_environment')) {
+    // Sometimes this function is called when the 'solrsearch_index_bundles' is
+    // not created yet.
+    $environments = array();
+  }
+  else {
+    // If ctools is available use its crud functions to load the environments.
+    if (module_exists('ctools')) {
+      ctools_include('export');
+      $environments = ctools_export_load_object('solrsearch_environment', 'all');
+      // Convert environments to array.
+      foreach ($environments as &$environment) {
+        $environment = (array) $environment;
+      }
+    }
+    else {
+      $environments = db_query('SELECT * FROM {solrsearch_environment}')->fetchAllAssoc('env_id', PDO::FETCH_ASSOC);
+    }
+
+    // Load conf and index bundles. We don't use 'subrecords callback' property
+    // of ctools export API.
+    solrsearch_environment_load_subrecords($environments);
+
+    cache_set('solrsearch:environments', $environments, 'cache_solrsearch');
+  }
+
+  // Allow overrides of environments from settings.php
+  $conf_environments = variable_get('solrsearch_environments', array());
+  if (!empty($conf_environments)) {
+    $environments = drupal_array_merge_deep($environments, $conf_environments);
+  }
+
+  return $environments;
+}
+
+/**
+ * Function that loads an environment
+ *
+ * @param $env_id
+ *   The environment ID it needs to load.
+ *
+ * @return $environment
+ *   The environment that was requested or FALSE if non-existent
+ */
+function solrsearch_environment_load($env_id) {
+  $environments = solrsearch_load_all_environments();
+  return isset($environments[$env_id]) ? $environments[$env_id] : FALSE;
+}
+
+/**
+ * Access callback for the delete page of an environment.
+ *
+ * @param $permission
+ *   The permission that you allow access to
+ * @param $environment
+ *   The environment you want to delete. Core environment cannot be deleted
+ */
+function solrsearch_environment_delete_page_access($permission, $environment) {
+  $is_default = $environment['env_id'] == solrsearch_default_environment();
+  if ($is_default && !user_access($permission)) {
+    return FALSE;
+  }
+  return TRUE;
+}
+
+/**
+ * Function that deletes an environment
+ *
+ * @param $env_id
+ *   The environment ID it needs to delete.
+ *
+ */
+function solrsearch_environment_delete($env_id) {
+  $environment = solrsearch_environment_load($env_id);
+  if ($environment) {
+    db_delete('solrsearch_environment')
+      ->condition('env_id', $env_id)
+      ->execute();
+    db_delete('solrsearch_environment_variable')
+      ->condition('env_id', $env_id)
+      ->execute();
+    db_delete('solrsearch_index_bundles')
+      ->condition('env_id', $env_id)
+      ->execute();
+
+    module_invoke_all('solrsearch_environment_delete', $environment);
+    solrsearch_environments_clear_cache();
+  }
+}
+
+/**
+ * Function that clones an environment
+ *
+ * @param $env_id
+ *   The environment ID it needs to clone.
+ *
+ */
+function solrsearch_environment_clone($env_id) {
+  $environment = solrsearch_environment_load($env_id);
+  $environments = solrsearch_load_all_environments();
+  $environment['env_id'] = solrsearch_create_unique_id($environments, $env_id);
+  $environment['name'] = $environment['name'] . ' [cloned]';
+  solrsearch_environment_save($environment);
+}
+
+/**
+ * Generator for an unique ID of an environment
+ *
+ * @param $environments
+ *   The environments that are available
+ * @param $original_environment
+ *   The environment it needs to replicate an ID for.
+ *
+ * @return
+ *   The new environment ID
+ */
+function solrsearch_create_unique_id($existing, $id) {
+  $count = 0;
+  $cloned_env_int = 0;
+  do {
+    $new_id = $id . '_' . $count;
+    $count++;
+  } while (isset($existing[$new_id]));
+  return $new_id;
+}
+
+/**
+ * Function that saves an environment
+ *
+ * @param $environment
+ *   The environment it needs to save.
+ *
+ */
+function solrsearch_environment_save($environment) {
+  module_load_include('inc', 'solrsearch', 'solrsearch.index');
+  $default = array('env_id' => '', 'name' => '', 'url' => '', 'service_class' => '');
+
+  $conf = isset($environment['conf']) ? $environment['conf'] : array();
+  $index_bundles = isset($environment['index_bundles']) ? $environment['index_bundles'] : array();
+  // Remove any unexpected fields.
+  // @todo - get this from the schema?.
+  $environment = array_intersect_key($environment, $default);
+  db_merge('solrsearch_environment')
+    ->key(array('env_id' => $environment['env_id']))
+    ->fields($environment)
+    ->execute();
+  // Update the environment variables (if any).
+  foreach ($conf as $name => $value) {
+    db_merge('solrsearch_environment_variable')
+      ->key(array('env_id' => $environment['env_id'], 'name' => $name))
+      ->fields(array('value' => serialize($value)))
+      ->execute();
+  }
+  // Update the index bundles (if any).
+  foreach ($index_bundles as $entity_type => $bundles) {
+    solrsearch_index_set_bundles($environment['env_id'], $entity_type, $bundles);
+  }
+  solrsearch_environments_clear_cache();
+}
+
+/**
+ * Clear all caches for environments.
+ */
+function solrsearch_environments_clear_cache() {
+  cache_clear_all('solrsearch:environments', 'cache_solrsearch');
+  drupal_static_reset('solrsearch_load_all_environments');
+  drupal_static_reset('solrsearch_get_solr');
+  if (module_exists('ctools')) {
+    ctools_include('export');
+    ctools_export_load_object_reset('solrsearch_environment');
+  }
+}
+
+/**
+ * Get a named variable, or return the default.
+ *
+ * @see variable_get()
+ */
+function solrsearch_environment_variable_get($env_id, $name, $default = NULL) {
+  $environment = solrsearch_environment_load($env_id);
+  if (isset($environment['conf'][$name])) {
+    return $environment['conf'][$name];
+  }
+  return $default;
+}
+
+/**
+ * Set a named variable, or return the default.
+ *
+ * @see variable_set()
+ */
+function solrsearch_environment_variable_set($env_id, $name, $value) {
+  db_merge('solrsearch_environment_variable')
+    ->key(array('env_id' => $env_id, 'name' => $name))
+    ->fields(array('value' => serialize($value)))
+    ->execute();
+  solrsearch_environments_clear_cache();
+}
+
+/**
+ * Get a named variable, or return the default.
+ *
+ * @see variable_del()
+ */
+function solrsearch_environment_variable_del($env_id, $name) {
+  db_delete('solrsearch_environment_variable')
+    ->condition('env_id', $env_id)
+    ->condition('name', $name)
+    ->execute();
+  solrsearch_environments_clear_cache();
+}
+
+/**
+ * Checks if a specific Solr Search server is available.
+ *
+ * @return boolean TRUE if the server can be pinged, FALSE otherwise.
+ */
+function solrsearch_server_status($url, $class = NULL) {
+
+  $status = &drupal_static(__FUNCTION__, array());
+
+  if (!interface_exists('DrupalApacheSolrServiceInterface')) {
+    require_once(dirname(__FILE__) . '/solrsearch.interface.inc');
+  }
+
+  if (empty($class)) {
+    $class = variable_get('solrsearch_service_class', 'DrupalsolrsearchService');
+  }
+
+  $key = $url . '|' . $class;
+  // Static store insures we don't ping the server more than once per page load.
+  if (!isset($status[$key])) {
+    $ping = FALSE;
+    try {
+      // Takes advantage of auto-loading.
+      // @Todo : Do we have to specify the env_id?
+      $solr = new $class($url);
+      $ping = @$solr->ping(variable_get('solrsearch_ping_timeout', 4));
+    }
+    catch (Exception $e) {
+      watchdog('Solr Search', nl2br(check_plain($e->getMessage())), NULL, WATCHDOG_ERROR);
+    }
+    $status[$key] = $ping;
+  }
+  return $status[$key];
+}
+
+/**
+ * Execute a keyword search based on a query object.
+ *
+ * Normally this function is used with the default (dismax) handler for keyword
+ * searches. The $final_query that's returned will have been modified by
+ * both hook_solrsearch_query_prepare() and hook_solrsearch_query_alter().
+ *
+ * @param $current_query
+ *   A query object from solrsearch_drupal_query().  It will be modified by
+ *   hook_solrsearch_query_prepare() and then cached in solrsearch_current_query().
+ * @param $page
+ *   For paging into results, using $current_query->params['rows'] results per page.
+ *
+ * @return array($final_query, $response)
+ *
+ * @throws Exception
+ */
+function solrsearch_do_query(DrupalSolrQueryInterface $current_query) {
+
+  if (!is_object($current_query)) {
+    throw new Exception(t('NULL query object in function solrsearch_do_query()'));
+  }
+
+  // Allow modules to alter the query prior to statically caching it.
+  // This can e.g. be used to add available sorts.
+  $searcher = $current_query->getSearcher();
+
+  if (module_exists('facetapi')) {
+
+    // Gets enabled facets, adds filter queries to $params.
+
+    $adapter = facetapi_adapter_load($searcher);
+    if ($adapter) {
+
+      // Realm could be added but we want all the facets
+      $adapter->addActiveFilters($current_query);
+    }
+  }
+
+  foreach (module_implements('solrsearch_query_prepare') as $module) {
+    $function_name = $module . '_solrsearch_query_prepare';
+    $function_name($current_query);
+  }
+
+  // Cache the original query. Since all the built queries go through
+  // this process, all the hook_invocations will happen later
+  $env_id = $current_query->solr('getId');
+
+  // Add our defType setting here. Normally this would be dismax or the setting
+  // from the solrconfig.xml. This allows the setting to be overridden.
+  $defType = solrsearch_environment_variable_get($env_id, 'solrsearch_query_type');
+  if (!empty($defType)) {
+    $current_query->addParam('defType', $defType);
+  }
+
+  $query = solrsearch_current_query($env_id, $current_query);
+
+  // Verify if this query was already executed in the same page load
+  if ($response = solrsearch_static_response_cache($searcher)) {
+    // Return cached query object
+
+    return array($query, $response);
+  }
+
+  $query->addParam('start', $query->page * $query->getParam('rows'));
+
+  // This hook allows modules to modify the query and params objects.
+  drupal_alter('solrsearch_query', $query);
+
+  if ($query->abort_search) {
+    // A module implementing HOOK_solrsearch_query_alter() aborted the search.
+    return array(NULL, array());
+  }
+
+
+  $keys = $query->getParam('q');
+
+
+  //dont want this because not supported by my search module in drupal dwinter
+  /* if (strlen($keys) == 0 && ($filters = $query->getFilters())) { */
+  /*   // Move the fq params to q.alt for better performance. Only suitable */
+  /*   // when using dismax or edismax, so we keep this out of the query class itself */
+  /*   // for now. */
+  /*   $qalt = array(); */
+  /*   foreach ($filters as $delta => $filter) { */
+  /*     // Move the fq param if it has no local params and is not negative. */
+  /*     if (!$filter['#exclude'] && !$filter['#local']) { */
+  /*       $qalt[] = '(' . $query->makeFilterQuery($filter) . ')'; */
+  /*       $query->removeFilter($filter['#name'], $filter['#value'], $filter['#exclude']); */
+  /*     } */
+  /*   } */
+  /*   if ($qalt) { */
+  /*     $query->addParam('q.alt', implode(' ', $qalt)); */
+  /*   } */
+  /* } */
+  // We must run htmlspecialchars() here since converted entities are in the index.
+  // and thus bare entities &, > or < won't match. Single quotes are converted
+  // too, but not double quotes since the dismax parser looks at them for
+  // phrase queries.
+  $keys = htmlspecialchars($keys, ENT_NOQUOTES, 'UTF-8');
+  $keys = str_replace("'", '&#039;', $keys);
+
+
+  $response = $query->search($keys);
+
+
+  // The response is cached so that it is accessible to the blocks and anything
+  // else that needs it beyond the initial search.
+  solrsearch_static_response_cache($searcher, $response);
+  return array($query, $response);
+}
+
+/**
+ * It is important to hold on to the Solr response object for the duration of the
+ * page request so that we can use it for things like building facet blocks.
+ *
+ * @param $searcher
+ *   Name of the searcher - e.g. from $query->getSearcher().
+ */
+function solrsearch_static_response_cache($searcher, $response = NULL) {
+  $_response = &drupal_static(__FUNCTION__, array());
+
+  if (is_object($response)) {
+    $_response[$searcher] = clone $response;
+  }
+  if (!isset($_response[$searcher])) {
+    $_response[$searcher] = NULL;
+  }
+  return $_response[$searcher];
+}
+
+/**
+ * Factory function for query objects.
+ *
+ * @param string $name
+ *   The search name, used for finding the correct blocks and other config.
+ *   Typically "solrsearch".
+ * @param array $params
+ *   Array of params , such as 'q', 'fq' to be applied.
+ * @param string $solrsort
+ *   Visible string telling solr how to sort.
+ * @param string $base_path
+ *   The search base path (without the keywords) for this query.
+ * @param DrupalApacheSolrServiceInterface $solr
+ *   An instance of DrupalApacheSolrServiceInterface.
+ *
+ * @return DrupalSolrQueryInterface
+ *   DrupalSolrQueryInterface object.
+ *
+ * @throws Exception
+ */
+function solrsearch_drupal_query($name, array $params = array(), $solrsort = '', $base_path = '', DrupalApacheSolrServiceInterface $solr = NULL, $context = array()) {
+
+
+  if (!interface_exists('DrupalSolrQueryInterface')) {
+    require_once(dirname(__FILE__) . '/solrsearch.interface.inc');
+  }
+  $class_info = variable_get('solrsearch_query_class', array(
+    'file' => 'Solr_Base_Query',
+    'module' => 'solrsearch',
+    'class' => 'SolrBaseQuery'));
+  $class = $class_info['class'];
+  if (!class_exists($class_info['class']) && isset($class_info['file']) && isset($class_info['module'])) {
+    module_load_include('php', $class_info['module'], $class_info['file']);
+  }
+  if (empty($solr)) {
+    $solr = solrsearch_get_solr();
+  }
+
+  return new $class($name, $solr, $params, $solrsort, $base_path, $context);
+}
+
+/**
+ * Factory function for query objects.
+ *
+ * @param $operator
+ *   Whether the subquery should be added to another query as OR or AND
+ *
+ * @return DrupalSolrQueryInterface|false
+ *   Subquery or error.
+ *
+ * @throws Exception
+ */
+function solrsearch_drupal_subquery($operator = 'OR') {
+  if (!interface_exists('DrupalSolrQueryInterface')) {
+    require_once(dirname(__FILE__) . '/solrsearch.interface.inc');
+  }
+
+  $class_info = variable_get('solrsearch_subquery_class', array(
+    'file' => 'Solr_Base_Query',
+    'module' => 'solrsearch',
+    'class' => 'SolrFilterSubQuery'));
+  $class = $class_info['class'];
+  if (!class_exists($class_info['class']) && isset($class_info['file']) && isset($class_info['module'])) {
+    module_load_include('php', $class_info['module'], $class_info['file']);
+  }
+  $query = new $class($operator);
+  return $query;
+}
+
+/**
+ * Static getter/setter for the current query. Only set once per page.
+ *
+ * @param $env_id
+ *   Environment from which to save or get the current query
+ * @param DrupalSolrQueryInterface $query
+ *   $query object to save in the static
+ *
+ * @return DrupalSolrQueryInterface|null
+ *   return the $query object if it is available in the drupal_static or null otherwise
+ */
+function solrsearch_current_query($env_id, DrupalSolrQueryInterface $query = NULL) {
+  $saved_query = &drupal_static(__FUNCTION__, NULL);
+  if (is_object($query)) {
+    $saved_query[$env_id] = clone $query;
+  }
+  if (empty($saved_query[$env_id])) {
+    return NULL;
+  }
+  return is_object($saved_query[$env_id]) ? clone $saved_query[$env_id] : NULL;
+}
+
+/**
+ *
+ */
+
+/**
+ * Construct a dynamic index name based on information about a field.
+ *
+ * @param array $field
+ *   array(
+ *     'index_type' => 'integer',
+ *     'multiple' => TRUE,
+ *     'name' => 'fieldname',
+ *   ),
+ * @return string
+ *   Fieldname as it appears in the solr index
+ */
+function solrsearch_index_key($field) {
+  $index_type = !empty($field['index_type']) ? $field['index_type'] : NULL;
+  switch ($index_type) {
+    case 'text':
+      $type_prefix = 't';
+      break;
+    case 'text-omitNorms':
+      $type_prefix = 'to';
+      break;
+    case 'text-unstemmed':
+      $type_prefix = 'tu';
+      break;
+    case 'text-edgeNgram':
+      $type_prefix = 'te';
+      break;
+    case 'text-whiteSpace':
+      $type_prefix = 'tw';
+      break;
+    case 'integer':
+      $type_prefix = 'i'; // long integer
+      break;
+    case 'half-int':
+      $type_prefix = 'h'; // 32 bit integer
+      break;
+    case 'float':
+      $type_prefix = 'f'; // float; sortable.
+      break;
+    case 'double':
+      $type_prefix = 'p'; // double; sortable d was used for date.
+      break;
+    case 'boolean':
+      $type_prefix = 'b';
+      break;
+    case 'tint':
+      $type_prefix = 'it'; // long integer trie; sortable, best for range queries
+      break;
+    case 'thalf-int':
+      $type_prefix = 'ht'; // 32 bit integer trie (sortable)
+      break;
+    case 'tfloat':
+      $type_prefix = 'ft'; // float trie; sortable, best for range queries.
+      break;
+    case 'tdouble':
+      $type_prefix = 'pt'; // double trie;
+      break;
+    case 'sint':
+      $type_prefix = 'is'; // long integer sortable (deprecated)
+      break;
+    case 'half-sint':
+      $type_prefix = 'hs'; // 32 bit integer long sortable (deprecated)
+      break;
+    case 'sfloat':
+      $type_prefix = 'fs'; // float, sortable (use for sorting missing last) (deprecated).
+      break;
+    case 'sdouble':
+      $type_prefix = 'ps'; // double sortable; (use for sorting missing last) (deprecated).
+      break;
+    case 'date':
+      $type_prefix = 'd'; // date trie (sortable)
+      break;
+    case 'date-deprecated':
+      $type_prefix = 'dd'; // date (regular)
+      break;
+    case 'binary':
+      $type_prefix = 'x'; // Anything that is base64 encoded
+      break;
+    case 'storage':
+      $type_prefix = 'z'; // Anything that just need to be stored, not indexed
+      break;
+    case 'point':
+      $type_prefix = 'point'; // PointType. "52.3672174,4.9126891"
+      break;
+    case 'location':
+      $type_prefix = 'loc'; // LatLonType. "52.3672174,4.9126891"
+      break;
+    case 'geohash':
+      $type_prefix = 'geo'; // GeohashField. "42.6" http://en.wikipedia.org/wiki/Geohash
+      break;
+    case 'string':
+    default:
+      $type_prefix = 's'; // String
+  }
+  $sm = !empty($field['multiple']) ? 'm_' : 's_';
+  // Block deltas are limited to 32 chars.
+  return substr($type_prefix . $sm . $field['name'], 0, 32);
+}
+
+/**
+ * Try to map a schema field name to a human-readable description.
+ */
+function solrsearch_field_name_map($field_name) {
+  $map = &drupal_static(__FUNCTION__);
+
+  if (!isset($map)) {
+    $map = array(
+      'content' => t('The full, rendered content (e.g. the rendered node body)'),
+      'ts_comments' => t('The rendered comments associated with a node'),
+      'tos_content_extra' => t('Extra rendered content or keywords'),
+      'tos_name_formatted' => t('Author name (Formatted)'),
+      'label' => t('Title or label'),
+      'teaser' => t('Teaser or preview'),
+      'tos_name' => t('Author name'),
+      'path_alias' => t('Path alias'),
+      'taxonomy_names' => t('All taxonomy term names'),
+      'tags_h1' => t('Body text inside H1 tags'),
+      'tags_h2_h3' => t('Body text inside H2 or H3 tags'),
+      'tags_h4_h5_h6' => t('Body text inside H4, H5, or H6 tags'),
+      'tags_inline' => t('Body text in inline tags like EM or STRONG'),
+      'tags_a' => t('Body text inside links (A tags)'),
+      'tid' => t('Taxonomy term IDs'),
+      'is_uid' => t('User IDs'),
+      'bundle' => t('Content type names eg. article'),
+      'entity_type' => t('Entity type names eg. node'),
+      'ss_language' => t('Language type eg. en or und (undefinded)'),
+    );
+    if (module_exists('taxonomy')) {
+      foreach (taxonomy_get_vocabularies() as $vocab) {
+        $map['tm_vid_' . $vocab->vid . '_names'] = t('Taxonomy term names only from the %name vocabulary', array('%name' => $vocab->name));
+        $map['im_vid_' . $vocab->vid] = t('Taxonomy term IDs from the %name vocabulary', array('%name' => $vocab->name));
+      }
+    }
+    foreach (solrsearch_entity_fields('node') as $field_nm => $nodefields) {
+      foreach ($nodefields as $field_info) {
+        $map[solrsearch_index_key($field_info)] = t('Field of type @type: %label', array('@type' => $field_info['field']['type'], '%label' => $field_info['display_name']));
+      }
+    }
+    drupal_alter('solrsearch_field_name_map', $map);
+  }
+  return isset($map[$field_name]) ? $map[$field_name] : $field_name;
+}
+
+/**
+ * Validation function for the Facet API facet settings form.
+ *
+ * Solr Search does not support the combination of OR facets
+ * and facet missing, so catch that at validation.
+ */
+function solrsearch_facet_form_validate($form, &$form_state) {
+  if (($form_state['values']['global']['operator'] == FACETAPI_OPERATOR_OR) && $form_state['values']['global']['facet_missing']) {
+    form_set_error('operator', t('Solr Search does not support <em>facet missing</em> in combination with the OR operator.'));
+  }
+}
+
+/**
+ * Implements hook_entity_info_alter().
+ */
+function solrsearch_entity_info_alter(&$entity_info) {
+  // Load all environments
+  $environments = solrsearch_load_all_environments();
+
+  // Set those values that we know.  Other modules can do so
+  // for their own entities if they want.
+  $default_entity_info = array();
+  $default_entity_info['node']['indexable'] = TRUE;
+  $default_entity_info['node']['status callback'][] = 'solrsearch_index_node_status_callback';
+  $default_entity_info['node']['document callback'][] = 'solrsearch_index_node_solr_document';
+  $default_entity_info['node']['reindex callback'] = 'solrsearch_index_node_solr_reindex';
+  $default_entity_info['node']['bundles changed callback'] = 'solrsearch_index_node_bundles_changed';
+  $default_entity_info['node']['index_table'] = 'solrsearch_index_entities_node';
+  $default_entity_info['node']['cron_check'] = 'solrsearch_index_node_check_table';
+  // solrsearch_search implements a new callback for every entity type
+  // $default_entity_info['node']['solrsearch']['result callback'] = 'solrsearch_search_node_result';
+  //Allow implementations of HOOK_solrsearch_entity_info to modify these default indexers
+  drupal_alter('solrsearch_entity_info', $default_entity_info);
+
+  // First set defaults so that we don't need to worry about NULL keys.
+  foreach (array_keys($entity_info) as $type) {
+    if (!isset($entity_info[$type]['solrsearch'])) {
+      $entity_info[$type]['solrsearch'] = array();
+    }
+    if (isset($default_entity_info[$type])) {
+      $entity_info[$type]['solrsearch'] += $default_entity_info[$type];
+    }
+    $default = array(
+      'indexable' => FALSE,
+      'status callback' => '',
+      'document callback' => '',
+      'reindex callback' => '',
+      'bundles changed callback' => '',
+    );
+    $entity_info[$type]['solrsearch'] += $default;
+  }
+
+  // For any supported entity type and bundle, flag it for indexing.
+  foreach ($entity_info as $entity_type => $info) {
+    if ($info['solrsearch']['indexable']) {
+      // Loop over each environment and check if any of them have other entity
+      // bundles of any entity type enabled and set the index value to TRUE
+      foreach ($environments as $env) {
+        // Skip if the environment is set to read only
+        if (empty($env['env_id']['conf']['solrsearch_read_only'])) {
+          // Get the supported bundles
+          $supported = solrsearch_get_index_bundles($env['env_id'], $entity_type);
+          // For each bundle in drupal, compare to the supported solrsearch
+          // bundles and enable where possible
+          foreach (array_keys($info['bundles']) as $bundle) {
+            if (in_array($bundle, $supported)) {
+              $entity_info[$entity_type]['bundles'][$bundle]['solrsearch']['index'] = TRUE;
+            }
+          }
+        }
+      }
+    }
+  }
+}
+
+/**
+ * Gets a list of the bundles on the specified entity type that should be indexed.
+ *
+ * @param string $core
+ *   The Solr environment for which to index entities.
+ * @param string $entity_type
+ *   The entity type to index.
+ * @return array
+ *   The bundles that should be indexed.
+ */
+function solrsearch_get_index_bundles($env_id, $entity_type) {
+  $environment = solrsearch_environment_load($env_id);
+  return !empty($environment['index_bundles'][$entity_type]) ? $environment['index_bundles'][$entity_type] : array();
+}
+
+/**
+ * Implements hook_entity_insert().
+ */
+function solrsearch_entity_insert($entity, $type) {
+  // For our purposes there's really no difference between insert and update.
+  return solrsearch_entity_update($entity, $type);
+}
+
+/**
+ * Determines if we should index the provided entity.
+ *
+ * Whether or not a given entity is indexed is determined on a per-bundle basis.
+ * Entities/Bundles that have no index flag are presumed to not get indexed.
+ *
+ * @param stdClass $entity
+ *   The entity we may or may not want to index.
+ * @param string $type
+ *   The type of entity.
+ * @return boolean
+ *   TRUE if this entity should be indexed, FALSE otherwise.
+ */
+function solrsearch_entity_should_index($entity, $type) {
+  $info = entity_get_info($type);
+  list($id, $vid, $bundle) = entity_extract_ids($type, $entity);
+
+  if ($bundle && isset($info['bundles'][$bundle]['solrsearch']['index']) && $info['bundles'][$bundle]['solrsearch']['index']) {
+    return TRUE;
+  }
+  return FALSE;
+}
+
+/**
+ * Implements hook_entity_update().
+ */
+function solrsearch_entity_update($entity, $type) {
+  // Include the index file for the status callback
+  module_load_include('inc', 'solrsearch', 'solrsearch.index');
+  if (solrsearch_entity_should_index($entity, $type)) {
+    $info = entity_get_info($type);
+    list($id, $vid, $bundle) = entity_extract_ids($type, $entity);
+
+    // Check status callback before sending to the index
+    $status_callbacks = solrsearch_entity_get_callback($type, 'status callback', $bundle);
+
+    $status = TRUE;
+    if (is_array($status_callbacks)) {
+      foreach($status_callbacks as $status_callback) {
+        if (is_callable($status_callback)) {
+          // by placing $status in front we prevent calling any other callback
+          // after one status callback returned false
+          $status = $status && $status_callback($id, $type);
+        }
+      }
+    }
+
+    // Delete the entity from our index if the status callback returns FALSE
+    if (!$status) {
+      solrsearch_entity_delete($entity, $type);
+      return NULL;
+    }
+
+    $indexer_table = solrsearch_get_indexer_table($type);
+
+    // If we haven't seen this entity before it may not be there, so merge
+    // instead of update.
+    db_merge($indexer_table)
+      ->key(array(
+      'entity_type' => $type,
+      'entity_id' => $id,
+      ))
+      ->fields(array(
+        'bundle' => $bundle,
+        'status' => 1,
+        'changed' => REQUEST_TIME,
+      ))
+      ->execute();
+  }
+}
+
+/**
+ * Retrieve the indexer table for an entity type.
+ */
+function solrsearch_get_indexer_table($type) {
+  $entity_info = entity_get_info();
+  if (isset($entity_info[$type]['solrsearch']['index_table'])) {
+    $indexer_table = $entity_info[$type]['solrsearch']['index_table'];
+  }
+  else {
+    $indexer_table = 'solrsearch_index_entities';
+  }
+  return $indexer_table;
+}
+
+/**
+ * Implements hook_entity_delete().
+ */
+function solrsearch_entity_delete($entity, $entity_type) {
+  $env_id = solrsearch_default_environment();
+
+  // Delete the entity's entry from a fictional table of all entities.
+  $info = entity_get_info($entity_type);
+  list($entity_id) = entity_extract_ids($entity_type, $entity);
+  solrsearch_remove_entity($env_id, $entity_type, $entity_id);
+}
+
+function solrsearch_remove_entity($env_id, $entity_type, $entity_id) {
+  module_load_include('inc', 'solrsearch', 'solrsearch.index');
+
+  $indexer_table = solrsearch_get_indexer_table($entity_type);
+  if (solrsearch_index_delete_entity_from_index($env_id, $entity_type, $entity_id)) {
+    // There was no exception, so delete from the table.
+    db_delete($indexer_table)
+      ->condition('entity_type', $entity_type)
+      ->condition('entity_id', $entity_id)
+      ->execute();
+  }
+  else {
+    // Set status 0 so we try to delete from the index again in the future.
+    db_update($indexer_table)
+      ->condition('entity_id', $entity_id)
+      ->fields(array('changed' => REQUEST_TIME, 'status' => 0))
+      ->execute();
+  }
+}
+
+/**
+ * Returns array containing information about node fields that should be indexed
+ */
+function solrsearch_entity_fields($entity_type = 'node') {
+  $fields = &drupal_static(__FUNCTION__, array());
+
+  if (!isset($fields[$entity_type])) {
+    $fields[$entity_type] = array();
+
+    $mappings = module_invoke_all('solrsearch_field_mappings');
+    foreach (array_keys($mappings) as $key) {
+      // Set all values with defaults.
+      $defaults = array(
+          'dependency plugins' => array('bundle', 'role'),
+          'map callback' => FALSE,
+          'name callback' => '',
+          'hierarchy callback' => FALSE,
+          'indexing_callback' => '',
+          'index_type' => 'string',
+          'facets' => FALSE,
+          'facet missing allowed' => FALSE,
+          'facet mincount allowed' => FALSE,
+          // Field API allows any field to be multi-valued.
+          'multiple' => FALSE,
+        );
+      if ($key !== 'per-field') {
+        $mappings[$key] += $defaults;
+      }
+      else {
+        foreach (array_keys($mappings[$key]) as $field_key) {
+          $mappings[$key][$field_key] += $defaults;
+        }
+      }
+    }
+
+    // Allow other modules to add or alter mappings.
+    drupal_alter('solrsearch_field_mappings', $mappings, $entity_type);
+
+    $modules = system_get_info('module');
+    $instances = field_info_instances($entity_type);
+    foreach (field_info_fields() as $field_name => $field) {
+      $row = array();
+      if (isset($field['bundles'][$entity_type]) && (isset($mappings['per-field'][$field_name]) || isset($mappings[$field['type']]))) {
+        // Find the mapping.
+        if (isset($mappings['per-field'][$field_name])) {
+          $row = $mappings['per-field'][$field_name];
+        }
+        else {
+          $row = $mappings[$field['type']];
+        }
+        // The field info array.
+        $row['field'] = $field;
+
+        // Cardinality: The number of values the field can hold. Legal values
+        // are any positive integer or FIELD_CARDINALITY_UNLIMITED.
+        if ($row['field']['cardinality'] != 1) {
+          $row['multiple'] = TRUE;
+        }
+
+        // @todo: for fields like taxonomy we are indexing multiple Solr fields
+        // per entity field, but are keying on a single Solr field name here.
+        $function = !empty($row['name callback']) ? $row['name callback'] : NULL;
+        if ($function && is_callable($function)) {
+          $row['name'] = $function($field);
+        }
+        else {
+          $row['name'] = $field['field_name'];
+        }
+        $row['module_name'] = $modules[$field['module']]['name'];
+        // Set display name
+        $display_name = array();
+        foreach ($field['bundles'][$entity_type] as $bundle) {
+          if (empty($instances[$bundle][$field_name]['display']['search_index']) || $instances[$bundle][$field_name]['display']['search_index'] != 'hidden') {
+            $row['display_name'] = $instances[$bundle][$field_name]['label'];
+            $row['bundles'][] = $bundle;
+          }
+        }
+        // Only add to the $fields array if some instances are displayed for the search index.
+        if (!empty($row['bundles'])) {
+          // Use the Solr index key as the array key.
+          $fields[$entity_type][solrsearch_index_key($row)][] = $row;
+        }
+      }
+    }
+  }
+  return $fields[$entity_type];
+}
+
+/**
+ * Implements hook_solrsearch_index_document_build().
+ */
+function field_solrsearch_index_document_build(solrsearchDocument $document, $entity, $entity_type) {
+  $info = entity_get_info($entity_type);
+  if ($info['fieldable']) {
+    // Handle fields including taxonomy.
+    $indexed_fields = solrsearch_entity_fields($entity_type);
+    foreach ($indexed_fields as $index_key => $nodefields) {
+      foreach ($nodefields as $field_info) {
+        $field_name = $field_info['field']['field_name'];
+        // See if the node has fields that can be indexed
+        if (isset($entity->{$field_name})) {
+          // Got a field.
+          $function = $field_info['indexing_callback'];
+          if ($function && function_exists($function)) {
+            // NOTE: This function should always return an array.  One
+            // entity field may be indexed to multiple Solr fields.
+            $fields = $function($entity, $field_name, $index_key, $field_info);
+            foreach ($fields as $field) {
+              // It's fine to use this method also for single value fields.
+              $document->setMultiValue($field['key'], $field['value']);
+            }
+          }
+        }
+      }
+    }
+  }
+}
+
+/**
+ * Implements hook_solrsearch_index_document_build_node().
+ *
+ * Adds book module support
+ */
+function solrsearch_solrsearch_index_document_build_node(solrsearchDocument $document, $entity, $env_id) {
+  // Index book module data.
+  if (!empty($entity->book['bid'])) {
+    // Hard-coded - must change if solrsearch_index_key() changes.
+    $document->is_book_bid = (int) $entity->book['bid'];
+  }
+}
+
+/**
+ * Strip html tags and also control characters that cause Jetty/Solr to fail.
+ */
+function solrsearch_clean_text($text) {
+  // Remove invisible content.
+  $text = preg_replace('@<(applet|audio|canvas|command|embed|iframe|map|menu|noembed|noframes|noscript|script|style|svg|video)[^>]*>.*</\1>@siU', ' ', $text);
+  // Add spaces before stripping tags to avoid running words together.
+  $text = filter_xss(str_replace(array('<', '>'), array(' <', '> '), $text), array());
+  // Decode entities and then make safe any < or > characters.
+  $text = htmlspecialchars(html_entity_decode($text, ENT_QUOTES, 'UTF-8'), ENT_QUOTES, 'UTF-8');
+  // Remove extra spaces.
+  $text = preg_replace('/\s+/s', ' ', $text);
+  // Remove white spaces around punctuation marks probably added
+  // by the safety operations above. This is not a world wide perfect solution,
+  // but a rough attempt for at least US and Western Europe.
+  // Pc: Connector punctuation
+  // Pd: Dash punctuation
+  // Pe: Close punctuation
+  // Pf: Final punctuation
+  // Pi: Initial punctuation
+  // Po: Other punctuation, including ¿?¡!,.:;
+  // Ps: Open punctuation
+  $text = preg_replace('/\s(\p{Pc}|\p{Pd}|\p{Pe}|\p{Pf}|!|\?|,|\.|:|;)/s', '$1', $text);
+  $text = preg_replace('/(\p{Ps}|¿|¡)\s/s', '$1', $text);
+  return $text;
+}
+
+/**
+ * Use the list.module's list_allowed_values() to format the
+ * field based on its value ($facet).
+ *
+ *  @param $facet string
+ *    The indexed value
+ *  @param $options
+ *    An array of options including the hook_block $delta.
+ */
+function solrsearch_fields_list_facet_map_callback($facets, $options) {
+  $map = array();
+  $allowed_values = array();
+  // @see list_field_formatter_view()
+  $fields = field_info_fields();
+  $field_name = $options['field']['field_name'];
+  if (isset($fields[$field_name])) {
+    $allowed_values = list_allowed_values($fields[$field_name]);
+  }
+  foreach ($facets as $key) {
+    if (isset($allowed_values[$key])) {
+      $map[$key]['#markup'] = field_filter_xss($allowed_values[$key]);
+    }
+    elseif ($key == '_empty_' && $options['facet missing allowed']) {
+      // Facet missing.
+      $map[$key]['#markup'] = theme('facetapi_facet_missing', array('field_name' => $options['display_name']));
+    }
+    else {
+      $map[$key]['#markup'] = field_filter_xss($key);
+    }
+    // The value has already been filtered.
+    $map[$key]['#html'] = TRUE;
+  }
+  return $map;
+}
+
+/**
+ *  @param $facet string
+ *    The indexed value
+ *  @param $options
+ *    An array of options including the hook_block $delta.
+ *  @see http://drupal.org/node/1059372
+ */
+function solrsearch_nodereference_map_callback($facets, $options) {
+  $map = array();
+  $allowed_values = array();
+  // @see list_field_formatter_view()
+  $fields = field_info_fields();
+  $field_name = $options['field']['field_name'];
+  if (isset($fields[$field_name])) {
+    $allowed_values = node_reference_potential_references($fields[$field_name]);
+  }
+  foreach ($facets as $key) {
+    if (isset($allowed_values[$key])) {
+      $map[$key]['#markup'] = field_filter_xss($allowed_values[$key]['title']);
+    }
+    elseif ($key == '_empty_' && $options['facet missing allowed']) {
+      // Facet missing.
+      $map[$key]['#markup'] = theme('facetapi_facet_missing', array('field_name' => $options['display_name']));
+    }
+    else {
+      $map[$key]['#markup'] = field_filter_xss($key);
+    }
+    // The value has already been filtered.
+    $map[$key]['#html'] = TRUE;
+  }
+  return $map;
+}
+
+/**
+ *  @param $facet string
+ *    The indexed value
+ *  @param $options
+ *    An array of options including the hook_block $delta.
+ *  @see http://drupal.org/node/1059372
+ */
+function solrsearch_userreference_map_callback($facets, $options) {
+  $map = array();
+  $allowed_values = array();
+  // @see list_field_formatter_view()
+  $fields = field_info_fields();
+  $field_name = $options['field']['field_name'];
+  if (isset($fields[$field_name])) {
+    $allowed_values = user_reference_potential_references($fields[$field_name]);
+  }
+  foreach ($facets as $key) {
+    if (isset($allowed_values[$key])) {
+      $map[$key]['#markup'] = field_filter_xss($allowed_values[$key]['title']);
+    }
+    elseif ($key == '_empty_' && $options['facet missing allowed']) {
+      // Facet missing.
+      $map[$key]['#markup'] = theme('facetapi_facet_missing', array('field_name' => $options['display_name']));
+    }
+    else {
+      $map[$key]['#markup'] = field_filter_xss($key);
+    }
+    // The value has already been filtered.
+    $map[$key]['#html'] = TRUE;
+  }
+  return $map;
+}
+
+/**
+ * Mapping callback for entity references.
+ */
+function solrsearch_entityreference_facet_map_callback(array $values, array $options) {
+  $map = array();
+  // Gathers entity ids so we can load multiple entities at a time.
+  $entity_ids = array();
+  foreach ($values as $value) {
+    list($entity_type, $id) = explode(':', $value);
+    $entity_ids[$entity_type][] = $id;
+  }
+  // Loads and maps entities.
+  foreach ($entity_ids as $entity_type => $ids) {
+    $entities = entity_load($entity_type, $ids);
+    foreach ($entities as $id => $entity) {
+      $key = $entity_type . ':' . $id;
+      $map[$key] = entity_label($entity_type, $entity);
+    }
+  }
+  return $map;
+}
+
+/**
+ * Returns the callback function appropriate for a given entity type/bundle.
+ *
+ * @param string $entity_type
+ *   The entity type for which we want to know the approprite callback.
+ * @param string $callback
+ *   The callback for which we want the appropriate function.
+ * @param string $bundle
+ *   If specified, the bundle of the entity in question.  Some callbacks may
+ *   be overridden on a bundle-level.  Not specified only the entity-level
+ *   callback will be checked.
+ * @return string
+ *   The function name for this callback, or NULL if not specified.
+ */
+function solrsearch_entity_get_callback($entity_type, $callback, $bundle = NULL) {
+  $info = entity_get_info($entity_type);
+
+  // A bundle-specific callback takes precedence over the generic one for the
+  // entity type.
+  if ($bundle && isset($info['bundles'][$bundle]['solrsearch'][$callback])) {
+    $callback_function = $info['bundles'][$bundle]['solrsearch'][$callback];
+  }
+  elseif (isset($info['solrsearch'][$callback])) {
+    $callback_function = $info['solrsearch'][$callback];
+  }
+  else {
+    $callback_function = NULL;
+  }
+  return $callback_function;
+}
+
+
+/**
+ * Function to retrieve all the nodes to index.
+ * Deprecated but kept for backwards compatibility
+ * @param String $namespace
+ * @param type $limit
+ */
+function solrsearch_get_nodes_to_index($namespace, $limit) {
+  $env_id = solrsearch_default_environment();
+  // Hardcode node as an entity type
+  module_load_include('inc', 'solrsearch', 'solrsearch.index');
+  solrsearch_index_get_entities_to_index($env_id, 'node', $limit);
+}
+
+
+/**
+ * Implements hook_theme().
+ */
+function solrsearch_theme() {
+  return array(
+    'solrsearch_result'  => array(
+      'variables' => array('result' => NULL, 'module' => NULL),
+      'file' => 'solrsearch.pages.inc',
+      'template' => 'solr-search-result',
+    ),
+      'solrsearch_results' => array(
+          'variables' => array('results' => NULL, 'module' => NULL),
+          'file' => 'solrsearch.pages.inc',
+          'template' => 'solr-search-results',
+      ),
+      'solrsearch_term_list_author' => array(
+          'variables' => array('authors' => null),
+          'file' => 'solrsearch_terms.inc',
+          'template' => 'solrsearch-term-list-author',
+      ),
+
+      'solrsearch_term_list_title' => array(
+          'variables' => array('authors' => null),
+          'file' => 'solrsearch_terms.inc',
+          'template' => 'solrsearch-term-list-title',
+      ),
+
+      'solrsearch_term_selection_form' => array(
+          'variables' => array('authors' => null,'cnt' => null, 'letter' => null),
+          'file' => 'solrsearch_terms.inc',
+          'template' => 'solrsearch-term-selection-form',
+      ),
+
+
+    /**
+     * Returns a list of links generated by solrsearch_sort_link
+     */
+    'solrsearch_sort_list' => array(
+      'variables' => array('items' => NULL),
+    ),
+    /**
+     * Returns a link which can be used to search the results.
+     */
+    'solrsearch_sort_link' => array(
+      'variables' => array('text' => NULL, 'path' => NULL, 'options' => NULL, 'active' => FALSE, 'direction' => ''),
+    ),
+    /**
+     * Themes the title links in admin settings pages.
+     */
+    'solrsearch_settings_title' => array(
+      'variables' => array('env_id' => NULL),
+    ),
+  );
+}
+
+/**
+ * Implements hook_hook_info().
+ */
+function solrsearch_hook_info() {
+  $hooks = array(
+    'solrsearch_field_mappings' => array(
+      'group' => 'solrsearch',
+    ),
+    'solrsearch_field_mappings_alter' => array(
+      'group' => 'solrsearch',
+    ),
+    'solrsearch_query_prepare' => array(
+      'group' => 'solrsearch',
+    ),
+    'solrsearch_query_alter' => array(
+      'group' => 'solrsearch',
+    ),
+    'solrsearch_search_result_alter' => array(
+      'group' => 'solrsearch',
+    ),
+    'solrsearch_environment_delete' => array(
+      'group' => 'solrsearch',
+    )
+  );
+  $hooks['solrsearch_index_document_build'] = array(
+    'group' => 'solrsearch',
+  );
+  return $hooks;
+}
+
+/**
+ * Implements hook_solrsearch_field_mappings().
+ */
+function field_solrsearch_field_mappings() {
+  $mappings = array(
+    'list_integer' => array(
+      'indexing_callback' => 'solrsearch_fields_default_indexing_callback',
+      'map callback' => 'solrsearch_fields_list_facet_map_callback',
+      'index_type' => 'integer',
+      'facets' => TRUE,
+      'query types' => array('term', 'numeric_range'),
+      'query type' => 'term',
+      'facet missing allowed' => TRUE,
+    ),
+    'list_float' => array(
+      'indexing_callback' => 'solrsearch_fields_default_indexing_callback',
+      'map callback' => 'solrsearch_fields_list_facet_map_callback',
+      'index_type' => 'float',
+      'facets' => TRUE,
+      'query types' => array('term', 'numeric_range'),
+      'query type' => 'term',
+      'facet missing allowed' => TRUE,
+    ),
+    'list_text' => array(
+      'indexing_callback' => 'solrsearch_fields_default_indexing_callback',
+      'map callback' => 'solrsearch_fields_list_facet_map_callback',
+      'index_type' => 'string',
+      'facets' => TRUE,
+      'facet missing allowed' => TRUE,
+    ),
+    'list_boolean' => array(
+      'indexing_callback' => 'solrsearch_fields_default_indexing_callback',
+      'map callback' => 'solrsearch_fields_list_facet_map_callback',
+      'index_type' => 'boolean',
+      'facets' => TRUE,
+      'facet missing allowed' => TRUE,
+    ),
+    'number_integer' => array(
+      'indexing_callback' => 'solrsearch_fields_default_indexing_callback',
+      'index_type' => 'tint',
+      'facets' => TRUE,
+      'query types' => array('term', 'numeric_range'),
+      'query type' => 'term',
+      'facet mincount allowed' => TRUE,
+    ),
+    'number_decimal' => array(
+      'indexing_callback' => 'solrsearch_fields_default_indexing_callback',
+      'index_type' => 'tfloat',
+      'facets' => TRUE,
+      'query types' => array('term', 'numeric_range'),
+      'query type' => 'term',
+      'facet mincount allowed' => TRUE,
+    ),
+    'number_float' => array(
+      'indexing_callback' => 'solrsearch_fields_default_indexing_callback',
+      'index_type' => 'tfloat',
+      'facets' => TRUE,
+      'query types' => array('term', 'numeric_range'),
+      'query type' => 'term',
+      'facet mincount allowed' => TRUE,
+    ),
+    'taxonomy_term_reference' => array(
+      'map callback' => 'facetapi_map_taxonomy_terms',
+      'hierarchy callback' => 'facetapi_get_taxonomy_hierarchy',
+      'indexing_callback' => 'solrsearch_term_reference_indexing_callback',
+      'index_type' => 'integer',
+      'facet_block_callback' => 'solrsearch_search_taxonomy_facet_block',
+      'facets' => TRUE,
+      'query types' => array('term'),
+      'query type' => 'term',
+      'facet mincount allowed' => TRUE,
+    ),
+  );
+
+  return $mappings;
+}
+
+/**
+ * Implements hook_solrsearch_field_mappings() on behalf of date module.
+ */
+function date_solrsearch_field_mappings() {
+  $mappings = array();
+  $default = array(
+    'indexing_callback' => 'solrsearch_date_default_indexing_callback',
+    'index_type' => 'date',
+    'facets' => TRUE,
+    'query types' => array('date'),
+    'query type' => 'date',
+    'min callback' => 'solrsearch_get_min_date',
+    'max callback' => 'solrsearch_get_max_date',
+    'map callback' => 'facetapi_map_date',
+  );
+
+  // DATE and DATETIME fields can use the same indexing callback.
+  $mappings['date'] = $default;
+  $mappings['datetime'] = $default;
+
+  // DATESTAMP fields need a different callback.
+  $mappings['datestamp'] = $default;
+  $mappings['datestamp']['indexing_callback'] = 'solrsearch_datestamp_default_indexing_callback';
+
+  return $mappings;
+}
+
+
+/**
+ * Callback that returns the minimum date of the facet's datefield.
+ *
+ * @param $facet
+ *   An array containing the facet definition.
+ *
+ * @return
+ *   The minimum time in the node table.
+ *
+ * @todo Cache this value.
+ */
+function solrsearch_get_min_date(array $facet) {
+  // FieldAPI date fields.
+  $table = 'field_data_' . $facet['field api name'];
+  $column = $facet['field api name'] . '_value';
+  $query = db_select($table, 't');
+  $query->addExpression('MIN(' . $column . ')', 'min');
+  $query_min = $query->execute()->fetch()->min;
+  // Update to unix timestamp if this is an ISO or other format.
+  if (!is_int($query_min)) {
+    $return = strtotime($query_min);
+    if ($return === FALSE) {
+      // Not a string that strtotime accepts (ex. '0000-00-00T00:00:00').
+      // Return default start date of 1 as the date query type getDateRange()
+      // function expects a non-0 integer.
+      $return = 1;
+    }
+  }
+  return $return;
+}
+
+/**
+ * Callback that returns the maximum value of the facet's date field.
+ *
+ * @param $facet
+ *   An array containing the facet definition.
+ *
+ * @return
+ *   The maximum time of the field.
+ *
+ * @todo Cache this value.
+ */
+function solrsearch_get_max_date(array $facet) {
+
+  // FieldAPI date fields.
+  $table = 'field_data_' . $facet['field api name'];
+  $column = $facet['field api name'] . '_value';
+  $query = db_select($table, 't');
+  $query->addExpression('MAX(' . $column . ')', 'max');
+  $query_max = $query->execute()->fetch()->max;
+  // Update to unix timestamp if this is an ISO or other format.
+  if (!is_int($query_max)) {
+    $return = strtotime($query_max);
+    if ($return === FALSE) {
+      // Not a string that strtotime accepts (ex. '0000-00-00T00:00:00').
+      // Return default end date of 1 year from now.
+      $return = time() + (52 * 7 * 24 * 60 * 60);
+    }
+  }
+  return $return;
+}
+
+/**
+ * Implements hook_solrsearch_field_mappings() on behalf of References (node_reference).
+ * @see http://drupal.org/node/1059372
+ */
+function node_reference_solrsearch_field_mappings() {
+  $mappings = array(
+    'node_reference' => array(
+      'indexing_callback' => 'solrsearch_nodereference_indexing_callback',
+      'index_type' => 'integer',
+      'map callback' => 'solrsearch_nodereference_map_callback',
+      'facets' => TRUE,
+    )
+  );
+
+  return $mappings;
+}
+
+/**
+ * Implements hook_solrsearch_field_mappings() on behalf of References (user_reference).
+ * @see http://drupal.org/node/1059372
+ */
+function user_reference_solrsearch_field_mappings() {
+  $mappings = array(
+    'user_reference' => array(
+      'indexing_callback' => 'solrsearch_userreference_indexing_callback',
+      'index_type' => 'integer',
+      'map callback' => 'solrsearch_userreference_map_callback',
+      'facets' => TRUE,
+    ),
+  );
+
+  return $mappings;
+}
+/**
+ * Implements hook_solrsearch_field_mappings() on behalf of EntityReferences (entityreference)
+ * @see http://drupal.org/node/1572722
+ */
+function entityreference_solrsearch_field_mappings() {
+  $mappings = array(
+    'entityreference' => array(
+      'indexing_callback' => 'solrsearch_entityreference_indexing_callback',
+      'map callback' => 'solrsearch_entityreference_facet_map_callback',
+      'index_type' => 'string',
+      'facets' => TRUE,
+      'query types' => array('term'),
+      'facet missing allowed' => TRUE,
+    ),
+  );
+
+  return $mappings;
+}
+
+/**
+ * A replacement for l()
+ *  - doesn't add the 'active' class
+ *  - retains all $_GET parameters that solrsearch may not be aware of
+ *  - if set, $options['query'] MUST be an array
+ *
+ * @see http://api.drupal.org/api/function/l/6
+ *   for parameters and options.
+ *
+ * @return
+ *   an HTML string containing a link to the given path.
+ */
+function solrsearch_l($text, $path, $options = array()) {
+  // Merge in defaults.
+  $options += array(
+    'attributes' => array(),
+    'html' => FALSE,
+    'query' => array(),
+  );
+
+  // Don't need this, and just to be safe.
+  unset($options['attributes']['title']);
+
+  // Retain GET parameters that Solr Search knows nothing about.
+  $get = array_diff_key($_GET, array('q' => 1, 'page' => 1, 'solrsort' => 1), $options['query']);
+  $options['query'] += $get;
+
+  return '<a href="' . check_url(url($path, $options)) . '"' . drupal_attributes($options['attributes']) . '>' . ($options['html'] ? $text : check_plain(html_entity_decode($text))) . '</a>';
+}
+
+function theme_solrsearch_sort_link($vars) {
+  $icon = '';
+  if ($vars['direction']) {
+    $icon = ' ' . theme('tablesort_indicator', array('style' => $vars['direction']));
+  }
+  if ($vars['active']) {
+    if (isset($vars['options']['attributes']['class'])) {
+      $vars['options']['attributes']['class'] .= ' active';
+    }
+    else {
+      $vars['options']['attributes']['class'] = 'active';
+    }
+  }
+  return $icon . solrsearch_l($vars['text'], $vars['path'], $vars['options']);
+}
+
+function theme_solrsearch_sort_list($vars) {
+  // theme('item_list') expects a numerically indexed array.
+  $vars['items'] = array_values($vars['items']);
+  return theme('item_list', array('items' => $vars['items']));
+}
+
+/**
+ * Themes the title for settings pages.
+ */
+function theme_solrsearch_settings_title($vars) {
+  $output = '';
+
+  // Gets environment information, builds header with nested link to the environment's
+  // edit page. Skips building title if environment info could not be retrieved.
+  if ($environment = solrsearch_environment_load($vars['env_id'])) {
+    $url = url(
+      'admin/config/search/solrsearch/settings/',
+      array('query' => array('destination' => current_path()))
+    );
+    $output .= '<h3>';
+    $output .= t(
+      'Settings for: @environment (<a href="@url">Overview</a>)',
+      array('@url' => $url, '@environment' => $environment['name'])
+    );
+    $output .= "</h3>\n";
+  }
+
+  return $output;
+}
+
+/**
+ * Export callback to load the view subrecords, which are the index bundles.
+ */
+function solrsearch_environment_load_subrecords(&$environments) {
+  if (empty($environments)) {
+    // Nothing to do.
+    return NULL;
+  }
+
+  $all_index_bundles = db_select('solrsearch_index_bundles', 'ib')
+    ->fields('ib', array('env_id', 'entity_type', 'bundle'))
+    ->condition('env_id', array_keys($environments), 'IN')
+    ->orderBy('env_id')
+    ->orderBy('entity_type')
+    ->orderBy('bundle')
+    ->execute()
+    ->fetchAll(PDO::FETCH_ASSOC);
+
+  $all_index_bundles_keyed = array();
+  foreach ($all_index_bundles as $env_info) {
+    extract($env_info);
+    $all_index_bundles_keyed[$env_id][$entity_type][] = $bundle;
+  }
+
+  $all_variables = db_select('solrsearch_environment_variable', 'v')
+    ->fields('v', array('env_id', 'name', 'value'))
+    ->condition('env_id', array_keys($environments), 'IN')
+    ->orderBy('env_id')
+    ->orderBy('name')
+    ->orderBy('value')
+    ->execute()
+    ->fetchAll(PDO::FETCH_ASSOC);
+
+  $variables = array();
+  foreach ($all_variables as $variable) {
+    extract($variable);
+    $variables[$env_id][$name] = unserialize($value);
+  }
+
+  foreach ($environments as $env_id => &$environment) {
+    $index_bundles = !empty($all_index_bundles_keyed[$env_id]) ? $all_index_bundles_keyed[$env_id] : array();
+    $conf = !empty($variables[$env_id]) ? $variables[$env_id] : array();
+    if (is_array($environment)) {
+      // Environment is an array.
+      // If we have different values in the database compared with what we
+      // have in the given environment argument we allow the admin to revert
+      // the db values so we can stick with a consistent system
+      if (!empty($environment['index_bundles']) && !empty($index_bundles) && $environment['index_bundles'] !== $index_bundles) {
+        unset($environment['in_code_only']);
+        $environment['type'] = 'Overridden';
+      }
+      if (!empty($environment['conf']) && !empty($conf) && $environment['conf'] !== $conf) {
+        unset($environment['in_code_only']);
+        $environment['type'] = 'Overridden';
+      }
+      $environment['index_bundles'] = (empty($environment['index_bundles']) || !empty($index_bundles)) ? $index_bundles : $environment['index_bundles'];
+      $environment['conf'] = (empty($environment['conf']) || !empty($conf)) ? $conf : $environment['conf'];
+    }
+    elseif (is_object($environment)) {
+      // Environment is an object.
+      if ($environment->index_bundles !== $index_bundles && !empty($index_bundles)) {
+        unset($environment->in_code_only);
+        $environment->type = 'Overridden';
+      }
+      if ($environment->conf !== $conf && !empty($conf)) {
+        unset($environment->in_code_only);
+        $environment->type = 'Overridden';
+      }
+      $environment->index_bundles = (empty($environment->index_bundles) || !empty($index_bundles)) ? $index_bundles : $environment->index_bundles;
+      $environment->conf = (empty($environment->conf) || !empty($conf)) ? $conf : $environment->conf;
+    }
+  }
+}
+
+/**
+ * Callback for saving Solr Search environment CTools exportables.
+ *
+ * CTools uses objects, while Solr Search uses arrays; turn CTools value into an
+ * array, then call the normal save function.
+ *
+ * @param stdclass $environment
+ *   An environment object.
+ */
+function solrsearch_ctools_environment_save($environment) {
+  solrsearch_environment_save((array) $environment);
+}
+
+/**
+ * Callback for reverting Solr Search environment CTools exportables.
+ *
+ * @param mixed $env_id
+ *   An environment machine name. CTools may provide an id OR a complete
+ *   environment object; Since Solr Search loads environments as arrays, this
+ *   may also be an environment array.
+ */
+function solrsearch_ctools_environment_delete($env_id) {
+  if (is_object($env_id) || is_array($env_id)) {
+    $env_id = (object) $env_id;
+    $env_id = $env_id->env_id;
+  }
+  solrsearch_environment_delete($env_id);
+}
+
+/**
+ * Callback for exporting Solr Search environments as CTools exportables.
+ *
+ * @param array $environment
+ *   An environment array from Solr Search.
+ * @param string $indent
+ *   White space for indentation from CTools.
+ */
+function solrsearch_ctools_environment_export($environment, $indent) {
+  ctools_include('export');
+  $environment = (object) $environment;
+  // Re-load the enviroment, since in some cases the conf
+  // is stripped since it's not in the actual schema.
+  $environment = (object) solrsearch_environment_load($environment->env_id);
+
+  $index_bundles = array();
+  foreach (entity_get_info() as $type => $info) {
+    if ($bundles = solrsearch_get_index_bundles($environment->env_id, $type)) {
+      $index_bundles[$type] = $bundles;
+    }
+  }
+  $additions_top = array();
+  $additions_bottom = array('conf' => $environment->conf, 'index_bundles' => $index_bundles);
+  return ctools_export_object('solrsearch_environment', $environment, $indent, NULL, $additions_top, $additions_bottom);
+}
+
+
+
+
+/**
+* Performs a search by calling hook_search_execute().
+*
+* @param $keys
+*   Keyword query to search on.
+* @param $module
+*   Search module to search.
+* @param $conditions
+*   Optional array of additional search conditions.
+*
+* @return
+*   Renderable array of search results. No return value if $keys are not
+*   supplied or if the given search module is not active.
+*/
+function solrsearch_data($keys, $module, $conditions = NULL) {
+  if (module_hook($module, 'search_execute')) {
+
+
+    $results = module_invoke($module, 'search_execute', $keys, $conditions);
+
+      #$params = $query ->getParams();
+    #$description = t('Showing items @start through @end of @total.', array(
+    #    '@start' => $params['start'] + 1,
+    #    '@end' => $params['start'] + $params['rows'] - 1,
+    #    '@total' => $total,
+    #));
+
+
+    if (module_hook($module, 'search_page')) {
+      return module_invoke($module, 'search_page', $results);
+    }
+    else {
+      return array(
+          '#theme' => 'solrsearch_results',
+          '#results' => $results,
+          '#module' => $module,
+      );
+    }
+  }
+}
+
+function solrsearch_callback_user_values(array $facet){
+ #ToDO is this used?
+}
+
+
+function solrsearch_permission() {
+  return array(
+      'view restricted content' => array(
+          'title' => t('View restricted content'),
+          'description' => t('Allow users to view content which is restricted.'),
+      ),
+  );
+}