Integrating Mollom with AHAH and your custom Drupal Forms API form
The basic process of integrating an AHAH handler for a Drupal 6 form causes the form to be submitted back to a custom URL, reprocess and regenerate the form. In this case, once a user inputs their post codes we need to generate a list of electorates that that postcode could belong to. This involves generating a custom submit handler to process the form, and then you return an JSON object with the JSON response.
The process of this is relatively well documented here and here. This methodology creates a stable and workable AHAH submit handler to process the form.
One of the issues we needed to counter for in the process of building this form was the possibility of SPAM. It would not be good for our system to be hijacked to send SPAM messages to politicians. Because I have previously had quite a positive experience with Mollom as a SPAM filtering service, I selected Mollom to provide this role. Their high performance API, relative accuracy and low cost are both attractive in this case.
To understand the problem we encountered you need to clearly understand what happens when you submit a form via AJAX:
- The form is received, and you reconstruct the Form API handlers to properly submit the form (this would normally be handled in drupal_get_form)
- You "process" the form to ensure that all current values are updated, and the form is now ready to handle and process any state chagnes
- You "regenerate" the form with the saved form data, as though you were going to redisplay the form
- You return the portion of HTML you would like inserted into your form via a JSON object.
The hidden trap is that step 2 actually involves validating and potentially submitting the form. To avoid submitting the form, you need to ensure is that you have this line:
$form_state['rebuild'] = true;
before you run drupal_process_form. Failure to do this will result in your submit function being called if you have put the form into a valid state, resulting in whatever actions you have set being executed prematurely. This might be obvious to some, but it wasn't easily noticeable from the examples and documentation I had read.
The Mollom workflow for Text Analysis involves submitting the form to Mollom during the verification stage to determine if a CAPTCHA is required to be displayed for the end user. In the case of an AHAH callback, you do not want this to occur, as you will end up submitting the same form to Mollom several times, sometimes with insignificant data. This is not good for your Mollom non-spam submission count, and is also not good for your site performance (even though Mollom is very fast).
We battled with how to disable this for quite a while - and had considered forking the Mollom module, overriding the form hook alter code, inserting errors to prevent submission, writing custom calls to the validation code and a range of other things.
In the end, the solution is quite simple. In your AHAH handler, add the following chunk of code after your $form_state['rebuild'] line.
$form_state['mollom']['require_analysis']=false;
Because the $form_stat['mollom'] is stateless, it only applies to the current session, and prevents the Mollom module from submitting the form to Mollom for validation on this submission.
In the end, our submission handler looks like:
<?php
$form_state
= array(
'storage' => NULL,
'submitted' => FALSE,
);
$form_build_id = $_POST['form_build_id'];
$form = form_get_cache($form_build_id, $form_state);
$args = $form['#parameters'];
$form_id = array_shift($args);
$form_state['action'] = $form['#action'];
$form_state['post'] = $form['#post'] = $_POST;
$form['#programmed'] = $form['#redirect'] = FALSE;
$form_state['rebuild'] = true;
// Stash original form action to avoid overwriting with drupal_rebuild_form().
$form_state['mollom']['require_analysis']=false;
drupal_process_form($form_id, $form, $form_state);
// Rebuild the form.
$form = drupal_rebuild_form($form_id, $form_state, $args, $form_build_id);
// Grab the "electorate" element from the form build and get it ready to dispaly
$output = $form['electorate-wrap']['electorate'];
// Get the JS settings so we can merge them.
$javascript = drupal_add_js(NULL, NULL, 'header');
$settings = call_user_func_array('array_merge_recursive', $javascript['setting']);
// Some custom Javascript to perform various change functions affecting the
// visuals of the form once this is called
// There may be a more "drupal" way of hooking into the AHAH return events,
// but I certainly couldn't find them
$newJs = '<script>setTimeout(function(){$("#edit-electorate").change();}, 300);setTimeout(function(){fixElectorateChange();},300);setTimeout(function(){fixElectorateSelect()},300)</script>';
// Most of the examples out there return validation errors in the result - we need to eat them
// (consume them might be a more "correct" term so they don't appear later, but not display
$eat = theme('status_messages');
$output = $form['electorate-wrap']['electorate'];
// Use druapl_to_js instead of drupal_json because of some browser based bugs
print drupal_to_js(array(
'status' => TRUE,
'data' => drupal_render($output).$newJs,
'settings' => array('ahah' => $settings['ahah']),
));
exit;
?>Obviously these pointers only apply where you do not want to in fact submit the form to Mollom - if we actually wanted submission to occur none of this would be necessary.