diff options
author | Steven Wittens <steven@10.no-reply.drupal.org> | 2005-10-18 14:41:27 +0000 |
---|---|---|
committer | Steven Wittens <steven@10.no-reply.drupal.org> | 2005-10-18 14:41:27 +0000 |
commit | 909d6928acb47cb9b50740e589b5e7447353e19c (patch) | |
tree | 6662a983847628472dbd1140d20a9b1e30311d0d /modules/node/node.module | |
parent | 782d5c98c9b37f1d22152af53c6b0604674c38c8 (diff) | |
download | brdo-909d6928acb47cb9b50740e589b5e7447353e19c.tar.gz brdo-909d6928acb47cb9b50740e589b5e7447353e19c.tar.bz2 |
- #28159: Advanced search features (hello from DrupalCon)
Presentation about it:
http://www.acko.net/files/drupal-search-slim.pdf
Diffstat (limited to 'modules/node/node.module')
-rw-r--r-- | modules/node/node.module | 170 |
1 files changed, 168 insertions, 2 deletions
diff --git a/modules/node/node.module b/modules/node/node.module index da0f2d9e1..9187583e8 100644 --- a/modules/node/node.module +++ b/modules/node/node.module @@ -597,17 +597,112 @@ function node_search($op = 'search', $keys = null) { switch ($op) { case 'name': return t('content'); + case 'reset': variable_del('node_cron_last'); return; + case 'status': $last = variable_get('node_cron_last', 0); $total = db_result(db_query('SELECT COUNT(*) FROM {node} WHERE status = 1 AND moderate = 0')); $remaining = db_result(db_query('SELECT COUNT(*) FROM {node} n LEFT JOIN {node_comment_statistics} c ON n.nid = c.nid WHERE n.status = 1 AND n.moderate = 0 AND (n.created > %d OR n.changed > %d OR c.last_comment_timestamp > %d)', $last, $last, $last)); 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('#type' => 'markup', '#value' => '<em>'. 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.') .'</em>'); + + $ranking = array('node_rank_relevance' => t('Keyword relevance'), + 'node_rank_recent' => t('Recently posted')); + if (module_exist('comment')) { + $ranking['node_rank_comments'] = t('Number of comments'); + } + if (module_exist('statistics') && variable_get('statistics_count_content_views', 0)) { + $ranking['node_rank_views'] = t('Number of views'); + } + + // Note: reversed to reflect that higher number = higher ranking. + $options = drupal_map_assoc(range(0, 10)); + foreach ($ranking as $var => $title) { + $form['content_ranking']['factors'][$var] = array('#title' => $title, '#type' => 'select', '#options' => $options, '#default_value' => variable_get($var, 5)); + } + return $form; + case 'search': - list($join, $where) = _db_rewrite_sql(); - $find = do_search($keys, 'node', 'INNER JOIN {node} n ON n.nid = i.sid '. $join .' INNER JOIN {users} u ON n.uid = u.uid', 'n.status = 1'. (empty($where) ? '' : ' AND '. $where)); + // 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 ($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.nid = tn.nid'; + $keys = search_query_insert($keys, 'category'); + } + + // Build ranking expression (we try to map each parameter to a + // uniform distribution in the range 0..1). + $ranking = array(); + $arguments2 = array(); + $join2 = ''; + // Used to avoid joining on node_comment_statistics twice + $stats_join = false; + if ($weight = (int)variable_get('node_rank_relevance', 5)) { + // Average relevance values hover around 0.15 + $ranking[] = '%d * i.relevance'; + $arguments2[] = $weight; + } + if ($weight = (int)variable_get('node_rank_recent', 5)) { + // Exponential decay with half-life of 6 months, starting at last indexed node + $ranking[] = '%d * POW(2, (GREATEST(n.created, n.changed, c.last_comment_timestamp) - %d) * 6.43e-8)'; + $arguments2[] = $weight; + $arguments2[] = (int)variable_get('node_cron_last', 0); + $join2 .= ' INNER JOIN {node} n ON n.nid = i.sid LEFT JOIN {node_comment_statistics} c ON c.nid = i.sid'; + $stats_join = true; + } + if (module_exist('comment') && $weight = (int)variable_get('node_rank_comments', 5)) { + // Inverse law that maps the highest reply count on the site to 1 and 0 to 0. + $scale = variable_get('node_cron_comments_scale', 0.0); + $ranking[] = '%d * (2.0 - 2.0 / (1.0 + c.comment_count * %f))'; + $arguments2[] = $weight; + $arguments2[] = $scale; + if (!$stats_join) { + $join2 .= ' LEFT JOIN {node_comment_statistics} c ON c.nid = i.sid'; + } + } + if (module_exist('statistics') && variable_get('statistics_count_content_views', 0) && + $weight = (int)variable_get('node_rank_views', 5)) { + // Inverse law that maps the highest view count on the site to 1 and 0 to 0. + $scale = variable_get('node_cron_views_scale', 0.0); + $ranking[] = '%d * (2.0 - 2.0 / (1.0 + nc.totalcount * %f))'; + $arguments2[] = $weight; + $arguments2[] = $scale; + $join2 .= ' LEFT JOIN {node_counter} nc ON n.nid = nc.nid'; + } + $select2 = (count($ranking) ? implode(' + ', $ranking) : 'i.relevance') . ' AS score'; + + // Do search + $find = do_search($keys, 'node', 'INNER JOIN {node} n ON n.nid = i.sid '. $join1 .' INNER JOIN {users} u ON n.uid = u.uid', $conditions1 . (empty($where1) ? '' : ' AND '. $where1), $arguments1, $select2, $join2, $arguments2); + + // Load results $results = array(); foreach ($find as $item) { $node = node_load($item); @@ -622,19 +717,86 @@ function node_search($op = 'search', $keys = null) { // Allow modules to change $node->body before viewing. node_invoke_nodeapi($node, 'view', false, false); + // Fetch comments for snippet + $node->body .= module_invoke('comment', 'nodeapi', $node, 'update index'); + $extra = node_invoke_nodeapi($node, 'search result'); $results[] = array('link' => url('node/'. $item), 'type' => node_get_name($node), 'title' => $node->title, 'user' => theme('username', $node), 'date' => $node->changed, + 'node' => $node, 'extra' => $extra, 'snippet' => search_excerpt($keys, $node->body)); } return $results; + + case 'form': + $form = array(); + + // Keyword boxes + $form['advanced'] = array('#type' => 'fieldset', '#title' => t('Advanced search'), '#collapsible' => true, '#collapsed' => true, '#attributes' => array('class' => 'search-advanced')); + + $form['advanced']['keywords'] = array('#type' => 'markup', '#prefix' => '<div class="criterium">', '#suffix' => '</div>'); + $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')) { + $form['advanced']['category'] = array('#type' => 'select', '#title' => t('Only in the category'), '#prefix' => '<div class="criterium">', '#suffix' => '</div>', '#options' => $taxonomy, '#extra' => 'size="10"', '#multiple' => true); + } + + // Node types + $types = node_get_types(); + $form['advanced']['type'] = array('#type' => 'checkboxes', '#title' => t('Only of the type'), '#prefix' => '<div class="criterium">', '#suffix' => '</div>', '#options' => $types, '#multiple' => true); + $form['advanced']['submit'] = array('#type' => 'submit', '#value' => t('Advanced Search'), '#prefix' => '<div class="action">', '#suffix' => '</div>'); + return $form; + + case 'post': + // Insert extra restrictions into the search keywords string. + $edit = &$_POST['edit']; + if (is_array($edit['type'])) { + $keys = search_query_insert($keys, 'type', implode(',', array_keys($edit['type']))); + } + if (is_array($edit['category'])) { + $keys = search_query_insert($keys, 'category', implode(',', $edit['category'])); + } + if ($edit['or'] != '') { + if (preg_match_all('/ ("[^"]+"|[^" ]+)/i', ' '. $edit['or'], $matches)) { + $keys = $keys .' '. implode(' OR ', $matches[1]); + } + } + if ($edit['negative'] != '') { + if (preg_match_all('/ ("[^"]+"|[^" ]+)/i', ' '. $edit['negative'], $matches)) { + $keys = $keys .' -'. implode(' -', $matches[1]); + } + } + if ($edit['phrase'] != '') { + $keys .= ' "'. str_replace('"', ' ', $edit['phrase']) .'"'; + } + return trim($keys); } } +function theme_node_search_admin($form) { + $output = form_render($form['info']); + + $header = array(t('Factor'), t('Weight')); + foreach (element_children($form['factors']) as $key) { + $row = array(); + $row[] = $form['factors'][$key]['#title']; + unset($form['factors'][$key]['#title']); + $row[] = form_render($form['factors'][$key]); + $rows[] = $row; + } + $output .= theme('table', $header, $rows); + + $output .= form_render($form); + return $output; +} + /** * Menu callback; presents general node configuration options. */ @@ -1864,6 +2026,10 @@ function node_update_index() { $last = variable_get('node_cron_last', 0); $limit = (int)variable_get('search_cron_limit', 100); + // Store the maximum possible comments per thread (used for ranking by reply count) + variable_set('node_cron_comments_scale', 1.0 / max(1, db_result(db_query('SELECT MAX(comment_count) FROM {node_comment_statistics}')))); + 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, c.last_comment_timestamp FROM {node} n LEFT JOIN {node_comment_statistics} c ON n.nid = c.nid WHERE n.status = 1 AND n.moderate = 0 AND (n.created > %d OR n.changed > %d OR c.last_comment_timestamp > %d) ORDER BY GREATEST(n.created, n.changed, c.last_comment_timestamp) ASC', $last, $last, $last, 0, $limit); while ($node = db_fetch_object($result)) { |