'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'), 'file' => 'apachesolr.admin.inc', ); $items['admin/settings/apachesolr/settings'] = array( 'title' => 'Settings', 'weight' => -10, 'access callback' => 'user_access', 'access arguments' => array('administer site configuration'), '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 callback' => 'user_access', 'access arguments' => array('administer site configuration'), 'file' => 'apachesolr.admin.inc', 'type' => MENU_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, 'file' => 'apachesolr.admin.inc', 'type' => MENU_LOCAL_TASK, ); 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; } } /** * 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 when checking requirements.')); } } catch (Exception $e) { watchdog('Apache Solr', $e->getMessage(), NULL, WATCHDOG_ERROR); } $value = $ping ? $t('Your site has contacted the Apache Solr server.') : $t('Your site was unable to contact the Apache Solr server.'); $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('Apache Solr'), 'value' => $value, 'description' => $description, 'severity' => $severity, ); return $requirements; } } /** * 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': 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. 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(); $old_position = apachesolr_get_last_index($namespace); $position = $old_position; while ($row = db_fetch_object($result)) { // Variables to track the last item changed. $position['last_change'] = $row->changed; $position['last_nid'] = $row->nid; $callback($documents, $row->nid); } 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); } } catch (Exception $e) { watchdog('Apache Solr', $e->getMessage(), NULL, 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; } /** * 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) { // Set reset = TRUE to avoid static caching of all nodes that get indexed. $node = node_load($nid, NULL, TRUE); if (empty($node)) { return FALSE; } $document = FALSE; // Let any module exclude this node from the index. $build_document = TRUE; foreach (module_implements('apachesolr_node_exclude') as $module) { $exclude = module_invoke($module, 'apachesolr_node_exclude', $node); if (!empty($exclude)) { $build_document = FALSE; } } if ($build_document) { // 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->nid = $node->nid; $document->uid = $node->uid; $document->title = $node->title; $document->status = $node->status; $document->sticky = $node->sticky; $document->promote = $node->promote; $document->moderate = $node->moderate; $document->tnid = $node->tnid; $document->translate = $node->translate; $document->language = $node->language; $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); $path = 'node/' . $node->nid; $document->url = url($path, array('absolute' => TRUE)); $document->path = $path; // 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; $output = drupal_get_path_alias($path, $language); if ($output && $output != $path) { $document->path_alias = apachesolr_strip_ctl_chars($output); } } // Get CCK fields list $cck_fields = apachesolr_cck_fields(); 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 gmdate('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('im_vid_'. $ancestor->vid, $ancestor->tid); $name = apachesolr_clean_text($ancestor->name); $document->setMultiValue('vid', $ancestor->vid); $document->{'ts_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('sm_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)) .')[^>]*>(.*)@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)); } 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(); // Re-populate the luke cache. // Note: we use 2 since 1 fails on Ubuntu Hardy. $solr->getLuke(); $last = variable_get('apachesolr_last_optimize', 0); $time = time(); // Make sure to omtimize once per day. if ($time - $last > 60*60*24) { $solr->optimize(FALSE, FALSE); variable_set('apachesolr_last_optimize', $time); } } 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; } } /** * Return the enabled facets from the specified block array. * * @param $module * The module (optional). * @return * An array consisting 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; } /** * Implementation of hook_block(). */ function apachesolr_block($op = 'list', $delta = 0, $edit = array()) { switch ($op) { case 'list': // Add the blocks $blocks['sort'] = array( 'info' => t('Apache Solr Core: Sorting'), '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(); switch ($delta) { case 'sort': $sorts = array( 'relevancy' => array('name' => t('Relevancy'), 'default' => 'asc'), 'sort_title' => array('name' => t('Title'), 'default' => 'asc'), 'type' => array('name' => t('Type'), 'default' => 'asc'), 'sort_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" ? '' : "{$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)); default: break; } } 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) { // Solr sends this back if it's empty. if ($facet == '_empty_') { continue; } $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($facet_field, $facet)) { $contains_active = TRUE; $new_query->remove_field($facet_field, $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($facet_field, $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); // 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; } /** * 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()); $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), ); 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); } /** * 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, $caller) { foreach (module_implements('apachesolr_modify_query') as $module) { $function_name = "{$module}_apachesolr_modify_query"; $function_name($query, $params, $caller); } // 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 = $_GET['filters']; } if (empty($solrsort) && !empty($_GET['solrsort'])) { $solrsort = $_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'] ? '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 (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' => ''), ), ); } 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, $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 $link) { $items[] = array('data' => $link, 'class' => 'apachesolr-hidden-facet'); } } $admin_link = ''; if (user_access('administer site configuration')) { $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) { return theme('item_list', $items); }