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	10 May 2008 19:18:47 -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]);
 }
 
 /**
@@ -1137,8 +1227,11 @@
 /**
  * Perform a standard search on the given keys, and return the formatted results.
  */
-function search_data($keys = NULL, $type = 'node') {
-
+function search_data($keys = NULL, $type = NULL) {
+  if($type == NULL) {
+    $type = search_get_default_module();
+  }
+  
   if (isset($keys)) {
     if (module_hook($type, 'search')) {
       $results = module_invoke($type, 'search', 'search', $keys);
@@ -1288,3 +1381,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	10 May 2008 19:45:17 -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,7 +18,9 @@
       // 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();
@@ -29,8 +31,16 @@
       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) {
+          error_log("Invoking search for $name");
+          $results .= search_data($keys, $name);
+        }
+      }
+      // TODO: We now need to normalize the search results based on scores.
+      $results .= search_data($keys, $type);
+      
       if ($results) {
         $results = theme('box', t('Search results'), $results);
       }
@@ -123,8 +133,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

