l(t('running cron'), 'admin/logs/status/run-cron', array(), 'destination=admin/settings/apachesolr/index'), '%percentage' => ((int)min(100, 100 * ($total - $remaining) / max(1, $total))) .'%', '@items' => format_plural($remaining, t('is 1 item'), t('are @count items') ))); } } /** * Implementation of hook_menu(). */ function apachesolr_search_menu($may_cache) { $items = array(); if ($may_cache) { $items[] = array( 'path' => 'admin/settings/apachesolr/query-fields', 'title' => t('Search fields'), 'callback' => 'apachesolr_search_settings_page', 'access' => user_access('administer search'), 'weight' => 1, 'type' => MENU_LOCAL_TASK, ); $items[] = array( 'path' => 'admin/settings/apachesolr/content-bias', 'title' => t('Content bias settings'), 'callback' => 'apachesolr_boost_settings_page', 'access' => user_access('administer search'), 'weight' => 1, 'type' => MENU_LOCAL_TASK, ); } return $items; } /** * Implementation of hook_update_index(). */ function apachesolr_search_update_index() { $cron_limit = variable_get('apachesolr_cron_limit', 50); $rows = apachesolr_get_nodes_to_index('apachesolr_search', $cron_limit); apachesolr_index_nodes($rows, 'apachesolr_search'); } /** * Implementation of 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(). */ function apachesolr_search_search($op = 'search', $keys = NULL) { switch ($op) { case 'name': return t('Search'); case 'reset': apachesolr_clear_last_index('apachesolr_search'); return; case 'status': return apachesolr_index_status('apachesolr_search'); case 'search': $filters = isset($_GET['filters']) ? $_GET['filters'] : ''; $solrsort = isset($_GET['solrsort']) ? $_GET['solrsort'] : ''; $page = isset($_GET['page']) ? $_GET['page'] : 0; try { $results = apachesolr_search_execute($keys, $filters, $solrsort, 'search/' . arg(1), $page); return $results; } catch (Exception $e) { watchdog('Apache Solr', nl2br(check_plain($e->getMessage())), WATCHDOG_ERROR); apachesolr_failure(t('Solr search'), $keys); } break; } // switch } /** * Re-implementation of search_view(). */ function apachesolr_search_view($type = NULL) { // Search form submits with POST but redirects to GET. $results = ''; if (!isset($_POST['form_id'])) { if (empty($type)) { // Note: search/X can not be a default tab because it would take on the // path of its parent (search). It would prevent remembering keywords when // switching tabs. This is why we drupal_goto to it from the parent instead. drupal_goto('search/apachesolr_search'); } $keys = trim(search_get_keys()); $filters = ''; if (isset($_GET['filters'])) { $filters = trim($_GET['filters']); } // Only perform search if there is non-whitespace search term or filters: if ($keys || $filters) { // Log the search keys: $log = $keys; if ($filters) { $log .= 'filters='. $filters; } watchdog('search', t('!log (!search)', array('!log' => $log, '!search' => 'Search')), WATCHDOG_NOTICE, l(t('results'), 'search/'. $type .'/'. $keys)); // Collect the search results: $results = search_data($keys, $type); if ($results) { $results = theme('box', t('Search results'), $results); } else { $results = theme('box', t('Your search yielded no results'), variable_get('apachesolr_search_noresults', apachesolr_search_noresults())); } } } // Construct the search form. return drupal_get_form('search_form', NULL, $keys, $type) . $results; } function apachesolr_search_noresults() { return t(''); } /** * Execute a search results based on keyword, filter, and sort strings. * * @throws Exception */ function apachesolr_search_execute($keys, $filters, $solrsort, $base_path = '', $page = 0, $caller = 'apachesolr_search') { $params = array(); // This is the object that knows about the query coming from the user. $query = apachesolr_drupal_query($keys, $filters, $solrsort, $base_path); if (empty($query)) { throw new Exception(t('Could not construct a Solr query in function apachesolr_search_search()')); } $params += apachesolr_search_basic_params($query); if ($keys) { $params += apachesolr_search_highlighting_params($query); $params += apachesolr_search_spellcheck_params($query); } else { // No highlighting, use the teaser as a snippet. $params['fl'] .= ',teaser'; } apachesolr_search_add_facet_params($params, $query); apachesolr_search_add_boost_params($params, $query, apachesolr_get_solr()); list($final_query, $response) = apachesolr_do_query($caller, $query, $params, $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(menu_get_active_breadcrumb(), $query->get_breadcrumb())); return apachesolr_process_response($response, $final_query, $params); } function apachesolr_search_basic_params($query) { $params = array( 'fl' => 'id,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' ); 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($query) { $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); return $params; } function apachesolr_search_spellcheck_params($query) { $params = array(); if (variable_get('apachesolr_search_spellcheck', FALSE)) { //Add new parameter to the search request $params['spellcheck.q'] = $query->get_query_basic(); $params['spellcheck'] = 'true'; } return $params; } function apachesolr_search_add_facet_params(&$params, $query) { $facet_query_limits = variable_get('apachesolr_facet_query_limits', array()); $facet_missing = variable_get('apachesolr_facet_missing', array()); foreach (apachesolr_get_enabled_facets() 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) { // TODO: generalize handling of date and range facets. if ($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 { $params['facet.field'][] = $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(&$params, $query, $solr) { // 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 = $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]; } } } $data = $solr->getLuke(); if (isset($data->index->numDocs)) { $total = $data->index->numDocs; } else { $total = db_result(db_query("SELECT COUNT(nid) FROM {node}")); } // 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/DisMaxRequestHandler // rord() returns 1 for the newset doc, and the number in the index for // the oldest doc. The function is thus: $total/(rord()*$steepness + $total). $date_settings = variable_get('apachesolr_search_date_boost', '4:200.0'); list($date_steepness, $date_boost) = explode(':', $date_settings); if ($date_boost) { $params['bf'][] = "recip(rord(created),$date_steepness,$total,$total)^$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(rord(comment_count),$comment_steepness,$total,$total)^$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) { $params['bf'][] = "recip(rord(last_comment_or_change),$changed_steepness,$total,$total)^$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_process_response($response, $query, $params) { $results = array(); // We default to getting snippets from the body. $hl_fl = is_null($params['hl.fl']) ? 'body' : $params['hl.fl']; $total = $response->response->numFound; apachesolr_pager_init($total, $params['rows']); if ($total > 0) { foreach ($response->response->docs as $doc) { $extra = array(); // Find the nicest available snippet. if (isset($response->highlighting->{$doc->id}->$hl_fl)) { $snippet = theme('apachesolr_search_snippets', $doc, $response->highlighting->{$doc->id}->$hl_fl); } elseif (isset($doc->teaser)) { $snippet = theme('apachesolr_search_snippets', $doc, array(truncate_utf8($doc->teaser, 256, TRUE))); } else { $snippet = ''; } if (!isset($doc->body)) { $doc->body = $snippet; } $doc->created = strtotime($doc->created); $doc->changed = strtotime($doc->changed); // Allow modules to alter each document. drupal_alter('apachesolr_search_result', $doc); $fields = array(); foreach ($doc->getFieldNames() as $field_name) { $fields[$field_name] = $doc->getField($field_name); } // Copy code from comment_nodeapi(). $extra[] = format_plural($doc->comment_count, '1 comment', '@count comments'); $results[] = array( 'link' => url($doc->path, NULL, NULL, TRUE), 'type' => apachesolr_search_get_type($doc->type), // template_preprocess_search_result() runs check_plain() on the title // again. Decode to correct the display. 'title' => htmlspecialchars_decode($doc->title, ENT_QUOTES), 'user' => theme('username', $doc), 'date' => $doc->created, 'node' => $doc, 'extra' => $extra, 'score' => $doc->score, 'snippet' => $snippet, 'fields' => $fields, ); } // 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; } function apachesolr_search_date_range($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_result(db_query("SELECT MIN($facet_field) FROM {node} WHERE status = 1"))); // 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_result(db_query("SELECT MAX($facet_field) FROM {node} WHERE status = 1")) - 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"); } /** * Implementation of 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', ); } // Get taxonomy vocabulary facets. if (module_exists('taxonomy')) { $vocabs = taxonomy_get_vocabularies(); foreach ($vocabs as $vid => $vocab) { // In this case the delta and facet field are the same. $delta = 'im_vid_' . $vid; $facets[$delta] = array( 'info' => t('Taxonomy vocabulary: Filter by taxonomy @name', array('@name' => $vocab->name)), 'facet_field' => $delta, ); } } // Get CCK field facets. $fields = apachesolr_cck_fields(); if ($fields) { foreach ($fields as $name => $field) { // $delta can only be 32 chars, and the CCK field name may be this // long also, so we cannot add anything to it. $facets[$field['field_name']] = array_merge($field, array( 'info' => t('CCK @field_type field: Filter by @field (@field_name)', array('@field_type' => $field['field_type'], '@field' => $field['label'], '@field_name' => $field['field_name'])), 'facet_field' => apachesolr_index_key($field), ) ); } } return $facets; } /** * Implementation of hook_block(). */ function apachesolr_search_block($op = 'list', $delta = 0, $edit = array()) { switch ($op) { case 'list': $enabled_facets = apachesolr_get_enabled_facets('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] /* From D6: + array('cache' => BLOCK_CACHE_PER_PAGE,) */; } } $blocks['currentsearch'] = array( 'info' => t('Apache Solr Search: Current search'), /* From D6: '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(); $facets = apachesolr_get_enabled_facets('apachesolr_search'); if (empty($facets[$delta]) && ($delta != 'currentsearch')) { return; } // Handle taxonomy vocabulary facets if ((strpos($delta, 'im_vid_') === 0)) { return apachesolr_search_taxonomy_facet_block($response, $query, $delta); } switch ($delta) { case 'currentsearch': return apachesolr_search_currentsearch_block($response, $query); case 'is_book_bid': return apachesolr_facet_block($response, $query, 'apachesolr_search', $delta, $delta, t('Filter by book'), 'apachesolr_search_get_book'); case 'language': return apachesolr_facet_block($response, $query, 'apachesolr_search', $delta, $delta, t('Filter by language'), 'apachesolr_search_language_name'); case 'uid': return apachesolr_facet_block($response, $query, 'apachesolr_search', $delta, $delta, t('Filter by author'), 'apachesolr_search_get_username'); case 'type': return apachesolr_facet_block($response, $query, 'apachesolr_search', $delta, $delta, t('Filter by type'), 'apachesolr_search_get_type'); case 'changed': return apachesolr_date_facet_block($response, $query, 'apachesolr_search', $delta, $delta, t('Filter by modification date')); case 'created': return apachesolr_date_facet_block($response, $query, 'apachesolr_search', $delta, $delta, t('Filter by post date')); default: if ($fields = apachesolr_cck_fields()) { foreach ($fields as $name => $field) { if ($field['field_name'] == $delta) { $index_key = apachesolr_index_key($field); $callback = isset($field['display_callback']) ? $field['display_callback'] : FALSE; return apachesolr_facet_block($response, $query, 'apachesolr_search', $delta, $index_key, t('Filter by @field', array('@field' => $field['label'])), $callback); } } } } break; } break; case 'configure': if ($delta != 'currentsearch') { return apachesolr_facetcount_form('apachesolr_search', $delta); } break; case 'save': if ($delta != 'currentsearch') { apachesolr_facetcount_save($edit); } break; } } /** * 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; } /** * Generate the facet block for a taxonomy vid delta. */ function apachesolr_search_taxonomy_facet_block($response, $query, $delta) { $vid = substr($delta, 7); if (!module_exists('taxonomy') || !is_numeric($vid)) { return; } // Check that we have a response and a valid vid. if (is_object($response->facet_counts->facet_fields->$delta) && ($vocab = taxonomy_get_vocabulary($vid))) { $reflect_hierarchy = apachesolr_search_get_hierarchical_vocabularies(); $contains_active = FALSE; $facets = array(); foreach ($response->facet_counts->facet_fields->$delta as $tid => $count) { // TODO - for now we don't handle facet missing. if ($tid != '_empty_') { $active = $query->has_filter('tid', $tid); if ($active) { $contains_active = TRUE; } $facets[$tid] = array( '#name' => 'tid', '#value' => $tid, '#exclude' => FALSE, '#count' => $count, '#parent' => 0, '#children' => array(), '#has_children' => FALSE, '#active' => $active, ); } } if ($facets && $reflect_hierarchy[$vocab->vid]) { $placeholders = db_placeholders($facets); $tids = array_keys($facets); // @todo: faster as 2x separate queries? $result = db_query("SELECT tid, parent FROM {term_hierarchy} WHERE parent > 0 AND (tid IN ($placeholders) OR parent IN ($placeholders))", array_merge($tids, $tids)); while ($term = db_fetch_object($result)) { // Mark all terms that are parents for later CSS class. // We assume data in the Solr index is complete - potential for some // breakage here. if (isset($facets[$term->parent])) { $facets[$term->parent]['#has_children'] = TRUE; if (isset($facets[$term->tid])) { $facets[$term->tid]['#parent'] = $term->parent; // Use a reference so we see the updated data. $facets[$term->parent]['#children'][] = &$facets[$term->tid]; } } } // Check for the case like starting on a taxonomy/term/$tid page // where parents are not marked as active. // @todo: can we make this more efficient? do { $added_active = FALSE; foreach ($facets as $tid => $field) { if ($field['#active'] && $field['#parent'] && !$facets[$field['#parent']]['#active']) { // This parent has an active child. $added_active = TRUE; $query->add_filter('tid', $field['#parent']); $facets[$field['#parent']]['#active'] = TRUE; } } } while ($added_active); foreach ($facets as $tid => $field) { if (!empty($field['#parent'])) { // We will render it via its parent. unset($facets[$tid]); } } } $items = apachesolr_search_nested_facet_items($query, $facets, $response->response->numFound); // Process all terms into an item list if ($items && ($response->response->numFound > 1 || $contains_active)) { // Get information needed by the taxonomy blocks about limits. $initial_limits = variable_get('apachesolr_facet_query_initial_limits', array()); $limit_default = variable_get('apachesolr_facet_query_initial_limit_default', 10); $limit = isset($initial_limits['apachesolr_search'][$delta]) ? $initial_limits['apachesolr_search'][$delta] : $limit_default; return array( 'subject' => t('Filter by @name', array('@name' => $vocab->name)), 'content' => theme('apachesolr_facet_list', $items, $limit), ); } } } /** * Callback function for the 'Filter by book' facet block. */ function apachesolr_search_get_book($facet, &$options) { if (is_numeric($facet)) { return db_result(db_query('SELECT title FROM {node} WHERE nid = %d', $facet)); } 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; } /** * 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($query, $facets, $num_found, $sort = TRUE) { $items = array(); foreach ($facets as $field) { $breadcrumb_name = 'apachesolr_breadcrumb_'. $field['#name']; drupal_alter('apachesolr_theme_breadcrumb', $breadcrumb_name); $facet_text = theme($breadcrumb_name, $field, $field['#exclude']); if (!$facet_text) { $facet_text = $field['#value']; } $link = array(); $new_query = clone $query; if (!empty($field['#active'])) { // '*' sorts before all numbers. $sortpre = '*'; 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', $facet_text, $new_query->get_path(), $options); } else { $sortpre = 1000000 - $field['#count']; $new_query->add_filter($field['#name'], $field['#value']); $options = array('query' => $new_query->get_url_queryvalues()); $link['data'] = theme('apachesolr_facet_link', $facet_text, $new_query->get_path(), $options, $field['#count'], FALSE, $num_found); } // We don't display children unless the parent is clicked. if (!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) { ksort($items); } return array_values($items); } /** * 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('#value' => $facet)); } return ''; } /** * Callback function for the 'Filter by type' facet block. */ function apachesolr_search_get_type($facet) { $type = node_get_types('name', $facet); // A disabled or missing node type returns FALSE. $name = ($type === FALSE) ? $facet : $type; return apachesolr_tt("nodetype:type:$facet:name", $name); } /** * Process a block search form submission. * * @see search_box_form_submit() */ function apachesolr_search_search_box_form_submit($form_id, $form_values) { // The search form relies on control of the redirect destination for its // functionality, so we override any static destination set in the request, // for example by drupal_access_denied() or drupal_not_found() // (see http://drupal.org/node/292565). if (isset($_REQUEST['destination'])) { unset($_REQUEST['destination']); } if (isset($_REQUEST['edit']['destination'])) { unset($_REQUEST['edit']['destination']); } $keys = $form_values[$form_id .'_keys']; // Handle Apache webserver clean URL quirks. if (variable_get('clean_url', '0')) { $keys = str_replace('+', '%2B', $keys); } return 'search/apachesolr_search/'. trim($keys); } /** * Added form submit function to account for Apache mode_rewrite quirks. * * @see apachesolr_search_form_search_form_alter() */ function apachesolr_search_form_search_submit($form_id, $form_values) { $fv = $form_values; $keys = $fv['processed_keys']; $base = 'search/' . $fv['module'] . '/'; if (variable_get('clean_url', '0')) { $keys = str_replace('+', '%2B', $keys); } $get = unserialize($fv['apachesolr_search']['get']); $queryvalues = unserialize($fv['apachesolr_search']['queryvalues']); if (!empty($fv['apachesolr_search']['retain-filters']) && $queryvalues) { $get = $queryvalues + $get; $get['retain-filters'] = '1'; } if ($keys == '' && !$queryvalues) { form_set_error('keys', t('Please enter some keywords.')); } if (empty($get) || !is_array($get)) { return $base . $keys; } else { return array($base . $keys, drupal_query_string_encode($get)); } } /** * Implementation of hook_form_alter(). * * This adds options to the apachesolr admin form. */ function apachesolr_search_form_alter($form_id, &$form) { switch ($form_id) { case 'apachesolr_delete_index_form': if ($form_values['op'] == t('Delete the index')) { // In D6: $form['submit']['#submit'][] = 'apaechesolr_search_build_spellcheck'; $form['#submit']['apachesolr_search_build_spellcheck'] = array(); } break; case 'search_form': if ($form['module']['#value'] == 'apachesolr_search') { $form['#submit']['apachesolr_search_form_search_submit'] = array(); // 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' => serialize($queryvalues), ); $form['basic']['apachesolr_search']['get'] = array( '#type' => 'hidden', '#default_value' => serialize(array_diff_key($_GET, array('q' => 1, 'page' => 1, 'filters' => 1, 'solrsort' => 1, 'retain-filters' => 1))), ); if ($queryvalues || isset($_POST ['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', FALSE) && $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['basic']['suggestion'] = array( '#prefix' => '
', '#suffix' => '
', '#type' => 'item', '#title' => t('Did you mean'), '#value' => l($new_keywords, $query->get_path($new_keywords)), ); } } } } } break; case 'search_block_form': if (variable_get('apachesolr_search_make_default', 0)) { if (!isset($form['#submit'])) { $form['#submit']['apachesolr_search_search_box_form_submit'] = array(); } else { $key = isset($form['#submit']['search_box_form_submit']) ? 'search_box_form_submit' : FALSE; if ($key !== FALSE) { // Replace the search module's function. unset($form['#submit'][$key]); $form['#submit']['apachesolr_search_search_box_form_submit'] = array(); } } } break; case 'search_theme_form': apachesolr_search_form_alter('search_block_form', $form); break; case 'apachesolr_settings': $form['advanced']['apachesolr_search_make_default'] = array( '#type' => 'radios', '#title' => t('Make Apache Solr Search the default'), '#default_value' => variable_get('apachesolr_search_make_default', 0), '#options' => array(0 => t('Disabled'), 1 => t('Enabled')), '#description' => t('Hides core node search, and makes the search block submit to Apache Solr Search'), ); $form['advanced']['apachesolr_search_default_previous'] = array( '#type' => 'value', '#value' => variable_get('apachesolr_search_make_default', 0), ); $form['advanced']['apachesolr_search_taxonomy_links'] = array( '#type' => 'radios', '#title' => t('Use Apache Solr for taxonomy links'), '#default_value' => variable_get('apachesolr_search_taxonomy_links', 0), '#description' => t('Note: Vocabularies that need this behavior need to be checked off on the enabled filters settings page', array('@enabled_filters_url' => url('admin/settings/apachesolr/enabled-filters'))), '#options' => array(0 => t('Disabled'), 1 => t('Enabled')), ); $form['advanced']['apachesolr_search_taxonomy_previous'] = array( '#type' => 'value', '#value' => variable_get('apachesolr_search_taxonomy_links', 0), ); $form['apachesolr_search_spellcheck'] = array( '#type' => 'checkbox', '#title' => t('Enable spellchecker and suggestions'), '#default_value' => variable_get('apachesolr_search_spellcheck', FALSE), '#description' => t('Enable spellchecker and get word suggestions. Also known as the "Did you mean ... ?" feature.'), ); $form['#submit']['apachesolr_search_build_spellcheck'] = array(); $form['#submit']['apachesolr_search_make_default_submit'] = array(); // Move buttons to the bottom. $buttons = $form['buttons']; unset($form['buttons']); $form['buttons'] = $buttons; break; case 'search_admin_settings': $form['indexing_throttle']['search_cron_limit']['#options']['0'] = '0'; ksort($form['indexing_throttle']['search_cron_limit']['#options']); break; } } /** * Form submit funtion - do a menu rebuild if needed. * * @see apachesolr_search_form_apachesolr_settings_alter() */ function apachesolr_search_make_default_submit($form_id, $form_values) { // We use variable_get() instead of the form values so as to also handle reset to defaults. if ($form_values['apachesolr_search_default_previous'] != variable_get('apachesolr_search_make_default', 0) || $form_values['apachesolr_search_taxonomy_previous'] != variable_get('apachesolr_search_taxonomy_links', 0)) { // Take account of path changes menu_rebuild(); } } function apachesolr_search_build_spellcheck($form_id, $form_values) { 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())), WATCHDOG_ERROR); } } /** * Alters the function used to theme breadcrumbs * @param string $fieldname * */ function apachesolr_search_apachesolr_theme_breadcrumb_alter(&$fieldname) { $matches = preg_split('/_cck_/', $fieldname); if (isset($matches[1])) { $fieldname = 'apachesolr_breadcrumb_cck'; } } function theme_apachesolr_breadcrumb_cck($field) { $matches = preg_split('/_cck_/', $field['#name']); if (isset($matches[1])) { $mappings = apachesolr_cck_fields(); if (isset($mappings[$matches[1]]['display_callback'])) { $function = $mappings[$matches[1]]['display_callback']; if (function_exists($function)) { $facet = $field['#value']; $options = array('delta' => $matches[1]); return $function($facet, $options); } } } return $field['#value']; } function theme_apachesolr_breadcrumb_language($field, $exclude = FALSE) { return apachesolr_search_language_name($field['#value']); } /** * Proxy theme function for 'created' and 'changed' date fields. */ function theme_apachesolr_breadcrumb_date_range($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($field) { if ($field['#value'] == 0) { return variable_get('anonymous', t('Anonymous')); } else { return db_result(db_query("SELECT name FROM {users} WHERE uid = %d", $field['#value'])); } } /** * Return the term name from $tid, or $tid as a fallback. */ function theme_apachesolr_breadcrumb_tid($field) { if (function_exists('taxonomy_get_term')) { if ($term = taxonomy_get_term($field['#value'])) { return $term->name; } } return $field['#value']; } /** * Return the human readable text for a content type. */ function theme_apachesolr_breadcrumb_type($field) { $name = node_get_types('name', $field['#value']); return apachesolr_tt("nodetype:type:{$field['#value']}:name", $name); } /** * Return the title of a book. */ function theme_apachesolr_breadcrumb_is_book_bid($field) { if (is_numeric($field['#value'])) { return db_result(db_query('SELECT title FROM {node} WHERE nid = %d', $field['#value'])); } else { return t('Not in any book'); } } /** * Return current search block contents */ function theme_apachesolr_currentsearch($total_found, $links) { return theme_item_list($links, format_plural($total_found, 'Search found 1 item', 'Search found @count items')); } /** * Theme the highlighted snippet text for a search entry. * * @param object $doc * @param array $snippets * */ function theme_apachesolr_search_snippets($doc, $snippets) { return implode(' ... ', $snippets) .' ...'; } function apachesolr_get_parent_terms($tids) { // Find the starting tid terms and then all their parents. $parent_terms = array(); $new_tids = $tids; do { $result = db_query(db_rewrite_sql("SELECT t.tid, t.parent FROM {term_hierarchy} t WHERE t.tid IN (". db_placeholders($new_tids) .")", 't', 'tid'), $new_tids); $new_tids = array(); while ($term = db_fetch_object($result)) { $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, $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', $keys, $query->get_path(''), array('query' => $query->get_url_queryvalues())); } // Find all taxonomy terms to be treated in a hierarchy. if (module_exists('taxonomy')) { $reflect_hierarchy = apachesolr_search_get_hierarchical_vocabularies(); foreach ($fields as $index => $field) { if ($field['#name'] && 'tid' == $field['#name']) { $term = taxonomy_get_term($field['#value']); if ($reflect_hierarchy[$term->vid]) { $fields[$index] += array('#parent' => 0, '#children' => array()); // Just save the index for later lookup. $facets[$term->tid] = $index; } } } if ($facets) { // Get all term hierarchy information. $all_terms = apachesolr_get_parent_terms(array_keys($facets)); foreach ($all_terms as $tid => $term) { if (!isset($facets[$tid])) { // This is a parent that is missing from the query. E.g. we started // on a taxonomy/term/$tid page. $query->add_filter('tid', $tid); // Ordering is wonky, but oh well... $fields[] = array('#name' => 'tid', '#value' => $tid, '#exclude' => FALSE, '#parent' => 0, '#children' => array()); // Get the index of the newly added facet. end($fields); $facets[$tid] = key($fields); } } foreach ($all_terms as $tid => $term) { $index = $facets[$term->tid]; if (isset($facets[$term->parent])) { // Use a reference so we see the updated data. $fields[$facets[$term->parent]]['#children'][] = &$fields[$index]; $fields[$index]['#parent'] = $term->parent; } } } } // 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', $response->response->numFound, $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) { // If the vocabulary is not multiple-parent hierarchical and not // freetagging and not designated to be forced to display flat. if ($voc->hierarchy != 2 && $voc->tags != 1 && empty($force_flat[$voc->vid])) { $result[$voc->vid] = 1; } } } } return $result; }