Development Seed

Blog

Integrating Drupal, Democracy in Action, and i18n

Making It Possible To Collect User Information in Five Languages

The United Nations Millennium Campaign gathered millions of people around the world this week to stand up against poverty and in support of the UN’s Millennium Development Goals. We were heavily involved in developing the Stand Up Speak Out campaign’s Drupal website, StandAgainstPoverty.org, and had a great time adding some interesting and important features to the site.

A very important part of that development work revolved around the need to make the site viewable in five languages (English, French, Portuguese, Spanish and German, for those keeping score). The real trick was that the campaign wanted to collect information from the people visiting their website using their CRM system – Democracy In Action’s (DiA) Salsa toolset – but the toolset only handled information in English. I’ll explain in this blog post how we used Drupal to get DiA’s CRM to also handle information in the site’s other four languages.

We used a number of i18n-related modules to make translation easy and integrated several forms created using DiA in a way that made the forms i18n-friendly for fast translation. The approach described below is not ideal, but it was executable in the short timeframe in which we were operating. Ideally, we’d make better use of DiA’s API so that form-creation is accomplished in DiA’s Salsa interface, and Drupal could query the details of the various form pages and build Drupal forms on the fly with that data.

Given our time constraints, we built the forms manually. For anyone who needs to quickly build forms in Drupal, I can’t recommend the Forms API Quickstart Guide on Drupal.org highly enough – it helped shake out the cobwebs.

Building the two forms was straightforward enough. I began by adding a menu callback to my module, then added the page building function, and then built my form out.

// Menu callback$items[] = array(  'path' => 'path/to/my/form',  'title' => 'DiA Signup Form',  'callback' => 'dia_signup_page',  'access' => TRUE,);[...]// Page building functionfunction dia_signup_page() {  return drupal_get_form('dia_form');}

There are several things to keep in mind here. First, in order to make these forms available to the l10n_client for easy translation, you will need to wrap your field titles in Drupal’s t() function.

// Sample form field$form['dia']['First_Name'] = array(  '#type' => 'textfield',  // Make translation easy—always use the t() function!  '#title' => t('First Name'),  '#size' => 30,  '#maxlength' => 64,);

In Drupal, you can save your hidden form fields and add them in your form submission function, thereby eliminating another place where unexpected client input may cause headaches (or worse). DiA forms make heavy use of hidden fields when custom data is collected or when user submitted records are added to DIA groups.

Another consideration is that Drupal doesn’t allow identically-named form fields. These are fine in html as the data are concatenated into a comma-delimited list by most browsers. In Drupal, however, I had to create five uniquely named “to:” fields for DiA’s tell-a-friend form (to1, to2, etc.). These fields will get reassembled upon submission.

This brings us to the validation function. In this function, I recreated DiA’s error checking to make sure that users submitted all the required fields. In their forms, DiA uses a hidden field to transmit this information to the server. We can retain this information server-side in this function without letting it make a trip to the client. Again, I made sure all the error messages were run through our friend t(). I had to make sure that this validation function is at least as restrictive as DiA’s validation because we don’t want the submission function to have to check DiA’s return data – we want to get it right in Drupal.

function dia_form_validate($form_id, $form_values) {  if ($form_values['First_Name'] == '') {    // Never forget to use t()!    form_set_error('First_Name', t('You must enter a first name.'));  }  [...]}

Next came the submit function. This function needed to do two things: First, it must assemble our form fields, both the user submitted data that has passed the validation function and DiA’s hidden fields. Next it will post this data, via curl, to DiA’s html API. The hidden form fields are added as key-value pairs to an array and this array is merged with the submitted fields, which are in the $form_values array. (Make sure to re-assemble any form fields you needed to split apart due to Drupal’s form element unique-naming requirements before you merge the two arrays.) Once the variables are assembled, I looped over the resulting array and built an http query string, making sure to htmlencode the values for each parameter. Finally, we’re ready to post to DiA. This is done via a simple invocation of cURL: declaring the URL and the post variables, and executing and closing the handle.

function dia_form_submit($form_id, $form_values) {  // First is an example of a hidden field; we hard-code it's value.  // Next is an example of a user-submitted field; it comes from $form_values  $dia_fields = array(    'table' => 'supporter',    'First_Name' => $form_values['First_Name'],    [...]  );  // Tack on the field that needed to be re-assembled  for ($i = 0; $i <= 5; $i++) {    $dia_fields['to'] .= $form_values['to' . $i] . ",";  }  // Build the http query string  foreach($dia_fields AS $field => $value) {    $qs .= $field . "=" . urlencode($value) . "&";  }  // Setting up the cURL session  // This is the DiA API url.  $ch = curl_init('http://www.democracyinaction.org/dia/processEditValues.jsp');  curl_setopt($ch, CURLOPT_POST, 1);  curl_setopt($ch, CURLOPT_RETURNTRANSFER,TRUE);  curl_setopt($ch, CURLOPT_POSTFIELDS,$qs);  curl_setopt($ch, CURLOPT_HEADER,1);  $return_data = curl_exec($ch);  curl_close($ch);  [ Error checking code to make sure the HTTP transaction didn't fail. ]  // Assuming all went well, thank your user (using t()!) and send them on their way  drupal_set_message(t('Thanks for signing up!'));  return 'path/to/next/page';  }}

Once that’s done, I set our user message using t() within drupal_set_message() and sent users on their way. Whether you use the DiA-specified redirect or impose one of your own is up to you. One final caution – if you anticipate a good deal of traffic on your Drupalized DiA forms, you should alert DiA and ask them to whitelist your application servers, as DiA’s servers are configured to cease accepting form submissions from your servers after a certain number of transactions for obvious reasons.

And that’s how we made it possible for the UN Millennium Campaign to collect information in five languages using their standard CRM.