l(t('running cron'), 'admin/reports/status/run-cron', array('query' => array('destination' => 'admin/config/search/apachesolr/index'))), '%percentage' => ((int)min(100, 100 * ($total - $remaining) / max(1, $total))) .'%', '@items' => format_plural($remaining, t('is 1 item'), t('are @count items') ))); } } /** * Implements hook_menu(). */ function apachesolr_search_menu() { $items['admin/config/search/apachesolr/query-fields'] = array( 'title' => 'Search fields', 'page callback' => 'apachesolr_search_settings_page', 'access arguments' => array('administer search'), 'weight' => 1, 'type' => MENU_LOCAL_TASK, 'file' => 'apachesolr_search.admin.inc', ); $items['admin/config/search/apachesolr/content-bias'] = array( 'title' => 'Content bias settings', 'page callback' => 'apachesolr_boost_settings_page', 'access arguments' => array('administer search'), 'weight' => 1, 'type' => MENU_LOCAL_TASK, 'file' => 'apachesolr_search.admin.inc', ); return $items; } /** * Implements hook_cron(). Indexes nodes. */ function apachesolr_search_cron() { $cron_limit = variable_get('apachesolr_cron_limit', 50); $rows = apachesolr_get_nodes_to_index('apachesolr_search', $cron_limit); apachesolr_index_nodes($rows, 'apachesolr_search'); } /** * Implements hook_apachesolr_types_exclude(). */ function apachesolr_search_apachesolr_types_exclude($namespace) { if ($namespace == 'apachesolr_search') { $excluded_types = variable_get('apachesolr_search_excluded_types', array()); return array_filter($excluded_types); } } /** * Implementation of hook_search_info(). */ function apachesolr_search_search_info() { return variable_get('apachesolr_search_search_info', array( 'title' => 'Site', 'path' => 'site', 'conditions_callback' => 'apachesolr_search_conditions', )); } /** * Implementation of hook_search_reset(). */ function apachesolr_search_search_reset(){ apachesolr_clear_last_index('apachesolr_search'); } /** * Implementation of hook_search_status(). */ function apachesolr_search_search_status(){ return apachesolr_index_status('apachesolr_search'); } /** * Implementation of hook_search_execute(). */ function apachesolr_search_search_execute($keys = NULL, $conditions = NULL) { $filters = isset($conditions['filters']) ? $conditions['filters'] : ''; $solrsort = isset($_GET['solrsort']) ? $_GET['solrsort'] : ''; $empty_search_behavior = isset($conditions['apachesolr_search_browse']) ? $conditions['apachesolr_search_browse'] : ''; // For perfomance reasons, do not run a search if search is empty and // $empty_search_behavior condition is 'browse' if (!$keys && !$filters && $empty_search_behavior == 'browse') { // Pass empty search behavior as string on to apachesolr_search_search_page() return array('apachesolr_search_browse' => $empty_search_behavior); } try { $results = apachesolr_search_run($keys, $filters, $solrsort, 'search/' . arg(1), pager_find_page()); if (!$keys && !$filters && $empty_search_behavior == 'blocks') { // If search was empty and empty search behavior is 'blocks', discard // the result items and pass behavior on to apachesolr_search_search_page() $results['apachesolr_search_browse'] = $empty_search_behavior; } return $results; } catch (Exception $e) { watchdog('Apache Solr', nl2br(check_plain($e->getMessage())), NULL, WATCHDOG_ERROR); apachesolr_failure(t('Solr search'), $keys); } } /** * Implementation of a search_view() conditions callback. */ function apachesolr_search_conditions() { $conditions = array(); if (isset($_GET['filters']) && trim($_GET['filters'])) { $conditions['filters'] = trim($_GET['filters']); } if (variable_get('apachesolr_search_browse', 'browse') != 'none') { // Set a condition so the search is triggered. $conditions['apachesolr_search_browse'] = variable_get('apachesolr_search_browse', 'browse'); } return $conditions; } /** * Implements hook_search_page(). */ function apachesolr_search_search_page($results) { if (!empty($results['apachesolr_search_browse'])) { // Show facet browsing blocks. $output = apachesolr_search_page_browse($results['apachesolr_search_browse']); } elseif ($results) { $output = array( '#theme' => 'search_results', '#results' => $results, '#module' => 'apachesolr_search', ); } else { // Give the user some custom help text. $output = array('#markup' => theme('apachesolr_search_noresults')); } return $output; } /** * Implements hook_apachesolr_document_handlers(). * * @param string $type * Entity type. 'node', 'comment', and so forth. Used to evaluate whether this module * should be interested in creating documents. * @param string $namespace * Usually the name of the module that is initiating indexing. In this case * we want to register a handler if the namespace is 'apachesolr_search'. * @return array $handlers * An array of strings that are function names. Each function returns a $document from * an entity (of type $type). */ function apachesolr_search_apachesolr_document_handlers($type, $namespace) { if ($type == 'node' && $namespace == 'apachesolr_search') { return array('apachesolr_node_to_document'); } } /** * Implments hook_finalize_query(). * * Handle OR facets. */ function apachesolr_search_finalize_query(DrupalSolrQueryInterface $query) { $ors = array(); $facet_info = apachesolr_get_facet_definitions(); foreach ($facet_info as $infos) { foreach ($infos as $delta => $facet) { if ($facet['operator'] == 'OR') { $ors[] = $delta; } } } if (isset($query->params['fq'])) { $filter_queries = $query->params['fq']; foreach ($filter_queries as $delta => $values) { $fq = $tag = ''; $op = 'AND'; $fields = array($delta); if (array_intersect($fields, $ors)) { $tag = "{!tag=$delta}"; $op = 'OR'; } $fq = implode(" $op ", $query->params['fq'][$delta]); $query->params['fq'][] = $tag . $fq; unset($query->params['fq'][$delta]); } } } /** * Handle browse results for empty searches. */ function apachesolr_search_page_browse($empty_search_behavior) { $output = array(); // Switch in case we come up with new flags. switch ($empty_search_behavior) { case 'browse': try { $blocks = apachesolr_search_browse('', '', '', 'search/' . arg(1)); if (count($blocks) > 0) { $output[] = array('#markup' => theme('apachesolr_browse_blocks', array('blocks' => $blocks))); } } catch (Exception $e) { watchdog('Apache Solr', nl2br(check_plain($e->getMessage())), NULL, WATCHDOG_ERROR); apachesolr_failure(t('Solr search'), $keys); } break; } return $output; } /** * Execute a search and show a listing of all enabled facets. * * @throws Exception */ function apachesolr_search_browse($keys = '', $filters = '', $solrsort = '', $base_path = '') { global $user, $theme_key; $query = apachesolr_drupal_query($keys, $filters, $solrsort, $base_path); $query->params = array( 'start' => 0, 'rows' => 0, 'facet' => 'true', 'facet.mincount' => 1, 'facet.sort' => 'true' ); apachesolr_search_add_facet_params($query); apachesolr_current_query($query); apachesolr_modify_query($query, 'apachesolr'); $response = $query->search(); if (empty($response)) { return; } apachesolr_static_response_cache($response); apachesolr_has_searched(TRUE); //this sets $theme_key global variable drupal_theme_initialize(); // Get blocks for all enabled filters $blocks = array(); $rids = array_keys($user->roles); $server_id = apachesolr_default_server(); foreach (apachesolr_get_enabled_facets($server_id) as $module => $module_facets) { if (!module_exists($module)) { // When modules are disabled their facet settings may remain. continue; } foreach ($module_facets as $delta => $facet_field) { if ($delta == 'currentsearch') { continue; } $fc = $response->facet_counts; if ((isset($fc->facet_fields->$facet_field) && count((array) $fc->facet_fields->$facet_field) > 0) || (isset($fc->facet_dates->$facet_field) && count((array) $fc->facet_dates->$facet_field) > 0)) { // This bit is modeled on block.module, block_list(). $data = module_invoke($module, 'block_view', $delta); $query = db_select('block', 'b'); $br_alias = $query->leftJoin('block_role', 'br', 'b.module = br.module AND b.delta = br.delta'); $query ->fields('b') ->distinct() ->condition('b.module', $module) ->condition('b.delta', $delta) ->condition('b.theme', $theme_key) ->condition('b.status', 1) ->condition(db_or()->condition('br.rid', $rids, 'IN')->isNull('br.rid')) ->range(0, 1); $query->addTag('node_access'); $block = $query->execute()->fetchObject(); if ($block) { // TODO: not sure if we should show facets that have no corresponding enabled blocks here. // Anyways without this condition there will be warnings if fetchObject() returns false // We can safely assume these values, since we're taking over the block display. $block->visibility = TRUE; $block->enabled = TRUE; $block->module = $module; // Allow modules to modify the block before it is viewed, via either // hook_block_view_alter() or hook_block_view_MODULE_DELTA_alter(). drupal_alter(array('block_view', "block_view_{$block->module}_{$block->delta}"), $data, $block); if (isset($data) && is_array($data)) { foreach ($data as $k => $v) { $block->$k = $v; } } if (isset($block->content) && $block->content && $block->enabled && $block->visibility) { // Normalize to the drupal_render() structure. if (is_array($block->content)) { $block->content = drupal_render($block->content); } $block->subject = str_replace(t('Filter by '), t('Browse by '), $block->subject); $blocks["{$module}_{$delta}"] = $block; } } } } } apachesolr_has_searched(FALSE); usort($blocks, create_function('$a, $b', 'return $a->weight - $b->weight;')); return $blocks; } /** * Theming function that shows a groups of blocks so users can start a search from a filter. * * @param $blocks an array of block objects. */ function theme_apachesolr_browse_blocks($vars) { $blocks = $vars['blocks']; $result = "

" . t('Browse available categories') .'

'; $result .= '

' . t('Pick a category to launch a search.') . '

'; foreach ($blocks as $facet_field => $block) { //$result .= theme('block', array('block' => $block)); $result .= theme('block', array('elements' => array('#block' => $block, '#children' => $block->content))); } $result .= '
'; return $result; } /** * Execute a search results based on keyword, filter, and sort strings. * * @param $keys * @param $filterstring * @param $solrsort * @param $base_path * For constructing filter and sort links. Leave empty unless the links need to point somewhere * other than the base path of the current request. * @param integer $page * For pagination. * @param $caller * * @return Apache_Solr_Response $response * * @throws Exception */ function apachesolr_search_run($keys, $filterstring, $solrsort, $base_path = '', $page = 0, $caller = 'apachesolr_search') { // This is the object that knows about the query coming from the user. $query = apachesolr_drupal_query($keys, $filterstring, $solrsort, $base_path); apachesolr_search_basic_params($query); if ($keys) { apachesolr_search_highlighting_params($query); apachesolr_search_add_spellcheck_params($query); } else { // No highlighting, use the teaser as a snippet. $query->params['fl'] .= ',teaser'; } apachesolr_search_add_facet_params($query); apachesolr_search_add_boost_params($query); list($final_query, $response) = apachesolr_do_query($caller, $query, $page); apachesolr_has_searched(TRUE); // Add search terms and filters onto the breadcrumb. // We use the original $query to avoid exposing, for example, nodeaccess // filters in the breadcrumb. drupal_set_breadcrumb(array_merge(drupal_get_breadcrumb(), $query->get_breadcrumb())); return apachesolr_search_process_response($response, $final_query); } function apachesolr_search_basic_params(DrupalSolrQueryInterface $query = NULL) { $params = array( 'fl' => 'id,entity_id,entity,bundle,bundle_name,nid,title,comment_count,type,created,changed,score,path,url,uid,name', 'rows' => variable_get('apachesolr_rows', 10), 'facet' => 'true', 'facet.mincount' => 1, 'facet.sort' => 'true' ); if ($query) { $query->params += $params; } return $params; } /** * Add highlighting settings to the search params. * * These settings are set in solrconfig.xml. * See the defaults there. * If you wish to override them, you can via settings.php */ function apachesolr_search_highlighting_params(DrupalSolrQueryInterface $query = NULL) { $params['hl'] = variable_get('apachesolr_hl_active', NULL); $params['hl.fragsize']= variable_get('apachesolr_hl_textsnippetlength', NULL); $params['hl.simple.pre'] = variable_get('apachesolr_hl_pretag', NULL); $params['hl.simple.post'] = variable_get('apachesolr_hl_posttag', NULL); $params['hl.snippets'] = variable_get('apachesolr_hl_numsnippets', NULL); $params['hl.fl'] = variable_get('apachesolr_hl_fieldtohightlight', NULL); if ($query) { $query->params += $params; } return $params; } function apachesolr_search_add_spellcheck_params(DrupalSolrQueryInterface $query) { $params = &$query->params; if (variable_get('apachesolr_search_spellcheck', TRUE)) { //Add new parameter to the search request $params['spellcheck.q'] = $query->get_query_basic(); $params['spellcheck'] = 'true'; } } function apachesolr_search_add_facet_params(DrupalSolrQueryInterface $query) { $facet_query_limits = variable_get('apachesolr_facet_query_limits', array()); $facet_missing = variable_get('apachesolr_facet_missing', array()); $facet_info = apachesolr_get_facet_definitions(); $params = &$query->params; $server_id = $query->solr('get_server_id'); foreach (apachesolr_get_enabled_facets($server_id) as $module => $module_facets) { foreach ($module_facets as $delta => $facet_field) { // Checking the block visibility saves us from adding facet fields to the query // which won't be used. if (apachesolr_block_visibility($query, $module, $delta)) { // TODO: generalize handling of date and range facets. // TODO: put field type in the facet definitions. // NOTE: This is a dependency on the apachesolr_date module. Using function_exists to avoid problems. if (isset($facet_info[$module][$delta]['field_type']) && in_array($facet_info[$module][$delta]['field_type'], array('date', 'datetime', 'datestamp')) && function_exists('apachesolr_date_search_date_range')) { list($start, $end, $gap) = apachesolr_date_search_date_range($query, $facet_field); if ($gap) { $params['facet.date'][] = $facet_field; $params['f.'. $facet_field .'.facet.date.start'] = $start; $params['f.'. $facet_field .'.facet.date.end'] = $end; $params['f.'. $facet_field .'.facet.date.gap'] = $gap; } } elseif ($module == 'apachesolr_search' && ($facet_field == 'created' || $facet_field == 'changed')) { list($start, $end, $gap) = apachesolr_search_date_range($query, $facet_field); if ($gap) { $params['facet.date'][] = $facet_field; $params['f.'. $facet_field .'.facet.date.start'] = $start; $params['f.'. $facet_field .'.facet.date.end'] = $end; $params['f.'. $facet_field .'.facet.date.gap'] = $gap; } } else { $ex = ''; if ($facet_info[$module][$delta]['operator'] == 'OR') { $ex = "{!ex=$facet_field}"; } $params['facet.field'][] = $ex . $facet_field; // Facet limits if (isset($facet_query_limits[$module][$delta])) { $params['f.' . $facet_field . '.facet.limit'] = $facet_query_limits[$module][$delta]; } // Facet missing if (!empty($facet_missing[$module][$delta])) { $params['f.' . $facet_field . '.facet.missing'] = 'true'; } } } } } if (!empty($params['facet.field'])) { // Add a default limit for fields where no limit was set. $params['facet.limit'] = variable_get('apachesolr_facet_query_limit_default', 20); } } function apachesolr_search_add_boost_params(DrupalSolrQueryInterface $query) { $params = &$query->params; // Note - we have query fields set in solrconfig.xml, which will operate when // none are set. $qf = variable_get('apachesolr_search_query_fields', array()); $fields = $query->solr('getFields'); if ($qf && $fields) { foreach ($fields as $field_name => $field) { if (!empty($qf[$field_name])) { if ($field_name == 'body') { // Body is the only normed field. $qf[$field_name] *= 40.0; } $params['qf'][] = $field_name . '^'. $qf[$field_name]; } } } // For the boost functions for the created timestamp, etc we use the // standard date-biasing function, as suggested (but steeper) at // http://wiki.apache.org/solr/SolrRelevancyFAQ#How_can_I_boost_the_score_of_newer_documents // ms() returns the time difference in ms between now and the date // The function is thus: $ab/(ms(NOW,date)*$steepness + $ab). $date_settings = variable_get('apachesolr_search_date_boost', '4:200.0'); list($date_steepness, $date_boost) = explode(':', $date_settings); if ($date_boost) { $ab = 4 / $date_steepness; $params['bf'][] = "recip(ms(NOW,created),3.16e-11,$ab,$ab)^$date_boost"; } // Boost on comment count. $comment_settings = variable_get('apachesolr_search_comment_boost', '0:0'); list($comment_steepness, $comment_boost) = explode(':', $comment_settings); if ($comment_boost) { $params['bf'][] = "recip(div(1,max(comment_count,1)),$comment_steepness,10,10)^$comment_boost"; } // Boost for a more recent comment or node edit. $changed_settings = variable_get('apachesolr_search_changed_boost', '0:0'); list($changed_steepness, $changed_boost) = explode(':', $changed_settings); if ($changed_boost) { $ab = 4 / $changed_steepness; $params['bf'][] = "recip(ms(NOW,created),3.16e-11,$ab,$ab)^$changed_boost"; } // Boost for nodes with sticky bit set. $sticky_boost = variable_get('apachesolr_search_sticky_boost', 0); if ($sticky_boost) { $params['bq'][] = "sticky:true^$sticky_boost"; } // Boost for nodes with promoted bit set. $promote_boost = variable_get('apachesolr_search_promote_boost', 0); if ($promote_boost) { $params['bq'][] = "promote:true^$promote_boost"; } // Modify the weight of results according to the node types. $type_boosts = variable_get('apachesolr_search_type_boosts', array()); if (!empty($type_boosts)) { foreach ($type_boosts as $type => $boost) { // Only add a param if the boost is != 0 (i.e. > "Normal"). if ($boost) { $params['bq'][] = "type:$type^$boost"; } } } } function apachesolr_search_process_response($response, DrupalSolrQueryInterface $query) { $results = array(); // We default to getting snippets from the body. $hl_fl = isset($query->params['hl.fl']) ? explode(',', $query->params['hl.fl']) : array('body'); $total = $response->response->numFound; pager_default_initialize($total, $query->params['rows']); if ($total > 0) { foreach ($response->response->docs as $doc) { $extra = array(); // Start with an empty snippets array. $snippets = array(); // Find the nicest available snippet. foreach ($hl_fl as $hl_param) { if (isset($response->highlighting->{$doc->id}->$hl_param)) { // Merge arrays preserving keys. foreach ($response->highlighting->{$doc->id}->$hl_param as $values) { $snippets[$hl_param] = $values; } } } // If there's no snippet at this point, add the teaser. if (!$snippets) { if (isset($doc->teaser)) { $snippets[] = truncate_utf8($doc->teaser, 256, TRUE); } } $snippet = theme('apachesolr_search_snippets__'. $doc->entity . '__' . $doc->bundle, array('doc' => $doc, 'snippets' => $snippets)); if (!isset($doc->body)) { $doc->body = $snippet; } // Normalize common dates so that we can use Drupal's normal date and // time handling. if (isset($doc->created)) { $doc->created = strtotime($doc->created); } if (isset($doc->changed)) { $doc->changed = strtotime($doc->changed); } $extra = array(); // Allow modules to alter each document and its extra information. drupal_alter('apachesolr_search_result', $doc, $extra); $fields = (array) $doc; $result = array( // link is a required field, so handle it centrally. 'link' => url($doc->path, array('absolute' => TRUE)), // template_preprocess_search_result() runs check_plain() on the title // again. Decode to correct the display. 'title' => htmlspecialchars_decode($doc->title, ENT_QUOTES), // These values are not required by the search module but are provided // to give entity callbacks and themers more flexibility. 'score' => $doc->score, 'snippets' => $snippets, 'snippet' => $snippet, 'fields' => $fields, 'entity_type' => $doc->entity, 'bundle' => $doc->bundle, ); // Call entity-type-specific callbacks for extra handling. $function = apachesolr_entity_get_callback($doc->entity, 'result callback'); if (function_exists($function)) { $function($doc, $result, $extra); } $result['extra'] = $extra; $results[] = $result; } // Hook to allow modifications of the retrieved results foreach (module_implements('apachesolr_process_results') as $module) { $function = $module .'_apachesolr_process_results'; $function($results); } } return $results; } /** * Implements hook_entity_info_alter(). */ function apachesolr_search_entity_info_alter(&$entity_info) { // First set defaults so that we needn't worry about NULL keys. foreach (array_keys($entity_info) as $type) { $entity_info[$type] += array('apachesolr' => array()); $entity_info[$type]['apachesolr'] += array( 'result callback' => '', ); } // Now set those values that we know. Other modules can do so // for their own entities if they want. $entity_info['node']['apachesolr']['result callback'] = 'apachesolr_search_node_result'; } /** * Callback function for node search results. * * @param stdClass $doc * The result document from Apache Solr. * @param array $result * The result array for this record to which to add. */ function apachesolr_search_node_result($doc, &$result, &$extra) { $result += array( 'type' => apachesolr_search_get_type($doc->type), 'user' => theme('username', array('account' => $doc)), 'date' => $doc->created, 'node' => $doc, ); if (isset($doc->comment_count)) { $extra['comments'] = format_plural($doc->comment_count, '1 comment', '@count comments'); } } /** * Template preprocess for apachesolr search results. * * We need to add additional entity/bundle-based templates */ function apachesolr_search_preprocess_search_result(&$variables) { // If this search result is coming from our module, we want to improve the // template potential to make life easier for themers. if ($variables['module'] == 'apachesolr_search') { $result = $variables['result']; $variables['theme_hook_suggestions'][] = 'search_result__' . $variables['module'] . '__' . $result['entity_type'] . '__' . $result['bundle']; } } function apachesolr_search_preprocess_search_results(&$variables) { // If this is a solr search, expose more data to themes to play with. if ($variables['module'] == 'apachesolr_search') { $variables['response'] = apachesolr_static_response_cache(); if (empty($variables['response'])) { $variables['description'] = ''; return; } $variables['query'] = apachesolr_current_query(); $total = $variables['response']->response->numFound; $params = $variables['query']->params; $variables['description'] = t('Showing items @start through @end of @total.', array( '@start' => $params['start'] + 1, '@end' => $params['start'] + $params['rows'] - 1, '@total' => $total, )); } } function apachesolr_search_date_range(DrupalSolrQueryInterface $query, $facet_field) { foreach ($query->get_filters($facet_field) as $filter) { // If we had an ISO date library we could use ISO dates // directly. Instead, we convert to Unix timestamps for comparison. // Only use dates if we are able to parse into timestamps. $start = strtotime($filter['#start']); $end = strtotime($filter['#end']); if ($start && $end && ($start < $end)) { $start_iso = $filter['#start']; $end_iso = $filter['#end']; // Determine the drilldown gap for this range. $gap = apachesolr_date_gap_drilldown(apachesolr_date_find_query_gap($start_iso, $end_iso)); } } // If there is no $delta field in query object, get initial // facet.date.* params from the DB and determine the best search // gap to use. This callback assumes $delta is 'changed' or 'created'. if (!isset($start_iso)) { $start_iso = apachesolr_date_iso(db_query("SELECT MIN($facet_field) FROM {node} WHERE status = 1")->fetchField()); // Subtract one second, so that this range's $end_iso is not equal to the // next range's $start_iso. $end_iso = apachesolr_date_iso(db_query("SELECT MAX($facet_field) FROM {node} WHERE status = 1")->fetchField() - 1); $gap = apachesolr_date_determine_gap($start_iso, $end_iso); } // Return a query range from the beginning of a gap period to the beginning // of the next gap period. We ALWAYS generate query ranges of this form // and the apachesolr_date_*() helper functions require it. return array("$start_iso/$gap", "$end_iso+1$gap/$gap", "+1$gap"); } /** * Helper function, finds node types that are tagged by the specified vocabulary */ function apachesolr_search_vocabulary_content_types($vid){ $fields = field_info_fields(); $types = array(); foreach (field_info_instances('node') as $bundle_name => $bundle) { foreach ($bundle as $field_name => $inst){ $f = $fields[$field_name]; if ($f['type'] == 'taxonomy_term_reference' && $f['settings']['allowed_values'][0]['vid'] == $vid) { $types[] = $bundle_name; } } } return array_unique($types); } /** * Implements hook_apachesolr_facets(). * * Returns an array keyed by block delta. */ function apachesolr_search_apachesolr_facets() { $facets = array(); $facets['type'] = array( 'info' => t('Node attribute: Filter by content type'), 'facet_field' => 'type', ); $facets['uid'] = array( 'info' => t('Node attribute: Filter by author'), 'facet_field' => 'uid', ); $facets['language'] = array( 'info' => t('Node attribute: Filter by language'), 'facet_field' => 'language', ); $facets['changed'] = array( 'info' => t('Node attribute: Filter by updated date'), 'facet_field' => 'changed', ); $facets['created'] = array( 'info' => t('Node attribute: Filter by post date'), 'facet_field' => 'created', ); // A book module facet. if (module_exists('book')) { $facets['is_book_bid'] = array( 'info' => t('Book: Filter by Book'), 'facet_field' => 'is_book_bid', 'content_types' => variable_get('book_allowed_types', array('book')), ); } // Get field facets. $fields = apachesolr_entity_fields('node'); foreach ($fields as $index_key => $field_info) { $facets[$index_key] = array( 'info' => t('@module_name Field: Filter by @field_dname (@field_name)', array('@module_name' => $field_info['module_name'], '@field_name' => $field_info['field']['field_name'], '@field_dname' => $field_info['display_name'])), 'facet_field' => $index_key, 'content_types' => $field_info['bundles'], ) + $field_info; } return $facets; } /** * Implements hook_block_info(). */ function apachesolr_search_block_info() { $server_id = apachesolr_default_server(); $enabled_facets = apachesolr_get_enabled_facets($server_id, 'apachesolr_search'); $facets = apachesolr_search_apachesolr_facets(); // Add the blocks $blocks = array(); foreach ($enabled_facets as $delta => $facet_field) { if (isset($facets[$delta])) { $blocks[$delta] = $facets[$delta] + array('cache' => DRUPAL_CACHE_PER_PAGE); } } $blocks['currentsearch'] = array( 'info' => t('Apache Solr Search: Current search'), 'cache' => DRUPAL_CACHE_PER_PAGE, ); return $blocks; } /** * Implements hook_block_view(). */ function apachesolr_search_block_view($delta = '') { 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(); $server_id = apachesolr_default_server(); $facets = apachesolr_get_enabled_facets($server_id, 'apachesolr_search'); if (empty($facets[$delta]) && ($delta != 'currentsearch')) { return; } if (!apachesolr_block_visibility($query, 'apachesolr_search', $delta)) { return; } switch ($delta) { case 'currentsearch': return apachesolr_search_currentsearch_block($response, $query); case 'is_book_bid': return apachesolr_facet_block($response, $query, 'apachesolr_search', $delta, t('Filter by book'), 'apachesolr_search_get_book'); case 'language': return apachesolr_facet_block($response, $query, 'apachesolr_search', $delta, t('Filter by language'), 'apachesolr_search_language_name'); case 'uid': return apachesolr_facet_block($response, $query, 'apachesolr_search', $delta, t('Filter by author'), 'apachesolr_search_get_username'); case 'type': return apachesolr_facet_block($response, $query, 'apachesolr_search', $delta, t('Filter by type'), 'apachesolr_search_get_type'); case 'changed': return apachesolr_date_facet_block($response, $query, 'apachesolr_search', $delta, t('Filter by modification date')); case 'created': return apachesolr_date_facet_block($response, $query, 'apachesolr_search', $delta, t('Filter by post date')); default: if ($fields = apachesolr_entity_fields('node')) { if (isset($fields[$delta])) { // The $delta is the index key. $field_info = $fields[$delta]; $callback = $field_info['display_callback'] ? $field_info['display_callback'] : FALSE; $block_function = ($field_info['facet_block_callback'] && function_exists($field_info['facet_block_callback'])) ? $field_info['facet_block_callback'] : 'apachesolr_facet_block'; return $block_function($response, $query, 'apachesolr_search', $delta, t('Filter by @field', array('@field' => $field_info['display_name'])), $callback); } } break; } } } /** * Implements of hook_block_configure(). */ function apachesolr_search_block_configure($delta = '') { if ($delta != 'currentsearch') { return apachesolr_facetcount_form('apachesolr_search', $delta); } } /** * Implements of hook_block_save(). */ function apachesolr_search_block_save($delta = '', $edit = array()) { if ($delta != 'currentsearch') { apachesolr_facetcount_save($edit); } } /** * Generate a list including the field and all its children. */ function apachesolr_search_collect_children($field) { $remove[] = $field; if (!empty($field['#children'])) { foreach ($field['#children'] as $child_field) { $remove = array_merge($remove, apachesolr_search_collect_children($child_field)); } } return $remove; } /** * Recursive function that returns a nested array of facet values for use with * theme_item_list(). * * @param $query * The current Solr query. * @param $facets * Array of facet items to prepare for rendering, possibly as nested lists. * @param $num_found * The number of documents in the current response. * @param $sort * If true, the returned list will be sorted based on the count of each * facets, it's text representation and wither it's active. If false, * the facets will be returned in the order they were received. */ function apachesolr_search_nested_facet_items(DrupalSolrQueryInterface $query, $facets, $num_found, $sort = TRUE) { $facet_query_sorts = variable_get('apachesolr_facet_query_sorts', array()); $facet_show_children = variable_get('apachesolr_facet_show_children', array()); $items = array(); foreach ($facets as $field) { $facet_text = ''; $field_name = $field['#name']; $facet_definition = apachesolr_get_facet_definition_by_field_name($field_name); if (isset($facet_definition['display_callback'])) { $function = $facet_definition['display_callback']; if (function_exists($function)) { $facet_text = $function($field['#value'], $field); } } if (!$facet_text) { $breadcrumb_name = 'apachesolr_breadcrumb_' . $field['#name']; drupal_alter('apachesolr_theme_breadcrumb', $breadcrumb_name); $facet_text = theme($breadcrumb_name, array('field' => $field, 'exclude' => $field['#exclude'])); } if (!$facet_text) { $facet_text = $field['#value']; } $active = !empty($field['#active']); $link = array(); $new_query = clone $query; if ($active) { foreach (apachesolr_search_collect_children($field) as $child) { $new_query->remove_filter($child['#name'], $child['#value']); } $options['query'] = $new_query->get_url_queryvalues(); $link['data'] = theme('apachesolr_unclick_link', array( 'facet_text' => $facet_text, 'path' => $new_query->get_path(), 'options' => $options)); } else { $new_query->add_filter($field['#name'], $field['#value']); $options = array('query' => $new_query->get_url_queryvalues()); $link['data'] = theme('apachesolr_facet_link', array( 'facet_text' => $facet_text, 'path' => $new_query->get_path(), 'options' => $options, 'count' => $field['#count'], 'active' => FALSE, 'num_found' => $num_found)); } if ($active) { // '*' sorts before all numbers. $sortpre = '*'; } elseif (isset($facet_query_sorts['apachesolr_search'][$field_name]) && strpos($facet_query_sorts['apachesolr_search'][$field_name], 'index key') === 0) { // If this block is to be alphabetically sorted by key, change $sortpre. $sortpre = $field_name; } elseif (isset($facet_query_sorts['apachesolr_search'][$field_name]) && strpos($facet_query_sorts['apachesolr_search'][$field_name], 'index') === 0) { // If this block is to be alphabetically/numerically sorted by value, change $sortpre. $sortpre = $facet_text; } else { $sortpre = 1000000 - $field['#count']; } // Only display children if the block enables it or the parent is clicked. if ((isset($facet_show_children['apachesolr_search'][$field_name]) && $facet_show_children['apachesolr_search'][$field_name]) || (!empty($field['#children']) && $field['#active'] == TRUE)) { $link['children'] = apachesolr_search_nested_facet_items($query, $field['#children'], $num_found, $sort); $link['class'] = "expanded-facet"; } elseif (!empty($field['#has_children'])) { $link['class'] = "collapsed"; } $items[$sortpre . '*' . $facet_text . $field['#name'] . $field['#value']] = $link; } if ($sort && $items) { switch ($facet_query_sorts['apachesolr_search'][$field_name]) { case 'index numeric asc': ksort($items, SORT_NUMERIC); break; case 'index numeric desc': krsort($items, SORT_NUMERIC); break; case 'index desc': case 'index key desc': krsort($items, SORT_STRING); break; case 'index asc': case 'index key asc': default: ksort($items, SORT_STRING); break; } } return array_values($items); } /** * Callback function for the 'Filter by book' facet block. */ function apachesolr_search_get_book($facet, &$options) { if (is_numeric($facet)) { return db_query('SELECT title FROM {node} WHERE nid = :nid', array(':nid' => $facet))->fetchField(); } else { $options['html'] = TRUE; return theme('placeholder', t('Not in any book')); } } function apachesolr_search_language_name($lang) { static $list = NULL; if (!isset($list)) { if (function_exists('locale_language_list')) { $list = locale_language_list(); } $list['und'] = t('Language neutral'); } return ($lang && isset($list[$lang])) ? $list[$lang] : $lang; } /** * Callback function for the 'Filter by name' facet block. */ function apachesolr_search_get_username($facet) { if (is_numeric($facet)) { return theme('apachesolr_breadcrumb_uid', array('field' => array('#value' => $facet))); } return ''; } /** * Callback function for the 'Filter by type' facet block. */ function apachesolr_search_get_type($facet) { $type = node_type_get_name($facet); if ($type === FALSE && $facet == 'comment') { $type = t('Comment'); } // A disabled or missing node type returns FALSE. return ($type === FALSE) ? $facet : $type; } /** * Implements hook_form_[form_id]_alter(). * * This adds spelling suggestions, retain filters to the search form. */ function apachesolr_search_form_search_form_alter(&$form, $form_state) { if ($form['module']['#value'] == 'apachesolr_search') { $form['#submit'][] = 'apachesolr_search_form_search_submit'; // No other modification make sense unless a query is active. // Note - this means that the query must always be run before // calling drupal_get_form('search_form'). $apachesolr_has_searched = apachesolr_has_searched(); $queryvalues = array(); if ($apachesolr_has_searched) { $query = apachesolr_current_query(); $queryvalues = $query->get_url_queryvalues(); } $form['basic']['apachesolr_search']['#tree'] = TRUE; $form['basic']['apachesolr_search']['queryvalues'] = array( '#type' => 'hidden', '#default_value' => json_encode($queryvalues), ); $form['basic']['apachesolr_search']['get'] = array( '#type' => 'hidden', '#default_value' => json_encode(array_diff_key($_GET, array('q' => 1, 'page' => 1, 'filters' => 1, 'solrsort' => 1, 'retain-filters' => 1))), ); //drupal_set_message('
'.print_r($form_state,1));
    if ($queryvalues || isset($form_state['input']['apachesolr_search']['retain-filters'])) {
      $form['basic']['apachesolr_search']['retain-filters'] = array(
        '#type' => 'checkbox',
        '#title' => t('Retain current filters'),
        '#default_value' => (int) isset($_GET['retain-filters']),
      );
    }

    if (variable_get('apachesolr_search_spellcheck', TRUE) && $apachesolr_has_searched && ($response = apachesolr_static_response_cache())) {
      // Get spellchecker suggestions into an array.
      if (isset($response->spellcheck->suggestions) && $response->spellcheck->suggestions) {
        $suggestions = get_object_vars($response->spellcheck->suggestions);
        if ($suggestions) {
          // Get the original query and replace words.
          $query = apachesolr_current_query();

          foreach ($suggestions as $word => $value) {
            $replacements[$word] = $value->suggestion[0];
          }
          $new_keywords = strtr($query->get_query_basic(), $replacements);

          // Show only if suggestion is different than current query.
          if ($query->get_query_basic() != $new_keywords) {
            $form['apachesolr_Search']['suggestion'] = array(
              '#prefix' => '
', '#suffix' => '
', '#markup' => '' . l($new_keywords, $query->get_path($new_keywords)), ); } } } } } } /** * Added form submit function to retain filters. * * @see apachesolr_search_form_search_form_alter() */ function apachesolr_search_form_search_submit($form, &$form_state) { $fv = $form_state['values']; $get = json_decode($fv['apachesolr_search']['get'], TRUE); $queryvalues = json_decode($fv['apachesolr_search']['queryvalues'], TRUE); if (!empty($fv['apachesolr_search']['retain-filters']) && $queryvalues) { $get = $queryvalues + $get; $get['retain-filters'] = '1'; } // Add the query values into the redirect. $form_state['redirect'] = array($form_state['redirect'], array('query' => $get)); } /** * Implements hook_form_[form_id]_alter(). * * This adds options to the apachesolr admin form. */ function apachesolr_search_form_apachesolr_settings_alter(&$form, $form_state) { $form['apachesolr_search_browse'] = array( '#type' => 'radios', '#title' => t('Behavior on empty search'), '#options' => array( 'none' => t("Show search box"), 'browse' => t("Show search box and enabled filters' blocks under the search box"), 'blocks' => t("Show search box and enabled filters' blocks in their configured regions"), 'results' => t("Show search box, enabled filters' blocks in their configured regions and first page of all available results"), ), '#default_value' => variable_get('apachesolr_search_browse', 'browse'), '#description' => t("This is what is shown when the user enters an empty search, or removes all filters from an active search. Remember to enable filters on the !filterslink and assign blocks to regions on the !blocklink", array( '!filterslink' => l('enabled filters page', 'admin/config/search/apachesolr/enabled-filters'), '!blocklink' => l('block settings page', 'admin/build/block'), )), ); $form['apachesolr_search_spellcheck'] = array( '#type' => 'checkbox', '#title' => t('Enable spellchecker and suggestions'), '#default_value' => variable_get('apachesolr_search_spellcheck', TRUE), '#description' => t('Enable spellchecker and get word suggestions. Also known as the "Did you mean ... ?" feature.'), ); $form['#submit'][] = 'apachesolr_search_build_spellcheck'; if (isset($form['buttons'])) { // Move buttons to the bottom. $buttons = $form['buttons']; unset($form['buttons']); $form['buttons'] = $buttons; } } /** * Implements hook_form_[form_id]_alter(). * * Rebuild (empty) the spellcheck dictionary when the index is deleted.. */ function apachesolr_search_form_apachesolr_delete_index_form_alter(&$form, $form_state) { $form['submit']['#submit'][] = 'apachesolr_search_build_spellcheck'; } function apachesolr_search_build_spellcheck($form, &$form_state) { try { $solr = apachesolr_get_solr(); $params['spellcheck'] = 'true'; $params['spellcheck.build'] = 'true'; $response = $solr->search('solr', 0, 0, $params); } catch (Exception $e) { watchdog('Apache Solr', nl2br(check_plain($e->getMessage())), NULL, WATCHDOG_ERROR); } } /** * Implements hook_theme(). */ function apachesolr_search_theme() { return array( 'apachesolr_breadcrumb_is_book_bid' => array( 'variables' => array('field' => NULL, 'exclude' => FALSE), ), 'apachesolr_breadcrumb_uid' => array( 'variables' => array('field' => NULL, 'exclude' => FALSE), ), 'apachesolr_breadcrumb_tid' => array( 'variables' => array('field' => NULL, 'exclude' => FALSE), ), 'apachesolr_breadcrumb_type' => array( 'variables' => array('field' => NULL, 'exclude' => FALSE), ), 'apachesolr_breadcrumb_language' => array( 'variables' => array('field' => NULL, 'exclude' => FALSE), ), 'apachesolr_breadcrumb_field' => array( 'variables' => array('field' => NULL), ), 'apachesolr_breadcrumb_changed' => array( 'variables' => array('field' => NULL), 'function' => 'theme_apachesolr_breadcrumb_date_range', ), 'apachesolr_breadcrumb_created' => array( 'variables' => array('field' => NULL), 'function' => 'theme_apachesolr_breadcrumb_date_range', ), 'apachesolr_browse_blocks' => array( 'variables' => array('blocks' => NULL), ), 'apachesolr_currentsearch' => array( 'variables' => array('total_found' => NULL, 'links' => NULL), ), 'apachesolr_search_snippets' => array( 'variables' => array('doc' => NULL, 'snippets' => array()), ), 'apachesolr_search_noresults' => array( 'variables' => array(), ), ); } /** * Alters the function used to theme breadcrumbs * @param string $fieldname * */ function apachesolr_search_apachesolr_theme_breadcrumb_alter(&$fieldname) { $matches = preg_split('/_field_/', $fieldname); if (isset($matches[1])) { $fieldname = 'apachesolr_breadcrumb_field'; } } /** * Theme function for fields in breadcrumbs. * TODO: The logic for getting here is too convoluted, and * there are too many bizarre naming conventions in play (_field_, _end). * The checks for _field_ and _end MUST get refactored. */ function theme_apachesolr_breadcrumb_field($vars) { $field = $vars['field']; $matches = preg_split('/_field_/', $field['#name']); if (isset($matches[1])) { $match = 'field_'.$matches[1]; // TODO: If the apachesolr_date module is present we might // have fields with the suffix '_end'. These are the end-dates // and need the suffix removed. if (module_exists('apachesolr_date')) { $match = preg_replace('/_end{1}$/', '', $match); } //$mappings = apachesolr_cck_fields(); $mappings = apachesolr_entity_fields('node'); if (isset($mappings[$match]['display_callback'])) { $function = $mappings[$match]['display_callback']; if (function_exists($function)) { $facet = $field['#value']; $options = array_merge($mappings[$match], array('delta' => $matches[1])); return $function($facet, $options); } } } return $field['#value']; } function theme_apachesolr_breadcrumb_language($vars) { $field = $vars['field']; return apachesolr_search_language_name($field['#value']); } /** * Proxy theme function for 'created' and 'changed' date fields. */ function theme_apachesolr_breadcrumb_date_range($vars) { $field = $vars['field']; if (preg_match('@[\[\{](\S+) TO (\S+)[\]\}]@', $field['#value'], $match)) { return apachesolr_date_format_range($match[1], $match[2]); } return $field['#value']; } /** * Return the username from $uid */ function theme_apachesolr_breadcrumb_uid($vars) { $field = $vars['field']; if ($field['#value'] == 0) { return variable_get('anonymous', t('Anonymous')); } else { return db_query("SELECT name FROM {users} WHERE uid = :uid", array(':uid' => $field['#value']))->fetchField(); } } /** * Return the term name from $tid, or $tid as a fallback. */ function theme_apachesolr_breadcrumb_tid($vars) { $field = $vars['field']; if (function_exists('taxonomy_term_load')) { if ($term = taxonomy_term_load($field['#value'])) { return $term->name; } } return $field['#value']; } /** * Return the human readable text for a content type. */ function theme_apachesolr_breadcrumb_type($vars) { $field = $vars['field']; return node_type_get_name($field['#value']); } /** * Return the title of a book. */ function theme_apachesolr_breadcrumb_is_book_bid($vars) { $field = $vars['field']; if (is_numeric($field['#value'])) { return db_query('SELECT title FROM {node} WHERE nid = :nid', array(':nid' => $field['#value']))->fetchField(); } else { return t('Not in any book'); } } /** * Return current search block contents */ //function theme_apachesolr_currentsearch($total_found, $links) { function theme_apachesolr_currentsearch($vars) { return theme_item_list(array( 'items' => $vars['links'], 'title' => format_plural($vars['total_found'], 'Search found 1 item', 'Search found @count items'), 'type' => 'ul', 'attributes' => array())); } /** * Theme the highlighted snippet text for a search entry. * * @param array $vars * */ function theme_apachesolr_search_snippets($vars) { $result = ''; if (is_array($vars['snippets'])) { $snippets = $vars['snippets']; if (isset($snippets['body'])) { $result .= $snippets['body']; unset($snippets['body']); } if (isset($snippets['teaser'])) { $result .= (strlen($result) > 0) ? ' ... ' : ''; $result .= $snippets['teaser']; unset($snippets['teaser']); } $result .= (strlen($result) > 0) ? ' ... ' : ''; $result .= implode(' ... ', $snippets); } return $result . ' ...'; } /** * Brief message to display when no results match the query. * * @see search_help() */ function theme_apachesolr_search_noresults() { return t(''); } function apachesolr_search_get_parent_terms($tids) { // Find the starting tid terms and then all their parents. $parent_terms = array(); $new_tids = $tids; do { $query = db_select('taxonomy_term_hierarchy', 't') ->fields('t', array('tid', 'parent')) ->innerJoin('taxonomy_term_data', 'td', 't.tid = td.tid') ->fields('td', array('vid', 'name', 'weight')) ->condition('t.tid', $new_tids, 'IN'); $query->addTag('node_access'); $result = $query->execute(); $new_tids = array(); foreach($result as $term){ $parent_terms[$term->tid] = $term; if ($term->parent > 0) { $new_tids[] = $term->parent; } } } while ($new_tids); return $parent_terms; } /** * Return the contents of the "Current search" block. * * @param $response * The Solr response object. * @param $query * The Solr query object. */ function apachesolr_search_currentsearch_block($response, DrupalSolrQueryInterface $query) { $fields = $query->get_filters(); $links = array(); $facets = array(); // If current search has keys, offer current search without them if ($keys = $query->get_query_basic()) { $links[] = theme('apachesolr_unclick_link', array( 'facet_text' => $keys, 'path' => $query->get_path(''), 'options' => array('query' => $query->get_url_queryvalues()))); } // We don't directly render any items with a parent. foreach ($fields as $index => $field) { $fields[$index]['#active'] = TRUE; if (!empty($fields[$index]['#parent']) || !$field['#name']) { // We will render it via its parent. unset($fields[$index]); } } $links = array_merge($links, apachesolr_search_nested_facet_items($query, $fields, $response->response->numFound, FALSE)); if ($links) { $content = theme('apachesolr_currentsearch', array('total_found' => $response->response->numFound, 'links' => $links)); return array('subject' => t('Current search'), 'content' => $content); } } /** * Return an array of taxonomy facets that should be displayed hierarchically. */ function apachesolr_search_get_hierarchical_vocabularies() { static $result; if (!isset($result)) { $result = array(); if (function_exists('taxonomy_get_vocabularies')) { $vocabularies = taxonomy_get_vocabularies(); $force_flat = variable_get('apachesolr_search_force_flat_vocabularies', array()); foreach ($vocabularies as $voc) { // See if the vocabulary is hierarchical and not not designated to be // forced to display flat. Note that even free-tagging vocabs may // have admin-defined hierarchy. if ($voc->hierarchy == 1 && empty($force_flat[$voc->vid])) { $result[$voc->vid] = 1; } } } } return $result; }