# HG changeset patch # User Dan # Date 1227936057 18000 # Node ID e8cf183834253962824b8c499c484accb868b4b3 # Parent 9b4cd3ef42f3b13a329ccf39dde6d038c39ab0b6 Added a new search API that allows much easier registration of search results. Basically you give the engine a table, a few columns to look at, and tell it how to format the results and you're done. diff -r 9b4cd3ef42f3 -r e8cf18383425 includes/functions.php --- a/includes/functions.php Sat Nov 29 00:19:39 2008 -0500 +++ b/includes/functions.php Sat Nov 29 00:20:57 2008 -0500 @@ -3045,6 +3045,77 @@ } /** + * Registers a new type of search result. Because this is so tricky to do but keep clean, this function takes an associative array as its + * only parameter. This array configures the function. The required keys are: + * - table: the database table to search + * - titlecolumn: the column that will be used as the title of the search result. This will have a weight of 1.5 + * - uniqueid: a TPL-format string, variables being column names, that will be unique for every result. This should contain a string that + * will be specific to your *type* of search result in addition to a primary key or other unique identifier. + * - linkformat: an array with the keys page_id and namespace which are where your result will link, plus the following additional options: + * - append: added to the full generated URL + * - query: query string without initial "?" + * Additional options: + * - datacolumn + * - additionalcolumns: additional data to select if you want to use a custom formatting callback + * - formatcallback: a callback or TPL string. If a callback, it will be called with the parameters being the current result row and an + * array of words in case you want to highlight anything; the callback will be expected to return a string containing + * a fully formatted and sanitized blob of HTML. If formatcallback is a TPL string, variables will be named after table + * columns. + * - additionalwhere: additional SQL to inject into WHERE clause, in the format of "AND foo = bar" + * @example Working example of adding users to search results: + + register_search_handler(array( + 'table' => 'users', + 'titlecolumn' => 'username', + 'uniqueid' => 'ns=User;cid={username}', + 'additionalcolumns' => array('user_id'), + 'resultnote' => '[Member]', + 'linkformat' => array( + 'page_id' => '{username}', + 'namespace' => 'User' + ), + 'formatcallback' => 'format_user_search_result', + )); + + function format_user_search_result($row) + { + global $session, $lang; + $rankdata = $session->get_user_rank(intval($row['user_id'])); + $rankspan = '' . $lang->get($rankdata['rank_title']) . ''; + if ( empty($rankdata['user_title']) ) + { + return $rankspan; + } + else + { + return '"' . htmlspecialchars($rankdata['user_title']) . "\" ($rankspan)"; + } + } + + * @param array Options array - see function documentation + * @return null + */ + +global $search_handlers; +$search_handlers = array(); + +function register_search_handler($options) +{ + global $search_handlers; + + $required = array('table', 'titlecolumn', 'uniqueid', 'linkformat'); + foreach ( $required as $key ) + { + if ( !isset($options[$key]) ) + { + throw new Exception("Required search handler option '$key' is missing"); + } + } + $search_handlers[] = $options; + return null; +} + +/** * From http://us2.php.net/urldecode - decode %uXXXX * @param string The urlencoded string * @return string diff -r 9b4cd3ef42f3 -r e8cf18383425 includes/search.php --- a/includes/search.php Sat Nov 29 00:19:39 2008 -0500 +++ b/includes/search.php Sat Nov 29 00:20:57 2008 -0500 @@ -105,6 +105,12 @@ global $lang; $warnings = array(); + + // + // STAGE 0: PARSE SEARCH QUERY + // Identify all terms of the query. Separate between what is required and what is not, and what should be sent through the index as + // opposed to straight-out LIKE-selected. + // $query = parse_search_query($query, $warnings); @@ -538,6 +544,8 @@ // pages and add text, etc. as necessary. // Plugins are COMPLETELY responsible for using the search terms and handling Boolean logic properly + inject_custom_search_results($query, $query_phrase, $scores, $page_data, $case_sensitive, $word_list); + $code = $plugins->setHook('search_global_inner'); foreach ( $code as $cmd ) { @@ -922,4 +930,186 @@ return $stopwords; } +/** + * Private function to inject custom results into a search. + */ + +function inject_custom_search_results(&$query, &$query_phrase, &$scores, &$page_data, &$case_sensitive, &$word_list) +{ + global $db, $session, $paths, $template, $plugins; // Common objects + global $lang; + + global $search_handlers; + + // global functions + $terms = array( + 'any' => array_merge($query['any'], $query_phrase['any']), + 'req' => array_merge($query['req'], $query_phrase['req']), + 'not' => $query['not'] + ); + + foreach ( $search_handlers as &$options ) + { + $where = array('any' => array(), 'req' => array(), 'not' => array()); + $where_any =& $where['any']; + $where_req =& $where['req']; + $where_not =& $where['not']; + $title_col = ( $case_sensitive ) ? $options['titlecolumn'] : 'lcase(' . $options['titlecolumn'] . ')'; + if ( isset($options['datacolumn']) ) + $desc_col = ( $case_sensitive ) ? $options['datacolumn'] : 'lcase(' . $options['datacolumn'] . ')'; + else + $desc_col = "''"; + foreach ( $terms['any'] as $term ) + { + $term = escape_string_like($term); + if ( !$case_sensitive ) + $term = strtolower($term); + $where_any[] = "( $title_col LIKE '%{$term}%' OR $desc_col LIKE '%{$term}%' )"; + } + foreach ( $terms['req'] as $term ) + { + $term = escape_string_like($term); + if ( !$case_sensitive ) + $term = strtolower($term); + $where_req[] = "( $title_col LIKE '%{$term}%' OR $desc_col LIKE '%{$term}%' )"; + } + foreach ( $terms['not'] as $term ) + { + $term = escape_string_like($term); + if ( !$case_sensitive ) + $term = strtolower($term); + $where_not[] = "$title_col NOT LIKE '%{$term}%' AND $desc_col NOT LIKE '%{$term}%'"; + } + if ( empty($where_any) ) + unset($where_any, $where['any']); + if ( empty($where_req) ) + unset($where_req, $where['req']); + if ( empty($where_not) ) + unset($where_not, $where['not']); + + $where_any = '(' . implode(' OR ', $where_any) . '' . ( isset($where['req']) || isset($where['not']) ? ' OR 1 = 1' : '' ) . ')'; + + if ( isset($where_req) ) + $where_req = implode(' AND ', $where_req); + if ( isset($where_not) ) + $where_not = implode( 'AND ', $where_not); + + $where = implode(' AND ', $where); + + $columns = $options['titlecolumn']; + if ( isset($options['datacolumn']) ) + $columns .= ", {$options['datacolumn']}"; + if ( isset($options['additionalcolumns']) ) + $columns .= ', ' . implode(', ', $options['additionalcolumns']); + + $sql = "SELECT $columns FROM " . table_prefix . "{$options['table']} WHERE ( $where ) $additionalwhere;"; + + if ( !($q = $db->sql_unbuffered_query($sql)) ) + { + $db->_die('Automatically generated search query'); + } + + if ( $row = $db->fetchrow() ) + { + do + { + $parser = $template->makeParserText($options['uniqueid']); + $parser->assign_vars($row); + $idstring = $parser->run(); + + // Score this result + foreach ( $word_list as $term ) + { + if ( $case_sensitive ) + { + if ( strstr($row[$options['titlecolumn']], $term) ) + { + ( isset($scores[$idstring]) ) ? $scores[$idstring] += 1.5 : $scores[$idstring] = 1.5; + } + else if ( isset($options['datacolumn']) && strstr($row[$options['datacolumn']], $term) ) + { + ( isset($scores[$idstring]) ) ? $scores[$idstring]++ : $scores[$idstring] = 1; + } + } + else + { + if ( stristr($row[$options['titlecolumn']], $term) ) + { + ( isset($scores[$idstring]) ) ? $scores[$idstring] += 1.5 : $scores[$idstring] = 1.5; + } + else if ( isset($options['datacolumn']) && stristr($row[$options['datacolumn']], $term) ) + { + ( isset($scores[$idstring]) ) ? $scores[$idstring]++ : $scores[$idstring] = 1; + } + } + } + // Generate text... + $text = ''; + if ( isset($options['datacolumn']) && !isset($options['formatcallback']) ) + { + $text = highlight_and_clip_search_result(htmlspecialchars($row[$options['datacolumn']]), $word_list); + } + else if ( isset($options['formatcallback']) ) + { + if ( is_callable($options['formatcallback']) ) + { + $text = @call_user_func($options['formatcallback'], $row, $word_list); + } + else + { + $parser = $template->makeParserText($options['formatcallback']); + $parser->assign_vars($row); + $text = $parser->run(); + } + } + + // Inject result + + if ( isset($scores[$idstring]) ) + { + $parser = $template->makeParserText($options['linkformat']['page_id']); + $parser->assign_vars($row); + $page_id = $parser->run(); + + $parser = $template->makeParserText($options['linkformat']['namespace']); + $parser->assign_vars($row); + $namespace = $parser->run(); + + $page_data[$idstring] = array( + 'page_name' => highlight_search_result(htmlspecialchars($row[$options['titlecolumn']]), $word_list), + 'page_text' => $text, + 'score' => $scores[$idstring], + 'page_id' => $page_id, + 'namespace' => $namespace, + ); + + // Any additional flags that need to be added to the result? + // The small usually-bracketed text to the left of the title + if ( isset($options['resultnote']) ) + { + $page_data[$idstring]['page_note'] = $options['resultnote']; + } + // Should we include the length? + if ( isset($options['datacolumn']) ) + { + $page_data[$idstring]['page_length'] = strlen($row[$options['datacolumn']]); + } + else + { + $page_data[$idstring]['page_length'] = 0; + $page_data[$idstring]['zero_length'] = true; + } + // Anything to append to result links? + if ( isset($options['linkformat']['append']) ) + { + $page_data[$idstring]['url_append'] = $options['linkformat']['append']; + } + } + } + while ( $row = $db->fetchrow($q) ); + $db->free_result($q); + } + } +} + ?> diff -r 9b4cd3ef42f3 -r e8cf18383425 plugins/SpecialSearch.php --- a/plugins/SpecialSearch.php Sat Nov 29 00:19:39 2008 -0500 +++ b/plugins/SpecialSearch.php Sat Nov 29 00:20:57 2008 -0500 @@ -175,12 +175,12 @@ 'PAGE_TEXT' => $result['page_text'], 'PAGE_LENGTH' => $result['page_length'], 'RELEVANCE_SCORE' => $result['score'], - 'RESULT_URL' => makeUrlNS($result['namespace'], $result['page_id'], false, true), + 'RESULT_URL' => makeUrlNS($result['namespace'], $result['page_id'], false, true) . ( isset($result['url_append']) ? $result['url_append'] : '' ), 'PAGE_LENGTH_UNIT' => $length_unit, 'PAGE_URL' => $url, 'PAGE_NOTE' => ( isset($result['page_note']) ? $result['page_note'] . ' ' : '' ) )); - $has_content = ( $result['namespace'] == 'Special' ); + $has_content = ( $result['namespace'] == 'Special' || !empty($result['zero_length']) ); $code = $plugins->setHook('search_global_results'); foreach ( $code as $cmd )