diff --git a/xapian.install b/xapian.install index 838b3fd..d6c2e67 100644 --- a/xapian.install +++ b/xapian.install @@ -92,3 +92,12 @@ function xapian_update_6001() { return $ret; } + +/** + * Flush the cache so that the settings shows up on the search admin page. + */ +function xapian_update_6002() { + cache_clear_all(); + menu_rebuild(); + return array(); +} diff --git a/xapian.module b/xapian.module index 30ba0c6..6f8b5ce 100644 --- a/xapian.module +++ b/xapian.module @@ -22,6 +22,95 @@ define('XAPIAN_LOCAL', 0); define('XAPIAN_REMOTE', 1); /** + * Implementation of hook_perm(). + */ +function xapian_perm() { + return array( + 'administer xapian' + ); +} + +/** + * Implementation of hook_search(). + * + * @param unknown_type $op + * @param unknown_type $keys + * @return unknown + */ +function xapian_search($op = 'search', $keys = NULL) { + switch ($op) { + case 'name': + return t('Search'); + break; + + case 'reset': + xapian_clear_index(); + break; + + case 'status': + return xapian_index_status(); + break; + + case 'admin': + return xapian_admin(); + break; + + case 'search': + return xapian_do_search($keys); + break; + } +} + +function xapian_index_status() { + $count_result = db_query("SELECT COUNT(*) FROM {xapian_index_queue}"); + $total = db_result(db_query('SELECT COUNT(*) FROM {node} WHERE status = 1')); + $remaining = db_result($count_result); + + return array('remaining' => $remaining, 'total' => $total); +} + +/** + * Implementation of hook_form_alter(). + * + * @param unknown_type $form + * @param unknown_type $form_state + * @param unknown_type $form_id + */ +function xapian_form_alter(&$form, &$form_state, $form_id) { + if (variable_get('xapian_takeover', FALSE)) { + if ($form_id == 'search_theme_form' || $form_id == 'search_block_form') { + $form['#submit'][] = 'xapian_search_submit'; + } + } +} + +/** + * Implementation of hook_submit(). + * + * @param unknown_type $form + * @param unknown_type $form_state + */ +function xapian_search_submit($form, &$form_state) { + $form_id = $form['form_id']['#value']; + $form_state['redirect'] = 'search/xapian/'. trim($form_state['values'][$form_id]); +} + +/** + * Implementation of hook_menu_alter(). + * + * @param unknown_type $items + */ +function xapian_menu_alter(&$items) { + if (variable_get('xapian_takeover', FALSE)) { + // Remove the std content/node search + unset($items['search/node/%menu_tail']); + + // Move Xapian search to the front + $items['search/xapian/%menu_tail']['weight'] = -1; + } +} + +/** * Provide default error handler for xapian * * @param int $errno @@ -91,41 +180,6 @@ function xapian_requirements($phase) { } /** - * Implementation of hook_perm - */ -function xapian_perm() { - return array( - 'administer xapian' - ); -} - -/** - * Implementation of hook_menu - * - * @param bool $may_cache - * @return array - */ -function xapian_menu() { - $items['admin/settings/xapian'] = array( - 'title' => 'Xapian settings', - 'description' => 'Settings for the Xapian search library', - 'page callback' => 'drupal_get_form', - 'page arguments' => array('xapian_admin'), - 'access arguments' => array('administer xapian'), - 'type' => MENU_NORMAL_ITEM, - ); - $items['admin/settings/xapian/wipe'] = array( - 'title' => 'Rebuild index', - 'page callback' => 'drupal_get_form', - 'page arguments' => array('xapian_reindex_all_confirm'), - 'access arguments' => array('administer search'), - 'type' => MENU_CALLBACK, - ); - - return $items; -} - -/** * implemtation of hook_admin * * @return array @@ -136,14 +190,32 @@ function xapian_admin() { $database_type = variable_get('xapian_database_type', XAPIAN_LOCAL); + // Create an all encompasing Xapian fieldset + $form['xapian'] = array( + '#type' => 'fieldset', + '#collapsed' => TRUE, + '#collapsible' => TRUE, + '#weight' => 0, + '#title' => t('Xapian search settings') + ); + + $form['xapian']['xapian_takeover'] = array( + '#type' => 'checkbox', + '#title' => t('Xapian only, hide drupal search'), + '#default_value' => variable_get('xapian_takeover', FALSE), + '#description' => t('Enable this option to replace the standard drupal search entirely.'), + ); + // Create a database fieldset - $form['database'] = array( + $form['xapian']['database'] = array( '#type' => 'fieldset', + '#collapsed' => ($database_type ? FALSE : TRUE), + '#collapsible' => TRUE, '#title' => t('Xapian database') ); // Database type - $form['database']['xapian_database_type'] = array( + $form['xapian']['database']['xapian_database_type'] = array( '#type' => 'radios', '#title' => t('Type'), '#default_value' => $database_type ? $database_type : XAPIAN_LOCAL, @@ -153,13 +225,13 @@ function xapian_admin() { ); // local database settings - $form['database']['local_database'] = array( + $form['xapian']['database']['local_database'] = array( '#type' => 'fieldset', '#title' => t('Local database options'), '#collapsible' => TRUE, '#collapsed' => ($database_type != XAPIAN_LOCAL) ); - $form['database']['local_database']['xapian_database_path'] = array( + $form['xapian']['database']['local_database']['xapian_database_path'] = array( '#type' => 'textfield', '#title' => t('Path to Xapian database'), '#default_value' => variable_get('xapian_database_path', file_directory_path() .'/xapian_database'), @@ -168,20 +240,20 @@ function xapian_admin() { ); // Remote database settings - $form['database']['remote_database'] = array( + $form['xapian']['database']['remote_database'] = array( '#type' => 'fieldset', '#title' => t('Remote database options'), '#collapsible' => TRUE, '#collapsed' => ($database_type != XAPIAN_REMOTE), ); - $form['database']['remote_database']['xapian_database_hostname'] = array( + $form['xapian']['database']['remote_database']['xapian_database_hostname'] = array( '#type' => 'textfield', '#title' => t('Database server'), '#default_value' => variable_get('xapian_database_hostname', ''), '#required' => ($database_type == XAPIAN_REMOTE), '#description' => t('IP address or host name of remote server running xapian-tcpsrv.'), ); - $form['database']['remote_database']['xapian_database_port'] = array( + $form['xapian']['database']['remote_database']['xapian_database_port'] = array( '#type' => 'textfield', '#title' => t('Database port'), '#default_value' => variable_get('xapian_database_port', '6431'), @@ -191,7 +263,7 @@ function xapian_admin() { ); // Optional write-only database (see http://drupal.org/node/282855) - $form['database']['writeonly'] = array( + $form['xapian']['database']['writeonly'] = array( '#type' => 'fieldset', '#title' => t('Optional write-only database settings'), '#collapsible' => TRUE, @@ -199,13 +271,13 @@ function xapian_admin() { '#description' => t('Leave these optional settings blank to use the above settings for both read and write database access. If you would like to send write queries to a different database than read queries, configure the remote write-only database settings below. Using a separate remote write-only server allows you to efficiently scale your search solution across multiple web servers, and avoids potential issues with lock contention.'), ); - $form['database']['writeonly']['xapian_write_database_hostname'] = array( + $form['xapian']['database']['writeonly']['xapian_write_database_hostname'] = array( '#type' => 'textfield', '#title' => t('Write-only database server'), '#default_value' => variable_get('xapian_write_database_hostname', ''), '#description' => t('IP address or host name of remote server running %writable.', array('%writable' => t('xapian-tcpsrv --writable'))), ); - $form['database']['writeonly']['xapian_write_database_port'] = array( + $form['xapian']['database']['writeonly']['xapian_write_database_port'] = array( '#type' => 'textfield', '#title' => t('Write-only database port'), '#default_value' => variable_get('xapian_write_database_port', ''), @@ -214,49 +286,27 @@ function xapian_admin() { ); // indexing settings - $form['performance'] = array( + $form['xapian']['performance'] = array( '#type' => 'fieldset', - '#title' => t('Performance') - ); - $count_result = db_query("SELECT COUNT(*) FROM {xapian_index_queue}"); - $nodes = db_result($count_result); - $form['performance']['count'] = array( - '#value' => t('

There are %count items waiting to be indexed.

', array('%count' => $nodes)), - ); - $form['performance']['index'] = array( - '#type' => 'submit', - '#value' => t('Re-index site'), + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#title' => t('Xapian Performance') ); - $form['performance']['xapian_index_immediately'] = array( + $form['xapian']['performance']['xapian_index_immediately'] = array( '#type' => 'checkbox', '#title' => t('Index immediately'), '#default_value' => variable_get('xapian_index_immediately', TRUE), '#description' => t('Enable this option to index content immediately as it is created and updated. Disable this option to delay indexing until cron runs. Your should disable this option on larger websites.'), ); - $items = drupal_map_assoc(array(10, 20, 50, 100, 500, 1000, 5000, 10000)); - $form['performance']['xapian_indexing_throttle'] = array( - '#type' => 'select', - '#title' => t('Items to index per cron run'), - '#description' => t('The maximum number of items that will be indexed in one cron run. Set this number lower if your cron is timing out or if PHP is running out of memory.'), - '#options' => $items, - '#default_value' => variable_get('xapian_indexing_throttle', 100), - ); // Display settings - $form['display'] = array( + $form['xapian']['display'] = array( '#type' => 'fieldset', - '#title' => t('Display') - ); - $form['display']['xapian_search_results_per_page'] = array( - '#type' => 'textfield', - '#title' => t('Number of search results per page'), - '#size' => 3, - '#maxlength' => 3, - '#default_value' => variable_get('xapian_search_results_per_page', 10), - '#description' => t('This setting determines the number of entries per page displayed on the search results.'), - '#validate' => array('_xapian_validate_positive_integer' => array('xapian_search_results_per_page')), + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#title' => t('Xapian Display') ); - $form['display']['xapian_node_count_type'] = array( + $form['xapian']['display']['xapian_node_count_type'] = array( '#type' => 'radios', '#title' => t('Result count'), '#description' => t('This setting determines the value that xapian returns for the result count returned from queries (used for number of pages in pagers, etc.)'), @@ -269,11 +319,13 @@ function xapian_admin() { ); // Logging options - $form['diagnostic'] = array( + $form['xapian']['diagnostic'] = array( '#type' => 'fieldset', - '#title' => t('Logging') + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#title' => t('Xapain Logging') ); - $form['diagnostic']['xapian_log_queries'] = array( + $form['xapian']['diagnostic']['xapian_log_queries'] = array( '#type' => 'checkbox', '#title' => t('Log searches'), '#description' => t('Log search queries and time taken for search to the watchdog log.'), @@ -281,18 +333,20 @@ function xapian_admin() { ); // Node Type Settings - $form['node_types'] = array( + $form['xapian']['node_types'] = array( '#type' => 'fieldset', - '#title' => t('Node types') + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#title' => t('Xapian Node types') ); - $form['node_types']['markup'] = array( + $form['xapian']['node_types']['markup'] = array( '#value' => t("

Select the node types to EXCLUDE from Xapian's indexing

"), ); $types = node_get_types('types'); foreach ($types as $key => $type) { $options[$key] = $type->name; } - $form['node_types']['xapian_excluded_nodes'] = array( + $form['xapian']['node_types']['xapian_excluded_nodes'] = array( '#type' => 'select', '#title' => t('Exclude indexing on'), '#options' => $options, @@ -301,13 +355,15 @@ function xapian_admin() { ); // Algorithms - $form['algorithms'] = array( + $form['xapian']['algorithms'] = array( '#type' => 'fieldset', - '#title' => t('Algorithms') + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#title' => t('Xapian Algorithms') ); $languages = _xapian_languages(); // See http://xapian.org/docs/stemming.html - $form['algorithms']['xapian_stem_language'] = array( + $form['xapian']['algorithms']['xapian_stem_language'] = array( '#type' => 'select', '#title' => t('Stemming language'), '#options' => $languages, @@ -317,23 +373,17 @@ function xapian_admin() { $form['#submit'][] = 'xapian_admin_submit'; - return system_settings_form($form); + return $form; } /** - * Validation function for admin form + * Implementation of hook_submit(). * - * @param string $form_id - * @param array $form_values + * @param unknown_type $form + * @param unknown_type $form_state */ -function xapian_admin_validate($form, &$form_state) { - if (t('Re-index site') == $form_state['values']['op']) { - drupal_goto('admin/settings/xapian/wipe'); - } -} - function xapian_admin_submit($form, &$form_state) { - if ($form_state['node_types']['xapian_excluded_nodes']['#values'] != variable_get('xapian_excluded_nodes', array())) { + if ($form_state['values']['xapian_excluded_nodes']['#values'] != variable_get('xapian_excluded_nodes', array())) { drupal_set_message('You should reindex your entire site after changing the types'); } } @@ -480,10 +530,12 @@ function xapian_query($query_string, $start = 0, $length = 10, $extra = array(), $query = $query_parser->parse_query($query_string); // Build subqueries from $extra array. - foreach ($extra as $subq) { - if (!empty($subq)) { - $subquery = new XapianQuery(XapianQuery::OP_OR, $subq); - $query = new XapianQuery(XapianQuery::OP_AND, array($subquery, $query)); + if (is_array($extra)) { + foreach ($extra as $subq) { + if (!empty($subq)) { + $subquery = new XapianQuery(XapianQuery::OP_OR, $subq); + $query = new XapianQuery(XapianQuery::OP_AND, array($subquery, $query)); + } } } @@ -497,9 +549,18 @@ function xapian_query($query_string, $start = 0, $length = 10, $extra = array(), $count++; $document = $i->get_document(); if (is_object($document)) { - $results[$count]->type = 'node'; - $results[$count]->sid = (int)($document->get_data()); - $results[$count]->score = (int)($i->get_percent()); + $node = node_load((int)($document->get_data())); + $results[$count] = array( + 'link' => url('node/'. $item->sid, array('absolute' => TRUE)), + 'type' => check_plain(node_get_types('name', $node)), + 'title' => $node->title, + 'user' => theme('username', $node), + 'date' => $node->changed, + 'node' => $node, + 'extra' => $extra, + 'score' => (int)($i->get_percent()), + 'snippet' => search_excerpt($query_string, $node->body), + ); } $i->next(); } @@ -527,7 +588,7 @@ function xapian_query($query_string, $start = 0, $length = 10, $extra = array(), break; } - return array($count, $results); + return $results; } catch (Exception $e) { watchdog('xapian', $e->getMessage()); @@ -536,39 +597,31 @@ function xapian_query($query_string, $start = 0, $length = 10, $extra = array(), } /** - * Check to see if we can implement our own hook_search + * Our xapian implementation of hook_search + * + * @param unknown_type $keywords + * @return array */ -if (!function_exists('do_search')) { - /** - * Our xapian implementation of hook_search - * - * @param unknown_type $keywords - * @param unknown_type $type - * @param unknown_type $join1 - * @param unknown_type $where1 - * @param unknown_type $arguments1 - * @param unknown_type $select2 - * @param unknown_type $join2 - * @param unknown_type $arguments2 - * @param unknown_type $sort_parameters - * @return unknown - */ - function do_search($keywords, $type, $join1 = '', $where1 = '1', $arguments1 = array(), $select2 = 'i.relevance AS score', $join2 = '', $arguments2 = array(), $sort_parameters = 'ORDER BY score DESC') { - global $pager_total; - global $pager_page_array; - - if (!empty($_REQUEST['page'])) { - $page = $_REQUEST['page']; - } - else { - $page = 0; - } +function xapian_do_search($keys) { + global $pager_total; + global $pager_page_array; - $start = variable_get('xapian_search_results_per_page', 10) * $page; - $length = variable_get('xapian_search_results_per_page', 10); + if (!empty($_REQUEST['page'])) { + $page = $_REQUEST['page']; + } + else { + $page = 0; + } + + $keywords = search_get_keys(); + + $start = variable_get('xapian_search_results_per_page', 10) * $page; + $length = variable_get('xapian_search_results_per_page', 10); - $terms = array(); - $types = array(); + $terms = array(); + $types = array(); + $extra = array(); + if (is_array($arguments1)) { foreach ($arguments1 as $condition) { // If its a number, its a taxonomy term if (is_numeric($condition)) { @@ -583,14 +636,14 @@ if (!function_exists('do_search')) { } } $extra = array($terms, $types); + } - list($count, $results) = xapian_query($keywords, $start, $length, $extra); + $results = xapian_query($keywords, $start, $length, $extra); - $pager_total[0] = (int)($count / variable_get('xapian_search_results_per_page', 10)) + 1; - $pager_page_array[0] = $page; + $pager_total[0] = (int)($count / variable_get('xapian_search_results_per_page', 10)) + 1; + $pager_page_array[0] = $page; - return $results; - } + return $results; } /** @@ -804,9 +857,9 @@ function xapian_index_queued_nodes() { // Re-index content that wasn't flushed to disk last time, minus the node // which failed last time. $sql = 'SELECT nid FROM {xapian_index_queue} WHERE status > 0 ORDER BY indexed ASC'; - $config = variable_get('xapian_indexing_throttle', 100); + $limit = variable_get('search_cron_limit', 100); $error = $not_flushed - 1; - $limit = $config > $error ? $error : $config; + $limit = $limit > $error ? $error : $limit; } else if ($not_flushed == 1) { // Re-index a node that previously failed to index, removing it from the @@ -818,7 +871,7 @@ function xapian_index_queued_nodes() { } else { $sql = 'SELECT nid FROM {xapian_index_queue} ORDER BY COALESCE(priority, 0) DESC, added ASC'; - $limit = variable_get('xapian_indexing_throttle', 100); + $limit = (int)variable_get('search_cron_limit', 100); } $result = db_query_range($sql, 0, $limit); @@ -887,20 +940,9 @@ function xapian_cron() { /** * Confirm recreation of search index. */ -function xapian_reindex_all_confirm() { - return confirm_form(array(), t('Are you sure you want to re-index the site?'), 'admin/settings/xapian', t('

This will queue up all site content to be re-indexed when cron runs. The search index is not cleared, instead it is systematically updated. Searching will continue to work.

If you have immediate indexing enabled, new content will continue to be indexed. If you do not have immediate indexing enabled, new content will not be indexed until all existing content has been re-indexed. This action cannot be undone.

'), t('Re-index site'), t('Cancel')); -} - -/** - * Reindex all site content. - */ -function xapian_reindex_all_confirm_submit($form, &$form_state) { - if ($form_state['values']['confirm']) { - db_query('TRUNCATE {xapian_index_queue}'); - db_query('INSERT INTO {xapian_index_queue} (nid, added) SELECT nid, UNIX_TIMESTAMP() FROM {node}'); - drupal_set_message(t('The search index will be rebuilt.')); - $form_state['redirect'] = 'admin/settings/xapian'; - } +function xapian_clear_index() { + db_query('TRUNCATE {xapian_index_queue}'); + db_query('INSERT INTO {xapian_index_queue} (nid, added) SELECT nid, UNIX_TIMESTAMP() FROM {node}'); } /**