view 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 source

<?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.'),
      ),
  );
}