Index: mollom.admin.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/mollom/Attic/mollom.admin.inc,v
retrieving revision 1.1.2.9
diff -u -p -r1.1.2.9 mollom.admin.inc
--- mollom.admin.inc	19 Nov 2009 16:48:59 -0000	1.1.2.9
+++ mollom.admin.inc	20 Nov 2009 12:59:06 -0000
@@ -7,10 +7,227 @@
  */
 
 /**
- * Form builder; Administration settings form.
+ * Menu callback; Displays a list of forms configured for Mollom.
+ */
+function mollom_admin_form_list() {
+  $modes = array(
+    MOLLOM_MODE_DISABLED => t('No protection'),
+    MOLLOM_MODE_CAPTCHA => t('CAPTCHA only'),
+    MOLLOM_MODE_ANALYSIS => t('Text analysis and CAPTCHA backup'),
+  );
+
+  $header = array(
+    t('Form'),
+    t('Protection mode'),
+    array('data' => t('Operations'), 'colspan' => 2),
+  );
+  $rows = array();
+  $result = db_query("SELECT form_id FROM {mollom_form}");
+  while ($form_id = db_result($result)) {
+    $mollom_form = mollom_admin_form_load($form_id);
+    $rows[] = array(
+      $mollom_form['title'],
+      $modes[$mollom_form['mode']],
+      l(t('Configure'), 'admin/settings/mollom/manage/' . $form_id),
+      l(t('Unprotect'), 'admin/settings/mollom/unprotect/' . $form_id),
+    );
+  }
+
+  // Add a row to add a form.
+  if (empty($rows)) {
+    $rows[] = array(array('data' => l(t('Add form'), 'admin/settings/mollom/add'), 'colspan' => 4));
+  }
+
+  return theme('table', $header, $rows);
+}
+
+/**
+ * Helper function to return registered forms suitable for #options.
+ */
+function mollom_admin_form_options() {
+  // Retrieve all registered forms.
+  $form_info = mollom_get_form_info();
+
+  // Remove already configured form ids.
+  $result = db_query("SELECT form_id FROM {mollom_form}");
+  while ($form_id = db_result($result)) {
+    unset($form_info[$form_id]);
+  }
+
+  // Load module information.
+  $modules = module_implements('mollom_form_info');
+  $placeholders = db_placeholders($modules, 'varchar');
+  $result = db_query("SELECT name, info FROM {system} WHERE type = 'module' AND name IN ($placeholders)", $modules);
+  $modules = array();
+  while ($row = db_fetch_object($result)) {
+    $module_info = unserialize($row->info);
+    $modules[$row->name] = t($module_info['name']);
+  }
+
+  // Transform form information into an associative array suitable for #options.
+  foreach ($form_info as $form_id => $info) {
+    $form_info[$form_id] = $modules[$info['module']] . ': ' . $info['title'];
+  }
+  // Sort form options by title.
+  asort($form_info);
+
+  return $form_info;
+}
+
+/**
+ * Form builder; Configure Mollom protection for a form.
+ */
+function mollom_admin_configure_form(&$form_state, $mollom_form = NULL) {
+  // If no $mollom_form was passed, then we are adding a new form configuration.
+  if (!isset($mollom_form)) {
+    if (!isset($form_state['storage']['mollom_form'])) {
+      $form_state['storage']['step'] = 'select';
+    }
+    else {
+      $form_state['storage']['step'] = 'configure';
+      $mollom_form = $form_state['storage']['mollom_form'];
+    }
+  }
+  // Otherwise, we are editing an existing form configuration.
+  else {
+    $form_state['storage']['step'] = 'configure';
+    $form_state['storage']['mollom_form'] = $mollom_form;
+  }
+
+  $form['#tree'] = TRUE;
+
+  switch ($form_state['storage']['step']) {
+    case 'select':
+      drupal_add_js(drupal_get_path('module', 'mollom') . '/mollom.js');
+
+      $form['mollom']['form_id'] = array(
+        '#type' => 'select',
+        '#title' => t('Form'),
+        '#options' => mollom_admin_form_options(),
+        '#required' => TRUE,
+      );
+      $form['actions']['next'] = array(
+        '#type' => 'submit',
+        '#value' => t('Next'),
+        '#submit' => array('mollom_admin_configure_form_next_submit'),
+      );
+      break;
+
+    case 'configure':
+      // Display a list of fields for textual analysis (last step).
+      $form['mollom']['form_id'] = array(
+        '#type' => 'value',
+        '#value' => $mollom_form['form_id'],
+      );
+      $form['mollom']['form_title'] = array(
+        '#type' => 'item',
+        '#title' => t('Form'),
+        '#value' => $mollom_form['title'],
+      );
+      $form['mollom']['fields'] = array(
+        '#type' => 'checkboxes',
+        '#title' => t('Fields to analyze'),
+        '#options' => $mollom_form['parents'],
+        '#default_value' => $mollom_form['fields'],
+        '#description' => t('If no fields are selected, the form will be protected by a CAPTCHA.'),
+      );
+      if (empty($form['mollom']['fields']['#options'])) {
+        $form['mollom']['fields']['#description'] = t('No fields are available.');
+      }
+      $form['actions']['submit'] = array(
+        '#type' => 'submit',
+        '#value' => t('Save'),
+      );
+      break;
+  }
+
+  $form['actions']['cancel'] = array(
+    '#value' => l(t('Cancel'), 'admin/settings/mollom'),
+  );
+
+  return $form;
+}
+
+/**
+ * Form submit handler for 'Next' button on Mollom form configuration form.
+ */
+function mollom_admin_configure_form_next_submit($form, &$form_state) {
+  $mollom_form = $form_state['values']['mollom'];
+
+  // Load form information into $form_state for configuration.
+  $form_state['storage']['mollom_form'] = mollom_admin_form_load($mollom_form['form_id']);
+
+  $form_state['storage']['step'] = 'configure';
+}
+
+/**
+ * Form submit handler for Mollom form configuration form.
+ */
+function mollom_admin_configure_form_submit($form, &$form_state) {
+  $mollom_form = $form_state['values']['mollom'];
+  // Merge in form information from $form_state.
+  $mollom_form += $form_state['storage']['mollom_form'];
+  // Update form information in $form_state for potential rebuilds.
+  $form_state['storage']['mollom_form'] = $mollom_form;
+
+  // Prepare selected fields for storage.
+  $mollom_form['fields'] = array_keys(array_filter($mollom_form['fields']));
+  // Determine form protection to use; use CAPTCHA-only protection if no fields
+  // were selected, otherwise text analysis.
+  $mollom_form['mode'] = (!empty($mollom_form['fields']) ? MOLLOM_MODE_ANALYSIS : MOLLOM_MODE_CAPTCHA);
+
+  $status = mollom_form_save($mollom_form);
+  if ($status === SAVED_NEW) {
+    drupal_set_message('The form protection has been added.');
+  }
+  else {
+    drupal_set_message('The form protection has been updated.');
+  }
+
+  unset($form_state['storage']);
+  $form_state['redirect'] = 'admin/settings/mollom';
+}
+
+/**
+ * Form builder; Remove Mollom protection from a form.
+ */
+function mollom_admin_unprotect_form(&$form_state, $mollom_form) {
+  $form['#tree'] = TRUE;
+  $form['form'] = array(
+    '#type' => 'item',
+    '#title' => t('Form'),
+    '#value' => $mollom_form['title'],
+  );
+  $form['mollom']['form_id'] = array(
+    '#type' => 'value',
+    '#value' => $mollom_form['form_id'],
+  );
+
+  return confirm_form($form,
+    t('Are you sure you want to unprotect this form?'),
+    'admin/settings/mollom',
+    t('Mollom will no longer protect this form from spam.')
+  );
+}
+
+/**
+ * Form submit handler for mollom_admin_unprotect_form().
+ */
+function mollom_admin_unprotect_form_submit($form, &$form_state) {
+  db_query("DELETE FROM {mollom_form} WHERE form_id = '%s'", $form_state['values']['mollom']['form_id']);
+
+  $form_state['redirect'] = 'admin/settings/mollom';
+}
+
+/**
+ * Form builder; Global Mollom settings form.
  */
 function mollom_admin_settings() {
   $keys = _mollom_access();
+  // Since keys are not verified in HTTP POST requests, we need to default to
+  // a successful key status. Otherwise, the fallback form elements would not
+  // be accessible and therefore no value would be stored.
+  $status = TRUE;
 
   if ($keys) {
     if (!$_POST) {
@@ -24,72 +241,30 @@ function mollom_admin_settings() {
       // Verify the key and output a status message.
       // @todo This must be a form #validate handler; the form must not be
       //   submitted if validation fails.
-      _mollom_verify_key();
+      $status = _mollom_verify_key();
     }
-
-    $description = '<p>' . t("Mollom can be used to block all types of spam received on your website's protected forms. Each form can be set to one of the following options:") . '</p>';
-    $description .= '<ul><li>' . t("<strong>Text analysis and CAPTCHA backup</strong>: Mollom analyzes the data submitted on the form and presents a CAPTCHA challenge if necessary. This option is strongly recommended, as it takes full advantage of the Mollom anti-spam service to categorize your posts into ham (not spam) and spam.") . '</li>';
-    $description .= '<li>' . t("<strong>CAPTCHA only</strong>: the form's data is not sent to Mollom for analysis, and a remotely-hosted CAPTCHA challenge is always presented. This option is useful when you wish to always display a CAPTCHA or want to send less data to the Mollom network. Note, however, that forms displayed with a CAPTCHA are never cached, so always displaying a CAPTCHA challenge may reduce performance.") . '</li>';
-    $description .= '<li>' . t('<strong>No protection</strong>: Mollom is not used with this form.') . '</li></ul>';
-    $description .= '<p>';
-    $description .= t("Data is processsed and stored as explained in our <a href=\"@mollom-privacy\">Web Service Privacy Policy</a>. It is your responsibility to provide any necessary notices and obtain the appropriate consent regarding Mollom's use of your data. For more information, see <a href=\"@mollom-works\">How Mollom Works</a> and the <a href=\"@mollom-faq\">Mollom FAQ</a>.", array(
-      '@mollom-privacy' => 'http://mollom.com/service-agreement-free-subscriptions',
-      '@mollom-works' => 'http://mollom.com/how-mollom-works',
-      '@mollom-faq' => 'http://mollom.com/faq'));
-    $description .= '</p>';
-
-    $form['spam'] = array(
-      '#type' => 'fieldset',
-      '#title' => t('Spam protection settings'),
-      '#description' => $description,
-      '#collapsible' => TRUE,
-    );
-
-    $modes = array(
-      MOLLOM_MODE_DISABLED => t('No protection'),
-      MOLLOM_MODE_CAPTCHA => t('CAPTCHA only'),
-      MOLLOM_MODE_ANALYSIS => t('Text analysis and CAPTCHA backup'),
-    );
-
-    $forms = _mollom_protectable_forms();
-    foreach ($forms as $form_id => $details) {
-      $mode = _mollom_get_mode($form_id);
-      // @todo Use #tree instead.
-      $name = 'mollom_' . $form_id;
-
-      // @todo What's happening here?
-      $options = array_slice($modes, 0, $details['mode'] + 1);
-
-      $form['spam'][$name] = array(
-        '#type' => 'select',
-        '#title' => t('Protect @name', array('@name' => $details['name'])),
-        '#options' => $options,
-        '#default_value' => $mode,
-      );
-    }
-
-    $form['server'] = array(
-      '#type' => 'fieldset',
-      '#title' => t('Server settings'),
-      '#collapsible' => TRUE,
-      '#collapsed' => $keys,
-    );
-    $form['server']['mollom_fallback'] = array(
-      '#type' => 'radios',
-      '#title' => t('Fallback strategy'),
-      // Default to treating everything as inappropriate.
-      '#default_value' => variable_get('mollom_fallback', MOLLOM_FALLBACK_BLOCK),
-      '#options' => array(
-        MOLLOM_FALLBACK_BLOCK => t('Block all submissions of protected forms until the server problems are resolved'),
-        MOLLOM_FALLBACK_ACCEPT => t('Leave all forms unprotected and accept all submissions'),
-      ),
-      '#description' => t('When the Mollom servers are down or otherwise unreachable, no text analysis is performed and no CAPTCHAs are generated. If this occurs, your Drupal site will use the configured fallback strategy, and will either accept all submissions without spam checking, or block all submissions until the server or connection problems are resolved. Subscribers to <a href="@pricing">Mollom Plus</a> receive access to <a href="@sla">Mollom\'s high-availability backend infrastructure</a>, not available to free users, reducing potential downtime.', array(
-        '@pricing' => 'http://mollom.com/pricing',
-        '@sla' => 'http://mollom.com/standard-service-level-agreement',
-      )),
-    );
   }
 
+  $form['server'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Server settings'),
+    '#access' => $status,
+  );
+  $form['server']['mollom_fallback'] = array(
+    '#type' => 'radios',
+    '#title' => t('Fallback strategy'),
+    // Default to treating everything as inappropriate.
+    '#default_value' => variable_get('mollom_fallback', MOLLOM_FALLBACK_BLOCK),
+    '#options' => array(
+      MOLLOM_FALLBACK_BLOCK => t('Block all submissions of protected forms until the server problems are resolved'),
+      MOLLOM_FALLBACK_ACCEPT => t('Leave all forms unprotected and accept all submissions'),
+    ),
+    '#description' => t('When the Mollom servers are down or otherwise unreachable, no text analysis is performed and no CAPTCHAs are generated. If this occurs, your Drupal site will use the configured fallback strategy, and will either accept all submissions without spam checking, or block all submissions until the server or connection problems are resolved. Subscribers to <a href="@pricing">Mollom Plus</a> receive access to <a href="@sla">Mollom\'s high-availability backend infrastructure</a>, not available to free users, reducing potential downtime.', array(
+      '@pricing' => 'http://mollom.com/pricing',
+      '@sla' => 'http://mollom.com/standard-service-level-agreement',
+    )),
+  );
+
   $form['access-keys'] = array(
     '#type' => 'fieldset',
     '#title' => t('Mollom access keys'),
@@ -99,7 +274,7 @@ function mollom_admin_settings() {
       '@mollom-manager-add' => 'http://mollom.com/site-manager/add',
       '@mollom-manager' => 'http://mollom.com/site-manager',
     )),
-    '#collapsible' => TRUE,
+    '#collapsible' => $keys,
     '#collapsed' => $keys,
   );
   $form['access-keys']['mollom_public_key'] = array(
@@ -128,12 +303,15 @@ function _mollom_verify_key() {
 
   if ($status === NETWORK_ERROR) {
     drupal_set_message(t('We tried to contact the Mollom servers but we encountered a network error. Please make sure that your web server can make outgoing HTTP requests.'), 'error');
+    return FALSE;
   }
   elseif ($status === MOLLOM_ERROR) {
     drupal_set_message(t('We contacted the Mollom servers to verify your keys: your keys do not exist or are no longer valid. Please visit the <em>Manage sites</em> page on the Mollom website again: <a href="@mollom-user">@mollom-user</a>.', array('@mollom-user' => 'http://mollom.com/user')), 'error');
+    return FALSE;
   }
   else {
     drupal_set_message(t('We contacted the Mollom servers to verify your keys: the Mollom services are operating correctly. We are now blocking spam.'));
+    return TRUE;
   }
 }
 
Index: mollom.api.php
===================================================================
RCS file: mollom.api.php
diff -N mollom.api.php
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ mollom.api.php	20 Nov 2009 11:33:59 -0000
@@ -0,0 +1,68 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * API documentation for Mollom module.
+ */
+
+/**
+ * Return information about forms that can be protected by Mollom.
+ *
+ * Mollom invokes this hook for all modules to gather information about forms
+ * that can be protected. Only forms that have been registered via this hook are
+ * configurable in Mollom's administration interface.
+ *
+ * @return
+ *   An associative array containing information about forms that can be
+ *   protected. Each key is a $form_id whose value is an associative array:
+ *   - title: The human-readable name of the form.
+ *   - mode: (optional) The default protection mode for the form, which can be
+ *     one of:
+ *     - MOLLOM_MODE_DISABLED: No protection.
+ *     - MOLLOM_MODE_CAPTCHA: CAPTCHA-only protection.
+ *     - MOLLOM_MODE_ANALYSIS: Text analysis of submitted form values with
+ *       fallback to CAPTCHA.
+ *     If omitted, the form will not be configured upon installation of Mollom
+ *     module.
+ *   - parents: (optional) An associative array of elements in the form that can
+ *     be configured for Mollom's text analysis. The site administrator can only
+ *     select the form elements to process (and exclude certain elements) when a
+ *     form registers elements. Each key is a form API element #parents string
+ *     representation of the location of an element in the form. For example, a
+ *     key of "myelement" denotes a form element value on the top-level of
+ *     submitted form values. For nested elements, a key of "parent][child"
+ *     denotes that the value of 'child' is found below 'parent' in the
+ *     submitted form values. Each value contains the form element label. If
+ *     omitted, Mollom can only provide a CAPTCHA protection for the form.
+ */
+function hook_mollom_form_info() {
+  $forms['comment_form'] = array(
+    'title' => t('Comment form'),
+    'mode' => MOLLOM_MODE_ANALYSIS,
+    'parents' => array(
+      'subject' => t('Subject'),
+      'body' => t('Body'),
+    ),
+  );
+  $forms['user_register'] = array(
+    'title' => t('User registration form'),
+    'mode' => MOLLOM_MODE_CAPTCHA,
+  );
+
+  return $forms;
+}
+
+/**
+ * Alter registered information about forms that can be protected by Mollom.
+ *
+ * @param &$form_info
+ *   An associative array containing protectable forms. See
+ *   hook_mollom_form_info() for details.
+ */
+function hook_mollom_form_info_alter(&$form_info) {
+  if (isset($form_info['comment_form'])) {
+    $form_info['comment_form']['parents']['mymodule_field'] = t('My additional field');
+  }
+}
+
Index: mollom.form.inc
===================================================================
RCS file: mollom.form.inc
diff -N mollom.form.inc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ mollom.form.inc	20 Nov 2009 11:36:46 -0000
@@ -0,0 +1,121 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Implements hook_mollom_form_info() on behalf of other modules.
+ */
+
+/**
+ * Implements hook_mollom_form_info().
+ */
+function comment_mollom_form_info() {
+  $forms['comment_form'] = array(
+    'title' => t('Comment form'),
+    'mode' => MOLLOM_MODE_ANALYSIS,
+    'parents' => array(
+      'subject' => t('Subject'),
+      'body' => t('Body'),
+    ),
+  );
+
+  return $forms;
+}
+
+/**
+ * Implements hook_mollom_form_info().
+ */
+function contact_mollom_form_info() {
+  $forms['contact_mail_page'] = array(
+    'title' => t('Site-wide contact form'),
+    'mode' => MOLLOM_MODE_ANALYSIS,
+    'parents' => array(
+      'subject' => t('Subject'),
+      'message' => t('Message'),
+    ),
+  );
+  $forms['contact_mail_user'] = array(
+    'title' => t('User contact form'),
+    'mode' => MOLLOM_MODE_ANALYSIS,
+    'parents' => array(
+      'subject' => t('Subject'),
+      'message' => t('Message'),
+    ),
+  );
+
+  return $forms;
+}
+
+/**
+ * Implements hook_mollom_form_info().
+ */
+function node_mollom_form_info() {
+  $types = node_get_types('types');
+  $forms = array();
+  foreach ($types as $type) {
+    $forms[$type->type . '_node_form'] = array(
+      'title' => t('@name form', array('@name' => $type->name)),
+      'mode' => MOLLOM_MODE_ANALYSIS,
+    );
+    $parents = array();
+    // @see node_content_form()
+    if ($type->has_title) {
+      $parents['title'] = check_plain($type->title_label);
+    }
+    if ($type->has_body) {
+      $parents['body'] = check_plain($type->body_label);
+      // @todo How do we get invisible & dependent 'format' support in here?
+      // $parents['format'] = t('Input format'),
+    }
+    // Add CCK fields by default.
+    if (module_exists('content')) {
+      $content_info = content_types($type->type);
+      foreach ($content_info['fields'] as $field_name => $field) {
+        // We only consider text fields for text analysis.
+        if ($field['type'] == 'text') {
+          $parents[$field_name] = check_plain(t($field['widget']['label']));
+        }
+      }
+    }
+    $forms[$type->type . '_node_form']['parents'] = $parents;
+  }
+
+  return $forms;
+}
+
+/**
+ * Implements hook_mollom_form_info().
+ */
+function user_mollom_form_info() {
+  $forms['user_register'] = array(
+    'title' => t('User registration form'),
+    'mode' => MOLLOM_MODE_CAPTCHA,
+  );
+  $forms['user_pass'] = array(
+    'title' => t('User password request form'),
+    'mode' => MOLLOM_MODE_CAPTCHA,
+  );
+
+  return $forms;
+}
+
+/**
+ * Implements hook_mollom_form_info().
+ *
+ * @todo Move this into Webform module.
+ */
+function webform_mollom_form_info() {
+  // @todo
+  $webforms = db_query("SELECT n.nid, n.title FROM {node} n WHERE n.type = 'webform' AND n.status = 1");
+  while ($webform = db_fetch_object($webforms)) {
+    $forms['webform_client_form_'. $webform->nid] = array(
+      'title' => $webform->title,
+      'mode' => MOLLOM_MODE_ANALYSIS,
+      'parents' => array(
+      ),
+    );
+  }
+
+  return $forms;
+}
+
Index: mollom.install
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/mollom/mollom.install,v
retrieving revision 1.2.2.11
diff -u -p -r1.2.2.11 mollom.install
--- mollom.install	17 Nov 2009 17:01:49 -0000	1.2.2.11
+++ mollom.install	20 Nov 2009 16:40:36 -0000
@@ -53,6 +53,32 @@ function mollom_schema() {
     'primary key' => array('did'),
   );
 
+  $schema['mollom_form'] = array(
+    'description' => 'Stores configuration for forms protected by Mollom.',
+    'fields' => array(
+      'form_id' => array(
+        'description' => 'The $form_id of the form being protected.',
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+      ),
+      'mode' => array(
+        'description' => 'The configured protection mode to use for the form.',
+        'type' => 'int',
+        'size' => 'tiny',
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'fields' => array(
+        'type' => 'text',
+        'serialize' => TRUE,
+      ),
+      // @todo 'module' required for maintenance.
+    ),
+    'primary key' => array('form_id'),
+  );
+
   $schema['cache_mollom'] = drupal_get_schema_unprocessed('system', 'cache');
   $schema['cache_mollom']['description'] = 'Cache table for the Mollom module to store information for forms it protects.';
 
@@ -64,6 +90,21 @@ function mollom_schema() {
  */
 function mollom_install() {
   drupal_install_schema('mollom');
+
+  // Install default form configuration for enabled, supported modules.
+  foreach (module_list(FALSE, FALSE) as $module) {
+    drupal_load('module', $module);
+  }
+  drupal_load('module', 'mollom');
+
+  $form_info = mollom_get_form_info();
+  foreach ($form_info as $form_id => $info) {
+    if (!empty($info['mode'])) {
+      $info['form_id'] = $form_id;
+      $info['fields'] = array_keys($info['parents']);
+      mollom_form_save($info);
+    }
+  }
 }
 
 /**
@@ -97,14 +138,18 @@ function mollom_update_2() {
  * Upgrade form protection storage.
  */
 function mollom_update_3() {
-  // Load the Drupal module so that _mollom_protectable_forms() is available.
+  // Load mollom_get_form_info() and hook_mollom_form_info() implementations.
+  foreach (module_list(FALSE, FALSE) as $module) {
+    drupal_load('module', $module);
+  }
   drupal_load('module', 'mollom');
 
-  foreach (_mollom_protectable_forms() as $form_id => $details) {
+  foreach (mollom_get_form_info() as $form_id => $info) {
     $name = 'mollom_' . $form_id;
     $mode = variable_get($name, NULL);
-    if ($mode && $details['mode'] == MOLLOM_MODE_ANALYSIS) {
-      // $mode was stored as 1, default to MOLLOM_MODE_ANALYSIS if the form supports it.
+    // $mode was stored as 1; default to MOLLOM_MODE_ANALYSIS if the form
+    // supports it.
+    if (isset($mode) && $info['mode'] == MOLLOM_MODE_ANALYSIS) {
       variable_set($name, MOLLOM_MODE_ANALYSIS);
     }
   }
@@ -120,3 +165,56 @@ function mollom_update_4() {
   return $ret;
 }
 
+/**
+ * Add the {mollom_form} table.
+ */
+function mollom_update_6105() {
+  $ret = array();
+  $schema = array(
+    'fields' => array(
+      'form_id' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+      ),
+      'mode' => array(
+        'description' => 'The configured protection mode to use for the form.',
+        'type' => 'int',
+        'size' => 'tiny',
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'fields' => array(
+        'type' => 'text',
+        'serialize' => TRUE,
+      ),
+      // @todo 'module' required for maintenance.
+    ),
+    'primary key' => array('form_id'),
+  );
+  db_create_table($ret, 'mollom_form', $schema);
+
+  // Migrate form configuration for enabled, supported modules.
+  foreach (module_list(FALSE, FALSE) as $module) {
+    drupal_load('module', $module);
+  }
+  drupal_load('module', 'mollom');
+
+  $form_info = mollom_get_form_info();
+  $result = db_query("SELECT name, value FROM {variable} WHERE name LIKE 'mollom_%%' AND name NOT IN ('mollom_servers', 'mollom_fallback', 'mollom_public_key', 'mollom_private_key')");
+  while ($row = db_fetch_object($result)) {
+    $form_id = substr($row->name, 7);
+    $mode = unserialize($row->value);
+    if (!empty($mode) && isset($form_info[$form_id])) {
+      $info = $form_info[$form_id];
+      $info['form_id'] = $form_id;
+      $info['mode'] = $mode;
+      $info['fields'] = array_keys($info['parents']);
+      mollom_form_save($info);
+    }
+    variable_del($row->name);
+  }
+  return $ret;
+}
+
Index: mollom.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/mollom/mollom.module,v
retrieving revision 1.2.2.101
diff -u -p -r1.2.2.101 mollom.module
--- mollom.module	17 Nov 2009 23:39:49 -0000	1.2.2.101
+++ mollom.module	20 Nov 2009 14:10:10 -0000
@@ -91,12 +91,34 @@ define('MOLLOM_REDIRECT', 1200);
  */
 function mollom_help($path, $arg) {
   if ($path == 'admin/settings/mollom') {
-    return t("Allowing users to react, participate and contribute while still keeping your site's content under control can be a huge challenge. Mollom is a web service that helps you identify content quality and, more importantly, helps you stop spam. When content moderation becomes easier, you have more time and energy to interact with your web community. More information about Mollom is available on the <a href=\"@mollom-website\">Mollom website</a> or in the <a href=\"@mollom-faq\">Mollom FAQ</a>.",
+    return t('All listed forms below are protected by Mollom. You can <a href="@add-form-url">add a form</a> to protect, configure already protected forms, or remove the protection.', array(
+      '@add-form-url' => url('admin/settings/mollom/add'),
+    ));
+  }
+  if ($path == 'admin/help#mollom') {
+    $output = '<p>';
+    $output = t("Allowing users to react, participate and contribute while still keeping your site's content under control can be a huge challenge. Mollom is a web service that helps you identify content quality and, more importantly, helps you stop spam. When content moderation becomes easier, you have more time and energy to interact with your web community. More information about Mollom is available on the <a href=\"@mollom-website\">Mollom website</a> or in the <a href=\"@mollom-faq\">Mollom FAQ</a>.",
       array(
         '@mollom-website' => 'http://mollom.com',
         '@mollom-faq' => 'http://mollom.com/faq',
       )
     );
+    $output .= '</p><p>';
+    $output .= t("Mollom can be used to block all types of spam received on your website's protected forms. Each form can be set to one of the following options:");
+    $output .= '</p><ul><li>';
+    $output .= t("<strong>Text analysis and CAPTCHA backup</strong>: Mollom analyzes the data submitted on the form and presents a CAPTCHA challenge if necessary. This option is strongly recommended, as it takes full advantage of the Mollom anti-spam service to categorize your posts into ham (not spam) and spam.");
+    $output .= '</li><li>';
+    $output .= t("<strong>CAPTCHA only</strong>: the form's data is not sent to Mollom for analysis, and a remotely-hosted CAPTCHA challenge is always presented. This option is useful when you wish to always display a CAPTCHA or want to send less data to the Mollom network. Note, however, that forms displayed with a CAPTCHA are never cached, so always displaying a CAPTCHA challenge may reduce performance.");
+    $output .= '</li><li>';
+    $output .= t('<strong>No protection</strong>: Mollom is not used with this form.');
+    $output .= '</li></ul><p>';
+    $output .= t("Data is processsed and stored as explained in our <a href=\"@mollom-privacy\">Web Service Privacy Policy</a>. It is your responsibility to provide any necessary notices and obtain the appropriate consent regarding Mollom's use of your data. For more information, see <a href=\"@mollom-works\">How Mollom Works</a> and the <a href=\"@mollom-faq\">Mollom FAQ</a>.", array(
+      '@mollom-privacy' => 'http://mollom.com/service-agreement-free-subscriptions',
+      '@mollom-works' => 'http://mollom.com/how-mollom-works',
+      '@mollom-faq' => 'http://mollom.com/faq')
+    );
+    $output .= '</p>';
+    return $output;
   }
 }
 
@@ -155,16 +177,54 @@ function mollom_menu() {
     'file' => 'mollom.pages.inc',
     'type' => MENU_CALLBACK,
   );
+
   $items['admin/settings/mollom'] = array(
     'title' => 'Mollom',
     'description' => 'Mollom is a web service that helps you manage your community.',
+    'page callback' => 'mollom_admin_form_list',
+    'access arguments' => array('administer mollom'),
+    'file' => 'mollom.admin.inc',
+  );
+  $items['admin/settings/mollom/list'] = array(
+    'title' => 'List',
+    'type' => MENU_DEFAULT_LOCAL_TASK,
+    'weight' => -10,
+  );
+  $items['admin/settings/mollom/add'] = array(
+    'title' => 'Add form',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('mollom_admin_configure_form'),
+    'access arguments' => array('administer mollom'),
+    'type' => MENU_LOCAL_TASK,
+    'file' => 'mollom.admin.inc',
+  );
+  $items['admin/settings/mollom/manage/%mollom_admin_form'] = array(
+    'title' => 'Configure',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('mollom_admin_configure_form', 4),
+    'access arguments' => array('administer mollom'),
+    'file' => 'mollom.admin.inc',
+  );
+  $items['admin/settings/mollom/unprotect/%mollom_admin_form'] = array(
+    'title' => 'Unprotect form',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('mollom_admin_unprotect_form', 4),
+    'access arguments' => array('administer mollom'),
+    'type' => MENU_CALLBACK,
+    'file' => 'mollom.admin.inc',
+  );
+  $items['admin/settings/mollom/settings'] = array(
+    'title' => 'Settings',
+    'description' => 'Configure Mollom keys and global settings.',
     'page callback' => 'drupal_get_form',
     'page arguments' => array('mollom_admin_settings'),
     'access arguments' => array('administer mollom'),
+    'type' => MENU_LOCAL_TASK,
     'file' => 'mollom.admin.inc',
   );
+
   $items['admin/reports/mollom'] = array(
-    'title' => 'Mollom',
+    'title' => 'Mollom statistics',
     'description' => 'Reports and usage statistics for the Mollom module.',
     'page callback' => 'mollom_reports_page',
     'access callback' => '_mollom_access',
@@ -305,11 +365,11 @@ function mollom_comment($comment, $op) {
  * This function intercepts all forms in Drupal and Mollom-enables them if
  * necessary.
  */
-function mollom_form_alter(&$form, $form_state, $form_id) {
+function mollom_form_alter(&$form, &$form_state, $form_id) {
   // Site administrators don't have their content checked with Mollom.
   if (!user_access('post with no checking')) {
-    // Retrieve the mode of protection required for this form.
-    if ($mode = _mollom_get_mode($form_id)) {
+    // Retrieve the protection mode for this form.
+    if ($mollom_form = mollom_form_load($form_id)) {
       // Compute the weight of the CAPTCHA so we can position it in the form.
       $weight = 99999;
       foreach (element_children($form) as $key) {
@@ -332,7 +392,8 @@ function mollom_form_alter(&$form, $form
       // Add Mollom form protection widget.
       $form['mollom'] = array(
         '#type' => 'mollom',
-        '#mode' => $mode,
+        '#mode' => $mollom_form['mode'],
+        '#fields' => $mollom_form['fields'],
         '#weight' => $weight,
       );
       // Add a submit handler that will clean the Mollom state as soon as the
@@ -468,61 +529,154 @@ function mollom_data_node_form($form_sta
  *   - MOLLOM_MODE_DISABLED: None.
  *   - MOLLOM_MODE_CAPTCHA: CAPTCHA only.
  *   - MOLLOM_MODE_ANALYSIS: Text analysis with CAPTCHA fallback.
- *
- * @todo Store form ids in a single variable.
  */
 function _mollom_get_mode($form_id) {
-  $mode = variable_get('mollom_' . $form_id, NULL);
-  if (!isset($mode)) {
-    $forms = _mollom_protectable_forms();
-    return isset($forms[$form_id]['mode']) ? $forms[$form_id]['mode'] : MOLLOM_MODE_DISABLED;
+  $mollom_form = mollom_form_load($form_id);
+  return isset($mollom_form['mode']) ? $mollom_form['mode'] : MOLLOM_MODE_DISABLED;
+}
+
+/**
+ * Returns information about protectable forms registered via hook_mollom_form_info().
+ *
+ * @param $form_id
+ *   (optional) The form id to return information for. If omitted, information
+ *   for all registered forms is returned.
+ */
+function mollom_get_form_info($form_id = NULL) {
+  static $form_info;
+
+  if (!isset($form_info)) {
+    module_load_include('inc', 'mollom', 'mollom.form');
+    $form_info = array();
+    foreach (module_implements('mollom_form_info') as $module) {
+      $function = $module . '_mollom_form_info';
+      $module_forms = $function();
+      foreach ($module_forms as $id => $info) {
+        // Ensure basic properties for all forms.
+        $module_forms[$id] += array(
+          'module' => $module,
+          'form_id' => $id,
+          'title' => $id,
+          'parents' => array(),
+        );
+      }
+      $form_info = array_merge_recursive($form_info, $module_forms);
+    }
+
+    // Allow modules to alter the default form information.
+    drupal_alter('mollom_form_info', $form_info);
   }
-  return $mode;
+
+  if (isset($form_id)) {
+    return isset($form_info[$form_id]) ? $form_info[$form_id] : array();
+  }
+  return $form_info;
 }
 
 /**
- * Returns a list of forms that can be protected with Mollom.
+ * Menu argument loader; Loads Mollom configuration for a given form id.
  */
-function _mollom_protectable_forms() {
-  static $forms = NULL;
+function mollom_form_load($form_id) {
+  $mollom_form = db_fetch_array(db_query_range("SELECT * FROM {mollom_form} WHERE form_id = '%s'", $form_id, 0, 1));
+  if ($mollom_form) {
+    $mollom_form['fields'] = unserialize($mollom_form['fields']);
+  }
+  return $mollom_form;
+}
 
-  if (!isset($forms)) {
-    if (module_exists('comment')) {
-      $forms['comment_form'] = array(
-        'name' => 'comment form',
-        'mode' => MOLLOM_MODE_ANALYSIS,
-      );
-    }
-    if (module_exists('contact')) {
-      $forms['contact_mail_page'] = array(
-        'name' => 'site-wide contact form',
-        'mode' => MOLLOM_MODE_ANALYSIS,
-      );
+/**
+ * Loads Mollom information and configuration for a given form id.
+ *
+ * This needs to live in mollom.module or the menu system won't recognize it.
+ */
+function mollom_admin_form_load($form_id) {
+  // Load form configuration.
+  $mollom_form = mollom_form_load($form_id);
+  if (!$mollom_form) {
+    $mollom_form = array();
+  }
 
-      $forms['contact_mail_user'] = array(
-        'name' => 'per-user contact forms',
-        'mode' => MOLLOM_MODE_ANALYSIS,
-      );
+  // Attach form registry information.
+  $mollom_form += mollom_get_form_info($form_id);
+
+  // Ensure default values for administrative configuration.
+  $mollom_form += array(
+    'form_id' => $form_id,
+    'title' => $form_id,
+    'parents' => array(),
+    'fields' => array(),
+  );
+
+  return $mollom_form;
+}
+
+/**
+ * Saves a Mollom form configuration.
+ */
+function mollom_form_save(&$mollom_form) {
+  $exists = db_result(db_query_range("SELECT 1 FROM {mollom_form} WHERE form_id = '%s'", $mollom_form['form_id'], 0, 1));
+  if ($exists) {
+    $status = drupal_write_record('mollom_form', $mollom_form, 'form_id');
+  }
+  else {
+    $status = drupal_write_record('mollom_form', $mollom_form);
+  }
+  // Allow modules to react on saved form configurations.
+  if (isset($status) && $status) {
+    if ($status === SAVED_NEW) {
+      module_invoke_all('mollom_form_insert', $mollom_form);
     }
-    $forms['user_register'] = array(
-      'name' => 'user registration form',
-      'mode' => MOLLOM_MODE_CAPTCHA,
-    );
-    $forms['user_pass'] = array(
-      'name' => 'user password request form',
-      'mode' => MOLLOM_MODE_CAPTCHA,
-    );
-    // Add all node forms.
-    $types = node_get_types('names');
-    foreach ($types as $type => $name) {
-      $forms[$type . '_node_form'] = array(
-        'name' => drupal_strtolower($name) . ' form',
-        'mode' => MOLLOM_MODE_ANALYSIS,
-      );
+    elseif ($status === SAVED_UPDATED) {
+      module_invoke_all('mollom_form_update', $mollom_form);
     }
   }
+  return $status;
+}
 
-  return $forms;
+/**
+ * Given an array of values and an array of fields, extract data for use.
+ *
+ * @param $values
+ *   An array of data, usually $form_state['values'].
+ * @param $fields
+ *   An array of strings representing fields to extract. Nested fields are in
+ *   the form of 'parent[child]'.
+ */
+function mollom_form_get_values($values, $fields) {
+  global $user;
+
+  $field_data = array();
+  foreach ($fields as $field) {
+    // Nested field values use 'parent][child', so we need to recurse.
+    $parents = explode('][', $field);
+    $field_value = $values;
+    foreach ($parents as $key) {
+      $field_value = isset($field_value[$key]) ? $field_value[$key] : NULL;
+    }
+    // Only save values that are not empty.
+    if (isset($field_value) && strlen($field_value)) {
+      $field_data[$field] = $field_value;
+    }
+  }
+
+  $data = array();
+  // Add the data from the fields.
+  // @todo Allow hook_mollom_form_info() to specify a form element mapping.
+  $data['post_title'] = isset($values['title']) ? $values['title'] : (isset($values['subject']) ? $values['subject'] : NULL);
+  $data['post_body'] = !empty($fields_data) ? implode("\n", $fields_data) : NULL;
+
+  // Add common data.
+  $data['author_name'] = isset($values['name']) ? $values['name'] : (isset($user->name) ? $user->name : NULL);
+  $data['author_mail'] = isset($values['mail']) ? $values['mail'] : (isset($user->mail) ? $user->mail : NULL);
+  $data['author_url'] = isset($values['homepage']) ? $values['homepage'] : NULL;
+  $data['author_openid'] = $user->uid ? _mollom_get_openid($user) : NULL;
+  $data['author_id'] = $user->uid ? $user->uid : NULL;
+  // @todo For any reason, the previous data collector functions used the
+  //   content's internal system id (i.e. nid, cid, etc.) instead of the IP
+  //   address...?
+  $data['author_ip'] = ip_address();
+
+  return $data;
 }
 
 /**
@@ -637,6 +791,7 @@ function mollom_expand_element($element,
       '#require_captcha' => $element['#mode'] == MOLLOM_MODE_CAPTCHA,
       '#passed_captcha' => FALSE,
       '#user_session_id' => session_id(),
+      '#fields' => $element['#fields'],
     );
   }
 
@@ -726,6 +881,7 @@ function mollom_validate_analysis(&$moll
   $data = array();
 
   $form_id = $form_state['values']['form_id'];
+  $data = mollom_form_get_values($form_state['values'], $mollom_state['#fields']);
 
   $pos = strpos($form_id, '_node_form');
   if ($pos !== FALSE) {
@@ -970,11 +1126,12 @@ function mollom($method, $data = array()
 /**
  * Helper function to debug the form API workflow.
  *
- * Uncomment the function body to activate.
+ * Comment the return to enable.
  */
 function _mollom_debug($message) {
-  // print $message .'<br />';
-  // error_log($message);
+  return;
+  print $message .'<br />';
+  error_log($message);
 }
 
 /**
Index: tests/mollom.test
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/mollom/tests/Attic/mollom.test,v
retrieving revision 1.1.2.20
diff -u -p -r1.1.2.20 mollom.test
--- tests/mollom.test	19 Nov 2009 16:48:59 -0000	1.1.2.20
+++ tests/mollom.test	20 Nov 2009 21:17:53 -0000
@@ -127,6 +127,78 @@ class MollomWebTestCase extends DrupalWe
   }
 
   /**
+   * Configure Mollom protection for a given form.
+   *
+   * @param $form_id
+   *   The form id to configure.
+   * @param $fields
+   *   (optional) A list of form elements to enable for text analysis.
+   */
+  protected function setProtection($form_id, $fields = NULL) {
+    // Determine whether the form is already protected.
+    $exists = db_result(db_query_range("SELECT 1 FROM {mollom_form} WHERE form_id = '%s'", $form_id, 0, 1));
+    // Add a new form.
+    if (!$exists) {
+      $this->drupalGet('admin/settings/mollom');
+      $this->clickLink(t('Add form'));
+      $edit = array(
+        'mollom[form_id]' => $form_id,
+      );
+      $this->drupalPost(NULL, $edit, t('Next'));
+    }
+    // Edit an existing form.
+    else {
+      $this->drupalGet('admin/settings/mollom/manage/' . $form_id);
+    }
+
+    $edit = array();
+    // Explicitly enable the passed fields, if $fields were passed.
+    if (isset($fields)) {
+      foreach ($fields as $field) {
+        $edit['mollom[fields][' . $field . ']'] = TRUE;
+      }
+    }
+    $form_info = mollom_get_form_info($form_id);
+    foreach (array_keys($form_info['parents']) as $field) {
+      // Due to SimpleTest's form handling of checkboxes, we need to disable all
+      // remaining checkboxes manually.
+      if (isset($fields)) {
+        if (!isset($edit[$field])) {
+          $edit['mollom[fields][' . $field . ']'] = FALSE;
+        }
+      }
+      // If no $fields were passed, enable all elements exposed by the
+      // implementation.
+      else {
+        $edit['mollom[fields][' . $field . ']'] = TRUE;
+      }
+    }
+    $this->drupalPost(NULL, $edit, t('Save'));
+    if (!$exists) {
+      $this->assertText(t('The form protection has been added.'));
+    }
+    else {
+      $this->assertText(t('The form protection has been updated.'));
+    }
+  }
+
+  /**
+   * Remove Mollom protection for a given form.
+   *
+   * @param $form_id
+   *   The form id to configure.
+   */
+  protected function delProtection($form_id) {
+    // Determine whether the form is protected.
+    $exists = db_result(db_query_range("SELECT 1 FROM {mollom_form} WHERE form_id = '%s'", $form_id, 0, 1));
+    if ($exists) {
+      $this->drupalGet('admin/settings/mollom/unprotect/' . $form_id);
+      $this->assertText(t('Mollom will no longer protect this form from spam.'), t('Unprotect confirmation form found.'));
+      $this->drupalPost(NULL, array(), t('Confirm'));
+    }
+  }
+
+  /**
    * Test that the CAPTCHA field is found on the current page.
    */
   protected function assertCaptchaField() {
@@ -297,7 +369,7 @@ class MollomAccessTestCase extends Mollo
   function testKeyPairs() {
     // Check that a success message is shown.
     $this->drupalLogin($this->admin_user);
-    $this->drupalGet('admin/settings/mollom');
+    $this->drupalGet('admin/settings/mollom/settings');
     $this->assertText(t('We contacted the Mollom servers to verify your keys: the Mollom services are operating correctly. We are now blocking spam.'));
 
     // Set up invalid test keys and check that an error message is shown.
@@ -305,10 +377,10 @@ class MollomAccessTestCase extends Mollo
       'mollom_public_key' => 'invalid-public-key',
       'mollom_private_key' => 'invalid-private-key',
     );
-    $this->drupalPost('admin/settings/mollom', $edit, t('Save configuration'));
+    $this->drupalPost(NULL, $edit, t('Save configuration'));
     $this->assertText(t('The configuration options have been saved.'));
 
-    $this->drupalGet('admin/settings/mollom');
+    $this->drupalGet('admin/settings/mollom/settings');
     $this->assertRaw(t('"messages error"'), t('The Mollom settings page reports that the Mollom keys are invalid.'));
   }
 
@@ -326,8 +398,8 @@ class MollomAccessTestCase extends Mollo
     $this->assertResponse(200);
 
     // Check access for a user that has everything except the 'administer
-    // site configuration' permission. This user should not have access
-    // to the Mollom settings page.
+    // mollom' permission. This user should not have access to the Mollom
+    // settings page.
     $this->web_user = $this->drupalCreateUser(array_diff(module_invoke_all('perm'), array('administer mollom')));
     $this->drupalLogin($this->web_user);
     $this->drupalGet('admin/settings/mollom');
@@ -346,8 +418,10 @@ class MollomFallbackTestCase extends Mol
 
   function setUp() {
     parent::setUp();
+
     // Enable Mollom for the request password form.
-    variable_set('mollom_user_pass', MOLLOM_MODE_CAPTCHA);
+    $this->drupalLogin($this->admin_user);
+    $this->setProtection('user_pass');
   }
 
   /**
@@ -356,8 +430,7 @@ class MollomFallbackTestCase extends Mol
    */
   function testFallbackMechanismBlock() {
     // Set the fallback strategy to 'blocking mode'.
-    $this->drupalLogin($this->admin_user);
-    $this->drupalPost('admin/settings/mollom', array('mollom_fallback' => MOLLOM_FALLBACK_BLOCK), t('Save configuration'));
+    $this->drupalPost('admin/settings/mollom/settings', array('mollom_fallback' => MOLLOM_FALLBACK_BLOCK), t('Save configuration'));
     $this->assertText('The configuration options have been saved.');
     $this->drupalLogout();
 
@@ -378,8 +451,7 @@ class MollomFallbackTestCase extends Mol
    */
   function testFallbackMechanismAccept() {
     // Set the fallback strategy to 'accept mode'.
-    $this->drupalLogin($this->admin_user);
-    $this->drupalPost('admin/settings/mollom', array('mollom_fallback' => MOLLOM_FALLBACK_ACCEPT), t('Save configuration'));
+    $this->drupalPost('admin/settings/mollom/settings', array('mollom_fallback' => MOLLOM_FALLBACK_ACCEPT), t('Save configuration'));
     $this->assertText('The configuration options have been saved.');
     $this->drupalLogout();
 
@@ -400,6 +472,7 @@ class MollomFallbackTestCase extends Mol
    * @todo @todo Test mail sending with assertMail() now that it is available.
    */
   function testFailoverMechanism() {
+    $this->drupalLogout();
     // Set the fallback strategy to 'blocking mode', so that if the failover
     // mechanism does not work, we would expect to get a warning.
     variable_set('mollom_fallback', MOLLOM_FALLBACK_BLOCK);
@@ -411,8 +484,7 @@ class MollomFallbackTestCase extends Mol
       'http://fake-host-2',
       'http://xmlrpc1.mollom.com', // The real server.
       'http://fake-host-3',
-      )
-    );
+    ));
 
     // Validate that the request password form has a CAPTCHA text field and
     // that a user is not blocked from submitting it.
@@ -477,11 +549,13 @@ class MollomUserFormsTestCase extends Mo
   /**
    * Make sure that the request password form is protected correctly.
    *
-   * @todo @todo Test mail sending with assertMail() now that it is available.
+   * @todo Test mail sending with assertMail() now that it is available.
    */
   function testProtectRequestPassword() {
     // We first enable Mollom for the request password form.
-    variable_set('mollom_user_pass', MOLLOM_MODE_CAPTCHA);
+    $this->drupalLogin($this->admin_user);
+    $this->setProtection('user_pass');
+    $this->drupalLogout();
 
     // Create a new user.
     $this->web_user = $this->drupalCreateUser();
@@ -502,7 +576,9 @@ class MollomUserFormsTestCase extends Mo
    */
   function testProtectRegisterUser() {
     // We first enable Mollom for the user registration form.
-    variable_set('mollom_user_register', MOLLOM_MODE_CAPTCHA);
+    $this->drupalLogin($this->admin_user);
+    $this->setProtection('user_register');
+    $this->drupalLogout();
 
     // Validate that the user registration form has a CAPTCHA text field.
     $this->drupalGet('user/register');
@@ -543,7 +619,6 @@ class MollomCommentFormTestCase extends 
     $this->web_user = $this->drupalCreateUser(array('access comments', 'post comments', 'post comments without approval', 'create story content'));
     $this->node = $this->drupalCreateNode(array('type' => 'story', 'uid' => $this->web_user->uid));
     variable_set('comment_preview_story', COMMENT_PREVIEW_OPTIONAL);
-    $this->drupalLogin($this->web_user);
   }
 
   /**
@@ -551,9 +626,12 @@ class MollomCommentFormTestCase extends 
    */
   function testUnprotectedCommentForm() {
     // Disable Mollom for comments.
-    variable_set('mollom_comment_form', MOLLOM_MODE_DISABLED);
+    $this->drupalLogin($this->admin_user);
+    $this->delProtection('comment_form');
+    $this->drupalLogout();
 
     // Request the comment reply form. There should be no CAPTCHA.
+    $this->drupalLogin($this->web_user);
     $this->drupalGet('comment/reply/'. $this->node->nid);
     $this->assertNoCaptchaField();
 
@@ -563,7 +641,7 @@ class MollomCommentFormTestCase extends 
 
     // Save the comment and make sure it appears.
     $this->drupalPost(NULL, array(), t('Save'));
-    $this->assertRaw('<p>spam</p>', t('A comment that is known to be ham appears on the screen after it is submitted.'));
+    $this->assertRaw('<p>spam</p>', t('A comment that is known to be spam appears on the screen after it is submitted.'));
   }
 
   /**
@@ -571,9 +649,12 @@ class MollomCommentFormTestCase extends 
    */
   function testCaptchaProtectedCommentForm() {
     // Enable Mollom CAPTCHA protection for comments.
-    variable_set('mollom_comment_form', MOLLOM_MODE_CAPTCHA);
+    $this->drupalLogin($this->admin_user);
+    $this->setProtection('comment_form', array());
+    $this->drupalLogout();
 
     // Request the comment reply form. There should be a CAPTCHA form.
+    $this->drupalLogin($this->web_user);
     $this->drupalGet('comment/reply/'. $this->node->nid);
     $this->assertCaptchaField();
 
@@ -599,9 +680,12 @@ class MollomCommentFormTestCase extends 
    */
   function testTextAnalysisProtectedCommentForm() {
     // Enable Mollom text-classification for comments.
-    variable_set('mollom_comment_form', MOLLOM_MODE_ANALYSIS);
+    $this->drupalLogin($this->admin_user);
+    $this->setProtection('comment_form');
+    $this->drupalLogout();
 
     // Request the comment reply form.  Initially, there should be no CAPTCHA.
+    $this->drupalLogin($this->web_user);
     $this->drupalGet('comment/reply/'. $this->node->nid);
     $this->assertNoCaptchaField();
 
@@ -688,7 +772,6 @@ class MollomContactFormTestCase extends 
     parent::setUp('contact');
 
     $this->web_user = $this->drupalCreateUser(array('access site-wide contact form', 'access user profiles'));
-    $this->drupalLogin($this->web_user);
   }
 
   /**
@@ -698,8 +781,11 @@ class MollomContactFormTestCase extends 
    */
   function testProtectContactUserForm() {
     // Enable Mollom for the contact form.
-    variable_set('mollom_contact_mail_user', MOLLOM_MODE_ANALYSIS);
+    $this->drupalLogin($this->admin_user);
+    $this->setProtection('contact_mail_user');
+    $this->drupalLogout();
 
+    $this->drupalLogin($this->web_user);
     $url = 'user/' . $this->admin_user->uid . '/contact';
     $button = t('Send e-mail');
     $success = t('The message has been sent.');
@@ -724,9 +810,12 @@ class MollomContactFormTestCase extends 
    */
   function testProtectContactSiteForm() {
     // Enable Mollom for the contact form.
-    variable_set('mollom_contact_mail_page', MOLLOM_MODE_ANALYSIS);
+    $this->drupalLogin($this->admin_user);
+    $this->setProtection('contact_mail_page');
+    $this->drupalLogout();
 
     // Add some fields to the contact form so that it is active.
+    $this->drupalLogin($this->web_user);
     db_query("INSERT INTO {contact} (category, recipients, reply) VALUES ('%s', '%s', '%s')", 'test category', $this->web_user->mail, 'test auto-reply');
 
     $url = 'contact';
