You are here

apachesolr.module in Apache Solr Search 6

Integration with the Apache Solr search application.

File

apachesolr.module
View source
<?php

/**
 * @file
 *   Integration with the Apache Solr search application.
 */
define('APACHESOLR_READ_WRITE', 0);
define('APACHESOLR_READ_ONLY', 1);
define('APACHESOLR_API_VERSION', '1.0');

/**
 * Implementation of hook_init().
 *
 * PHP 5.1 compatability code.
 */
function apachesolr_init() {
  if (!function_exists('json_decode')) {

    // Zend files include other files.
    set_include_path(dirname(__FILE__) . PATH_SEPARATOR . get_include_path());
    require_once 'Zend/Json/Decoder.php';
    require_once 'Zend/Json/Encoder.php';

    /**
     * Substitute for missing PHP built-in functions.
     */
    function json_decode($string, $assoc = FALSE) {
      if ($assoc) {
        $objectDecodeType = Zend_Json::TYPE_ARRAY;
      }
      else {
        $objectDecodeType = Zend_Json::TYPE_OBJECT;
      }
      return Zend_Json_Decoder::decode($string, $objectDecodeType);
    }
    function json_encode($data) {
      return Zend_Json_Encoder::encode($data, $objectDecodeType);
    }
  }
}

/**
 * Implementation of hook_menu().
 */
function apachesolr_menu() {
  $items = array();
  $items['admin/settings/apachesolr'] = array(
    'title' => 'Apache Solr',
    'description' => 'Administer Apache Solr.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'apachesolr_settings',
    ),
    'access callback' => 'user_access',
    'access arguments' => array(
      'administer search',
    ),
    'file' => 'apachesolr.admin.inc',
  );
  $items['admin/settings/apachesolr/settings'] = array(
    'title' => 'Settings',
    'weight' => -10,
    'access arguments' => array(
      'administer search',
    ),
    'file' => 'apachesolr.admin.inc',
    'type' => MENU_DEFAULT_LOCAL_TASK,
  );
  $items['admin/settings/apachesolr/enabled-filters'] = array(
    'title' => 'Enabled filters',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'apachesolr_enabled_facets_form',
    ),
    'weight' => -7,
    'access arguments' => array(
      'administer search',
    ),
    'file' => 'apachesolr.admin.inc',
    'type' => MENU_LOCAL_TASK,
  );
  $items['admin/settings/apachesolr/index'] = array(
    'title' => 'Search index',
    'page callback' => 'apachesolr_index_page',
    'access arguments' => array(
      'administer search',
    ),
    'weight' => -8,
    'file' => 'apachesolr.admin.inc',
    'type' => MENU_LOCAL_TASK,
  );
  $items['admin/settings/apachesolr/index/clear/confirm'] = array(
    'title' => 'Confirm the re-indexing of all content',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'apachesolr_clear_index_confirm',
    ),
    'access arguments' => array(
      'administer search',
    ),
    'file' => 'apachesolr.admin.inc',
    'type' => MENU_CALLBACK,
  );
  $items['admin/settings/apachesolr/index/delete/confirm'] = array(
    'title' => 'Confirm index deletion',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'apachesolr_delete_index_confirm',
    ),
    'access arguments' => array(
      'administer search',
    ),
    'file' => 'apachesolr.admin.inc',
    'type' => MENU_CALLBACK,
  );
  $items['admin/reports/apachesolr'] = array(
    'title' => 'Apache Solr search index',
    'page callback' => 'apachesolr_index_report',
    'access arguments' => array(
      'access site reports',
    ),
    'file' => 'apachesolr.admin.inc',
  );
  $items['admin/reports/apachesolr/index'] = array(
    'title' => 'Search index',
    'file' => 'apachesolr.admin.inc',
    'type' => MENU_DEFAULT_LOCAL_TASK,
  );
  $items['admin/reports/apachesolr/conf'] = array(
    'title' => 'Configuration files',
    'page callback' => 'apachesolr_config_files_overview',
    'access arguments' => array(
      'access site reports',
    ),
    'file' => 'apachesolr.admin.inc',
    'weight' => 5,
    'type' => MENU_LOCAL_TASK,
  );
  $items['admin/reports/apachesolr/conf/%'] = array(
    'title' => 'Configuration file',
    'page callback' => 'apachesolr_config_file',
    'page arguments' => array(
      4,
    ),
    'access arguments' => array(
      'access site reports',
    ),
    'file' => 'apachesolr.admin.inc',
    'type' => MENU_CALLBACK,
  );
  $items['admin/settings/apachesolr/mlt/add_block'] = array(
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'apachesolr_mlt_add_block_form',
    ),
    'access arguments' => array(
      'administer search',
    ),
    'file' => 'apachesolr.admin.inc',
    'type' => MENU_CALLBACK,
  );
  $items['admin/settings/apachesolr/mlt/delete_block/%'] = array(
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'apachesolr_mlt_delete_block_form',
      5,
    ),
    'access arguments' => array(
      'administer search',
    ),
    'file' => 'apachesolr.admin.inc',
    'type' => MENU_CALLBACK,
  );
  return $items;
}

/**
 * Determines Apache Solr'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 apachesolr_failure($search_name, $querystring) {
  $fail_rule = variable_get('apachesolr_failure', 'show_error');
  switch ($fail_rule) {
    case 'show_error':
      drupal_set_message(t('The Apache Solr search engine is not available. Please contact your site administrator.'), 'error');
      break;
    case 'show_drupal_results':
      drupal_set_message(t("%search_name is not available. Your search is being redirected.", array(
        '%search_name' => $search_name,
      )));
      drupal_goto('search/node/' . drupal_urlencode($querystring));
      break;
    case 'show_no_results':
      return;
  }
}

/**
 * Like $site_key in _update_refresh() - returns a site-specific hash.
 */
function apachesolr_site_hash() {
  if (!($hash = variable_get('apachesolr_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('apachesolr_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 apachesolr_document_id($id, $entity = 'node') {
  return apachesolr_site_hash() . "/{$entity}/" . $id;
}

/**
 * Implementation of hook_user().
 *
 * Mark nodes as needing re-indexing if the author name changes.
 */
function apachesolr_user($op, &$edit, &$account) {
  switch ($op) {
    case 'update':
      if (isset($edit['name']) && $account->name != $edit['name']) {
        switch ($GLOBALS['db_type']) {
          case 'mysql':
          case 'mysqli':
            db_query("UPDATE {apachesolr_search_node} asn INNER JOIN {node} n ON asn.nid = n.nid SET asn.changed = %d WHERE n.uid = %d", time(), $account->uid);
            break;
          default:
            db_query("UPDATE {apachesolr_search_node} SET changed = %d WHERE nid IN (SELECT nid FROM {node} WHERE uid = %d)", time(), $account->uid);
            break;
        }
      }
      break;
  }
}

/**
 * Implementation of hook_taxonomy().
 *
 * Mark nodes as needing re-indexing if a term name changes.
 */
function apachesolr_taxonomy($op, $type, $edit) {
  if ($type == 'term' && $op == 'update') {
    switch ($GLOBALS['db_type']) {
      case 'mysql':
      case 'mysqli':
        db_query("UPDATE {apachesolr_search_node} asn INNER JOIN {term_node} tn ON asn.nid = tn.nid SET asn.changed = %d WHERE tn.tid = %d", time(), $edit['tid']);
        break;
      default:
        db_query("UPDATE {apachesolr_search_node} SET changed = %d WHERE nid IN (SELECT nid FROM {term_node} WHERE tid = %d)", time(), $edit['tid']);
        break;
    }
  }

  // TODO: the rest, such as term deletion.
}

/**
 * Implementation of hook_comment().
 *
 * Mark nodes as needing re-indexing if comments are added or changed.
 * Like search_comment().
 */
function apachesolr_comment($edit, $op) {
  $edit = (array) $edit;
  switch ($op) {

    // Reindex the node when comments are added or changed
    case 'insert':
    case 'update':
    case 'delete':
    case 'publish':
    case 'unpublish':

      // TODO: do we want to skip this if we are excluding comments
      // from the index for this node type?
      apachesolr_mark_node($edit['nid']);
      break;
  }
}

/**
 * Mark one node as needing re-indexing.
 */
function apachesolr_mark_node($nid) {
  db_query("UPDATE {apachesolr_search_node} SET changed = %d WHERE nid = %d", time(), $nid);
}

/**
 * Implementation of hook_node_type().
 *
 * Mark nodes as needing re-indexing if a node type name changes.
 */
function apachesolr_node_type($op, $info) {
  if ($op != 'delete' && !empty($info->old_type) && $info->old_type != $info->type) {

    // We cannot be sure we are going before or after node module.
    switch ($GLOBALS['db_type']) {
      case 'mysql':
      case 'mysqli':
        db_query("UPDATE {apachesolr_search_node} asn INNER JOIN {node} n ON asn.nid = n.nid SET asn.changed = %d WHERE (n.type = '%s' OR n.type = '%s')", time(), $info->old_type, $info->type);
        break;
      default:
        db_query("UPDATE {apachesolr_search_node} SET changed = %d WHERE nid IN (SELECT nid FROM {node} WHERE type = '%s' OR type = '%s')", time(), $info->old_type, $info->type);
        break;
    }
  }
}

/**
 * Helper function for modules implmenting hook_search's 'status' op.
 */
function apachesolr_index_status($namespace) {
  list($excluded_types, $args, $join_sql, $exclude_sql) = _apachesolr_exclude_types($namespace);
  $total = db_result(db_query("SELECT COUNT(asn.nid) FROM {apachesolr_search_node} asn " . $join_sql . "WHERE asn.status = 1 " . $exclude_sql, $excluded_types));
  $remaining = db_result(db_query("SELECT COUNT(asn.nid) FROM {apachesolr_search_node} asn " . $join_sql . "WHERE (asn.changed > %d OR (asn.changed = %d AND asn.nid > %d)) AND asn.status = 1 " . $exclude_sql, $args));
  return array(
    'remaining' => $remaining,
    'total' => $total,
  );
}

/**
 * Returns last changed and last nid for an indexing namespace.
 */
function apachesolr_get_last_index($namespace) {
  $stored = variable_get('apachesolr_index_last', array());
  return isset($stored[$namespace]) ? $stored[$namespace] : array(
    'last_change' => 0,
    'last_nid' => 0,
  );
}

/**
 * Clear a specific namespace's last changed and nid, or clear all.
 */
function apachesolr_clear_last_index($namespace = '') {
  if ($namespace) {
    $stored = variable_get('apachesolr_index_last', array());
    unset($stored[$namespace]);
    variable_set('apachesolr_index_last', $stored);
  }
  else {
    variable_del('apachesolr_index_last');
  }
}

/**
 * Truncate and rebuild the apachesolr_search_node table, reset the apachesolr_index_last variable.
 * This is the most complete way to force reindexing, or to build the indexing table for the
 * first time.
 *
 * @param $type
 *   A single content type to be reindexed, leaving the others unaltered.
 */
function apachesolr_rebuild_index_table($type = NULL) {
  if (isset($type)) {
    switch ($GLOBALS['db_type']) {
      case 'mysql':
      case 'mysqli':
        db_query("DELETE FROM {apachesolr_search_node} USING {apachesolr_search_node} asn INNER JOIN {node} n ON asn.nid = n.nid WHERE n.type = '%s'", $type);
        break;
      default:
        db_query("DELETE FROM {apachesolr_search_node} WHERE nid IN (SELECT nid FROM {node} WHERE type = '%s')", $type);
        break;
    }

    // Populate table
    db_query("INSERT INTO {apachesolr_search_node} (nid, status, changed)\n              SELECT n.nid, n.status, %d AS changed\n              FROM {node} n WHERE n.type = '%s'", time(), $type);
  }
  else {
    db_query("DELETE FROM {apachesolr_search_node}");

    // Populate table.
    if (module_exists('comment')) {

      // If comment module is enabled, use last_comment_timestamp as well.
      db_query("INSERT INTO {apachesolr_search_node} (nid, status, changed)\n              SELECT n.nid, n.status, GREATEST(n.created, n.changed, COALESCE(c.last_comment_timestamp, 0)) AS changed\n              FROM {node} n\n              LEFT JOIN {node_comment_statistics} c ON n.nid = c.nid");
    }
    else {
      db_query("INSERT INTO {apachesolr_search_node} (nid, status, changed)\n              SELECT n.nid, n.status, GREATEST(n.created, n.changed) AS changed\n              FROM {node} n");
    }

    // Make sure no nodes end up with a timestamp that's in the future.
    $time = time();
    db_query("UPDATE {apachesolr_search_node} SET changed = %d WHERE changed > %d", $time, $time);
    apachesolr_clear_last_index();
  }
}
function _apachesolr_exclude_types($namespace) {
  extract(apachesolr_get_last_index($namespace));
  $excluded_types = module_invoke_all('apachesolr_types_exclude', $namespace);
  $args = array(
    $last_change,
    $last_change,
    $last_nid,
  );
  $join_sql = '';
  $exclude_sql = '';
  if ($excluded_types) {
    $excluded_types = array_unique($excluded_types);
    $join_sql = "INNER JOIN {node} n ON n.nid = asn.nid ";
    $exclude_sql = "AND n.type NOT IN(" . db_placeholders($excluded_types, 'varchar') . ") ";
    $args = array_merge($args, $excluded_types);
  }
  return array(
    $excluded_types,
    $args,
    $join_sql,
    $exclude_sql,
  );
}

/**
 * Returns an array of rows from a query based on an indexing namespace.
 */
function apachesolr_get_nodes_to_index($namespace, $limit) {
  $rows = array();
  if (variable_get('apachesolr_read_only', APACHESOLR_READ_WRITE) == APACHESOLR_READ_ONLY) {
    return $rows;
  }
  list($excluded_types, $args, $join_sql, $exclude_sql) = _apachesolr_exclude_types($namespace);
  $result = db_query_range("SELECT asn.nid, asn.changed FROM {apachesolr_search_node} asn " . $join_sql . "WHERE (asn.changed > %d OR (asn.changed = %d AND asn.nid > %d)) AND asn.status = 1 " . $exclude_sql . "ORDER BY asn.changed ASC, asn.nid ASC", $args, 0, $limit);
  while ($row = db_fetch_object($result)) {
    $rows[] = $row;
  }
  return $rows;
}

/**
 * Function to handle the indexing of nodes.
 *
 * The calling function must supply a name space or track/store
 * the timestamp and nid returned.
 * Returns FALSE if no nodes were indexed (none found or error).
 */
function apachesolr_index_nodes($rows, $namespace = '', $callback = 'apachesolr_add_node_document') {
  if (!$rows) {

    // Nothing to do.
    return FALSE;
  }
  try {

    // Get the $solr object
    $solr = apachesolr_get_solr();

    // If there is no server available, don't continue.
    if (!$solr
      ->ping(variable_get('apachesolr_ping_timeout', 4))) {
      throw new Exception(t('No Solr instance available during indexing.'));
    }
  } catch (Exception $e) {
    watchdog('Apache Solr', nl2br(check_plain($e
      ->getMessage())), NULL, WATCHDOG_ERROR);
    return FALSE;
  }
  module_load_include('inc', 'apachesolr', 'apachesolr.index');
  $documents = array();
  $old_position = apachesolr_get_last_index($namespace);
  $position = $old_position;

  // Always build the content for the index as an anonynmous user.
  global $user;
  session_save_session(FALSE);
  $saved_user = $user;
  $user = drupal_anonymous_user();
  foreach ($rows as $row) {
    try {
      $callback($documents, $row->nid, $namespace);

      // Variables to track the last item changed.
      $position['last_change'] = $row->changed;
      $position['last_nid'] = $row->nid;
    } catch (Exception $e) {

      // Something bad happened - log the error.
      watchdog('Apache Solr', 'Error constructing documents to index: <br /> !message', array(
        '!message' => "Node ID: {$row->nid}<br />" . nl2br(strip_tags($e
          ->getMessage())),
      ), WATCHDOG_ERROR);
    }
  }

  // Restore the user.
  $user = $saved_user;
  session_save_session(TRUE);
  if (count($documents)) {
    try {
      watchdog('Apache Solr', 'Adding @count documents.', array(
        '@count' => count($documents),
      ));

      // Chunk the adds by 20s
      $docs_chunk = array_chunk($documents, 20);
      foreach ($docs_chunk as $docs) {
        $solr
          ->addDocuments($docs);
      }

      // Set the timestamp to indicate an index update.
      apachesolr_index_updated(time());
    } catch (Exception $e) {
      $nids = array();
      if (!empty($docs)) {
        foreach ($docs as $doc) {
          $nids[] = $doc->nid;
        }
      }
      watchdog('Apache Solr', 'Indexing failed on one of the following nodes: @nids <br /> !message', array(
        '@nids' => implode(', ', $nids),
        '!message' => nl2br(strip_tags($e
          ->getMessage())),
      ), WATCHDOG_ERROR);
      return FALSE;
    }
  }

  // Save the new position in case it changed.
  if ($namespace && $position != $old_position) {
    $stored = variable_get('apachesolr_index_last', array());
    $stored[$namespace] = $position;
    variable_set('apachesolr_index_last', $stored);
  }
  return $position;
}

/**
 * Convert date from timestamp into ISO 8601 format.
 * http://lucene.apache.org/solr/api/org/apache/solr/schema/DateField.html
 */
function apachesolr_date_iso($date_timestamp) {
  return gmdate('Y-m-d\\TH:i:s\\Z', $date_timestamp);
}
function apachesolr_delete_node_from_index($node) {
  static $failed = FALSE;
  if ($failed) {
    return FALSE;
  }
  try {
    $solr = apachesolr_get_solr();
    $solr
      ->deleteById(apachesolr_document_id($node->nid));
    apachesolr_index_updated(time());
    return TRUE;
  } catch (Exception $e) {
    watchdog('Apache Solr', nl2br(check_plain($e
      ->getMessage())), NULL, WATCHDOG_ERROR);

    // Don't keep trying queries if they are failing.
    $failed = TRUE;
    return FALSE;
  }
}

/**
 * Helper function to keep track of when the index has been updated.
 */
function apachesolr_index_updated($updated = NULL) {
  if (isset($updated)) {
    if ($updated) {
      variable_set('apachesolr_index_updated', (int) $updated);
    }
    else {
      variable_del('apachesolr_index_updated');
    }
  }
  return variable_get('apachesolr_index_updated', 0);
}

/**
 * Implementation of hook_cron().
 */
function apachesolr_cron() {

  // Indexes in read-only mode do not change the index, so will not update, delete, or optimize during cron.
  if (variable_get('apachesolr_read_only', APACHESOLR_READ_WRITE) == APACHESOLR_READ_ONLY) {
    return;
  }

  // Mass update and delete functions are in the include file.
  module_load_include('inc', 'apachesolr', 'apachesolr.index');
  apachesolr_cron_check_node_table();
  try {
    $solr = apachesolr_get_solr();

    // Optimize the index (by default once a day).
    $optimize_interval = variable_get('apachesolr_optimize_interval', 60 * 60 * 24);
    $last = variable_get('apachesolr_last_optimize', 0);
    $time = time();
    if ($optimize_interval && $time - $last > $optimize_interval) {
      $solr
        ->optimize(FALSE, FALSE);
      variable_set('apachesolr_last_optimize', $time);
      apachesolr_index_updated($time);
    }

    // Only clear the cache if the index changed.
    // TODO: clear on some schedule if running multi-site.
    $updated = apachesolr_index_updated();
    if ($updated) {
      $solr
        ->clearCache();

      // Re-populate the luke cache.
      $solr
        ->getLuke();

      // TODO: an admin interface for setting this.  Assume for now 5 minutes.
      if ($time - $updated >= variable_get('apachesolr_cache_delay', 300)) {

        // Clear the updated flag.
        apachesolr_index_updated(FALSE);
      }
    }
  } catch (Exception $e) {
    watchdog('Apache Solr', nl2br(check_plain($e
      ->getMessage())) . ' in apachesolr_cron', NULL, WATCHDOG_ERROR);
  }
}

/**
 * Implementation of hook_flush_caches().
 */
function apachesolr_flush_caches() {
  return array(
    'cache_apachesolr',
  );
}

/**
 * A wrapper for cache_clear_all to be used as a submit handler on forms that
 * require clearing Luke cache etc.
 */
function apachesolr_clear_cache() {
  try {
    $solr = apachesolr_get_solr();
    $solr
      ->clearCache();
  } catch (Exception $e) {
    watchdog('Apache Solr', nl2br(check_plain($e
      ->getMessage())), NULL, WATCHDOG_ERROR);
    drupal_set_message(nl2br(check_plain($e
      ->getMessage())), 'warning');
  }
}

/**
 * Implementation of hook_nodeapi().
 */
function apachesolr_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
  switch ($op) {
    case 'delete':
      _apachesolr_nodeapi_delete($node);
      break;
    case 'insert':

      // Make sure no node ends up with a timestamp that's in the future
      // by using time() rather than the node's changed or created timestamp.
      db_query("INSERT INTO {apachesolr_search_node} (nid, status, changed) VALUES  (%d, %d, %d)", $node->nid, $node->status, time());
      break;
    case 'update':
      _apachesolr_nodeapi_update($node);
      break;
  }
}

/**
 * Helper function for hook_nodeapi().
 */
function _apachesolr_nodeapi_delete($node, $set_message = TRUE) {
  if (apachesolr_delete_node_from_index($node)) {

    // There was no exception, so delete from the table.
    db_query("DELETE FROM {apachesolr_search_node} WHERE nid = %d", $node->nid);
    if ($set_message && user_access('administer search') && variable_get('apachesolr_set_nodeapi_messages', 1)) {
      apachesolr_set_stats_message('Deleted content will be removed from the Apache Solr search index in approximately @autocommit_time.');
    }
  }
}

/**
 * Helper function for hook_nodeapi().
 */
function _apachesolr_nodeapi_update($node, $set_message = TRUE) {

  // Check if the node has gone from published to unpublished.
  if (!$node->status && db_result(db_query("SELECT status FROM {apachesolr_search_node} WHERE nid = %d", $node->nid))) {
    if (apachesolr_delete_node_from_index($node)) {

      // There was no exception, so update the table.
      db_query('UPDATE {apachesolr_search_node} SET changed = %d, status = %d WHERE nid = %d', time(), $node->status, $node->nid);
      if ($set_message && user_access('administer search') && variable_get('apachesolr_set_nodeapi_messages', 1)) {
        apachesolr_set_stats_message('Unpublished content will be removed from the Apache Solr search index in approximately @autocommit_time.');
      }
    }
  }
  else {
    db_query('UPDATE {apachesolr_search_node} SET changed = %d, status = %d WHERE nid = %d', time(), $node->status, $node->nid);
  }
}

/**
 * Implementation of hook_content_fieldapi().
 */
function apachesolr_content_fieldapi($op, $field) {
  switch ($op) {
    case 'delete instance':
      apachesolr_mark_node_type($field['type_name']);
      break;
    case 'update instance':

      // Get the previous value from the table.
      $previous = content_field_instance_read(array(
        'field_name' => $field['field_name'],
        'type_name' => $field['type_name'],
      ));
      $prev_field = array_pop($previous);
      if ($field['display_settings'][NODE_BUILD_SEARCH_INDEX]['exclude'] != $prev_field['display_settings'][NODE_BUILD_SEARCH_INDEX]['exclude']) {
        apachesolr_mark_node_type($field['type_name']);
      }
      elseif ($field['multiple'] != $prev_field['multiple']) {
        apachesolr_mark_node_type($field['type_name']);
      }
      break;
  }
}

/**
 * Mark all nodes of one type as needing re-indexing.
 */
function apachesolr_mark_node_type($type_name) {
  switch ($GLOBALS['db_type']) {
    case 'mysql':
    case 'mysqli':
      db_query("UPDATE {apachesolr_search_node} asn INNER JOIN {node} n ON asn.nid = n.nid SET asn.changed = %d WHERE n.type = '%s'", time(), $type_name);
      break;
    default:
      db_query("UPDATE {apachesolr_search_node} SET changed = %d WHERE nid IN (SELECT nid FROM {node} WHERE type = '%s')", time(), $type_name);
      break;
  }
}

/**
 * Call drupal_set_message() with the text.
 *
 * The text is translated with t() and substituted using Solr stats.
 */
function apachesolr_set_stats_message($text, $type = 'status', $repeat = FALSE) {
  try {
    $solr = apachesolr_get_solr();
    $stats_summary = $solr
      ->getStatsSummary();
    drupal_set_message(t($text, $stats_summary), $type, FALSE);
  } catch (Exception $e) {
    watchdog('Apache Solr', nl2br(check_plain($e
      ->getMessage())), NULL, WATCHDOG_ERROR);
  }
}

/**
 * Return the enabled facets from the specified block array.
 *
 * @param $module
 *   The module (optional).
 * @return
 *   An array consisting of info for facets that have been enabled
 *   for the specified module, or all enabled facets.
 */
function apachesolr_get_enabled_facets($module = NULL) {
  $enabled = variable_get('apachesolr_enabled_facets', array());
  if (isset($module)) {
    return isset($enabled[$module]) ? $enabled[$module] : array();
  }
  return $enabled;
}

/**
 * Save the enabled facets for all modules.
 *
 * @param $enabled
 *   An array consisting of info for all enabled facets.
 * @return
 *   The array consisting of info for all enabled facets.
 */
function apachesolr_save_enabled_facets($enabled) {
  variable_set('apachesolr_enabled_facets', $enabled);
  return $enabled;
}

/**
 * Save the enabled facets for one module.
 *
 * @param $module
 *   The module name.
 * @param $facets
 *   Associative array of $delta => $facet_field pairs.  If omitted, all facets
 *   for $module are disabled.
 * @return
 *   An array consisting of info for all enabled facets.
 */
function apachesolr_save_module_facets($module, $facets = array()) {
  $enabled = variable_get('apachesolr_enabled_facets', array());
  if (!empty($facets) && is_array($facets)) {
    $enabled[$module] = $facets;
  }
  else {
    unset($enabled[$module]);
  }
  variable_set('apachesolr_enabled_facets', $enabled);
  return $enabled;
}

/**
 * Implementation of hook_block().
 */
function apachesolr_block($op = 'list', $delta = 0, $edit = array()) {
  static $access;
  switch ($op) {
    case 'list':

      // Get all of the moreLikeThis blocks that the user has created
      $blocks = apachesolr_mlt_list_blocks();

      // Add the sort block.
      $blocks['sort'] = array(
        'info' => t('Apache Solr Core: Sorting'),
        'cache' => BLOCK_CACHE_PER_PAGE,
      );
      return $blocks;
    case 'view':
      if ($delta != 'sort' && ($node = menu_get_object()) && (!arg(2) || arg(2) == 'view')) {
        $suggestions = array();

        // Determine whether the user can view the current node.
        if (!isset($access)) {
          $access = node_access('view', $node);
        }
        $block = apachesolr_mlt_load_block($delta);
        if ($access && $block) {
          $docs = apachesolr_mlt_suggestions($block, apachesolr_document_id($node->nid));
          if (!empty($docs)) {
            $suggestions['subject'] = check_plain($block['name']);
            $suggestions['content'] = theme('apachesolr_mlt_recommendation_block', $docs, $delta);
            if (user_access('administer search')) {
              $suggestions['content'] .= l(t('Configure this block'), 'admin/build/block/configure/apachesolr/' . $delta, array(
                'attributes' => array(
                  'class' => 'apachesolr-mlt-admin-link',
                ),
              ));
            }
          }
        }
        return $suggestions;
      }
      elseif (apachesolr_has_searched() && $delta == 'sort') {

        // Get the query and response. Without these no blocks make sense.
        $response = apachesolr_static_response_cache();
        if (empty($response) || $response->response->numFound < 2) {
          return;
        }
        $query = apachesolr_current_query();
        $sorts = $query
          ->get_available_sorts();

        // Get the current sort as an array.
        $solrsort = $query
          ->get_solrsort();
        $sort_links = array();
        $path = $query
          ->get_path();
        $new_query = clone $query;
        $toggle = array(
          'asc' => 'desc',
          'desc' => 'asc',
        );
        foreach ($sorts as $name => $sort) {
          $active = $solrsort['#name'] == $name;
          if ($name == 'score') {

            // We only sort by ascending score.
            $direction = '';
            $new_direction = 'asc';
          }
          elseif ($active) {
            $direction = $toggle[$solrsort['#direction']];
            $new_direction = $toggle[$solrsort['#direction']];
          }
          else {
            $direction = '';
            $new_direction = $sort['default'];
          }
          $new_query
            ->set_solrsort($name, $new_direction);
          $sort_links[$name] = array(
            'title' => $sort['title'],
            'path' => $path,
            'options' => array(
              'query' => $new_query
                ->get_url_queryvalues(),
            ),
            'active' => $active,
            'direction' => $direction,
          );
        }

        // Allow other modules to add or remove sorts.
        drupal_alter('apachesolr_sort_links', $sort_links);
        if (!empty($sort_links)) {
          foreach ($sort_links as $name => $link) {
            $themed_links[$name] = theme('apachesolr_sort_link', $link['title'], $link['path'], $link['options'], $link['active'], $link['direction']);
          }
          return array(
            'subject' => t('Sort by'),
            'content' => theme('apachesolr_sort_list', $themed_links),
          );
        }
      }
      break;
    case 'configure':
      if ($delta != 'sort') {
        require_once drupal_get_path('module', 'apachesolr') . '/apachesolr.admin.inc';
        return apachesolr_mlt_block_form($delta);
      }
      break;
    case 'save':
      if ($delta != 'sort') {
        require_once drupal_get_path('module', 'apachesolr') . '/apachesolr.admin.inc';
        apachesolr_mlt_save_block($edit, $delta);
      }
      break;
  }
}

/**
 * Helper function for displaying a facet block.
 */
function apachesolr_facet_block($response, $query, $module, $delta, $facet_field, $filter_by, $facet_callback = FALSE) {
  if (!empty($response->facet_counts->facet_fields->{$facet_field})) {
    $contains_active = FALSE;
    $items = array();
    foreach ($response->facet_counts->facet_fields->{$facet_field} as $facet => $count) {
      $options = array();
      $exclude = FALSE;

      // Solr sends this back if it's empty.
      if ($facet == '_empty_') {
        $exclude = TRUE;
        $facet = '[* TO *]';
        $options['html'] = TRUE;
      }
      if ($facet_callback && function_exists($facet_callback)) {
        $facet_text = $facet_callback($facet, $options);
      }
      elseif ($exclude) {
        $facet_text = theme('placeholder', t('Missing this field'));
      }
      else {
        $facet_text = $facet;
      }
      $active = $query
        ->has_filter($facet_field, $facet);
      if ($active) {

        // '*' sorts before all numbers.
        $sortpre = '*';
      }
      elseif ($exclude) {

        // '-' sorts before all numbers, but after '*'.
        $sortpre = '-';
      }
      else {
        $sortpre = 1000000 - $count;
      }
      $new_query = clone $query;
      if ($active) {
        $contains_active = TRUE;
        $new_query
          ->remove_filter($facet_field, $facet);
        $options['query'] = $new_query
          ->get_url_queryvalues();
        $link = theme('apachesolr_unclick_link', $facet_text, $new_query
          ->get_path(), $options);
      }
      else {
        $new_query
          ->add_filter($facet_field, $facet, $exclude);
        $options['query'] = $new_query
          ->get_url_queryvalues();
        $link = theme('apachesolr_facet_link', $facet_text, $new_query
          ->get_path(), $options, $count, FALSE, $response->response->numFound);
      }
      if ($count || $active) {
        $items[$sortpre . '*' . $facet_text] = $link;
      }
    }

    // Unless a facet is active only display 2 or more.
    if ($items && ($response->response->numFound > 1 || $contains_active)) {
      ksort($items, SORT_STRING);

      // Get information needed by the rest of the blocks about limits.
      $initial_limits = variable_get('apachesolr_facet_query_initial_limits', array());
      $limit = isset($initial_limits[$module][$delta]) ? $initial_limits[$module][$delta] : variable_get('apachesolr_facet_query_initial_limit_default', 10);
      $output = theme('apachesolr_facet_list', $items, $limit);
      return array(
        'subject' => $filter_by,
        'content' => $output,
      );
    }
  }
  return NULL;
}

/**
 * Helper function for displaying a date facet block.
 *
 * TODO: Refactor with apachesolr_facet_block().
 */
function apachesolr_date_facet_block($response, $query, $module, $delta, $facet_field, $filter_by, $facet_callback = FALSE) {
  $items = array();
  $new_query = clone $query;
  foreach (array_reverse($new_query
    ->get_filters($facet_field)) as $filter) {
    $options = array();

    // Iteratively remove the date facets.
    $new_query
      ->remove_filter($facet_field, $filter['#value']);
    if ($facet_callback && function_exists($facet_callback)) {
      $facet_text = $facet_callback($filter['#start'], $options);
    }
    else {
      $facet_text = apachesolr_date_format_iso_by_gap(apachesolr_date_find_query_gap($filter['#start'], $filter['#end']), $filter['#start']);
    }
    $options['query'] = $new_query
      ->get_url_queryvalues();
    array_unshift($items, theme('apachesolr_unclick_link', $facet_text, $new_query
      ->get_path(), $options));
  }

  // Add links for additional date filters.
  if (!empty($response->facet_counts->facet_dates->{$facet_field})) {
    $field = clone $response->facet_counts->facet_dates->{$facet_field};
    $end = $field->end;
    unset($field->end);
    $gap = $field->gap;
    unset($field->gap);
    if (isset($field->start)) {
      $start = $field->start;
      unset($field->start);
    }

    // Treat each date facet as a range start, and use the next date
    // facet as range end.  Use 'end' for the final end.
    $range_end = array();
    foreach ($field as $facet => $count) {
      if (isset($prev_facet)) {
        $range_end[$prev_facet] = $facet;
      }
      $prev_facet = $facet;
    }
    $range_end[$prev_facet] = $end;
    foreach ($field as $facet => $count) {
      $options = array();

      // Solr sends this back if it's empty.
      if ($facet == '_empty_' || $count == 0) {
        continue;
      }
      if ($facet_callback && function_exists($facet_callback)) {
        $facet_text = $facet_callback($facet, $options);
      }
      else {
        $facet_text = apachesolr_date_format_iso_by_gap(substr($gap, 2), $facet);
      }
      $new_query = clone $query;
      $new_query
        ->add_filter($facet_field, '[' . $facet . ' TO ' . $range_end[$facet] . ']');
      $options['query'] = $new_query
        ->get_url_queryvalues();
      $items[] = theme('apachesolr_facet_link', $facet_text, $new_query
        ->get_path(), $options, $count, FALSE, $response->response->numFound);
    }
  }
  if (count($items) > 0) {

    // Get information needed by the rest of the blocks about limits.
    $initial_limits = variable_get('apachesolr_facet_query_initial_limits', array());
    $limit = isset($initial_limits[$module][$delta]) ? $initial_limits[$module][$delta] : variable_get('apachesolr_facet_query_initial_limit_default', 10);
    $output = theme('apachesolr_facet_list', $items, $limit);
    return array(
      'subject' => $filter_by,
      'content' => $output,
    );
  }
  return NULL;
}

/**
 * Determine the gap in a date range query filter that we generated.
 *
 * This function assumes that the start and end dates are the
 * beginning and end of a single period: 1 year, month, day, hour,
 * minute, or second (all date range query filters we generate meet
 * this criteria).  So, if the seconds are different, it is a second
 * gap.  If the seconds are the same (incidentally, they will also be
 * 0) but the minutes are different, it is a minute gap.  If the
 * minutes are the same but hours are different, it's an hour gap.
 * etc.
 *
 * @param $start
 *   Start date as an ISO date string.
 * @param $end
 *   End date as an ISO date string.
 * @return
 *   YEAR, MONTH, DAY, HOUR, MINUTE, or SECOND.
 */
function apachesolr_date_find_query_gap($start_iso, $end_iso) {
  $gaps = array(
    'SECOND' => 6,
    'MINUTE' => 5,
    'HOUR' => 4,
    'DAY' => 3,
    'MONTH' => 2,
    'YEAR' => 1,
  );
  $re = '@(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2}):(\\d{2})@';
  if (preg_match($re, $start_iso, $start) && preg_match($re, $end_iso, $end)) {
    foreach ($gaps as $gap => $idx) {
      if ($start[$idx] != $end[$idx]) {
        return $gap;
      }
    }
  }

  // can't tell
  return 'YEAR';
}

/**
 * Format an ISO date string based on the gap used to generate it.
 *
 * This function assumes that gaps less than one day will be displayed
 * in a search context in which a larger containing gap including a
 * day is already displayed.  So, HOUR, MINUTE, and SECOND gaps only
 * display time information, without date.
 *
 * @param $gap
 *   A gap.
 * @param $iso
 *   An ISO date string.
 * @return
 *   A gap-appropriate formatted date.
 */
function apachesolr_date_format_iso_by_gap($gap, $iso) {

  // TODO: If we assume that multiple search queries are formatted in
  // order, we could store a static list of all gaps we've formatted.
  // Then, if we format an HOUR, MINUTE, or SECOND without previously
  // having formatted a DAY or later, we could include date
  // information.  However, we'd need to do that per-field and I'm not
  // our callers always have field information handy.
  $unix = strtotime($iso);
  if ($unix !== FALSE) {
    switch ($gap) {
      case 'YEAR':
        return format_date($unix, 'custom', 'Y', 0);
      case 'MONTH':
        return format_date($unix, 'custom', 'F Y', 0);
      case 'DAY':
        return format_date($unix, 'custom', 'F j, Y', 0);
      case 'HOUR':
        return format_date($unix, 'custom', 'g A', 0);
      case 'MINUTE':
        return format_date($unix, 'custom', 'g:i A', 0);
      case 'SECOND':
        return format_date($unix, 'custom', 'g:i:s A', 0);
    }
  }
  return $iso;
}

/**
 * Format the beginning of a date range query filter that we
 * generated.
 *
 * @param $start_iso
 *   The start date.
 * @param $end_iso
 *   The end date.
 * @return
 *   A display string reprepsenting the date range, such as "January
 * 2009" for "2009-01-01T00:00:00Z TO 2009-02-01T00:00:00Z"
 */
function apachesolr_date_format_range($start_iso, $end_iso) {
  $gap = apachesolr_date_find_query_gap($start_iso, $end_iso);
  return apachesolr_date_format_iso_by_gap($gap, $start_iso);
}

/**
 * Determine the best search gap to use for an arbitrary date range.
 *
 * Generally, we the maximum gap that fits between the start and end
 * date.  If they are more than a year apart, 1 year; if they are more
 * than a month apart, 1 month; etc.
 *
 * This function uses Unix timestamps for its computation and so is
 * not useful for dates outside that range.
 *
 * @param $start
 *   Start date as an ISO date string.
 * @param $end
 *   End date as an ISO date string.
 * @return
 *   YEAR, MONTH, DAY, HOUR, MINUTE, or SECOND depending on how far
 *   apart $start and $end are.
 */
function apachesolr_date_determine_gap($start, $end) {
  $start = strtotime($start);
  $end = strtotime($end);
  if ($end - $start >= 86400 * 365) {
    return 'YEAR';
  }
  if (date('Ym', $start) != date('Ym', $end)) {
    return 'MONTH';
  }
  if ($end - $start > 86400) {
    return 'DAY';
  }
  return 'HOUR';
}

/**
 * Return the next smaller date gap.
 *
 * @param $gap
 *   A gap.
 * @return
 *   The next smaller gap, or NULL if there is no smaller gap.
 */
function apachesolr_date_gap_drilldown($gap) {
  $drill = array(
    'YEAR' => 'MONTH',
    'MONTH' => 'DAY',
    'DAY' => 'HOUR',
  );
  return isset($drill[$gap]) ? $drill[$gap] : NULL;
}

/**
 * Used by the 'configure' $op of hook_block so that modules can generically set
 * facet limits on their blocks.
 */
function apachesolr_facetcount_form($module, $delta) {
  $initial = variable_get('apachesolr_facet_query_initial_limits', array());
  $limits = variable_get('apachesolr_facet_query_limits', array());
  $children = variable_get('apachesolr_facet_show_children', array());
  $facet_missing = variable_get('apachesolr_facet_missing', array());
  $limit = drupal_map_assoc(array(
    50,
    40,
    30,
    20,
    15,
    10,
    5,
    3,
  ));
  $form['apachesolr_facet_query_initial_limit'] = array(
    '#type' => 'select',
    '#title' => t('Initial filter links'),
    '#options' => $limit,
    '#description' => t('The initial number of filter links to show in this block.'),
    '#default_value' => isset($initial[$module][$delta]) ? $initial[$module][$delta] : variable_get('apachesolr_facet_query_initial_limit_default', 10),
  );
  $limit = drupal_map_assoc(array(
    100,
    75,
    50,
    40,
    30,
    20,
    15,
    10,
    5,
    3,
  ));
  $form['apachesolr_facet_query_limit'] = array(
    '#type' => 'select',
    '#title' => t('Maximum filter links'),
    '#options' => $limit,
    '#description' => t('The maximum number of filter links to show in this block.'),
    '#default_value' => isset($limits[$module][$delta]) ? $limits[$module][$delta] : variable_get('apachesolr_facet_query_limit_default', 20),
  );
  $form['apachesolr_facet_show_children'] = array(
    '#type' => 'radios',
    '#title' => t('Always show child facets'),
    '#options' => array(
      0 => t('No'),
      1 => t('Yes'),
    ),
    '#description' => t('Show the child facets even if the parent facet is not selected.'),
    '#default_value' => isset($children[$module][$delta]) ? $children[$module][$delta] : 0,
  );
  $form['apachesolr_facet_missing'] = array(
    '#type' => 'radios',
    '#title' => t('Include a facet for missing'),
    '#options' => array(
      0 => t('No'),
      1 => t('Yes'),
    ),
    '#description' => t('A facet can be generated corresponding to all documents entirely missing this field.'),
    '#default_value' => isset($facet_missing[$module][$delta]) ? $facet_missing[$module][$delta] : 0,
  );
  return $form;
}

/**
 * Used by the 'save' $op of hook_block so that modules can generically set
 * facet limits on their blocks.
 */
function apachesolr_facetcount_save($edit) {

  // Save query limits
  $module = $edit['module'];
  $delta = $edit['delta'];
  $limits = variable_get('apachesolr_facet_query_limits', array());
  $limits[$module][$delta] = (int) $edit['apachesolr_facet_query_limit'];
  variable_set('apachesolr_facet_query_limits', $limits);
  $initial = variable_get('apachesolr_facet_query_initial_limits', array());
  $initial[$module][$delta] = (int) $edit['apachesolr_facet_query_initial_limit'];
  variable_set('apachesolr_facet_query_initial_limits', $initial);
  $children = variable_get('apachesolr_facet_show_children', array());
  $children[$module][$delta] = (int) $edit['apachesolr_facet_show_children'];
  variable_set('apachesolr_facet_show_children', $children);
  $facet_missing = variable_get('apachesolr_facet_missing', array());
  $facet_missing[$module][$delta] = (int) $edit['apachesolr_facet_missing'];
  variable_set('apachesolr_facet_missing', $facet_missing);
}

/**
 * Initialize a pager for theme('pager') without running an SQL query.
 *
 * @see pager_query()
 *
 * @param $total
 *  The total number of items found.
 * @param $limit
 *  The number of items you will display per page.
 * @param $element
 *  An optional integer to distinguish between multiple pagers on one page.
 *
 * @return
 *  The current page for $element. 0 by default if $_GET['page'] is empty.
 */
function apachesolr_pager_init($total, $limit = 10, $element = 0) {
  global $pager_page_array, $pager_total, $pager_total_items;
  $page = isset($_GET['page']) ? $_GET['page'] : '';

  // Convert comma-separated $page to an array, used by other functions.
  $pager_page_array = explode(',', $page);

  // We calculate the total of pages as ceil(items / limit).
  $pager_total_items[$element] = $total;
  $pager_total[$element] = ceil($pager_total_items[$element] / $limit);
  $pager_page_array[$element] = max(0, min((int) $pager_page_array[$element], (int) $pager_total[$element] - 1));
  return $pager_page_array[$element];
}

/**
 * This hook allows modules to modify the query and params objects.
 *
 * Example:
 *
 * function my_module_apachesolr_modify_query(&$query, &$params, $caller) {
 *   // I only want to see articles by the admin!
 *   $query->add_filter("uid", 1);
 *
 * }
 */
function apachesolr_modify_query(&$query, &$params, $caller) {
  if (empty($query)) {

    // This should only happen if Solr is not set up - avoids fatal errors.
    return;
  }

  // Call the hooks first because otherwise any modifications to the
  // $query object don't end up in the $params.
  foreach (module_implements('apachesolr_modify_query') as $module) {
    $function_name = $module . '_apachesolr_modify_query';
    $function_name($query, $params, $caller);
  }

  // TODO: The query object should hold all the params.
  // Add array of fq parameters.
  if ($query && ($fq = $query
    ->get_fq())) {
    $params['fq'] = $fq;
  }

  // Add sort if present.
  if ($query) {
    $sort = $query
      ->get_solrsort();
    $sortstring = $sort['#name'] . ' ' . $sort['#direction'];

    // We don't bother telling Solr to do its default sort.
    if ($sortstring != 'score asc') {
      $params['sort'] = $sortstring;
    }
  }
}

/**
 * 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 apachesolr_has_searched($searched = NULL) {
  static $_searched = FALSE;
  if (is_bool($searched)) {
    $_searched = $searched;
  }
  return $_searched;
}

/**
 * Factory method for solr singleton object. Structure allows for an arbitrary
 * number of solr objects to be used based on the host, port, path combination.
 * Get an instance like this:
 *   $solr = apachesolr_get_solr();
 *
 * @throws Exception
 */
function apachesolr_get_solr($host = NULL, $port = NULL, $path = NULL) {
  static $solr_cache;
  if (empty($host)) {
    $host = variable_get('apachesolr_host', 'localhost');
  }
  if (empty($port)) {
    $port = variable_get('apachesolr_port', '8983');
  }
  if (empty($path)) {
    $path = variable_get('apachesolr_path', '/solr');
  }
  if (empty($solr_cache[$host][$port][$path])) {
    list($module, $filepath, $class) = variable_get('apachesolr_service_class', array(
      'apachesolr',
      'Drupal_Apache_Solr_Service.php',
      'Drupal_Apache_Solr_Service',
    ));
    include_once drupal_get_path('module', $module) . '/' . $filepath;
    $solr = new $class($host, $port, $path);
    $solr_cache[$host][$port][$path] = $solr;
  }
  return $solr_cache[$host][$port][$path];
}

/**
 * Execute a 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_apachesolr_prepare_query() and hook_apachesolr_modify_query().
 *
 * @param $caller
 *   String, name of the calling module or function for use as a cache namespace.
 * @param $current_query
 *   A query object from apachesolr_drupal_query().  It will be modified by
 *   hook_apachesolr_prepare_query() and then cached in apachesolr_current_query().
 * @param $params
 *   Array of parameters to pass to Solr.  Must include at least 'rows'.
 * @param $page
 *   For paging into results, using $params['rows'] results per page.
 *
 * @return array($final_query, $response)
 *
 * @throws Exception
 */
function apachesolr_do_query($caller, $current_query, &$params = array(
  'rows' => 10,
), $page = 0) {

  // Allow modules to alter the query prior to statically caching it.
  // This can e.g. be used to add available sorts.
  foreach (module_implements('apachesolr_prepare_query') as $module) {
    $function_name = $module . '_apachesolr_prepare_query';
    $function_name($current_query, $params, $caller);
  }

  // Cache the original query. Since all the built queries go through
  // this process, all the hook_invocations will happen later
  $query = apachesolr_current_query($current_query, $caller);

  // This hook allows modules to modify the query and params objects.
  apachesolr_modify_query($query, $params, $caller);
  $params['start'] = $page * $params['rows'];
  if (!$query) {
    return array(
      NULL,
      array(),
    );
  }

  // Final chance for the caller to modify the query and params. The signature
  // is: CALLER_finalize_query(&$query, &$params);
  $function = $caller . '_finalize_query';
  if (function_exists($function)) {
    $function($query, $params);
  }
  $keys = $query
    ->get_query_basic();
  if ($keys == '' && isset($params['fq'])) {

    // Move the fq params to q.alt for better performance.
    $qalt = array();
    foreach ($params['fq'] as $delta => $value) {

      // Move the fq param if it has no local params and is not negative.
      if (!preg_match('/^(?:\\{!|-)/', $value)) {
        $qalt[] = '(' . $value . ')';
        unset($params['fq'][$delta]);
      }
    }
    if ($qalt) {
      $params['q.alt'] = implode(' ', $qalt);
    }
  }

  // This is the object that does the communication with the solr server.
  $solr = apachesolr_get_solr();

  // 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 = $solr
    ->search($keys, $params['start'], $params['rows'], $params);

  // The response is cached so that it is accessible to the blocks and anything
  // else that needs it beyond the initial search.
  apachesolr_static_response_cache($response, $caller);
  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.
 *
 * @todo reverse the order of parameters in future branches.
 */
function apachesolr_static_response_cache($response = NULL, $namespace = 'apachesolr_search') {
  static $_response = array();
  if (is_object($response)) {
    $_response[$namespace] = clone $response;
  }
  if (!isset($_response[$namespace])) {
    $_response[$namespace] = NULL;
  }
  return $_response[$namespace];
}

/**
 * Factory function for query objects.
 *
 * @param $keys
 *   The string that a user would type into the search box. Suitable input
 *   may come from search_get_keys().
 *
 * @param $filters
 *   Key and value pairs that are applied as a filter query.
 *
 * @param $solrsort
 *   Visible string telling solr how to sort.
 *
 * @param $base_path
 *   The search base path (without the keywords) for this query.
 */
function apachesolr_drupal_query($keys = '', $filters = '', $solrsort = '', $base_path = '') {
  list($module, $class) = variable_get('apachesolr_query_class', array(
    'apachesolr',
    'Solr_Base_Query',
  ));
  module_load_include('php', $module, $class);
  try {
    $query = new $class(apachesolr_get_solr(), $keys, $filters, $solrsort, $base_path);
  } catch (Exception $e) {
    watchdog('Apache Solr', nl2br(check_plain($e
      ->getMessage())), NULL, WATCHDOG_ERROR);
    $query = NULL;
  }
  return $query;
}

/**
 * Static getter/setter for the current query
 *
 * @todo reverse the order of parameters in future branches.
 */
function apachesolr_current_query($query = NULL, $namespace = 'apachesolr_search') {
  static $saved_query = array();
  if (is_object($query)) {
    $saved_query[$namespace] = clone $query;
  }
  return is_object($saved_query[$namespace]) ? clone $saved_query[$namespace] : NULL;
}

/**
 * array('index_type' => 'integer',
 *        'multiple' => TRUE,
 *        'name' => 'fieldname',
 *        ),
 */
function apachesolr_index_key($field) {
  switch ($field['index_type']) {
    case 'text':
      $type_prefix = 't';
      break;
    case 'string':
      $type_prefix = 's';
      break;
    case 'integer':
      $type_prefix = 'i';
      break;
    case 'sint':
      $type_prefix = 'si';
      break;
    case 'double':
      $type_prefix = 'p';

      // reserve d for date
      break;
    case 'boolean':
      $type_prefix = 'b';
      break;
    case 'date':
      $type_prefix = 'd';
      break;
    case 'float':
      $type_prefix = 'f';
      break;
    case 'tdate':
      $type_prefix = 'td';
      break;
    case 'tint':
      $type_prefix = 'ti';
      break;
    case 'tlong':
      $type_prefix = 'tl';
      break;
    case 'tfloat':
      $type_prefix = 'tf';
      break;
    case 'tdouble':
      $type_prefix = 'tp';
      break;
    default:
      $type_prefix = 's';
  }
  $sm = $field['multiple'] ? 'm_' : 's_';
  return $type_prefix . $sm . $field['name'];
}

/**
 * Try to map a schema field name to a human-readable description.
 */
function apachesolr_field_name_map($field_name) {
  require_once drupal_get_path('module', 'apachesolr') . '/apachesolr.admin.inc';
  return _apachesolr_field_name_map($field_name);
}

/**
 * Invokes hook_apachesolr_cck_fields_alter() to find out how to handle CCK fields.
 */
function apachesolr_cck_fields() {
  static $fields;
  if (!isset($fields)) {
    $fields = array();

    // If CCK isn't enabled, do nothing.
    if (module_exists('content')) {
      module_load_include('inc', 'content', 'includes/content.crud');
      $cck_field_instances = content_field_instance_read();

      // A single default mapping for all text fields using optionwidgets.
      $mappings['text'] = array(
        'optionwidgets_select' => array(
          'callback' => '',
          'index_type' => 'string',
          'facets' => TRUE,
        ),
        'optionwidgets_buttons' => array(
          'callback' => '',
          'index_type' => 'string',
          'facets' => TRUE,
        ),
        'optionwidgets_onoff' => array(
          'callback' => '',
          'index_type' => 'string',
          'facets' => TRUE,
        ),
      );

      // A single default mapping for all integer fields using optionwidgets.
      $mappings['number_integer'] = array(
        'optionwidgets_select' => array(
          'callback' => '',
          'index_type' => 'sint',
          'facets' => TRUE,
        ),
        'optionwidgets_buttons' => array(
          'callback' => '',
          'index_type' => 'sint',
          'facets' => TRUE,
        ),
        'optionwidgets_onoff' => array(
          'callback' => '',
          'index_type' => 'sint',
          'facets' => TRUE,
        ),
      );

      // A field-specific mapping would look like:
      // $mappings['per-field']['field_model_name'] = array('callback' => '', 'index_type' => 'string', 'facets' => TRUE);
      // or
      // $mappings['per-field']['field_model_price'] = array('callback' => '', 'index_type' => 'float', 'facets' => TRUE);
      // Allow other modules to add or alter mappings.
      drupal_alter('apachesolr_cck_fields', $mappings);
      foreach ($cck_field_instances as $instance) {
        $field_type = $instance['type'];
        $field_name = $instance['field_name'];
        $widget_type = $instance['widget']['type'];

        // Only deal with fields that have index mappings and that have not been marked for exclusion.
        if ((isset($mappings[$field_type][$widget_type]) || isset($mappings['per-field'][$field_name])) && empty($instance['display_settings'][NODE_BUILD_SEARCH_INDEX]['exclude'])) {
          if (isset($mappings['per-field'][$field_name])) {
            $instance['index_type'] = $mappings['per-field'][$field_name]['index_type'];
            $instance['callback'] = $mappings['per-field'][$field_name]['callback'];

            // Default 'facets' to TRUE for backwards compatibility.
            $instance['facets'] = isset($mappings['per-field'][$field_name]['facets']) ? $mappings['per-field'][$field_name]['facets'] : TRUE;
          }
          else {
            $instance['index_type'] = $mappings[$field_type][$widget_type]['index_type'];
            $instance['callback'] = $mappings[$field_type][$widget_type]['callback'];

            // Default 'facets' to TRUE for backwards compatibility.
            $instance['facets'] = isset($mappings[$field_type][$widget_type]['facets']) ? $mappings[$field_type][$widget_type]['facets'] : TRUE;
          }
          $instance['multiple'] = (bool) $instance['multiple'];
          $instance['name'] = 'cck_' . $field_name;
          if (isset($fields[$field_name]) && is_array($fields[$field_name])) {

            // Merge together settings when used for multiple node types.
            $fields[$field_name] = array_merge($fields[$field_name], $instance);
          }
          else {
            $fields[$field_name] = $instance;
          }
          $fields[$field_name]['content_types'][] = $instance['type_name'];
          unset($fields[$field_name]['type_name']);
        }
      }
    }
  }
  return $fields;
}

/**
 * Implementation of hook_theme().
 */
function apachesolr_theme() {
  return array(
    /**
     * Returns a link for a facet term, with the number (count) of results for that term
     */
    'apachesolr_facet_link' => array(
      'arguments' => array(
        'facet_text' => NULL,
        'path' => NULL,
        'options' => NULL,
        'count' => NULL,
        'active' => FALSE,
        'num_found' => NULL,
      ),
    ),
    /**
     * Returns a link to remove a facet filter from the current search.
     */
    'apachesolr_unclick_link' => array(
      'arguments' => array(
        'facet_text' => NULL,
        'path' => NULL,
        'options' => NULL,
      ),
    ),
    /**
     * Returns a list of links from the above functions (apachesolr_facet_item and apachesolr_unclick_link)
     */
    'apachesolr_facet_list' => array(
      'arguments' => array(
        'items' => NULL,
        'display_limit' => NULL,
      ),
    ),
    /**
     * Returns a list of links generated by apachesolr_sort_link
     */
    'apachesolr_sort_list' => array(
      'arguments' => array(
        'items' => NULL,
      ),
    ),
    /**
     * Returns a link which can be used to search the results.
     */
    'apachesolr_sort_link' => array(
      'arguments' => array(
        'text' => NULL,
        'path' => NULL,
        'options' => NULL,
        'active' => FALSE,
        'direction' => '',
      ),
    ),
    /**
     * Returns a list of results (docs) in content recommendation block
     */
    'apachesolr_mlt_recommendation_block' => array(
      'arguments' => array(
        'docs' => NULL,
        'delta' => NULL,
      ),
    ),
  );
}

/**
 * Performs a moreLikeThis query using the settings and retrieves documents.
 *
 * @param $settings
 *   An array of settings.
 * @param $id
 *   The Solr ID of the document for which you want related content.
 *   For a node that is apachesolr_document_id($node->nid)
 *
 * @return An array of response documents, or NULL
 */
function apachesolr_mlt_suggestions($settings, $id) {
  try {
    $solr = apachesolr_get_solr();
    $fields = array(
      'mlt_mintf' => 'mlt.mintf',
      'mlt_mindf' => 'mlt.mindf',
      'mlt_minwl' => 'mlt.minwl',
      'mlt_maxwl' => 'mlt.maxwl',
      'mlt_maxqt' => 'mlt.maxqt',
      'mlt_boost' => 'mlt.boost',
      'mlt_qf' => 'mlt.qf',
    );
    $params = array(
      'qt' => 'mlt',
      'fl' => 'nid,title,path,url',
      'mlt.fl' => implode(',', $settings['mlt_fl']),
    );
    foreach ($fields as $form_key => $name) {
      if (!empty($settings[$form_key])) {
        $params[$name] = $settings[$form_key];
      }
    }
    $query = apachesolr_drupal_query('id:' . $id);

    // This hook allows modules to modify the query and params objects.
    apachesolr_modify_query($query, $params, 'apachesolr_mlt');
    if (empty($query)) {
      return;
    }
    $response = $solr
      ->search($query
      ->get_query_basic(), 0, $settings['num_results'], $params);
    if ($response->response) {
      $docs = (array) end($response->response);
      return $docs;
    }
  } catch (Exception $e) {
    watchdog('Apache Solr', nl2br(check_plain($e
      ->getMessage())), NULL, WATCHDOG_ERROR);
  }
}

/**
 * Implementation of hook_form_[form_id]_alter().
 *
 * Make sure to flush cache when content types are changed.
 */
function apachesolr_form_node_type_form_alter(&$form, $form_state) {
  $form['#submit'][] = 'apachesolr_clear_cache';
}

/**
 * Implementation of hook_form_[form_id]_alter().
 *
 * Make sure to flush cache when fields are added.
 */
function apachesolr_form_content_field_overview_form_alter(&$form, $form_state) {
  $form['#submit'][] = 'apachesolr_clear_cache';
}

/**
 * Implementation of hook_form_[form_id]_alter().
 *
 * Make sure to flush cache when fields are updated.
 */
function apachesolr_form_content_field_edit_form_alter(&$form, $form_state) {
  $form['#submit'][] = 'apachesolr_clear_cache';
}

/**
 * Implementation of hook_form_[form_id]_alter
 */
function apachesolr_form_block_admin_display_form_alter(&$form) {
  foreach ($form as $key => $block) {
    if (strpos($key, "apachesolr_mlt-") === 0 && $block['module']['#value'] == 'apachesolr') {
      $form[$key]['delete'] = array(
        '#value' => l(t('delete'), 'admin/settings/apachesolr/mlt/delete_block/' . $block['delta']['#value']),
      );
    }
  }
}

/**
 * Implementation of hook_form_[form_id]_alter().
 *
 * Hide the core 'title' field in favor of our 'name' field..
 */
function apachesolr_form_block_admin_configure_alter(&$form, $form_state) {
  if ($form['module']['#value'] == 'apachesolr' && $form['delta']['#value'] != 'sort') {
    $form['block_settings']['title']['#access'] = FALSE;
  }
}

/**
 * Returns a list of blocks. Used by hook_block
 */
function apachesolr_mlt_list_blocks() {
  $blocks = variable_get('apachesolr_mlt_blocks', array());
  foreach ($blocks as $delta => $settings) {
    $blocks[$delta] += array(
      'info' => t('Apache Solr recommendations: !name', array(
        '!name' => $settings['name'],
      )),
      'cache' => BLOCK_CACHE_PER_PAGE,
    );
  }
  return $blocks;
}
function apachesolr_mlt_load_block($delta) {
  $blocks = variable_get('apachesolr_mlt_blocks', array());
  return isset($blocks[$delta]) ? $blocks[$delta] : FALSE;
}
function theme_apachesolr_mlt_recommendation_block($docs, $delta) {
  $links = array();
  foreach ($docs as $result) {

    // Suitable for single-site mode.
    $links[] = l($result->title, $result->path, array(
      'html' => TRUE,
    ));
  }
  return theme('item_list', $links);
}
function theme_apachesolr_facet_link($facet_text, $path, $options = array(), $count, $active = FALSE, $num_found = NULL) {
  $options['attributes']['class'][] = 'apachesolr-facet';
  if ($active) {
    $options['attributes']['class'][] = 'active';
  }
  $options['attributes']['class'] = implode(' ', $options['attributes']['class']);
  return apachesolr_l($facet_text . " ({$count})", $path, $options);
}

/**
 * A replacement for l()
 *  - doesn't add the 'active' class
 *  - retains all $_GET parameters that ApacheSolr 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 apachesolr_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']);

  // Double encode + characters for clean URL Apache quirks.
  if (variable_get('clean_url', '0')) {
    $path = str_replace('+', '%2B', $path);
  }

  // Retain GET parameters that ApacheSolr knows nothing about.
  $query = apachesolr_current_query();
  $get = array_diff_key($_GET, array(
    'q' => 1,
    'page' => 1,
  ), $options['query'], $query
    ->get_url_queryvalues());
  $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_apachesolr_unclick_link($facet_text, $path, $options = array()) {
  if (empty($options['html'])) {
    $facet_text = check_plain(html_entity_decode($facet_text));
  }
  else {

    // Don't pass this option as TRUE into apachesolr_l().
    unset($options['html']);
  }
  $options['attributes']['class'] = 'apachesolr-unclick';
  return apachesolr_l("(-)", $path, $options) . ' ' . $facet_text;
}
function theme_apachesolr_sort_link($text, $path, $options = array(), $active = FALSE, $direction = '') {
  $icon = '';
  if ($direction) {
    $icon = ' ' . theme('tablesort_indicator', $direction);
  }
  if ($active) {
    if (isset($options['attributes']['class'])) {
      $options['attributes']['class'] .= ' active';
    }
    else {
      $options['attributes']['class'] = 'active';
    }
  }
  return $icon . apachesolr_l($text, $path, $options);
}
function theme_apachesolr_facet_list($items, $display_limit = 0) {

  // theme('item_list') expects a numerically indexed array.
  $items = array_values($items);

  // If there is a limit and the facet count is over the limit, hide the rest.
  if ($display_limit > 0 && count($items) > $display_limit) {

    // Show/hide extra facets.
    drupal_add_js(drupal_get_path('module', 'apachesolr') . '/apachesolr.js');

    // Split items array into displayed and hidden.
    $hidden_items = array_splice($items, $display_limit);
    foreach ($hidden_items as $hidden_item) {
      if (!is_array($hidden_item)) {
        $hidden_item = array(
          'data' => $hidden_item,
        );
      }
      $hidden_item['class'] = isset($hidden_item['class']) ? $hidden_item['class'] . ' apachesolr-hidden-facet' : 'apachesolr-hidden-facet';
      $items[] = $hidden_item;
    }
  }
  $admin_link = '';
  if (user_access('administer search')) {
    $admin_link = l(t('Configure enabled filters'), 'admin/settings/apachesolr/enabled-filters');
  }
  return theme('item_list', $items) . $admin_link;
}
function theme_apachesolr_sort_list($items) {

  // theme('item_list') expects a numerically indexed array.
  $items = array_values($items);
  return theme('item_list', $items);
}

/**
 * The interface for all 'query' objects.
 */
interface Drupal_Solr_Query_Interface {

  /**
   * Get all filters, or the subset of filters for one field.
   *
   * @param $name
   *   Optional name of a Solr field.
   */
  function get_filters($name = NULL);

  /**
   * Checks to see if a specific filter is already present.
   *
   * @param string $field
   * the facet field to check
   *
   * @param string $value
   * The facet value to check against
   */
  function has_filter($field, $value);

  /**
   * Add a filter to a query.
   *
   * @param string $field
   *   the facet field to apply to this query
   *
   * @param string value
   *   the value of the facet to apply
   *
   * @param boolean $exclude
   *   Optional paramter.  If TRUE, the filter will be negative,
   *   meaning that matching values will be excluded from the
   *   result set.
   */
  function add_filter($field, $value, $exclude = FALSE);

  /**
   * Remove a filter from the query.
   *
   * @param string $field
   * the facet field to remove
   *
   * @param string $value
   * The facet value to remove
   * This value can be NULL
   */
  function remove_filter($field, $value = NULL);

  /**
   * Get the query's keywords.
   */
  function get_keys();

  /**
   * Set the query's keywords.
   *
   * @param string $keys
   *   The new keywords.
   */
  function set_keys($keys);

  /**
   * Removes the query's keywords.
   */
  function remove_keys();

  /**
   * Return the search path (including the search keywords).
   */
  function get_path();

  /**
   * Return filters and sort in a form suitable for a query param to url().
   */
  function get_url_queryvalues();

  /**
   * Return the basic string query.
   */
  function get_query_basic();

  /**
   * Return the sorts that are provided by the query object.
   *
   * @return array all the sorts provided
   */
  function get_available_sorts();

  /**
   * Make a sort available.
   */
  function set_available_sort($field, $sort);

  /**
   * Get the solrsort.
   *
   * Returns the non-urlencode, non-aliased sort field and direction.
   * as an array keyed with '#name' and '#direction'.
   */
  function get_solrsort();

  /**
   * Set the solrsort.
   *
   * @param $field
   *  The name of a field in the solr index that's an allowed sort.
   *
   * @param $direction
   *  'asc' or 'desc'
   */
  function set_solrsort($field, $direction);

  /**
   * Add a subquery to the query.
   *
   * @param Drupal_Solr_Query_Interface $query
   *   The query to add to the orginal query - may have keywords or filters.
   *
   * @param string $fq_operator
   *   The operator to use within the filter part of the subquery
   *
   * @param string $q_operator
   *   The operator to use in joining the subquery to
   *   the main keywords.  Note - this is unlikely to work
   *   with the Dismax handler when the main query is only
   *   keywords.
   */
  function add_subquery(Drupal_Solr_Query_Interface $query, $fq_operator = 'OR', $q_operator = 'AND');

  /**
   * Remove a specific subquery.
   *
   * @param Drupal_Solr_Query_Interface $query
   * the query to remove
   */
  function remove_subquery(Drupal_Solr_Query_Interface $query);

  /**
   * Remove all subqueries.
   */
  function remove_subqueries();

}

/** 
 * Wrapper function for tt() if i18nstrings enabled.
 */
function apachesolr_tt($name, $string, $langcode = NULL, $update = FALSE) {
  if (module_exists('i18nstrings')) {
    return tt($name, $string, $langcode, $update);
  }
  else {
    return $string;
  }
}

Functions

Namesort descending Description
apachesolr_block Implementation of hook_block().
apachesolr_cck_fields Invokes hook_apachesolr_cck_fields_alter() to find out how to handle CCK fields.
apachesolr_clear_cache A wrapper for cache_clear_all to be used as a submit handler on forms that require clearing Luke cache etc.
apachesolr_clear_last_index Clear a specific namespace's last changed and nid, or clear all.
apachesolr_comment Implementation of hook_comment().
apachesolr_content_fieldapi Implementation of hook_content_fieldapi().
apachesolr_cron Implementation of hook_cron().
apachesolr_current_query Static getter/setter for the current query
apachesolr_date_determine_gap Determine the best search gap to use for an arbitrary date range.
apachesolr_date_facet_block Helper function for displaying a date facet block.
apachesolr_date_find_query_gap Determine the gap in a date range query filter that we generated.
apachesolr_date_format_iso_by_gap Format an ISO date string based on the gap used to generate it.
apachesolr_date_format_range Format the beginning of a date range query filter that we generated.
apachesolr_date_gap_drilldown Return the next smaller date gap.
apachesolr_date_iso Convert date from timestamp into ISO 8601 format. http://lucene.apache.org/solr/api/org/apache/solr/schema/DateField.html
apachesolr_delete_node_from_index
apachesolr_document_id Generate a unique ID for an entity being indexed.
apachesolr_do_query Execute a search based on a query object.
apachesolr_drupal_query Factory function for query objects.
apachesolr_facetcount_form Used by the 'configure' $op of hook_block so that modules can generically set facet limits on their blocks.
apachesolr_facetcount_save Used by the 'save' $op of hook_block so that modules can generically set facet limits on their blocks.
apachesolr_facet_block Helper function for displaying a facet block.
apachesolr_failure Determines Apache Solr'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.
apachesolr_field_name_map Try to map a schema field name to a human-readable description.
apachesolr_flush_caches Implementation of hook_flush_caches().
apachesolr_form_block_admin_configure_alter Implementation of hook_form_[form_id]_alter().
apachesolr_form_block_admin_display_form_alter Implementation of hook_form_[form_id]_alter
apachesolr_form_content_field_edit_form_alter Implementation of hook_form_[form_id]_alter().
apachesolr_form_content_field_overview_form_alter Implementation of hook_form_[form_id]_alter().
apachesolr_form_node_type_form_alter Implementation of hook_form_[form_id]_alter().
apachesolr_get_enabled_facets Return the enabled facets from the specified block array.
apachesolr_get_last_index Returns last changed and last nid for an indexing namespace.
apachesolr_get_nodes_to_index Returns an array of rows from a query based on an indexing namespace.
apachesolr_get_solr Factory method for solr singleton object. Structure allows for an arbitrary number of solr objects to be used based on the host, port, path combination. Get an instance like this: $solr = apachesolr_get_solr();
apachesolr_has_searched Semaphore that indicates whether a search has been done. Blocks use this later to decide whether they should load or not.
apachesolr_index_key array('index_type' => 'integer', 'multiple' => TRUE, 'name' => 'fieldname', ),
apachesolr_index_nodes Function to handle the indexing of nodes.
apachesolr_index_status Helper function for modules implmenting hook_search's 'status' op.
apachesolr_index_updated Helper function to keep track of when the index has been updated.
apachesolr_init Implementation of hook_init().
apachesolr_l A replacement for l()
apachesolr_mark_node Mark one node as needing re-indexing.
apachesolr_mark_node_type Mark all nodes of one type as needing re-indexing.
apachesolr_menu Implementation of hook_menu().
apachesolr_mlt_list_blocks Returns a list of blocks. Used by hook_block
apachesolr_mlt_load_block
apachesolr_mlt_suggestions Performs a moreLikeThis query using the settings and retrieves documents.
apachesolr_modify_query This hook allows modules to modify the query and params objects.
apachesolr_nodeapi Implementation of hook_nodeapi().
apachesolr_node_type Implementation of hook_node_type().
apachesolr_pager_init Initialize a pager for theme('pager') without running an SQL query.
apachesolr_rebuild_index_table Truncate and rebuild the apachesolr_search_node table, reset the apachesolr_index_last variable. This is the most complete way to force reindexing, or to build the indexing table for the first time.
apachesolr_save_enabled_facets Save the enabled facets for all modules.
apachesolr_save_module_facets Save the enabled facets for one module.
apachesolr_set_stats_message Call drupal_set_message() with the text.
apachesolr_site_hash Like $site_key in _update_refresh() - returns a site-specific hash.
apachesolr_static_response_cache 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.
apachesolr_taxonomy Implementation of hook_taxonomy().
apachesolr_theme Implementation of hook_theme().
apachesolr_tt Wrapper function for tt() if i18nstrings enabled.
apachesolr_user Implementation of hook_user().
theme_apachesolr_facet_link
theme_apachesolr_facet_list
theme_apachesolr_mlt_recommendation_block
theme_apachesolr_sort_link
theme_apachesolr_sort_list
theme_apachesolr_unclick_link
_apachesolr_exclude_types
_apachesolr_nodeapi_delete Helper function for hook_nodeapi().
_apachesolr_nodeapi_update Helper function for hook_nodeapi().

Constants

Namesort descending Description
APACHESOLR_API_VERSION
APACHESOLR_READ_ONLY
APACHESOLR_READ_WRITE @file Integration with the Apache Solr search application.

Interfaces

Namesort descending Description
Drupal_Solr_Query_Interface The interface for all 'query' objects.