diff --git a/modules/block/block.install b/modules/block/block.install
index 95129143b2a9befce28339c776907fe8fd7a9e8c..1a41fd433ba1c48d8101c04a14f6051415808640 100644
--- a/modules/block/block.install
+++ b/modules/block/block.install
@@ -187,8 +187,8 @@ function block_schema() {
         'size' => 'small',
         'not null' => TRUE,
         'default' => 0,
-        'description' => "Block body's {filter_format}.format; for example, 1 = Filtered HTML.",
-      )
+        'description' => 'The {filter_format}.format of the block body.',
+      ),
     ),
     'unique keys' => array(
       'info' => array('info'),
diff --git a/modules/block/block.test b/modules/block/block.test
index 509264e26f34e266d7a153fa7e1270f5505d8eae..73403d4b7d36e4e440cf7577450363cbdad428bb 100644
--- a/modules/block/block.test
+++ b/modules/block/block.test
@@ -79,7 +79,8 @@ class BlockTestCase extends DrupalWebTestCase {
     $custom_block['info'] = $this->randomName(8);
     $custom_block['title'] = $this->randomName(8);
     $custom_block['body'] = '<h1>Full HTML</h1>';
-    $custom_block['body_format'] = 2;
+    $full_html_format_id = db_query_range('SELECT format FROM {filter_format} WHERE name = :name', 0, 1, array(':name' => 'Full HTML'))->fetchField();
+    $custom_block['body_format'] = $full_html_format_id;
     $this->drupalPost('admin/structure/block/add', $custom_block, t('Save block'));
 
     // Set the created custom block to a specific region.
diff --git a/modules/field/modules/text/text.test b/modules/field/modules/text/text.test
index e098b3f8c85f8fd0a7eb7b7062a1566d3406509b..87b09c45ae934c858a17fe635e2c8ea9f244c6d0 100644
--- a/modules/field/modules/text/text.test
+++ b/modules/field/modules/text/text.test
@@ -195,7 +195,7 @@ class TextFieldTestCase extends DrupalWebTestCase {
     $format_id = db_query("SELECT format FROM {filter_format} WHERE name = :name", array(':name' => $edit['name']))->fetchField();
     $permission = filter_permission_name(filter_format_load($format_id));
     $rid = max(array_keys($this->web_user->roles));
-    user_role_grant_permissions($rid, array($permission), TRUE);
+    user_role_grant_permissions($rid, array($permission));
     $this->drupalLogin($this->web_user);
 
     // Display edition form.
diff --git a/modules/filter/filter.install b/modules/filter/filter.install
index 743248149c5260f470f6a82c05a09bd7f6e92864..5a04ee69b09deeee28fcc2c81c2756501f88b21b 100644
--- a/modules/filter/filter.install
+++ b/modules/filter/filter.install
@@ -106,6 +106,42 @@ function filter_schema() {
   return $schema;
 }
 
+/**
+ * Implements hook_install().
+ */
+function filter_install() {
+  // All sites require at least one text format (the fallback format) that all
+  // users have access to, so add it here. We initialize it as a simple, safe
+  // plain text format with very basic formatting, but it can be modified by
+  // installation profiles to have other properties.
+  $plain_text_format = array(
+    'name' => 'Plain text',
+    'weight' => 10,
+    'filters' => array(
+      // Escape all HTML.
+      'filter_html_escape' => array(
+        'weight' => 0,
+        'status' => 1,
+      ),
+      // URL filter.
+      'filter_url' => array(
+        'weight' => 1,
+        'status' => 1,
+      ),
+      // Line break filter.
+      'filter_autop' => array(
+        'weight' => 2,
+        'status' => 1,
+      ),
+    ),
+  );
+  $plain_text_format = (object) $plain_text_format;
+  filter_format_save($plain_text_format);
+
+  // Set the fallback format to plain text.
+  variable_set('filter_fallback_format', $plain_text_format->format);
+}
+
 /**
  * Implements hook_update_dependencies().
  */
@@ -317,18 +353,27 @@ function filter_update_7005() {
   $start_name = 'Plain text';
   $format_name = $start_name;
   while ($format = db_query('SELECT format FROM {filter_format} WHERE name = :name', array(':name' => $format_name))->fetchField()) {
-    $id = empty($id) ? 1 : $id + 1;
+    $id = empty($id) ? 2 : $id + 1;
     $format_name = $start_name . ' ' . $id;
   }
   $fallback_format = new stdClass();
   $fallback_format->name = $format_name;
-  $fallback_format->cache = 1;
   $fallback_format->weight = 1;
   // This format should output plain text, so we escape all HTML and apply the
-  // line break filter only.
+  // line break and URL filters only.
   $fallback_format->filters = array(
-    'filter_html_escape' => array('status' => 1),
-    'filter_autop' => array('status' => 1),
+    'filter_html_escape' => array(
+      'weight' => 0,
+      'status' => 1,
+    ),
+    'filter_url' => array(
+      'weight' => 1,
+      'status' => 1,
+    ),
+    'filter_autop' => array(
+      'weight' => 2,
+      'status' => 1,
+    ),
   );
   filter_format_save($fallback_format);
   variable_set('filter_fallback_format', $fallback_format->format);
diff --git a/modules/filter/filter.module b/modules/filter/filter.module
index 563bdb5b78910434075650f6865509010f427669..36385c22cf9d2936d2d3af66196adba288a9dc3d 100644
--- a/modules/filter/filter.module
+++ b/modules/filter/filter.module
@@ -153,14 +153,17 @@ function filter_format_load($format_id) {
  *   - 'name': The title of the text format.
  *   - 'format': (optional) The internal ID of the text format. If omitted, a
  *     new text format is created.
- *   - 'roles': (optional) An associative array containing the roles allowed to
- *     access/use the text format.
+ *   - 'weight': (optional) The weight of the text format, which controls its
+ *     placement in text format lists. If omitted, the weight is set to 0.
  *   - 'filters': (optional) An associative, multi-dimensional array of filters
- *     assigned to the text format, using the properties:
- *     - 'weight': The weight of the filter in the text format.
- *     - 'status': A boolean indicating whether the filter is enabled in the
- *       text format.
- *     - 'module': The name of the module implementing the filter.
+ *     assigned to the text format, keyed by the name of each filter and using
+ *     the properties:
+ *     - 'weight': (optional) The weight of the filter in the text format. If
+ *       omitted, either the currently stored weight is retained (if there is
+ *       one), or the filter is assigned a weight of 10, which will usually
+ *       put it at the bottom of the list.
+ *     - 'status': (optional) A boolean indicating whether the filter is
+ *       enabled in the text format. If omitted, the filter will be disabled.
  *     - 'settings': (optional) An array of configured settings for the filter.
  *       See hook_filter_info() for details.
  */
@@ -315,6 +318,24 @@ function filter_cron() {
   cache_clear_all(NULL, 'cache_filter');
 }
 
+/**
+ * Implements hook_modules_enabled().
+ */
+function filter_modules_enabled($modules) {
+  // Reset the static cache of module-provided filters, in case any of the
+  // newly enabled modules defines a new filter or alters existing ones.
+  drupal_static_reset('filter_get_filters');
+}
+
+/**
+ * Implements hook_modules_disabled().
+ */
+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');
+}
+
 /**
  * Retrieve a list of text formats, ordered by weight.
  *
@@ -439,6 +460,31 @@ function filter_default_format($account = NULL) {
 
 /**
  * Returns the ID of the fallback text format that all users have access to.
+ *
+ * The fallback text format is a regular text format in every respect, except
+ * it does not participate in the filter permission system and cannot be
+ * deleted. It needs to exist because any user who has permission to create
+ * formatted content must always have at least one text format they can use.
+ *
+ * Because the fallback format is available to all users, it should always be
+ * configured securely. For example, when the Filter module is installed, this
+ * format is initialized to output plain text. Installation profiles and site
+ * administrators have the freedom to configure it further.
+ *
+ * When a text format is deleted, all content that previously had that format
+ * assigned should be switched to the fallback format. To facilitate this,
+ * Drupal passes in the fallback format object as one of the parameters of
+ * hook_filter_format_delete().
+ *
+ * Note that the fallback format is completely distinct from the default
+ * format, which differs per user and is simply the first format which that
+ * user has access to. The default and fallback formats are only guaranteed to
+ * be the same for users who do not have access to any other format; otherwise,
+ * the fallback format's weight determines its placement with respect to the
+ * user's other formats.
+ *
+ * @see hook_filter_format_delete()
+ * @see filter_default_format()
  */
 function filter_fallback_format() {
   // This variable is automatically set in the database for all installations
diff --git a/modules/search/search.test b/modules/search/search.test
index 179e81a105c9a3bc0975d8a18b28d30c842f02c0..d428ed87bada73164e6ae01d1df2961a14fd491f 100644
--- a/modules/search/search.test
+++ b/modules/search/search.test
@@ -502,8 +502,9 @@ class SearchCommentTestCase extends DrupalWebTestCase {
 
     variable_set('comment_preview_article', DRUPAL_OPTIONAL);
     // Enable check_plain() for 'Filtered HTML' text format.
+    $filtered_html_format_id = db_query_range('SELECT format FROM {filter_format} WHERE name = :name', 0, 1, array(':name' => 'Filtered HTML'))->fetchField();
     $edit = array(
-      'filters[filter_html_escape][status]' => 1,
+      'filters[filter_html_escape][status]' => $filtered_html_format_id,
     );
     $this->drupalPost('admin/config/content/formats/1', $edit, t('Save configuration'));
     // Allow anonymous users to search content.
@@ -525,7 +526,8 @@ class SearchCommentTestCase extends DrupalWebTestCase {
     $edit_comment = array();
     $edit_comment['subject'] = 'Test comment subject';
     $edit_comment['comment_body[' . LANGUAGE_NONE . '][0][value]'] = '<h1>' . $comment_body . '</h1>';
-    $edit_comment['comment_body[' . LANGUAGE_NONE . '][0][value_format]'] = 2;
+    $full_html_format_id = db_query_range('SELECT format FROM {filter_format} WHERE name = :name', 0, 1, array(':name' => 'Full HTML'))->fetchField();
+    $edit_comment['comment_body[' . LANGUAGE_NONE . '][0][value_format]'] = $full_html_format_id;
     $this->drupalPost('comment/reply/' . $node->nid, $edit_comment, t('Save'));
 
     // Invoke search index update.
diff --git a/profiles/minimal/minimal.install b/profiles/minimal/minimal.install
index fc1c5a14ba1448da75a426adfa6f4e1101fefa94..1d695827699ac79384973b5b24dc3353d447749c 100644
--- a/profiles/minimal/minimal.install
+++ b/profiles/minimal/minimal.install
@@ -7,29 +7,6 @@
  * Perform actions to set up the site for this profile.
  */
 function minimal_install() {
-  // Add text formats.
-  $plain_text_format = array(
-    'name' => 'Plain text',
-    'weight' => 10,
-    'filters' => array(
-      // Escape all HTML.
-      'filter_html_escape' => array(
-        'weight' => 0,
-        'status' => 1,
-      ),
-      // Line break filter.
-      'filter_autop' => array(
-        'weight' => 1,
-        'status' => 1,
-      ),
-    ),
-  );
-  $plain_text_format = (object) $plain_text_format;
-  filter_format_save($plain_text_format);
-
-  // Set the fallback format to plain text.
-  variable_set('filter_fallback_format', $plain_text_format->format);
-
   // Enable some standard blocks.
   $values = array(
     array(
diff --git a/profiles/standard/standard.install b/profiles/standard/standard.install
index 450347bb2004bd647ccb7ced48903a80afdfc366..ccba7eb1b0d9fa83adfc9cc6d0cde6cfb57f6851 100644
--- a/profiles/standard/standard.install
+++ b/profiles/standard/standard.install
@@ -61,28 +61,6 @@ function standard_install() {
   $full_html_format = (object) $full_html_format;
   filter_format_save($full_html_format);
 
-  $plain_text_format = array(
-    'name' => 'Plain text',
-    'weight' => 10,
-    'filters' => array(
-      // Escape all HTML.
-      'filter_html_escape' => array(
-        'weight' => 0,
-        'status' => 1,
-      ),
-      // Line break filter.
-      'filter_autop' => array(
-        'weight' => 1,
-        'status' => 1,
-      ),
-    ),
-  );
-  $plain_text_format = (object) $plain_text_format;
-  filter_format_save($plain_text_format);
-
-  // Set the fallback format to plain text.
-  variable_set('filter_fallback_format', $plain_text_format->format);
-
   // Enable some standard blocks.
   $values = array(
     array(