Index: mollom.admin.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/mollom/mollom.admin.inc,v retrieving revision 1.41 diff -u -p -r1.41 mollom.admin.inc --- mollom.admin.inc 26 Sep 2010 19:42:55 -0000 1.41 +++ mollom.admin.inc 27 Sep 2010 16:12:14 -0000 @@ -13,9 +13,8 @@ function mollom_admin_form_list() { _mollom_testing_mode_warning(); $modes = array( - MOLLOM_MODE_DISABLED => t('None'), - MOLLOM_MODE_CAPTCHA => t('CAPTCHA'), MOLLOM_MODE_ANALYSIS => t('Text analysis'), + MOLLOM_MODE_CAPTCHA => t('CAPTCHA'), ); $header = array( @@ -29,7 +28,10 @@ function mollom_admin_form_list() { $mollom_form = mollom_form_load($form_id); $rows[] = array( $mollom_form['title'], - $modes[$mollom_form['mode']], + t('!protection-mode (@reject)', array( + '!protection-mode' => $modes[$mollom_form['mode']], + '@reject' => $mollom_form['reject'] ? t('reject') : t('unpublish'), + )), l(t('Configure'), 'admin/config/content/mollom/manage/' . $form_id), l(t('Unprotect'), 'admin/config/content/mollom/unprotect/' . $form_id), ); @@ -153,6 +155,27 @@ function mollom_admin_configure_form($fo ); if (!empty($mollom_form['elements'])) { + // By default, Mollom module rejects all posts that did not successfully + // pass mollom.checkContent. Instead of rejecting posts, site admins + // can optionally configure that posts shall be unpublished instead, so + // they can go through manual moderation. The negated form widget may + // look odd, but users can rather relate to more common terms like + // "unpublish" or "moderation". Furthermore, this setting should stay + // optional and disabled by default, as Mollom should reject bad posts. + $form['mollom']['reject'] = array( + '#type' => 'checkbox', + '#title' => t('Reject bad posts'), + '#default_value' => $mollom_form['reject'], + '#description' => t('Disable to manually moderate all posts.'), + // Only possible for forms supporting moderation of unpublished posts. + '#access' => !empty($mollom_form['reject callback']), + '#states' => array( + 'visible' => array( + ':input[name="mollom[mode]"]' => array('value' => (string) MOLLOM_MODE_ANALYSIS), + ), + ), + ); + // If not re-configuring an existing protection, make it the default. if (!isset($mollom_form['mode'])) { $form['mollom']['mode']['#default_value'] = MOLLOM_MODE_ANALYSIS; Index: mollom.api.php =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/mollom/mollom.api.php,v retrieving revision 1.6 diff -u -p -r1.6 mollom.api.php --- mollom.api.php 12 Sep 2010 22:05:55 -0000 1.6 +++ mollom.api.php 27 Sep 2010 16:12:14 -0000 @@ -150,6 +150,9 @@ * $form_info = array( * // Optional: User permission list to skip Mollom's protection for. * 'bypass access' => array('administer instant messages'), + * // Optional: Function to invoke to unpublish a bad form submission + * // instead of rejecting it. + * 'reject callback' => 'im_mollom_reject', * // Optional: To allow textual analysis of the form values, the form * // elements needs to be registered individually. The keys are the * // field keys in $form_state['values']. Sub-keys are noted using "][" @@ -205,6 +208,16 @@ * Additionally, the "post_id" data property always needs to be mapped to a form * element that holds the entity id. * + * When registering a 'reject callback', then the registered function needs to + * be available when the form is validated, and it is responsible for changing + * the submitted form values in a way that results in an unpublished post ending + * up in a moderation queue: + * @code + * function im_mollom_reject(&$form, &$form_state) { + * $form_state['values']['status'] = 0; + * } + * @endcode + * * @see mollom_node * @see mollom_comment * @see mollom_user @@ -276,6 +289,10 @@ function hook_mollom_form_list() { * current user to determine whether to protect the form with Mollom or do * not validate submitted form values. If the current user has at least one * of the listed permissions, the form will not be protected. + * - reject callback: (optional) A function name to invoke when a form + * submission would normally be rejected. This allows modules to put such + * posts into a moderation queue (i.e., accept but not publish them) by + * altering the $form or $form_state information being passed by reference. * - mail ids: (optional) An array of mail IDs that will be sent as a result * of this form being submitted. When these mails are sent, a 'report to * Mollom' link will be included at the bottom of the mail body. Be sure to Index: mollom.install =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/mollom/mollom.install,v retrieving revision 1.28 diff -u -p -r1.28 mollom.install --- mollom.install 25 Sep 2010 01:05:41 -0000 1.28 +++ mollom.install 27 Sep 2010 16:17:55 -0000 @@ -174,6 +174,13 @@ function mollom_schema() { 'not null' => FALSE, 'serialize' => TRUE, ), + 'reject' => array( + 'description' => 'Whether to reject (1) or unpublish (0) bad form submissions.', + 'type' => 'int', + 'size' => 'tiny', + 'not null' => TRUE, + 'default' => 1, + ), 'enabled_fields' => array( 'description' => 'Form elements to analyze.', 'type' => 'text', @@ -723,3 +730,17 @@ function mollom_update_7007() { } } } + +/** + * Add {mollom_form}.reject column to form configuration. + */ +function mollom_update_7008() { + if (!db_field_exists('mollom_form', 'reject')) { + db_add_field('mollom_form', 'reject', array( + 'type' => 'int', + 'size' => 'tiny', + 'not null' => TRUE, + 'default' => 1, + )); + } +} Index: mollom.module =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/mollom/mollom.module,v retrieving revision 1.85 diff -u -p -r1.85 mollom.module --- mollom.module 25 Sep 2010 01:05:41 -0000 1.85 +++ mollom.module 27 Sep 2010 16:43:09 -0000 @@ -399,6 +399,26 @@ function mollom_cron() { db_delete('mollom') ->condition('changed', $expired, '<') ->execute(); + + // Delete all bad posts older than two weeks, which have not been published. + $form_list = mollom_form_list(); + $delete = array(); + $query = db_select('mollom', 'm') + ->fields('m') + ->condition('changed', REQUEST_TIME - 86400 * 14, '<') + ->condition(db_or() + ->condition('spam', MOLLOM_ANALYSIS_SPAM) + ->condition('profanity', 0.5, '>=') + ); + foreach ($query->execute() as $data) { + if (isset($form_list[$data->form_id]['entity delete multiple callback'])) { + $function = $form_list[$data->form_id]['entity delete multiple callback']; + $delete[$function][] = $data->did; + } + } + foreach ($delete as $function => $ids) { + $function($ids); + } } /** @@ -443,10 +463,6 @@ function mollom_data_load($entity, $id) * @todo Remove usage of global $mollom variable. */ function mollom_data_save($entity, $id, $form_id, $response) { - // Nothing to do, if we do not have a valid Mollom response. - if (empty($GLOBALS['mollom']['response']['session_id'])) { - return FALSE; - } $data = array( 'entity' => $entity, 'did' => $id, @@ -627,6 +643,7 @@ function mollom_form_alter(&$form, &$for // Add Mollom form validation handlers. $form['#validate'][] = 'mollom_validate_analysis'; $form['#validate'][] = 'mollom_validate_captcha'; + $form['#validate'][] = 'mollom_validate_post'; // Add a submit handler to remove form state storage. $form['#submit'][] = 'mollom_form_submit'; @@ -755,6 +772,7 @@ function mollom_form_info($form_id, $mod 'module' => $module, 'entity' => NULL, 'mode' => NULL, + 'reject' => TRUE, 'bypass access' => array(), 'elements' => array(), 'mapping' => array(), @@ -1223,6 +1241,13 @@ function mollom_process_mollom($element, } $form_state['mollom'] += $element['#mollom_form']; + // By default, bad form submissions are rejected, unless the form was + // configured to unpublish bad posts. 'reject' may only be FALSE, if there is + // a valid 'reject callback'. Otherwise, it must be TRUE. + if (empty($form_state['mollom']['reject callback']) || !function_exists($form_state['mollom']['reject callback'])) { + $form_state['mollom']['reject'] = TRUE; + } + // Add the Mollom session element. $element['session_id'] = array( '#type' => 'hidden', @@ -1321,10 +1346,19 @@ function mollom_validate_analysis(&$form $form_state['mollom']['response'] = $result; $form['mollom']['session_id']['#value'] = $result['session_id']; + // Prepare watchdog message teaser text. + $teaser = truncate_utf8(strip_tags(isset($data['post_title']) ? $data['post_title'] : isset($data['post_body']) ? $data['post_body'] : '--'), 40); + // Handle the profanity check result. if (isset($result['profanity']) && $result['profanity'] >= 0.5) { - form_set_error('mollom', t('Your submission has triggered the profanity filter and will not be accepted until the inappropriate language is removed.')); - watchdog('mollom', 'Profanity:
@message
Result:
@result
', array('@message' => print_r($data, TRUE), '@result' => print_r($result, TRUE))); + if ($form_state['mollom']['reject']) { + form_set_error('mollom', t('Your submission has triggered the profanity filter and will not be accepted until the inappropriate language is removed.')); + } + _mollom_watchdog(array( + 'Profanity: %teaser' => array('%teaser' => $teaser), + 'Data:
@data
' => array('@data' => $data), + 'Result:
@result
' => array('@result' => $result), + )); } // Handle the spam check result. @@ -1335,7 +1369,6 @@ function mollom_validate_analysis(&$form // 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: @@ -1349,7 +1382,9 @@ function mollom_validate_analysis(&$form 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.')); + if ($form_state['mollom']['reject']) { + 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:
@data
' => array('@data' => $data), @@ -1475,6 +1510,26 @@ function mollom_validate_captcha(&$form, } /** + * Form validation handler to perform post-validation tasks. + * + * Since our individual form validation handlers are not re-run after positive + * validation, any changes applied to form values will not persist across + * multiple form submission attempts and rebuilds. + */ +function mollom_validate_post(&$form, &$form_state) { + // Unpublish a post instead of rejecting it. If 'reject' is not TRUE, then + // the 'reject callback' is responsible for altering $form_state in a way that + // the post ends up unpublished in a moderation queue. Most callbacks will + // only want to set a value in $form_state. Technically, modules do not need + // to implement a 'reject callback' to achieve this, they may simply add a + // custom form validation handler (or use an existing one). + if (!$form_state['mollom']['reject']) { + $function = $form_state['mollom']['reject callback']; + $function($form, $form_state); + } +} + +/** * Form submit handler to flush Mollom session and form information from cache. */ function mollom_form_submit($form, &$form_state) { @@ -1979,19 +2034,13 @@ function node_mollom_form_list() { 'entity' => 'node', 'bundle' => $type->type, 'delete form' => 'node_delete_confirm', + 'entity delete multiple callback' => 'node_delete_multiple', ); } return $forms; } /** - * Implements hook_node_delete(). - */ -function mollom_node_delete($node) { - mollom_data_delete('node', $node->nid); -} - -/** * Implements hook_mollom_form_info(). */ function node_mollom_form_info($form_id) { @@ -2003,6 +2052,7 @@ function node_mollom_form_info($form_id) // @todo This is incompatible with node access. 'bypass access' => array('bypass node access', 'edit any ' . $type->type . ' content'), 'bundle' => $type->type, + 'reject callback' => 'node_mollom_reject', 'elements' => array(), 'mapping' => array( 'post_id' => 'nid', @@ -2027,6 +2077,20 @@ function node_mollom_form_info($form_id) } /** + * Mollom reject callback. + */ +function node_mollom_reject(&$form, &$form_state) { + $form_state['values']['status'] = 0; +} + +/** + * Implements hook_node_delete(). + */ +function mollom_node_delete($node) { + mollom_data_delete('node', $node->nid); +} + +/** * Implements hook_form_FORMID_alter(). */ function mollom_form_node_multiple_delete_confirm_alter(&$form, &$form_state) { @@ -2069,6 +2133,7 @@ function comment_mollom_form_list() { 'entity' => 'comment', 'bundle' => 'comment_node_' . $type->type, 'delete form' => 'comment_confirm_delete', + 'entity delete multiple callback' => 'comment_delete_multiple', ); } return $forms; @@ -2081,6 +2146,7 @@ function comment_mollom_form_info($form_ $form_info = array( 'mode' => MOLLOM_MODE_ANALYSIS, 'bypass access' => array('administer comments'), + 'reject callback' => 'comment_mollom_reject', 'elements' => array( 'subject' => t('Subject'), // @todo Update for Field API. @@ -2099,6 +2165,40 @@ function comment_mollom_form_info($form_ } /** + * Mollom reject callback. + */ +function comment_mollom_reject(&$form, &$form_state) { + $form_state['values']['status'] = COMMENT_NOT_PUBLISHED; +} + +/** + * Implements hook_comment_presave(). + */ +function mollom_comment_presave($comment) { + // If an existing comment is published and we have session data stored for it, + // send 'ham' feedback to Mollom. + if (!empty($comment->cid) && $comment->status == COMMENT_PUBLISHED) { + if ($data = mollom_data_load('comment', $comment->cid)) { + _mollom_send_feedback($data->session, 'ham'); + // Update the stored session data, so this comment is no longer deleted + // in upcoming cron runs. + // @todo Actually, we should update the stored values for 'spam' and/or + // 'profanity', so they no longer appear in content overviews that join + // on {mollom}. However, as of now, the stored values are 1:1 the return + // values of Mollom servers, and just updating them would mean that when + // editing a post, we'd potentially overwrite the "good" values again. + // Hence, we likely need to do both: Update the analysis values AND use + // a new column to prevent existing published content from being deleted. + db_update('mollom') + ->fields(array('delete' => 0)) + ->condition('entity', 'comment') + ->condition('did', $comment->cid) + ->execute(); + } + } +} + +/** * Implements hook_comment_delete(). */ function mollom_comment_delete($comment) { @@ -2144,11 +2244,10 @@ function user_mollom_form_list() { 'title' => t('User registration form'), 'entity' => 'user', 'delete form' => 'user_cancel_confirm_form', + 'entity delete multiple callback' => 'user_delete_multiple', ); $forms['user_pass'] = array( 'title' => t('User password request form'), - 'entity' => 'user', - 'delete form' => 'user_cancel_confirm_form', ); return $forms; } @@ -2162,6 +2261,7 @@ function user_mollom_form_info($form_id) $form_info = array( 'mode' => MOLLOM_MODE_CAPTCHA, 'bypass access' => array('administer users'), + 'reject callback' => 'user_mollom_reject', 'mapping' => array( 'post_id' => 'uid', 'author_name' => 'name', @@ -2208,6 +2308,13 @@ function mollom_form_user_multiple_cance } /** + * Mollom reject callback. + */ +function user_mollom_reject(&$form, &$form_state) { + $form_state['values']['status'] = 0; +} + +/** * @} End of "name mollom_user". */