Index: modules/node/node.info
===================================================================
RCS file: /cvs/drupal/drupal/modules/node/node.info,v
retrieving revision 1.10
diff -u -p -r1.10 node.info
--- modules/node/node.info 12 Oct 2008 01:23:04 -0000 1.10
+++ modules/node/node.info 7 Mar 2009 17:31:40 -0000
@@ -8,5 +8,6 @@ files[] = node.module
files[] = content_types.inc
files[] = node.admin.inc
files[] = node.pages.inc
+files[] = node.sql_search.inc
files[] = node.install
required = TRUE
Index: modules/node/node.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/node/node.module,v
retrieving revision 1.1027
diff -u -p -r1.1027 node.module
--- modules/node/node.module 26 Feb 2009 07:30:27 -0000 1.1027
+++ modules/node/node.module 7 Mar 2009 17:31:41 -0000
@@ -1391,137 +1391,6 @@ function _node_rankings() {
/**
- * Implementation of hook_search().
- */
-function node_search($op = 'search', $keys = NULL) {
- switch ($op) {
- case 'name':
- return t('Content');
-
- case 'reset':
- db_query("UPDATE {search_dataset} SET reindex = %d WHERE type = 'node'", REQUEST_TIME);
- return;
-
- case 'status':
- $total = db_result(db_query('SELECT COUNT(*) FROM {node} WHERE status = 1'));
- $remaining = db_result(db_query("SELECT COUNT(*) FROM {node} n LEFT JOIN {search_dataset} d ON d.type = 'node' AND d.sid = n.nid WHERE n.status = 1 AND d.sid IS NULL OR d.reindex <> 0"));
- return array('remaining' => $remaining, 'total' => $total);
-
- case 'admin':
- $form = array();
- // Output form for defining rank factor weights.
- $form['content_ranking'] = array(
- '#type' => 'fieldset',
- '#title' => t('Content ranking'),
- );
- $form['content_ranking']['#theme'] = 'node_search_admin';
- $form['content_ranking']['info'] = array(
- '#value' => '' . t('The following numbers control which properties the content search should favor when ordering the results. Higher numbers mean more influence, zero means the property is ignored. Changing these numbers does not require the search index to be rebuilt. Changes take effect immediately.') . ''
- );
-
- // Note: reversed to reflect that higher number = higher ranking.
- $options = drupal_map_assoc(range(0, 10));
- foreach (module_invoke_all('ranking') as $var => $values) {
- $form['content_ranking']['factors']['node_rank_'. $var] = array(
- '#title' => $values['title'],
- '#type' => 'select',
- '#options' => $options,
- '#default_value' => variable_get('node_rank_'. $var, 0),
- );
- }
- return $form;
-
- case 'search':
- // Build matching conditions
- list($join1, $where1) = _db_rewrite_sql();
- $arguments1 = array();
- $conditions1 = 'n.status = 1';
-
- if ($type = search_query_extract($keys, 'type')) {
- $types = array();
- foreach (explode(',', $type) as $t) {
- $types[] = "n.type = '%s'";
- $arguments1[] = $t;
- }
- $conditions1 .= ' AND (' . implode(' OR ', $types) . ')';
- $keys = search_query_insert($keys, 'type');
- }
-
- if ($term = search_query_extract($keys, 'term')) {
- $terms = array();
- foreach (explode(',', $term) as $c) {
- $terms[] = "tn.tid = %d";
- $arguments1[] = $c;
- }
- $conditions1 .= ' AND (' . implode(' OR ', $terms) . ')';
- $join1 .= ' INNER JOIN {taxonomy_term_node} tn ON n.vid = tn.vid';
- $keys = search_query_insert($keys, 'term');
- }
-
- if ($languages = search_query_extract($keys, 'language')) {
- $terms = array();
- foreach (explode(',', $languages) as $l) {
- $terms[] = "n.language = '%s'";
- $arguments1[] = $l;
- }
- $conditions1 .= ' AND (' . implode(' OR ', $terms) . ')';
- $keys = search_query_insert($keys, 'language');
- }
-
- // Get the ranking expressions.
- $rankings = _node_rankings();
-
- // When all search factors are disabled (ie they have a weight of zero),
- // The default score is based only on keyword relevance.
- if ($rankings['total'] == 0) {
- $total = 1;
- $arguments2 = array();
- $join2 = '';
- $select2 = 'SUM(i.relevance) AS calculated_score';
- }
- else {
- $total = $rankings['total'];
- $arguments2 = $rankings['arguments'];
- $join2 = implode(' ', $rankings['join']);
- $select2 = 'SUM('. implode(' + ', $rankings['score']) .') AS calculated_score';
- }
-
- // Do search.
- $find = do_search($keys, 'node', 'INNER JOIN {node} n ON n.nid = i.sid ' . $join1, $conditions1 . (empty($where1) ? '' : ' AND ' . $where1), $arguments1, $select2, $join2, $arguments2);
-
- // Load results.
- $results = array();
- foreach ($find as $item) {
- // Build the node body.
- $node = node_load($item->sid);
- $node->build_mode = NODE_BUILD_SEARCH_RESULT;
- $node = node_build_content($node, FALSE, FALSE);
- $node->body = drupal_render($node->content);
-
- // Fetch comments for snippet.
- $node->body .= module_invoke('comment', 'nodeapi', $node, 'update_index');
- // Fetch terms for snippet.
- $node->body .= module_invoke('taxonomy', 'nodeapi', $node, 'update_index');
-
- $extra = node_invoke_nodeapi($node, 'search_result');
-
- $results[] = 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' => $total ? ($item->calculated_score / $total) : 0,
- 'snippet' => search_excerpt($keys, $node->body),
- );
- }
- return $results;
- }
-}
-
-/**
* Implementation of hook_ranking().
*/
function node_ranking() {
@@ -1853,6 +1722,9 @@ function node_page_title($node) {
*/
function node_init() {
drupal_add_css(drupal_get_path('module', 'node') . '/node.css');
+ if (module_exists('sql_search')) {
+ module_load_include('inc','node','node.sql_search');
+ }
}
function node_last_changed($nid) {
@@ -2075,174 +1947,7 @@ function node_page_view($node) {
return node_show($node);
}
-/**
- * Implementation of hook_update_index().
- */
-function node_update_index() {
- $limit = (int)variable_get('search_cron_limit', 100);
-
- variable_set('node_cron_views_scale', 1.0 / max(1, db_result(db_query('SELECT MAX(totalcount) FROM {node_counter}'))));
-
- $result = db_query_range("SELECT n.nid FROM {node} n LEFT JOIN {search_dataset} d ON d.type = 'node' AND d.sid = n.nid WHERE d.sid IS NULL OR d.reindex <> 0 ORDER BY d.reindex ASC, n.nid ASC", 0, $limit);
-
- while ($node = db_fetch_object($result)) {
- _node_index_node($node);
- }
-}
-
-/**
- * Index a single node.
- *
- * @param $node
- * The node to index.
- */
-function _node_index_node($node) {
- $node = node_load($node->nid);
-
- // save the changed time of the most recent indexed node, for the search results half-life calculation
- variable_set('node_cron_last', $node->changed);
-
- // Build the node body.
- $node->build_mode = NODE_BUILD_SEARCH_INDEX;
- $node = node_build_content($node, FALSE, FALSE);
- $node->body = drupal_render($node->content);
-
- $text = '
' . check_plain($node->title) . '
' . $node->body;
-
- // Fetch extra data normally not visible
- $extra = node_invoke_nodeapi($node, 'update_index');
- foreach ($extra as $t) {
- $text .= $t;
- }
-
- // Update index
- search_index($node->nid, 'node', $text);
-}
-
-/**
- * Implementation of hook_form_FORM_ID_alter().
- */
-function node_form_search_form_alter(&$form, $form_state) {
- if ($form['module']['#value'] == 'node' && user_access('use advanced search')) {
- // Keyword boxes:
- $form['advanced'] = array(
- '#type' => 'fieldset',
- '#title' => t('Advanced search'),
- '#collapsible' => TRUE,
- '#collapsed' => TRUE,
- '#attributes' => array('class' => 'search-advanced'),
- );
- $form['advanced']['keywords'] = array(
- '#prefix' => '',
- '#suffix' => '
',
- );
- $form['advanced']['keywords']['or'] = array(
- '#type' => 'textfield',
- '#title' => t('Containing any of the words'),
- '#size' => 30,
- '#maxlength' => 255,
- );
- $form['advanced']['keywords']['phrase'] = array(
- '#type' => 'textfield',
- '#title' => t('Containing the phrase'),
- '#size' => 30,
- '#maxlength' => 255,
- );
- $form['advanced']['keywords']['negative'] = array(
- '#type' => 'textfield',
- '#title' => t('Containing none of the words'),
- '#size' => 30,
- '#maxlength' => 255,
- );
-
- // Taxonomy box:
- if ($taxonomy = module_invoke('taxonomy', 'form_all', 1)) {
- $form['advanced']['term'] = array(
- '#type' => 'select',
- '#title' => t('Only in the term(s)'),
- '#prefix' => '',
- '#size' => 10,
- '#suffix' => '
',
- '#options' => $taxonomy,
- '#multiple' => TRUE,
- );
- }
-
- // Node types:
- $types = array_map('check_plain', node_get_types('names'));
- $form['advanced']['type'] = array(
- '#type' => 'checkboxes',
- '#title' => t('Only of the type(s)'),
- '#prefix' => '',
- '#suffix' => '
',
- '#options' => $types,
- );
- $form['advanced']['submit'] = array(
- '#type' => 'submit',
- '#value' => t('Advanced search'),
- '#prefix' => '',
- '#suffix' => '
',
- );
-
- // Languages:
- $language_options = array();
- foreach (language_list('language') as $key => $object) {
- $language_options[$key] = $object->name;
- }
- if (count($language_options) > 1) {
- $form['advanced']['language'] = array(
- '#type' => 'checkboxes',
- '#title' => t('Languages'),
- '#prefix' => '',
- '#suffix' => '
',
- '#options' => $language_options,
- );
- }
-
- $form['#validate'][] = 'node_search_validate';
- }
-}
-
-/**
- * Form API callback for the search form. Registered in node_form_alter().
- */
-function node_search_validate($form, &$form_state) {
- // Initialize using any existing basic search keywords.
- $keys = $form_state['values']['processed_keys'];
-
- // Insert extra restrictions into the search keywords string.
- if (isset($form_state['values']['type']) && is_array($form_state['values']['type'])) {
- // Retrieve selected types - Forms API sets the value of unselected checkboxes to 0.
- $form_state['values']['type'] = array_filter($form_state['values']['type']);
- if (count($form_state['values']['type'])) {
- $keys = search_query_insert($keys, 'type', implode(',', array_keys($form_state['values']['type'])));
- }
- }
-
- if (isset($form_state['values']['term']) && is_array($form_state['values']['term'])) {
- $keys = search_query_insert($keys, 'term', implode(',', $form_state['values']['term']));
- }
- if (isset($form_state['values']['language']) && is_array($form_state['values']['language'])) {
- $keys = search_query_insert($keys, 'language', implode(',', array_filter($form_state['values']['language'])));
- }
- if ($form_state['values']['or'] != '') {
- if (preg_match_all('/ ("[^"]+"|[^" ]+)/i', ' ' . $form_state['values']['or'], $matches)) {
- $keys .= ' ' . implode(' OR ', $matches[1]);
- }
- }
- if ($form_state['values']['negative'] != '') {
- if (preg_match_all('/ ("[^"]+"|[^" ]+)/i', ' ' . $form_state['values']['negative'], $matches)) {
- $keys .= ' -' . implode(' -', $matches[1]);
- }
- }
- if ($form_state['values']['phrase'] != '') {
- $keys .= ' "' . str_replace('"', ' ', $form_state['values']['phrase']) . '"';
- }
- if (!empty($keys)) {
- form_set_value($form['basic']['inline']['processed_keys'], trim($keys), $form_state);
- }
-}
-
+/** FIXME: need to fix this hook_form_search_form_alter in the new include file! **/
/**
* @defgroup node_access Node access rights
* @{
Index: modules/node/node.sql_search.inc
===================================================================
RCS file: modules/node/node.sql_search.inc
diff -N modules/node/node.sql_search.inc
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ modules/node/node.sql_search.inc 7 Mar 2009 17:31:41 -0000
@@ -0,0 +1,314 @@
+ t('Content'),
+ 'access' => 'access content',
+ );
+
+ case 'reset':
+ db_query("UPDATE {search_dataset} SET reindex = %d WHERE type = 'node'", time());
+ return;
+
+ case 'wipe':
+ db_query("DELETE FROM {search_index} WHERE sid = %d AND type = '%s'", $sid, $type);
+ // Don't remove links if re-indexing.
+ if (!$reindex) {
+ db_query("DELETE FROM {search_node_links} WHERE sid = %d AND type = '%s'", $sid, $type);
+ }
+ return;
+ case 'status':
+ $total = db_result(db_query('SELECT COUNT(*) FROM {node} WHERE status = 1'));
+ $remaining = db_result(db_query("SELECT COUNT(*) FROM {node} n LEFT JOIN {search_dataset} d ON d.type = 'node' AND d.sid = n.nid WHERE n.status = 1 AND d.sid IS NULL OR d.reindex <> 0"));
+ return array('remaining' => $remaining, 'total' => $total);
+ case 'admin':
+ $form = array();
+ // Output form for defining rank factor weights.
+ $form['content_ranking'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Content ranking'),
+ );
+ $form['content_ranking']['#theme'] = 'node_search_admin';
+ $form['content_ranking']['info'] = array(
+ '#value' => '' . t('The following numbers control which properties the content search should favor when ordering the results. Higher numbers mean more influence, zero means the property is ignored. Changing these numbers does not require the search index to be rebuilt. Changes take effect immediately.') . ''
+ );
+
+ // Note: reversed to reflect that higher number = higher ranking.
+ $options = drupal_map_assoc(range(0, 10));
+ foreach (module_invoke_all('ranking') as $var => $values) {
+ $form['content_ranking']['factors']['node_rank_'. $var] = array(
+ '#title' => $values['title'],
+ '#type' => 'select',
+ '#options' => $options,
+ '#default_value' => variable_get('node_rank_'. $var, 0),
+ );
+ }
+ return $form;
+
+ case 'search':
+ // Build matching conditions
+ /** FIXME: DB NEXT GENERATION **/
+ list($join1, $where1) = _db_rewrite_sql();
+ $arguments1 = array();
+ $conditions1 = 'n.status = 1';
+
+ if ($type = search_query_extract($keys, 'type')) {
+ $types = array();
+ foreach (explode(',', $type) as $t) {
+ $types[] = "n.type = '%s'";
+ $arguments1[] = $t;
+ }
+ $conditions1 .= ' AND (' . implode(' OR ', $types) . ')';
+ $keys = search_query_insert($keys, 'type');
+ }
+
+ if ($category = search_query_extract($keys, 'category')) {
+ $categories = array();
+ foreach (explode(',', $category) as $c) {
+ $categories[] = "tn.tid = %d";
+ $arguments1[] = $c;
+ }
+ $conditions1 .= ' AND (' . implode(' OR ', $categories) . ')';
+ $join1 .= ' INNER JOIN {term_node} tn ON n.vid = tn.vid';
+ $keys = search_query_insert($keys, 'category');
+ }
+
+ if ($languages = search_query_extract($keys, 'language')) {
+ $categories = array();
+ foreach (explode(',', $languages) as $l) {
+ $categories[] = "n.language = '%s'";
+ $arguments1[] = $l;
+ }
+ $conditions1 .= ' AND (' . implode(' OR ', $categories) . ')';
+ $keys = search_query_insert($keys, 'language');
+ }
+
+ // Get the ranking expressions.
+ $rankings = _node_rankings();
+
+ // When all search factors are disabled (ie they have a weight of zero),
+ // The default score is based only on keyword relevance.
+ if ($rankings['total'] == 0) {
+ $total = 1;
+ $arguments2 = array();
+ $join2 = '';
+ $select2 = 'i.relevance AS score';
+ }
+ else {
+ $total = $rankings['total'];
+ $arguments2 = $rankings['arguments'];
+ $join2 = implode(' ', $rankings['join']);
+ $select2 = '('. implode(' + ', $rankings['score']) .') AS score';
+ }
+
+ // Do search.
+ $find = sql_search_do_search($keys, 'node', 'INNER JOIN {node} n ON n.nid = i.sid ' . $join1, $conditions1 . (empty($where1) ? '' : ' AND ' . $where1), $arguments1, $select2, $join2, $arguments2);
+
+ // Load results.
+ $results = array();
+ foreach ($find as $item) {
+ // Build the node body.
+ $node = node_load($item->sid);
+ $node->build_mode = NODE_BUILD_SEARCH_RESULT;
+ $node = node_build_content($node, FALSE, FALSE);
+ $node->body = drupal_render($node->content);
+
+ // Fetch comments for snippet.
+ $node->body .= module_invoke('comment', 'nodeapi', $node, 'update index');
+ // Fetch terms for snippet.
+ $node->body .= module_invoke('taxonomy', 'nodeapi', $node, 'update index');
+
+ $extra = node_invoke_nodeapi($node, 'search result');
+
+ $results[] = 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' => $total ? ($item->score / $total) : 0,
+ 'snippet' => search_excerpt($keys, $node->body),
+ );
+ }
+ return $results;
+ }
+}
+
+/**
+ * Implementation of hook_form_alter().
+ */
+function sql_search_form_search_form_alter(&$form, $form_state) {
+ // Advanced node search form
+ if (variable_get('search_handler', _search_default_handler()) == 'sql_search' && $form['search_type']['#value'] == 'node' && user_access('use advanced search')) {
+ // Keyword boxes:
+ $form['advanced'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Advanced search'),
+ '#collapsible' => TRUE,
+ '#collapsed' => TRUE,
+ '#attributes' => array('class' => 'search-advanced'),
+ );
+ $form['advanced']['keywords'] = array(
+ '#prefix' => '',
+ '#suffix' => '
',
+ );
+ $form['advanced']['keywords']['or'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Containing any of the words'),
+ '#size' => 30,
+ '#maxlength' => 255,
+ );
+ $form['advanced']['keywords']['phrase'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Containing the phrase'),
+ '#size' => 30,
+ '#maxlength' => 255,
+ );
+ $form['advanced']['keywords']['negative'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Containing none of the words'),
+ '#size' => 30,
+ '#maxlength' => 255,
+ );
+
+ // Taxonomy box:
+ if ($taxonomy = module_invoke('taxonomy', 'form_all', 1)) {
+ $form['advanced']['category'] = array(
+ '#type' => 'select',
+ '#title' => t('Only in the category(s)'),
+ '#prefix' => '',
+ '#size' => 10,
+ '#suffix' => '
',
+ '#options' => $taxonomy,
+ '#multiple' => TRUE,
+ );
+ }
+
+ // Node types:
+ $types = array_map('check_plain', node_get_types('names'));
+ $form['advanced']['type'] = array(
+ '#type' => 'checkboxes',
+ '#title' => t('Only of the type(s)'),
+ '#prefix' => '',
+ '#suffix' => '
',
+ '#options' => $types,
+ );
+ $form['advanced']['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Advanced search'),
+ '#prefix' => '',
+ '#suffix' => '
',
+ );
+
+ // Languages:
+ $language_options = array();
+ foreach (language_list('language') as $key => $object) {
+ $language_options[$key] = $object->name;
+ }
+ if (count($language_options) > 1) {
+ $form['advanced']['language'] = array(
+ '#type' => 'checkboxes',
+ '#title' => t('Languages'),
+ '#prefix' => '',
+ '#suffix' => '
',
+ '#options' => $language_options,
+ );
+ }
+
+
+ $form['#validate'][] = 'node_advanced_search_validate';
+ }
+}
+
+/**
+ * Form API callback for the search form. Registered in node_form_alter().
+ */
+function node_advanced_search_validate($form, &$form_state) {
+ // Initialise using any existing basic search keywords.
+ $keys = $form_state['values']['processed_keys'];
+
+ // Insert extra restrictions into the search keywords string.
+ if (isset($form_state['values']['type']) && is_array($form_state['values']['type'])) {
+ // Retrieve selected types - Forms API sets the value of unselected checkboxes to 0.
+ $form_state['values']['type'] = array_filter($form_state['values']['type']);
+ if (count($form_state['values']['type'])) {
+ $keys = search_query_insert($keys, 'type', implode(',', array_keys($form_state['values']['type'])));
+ }
+ }
+
+ if (isset($form_state['values']['category']) && is_array($form_state['values']['category'])) {
+ $keys = search_query_insert($keys, 'category', implode(',', $form_state['values']['category']));
+ }
+ if (isset($form_state['values']['language']) && is_array($form_state['values']['language'])) {
+ $keys = search_query_insert($keys, 'language', implode(',', array_filter($form_state['values']['language'])));
+ }
+ if ($form_state['values']['or'] != '') {
+ if (preg_match_all('/ ("[^"]+"|[^" ]+)/i', ' ' . $form_state['values']['or'], $matches)) {
+ $keys .= ' ' . implode(' OR ', $matches[1]);
+ }
+ }
+ if ($form_state['values']['negative'] != '') {
+ if (preg_match_all('/ ("[^"]+"|[^" ]+)/i', ' ' . $form_state['values']['negative'], $matches)) {
+ $keys .= ' -' . implode(' -', $matches[1]);
+ }
+ }
+ if ($form_state['values']['phrase'] != '') {
+ $keys .= ' "' . str_replace('"', ' ', $form_state['values']['phrase']) . '"';
+ }
+ if (!empty($keys)) {
+ form_set_value($form['basic']['inline']['processed_keys'], trim($keys), $form_state);
+ }
+}
+
+/**
+ * Invoke node update index
+ */
+function node_update_index() {
+ $limit = (int)variable_get('search_cron_limit', 100);
+
+ variable_set('node_cron_views_scale', 1.0 / max(1, db_result(db_query('SELECT MAX(totalcount) FROM {node_counter}'))));
+
+ $result = db_query_range("SELECT n.nid FROM {node} n LEFT JOIN {search_dataset} d ON d.type = 'node' AND d.sid = n.nid WHERE d.sid IS NULL OR d.reindex <> 0 ORDER BY d.reindex ASC, n.nid ASC", 0, $limit);
+
+ while ($node = db_fetch_object($result)) {
+ _node_index($node);
+ }
+}
+
+/**
+ * Index a single node.
+ *
+ * @param $node
+ * The node to index.
+ */
+function _node_index($node) {
+ $node = node_load($node->nid);
+
+ // save the changed time of the most recent indexed node, for the search results half-life calculation
+ variable_set('node_cron_last', $node->changed);
+
+ // Build the node body.
+ $node->build_mode = NODE_BUILD_SEARCH_INDEX;
+ $node = node_build_content($node, FALSE, FALSE);
+ $node->body = drupal_render($node->content);
+
+ $text = '' . check_plain($node->title) . '
' . $node->body;
+
+ // Fetch extra data normally not visible
+ $extra = node_invoke_nodeapi($node, 'update index');
+ foreach ($extra as $t) {
+ $text .= $t;
+ }
+
+ // Update index
+ sql_search_index($node->nid, 'node', $text);
+}
+
+
Index: modules/search/search.admin.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/search/search.admin.inc,v
retrieving revision 1.8
diff -u -p -r1.8 search.admin.inc
--- modules/search/search.admin.inc 11 Jan 2009 21:19:18 -0000 1.8
+++ modules/search/search.admin.inc 7 Mar 2009 17:31:41 -0000
@@ -7,26 +7,6 @@
*/
/**
- * Menu callback: confirm wiping of the index.
- */
-function search_wipe_confirm() {
- return confirm_form(array(), t('Are you sure you want to re-index the site?'),
- 'admin/settings/search', t(' The search index is not cleared but systematically updated to reflect the new settings. Searching will continue to work but new content won\'t be indexed until all existing content has been re-indexed. This action cannot be undone.'), t('Re-index site'), t('Cancel'));
-}
-
-/**
- * Handler for wipe confirmation
- */
-function search_wipe_confirm_submit(&$form, &$form_state) {
- if ($form['confirm']) {
- search_wipe();
- drupal_set_message(t('The index will be rebuilt.'));
- $form_state['redirect'] = 'admin/settings/search';
- return;
- }
-}
-
-/**
* Menu callback; displays the search module settings page.
*
* @ingroup forms
@@ -34,77 +14,34 @@ function search_wipe_confirm_submit(&$fo
* @see search_admin_settings_validate()
*/
function search_admin_settings() {
- // Collect some stats
- $remaining = 0;
- $total = 0;
- foreach (module_implements('search') as $module) {
- $function = $module . '_search';
- $status = $function('status');
- $remaining += $status['remaining'];
- $total += $status['total'];
+ $form = array();
+ $handlers = search_handlers();
+ // Return with an error message if there are no available types.
+ if (empty($handlers)) {
+ drupal_set_message(t('No search handlers found. To enable search, you must first install and enable a search handler module.'), 'warning');
+ return array();
}
- $count = format_plural($remaining, 'There is 1 item left to index.', 'There are @count items left to index.');
- $percentage = ((int)min(100, 100 * ($total - $remaining) / max(1, $total))) . '%';
- $status = '' . t('%percentage of the site has been indexed.', array('%percentage' => $percentage)) . ' ' . $count . '
';
- $form['status'] = array('#type' => 'fieldset', '#title' => t('Indexing status'));
- $form['status']['status'] = array('#markup' => $status);
- $form['status']['wipe'] = array('#type' => 'submit', '#value' => t('Re-index site'));
-
- $items = drupal_map_assoc(array(10, 20, 50, 100, 200, 500));
- // Indexing throttle:
- $form['indexing_throttle'] = array(
- '#type' => 'fieldset',
- '#title' => t('Indexing throttle')
- );
- $form['indexing_throttle']['search_cron_limit'] = array(
+ foreach ($handlers as $type => $values) {
+ $options[$type] = $values['title'];
+ }
+ /** FIXME: are there use cases for using multiple search handlers? **/
+ $form['search_handler'] = array(
'#type' => 'select',
- '#title' => t('Number of items to index per cron run'),
- '#default_value' => 100,
- '#options' => $items,
- '#description' => t('The maximum number of items indexed in each pass of a cron maintenance task. If necessary, reduce the number of items to prevent timeouts and memory errors while indexing.', array('@cron' => url('admin/reports/status')))
- );
- // Indexing settings:
- $form['indexing_settings'] = array(
- '#type' => 'fieldset',
- '#title' => t('Indexing settings')
- );
- $form['indexing_settings']['info'] = array(
- '#markup' => t('Changing the settings below will cause the site index to be rebuilt. The search index is not cleared but systematically updated to reflect the new settings. Searching will continue to work but new content won\'t be indexed until all existing content has been re-indexed.
The default settings should be appropriate for the majority of sites.
')
- );
- $form['indexing_settings']['minimum_word_size'] = array(
- '#type' => 'textfield',
- '#title' => t('Minimum word length to index'),
- '#default_value' => 3,
- '#size' => 5,
- '#maxlength' => 3,
- '#description' => t('The number of characters a word has to be to be indexed. A lower setting means better search result ranking, but also a larger database. Each search query must contain at least one keyword that is this size (or longer).')
+ '#title' => t('Search handler'),
+ '#description' => t('Select your preferred search handler.'),
+ '#default_value' => variable_get('search_handler', _search_default_handler()),
+ '#options' => $options,
);
- $form['indexing_settings']['overlap_cjk'] = array(
- '#type' => 'checkbox',
- '#title' => t('Simple CJK handling'),
- '#default_value' => TRUE,
- '#description' => t('Whether to apply a simple Chinese/Japanese/Korean tokenizer based on overlapping sequences. Turn this off if you want to use an external preprocessor for this instead. Does not affect other languages.')
- );
-
- $form['#validate'] = array('search_admin_settings_validate');
- // Per module settings
- $form = array_merge($form, module_invoke_all('search', 'admin'));
- return system_settings_form($form, TRUE);
+ $form = system_settings_form($form);
+ $form['#submit'][] = 'search_admin_settings_menu_rebuild';
+ return $form;
}
/**
* Validate callback.
*/
function search_admin_settings_validate($form, &$form_state) {
- if ($form_state['values']['op'] == t('Re-index site')) {
- drupal_goto('admin/settings/search/wipe');
- }
- // If these settings change, the index needs to be rebuilt.
- if ((variable_get('minimum_word_size', 3) != $form_state['values']['minimum_word_size']) ||
- (variable_get('overlap_cjk', TRUE) != $form_state['values']['overlap_cjk'])) {
- drupal_set_message(t('The index will be rebuilt.'));
- search_wipe();
- }
+ menu_rebuild();
}
Index: modules/search/search.install
===================================================================
RCS file: /cvs/drupal/drupal/modules/search/search.install,v
retrieving revision 1.19
diff -u -p -r1.19 search.install
--- modules/search/search.install 15 Nov 2008 13:01:09 -0000 1.19
+++ modules/search/search.install 7 Mar 2009 17:31:41 -0000
@@ -1,157 +1,7 @@
'Stores items that will be searched.',
- 'fields' => array(
- 'sid' => array(
- 'type' => 'int',
- 'unsigned' => TRUE,
- 'not null' => TRUE,
- 'default' => 0,
- 'description' => 'Search item ID, e.g. node ID for nodes.',
- ),
- 'type' => array(
- 'type' => 'varchar',
- 'length' => 16,
- 'not null' => FALSE,
- 'description' => 'Type of item, e.g. node.',
- ),
- 'data' => array(
- 'type' => 'text',
- 'not null' => TRUE,
- 'size' => 'big',
- 'description' => 'List of space-separated words from the item.',
- ),
- 'reindex' => array(
- 'type' => 'int',
- 'unsigned' => TRUE,
- 'not null' => TRUE,
- 'default' => 0,
- 'description' => 'Set to force node reindexing.',
- ),
- ),
- 'primary key' => array('sid', 'type'),
- );
-
- $schema['search_index'] = array(
- 'description' => 'Stores the search index, associating words, items and scores.',
- 'fields' => array(
- 'word' => array(
- 'type' => 'varchar',
- 'length' => 50,
- 'not null' => TRUE,
- 'default' => '',
- 'description' => 'The {search_total}.word that is associated with the search item.',
- ),
- 'sid' => array(
- 'type' => 'int',
- 'unsigned' => TRUE,
- 'not null' => TRUE,
- 'default' => 0,
- 'description' => 'The {search_dataset}.sid of the searchable item to which the word belongs.',
- ),
- 'type' => array(
- 'type' => 'varchar',
- 'length' => 16,
- 'not null' => FALSE,
- 'description' => 'The {search_dataset}.type of the searchable item to which the word belongs.',
- ),
- 'score' => array(
- 'type' => 'float',
- 'not null' => FALSE,
- 'description' => 'The numeric score of the word, higher being more important.',
- ),
- ),
- 'indexes' => array(
- 'sid_type' => array('sid', 'type'),
- ),
- 'primary key' => array('word', 'sid', 'type'),
- );
-
- $schema['search_total'] = array(
- 'description' => 'Stores search totals for words.',
- 'fields' => array(
- 'word' => array(
- 'description' => 'Primary Key: Unique word in the search index.',
- 'type' => 'varchar',
- 'length' => 50,
- 'not null' => TRUE,
- 'default' => '',
- ),
- 'count' => array(
- 'description' => "The count of the word in the index using Zipf's law to equalize the probability distribution.",
- 'type' => 'float',
- 'not null' => FALSE,
- ),
- ),
- 'primary key' => array('word'),
- );
-
- $schema['search_node_links'] = array(
- 'description' => 'Stores items (like nodes) that link to other nodes, used to improve search scores for nodes that are frequently linked to.',
- 'fields' => array(
- 'sid' => array(
- 'type' => 'int',
- 'unsigned' => TRUE,
- 'not null' => TRUE,
- 'default' => 0,
- 'description' => 'The {search_dataset}.sid of the searchable item containing the link to the node.',
- ),
- 'type' => array(
- 'type' => 'varchar',
- 'length' => 16,
- 'not null' => TRUE,
- 'default' => '',
- 'description' => 'The {search_dataset}.type of the searchable item containing the link to the node.',
- ),
- 'nid' => array(
- 'type' => 'int',
- 'unsigned' => TRUE,
- 'not null' => TRUE,
- 'default' => 0,
- 'description' => 'The {node}.nid that this item links to.',
- ),
- 'caption' => array(
- 'type' => 'text',
- 'size' => 'big',
- 'not null' => FALSE,
- 'description' => 'The text used to link to the {node}.nid.',
- ),
- ),
- 'primary key' => array('sid', 'type', 'nid'),
- 'indexes' => array(
- 'nid' => array('nid'),
- ),
- );
-
- return $schema;
-}
-
+/** FIXME: DO WE NEED TO UPDATE HERE OR IN SQL_SEARCH? **/
/**
* Replace unique keys in 'search_dataset' and 'search_index' by primary keys.
*/
Index: modules/search/search.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/search/search.module,v
retrieving revision 1.284
diff -u -p -r1.284 search.module
--- modules/search/search.module 1 Mar 2009 07:30:24 -0000 1.284
+++ modules/search/search.module 7 Mar 2009 17:31:41 -0000
@@ -97,17 +97,8 @@ function search_help($path, $arg) {
switch ($path) {
case 'admin/help#search':
$output = '' . t('The search module adds the ability to search for content by keywords. Search is often the only practical way to find content on a large site, and is useful for finding both users and posts.') . '
';
- $output .= '' . t('To provide keyword searching, the search engine maintains an index of words found in your site\'s content. To build and maintain this index, a correctly configured cron maintenance task is required. Indexing behavior can be adjusted using the search settings page; for example, the Number of items to index per cron run sets the maximum number of items indexed in each pass of a cron maintenance task. If necessary, reduce this number to prevent timeouts and memory errors when indexing.', array('@cron' => url('admin/reports/status'), '@searchsettings' => url('admin/settings/search'))) . '
';
$output .= '' . t('For more information, see the online handbook entry for Search module.', array('@search' => 'http://drupal.org/handbook/modules/search/')) . '
';
return $output;
- case 'admin/settings/search':
- return '' . t('The search engine maintains an index of words found in your site\'s content. To build and maintain this index, a correctly configured cron maintenance task is required. Indexing behavior can be adjusted using the settings below.', array('@cron' => url('admin/reports/status'))) . '
';
- case 'search#noresults':
- return t('
-- Check if your spelling is correct.
-- Remove quotes around phrases to search for each word individually. bike shed will often show more results than "bike shed".
-- Consider loosening your query with OR. bike OR shed will often show more results than bike shed.
-
');
}
}
@@ -190,19 +181,12 @@ function search_menu() {
);
$items['admin/settings/search'] = array(
'title' => 'Search settings',
- 'description' => 'Configure relevance settings for search and other indexing options',
+ 'description' => 'Configure settings for search', /** FIXME: from patch **/
'page callback' => 'drupal_get_form',
'page arguments' => array('search_admin_settings'),
'access arguments' => array('administer search'),
'type' => MENU_NORMAL_ITEM,
);
- $items['admin/settings/search/wipe'] = array(
- 'title' => 'Clear index',
- 'page callback' => 'drupal_get_form',
- 'page arguments' => array('search_wipe_confirm'),
- 'access arguments' => array('administer search'),
- 'type' => MENU_CALLBACK,
- );
$items['admin/reports/search'] = array(
'title' => 'Top search phrases',
'description' => 'View most popular search phrases.',
@@ -213,192 +197,27 @@ function search_menu() {
);
foreach (module_implements('search') as $module) {
- $items['search/' . $module . '/%menu_tail'] = array(
- 'title callback' => 'module_invoke',
- 'title arguments' => array($module, 'search', 'name', TRUE),
- 'page callback' => 'search_view',
- 'page arguments' => array($module),
- 'access callback' => '_search_menu',
- 'access arguments' => array($module),
- 'type' => MENU_LOCAL_TASK,
- 'parent' => 'search',
- );
- }
- return $items;
-}
-
-function _search_menu($name) {
- return user_access('search content') && module_invoke($name, 'search', 'name');
-}
-
-/**
- * Wipes a part of or the entire search index.
- *
- * @param $sid
- * (optional) The SID of the item to wipe. If specified, $type must be passed
- * too.
- * @param $type
- * (optional) The type of item to wipe.
- */
-function search_wipe($sid = NULL, $type = NULL, $reindex = FALSE) {
- if ($type == NULL && $sid == NULL) {
- module_invoke_all('search', 'reset');
- }
- else {
- db_query("DELETE FROM {search_dataset} WHERE sid = %d AND type = '%s'", $sid, $type);
- db_query("DELETE FROM {search_index} WHERE sid = %d AND type = '%s'", $sid, $type);
- // Don't remove links if re-indexing.
- if (!$reindex) {
- db_query("DELETE FROM {search_node_links} WHERE sid = %d AND type = '%s'", $sid, $type);
+ $types = search_types();
+ foreach ($types as $name => $values) {
+ $items['search/' . $module . '/%menu_tail'] = array(
+/* 'title callback' => 'module_invoke',
+ 'title arguments' => array($module, 'search', 'name', TRUE), */
+ 'title' => $values['title'],
+ 'page callback' => 'search_view',
+ 'page arguments' => array($module),
+ 'access callback' => '_search_menu',
+ 'access arguments' => array($values['access']),
+ 'type' => MENU_LOCAL_TASK,
+ 'parent' => 'search',
+ );
}
}
+ return $items;
}
-/**
- * Marks a word as dirty (or retrieves the list of dirty words). This is used
- * during indexing (cron). Words which are dirty have outdated total counts in
- * the search_total table, and need to be recounted.
- */
-function search_dirty($word = NULL) {
- static $dirty = array();
- if ($word !== NULL) {
- $dirty[$word] = TRUE;
- }
- else {
- return $dirty;
- }
-}
-
-/**
- * Implementation of hook_cron().
- *
- * Fires hook_update_index() in all modules and cleans up dirty words (see
- * search_dirty).
- */
-function search_cron() {
- // We register a shutdown function to ensure that search_total is always up
- // to date.
- register_shutdown_function('search_update_totals');
-
- // Update word index
- module_invoke_all('update_index');
-}
-
-/**
- * This function is called on shutdown to ensure that search_total is always
- * up to date (even if cron times out or otherwise fails).
- */
-function search_update_totals() {
- // Update word IDF (Inverse Document Frequency) counts for new/changed words
- foreach (search_dirty() as $word => $dummy) {
- // Get total count
- $total = db_result(db_query("SELECT SUM(score) FROM {search_index} WHERE word = '%s'", $word));
- // Apply Zipf's law to equalize the probability distribution
- $total = log10(1 + 1/(max(1, $total)));
- db_merge('search_total')->key(array('word' => $word))->fields(array('count' => $total))->execute();
- }
- // Find words that were deleted from search_index, but are still in
- // search_total. We use a LEFT JOIN between the two tables and keep only the
- // rows which fail to join.
- $result = db_query("SELECT t.word AS realword, i.word FROM {search_total} t LEFT JOIN {search_index} i ON t.word = i.word WHERE i.word IS NULL");
- while ($word = db_fetch_object($result)) {
- db_query("DELETE FROM {search_total} WHERE word = '%s'", $word->realword);
- }
-}
-
-/**
- * Simplifies a string according to indexing rules.
- */
-function search_simplify($text) {
- // Decode entities to UTF-8
- $text = decode_entities($text);
-
- // Lowercase
- $text = drupal_strtolower($text);
-
- // Call an external processor for word handling.
- search_invoke_preprocess($text);
-
- // Simple CJK handling
- if (variable_get('overlap_cjk', TRUE)) {
- $text = preg_replace_callback('/[' . PREG_CLASS_CJK . ']+/u', 'search_expand_cjk', $text);
- }
-
- // To improve searching for numerical data such as dates, IP addresses
- // or version numbers, we consider a group of numerical characters
- // separated only by punctuation characters to be one piece.
- // This also means that searching for e.g. '20/03/1984' also returns
- // results with '20-03-1984' in them.
- // Readable regexp: ([number]+)[punctuation]+(?=[number])
- $text = preg_replace('/([' . PREG_CLASS_NUMBERS . ']+)[' . PREG_CLASS_PUNCTUATION . ']+(?=[' . PREG_CLASS_NUMBERS . '])/u', '\1', $text);
-
- // The dot, underscore and dash are simply removed. This allows meaningful
- // search behavior with acronyms and URLs.
- $text = preg_replace('/[._-]+/', '', $text);
-
- // With the exception of the rules above, we consider all punctuation,
- // marks, spacers, etc, to be a word boundary.
- $text = preg_replace('/[' . PREG_CLASS_SEARCH_EXCLUDE . ']+/u', ' ', $text);
-
- return $text;
-}
-
-/**
- * Basic CJK tokenizer. Simply splits a string into consecutive, overlapping
- * sequences of characters ('minimum_word_size' long).
- */
-function search_expand_cjk($matches) {
- $min = variable_get('minimum_word_size', 3);
- $str = $matches[0];
- $l = drupal_strlen($str);
- // Passthrough short words
- if ($l <= $min) {
- return ' ' . $str . ' ';
- }
- $tokens = ' ';
- // FIFO queue of characters
- $chars = array();
- // Begin loop
- for ($i = 0; $i < $l; ++$i) {
- // Grab next character
- $current = drupal_substr($str, 0, 1);
- $str = substr($str, strlen($current));
- $chars[] = $current;
- if ($i >= $min - 1) {
- $tokens .= implode('', $chars) . ' ';
- array_shift($chars);
- }
- }
- return $tokens;
-}
-
-/**
- * Splits a string into tokens for indexing.
- */
-function search_index_split($text) {
- static $last = NULL;
- static $lastsplit = NULL;
-
- if ($last == $text) {
- return $lastsplit;
- }
- // Process words
- $text = search_simplify($text);
- $words = explode(' ', $text);
- array_walk($words, '_search_index_truncate');
-
- // Save last keyword result
- $last = $text;
- $lastsplit = $words;
-
- return $words;
-}
-
-/**
- * Helper function for array_walk in search_index_split.
- */
-function _search_index_truncate(&$text) {
- $text = truncate_utf8($text, 50);
+/** FIXME: LOST COMMENTS HSERE **/
+function _search_menu($access) {
+ return user_access('search content') && ($access ? user_access($access) : TRUE);
}
/**
@@ -410,296 +229,9 @@ function search_invoke_preprocess(&$text
}
}
+/** FIXME: remember to replace the new hook_nodeapi and hook_comment_ stuff in sql_search! **/
/**
- * Update the full-text search index for a particular item.
- *
- * @param $sid
- * A number identifying this particular item (e.g. node id).
- *
- * @param $type
- * A string defining this type of item (e.g. 'node')
- *
- * @param $text
- * The content of this item. Must be a piece of HTML text.
- *
- * @ingroup search
- */
-function search_index($sid, $type, $text) {
- $minimum_word_size = variable_get('minimum_word_size', 3);
-
- // Link matching
- global $base_url;
- $node_regexp = '@href=[\'"]?(?:' . preg_quote($base_url, '@') . '/|' . preg_quote(base_path(), '@') . ')(?:\?q=)?/?((?![a-z]+:)[^\'">]+)[\'">]@i';
-
- // Multipliers for scores of words inside certain HTML tags. The weights are stored
- // in a variable so that modules can overwrite the default weights.
- // Note: 'a' must be included for link ranking to work.
- $tags = variable_get('search_tag_weights', array(
- 'h1' => 25,
- 'h2' => 18,
- 'h3' => 15,
- 'h4' => 12,
- 'h5' => 9,
- 'h6' => 6,
- 'u' => 3,
- 'b' => 3,
- 'i' => 3,
- 'strong' => 3,
- 'em' => 3,
- 'a' => 10));
-
- // Strip off all ignored tags to speed up processing, but insert space before/after
- // them to keep word boundaries.
- $text = str_replace(array('<', '>'), array(' <', '> '), $text);
- $text = strip_tags($text, '<' . implode('><', array_keys($tags)) . '>');
-
- // Split HTML tags from plain text.
- $split = preg_split('/\s*<([^>]+?)>\s*/', $text, -1, PREG_SPLIT_DELIM_CAPTURE);
- // Note: PHP ensures the array consists of alternating delimiters and literals
- // and begins and ends with a literal (inserting $null as required).
-
- $tag = FALSE; // Odd/even counter. Tag or no tag.
- $link = FALSE; // State variable for link analyzer
- $score = 1; // Starting score per word
- $accum = ' '; // Accumulator for cleaned up data
- $tagstack = array(); // Stack with open tags
- $tagwords = 0; // Counter for consecutive words
- $focus = 1; // Focus state
-
- $results = array(0 => array()); // Accumulator for words for index
-
- foreach ($split as $value) {
- if ($tag) {
- // Increase or decrease score per word based on tag
- list($tagname) = explode(' ', $value, 2);
- $tagname = drupal_strtolower($tagname);
- // Closing or opening tag?
- if ($tagname[0] == '/') {
- $tagname = substr($tagname, 1);
- // If we encounter unexpected tags, reset score to avoid incorrect boosting.
- if (!count($tagstack) || $tagstack[0] != $tagname) {
- $tagstack = array();
- $score = 1;
- }
- else {
- // Remove from tag stack and decrement score
- $score = max(1, $score - $tags[array_shift($tagstack)]);
- }
- if ($tagname == 'a') {
- $link = FALSE;
- }
- }
- else {
- if (isset($tagstack[0]) && $tagstack[0] == $tagname) {
- // None of the tags we look for make sense when nested identically.
- // If they are, it's probably broken HTML.
- $tagstack = array();
- $score = 1;
- }
- else {
- // Add to open tag stack and increment score
- array_unshift($tagstack, $tagname);
- $score += $tags[$tagname];
- }
- if ($tagname == 'a') {
- // Check if link points to a node on this site
- if (preg_match($node_regexp, $value, $match)) {
- $path = drupal_get_normal_path($match[1]);
- if (preg_match('!(?:node|book)/(?:view/)?([0-9]+)!i', $path, $match)) {
- $linknid = $match[1];
- if ($linknid > 0) {
- // Note: ignore links to uncachable nodes to avoid redirect bugs.
- $node = db_fetch_object(db_query('SELECT n.title, n.nid, n.vid, r.format FROM {node} n INNER JOIN {node_revision} r ON n.vid = r.vid WHERE n.nid = %d', $linknid));
- if (filter_format_allowcache($node->format)) {
- $link = TRUE;
- $linktitle = $node->title;
- }
- }
- }
- }
- }
- }
- // A tag change occurred, reset counter.
- $tagwords = 0;
- }
- else {
- // Note: use of PREG_SPLIT_DELIM_CAPTURE above will introduce empty values
- if ($value != '') {
- if ($link) {
- // Check to see if the node link text is its URL. If so, we use the target node title instead.
- if (preg_match('!^https?://!i', $value)) {
- $value = $linktitle;
- }
- }
- $words = search_index_split($value);
- foreach ($words as $word) {
- // Add word to accumulator
- $accum .= $word . ' ';
- $num = is_numeric($word);
- // Check wordlength
- if ($num || drupal_strlen($word) >= $minimum_word_size) {
- // Normalize numbers
- if ($num) {
- $word = (int)ltrim($word, '-0');
- }
-
- // Links score mainly for the target.
- if ($link) {
- if (!isset($results[$linknid])) {
- $results[$linknid] = array();
- }
- $results[$linknid][] = $word;
- // Reduce score of the link caption in the source.
- $focus *= 0.2;
- }
- // Fall-through
- if (!isset($results[0][$word])) {
- $results[0][$word] = 0;
- }
- $results[0][$word] += $score * $focus;
-
- // Focus is a decaying value in terms of the amount of unique words up to this point.
- // From 100 words and more, it decays, to e.g. 0.5 at 500 words and 0.3 at 1000 words.
- $focus = min(1, .01 + 3.5 / (2 + count($results[0]) * .015));
- }
- $tagwords++;
- // Too many words inside a single tag probably mean a tag was accidentally left open.
- if (count($tagstack) && $tagwords >= 15) {
- $tagstack = array();
- $score = 1;
- }
- }
- }
- }
- $tag = !$tag;
- }
-
- search_wipe($sid, $type, TRUE);
-
- // Insert cleaned up data into dataset
- db_query("INSERT INTO {search_dataset} (sid, type, data, reindex) VALUES (%d, '%s', '%s', %d)", $sid, $type, $accum, 0);
-
- // Insert results into search index
- foreach ($results[0] as $word => $score) {
- // If a word already exists in the database, its score gets increased
- // appropriately. If not, we create a new record with the appropriate
- // starting score.
- db_merge('search_index')->key(array(
- 'word' => $word,
- 'sid' => $sid,
- 'type' => $type,
- ))->fields(array('score' => $score))->expression('score', 'score + :score', array(':score' => $score))
- ->execute();
- search_dirty($word);
- }
- unset($results[0]);
-
- // Get all previous links from this item.
- $result = db_query("SELECT nid, caption FROM {search_node_links} WHERE sid = %d AND type = '%s'", $sid, $type);
- $links = array();
- while ($link = db_fetch_object($result)) {
- $links[$link->nid] = $link->caption;
- }
-
- // Now store links to nodes.
- foreach ($results as $nid => $words) {
- $caption = implode(' ', $words);
- if (isset($links[$nid])) {
- if ($links[$nid] != $caption) {
- // Update the existing link and mark the node for reindexing.
- db_query("UPDATE {search_node_links} SET caption = '%s' WHERE sid = %d AND type = '%s' AND nid = %d", $caption, $sid, $type, $nid);
- search_touch_node($nid);
- }
- // Unset the link to mark it as processed.
- unset($links[$nid]);
- }
- else {
- // Insert the existing link and mark the node for reindexing.
- db_query("INSERT INTO {search_node_links} (caption, sid, type, nid) VALUES ('%s', %d, '%s', %d)", $caption, $sid, $type, $nid);
- search_touch_node($nid);
- }
- }
- // Any left-over links in $links no longer exist. Delete them and mark the nodes for reindexing.
- foreach ($links as $nid => $caption) {
- db_query("DELETE FROM {search_node_links} WHERE sid = %d AND type = '%s' AND nid = %d", $sid, $type, $nid);
- search_touch_node($nid);
- }
-}
-
-/**
- * Change a node's changed timestamp to 'now' to force reindexing.
- *
- * @param $nid
- * The nid of the node that needs reindexing.
- */
-function search_touch_node($nid) {
- db_query("UPDATE {search_dataset} SET reindex = %d WHERE sid = %d AND type = 'node'", REQUEST_TIME, $nid);
-}
-
-/**
- * Implementation of hook_nodeapi_update_index().
- */
-function search_nodeapi_update_index($node) {
- // Transplant links to a node into the target node.
- $result = db_query("SELECT caption FROM {search_node_links} WHERE nid = %d", $node->nid);
- $output = array();
- while ($link = db_fetch_object($result)) {
- $output[] = $link->caption;
- }
- return '(' . implode(', ', $output) . ')';
-}
-
-/**
- * Implementation of hook_nodeapi_update().
- */
-function search_nodeapi_update($node) {
- // Reindex the node when it is updated. The node is automatically indexed
- // when it is added, simply by being added to the node table.
- search_touch_node($node->nid);
-}
-
-/**
- * Implementation of hook_comment_insert().
- */
-function search_comment_insert($form_values) {
- // Reindex the node when comments are added.
- search_touch_node($form_values['nid']);
-}
-
-/**
- * Implementation of hook_comment_update().
- */
-function search_comment_update($form_values) {
- // Reindex the node when comments are changed.
- search_touch_node($form_values['nid']);
-}
-
-/**
- * Implementation of hook_comment_delete().
- */
-function search_comment_delete($comment) {
- // Reindex the node when comments are deleted.
- search_touch_node($comment->nid);
-}
-
-/**
- * Implementation of hook_comment_publish().
- */
-function search_comment_publish($form_values) {
- // Reindex the node when comments are published.
- search_touch_node($form_values['nid']);
-}
-
-/**
- * Implementation of hook_comment_unpublish().
- */
-function search_comment_unpublish($comment) {
- // Reindex the node when comments are unpublished.
- search_touch_node($comment->nid);
-}
-
-/**
- * Extract a module-specific search option from a search query. e.g. 'type:book'
+ * Extract a type-specific search option from a search query. e.g. 'type:book'
*/
function search_query_extract($keys, $option) {
if (preg_match('/(^| )' . $option . ':([^ ]*)( |$)/i', $keys, $matches)) {
@@ -708,7 +240,7 @@ function search_query_extract($keys, $op
}
/**
- * Return a query with the given module-specific search option inserted in.
+ * Return a query with the given type-specific search option inserted in.
* e.g. 'type:book'.
*/
function search_query_insert($keys, $option, $value = '') {
@@ -722,24 +254,13 @@ function search_query_insert($keys, $opt
}
/**
- * Parse a search query into SQL conditions.
- *
- * We build two queries that matches the dataset bodies. @See do_search for
- * more about these.
+ * Parse a search query into an array of conditions.
*
* @param $text
* The search keys.
* @return
- * A list of six elements.
- * * A series of statements AND'd together which will be used to provide all
- * possible matches.
- * * Arguments for this query part.
- * * A series of exact word matches OR'd together.
- * * Arguments for this query part.
- * * A boolean indicating whether this is a simple query or not. Negative
- * terms, presence of both AND / OR make this FALSE.
- * * A boolean indicating the presence of a lowercase or. Maybe the user
- * wanted to use OR.
+ *
+ * An array of keys and conditions.
*/
function search_parse_query($text) {
$keys = array('positive' => array(), 'negative' => array());
@@ -764,7 +285,7 @@ function search_parse_query($text) {
$simple = FALSE;
}
// Simplify keyword according to indexing rules and external preprocessors
- $words = search_simplify($match[2]);
+ $words = search_invoke_handler('simplify', $match[2]);
// Re-explode in case simplification added more words, except when matching a phrase
$words = $phrase ? array($words) : preg_split('/ /', $words, -1, PREG_SPLIT_NO_EMPTY);
// Negative matches
@@ -804,196 +325,7 @@ function search_parse_query($text) {
}
$or = FALSE;
}
-
- // Convert keywords into SQL statements.
- $query = array();
- $query2 = array();
- $arguments = array();
- $arguments2 = array();
- $matches = 0;
- $simple_and = FALSE;
- $simple_or = FALSE;
- // Positive matches
- foreach ($keys['positive'] as $key) {
- // Group of ORed terms
- if (is_array($key) && count($key)) {
- $simple_or = TRUE;
- $queryor = array();
- $any = FALSE;
- foreach ($key as $or) {
- list($q, $num_new_scores) = _search_parse_query($or, $arguments2);
- $any |= $num_new_scores;
- if ($q) {
- $queryor[] = $q;
- $arguments[] = "% $or %";
- }
- }
- if (count($queryor)) {
- $query[] = '(' . implode(' OR ', $queryor) . ')';
- // A group of OR keywords only needs to match once
- $matches += ($any > 0);
- }
- }
- // Single ANDed term
- else {
- $simple_and = TRUE;
- list($q, $num_new_scores, $num_valid_words) = _search_parse_query($key, $arguments2);
- if ($q) {
- $query[] = $q;
- $arguments[] = "% $key %";
- if (!$num_valid_words) {
- $simple = FALSE;
- }
- // Each AND keyword needs to match at least once
- $matches += $num_new_scores;
- }
- }
- }
- if ($simple_and && $simple_or) {
- $simple = FALSE;
- }
- // Negative matches
- foreach ($keys['negative'] as $key) {
- list($q) = _search_parse_query($key, $arguments2, TRUE);
- if ($q) {
- $query[] = $q;
- $arguments[] = "% $key %";
- $simple = FALSE;
- }
- }
- $query = implode(' AND ', $query);
-
- // Build word-index conditions for the first pass
- $query2 = substr(str_repeat("i.word = '%s' OR ", count($arguments2)), 0, -4);
-
- return array($query, $arguments, $query2, $arguments2, $matches, $simple, $warning);
-}
-
-/**
- * Helper function for search_parse_query();
- */
-function _search_parse_query(&$word, &$scores, $not = FALSE) {
- $num_new_scores = 0;
- $num_valid_words = 0;
- // Determine the scorewords of this word/phrase
- if (!$not) {
- $split = explode(' ', $word);
- foreach ($split as $s) {
- $num = is_numeric($s);
- if ($num || drupal_strlen($s) >= variable_get('minimum_word_size', 3)) {
- $s = $num ? ((int)ltrim($s, '-0')) : $s;
- if (!isset($scores[$s])) {
- $scores[$s] = $s;
- $num_new_scores++;
- }
- $num_valid_words++;
- }
- }
- }
- // Return matching snippet and number of added words
- return array("d.data " . ($not ? 'NOT ' : '') . "LIKE '%s'", $num_new_scores, $num_valid_words);
-}
-
-/**
- * Do a query on the full-text search index for a word or words.
- *
- * This function is normally only called by each module that support the
- * indexed search (and thus, implements hook_update_index()).
- *
- * Results are retrieved in two logical passes. However, the two passes are
- * joined together into a single query. And in the case of most simple
- * queries the second pass is not even used.
- *
- * The first pass selects a set of all possible matches, which has the benefit
- * of also providing the exact result set for simple "AND" or "OR" searches.
- *
- * The second portion of the query further refines this set by verifying
- * advanced text conditions (such negative or phrase matches)
- *
- * @param $keywords
- * A search string as entered by the user.
- *
- * @param $type
- * A string identifying the calling module.
- *
- * @param $join1
- * (optional) Inserted into the JOIN part of the first SQL query.
- * For example "INNER JOIN {node} n ON n.nid = i.sid".
- *
- * @param $where1
- * (optional) Inserted into the WHERE part of the first SQL query.
- * For example "(n.status > %d)".
- *
- * @param $arguments1
- * (optional) Extra SQL arguments belonging to the first query.
- *
- * @param $columns2
- * (optional) Inserted into the SELECT pat of the second query. Must contain
- * a column selected as 'calculated_score'.
- * defaults to 'SUM(i.relevance) AS calculated_score'
- *
- * @param $join2
- * (optional) Inserted into the JOIN par of the second SQL query.
- * For example "INNER JOIN {node_comment_statistics} n ON n.nid = i.sid"
- *
- * @param $arguments2
- * (optional) Extra SQL arguments belonging to the second query parameter.
- *
- * @param $sort_parameters
- * (optional) SQL arguments for sorting the final results.
- * Default: 'ORDER BY calculated_score DESC'
- *
- * @return
- * An array of SIDs for the search results.
- *
- * @ingroup search
- */
-function do_search($keywords, $type, $join1 = '', $where1 = '1 = 1', $arguments1 = array(), $columns2 = 'SUM(i.relevance) AS calculated_score', $join2 = '', $arguments2 = array(), $sort_parameters = 'ORDER BY calculated_score DESC') {
- $query = search_parse_query($keywords);
-
- if ($query[2] == '') {
- form_set_error('keys', format_plural(variable_get('minimum_word_size', 3), 'You must include at least one positive keyword with 1 character or more.', 'You must include at least one positive keyword with @count characters or more.'));
- }
- if ($query[6]) {
- if ($query[6] == 'or') {
- drupal_set_message(t('Search for either of the two terms with uppercase OR. For example, cats OR dogs.'));
- }
- }
- if ($query === NULL || $query[0] == '' || $query[2] == '') {
- return array();
- }
-
- // Build query for keyword normalization.
- $conditions = "$where1 AND ($query[2]) AND i.type = '%s'";
- $arguments1 = array_merge($arguments1, $query[3], array($type));
- $join = "INNER JOIN {search_total} t ON i.word = t.word $join1";
- if (!$query[5]) {
- $conditions .= " AND ($query[0])";
- $arguments1 = array_merge($arguments1, $query[1]);
- $join .= " INNER JOIN {search_dataset} d ON i.sid = d.sid AND i.type = d.type";
- }
-
- // Calculate maximum keyword relevance, to normalize it.
- $select = "SELECT SUM(i.score * t.count) AS calculated_score FROM {search_index} i $join WHERE $conditions GROUP BY i.type, i.sid HAVING COUNT(*) >= %d ORDER BY calculated_score DESC";
- $arguments = array_merge($arguments1, array($query[4]));
- $normalize = db_result(db_query_range($select, $arguments, 0, 1));
- if (!$normalize) {
- return array();
- }
- $columns2 = str_replace('i.relevance', '(' . (1.0 / $normalize) . ' * i.score * t.count)', $columns2);
-
- // Build query to retrieve results.
- $select = "SELECT i.type, i.sid, $columns2 FROM {search_index} i $join $join2 WHERE $conditions GROUP BY i.type, i.sid HAVING COUNT(*) >= %d";
- $count_select = "SELECT COUNT(*) FROM ($select) n1";
- $arguments = array_values(array_merge($arguments2, $arguments1, array($query[4])));
-
- // Do actual search query
- $result = pager_query("$select $sort_parameters", 10, 0, $count_select, $arguments);
- $results = array();
- while ($item = db_fetch_object($result)) {
- $results[] = $item;
- }
- return $results;
+ return array($keys, $or, $warning, $simple);
}
/**
@@ -1070,7 +402,7 @@ function search_form(&$form_state, $acti
'#action' => $action,
'#attributes' => array('class' => 'search-form'),
);
- $form['module'] = array('#type' => 'value', '#value' => $type);
+ $form['search_type'] = array('#type' => 'value', '#value' => $type);
$form['basic'] = array('#type' => 'item', '#title' => $prompt);
$form['basic']['inline'] = array('#prefix' => '', '#suffix' => '
');
$form['basic']['inline']['keys'] = array(
@@ -1177,17 +509,7 @@ function template_preprocess_search_bloc
function search_data($keys = NULL, $type = 'node') {
if (isset($keys)) {
- if (module_hook($type, 'search')) {
- $results = module_invoke($type, 'search', 'search', $keys);
- if (isset($results) && is_array($results) && count($results)) {
- if (module_hook($type, 'search_page')) {
- return module_invoke($type, 'search_page', $results);
- }
- else {
- return theme('search_results', $results, $type);
- }
- }
- }
+ return search_invoke_handler('data', $type, $keys);
}
}
@@ -1304,16 +626,68 @@ function search_excerpt($keys, $text) {
}
/**
- * @} End of "defgroup search".
- */
-
-/**
* Helper function for array_walk in search_except.
*/
function _search_excerpt_replace(&$text) {
$text = preg_quote($text, '/');
}
+/**
+ * Return available search handlers.
+ */
+function search_handlers($refresh = FALSE) {
+ static $handlers;
+ if (empty($handlers)) {
+ $handlers = module_invoke_all('search');
+ }
+ return $handlers;
+}
+
+/**
+ * Return the search types supported by the active search handler.
+ */
+function search_types($names = FALSE, $refresh = FALSE) {
+ static $types;
+ if (empty($types) || $refresh) {
+ $handlers = search_handlers();
+ $active_handler = search_active_handler();
+ $types = isset($handlers[$active_handler]) ? $handlers[$active_handler]['types'] : array();
+ }
+ return $names ? array_keys($types) : $types;
+}
+
+/**
+ * Return the active search handler.
+ */
+function search_active_handler() {
+ return variable_get('search_handler', _search_default_handler());
+}
+
+/**
+ * Return the default search handler.
+ */
+function _search_default_handler() {
+ return module_exists('sql_search') ? 'sql_search' : NULL;
+}
+
+/**
+ * Call a hook in the active search handler.
+ */
+function search_invoke_handler($hook) {
+ $args = func_get_args();
+ unset($args[0]);
+ $active_handler = search_active_handler();
+ if ($active_handler) {
+ return call_user_func_array($active_handler . '_' . $hook, $args);
+ }
+ /** FIXME: we probably shouldn't display an admin-style warning message for users **/
+ drupal_set_message(t('No search handlers found. To enable search, you must first install and enable a search handler module.'), 'warning');
+}
+
+/**
+ * @} End of "defgroup search".
+ */
+
function search_forms() {
$forms['search_theme_form']= array(
'callback' => 'search_box',
Index: modules/search/search.pages.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/search/search.pages.inc,v
retrieving revision 1.5
diff -u -p -r1.5 search.pages.inc
--- modules/search/search.pages.inc 14 Apr 2008 17:48:41 -0000 1.5
+++ modules/search/search.pages.inc 7 Mar 2009 17:31:41 -0000
@@ -124,7 +124,7 @@ function search_form_submit($form, &$for
// Fall through to the drupal_goto() call.
}
- $type = $form_state['values']['module'] ? $form_state['values']['module'] : 'node';
+ $type = $form_state['values']['search_type'] ? $form_state['values']['search_type'] : 'node';
$form_state['redirect'] = 'search/' . $type . '/' . $keys;
return;
}
Index: modules/user/user.info
===================================================================
RCS file: /cvs/drupal/drupal/modules/user/user.info,v
retrieving revision 1.10
diff -u -p -r1.10 user.info
--- modules/user/user.info 12 Oct 2008 01:23:07 -0000 1.10
+++ modules/user/user.info 7 Mar 2009 17:31:41 -0000
@@ -8,4 +8,5 @@ files[] = user.module
files[] = user.admin.inc
files[] = user.pages.inc
files[] = user.install
+files[] = user.sql_search.inc
required = TRUE
Index: modules/user/user.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/user/user.module,v
retrieving revision 1.966
diff -u -p -r1.966 user.module
--- modules/user/user.module 26 Feb 2009 07:30:29 -0000 1.966
+++ modules/user/user.module 7 Mar 2009 17:31:43 -0000
@@ -717,38 +717,6 @@ function user_file_delete($file) {
}
/**
- * Implementation of hook_search().
- */
-function user_search($op = 'search', $keys = NULL, $skip_access_check = FALSE) {
- switch ($op) {
- case 'name':
- if ($skip_access_check || user_access('access user profiles')) {
- return t('Users');
- }
- case 'search':
- if (user_access('access user profiles')) {
- $find = array();
- // Replace wildcards with MySQL/PostgreSQL wildcards.
- $keys = preg_replace('!\*+!', '%', $keys);
- if (user_access('administer users')) {
- // Administrators can also search in the otherwise private email field.
- $result = pager_query("SELECT name, uid, mail FROM {users} WHERE LOWER(name) LIKE LOWER('%%%s%%') OR LOWER(mail) LIKE LOWER('%%%s%%')", 15, 0, NULL, $keys, $keys);
- while ($account = db_fetch_object($result)) {
- $find[] = array('title' => $account->name . ' (' . $account->mail . ')', 'link' => url('user/' . $account->uid, array('absolute' => TRUE)));
- }
- }
- else {
- $result = pager_query("SELECT name, uid FROM {users} WHERE LOWER(name) LIKE LOWER('%%%s%%')", 15, 0, NULL, $keys);
- while ($account = db_fetch_object($result)) {
- $find[] = array('title' => $account->name, 'link' => url('user/' . $account->uid, array('absolute' => TRUE)));
- }
- }
- return $find;
- }
- }
-}
-
-/**
* Implementation of hook_elements().
*/
function user_elements() {
Index: modules/user/user.sql_search.inc
===================================================================
RCS file: modules/user/user.sql_search.inc
diff -N modules/user/user.sql_search.inc
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ modules/user/user.sql_search.inc 7 Mar 2009 17:31:43 -0000
@@ -0,0 +1,35 @@
+ t('Users'),
+ 'access' => 'access user profiles',
+ );
+ case 'search':
+ if (user_access('access user profiles')) {
+ $find = array();
+ // Replace wildcards with MySQL/PostgreSQL wildcards.
+ $keys = preg_replace('!\*+!', '%', $keys);
+ if (user_access('administer users')) {
+ // Administrators can also search in the otherwise private email field.
+ $result = pager_query("SELECT name, uid, mail FROM {users} WHERE LOWER(name) LIKE LOWER('%%%s%%') OR LOWER(mail) LIKE LOWER('%%%s%%')", 15, 0, NULL, $keys, $keys);
+ while ($account = db_fetch_object($result)) {
+ $find[] = array('title' => $account->name . ' (' . $account->mail . ')', 'link' => url('user/' . $account->uid, array('absolute' => TRUE)));
+ }
+ }
+ else {
+ $result = pager_query("SELECT name, uid FROM {users} WHERE LOWER(name) LIKE LOWER('%%%s%%')", 15, 0, NULL, $keys);
+ while ($account = db_fetch_object($result)) {
+ $find[] = array('title' => $account->name, 'link' => url('user/' . $account->uid, array('absolute' => TRUE)));
+ }
+ }
+ return $find;
+ }
+ }
+}