Index: modules/search/search.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/search/search.test,v
retrieving revision 1.27
diff -u -r1.27 search.test
--- modules/search/search.test	31 Jul 2009 19:01:02 -0000	1.27
+++ modules/search/search.test	11 Aug 2009 15:37:13 -0000
@@ -461,7 +461,7 @@
     variable_set('comment_preview_article', COMMENT_PREVIEW_OPTIONAL);
     // Enable check_plain() for 'Filtered HTML' text format.
     $edit = array(
-      'filters[filter/4]' => 1,
+      'filters[filter_html_escape]' => 1,
     );
     $this->drupalPost('admin/settings/formats/1', $edit, t('Save configuration'));
     // Allow anonymous users to search content.
Index: modules/filter/filter.install
===================================================================
RCS file: /cvs/drupal/drupal/modules/filter/filter.install,v
retrieving revision 1.16
diff -u -r1.16 filter.install
--- modules/filter/filter.install	27 May 2009 18:33:57 -0000	1.16
+++ modules/filter/filter.install	11 Aug 2009 15:36:41 -0000
@@ -10,90 +10,83 @@
  * Implement hook_schema().
  */
 function filter_schema() {
-  $schema['filter'] = array(
-    'description' => 'Table that maps filters (HTML corrector) to text formats (Filtered HTML).',
+  $schema['filter_format'] = array(
+    'description' => 'Stores text formats: custom groupings of filters, such as Filtered HTML.',
     'fields' => array(
-      'fid' => array(
+      'format_id' => array(
         'type' => 'serial',
         'not null' => TRUE,
-        'description' => 'Primary Key: Auto-incrementing filter ID.',
+        'description' => 'Primary Key: Unique ID for format.',
       ),
-      'format' => array(
-        'type' => 'int',
+      'name' => array(
+        'type' => 'varchar',
+        'length' => 255,
         'not null' => TRUE,
-        'default' => 0,
-        'description' => 'Foreign key: The {filter_format}.format to which this filter is assigned.',
+        'default' => '',
+        'description' => 'Name of the text format (Filtered HTML).',
       ),
-      'module' => array(
+      'roles' => array(
         'type' => 'varchar',
-        'length' => 64,
+        'length' => 255,
         'not null' => TRUE,
         'default' => '',
-        'description' => 'The origin module of the filter.',
+        'description' => 'A comma-separated string of roles; references {role}.rid.', // This is bad since you can't use joins, nor index.
       ),
-      'delta' => array(
+      'cache' => array(
         'type' => 'int',
         'not null' => TRUE,
         'default' => 0,
         'size' => 'tiny',
-        'description' => 'ID to identify which filter within module is being referenced.',
+        'description' => 'Flag to indicate whether format is cacheable. (1 = cacheable, 0 = not cacheable)',
       ),
       'weight' => array(
         'type' => 'int',
         'not null' => TRUE,
         'default' => 0,
         'size' => 'tiny',
-        'description' => 'Weight of filter within format.',
+        'description' => 'Weight of text format to use when listing.',
       )
     ),
-    'primary key' => array('fid'),
+    'primary key' => array('format_id'),
     'unique keys' => array(
-      'fmd' => array('format', 'module', 'delta'),
-    ),
-    'indexes' => array(
-      'list' => array('format', 'weight', 'module', 'delta'),
+      'name' => array('name'),
     ),
   );
-  $schema['filter_format'] = array(
-    'description' => 'Stores text formats: custom groupings of filters, such as Filtered HTML.',
+
+  $schema['filter_format_filter'] = array(
+    'description' => 'Table that maps text formats (Filtered HTML) to text formats (HTML corrector).',
     'fields' => array(
-      'format' => array(
-        'type' => 'serial',
+      'format_id' => array(
+        'type' => 'int',
         'not null' => TRUE,
-        'description' => 'Primary Key: Unique ID for format.',
+        'default' => 0,
+        'description' => 'Foreign key: The {filter_format}.format_id',
       ),
-      'name' => array(
+      'module' => array(
         'type' => 'varchar',
-        'length' => 255,
+        'length' => 64,
         'not null' => TRUE,
         'default' => '',
-        'description' => 'Name of the text format (Filtered HTML).',
+        'description' => 'The origin module of the filter.',
       ),
-      'roles' => array(
+      'filter_id' => array(
         'type' => 'varchar',
-        'length' => 255,
+        'length' => 64,
         'not null' => TRUE,
         'default' => '',
-        'description' => 'A comma-separated string of roles; references {role}.rid.', // This is bad since you can't use joins, nor index.
-      ),
-      'cache' => array(
-        'type' => 'int',
-        'not null' => TRUE,
-        'default' => 0,
-        'size' => 'tiny',
-        'description' => 'Flag to indicate whether format is cacheable. (1 = cacheable, 0 = not cacheable)',
-      ),
+        'description' => 'The filter referenced unique ID as defined in hook_filter_info().',
+      ),      
       'weight' => array(
         'type' => 'int',
         'not null' => TRUE,
         'default' => 0,
         'size' => 'tiny',
-        'description' => 'Weight of text format to use when listing.',
+        'description' => 'Weight of filter within format.',
       )
     ),
-    'primary key' => array('format'),
-    'unique keys' => array(
-      'name' => array('name'),
+    'primary key' => array('format_id', 'module', 'filter_id'),
+    'foreign keys' => array(
+      'format_id' => array('filter_format' => 'format_id'),
     ),
   );
 
Index: modules/filter/filter.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/filter/filter.test,v
retrieving revision 1.28
diff -u -r1.28 filter.test
--- modules/filter/filter.test	27 Jul 2009 20:15:35 -0000	1.28
+++ modules/filter/filter.test	11 Aug 2009 15:37:07 -0000
@@ -1,7 +1,49 @@
 <?php
 // $Id: filter.test,v 1.28 2009/07/27 20:15:35 dries Exp $
 
-class FilterAdminTestCase extends DrupalWebTestCase {
+/**
+* Class with common helper methods to test filter module.
+*/
+class FilterWebTestCase extends DrupalWebTestCase {
+  protected $filtered_html_format, $full_html_format, $admin_user;
+  
+  /**
+   * Enable modules and create users with specific permissions.
+   */  
+  public function setUp() {
+    parent::setUp();
+    
+    // Create user.
+    $this->admin_user = $this->drupalCreateUser(array('administer filters'));
+    $this->drupalLogin($this->admin_user);
+    
+    $this->filtered_html_format = $this->getFormatByName('Filtered HTML');
+    $this->full_html_format = $this->getFormatByName('Full HTML');
+  }
+  
+  /**
+   * Get a text format from the database based on its name.
+   *
+   * @param $name
+   *   The name of the text format.
+   * @return 
+   *   The format object with the given name.
+   */
+  protected function getFormatByName($name) {
+    $format = db_select('filter_format', 'f')
+      ->fields('f')
+      ->condition('name', $name)
+      ->execute()
+      ->fetchObject();
+    $this->assertFalse(empty($format), t('Text format %format found.', array('%format' => $name)));
+    return $format;
+  }
+}
+
+/**
+ * Tests for format administration functionality.
+ */
+class FilterAdminTestCase extends FilterWebTestCase  {
   public static function getInfo() {
     return array(
       'name' => 'Filter administration functionality',
@@ -9,100 +51,147 @@
       'group' => 'Filter',
     );
   }
-
+  
   /**
-   * Test filter administration functionality.
+   * Test text formats administration functionality.
    */
-  function testFilterAdmin() {
-    // URL filter.
-    $first_filter = 2;
-    // Line filter.
-    $second_filter = 1;
-
-    // Create users.
-    $admin_user = $this->drupalCreateUser(array('administer filters'));
-    $web_user = $this->drupalCreateUser(array('create page content'));
-    $this->drupalLogin($admin_user);
-
-    list($filtered, $full) = $this->checkFilterFormats();
+  function testFormatsAdmin() {
+    $default_format_id = variable_get('filter_default_format', 1);
 
-    // Change default filter.
+    // Check if the default format is set correctly.
+    $this->drupalGet('admin/settings/formats');
+    $this->assertFieldByName('default', $default_format_id, t('Default format set correctly.'));
+
+    // Check if the default format can not be deleted. 
+    $this->assertNoRaw('admin/settings/formats/delete/' . $default_format_id, t('Delete link not found for default format.'));
+    $this->drupalGet('admin/settings/formats/delete/' . $default_format_id);
+    $this->assertRaw(t('The default format cannot be deleted.'), t('The default format cannot be deleted.'));
+    
+    // Change default format and check if set correctly.
     $edit = array();
-    $edit['default'] = $full;
+    $edit['default'] = $this->full_html_format->format_id;
     $this->drupalPost('admin/settings/formats', $edit, t('Save changes'));
-    $this->assertText(t('Default format updated.'), t('Default filter updated successfully.'));
-
-    $this->assertNoRaw('admin/settings/formats/delete/' . $full, t('Delete link not found.'));
-
-    // Add an additional tag.
+    $this->assertText(t('Default format updated.'), t('Default format updated successfully.'));
+    $this->assertFieldByName('default', $this->full_html_format->format_id, t('Default format set correctly.'));
+    
+    // Add a new format.
+    $edit = array();
+    $edit['name'] = $this->randomName();
+    $edit['roles[2]'] = TRUE;
+    $edit['filters[filter_url]'] = TRUE;
+    $edit['filters[filter_html_escape]'] = TRUE;
+    $this->drupalPost('admin/settings/formats/add', $edit, t('Save configuration'));
+    $this->assertRaw(t('Added text format %format.', array('%format' => $edit['name'])), t('New format created.'));
+    $new_format = $this->getFormatByName($edit['name']);
+    $this->assertFalse(empty($new_format), t('Format found in database.'));
+    
+    // Test if format settings were saved correctly. 
+    $this->drupalGet('admin/settings/formats/' . $new_format->format_id);
+    // Check format name.
+    $this->assertFieldByName('name', $edit['name'], t('Format name set correctly')); 
+    // Check if filters are set correctly.
+    $this->assertFieldChecked('edit-filters-filter-url', t('Convert URLs into links filter selected.'));
+    $this->assertFieldChecked('edit-filters-filter-html-escape', t('Escape all HTML filter selected.'));
+    $this->assertNoFieldChecked('edit-filters-filter-autop', t('Convert line breaks filter not selected'));
+    // Check if roles are set correctly
+    $this->assertNoFieldChecked('edit-roles-1', t('anonymous user role not selected'));
+    $this->assertFieldChecked('edit-roles-2', t('authenticated user role selected.'));
+    
+    // Test updating format settings.
+    $edit = array();
+    $edit['name'] = $this->randomName();
+    $edit['roles[1]'] = TRUE;
+    $edit['roles[2]'] = FALSE;
+    $edit['filters[filter_url]'] = TRUE;
+    $edit['filters[filter_html_escape]'] = FALSE;
+    $edit['filters[filter_html]'] = TRUE;    
+    $this->drupalPost('admin/settings/formats/' . $new_format->format_id, $edit, t('Save configuration'));
+    $this->drupalGet('admin/settings/formats/' . $new_format->format_id);
+    $this->assertFieldByName('name', $edit['name'], t('Format name set correctly')); 
+    // Check if filters are set correctly.
+    $this->assertFieldChecked('edit-filters-filter-url', t('Convert URLs into links filter selected.'));
+    $this->assertFieldChecked('edit-filters-filter-html', t('Limit allowed HTML tags selected'));
+    $this->assertNoFieldChecked('edit-filters-filter-html-escape', t('Escape all HTML filter not selected.'));
+    $this->assertNoFieldChecked('edit-filters-filter-autop', t('Convert line breaks filter not selected'));
+    // Check if roles are set correctly
+    $this->assertFieldChecked('edit-roles-1', t('anonymous user role selected'));
+    $this->assertNoFieldChecked('edit-roles-2', t('authenticated user role not selected.'));    
+    
+    // Test format deletion.
+    $this->drupalPost('admin/settings/formats/delete/' . $new_format->format_id, array(), t('Delete'));
+    $this->assertRaw(t('Deleted text format %format.', array('%format' => $edit['name'])), t('Format successfully deleted.'));
+    // Check if the format was deleted from {filter_format} database table.
+    $format = db_select('filter_format', 'f')->fields('f')->condition('format_id', $new_format->format_id)->execute()->fetchObject();
+    $this->assertTrue(empty($format), t('Format successfully deleted from database.'));
+    // Check if the format relationships were deleted from database.
+    $filters = db_select('filter_format_filter', 'f')->fields('f')->condition('format_id', $new_format->format_id)->execute()->fetchObject();
+    $this->assertTrue(empty($filters), t('Format - filters relationship successfully deleted from database.'));
+    
+    
+    // Test format configuration.
     $edit = array();
-    $edit['allowed_html_1'] = '<a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd> <quote>';
-    $this->drupalPost('admin/settings/formats/' . $filtered . '/configure', $edit, t('Save configuration'));
+    // Setting for filter_html. 
+    $edit['allowed_html_' . $this->filtered_html_format->format_id] = '<em> <strong>';
+    // Setting for filter_url.
+    $edit['filter_url_length_' . $this->filtered_html_format->format_id] = 10;
+    $this->drupalPost('admin/settings/formats/' . $this->filtered_html_format->format_id . '/configure', $edit, t('Save configuration'));
     $this->assertText(t('The configuration options have been saved.'), t('Allowed HTML tag added.'));
-
-    $this->assertRaw(htmlentities($edit['allowed_html_1']), t('Tag displayed.'));
-
+    $this->assertFieldByName('allowed_html_' . $this->filtered_html_format->format_id, '<em> <strong>');
+    $this->assertFieldByName('filter_url_length_' . $this->filtered_html_format->format_id, 10);
+    // Check if the cache was cleared when the settings changed.
     $result = db_query('SELECT * FROM {cache_filter}')->fetchObject();
     $this->assertFalse($result, t('Cache cleared.'));
 
-    // Reorder filters.
+    // Test filters reordering within a format.
     $edit = array();
-    $edit['weights[filter/' . $second_filter . ']'] = 1;
-    $edit['weights[filter/' . $first_filter . ']'] = 2;
-    $this->drupalPost('admin/settings/formats/' . $filtered . '/order', $edit, t('Save configuration'));
+    $edit['weights[filter_url]'] = 5;
+    $edit['weights[filter_html]'] = -5;
+    $this->drupalPost('admin/settings/formats/' . $this->filtered_html_format->format_id . '/order', $edit, t('Save configuration'));
     $this->assertText(t('The filter ordering has been saved.'), t('Order saved successfully.'));
+    $order = db_select('filter_format_filter', 'ff')
+      ->fields('ff', array('filter_id'))
+      ->condition('format_id', $this->filtered_html_format->format_id)
+      ->orderBy('weight')
+      ->execute()
+      ->fetchCol();
+    $this->assertTrue(array_search('filter_html', $order) < array_search('filter_url', $order), t('Correct filter ordering within the format.'));
+  }
+}
 
-    $result = db_query('SELECT * FROM {filter} WHERE format = :format ORDER BY weight ASC', array(':format' => $filtered));
-    $filters = array();
-    foreach ($result as $filter) {
-      if ($filter->delta == $second_filter || $filter->delta == $first_filter) {
-        $filters[] = $filter;
-      }
-    }
-    $this->assertTrue(($filters[0]->delta == $second_filter && $filters[1]->delta == $first_filter), t('Order confirmed.'));
-
-    // Add filter.
-    $edit = array();
-    $edit['name'] = $this->randomName();
-    $edit['roles[2]'] = TRUE;
-    $edit['filters[filter/' . $second_filter . ']'] = TRUE;
-    $edit['filters[filter/' . $first_filter . ']'] = TRUE;
-    $this->drupalPost('admin/settings/formats/add', $edit, t('Save configuration'));
-    $this->assertRaw(t('Added text format %format.', array('%format' => $edit['name'])), t('New filter created.'));
-
-    $format = $this->getFilter($edit['name']);
-    $this->assertNotNull($format, t('Format found in database.'));
-
-    if ($format !== NULL) {
-      $this->assertFieldByName('roles[2]', '', t('Role found.'));
-      $this->assertFieldByName('filters[filter/' . $second_filter . ']', '', t('Line break filter found.'));
-      $this->assertFieldByName('filters[filter/' . $first_filter . ']', '', t('Url filter found.'));
-
-      // Delete new filter.
-      $this->drupalPost('admin/settings/formats/delete/' . $format->format, array(), t('Delete'));
-      $this->assertRaw(t('Deleted text format %format.', array('%format' => $edit['name'])), t('Format successfully deleted.'));
-    }
-
-    // Change default filter back.
-    $edit = array();
-    $edit['default'] = $filtered;
-    $this->drupalPost('admin/settings/formats', $edit, t('Save changes'));
-    $this->assertText(t('Default format updated.'), t('Default filter updated successfully.'));
+/**
+ * Test for format access control.
+ */
+class FilterFormatAccessTestCase extends FilterWebTestCase {
+  public static function getInfo() {
+    return array(
+      'name' => 'Formats access control',
+      'description' => 'Test format access control.',
+      'group' => 'Filter',
+    );
+  }
+  function testFormatAccess() {
+    $web_user = $this->drupalCreateUser(array('create page content', 'edit own page content'));
+    $this->drupalLogin($web_user);
 
-    $this->assertNoRaw('admin/settings/formats/delete/' . $filtered, t('Delete link not found.'));
+    $this->drupalGet('node/add/page');
+    $option = $this->xpath('//select[@name="body[0][value_format]"]/option[@value="' . $this->full_html_format->format_id  . '"]');
+    $this->assertTrue(empty($option), t('Format Full HTML can not be accessed.'));
 
+    // Switch user.
+    $this->drupalLogin($this->admin_user);
+   
     // Allow authenticated users on full HTML.
     $edit = array();
     $edit['roles[2]'] = TRUE;
-    $this->drupalPost('admin/settings/formats/' . $full, $edit, t('Save configuration'));
+    $this->drupalPost('admin/settings/formats/' . $this->full_html_format->format_id, $edit, t('Save configuration'));
     $this->assertText(t('The text format settings have been updated.'), t('Full HTML format successfully updated.'));
 
     // Switch user.
-    $this->drupalLogout();
     $this->drupalLogin($web_user);
-
+    
     $this->drupalGet('node/add/page');
-    $this->assertRaw('<option value="' . $full . '">Full HTML</option>', t('Full HTML filter accessible.'));
+    $option = $this->xpath('//select[@name="body[0][value_format]"]/option[@value="' . $this->full_html_format->format_id  . '"]');
+    $this->assertFalse(empty($option), t('Format Full HTML can be accessed.'));
 
     // Use filtered HTML and see if it removes tags that are not allowed.
     $body = $this->randomName();
@@ -111,74 +200,13 @@
     $edit = array();
     $edit['title'] = $this->randomName();
     $edit['body[0][value]'] = $body . '<random>' . $extra_text . '</random>';
-    $edit['body[0][value_format]'] = $filtered;
+    $edit['body[0][value_format]'] = $this->filtered_html_format->format_id;
     $this->drupalPost('node/add/page', $edit, t('Save'));
     $this->assertRaw(t('Page %title has been created.', array('%title' => $edit['title'])), t('Filtered node created.'));
-
     $node = $this->drupalGetNodeByTitle($edit['title']);
     $this->assertTrue($node, t('Node found in database.'));
-
     $this->drupalGet('node/' . $node->nid);
     $this->assertText($body . $extra_text, t('Filter removed invalid tag.'));
-
-    // Switch user.
-    $this->drupalLogout();
-    $this->drupalLogin($admin_user);
-
-    // Clean up.
-    // Allowed tags.
-    $edit = array();
-    $edit['allowed_html_1'] = '<a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>';
-    $this->drupalPost('admin/settings/formats/' . $filtered . '/configure', $edit, t('Save configuration'));
-    $this->assertText(t('The configuration options have been saved.'), t('Changes reverted.'));
-
-    // Full HTML.
-    $edit = array();
-    $edit['roles[2]'] = FALSE;
-    $this->drupalPost('admin/settings/formats/' . $full, $edit, t('Save configuration'));
-    $this->assertText(t('The text format settings have been updated.'), t('Full HTML format successfully reverted.'));
-
-    // Filter order.
-    $edit = array();
-    $edit['weights[filter/' . $second_filter . ']'] = 2;
-    $edit['weights[filter/' . $first_filter . ']'] = 1;
-    $this->drupalPost('admin/settings/formats/' . $filtered . '/order', $edit, t('Save configuration'));
-    $this->assertText(t('The filter ordering has been saved.'), t('Order successfully reverted.'));
-  }
-
-  /**
-   * Query the database to get the two basic formats.
-   *
-   * @return
-   *   An array containing filtered and full filter ids.
-   */
-  function checkFilterFormats() {
-    $result = db_query('SELECT format, name FROM {filter_format}');
-
-    $filtered = -1;
-    $full = -1;
-    foreach ($result as $format) {
-      if ($format->name == 'Filtered HTML') {
-        $filtered = $format->format;
-      }
-      elseif ($format->name == 'Full HTML') {
-        $full = $format->format;
-      }
-    }
-
-    return array($filtered, $full);
-  }
-
-  /**
-   * Get filter by name.
-   *
-   * @param $name
-   *   Name of filter to find.
-   * @return
-   *   A filter object.
-   */
-  function getFilter($name) {
-    return db_query("SELECT * FROM {filter_format} WHERE name = :name", array(':name' => $name))->fetchObject();
   }
 }
 
@@ -195,6 +223,16 @@
       'group' => 'Filter',
     );
   }
+  
+  /**
+   * Create a random fake format, not stored in database. 
+   */
+  protected function createFakeFormat() {
+    $format = new stdClass();
+    $format->name = $this->randomName();
+    $format->format_id = -mt_rand();
+    return $format;
+  }
 
   /**
    * Test the line break filter.
@@ -421,35 +459,35 @@
    *   or better a whitelist approach should be used for that too.
    */
   function testFilter() {
-    // Check that access restriction really works.
+    $format = $this->createFakeFormat();
 
     // HTML filter is not able to secure some tags, these should never be
     // allowed.
-    $f = filter_filter('process', 0, 'no_such_format', '<script />');
+    $f = _filter_html('<script />', $format);
     $this->assertNoNormalized($f, 'script', t('HTML filter should always remove script tags.'));
 
-    $f = filter_filter('process', 0, 'no_such_format', '<iframe />');
+    $f = _filter_html('<iframe />', $format);
     $this->assertNoNormalized($f, 'iframe', t('HTML filter should always remove iframe tags.'));
 
-    $f = filter_filter('process', 0, 'no_such_format', '<object />');
+    $f = _filter_html('<object />', $format);
     $this->assertNoNormalized($f, 'object', t('HTML filter should always remove object tags.'));
 
-    $f = filter_filter('process', 0, 'no_such_format', '<style />');
+    $f = _filter_html('<style />', $format);
     $this->assertNoNormalized($f, 'style', t('HTML filter should always remove style tags.'));
 
     // Some tags make CSRF attacks easier, let the user take the risk herself.
-    $f = filter_filter('process', 0, 'no_such_format', '<img />');
+    $f = _filter_html('<img />', $format);
     $this->assertNoNormalized($f, 'img', t('HTML filter should remove img tags on default.'));
 
-    $f = filter_filter('process', 0, 'no_such_format', '<input />');
+    $f = _filter_html('<input />', $format);
     $this->assertNoNormalized($f, 'img', t('HTML filter should remove input tags on default.'));
 
     // Filtering content of some attributes is infeasible, these shouldn't be
     // allowed too.
-    $f = filter_filter('process', 0, 'no_such_format', '<p style="display: none;" />');
+    $f = _filter_html('<p style="display: none;" />', $format);
     $this->assertNoNormalized($f, 'style', t('HTML filter should remove style attribute on default.'));
 
-    $f = filter_filter('process', 0, 'no_such_format', '<p onerror="alert(0);" />');
+    $f = _filter_html('<p onerror="alert(0);" />', $format);
     $this->assertNoNormalized($f, 'onerror', t('HTML filter should remove on* attributes on default.'));
   }
 
@@ -457,23 +495,24 @@
    * Test the spam deterrent.
    */
   function testNoFollowFilter() {
-    variable_set('filter_html_nofollow_f', TRUE);
+    $format = $this->createFakeFormat();
+    variable_set('filter_html_nofollow_' . $format->format_id, TRUE);
 
     // Test if the rel="nofollow" attribute is added, even if we try to prevent
     // it.
-    $f = _filter_html('<a href="http://www.example.com/">text</a>', 'f');
+    $f = _filter_html('<a href="http://www.example.com/">text</a>', $format);
     $this->assertNormalized($f, 'rel="nofollow"', t('Spam deterrent -- no evasion.'));
 
-    $f = _filter_html('<A href="http://www.example.com/">text</a>', 'f');
+    $f = _filter_html('<A href="http://www.example.com/">text</a>', $format);
     $this->assertNormalized($f, 'rel="nofollow"', t('Spam deterrent evasion -- capital A.'));
 
-    $f = _filter_html("<a/href=\"http://www.example.com/\">text</a>", 'f');
+    $f = _filter_html("<a/href=\"http://www.example.com/\">text</a>", $format);
     $this->assertNormalized($f, 'rel="nofollow"', t('Spam deterrent evasion -- non whitespace character after tag name.'));
 
-    $f = _filter_html("<\0a\0 href=\"http://www.example.com/\">text</a>", 'f');
+    $f = _filter_html("<\0a\0 href=\"http://www.example.com/\">text</a>", $format);
     $this->assertNormalized($f, 'rel="nofollow"', t('Spam deterrent evasion -- some nulls.'));
 
-    $f = _filter_html('<!--[if true]><a href="http://www.example.com/">text</a><![endif]-->', 'f');
+    $f = _filter_html('<!--[if true]><a href="http://www.example.com/">text</a><![endif]-->', $format);
     $this->assertNormalized($f, 'rel="nofollow"', t('Spam deterrent evasion -- link within a comment.'));
   }
 
@@ -516,76 +555,77 @@
    * Test the URL filter.
    */
   function testUrlFilter() {
-    variable_set('filter_url_length_f', 496);
+    $format = $this->createFakeFormat();
+    variable_set('filter_url_length_' . $format->format_id, 496);
 
     // Converting URLs.
-    $f = _filter_url('http://www.example.com/', 'f');
+    $f = _filter_url('http://www.example.com/', $format);
     $this->assertEqual($f, '<a href="http://www.example.com/" title="http://www.example.com/">http://www.example.com/</a>', t('Converting URLs.'));
 
-    $f = _filter_url('http://www.example.com/?a=1&b=2', 'f');
+    $f = _filter_url('http://www.example.com/?a=1&b=2', $format);
     $this->assertEqual($f, '<a href="http://www.example.com/?a=1&amp;b=2" title="http://www.example.com/?a=1&amp;b=2">http://www.example.com/?a=1&amp;b=2</a>', t('Converting URLs -- ampersands.'));
 
-    $f = _filter_url('ftp://user:pass@ftp.example.com/dir1/dir2', 'f');
+    $f = _filter_url('ftp://user:pass@ftp.example.com/dir1/dir2', $format);
     $this->assertEqual($f, '<a href="ftp://user:pass@ftp.example.com/dir1/dir2" title="ftp://user:pass@ftp.example.com/dir1/dir2">ftp://user:pass@ftp.example.com/dir1/dir2</a>', t('Converting URLs -- FTP scheme.'));
 
     // Converting domain names.
-    $f = _filter_url('www.example.com', 'f');
+    $f = _filter_url('www.example.com', $format);
     $this->assertEqual($f, '<a href="http://www.example.com" title="www.example.com">www.example.com</a>', t('Converting domain names.'));
 
-    $f = _filter_url('<li>www.example.com</li>', 'f');
+    $f = _filter_url('<li>www.example.com</li>', $format);
     $this->assertEqual($f, '<li><a href="http://www.example.com" title="www.example.com">www.example.com</a></li>', t('Converting domain names -- domain in a list.'));
 
-    $f = _filter_url('(www.example.com/dir?a=1&b=2#a)', 'f');
+    $f = _filter_url('(www.example.com/dir?a=1&b=2#a)', $format);
     $this->assertEqual($f, '(<a href="http://www.example.com/dir?a=1&amp;b=2#a" title="www.example.com/dir?a=1&amp;b=2#a">www.example.com/dir?a=1&amp;b=2#a</a>)', t('Converting domain names --  domain in parentheses.'));
 
     // Converting e-mail addresses.
-    $f = _filter_url('johndoe@example.com', 'f');
+    $f = _filter_url('johndoe@example.com', $format);
     $this->assertEqual($f, '<a href="mailto:johndoe@example.com">johndoe@example.com</a>', t('Converting e-mail addresses.'));
 
-    $f = _filter_url('aaa@sub.tv', 'f');
+    $f = _filter_url('aaa@sub.tv', $format);
     $this->assertEqual($f, '<a href="mailto:aaa@sub.tv">aaa@sub.tv</a>', t('Converting e-mail addresses -- a short e-mail from Tuvalu.'));
 
     // URL trimming.
-    variable_set('filter_url_length_f', 28);
+    variable_set('filter_url_length_' . $format->format_id , 28);
 
-    $f = _filter_url('http://www.example.com/d/ff.ext?a=1&b=2#a1', 'f');
+    $f = _filter_url('http://www.example.com/d/ff.ext?a=1&b=2#a1', $format);
     $this->assertNormalized($f, 'http://www.example.com/d/ff....', t('URL trimming.'));
 
     // Not breaking existing links.
-    $f = _filter_url('<a href="http://www.example.com">www.example.com</a>', 'f');
+    $f = _filter_url('<a href="http://www.example.com">www.example.com</a>', $format);
     $this->assertEqual($f, '<a href="http://www.example.com">www.example.com</a>', t('Converting URLs -- do not break existing links.'));
 
-    $f = _filter_url('<a href="foo">http://www.example.com</a>', 'f');
+    $f = _filter_url('<a href="foo">http://www.example.com</a>', $format);
     $this->assertEqual($f, '<a href="foo">http://www.example.com</a>', t('Converting URLs -- do not break existing, relative links.'));
 
     // Addresses within some tags such as code or script should not be converted.
-    $f = _filter_url('<code>http://www.example.com</code>', 'f');
+    $f = _filter_url('<code>http://www.example.com</code>', $format);
     $this->assertEqual($f, '<code>http://www.example.com</code>', t('Converting URLs -- skip code contents.'));
 
-    $f = _filter_url('<code><em>http://www.example.com</em></code>', 'f');
+    $f = _filter_url('<code><em>http://www.example.com</em></code>', $format);
     $this->assertEqual($f, '<code><em>http://www.example.com</em></code>', t('Converting URLs -- really skip code contents.'));
 
-    $f = _filter_url('<script>http://www.example.com</script>', 'f');
+    $f = _filter_url('<script>http://www.example.com</script>', $format);
     $this->assertEqual($f, '<script>http://www.example.com</script>', t('Converting URLs -- do not process scripts.'));
 
     // Addresses in attributes should not be converted.
-    $f = _filter_url('<p xmlns="http://www.example.com" />', 'f');
+    $f = _filter_url('<p xmlns="http://www.example.com" />', $format);
     $this->assertEqual($f, '<p xmlns="http://www.example.com" />', t('Converting URLs -- do not convert addresses in attributes.'));
 
-    $f = _filter_url('<a title="Go to www.example.com" href="http://www.example.com">text</a>', 'f');
+    $f = _filter_url('<a title="Go to www.example.com" href="http://www.example.com">text</a>', $format);
     $this->assertEqual($f, '<a title="Go to www.example.com" href="http://www.example.com">text</a>', t('Converting URLs -- do not break existing links with custom title attribute.'));
 
     // Even though a dot at the end of a URL can indicate a fully qualified
     // domain name, such usage is rare compared to using a link at the end
     // of a sentence, so remove the dot from the link.
     // @todo It can also be used at the end of a filename or a query string.
-    $f = _filter_url('www.example.com.', 'f');
+    $f = _filter_url('www.example.com.', $format);
     $this->assertEqual($f, '<a href="http://www.example.com" title="www.example.com">www.example.com</a>.', t('Converting URLs -- do not recognize a dot at the end of a domain name (FQDNs).'));
 
-    $f = _filter_url('http://www.example.com.', 'f');
+    $f = _filter_url('http://www.example.com.', $format);
     $this->assertEqual($f, '<a href="http://www.example.com" title="http://www.example.com">http://www.example.com</a>.', t('Converting URLs -- do not recognize a dot at the end of an URL (FQDNs).'));
 
-    $f = _filter_url('www.example.com/index.php?a=.', 'f');
+    $f = _filter_url('www.example.com/index.php?a=.', $format);
     $this->assertEqual($f, '<a href="http://www.example.com/index.php?a=" title="www.example.com/index.php?a=">www.example.com/index.php?a=</a>.', t('Converting URLs -- do forget about a dot at the end of a query string.'));
   }
 
@@ -677,22 +717,6 @@
     $this->assertEqual($f, '<p>دروبال</p>', t('HTML corrector -- Encoding is correctly kept.'));
   }
 
-  function createFormat($filter) {
-    $edit = array(
-      'name' => $this->randomName(),
-      'roles[2]' => TRUE,
-      'filters[filter/' . $filter . ']' => TRUE,
-    );
-    $this->drupalPost('admin/settings/filter/add', $edit, t('Save configuration'));
-    return db_query("SELECT * FROM {filter_format} WHERE name = :name", array(':name' => $edit['name']))->fetchObject();
-  }
-
-  function deleteFormat($format) {
-    if ($format !== NULL) {
-      $this->drupalPost('admin/settings/formats/delete/' . $format->format, array(), t('Delete'));
-    }
-  }
-
   /**
    * Asserts that a text transformed to lowercase with HTML entities decoded does contains a given string.
    *
Index: modules/filter/filter.pages.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/filter/filter.pages.inc,v
retrieving revision 1.7
diff -u -r1.7 filter.pages.inc
--- modules/filter/filter.pages.inc	8 Mar 2009 21:25:18 -0000	1.7
+++ modules/filter/filter.pages.inc	11 Aug 2009 15:36:53 -0000
@@ -11,12 +11,16 @@
  * Menu callback; show a page with long filter tips.
  */
 function filter_tips_long() {
-  $format = arg(2);
-  if ($format) {
+  $format_id = arg(2);
+  $output = '';
+  if ($format_id) {
+    $format = filter_format_load($format_id);
     $output = theme('filter_tips', _filter_tips($format, TRUE), TRUE);
   }
   else {
-    $output = theme('filter_tips', _filter_tips(-1, TRUE), TRUE);
+    foreach (filter_formats() as $format) {
+      $output .= theme('filter_tips', _filter_tips($format, TRUE), TRUE);
+    }
   }
   return $output;
 }
Index: modules/filter/filter.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/filter/filter.module,v
retrieving revision 1.266
diff -u -r1.266 filter.module
--- modules/filter/filter.module	11 Aug 2009 12:00:19 -0000	1.266
+++ modules/filter/filter.module	11 Aug 2009 15:36:52 -0000
@@ -88,10 +88,10 @@
     'type' => MENU_LOCAL_TASK,
     'weight' => 1,
   );
-  $items['admin/settings/formats/delete'] = array(
+  $items['admin/settings/formats/delete/%filter_format'] = array(
     'title' => 'Delete text format',
     'page callback' => 'drupal_get_form',
-    'page arguments' => array('filter_admin_delete'),
+    'page arguments' => array('filter_admin_delete', 4),
     'access arguments' => array('administer filters'),
     'type' => MENU_CALLBACK,
   );
@@ -165,130 +165,122 @@
   cache_clear_all(NULL, 'cache_filter');
 }
 
-/**
- * Implement hook_filter_tips().
- */
-function filter_filter_tips($delta, $format, $long = FALSE) {
+function _filter_html_tips($format, $long = FALSE) {
   global $base_url;
-  switch ($delta) {
-    case 0:
-      if ($allowed_html = variable_get("allowed_html_$format", '<a> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd>')) {
-        switch ($long) {
-          case 0:
-            return t('Allowed HTML tags: @tags', array('@tags' => $allowed_html));
-          case 1:
-            $output = '<p>' . t('Allowed HTML tags: @tags', array('@tags' => $allowed_html)) . '</p>';
-            if (!variable_get("filter_html_help_$format", 1)) {
-              return $output;
-            }
-
-            $output .= '<p>' . t('This site allows HTML content. While learning all of HTML may feel intimidating, learning how to use a very small number of the most basic HTML "tags" is very easy. This table provides examples for each tag that is enabled on this site.') . '</p>';
-            $output .= '<p>' . t('For more information see W3C\'s <a href="@html-specifications">HTML Specifications</a> or use your favorite search engine to find other sites that explain HTML.', array('@html-specifications' => 'http://www.w3.org/TR/html/')) . '</p>';
-            $tips = array(
-              'a' => array( t('Anchors are used to make links to other pages.'), '<a href="' . $base_url . '">' . variable_get('site_name', 'Drupal') . '</a>'),
-              'br' => array( t('By default line break tags are automatically added, so use this tag to add additional ones. Use of this tag is different because it is not used with an open/close pair like all the others. Use the extra " /" inside the tag to maintain XHTML 1.0 compatibility'), t('Text with <br />line break')),
-              'p' => array( t('By default paragraph tags are automatically added, so use this tag to add additional ones.'), '<p>' . t('Paragraph one.') . '</p> <p>' . t('Paragraph two.') . '</p>'),
-              'strong' => array( t('Strong'), '<strong>' . t('Strong') . '</strong>'),
-              'em' => array( t('Emphasized'), '<em>' . t('Emphasized') . '</em>'),
-              'cite' => array( t('Cited'), '<cite>' . t('Cited') . '</cite>'),
-              'code' => array( t('Coded text used to show programming source code'), '<code>' . t('Coded') . '</code>'),
-              'b' => array( t('Bolded'), '<b>' . t('Bolded') . '</b>'),
-              'u' => array( t('Underlined'), '<u>' . t('Underlined') . '</u>'),
-              'i' => array( t('Italicized'), '<i>' . t('Italicized') . '</i>'),
-              'sup' => array( t('Superscripted'), t('<sup>Super</sup>scripted')),
-              'sub' => array( t('Subscripted'), t('<sub>Sub</sub>scripted')),
-              'pre' => array( t('Preformatted'), '<pre>' . t('Preformatted') . '</pre>'),
-              'abbr' => array( t('Abbreviation'), t('<abbr title="Abbreviation">Abbrev.</abbr>')),
-              'acronym' => array( t('Acronym'), t('<acronym title="Three-Letter Acronym">TLA</acronym>')),
-              'blockquote' => array( t('Block quoted'), '<blockquote>' . t('Block quoted') . '</blockquote>'),
-              'q' => array( t('Quoted inline'), '<q>' . t('Quoted inline') . '</q>'),
-              // Assumes and describes tr, td, th.
-              'table' => array( t('Table'), '<table> <tr><th>' . t('Table header') . '</th></tr> <tr><td>' . t('Table cell') . '</td></tr> </table>'),
-              'tr' => NULL, 'td' => NULL, 'th' => NULL,
-              'del' => array( t('Deleted'), '<del>' . t('Deleted') . '</del>'),
-              'ins' => array( t('Inserted'), '<ins>' . t('Inserted') . '</ins>'),
-               // Assumes and describes li.
-              'ol' => array( t('Ordered list - use the &lt;li&gt; to begin each list item'), '<ol> <li>' . t('First item') . '</li> <li>' . t('Second item') . '</li> </ol>'),
-              'ul' => array( t('Unordered list - use the &lt;li&gt; to begin each list item'), '<ul> <li>' . t('First item') . '</li> <li>' . t('Second item') . '</li> </ul>'),
-              'li' => NULL,
-              // Assumes and describes dt and dd.
-              'dl' => array( t('Definition lists are similar to other HTML lists. &lt;dl&gt; begins the definition list, &lt;dt&gt; begins the definition term and &lt;dd&gt; begins the definition description.'), '<dl> <dt>' . t('First term') . '</dt> <dd>' . t('First definition') . '</dd> <dt>' . t('Second term') . '</dt> <dd>' . t('Second definition') . '</dd> </dl>'),
-              'dt' => NULL, 'dd' => NULL,
-              'h1' => array( t('Heading'), '<h1>' . t('Title') . '</h1>'),
-              'h2' => array( t('Heading'), '<h2>' . t('Subtitle') . '</h2>'),
-              'h3' => array( t('Heading'), '<h3>' . t('Subtitle three') . '</h3>'),
-              'h4' => array( t('Heading'), '<h4>' . t('Subtitle four') . '</h4>'),
-              'h5' => array( t('Heading'), '<h5>' . t('Subtitle five') . '</h5>'),
-              'h6' => array( t('Heading'), '<h6>' . t('Subtitle six') . '</h6>')
-            );
-            $header = array(t('Tag Description'), t('You Type'), t('You Get'));
-            preg_match_all('/<([a-z0-9]+)[^a-z0-9]/i', $allowed_html, $out);
-            foreach ($out[1] as $tag) {
-              if (array_key_exists($tag, $tips)) {
-                if ($tips[$tag]) {
-                  $rows[] = array(
-                    array('data' => $tips[$tag][0], 'class' => 'description'),
-                    array('data' => '<code>' . check_plain($tips[$tag][1]) . '</code>', 'class' => 'type'),
-                    array('data' => $tips[$tag][1], 'class' => 'get')
-                  );
-                }
-              }
-              else {
-                $rows[] = array(
-                  array('data' => t('No help provided for tag %tag.', array('%tag' => $tag)), 'class' => 'description', 'colspan' => 3),
-                );
-              }
-            }
-            $output .= theme('table', $header, $rows);
-
-            $output .= '<p>' . t('Most unusual characters can be directly entered without any problems.') . '</p>';
-            $output .= '<p>' . t('If you do encounter problems, try using HTML character entities. A common example looks like &amp;amp; for an ampersand &amp; character. For a full list of entities see HTML\'s <a href="@html-entities">entities</a> page. Some of the available characters include:', array('@html-entities' => 'http://www.w3.org/TR/html4/sgml/entities.html')) . '</p>';
+  if ($allowed_html = variable_get("allowed_html_$format->format_id", '<a> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd>')) {
+    switch ($long) {
+      case 0:
+        return t('Allowed HTML tags: @tags', array('@tags' => $allowed_html));
+      case 1:
+        $output = '<p>' . t('Allowed HTML tags: @tags', array('@tags' => $allowed_html)) . '</p>';
+        if (!variable_get("filter_html_help_$format->format_id", 1)) {
+          return $output;
+        }
 
-            $entities = array(
-              array( t('Ampersand'), '&amp;'),
-              array( t('Greater than'), '&gt;'),
-              array( t('Less than'), '&lt;'),
-              array( t('Quotation mark'), '&quot;'),
-            );
-            $header = array(t('Character Description'), t('You Type'), t('You Get'));
-            unset($rows);
-            foreach ($entities as $entity) {
+        $output .= '<p>' . t('This site allows HTML content. While learning all of HTML may feel intimidating, learning how to use a very small number of the most basic HTML "tags" is very easy. This table provides examples for each tag that is enabled on this site.') . '</p>';
+        $output .= '<p>' . t('For more information see W3C\'s <a href="@html-specifications">HTML Specifications</a> or use your favorite search engine to find other sites that explain HTML.', array('@html-specifications' => 'http://www.w3.org/TR/html/')) . '</p>';
+        $tips = array(
+          'a' => array( t('Anchors are used to make links to other pages.'), '<a href="' . $base_url . '">' . variable_get('site_name', 'Drupal') . '</a>'),
+          'br' => array( t('By default line break tags are automatically added, so use this tag to add additional ones. Use of this tag is different because it is not used with an open/close pair like all the others. Use the extra " /" inside the tag to maintain XHTML 1.0 compatibility'), t('Text with <br />line break')),
+          'p' => array( t('By default paragraph tags are automatically added, so use this tag to add additional ones.'), '<p>' . t('Paragraph one.') . '</p> <p>' . t('Paragraph two.') . '</p>'),
+          'strong' => array( t('Strong'), '<strong>' . t('Strong') . '</strong>'),
+          'em' => array( t('Emphasized'), '<em>' . t('Emphasized') . '</em>'),
+          'cite' => array( t('Cited'), '<cite>' . t('Cited') . '</cite>'),
+          'code' => array( t('Coded text used to show programming source code'), '<code>' . t('Coded') . '</code>'),
+          'b' => array( t('Bolded'), '<b>' . t('Bolded') . '</b>'),
+          'u' => array( t('Underlined'), '<u>' . t('Underlined') . '</u>'),
+          'i' => array( t('Italicized'), '<i>' . t('Italicized') . '</i>'),
+          'sup' => array( t('Superscripted'), t('<sup>Super</sup>scripted')),
+          'sub' => array( t('Subscripted'), t('<sub>Sub</sub>scripted')),
+          'pre' => array( t('Preformatted'), '<pre>' . t('Preformatted') . '</pre>'),
+          'abbr' => array( t('Abbreviation'), t('<abbr title="Abbreviation">Abbrev.</abbr>')),
+          'acronym' => array( t('Acronym'), t('<acronym title="Three-Letter Acronym">TLA</acronym>')),
+          'blockquote' => array( t('Block quoted'), '<blockquote>' . t('Block quoted') . '</blockquote>'),
+          'q' => array( t('Quoted inline'), '<q>' . t('Quoted inline') . '</q>'),
+          // Assumes and describes tr, td, th.
+          'table' => array( t('Table'), '<table> <tr><th>' . t('Table header') . '</th></tr> <tr><td>' . t('Table cell') . '</td></tr> </table>'),
+          'tr' => NULL, 'td' => NULL, 'th' => NULL,
+          'del' => array( t('Deleted'), '<del>' . t('Deleted') . '</del>'),
+          'ins' => array( t('Inserted'), '<ins>' . t('Inserted') . '</ins>'),
+           // Assumes and describes li.
+          'ol' => array( t('Ordered list - use the &lt;li&gt; to begin each list item'), '<ol> <li>' . t('First item') . '</li> <li>' . t('Second item') . '</li> </ol>'),
+          'ul' => array( t('Unordered list - use the &lt;li&gt; to begin each list item'), '<ul> <li>' . t('First item') . '</li> <li>' . t('Second item') . '</li> </ul>'),
+          'li' => NULL,
+          // Assumes and describes dt and dd.
+          'dl' => array( t('Definition lists are similar to other HTML lists. &lt;dl&gt; begins the definition list, &lt;dt&gt; begins the definition term and &lt;dd&gt; begins the definition description.'), '<dl> <dt>' . t('First term') . '</dt> <dd>' . t('First definition') . '</dd> <dt>' . t('Second term') . '</dt> <dd>' . t('Second definition') . '</dd> </dl>'),
+          'dt' => NULL, 'dd' => NULL,
+          'h1' => array( t('Heading'), '<h1>' . t('Title') . '</h1>'),
+          'h2' => array( t('Heading'), '<h2>' . t('Subtitle') . '</h2>'),
+          'h3' => array( t('Heading'), '<h3>' . t('Subtitle three') . '</h3>'),
+          'h4' => array( t('Heading'), '<h4>' . t('Subtitle four') . '</h4>'),
+          'h5' => array( t('Heading'), '<h5>' . t('Subtitle five') . '</h5>'),
+          'h6' => array( t('Heading'), '<h6>' . t('Subtitle six') . '</h6>')
+        );
+        $header = array(t('Tag Description'), t('You Type'), t('You Get'));
+        preg_match_all('/<([a-z0-9]+)[^a-z0-9]/i', $allowed_html, $out);
+        foreach ($out[1] as $tag) {
+          if (array_key_exists($tag, $tips)) {
+            if ($tips[$tag]) {
               $rows[] = array(
-                array('data' => $entity[0], 'class' => 'description'),
-                array('data' => '<code>' . check_plain($entity[1]) . '</code>', 'class' => 'type'),
-                array('data' => $entity[1], 'class' => 'get')
+                array('data' => $tips[$tag][0], 'class' => 'description'),
+                array('data' => '<code>' . check_plain($tips[$tag][1]) . '</code>', 'class' => 'type'),
+                array('data' => $tips[$tag][1], 'class' => 'get')
               );
             }
-            $output .= theme('table', $header, $rows);
-            return $output;
+          }
+          else {
+            $rows[] = array(
+              array('data' => t('No help provided for tag %tag.', array('%tag' => $tag)), 'class' => 'description', 'colspan' => 3),
+            );
+          }
         }
-      }
-      break;
+        $output .= theme('table', $header, $rows);
 
-    case 1:
-      switch ($long) {
-        case 0:
-          return t('Lines and paragraphs break automatically.');
-        case 1:
-          return t('Lines and paragraphs are automatically recognized. The &lt;br /&gt; line break, &lt;p&gt; paragraph and &lt;/p&gt; close paragraph tags are inserted automatically. If paragraphs are not recognized simply add a couple blank lines.');
-      }
-      break;
+        $output .= '<p>' . t('Most unusual characters can be directly entered without any problems.') . '</p>';
+        $output .= '<p>' . t('If you do encounter problems, try using HTML character entities. A common example looks like &amp;amp; for an ampersand &amp; character. For a full list of entities see HTML\'s <a href="@html-entities">entities</a> page. Some of the available characters include:', array('@html-entities' => 'http://www.w3.org/TR/html4/sgml/entities.html')) . '</p>';
 
-    case 2:
-      return t('Web page addresses and e-mail addresses turn into links automatically.');
-      break;
-
-    case 4:
-      return t('No HTML tags allowed.');
-      break;
+        $entities = array(
+          array( t('Ampersand'), '&amp;'),
+          array( t('Greater than'), '&gt;'),
+          array( t('Less than'), '&lt;'),
+          array( t('Quotation mark'), '&quot;'),
+        );
+        $header = array(t('Character Description'), t('You Type'), t('You Get'));
+        unset($rows);
+        foreach ($entities as $entity) {
+          $rows[] = array(
+            array('data' => $entity[0], 'class' => 'description'),
+            array('data' => '<code>' . check_plain($entity[1]) . '</code>', 'class' => 'type'),
+            array('data' => $entity[1], 'class' => 'get')
+          );
+        }
+        $output .= theme('table', $header, $rows);
+        return $output;
+    }
+  }
+}
 
+function _filter_autop_tips($format, $long = FALSE) {
+  switch ($long) {
+    case 0:
+      return t('Lines and paragraphs break automatically.');
+    case 1:
+      return t('Lines and paragraphs are automatically recognized. The &lt;br /&gt; line break, &lt;p&gt; paragraph and &lt;/p&gt; close paragraph tags are inserted automatically. If paragraphs are not recognized simply add a couple blank lines.');
   }
 }
 
+function _filter_url_tips($format, $long = FALSE) {
+  return t('Web page addresses and e-mail addresses turn into links automatically.');
+}
+
+function _filter_html_escape_tips($format, $long = FALSE) {
+  return t('No HTML tags allowed.');
+}
+
 /**
  * Retrieve a list of text formats.
  */
-function filter_formats($index = NULL) {
+function filter_formats($format_id = NULL) {
   global $user;
   static $formats;
 
@@ -298,50 +290,66 @@
   if (!isset($formats)) {
     $formats = array();
 
-    $query = db_select('filter_format', 'f');
-    $query->addField('f', 'format', 'format');
-    $query->addField('f', 'name', 'name');
-    $query->addField('f', 'roles', 'roles');
-    $query->addField('f', 'cache', 'cache');
-    $query->addField('f', 'weight', 'weight');
-    $query->orderBy('weight');
+    $query = db_select('filter_format', 'f')->fields('f')->orderBy('weight');
 
     // Build query for selecting the format(s) based on the user's roles.
     if (!$all) {
-      $or = db_or()->condition('format', variable_get('filter_default_format', 1));
+      $or = db_or()->condition('format_id', variable_get('filter_default_format', 1));
       foreach ($user->roles as $rid => $role) {
         $or->condition('roles', '%' . (int)$rid . '%', 'LIKE');
       }
       $query->condition($or);
     }
 
-    $formats = $query->execute()->fetchAllAssoc('format');
+    $formats = $query->execute()->fetchAllAssoc('format_id');
   }
-  if (isset($index)) {
-    return isset($formats[$index]) ? $formats[$index] : FALSE;
+
+  if (isset($format_id)) {
+    return isset($formats[$format_id]) ? $formats[$format_id] : FALSE;
   }
   return $formats;
 }
 
 /**
- * Build a list of all filters.
+ * Collect the filter definitions from modules that implement hook_filter_info().
  */
-function filter_list_all() {
-  $filters = array();
+function filter_build() {
+  static $filter_info;
 
-  foreach (module_implements('filter') as $module) {
-    $function = $module . '_filter';
-    $list = $function('list');
-    if (isset($list) && is_array($list)) {
-      foreach ($list as $delta => $name) {
-        $filters[$module . '/' . $delta] = (object)array('module' => $module, 'delta' => $delta, 'name' => $name);
+  if (!isset($filter_info)) {
+    // We need to manually call each module so that we can know which module
+    // a given item came from.
+    $filter_info = array();
+    foreach (module_implements('filter_info') as $module) {
+      $info = call_user_func($module . '_filter_info');
+      if (isset($info) && is_array($info)) {
+        foreach ($info as $filter_id => $filter) {
+          $filter += array(
+            'process callback' => '',
+            'settings callback' => '',
+            'tips callback' => '',
+            'cache' => TRUE,
+          );
+          $item = (object)$filter;
+          $item->process_callback = $filter['process callback'];
+          $item->settings_callback = $filter['settings callback'];
+          $item->tips_callback = $filter['tips callback'];
+          $item->module = $module;
+          $filter_info[$filter_id] = $item;
+        }
       }
     }
+    drupal_alter('filter_info', $filter_info);
+    uasort($filter_info, '_filter_list_cmp');
   }
+  return $filter_info;
+}
 
-  uasort($filters, '_filter_list_cmp');
-
-  return $filters;
+/**
+ * Build a list of all filters.
+ */
+function filter_list_all() {
+  return filter_build();
 }
 
 /**
@@ -352,42 +360,53 @@
 }
 
 /**
- * Resolve a format id, including the default format.
+ * Resolve a format ID, including the default format.
  */
 function filter_resolve_format($format) {
   return $format == FILTER_FORMAT_DEFAULT ? variable_get('filter_default_format', 1) : $format;
 }
+
+/**
+ * Check is the given format object is configured as the deafult format.
+ *
+ * @param $format
+ *   The format object.
+ * @return
+ *   TRUE if the given format is the default format, FALSE otherwise.
+ */
+function filter_is_default_format($format) {
+  return  $format->format_id == variable_get('filter_default_format', 1);
+}
+
 /**
  * Check if text in a certain text format is allowed to be cached.
  */
-function filter_format_allowcache($format) {
+function filter_format_allowcache($format_id) {
   static $cache = array();
-  $format = filter_resolve_format($format);
-  if (!isset($cache[$format])) {
-    $cache[$format] = db_query('SELECT cache FROM {filter_format} WHERE format = :format', array(':format' => $format))->fetchField();
+  $format_id = filter_resolve_format($format_id);
+  if (!isset($cache[$format_id])) {
+    $cache[$format_id] = db_select('filter_format', 'f')->fields('f', array('cache'))->condition('format_id', $format_id)->execute()->fetchField();
   }
-  return $cache[$format];
+  return $cache[$format_id];
 }
 
 /**
  * Retrieve a list of filters for a certain format.
  */
-function filter_list_format($format) {
-  static $filters = array();
-
-  if (!isset($filters[$format])) {
-    $filters[$format] = array();
-    $result = db_query("SELECT * FROM {filter} WHERE format = :format ORDER BY weight, module, delta", array(':format' => (int) $format));
+function filter_list_format($format_id) {
+  $filters = &drupal_static(__FUNCTION__);
+  $filter_info = filter_list_all();
+
+  if (!isset($filters[$format_id])) {
+    $filters[$format_id] = array();
+    $result = db_query("SELECT filter_id, ff.weight FROM {filter_format} f INNER JOIN {filter_format_filter} ff ON f.format_id = ff.format_id WHERE f.format_id = :format_id ORDER BY ff.weight", array(':format_id' => $format_id));
     foreach ($result as $filter) {
-      $list = module_invoke($filter->module, 'filter', 'list');
-      if (isset($list) && is_array($list) && isset($list[$filter->delta])) {
-        $filter->name = $list[$filter->delta];
-        $filters[$format][$filter->module . '/' . $filter->delta] = $filter;
-      }
+      $filters[$format_id][$filter->filter_id] = $filter_info[$filter->filter_id];
+      $filters[$format_id][$filter->filter_id]->weight = $filter->weight;
     }
   }
 
-  return $filters[$format];
+  return $filters[$format_id];
 }
 
 /**
@@ -410,8 +429,8 @@
  *
  * @param $text
  *   The text to be filtered.
- * @param $format
- *   The format of the text to be filtered. Specify FILTER_FORMAT_DEFAULT for
+ * @param $format_id
+ *   The format ID of the text to be filtered. Specify FILTER_FORMAT_DEFAULT for
  *   the default format.
  * @param $langcode
  *   Optional: the language code of the text to be filtered, e.g. 'en' for
@@ -428,13 +447,13 @@
  *   The caller may set this to FALSE when the output is already cached
  *   elsewhere to avoid duplicate cache lookups and storage.
  */
-function check_markup($text, $format = FILTER_FORMAT_DEFAULT, $langcode = '', $check = TRUE, $cache = TRUE) {
+function check_markup($text, $format_id = FILTER_FORMAT_DEFAULT, $langcode = '', $check = TRUE, $cache = TRUE) {
   // When $check = TRUE, do an access check on $format.
-  if (isset($text) && (!$check || filter_access($format))) {
-    $format = filter_resolve_format($format);
+  if (isset($text) && (!$check || filter_access($format_id))) {
+    $format_id = filter_resolve_format($format_id);
 
     // Check for a cached version of this piece of text.
-    $cache_id = $format . ':' . $langcode . ':' . md5($text);
+    $cache_id = $format_id . ':' . $langcode . ':' . md5($text);
     if ($cache && $cached = cache_get($cache_id, 'cache_filter')) {
       return $cached->data;
     }
@@ -444,20 +463,27 @@
     $text = str_replace(array("\r\n", "\r"), "\n", $text);
 
     // Get a complete list of filters, ordered properly.
-    $filters = filter_list_format($format);
+    $filters = filter_list_format($format_id);
+
+    // Load a format object to pass to filters callback in case they need it.
+    $format = filter_format_load($format_id);
 
     // Give filters the chance to escape HTML-like data such as code or formulas.
     foreach ($filters as $filter) {
-      $text = module_invoke($filter->module, 'filter', 'prepare', $filter->delta, $format, $text, $langcode, $cache_id);
+      if (isset($filter->prepare) && drupal_function_exists($filter->prepare)) {
+        $text = call_user_func($filter->prepare, $text, $format, $langcode, $cache_id);
+      }
     }
 
     // Perform filtering.
     foreach ($filters as $filter) {
-      $text = module_invoke($filter->module, 'filter', 'process', $filter->delta, $format, $text, $langcode, $cache_id);
+      if (isset($filter->process_callback) && drupal_function_exists($filter->process_callback)) {
+        $text = call_user_func($filter->process_callback, $text, $format, $langcode, $cache_id);
+      }
     }
 
     // Store in cache with a minimum expiration time of 1 day.
-    if ($cache && filter_format_allowcache($format)) {
+    if ($cache && filter_format_allowcache($format_id)) {
       cache_set($cache_id, $text, 'cache_filter', REQUEST_TIME + (60 * 60 * 24));
     }
   }
@@ -500,8 +526,8 @@
     '#weight' => 2,
   );
   foreach ($formats as $format) {
-    $options[$format->format] = $format->name;
-    $form['format_guidelines'][$format->format] = array(
+    $options[$format->format_id] = $format->name;
+    $form['format_guidelines'][$format->format_id] = array(
       '#markup' => theme('filter_guidelines', $format),
     );
   }
@@ -528,14 +554,14 @@
 /**
  * Returns TRUE if the user is allowed to access this format.
  */
-function filter_access($format) {
-  $format = filter_resolve_format($format);
-  if (user_access('administer filters') || ($format == variable_get('filter_default_format', 1))) {
+function filter_access($format_id) {
+  $format_id = filter_resolve_format($format_id);
+  if (user_access('administer filters') || ($format_id == variable_get('filter_default_format', 1))) {
     return TRUE;
   }
   else {
     $formats = filter_formats();
-    return isset($formats[$format]);
+    return isset($formats[$format_id]);
   }
 }
 
@@ -545,26 +571,16 @@
 
 
 /**
- * Helper function for fetching filter tips.
+ * Helper function for fetching filter tips for the given format.
  */
 function _filter_tips($format, $long = FALSE) {
-  if ($format == -1) {
-    $formats = filter_formats();
-  }
-  else {
-    $formats = array(db_query("SELECT * FROM {filter_format} WHERE format = :format", array(':format' => $format))->fetchObject());
-  }
-
-  $tips = array();
+  $filters = filter_list_format($format->format_id);
 
-  foreach ($formats as $format) {
-    $filters = filter_list_format($format->format);
-
-    $tips[$format->name] = array();
-    foreach ($filters as $id => $filter) {
-      if ($tip = module_invoke($filter->module, 'filter_tips', $filter->delta, $format->format, $long)) {
-        $tips[$format->name][] = array('tip' => $tip, 'id' => $id);
-      }
+  $tips[$format->name] = array();
+  foreach ($filters as $filter_id => $filter) {
+    if (isset($filter->tips_callback) && drupal_function_exists($filter->tips_callback)) {
+      $tip = call_user_func($filter->tips_callback, $format, $long);
+      $tips[$format->name][] = array('tip' => $tip, 'id' => $filter_id);
     }
   }
 
@@ -578,7 +594,7 @@
  * and returns a full DOMDocument object that represents this document.
  * You can use filter_dom_serialize() to serialize this DOMDocument
  * back to a XHTML snippet.
- * 
+ *
  * @param $text
  *   The partial (X)HTML snippet to load. Invalid mark-up
  *   will be corrected on import.
@@ -600,7 +616,7 @@
  *
  * The resulting XHTML snippet will be properly formatted
  * to be compatible with HTML user agents.
- * 
+ *
  * @param $dom_document
  *   A DOMDocument object to serialize, only the tags below
  *   the first <body> node will be converted.
@@ -632,7 +648,7 @@
  */
 function theme_filter_guidelines($format) {
   $name = isset($format->name) ? '<label>' . $format->name . ':</label>' : '';
-  return '<div id="filter-guidelines-' . $format->format . '" class="filter-guidelines-item">' . $name . theme('filter_tips', _filter_tips($format->format, FALSE)) . '</div>';
+  return '<div id="filter-guidelines-' . $format->format_id . '" class="filter-guidelines-item">' . $name . theme('filter_tips', _filter_tips($format, FALSE)) . '</div>';
 }
 
 /**
@@ -642,9 +658,9 @@
  */
 
 /**
- * Implement hook_filter().
- *  
- * Set up a basic set of essential filters:
+ * Implement hook_filter_info().
+ *
+ * Define a basic set of essential filters:
  * - Limit allowed HTML tags:
  *     Restricts user-supplied HTML to certain tags, and removes dangerous
  *     components in allowed tags.
@@ -657,56 +673,39 @@
  * - Escape all HTML:
  *     Converts all HTML tags into visible text.
  */
-function filter_filter($op, $delta = 0, $format = -1, $text = '') {
-  switch ($op) {
-    case 'list':
-      return array(0 => t('Limit allowed HTML tags'), 1 => t('Convert line breaks'), 2 => t('Convert URLs into links'), 3 => t('Correct broken HTML'), 4 => t('Escape all HTML'));
-
-    case 'description':
-      switch ($delta) {
-        case 0:
-          return t('Allows you to restrict the HTML tags the user can use. It will also remove harmful content such as JavaScript events, JavaScript URLs and CSS styles from those tags that are not removed.');
-        case 1:
-          return t('Converts line breaks into HTML (i.e. &lt;br&gt; and &lt;p&gt;) tags.');
-        case 2:
-          return t('Turns web and e-mail addresses into clickable links.');
-        case 3:
-          return t('Corrects faulty and chopped off HTML in postings.');
-        case 4:
-          return t('Escapes all HTML tags, so they will be visible instead of being effective.');
-        default:
-          return;
-      }
-
-    case 'process':
-      switch ($delta) {
-        case 0:
-          return _filter_html($text, $format);
-        case 1:
-          return _filter_autop($text);
-        case 2:
-          return _filter_url($text, $format);
-        case 3:
-          return _filter_htmlcorrector($text);
-        case 4:
-          return trim(check_plain($text));
-        default:
-          return $text;
-      }
-
-    case 'settings':
-      switch ($delta) {
-        case 0:
-          return _filter_html_settings($format);
-        case 2:
-          return _filter_url_settings($format);
-        default:
-          return;
-      }
-
-    default:
-      return $text;
-  }
+function filter_filter_info() {
+  $filters['filter_html'] = array(
+    'name' => t('Limit allowed HTML tags'),
+    'description' => t('Allows you to restrict the HTML tags the user can use. It will also remove harmful content such as JavaScript events, JavaScript URLs and CSS styles from those tags that are not removed.'),
+    'process callback' => '_filter_html',
+    'settings callback' => '_filter_html_settings',
+    'tips callback'  => '_filter_html_tips'
+  );
+  $filters['filter_autop'] = array(
+    'name' => t('Convert line breaks'),
+    'description' => t('Converts line breaks into HTML (i.e. &lt;br&gt; and &lt;p&gt;) tags.'),
+    'process callback' => '_filter_autop',
+    'tips callback' => '_filter_autop_tips'
+  );
+  $filters['filter_url'] = array(
+    'name' => t('Convert URLs into links'),
+    'description' => t('Turns web and e-mail addresses into clickable links.'),
+    'process callback' => '_filter_url',
+    'settings callback' => '_filter_url_settings',
+    'tips callback' => '_filter_url_tips'
+  );
+  $filters['filter_htmlcorrector'] = array(
+    'name' =>  t('Correct broken HTML'),
+    'description' => t('Corrects faulty and chopped off HTML in postings.'),
+    'process callback' => '_filter_htmlcorrector',
+  );
+  $filters['filter_html_escape'] = array(
+    'name' => t('Escape all HTML'),
+    'description' => t('Escapes all HTML tags, so they will be visible instead of being effective.'),
+    'process callback' => '_filter_html_escape',
+    'tips callback' => '_filter_html_escape_tips'
+  );
+  return $filters;
 }
 
 /**
@@ -718,24 +717,24 @@
     '#title' => t('HTML filter'),
     '#collapsible' => TRUE,
   );
-  $form['filter_html']["allowed_html_$format"] = array(
+  $form['filter_html']["allowed_html_$format->format_id"] = array(
     '#type' => 'textfield',
     '#title' => t('Allowed HTML tags'),
-    '#default_value' => variable_get("allowed_html_$format", '<a> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd>'),
+    '#default_value' => variable_get("allowed_html_$format->format_id", '<a> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd>'),
     '#size' => 64,
     '#maxlength' => 1024,
     '#description' => t('Specify a list of tags which should not be stripped. (Note that JavaScript event attributes are always stripped.)'),
   );
-  $form['filter_html']["filter_html_help_$format"] = array(
+  $form['filter_html']["filter_html_help_$format->format_id"] = array(
     '#type' => 'checkbox',
     '#title' => t('Display HTML help'),
-    '#default_value' => variable_get("filter_html_help_$format", 1),
+    '#default_value' => variable_get("filter_html_help_$format->format_id", 1),
     '#description' => t('If enabled, Drupal will display some basic HTML help in the long filter tips.'),
   );
-  $form['filter_html']["filter_html_nofollow_$format"] = array(
+  $form['filter_html']["filter_html_nofollow_$format->format_id"] = array(
     '#type' => 'checkbox',
     '#title' => t('Spam link deterrent'),
-    '#default_value' => variable_get("filter_html_nofollow_$format", FALSE),
+    '#default_value' => variable_get("filter_html_nofollow_$format->format_id", FALSE),
     '#description' => t('If enabled, Drupal will add rel="nofollow" to all links, as a measure to reduce the effectiveness of spam links. Note: this will also prevent valid links from being followed by search engines, therefore it is likely most effective when enabled for anonymous users.'),
   );
   return $form;
@@ -745,10 +744,10 @@
  * HTML filter. Provides filtering of input into accepted HTML.
  */
 function _filter_html($text, $format) {
-  $allowed_tags = preg_split('/\s+|<|>/', variable_get("allowed_html_$format", '<a> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd>'), -1, PREG_SPLIT_NO_EMPTY);
+  $allowed_tags = preg_split('/\s+|<|>/', variable_get("allowed_html_$format->format_id", '<a> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd>'), -1, PREG_SPLIT_NO_EMPTY);
   $text = filter_xss($text, $allowed_tags);
 
-  if (variable_get("filter_html_nofollow_$format", FALSE)) {
+  if (variable_get("filter_html_nofollow_$format->format_id", FALSE)) {
     $text = preg_replace('/<a([^>]+)>/i', '<a\\1 rel="nofollow">', $text);
   }
 
@@ -764,10 +763,10 @@
     '#title' => t('URL filter'),
     '#collapsible' => TRUE,
   );
-  $form['filter_urlfilter']['filter_url_length_' . $format] = array(
+  $form['filter_urlfilter']['filter_url_length_' . $format->format_id] = array(
     '#type' => 'textfield',
     '#title' => t('Maximum link text length'),
-    '#default_value' => variable_get('filter_url_length_' . $format, 72),
+    '#default_value' => variable_get('filter_url_length_' . $format->format_id, 72),
     '#maxlength' => 4,
     '#description' => t('URLs longer than this number of characters will be truncated to prevent long strings that break formatting. The link itself will be retained; just the text portion of the link will be truncated.'),
   );
@@ -780,7 +779,7 @@
  */
 function _filter_url($text, $format) {
   // Pass length to regexp callback
-  _filter_url_trim(NULL, variable_get('filter_url_length_' . $format, 72));
+  _filter_url_trim(NULL, variable_get("filter_url_length_$format->format_id", 72));
 
   $text = ' ' . $text . ' ';
 
Index: modules/filter/filter.admin.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/filter/filter.admin.inc,v
retrieving revision 1.30
diff -u -r1.30 filter.admin.inc
--- modules/filter/filter.admin.inc	12 Jun 2009 08:39:37 -0000	1.30
+++ modules/filter/filter.admin.inc	11 Aug 2009 15:36:41 -0000
@@ -47,12 +47,12 @@
     drupal_set_message(t('Default format updated.'));
     variable_set('filter_default_format', $form_state['values']['default']);
   }
-  foreach ($form_state['values'] as $id => $data) {
+  foreach ($form_state['values'] as $format_id => $data) {
     if (is_array($data) && isset($data['weight'])) {
       // Only update if this is a form element with weight.
       db_update('filter_format')
         ->fields(array('weight' => $data['weight']))
-        ->condition('format', $id)
+        ->condition('format_id', $format_id)
         ->execute();
     }
   }
@@ -98,7 +98,7 @@
 function filter_admin_format_page($format = NULL) {
   if (!isset($format->name)) {
     drupal_set_title(t('Add text format'), PASS_THROUGH);
-    $format = (object)array('name' => '', 'roles' => '', 'format' => '');
+    $format = (object)array('name' => '', 'roles' => '', 'format_id' => 0);
   }
   return drupal_get_form('filter_admin_format_form', $format);
 }
@@ -111,8 +111,8 @@
  * @see filter_admin_format_form_submit()
  */
 function filter_admin_format_form(&$form_state, $format) {
-  $default = ($format->format == variable_get('filter_default_format', 1));
-  if ($default) {
+  $is_default = filter_is_default_format($format);
+  if ($is_default) {
     $help = t('All roles for the default format must be enabled and cannot be changed.');
     $form['default_format'] = array('#type' => 'hidden', '#value' => 1);
   }
@@ -127,7 +127,7 @@
   // Add a row of checkboxes for form group.
   $form['roles'] = array('#type' => 'fieldset',
     '#title' => t('Roles'),
-    '#description' => $default ? $help : t('Choose which roles may use this text format. Note that roles with the "administer filters" permission can always use all text formats.'),
+    '#description' => $is_default ? $help : t('Choose which roles may use this text format. Note that roles with the "administer filters" permission can always use all text formats.'),
     '#tree' => TRUE,
   );
 
@@ -135,33 +135,33 @@
     $checked = strpos($format->roles, ",$rid,") !== FALSE;
     $form['roles'][$rid] = array('#type' => 'checkbox',
       '#title' => $name,
-      '#default_value' => ($default || $checked),
+      '#default_value' => ($is_default || $checked),
     );
-    if ($default) {
+    if ($is_default) {
       $form['roles'][$rid]['#disabled'] = TRUE;
     }
   }
   // Table with filters
   $all = filter_list_all();
-  $enabled = filter_list_format($format->format);
+  $enabled = filter_list_format($format->format_id);
 
   $form['filters'] = array('#type' => 'fieldset',
     '#title' => t('Filters'),
     '#description' => t('Choose the filters that will be used in this text format.'),
     '#tree' => TRUE,
   );
-  foreach ($all as $id => $filter) {
-    $form['filters'][$id] = array('#type' => 'checkbox',
+  foreach ($all as $filter_id => $filter) {
+    $form['filters'][$filter_id] = array('#type' => 'checkbox',
       '#title' => $filter->name,
-      '#default_value' => isset($enabled[$id]),
-      '#description' => module_invoke($filter->module, 'filter', 'description', $filter->delta),
+      '#default_value' => isset($enabled[$filter_id]),
+      '#description' => $filter->description,
     );
   }
-  if (!empty($format->format)) {
-    $form['format'] = array('#type' => 'hidden', '#value' => $format->format);
+  if (!empty($format->format_id)) {
+    $form['format_id'] = array('#type' => 'hidden', '#value' => $format->format_id);
 
     // Composition tips (guidelines)
-    $tips = _filter_tips($format->format, FALSE);
+    $tips = _filter_tips($format, FALSE);
     $tiplist = theme('filter_tips', $tips, FALSE);
     if (!$tiplist) {
       $tiplist = '<p>' . t('No guidelines available.') . '</p>';
@@ -182,9 +182,9 @@
  * Validate text format form submissions.
  */
 function filter_admin_format_form_validate($form, &$form_state) {
-  if (!isset($form_state['values']['format'])) {
+  if (!isset($form_state['values']['format_id'])) {
     $name = trim($form_state['values']['name']);
-    $result = db_query("SELECT format FROM {filter_format} WHERE name = :name", array(':name' => $name))->fetchField();
+    $result = db_query("SELECT format_id FROM {filter_format} WHERE name = :name", array(':name' => $name))->fetchField();
     if ($result) {
       form_set_error('name', t('Text format names must be unique. A format named %name already exists.', array('%name' => $name)));
     }
@@ -195,77 +195,80 @@
  * Process text format form submissions.
  */
 function filter_admin_format_form_submit($form, &$form_state) {
-  $format = isset($form_state['values']['format']) ? $form_state['values']['format'] : NULL;
-  $current = filter_list_format($format);
-  $name = trim($form_state['values']['name']);
-  $cache = TRUE;
-
-  // Add a new text format.
-  if (!$format) {
-    $new = TRUE;
-    db_insert('filter_format')
-      ->fields(array('name' => $name))
-      ->execute();
-    $format = db_query("SELECT MAX(format) AS format FROM {filter_format}")->fetchField();
-    drupal_set_message(t('Added text format %format.', array('%format' => $name)));
-  }
-  else {
-    drupal_set_message(t('The text format settings have been updated.'));
-  }
-  db_delete('filter')
-    ->condition('format', $format)
-    ->execute();
-  $query = db_insert('filter')->fields(array('format', 'module', 'delta', 'weight'));
-  foreach ($form_state['values']['filters'] as $id => $checked) {
-    if ($checked) {
-      list($module, $delta) = explode('/', $id);
-      // Add new filters to the bottom.
-      $weight = isset($current[$id]->weight) ? $current[$id]->weight : 10;
-      $query->values(array(
-        'format' => $format,
-        'module' => $module,
-        'delta'  => $delta,
-        'weight' => $weight,
-      ));
-      // Check if there are any 'no cache' filters.
-      $cache &= !module_invoke($module, 'filter', 'no cache', $delta);
-    }
-    $query->execute();
-  }
-
+  $format_id = isset($form_state['values']['format_id']) ? $form_state['values']['format_id'] : NULL;
+  $current = filter_list_format($format_id);
+  $form_state['values']['name'] = trim($form_state['values']['name']);
+  
+  $format = (object)$form_state['values'];
+  
   // We store the roles as a string for ease of use.
   // We should always set all roles to TRUE when saving a default role.
   // We use leading and trailing comma's to allow easy substring matching.
-  $roles = array();
+  $format->roles = array();
   if (isset($form_state['values']['roles'])) {
     foreach ($form_state['values']['roles'] as $id => $checked) {
       if ($checked) {
-        $roles[] = $id;
+        $format->roles[] = $id;
       }
     }
   }
   if (!empty($form_state['values']['default_format'])) {
-    $roles = ',' . implode(',', array_keys(user_roles())) . ',';
+    $format->roles = ',' . implode(',', array_keys(user_roles())) . ',';
   }
   else {
-    $roles = ',' . implode(',', $roles) . ',';
+    $format->roles = ',' . implode(',', $format->roles) . ',';
   }
 
-  db_update('filter_format')
-    ->fields(array(
-      'cache' => $cache,
-      'name'  => $name,
-      'roles' => $roles,
-    ))
-    ->condition('format', $format)
-    ->execute();
+  $filter_info = filter_list_all();
+  $format->cache = TRUE;
+  $enabled_filters = array();
+  foreach ($form_state['values']['filters'] as $filter_id => $checked) {
+    if ($checked) {
+      $filter  = $filter_info[$filter_id];       
+      // Add new filters to the bottom.
+      $filter->weight = isset($current[$filter_id]->weight) ? $current[$filter_id]->weight : 10;
+      // Check if there are any 'no cache' filters.
+      $format->cache &= empty($filter->cache) || $filter->cache; // cache by default.
+      $enabled_filters[$filter_id] = $filter;
+    }
+  }
+  
+  // Add a new text format.
+  if (!$format_id) {
+    $new = TRUE;
+    drupal_write_record('filter_format', $format);
+    
+    drupal_set_message(t('Added text format %format_name.', array('%format_name' => $format->name)));
+  }
+  else {
+    // Remove all filters associated to the format.
+    db_delete('filter_format_filter')
+      ->condition('format_id', $format->format_id)
+      ->execute();
+    drupal_write_record('filter_format', $format, 'format_id');
+   
+    drupal_set_message(t('The text format settings have been updated.'));
+  }
 
-  cache_clear_all($format . ':', 'cache_filter', TRUE);
+  $query = db_insert('filter_format_filter')->fields(array('format_id', 'module', 'filter_id', 'weight'));
+  foreach ($enabled_filters as $filter_id => $filter) {
+    $query->values(array(
+      'format_id' => $format->format_id,
+      'module' => $filter->module,
+      'filter_id' => $filter_id,
+      'weight' => $filter->weight,
+    ));
+  }
+  $query->execute();
+  
+  cache_clear_all($format->format_id . ':', 'cache_filter', TRUE);
 
-  // If a new filter was added, return to the main list of filters. Otherwise, stay on edit filter page to show new changes.
+  // If a new format was added, return to the main list of formats. 
+  // Otherwise, stay on edit format page to show new changes.
   $return = 'admin/settings/formats';
   if (!empty($new)) {
-    $return .= '/' . $format;
+    $return .= '/' . $format->format_id;
+    module_invoke_all('filter_format_update', $format);
   }
   $form_state['redirect'] = $return;
   return;
@@ -277,24 +280,14 @@
  * @ingroup forms
  * @see filter_admin_delete_submit()
  */
-function filter_admin_delete() {
-  $format = arg(4);
-  $format = db_query('SELECT * FROM {filter_format} WHERE format = :format', array(':format' => $format))->fetchObject();
-
-  if ($format) {
-    if ($format->format != variable_get('filter_default_format', 1)) {
-      $form['format'] = array('#type' => 'hidden', '#value' => $format->format);
-      $form['name'] = array('#type' => 'hidden', '#value' => $format->name);
-
-      return confirm_form($form, t('Are you sure you want to delete the text format %format?', array('%format' => $format->name)), 'admin/settings/formats', t('If you have any content left in this text format, it will be switched to the default text format. This action cannot be undone.'), t('Delete'), t('Cancel'));
-    }
-    else {
-      drupal_set_message(t('The default format cannot be deleted.'));
-      drupal_goto('admin/settings/formats');
-    }
+function filter_admin_delete($form_state, $format) {
+  if (!filter_is_default_format($format)) {
+    $form['format'] = array('#type' => 'value', '#value' => $format);
+    return confirm_form($form, t('Are you sure you want to delete the text format %format?', array('%format' => $format->name)), 'admin/settings/formats', t('If you have any content left in this text format, it will be switched to the default text format. This action cannot be undone.'), t('Delete'), t('Cancel'));
   }
   else {
-    drupal_not_found();
+    drupal_set_message(t('The default format cannot be deleted.'));
+    drupal_goto('admin/settings/formats');
   }
 }
 
@@ -302,30 +295,20 @@
  * Process filter delete form submission.
  */
 function filter_admin_delete_submit($form, &$form_state) {
+  $format = $form_state['values']['format'];
+
   db_delete('filter_format')
-    ->condition('format', $form_state['values']['format'])
+    ->condition('format_id', $format->format_id)
     ->execute();
-  db_delete('filter')
-    ->condition('format', $form_state['values']['format'])
-    ->execute();
-
-  $default = variable_get('filter_default_format', 1);
-  // Replace existing instances of the deleted format with the default format.
-  if (db_table_exists('comment')) {
-    db_update('comment')
-      ->fields(array('format' => $default))
-      ->condition('format', $form_state['values']['format'])
+    
+  db_delete('filter_format_filter')
+    ->condition('format_id', $format->format_id)
       ->execute();
-  }
-  if (db_table_exists('box')) {
-    db_update('box')
-      ->fields(array('format' => $default))
-      ->condition('format', $form_state['values']['format'])
-      ->execute();
-  }
 
-  cache_clear_all($form_state['values']['format'] . ':', 'cache_filter', TRUE);
-  drupal_set_message(t('Deleted text format %format.', array('%format' => $form_state['values']['name'])));
+  module_invoke_all('filter_format_delete', $format);
+  cache_clear_all($format->format_id . ':', 'cache_filter', TRUE);
+
+  drupal_set_message(t('Deleted text format %format_name.', array('%format_name' => $format->name)));
 
   $form_state['redirect'] = 'admin/settings/formats';
   return;
@@ -346,12 +329,14 @@
  * @ingroup forms
  */
 function filter_admin_configure(&$form_state, $format) {
-  $list = filter_list_format($format->format);
+  $filters = filter_list_format($format->format_id);
   $form = array();
-  foreach ($list as $filter) {
-    $form_module = module_invoke($filter->module, 'filter', 'settings', $filter->delta, $format->format);
-    if (isset($form_module) && is_array($form_module)) {
-      $form = array_merge($form, $form_module);
+  foreach ($filters as $filter) {
+    if (isset($filter->settings_callback) && drupal_function_exists($filter->settings_callback)) {
+      $settings_form = call_user_func($filter->settings_callback, $format);
+      if (isset($settings_form) && is_array($settings_form)) {
+        $form = array_merge($form, $settings_form);
+      }
     }
   }
 
@@ -361,7 +346,7 @@
   else {
     $form['error'] = array('#markup' => t('No settings are available.'));
   }
-  $form['format'] = array('#type' => 'hidden', '#value' => $format->format);
+  $form['format'] = array('#type' => 'value', '#value' => $format);
   $form['#submit'][] = 'filter_admin_configure_submit';
   return $form;
 }
@@ -370,7 +355,7 @@
  * Clear the filter's cache when configuration settings are saved.
  */
 function filter_admin_configure_submit($form, &$form_state) {
-  cache_clear_all($form_state['values']['format'] . ':', 'cache_filter', TRUE);
+  cache_clear_all($form_state['values']['format']->format_id . ':', 'cache_filter', TRUE);
 }
 
 /**
@@ -390,14 +375,14 @@
  */
 function filter_admin_order(&$form_state, $format = NULL) {
   // Get list (with forced refresh).
-  $filters = filter_list_format($format->format);
+  $filters = filter_list_format($format->format_id);
 
   $form['weights'] = array('#tree' => TRUE);
-  foreach ($filters as $id => $filter) {
-    $form['names'][$id] = array('#markup' => $filter->name);
-    $form['weights'][$id] = array('#type' => 'weight', '#default_value' => $filter->weight);
+  foreach ($filters as $filter_id => $filter) {
+    $form['names'][$filter_id] = array('#markup' => $filter->name);
+    $form['weights'][$filter_id] = array('#type' => 'weight', '#default_value' => $filter->weight);
   }
-  $form['format'] = array('#type' => 'hidden', '#value' => $format->format);
+  $form['format'] = array('#type' => 'value', '#value' => $format);
   $form['submit'] = array('#type' => 'submit', '#value' => t('Save configuration'));
 
   return $form;
@@ -434,16 +419,28 @@
  * Process filter order configuration form submission.
  */
 function filter_admin_order_submit($form, &$form_state) {
-  foreach ($form_state['values']['weights'] as $id => $weight) {
-    list($module, $delta) = explode('/', $id);
-    db_update('filter')
+  $format = $form_state['values']['format'];
+  foreach ($form_state['values']['weights'] as $filter_id => $weight) {
+    db_update('filter_format_filter')
       ->fields(array('weight' => $weight))
-      ->condition('format', $form_state['values']['format'])
-      ->condition('module', $module)
-      ->condition('delta', $delta)
+      ->condition('format_id', $format->format_id)
+      ->condition('filter_id', $filter_id)
       ->execute();
   }
   drupal_set_message(t('The filter ordering has been saved.'));
 
-  cache_clear_all($form_state['values']['format'] . ':', 'cache_filter', TRUE);
+  module_invoke_all('filter_format_update', $format);
+  cache_clear_all($format->format_id . ':', 'cache_filter', TRUE);
 }
+
+/**
+ * Implement hook_modules_uninstalled().
+ */
+function filter_modules_uninstalled($modules) {
+  // Remove filters from unistalled modules.
+  foreach ($modules as $module) {
+    db_delete('filter_format_filter')
+      ->condition('module', $module)
+      ->execute();
+  }
+}
\ No newline at end of file
Index: modules/field/modules/text/text.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/modules/text/text.test,v
retrieving revision 1.9
diff -u -r1.9 text.test
--- modules/field/modules/text/text.test	27 Jul 2009 20:15:35 -0000	1.9
+++ modules/field/modules/text/text.test	11 Aug 2009 15:36:36 -0000
@@ -169,7 +169,7 @@
     $this->assertRaw(str_replace('<br />', '', $value), t('Filtered value is displayed correctly'));
 
     // Allow the user to use the 'Full HTML' format.
-    db_update('filter_format')->fields(array('roles' => ',2,'))->condition('format', 2)->execute();
+    db_update('filter_format')->fields(array('roles' => ',2,'))->condition('format_id', 2)->execute();
 
     // Display edition form.
     // We should now have a 'text format' selector.
Index: modules/field/modules/text/text.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/modules/text/text.module,v
retrieving revision 1.17
diff -u -r1.17 text.module
--- modules/field/modules/text/text.module	4 Aug 2009 06:38:56 -0000	1.17
+++ modules/field/modules/text/text.module	11 Aug 2009 15:36:34 -0000
@@ -361,7 +361,7 @@
   // parse errors.
   if (isset($format)) {
     $filters = filter_list_format($format);
-    if (isset($filters['php/0']) && strpos($text, '<?') !== FALSE) {
+    if (isset($filters['php_code']) && strpos($text, '<?') !== FALSE) {
       return $text;
     }
   }
@@ -400,7 +400,7 @@
   $line_breaks = array('<br />' => 6, '<br>' => 4);
   // Newline only indicates a line break if line break converter
   // filter is present.
-  if (isset($filters['filter/1'])) {
+  if (isset($filters['filter_autop'])) {
     $line_breaks["\n"] = 1;
   }
   $break_points[] = $line_breaks;
@@ -428,7 +428,7 @@
   }
 
   // If the htmlcorrector filter is present, apply it to the generated summary.
-  if (isset($filters['filter/3'])) {
+  if (isset($filters['filter_htmlcorrector'])) {
     $summary = _filter_htmlcorrector($summary);
   }
 
Index: modules/system/system.install
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.install,v
retrieving revision 1.365
diff -u -r1.365 system.install
--- modules/system/system.install	8 Aug 2009 20:52:32 -0000	1.365
+++ modules/system/system.install	11 Aug 2009 15:37:38 -0000
@@ -398,14 +398,14 @@
     ->execute();
 
   // Add text formats.
-  $filtered_html_format = db_insert('filter_format')
+  $filtered_html_format_id = db_insert('filter_format')
     ->fields(array(
       'name' => 'Filtered HTML',
       'roles' => ',' . DRUPAL_ANONYMOUS_RID . ',' . DRUPAL_AUTHENTICATED_RID . ',',
       'cache' => 1,
     ))
     ->execute();
-  $full_html_format = db_insert('filter_format')
+  $full_html_format_id = db_insert('filter_format')
     ->fields(array(
       'name' => 'Full HTML',
       'roles' => '',
@@ -416,62 +416,62 @@
   // Enable filters for each text format.
 
   // Filtered HTML:
-  db_insert('filter')
-    ->fields(array('format', 'module', 'delta', 'weight'))
+  db_insert('filter_format_filter')
+    ->fields(array('format_id', 'module', 'filter_id', 'weight'))
     // URL filter.
     ->values(array(
-      'format' => $filtered_html_format,
+      'format_id' => $filtered_html_format_id,
       'module' => 'filter',
-      'delta' => 2,
+      'filter_id' => 'filter_url',
       'weight' => 0,
     ))
     // HTML filter.
     ->values(array(
-      'format' => $filtered_html_format,
+      'format_id' => $filtered_html_format_id,
       'module' => 'filter',
-      'delta' => 0,
+      'filter_id' => 'filter_html',
       'weight' => 1,
     ))
     // Line break filter.
     ->values(array(
-      'format' => $filtered_html_format,
+      'format_id' => $filtered_html_format_id,
       'module' => 'filter',
-      'delta' => 1,
+      'filter_id' => 'filter_autop',
       'weight' => 2,
     ))
     // HTML corrector filter.
     ->values(array(
-      'format' => $filtered_html_format,
+      'format_id' => $filtered_html_format_id,
       'module' => 'filter',
-      'delta' => 3,
+      'filter_id' => 'filter_htmlcorrector',
       'weight' => 10,
     ))
   // Full HTML:
     // URL filter.
     ->values(array(
-      'format' => $full_html_format,
+      'format_id' => $full_html_format_id,
       'module' => 'filter',
-      'delta' => 2,
+      'filter_id' => 'filter_url',
       'weight' => 0,
     ))
     // Line break filter.
     ->values(array(
-      'format' => $full_html_format,
+      'format_id' => $full_html_format_id,
       'module' => 'filter',
-      'delta' => 1,
+      'filter_id' => 'filter_autop',
       'weight' => 1,
     ))
     // HTML corrector filter.
     ->values(array(
-      'format' => $full_html_format,
+      'format_id' => $full_html_format_id,
       'module' => 'filter',
-      'delta' => 3,
+      'filter_id' => 'filter_htmlcorrector',
       'weight' => 10,
     ))
     ->execute();
 
   // Set the default input format to Filtered HTML.
-  variable_set('filter_default_format', $filtered_html_format);
+  variable_set('filter_default_format', $filtered_html_format_id);
 
   variable_set('node_options_forum', array(0 => 'status'));
 
