View source
<?php
define('PREG_CLASS_SEARCH_EXCLUDE', '\\x{0}-\\x{2f}\\x{3a}-\\x{40}\\x{5b}-\\x{60}\\x{7b}-\\x{bf}\\x{d7}\\x{f7}\\x{2b0}-' . '\\x{385}\\x{387}\\x{3f6}\\x{482}-\\x{489}\\x{559}-\\x{55f}\\x{589}-\\x{5c7}\\x{5f3}-' . '\\x{61f}\\x{640}\\x{64b}-\\x{65e}\\x{66a}-\\x{66d}\\x{670}\\x{6d4}\\x{6d6}-\\x{6ed}' . '\\x{6fd}\\x{6fe}\\x{700}-\\x{70f}\\x{711}\\x{730}-\\x{74a}\\x{7a6}-\\x{7b0}\\x{901}-' . '\\x{903}\\x{93c}\\x{93e}-\\x{94d}\\x{951}-\\x{954}\\x{962}-\\x{965}\\x{970}\\x{981}-' . '\\x{983}\\x{9bc}\\x{9be}-\\x{9cd}\\x{9d7}\\x{9e2}\\x{9e3}\\x{9f2}-\\x{a03}\\x{a3c}-' . '\\x{a4d}\\x{a70}\\x{a71}\\x{a81}-\\x{a83}\\x{abc}\\x{abe}-\\x{acd}\\x{ae2}\\x{ae3}' . '\\x{af1}-\\x{b03}\\x{b3c}\\x{b3e}-\\x{b57}\\x{b70}\\x{b82}\\x{bbe}-\\x{bd7}\\x{bf0}-' . '\\x{c03}\\x{c3e}-\\x{c56}\\x{c82}\\x{c83}\\x{cbc}\\x{cbe}-\\x{cd6}\\x{d02}\\x{d03}' . '\\x{d3e}-\\x{d57}\\x{d82}\\x{d83}\\x{dca}-\\x{df4}\\x{e31}\\x{e34}-\\x{e3f}\\x{e46}-' . '\\x{e4f}\\x{e5a}\\x{e5b}\\x{eb1}\\x{eb4}-\\x{ebc}\\x{ec6}-\\x{ecd}\\x{f01}-\\x{f1f}' . '\\x{f2a}-\\x{f3f}\\x{f71}-\\x{f87}\\x{f90}-\\x{fd1}\\x{102c}-\\x{1039}\\x{104a}-' . '\\x{104f}\\x{1056}-\\x{1059}\\x{10fb}\\x{10fc}\\x{135f}-\\x{137c}\\x{1390}-\\x{1399}' . '\\x{166d}\\x{166e}\\x{1680}\\x{169b}\\x{169c}\\x{16eb}-\\x{16f0}\\x{1712}-\\x{1714}' . '\\x{1732}-\\x{1736}\\x{1752}\\x{1753}\\x{1772}\\x{1773}\\x{17b4}-\\x{17db}\\x{17dd}' . '\\x{17f0}-\\x{180e}\\x{1843}\\x{18a9}\\x{1920}-\\x{1945}\\x{19b0}-\\x{19c0}\\x{19c8}' . '\\x{19c9}\\x{19de}-\\x{19ff}\\x{1a17}-\\x{1a1f}\\x{1d2c}-\\x{1d61}\\x{1d78}\\x{1d9b}-' . '\\x{1dc3}\\x{1fbd}\\x{1fbf}-\\x{1fc1}\\x{1fcd}-\\x{1fcf}\\x{1fdd}-\\x{1fdf}\\x{1fed}-' . '\\x{1fef}\\x{1ffd}-\\x{2070}\\x{2074}-\\x{207e}\\x{2080}-\\x{2101}\\x{2103}-\\x{2106}' . '\\x{2108}\\x{2109}\\x{2114}\\x{2116}-\\x{2118}\\x{211e}-\\x{2123}\\x{2125}\\x{2127}' . '\\x{2129}\\x{212e}\\x{2132}\\x{213a}\\x{213b}\\x{2140}-\\x{2144}\\x{214a}-\\x{2b13}' . '\\x{2ce5}-\\x{2cff}\\x{2d6f}\\x{2e00}-\\x{3005}\\x{3007}-\\x{303b}\\x{303d}-\\x{303f}' . '\\x{3099}-\\x{309e}\\x{30a0}\\x{30fb}-\\x{30fe}\\x{3190}-\\x{319f}\\x{31c0}-\\x{31cf}' . '\\x{3200}-\\x{33ff}\\x{4dc0}-\\x{4dff}\\x{a015}\\x{a490}-\\x{a716}\\x{a802}\\x{a806}' . '\\x{a80b}\\x{a823}-\\x{a82b}\\x{d800}-\\x{f8ff}\\x{fb1e}\\x{fb29}\\x{fd3e}\\x{fd3f}' . '\\x{fdfc}-\\x{fe6b}\\x{feff}-\\x{ff0f}\\x{ff1a}-\\x{ff20}\\x{ff3b}-\\x{ff40}\\x{ff5b}-' . '\\x{ff65}\\x{ff70}\\x{ff9e}\\x{ff9f}\\x{ffe0}-\\x{fffd}');
define('PREG_CLASS_NUMBERS', '\\x{30}-\\x{39}\\x{b2}\\x{b3}\\x{b9}\\x{bc}-\\x{be}\\x{660}-\\x{669}\\x{6f0}-\\x{6f9}' . '\\x{966}-\\x{96f}\\x{9e6}-\\x{9ef}\\x{9f4}-\\x{9f9}\\x{a66}-\\x{a6f}\\x{ae6}-\\x{aef}' . '\\x{b66}-\\x{b6f}\\x{be7}-\\x{bf2}\\x{c66}-\\x{c6f}\\x{ce6}-\\x{cef}\\x{d66}-\\x{d6f}' . '\\x{e50}-\\x{e59}\\x{ed0}-\\x{ed9}\\x{f20}-\\x{f33}\\x{1040}-\\x{1049}\\x{1369}-' . '\\x{137c}\\x{16ee}-\\x{16f0}\\x{17e0}-\\x{17e9}\\x{17f0}-\\x{17f9}\\x{1810}-\\x{1819}' . '\\x{1946}-\\x{194f}\\x{2070}\\x{2074}-\\x{2079}\\x{2080}-\\x{2089}\\x{2153}-\\x{2183}' . '\\x{2460}-\\x{249b}\\x{24ea}-\\x{24ff}\\x{2776}-\\x{2793}\\x{3007}\\x{3021}-\\x{3029}' . '\\x{3038}-\\x{303a}\\x{3192}-\\x{3195}\\x{3220}-\\x{3229}\\x{3251}-\\x{325f}\\x{3280}-' . '\\x{3289}\\x{32b1}-\\x{32bf}\\x{ff10}-\\x{ff19}');
define('PREG_CLASS_PUNCTUATION', '\\x{21}-\\x{23}\\x{25}-\\x{2a}\\x{2c}-\\x{2f}\\x{3a}\\x{3b}\\x{3f}\\x{40}\\x{5b}-\\x{5d}' . '\\x{5f}\\x{7b}\\x{7d}\\x{a1}\\x{ab}\\x{b7}\\x{bb}\\x{bf}\\x{37e}\\x{387}\\x{55a}-\\x{55f}' . '\\x{589}\\x{58a}\\x{5be}\\x{5c0}\\x{5c3}\\x{5f3}\\x{5f4}\\x{60c}\\x{60d}\\x{61b}\\x{61f}' . '\\x{66a}-\\x{66d}\\x{6d4}\\x{700}-\\x{70d}\\x{964}\\x{965}\\x{970}\\x{df4}\\x{e4f}' . '\\x{e5a}\\x{e5b}\\x{f04}-\\x{f12}\\x{f3a}-\\x{f3d}\\x{f85}\\x{104a}-\\x{104f}\\x{10fb}' . '\\x{1361}-\\x{1368}\\x{166d}\\x{166e}\\x{169b}\\x{169c}\\x{16eb}-\\x{16ed}\\x{1735}' . '\\x{1736}\\x{17d4}-\\x{17d6}\\x{17d8}-\\x{17da}\\x{1800}-\\x{180a}\\x{1944}\\x{1945}' . '\\x{2010}-\\x{2027}\\x{2030}-\\x{2043}\\x{2045}-\\x{2051}\\x{2053}\\x{2054}\\x{2057}' . '\\x{207d}\\x{207e}\\x{208d}\\x{208e}\\x{2329}\\x{232a}\\x{23b4}-\\x{23b6}\\x{2768}-' . '\\x{2775}\\x{27e6}-\\x{27eb}\\x{2983}-\\x{2998}\\x{29d8}-\\x{29db}\\x{29fc}\\x{29fd}' . '\\x{3001}-\\x{3003}\\x{3008}-\\x{3011}\\x{3014}-\\x{301f}\\x{3030}\\x{303d}\\x{30a0}' . '\\x{30fb}\\x{fd3e}\\x{fd3f}\\x{fe30}-\\x{fe52}\\x{fe54}-\\x{fe61}\\x{fe63}\\x{fe68}' . '\\x{fe6a}\\x{fe6b}\\x{ff01}-\\x{ff03}\\x{ff05}-\\x{ff0a}\\x{ff0c}-\\x{ff0f}\\x{ff1a}' . '\\x{ff1b}\\x{ff1f}\\x{ff20}\\x{ff3b}-\\x{ff3d}\\x{ff3f}\\x{ff5b}\\x{ff5d}\\x{ff5f}-' . '\\x{ff65}');
define('PREG_CLASS_CJK', '\\x{3041}-\\x{30ff}\\x{31f0}-\\x{31ff}\\x{3400}-\\x{4db5}' . '\\x{4e00}-\\x{9fbb}\\x{f900}-\\x{fad9}');
function search_help($section) {
switch ($section) {
case 'admin/help#search':
$output = '<p>' . 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. Search is useful for finding users and posts by searching on keywords.') . '</p>';
$output .= '<p>' . t('The search engine works by maintaining an index of the words in your site\'s content. It indexes the posts and users. You can adjust the settings to tweak the indexing behaviour. Note that the search requires cron to be set up correctly. The index percentage sets the maximum amount 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.') . '</p>';
$output .= '<p>' . t('For more information please read the configuration and customization handbook <a href="@search">Search page</a>.', array(
'@search' => 'http://drupal.org/handbook/modules/search/',
)) . '</p>';
return $output;
case 'admin/settings/search':
return '<p>' . t('The search engine works by maintaining an index of the words in your site\'s content. You can adjust the settings below to tweak the indexing behaviour. Note that the search requires cron to be set up correctly.') . '</p>';
case 'search#noresults':
return t('<ul>
<li>Check if your spelling is correct.</li>
<li>Remove quotes around phrases to match each word individually: <em>"blue smurf"</em> will match less than <em>blue smurf</em>.</li>
<li>Consider loosening your query with <em>OR</em>: <em>blue smurf</em> will match less than <em>blue OR smurf</em>.</li>
</ul>');
}
}
function search_perm() {
return array(
'search content',
'use advanced search',
'administer search',
);
}
function search_block($op = 'list', $delta = 0) {
if ($op == 'list') {
$blocks[0]['info'] = t('Search form');
return $blocks;
}
else {
if ($op == 'view' && user_access('search content')) {
$block['content'] = drupal_get_form('search_block_form');
$block['subject'] = t('Search');
return $block;
}
}
}
function search_menu($may_cache) {
$items = array();
if ($may_cache) {
$items[] = array(
'path' => 'search',
'title' => t('Search'),
'callback' => 'search_view',
'access' => user_access('search content'),
'type' => MENU_SUGGESTED_ITEM,
);
$items[] = array(
'path' => 'admin/settings/search',
'title' => t('Search settings'),
'description' => t('Configure relevance settings for search and other indexing options'),
'callback' => 'drupal_get_form',
'callback arguments' => array(
'search_admin_settings',
),
'access' => user_access('administer search'),
'type' => MENU_NORMAL_ITEM,
);
$items[] = array(
'path' => 'admin/settings/search/wipe',
'title' => t('Clear index'),
'callback' => 'drupal_get_form',
'callback arguments' => array(
'search_wipe_confirm',
),
'access' => user_access('administer search'),
'type' => MENU_CALLBACK,
);
$items[] = array(
'path' => 'admin/logs/search',
'title' => t('Top search phrases'),
'description' => t('View most popular search phrases.'),
'callback' => 'watchdog_top',
'callback arguments' => array(
'search',
),
);
}
else {
if (arg(0) == 'search') {
$keys = search_get_keys();
$keys = strlen($keys) ? '/' . $keys : '';
foreach (module_list() as $name) {
if (module_hook($name, 'search') && ($title = module_invoke($name, 'search', 'name'))) {
$items[] = array(
'path' => 'search/' . $name . $keys,
'title' => $title,
'callback' => 'search_view',
'access' => user_access('search content'),
'type' => MENU_LOCAL_TASK,
);
}
}
}
}
return $items;
}
function search_admin_settings_validate($form_id, $form_values) {
if ($form_values['op'] == t('Re-index site')) {
drupal_goto('admin/settings/search/wipe');
}
if (variable_get('minimum_word_size', 3) != $form_values['minimum_word_size'] || variable_get('overlap_cjk', TRUE) != $form_values['overlap_cjk']) {
drupal_set_message(t('The index will be rebuilt.'));
search_wipe();
}
}
function search_admin_settings() {
$remaining = 0;
$total = 0;
foreach (module_list() as $module) {
if (module_hook($module, 'search')) {
$status = module_invoke($module, 'search', 'status');
$remaining += $status['remaining'];
$total += $status['total'];
}
}
$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 = '<p><strong>' . t('%percentage of the site has been indexed.', array(
'%percentage' => $percentage,
)) . ' ' . $count . '</strong></p>';
$form['status'] = array(
'#type' => 'fieldset',
'#title' => t('Indexing status'),
);
$form['status']['status'] = array(
'#value' => $status,
);
$form['status']['wipe'] = array(
'#type' => 'submit',
'#value' => t('Re-index site'),
);
$items = drupal_map_assoc(array(
10,
20,
50,
100,
200,
500,
));
$form['indexing_throttle'] = array(
'#type' => 'fieldset',
'#title' => t('Indexing throttle'),
);
$form['indexing_throttle']['search_cron_limit'] = array(
'#type' => 'select',
'#title' => t('Items to index per cron run'),
'#default_value' => variable_get('search_cron_limit', 100),
'#options' => $items,
'#description' => t('The maximum amount 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.'),
);
$form['indexing_settings'] = array(
'#type' => 'fieldset',
'#title' => t('Indexing settings'),
);
$form['indexing_settings']['info'] = array(
'#value' => '<em>' . t('<p>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.</p><p>The default settings should be appropriate for the majority of sites.</p>') . '</em>',
);
$form['indexing_settings']['minimum_word_size'] = array(
'#type' => 'textfield',
'#title' => t('Minimum word length to index'),
'#default_value' => variable_get('minimum_word_size', 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).'),
);
$form['indexing_settings']['overlap_cjk'] = array(
'#type' => 'checkbox',
'#title' => t('Simple CJK handling'),
'#default_value' => variable_get('overlap_cjk', 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 = array_merge($form, module_invoke_all('search', 'admin'));
return system_settings_form($form);
}
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'));
}
function search_wipe_confirm_submit($form_id, &$form) {
if ($form['confirm']) {
search_wipe();
drupal_set_message(t('The index will be rebuilt.'));
return 'admin/settings/search';
}
}
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 fromsid = %d AND fromtype = '%s'", $sid, $type);
db_query("DELETE FROM {search_index} WHERE sid = %d AND type = '%s'" . ($reindex ? " AND fromsid = 0" : ''), $sid, $type);
}
}
function search_dirty($word = NULL) {
static $dirty = array();
if ($word !== NULL) {
$dirty[$word] = TRUE;
}
else {
return $dirty;
}
}
function search_cron() {
register_shutdown_function('search_update_totals');
foreach (module_list() as $module) {
module_invoke($module, 'update_index');
}
}
function search_update_totals() {
foreach (search_dirty() as $word => $dummy) {
$total = db_result(db_query("SELECT SUM(score) FROM {search_index} WHERE word = '%s'", $word));
$total = log10(1 + 1 / max(1, $total));
db_query("UPDATE {search_total} SET count = %f WHERE word = '%s'", $total, $word);
if (!db_affected_rows()) {
db_query("INSERT INTO {search_total} (word, count) VALUES ('%s', %f)", $word, $total);
}
}
$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);
}
}
function search_simplify($text) {
$text = decode_entities($text);
$text = drupal_strtolower($text);
search_preprocess($text);
if (variable_get('overlap_cjk', TRUE)) {
$text = preg_replace_callback('/[' . PREG_CLASS_CJK . ']+/u', 'search_expand_cjk', $text);
}
$text = preg_replace('/([' . PREG_CLASS_NUMBERS . ']+)[' . PREG_CLASS_PUNCTUATION . ']+(?=[' . PREG_CLASS_NUMBERS . '])/u', '\\1', $text);
$text = preg_replace('/[._-]+/', '', $text);
$text = preg_replace('/[' . PREG_CLASS_SEARCH_EXCLUDE . ']+/u', ' ', $text);
return $text;
}
function search_expand_cjk($matches) {
$min = variable_get('minimum_word_size', 3);
$str = $matches[0];
$l = drupal_strlen($str);
if ($l <= $min) {
return ' ' . $str . ' ';
}
$tokens = ' ';
$chars = array();
for ($i = 0; $i < $l; ++$i) {
$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;
}
function search_index_split($text) {
static $last = NULL;
static $lastsplit = NULL;
if ($last == $text) {
return $lastsplit;
}
$text = search_simplify($text);
$words = explode(' ', $text);
array_walk($words, '_search_index_truncate');
$last = $text;
$lastsplit = $words;
return $words;
}
function _search_index_truncate(&$text) {
$text = truncate_utf8($text, 50);
}
function search_preprocess(&$text) {
foreach (module_implements('search_preprocess') as $module) {
$text = module_invoke($module, 'search_preprocess', $text);
}
}
function search_index($sid, $type, $text) {
$minimum_word_size = variable_get('minimum_word_size', 3);
global $base_url;
$node_regexp = '@href=[\'"]?(?:' . preg_quote($base_url, '@') . '/|' . preg_quote(base_path(), '@') . ')(?:\\?q=)?/?((?![a-z]+:)[^\'">]+)[\'">]@i';
$tags = 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,
);
$text = str_replace(array(
'<',
'>',
), array(
' <',
'> ',
), $text);
$text = strip_tags($text, '<' . implode('><', array_keys($tags)) . '>');
$split = preg_split('/\\s*<([^>]+?)>\\s*/', $text, -1, PREG_SPLIT_DELIM_CAPTURE);
$tag = FALSE;
$link = FALSE;
$score = 1;
$accum = ' ';
$tagstack = array();
$tagwords = 0;
$focus = 1;
$results = array(
0 => array(),
);
foreach ($split as $value) {
if ($tag) {
list($tagname) = explode(' ', $value, 2);
$tagname = drupal_strtolower($tagname);
if ($tagname[0] == '/') {
$tagname = substr($tagname, 1);
if (!count($tagstack) || $tagstack[0] != $tagname) {
$tagstack = array();
$score = 1;
}
else {
$score = max(1, $score - $tags[array_shift($tagstack)]);
}
if ($tagname == 'a') {
$link = FALSE;
}
}
else {
if ($tagstack[0] == $tagname) {
$tagstack = array();
$score = 1;
}
else {
array_unshift($tagstack, $tagname);
$score += $tags[$tagname];
}
if ($tagname == 'a') {
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) {
$node = db_fetch_object(db_query('SELECT n.title, n.nid, n.vid, r.format FROM {node} n INNER JOIN {node_revisions} r ON n.vid = r.vid WHERE n.nid = %d', $linknid));
if (filter_format_allowcache($node->format)) {
$link = TRUE;
$linktitle = $node->title;
}
}
}
}
}
}
$tagwords = 0;
}
else {
if ($value != '') {
if ($link) {
if (preg_match('!^https?://!i', $value)) {
$value = $linktitle;
}
}
$words = search_index_split($value);
foreach ($words as $word) {
$accum .= $word . ' ';
$num = is_numeric($word);
if ($num || drupal_strlen($word) >= $minimum_word_size) {
if ($num) {
$word = (int) ltrim($word, '-0');
}
if ($link) {
if (!isset($results[$linknid])) {
$results[$linknid] = array();
}
$results[$linknid][$word] += $score * $focus;
}
else {
$results[0][$word] += $score * $focus;
$focus = min(1, 0.01 + 3.5 / (2 + count($results[0]) * 0.015));
}
}
$tagwords++;
if (count($tagstack) && $tagwords >= 15) {
$tagstack = array();
$score = 1;
}
}
}
}
$tag = !$tag;
}
search_wipe($sid, $type, TRUE);
db_query("INSERT INTO {search_dataset} (sid, type, data) VALUES (%d, '%s', '%s')", $sid, $type, $accum);
foreach ($results[0] as $word => $score) {
db_query("INSERT INTO {search_index} (word, sid, type, score) VALUES ('%s', %d, '%s', %f)", $word, $sid, $type, $score);
search_dirty($word);
}
unset($results[0]);
foreach ($results as $nid => $words) {
foreach ($words as $word => $score) {
db_query("INSERT INTO {search_index} (word, sid, type, fromsid, fromtype, score) VALUES ('%s', %d, '%s', %d, '%s', %f)", $word, $nid, 'node', $sid, $type, $score);
search_dirty($word);
}
}
}
function search_query_extract($keys, $option) {
if (preg_match('/(^| )' . $option . ':([^ ]*)( |$)/i', $keys, $matches)) {
return $matches[2];
}
}
function search_query_insert($keys, $option, $value = '') {
if (search_query_extract($keys, $option)) {
$keys = trim(preg_replace('/(^| )' . $option . ':[^ ]*/i', '', $keys));
}
if ($value != '') {
$keys .= ' ' . $option . ':' . $value;
}
return $keys;
}
function search_parse_query($text) {
$keys = array(
'positive' => array(),
'negative' => array(),
);
preg_match_all('/ (-?)("[^"]+"|[^" ]+)/i', ' ' . $text, $matches, PREG_SET_ORDER);
if (count($matches) < 1) {
return NULL;
}
$or = FALSE;
foreach ($matches as $match) {
$phrase = FALSE;
if ($match[2][0] == '"') {
$match[2] = substr($match[2], 1, -1);
$phrase = TRUE;
}
$words = search_simplify($match[2]);
$words = $phrase ? array(
$words,
) : preg_split('/ /', $words, -1, PREG_SPLIT_NO_EMPTY);
if ($match[1] == '-') {
$keys['negative'] = array_merge($keys['negative'], $words);
}
elseif ($match[2] == 'OR' && count($keys['positive'])) {
$last = array_pop($keys['positive']);
if (!is_array($last)) {
$last = array(
$last,
);
}
$keys['positive'][] = $last;
$or = TRUE;
continue;
}
else {
if ($or) {
$keys['positive'][count($keys['positive']) - 1] = array_merge($keys['positive'][count($keys['positive']) - 1], $words);
}
else {
$keys['positive'] = array_merge($keys['positive'], $words);
}
}
$or = FALSE;
}
$query = array();
$query2 = array();
$arguments = array();
$arguments2 = array();
$matches = 0;
foreach ($keys['positive'] as $key) {
if (is_array($key) && count($key)) {
$queryor = array();
$any = FALSE;
foreach ($key as $or) {
list($q, $count) = _search_parse_query($or, $arguments2);
$any |= $count;
if ($q) {
$queryor[] = $q;
$arguments[] = $or;
}
}
if (count($queryor)) {
$query[] = '(' . implode(' OR ', $queryor) . ')';
$matches += $any > 0;
}
}
else {
list($q, $count) = _search_parse_query($key, $arguments2);
if ($q) {
$query[] = $q;
$arguments[] = $key;
$matches += $count;
}
}
}
foreach ($keys['negative'] as $key) {
list($q) = _search_parse_query($key, $arguments2, TRUE);
if ($q) {
$query[] = $q;
$arguments[] = $key;
}
}
$query = implode(' AND ', $query);
$query2 = substr(str_repeat("i.word = '%s' OR ", count($arguments2)), 0, -4);
return array(
$query,
$arguments,
$query2,
$arguments2,
$matches,
);
}
function _search_parse_query(&$word, &$scores, $not = FALSE) {
$count = 0;
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;
$count++;
}
}
}
}
return array(
"d.data " . ($not ? 'NOT ' : '') . "LIKE '%% %s %%'",
$count,
);
}
function do_search($keywords, $type, $join1 = '', $where1 = '1', $arguments1 = array(), $select2 = 'i.relevance AS score', $join2 = '', $arguments2 = array(), $sort_parameters = 'ORDER BY score DESC') {
$query = search_parse_query($keywords);
if ($query[2] == '') {
form_set_error('keys', t('You must include at least one positive keyword with @count characters or more.', array(
'@count' => variable_get('minimum_word_size', 3),
)));
}
if ($query === NULL || $query[0] == '' || $query[2] == '') {
return array();
}
$conditions = $where1 . ' AND (' . $query[2] . ") AND i.type = '%s'";
$arguments = array_merge($arguments1, $query[3], array(
$type,
$query[4],
));
$result = db_query_temporary("SELECT i.type, i.sid, SUM(i.score * t.count) AS relevance, COUNT(*) AS matches FROM {search_index} i INNER JOIN {search_total} t ON i.word = t.word {$join1} WHERE {$conditions} GROUP BY i.type, i.sid HAVING COUNT(*) >= %d", $arguments, 'temp_search_sids');
$normalize = db_result(db_query('SELECT MAX(relevance) FROM temp_search_sids'));
if (!$normalize) {
return array();
}
$select2 = str_replace('i.relevance', '(' . 1.0 / $normalize . ' * i.relevance)', $select2);
$conditions = '(' . $query[0] . ')';
$arguments = array_merge($arguments2, $query[1]);
$result = db_query_temporary("SELECT i.type, i.sid, {$select2} FROM temp_search_sids i INNER JOIN {search_dataset} d ON i.sid = d.sid AND i.type = d.type {$join2} WHERE {$conditions} {$sort_parameters}", $arguments, 'temp_search_results');
if (($count = db_result(db_query('SELECT COUNT(*) FROM temp_search_results'))) == 0) {
return array();
}
$count_query = "SELECT {$count}";
$result = pager_query("SELECT * FROM temp_search_results", 10, 0, $count_query);
$results = array();
while ($item = db_fetch_object($result)) {
$results[] = $item;
}
return $results;
}
function search_get_keys() {
$path = explode('/', $_GET['q'], 3);
return count($path) == 3 ? $path[2] : $_REQUEST['keys'];
}
function search_view() {
$type = arg(1);
if (!isset($_POST['form_id'])) {
if ($type == '') {
drupal_goto('search/node');
}
$keys = search_get_keys();
if (trim($keys)) {
watchdog('search', t('%keys (@type).', array(
'%keys' => $keys,
'@type' => module_invoke($type, 'search', 'name'),
)), WATCHDOG_NOTICE, l(t('results'), 'search/' . $type . '/' . $keys));
$results = search_data($keys, $type);
if ($results) {
$results = theme('box', t('Search results'), $results);
}
else {
$results = theme('box', t('Your search yielded no results'), search_help('search#noresults'));
}
}
$output = drupal_get_form('search_form', NULL, $keys, $type);
$output .= $results;
return $output;
}
return drupal_get_form('search_form', NULL, $keys, $type);
}
function search_form($action = '', $keys = '', $type = NULL, $prompt = NULL) {
drupal_add_css(drupal_get_path('module', 'search') . '/search.css', 'module', 'all', FALSE);
if (!$action) {
$action = url('search/' . $type);
}
if (is_null($prompt)) {
$prompt = t('Enter your keywords');
}
$form = array(
'#action' => $action,
'#attributes' => array(
'class' => 'search-form',
),
);
$form['module'] = array(
'#type' => 'value',
'#value' => $type,
);
$form['basic'] = array(
'#type' => 'item',
'#title' => $prompt,
);
$form['basic']['inline'] = array(
'#prefix' => '<div class="container-inline">',
'#suffix' => '</div>',
);
$form['basic']['inline']['keys'] = array(
'#type' => 'textfield',
'#title' => '',
'#default_value' => $keys,
'#size' => $prompt ? 40 : 20,
'#maxlength' => 255,
);
$form['basic']['inline']['processed_keys'] = array(
'#type' => 'value',
'#value' => array(),
);
$form['basic']['inline']['submit'] = array(
'#type' => 'submit',
'#value' => t('Search'),
);
return $form;
}
function search_form_validate($form_id, $form_values, $form) {
form_set_value($form['basic']['inline']['processed_keys'], trim($form_values['keys']));
}
function search_form_submit($form_id, $form_values) {
$keys = $form_values['processed_keys'];
if ($keys == '') {
form_set_error('keys', t('Please enter some keywords.'));
}
$type = $form_values['module'] ? $form_values['module'] : 'node';
return 'search/' . $type . '/' . $keys;
}
function search_box($form_id) {
$form[$form_id . '_keys'] = array(
'#type' => 'textfield',
'#size' => 15,
'#default_value' => '',
'#attributes' => array(
'title' => t('Enter the terms you wish to search for.'),
),
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Search'),
);
$form['#base'] = 'search_box_form';
return $form;
}
function search_box_form_submit($form_id, $form_values) {
if (isset($_REQUEST['destination'])) {
unset($_REQUEST['destination']);
}
if (isset($_REQUEST['edit']['destination'])) {
unset($_REQUEST['edit']['destination']);
}
return 'search/node/' . trim($form_values[$form_id . '_keys']);
}
function theme_search_theme_form($form) {
return '<div id="search" class="container-inline">' . drupal_render($form) . '</div>';
}
function theme_search_block_form($form) {
return '<div class="container-inline">' . drupal_render($form) . '</div>';
}
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_page', $results, $type);
}
}
}
}
}
function search_excerpt($keys, $text) {
$boundary = '(?:(?<=[' . PREG_CLASS_SEARCH_EXCLUDE . PREG_CLASS_CJK . '])|(?=[' . PREG_CLASS_SEARCH_EXCLUDE . PREG_CLASS_CJK . ']))';
preg_match_all('/ ("([^"]+)"|(?!OR)([^" ]+))/', ' ' . $keys, $matches);
$keys = array_merge($matches[2], $matches[3]);
$text = ' ' . strip_tags(str_replace(array(
'<',
'>',
), array(
' <',
'> ',
), $text)) . ' ';
array_walk($keys, '_search_excerpt_replace');
$workkeys = $keys;
$ranges = array();
$included = array();
$length = 0;
while ($length < 256 && count($workkeys)) {
foreach ($workkeys as $k => $key) {
if (strlen($key) == 0) {
unset($workkeys[$k]);
unset($keys[$k]);
continue;
}
if ($length >= 256) {
break;
}
if (!isset($included[$key])) {
$included[$key] = 0;
}
if (preg_match('/' . $boundary . $key . $boundary . '/iu', $text, $match, PREG_OFFSET_CAPTURE, $included[$key])) {
$p = $match[0][1];
if (($q = strpos($text, ' ', max(0, $p - 60))) !== FALSE) {
$end = substr($text, $p, 80);
if (($s = strrpos($end, ' ')) !== FALSE) {
$ranges[$q] = $p + $s;
$length += $p + $s - $q;
$included[$key] = $p + 1;
}
else {
unset($workkeys[$k]);
}
}
else {
unset($workkeys[$k]);
}
}
else {
unset($workkeys[$k]);
}
}
}
if (count($ranges) == 0) {
return truncate_utf8($text, 256) . ' ...';
}
ksort($ranges);
$newranges = array();
foreach ($ranges as $from2 => $to2) {
if (!isset($from1)) {
$from1 = $from2;
$to1 = $to2;
continue;
}
if ($from2 <= $to1) {
$to1 = max($to1, $to2);
}
else {
$newranges[$from1] = $to1;
$from1 = $from2;
$to1 = $to2;
}
}
$newranges[$from1] = $to1;
$out = array();
foreach ($newranges as $from => $to) {
$out[] = substr($text, $from, $to - $from);
}
$text = (isset($newranges[0]) ? '' : '... ') . implode(' ... ', $out) . ' ...';
$text = preg_replace('/' . $boundary . '(' . implode('|', $keys) . ')' . $boundary . '/iu', '<strong>\\0</strong>', $text);
return $text;
}
function _search_excerpt_replace(&$text) {
$text = preg_quote($text, '/');
}
function theme_search_item($item, $type) {
$output = ' <dt class="title"><a href="' . check_url($item['link']) . '">' . check_plain($item['title']) . '</a></dt>';
$info = array();
if ($item['type']) {
$info[] = check_plain($item['type']);
}
if ($item['user']) {
$info[] = $item['user'];
}
if ($item['date']) {
$info[] = format_date($item['date'], 'small');
}
if (is_array($item['extra'])) {
$info = array_merge($info, $item['extra']);
}
$output .= ' <dd>' . ($item['snippet'] ? '<p>' . $item['snippet'] . '</p>' : '') . '<p class="search-info">' . implode(' - ', $info) . '</p></dd>';
return $output;
}
function theme_search_page($results, $type) {
$output = '<dl class="search-results">';
foreach ($results as $entry) {
$output .= theme('search_item', $entry, $type);
}
$output .= '</dl>';
$output .= theme('pager', NULL, 10, 0);
return $output;
}
function search_forms() {
$forms['search_theme_form'] = array(
'callback' => 'search_box',
'callback arguments' => array(
'search_theme_form',
),
);
$forms['search_block_form'] = array(
'callback' => 'search_box',
'callback arguments' => array(
'search_block_form',
),
);
return $forms;
}