Index: mollom.admin.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/mollom/mollom.admin.inc,v retrieving revision 1.42 diff -u -p -r1.42 mollom.admin.inc --- mollom.admin.inc 29 Sep 2010 02:34:30 -0000 1.42 +++ mollom.admin.inc 8 Oct 2010 15:52:23 -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('block') : t('moderate'), + )), l(t('Configure'), 'admin/config/content/mollom/manage/' . $form_id), l(t('Unprotect'), 'admin/config/content/mollom/unprotect/' . $form_id), ); @@ -153,6 +155,24 @@ function mollom_admin_configure_form($fo ); if (!empty($mollom_form['elements'])) { + $form['mollom']['reject'] = array( + '#type' => 'radios', + '#title' => t('Bad posts'), + '#default_value' => $mollom_form['reject'], + '#options' => array( + 1 => t('Block bad posts'), + 0 => t('Moderate bad posts manually'), + ), + // Only possible for forms supporting moderation of unpublished posts. + '#access' => !empty($mollom_form['moderation callback']), + // Only possible for forms protected via text analysis. + '#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 8 Oct 2010 16:17:35 -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 put a bad form submission into a + * // moderation queue instead of rejecting it. + * 'moderation callback' => 'im_mollom_form_moderation', * // 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 'moderation 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_form_moderation(&$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. + * - moderation 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., to accept but not publish them) by + * altering the $form or $form_state that are 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.29 diff -u -p -r1.29 mollom.install --- mollom.install 5 Oct 2010 01:05:13 -0000 1.29 +++ mollom.install 8 Oct 2010 15:58:41 -0000 @@ -109,6 +109,13 @@ function mollom_schema() { 'not null' => TRUE, 'default' => 0, ), + 'moderate' => array( + 'description' => 'Whether the content needs to be moderated.', + 'type' => 'int', + 'size' => 'tiny', + 'not null' => TRUE, + 'default' => 0, + ), // 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 @@ -174,6 +181,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', @@ -751,3 +765,27 @@ function mollom_update_7008() { db_drop_index('mollom', 'session'); db_add_index('mollom', 'session_id', array('session_id')); } + +/** + * Add {mollom_form}.reject and {mollom}.reject columns. + */ +function mollom_update_7009() { + if (!db_field_exists('mollom_form', 'reject')) { + db_add_field('mollom_form', 'reject', array( + 'description' => 'Whether to reject (1) or unpublish (0) bad form submissions.', + 'type' => 'int', + 'size' => 'tiny', + 'not null' => TRUE, + 'default' => 1, + )); + } + if (!db_field_exists('mollom', 'moderate')) { + db_add_field('mollom', 'moderate', array( + 'description' => 'Whether the content needs to be moderated.', + 'type' => 'int', + 'size' => 'tiny', + 'not null' => TRUE, + 'default' => 0, + )); + } +} Index: mollom.module =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/mollom/mollom.module,v retrieving revision 1.87 diff -u -p -r1.87 mollom.module --- mollom.module 5 Oct 2010 01:05:13 -0000 1.87 +++ mollom.module 8 Oct 2010 16:20:49 -0000 @@ -458,6 +458,29 @@ function mollom_data_save($data) { } /** + * Updates stored Mollom session data to mark a bad post as moderated. + * + * @param $entity + * The entity type of the moderated post. + * @param $id + * The entity id of the moderated post. + */ +function mollom_data_moderate($entity, $id) { + $data = mollom_data_load($entity, $id); + // Nothing to do, if no data exists. + if (!$data) { + return; + } + + // Report the session to Mollom. + _mollom_send_feedback($data->session, 'ham'); + + // Mark the session data as moderated. + $data->moderate = 0; + mollom_data_save($data); +} + +/** * Deletes a Mollom session data record from the database. * * @param $entity @@ -618,6 +641,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'; @@ -747,6 +771,7 @@ function mollom_form_info($form_id, $mod 'module' => $module, 'entity' => NULL, 'mode' => NULL, + 'reject' => TRUE, 'bypass access' => array(), 'elements' => array(), 'mapping' => array(), @@ -1225,6 +1250,7 @@ function mollom_process_mollom($element, 'require_analysis' => $element['#mollom_form']['mode'] == MOLLOM_MODE_ANALYSIS, 'require_captcha' => $element['#mollom_form']['mode'] == MOLLOM_MODE_CAPTCHA, 'passed_captcha' => FALSE, + 'require_moderation' => FALSE, 'response' => array( 'session_id' => '', ), @@ -1232,6 +1258,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 'moderation callback'. Otherwise, it must be TRUE. + if (empty($form_state['mollom']['moderation callback']) || !function_exists($form_state['mollom']['moderation callback'])) { + $form_state['mollom']['reject'] = TRUE; + } + // Add the Mollom session element. $element['session_id'] = array( '#type' => 'hidden', @@ -1330,10 +1363,22 @@ 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.')); + } + else { + $form_state['mollom']['require_moderation'] = TRUE; + } + _mollom_watchdog(array( + 'Profanity: %teaser' => array('%teaser' => $teaser), + 'Data:
@data
' => array('@data' => $data), + 'Result:
@result
' => array('@result' => $result), + )); } // Handle the spam check result. @@ -1344,7 +1389,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: @@ -1358,7 +1402,12 @@ 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.')); + } + else { + $form_state['mollom']['require_moderation'] = TRUE; + } _mollom_watchdog(array( 'Spam: %teaser' => array('%teaser' => $teaser), 'Data:
@data
' => array('@data' => $data), @@ -1484,6 +1533,20 @@ function mollom_validate_captcha(&$form, } /** + * Form validation handler to perform post-validation tasks. + */ +function mollom_validate_post(&$form, &$form_state) { + // Unpublish a post instead of rejecting it. If 'reject' is FALSE, then the + // 'moderation callback' is responsible for altering $form_state in a way that + // the post ends up in a moderation queue. Most callbacks will only want to + // set or change a value in $form_state. + if (!$form_state['mollom']['reject'] && $form_state['mollom']['require_moderation']) { + $function = $form_state['mollom']['moderation callback']; + $function($form, $form_state); + } +} + +/** * Form submit handler to flush Mollom session and form information from cache. */ function mollom_form_submit($form, &$form_state) { @@ -1503,10 +1566,13 @@ function mollom_form_submit($form, &$for $values = 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($values['post_id'])) { + // Save the Mollom session data. $data = (object) $form_state['mollom']['response']; $data->entity = $form_state['mollom']['entity']; $data->id = $values['post_id']; $data->form_id = $form_state['mollom']['form_id']; + // Set the moderation flag for forms accepting bad posts. + $data->moderate = $form_state['mollom']['require_moderation']; mollom_data_save($data); } } @@ -1996,19 +2062,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) { @@ -2020,6 +2080,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, + 'moderation callback' => 'node_mollom_form_moderation', 'elements' => array(), 'mapping' => array( 'post_id' => 'nid', @@ -2044,6 +2105,31 @@ function node_mollom_form_info($form_id) } /** + * Mollom form moderation callback for nodes. + */ +function node_mollom_form_moderation(&$form, &$form_state) { + $form_state['values']['status'] = 0; +} + +/** + * Implements hook_node_presave(). + */ +function mollom_node_presave($comment) { + // If an existing node is published and we have session data stored for it, + // mark the data as moderated. + if (!empty($node->nid) && $node->status) { + mollom_data_moderate('node', $node->nid); + } +} + +/** + * 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) { @@ -2086,6 +2172,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; @@ -2098,6 +2185,7 @@ function comment_mollom_form_info($form_ $form_info = array( 'mode' => MOLLOM_MODE_ANALYSIS, 'bypass access' => array('administer comments'), + 'moderation callback' => 'comment_mollom_form_moderation', 'elements' => array( 'subject' => t('Subject'), // @todo Update for Field API. @@ -2116,6 +2204,24 @@ function comment_mollom_form_info($form_ } /** + * Mollom form moderation callback for comments. + */ +function comment_mollom_form_moderation(&$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, + // mark the data as moderated. + if (!empty($comment->cid) && $comment->status == COMMENT_PUBLISHED) { + mollom_data_moderate('comment', $comment->cid); + } +} + +/** * Implements hook_comment_delete(). */ function mollom_comment_delete($comment) { @@ -2161,11 +2267,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; } @@ -2179,6 +2284,7 @@ function user_mollom_form_info($form_id) $form_info = array( 'mode' => MOLLOM_MODE_CAPTCHA, 'bypass access' => array('administer users'), + 'moderation callback' => 'user_mollom_form_moderation', 'mapping' => array( 'post_id' => 'uid', 'author_name' => 'name', @@ -2203,6 +2309,13 @@ function user_mollom_form_info($form_id) } /** + * Mollom form moderation callback for user accounts. + */ +function user_mollom_form_moderation(&$form, &$form_state) { + $form_state['values']['status'] = 0; +} + +/** * Implements hook_form_FORMID_alter(). */ function mollom_form_user_multiple_cancel_confirm_alter(&$form, &$form_state) { Index: tests/mollom.test =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/mollom/tests/mollom.test,v retrieving revision 1.67 diff -u -p -r1.67 mollom.test --- tests/mollom.test 5 Oct 2010 12:01:02 -0000 1.67 +++ tests/mollom.test 8 Oct 2010 17:10:28 -0000 @@ -89,11 +89,16 @@ class MollomWebTestCase extends DrupalWe $this->resetSessionID(); $this->messages = array(); + // @see DrupalWebTestCase::setUp() + $modules = func_get_args(); + if (isset($modules[0]) && is_array($modules[0])) { + $modules = $modules[0]; + } + // If not explicitly disabled by a test, setup with Mollom and default admin // user. if (empty($this->disableDefaultSetup)) { // Call parent::setUp() allowing Mollom test cases to pass further modules. - $modules = func_get_args(); $modules[] = 'mollom'; $modules[] = 'dblog'; parent::setUp($modules); @@ -108,7 +113,6 @@ class MollomWebTestCase extends DrupalWe )); } else { - $modules = func_get_args(); $modules[] = 'dblog'; parent::setUp($modules); } @@ -311,6 +315,7 @@ class MollomWebTestCase extends DrupalWe $edit += array( 'mollom[mode]' => $mode, + 'mollom[reject]' => 1, ); // Process the enabled fields. @@ -637,8 +642,8 @@ class MollomWebTestCase extends DrupalWe * @see MollomWebTestCase->assertSessionID() * @see DrupalWebTestCase->drupalPost() */ - protected function drupalPost($path, $edit, $submit, array $options = array(), array $headers = array(), $form_html_id = NULL) { - $output = parent::drupalPost($path, $edit, $submit, $options, $headers, $form_html_id); + protected function drupalPost($path, $edit, $submit, array $options = array(), array $headers = array(), $form_html_id = NULL, $extra_post = NULL) { + $output = parent::drupalPost($path, $edit, $submit, $options, $headers, $form_html_id, $extra_post); $options += array('watchdog' => TRUE); $this->assertMollomWatchdogMessages($options['watchdog']); return $output; @@ -2608,6 +2613,137 @@ class MollomDataTestCase extends MollomW } /** + * Tests text analysis functionality. + * + * @todo Verify that no button captions appear in the data that is sent for + * analyis; i.e., no "Add" string for mollom_test_form. + */ +class MollomAnalysisTestCase extends MollomWebTestCase { + protected $profile = 'testing'; + + public static function getInfo() { + return array( + 'name' => 'Text analysis', + 'description' => 'Tests text analysis functionality.', + 'group' => 'Mollom', + ); + } + + function setUp() { + // @todo This is the new default setUp() procedure for all new tests, which + // should be moved into MollomWebTestCase::setUp() after cleaning up the + // tests. + $this->disableDefaultSetup = TRUE; + parent::setUp(array('mollom', 'mollom_test')); + $this->setKeys(); + $this->assertValidKeys(); + + $this->admin_user = $this->drupalCreateUser(array( + 'access administration pages', + 'administer mollom', + )); + $this->web_user = $this->drupalCreateUser(array()); + } + + /** + * Tests accepting bad posts, partially publishing them, and delayed rejection. + */ + function testDelayedReject() { + // Configure mollom_test_form to accept bad posts. + $this->drupalLogin($this->admin_user); + $this->setProtection('mollom_test_form', MOLLOM_MODE_ANALYSIS, NULL, array( + 'mollom[checks][profanity]' => TRUE, + 'mollom[reject]' => FALSE, + )); + $this->drupalLogout(); + + // Verify that we are able to post spam and the post is unpublished. + $edit = array( + 'title' => $this->randomString(), + 'body' => 'spam profanity', + ); + $this->drupalPost('mollom-test/form', $edit, 'Submit'); + $mid = $this->assertTestSubmitData(); + $data = $this->assertMollomData('mollom_test', $mid); + $record = mollom_test_load($mid); + $this->assertEqual($record['status'], 0, t('Unpublished test post found.')); + $this->assertSame('spam', $data->spam, MOLLOM_ANALYSIS_SPAM); + $this->assertSame('profanity', $data->profanity, 1); + $this->assertSame('reject', $data->reject, 1); + + // Verify that editing the post does neither changes the session data, nor + // the publishing status. + // @todo + + // Verify that publishing the post changes the session data accordingly. + $this->drupalLogin($this->admin_user); + $mid = $record['mid']; + $edit = array( + 'status' => TRUE, + ); + $this->drupalPost('mollom-test/form/' . $mid, $edit, 'Submit'); + $mid = $this->assertTestSubmitData($mid); + $data = $this->assertMollomData('mollom_test', $mid); + $record = mollom_test_load($mid); + $this->assertEqual($record['status'], 1, t('Published test post found.')); + $this->assertSame('spam', $data->spam, MOLLOM_ANALYSIS_HAM); + $this->assertSame('profanity', $data->profanity, 0); + $this->assertSame('reject', $data->reject, 0); + + // Verify that the published post is not deleted in an upcoming cron run. + // @todo + + // Verify that we are able to post spam and the unpublished post is deleted + // in an upcoming cron run. + // @todo + + // Verify that neither ham or unsure spam posts, nor not profane posts are + // marked for delayed rejection. + $this->drupalLogout(); + $expectations = array( + 'ham' => array('spam' => MOLLOM_ANALYSIS_HAM, 'profanity' => 0), + 'unsure' => array('spam' => MOLLOM_ANALYSIS_UNSURE, 'profanity' => 0), + $this->randomString() => array('spam' => MOLLOM_ANALYSIS_UNSURE, 'profanity' => 0), + ); + foreach ($expectations as $body => $expected) { + $edit = array( + 'title' => $this->randomString(), + 'body' => $body, + ); + $this->drupalPost('mollom-test/form', $edit, 'Submit'); + if ($expected['spam'] == MOLLOM_ANALYSIS_UNSURE) { + $this->postCorrectCaptcha(NULL, array(), 'Submit'); + } + $mid = $this->assertTestSubmitData(); + $data = $this->assertMollomData('mollom_test', $mid); + $record = mollom_test_load($mid); + $this->assertEqual($record['status'], 1, t('Published test post %body found.', array('%body' => $body))); + $this->assertSame('spam', $data->spam, $expected['spam']); + $this->assertSame('profanity', $data->profanity, $expected['profanity']); + $this->assertSame('reject', $data->reject, 0); + } + } + + /** + * Asserts a successful mollom_test_form submission. + * + * @param $old_mid + * (optional) The existing test record id to assert. + */ + protected function assertTestSubmitData($old_mid = NULL) { + $this->assertText('Successful form submission.'); + $mid = $this->getFieldValueByName('mid'); + if (isset($old_mid)) { + $this->assertSame('Test record id', $mid, $old_mid); + } + else { + $this->assertTrue($mid > 0, t('Test record id @id found.', array('@id' => $mid))); + } + return $mid; + } +} + +/** * Tests report to Mollom functionality. */ class MollomReportTestCase extends MollomWebTestCase { Index: tests/mollom_test.install =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/mollom/tests/mollom_test.install,v retrieving revision 1.2 diff -u -p -r1.2 mollom_test.install --- tests/mollom_test.install 17 Feb 2010 09:05:13 -0000 1.2 +++ tests/mollom_test.install 6 Oct 2010 15:54:30 -0000 @@ -13,11 +13,29 @@ function mollom_test_schema() { $schema['mollom_test'] = array( 'description' => 'Stores testing data for Mollom test form.', 'fields' => array( - 'mid' => array('type' => 'serial', 'not null' => TRUE, + 'mid' => array( 'description' => 'Primary key: Unique mollom_test entity ID.', + 'type' => 'serial', + 'not null' => TRUE, ), - 'body' => array('type' => 'text', 'not null' => TRUE, - 'description' => 'The body field of mollom_test_form().', + 'title' => array( + 'description' => 'Title of the post.', + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'body' => array( + 'description' => 'Body of the post.', + 'type' => 'text', + 'not null' => TRUE, + ), + 'status' => array( + 'description' => 'Publishing status.', + 'type' => 'int', + 'size' => 'tiny', + 'not null' => TRUE, + 'default' => 1, ), ), 'primary key' => array('mid'), Index: tests/mollom_test.module =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/mollom/tests/mollom_test.module,v retrieving revision 1.14 diff -u -p -r1.14 mollom_test.module --- tests/mollom_test.module 11 Sep 2010 02:22:27 -0000 1.14 +++ tests/mollom_test.module 8 Oct 2010 17:09:23 -0000 @@ -18,6 +18,7 @@ function mollom_test_xmlrpc() { 'mollom.checkContent' => 'mollom_test_check_content', 'mollom.getImageCaptcha' => 'mollom_test_get_captcha', 'mollom.checkCaptcha' => 'mollom_test_check_captcha', + 'mollom.sendFeedback' => 'mollom_test_send_feedback', ); } @@ -170,6 +171,20 @@ function mollom_test_check_captcha($data } /** + * XML-RPC callback for mollom.sendFeedback to send feedback for a moderated post. + */ +function mollom_test_send_feedback($data) { + $storage = variable_get(__FUNCTION__, array()); + $storage[] = $data; + variable_set(__FUNCTION__, $storage); + + if (in_array($data['feedback'], array('spam', 'profanity', 'low-quality', 'unwanted', 'ham'))) { + return TRUE; + } + xmlrpc_error(MOLLOM_ERROR); +} + +/** * Implements hook_menu(). */ function mollom_test_menu() { @@ -189,6 +204,8 @@ function mollom_test_mollom_form_list() $forms['mollom_test_form'] = array( 'title' => 'Mollom test form', 'entity' => 'mollom_test', + 'entity delete multiple callback' => 'mollom_test_delete_multiple', + 'moderation callback' => 'mollom_test_mollom_form_moderation', ); return $forms; } @@ -198,9 +215,7 @@ function mollom_test_mollom_form_list() */ function mollom_test_mollom_form_info($form_id) { $form_info = array( - 'title' => 'Mollom test form', 'bypass access' => array('administer mollom'), - 'entity' => 'mollom_test', 'elements' => array( 'title' => 'Title', 'body' => 'Body', @@ -221,20 +236,22 @@ function mollom_test_mollom_form_info($f * Form builder for Mollom test form. */ function mollom_test_form($form, &$form_state, $mid = NULL) { - if (empty($form_state['storage'])) { - $body = ''; - if (isset($mid)) { - $body = db_query("SELECT body FROM {mollom_test} WHERE mid = :mid", array(':mid' => $mid))->fetchField(); - } - $form_state['storage'] = array( - 'mid' => $mid, - 'title' => '', - 'body' => $body, - 'exclude' => '', - 'parent' => array('child' => ''), - 'field' => array(), - ); - } + // Due to #limit_validation_errors, submitting the form with the "Add" button + // will only expose validated values in the submit handler, so our storage may + // be incomplete. Therefore, the default values always have to be overloaded. + $form_state += array('storage' => array()); + if (isset($mid) && ($record = mollom_test_load($mid))) { + $form_state['storage'] = $record; + } + $form_state['storage'] += array( + 'mid' => $mid, + 'title' => '', + 'body' => '', + 'exclude' => '', + 'parent' => array('child' => ''), + 'field' => array(), + 'status' => 1, + ); // Always add an empty field the user can submit. $form_state['storage']['field']['new'] = ''; @@ -282,11 +299,19 @@ function mollom_test_form($form, &$form_ $form['field']['submit'] = array( '#type' => 'submit', '#value' => 'Add', - '#validate_parents' => array('field'), + '#limit_validation_errors' => array(array('field')), '#submit' => array('mollom_test_form_field_submit'), '#weight' => 1000, ); + $form['status'] = array( + '#type' => 'checkbox', + '#title' => 'Published', + '#default_value' => $form_state['storage']['status'], + // For simplicity, re-use Mollom module's administration permission. + '#access' => user_access('administer mollom'), + ); + $form['actions'] = array('#type' => 'actions'); $form['actions']['submit'] = array('#type' => 'submit', '#value' => 'Submit'); @@ -297,10 +322,18 @@ function mollom_test_form($form, &$form_ * Form element submit handler for mollom_test_form(). */ function mollom_test_form_field_submit($form, &$form_state) { - // Store the new value and clear out the 'new' field. + // Remove internal Form API values. + form_state_values_clean($form_state); + // Remove all empty values of the multiple value field. + $form_state['values']['field'] = array_filter($form_state['values']['field']); + // Update the storage with submitted values. $form_state['storage'] = $form_state['values']; - $form_state['storage']['field'][] = $form_state['values']['field']['new']; + // Store the new value and clear out the 'new' field. + if (isset($form_state['values']['field']['new'])) { + $form_state['storage']['field'][] = $form_state['values']['field']['new']; + } unset($form_state['input']['field']['new']); + $form_state['rebuild'] = TRUE; } @@ -311,6 +344,12 @@ function mollom_test_form_submit($form, $form_state['values']['field'][] = $form_state['values']['field']['new']; unset($form_state['values']['field']['new']); + // Allow modules to alter the record before saving. This is done for code + // consistency only. + // @see mollom_mollom_test_presave() + form_state_values_clean($form_state); + module_invoke_all('mollom_test_presave', $form_state['values']); + // Store submission. $update = !empty($form_state['values']['mid']) ? 'mid' : array(); drupal_write_record('mollom_test', $form_state['values'], $update); @@ -331,3 +370,40 @@ function mollom_test_form_alter(&$form, $form_state['mollom']['require_captcha'] = FALSE; } } + +/** + * Mollom form moderation callback for a mollom_test record. + */ +function mollom_test_mollom_form_moderation(&$form, &$form_state) { + $form_state['values']['status'] = 0; +} + +/** + * Implements hook_mollom_test_presave() on behalf of mollom.module. + */ +function mollom_mollom_test_presave($record) { + // If an existing record is published and we have session data stored for it, + // mark the data as moderated. + if (!empty($record['mid']) && $record['status']) { + mollom_data_moderate('mollom_test', $record['mid']); + } +} + +/** + * Loads a {mollom_test} data record by id. + */ +function mollom_test_load($mid) { + return db_query('SELECT * FROM {mollom_test} WHERE mid = :mid', array(':mid' => $mid))->fetchAssoc(); +} + +/** + * Deletes multiple stored {mollom_test} data records. + * + * @param $mids + * The mids to delete. + */ +function mollom_test_delete_multiple(array $mids) { + db_delete('mollom_test') + ->condition('mid', $mids) + ->execute(); +}