From 0dc85f6b1f5036027781d781fd01f9e43d5bc6b1 Mon Sep 17 00:00:00 2001 From: sun Date: Thu, 18 Apr 2013 23:13:20 +0200 Subject: [PATCH 1/2] - #1974190 by sun: Fixed Nodes cannot be moderated from CMP. --- mollom.module | 174 +++++++++++++++++++++++++----------------- tests/mollom.test | 224 ++++++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 281 insertions(+), 117 deletions(-) diff --git a/mollom.module b/mollom.module index cbefde7..af1fa1c 100644 --- a/mollom.module +++ b/mollom.module @@ -2290,60 +2290,61 @@ function _mollom_format_log(array $log) { // Only prettify non-scalar values plus Booleans. // I.e., NULL, TRUE, FALSE, array, and object. if (is_scalar($array) && !is_bool($array)) { - continue; + $value = $array; } - $flat_value = NULL; - // Convert arrays and objects. - // @todo Objects? - if (isset($array) && !is_scalar($array)) { - $ref = &$array; - $key = key($ref); - $parents = array(); - $flat_value = ''; - while ($key !== NULL) { - if (is_scalar($ref[$key]) || is_bool($ref[$key]) || is_null($ref[$key])) { - $value = var_export($ref[$key], TRUE); - // Indent all values to have a visual separation from the last. - $flat_value .= str_repeat(' ', count($parents) + 1) . "{$key} = {$value}\n"; - } - - // Recurse into nested keys, if the current key is not scalar. - if (is_array($ref[$key])) { - $flat_value .= str_repeat(' ', count($parents) + 1) . "{$key} =\n"; - $parents[] = &$ref; - $ref = &$ref[$key]; - $key = key($ref); - } - else { - // Move to next key if there is one. - next($ref); - if (key($ref) !== NULL) { - $key = key($ref); + else { + $flat_value = NULL; + // Convert arrays and objects. + // @todo Objects? + if (isset($array) && !is_scalar($array)) { + $ref = &$array; + $key = key($ref); + $parents = array(); + $flat_value = ''; + while ($key !== NULL) { + if (is_scalar($ref[$key]) || is_bool($ref[$key]) || is_null($ref[$key])) { + $value = var_export($ref[$key], TRUE); + // Indent all values to have a visual separation from the last. + $flat_value .= str_repeat(' ', count($parents) + 1) . "{$key} = {$value}\n"; } - // Move back to parent key, if there is one. - elseif ($parent = array_pop($parents)) { - $ref = &$parent; - next($ref); + + // Recurse into nested keys, if the current key is not scalar. + if (is_array($ref[$key])) { + $flat_value .= str_repeat(' ', count($parents) + 1) . "{$key} =\n"; + $parents[] = &$ref; + $ref = &$ref[$key]; $key = key($ref); } - // Otherwise, reached the end of array and recursion. else { - $key = NULL; + // Move to next key if there is one. + next($ref); + if (key($ref) !== NULL) { + $key = key($ref); + } + // Move back to parent key, if there is one. + elseif ($parent = array_pop($parents)) { + $ref = &$parent; + next($ref); + $key = key($ref); + } + // Otherwise, reached the end of array and recursion. + else { + $key = NULL; + } } } } - } - - $value = NULL; - // Use prettified string representation. - if ($flat_value !== NULL) { - $value = $flat_value; - } - // Use var_export() for Booleans. - // Do not output NULL values on the top-level to allow for labels without - // following value. - elseif ($array !== NULL) { - $value = var_export($array, TRUE); + $value = NULL; + // Use prettified string representation. + if ($flat_value !== NULL) { + $value = $flat_value; + } + // Use var_export() for Booleans. + // Do not output NULL values on the top-level to allow for labels without + // following value. + elseif ($array !== NULL) { + $value = var_export($array, TRUE); + } } // Inject all other key/value pairs as @headingN (and optional @@ -2730,16 +2731,14 @@ function mollom_entity_delete($entity, $type) { /** * @name mollom_moderation Mollom Moderation integration. * @{ - * - * @todo Implement hook_mollom_data_delete() and update 'stored' = 0. */ /** * Implements hook_mollom_data_insert(). */ function mollom_mollom_data_insert($data) { - $mollom_form = mollom_form_load($data->form_id); - if (empty($mollom_form['moderation'])) { + // Only content can be updated. + if (empty($data->contentId)) { return; } // Exclude data for form submissions not resulting in a locally stored entity. @@ -2748,8 +2747,9 @@ function mollom_mollom_data_insert($data) { if ($data->entity == 'mollom_content' || $data->entity == 'mollom_captcha') { return; } - // Only content can be updated. - if (empty($data->contentId)) { + // Skip forms for which Mollom moderation integration is not enabled. + $mollom_form = mollom_form_load($data->form_id); + if (empty($mollom_form['moderation'])) { return; } @@ -2786,8 +2786,8 @@ function mollom_mollom_data_insert($data) { * Implements hook_mollom_data_delete(). */ function mollom_mollom_data_delete($data) { - $mollom_form = mollom_form_load($data->form_id); - if (empty($mollom_form['moderation'])) { + // Only content can be deleted. + if (empty($data->contentId)) { return; } // Exclude data for form submissions not resulting in a locally stored entity. @@ -2796,8 +2796,9 @@ function mollom_mollom_data_delete($data) { if ($data->entity == 'mollom_content' || $data->entity == 'mollom_captcha') { return; } - // Only content can be deleted. - if (empty($data->contentId)) { + // Skip forms for which Mollom moderation integration is not enabled. + $mollom_form = mollom_form_load($data->form_id); + if (empty($mollom_form['moderation'])) { return; } @@ -2988,6 +2989,22 @@ function mollom_moderate($data, $action) { $form_info = mollom_form_load($data->form_id); if ($form_info) { + if (isset($form_info['entity delete callback'])) { + $delete_callback = $form_info['entity delete callback']; + $messages[] = "May delete $form_info[entity] entity via {$delete_callback}()."; + } + elseif (isset($form_info['entity delete multiple callback'])) { + $delete_multiple_callback = $form_info['entity delete multiple callback']; + $messages[] = "May delete $form_info[entity] entity via {$delete_multiple_callback}()."; + } + elseif (function_exists($data->entity . '_delete')) { + $delete_callback = $data->entity . '_delete'; + $messages[] = "May delete $form_info[entity] entity via {$delete_callback}()."; + } + elseif (isset($form_info['delete form']) && isset($load_callback)) { + $messages[] = "May delete $form_info[entity] entity via {$form_info['delete form']} form."; + } + // Programmatically invoke publish action. if ($action == 'approve') { $messages[] = "Attempt to load $form_info[entity] entity via entity_load()."; @@ -3005,13 +3022,25 @@ function mollom_moderate($data, $action) { } } } - // Invoke entity delete multiple callback. - elseif (!empty($form_info['entity delete multiple callback'])) { - $delete_callback = $form_info['entity delete multiple callback']; - $messages[] = "Attempt to delete $form_info[entity] entity via $delete_callback()."; + // Invoke entity delete callback. + elseif (isset($delete_callback)) { + $messages[] = "Attempt to delete $form_info[entity] entity via $delete_callback({$data->id})."; if (function_exists($delete_callback)) { - $delete_callback(array($data->id)); - $messages[] = "Deleted $form_info[entity] entity via $delete_callback()."; + $delete_callback($data->id); + $messages[] = "Deleted $form_info[entity] entity via $delete_callback({$data->id})."; + + // Entity delete callbacks do not return success or failure, so we can + // only assume success. + $result = TRUE; + } + } + // Invoke entity delete multiple callback. + // @todo Remove 'entity delete multiple' callback support later. + elseif (isset($delete_multiple_callback)) { + $messages[] = "Attempt to delete $form_info[entity] entity via $delete_multiple_callback(array({$data->id}))."; + if (function_exists($delete_multiple_callback)) { + $delete_multiple_callback(array($data->id)); + $messages[] = "Deleted $form_info[entity] entity via $delete_multiple_callback(array({$data->id}))."; // Entity delete callbacks do not return success or failure, so we can // only assume success. @@ -3020,7 +3049,7 @@ function mollom_moderate($data, $action) { } // Programmatically invoke delete confirmation form. elseif (isset($form_info['delete form'])) { - $messages[] = "Attempt to delete $form_info[entity] entity via {$form_info['delete form']} form."; + $messages[] = "Attempt to delete $form_info[entity] entity {$data->id} via {$form_info['delete form']} form."; if (isset($form_info['delete form file'])) { $file = $form_info['delete form file'] + array( 'type' => 'inc', @@ -3028,7 +3057,7 @@ function mollom_moderate($data, $action) { 'name' => NULL, ); module_load_include($file['type'], $file['module'], $file['name']); - $messages[] = "Loaded {$file['name']}.{$file['type']} in {$file['module']}."; + $messages[] = "Loaded {$file['name']}.{$file['type']} in {$file['module']} module."; } // Programmatic form submissions are not able to automatically use the // primary form submit button/action, so we need to resemble @@ -3065,7 +3094,11 @@ function mollom_moderate($data, $action) { $messages = implode("\n", $messages); - if ($result) { + // Double-check for error messages, since entity delete callbacks typically do + // not return any status. + $drupal_messages = drupal_get_messages(); + + if ($result && !isset($drupal_messages['error'])) { mollom_log(array( 'message' => '%action moderation success for @entity @id', 'arguments' => array( @@ -3073,12 +3106,18 @@ function mollom_moderate($data, $action) { '@entity' => $data->entity, '@id' => $data->id, ), +// 'Actions:' => $messages, )); } else { // 400 is not really appropriate, but comes closest. drupal_add_http_header('Status', '400 Unable to process moderation request'); + $errors = array(); + if (isset($drupal_messages['error'])) { + $errors['Error messages:'] = implode("\n", $drupal_messages['error']); + } + mollom_log(array( 'message' => '%action moderation failure for @entity @id', 'arguments' => array( @@ -3086,11 +3125,10 @@ function mollom_moderate($data, $action) { '@entity' => $data->entity, '@id' => $data->id, ), - 'Form ID:' => $data->form_id, 'Actions:' => $messages, + ) + $errors + array( 'Mollom data:' => (array) $data, 'Mollom form:' => $form_info, - 'System messages:' => drupal_get_messages(), ), WATCHDOG_ERROR); } @@ -3122,7 +3160,6 @@ function node_mollom_form_list() { 'delete form file' => array( 'name' => 'node.pages', ), - 'entity delete multiple callback' => 'node_delete_multiple', 'report access' => array('bypass node access', 'administer nodes'), ); } @@ -3300,7 +3337,6 @@ function user_mollom_form_list() { 'delete form' => 'user_cancel_confirm_form', 'report path' => 'user/%id/cancel', 'report access' => array('administer users'), - 'entity delete multiple callback' => 'user_delete_multiple', ); $forms['user_profile_form'] = $forms['user_register_form']; $forms['user_profile_form']['title'] = t('User profile form'); diff --git a/tests/mollom.test b/tests/mollom.test index fb4587e..14df4ba 100644 --- a/tests/mollom.test +++ b/tests/mollom.test @@ -4953,59 +4953,17 @@ class MollomModerateUserTestCase extends MollomWebTestCase { /** * Tests integration with Mollom Moderation services. */ -class MollomModerationIntegrationTestCase extends MollomWebTestCase { +abstract class MollomModerationBaseTestCase extends MollomWebTestCase { protected $mollomClass = 'MollomDrupalTestLocal'; - public static function getInfo() { - return array( - 'name' => 'Moderation integration', - 'description' => 'Tests integration with Mollom Moderation services.', - 'group' => 'Mollom', - ); - } - - function setUp() { - parent::setUp(array('mollom_test')); - - $this->drupalLogin($this->admin_user); - $this->setProtectionUI('mollom_test_form', MOLLOM_MODE_ANALYSIS, NULL, array( - 'mollom[discard]' => 0, - 'mollom[moderation]' => 1, - )); - $this->drupalLogout(); - } - - /** - * Creates a mollom_test post. - * - * @param array $edit - * (optional) Custom form input for mollom_test form. - * - * @return stdClass - * The Mollom data object for the created post. - */ - protected function drupalCreateTestPost(array $edit = array()) { - $edit += array( - 'title' => 'spam', - ); - $this->drupalPost('mollom-test/form', $edit, 'Submit'); - $this->assertText('Successful form submission'); - $mid = $this->getFieldValueByName('mid'); - $record = mollom_test_load($mid); - $this->assertSame('Publishing status', $record->status, 0); - $data = mollom_data_load('mollom_test', $mid); - $this->assertTrue($data, 'Mollom data for post exists.'); - return $data; - } - /** - * Creates a mollom_test post. + * Registers a test user through the UI. * * @param array $edit - * (optional) Custom form input for mollom_test form. + * (optional) Custom form input for user registration form. * * @return stdClass - * The Mollom data object for the created post. + * The Mollom data object for the created user. */ protected function drupalCreateTestUser(array $edit = array()) { $edit += array( @@ -5015,10 +4973,10 @@ class MollomModerationIntegrationTestCase extends MollomWebTestCase { $edit['pass[pass1]'] = $edit['pass[pass2]'] = user_password(); $this->drupalPost('user/register', $edit, t('Create new account')); $this->assertText(t('Registration successful. You are now logged in.')); + $account = user_load_by_mail($edit['mail']); $this->assertSame('Account status', $account->status, 1); - $data = mollom_data_load('user', $account->uid); - $this->assertTrue($data, 'Mollom data for user exists.'); + $data = $this->assertMollomData('user', $account->uid); return $data; } @@ -5098,6 +5056,54 @@ class MollomModerationIntegrationTestCase extends MollomWebTestCase { return $out; } +} + +/** + * Tests integration with Mollom Moderation services. + */ +class MollomModerationIntegrationTestCase extends MollomModerationBaseTestCase { + public static function getInfo() { + return array( + 'name' => 'Moderation integration', + 'description' => 'Tests integration with Mollom Moderation services.', + 'group' => 'Mollom', + ); + } + + function setUp() { + parent::setUp(array('mollom_test')); + + $this->drupalLogin($this->admin_user); + $this->setProtectionUI('mollom_test_form', MOLLOM_MODE_ANALYSIS, NULL, array( + 'mollom[discard]' => 0, + 'mollom[moderation]' => 1, + )); + $this->drupalLogout(); + } + + /** + * Creates a mollom_test post. + * + * @param array $edit + * (optional) Custom form input for mollom_test form. + * + * @return stdClass + * The Mollom data object for the created post. + */ + protected function drupalCreateTestPost(array $edit = array()) { + $edit += array( + 'title' => 'spam', + ); + $this->drupalPost('mollom-test/form', $edit, 'Submit'); + $this->assertText('Successful form submission'); + $mid = $this->getFieldValueByName('mid'); + $record = mollom_test_load($mid); + $this->assertSame('Publishing status', $record->status, 0); + $data = mollom_data_load('mollom_test', $mid); + $this->assertTrue($data, 'Mollom data for post exists.'); + return $data; + } + /** * Tests that the 'stored' property of checked content is updated according to local events. * @@ -5351,3 +5357,125 @@ class MollomModerationIntegrationTestCase extends MollomWebTestCase { } } + +/** + * Tests CMP integration for various entity types. + */ +class MollomModerationEntityTestCase extends MollomModerationBaseTestCase { + public static function getInfo() { + return array( + 'name' => 'Moderation entities', + 'description' => 'Tests CMP integration for various entity types.', + 'group' => 'Mollom', + ); + } + + function setUp() { + parent::setUp(array('node', 'comment')); + + // Allow registration by site visitors without administrator approval. + variable_set('user_register', 1); + variable_set('user_email_verification', FALSE); + + $this->drupalCreateContentType(array('type' => 'article', 'name' => 'Article')); + variable_set('comment_preview_article', DRUPAL_OPTIONAL); + variable_set('comment_form_location_article', COMMENT_FORM_BELOW); + user_role_grant_permissions(DRUPAL_AUTHENTICATED_RID, array('create article content', 'view own unpublished content', 'access content', 'access comments', 'post comments')); + + $this->setProtection('article_node_form', MOLLOM_MODE_ANALYSIS, array( + 'moderation' => 1, + )); + $this->setProtection('comment_node_article_form', MOLLOM_MODE_ANALYSIS, array( + 'moderation' => 1, + )); + $this->setProtection('user_register_form', MOLLOM_MODE_ANALYSIS, array( + 'moderation' => 1, + )); + } + + function testModeration() { + // Register a new user. (we are directly logged in) + $account_data = $this->drupalCreateTestUser(); + $account = user_load($account_data->id, TRUE); + + $record = $this->getServerRecord(); + $this->assertSame('stored', $record['stored'], 1); + $this->assertSame('url', $record['url'], url('user/' . $account->uid, array('absolute' => TRUE))); + + // Create a test node. + $edit = array( + 'title' => 'ham node', + ); + $this->drupalPost('node/add/article', $edit, t('Save')); + $node = $this->drupalGetNodeByTitle($edit['title']); + $node_data = $this->assertMollomData('node', $node->nid); + $node_path = 'node/' . $node->nid; + + $record = $this->getServerRecord(); + $this->assertSame('stored', $record['stored'], 1); + $this->assertSame('url', $record['url'], url($node_path, array('absolute' => TRUE))); + + // Create a first test comment. + $edit = array( + 'subject' => 'ham comment 1', + 'comment_body[und][0][value]' => 'ham comment 1', + ); + $this->drupalPost($node_path, $edit, t('Save')); + $comment1 = db_query("SELECT * FROM {comment} WHERE subject = :title", array( + ':title' => $edit['subject'], + ))->fetch(); + $comment1_data = $this->assertMollomData('comment', $comment1->cid); + + $record = $this->getServerRecord(); + $this->assertSame('stored', $record['stored'], 1); + $this->assertSame('url', $record['url'], url('comment/' . $comment1->cid, array( + 'absolute' => TRUE, + ))); + $this->assertSame('contextUrl', $record['contextUrl'], url($node_path, array('absolute' => TRUE))); + $this->assertSame('contextTitle', $record['contextTitle'], $node->title); + + // Create a second test comment. + $edit = array( + 'subject' => 'ham comment 2', + 'comment_body[und][0][value]' => 'ham comment 2', + ); + $this->drupalPost($node_path, $edit, t('Save')); + $comment2 = db_query("SELECT * FROM {comment} WHERE subject = :title", array( + ':title' => $edit['subject'], + ))->fetch(); + $comment2_data = $this->assertMollomData('comment', $comment2->cid); + + $record = $this->getServerRecord(); + $this->assertSame('stored', $record['stored'], 1); + $this->assertSame('url', $record['url'], url('comment/' . $comment2->cid, array( + 'absolute' => TRUE, + ))); + $this->assertSame('contextUrl', $record['contextUrl'], url($node_path, array('absolute' => TRUE))); + $this->assertSame('contextTitle', $record['contextTitle'], $node->title); + + // Moderate the second comment. + $this->mollomRequest('POST', 'mollom/moderate/' . $comment2_data->contentId . '/delete'); + $this->assertResponse(200); + $this->assertMollomWatchdogMessages(); + $this->assertFalse(comment_load($comment2->cid)); + $record = $this->getServerRecord(); + $this->assertSame('stored', $record['stored'], 0); + + // Moderate the node (which should also delete the first comment). + $this->mollomRequest('POST', 'mollom/moderate/' . $node_data->contentId . '/delete'); + $this->assertResponse(200); + $this->assertMollomWatchdogMessages(); + $this->assertFalse(node_load($node->nid, NULL, TRUE)); + $this->assertFalse(comment_load($comment1->cid)); + $record = $this->getServerRecord(); + $this->assertSame('stored', $record['stored'], 0); + + // Moderate the user. + $this->mollomRequest('POST', 'mollom/moderate/' . $account_data->contentId . '/delete'); + $this->assertResponse(200); + $this->assertMollomWatchdogMessages(); + $this->assertFalse(user_load($account->uid, TRUE)); + $record = $this->getServerRecord(); + $this->assertSame('stored', $record['stored'], 0); + } +} -- 1.7.11.msysgit.1 From 29706ed5d92c008f17d67faafc4ddeed63a46d2b Mon Sep 17 00:00:00 2001 From: sun Date: Fri, 19 Apr 2013 00:06:36 +0200 Subject: [PATCH 2/2] - #1974190 by sun: Deprecated 'entity delete multiple callback'. --- tests/mollom.test | 2 +- tests/mollom_test.module | 24 ++++++++++-------------- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/tests/mollom.test b/tests/mollom.test index 14df4ba..69251aa 100644 --- a/tests/mollom.test +++ b/tests/mollom.test @@ -5126,7 +5126,7 @@ class MollomModerationIntegrationTestCase extends MollomModerationBaseTestCase { // Delete the post. // @todo Implement a delete (confirmation) form. - mollom_test_delete_multiple(array($mid)); + mollom_test_delete($mid); $this->assertMollomWatchdogMessages(); // Verify that 'stored=0' was passed to Mollom. diff --git a/tests/mollom_test.module b/tests/mollom_test.module index 2c6333a..5f3c701 100644 --- a/tests/mollom_test.module +++ b/tests/mollom_test.module @@ -85,11 +85,10 @@ function mollom_test_mollom_form_list() { 'title' => 'Mollom basic elements test form', ); // Same as mollom_test_form, but supports an entity delete callback. - // @todo Test mollom/moderate endpoint with entity delete callback. $forms['mollom_entity_test_form'] = array( 'title' => 'Mollom entity test form', 'entity' => 'mollom_test', - 'entity delete multiple callback' => 'mollom_test_delete_multiple', + 'entity delete callback' => 'mollom_test_delete', ); return $forms; } @@ -314,21 +313,18 @@ function mollom_test_save($record) { } /** - * Deletes multiple stored {mollom_test} data records. + * Deletes a {mollom_test} data record. * - * @param $mids - * The mids to delete. + * @param int $mid + * The mid to delete. */ -function mollom_test_delete_multiple(array $mids) { - foreach ($mids as $mid) { - $record = mollom_test_load($mid); - if ($record) { - module_invoke_all('entity_delete', $record, 'mollom_test'); - } +function mollom_test_delete($mid) { + $record = mollom_test_load($mid); + if ($record) { + module_invoke_all('entity_delete', $record, 'mollom_test'); } - db_delete('mollom_test') - ->condition('mid', $mids) + ->condition('mid', $mid) ->execute(); } @@ -349,7 +345,7 @@ function mollom_test_delete_form($form, &$form_state, $record) { * Form submission handler for mollom_test_delete_form(). */ function mollom_test_delete_form_submit($form, &$form_state) { - mollom_test_delete_multiple(array($form_state['values']['mid'])); + mollom_test_delete($form_state['values']['mid']); drupal_set_message('The record has been deleted.'); $form_state['redirect'] = 'mollom-test/form'; } -- 1.7.11.msysgit.1