Index: modules/field/modules/text/text.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/modules/text/text.module,v
retrieving revision 1.62
diff -u -p -r1.62 text.module
--- modules/field/modules/text/text.module	22 Aug 2010 12:55:04 -0000	1.62
+++ modules/field/modules/text/text.module	23 Aug 2010 15:01:26 -0000
@@ -660,8 +660,14 @@ function text_filter_format_update() {
 /**
  * Implements hook_filter_format_delete().
  *
- * @todo D8: Properly update filter format references in all fields. See
- *   http://drupal.org/node/556022 for details.
+ * When a text format is deleted, the Filter API remembers the old format's
+ * replacement format and uses the replacement when asked to render text in the
+ * old format. Thus, all we really need to do here is invalidate the field data
+ * cache. We could try to be more clever and only invalid selected cache
+ * elements, but input formats are not deleted that often.
+ *
+ * @todo D8: Properly update filter format references in all fields.
+ * @see http://drupal.org/node/556022
  */
 function text_filter_format_delete() {
   field_cache_clear();
Index: modules/filter/filter.api.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/filter/filter.api.php,v
retrieving revision 1.20
diff -u -p -r1.20 filter.api.php
--- modules/filter/filter.api.php	26 Jun 2010 01:55:29 -0000	1.20
+++ modules/filter/filter.api.php	23 Aug 2010 15:03:13 -0000
@@ -238,20 +238,29 @@ function hook_filter_format_update($form
  * All modules storing references to text formats have to implement this hook.
  *
  * When a text format is deleted, all content that previously had that format
- * assigned needs to be switched to the passed fallback format.
+ * assigned needs to be switched to the passed replacement format.
+ *
+ * In some cases this may not possible to do immediately. Therefore, the Filter
+ * API maintains a history of all deleted formats and their corresponding
+ * replacement formats. When the Filter API is asked to operate on a deleted
+ * format, it instead uses the corresponding replacement format. This feature
+ * of Filter API will be deprecated in the future, so modules should update
+ * their data immediately, if possible.
+ *
+ * @see filter_format_delete()
  *
  * @param $format
  *   The format object of the format being deleted.
- * @param $fallback
+ * @param $replacement
  *   The format object of the format to use as replacement.
  *
  * @see hook_filter_format_insert()
  * @see hook_filter_format_update()
  */
-function hook_filter_format_delete($format, $fallback) {
-  // Replace the deleted format with the fallback format.
+function hook_filter_format_delete($format, $replacement) {
+  // Replace the deleted format with the replacement format.
   db_update('my_module_table')
-    ->fields(array('format' => $fallback->format))
+    ->fields(array('format' => $replacement->format))
     ->condition('format', $format->format)
     ->execute();
 }
Index: modules/filter/filter.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/filter/filter.module,v
retrieving revision 1.340
diff -u -p -r1.340 filter.module
--- modules/filter/filter.module	22 Aug 2010 12:55:04 -0000	1.340
+++ modules/filter/filter.module	23 Aug 2010 15:04:30 -0000
@@ -256,16 +256,30 @@ function filter_format_save(&$format) {
 }
 
 /**
- * Delete a text format.
+ * Deletes a text format.
+ *
+ * Some modules may not be able to directly perform a mass-update to change all
+ * stored references to text formats. They may not even attempt to update text
+ * format values, even if that purposively leads to data integrity issues (such
+ * as breaking views that try to join and filter records based on the text
+ * format).
+ * Therefore, the Filter API had to be changed to permanently retain a mapping
+ * of deleted text formats and to dynamically adjust the text format to use for
+ * filtering in check_markup().
+ * When check_markup() or filter_format_load() are asked for a deleted format,
+ * they automatically use the corresponding replacement format instead.
+ *
+ * @see filter_get_replacement_formats()
+ * @see http://drupal.org/node/556022
  *
  * @param $format
  *   The text format object to be deleted.
- * @param $fallback_id
+ * @param $replacement_id
  *   (optional) The ID of the text format to use to reassign content that is
  *   currently using $format. If omitted, the currently stored
  *   filter_fallback_format() is used.
  */
-function filter_format_delete($format, $fallback_id = NULL) {
+function filter_format_delete($format, $replacement_id = NULL) {
   db_delete('filter_format')
     ->condition('format', $format->format)
     ->execute();
@@ -273,12 +287,17 @@ function filter_format_delete($format, $
     ->condition('format', $format->format)
     ->execute();
 
-  // Allow modules to react on text format deletion.
-  if (empty($fallback_id)) {
-    $fallback_id = filter_fallback_format();
+  // Determine the replacement format.
+  if (empty($replacement_id)) {
+    $replacement_id = filter_fallback_format();
   }
-  $fallback = filter_format_load($fallback_id);
-  module_invoke_all('filter_format_delete', $format, $fallback);
+  $replacement = filter_format_load($replacement_id);
+
+  // Update the text format replacement mapping.
+  filter_format_set_replacement($format->format, $replacement->format);
+
+  // Allow modules to react on text format deletion.
+  module_invoke_all('filter_format_delete', $format, $replacement);
 
   // Clear the filter cache whenever a text format is deleted.
   filter_formats_reset();
@@ -286,6 +305,56 @@ function filter_format_delete($format, $
 }
 
 /**
+ * Updates the replacement text format mapping for a deleted text format.
+ *
+ * @param $format_id
+ *   A text format id that is deleted.
+ * @param $replacement_id
+ *   The replacement text format for $format_id.
+ */
+function filter_format_set_replacement($format_id, $replacement_id) {
+  $deleted = variable_get('filter_format_replacements', array());
+  // Add the new entry.
+  $deleted[$format_id] = $replacement_id;
+  // Update any previously deleted formats whose replacement is $format_id
+  // to fall back to $replacement_id.
+  foreach ($deleted as $original_id => $old_replacement_id) {
+    if ($old_replacement_id == $format_id) {
+      $deleted[$original_id] = $replacement_id;
+    }
+  }
+  variable_set('filter_format_replacements', $deleted);
+}
+
+/**
+ * Returns a text format's replacement format, if any.
+ *
+ * @param $format_id
+ *   A text format that may have been deleted.
+ * @return
+ *   $format_id, or the replacement text format id for $format_id, if $format_id
+ *   has been deleted.
+ */
+function filter_format_get_replacement($format_id) {
+  $deleted = variable_get('filter_format_replacements', array());
+  return isset($deleted[$format_id]) ? $deleted[$format_id] : $format_id;
+}
+
+/**
+ * Retrieves the replacement text format mapping.
+ *
+ * @return
+ *   An associative array mapping each deleted text format id to a replacement
+ *   text format id.
+ *
+ * @see filter_format_get_replacement()
+ * @see filter_format_set_replacement()
+ */
+function filter_get_replacement_formats() {
+  return variable_get('filter_format_replacements', array());
+}
+
+/**
  * Display a text format form title.
  */
 function filter_admin_format_title($format) {
@@ -615,7 +684,8 @@ function _filter_format_is_cacheable($fo
  * before performing actions with the filter.
  *
  * @param $format_id
- *   The format ID to retrieve filters for.
+ *   The format ID to retrieve filters for. If the format has been deleted, the
+ *   filters for the replacement format are automatically returned.
  *
  * @return
  *   An array of filter objects associated to the given text format, keyed by
@@ -631,6 +701,8 @@ function filter_list_format($format_id) 
       $filters['all'][$record->format][$record->name] = $record;
     }
   }
+  // Check whether the format has been replaced.
+  $format_id = filter_format_get_replacement($format_id);
 
   if (!isset($filters[$format_id])) {
     $format_filters = array();
@@ -682,6 +754,10 @@ function check_markup($text, $format_id 
   if (empty($format_id)) {
     $format_id = filter_fallback_format();
   }
+  else {
+    // Check whether the format has been replaced.
+    $format_id = filter_format_get_replacement($format_id);
+  }
   // If the requested text format does not exist, the text cannot be filtered.
   if (!$format = filter_format_load($format_id)) {
     watchdog('filter', 'Missing text format: %format.', array('%format' => $format_id), WATCHDOG_ALERT);
@@ -824,6 +900,10 @@ function filter_process_format($element)
   if (empty($element['#format'])) {
     $element['#format'] = filter_default_format($user);
   }
+  else {
+    // Check whether the format has been deleted.
+    $element['#format'] = filter_format_get_replacement($element['#format']);
+  }
   $element['format']['format'] = array(
     '#type' => 'select',
     '#title' => t('Text format'),
Index: modules/filter/filter.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/filter/filter.test,v
retrieving revision 1.72
diff -u -p -r1.72 filter.test
--- modules/filter/filter.test	22 Aug 2010 12:55:04 -0000	1.72
+++ modules/filter/filter.test	23 Aug 2010 15:05:45 -0000
@@ -68,6 +68,54 @@ class FilterCRUDTestCase extends DrupalW
   }
 
   /**
+   * Tests check_markup() handling of text format replacements.
+   */
+  function testTextFormatReplacements() {
+    $plaintext_format = db_query_range('SELECT * FROM {filter_format} WHERE name = :name', 0, 1, array(':name' => 'Plain text'))->fetchObject();
+    $filtered_html_format = db_query_range('SELECT * FROM {filter_format} WHERE name = :name', 0, 1, array(':name' => 'Filtered HTML'))->fetchObject();
+    $full_html_format = db_query_range('SELECT * FROM {filter_format} WHERE name = :name', 0, 1, array(':name' => 'Full HTML'))->fetchObject();
+    $langcode = LANGUAGE_NONE;
+
+    $this->web_user = $this->drupalCreateUser(array(
+      'bypass node access',
+      filter_permission_name($filtered_html_format),
+      filter_permission_name($full_html_format),
+    ));
+    $this->drupalLogin($this->web_user);
+
+    // Create a node using Full HTML.
+    $html_text = '<div>looks different under Full HTML, Filtered HTML, and default replacement format</div>';
+    $node = array();
+    $node['type'] = 'article';
+    $node['uid'] = 1;
+    $node['body'][$langcode][0]['value'] = $html_text;
+    $node['body'][$langcode][0]['format'] = $full_html_format->format;
+    $node = $this->drupalCreateNode($node);
+
+    // Verify Full HTML is used.
+    $this->drupalGet('node/' . $node->nid);
+    $this->assertRaw($html_text, 'Content is rendered in Full HTML.');
+    $this->drupalGet('node/' . $node->nid . '/edit');
+    $this->assertFieldByName("body[$langcode][0][format]", $full_html_format->format, 'Text format widget defaults to Full HTML.');
+
+    // Delete Full HTML, replacing it with Filtered HTML.
+    filter_format_delete($full_html_format, $filtered_html_format->format);
+    $this->drupalGet('node/' . $node->nid);
+    $this->assertNoRaw($html_text, 'Content is not rendered in Full HTML.');
+    $this->assertRaw(strip_tags($html_text), 'Content was filtered with replacement format.');
+    $this->drupalGet('node/' . $node->nid . '/edit');
+    $this->assertFieldByName("body[$langcode][0][format]", $filtered_html_format->format, 'Text format widget defaults to Filtered HTML.');
+
+    // Delete Filtered HTML, verify default format is used.
+    filter_format_delete($filtered_html_format);
+    $this->drupalGet('node/' . $node->nid);
+    $this->assertRaw(check_markup($html_text, $plaintext_format->format), 'Content was rendered in Plain text.');
+    $this->drupalGet('node/' . $node->nid . '/edit');
+    $this->assertNoFieldByName("body[$langcode][0][format]", NULL, 'Text format widget not found.');
+    $this->assertText($plaintext_format->name);
+  }
+
+  /**
    * Verify that a text format is properly stored.
    */
   function verifyTextFormat($format) {
@@ -682,6 +730,13 @@ class FilterSecurityTestCase extends Dru
     // Delete the text format entirely.
     $this->drupalPost('admin/config/content/formats/' . $format_id . '/delete', array(), t('Delete'));
 
+    // For this test, we have to explicitly remove the deleted format from the
+    // text format replacement mapping; otherwise, the Filter API would
+    // automatically use the fallback format.
+    $deleted = variable_get('filter_format_replacements', array());
+    unset($deleted[$format_id]);
+    variable_set('filter_format_replacements', $deleted);
+
     // 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.'));
