Index: mollom.admin.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/mollom/mollom.admin.inc,v
retrieving revision 1.30
diff -u -p -r1.30 mollom.admin.inc
--- mollom.admin.inc	4 Aug 2010 03:58:46 -0000	1.30
+++ mollom.admin.inc	4 Aug 2010 19:06:03 -0000
@@ -476,57 +476,66 @@ function mollom_admin_blacklist_delete_s
 
 /**
  * Form builder; Global Mollom settings form.
+ *
+ * This form does not validate Mollom API keys, since the fallback method still
+ * needs to be able to be reconfigured in case Mollom services are down.
+ * mollom.verifyKey would invalidate the keys and throw an error; hence,
+ * _mollom_fallback() would invoke form_set_error(), effectively preventing this
+ * form from submitting.
  */
 function mollom_admin_settings($form, &$form_state) {
-  // When a user visits the Mollom administration page, automatically verify the
-  // keys and output any error messages.
+  // Output a positive status message, since users keep on asking whether
+  // Mollom should work or not. Re-check on every regular visit of this form to
+  // verify the module's configuration.
   if (empty($form_state['input'])) {
-    $status = _mollom_status();
+    $status = _mollom_status(TRUE);
+    // If there is any configuration error, then mollom_init() will have output
+    // it already.
     if ($status === TRUE) {
-      // Output a positive status message, since users keep on asking whether
-      // Mollom should work or not.
       drupal_set_message(t('We contacted the Mollom servers to verify your keys: the Mollom services are operating correctly. We are now blocking spam.'));
     }
   }
 
-  $form['server'] = array(
-    '#type' => 'fieldset',
-    '#title' => t('Fallback strategy'),
-    '#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 site will use the configured fallback strategy. Subscribers to <a href="@pricing-url">Mollom Plus</a> receive access to <a href="@sla-url">Mollom\'s high-availability backend infrastructure</a>, not available to free users, reducing potential downtime.', array(
-      '@pricing-url' => 'http://mollom.com/pricing',
-      '@sla-url' => 'http://mollom.com/standard-service-level-agreement',
-    )),
-  );
-  $form['server']['mollom_fallback'] = array(
-    '#type' => 'radios',
-    // 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'),
-    ),
-  );
-
   $form['access-keys'] = array(
     '#type' => 'fieldset',
-    '#title' => t('Mollom access keys'),
+    '#title' => t('Access keys'),
     '#description' => t('To use Mollom, you need a public and private key. To obtain your keys, <a href="@mollom-login-url">register and login on mollom.com</a>, and <a href="@mollom-manager-add-url">create a subscription</a> for your site. Once you created a subscription, copy your private and public access keys from the <a href="@mollom-manager-url">site manager</a> into the form fields below, and you are ready to go.', array(
       '@mollom-login-url' => 'http://mollom.com/user',
       '@mollom-manager-add-url' => 'http://mollom.com/site-manager/add',
       '@mollom-manager-url' => 'http://mollom.com/site-manager',
     )),
+    '#collapsible' => TRUE,
+    // Only show key configuration fields if they are not configured or invalid.
+    '#collapsed' => !isset($status) ? FALSE : $status === TRUE,
   );
+  // Keys are not #required to allow to install this module and configure it
+  // later.
   $form['access-keys']['mollom_public_key'] = array(
     '#type' => 'textfield',
     '#title' => t('Public key'),
     '#default_value' => variable_get('mollom_public_key', ''),
-    '#description' => t('The public key is used to uniquely identify you.'),
+    '#description' => t('Used to uniquely identify you.'),
   );
   $form['access-keys']['mollom_private_key'] = array(
     '#type' => 'textfield',
     '#title' => t('Private key'),
     '#default_value' => variable_get('mollom_private_key', ''),
-    '#description' => t('The private key is used to prevent someone from hijacking your requests. Similar to a password, it should never be shared with anyone.'),
+    '#description' => t('Used to prevent someone else from hijacking your requests. Similar to a password, it should never be shared with anyone.'),
+  );
+
+  $form['mollom_fallback'] = array(
+    '#type' => 'radios',
+    '#title' => t('Fallback strategy for protected forms'),
+    // Default to treating everything as inappropriate.
+    '#default_value' => variable_get('mollom_fallback', MOLLOM_FALLBACK_BLOCK),
+    '#options' => array(
+      MOLLOM_FALLBACK_BLOCK => t('Block all form submissions'),
+      MOLLOM_FALLBACK_ACCEPT => t('Accept all form submissions'),
+    ),
+    '#description' => t('In case the Mollom services are unreachable, no text analysis is performed and no CAPTCHAs are generated. If this occurs, your site will use the configured fallback strategy until the server problems are resolved. Subscribers to <a href="@pricing-url">Mollom Plus</a> receive access to <a href="@sla-url">Mollom\'s high-availability backend infrastructure</a>, not available to free users, reducing potential downtime.', array(
+      '@pricing-url' => 'http://mollom.com/pricing',
+      '@sla-url' => 'http://mollom.com/standard-service-level-agreement',
+    )),
   );
 
   $form['mollom_privacy_link'] = array(
Index: mollom.install
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/mollom/mollom.install,v
retrieving revision 1.22
diff -u -p -r1.22 mollom.install
--- mollom.install	31 May 2010 21:27:06 -0000	1.22
+++ mollom.install	4 Aug 2010 18:40:31 -0000
@@ -7,6 +7,68 @@
  */
 
 /**
+ * Implements hook_requirements().
+ *
+ * @param $check
+ *   (optional) Boolean whether to re-check the module's installation and
+ *   configuration status. Defaults to TRUE, as this argument is not passed for
+ *   hook_requirements() by default. Passing FALSE allows other run-time code
+ *   to re-generate requirements error messages to be displayed on other pages
+ *   than the site's system status report page.
+ *
+ * @see mollom_init()
+ * @see mollom_admin_settings()
+ * @see _mollom_status()
+ */
+function mollom_requirements($phase = 'runtime', $check = TRUE) {
+  $requirements = array();
+  if ($phase == 'runtime') {
+    $status = _mollom_status($check);
+    // Immediately return if everything is in order.
+    if ($status === TRUE) {
+      return $requirements;
+    }
+    // If not, something is wrong; prepare the requirements entry and set
+    // defaults for any yet unknown edge-cases.
+    $requirements['mollom'] = array(
+      'title' => 'Mollom API keys',
+      'value' => '',
+      'severity' => REQUIREMENT_ERROR,
+    );
+    // Append a link to the settings page to the error message on all pages,
+    // except on the settings page itself. These error messages also need to be
+    // shown on the settings page, since Mollom API keys can be entered later.
+    $admin_message = '';
+    if ($_GET['q'] != 'admin/config/content/mollom/settings') {
+      $admin_message = t('Visit the <a href="@settings-url">Mollom settings page</a> to configure your keys.', array(
+        '@settings-url' => url('admin/config/content/mollom/settings'),
+      ));
+    }
+    // Generate an appropriate error message:
+    // Missing API keys.
+    if (!$status['keys']) {
+      $requirements['mollom']['value'] = t('Not configured');
+      $requirements['mollom']['description'] = t('The Mollom API keys are not configured yet. !admin-message', array(
+        '!admin-message' => $admin_message,
+      ));
+    }
+    // Invalid API keys.
+    elseif ($status['keys valid'] === MOLLOM_ERROR) {
+      $requirements['mollom']['value'] = t('Invalid');
+      $requirements['mollom']['description'] = t('The configured Mollom API keys are invalid. !admin-message', array(
+        '!admin-message' => $admin_message,
+      ));
+    }
+    // Communication error.
+    elseif ($status['keys valid'] === NETWORK_ERROR) {
+      $requirements['mollom']['value'] = t('Network error');
+      $requirements['mollom']['description'] = t('The Mollom servers could not be contacted. Please make sure that your web server can make outgoing HTTP requests.');
+    }
+  }
+  return $requirements;
+}
+
+/**
  * Implements hook_schema().
  */
 function mollom_schema() {
@@ -110,6 +172,15 @@ function mollom_schema() {
 }
 
 /**
+ * Implements hook_install().
+ */
+function mollom_install() {
+  // Point the user to Mollom's settings page after installation.
+  $requirements = mollom_requirements('runtime', FALSE);
+  drupal_set_message($requirements['mollom']['description'], 'warning');
+}
+
+/**
  * Implements hook_uninstall().
  */
 function mollom_uninstall() {
Index: mollom.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/mollom/mollom.module,v
retrieving revision 1.64
diff -u -p -r1.64 mollom.module
--- mollom.module	4 Aug 2010 03:58:46 -0000	1.64
+++ mollom.module	4 Aug 2010 19:14:53 -0000
@@ -157,6 +157,27 @@ function mollom_help($path, $arg) {
 }
 
 /**
+ * Implements hook_init().
+ */
+function mollom_init() {
+  // On all Mollom administration pages, check the module configuration and
+  // display the corresponding requirements error, if invalid.
+  if (empty($_POST) && strpos($_GET['q'], 'admin/config/content/mollom') === 0 && user_access('administer mollom')) {
+    // First, check whether we have a bad status stored. If it was ever good,
+    // we should be safe to assume that we learned how to setup this module.
+    if (_mollom_status() !== TRUE) {
+      // Re-check, so we are sure it is still bad.
+      if (_mollom_status(TRUE) !== TRUE) {
+        // Fetch and display requirements error message, without re-checking.
+        module_load_install('mollom');
+        $requirements = mollom_requirements('runtime', FALSE);
+        drupal_set_message($requirements['mollom']['description'], 'error');
+      }
+    }
+  }
+}
+
+/**
  * Implements hook_menu().
  */
 function mollom_menu() {
@@ -317,38 +338,6 @@ function mollom_report_access($entity, $
 }
 
 /**
- * Implements hook_init().
- */
-function mollom_init() {
-  // When on an administration page, verify the Mollom keys.
-  if ((arg(0) == 'admin' && arg(1) == 'reports') ||
-      (arg(0) == 'admin' && arg(1) == 'config' && arg(2) == 'content' && arg(3) == 'mollom') &&
-      $_SERVER['REQUEST_METHOD'] == 'GET' &&
-      user_access('administer mollom')) {
-    // Verify that the keys have been configured.
-    $status = _mollom_status(TRUE);
-    if ($status !== TRUE) {
-      // Display the appropriate error message.
-      if (!$status['keys']) {
-        drupal_set_message(t('The Mollom API keys are not configured yet. Visit the <a href="@settings-url">Mollom settings page</a> to configure your keys.', array('@settings-url' => url('admin/config/content/mollom/settings'))), 'error');
-      }
-      elseif ($status['keys valid'] === NETWORK_ERROR) {
-        drupal_set_message(t('The Mollom servers could not be contacted. Please make sure that your web server can make outgoing HTTP requests.'), 'error');
-      }
-      elseif ($status['keys valid'] === MOLLOM_ERROR) {
-        drupal_set_message(t('The configured Mollom API keys are invalid. Visit the <a href="@settings-url">Mollom settings page</a> to configure your keys.', array('@settings-url' => url('admin/config/content/mollom/settings'))), 'error');
-      }
-    }
-  }
-  if (empty($_POST) && variable_get('mollom_testing_mode', 0) && user_access('administer mollom')) {
-    $message = t('Mollom testing mode is still enabled. Visit the <a href="@settings-url">Mollom settings page</a> to disable developer mode.', array(
-      '@settings-url' => url('admin/config/content/mollom/settings'),
-    ));
-    drupal_set_message($message, 'warning');
-  }
-}
-
-/**
  * Implements hook_permission().
  */
 function mollom_permission() {
@@ -578,6 +567,20 @@ function mollom_form_alter(&$form, &$for
     return;
   }
 
+  // @todo Show this message on all protected forms, regardless of permissions.
+  if (empty($_POST) && variable_get('mollom_testing_mode', 0)) {
+    $admin_message = '';
+    if (user_access('administer mollom') && $_GET['q'] != 'admin/config/content/mollom/settings') {
+      $admin_message = t('Visit the <a href="@settings-url">Mollom settings page</a> to disable it.', array(
+        '@settings-url' => url('admin/config/content/mollom/settings'),
+      ));
+    }
+    $message = t('Mollom testing mode is still enabled. !admin-message', array(
+      '!admin-message' => $admin_message,
+    ));
+    drupal_set_message($message, 'warning');
+  }
+
   // Site administrators don't have their content checked with Mollom.
   if (!user_access('bypass mollom protection')) {
     // Retrieve a list of all protected forms once.
@@ -1007,7 +1010,7 @@ function _mollom_get_openid($account) {
  *
  * @param $reset
  *   (optional) Boolean whether to reset the stored state and re-check.
- *   Defaults to FALSE.
+ *   Defaults to FALSE. Only re-checked once per request.
  *
  * @return
  *   TRUE if the module is considered operable, or an associative array
@@ -1018,6 +1021,14 @@ function _mollom_get_openid($account) {
  *   - servers: Boolean whether there is a non-empty list of Mollom servers.
  */
 function _mollom_status($reset = FALSE) {
+  $cached_reset_status = &$drupal_static(__FUNCTION__);
+
+  // Return statically cached status upon subsequent reset during the same
+  // request.
+  if ($reset && isset($cached_reset_status)) {
+    return $cached_reset_status;
+  }
+
   // Load stored status.
   $status = variable_get('mollom_status', array(
     'keys valid' => FALSE,
@@ -1034,15 +1045,19 @@ function _mollom_status($reset = FALSE) 
     variable_set('mollom_status', $status);
   }
 
-  if ($status['keys valid'] === TRUE) {
-    return TRUE;
+  // In case of an error, also indicate whether we have a non-empty server list.
+  if (!$status['keys valid']) {
+    $servers = variable_get('mollom_servers', array());
+    $status['servers'] = !empty($servers);
   }
 
-  // In case of an error, also indicate whether we have a non-empty server list.
-  $servers = variable_get('mollom_servers', array());
-  $status['servers'] = !empty($servers);
+  // Populate statically cached status upon first reset to shortcut subsequent
+  // resets.
+  if ($reset) {
+    $cached_reset_status = $status;
+  }
 
-  return $status;
+  return $status['keys valid'] === TRUE ? TRUE : $status;
 }
 
 /**
@@ -1050,6 +1065,10 @@ function _mollom_status($reset = FALSE) 
  */
 function _mollom_fallback() {
   $fallback = variable_get('mollom_fallback', MOLLOM_FALLBACK_BLOCK);
+  // @todo Prevents mollom_admin_settings() from implementing a proper form
+  //   validation. Add !empty($_POST) to this condition + manually invoke from
+  //   mollom_process_form() on GET requests? Or don't call it from mollom()?
+  //   Anything, but just don't mix FAPI logic into XML-RPC logic.
   if ($fallback == MOLLOM_FALLBACK_BLOCK) {
     form_set_error('mollom', t("The spam filter installed on this site is currently unavailable. Per site policy, we are unable to accept new submissions until that problem is resolved. Please try resubmitting the form in a couple of minutes."));
   }
Index: tests/mollom.test
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/mollom/tests/mollom.test,v
retrieving revision 1.49
diff -u -p -r1.49 mollom.test
--- tests/mollom.test	4 Aug 2010 03:58:46 -0000	1.49
+++ tests/mollom.test	4 Aug 2010 18:40:31 -0000
@@ -700,10 +700,20 @@ class MollomInstallationTestCase extends
    *
    * We walk through a regular installation of the Mollom module instead of using
    * setUp() to ensure that everything works as expected.
+   *
+   * Note: Partial error messages tested here; hence, no t().
    */
   function testInstallationProcess() {
-    // Install the Mollom module.
+    $admin_message = t('Visit the <a href="@settings-url">Mollom settings page</a> to configure your keys.', array(
+      '@settings-url' => url('admin/config/content/mollom/settings'),
+    ));
     $this->drupalLogin($this->admin_user);
+
+    // Ensure there is no requirements error by default.
+    $this->drupalGet('admin/reports/status');
+    $this->clickLink('run cron manually');
+
+    // Install the Mollom module.
     $this->drupalPost('admin/modules', array('modules[Other][mollom][enable]' => TRUE), t('Save configuration'));
 
     // Verify that forms can be submitted without valid Mollom module configuration.
@@ -726,23 +736,49 @@ class MollomInstallationTestCase extends
 
     // Verify presence of 'empty keys' error message.
     $this->drupalGet('admin/config/content/mollom');
-    $this->assertText(t('The Mollom API keys are not configured yet.'));
-    $this->assertNoText(t('The configured Mollom API keys are invalid.'));
+    $this->assertText('The Mollom API keys are not configured yet.');
+    $this->assertNoText('The configured Mollom API keys are invalid.');
 
-    // Verify presence of 'incorrect keys' error message.
+    // Verify requirements error about missing API keys.
+    $this->drupalGet('admin/reports/status');
+    $this->assertRaw(t('The Mollom API keys are not configured yet. !admin-message', array(
+      '!admin-message' => $admin_message,
+    )), t('Requirements error found.'));
+
+    // Configure invalid keys.
     $edit = array(
       'mollom_public_key' => 'foo',
       'mollom_private_key' => 'bar',
     );
     $this->drupalPost('admin/config/content/mollom/settings', $edit, t('Save configuration'), array('watchdog' => FALSE));
     $this->assertText(t('The configuration options have been saved.'));
-    $this->assertText(t('The configured Mollom API keys are invalid.'));
-    $this->assertNoText(t('The Mollom API keys are not configured yet.'));
+    $this->assertNoText($this->fallback_message, t('Fallback message not found.'));
 
-    // Verify presence of 'network problem' error message.
+    // Verify presence of 'incorrect keys' error message.
+    // @todo Error handling for invalid keys on settings page is already
+    //   covered in testKeyPairs().
+    $this->assertText('The configured Mollom API keys are invalid.');
+    $this->assertNoText('The Mollom API keys are not configured yet.');
+    $this->assertNoText(t('The Mollom servers could not be contacted. Please make sure that your web server can make outgoing HTTP requests.'));
+
+    // Verify requirements error about invalid API keys.
+    $this->drupalGet('admin/reports/status', array('watchdog' => FALSE));
+    $this->assertText('The configured Mollom API keys are invalid.');
+
+    // Ensure unreachable servers.
     variable_set('mollom_servers', array('http://fake-host'));
+
+    // Verify presence of 'network error' message.
     $this->drupalGet('admin/config/content/mollom/settings', array('watchdog' => FALSE));
-    $this->assertText(t('The Mollom servers could not be contacted.'));
+    $this->assertText(t('The Mollom servers could not be contacted. Please make sure that your web server can make outgoing HTTP requests.'));
+
+    // Ensure unreachable servers.
+    variable_set('mollom_servers', array('http://fake-host'));
+
+    // Verify requirements error about network error.
+    $this->drupalGet('admin/reports/status', array('watchdog' => FALSE));
+    $this->assertText(t('The Mollom servers could not be contacted. Please make sure that your web server can make outgoing HTTP requests.'));
+    $this->assertNoText($this->fallback_message, t('Fallback message not found.'));
 
     // Verify that valid keys work.
     $this->drupalGet('admin/config/content/mollom/settings', array('watchdog' => FALSE));
@@ -753,11 +789,12 @@ class MollomInstallationTestCase extends
     );
     $this->drupalPost(NULL, $edit, t('Save configuration'));
     $this->assertText(t('The configuration options have been saved.'));
-    $this->assertText(t('We are now blocking spam.'));
-    $this->assertNoText(t('The configured Mollom API keys are invalid.'));
-    $this->assertNoText(t('The Mollom API keys are not configured yet.'));
+    $this->assertText('We are now blocking spam.');
+    $this->assertNoText('The Mollom API keys are not configured yet.');
+    $this->assertNoText('The configured Mollom API keys are invalid.');
 
     // Verify presence of testing mode warning.
+    $this->drupalGet('admin/config/content/mollom/blacklist');
     $this->assertText('Mollom testing mode is still enabled.');
   }
 }
@@ -883,7 +920,7 @@ class MollomAccessTestCase extends Mollo
     );
     $this->drupalPost(NULL, $edit, t('Save configuration'), array('watchdog' => FALSE));
     $this->assertText(t('The configuration options have been saved.'));
-    $this->assertText(t('The configured Mollom API keys are invalid.'));
+    $this->assertText('The configured Mollom API keys are invalid.');
   }
 
   /**
