Index: modules/field/field.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/field.module,v
retrieving revision 1.71
diff -u -p -r1.71 field.module
--- modules/field/field.module	27 Mar 2010 05:52:49 -0000	1.71
+++ modules/field/field.module	9 Apr 2010 22:09:09 -0000
@@ -241,7 +241,7 @@ function field_modules_disabled($modules
     ->condition('storage_module', $modules, 'IN')
     ->execute();
 
-  field_cache_clear(TRUE);
+  field_cache_clear();
 }
 
 /**
Index: modules/field/modules/text/text.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/modules/text/text.module,v
retrieving revision 1.52
diff -u -p -r1.52 text.module
--- modules/field/modules/text/text.module	27 Mar 2010 12:49:32 -0000	1.52
+++ modules/field/modules/text/text.module	9 Apr 2010 22:09:48 -0000
@@ -590,3 +590,9 @@ function text_field_widget_error($elemen
   form_error($error_element, $error['message']);
 }
 
+/**
+ * Implements hook_filter_format_update().
+ */
+function text_filter_format_update($format) {
+  field_cache_clear();
+}
Index: modules/filter/filter.admin.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/filter/filter.admin.inc,v
retrieving revision 1.58
diff -u -p -r1.58 filter.admin.inc
--- modules/filter/filter.admin.inc	6 Mar 2010 19:40:21 -0000	1.58
+++ modules/filter/filter.admin.inc	9 Apr 2010 22:09:10 -0000
@@ -17,8 +17,16 @@ function filter_admin_overview($form) {
   $formats = filter_formats();
   $fallback_format = filter_fallback_format();
 
+  // Output a warning if there are formats containing missing filters.
+  $broken_filters = _filter_get_broken_filters();
+  if ($broken_filters) {
+    drupal_set_message(format_plural(count($broken_filters), 'The highlighted text format needs to be re-configured, because it is using filters that no longer exist.', 'The highlighted text formats need to be re-configured, because they are using filters that no longer exist.'), 'error');
+  }
+
   $form['#tree'] = TRUE;
   foreach ($formats as $id => $format) {
+    // Indicate broken formats.
+    $form['formats'][$id]['#is_broken'] = isset($broken_filters[$id]);
     // Check whether this is the fallback text format. This format is available
     // to all roles and cannot be deleted via the admin interface.
     $form['formats'][$id]['#is_fallback'] = ($id == $fallback_format);
@@ -74,7 +82,7 @@ function theme_filter_admin_overview($va
         drupal_render($form['formats'][$id]['configure']),
         drupal_render($form['formats'][$id]['delete']),
       ),
-      'class' => array('draggable'),
+      'class' => array('draggable', ($form['formats'][$id]['#is_broken'] ? 'error' : '')),
     );
   }
   $header = array(t('Name'), t('Roles'), t('Weight'), array('data' => t('Operations'), 'colspan' => 2));
Index: modules/filter/filter.install
===================================================================
RCS file: /cvs/drupal/drupal/modules/filter/filter.install,v
retrieving revision 1.35
diff -u -p -r1.35 filter.install
--- modules/filter/filter.install	12 Mar 2010 14:31:01 -0000	1.35
+++ modules/filter/filter.install	9 Apr 2010 22:09:10 -0000
@@ -7,6 +7,27 @@
  */
 
 /**
+ * Implements hook_requirements().
+ */
+function filter_requirements($phase) {
+  $requirements = array();
+
+  if ($phase == 'runtime') {
+    // Check for text formats containing missing/vanished filters.
+    if (_filter_get_broken_filters()) {
+      $requirements['filter'] = array(
+        'title' => t('Text formats'),
+        'value' => t('Missing filters'),
+        'severity' => REQUIREMENT_ERROR,
+        'description' => t('At least one text format contains a filter that was provided by a module that has been disabled. All content using one of these text formats will be hidden until <a href="@text-format-url">text formats have been re-configured</a>.', array('@text-format-url' => url('admin/config/content/formats'))),
+      );
+    }
+  }
+
+  return $requirements;
+}
+
+/**
  * Implements hook_schema().
  */
 function filter_schema() {
Index: modules/filter/filter.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/filter/filter.module,v
retrieving revision 1.324
diff -u -p -r1.324 filter.module
--- modules/filter/filter.module	26 Mar 2010 17:14:45 -0000	1.324
+++ modules/filter/filter.module	9 Apr 2010 22:15:04 -0000
@@ -187,6 +187,10 @@ function filter_format_load($format_id) 
 function filter_format_save(&$format) {
   $format->name = trim($format->name);
   $format->cache = _filter_format_is_cacheable($format);
+  // Programmatic saves may not contain any filters.
+  if (!isset($format->filters)) {
+    $format->filters = array();
+  }
 
   // Add a new text format.
   if (empty($format->format)) {
@@ -194,13 +198,20 @@ function filter_format_save(&$format) {
   }
   else {
     $return = drupal_write_record('filter_format', $format, 'format');
+
+    // When a text format is updated, we need to assume that its filter
+    // configuration has been verified and we are safe to remove any pointers
+    // to vanished filters.
+    if ($broken_filters = _filter_get_broken_filters($format->format, NULL, FALSE)) {
+      db_delete('filter')
+        ->condition('format', $format->format)
+        ->condition('name', $broken_filters, 'IN')
+        ->condition('name', array_keys($format->filters), 'NOT IN')
+        ->execute();
+    }
   }
 
   $filter_info = filter_get_filters();
-  // Programmatic saves may not contain any filters.
-  if (!isset($format->filters)) {
-    $format->filters = array();
-  }
   foreach ($filter_info as $name => $filter) {
     // Add new filters without weight to the bottom.
     if (!isset($format->filters[$name]['weight'])) {
@@ -273,6 +284,7 @@ function filter_format_delete($format) {
   $fallback = filter_format_load(filter_fallback_format());
   module_invoke_all('filter_format_delete', $format, $fallback);
 
+  // Clear the filter cache whenever a text format is deleted.
   filter_formats_reset();
   cache_clear_all($format->format . ':', 'cache_filter', TRUE);
 }
@@ -346,11 +358,38 @@ function filter_modules_enabled($modules
 
 /**
  * Implements hook_modules_disabled().
+ *
+ * If a module is disabled that provides a filter, all text formats using the
+ * filter have to be manually updated. This cannot be automated, because that
+ * would always break the intended filter processing logic.
+ *
+ * @see filter_formats()
+ * @see check_markup()
  */
 function filter_modules_disabled($modules) {
   // Reset the static cache of module-provided filters, in case any of the
   // newly disabled modules defined or altered any filters.
   drupal_static_reset('filter_get_filters');
+
+  if (_filter_get_broken_filters(NULL, $modules)) {
+    drupal_set_message(t('At least one text format contains a filter that was provided by a module that has been disabled. All content using one of these text formats will be hidden until <a href="@text-format-url">text formats have been re-configured</a>.', array('@text-format-url' => url('admin/config/content/formats'))), 'error');
+  }
+}
+
+/**
+ * Implements hook_modules_uninstalled().
+ *
+ * If a module is uninstalled that provides a filter, all text formats using the
+ * filter have to be manually updated. This cannot be automated, because that
+ * would always break the intended filter processing logic.
+ *
+ * @see filter_formats()
+ * @see check_markup()
+ */
+function filter_modules_uninstalled($modules) {
+  if (_filter_get_broken_filters(NULL, $modules)) {
+    drupal_set_message(t('At least one text format contains a filter that was provided by a module that has been uninstalled. All content using one of these text formats will be hidden until <a href="@text-format-url">text formats have been re-configured</a>.', array('@text-format-url' => url('admin/config/content/formats'))), 'error');
+  }
 }
 
 /**
@@ -636,9 +675,14 @@ function filter_list_format($format_id) 
         $filter->title = $filter_info[$name]['title'];
         // Unpack stored filter settings.
         $filter->settings = (isset($filter->settings) ? unserialize($filter->settings) : array());
-
-        $format_filters[$name] = $filter;
       }
+      else {
+        // The filter is assigned to the text format, but the module providing
+        // the filter no longer exists. We assign a negative status, so
+        // check_markup() can bail out.
+        $filter->status = -1;
+      }
+      $format_filters[$name] = $filter;
     }
     $filters[$format_id] = $format_filters;
   }
@@ -647,6 +691,58 @@ function filter_list_format($format_id) 
 }
 
 /**
+ * Helper function to return a list of vanished filters keyed by text format.
+ *
+ * @param $format_id
+ *   (optional) A text format id to limit the query to.
+ * @param $modules
+ *   (optional) A list of module names to limit the query to.
+ * @param $enabled_only
+ *   (optional) Boolean whether to search only for enabled filters. Defaults to
+ *   TRUE.
+ *
+ * @return
+ *   A list of assigned filters that no longer exist, keyed by text format. If
+ *   $format_id was passed, only filters for the given text format are
+ *   returned.
+ *
+ * @see filter_list_format()
+ */
+function _filter_get_broken_filters($format_id = NULL, $modules = NULL, $enabled_only = TRUE) {
+  // Retrieve all filters exposed by modules.
+  $filter_info = filter_get_filters();
+
+  // Query all filters not contained in the list of available filters.
+  $query = db_select('filter', 'f')
+    ->fields('f', array('format', 'name'))
+    ->condition('name', array_keys($filter_info), 'NOT IN');
+  // Optionally limit to enabled filters.
+  if ($enabled_only) {
+    $query->condition('status', 1);
+  }
+  // Optionally limit to text format.
+  if (isset($format_id)) {
+    $query->condition('format', $format_id);
+  }
+  // Optionally limit to a list of modules.
+  if (isset($modules)) {
+    $query->condition('module', $modules, 'IN');
+  }
+  $result = $query->execute()->fetchAll();
+
+  // Compile a list of vanished filters.
+  $broken_filters = array();
+  foreach ($result as $filter) {
+    $broken_filters[$filter->format][] = $filter->name;
+  }
+
+  if (isset($format_id)) {
+    return isset($broken_filters[$format_id]) ? $broken_filters[$format_id] : array();
+  }
+  return $broken_filters;
+}
+
+/**
  * Run all the enabled filters on a piece of text.
  *
  * Note: Because filters can inject JavaScript or execute PHP code, security is
@@ -673,7 +769,13 @@ function check_markup($text, $format_id 
   if (empty($format_id)) {
     $format_id = filter_fallback_format();
   }
-  $format = filter_format_load($format_id);
+  // If the requested text format does not exist, we return an empty text for
+  // security reasons. The text stays empty until the text format has been
+  // re-configured.
+  if (!$format = filter_format_load($format_id)) {
+    watchdog('filter', 'Missing text format: %format.', array('%format' => $format_id), WATCHDOG_ALERT);
+    return '';
+  }
 
   // Check for a cached version of this piece of text.
   $cache = $cache && !empty($format->cache);
@@ -695,9 +797,17 @@ function check_markup($text, $format_id 
 
   // Give filters the chance to escape HTML-like data such as code or formulas.
   foreach ($filters as $name => $filter) {
-    if ($filter->status && isset($filter_info[$name]['prepare callback']) && function_exists($filter_info[$name]['prepare callback'])) {
-      $function = $filter_info[$name]['prepare callback'];
-      $text = $function($text, $filter, $format, $langcode, $cache, $cache_id);
+    if ($filter->status >= 0) {
+      if ($filter->status && isset($filter_info[$name]['prepare callback']) && function_exists($filter_info[$name]['prepare callback'])) {
+        $function = $filter_info[$name]['prepare callback'];
+        $text = $function($text, $filter, $format, $langcode, $cache, $cache_id);
+      }
+    }
+    // If a filter ought to run, but no longer exists, return an empty text.
+    // The text stays empty until the text format has been re-configured.
+    else {
+      watchdog('filter', 'Missing filter %name in text format %format.', array('%name' => $name, '%format' => $format->name), WATCHDOG_ALERT);
+      return '';
     }
   }
 
@@ -1130,6 +1240,21 @@ function filter_filter_info() {
 }
 
 /**
+ * Implements hook_system_info_alter().
+ */
+function filter_system_info_alter(&$info, $file, $type) {
+  if ($type == 'module' && $file->name == 'filter') {
+    // If a module provides a filter which is currently being used in a text
+    // format, add it as a dependency for filter.module to prevent it being
+    // disabled.
+    $modules = db_query("SELECT module FROM {filter} WHERE status = 1 AND module <> 'filter'")->fetchCol();
+    foreach ($modules as $module) {
+      $info['dependencies'][] = $module;
+    }
+  }
+}
+
+/**
  * Settings callback for the HTML filter.
  */
 function _filter_html_settings($form, &$form_state, $filter, $format, $defaults) {
@@ -1450,3 +1575,4 @@ function _filter_html_escape_tips($filte
 /**
  * @} End of "Standard filters".
  */
+
Index: modules/filter/filter.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/filter/filter.test,v
retrieving revision 1.63
diff -u -p -r1.63 filter.test
--- modules/filter/filter.test	31 Mar 2010 20:05:06 -0000	1.63
+++ modules/filter/filter.test	9 Apr 2010 22:09:10 -0000
@@ -574,6 +574,88 @@ class FilterNoFormatTestCase extends Dru
 }
 
 /**
+ * Security tests for missing/vanished text formats or filters.
+ */
+class FilterBrokenTestCase extends DrupalWebTestCase {
+  public static function getInfo() {
+    return array(
+      'name' => 'Vanishing filters and text formats',
+      'description' => 'Test the behavior of check_markup() when a filter or text format vanishes.',
+      'group' => 'Filter',
+    );
+  }
+
+  function setUp() {
+    parent::setUp('php', 'filter_test');
+    $this->admin_user = $this->drupalCreateUser(array('administer modules', 'administer filters', 'administer site configuration'));
+    $this->drupalLogin($this->admin_user);
+  }
+
+  /**
+   * Test that filtered content is emptied when an actively used filter module is disabled.
+   */
+  function testDisableFilterModule() {
+    // Create a new node.
+    $node = $this->drupalCreateNode(array('promote' => 1));
+    $body_raw = $node->body[LANGUAGE_NONE][0]['value'];
+    $format_id = $node->body[LANGUAGE_NONE][0]['format'];
+    $this->drupalGet('node/' . $node->nid);
+    $this->assertText($body_raw, t('Node body found.'));
+
+    // Enable the filter_test_replace filter.
+    $edit = array(
+      'filters[filter_test_replace][status]' => 1,
+    );
+    $this->drupalPost('admin/config/content/formats/' . $format_id, $edit, t('Save configuration'));
+
+    // Verify that filter_test_replace filter replaced the content.
+    $this->drupalGet('node/' . $node->nid);
+    $this->assertNoText($body_raw, t('Node body not found.'));
+    $this->assertText('Filter: Testing filter', t('Testing filter output found.'));
+
+    // Disable the filter_test module.
+    $edit = array(
+      'modules[Testing][filter_test][enable]' => FALSE,
+    );
+    $this->drupalPost('admin/modules', $edit, t('Save configuration'));
+    $this->assertText(t('The configuration options have been saved.'), t('Module list has changed.'));
+    $this->assertRaw(t('At least one text format contains a filter that was provided by a module that has been disabled. All content using one of these text formats will be hidden until <a href="@text-format-url">text formats have been re-configured</a>.', array('@text-format-url' => url('admin/config/content/formats'))), t('Error message about disabled filter module displayed.'));
+
+    // Verify that the content is now empty, because the text format contains
+    // a filter that no longer exists.
+    $this->drupalGet('node/' . $node->nid);
+    $this->assertNoText($body_raw, t('Node body not found.'));
+    $this->assertNoText('Filter: Testing filter', t('Testing filter output not found.'));
+
+    // Ensure we get an error message on the text format overview page.
+    $this->drupalGet('admin/config/content/formats');
+    $this->assertText(t('The highlighted text format needs to be re-configured, because it is using filters that no longer exist.'), t('Error message about broken text formats found.'));
+
+    // Also ensure we get a requirements error.
+    $this->drupalGet('admin/reports/status');
+    $this->assertRaw(t('At least one text format contains a filter that was provided by a module that has been disabled. All content using one of these text formats will be hidden until <a href="@text-format-url">text formats have been re-configured</a>.', array('@text-format-url' => url('admin/config/content/formats'))), t('Requirements error found.'));
+
+    // Update text format.
+    $this->drupalPost('admin/config/content/formats/' . $format_id, array(), t('Save configuration'));
+
+    // Verify no error message.
+    $this->drupalGet('admin/config/content/formats');
+    $this->assertNoText(t('The highlighted text format needs to be re-configured, because it is using filters that no longer exist.'), t('Error message about broken text formats not found.'));
+
+    // Verify node content is displayed.
+    $this->drupalGet('node/' . $node->nid);
+    $this->assertText($body_raw, t('Node body found.'));
+
+    // Delete the text format entirely.
+    $this->drupalPost('admin/config/content/formats/' . $format_id . '/delete', array(), t('Delete'));
+
+    // Verify that the content is empty, because the text format does not exist.
+    $this->drupalGet('node/' . $node->nid);
+    $this->assertNoText($body_raw, t('Node body not found.'));
+  }
+}
+
+/**
  * Unit tests for core filters.
  */
 class FilterUnitTestCase extends DrupalUnitTestCase {
Index: modules/php/php.install
===================================================================
RCS file: /cvs/drupal/drupal/modules/php/php.install,v
retrieving revision 1.17
diff -u -p -r1.17 php.install
--- modules/php/php.install	9 Jan 2010 23:03:21 -0000	1.17
+++ modules/php/php.install	9 Apr 2010 22:15:04 -0000
@@ -36,10 +36,3 @@ function php_enable() {
     drupal_set_message(t('A !php-code text format has been created.', array('!php-code' => l('PHP code', 'admin/config/content/formats/' . $php_format->format))));
   }
 }
-
-/**
- * Implements hook_disable().
- */
-function php_disable() {
-  drupal_set_message(t('The PHP module has been disabled. Any existing content that was using the PHP filter will now be visible in plain text. This might pose a security risk by exposing sensitive information, if any, used in the PHP code.'));
-}
Index: modules/simpletest/tests/filter_test.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/filter_test.module,v
retrieving revision 1.4
diff -u -p -r1.4 filter_test.module
--- modules/simpletest/tests/filter_test.module	4 Dec 2009 16:49:47 -0000	1.4
+++ modules/simpletest/tests/filter_test.module	9 Apr 2010 22:09:10 -0000
@@ -28,6 +28,17 @@ function filter_test_filter_format_delet
 }
 
 /**
+ * Implements hook_system_info_alter().
+ */
+function filter_test_system_info_alter(&$info, $file) {
+  // Make this testing module visible on the module configuration page, so tests
+  // can disable it.
+  if ($file->name == 'filter_test') {
+    $info['hidden'] = FALSE;
+  }
+}
+
+/**
  * Implements hook_filter_info().
  */
 function filter_test_filter_info() {
@@ -36,6 +47,28 @@ function filter_test_filter_info() {
     'description' => 'Does nothing, but makes a text format uncacheable.',
     'cache' => FALSE,
   );
+  $filters['filter_test_replace'] = array(
+    'title' => 'Testing filter',
+    'description' => 'Replaces all content with filter and text format information.',
+    'process callback' => 'filter_test_replace',
+  );
   return $filters;
 }
 
+/**
+ * Process handler for filter_test_replace filter.
+ *
+ * Replaces all text with filter and text format information.
+ */
+function filter_test_replace($text, $filter, $format, $langcode, $cache, $cache_id) {
+  $text = array();
+  $text[] = 'Filter: ' . $filter->title . ' (' . $filter->name . ')';
+  $text[] = 'Format: ' . $format->name . ' (' . $format->format . ')';
+  $text[] = 'Language: ' . $langcode;
+  $text[] = 'Cache: ' . ($cache ? 'Enabled' : 'Disabled');
+  if ($cache_id) {
+    $text[] = 'Cache ID: ' . $cache_id;
+  }
+  return implode("<br />\n", $text);
+}
+
Index: modules/system/system-messages.css
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system-messages.css,v
retrieving revision 1.1
diff -u -p -r1.1 system-messages.css
--- modules/system/system-messages.css	3 Apr 2010 03:41:06 -0000	1.1
+++ modules/system/system-messages.css	9 Apr 2010 22:13:47 -0000
@@ -12,7 +12,7 @@ div.messages {
 }
 
 div.error,
-tr.error {
+table tr.error {
   background-color: #fcc;
 }
 
Index: modules/system/system.admin.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.admin.inc,v
retrieving revision 1.265
diff -u -p -r1.265 system.admin.inc
--- modules/system/system.admin.inc	2 Apr 2010 12:53:58 -0000	1.265
+++ modules/system/system.admin.inc	9 Apr 2010 22:15:04 -0000
@@ -834,7 +834,7 @@ function system_modules($form, $form_sta
   // Remove hidden modules from display list.
   foreach ($files as $filename => $file) {
     if (!empty($file->info['hidden']) || !empty($file->info['required'])) {
-      unset($files[$filename]);
+      $file->info['hidden'] = TRUE;
     }
   }
 
@@ -856,98 +856,100 @@ function system_modules($form, $form_sta
 
   // Iterate through each of the modules.
   foreach ($files as $filename => $module) {
-    $extra = array();
-    $extra['enabled'] = (bool) $module->status;
-    // If this module requires other modules, add them to the array.
-    foreach ($module->requires as $requires => $v) {
-      if (!isset($files[$requires])) {
-        $extra['requires'][$requires] = t('@module (<span class="admin-missing">missing</span>)', array('@module' => drupal_ucfirst($requires)));
-        $extra['disabled'] = TRUE;
-      }
-      else {
-        $requires_name = $files[$requires]->info['name'];
-        if ($incompatible_version = drupal_check_incompatibility($v, str_replace(DRUPAL_CORE_COMPATIBILITY . '-', '', $files[$requires]->info['version']))) {
-          $extra['requires'][$requires] = t('@module (<span class="admin-missing">incompatible with</span> version @version)', array(
-            '@module' => $requires_name . $incompatible_version,
-            '@version' => $files[$requires]->info['version'],
-          ));
+    if (empty($module->info['hidden'])) {
+      $extra = array();
+      $extra['enabled'] = (bool) $module->status;
+      // If this module requires other modules, add them to the array.
+      foreach ($module->requires as $requires => $v) {
+        if (!isset($files[$requires])) {
+          $extra['requires'][$requires] = t('@module (<span class="admin-missing">missing</span>)', array('@module' => drupal_ucfirst($requires)));
           $extra['disabled'] = TRUE;
         }
-        elseif ($files[$requires]->status) {
-          $extra['requires'][$requires] = t('@module (<span class="admin-enabled">enabled</span>)', array('@module' => $requires_name));
-        }
         else {
-          $extra['requires'][$requires] = t('@module (<span class="admin-disabled">disabled</span>)', array('@module' => $requires_name));
+          $requires_name = $files[$requires]->info['name'];
+          if ($incompatible_version = drupal_check_incompatibility($v, str_replace(DRUPAL_CORE_COMPATIBILITY . '-', '', $files[$requires]->info['version']))) {
+            $extra['requires'][$requires] = t('@module (<span class="admin-missing">incompatible with</span> version @version)', array(
+              '@module' => $requires_name . $incompatible_version,
+              '@version' => $files[$requires]->info['version'],
+            ));
+            $extra['disabled'] = TRUE;
+          }
+          elseif ($files[$requires]->status) {
+            $extra['requires'][$requires] = t('@module (<span class="admin-enabled">enabled</span>)', array('@module' => $requires_name));
+          }
+          else {
+            $extra['requires'][$requires] = t('@module (<span class="admin-disabled">disabled</span>)', array('@module' => $requires_name));
+          }
         }
       }
-    }
-    // Generate link for module's help page, if there is one.
-    if ($help_arg && $module->status && in_array($filename, module_implements('help'))) {
-      if (module_invoke($filename, 'help', "admin/help#$filename", $help_arg)) {
-        $extra['links']['help'] = array(
-          '#type' => 'link',
-          '#title' => t('Help'),
-          '#href' => "admin/help/$filename",
-          '#options' => array('attributes' => array('class' =>  array('module-link', 'module-link-help'), 'title' => t('Help'))),
-        );
+      // Generate link for module's help page, if there is one.
+      if ($help_arg && $module->status && in_array($filename, module_implements('help'))) {
+        if (module_invoke($filename, 'help', "admin/help#$filename", $help_arg)) {
+          $extra['links']['help'] = array(
+            '#type' => 'link',
+            '#title' => t('Help'),
+            '#href' => "admin/help/$filename",
+            '#options' => array('attributes' => array('class' =>  array('module-link', 'module-link-help'), 'title' => t('Help'))),
+          );
+        }
       }
-    }
-    // Generate link for module's permission, if the user has access to it.
-    if ($module->status && user_access('administer permissions') && in_array($filename, module_implements('permission'))) {
-      $extra['links']['permissions'] = array(
-        '#type' => 'link',
-        '#title' => t('Permissions'),
-        '#href' => 'admin/people/permissions',
-        '#options' => array('fragment' => 'module-' . $filename, 'attributes' => array('class' => array('module-link', 'module-link-permissions'), 'title' => t('Configure permissions'))),
-      );
-    }
-    // Generate link for module's configuration page, if the module provides
-    // one.
-    if ($module->status && isset($module->info['configure'])) {
-      $configure_link = menu_get_item($module->info['configure']);
-      if ($configure_link['access']) {
-        $extra['links']['configure'] = array(
+      // Generate link for module's permission, if the user has access to it.
+      if ($module->status && user_access('administer permissions') && in_array($filename, module_implements('permission'))) {
+        $extra['links']['permissions'] = array(
           '#type' => 'link',
-          '#title' => t('Configure'),
-          '#href' => $configure_link['href'],
-          '#options' => array('attributes' => array('class' => array('module-link', 'module-link-configure'), 'title' => $configure_link['description'])),
+          '#title' => t('Permissions'),
+          '#href' => 'admin/people/permissions',
+          '#options' => array('fragment' => 'module-' . $filename, 'attributes' => array('class' => array('module-link', 'module-link-permissions'), 'title' => t('Configure permissions'))),
         );
       }
-    }
-
-    // Mark dependents disabled so the user cannot remove required modules.
-    $dependents = array();
-    // If this module is required by other modules, list those, and then make it
-    // impossible to disable this one.
-    foreach ($module->required_by as $required_by => $v) {
-      // Hidden modules are unset already.
-      if (isset($files[$required_by])) {
-        if ($files[$required_by]->status == 1) {
-          $extra['required_by'][] = t('@module (<span class="admin-enabled">enabled</span>)', array('@module' => $files[$required_by]->info['name']));
-          $extra['disabled'] = TRUE;
+      // Generate link for module's configuration page, if the module provides
+      // one.
+      if ($module->status && isset($module->info['configure'])) {
+        $configure_link = menu_get_item($module->info['configure']);
+        if ($configure_link['access']) {
+          $extra['links']['configure'] = array(
+            '#type' => 'link',
+            '#title' => t('Configure'),
+            '#href' => $configure_link['href'],
+            '#options' => array('attributes' => array('class' => array('module-link', 'module-link-configure'), 'title' => $configure_link['description'])),
+          );
         }
-        else {
-          $extra['required_by'][] = t('@module (<span class="admin-disabled">disabled</span>)', array('@module' => $files[$required_by]->info['name']));
+      }
+
+      // Mark dependents disabled so the user cannot remove required modules.
+      $dependents = array();
+      // If this module is required by other modules, list those, and then make it
+      // impossible to disable this one.
+      foreach ($module->required_by as $required_by => $v) {
+        // Hidden modules are unset already.
+        if (isset($files[$required_by])) {
+          if ($files[$required_by]->status == 1) {
+            $extra['required_by'][] = t('@module (<span class="admin-enabled">enabled</span>)', array('@module' => $files[$required_by]->info['name']));
+            $extra['disabled'] = TRUE;
+          }
+          else {
+            $extra['required_by'][] = t('@module (<span class="admin-disabled">disabled</span>)', array('@module' => $files[$required_by]->info['name']));
+          }
         }
       }
+      $form['modules'][$module->info['package']][$filename] = _system_modules_build_row($module->info, $extra);
+    }
+    // Add basic information to the fieldsets.
+    foreach (element_children($form['modules']) as $package) {
+      $form['modules'][$package] += array(
+        '#type' => 'fieldset',
+        '#title' => t($package),
+        '#collapsible' => TRUE,
+        '#theme' => 'system_modules_fieldset',
+        '#header' => array(
+          array('data' => t('Enabled'), 'class' => array('checkbox')),
+          t('Name'),
+          t('Version'),
+          t('Description'),
+          array('data' => t('Operations'), 'colspan' => 3),
+        ),
+      );
     }
-    $form['modules'][$module->info['package']][$filename] = _system_modules_build_row($module->info, $extra);
-  }
-  // Add basic information to the fieldsets.
-  foreach (element_children($form['modules']) as $package) {
-    $form['modules'][$package] += array(
-      '#type' => 'fieldset',
-      '#title' => t($package),
-      '#collapsible' => TRUE,
-      '#theme' => 'system_modules_fieldset',
-      '#header' => array(
-        array('data' => t('Enabled'), 'class' => array('checkbox')),
-        t('Name'),
-        t('Version'),
-        t('Description'),
-        array('data' => t('Operations'), 'colspan' => 3),
-      ),
-    );
   }
 
   $form['actions'] = array('#type' => 'container', '#attributes' => array('class' => array('form-actions')));
@@ -1236,17 +1238,13 @@ function system_modules_submit($form, &$
     drupal_set_message(t('The configuration options have been saved.'));
   }
 
-  // Clear all caches.
-  registry_rebuild();
-  system_rebuild_theme_data();
-  drupal_theme_rebuild();
+  // Clear all caches. We need to invoke drupal_flush_all_caches() to ensure
+  // that also dependent caches are flushed, e.g. the filter cache and field
+  // cache, and also registered themes are rebuilt, since modules can also
+  // register themes.
+  drupal_flush_all_caches();
   cache_clear_all('system_list', 'cache_bootstrap');
-  node_types_rebuild();
-  menu_rebuild();
-  cache_clear_all('schema', 'cache');
   entity_info_cache_clear();
-  drupal_clear_css_cache();
-  drupal_clear_js_cache();
 
   $form_state['redirect'] = 'admin/modules';
 
@@ -1257,8 +1255,6 @@ function system_modules_submit($form, &$
 
   // Synchronize to catch any actions that were added or removed.
   actions_synchronize();
-
-  return;
 }
 
 /**
Index: modules/system/system.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.test,v
retrieving revision 1.120
diff -u -p -r1.120 system.test
--- modules/system/system.test	7 Apr 2010 15:07:59 -0000	1.120
+++ modules/system/system.test	9 Apr 2010 22:15:04 -0000
@@ -1107,14 +1107,8 @@ class SystemMainContentFallback extends 
    */
   function testMainContentFallback() {
     $edit = array();
-    // Disable the dashboard module, which depends on the block module.
-    $edit['modules[Core][dashboard][enable]'] = FALSE;
-    $this->drupalPost('admin/modules', $edit, t('Save configuration'));
-    $this->assertText(t('The configuration options have been saved.'), t('Modules status has been updated.'));
-    // Disable the block module.
-    $edit['modules[Core][block][enable]'] = FALSE;
-    $this->drupalPost('admin/modules', $edit, t('Save configuration'));
-    $this->assertText(t('The configuration options have been saved.'), t('Modules status has been updated.'));
+    module_disable(array('dashboard'));
+    module_disable(array('block'));
     module_list(TRUE);
     $this->assertFalse(module_exists('block'), t('Block module disabled.'));
 
@@ -1144,12 +1138,7 @@ class SystemMainContentFallback extends 
     $this->assertField('mail', t('User interface still available.'));
 
     // Enable the block module again.
-    $this->drupalLogin($this->admin_user);
-    $edit = array();
-    $edit['modules[Core][block][enable]'] = 'block';
-    $this->drupalPost('admin/modules', $edit, t('Save configuration'));
-    $this->assertText(t('The configuration options have been saved.'), t('Modules status has been updated.'));
-    module_list(TRUE);
+    module_enable(array('block'));
     $this->assertTrue(module_exists('block'), t('Block module re-enabled.'));
   }
 }
Index: profiles/standard/standard.profile
===================================================================
RCS file: /cvs/drupal/drupal/profiles/standard/standard.profile,v
retrieving revision 1.1
diff -u -p -r1.1 standard.profile
--- profiles/standard/standard.profile	4 Jan 2010 23:08:34 -0000	1.1
+++ profiles/standard/standard.profile	9 Apr 2010 22:15:04 -0000
@@ -12,3 +12,17 @@ function standard_form_alter(&$form, $fo
     $form['site_information']['site_name']['#default_value'] = $_SERVER['SERVER_NAME'];
   }
 }
+
+/**
+ * Implements hook_system_info_alter().
+ *
+ * The standard profile lists several modules as dependencies, however if these
+ * continue to be listed as dependencies on runtime, they can never be disabled.
+ * therefore remove all of them once the module is installed.
+ */
+function standard_system_info_alter(&$info, $file, $type) {
+  // Ensure this hook only runs after the installation process.
+  if ($type == 'profile' && $file->name == 'standard') {
+    $info['dependencies'] = array();
+  }
+}
