Index: node/node.module =================================================================== RCS file: /cvs/drupal/drupal/modules/node/node.module,v retrieving revision 1.963 diff -u -r1.963 node.module --- node/node.module 10 May 2008 13:19:50 -0000 1.963 +++ node/node.module 10 May 2008 16:58:52 -0000 @@ -1824,7 +1824,8 @@ */ function node_form_alter(&$form, $form_state, $form_id) { // Advanced node search form - if ($form_id == 'search_form' && $form['module']['#value'] == 'node' && user_access('use advanced search')) { + // TODO: The path check should be a function, and not done in the form alter. + if ( $form_id == 'search_form' && ( $form['module']['#value'] == 'node' || ( isset($form['search_mergers']) && in_array('node', array_keys($form['search_mergers'])))) && user_access('use advanced search') ) { // Keyword boxes: $form['advanced'] = array( '#type' => 'fieldset', Index: search/search.module =================================================================== RCS file: /cvs/drupal/drupal/modules/search/search.module,v retrieving revision 1.256 diff -u -r1.256 search.module --- search/search.module 6 May 2008 12:18:50 -0000 1.256 +++ search/search.module 11 May 2008 16:28:08 -0000 @@ -169,6 +169,7 @@ * Implementation of hook_menu(). */ function search_menu() { + $items['search'] = array( 'title' => 'Search', 'page callback' => 'search_view', @@ -183,6 +184,14 @@ 'access arguments' => array('administer search'), 'type' => MENU_NORMAL_ITEM, ); + $items['admin/settings/search/paths'] = array( + 'title' => 'Search path settings', + 'description' => 'Configure your search path settings', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('search_path_settings_form'), + 'access arguments' => array('administer search'), + 'type' => MENU_NORMAL_ITEM, + ); $items['admin/settings/search/wipe'] = array( 'title' => 'Clear index', 'page callback' => 'drupal_get_form', @@ -198,8 +207,18 @@ 'access arguments' => array('access site reports'), 'file path' => drupal_get_path('module', 'dblog'), ); - + + $module_path_settings = variable_get('search_path_settings', array()); foreach (module_implements('search') as $name) { + /** + * We do not show the path if the module has been merged under + * another search modules tab. + * Note: If there is only 1 local task, then it will not show + * as a local task. + */ + if($module_path_settings[$name]['merge_module'] != false) { + continue; + } $items['search/' . $name . '/%menu_tail'] = array( 'title callback' => 'module_invoke', 'title arguments' => array($name, 'search', 'name', TRUE), @@ -211,9 +230,72 @@ 'parent' => 'search', ); } + return $items; } +/** + * This form has the setup options to merge a search option + * under another tab. + * The inforamtion we nee + */ +function search_path_settings_form() { + $form = array(); + $modules = module_implements('search'); + $avail_search_tabs = array('0' => '-'); + foreach($modules as $module) { + $avail_search_tabs[$module] = $module; + } + // TODO: Logic to pick the default + $form['search_default'] = array( + '#type' => 'select', + '#title' => 'Default search', + '#options' => $avail_search_tabs, + '#default_value' => variable_get('search_default', search_get_default_module()), + ); + // Show the modules option settings + $form['search_opts']['#tree'] = true; + $current_settings = variable_get('search_path_settings', array()); + foreach($modules as $k => $module) { + $form['search_opts'][$module] = array( + '#type' => 'fieldset', + '#title' => "Options for the $module search implementation.", + '#collapsible' => true, + '#collapsed' => true, + ); + $form['search_opts'][$module]['merge_module'] = array( + '#type' => 'select', + '#title' => 'Module to merge under', + '#options' => array_diff($avail_search_tabs, array($module)), + '#default_value' => isset($current_settings[$module]['merge_module']) ? $current_settings[$module]['merge_module'] : '0', + ); + } + $form['save'] = array( + '#type' => 'submit', + '#value' => t('Save Settings'), + ); + return $form; +} + +function search_path_settings_form_validate($form, &$form_state) { + if($form_state['values']['search_default'] == '0') { + form_set_error('search_default', t('You must select a default search.')); + } + else if($form_state['values']['search_opts'][$form_state['values']['search_default']]['merge_module'] !== "0") { + form_set_error('search_default', t('You can not set the default module to be + that of a search that has been merged + under another search.')); + } + return; +} + +function search_path_settings_form_submit($form, &$form_state) { + variable_set('search_path_settings', $form_state['values']['search_opts']); + variable_set('search_default', $form_state['values']['search_default']); + drupal_set_message(t('Your settings have been saved.')); +} + + function _search_menu($name) { return user_access('search content') && module_invoke($name, 'search', 'name'); } @@ -1046,7 +1128,15 @@ // that hook into the basic search form. $form['basic']['inline']['processed_keys'] = array('#type' => 'value', '#value' => array()); $form['basic']['inline']['submit'] = array('#type' => 'submit', '#value' => t('Search')); - + + // Add a hidden value about the merged paths + $path_settings = variable_get('search_path_settings', array()); + $form['search_mergers'] = array(); + foreach($path_settings as $name => $settings) { + if($settings['merge_module'] == $type) { + $form['search_mergers'][$name] = array('#type' => 'hidden', '#value' => $name); + } + } return $form; } @@ -1077,7 +1167,7 @@ */ function search_box_form_submit($form, &$form_state) { $form_id = $form['form_id']['#value']; - $form_state['redirect'] = 'search/node/' . trim($form_state['values'][$form_id]); + $form_state['redirect'] = 'search/' . search_get_default_module() . '/' . trim($form_state['values'][$form_id]); } /** @@ -1136,18 +1226,31 @@ /** * Perform a standard search on the given keys, and return the formatted results. - */ -function search_data($keys = NULL, $type = 'node') { - + * @param $keys Search keywords + * @param $type The type of search to perform + * @param $themed Weather or not to retun the results themed already. If this is + * set to false, the function will return the raw results array. + * @return mixed The search results. + */ +function search_data($keys = NULL, $type = NULL, $themed = true) { + if($type == NULL) { + $type = search_get_default_module(); + } + 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); + if($themed) { + if (module_hook($type, 'search_page')) { + return module_invoke($type, 'search_page', $results); + } + else { + return theme('search_results', $results, $type); + } } else { - return theme('search_results', $results, $type); + return $results; } } } @@ -1288,3 +1391,16 @@ ); return $forms; } + +function search_get_default_module() { + static $default_search_module; + if($default_search_module != '') { + return $default_search_module; + } + $default_search_module = variable_get('search_default', ''); + if($default_search_module == '') { + // Return the first search we find that's implemented. + $default_search_module = array_pop(array_reverse(module_implements('search'))); + } + return $default_search_module; +} \ No newline at end of file Index: search/search.pages.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/search/search.pages.inc,v retrieving revision 1.5 diff -u -r1.5 search.pages.inc --- search/search.pages.inc 14 Apr 2008 17:48:41 -0000 1.5 +++ search/search.pages.inc 11 May 2008 15:42:35 -0000 @@ -9,7 +9,7 @@ /** * Menu callback; presents the search form and/or search results. */ -function search_view($type = 'node') { +function search_view($type = '') { // Search form submits with POST but redirects to GET. This way we can keep // the search query URL clean as a whistle: // search/type/keyword+keyword @@ -18,19 +18,47 @@ // Note: search/node can not be a default tab because it would take on the // path of its parent (search). It would prevent remembering keywords when // switching tabs. This is why we drupal_goto to it from the parent instead. - drupal_goto('search/node'); + // TODO: Instead of node being the default in this mannger, we should just + // require some type of search to be the default. + drupal_goto('search/' . search_get_default_module()); } $keys = search_get_keys(); // Only perform search if there is non-whitespace search term: $results = ''; + $merged_results = array(); if (trim($keys)) { // Log the search keys: watchdog('search', '%keys (@type).', array('%keys' => $keys, '@type' => module_invoke($type, 'search', 'name')), WATCHDOG_NOTICE, l(t('results'), 'search/' . $type . '/' . $keys)); // Collect the search results: - $results = search_data($keys, $type); - + $path_settings = variable_get('search_path_settings', array()); + foreach($path_settings as $name => $settings) { + if($settings['merge_module'] == $type && $name!= $type) { + $temp_results = search_data($keys, $name, false); + foreach($temp_results as $k => $r) { + $merged_results[] = $r; + } + } + } + // TODO: We now need to normalize the search results based on scores. + $temp_results = search_data($keys, $type, false); + foreach($temp_results as $k => $r) { + $merged_results[] = $r; + } + // TODO: In this fashion, we're looping thorugh the same data a few times, + // which is in-efficient. Doing this in 1 query is a more efficient method. + // Re-normalize the scores + usort($merged_results, "_search_merge_tab_sort"); + foreach($merged_results as $k => $result) { + if (isset($result['type']) && module_hook($result['type'], 'search_page')) { + $results .= module_invoke($type, 'search_page', array($result)); + } + else { + $results .= theme('search_results', array($result), $type); + } + } + if ($results) { $results = theme('box', t('Search results'), $results); } @@ -49,6 +77,20 @@ return drupal_get_form('search_form', NULL, empty($keys) ? '' : $keys, $type); } + +function _search_merge_tab_sort($a, $b) { + if(isset($a['score']) && isset($b['score'])) { + return $a['score'] > $b['score']; + } + elseif(isset($a['score'])) { + return true; + } + else { + return false; + } +} + + /** * Process variables for search-results.tpl.php. * @@ -123,8 +165,9 @@ form_set_error('keys', t('Please enter some keywords.')); // Fall through to the drupal_goto() call. } - - $type = $form_state['values']['module'] ? $form_state['values']['module'] : 'node'; + // TODO: If a module is not set, we need to use the default module finder. + // For now i'm defaulting to nothing. + $type = $form_state['values']['module'] ? $form_state['values']['module'] : variable_get('search_default', ''); $form_state['redirect'] = 'search/' . $type . '/' . $keys; return; } Index: search/search.test =================================================================== RCS file: /cvs/drupal/drupal/modules/search/search.test,v retrieving revision 1.1 diff -u -r1.1 search.test --- search/search.test 20 Apr 2008 18:23:29 -0000 1.1 +++ search/search.test 10 May 2008 20:33:44 -0000 @@ -159,4 +159,70 @@ // Check range. $this->assertEqual(!count($scores) || (min($scores) > 0.0 && max($scores) <= 1.0001), TRUE, "Query scoring '$query'"); } + } + +class SearchUnifiedTabsTestCase extends DrupalWebTestCase { + /** + * Implementation of getInfo(). + */ + function getInfo() { + return array( + 'name' => t('Search Unified Tabs'), + 'description' => t('Tests that search results show up under one unifed tab.'), + 'group' => t('Search'), + ); + } + + /** + * Implementation setUp(). + */ + function setUp() { + parent::setUp('search'); + } + + /** + * This function tests that search results show up + * under a unifed tab. + */ + function testUnifiedTabs() { + // Enable Search + $this->drupalModuleEnable('search'); + // Create a user + $perms = array('access content', 'administer search', 'search content'); + $user = $this->drupalCreateUser($perms); + $this->drupalLogin($user); + // Create a node + $edit = array( + 'body' => 'This is a page for testing.', + 'title' => 'Page by ' . $user->name, + ); + $this->drupalCreateNode($edit); + // Merge the node search under the user search + variable_set('search_path_settings', + array( + 'node' => + array('merge_module' => 'user'), + 'user' => + array('merge_module' => 0) + ) + ); + variable_set('search_default', 'user'); + // Index; We don't wanna rely on cron to index for us. + search_cron(); + search_update_totals(); + // Search + $edit = array( + 'keys' => $user->name + ); + $this->drupalGet('search/user'); + error_log($this->drupalGetContent()); + $this->drupalPost(NULL, $edit, 'edit-submit'); + + // We should get BOTH search results back + $this->assertRaw($user->name . '@example.com', 'Looking for the user ' . $user->name); + // Search for only something on the page + // We should only get the 1 page item back + + } +} \ No newline at end of file Index: user/user.module =================================================================== RCS file: /cvs/drupal/drupal/modules/user/user.module,v retrieving revision 1.907 diff -u -r1.907 user.module --- user/user.module 7 May 2008 19:34:24 -0000 1.907 +++ user/user.module 11 May 2008 15:40:28 -0000 @@ -588,7 +588,7 @@ $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); + $result = pager_query("SELECT name, uid, mail, 1 as score 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))); }