Index: modules/filter/filter.admin.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/filter/filter.admin.inc,v
retrieving revision 1.46
diff -u -p -r1.46 filter.admin.inc
--- modules/filter/filter.admin.inc	20 Sep 2009 07:32:17 -0000	1.46
+++ modules/filter/filter.admin.inc	25 Sep 2009 02:35:24 -0000
@@ -16,9 +16,15 @@ function filter_admin_overview($form) {
   // Overview of all formats.
   $formats = filter_formats();
   $fallback_format = filter_fallback_format();
+  $broken_formats = _filter_get_broken_filters();
+  if ($broken_formats) {
+    drupal_set_message(format_plural(count($broken_formats), '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_formats[$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);
@@ -71,7 +77,7 @@ function theme_filter_admin_overview($fo
         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));
@@ -133,7 +139,7 @@ function filter_admin_format_form($form,
   }
   // Table with filters
   $filter_info = filter_get_filters();
-  $filters = filter_list_format($format->format, TRUE);
+  $filters = filter_list_format($format->format);
 
   $form['filters'] = array('#type' => 'fieldset',
     '#title' => t('Filters'),
@@ -258,7 +264,7 @@ function filter_admin_configure($form, &
 
   $form['#format'] = $format;
   foreach ($filters as $name => $filter) {
-    if (isset($filter_info[$name]['settings callback']) && function_exists($filter_info[$name]['settings callback'])) {
+    if ($filter->status && isset($filter_info[$name]['settings callback']) && function_exists($filter_info[$name]['settings callback'])) {
       // Pass along stored filter settings and default settings, but also the
       // format object and all filters to allow for complex implementations.
       $defaults = (isset($filter_info[$name]['default settings']) ? $filter_info[$name]['default settings'] : array());
@@ -323,13 +329,14 @@ function filter_admin_order_page($format
  * @see filter_admin_order_submit()
  */
 function filter_admin_order($form, &$form_state, $format = NULL) {
-  // Get list (with forced refresh).
   $filters = filter_list_format($format->format);
 
   $form['weights'] = array('#tree' => TRUE);
   foreach ($filters as $id => $filter) {
-    $form['names'][$id] = array('#markup' => $filter->title);
-    $form['weights'][$id] = array('#type' => 'weight', '#default_value' => $filter->weight);
+    if ($filter->status) {
+      $form['names'][$id] = array('#markup' => $filter->title);
+      $form['weights'][$id] = array('#type' => 'weight', '#default_value' => $filter->weight);
+    }
   }
   $form['format'] = array('#type' => 'hidden', '#value' => $format->format);
   $form['submit'] = array('#type' => 'submit', '#value' => t('Save configuration'));
Index: modules/filter/filter.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/filter/filter.module,v
retrieving revision 1.291
diff -u -p -r1.291 filter.module
--- modules/filter/filter.module	20 Sep 2009 07:32:18 -0000	1.291
+++ modules/filter/filter.module	25 Sep 2009 02:37:21 -0000
@@ -178,6 +178,16 @@ function filter_format_save($format) {
     $return = drupal_write_record('filter_format', $format, 'format');
   }
 
+  // When a text format is saved, 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)) {
+    db_delete('filter')
+      ->condition('format', $format->format)
+      ->condition('name', $broken_filters, 'IN')
+      ->condition('name', array_keys($format->filters), 'NOT IN')
+      ->execute();
+  }
+
   // Get the filters currently active in the format, to add new filters
   // to the bottom.
   $current = filter_list_format($format->format);
@@ -603,17 +613,15 @@ function filter_format_allowcache($forma
  *
  * @param $format
  *   The format ID.
- * @param $include_disabled
- *   (optional) Boolean whether to retrieve all filters associated with the
- *   given format, including those that are disabled. Defaults to FALSE.
+ *
  * @return
  *   An array of filter objects assosiated to the given format.
  */
-function filter_list_format($format, $include_disabled = FALSE) {
+function filter_list_format($format) {
   $filters = &drupal_static(__FUNCTION__, array());
   $filter_info = filter_get_filters();
 
-  if (!isset($filters[$format]) || $include_disabled) {
+  if (!isset($filters[$format])) {
     $format_filters = array();
     $query = db_select('filter', 'filter')
       ->fields('filter')
@@ -621,9 +629,6 @@ function filter_list_format($format, $in
       ->orderBy('weight')
       ->orderBy('module')
       ->orderBy('name');
-    if (!$include_disabled) {
-      $query->condition('status', 1);
-    }
     $result = $query->execute()->fetchAllAssoc('name');
     foreach ($result as $name => $filter) {
       if (isset($filter_info[$name])) {
@@ -634,12 +639,14 @@ function filter_list_format($format, $in
         if (isset($filter_info[$name]['default settings'])) {
           $filter->settings = array_merge($filter_info[$name]['default settings'], $filter->settings);
         }
-        $format_filters[$name] = $filter;
       }
-    }
-    // Prevent statically caching of disabled filters.
-    if ($include_disabled) {
-      return $format_filters;
+      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] = $format_filters;
   }
@@ -648,6 +655,50 @@ function filter_list_format($format, $in
 }
 
 /**
+ * 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.
+ *
+ * @return
+ *   A list of assigned filters that no longer exist, keyed by text 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) {
+  // Retrieve currently available filters.
+  $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('status', 1)
+    ->condition('name', array_keys($filter_info), 'NOT IN');
+  // Optionally filter by text format.
+  if (isset($format_id)) {
+    $query->condition('format', $format_id);
+  }
+  // Optionally filter by a list of modules.
+  if (isset($modules) && is_array($modules)) {
+    $query->condition('module', $modules, 'IN');
+  }
+  $result = $query->execute();
+
+  // Compile the 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;
+}
+
+/**
  * @name Filtering functions
  * @{
  * Modules which need to have content filtered can use these functions to
@@ -683,6 +734,12 @@ function check_markup($text, $format = N
   if (empty($format)) {
     $format = filter_fallback_format();
   }
+  // If the requested text format does not exist, we return an empty text for
+  // security reasons. The text stays empty until the assigned format has been
+  // fixed/updated.
+  elseif (!filter_format_load($format)) {
+    return '';
+  }
 
   // Check for a cached version of this piece of text.
   $cache_id = $format . ':' . $langcode . ':' . md5($text);
@@ -700,15 +757,23 @@ function check_markup($text, $format = N
 
   // Give filters the chance to escape HTML-like data such as code or formulas.
   foreach ($filters as $name => $filter) {
-    if (isset($filter_info[$name]['prepare callback']) && function_exists($filter_info[$name]['prepare callback'])) {
-      $text = $filter_info[$name]['prepare callback']($text, $filter, $format, $langcode, $cache, $cache_id);
+    if ($filter->status != -1) {
+      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.
+    else {
+      return '';
     }
   }
 
   // Perform filtering.
   foreach ($filters as $name => $filter) {
-    if (isset($filter_info[$name]['process callback']) && function_exists($filter_info[$name]['process callback'])) {
-      $text = $filter_info[$name]['process callback']($text, $filter, $format, $langcode, $cache, $cache_id);
+    if ($filter->status && isset($filter_info[$name]['process callback']) && function_exists($filter_info[$name]['process callback'])) {
+      $function = $filter_info[$name]['process callback'];
+      $text = $function($text, $filter, $format, $langcode, $cache, $cache_id);
     }
   }
 
@@ -1159,3 +1224,38 @@ function _filter_html_escape($text) {
 /**
  * @} End of "Standard filters".
  */
+
+/**
+ * Implement hook_modules_disabled().
+ *
+ * In case a module providing a filter is disabled, the site administrator needs
+ * to manually update the text formats that have it applied. We cannot
+ * automatically disable or remove filters, because that would most likely break
+ * the intended filtering logic in a text format.
+ *
+ * @see filter_formats()
+ * @see check_markup()
+ */
+function filter_modules_disabled($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 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');
+  }
+}
+
+/**
+ * Implement hook_modules_uninstalled().
+ *
+ * In case a module providing a filter is uninstalled, the site administrator
+ * needs to manually update the text formats that have it applied. We cannot
+ * automatically disable or remove filters, because that would most likely break
+ * the intended filtering logic in a text format.
+ *
+ * @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');
+  }
+}
+
Index: modules/system/system.css
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.css,v
retrieving revision 1.63
diff -u -p -r1.63 system.css
--- modules/system/system.css	21 Sep 2009 08:52:41 -0000	1.63
+++ modules/system/system.css	25 Sep 2009 02:13:45 -0000
@@ -66,7 +66,7 @@ div.tree-child-horizontal {
 div.error {
   border: 1px solid #d77;
 }
-div.error, tr.error {
+div.error, table tr.error {
   background: #fcc;
   color: #200;
   padding: 2px;
