Index: includes/install.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/install.inc,v
retrieving revision 1.72
diff -u -p -r1.72 install.inc
--- includes/install.inc	1 Oct 2008 00:27:29 -0000	1.72
+++ includes/install.inc	10 Oct 2008 15:42:51 -0000
@@ -506,7 +506,10 @@ function drupal_install_modules($module_
   } while ($moved);
   asort($module_list);
   $module_list = array_keys($module_list);
-  array_filter($module_list, '_drupal_install_module');
+  $modules_installed = array_filter($module_list, '_drupal_install_module');
+  if (!empty($modules_installed)) {
+    module_invoke_all('modules_installed', $modules_installed);
+  }
   module_enable($module_list);
 }
 
@@ -574,46 +577,52 @@ function drupal_install_system() {
   module_rebuild_cache();
 }
 
-
 /**
  * Calls the uninstall function and updates the system table for a given module.
  *
- * @param $module
- *   The machine name of the module to uninstall.
+ * @param $module_list
+ *   The modules to uninstall.
  */
-function drupal_uninstall_module($module) {
-  // First, retrieve all the module's menu paths from db.
-  drupal_load('module', $module);
-  $paths = module_invoke($module, 'menu');
-
-  // Uninstall the module(s).
-  module_load_install($module);
-  module_invoke($module, 'uninstall');
-
-  // Now remove the menu links for all paths declared by this module.
-  if (!empty($paths)) {
-    $paths = array_keys($paths);
-    // Clean out the names of load functions.
-    foreach ($paths as $index => $path) {
-      $parts = explode('/', $path, MENU_MAX_PARTS);
-      foreach ($parts as $k => $part) {
-        if (preg_match('/^%[a-z_]*$/', $part)) {
-          $parts[$k] = '%';
+function drupal_uninstall_modules($module_list = array()) {
+  foreach ($module_list as $module) {
+    // First, retrieve all the module's menu paths from db.
+    drupal_load('module', $module);
+    $paths = module_invoke($module, 'menu');
+
+    // Uninstall the module.
+    module_load_install($module);
+    module_invoke($module, 'uninstall');
+
+    // Now remove the menu links for all paths declared by this module.
+    if (!empty($paths)) {
+      $paths = array_keys($paths);
+      // Clean out the names of load functions.
+      foreach ($paths as $index => $path) {
+        $parts = explode('/', $path, MENU_MAX_PARTS);
+        foreach ($parts as $k => $part) {
+          if (preg_match('/^%[a-z_]*$/', $part)) {
+            $parts[$k] = '%';
+          }
         }
+        $paths[$index] = implode('/', $parts);
       }
-      $paths[$index] = implode('/', $parts);
-    }
-    $placeholders = implode(', ', array_fill(0, count($paths), "'%s'"));
+      $placeholders = implode(', ', array_fill(0, count($paths), "'%s'"));
 
-    $result = db_query('SELECT * FROM {menu_links} WHERE router_path IN (' . $placeholders . ') AND external = 0 ORDER BY depth DESC', $paths);
-    // Remove all such items. Starting from those with the greatest depth will
-    // minimize the amount of re-parenting done by menu_link_delete().
-    while ($item = db_fetch_array($result)) {
-      _menu_delete_item($item, TRUE);
+      $result = db_query('SELECT * FROM {menu_links} WHERE router_path IN (' . $placeholders . ') AND external = 0 ORDER BY depth DESC', $paths);
+      // Remove all such items. Starting from those with the greatest depth will
+      // minimize the amount of re-parenting done by menu_link_delete().
+      while ($item = db_fetch_array($result)) {
+        _menu_delete_item($item, TRUE);
+      }
     }
+
+    drupal_set_installed_schema_version($module, SCHEMA_UNINSTALLED);
   }
 
-  drupal_set_installed_schema_version($module, SCHEMA_UNINSTALLED);
+  if (!empty($module_list)) {
+    // Call hook_module_uninstall to let other modules act
+    module_invoke_all('modules_uninstalled', $module_list);
+  }
 }
 
 /**
Index: includes/module.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/module.inc,v
retrieving revision 1.127
diff -u -p -r1.127 module.inc
--- includes/module.inc	27 Sep 2008 19:03:30 -0000	1.127
+++ includes/module.inc	10 Oct 2008 15:42:51 -0000
@@ -297,6 +297,12 @@ function module_enable($module_list) {
       node_access_needs_rebuild(TRUE);
     }
   }
+
+  if (!empty($invoke_modules)) {
+    // Invoke the hook_module_enable after all the modules have been
+    // enabled.
+    module_invoke_all('modules_enabled', $invoke_modules);
+  }
 }
 
 /**
@@ -322,6 +328,9 @@ function module_disable($module_list) {
   }
 
   if (!empty($invoke_modules)) {
+    // Invoke hook_module_disable before disabling modules,
+    // so we can still call module hooks to get information.
+    module_invoke_all('modules_disabled', $invoke_modules);
     // Refresh the module list to exclude the disabled modules.
     module_list(TRUE, FALSE);
     // Force to regenerate the stored list of hook implementations.
Index: modules/simpletest/tests/system_test.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/system_test.module,v
retrieving revision 1.2
diff -u -p -r1.2 system_test.module
--- modules/simpletest/tests/system_test.module	8 Sep 2008 20:49:47 -0000	1.2
+++ modules/simpletest/tests/system_test.module	10 Oct 2008 15:42:54 -0000
@@ -84,3 +84,39 @@ function system_test_redirect_invalid_sc
 function system_test_destination() {
   return 'The destination: ' . drupal_get_destination();
 }
+
+/**
+ * Implementation of hook_modules_installed().
+ */
+function system_test_modules_installed($modules) {
+  if (in_array('aggregator', $modules)) {
+    drupal_set_message(t('hook_modules_installed fired for aggregator'));
+  }
+}
+
+/**
+ * Implementation of hook_modules_enabled().
+ */
+function system_test_modules_enabled($modules) {
+  if (in_array('aggregator', $modules)) {
+    drupal_set_message(t('hook_modules_enabled fired for aggregator'));
+  }
+}
+
+/**
+ * Implementation of hook_modules_disabled().
+ */
+function system_test_modules_disabled($modules) {
+  if (in_array('aggregator', $modules)) {
+    drupal_set_message(t('hook_modules_disabled fired for aggregator'));
+  }
+}
+
+/**
+ * Implementation of hook_modules_uninstalled().
+ */
+function system_test_modules_uninstalled($modules) {
+  if (in_array('aggregator', $modules)) {
+    drupal_set_message(t('hook_modules_uninstalled fired for aggregator'));
+  }
+}
Index: modules/system/system.admin.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.admin.inc,v
retrieving revision 1.95
diff -u -p -r1.95 system.admin.inc
--- modules/system/system.admin.inc	9 Oct 2008 00:02:29 -0000	1.95
+++ modules/system/system.admin.inc	10 Oct 2008 15:42:55 -0000
@@ -1093,9 +1093,8 @@ function system_modules_uninstall_submit
 
   if (!empty($form['#confirmed'])) {
     // Call the uninstall routine for each selected module.
-    foreach (array_filter($form_state['values']['uninstall']) as $module => $value) {
-      drupal_uninstall_module($module);
-    }
+    $modules = array_keys($form_state['values']['uninstall']);
+    drupal_uninstall_modules($modules);
     drupal_set_message(t('The selected modules have been uninstalled.'));
 
     unset($form_state['storage']);
Index: modules/system/system.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.test,v
retrieving revision 1.12
diff -u -p -r1.12 system.test
--- modules/system/system.test	15 Sep 2008 20:48:09 -0000	1.12
+++ modules/system/system.test	10 Oct 2008 15:43:00 -0000
@@ -19,7 +19,7 @@ class EnableDisableCoreTestCase extends 
    * Implementation of setUp().
    */
   function setUp() {
-    parent::setUp();
+    parent::setUp('system_test');
 
     $this->admin_user = $this->drupalCreateUser(array('access administration pages', 'administer site configuration'));
     $this->drupalLogin($this->admin_user);
@@ -28,17 +28,22 @@ class EnableDisableCoreTestCase extends 
   /**
    * Enable a module, check the database for related tables, disable module,
    * check for related tables, unistall module, check for related tables.
+   * Also check for invocatoin of the hook_module_action hook.
    */
   function testEnableDisable() {
     // Enable aggregator, and check tables.
     $this->assertModules(array('aggregator'), FALSE);
     $this->assertTableCount('aggregator', FALSE);
 
+    // Install (and enable) aggregator module.
     $edit = array();
     $edit['modules[Core - optional][aggregator][enable]'] = 'aggregator';
     $this->drupalPost('admin/build/modules', $edit, t('Save configuration'));
     $this->assertText(t('The configuration options have been saved.'), t('Modules status has been updated.'));
 
+    // Check that hook_modules_installed and hook_modules_enabled hooks were invoked and check tables.
+    $this->assertText(t('hook_modules_installed fired for aggregator'), t('hook_modules_installed fired.'));
+    $this->assertText(t('hook_modules_enabled fired for aggregator'), t('hook_modules_enabled fired.'));
     $this->assertModules(array('aggregator'), TRUE);
     $this->assertTableCount('aggregator', TRUE);
 
@@ -48,9 +53,12 @@ class EnableDisableCoreTestCase extends 
     $this->drupalPost('admin/build/modules', $edit, t('Save configuration'));
     $this->assertText(t('The configuration options have been saved.'), t('Modules status has been updated.'));
 
+    // Check that hook_modules_disabled hook was invoked and check tables.
+    $this->assertText(t('hook_modules_disabled fired for aggregator'), t('hook_modules_disabled fired.'));
     $this->assertModules(array('aggregator'), FALSE);
     $this->assertTableCount('aggregator', TRUE);
 
+    // Uninstall the module.
     $edit = array();
     $edit['uninstall[aggregator]'] = 'aggregator';
     $this->drupalPost('admin/build/modules/uninstall', $edit, t('Uninstall'));
@@ -58,6 +66,8 @@ class EnableDisableCoreTestCase extends 
     $this->drupalPost(NULL, NULL, t('Uninstall'));
     $this->assertText(t('The selected modules have been uninstalled.'), t('Modules status has been updated.'));
 
+    // Check that hook_modules_uninstalled hook was invoked and check tables.
+    $this->assertText(t('hook_modules_uninstalled fired for aggregator'), t('hook_modules_uninstalled fired.'));
     $this->assertModules(array('aggregator'), FALSE);
     $this->assertTableCount('aggregator', FALSE);
   }
