'Apache Solr',
'description' => 'Administer Apache Solr.',
'page callback' => 'drupal_get_form',
'page arguments' => array('apachesolr_settings'),
'access callback' => 'user_access',
'access arguments' => array('administer site configuration'),
);
$items['admin/settings/apachesolr/settings'] = array(
'title' => 'Settings',
'weight' => -10,
'access callback' => 'user_access',
'access arguments' => array('administer site configuration'),
'type' => MENU_DEFAULT_LOCAL_TASK,
);
$items['admin/settings/apachesolr/index'] = array(
'title' => 'Search index',
'page callback' => 'apachesolr_index_page',
'access callback' => 'user_access',
'access arguments' => array('administer site configuration'),
'weight' => -8,
'type' => MENU_LOCAL_TASK,
);
return $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. localhost
or example.com
.'),
);
$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();
$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(), NULL, 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;
}
}
/**
* Gets information about the fields already in solr index.
*/
function apachesolr_index_page() {
try {
$solr = apachesolr_get_solr();
// Note: we use 2 since 1 fails on Ubuntu Hardy.
$data = $solr->getLuke(2);
}
catch (Exception $e) {
watchdog('apachesolr', $e->getMessage());
drupal_set_message($e->getMessage(), "warning");
$data->fields = array();
}
$output = '';
if (isset($data->index->numDocs)) {
$output .= '
' . t('Number of documents in index: @num', array('@num' => $data->index->numDocs)) . "
\n";
$output .= '' . t('Number of terms in index: @num', array('@num' => $data->index->numTerms)) . "
\n";
}
$fields = (array)$data->fields;
if ($fields) {
$output .= '' . t('Number of fields in index: @num', array('@num' => count($fields))) . "
\n";
$rows = array();
foreach ($fields as $name => $field) {
// TODO: try to map the name to something more meaningful.
$rows[$name] = array($name, $field->type, isset($field->index) ? $field->distinct : t('Not indexed'));
}
ksort($rows);
// Display the table of Field names, Index Types, and term counts.
$output .= theme('table', array(t('Field name'), t('Index type'), t('Distinct terms')), $rows);
}
else {
$output .= '' . t('No data on indexed fields.') . "
\n";
}
// 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' => 'Solr Index
',
);
$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_state) {
if (!$form_state['values']['delete_index']) {
form_set_error('delete_index', t('If you want to delete the Solr index, you must check the confirmation box.'));
}
}
function apachesolr_delete_index_form_submit($form, &$form_state) {
if ($form_state['values']['delete_index']) {
try {
// Instantiate a new Solr object.
$solr = apachesolr_get_solr();
$query = '*:*';
// Allow other modules to modify the delete query.
// For example, use the site hash so that you only delete this site's
// content: $query = 'hash:' . apachesolr_site_hash()
drupal_alter('apachesolr_delete_index', $query);
$solr->deleteByQuery($query);
$solr->commit();
apachesolr_clear_last_index();
// 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/reports/status/run-cron', array('query' => array('destination' => 'admin/settings/apachesolr/index'))))));
}
catch (Exception $e) {
watchdog('Apache Solr', $e->getMessage(), array(), WATCHDOG_ERROR);
}
}
}
/**
* 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;
$hash = md5($base_url . drupal_get_private_key() . 'apachesolr');
variable_set('apachesolr_site_hash', $hash);
}
return $hash;
}
function apachesolr_document_id($id, $type = 'node') {
return apachesolr_site_hash() . "/$type/" . $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']) {
db_query("UPDATE {apachesolr_search_node} SET changed = %d WHERE nid IN (SELECT nid FROM {node} WHERE uid = %d)", time(), $account->uid);
}
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')) {
db_query("UPDATE {apachesolr_search_node} SET changed = %d WHERE nid IN (SELECT nid FROM {term_node} WHERE tid = %d)", time(), $edit['tid']);
}
// 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':
db_query("UPDATE {apachesolr_search_node} SET changed = %d WHERE nid = %d", time(), $edit['nid']);
break;
}
}
/**
* 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.
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);
}
}
/**
* Helper function for modules implmenting hook_search's 'status' op.
*/
function apachesolr_index_status($namespace) {
extract(apachesolr_get_last_index($namespace));
$total = db_result(db_query('SELECT COUNT(*) FROM {node} WHERE status = 1'));
$remaining = db_result(db_query('SELECT COUNT(*) FROM {apachesolr_search_node} WHERE (changed > %d OR (changed = %d AND nid > %d)) AND status = 1', $last_change, $last_change, $last_nid));
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');
}
}
/**
* Returns a resource from a query based on an indexing namespace.
*/
function apachesolr_get_nodes_to_index($namespace, $limit) {
extract(apachesolr_get_last_index($namespace));
return db_query_range("SELECT nid, changed FROM {apachesolr_search_node} WHERE (changed > %d OR (changed = %d AND nid > %d)) AND status = 1 ORDER BY changed ASC, nid ASC", $last_change, $last_change, $last_nid, 0, $limit);
}
/**
* 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($result, $namespace = '', $callback = 'apachesolr_add_node_document') {
try {
// Get the $solr object
$solr = apachesolr_get_solr();
// If there is no server available, don't continue.
if (!$solr->ping()) {
throw new Exception(t('No Solr instance available during indexing'));
}
}
catch (Exception $e) {
watchdog('Apache Solr', $e->getMessage(), NULL, WATCHDOG_ERROR);
return FALSE;
}
$documents = array();
$solr_last_change = 0;
$solr_last_id = 0;
while ($row = db_fetch_object($result)) {
// Variables to track the last item changed.
$solr_last_change = $row->changed;
$solr_last_id = $row->nid;
$callback($documents, $row->nid);
}
if (is_object($solr) && 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);
}
$solr->commit();
$solr->optimize(FALSE, FALSE);
$last = array('last_change' => $solr_last_change, 'last_nid' => $solr_last_id);
if ($namespace) {
$stored = variable_get('apachesolr_index_last', array());
$stored[$namespace] = $last;
variable_set('apachesolr_index_last', $stored);
}
return $last;
}
catch (Exception $e) {
watchdog('Apache Solr', $e->getMessage(), NULL, WATCHDOG_ERROR);
}
}
return FALSE;
}
/**
* Add a document to the $documents array based on a node ID.
*/
function apachesolr_add_node_document(&$documents, $nid) {
if ($document = apachesolr_node_to_document($nid)) {
$documents[] = $document;
}
}
/**
* Strip control characters that cause Jetty/Solr to fail.
*/
function apachesolr_strip_ctl_chars($text) {
// See: http://w3.org/International/questions/qa-forms-utf-8.html
// Printable utf-8 does not include any of these chars below x7F
return preg_replace('@[\x00-\x08\x0B\x0C\x0E-\x1F]@', ' ', $text);
}
/**
* Strip html tags and also control characters that cause Jetty/Solr to fail.
*/
function apachesolr_clean_text($text) {
return strip_tags(preg_replace('@[\x00-\x08\x0B\x0C\x0E-\x1F]@', ' ', $text));
}
/**
* Given a node ID, return a document representing that node.
*/
function apachesolr_node_to_document($nid) {
// Get CCK fields list
$cck_fields = apachesolr_cck_fields();
// Set reset = TRUE to avoid static caching of all nodes that get indexed.
$node = node_load($nid, NULL, TRUE);
$document = FALSE;
if ($node->nid) {
// Build the node body.
$node->build_mode = NODE_BUILD_SEARCH_INDEX;
$node = node_build_content($node, FALSE, FALSE);
$node->body = drupal_render($node->content);
$node->title = apachesolr_clean_text($node->title);
$text = $node->body;
// Fetch extra data normally not visible, including comments.
$extra = node_invoke_nodeapi($node, 'update index');
$text .= "\n\n" . implode(' ', $extra);
$text = apachesolr_strip_ctl_chars($text);
$document = new Apache_Solr_Document();
$document->id = apachesolr_document_id($node->nid);
$document->site = url(NULL, array('absolute' => TRUE));
$document->hash = apachesolr_site_hash();
$document->url = url('node/' . $node->nid, array('absolute' => TRUE));
$document->nid = $node->nid;
$document->status = $node->status;
$document->uid = $node->uid;
$document->title = $node->title;
$document->body = strip_tags($text);
$document->type = $node->type;
$document->type_name = apachesolr_strip_ctl_chars(node_get_types('name', $node));
$document->created = apachesolr_date_iso($node->created);
$document->changed = apachesolr_date_iso($node->changed);
$last_change = (isset($node->last_comment_timestamp) && $node->last_comment_timestamp > $node->changed) ? $node->last_comment_timestamp : $node->changed;
$document->last_comment_or_change = apachesolr_date_iso($last_change);
$document->comment_count = isset($node->comment_count) ? $node->comment_count : 0;
$document->name = apachesolr_strip_ctl_chars($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;
}
}
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['safe']) && strlen($value['safe'])) {
if ($cck_info['multiple']) {
$document->setMultiValue($index_key, apachesolr_clean_text($value['safe']));
}
else {
$document->$index_key = apachesolr_clean_text($value['safe']);
}
}
}
}
}
apachesolr_add_tags_to_document($document, $text);
apachesolr_add_taxonomy_to_document($document, $node);
// Let modules add to the document - TODO convert to drupal_alter().
foreach (module_implements('apachesolr_update_index') as $module) {
$function = $module .'_apachesolr_update_index';
$function($document, $node);
}
}
return $document;
}
/**
* 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 date('Y-m-d\TH:i:s\Z', $date_timestamp);
}
/**
* Extract taxonomy from $node and add to dynamic fields.
*/
function apachesolr_add_taxonomy_to_document(&$document, $node) {
if (isset($node->taxonomy) && 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);
$name = apachesolr_clean_text($ancestor->name);
$document->setMultiValue('vid', $ancestor->vid);
$document->{'tsfield_vid_'. $ancestor->vid .'_names'} .= ' '. $name;
// We index each name as a string for cross-site faceting
// using the vocab name rather than vid in field construction .
$document->setMultiValue('smfield_vid_'. apachesolr_vocab_name($ancestor->vid), $name);
}
}
}
}
/**
* Helper function - return a safe (PHP identifier) vocabulary name.
*/
function apachesolr_vocab_name($vid) {
static $names = array();
if (!isset($names[$vid])) {
$vocab_name = db_result(db_query('SELECT v.name FROM {vocabulary} v WHERE v.vid = %d', $vid));
$names[$vid] = preg_replace('/[^a-zA-Z0-9_\x7f-\xff]/', '_', $vocab_name);
// Fallback for names ending up all as '_'.
$check = rtrim($names[$vid], '_');
if (!$check) {
$names[$vid] = '_' . $vid . '_';
}
}
return $names[$vid];
}
/**
* Extract HTML tag contents from $text and add to boost fields.
*
* $text must be stripped of control characters before hand.
*/
function apachesolr_add_tags_to_document(&$document, $text) {
$tags_to_index = variable_get('apachesolr_tags_to_index', array(
'h1' => 'tags_h1',
'h2' => 'tags_h2_h3',
'h3' => 'tags_h2_h3',
'h4' => 'tags_h4_h5_h6',
'h5' => 'tags_h4_h5_h6',
'h6' => 'tags_h4_h5_h6',
'u' => 'tags_inline',
'b' => 'tags_inline',
'i' => 'tags_inline',
'strong' => 'tags_inline',
'em' => 'tags_inline',
'a' => 'tags_a'
));
// Strip off all ignored tags.
$text = strip_tags($text, '<'. implode('><', array_keys($tags_to_index)) .'>');
preg_match_all('@<('. implode('|', array_keys($tags_to_index)) .')[^>]*>(.*)\1>@Ui', $text, $matches);
foreach ($matches[1] as $key => $tag) {
// We don't want to index links auto-generated by the url filter.
if ($tag != 'a' || !preg_match('@(?:http://|https://|ftp://|mailto:|smb://|afp://|file://|gopher://|news://|ssl://|sslv2://|sslv3://|tls://|tcp://|udp://|www\.)[a-zA-Z0-9]+@', $matches[2][$key])) {
$document->{$tags_to_index[$tag]} .= ' '. $matches[2][$key];
}
}
}
function apachesolr_delete_node_from_index($node) {
try {
$solr = apachesolr_get_solr();
$solr->deleteById(apachesolr_document_id($node->nid));
$solr->commit();
}
catch (Exception $e) {
watchdog('Apache Solr', $e->getMessage(), NULL, WATCHDOG_ERROR);
}
}
/**
* Implementation of hook_cron().
*/
function apachesolr_cron() {
try {
$solr = apachesolr_get_solr();
$solr->clearCache();
}
catch (Exception $e) {
watchdog('Apache Solr', $e->getMessage(), NULL, WATCHDOG_ERROR);
}
}
/**
* Implementation of hook_nodeapi().
*/
function apachesolr_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
switch ($op) {
case 'delete':
apachesolr_delete_node_from_index($node);
// TODO: check that there was no exception?
db_query("DELETE FROM {apachesolr_search_node} WHERE nid = %d", $node->nid);
break;
case 'insert':
db_query("INSERT INTO {apachesolr_search_node} (nid, status, changed) VALUES (%d, %d, GREATEST(%d, %d))", $node->nid, $node->status, $node->created, $node->changed);
break;
case 'update':
// 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))) {
apachesolr_delete_node_from_index($node);
}
// TODO: check that there was no exception?
db_query("UPDATE {apachesolr_search_node} SET changed = %d, status = %d WHERE nid = %d", time(), $node->status, $node->nid);
break;
}
}
function apachesolr_apachesolr_facets() {
return array('type');
}
/**
* 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'), 'cache' => BLOCK_CACHE_PER_PAGE);
$blocks['type'] = array('info' => t('ApacheSolr Core: Filter by type'), 'cache' => BLOCK_CACHE_PER_PAGE);
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_current_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'),
'created' => 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_basic();
$new_query = clone $query;
foreach ($sorts as $type => $sort) {
$new_sort = isset($solrsorts[$type]) ? $solrsorts[$type] == 'asc' ? 'desc' : 'asc' : $sort['default'];
$new_query->set_solrsort($type == "relevancy" ? '' : "solrsort={$type} {$new_sort}");
$active = isset($solrsorts[$type]) || ($type == "relevancy" && !$solrsorts);
$direction = isset($solrsorts[$type]) ? $solrsorts[$type] : '';
$sort_links[] = theme('apachesolr_sort_link', $sort['name'], $path, $new_query->get_url_querystring(), $active, $direction);
}
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;
}
break;
case 'configure':
if ($delta != 'sort') {
return apachesolr_facetcount_form($delta);
}
break;
case 'save':
if ($delta != 'sort') {
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);
// TODO: don't assume 'search' - find the real path.
$path = 'search/'. arg(1) .'/'. $new_query->get_query_basic();
$querystring = $new_query->get_url_querystring();
$unclick_link = theme('apachesolr_unclick_link', $path, $querystring);
}
else {
$new_query->add_field($delta, $facet);
$path = 'search/'. arg(1) .'/'. $new_query->get_query_basic();
$querystring = $new_query->get_url_querystring();
}
$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, $querystring, $active, $unclick_link, $response->numFound);
}
}
if (count($items) > 0) {
ksort($items);
$facet_display_limit = isset($facet_display_limits[$delta]) ? $facet_display_limits[$delta] : variable_get('apachesolr_facet_query_limit_default', 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);
}
function apachesolr_form_block_admin_configure_alter(&$form, $form_state) {
if (isset($form['block_settings']) && isset($form['block_settings']['apachesolr_facet_query_limit'])) {
$form['#validate'][] = 'apachesolr_facet_query_limit_validate';
}
}
/**
* Validates a facet query limit input. Must be a positive integer or -1.
*/
function apachesolr_facet_query_limit_validate($form, &$form_state) {
$value = intval($form_state['values']['apachesolr_facet_query_limit']);
if ($value < 1 && $value != -1) {
form_set_error('apachesolr_facet_limit', t('Please enter a number greater than 1 or -1 for the facet query limit.'));
return FALSE;
}
}
/**
* 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());
$facet_query_limit_default = variable_get('apachesolr_facet_query_limit_default', 10);
// 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);
}
/**
* This hook allows modules to modify the query and params objects.
*
* Example:
*
* function my_module_apachesolr_modify_query(&$query, &$params) {
* // I only want to see articles by the admin!
* $query->add_field("uid", 1);
*
* }
*/
function apachesolr_modify_query(&$query, &$params) {
foreach (module_implements('apachesolr_modify_query') as $module) {
$function_name = "{$module}_apachesolr_modify_query";
$function_name($query, $params);
}
// Add array of fq parameters.
if ($query && ($fq = $query->get_fq())) {
$params['fq'] = $fq;
}
}
/**
* 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 = 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);
try {
$solr_cache[$host][$port][$path] = new $class($host, $port, $path);
}
catch (Exception $e) {
watchdog('Apache Solr', $e->getMessage(), NULL, WATCHDOG_ERROR);
return;
}
}
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 = clone $response;
}
return $_response;
}
/**
* Factory function for query objects.
*
* The query object is built from the keys, filters, and sort.
*/
function apachesolr_drupal_query($keys = '', $filters = '', $solrsort = '') {
list($module, $class) = variable_get('apachesolr_query_class', array('apachesolr', 'Solr_Base_Query'));
include_once drupal_get_path('module', $module) .'/'. $class .'.php';
try {
$query = new $class(apachesolr_get_solr(), $keys, $filters, $solrsort);
}
catch (Exception $e) {
watchdog('Apache Solr', $e->getMessage(), NULL, WATCHDOG_ERROR);
$query = NULL;
}
return $query;
}
/**
* Factory function for query objects representing the current search URL.
*
* The query object is built from the keys in the URL, but these may be
* overridden by passing in parameters.
*/
function apachesolr_current_query($keys = '', $filters = '', $solrsort = '', $reset = FALSE) {
static $_queries = array();
if ($reset) {
$_queries = array();
}
if (empty($keys)) {
$keys = search_get_keys();
}
if (empty($filters) && !empty($_GET['filters'])) {
$filters = check_plain($_GET['filters']);
}
if (empty($solrsort) && !empty($_GET['solrsort'])) {
$solrsort = check_plain($_GET['solrsort']);
}
$index = $keys . '&filters=' . $filters;
if (empty($_queries) || !array_key_exists($index, $_queries)) {
$_queries[$index] = apachesolr_drupal_query($keys, $filters, $solrsort);
}
return is_object($_queries[$index]) ? clone $_queries[$index] : $_queries[$index];
}
/**
* 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;
default:
$type_prefix = 's';
}
$sm = $field['multiple'] ? 'mfield_' : 'sfield_';
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 (is_null($fields)) {
$fields = array();
// If CCK isn't enabled, do nothing.
if (module_exists('content')) {
// A single default mapping for text fields.
$mappings['text'] = array('callback' => '', 'index_type' => 'string', 'widget_types' => array('optionwidgets_select' => 1, 'optionwidgets_buttons' => 1));
$mappings = module_invoke_all('apachesolr_cck_field_mappings') + $mappings;
$result = db_query("SELECT i.field_name, f.multiple, f.type AS field_type, i.widget_type, i.label FROM {content_node_field_instance} i INNER JOIN {content_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 (isset($mappings[$row->field_type]) && !empty($mappings[$row->field_type]['widget_types'][$row->widget_type])) {
$row->index_type = $mappings[$row->field_type]['index_type'];
$row->callback = $mappings[$row->field_type]['callback'];
$row->multiple = (bool) $row->multiple;
$row->name = 'cck_' . $row->field_name;
$fields[$row->field_name] = (array) $row;
}
}
}
}
return $fields;
}
/**
* Implementation of hook_theme().
*/
function apachesolr_theme() {
return array(
'apachesolr_facet_item' => array(
'arguments' => array('name' => NULL, 'count' => NULL, 'path' => NULL, 'querystring' => '', 'active' => FALSE, 'unclick_link' => NULL, 'num_found' => NULL),
),
'apachesolr_unclick_link' => array(
'arguments' => array('url' => NULL, 'querystring' => ''),
),
'apachesolr_facet_list' => array(
'arguments' => array('items' => NULL),
),
'apachesolr_sort_list' => array(
'arguments' => array('items' => NULL),
),
'apachesolr_sort_link' => array(
'arguments' => array('text' => NULL, 'path' => NULL, 'querystring' => '', 'active' => FALSE, 'direction' => ''),
),
'apachesolr_breadcrumb_type' => array(
'arguments' => array('type' => NULL),
),
);
}
function theme_apachesolr_facet_item($name, $count, $path, $querystring = '', $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 apachesolr_l($name ." ($count)", $path, array('attributes' => $attributes, 'query' => $querystring));
}
}
function apachesolr_l($text, $path, $options = array()) {
// Merge in defaults.
$options += array(
'attributes' => array(),
);
return ''. check_plain($text) .'';
}
function theme_apachesolr_unclick_link($path, $querystring = '') {
return apachesolr_l("(-)", $path, array('query' => $querystring));
}
function theme_apachesolr_sort_link($text, $path, $querystring = '', $active = FALSE, $direction = '') {
$icon = '';
$attributes = array();
if ($direction) {
$icon = ' '. theme('tablesort_indicator', $direction);
}
if ($active) {
$attributes['class'] = 'active';
}
return $icon . apachesolr_l($text, $path, array('attributes' => $attributes, 'query' => $querystring));
}
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);
}