diff --git a/badbot.install b/badbot.install index 80676ad..1faad0c 100644 --- a/badbot.install +++ b/badbot.install @@ -8,7 +8,17 @@ * Implements hook_install(); */ function badbot_install() { - variable_set('badbot_forms_salt', md5(time() . mt_rand(0, 30))); + variable_set('badbot_forms_salt', user_password(30)); + $forms = array( + 'exact' => array( + 'contact_site_form', + 'user_register_form', + ), + 'pattern' => array( + 'comment_node_.*', + ), + ); + variable_set('badbot_forms_ids', $forms); } /** @@ -16,5 +26,22 @@ function badbot_install() { */ function badbot_uninstall() { variable_del('badbot_forms_salt'); + variable_del('badbot_forms_ids'); +} + +/** + * Implements hook_update_N(). + */ +function badbot_update_7101() { variable_del('badbot_forms_user_registration'); -} \ No newline at end of file + $forms = array( + 'exact' => array( + 'contact_site_form', + 'user_register_form', + ), + 'pattern' => array( + 'comment_node_.*', + ), + ); + variable_set('badbot_forms_ids', $forms); +} diff --git a/badbot.module b/badbot.module index 91c2c71..16a0656 100644 --- a/badbot.module +++ b/badbot.module @@ -1,4 +1,11 @@ 'Badbot', + 'description' => 'Configuration page for Badbot module', 'page callback' => 'badbot_settings', 'access arguments' => array('administer badbot'), ); @@ -52,50 +60,74 @@ function badbot_permission() { * Implements hook_form_alter(); */ function badbot_form_alter(&$form, &$form_state, $form_id) { - $form_ids = &drupal_static(__FUNCTION__); - - if (!isset($form_ids)) { - $form_ids = array(); + $forms = variable_get('badbot_forms_ids', FALSE); + $valid = FALSE; + + if (isset($forms['all'])) { + $valid = TRUE; + } + elseif (in_array($form_id, $forms['exact'])) { + $valid = TRUE; + } + elseif ($forms['pattern']) { + foreach ($forms['pattern'] as $key => $value) { + if (preg_match('`^' . $value . '$`', $form_id)) { + $valid = TRUE; + break; + } + } } - if ($form_id == 'user_register_form' && variable_get('badbot_forms_user_registration', FALSE) && variable_get('badbot_forms_salt', FALSE)) { - // name of the field which will be used as content for token generation - $field = 'mail'; - $validation_field = $field . '_validate'; + if ($valid && variable_get('badbot_forms_salt', FALSE)) { // include our core JS - drupal_add_js(drupal_get_path('module', 'badbot') . '/js/badbot.js'); + drupal_add_js(drupal_get_path('module', 'badbot') . '/js/badbot.js'); + + $form['badbot_wrapper'] = array( + '#type' => 'fieldset', + '#title' => t('Badbot Fields'), + '#description' => t('If you see these fields, something is wrong.'), + '#attributes' => array( + 'class' => array('element-hidden'), + ), + ); // create validation field which will be populated with the token upon form submission; - // this field is hidden from view, so normal users will never see it - $form[$validation_field] = array( - '#type' => 'textfield', - '#prefix' => '
', - '#suffix' => '
', - ); - - // track the names of our field and validation field in $form_state so we can identify - // them in the validation handler - $form_state['badbot']['field'] = $field; - $form_state['badbot']['validation_field'] = $validation_field; + // this field is hidden from view, so normal users will never see it. + $form['badbot_wrapper'][BADBOT_FIELD] = array( + '#type' => 'textfield', + '#default_value' => user_password(), + '#title' => t('Badbot seed'), + '#description' => t('If you see this field, something is wrong.'), + ); + $form['badbot_wrapper'][BADBOT_VALIDATION_FIELD] = array( + '#type' => 'textfield', + '#title' => t('badbot hash'), + '#description' => t('If you see this field, something is wrong.'), + ); + $form['badbot_wrapper']['last_name'] = array( + '#type' => 'textfield', + '#title' => t('Badbot catch'), + '#description' => t('If you see this field, something is wrong.'), + ); // track our form id & relevant fields and save to Drupal.settings for access from our - // core JS - $form_ids[] = array( - 'form_id' => $form['#id'], - 'field' => $field, - 'validation_field' => str_replace('_', '-', $validation_field), - ); - - drupal_add_js(array( - 'badbot' => array( - 'base_path' => url('', array('absolute' => TRUE)), - 'forms' => $form_ids, - ), - ), 'setting'); - - // validation handler to check the token - $form['#validate'][] = 'badbot_form_validate'; + // core JS. + $form_ids[] = array( + 'form_id' => $form['#id'], + 'field' => BADBOT_FIELD, + 'validation_field' => BADBOT_VALIDATION_FIELD, + ); + + drupal_add_js(array( + 'badbot' => array( + 'base_path' => url('', array('absolute' => TRUE)), + 'forms' => $form_ids, + ), + ), 'setting'); + + // validation handler to check the token. + $form['#validate'][] = 'badbot_form_validate'; } } @@ -107,10 +139,8 @@ function badbot_form_alter(&$form, &$form_state, $form_id) { * Callback for /admin/config/system/badbot */ function badbot_settings() { - $form = drupal_get_form('badbot_settings_form'); - - $output = drupal_render($form); - + $output = array(); + $output[] = drupal_get_form('badbot_settings_form'); return $output; } @@ -141,63 +171,98 @@ function badbot_js_token($field_data, $return = FALSE) { * @return [type] [description] */ function badbot_settings_form($form, &$form_state) { - $form['badbot'] = array( - '#type' => 'vertical_tabs', + $form['badbot_forms_salt'] = array( + '#title' => t('Salt'), + '#type' => 'textfield', + '#description' => t("This salt is used during the field hashing process. A salt had been genererated + for you when the module was installed, but you're free to change it. If you + don't know the consenquences of changing this value, it's best to leave it alone.") . + ' ' . t('Do not disclose this value to anyone. Treat it as you would a password.') . '' , + '#default_value' => variable_get('badbot_forms_salt'), ); - - $form['badbot']['forms'] = array( - '#title' => t('Forms'), - '#type' => 'fieldset', - '#description' => t('Enable JavaScript detection on select forms.') . '

', - ); - $form['badbot']['forms']['badbot_forms_salt'] = array( - '#title' => t('Salt'), - '#type' => 'textfield', - '#description' => t("This salt is used during the field hashing process. A salt had been genererated - for you when the module was installed, but you're free to change it. If you - don't know the consenquences of changing this value, it's best to leave it alone.") . - ' ' . t('Do not disclose this value to anyone. Treat it as you would a password.') . '' , - '#default_value' => variable_get('badbot_forms_salt'), - ); - - $form['badbot']['forms']['badbot_forms_user_registration'] = array( - '#title' => t('User Registration'), - '#type' => 'checkbox', - '#default_value' => variable_get('badbot_forms_user_registration', 0), - ); + $badbot_forms_ids_str = ''; + $badbot_forms_ids = variable_get('badbot_forms_ids', FALSE); + + // The more work we do here, the more efficient hook_form_alter() will be. + if (isset($badbot_forms_ids['all'])) { + $badbot_forms_ids_str .= "*\n"; + } + if (isset($badbot_forms_ids['exact'])) { + $badbot_forms_ids_str .= implode("\n", $badbot_forms_ids['exact']) . "\n"; + } + if (isset($badbot_forms_ids['pattern'])) { + $badbot_forms_ids_str .= str_replace('.*', '*', implode("\n", $badbot_forms_ids['pattern'])); + } + + $common['items'] = array( + 'contact_site_form', + 'user_register_form', + 'comment_node_*', + ); + + $form['badbot_forms_ids'] = array( + '#title' => t('Form Ids'), + '#type' => 'textarea', + '#description' => t("Type the IDs of the forms that you want Badbot to detect spam on.
You can use * to make Badbot work on all forms or as a wildcard for partial matching (e.g. node_comment* to catch all comment forms)
Some common IDs you might want to consider: !common_ids", array('!common_ids' => theme('item_list', $common))), + '#default_value' => trim($badbot_forms_ids_str), + '#element_validate' => array('badbot_forms_ids_validate'), + '#required' => TRUE, + ); return system_settings_form($form); } +function badbot_forms_ids_validate($element, &$form_state, $form) { + $original = trim($form_state['values']['badbot_forms_ids']); + if ($original != '*') { + $ids = explode("\n", str_replace('*', '.*', $original)); + foreach ($ids as $id) { + if ($id == '.*') { + $value['all'] = TRUE; + } + elseif (strpos($id, '*') === FALSE) { + $value['exact'][] = $id; + } + else { + $value['pattern'][] = $id; + } + } + } + $form_state['values']['badbot_forms_ids'] = $value; +} + /** * Form validation handler for JavaScript-check enabled forms. */ function badbot_form_validate($form, &$form_state) { $error = FALSE; - - $field = $form_state['badbot']['field']; - $validation_field = $form_state['badbot']['validation_field']; - + // ensure the token is what we expect it to be - if (!isset($form_state['values'][$validation_field]) || badbot_js_token($form_state['values'][$field], TRUE) != $form_state['values'][$validation_field]) { + if (!isset($form_state['values'][BADBOT_VALIDATION_FIELD]) + || badbot_js_token($form_state['values'][BADBOT_FIELD], TRUE) != $form_state['values'][BADBOT_VALIDATION_FIELD]) { $error = TRUE; - watchdog('badbot', 'Blocked form submission.'); + watchdog('badbot', 'Blocked form submission. (Bad token)'); } - - if($error) { - if(empty($error_message)) { - $error_message = t('Sorry, our spam system has flagged your account. Possible reasons for this include:'); - - $reasons = array(); - $reasons[] = t('You have Javascript turned off.'); - $reasons[] = t('Your email or IP address has been flagged as spam by third party services.'); - - $error_message .= '
' . theme('item_list', array('items' => $reasons)) .'
'; - $error_message .= t('For assistance, please contact the site administrator.'); - } - + + // Was it the empty field? + if ($form_state['values']['last_name']) { + $error = TRUE; + watchdog('badbot', 'Blocked form submission. (Non empty field: @value)', array('@value' => $form_state['values']['last_name'])); + } + + if ($error) { + $error_message = t('Sorry, our spam system has flagged your account. Possible reasons for this include:'); + + $reasons = array(); + $reasons[] = t('You have Javascript turned off.'); + $reasons[] = t('Your email or IP address has been flagged as spam by third party services.'); + + $error_message .= '
' . theme('item_list', array('items' => $reasons)) .'
'; + $error_message .= t('For assistance, please contact the site administrator.'); + form_set_error('', $error_message); + drupal_add_http_header('Status', '403 Forbidden'); } } diff --git a/js/badbot.js b/js/badbot.js index d0ab894..93047f5 100644 --- a/js/badbot.js +++ b/js/badbot.js @@ -5,31 +5,19 @@ Drupal.behaviors.badbot = { if (Drupal.settings.badbot.forms.length) { $(Drupal.settings.badbot.forms).each(function(i, form_data) { - var form = $('#' + form_data.form_id); - if (form.length) { - var generated = false; - - form.submit(function(e) { - if (!generated) { - e.preventDefault(); - - generated = true; - - var field_data = $('#edit-' + form_data.field, form).attr('value'); - - if(field_data && $('#edit-' + form_data.validation_field, form)) { - $.get(Drupal.settings.badbot.base_path + '/badbot/token/' + field_data, function(return_data){ - $('#edit-' + form_data.validation_field, form).attr('value', return_data); - - form.submit(); - }); - } - } - }); - } + $('#' + form_data.form_id).once('badbot', function() { + var $form = $(this); + var field_data = $('[name=' + form_data.field + ']', $form).attr('value'); + + if (field_data && $('[name=' + form_data.validation_field + ']', $form)) { + $.get(Drupal.settings.badbot.base_path + '/badbot/token/' + field_data, function(return_data) { + $('[name=' + form_data.validation_field + ']', $form).attr('value', return_data); + }); + } + }); }); } - } + } }; })(jQuery); \ No newline at end of file