Index: mollom.admin.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/mollom/mollom.admin.inc,v
retrieving revision 1.1.2.35
diff -u -p -r1.1.2.35 mollom.admin.inc
--- mollom.admin.inc	12 Sep 2010 18:08:04 -0000	1.1.2.35
+++ mollom.admin.inc	12 Sep 2010 21:07:26 -0000
@@ -94,6 +94,12 @@ function mollom_admin_configure_form(&$f
       $mollom_form = $form_state['storage']['mollom_form'];
     }
   }
+  // When adding a new form configuration, passing form_id via path argument.
+  elseif (is_string($mollom_form)) {
+    $mollom_form = mollom_form_new($mollom_form);
+    $form_state['storage']['step'] = 'configure';
+    $form_state['storage']['mollom_form'] = $mollom_form;
+  }
   // Otherwise, we are editing an existing form configuration.
   else {
     $form_state['storage']['step'] = 'configure';
@@ -118,6 +124,7 @@ function mollom_admin_configure_form(&$f
       break;
 
     case 'configure':
+      drupal_set_title(t('Configure %form-title protection', array('%form-title' => $mollom_form['title'])));
       $form['#after_build'][] = 'mollom_admin_configure_form_after_build';
 
       // Display a list of fields for textual analysis (last step).
@@ -125,34 +132,40 @@ function mollom_admin_configure_form(&$f
         '#type' => 'value',
         '#value' => $mollom_form['form_id'],
       );
-      $form['mollom']['form_title'] = array(
-        '#type' => 'item',
-        '#title' => t('Form'),
-        '#value' => $mollom_form['title'],
-      );
+
+      $modes = array();
+      // Textual analysis, if any elements are available.
+      if (!empty($mollom_form['elements'])) {
+        $modes[MOLLOM_MODE_ANALYSIS] = t('Text analysis');
+      }
+      // CAPTCHA-only, always available.
+      $modes[MOLLOM_MODE_CAPTCHA] = t('CAPTCHA');
+
       $form['mollom']['mode'] = array(
         '#type' => 'radios',
         '#title' => t('Protection mode'),
-        '#options' => array(
-          MOLLOM_MODE_CAPTCHA => t('CAPTCHA only'),
-        ),
+        '#options' => $modes,
         '#default_value' => $mollom_form['mode'],
       );
 
       if (!empty($mollom_form['elements'])) {
-        // Add the text analysis protection mode as first, suggested option.
-        $form['mollom']['mode']['#options'] = array(
-          MOLLOM_MODE_ANALYSIS => t('Text analysis and CAPTCHA backup'),
-        ) + $form['mollom']['mode']['#options'];
         // If not re-configuring an existing protection, make it the default.
-        if (!isset($form['mollom']['mode']['#default_value'])) {
+        if (!isset($mollom_form['mode'])) {
           $form['mollom']['mode']['#default_value'] = MOLLOM_MODE_ANALYSIS;
         }
 
-        // @todo Actually belongs to MOLLOM_MODE_ANALYSIS only.
-        $form['mollom']['mode']['#description'] = t('If %text-analysis-option is selected, a CAPTCHA will only be displayed if Mollom is unsure about the content.', array(
-          '%text-analysis-option' => $form['mollom']['mode']['#options'][MOLLOM_MODE_ANALYSIS],
-        ));
+        // Textual analysis filters.
+        $form['mollom']['checks'] = array(
+          '#type' => 'checkboxes',
+          '#title' => t('Analyze text for'),
+          '#options' => array(
+            'spam' => t('Spam'),
+          ),
+          '#default_value' => $mollom_form['checks'],
+          // D7 only.
+          '#value' => array('spam' => 'spam'),
+          '#access' => FALSE,
+        );
 
         // Form elements defined by hook_mollom_form_info() use the
         // 'parent][child' syntax, which Form API also uses internally for
@@ -175,7 +188,7 @@ function mollom_admin_configure_form(&$f
         }
         $form['mollom']['enabled_fields'] = array(
           '#type' => 'checkboxes',
-          '#title' => t('Fields to analyze'),
+          '#title' => t('Text fields to analyze'),
           '#options' => $elements,
           '#default_value' => $enabled_fields,
           '#required' => $mollom_form['mode'] == MOLLOM_MODE_ANALYSIS,
@@ -212,31 +225,43 @@ function mollom_admin_configure_form_aft
  */
 function mollom_admin_configure_form_next_submit($form, &$form_state) {
   $form_id = $form_state['values']['mollom']['form_id'];
+  $form_state['redirect'] = $_GET['q'] . '/' . $form_id;
+  // D6 only.
+  unset($form_state['storage']);
+}
 
-  // Load form information into $form_state for configuration.
-  $form_list = mollom_form_list();
-  $mollom_form = mollom_form_info($form_id, $form_list[$form_id]['module']);
-
-  // Enable all fields for textual analysis by default.
-  if (!empty($mollom_form['elements'])) {
-    $mollom_form['enabled_fields'] = array_keys($mollom_form['elements']);
-  }
-  else {
-    $mollom_form['enabled_fields'] = array();
-  }
-  $form_state['storage']['mollom_form'] = $mollom_form;
+/**
+ * Form validation handler for mollom_admin_configure_form().
+ */
+function mollom_admin_configure_form_validate(&$form, &$form_state) {
+  // For the 'configure' step, output custom #required form element errors for
+  // 'checks' and 'enabled_fields', as their labels do not work with the default
+  // #required form error message.
+  if ($form_state['storage']['step'] == 'configure') {
+    // Make field checkboxes required, if protection mode is textual analysis.
+    $required = ($form_state['values']['mollom']['mode'] == MOLLOM_MODE_ANALYSIS);
+    $form['mollom']['checks']['#required'] = $required;
+    $form['mollom']['enabled_fields']['#required'] = $required;
 
-  $form_state['storage']['step'] = 'configure';
+    if ($required && !array_filter($form_state['values']['mollom']['checks'])) {
+      form_error($form['mollom']['checks'], t('At least one text analysis check is required.'));
+    }
+    if ($required && !array_filter($form_state['values']['mollom']['enabled_fields'])) {
+      form_error($form['mollom']['enabled_fields'], t('At least one field is required for text analysis.'));
+    }
+  }
 }
 
 /**
- * Form submit handler for Mollom form configuration form.
+ * Form submit handler for mollom_admin_configure_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'];
 
+  // Only store a list of enabled textual analysis checks.
+  $mollom_form['checks'] = array_keys(array_filter($mollom_form['checks']));
   // Prepare selected fields for storage.
   $enabled_fields = array();
   foreach (array_keys(array_filter($mollom_form['enabled_fields'])) as $field) {
Index: mollom.api.php
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/mollom/mollom.api.php,v
retrieving revision 1.1.2.6
diff -u -p -r1.1.2.6 mollom.api.php
--- mollom.api.php	18 Aug 2010 00:43:27 -0000	1.1.2.6
+++ mollom.api.php	12 Sep 2010 19:49:18 -0000
@@ -148,9 +148,6 @@
  *   switch ($form_id) {
  *     case 'im_message_form':
  *       $form_info = array(
- *         // Identical to hook_mollom_form_list():
- *         'title' => t('Instant messaging form'),
- *         'entity' => 'im',
  *         // Optional: User permission list to skip Mollom's protection for.
  *         'bypass access' => array('administer instant messages'),
  *         // Optional: To allow textual analysis of the form values, the form
@@ -226,7 +223,12 @@
  *   protected, keyed by $form_id:
  *   - title: The human-readable name of the form.
  *   - entity: (optional) The internal name of the entity type the form is for,
- *     e.g. 'node' or 'comment'. See hook_mollom_form_info() for details.
+ *     e.g. 'node' or 'comment'. This is required for all forms that will store
+ *     the submitted content persistently. It is only optional for forms that do
+ *     not permanently store the submitted form values, such as contact forms
+ *     that only send an e-mail, but do not store it in the database.
+ *     Note that forms that specify 'entity' also need to specify 'post_id' in
+ *     the 'mapping' (see below).
  *   - report access callback: (optional) A function name to invoke to check
  *     access to Mollom's dedicated "report to Mollom" form, which should return
  *     either TRUE or FALSE (like any other menu "access callback").
@@ -265,7 +267,6 @@ function hook_mollom_form_list() {
  *
  * @return
  *   An associative array describing the form identified by $form_id:
- *   - title: The human-readable name of the form.
  *   - mode: (optional) The default protection mode for the form, which can be
  *     one of:
  *     - MOLLOM_MODE_ANALYSIS: Text analysis of submitted form values with
@@ -280,13 +281,6 @@ function hook_mollom_form_list() {
  *     Mollom' link will be included at the bottom of the mail body. Be sure to
  *     include only user-submitted mails and not any mails sent by Drupal since
  *     they should never be reported as spam.
- *   - entity: (optional) The internal name of the entity type the form is for,
- *     e.g. 'node' or 'comment'. This is required for all forms that will store
- *     the submitted content persistently. It is only optional for forms that do
- *     not permanently store the submitted form values, such as contact forms
- *     that only send an e-mail, but do not store it in the database.
- *     Note that forms that specify 'entity' also need to specify 'post_id' in
- *     the 'mapping' (see below).
  *   - elements: (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)
@@ -328,11 +322,9 @@ function hook_mollom_form_info($form_id)
     // Mymodule's comment form.
     case 'mymodule_comment_form':
       $form_info = array(
-        'title' => t('Comment form'),
         'mode' => MOLLOM_MODE_ANALYSIS,
         'bypass access' => array('administer comments'),
         'mail ids' => array('mymodule_comment_mail'),
-        'entity' => 'comment',
         'elements' => array(
           'subject' => t('Subject'),
           'body' => t('Body'),
@@ -350,9 +342,7 @@ function hook_mollom_form_info($form_id)
     // Mymodule's user registration form.
     case 'mymodule_user_register':
       $form_info = array(
-        'title' => t('User registration form'),
         'mode' => MOLLOM_MODE_CAPTCHA,
-        'entity' => 'user',
         'mapping' => array(
           'post_id' => 'uid',
           'author_name' => 'name',
Index: mollom.install
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/mollom/mollom.install,v
retrieving revision 1.2.2.32
diff -u -p -r1.2.2.32 mollom.install
--- mollom.install	12 Sep 2010 18:08:04 -0000	1.2.2.32
+++ mollom.install	12 Sep 2010 20:59:09 -0000
@@ -77,17 +77,17 @@ function mollom_requirements($phase = 'r
  */
 function mollom_schema() {
   $schema['mollom'] = array(
-    'description' => 'Tracks content spam statuses.',
+    'description' => 'Stores Mollom responses for content.',
     'fields' => array(
       'entity' => array(
-        'description' => 'The entity type of the content.',
+        'description' => 'Entity type of the content.',
         'type' => 'varchar',
         'length' => 32,
         'not null' => TRUE,
         'default' => '',
       ),
       'did' => array(
-        'description' => 'Unique data ID of the content.',
+        'description' => 'Unique entity ID of the content.',
         'type' => 'varchar',
         'length' => 32,
         'not null' => TRUE,
@@ -101,27 +101,29 @@ function mollom_schema() {
         'default' => '',
       ),
       'changed' => array(
-        'description' => 'The Unix timestamp when the data was changed.',
+        'description' => 'Unix timestamp when the data was changed.',
         'type' => 'int',
         'not null' => TRUE,
         'default' => 0,
       ),
-      'quality' => array(
-        'description' => "A quality rating assigned to the content to tell whether or not it's spam. Experimental; Mollom might return inconsistent values.",
-        'type' => 'varchar',
-        'length' => 255,
-        'not null' => TRUE,
-        'default' => '',
+      // Server response columns are NULL by default, because any default value
+      // would have an unintended meaning. Also, values are stored in individual
+      // columns, so as to be able to join and filter/sort on these values for
+      // improved content moderation.
+      'spam' => array(
+        'description' => 'Text analysis spam check result.',
+        'type' => 'int',
+        'size' => 'tiny',
+        'not null' => FALSE,
       ),
-      'reputation' => array(
-        'description' => "The reputation of the author. Experimental; Mollom might return inconsistent values.",
-        'type' => 'varchar',
-        'length' => 255,
-        'not null' => TRUE,
-        'default' => '',
+      'quality' => array(
+        'description' => 'Text analysis quality check result.',
+        'type' => 'float',
+        'size' => 'tiny',
+        'not null' => FALSE,
       ),
       'languages' => array(
-        'description' => "A space-delimited language codes the content might be written in. Experimental; Mollom might return inconsistent values.",
+        'description' => 'Text analysis language check result.',
         'type' => 'varchar',
         'length' => 255,
         'not null' => TRUE,
@@ -133,29 +135,36 @@ function mollom_schema() {
   );
 
   $schema['mollom_form'] = array(
-    'description' => 'Stores configuration for forms protected by Mollom.',
+    'description' => 'Stores configuration of forms protected by Mollom.',
     'fields' => array(
       'form_id' => array(
-        'description' => 'The $form_id of the form being protected.',
+        'description' => 'The protected form ID.',
         'type' => 'varchar',
         'length' => 255,
         'not null' => TRUE,
         'default' => '',
       ),
       'mode' => array(
-        'description' => 'The configured protection mode to use for the form.',
+        'description' => 'Protection mode for the form.',
         'type' => 'int',
         'size' => 'tiny',
         'not null' => TRUE,
         'default' => 0,
       ),
+      'checks' => array(
+        'description' => 'Text analyis checks to perform.',
+        'type' => 'text',
+        'not null' => FALSE,
+        'serialize' => TRUE,
+      ),
       'enabled_fields' => array(
-        'description' => 'A list of form elements configured for textual analysis.',
+        'description' => 'Form elements to analyze.',
         'type' => 'text',
+        'not null' => FALSE,
         'serialize' => TRUE,
       ),
       'module' => array(
-        'description' => 'The module name the $form_id belongs to.',
+        'description' => 'Module name owning the form.',
         'type' => 'varchar',
         'length' => 255,
         'not null' => TRUE,
@@ -238,8 +247,8 @@ function mollom_update_3() {
  * Add a reputation field to the mollom table.
  */
 function mollom_update_4() {
+  // Unused. Removed in mollom_update_6114().
   $ret = array();
-  db_add_field($ret, 'mollom', 'reputation', array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => ''));
   return $ret;
 }
 
@@ -257,7 +266,6 @@ function mollom_update_6105() {
         'default' => '',
       ),
       'mode' => array(
-        'description' => 'The configured protection mode to use for the form.',
         'type' => 'int',
         'size' => 'tiny',
         'not null' => TRUE,
@@ -268,7 +276,6 @@ function mollom_update_6105() {
         'serialize' => TRUE,
       ),
       'module' => array(
-        'description' => 'The module name the $form_id belongs to.',
         'type' => 'varchar',
         'length' => 255,
         'not null' => TRUE,
@@ -311,12 +318,12 @@ function mollom_update_6106() {
   $ret = array();
   // Add the 'entity' column.
   db_add_field($ret, 'mollom', 'entity', array(
-    'description' => 'The entity type of the content.',
     'type' => 'varchar',
     'length' => 32,
     'not null' => TRUE,
     'default' => '',
   ));
+
   // Change the primary key to prevent duplicate key errors in the following
   // data conversions.
   db_drop_primary_key($ret, 'mollom');
@@ -333,7 +340,6 @@ function mollom_update_6106() {
   // @todo We do not change the type to 'int' here to still support named
   //   identifiers. Reconsider this.
   db_change_field($ret, 'mollom', 'did', 'did', array(
-    'description' => 'Unique data ID of the content.',
     'type' => 'varchar',
     'length' => 32,
     'not null' => TRUE,
@@ -422,3 +428,67 @@ function mollom_update_6112() {
   }
   return $ret;
 }
+
+/**
+ * Replace {mollom_form}.data with {mollom_form}.checks.
+ */
+function mollom_update_6113() {
+  $ret = array();
+  // Add {mollom_form}.checks.
+  if (!db_column_exists('mollom_form', 'checks')) {
+    db_add_field($ret, 'mollom_form', 'checks', array(
+      'type' => 'text',
+      'not null' => FALSE,
+      'serialize' => TRUE,
+    ));
+    // Default all checks to 'spam', including CAPTCHA-only rows, so spam
+    // checking is enabled by default when switching the protection mode.
+    // @see update_sql()
+    $sql = "UPDATE {mollom_form} SET checks = '$checks'";
+    $result = db_query($sql, array(serialize(array('spam'))));
+    $ret[] = array('success' => $result !== FALSE, 'query' => check_plain($sql));
+  }
+  // {mollom_form}.data did never exist in D6.
+
+  return $ret;
+}
+
+/**
+ * Clean up and complete server response columns in {mollom}.
+ */
+function mollom_update_6114() {
+  $ret = array();
+  // Remove 'reputation' column introduced in mollom_update_4(); unused.
+  if (db_column_exists('mollom', 'reputation')) {
+    db_drop_field($ret, 'mollom', 'reputation');
+  }
+
+  // Change {mollom}.quality from varchar into float.
+  db_change_field($ret, 'mollom', 'quality', 'quality', array(
+    'type' => 'float',
+    'size' => 'tiny',
+    'not null' => FALSE,
+  ));
+
+  // Add {mollom}.spam.
+  if (!db_column_exists('mollom', 'spam')) {
+    db_add_field($ret, 'mollom', 'spam', array(
+      'type' => 'int',
+      'size' => 'tiny',
+      'not null' => FALSE,
+    ));
+    // Fill {mollom}.spam with approximate values based on {mollom}.quality.
+    // Note that this is just to have some values. 'quality' and 'spam' are
+    // completely unrelated otherwise.
+    // MOLLOM_ANALYSIS_SPAM
+    $ret[] = update_sql("UPDATE {mollom} SET spam = 2 WHERE quality < 0.5");
+    // MOLLOM_ANALYSIS_UNSURE
+    $ret[] = update_sql("UPDATE {mollom} SET spam = 3 WHERE quality = 0.5");
+    // MOLLOM_ANALYSIS_HAM
+    $ret[] = update_sql("UPDATE {mollom} SET spam = 1 WHERE quality > 0.5");
+  }
+
+  // Addition of {mollom}.profanity deferred to D7.
+
+  return $ret;
+}
Index: mollom.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/mollom/mollom.module,v
retrieving revision 1.2.2.160
diff -u -p -r1.2.2.160 mollom.module
--- mollom.module	12 Sep 2010 18:08:04 -0000	1.2.2.160
+++ mollom.module	12 Sep 2010 20:58:20 -0000
@@ -794,12 +794,12 @@ function mollom_form_info($form_id, $mod
     $form_info = array();
   }
 
-  // Ensure basic properties for all forms.
+  // Ensure default properties.
   $form_info += array(
     'form_id' => $form_id,
+    'title' => $form_id,
     'module' => $module,
     'entity' => NULL,
-    'title' => $form_id,
     'mode' => NULL,
     'bypass access' => array(),
     'elements' => array(),
@@ -814,22 +814,52 @@ function mollom_form_info($form_id, $mod
 }
 
 /**
+ * Creates a bare Mollom form configuration.
+ *
+ * @param $form_id
+ *   (optional) The form id to create the Mollom form configuration for.
+ */
+function mollom_form_new($form_id = NULL) {
+  $mollom_form = array();
+  if (isset($form_id)) {
+    $form_list = mollom_form_list();
+    if (isset($form_list[$form_id])) {
+      $mollom_form += $form_list[$form_id];
+    }
+    $mollom_form += mollom_form_info($form_id, $form_list[$form_id]['module']);
+  }
+  // Ensure default properties.
+  $mollom_form += array(
+    'form_id' => $form_id,
+    'title' => $form_id,
+    'mode' => NULL,
+    'checks' => array(),
+    'enabled_fields' => array(),
+  );
+  // Enable all fields for textual analysis by default.
+  if (!empty($mollom_form['elements'])) {
+    $mollom_form['checks'] = array('spam');
+    $mollom_form['enabled_fields'] = array_keys($mollom_form['elements']);
+  }
+
+  return $mollom_form;
+}
+
+/**
  * Menu argument loader; Loads Mollom configuration and form information for a given form id.
  */
 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['checks'] = unserialize($mollom_form['checks']);
     $mollom_form['enabled_fields'] = unserialize($mollom_form['enabled_fields']);
 
     // Attach form registry information.
+    $form_list = mollom_form_list();
+    if (isset($form_list[$form_id])) {
+      $mollom_form += $form_list[$form_id];
+    }
     $mollom_form += mollom_form_info($form_id, $mollom_form['module']);
-
-    // Ensure default values (partially for administrative configuration).
-    $mollom_form += array(
-      'form_id' => $form_id,
-      'title' => $form_id,
-      'elements' => array(),
-    );
   }
   return $mollom_form;
 }
@@ -926,14 +956,14 @@ function mollom_form_get_values($form_va
     // $mapping with the actual form element value, and continue to the next
     // field. Also unset this field from $exclude_fields, so we can process the
     // remaining mappings below.
-    if (isset($exclude_fields[$field])) {
+    if (isset($exclude_fields[$field]) && drupal_validate_utf8($value)) {
       $mapping[$exclude_fields[$field]] = $value;
       unset($exclude_fields[$field]);
       continue;
     }
     // Only add form element values that are not empty.
     if (isset($value)) {
-      if (is_string($value) && drupal_strlen($value)) {
+      if (is_string($value) && drupal_validate_utf8($value) && drupal_strlen($value)) {
         $post_body[$field] = $value;
       }
       // Recurse into nested values (e.g. multiple value fields).
@@ -941,7 +971,9 @@ function mollom_form_get_values($form_va
         // Ensure we have a flat array to implode(); form values of
         // field_attach_form() use several subkeys.
         _mollom_flatten_form_values($value);
-        $post_body[$field] = implode("\n", $value);
+        if (($value = implode("\n", $value)) && drupal_validate_utf8($value)) {
+          $post_body[$field] = $value;
+        }
       }
     }
   }
@@ -957,7 +989,7 @@ function mollom_form_get_values($form_va
     foreach ($parents as $key) {
       $value = isset($value[$key]) ? $value[$key] : NULL;
     }
-    if (isset($value)) {
+    if (isset($value) && drupal_validate_utf8($value)) {
       $mapping[$property] = $value;
     }
   }
@@ -1088,6 +1120,8 @@ function _mollom_get_openid($account) {
  *     returned by Mollom servers.
  *   - servers: Boolean whether there is a non-empty list of Mollom servers.
  *
+ * @see mollom_init()
+ * @see mollom_admin_settings()
  * @see mollom_requirements()
  */
 function _mollom_status($reset = FALSE) {
@@ -1341,10 +1375,15 @@ function mollom_process_mollom_session_i
  *
  * Validation needs to re-run in case of a form validation error (elsewhere in
  * the form). In case Mollom's textual analysis returns no definite result, we
- * must fall back to a CAPTCHA.
+ * must trigger a CAPTCHA, but text analysis is always performed, even if the
+ * CAPTCHA was solved correctly.
  */
 function mollom_validate_analysis(&$form, &$form_state) {
-  if (!$form_state['mollom']['require_analysis'] || $form_state['mollom']['require_captcha']) {
+  // Text analysis may only ever be skipped, if we do not require it in the
+  // first place. With regard to that, $form_state['mollom']['require_analysis']
+  // is only set once during initialization of $form_state['mollom'] in
+  // mollom_process_form() and must not be updated elsewhere.
+  if (!$form_state['mollom']['require_analysis']) {
     return;
   }
 
@@ -1355,15 +1394,14 @@ function mollom_validate_analysis(&$form
   if (isset($data['post_id'])) {
     unset($data['post_id']);
   }
-  if (!empty($form_state['mollom']['response']['session_id'])) {
-    $data['session_id'] = $form_state['mollom']['response']['session_id'];
-  }
+  $data['session_id'] = $form_state['mollom']['response']['session_id'];
+  $data['checks'] = implode(',', $form_state['mollom']['checks']);
   $result = mollom('mollom.checkContent', $data);
   // Use all available data properties for log messages below.
   $data += $all_data;
 
   // Trigger global fallback behavior if there is no result.
-  if (!isset($result['session_id']) || !isset($result['spam'])) {
+  if (!isset($result['session_id'])) {
     return _mollom_fallback();
   }
 
@@ -1374,56 +1412,86 @@ function mollom_validate_analysis(&$form
   }
   // Store the response returned by Mollom.
   $form_state['mollom']['response'] = $result;
+  $form['mollom']['session_id']['#value'] = $result['session_id'];
 
+  // Handle the spam check result.
+  // The Mollom backend is remembering results of previous mollom.checkContent
+  // invocations for a single user/post session. When content is re-checked
+  // during form validation, the result may change according to the values that
+  // have been submitted (which e.g. can change during previews). Only in case
+  // the spam check led to a MOLLOM_ANALYSIS_UNSURE result, and the user solved
+  // the CAPTCHA correctly, subsequent spam check results will likely be
+  // MOLLOM_ANALYSIS_HAM (though not guaranteed).
   $teaser = truncate_utf8(strip_tags(isset($data['post_title']) ? $data['post_title'] : isset($data['post_body']) ? $data['post_body'] : '--'), 40);
+  if (isset($result['spam'])) {
+    switch ($result['spam']) {
+      case MOLLOM_ANALYSIS_HAM:
+        $form_state['mollom']['require_captcha'] = FALSE;
+        _mollom_watchdog(array(
+          'Ham: %teaser' => array('%teaser' => $teaser),
+          'Data:<pre>@data</pre>' => array('@data' => $data),
+          'Result:<pre>@result</pre>' => array('@result' => $result),
+        ), WATCHDOG_INFO);
+        break;
+
+      case MOLLOM_ANALYSIS_SPAM:
+        $form_state['mollom']['require_captcha'] = FALSE;
+        form_set_error('mollom', t('Your submission has triggered the spam filter and will not be accepted.'));
+        _mollom_watchdog(array(
+          'Spam: %teaser' => array('%teaser' => $teaser),
+          'Data:<pre>@data</pre>' => array('@data' => $data),
+          'Result:<pre>@result</pre>' => array('@result' => $result),
+        ));
+        break;
 
-  switch ($result['spam']) {
-    case MOLLOM_ANALYSIS_HAM:
-      $form_state['mollom']['require_captcha'] = FALSE;
-      _mollom_watchdog(array(
-        'Ham: %teaser' => array('%teaser' => $teaser),
-        'Data:<pre>@data</pre>' => array('@data' => $data),
-        'Result:<pre>@result</pre>' => array('@result' => $result),
-      ), WATCHDOG_INFO);
-      break;
-
-    case MOLLOM_ANALYSIS_SPAM:
-      $form_state['mollom']['require_captcha'] = FALSE;
-      form_set_error('mollom', t('Your submission has triggered the spam filter and will not be accepted.'));
-      _mollom_watchdog(array(
-        'Spam: %teaser' => array('%teaser' => $teaser),
-        'Data:<pre>@data</pre>' => array('@data' => $data),
-        'Result:<pre>@result</pre>' => array('@result' => $result),
-      ));
-      break;
+      case MOLLOM_ANALYSIS_UNSURE:
+        _mollom_watchdog(array(
+          'Unsure: %teaser' => array('%teaser' => $teaser),
+          'Data:<pre>@data</pre>' => array('@data' => $data),
+          'Result:<pre>@result</pre>' => array('@result' => $result),
+        ), WATCHDOG_INFO);
+
+        // Only throw a validation error and retrieve a CAPTCHA, if we check
+        // this post for the first time. Otherwise, mollom_validate_captcha()
+        // issued the CAPTCHA and needs to validate it prior to throwing any
+        // errors.
+        if (!$form_state['mollom']['require_captcha']) {
+          $form_state['mollom']['require_captcha'] = TRUE;
+          form_set_error('mollom][captcha', t('To complete this form, please complete the word verification below.'));
+        }
+        break;
 
-    case MOLLOM_ANALYSIS_UNSURE:
-      $form_state['mollom']['require_captcha'] = TRUE;
-      form_set_error('mollom][captcha', t('To complete this form, please complete the word verification below.'));
-      _mollom_watchdog(array(
-        'Unsure: %teaser' => array('%teaser' => $teaser),
-        'Data:<pre>@data</pre>' => array('@data' => $data),
-        'Result:<pre>@result</pre>' => array('@result' => $result),
-      ), WATCHDOG_INFO);
-      break;
+      case MOLLOM_ANALYSIS_UNKNOWN:
+      default:
+        // If we end up here, something went totally wrong.
+        _mollom_fallback();
+        break;
+    }
   }
 }
 
 /**
- * Form validation handler for CAPTCHA form element.
+ * Form validation handler for Mollom's CAPTCHA form element.
+ *
+ * Validates whether a CAPTCHA was solved correctly. A form may contain a
+ * CAPTCHA, if it was configured to be protected by a CAPTCHA only, or when the
+ * text analysis result is "unsure".
  */
 function mollom_validate_captcha(&$form, &$form_state) {
-  if (!$form_state['mollom']['require_captcha']) {
-    return;
-  }
-
-  // When re-validating a form that already passed a CAPTCHA in a previous
-  // request, we need to re-populate our global variable for mollom_data_save().
-  if ($form_state['mollom']['passed_captcha']) {
+  // CAPTCHA validation may only be skipped, if we do not require it in the
+  // first place, or if the user already solved a CAPTCHA correctly. We need to
+  // validate, if $form_state['mollom']['require_captcha'] is TRUE, which is
+  // either set during initialization of $form_state['mollom'] in
+  // mollom_process_form(), or after performing text analysis. The second
+  // return condition, $form_state['mollom']['passed_captcha'], may only ever be
+  // set by this validation handler and must not be changed elsewhere.
+ if (!$form_state['mollom']['require_captcha'] || $form_state['mollom']['passed_captcha']) {
     return;
   }
 
   // Nothing to validate if there is no value.
+  // @todo The field is #required, so Form API should already handle this. Add a
+  //   test to be sure and remove this code.
   if (empty($form_state['values']['mollom']['captcha'])) {
     form_set_error('mollom][captcha', t('The word verification field is required.'));
     return;
@@ -1444,11 +1512,16 @@ function mollom_validate_captcha(&$form,
   // Use all available data properties for log messages below.
   $data += $all_data;
 
+  // Invoke fallback behavior upon a server error; communication errors are
+  // handled by mollom() already. A server error may happen in case of an
+  // expired or invalid session_id.
+  if ($result === MOLLOM_ERROR) {
+    return _mollom_fallback();
+  }
+
   // Store the response for #submit handlers.
   $form_state['mollom']['response']['captcha'] = $result;
 
-  // Explictly check for TRUE, since mollom.checkCaptcha() can also return an
-  // error message (e.g. expired or invalid session_id).
   if ($result === TRUE) {
     $form_state['mollom']['passed_captcha'] = TRUE;
 
@@ -1556,8 +1629,9 @@ function mollom_form_submit($form, &$for
   if (!empty($form_state['mollom']['entity']) && isset($form_state['mollom']['mapping']['post_id'])) {
     // For new entities, the entity's form submit handler will have added the
     // new entity id value into $form_state['values'], so we need to rebuild the
-    // data mapping.
-    $data = mollom_form_get_values($form_state['values'], $form_state['mollom']['enabled_fields'], $form_state['mollom']['mapping']);
+    // data mapping. We do not care for the actual fields, only for the value of
+    // the mapped post_id.
+    $data = mollom_form_get_values($form_state['values'], array(), $form_state['mollom']['mapping']);
     // We only consider non-empty and non-zero values as valid entity ids.
     if (!empty($data['post_id'])) {
       mollom_data_save($form_state['mollom']['entity'], $data['post_id']);
@@ -2031,8 +2105,7 @@ function mollom_mail_add_report_link(&$m
  */
 function node_mollom_form_list() {
   $forms = array();
-  $types = node_get_types('types');
-  foreach ($types as $type) {
+  foreach (node_get_types('types') as $type) {
     $form_id = $type->type . '_node_form';
     $forms[$form_id] = array(
       'title' => t('@name form', array('@name' => $type->name)),
@@ -2053,10 +2126,8 @@ function node_mollom_form_info($form_id)
 
   $type = node_get_types('type', $nodetype);
   $form_info = array(
-    'title' => t('@name form', array('@name' => $type->name)),
     // @todo This is incompatible with node access.
     'bypass access' => array('administer nodes', 'edit any ' . $type->type . ' content'),
-    'entity' => 'node',
     'bundle' => $type->type,
     'elements' => array(),
     'mapping' => array(
@@ -2073,11 +2144,10 @@ function node_mollom_form_info($form_id)
     $form_info['elements']['body'] = check_plain($type->body_label);
   }
 
-  // Add CCK fields by default.
+  // Add text fields.
   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') {
         $form_info['elements'][$field_name] = check_plain(t($field['widget']['label']));
       }
@@ -2156,10 +2226,8 @@ function comment_mollom_form_list() {
  */
 function comment_mollom_form_info($form_id) {
   $form_info = array(
-    'title' => t('Comment form'),
     'mode' => MOLLOM_MODE_ANALYSIS,
     'bypass access' => array('administer comments'),
-    'entity' => 'comment',
     'elements' => array(
       'subject' => t('Subject'),
       'comment' => t('Comment'),
@@ -2299,10 +2367,8 @@ function user_mollom_form_info($form_id)
   switch ($form_id) {
     case 'user_register':
       $form_info = array(
-        'title' => t('User registration form'),
         'mode' => MOLLOM_MODE_CAPTCHA,
         'bypass access' => array('administer users'),
-        'entity' => 'user',
         'mapping' => array(
           'post_id' => 'uid',
           'author_name' => 'name',
@@ -2313,10 +2379,8 @@ function user_mollom_form_info($form_id)
 
     case 'user_pass':
       $form_info = array(
-        'title' => t('User password request form'),
         'mode' => MOLLOM_MODE_CAPTCHA,
         'bypass access' => array('administer users'),
-        'entity' => 'user',
         'mapping' => array(
           'post_id' => 'uid',
           'author_name' => 'name',
@@ -2357,7 +2421,6 @@ function contact_mollom_form_info($form_
   switch ($form_id) {
     case 'contact_mail_page':
       $form_info = array(
-        'title' => t('Site-wide contact form'),
         'mode' => MOLLOM_MODE_ANALYSIS,
         'bypass access' => array('administer site-wide contact form'),
         'mail ids' => array('contact_page_mail'),
@@ -2375,7 +2438,6 @@ function contact_mollom_form_info($form_
 
     case 'contact_mail_user':
       $form_info = array(
-        'title' => t('User contact form'),
         'mode' => MOLLOM_MODE_ANALYSIS,
         'bypass access' => array('administer users'),
         'mail ids' => array('contact_user_mail'),
Index: tests/mollom.test
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/mollom/tests/mollom.test,v
retrieving revision 1.1.2.57
diff -u -p -r1.1.2.57 mollom.test
--- tests/mollom.test	12 Sep 2010 18:08:04 -0000	1.1.2.57
+++ tests/mollom.test	12 Sep 2010 20:19:00 -0000
@@ -264,9 +264,14 @@ class MollomWebTestCase extends DrupalWe
    * @param $mode
    *   The Mollom protection mode for the form.
    * @param $fields
-   *   (optional) A list of form elements to enable for text analysis.
+   *   (optional) A list of form elements to enable for text analysis. If
+   *   omitted and the form registers individual elements, all fields are
+   *   enabled by default.
+   * @param $edit
+   *   (optional) An array of POST data to pass through to drupalPost() when
+   *   configuring the form's protection.
    */
-  protected function setProtection($form_id, $mode = MOLLOM_MODE_ANALYSIS, $fields = NULL) {
+  protected function setProtection($form_id, $mode = MOLLOM_MODE_ANALYSIS, $fields = NULL, $edit = array()) {
     // Always start from overview page, also to make debugging easier.
     $this->drupalGet('admin/settings/mollom');
     // Determine whether the form is already protected.
@@ -274,40 +279,37 @@ class MollomWebTestCase extends DrupalWe
     // Add a new form.
     if (!$exists) {
       $this->clickLink(t('Add form'));
-      $edit = array(
+      $add_form_edit = array(
         'mollom[form_id]' => $form_id,
       );
-      $this->drupalPost(NULL, $edit, t('Next'));
+      $this->drupalPost(NULL, $add_form_edit, t('Next'));
     }
     // Edit an existing form.
     else {
       $this->drupalGet('admin/settings/mollom/manage/' . $form_id);
     }
 
-    $edit = array(
+    $edit += array(
       'mollom[mode]' => $mode,
     );
 
-    // Explicitly enable the passed fields, if $fields were passed.
-    if (isset($fields)) {
-      foreach ($fields as $field) {
-        $edit['mollom[enabled_fields][' . rawurlencode($field) . ']'] = TRUE;
-      }
-    }
+    // Process the enabled fields.
     $form_list = mollom_form_list();
     $form_info = mollom_form_info($form_id, $form_list[$form_id]['module']);
+    if (!empty($form_info['elements'])) {
+      $edit += array(
+        'mollom[checks][spam]' => TRUE,
+      );
+    }
     foreach (array_keys($form_info['elements']) 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[enabled_fields][' . rawurlencode($field) . ']'] = FALSE;
-        }
+      if (!isset($fields) || in_array($field, $fields)) {
+        // If the user specified all fields by default or to include this
+        // field, set its checkbox value to TRUE.
+        $edit['mollom[enabled_fields][' . rawurlencode($field) . ']'] = TRUE;
       }
-      // If no $fields were passed, enable all elements exposed by the
-      // implementation.
       else {
-        $edit['mollom[enabled_fields][' . rawurlencode($field) . ']'] = TRUE;
+        // Otherwise set the field's checkbox value to FALSE.
+        $edit['mollom[enabled_fields][' . rawurlencode($field) . ']'] = FALSE;
       }
     }
     $this->drupalPost(NULL, $edit, t('Save'));
@@ -545,6 +547,7 @@ class MollomWebTestCase extends DrupalWe
    *   (optional) The XML-RPC method name to retrieve submitted values from.
    *   Defaults to 'mollom.checkContent'.
    *
+   * @see MollomWebTestCase::resetServerRecords()
    * @see mollom_test_xmlrpc()
    */
   protected function getServerRecord($method = 'mollom.checkContent') {
@@ -562,6 +565,26 @@ class MollomWebTestCase extends DrupalWe
   }
 
   /**
+   * Resets recorded XML-RPC values.
+   *
+   * @param $method
+   *   (optional) The XML-RPC method name to reset records of. Defaults to
+   *   'mollom.checkContent'.
+   *
+   * @see MollomWebTestCase::getServerRecord()
+   * @see mollom_test_xmlrpc()
+   */
+  protected function resetServerRecords($method = 'mollom.checkContent') {
+    // Map the XML-RPC method name to the corresponding function callback name.
+    drupal_load('module', 'mollom_test');
+    $method_function_map = mollom_test_xmlrpc();
+    $function = $method_function_map[$method];
+
+    // Delete the variable.
+    variable_del($function);
+  }
+
+  /**
    * Wraps drupalGet() for additional watchdog message assertion.
    *
    * @param $options
@@ -817,11 +840,14 @@ class MollomResponseTestCase extends Mol
     );
 
     // Ensure proper response for 'ham' submissions.
+    // By default (i.e., omitting 'checks') we expect spam and quality checking
+    // only.
     $data['post_body'] = 'ham';
     $result = mollom('mollom.checkContent', $data);
     $this->assertMollomWatchdogMessages();
     $this->assertSame('spam', $result['spam'], MOLLOM_ANALYSIS_HAM);
     $this->assertSame('quality', $result['quality'], 1);
+    $this->assertTrue(!isset($result['profanity']), 'profanity not returned.');
     $session_id = $this->assertSessionID($result['session_id']);
 
     // Ensure proper response for 'spam' submissions, re-using session_id.
@@ -831,6 +857,7 @@ class MollomResponseTestCase extends Mol
     $this->assertMollomWatchdogMessages();
     $this->assertSame('spam', $result['spam'], MOLLOM_ANALYSIS_SPAM);
     $this->assertSame('quality', $result['quality'], 0);
+    $this->assertTrue(!isset($result['profanity']), 'profanity not returned.');
     $session_id = $this->assertSessionID($result['session_id']);
 
     // Ensure proper response for 'unsure' submissions, re-using session_id.
@@ -840,10 +867,98 @@ class MollomResponseTestCase extends Mol
     $this->assertMollomWatchdogMessages();
     $this->assertSame('spam', $result['spam'], MOLLOM_ANALYSIS_UNSURE);
     $this->assertSame('quality', $result['quality'], 0.5);
+    $this->assertTrue(!isset($result['profanity']), 'profanity not returned.');
+    $session_id = $this->assertSessionID($result['session_id']);
+
+    // Additionally enable profanity checking.
+    $data['post_body'] = 'spam profanity';
+    $data['checks'] = 'spam,quality,profanity';
+    $data['session_id'] = $session_id;
+    $result = mollom('mollom.checkContent', $data);
+    $this->assertMollomWatchdogMessages();
+    $this->assertSame('spam', $result['spam'], MOLLOM_ANALYSIS_SPAM);
+    $this->assertSame('quality', $result['quality'], 0);
+    $this->assertSame('profanity', $result['profanity'], 1);
+    $session_id = $this->assertSessionID($result['session_id']);
+
+    // Change the string to contain profanity only.
+    $data['post_body'] = 'profanity';
+    $data['checks'] = 'spam,quality,profanity';
+    $data['session_id'] = $session_id;
+    $result = mollom('mollom.checkContent', $data);
+    $this->assertMollomWatchdogMessages();
+    $this->assertSame('spam', $result['spam'], MOLLOM_ANALYSIS_UNSURE);
+    $this->assertSame('quality', $result['quality'], 0);
+    $this->assertSame('profanity', $result['profanity'], 1);
+    $session_id = $this->assertSessionID($result['session_id']);
+
+    // Disable spam checking, only do profanity checking.
+    $data['post_body'] = 'spam profanity';
+    $data['checks'] = 'profanity';
+    $data['session_id'] = $session_id;
+    $result = mollom('mollom.checkContent', $data);
+    $this->assertMollomWatchdogMessages();
+    $this->assertTrue(!isset($result['spam']), 'spam not returned.');
+    $this->assertTrue(!isset($result['quality']), 'quality not returned.');
+    $this->assertSame('profanity', $result['profanity'], 1);
+    $session_id = $this->assertSessionID($result['session_id']);
+
+    // Pass arbitrary string to profanity checking.
+    $data['post_body'] = $this->randomString(12);
+    $data['session_id'] = $session_id;
+    $result = mollom('mollom.checkContent', $data);
+    $this->assertMollomWatchdogMessages();
+    $this->assertTrue(!isset($result['spam']), 'spam not returned.');
+    $this->assertTrue(!isset($result['quality']), 'quality not returned.');
+    $this->assertSame('profanity', $result['profanity'], 0);
     $session_id = $this->assertSessionID($result['session_id']);
   }
 
   /**
+   * Tests results of mollom.checkContent() across requests for a single session.
+   */
+  function testCheckContentSession() {
+    $data = array(
+      'author_name' => $this->admin_user->name,
+      'author_mail' => $this->admin_user->mail,
+      'author_id' => $this->admin_user->uid,
+      'author_ip' => ip_address(),
+    );
+
+    // Sequence: Post unsure spam, correct CAPTCHA, change post into spam,
+    // expect it to be ham (due to correct CAPTCHA).
+    $data['post_body'] = 'unsure';
+    $result = mollom('mollom.checkContent', $data);
+    $this->assertMollomWatchdogMessages();
+    $this->assertSame('spam', $result['spam'], MOLLOM_ANALYSIS_UNSURE);
+    $data['session_id'] = $this->assertSessionID($result['session_id']);
+
+    $captcha_data = array(
+      'session_id' => $data['session_id'],
+      'author_ip' => $data['author_ip'],
+    );
+    $result = mollom('mollom.getImageCaptcha', $captcha_data);
+    $this->assertMollomWatchdogMessages();
+    $data['session_id'] = $this->assertSessionID($result['session_id']);
+
+    $captcha_data = array(
+      'session_id' => $data['session_id'],
+      'author_ip' => $data['author_ip'],
+      'author_id' => $data['author_id'],
+      'captcha_result' => 'correct',
+    );
+    $result = mollom('mollom.checkCaptcha', $captcha_data);
+    $this->assertMollomWatchdogMessages();
+    $this->assertIdentical($result, TRUE, t('CAPTCHA response was correct.'));
+
+    $data['post_body'] = 'spam';
+    $result = mollom('mollom.checkContent', $data);
+    $this->assertMollomWatchdogMessages();
+    $this->assertSame('spam', $result['spam'], MOLLOM_ANALYSIS_HAM);
+    $data['session_id'] = $this->assertSessionID($result['session_id']);
+  }
+
+  /**
    * Tests mollom.getImageCaptcha().
    */
   function testGetImageCaptcha() {
@@ -1499,7 +1614,7 @@ class MollomFormConfigurationTestCase ex
     );
     $this->drupalPost(NULL, $edit, t('Next'));
     $this->assertText($form_info['user_register']['title']);
-    $this->assertNoText(t('Fields to analyze'));
+    $this->assertNoText(t('Text fields to analyze'));
     $this->drupalPost(NULL, array(), t('Save'));
 
     // Verify that user registration form was protected.
@@ -1517,6 +1632,7 @@ class MollomFormConfigurationTestCase ex
         $this->assertText($info['title']);
         // Verify that forms specifying elements have all possible elements
         // preselected for textual analysis.
+        $edit = array();
         if (!empty($info['elements'])) {
           foreach ($info['elements'] as $field => $label) {
             $field = rawurlencode($field);
@@ -1525,9 +1641,10 @@ class MollomFormConfigurationTestCase ex
         }
         // Verify that CAPTCHA-only forms contain no configurable fields.
         else {
-          $this->assertNoText(t('Fields to analyze'));
+          $this->assertNoText(t('Analyze text for'));
+          $this->assertNoText(t('Text fields to analyze'));
         }
-        $this->drupalPost(NULL, array(), t('Save'));
+        $this->drupalPost(NULL, $edit, t('Save'));
         $this->assertText(t('The form protection has been added.'));
       }
     }
@@ -1716,23 +1833,26 @@ class MollomCommentFormTestCase extends 
     $this->assertPrivacyLink();
 
     // Try to save a comment that is 'unsure' and make sure there is a CAPTCHA.
-    $this->drupalPost(NULL, array('comment' => 'unsure'), t('Save'));
+    $edit = array(
+      'comment' => 'unsure',
+    );
+    $this->drupalPost(NULL, $edit, t('Save'));
     $this->assertCaptchaField();
     $session_id = $this->assertSessionIDInForm();
     $this->assertPrivacyLink();
 
-    // Try to submit the form by using an invalid CAPTCHA. At this point,
-    // the submission should be rejected and a new CAPTCHA generated (even
-    // if the text of the comment is changed to ham).
-    $this->postIncorrectCaptcha(NULL, array('comment' => 'ham'), t('Save'));
+    // Try to submit the form by solving the CAPTCHA incorrectly. At this point,
+    // the submission should be blocked and a new CAPTCHA generated, but only if
+    // the comment is still neither ham or spam.
+    $this->postIncorrectCaptcha(NULL, array(), t('Save'));
+    $this->assertCaptchaField();
     $session_id = $this->assertSessionIDInForm();
     $this->assertPrivacyLink();
 
-    // Now try using a valid CAPTCHA. The CAPTCHA form should no longer
-    // be present.
+    // Correctly solving the CAPTCHA should accept the form submission.
     $this->postCorrectCaptcha(NULL, array(), t('Save'));
-    $this->assertRaw('<p>ham</p>', t('A comment that is known to be ham appears on the screen after it is submitted.'));
-    $cid = db_result(db_query("SELECT cid FROM {comments} WHERE comment = '%s' ORDER BY timestamp DESC", array('ham')));
+    $this->assertRaw('<p>' . $edit['comment'] . '</p>', t('A comment that may contain spam was found.'));
+    $cid = db_result(db_query("SELECT cid FROM {comments} WHERE comment = '%s' ORDER BY timestamp DESC", array($edit['comment'])));
     $this->assertMollomData('comment', $cid, $session_id);
 
     // Try to save a new 'spam' comment; it should be rejected, with no CAPTCHA
@@ -2134,6 +2254,7 @@ class MollomDataTestCase extends MollomW
     $this->drupalPost('admin/content/node-type/story', $edit, t('Save content type'));
 
     // Log out and post a comment as anonymous user.
+    $this->resetServerRecords();
     $this->drupalLogout();
     $this->drupalGet('node/' . $node->nid);
     $this->clickLink(t('Add new comment'));
@@ -2166,6 +2287,7 @@ class MollomDataTestCase extends MollomW
     $this->assertSame('author_id', $data['author_id'], NULL);
 
     // Log in admin user and edit comment containing spam.
+    $this->resetServerRecords();
     $this->drupalLogin($this->admin_user);
     $this->drupalGet('comment/edit/' . $comment->cid);
     // Post without modification.
@@ -2216,7 +2338,7 @@ class MollomDataTestCase extends MollomW
     $this->assertSame('entity', $data->entity, $new_data->entity);
     $this->assertSame('id', $data->did, $new_data->did);
     $this->assertNotSame('session_id', $data->session, $new_data->session);
-    $this->assertNotSame('quality', $data->quality, $new_data->quality);
+    $this->assertSame('quality', $data->quality, $new_data->quality);
     $count = db_result(db_query("SELECT COUNT(1) FROM {mollom}"));
     $this->assertEqual($count, 1, t('Stored data in {mollom} was updated.'));
   }
Index: tests/mollom_test.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/mollom/tests/mollom_test.module,v
retrieving revision 1.1.2.9
diff -u -p -r1.1.2.9 mollom_test.module
--- tests/mollom_test.module	7 Aug 2010 02:49:44 -0000	1.1.2.9
+++ tests/mollom_test.module	12 Sep 2010 20:20:11 -0000
@@ -13,6 +13,7 @@ function mollom_test_xmlrpc() {
   return array(
     // $data contains a variable amount of properties, so we cannot specify a
     // signature.
+    'mollom.getServerList' => 'mollom_test_get_server_list',
     'mollom.verifyKey' => 'mollom_test_verify_key',
     'mollom.checkContent' => 'mollom_test_check_content',
     'mollom.getImageCaptcha' => 'mollom_test_get_captcha',
@@ -21,6 +22,17 @@ function mollom_test_xmlrpc() {
 }
 
 /**
+ * XML-RPC callback for mollom.getServerList to retrieve new server list.
+ */
+function mollom_test_get_server_list($data) {
+  $storage = variable_get(__FUNCTION__, array());
+  $storage[] = $data;
+  variable_set(__FUNCTION__, $storage);
+
+  return array($GLOBALS['base_url'] . '/xmlrpc.php?version=');
+}
+
+/**
  * XML-RPC callback for mollom.verifyKey to validate API keys.
  */
 function mollom_test_verify_key($data) {
@@ -48,55 +60,81 @@ function mollom_test_check_content($data
   $storage[] = $data;
   variable_set(__FUNCTION__, $storage);
 
-  // Check post_title and post_body to determine whether to return ham, spam, or
-  // unsure as response.
-  $spam = FALSE;
-  $ham = FALSE;
-
-  foreach (array('post_title', 'post_body') as $key) {
-    if (!isset($data[$key])) {
-      continue;
+  $response = array();
+
+  // Spam filter: Check post_title and post_body for ham, spam, or unsure.
+  if (!isset($data['checks']) || strpos($data['checks'], 'spam') !== FALSE) {
+    $spam = FALSE;
+    $ham = FALSE;
+    foreach (array('post_title', 'post_body') as $key) {
+      if (!isset($data[$key])) {
+        continue;
+      }
+      // 'spam' always has precedence.
+      if (strpos($data[$key], 'spam') !== FALSE) {
+        $spam = TRUE;
+      }
+      // Otherwise, check for 'ham'.
+      elseif (strpos($data[$key], 'ham') !== FALSE) {
+        $ham = TRUE;
+      }
+      // Lastly, take a forced 'unsure' into account.
+      elseif (strpos($data[$key], 'unsure') !== FALSE) {
+        $spam = TRUE;
+        $ham = TRUE;
+      }
+    }
+    if ($spam && $ham) {
+      $response['spam'] = MOLLOM_ANALYSIS_UNSURE;
+      $quality = 0.5;
+    }
+    elseif ($spam) {
+      $response['spam'] = MOLLOM_ANALYSIS_SPAM;
+      $quality = 0;
     }
-    // 'spam' always has precedence.
-    if (strpos($data[$key], 'spam') !== FALSE) {
-      $spam = TRUE;
+    elseif ($ham) {
+      $response['spam'] = MOLLOM_ANALYSIS_HAM;
+      $quality = 1;
     }
-    // Otherwise, check for 'ham'.
-    elseif (strpos($data[$key], 'ham') !== FALSE) {
-      $ham = TRUE;
+    else {
+      $response['spam'] = MOLLOM_ANALYSIS_UNKNOWN;
+      $quality = NULL;
     }
-    // Lastly, take a forced 'unsure' into account.
-    elseif (strpos($data[$key], 'unsure') !== FALSE) {
-      $spam = TRUE;
-      $ham = TRUE;
+  }
+
+  // Quality filter.
+  if (!isset($data['checks']) || strpos($data['checks'], 'quality') !== FALSE) {
+    if (isset($quality)) {
+      $response['quality'] = $quality;
+    }
+    // @todo No idea how quality is calculated during testing without spam
+    //   results above.
+    else {
+      $response['quality'] = 0;
     }
   }
-  if ($spam && $ham) {
-    $response = MOLLOM_ANALYSIS_UNSURE;
-    $quality = 0.5;
-  }
-  elseif ($spam) {
-    $response = MOLLOM_ANALYSIS_SPAM;
-    $quality = 0;
-  }
-  elseif ($ham) {
-    $response = MOLLOM_ANALYSIS_HAM;
-    $quality = 1;
-  }
-  else {
-    $response = MOLLOM_ANALYSIS_UNKNOWN;
-    $quality = NULL;
+
+  // Profanity filter.
+  if (isset($data['checks']) && strpos($data['checks'], 'profanity') !== FALSE) {
+    $profanity = 0.0;
+    foreach (array('post_title', 'post_body') as $key) {
+      if (isset($data[$key]) && strpos($data[$key], 'profanity') !== FALSE) {
+        $profanity = 1.0;
+      }
+    }
+    $response['profanity'] = $profanity;
   }
 
+  if (!empty($data['session_id'])) {
+    $response['session_id'] = $data['session_id'];
+  }
   // Drupal 6 Pressflow support.
-  if (function_exists('drupal_session_start')) {
+  elseif (function_exists('drupal_session_start')) {
     drupal_session_start();
+    $response['session_id'] = session_id();
   }
-  return array(
-    'session_id' => !empty($data['session_id']) ? $data['session_id'] : session_id(),
-    'spam' => $response,
-    'quality' => $quality,
-  );
+
+  return $response;
 }
 
 /**
