diff --git a/mollom.admin.inc b/mollom.admin.inc index e35a346..e1b21ee 100644 --- a/mollom.admin.inc +++ b/mollom.admin.inc @@ -15,7 +15,7 @@ function _mollom_supported_languages() { $predefined = _locale_get_predefined_list(); $supported = array_flip(MOLLOM::$LANGUAGES_SUPPORTED); $supported = array_combine(array_keys($supported), array_keys($supported)); - + // Define those mappings that differ between Drupal codes and Mollom codes. $mapped = array( 'nb' => 'no', @@ -82,6 +82,8 @@ function mollom_admin_site_status($force = FALSE, $update = FALSE) { * Menu callback; Displays a list of forms configured for Mollom. */ function mollom_admin_form_list() { + $exportable = module_exists('ctools'); + mollom_admin_site_status(); _mollom_testing_mode_warning(); @@ -98,11 +100,12 @@ function mollom_admin_form_list() { t('Protection mode'), array('data' => t('Operations'), 'colspan' => 2), ); - $result = db_query('SELECT form_id FROM {mollom_form}')->fetchCol(); + if ($exportable) { + $header[] = t('Storage'); + } $forms = array(); - $module_info = system_get_info('module'); - foreach ($result as $form_id) { - $forms[$form_id] = mollom_form_load($form_id); + foreach (mollom_form_load_all(TRUE) as $form_id => $mollom_form) { + $forms[$form_id] = $mollom_form; // system_get_info() only supports enabled modules. Default to the module's // machine name in case it is disabled. $module = $forms[$form_id]['module']; @@ -153,12 +156,52 @@ function mollom_admin_form_list() { '%module' => isset($module_info[$mollom_form['module']]['name']) ? t($module_info[$mollom_form['module']]['name']) : $mollom_form['module'], )), 'warning'); } - $row[] = array('data' => array( - '#type' => 'link', - '#title' => t('Unprotect'), - '#href' => 'admin/config/content/mollom/unprotect/' . $form_id, - )); - + if ($exportable) { + // Remove protection. + if ($mollom_form['export_type'] === EXPORT_IN_DATABASE) { + $row[] = array('data' => array( + '#type' => 'link', + '#title' => t('Unprotect'), + '#href' => 'admin/config/content/mollom/unprotect/' . $form_id, + )); + } + elseif (empty($mollom_form['disabled'])) { + $row[] = array('data' => array( + '#type' => 'link', + '#title' => t('Disable'), + '#href' => 'admin/config/content/mollom/disable/' . $form_id, + )); + } + else { + $row[] = array('data' => array( + '#type' => 'link', + '#title' => t('Enable'), + '#href' => 'admin/config/content/mollom/enable/' . $form_id, + )); + } + // Storage operations + if (!empty($mollom_form['disabled'])) { + $row[] = t('Disabled'); + } + else { + if ($mollom_form['export_type'] & EXPORT_IN_DATABASE && $mollom_form['export_type'] & EXPORT_IN_CODE) { + $row[] = t('Database overriding code (!revert | !export)', array('!revert' => l(t('revert'), 'admin/config/content/mollom/revert/' . $form_id), '!export' => l(t('export'), 'admin/config/content/mollom/export/' . $form_id))); + } + elseif ($mollom_form['export_type'] & EXPORT_IN_DATABASE) { + $row[] = t('In database (!export)', array('!export' => l(t('export'), 'admin/config/content/mollom/export/' . $form_id))); + } + elseif ($mollom_form['export_type'] & EXPORT_IN_CODE) { + $row[] = t('In code (!export)', array('!export' => l(t('export'), 'admin/config/content/mollom/export/' . $form_id))); + } + } + } + else { + $row[] = array('data' => array( + '#type' => 'link', + '#title' => t('Unprotect'), + '#href' => 'admin/config/content/mollom/unprotect/' . $form_id, + )); + } $rows[] = $row_attributes + array('data' => $row); } @@ -180,8 +223,7 @@ function mollom_admin_form_options() { $form_list = mollom_form_list(); // Remove already configured form ids. - $result = db_query('SELECT form_id FROM {mollom_form}')->fetchCol(); - foreach ($result as $form_id) { + foreach (mollom_form_load_all(TRUE) as $form_id => $mollom_form) { unset($form_list[$form_id]); } // If all registered forms are configured already, output a message, and @@ -1092,3 +1134,70 @@ function mollom_reports_page($form, &$form_state) { return $form; } +/** + * Form builder; Confirmation form for CTools operations. + */ +function mollom_admin_ctools_form($form, &$form_state, $action = 'revert', $mollom_form) { + $form = array(); + $form['#tree'] = TRUE; + $form['mollom']['form_id'] = array( + '#type' => 'value', + '#value' => $mollom_form['form_id'], + ); + $form['mollom']['action'] = array( + '#type' => 'value', + '#value' => $action, + ); + switch ($action) { + case 'revert': + $question = t('Are you sure you want to revert this form?'); + $description = t('The configuration for this form will be reverted to its default.'); + break; + + case 'disable': + $question = t('Are you sure you want to disable this form?'); + $description = t('Mollom will no longer protect this form from spam while this configuration is disabled.'); + break; + + case 'enable': + $question = t('Are you sure you want to enable this form?'); + $description = t('Mollom will begin protecting this form from spam.'); + break; + } + return confirm_form($form, $question, 'admin/settings/mollom', $description); +} + +/** + * Form submit handler for mollom_admin_revert_form(). + */ +function mollom_admin_ctools_form_submit($form, &$form_state) { + switch ($form_state['values']['mollom']['action']) { + case 'revert': + mollom_admin_unprotect_form_submit($form, $form_state); + break; + + case 'enable': + ctools_include('export'); + ctools_export_set_status('mollom_form', $form_state['values']['mollom']['form_id'], FALSE); + break; + + case 'disable': + ctools_include('export'); + ctools_export_set_status('mollom_form', $form_state['values']['mollom']['form_id'], TRUE); + break; + } + $form_state['redirect'] = 'admin/config/content/mollom'; +} + +/** + * Form builder; Export a Mollom form configuration. + */ +function mollom_admin_export_form($form, $form_state, $form_id) { + ctools_include('export'); + $objects = ctools_export_load_object('mollom_form', 'names', array($form_id)); + if (!empty($objects[$form_id])) { + $export = ctools_export_object('mollom_form', $objects[$form_id]); + $form = ctools_export_form($form, $form_state, $export, t('Export Mollom form configuration')); + } + return $form; +} diff --git a/mollom.info b/mollom.info index fd54320..78e3b80 100644 --- a/mollom.info +++ b/mollom.info @@ -11,3 +11,4 @@ files[] = includes/mollom.class.inc files[] = mollom.drupal.inc files[] = tests/mollom.test files[] = tests/mollom.class.test + diff --git a/mollom.install b/mollom.install index 0abeb82..b65af88 100644 --- a/mollom.install +++ b/mollom.install @@ -280,6 +280,7 @@ function mollom_schema() { 'size' => 'tiny', 'not null' => TRUE, 'default' => 0, + 'export callback' => 'mollom_ctools_var_export', ), 'checks' => array( 'description' => 'Text analyis checks to perform.', @@ -330,6 +331,16 @@ function mollom_schema() { ), ), 'primary key' => array('form_id'), + 'export' => array( + 'key' => 'form_id', + 'bulk export' => TRUE, + 'api' => array( + 'owner' => 'mollom', + 'api' => 'mollom', + 'minimum_version' => 1, + 'current_version' => 1, + ), + ), ); return $schema; diff --git a/mollom.module b/mollom.module index ba61693..ac15fe0 100644 --- a/mollom.module +++ b/mollom.module @@ -208,6 +208,41 @@ function mollom_menu() { 'access arguments' => array('administer mollom'), 'file' => 'mollom.admin.inc', ); + // Add storage operation callbacks if CTools is present. + if (module_exists('ctools')) { + $items['admin/config/content/mollom/revert/%mollom_form'] = array( + 'title' => 'Revert', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('mollom_admin_ctools_form', 'revert', 5), + 'access arguments' => array('administer mollom'), + 'type' => MENU_CALLBACK, + 'file' => 'mollom.admin.inc', + ); + $items['admin/config/content/mollom/disable/%mollom_form'] = array( + 'title' => 'Disable', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('mollom_admin_ctools_form', 'disable', 5), + 'access arguments' => array('administer mollom'), + 'type' => MENU_CALLBACK, + 'file' => 'mollom.admin.inc', + ); + $items['admin/config/content/mollom/enable/%mollom_form'] = array( + 'title' => 'Enable', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('mollom_admin_ctools_form', 'enable', 5), + 'access arguments' => array('administer mollom'), + 'type' => MENU_CALLBACK, + 'file' => 'mollom.admin.inc', + ); + $items['admin/config/content/mollom/export/%'] = array( + 'title' => 'Export', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('mollom_admin_export_form', 5), + 'access arguments' => array('administer mollom'), + 'type' => MENU_CALLBACK, + 'file' => 'mollom.admin.inc', + ); + } $items['admin/config/content/mollom/blacklist'] = array( 'title' => 'Blacklists', 'description' => 'Configure blacklists.', @@ -808,6 +843,10 @@ function mollom_form_alter(&$form, &$form_state, $form_id) { if (!user_access('bypass mollom protection')) { // Retrieve configuration for this form. if (isset($forms['protected'][$form_id]) && ($mollom_form = mollom_form_load($form_id))) { + // If form is disabled bypass validation. + if (!empty($mollom_form['disabled'])) { + return; + } // Determine whether to bypass validation for the current user. foreach ($mollom_form['bypass access'] as $permission) { if (user_access($permission)) { @@ -969,7 +1008,10 @@ function mollom_form_cache($reset = FALSE) { return $forms; } - $forms['protected'] = db_query("SELECT form_id, module FROM {mollom_form}")->fetchAllKeyed(); + $mollom_forms = mollom_form_load_all(TRUE); + foreach ($mollom_forms as $mollom_form) { + $forms['protected'][$mollom_form['form_id']] = $mollom_form['module']; + } // Build a list of delete confirmation forms of entities integrating with // Mollom, so we are able to alter the delete confirmation form to display @@ -1142,6 +1184,49 @@ function mollom_form_new($form_id) { } /** + * Return an array of all configured Mollom forms. + * + * @param $reset + * Boolean flag to reset the static cache. + */ +function mollom_form_load_all($reset = FALSE) { + static $mollom_forms; + + if (!isset($mollom_forms) || $reset) { + $mollom_forms = array(); + if (module_exists('ctools')) { + ctools_include('export'); + $mollom_forms = ctools_export_load_object('mollom_form'); + } + else { + $result = db_query("SELECT * FROM {mollom_form}"); + foreach ($result as $mollom_form) { + $mollom_form->enabled_fields = unserialize($mollom_form->enabled_fields); + $mollom_forms[$mollom_form->form_id] = $mollom_form; + } + } + foreach ($mollom_forms as $id => $mollom_form) { + // Cast each form as an array. + $mollom_form = (array) $mollom_form; + + // Attach form registry information. + $mollom_form += mollom_form_info($id, $mollom_form['module']); + + // Ensure default values (partially for administrative configuration). + $mollom_form += array( + 'form_id' => $id, + 'title' => $id, + 'elements' => array(), + ); + + $mollom_forms[$id] = $mollom_form; + } + } + + return $mollom_forms; +} + +/** * Menu argument loader; Loads Mollom configuration and form information for a given form id. */ function mollom_form_load($form_id) { @@ -1150,11 +1235,21 @@ function mollom_form_load($form_id) { return $cache->data; } else { - $mollom_form = db_query('SELECT * FROM {mollom_form} WHERE form_id = :form_id', array(':form_id' => $form_id))->fetchAssoc(); - if ($mollom_form) { - $mollom_form['checks'] = unserialize($mollom_form['checks']); - $mollom_form['enabled_fields'] = unserialize($mollom_form['enabled_fields']); - + $mollom_form = FALSE; + if (module_exists('ctools')) { + ctools_include('export'); + if ($objects = ctools_export_load_object('mollom_form', 'names', array($form_id))) { + $mollom_form = (array) $objects[$form_id]; + } + } + else { + $mollom_form = db_query('SELECT * FROM {mollom_form} WHERE form_id = :form_id', array(':form_id' => $form_id))->fetchAssoc(); + if ($mollom_form) { + $mollom_form['checks'] = unserialize($mollom_form['checks']); + $mollom_form['enabled_fields'] = unserialize($mollom_form['enabled_fields']); + } + } + if (!empty($mollom_form)) { // Attach form registry information. $form_info = mollom_form_info($form_id, $mollom_form['module']); $mollom_form += $form_info; @@ -1512,7 +1607,7 @@ function _mollom_get_openid($account) { * The type of entity to check. * @param $bundle * An array of bundle names to check. - * + * * @return array * An array of protected bundles for this entity type. */ @@ -3597,6 +3692,31 @@ function mollom_moderate($data, $action) { */ /** + * @name mollom_export CTools Bulk exporter integration for Mollom. + * @{ + */ + +/** + * Callback to export a single Mollom form configuration into code. + * + * @todo This looks unnecessary - is there no better default handling? + */ +function mollom_export_mollom_form($mollom_form) { + return ctools_export_object('mollom_form', $mollom_form); +} + +/** + * Callback to perform a verbatim export of the value of a table field. + */ +function mollom_ctools_var_export($myobject, $field, $value, $indent) { + return $value; +} + +/** + * @} End of "name mollom_export". + */ + +/** * @name mollom_node Node module integration for Mollom. * @{ */ diff --git a/tests/mollom.test b/tests/mollom.test index 28d9c81..51c3b5f 100644 --- a/tests/mollom.test +++ b/tests/mollom.test @@ -442,8 +442,15 @@ class MollomWebTestCase extends DrupalWebTestCase { protected function setProtectionUI($form_id, $mode = MOLLOM_MODE_ANALYSIS, $fields = NULL, $edit = array()) { // Always start from overview page, also to make debugging easier. $this->drupalGet('admin/config/content/mollom'); - // Determine whether the form is already protected. - $exists = db_query_range('SELECT 1 FROM {mollom_form} WHERE form_id = :form_id', 0, 1, array(':form_id' => $form_id))->fetchField(); + + // Load all forms, stored in db and/or code + $forms = mollom_form_load_all(TRUE); + + // Does the form exist already somewhere? + $exists = isset($forms[$form_id]); + // Does it exist in the database? + $in_database = db_query_range('SELECT 1 FROM {mollom_form} WHERE form_id = :form_id', 0, 1, array(':form_id' => $form_id))->fetchField(); + // Add a new form. if (!$exists) { $this->clickLink(t('Add form')); @@ -482,7 +489,11 @@ class MollomWebTestCase extends DrupalWebTestCase { } } $this->drupalPost(NULL, $edit, t('Save')); - if (!$exists) { + + if (!$exists && !$in_database) { + $this->assertText(t('The form protection has been added.')); + } + elseif ($exists && !$in_database) { $this->assertText(t('The form protection has been added.')); } else { @@ -1010,6 +1021,86 @@ class MollomWebTestCase extends DrupalWebTestCase { } /** + * Tests exporting form configuration functionality. + */ +class MollomTestingExportingFormConfiguration extends MollomWebTestCase { + public static function getInfo() { + return array( + 'name' => 'Exporting form configuration', + 'description' => 'Tests exporting of form configuration with CTools.', + 'group' => 'Mollom', + 'dependencies' => array('ctools'), + ); + } + + function setUp() { + parent::setUp(array('ctools', 'mollom_test')); + } + + function testExportingFormConfiguration() { + $this->drupalLogin($this->admin_user); + + // Create a new form configuration + $this->setProtectionUI('mollom_basic_test_form', MOLLOM_MODE_ANALYSIS); + + // Check if we can export it + $this->assertLinkByHref('admin/config/content/mollom/export/mollom_basic_test_form'); + $this->assertText('In database'); + $links = $this->xpath('//td[contains(., :text)]/a', array(':text' => 'In database')); + + // Is there a valid export link? + $export_url = (string) $links[0]['href']; + $this->assertLinkByHref($export_url); + $export_url = $GLOBALS['base_root'] . $export_url; + $this->drupalGet($export_url); + + // Does the export contains valid PHP code? + $this->assertRaw('$mollom_form = new stdClass();'); + + // Check if the exported form configuration is loaded in the overview + $this->drupalGet('admin/config/content/mollom'); + $this->assertText('Mollom exported basic test form'); + + // Check if not overridden at this time + $this->assertText('In code'); + $this->assertText('Disable'); + + $links = $this->xpath('//td[contains(., :text)]/following-sibling::td/a', array(':text' => 'Mollom exported basic test form')); + + // Check if we can disable/enable the form configuration + $disable_url = (string) $links[1]['href']; + $this->assertLinkByHref($disable_url); + $this->drupalPost($disable_url, array(), t('Confirm')); + $this->assertText('Enable'); + $this->assertText('Disabled'); + + $links = $this->xpath('//td[contains(., :text)]/following-sibling::td/a', array(':text' => 'Mollom exported basic test form')); + + $enable_url = (string) $links[1]['href']; + $this->assertLinkByHref($enable_url); + $this->drupalPost($enable_url, array(), t('Confirm')); + $this->assertText('Disable'); + + // Check if we can override the form configuration + $edit = array( + 'mollom[strictness]' => 'relaxed', + ); + $this->setProtectionUI('mollom_exported_basic_test_form', MOLLOM_MODE_ANALYSIS, NULL, $edit); + $this->assertText('Database overriding code'); + + // Check if we can update an already overridden form configuration + $this->setProtectionUI('mollom_exported_basic_test_form', MOLLOM_MODE_CAPTCHA); + + // Check if we can revert the form configuration to default + $links = $this->xpath('//td[contains(., :text)]/following-sibling::td/a', array(':text' => 'Mollom exported basic test form')); + $revert_url = (string) $links[2]['href']; + $this->assertLinkByHref($revert_url); + $this->drupalPost($revert_url, array(), t('Confirm')); + $this->assertText('In code'); + } +} + +/** * Tests testing mode functionality. */ class MollomTestingModeTestCase extends MollomWebTestCase { diff --git a/tests/mollom_test.module b/tests/mollom_test.module index 5f3c701..b788d95 100644 --- a/tests/mollom_test.module +++ b/tests/mollom_test.module @@ -6,6 +6,15 @@ */ /** + * Implements hook_ctools_plugin_api(). + */ +function mollom_test_ctools_plugin_api($module = NULL, $api = NULL) { + if ($module == "mollom" && $api == "mollom") { + return array("version" => "1"); + } +} + +/** * Implements hook_entity_info(). */ function mollom_test_entity_info() { @@ -61,6 +70,10 @@ function mollom_forms() { $forms['mollom_basic_test_form'] = array( 'callback' => 'mollom_test_form', ); + + $forms['mollom_exported_basic_test_form'] = array( + 'callback' => 'mollom_test_form', + ); return $forms; } @@ -80,6 +93,10 @@ function mollom_test_mollom_form_list() { $forms['mollom_basic_test_form'] = array( 'title' => 'Mollom basic test form', ); + // Same as above, but with exported form configuration. + $forms['mollom_exported_basic_test_form'] = array( + 'title' => 'Mollom exported basic test form', + ); // Same as above, but supports elements for text analysis. $forms['mollom_basic_elements_test_form'] = array( 'title' => 'Mollom basic elements test form', diff --git a/tests/mollom_test.mollom.inc b/tests/mollom_test.mollom.inc new file mode 100644 index 0000000..bb32238 --- /dev/null +++ b/tests/mollom_test.mollom.inc @@ -0,0 +1,37 @@ +disabled = FALSE; /* Edit this to true to make a default mollom_form disabled initially */ + $mollom_form->api_version = 1; + $mollom_form->form_id = 'mollom_exported_basic_test_form'; + $mollom_form->entity = ''; + $mollom_form->bundle = ''; + $mollom_form->mode = 2; + $mollom_form->checks = array( + 0 => 'spam', + ); + $mollom_form->unsure = 'captcha'; + $mollom_form->discard = TRUE; + $mollom_form->moderation = FALSE; + $mollom_form->enabled_fields = array( + 0 => 'title', + 1 => 'body', + 2 => 'exclude', + 3 => 'parent][child', + 4 => 'field', + ); + $mollom_form->strictness = 'normal'; + $mollom_form->module = 'mollom_test'; + + $export['mollom_exported_basic_test_form'] = $mollom_form; + + return $export; +}