You are here

apachesolr.module in Apache Solr Search 5

Integration with the Apache Solr search application.

File

apachesolr.module
View source
<?php

/**
 * @file
 *   Integration with the Apache Solr search application.
 */

/**
 * Implementation of hook_menu().
 */
function apachesolr_menu($may_cache) {
  $items = array();
  if ($may_cache) {
    $items[] = array(
      'path' => 'admin/settings/apachesolr',
      'title' => t('Apache Solr'),
      'description' => t('Administer Apache Solr.'),
      'callback' => 'drupal_get_form',
      'callback arguments' => 'apachesolr_settings',
      'access' => user_access('administer search'),
    );
    $items[] = array(
      'path' => 'admin/settings/apachesolr/settings',
      'title' => t('Settings'),
      'weight' => -10,
      'callback' => 'drupal_get_form',
      'callback arguments' => 'apachesolr_settings',
      'access' => user_access('administer search'),
      'type' => MENU_DEFAULT_LOCAL_TASK,
    );
    $items[] = array(
      'path' => 'admin/settings/apachesolr/index',
      'title' => t('Search index'),
      'weight' => -8,
      'callback' => 'apachesolr_index_page',
      'access' => user_access('administer search'),
      'type' => MENU_LOCAL_TASK,
    );
  }
  return $items;
}

/**
 * Implementation of hook_help().
 */
function apachesolr_help($section) {
  switch ($section) {
    case 'admin/settings/apachesolr/index':

      // Collect some stats (from search.module)
      $remaining = 0;
      $total = 0;
      foreach (module_list() as $module) {
        if (module_hook($module, 'search')) {
          $status = module_invoke($module, 'search', 'status');
          $remaining += $status['remaining'];
          $total += $status['total'];
        }
      }
      return t('Apache Solr search index is generated by !cron. %percentage of the site has been indexed. There @items left to index.', array(
        '!cron' => l(t('running cron'), 'admin/logs/status/run-cron', array(), 'destination=admin/settings/apachesolr/index'),
        '%percentage' => (int) min(100, 100 * ($total - $remaining) / max(1, $total)) . '%',
        '@items' => format_plural($remaining, t('is 1 item'), t('are @count items')),
      ));
  }
}
function apachesolr_settings() {
  $form = array();

  //perform a check to ensure the server is there
  $requirements = apachesolr_requirements('runtime');
  $status = $requirements['apachesolr']['severity'] == 2 ? 'error' : 'status';
  drupal_set_message($requirements['apachesolr']['value'], $status);
  $form['apachesolr_host'] = array(
    '#type' => 'textfield',
    '#title' => t('Solr host name'),
    '#default_value' => variable_get('apachesolr_host', 'localhost'),
    '#description' => t('Host name of your Solr server, e.g. <code>localhost</code> or <code>example.com</code>.'),
  );
  $form['apachesolr_port'] = array(
    '#type' => 'textfield',
    '#title' => t('Solr port'),
    '#default_value' => variable_get('apachesolr_port', '8983'),
    '#description' => t('Port on which the Solr server listens. Tomcat is 8080 by default.'),
  );
  $form['apachesolr_path'] = array(
    '#type' => 'textfield',
    '#title' => t('Solr path'),
    '#default_value' => variable_get('apachesolr_path', '/solr'),
    '#description' => t('Path that identifies the Solr request handler to be used. Leave this as /solr for now.'),
  );
  $options = array();
  foreach (array(
    5,
    10,
    15,
    20,
    25,
    30,
    40,
    50,
    60,
    70,
    80,
    90,
    100,
  ) as $option) {
    $options[$option] = $option;
  }
  $form['apachesolr_rows'] = array(
    '#type' => 'select',
    '#title' => t('Results per page'),
    '#default_value' => variable_get('apachesolr_rows', 10),
    '#options' => $options,
    '#description' => t('The number of results that will be shown per page.'),
  );
  $form['apachesolr_failure'] = array(
    '#type' => 'select',
    '#title' => t('On failure'),
    '#options' => array(
      'show_error' => t('Show error'),
      'show_drupal_results' => t('Show core Drupal results'),
      'show_no_results' => t('Show no results'),
    ),
    '#default_value' => variable_get('apachesolr_failure', 'show_error'),
    '#description' => t('What to display if ApacheSolr search is not available.'),
  );
  return system_settings_form($form);
}

/**
 * Determines ApacheSolr'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;
  }
}

/**
 * Implementation of hook_requirements().
 */
function apachesolr_requirements($phase) {

  // Ensure translations don't break at install time
  $t = get_t();
  if ($phase == 'runtime') {
    $host = variable_get('apachesolr_host', 'localhost');
    $port = variable_get('apachesolr_port', 8983);
    $path = variable_get('apachesolr_path', '/solr');
    $ping = FALSE;
    try {
      $solr =& apachesolr_get_solr($host, $port, $path);
      $ping = @$solr
        ->ping();

      // If there is no $solr object, there is no server available, so don't continue.
      if (!$ping) {
        throw new Exception(t('No Solr instance available during indexing'));
      }
    } catch (Exception $e) {
      watchdog('Apache Solr', $e
        ->getMessage(), WATCHDOG_ERROR);
    }
    $value = $ping ? $t('Solr can be pinged.') : $t('No Solr instance is available.');
    $severity = $ping ? 0 : 2;
    $description = theme('item_list', array(
      $t('Host: %host', array(
        '%host' => $host,
      )),
      $t('Port: %port', array(
        '%port' => $port,
      )),
      $t('Path: %path', array(
        '%path' => $path,
      )),
    ));
    $requirements['apachesolr'] = array(
      'title' => $t('ApacheSolr'),
      'value' => $value,
      'description' => $description,
      'severity' => $severity,
    );
    return $requirements;
  }
}
function apachesolr_index_page() {

  // Gets information about the fields already in solr index.
  include_once drupal_get_path('module', 'apachesolr') . '/Solr_Base_Query.php';
  $fields = Solr_Base_Query::get_fields_in_index();
  $rows = array();
  foreach ($fields as $name => $field) {
    $rows[] = array(
      $name,
      $field->type,
    );
  }
  $output = '';

  // Display the table of Field names and Field Index Types.
  $output .= theme('table', array(
    t('Field name'),
    t('Field index type'),
  ), $rows);

  // Display the Delete Index form.
  $output .= drupal_get_form('apachesolr_delete_index_form');
  return $output;
}

/**
 * Create a form for deleting the contents of the Solr index.
 */
function apachesolr_delete_index_form() {
  $form = array();
  $form['markup'] = array(
    '#type' => 'markup',
    '#value' => '<h3>Solr Index</h3>',
  );
  $form['delete_index'] = array(
    '#type' => 'checkbox',
    '#title' => t('Delete all documents'),
    '#description' => t('This option deletes all of the documents in the Solr index. You would do this if the index contains wrong content that you need to purge. This action shouldn\'t be necessary in normal cases. After deleting you will need to rebuild the index by running cron.'),
    '#default_value' => NULL,
  );
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Delete the index'),
  );
  return $form;
}
function apachesolr_delete_index_form_validate($form, $form_values) {
  if (!$form_values['delete_index']) {
    form_set_error('delete_index', t('If you want to delete the Solr index, you must check the confirmation box.'));
  }
  if (!user_access('administer site configuration')) {
    drupal_access_denied();
  }
}
function apachesolr_delete_index_form_submit($form, $form_values) {
  if ($form_values['delete_index']) {
    try {

      // Instantiate a new Solr object.
      $solr =& apachesolr_get_solr(variable_get('apachesolr_host', 'localhost'), variable_get('apachesolr_port', 8983), variable_get('apachesolr_path', '/solr'));

      // TODO: Add site so that you can only delete your site's content.
      $solr
        ->deleteByQuery('*:*');
      $solr
        ->commit();
      variable_del('apachesolr_last_change');
      variable_del('apachesolr_last_id');

      // This form can't be seen by anyone without 'administer site configuration'
      // permission, so no need to check perms before displaying a run-cron link.
      drupal_set_message(t('The Solr content index has been erased. You must now !run_cron until your entire site has been re-indexed.', array(
        '!run_cron' => l(t('run cron'), 'admin/logs/status/run-cron', array(
          'fragment' => 'module-user',
        )),
      )));
    } catch (Exception $e) {
      watchdog('Apache Solr', $e
        ->getMessage(), WATCHDOG_ERROR);
    }
  }
}

/**
 * The point of this class is to manage the update index needs of multiple
 * search modules. Each one needs to track its own list of nodes that need
 * updating.
 */
class ApacheSolrUpdate {
  public static $_namespaces = array();
  static function reset($namespace) {
    variable_del($namespace . '_last_change');
    variable_del($namespace . '_last_id');
  }
  static function get_change($namespace) {
    $var = variable_get($namespace . '_last_change', 0);
    return $var;
  }
  static function get_last($namespace) {
    $var = variable_get($namespace . '_last_id', 0);
    return $var;
  }

  /**
   * Function to generically handle the fetching of nodes that need indexing on a cron run.
   * It takes a namespace which needs to be unique to the calling module and manages
   * all of the global variables and the shutdown function so that every search
   * implementation can have its own without needing to duplicate the query.
   * Returns a db_query $result.
   * Modules need to then call apache_update_success after each node is successfully
   * indexed.
   */
  static function getNodesToIndex($namespace) {
    register_shutdown_function('apachesolr_shutdown');
    $cron_change = self::get_change($namespace);
    $cron_last = self::get_last($namespace);
    $cron_limit = variable_get('search_cron_limit', 100);
    $result = db_query_range('SELECT GREATEST(IF(c.last_comment_timestamp IS NULL, 0, c.last_comment_timestamp), n.changed) as last_change, n.nid ' . 'FROM {node} n LEFT JOIN {node_comment_statistics} c ON n.nid = c.nid ' . 'WHERE n.status = 1 ' . 'AND ((GREATEST(IF(c.last_comment_timestamp IS NULL , 0, c.last_comment_timestamp ), n.changed) = %d AND n.nid > %d) OR n.changed > %d OR c.last_comment_timestamp > %d) ' . 'ORDER BY last_change ASC, n.nid ASC', $cron_change, $cron_last, $cron_change, $cron_change, 0, $cron_limit);
    return $result;
  }
  static function success($namespace, $last_change, $last_id) {
    self::$_namespaces[$namespace] = array(
      'last_change' => $last_change,
      'last_id' => $last_id,
    );
  }
  static function update_index($namespace) {
    $solr = FALSE;
    try {

      // Get the $solr object
      $solr =& apachesolr_get_solr(variable_get('apachesolr_host', 'localhost'), variable_get('apachesolr_port', 8983), variable_get('apachesolr_path', '/solr'));

      // If there is no $solr object, there is no server available, so don't continue.
      if (!$solr
        ->ping()) {
        throw new Exception(t('No Solr instance available during indexing'));
      }
    } catch (Exception $e) {
      watchdog('Apache Solr', $e
        ->getMessage(), WATCHDOG_ERROR);
      return;
    }

    // Get CCK fields list
    $cck_fields = apachesolr_cck_fields();
    $result = self::getNodesToIndex($namespace);
    $count = 0;
    $documents = array();
    while ($row = db_fetch_object($result)) {

      // Variables to track the last item changed.
      $solr_last_change = $row->last_change;
      $solr_last_id = $row->nid;

      // Set reset = TRUE to avoid static caching of all nodes that get indexed.
      $node = node_load($row->nid, NULL, TRUE);
      if ($node->nid) {

        // Build the node body.
        $node = node_build_content($node, FALSE, FALSE);
        $node->body = drupal_render($node->content);
        $text = check_plain($node->title) . ' ' . $node->body;

        // Fetch extra data normally not visible
        $extra = node_invoke_nodeapi($node, 'update index');
        foreach ($extra as $t) {
          $text .= $t;
        }

        // Update solr index.
        try {
          $document = new Apache_Solr_Document();
          $site = url(NULL, NULL, NULL, TRUE);
          $hash = md5($site);
          $document->site = $site;
          $document->hash = $hash;
          $document->url = url('node/' . $node->nid, NULL, NULL, TRUE);
          $document->nid = $node->nid;
          $document->uid = $node->uid;
          $document->title = $node->title;
          $document->body = $node->body;
          $document->type = $node->type;
          $document->changed = $node->changed;
          $document->comment_count = $node->comment_count;
          $document->name = $node->name;
          $document->language = $node->language;

          // Path aliases can have important information about the content.
          // Add them to the index as well.
          if (function_exists('drupal_get_path_alias')) {

            // Add any path alias to the index, looking first for language specific
            // aliases but using language neutral aliases otherwise.
            $language = empty($node->language) ? '' : $node->language;
            $path = 'node/' . $node->nid;
            $output = drupal_get_path_alias($path, $language);
            if ($output && $output != $path) {
              $document->path = $output;
              $text .= $output;
            }
          }
          foreach ($cck_fields as $key => $cck_info) {
            if (isset($node->{$key})) {

              // Got a CCK field. See if it is to be indexed.
              $function = $cck_info['callback'];
              if ($cck_info['callback'] && function_exists($function)) {
                $field = call_user_func_array($function, array(
                  $node,
                  $key,
                ));
              }
              else {
                $field = $node->{$key};
              }
              $index_key = apachesolr_index_key($cck_info);
              foreach ($field as $value) {

                // Don't index NULLs or empty strings
                if (isset($value['view']) && strlen($value['view'])) {
                  if ($cck_info['multiple']) {
                    $document
                      ->setMultiValue($index_key, $value['view']);
                  }
                  else {
                    $document->{$index_key} = $value['view'];
                  }
                }
              }
            }
          }

          // This is the string value of the title. Used for sorting.
          $document->stitle = $node->title;
          if (is_array($node->taxonomy)) {
            foreach ($node->taxonomy as $term) {

              // Double indexing of tids lets us do effecient searches (on tid)
              // and do accurate per-vocabulary faceting.
              // By including the ancestors to a term in the index we make
              // sure that searches for general categories match specific
              // categories, e.g. Fruit -> apple, a search for fruit will find
              // content categorized with apple.
              $ancestors = taxonomy_get_parents_all($term->tid);
              foreach ($ancestors as $ancestor) {
                $document
                  ->setMultiValue('tid', $ancestor->tid);
                $document
                  ->setMultiValue('imfield_vid' . $ancestor->vid, $ancestor->tid);
                $document
                  ->setMultiValue('vid', $ancestor->vid);
                $document
                  ->setMultiValue('taxonomy_name', $ancestor->name);
                $text .= ' ' . $ancestor->name;
              }
            }
          }
          $document->text = $text;

          // Let modules add to the document
          foreach (module_implements('apachesolr_update_index') as $module) {
            $function = $module . '_apachesolr_update_index';
            $function($document, $node);
          }
          $documents[] = $document;
        } catch (Exception $e) {
          watchdog('Apache Solr', $e
            ->getMessage(), WATCHDOG_ERROR);
        }
      }
      self::success('apachesolr', $solr_last_change, $solr_last_id);
    }
    if (is_object($solr) && count($documents) > 0) {
      try {
        watchdog('Apache Solr', t('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);
        }
        $solr
          ->commit();
        $solr
          ->optimize(FALSE, FALSE);
      } catch (Exception $e) {
        watchdog('Apache Solr', $e
          ->getMessage(), WATCHDOG_ERROR);
      }
    }
  }

}
function apachesolr_apachesolr_facets() {
  return array(
    'type',
  );
}

/**
 * Registered shutdown function.
 */
function apachesolr_shutdown() {
  foreach (ApacheSolrUpdate::$_namespaces as $namespace => $vars) {
    extract($vars);
    if ($last_change && $last_id) {
      variable_set("{$namespace}_last_change", $last_change);
      variable_set("{$namespace}_last_id", $last_id);
    }
  }
}

/**
 * Implementation of hook_nodeapi().
 */
function apachesolr_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
  switch ($op) {
    case 'delete':
      try {
        $solr =& apachesolr_get_solr(variable_get('apachesolr_host', 'localhost'), variable_get('apachesolr_port', 8983), variable_get('apachesolr_path', '/solr'));
        $solr
          ->deleteById(url('node/' . $node->nid, NULL, NULL, TRUE));
        $solr
          ->commit();
      } catch (Exception $e) {
        watchdog('Apache Solr', $e
          ->getMessage(), WATCHDOG_ERROR);
      }
      break;
  }
}

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

      // Sorting block
      $blocks['sort'] = array(
        'info' => t('ApacheSolr Core: Sorting'),
      );
      $blocks['type'] = array(
        'info' => t('ApacheSolr Core: Filter by type'),
      );
      return $blocks;
    case 'view':
      if (apachesolr_has_searched()) {

        // Get the query and response. Without these no blocks make sense.
        $response =& apachesolr_static_response_cache();
        if (empty($response)) {
          return;
        }
        $query =& apachesolr_drupal_query();

        // Get information needed by the rest of the blocks about limits.
        $facet_display_limits = variable_get('apachesolr_facet_query_limits', array());
        switch ($delta) {
          case 'sort':
            $sorts = array(
              'relevancy' => array(
                'name' => t('Relevancy'),
                'default' => 'asc',
              ),
              'stitle' => array(
                'name' => t('Title'),
                'default' => 'asc',
              ),
              'type' => array(
                'name' => t('Type'),
                'default' => 'asc',
              ),
              'name' => array(
                'name' => t('Author'),
                'default' => 'asc',
              ),
              'changed' => array(
                'name' => t('Date'),
                'default' => 'desc',
              ),
            );
            $solrsorts = array();
            $sort_parameter = isset($_GET['solrsort']) ? check_plain($_GET['solrsort']) : FALSE;
            foreach (explode(',', $sort_parameter) as $solrsort) {
              $parts = explode(' ', $solrsort);
              if (!empty($parts[0]) && !empty($parts[1])) {
                $solrsorts[$parts[0]] = $parts[1];
              }
            }
            $sort_links = array();
            $path = 'search/' . arg(1) . '/' . $query
              ->get_query();
            foreach ($sorts as $type => $sort) {
              $new_sort = isset($solrsorts[$type]) ? $solrsorts[$type] == 'asc' ? 'desc' : 'asc' : $sort['default'];
              $sort_links[] = theme('apachesolr_sort_link', $sort['name'], $path, $type == "relevancy" ? '' : "solrsort={$type} {$new_sort}", isset($solrsorts[$type]) ? $solrsorts[$type] : '');
            }
            return array(
              'subject' => t('Sort by'),
              'content' => theme('apachesolr_sort_list', $sort_links),
            );
          case 'type':
            $filter_by = t('Filter by type');
            return apachesolr_facet_block($response, $query, $delta, $filter_by, 'apachesolr_get_type');
          default:
            break;
        }
      }
      break;
    case 'configure':
      return apachesolr_facetcount_form($delta);
      break;
    case 'save':
      apachesolr_facetcount_save($delta, $edit);
      break;
  }
}
function apachesolr_facet_block($response, $query, $delta, $filter_by, $facet_callback = FALSE) {
  if (!empty($response->facet_counts->facet_fields->{$delta})) {
    $contains_active = FALSE;
    $items = array();
    foreach ($response->facet_counts->facet_fields->{$delta} as $facet => $count) {
      $unclick_link = '';
      unset($active);
      if ($facet_callback && function_exists($facet_callback)) {
        $facet_text = $facet_callback($facet);
      }
      else {
        $facet_text = $facet;
      }
      $new_query = clone $query;
      if ($active = $query
        ->has_field($delta, $facet)) {
        $contains_active = TRUE;
        $new_query
          ->remove_field($delta, $facet);
        $path = 'search/' . arg(1) . '/' . $new_query
          ->get_query();
        $unclick_link = theme('apachesolr_unclick_link', $path);
      }
      else {
        $new_query
          ->add_field($delta, $facet);
        $path = 'search/' . arg(1) . '/' . $new_query
          ->get_query();
      }
      $countsort = $count == 0 ? '' : 1 / $count;

      // if numdocs == 1 and !active, don't add.
      if ($response->numFound == 1 && !$active) {

        // skip
      }
      else {
        $items[$active ? $countsort . $facet : 1 + $countsort . $facet] = theme('apachesolr_facet_item', $facet_text, $count, $path, $active, $unclick_link, $response->numFound);
      }
    }
    if (count($items) > 0) {
      ksort($items);
      $facet_display_limit = isset($facet_display_limits[$delta]) ? $facet_display_limits[$delta] : 10;
      $items = array_slice($items, 0, $facet_display_limit == -1 ? NULL : $facet_display_limit);
      $output = theme('apachesolr_facet_list', $items);
      return array(
        'subject' => t('@filter_by', array(
          '@filter_by' => $filter_by,
        )),
        'content' => $output,
      );
    }
  }
  return NULL;
}

/**
 * Callback function for the 'Filter by type' facet block.
 */
function apachesolr_get_type($facet) {
  return node_get_types('name', $facet);
}

/**
 * Implementation of hook_form_alter().
 */
function apachesolr_form_alter($form_id, &$form) {
  $arg = arg(1);
  $alias = drupal_lookup_path('alias', "search/{$arg}");

  // Ok, this really sucks. I want a way to know whether the action of the form
  // is supposed to be handled by an ApacheSolr module or not. I manually
  // exclude node and user here, but there are many other hook_search implementations
  // in the wild and this code will potentially interfere with them. It also
  // creates a Solr instance wasting resources.
  if ($alias && $arg != 'node' && $arg != 'user' && (preg_match("&/search/{$arg}&", $form['#action']) || preg_match("&/{$alias}&", $form['#action']) || strpos($form['#action'], $alias))) {
    if (!isset($_POST['form_id'])) {

      // Set up our validation function
      $form['#validate']['apachesolr_search_validate'] = array();

      // if no keys, there's nothing to do.
      if (empty($form['basic']['inline']['keys']['#default_value'])) {
        return;
      }

      // The $query is the true source for search key information
      if ($query =& apachesolr_drupal_query()) {
        $form['basic']['inline']['keys']['#default_value'] = $query
          ->get_query_basic();
      }
    }
  }
}

/**
 * Used by the 'configure' $op of hook_block so that modules can generically set
 * facet limits on their blocks.
 */
function apachesolr_facetcount_form($delta) {
  $facet_query_limits = variable_get('apachesolr_facet_query_limits', array());

  // If the block is not 'sort' (and therefore is a facet block),
  // display facet limit option.
  $form['apachesolr_facet_query_limit'] = array(
    '#type' => 'textfield',
    '#title' => t('Facet Query Limit'),
    '#required' => TRUE,
    '#description' => t('The number of facet links to show in this block. Set to -1 for unlimited. Default is 10.'),
    '#default_value' => isset($facet_query_limits[$delta]) ? $facet_query_limits[$delta] : 10,
  );
  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($delta, $edit) {

  // Save query limits
  $facet_query_limits = variable_get('apachesolr_facet_query_limits', array());
  $facet_query_limits[$delta] = intval($edit['apachesolr_facet_query_limit']);
  variable_set('apachesolr_facet_query_limits', $facet_query_limits);
}

/**
 * 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();
 */
function &apachesolr_get_solr($host = 'localhost', $port = 8983, $path = '/solr') {
  static $solr_cache;
  if (empty($solr_cache[$host][$port][$path])) {
    $include_path = get_include_path();
    set_include_path('./' . drupal_get_path('module', 'apachesolr') . '/SolrPhpClient/');
    include_once 'Apache/Solr/Service.php';
    set_include_path($include_path);
    $solr_cache[$host][$port][$path] = new Apache_Solr_Service($host, $port, $path);
  }
  return $solr_cache[$host][$port][$path];
}

/**
 * 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.
 */
function &apachesolr_static_response_cache($response = NULL) {
  static $_response;
  if (!empty($response)) {
    $_response = drupal_clone($response);
  }
  return $_response;
}

/*
 * The query object is built from the keys. If you want to build queries
 * programmatically you can pass in different keys. If you don't pass in
 * any keys, search_get_keys() is used instead.
 */
function &apachesolr_drupal_query($keys = NULL, $reset = FALSE) {
  static $_queries;
  if ($reset) {
    unset($_queries);
  }
  if (empty($keys)) {
    $keys = search_get_keys();
  }
  if (empty($_queries) || empty($_queries[$keys])) {
    include_once drupal_get_path('module', 'apachesolr') . '/Solr_Base_Query.php';
    $_queries[$keys] = new Solr_Base_Query($keys);
  }
  return $_queries[$keys];
}
function apachesolr_base_url() {
  return "http://" . variable_get('apachesolr_host', 'localhost') . ':' . variable_get('apachesolr_port', '8983') . variable_get('apachesolr_path', '/solr');
}

/**
 * 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 '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;
    default:
      $type_prefix = 's';
  }
  $sm = $field['multiple'] ? 'm' : 's';
  return $type_prefix . $sm . $field['name'];
}

/**
 * This invokes the hook_apachesolr_cck_field_mappings to find out how to handle
 * CCK fields.
 */
function apachesolr_cck_fields() {
  static $_fields;

  // If CCK isn't enabled, do nothing.
  if (module_exists('content')) {
    $mappings = module_invoke_all('apachesolr_cck_field_mappings');
    if (is_null($_fields)) {
      $_fields = array();
      $result = db_query("SELECT i.field_name, f.multiple, f.type, i.widget_type FROM {node_field_instance} i INNER JOIN {node_field} f ON i.field_name = f.field_name;");
      while ($row = db_fetch_object($result)) {

        // Only deal with fields that have options widgets (facets don't make sense otherwise), or fields that have specific mappings.
        if ($row->type == 'text' && in_array($row->widget_type, array(
          'options_select',
          'options_buttons',
        )) || in_array($row->type, array_keys($mappings))) {
          $_fields[$row->field_name] = array(
            'name' => $row->field_name,
            'multiple' => $row->multiple ? TRUE : FALSE,
            'field_type' => $row->type,
            'index_type' => empty($mappings) ? 'string' : $mappings[$row->type]['index_type'],
            'callback' => empty($mappings[$row->type]['callback']) ? NULL : $mappings[$row->type]['callback'],
          );
        }
      }
    }
    return $_fields;
  }
  else {
    return array();
  }
}
function apachesolr_simpletest() {
  $dir = drupal_get_path('module', 'apachesolr') . '/tests';
  $tests = file_scan_directory($dir, '\\.test$');
  return array_keys($tests);
}
function theme_apachesolr_facet_item($name, $count, $path, $active = FALSE, $unclick_link = NULL, $num_found = NULL) {
  $attributes = array();
  if ($active) {
    $attributes['class'] = 'active';
  }
  if ($unclick_link) {
    return $unclick_link . ' ' . check_plain($name);
  }
  else {
    return l($name . " ({$count})", $path, $attributes, isset($_GET['solrsort']) ? "solrsort=" . check_plain($_GET['solrsort']) : FALSE);
  }
}
function theme_apachesolr_unclick_link($path) {
  return l("(-)", $path, NULL, isset($_GET['solrsort']) ? "solrsort=" . check_plain($_GET['solrsort']) : FALSE);
}
function theme_apachesolr_sort_link($text, $path, $query, $direction = NULL) {
  $icon = '';
  if ($direction) {
    $icon = theme('tablesort_indicator', $direction);
  }
  return $icon . ' ' . l($text, $path, NULL, $query);
}
function theme_apachesolr_facet_list($items) {
  return theme('item_list', $items);
}
function theme_apachesolr_sort_list($items) {
  return theme('item_list', $items);
}

/**
 * Return the human readable text for a content type.
 */
function theme_apachesolr_breadcrumb_type($type) {
  return node_get_types('name', $type);
}

Functions

Namesort descending Description
apachesolr_apachesolr_facets
apachesolr_base_url
apachesolr_block Implementation of hook_block().
apachesolr_cck_fields This invokes the hook_apachesolr_cck_field_mappings to find out how to handle CCK fields.
apachesolr_delete_index_form Create a form for deleting the contents of the Solr index.
apachesolr_delete_index_form_submit
apachesolr_delete_index_form_validate
apachesolr_drupal_query
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
apachesolr_failure Determines ApacheSolr'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_form_alter Implementation of hook_form_alter().
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_get_type Callback function for the 'Filter by type' facet block.
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_help Implementation of hook_help().
apachesolr_index_key array('index_type' => 'integer', 'multiple' => TRUE, 'name' => 'fieldname', ),
apachesolr_index_page
apachesolr_menu Implementation of hook_menu().
apachesolr_nodeapi Implementation of hook_nodeapi().
apachesolr_requirements Implementation of hook_requirements().
apachesolr_settings
apachesolr_shutdown Registered shutdown function.
apachesolr_simpletest
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.
theme_apachesolr_breadcrumb_type Return the human readable text for a content type.
theme_apachesolr_facet_item
theme_apachesolr_facet_list
theme_apachesolr_sort_link
theme_apachesolr_sort_list
theme_apachesolr_unclick_link

Classes

Namesort descending Description
ApacheSolrUpdate The point of this class is to manage the update index needs of multiple search modules. Each one needs to track its own list of nodes that need updating.