You are here

webform_confirm_email.module in Webform Confirm Email Address 6

Add email confirmation rules to webforms used as letter campaigns

File

webform_confirm_email.module
View source
<?php

/*
Copyright 2010 Robin Millette <robin@millette.info>

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

/**
 * @file
 * Add email confirmation rules to webforms used as letter campaigns
 */

/**
 * Implementation of hook_form_alter().
 *
 * @param $form
 *   Nested array of form elements that comprise the form.
 * @param $form_state
 *   A keyed array containing the current state of the form.
 * @param $form_id
 *   A unique string identifying the form for validation, submission,
 *   theming, and hook_form_alter functions.
 */
function webform_confirm_email_form_alter(&$form, &$form_state, $form_id) {
  $nid = (int) $form['details']['nid']['#value'];
  if ('webform_client_form_' . $nid !== $form_id) {
    return;
  }
  if (!_webform_confirm_email_required($nid)) {
    return;
  }
  $form['#submit'][] = '_webform_confirm_email_submit';
}

/**
 * Implementation of hook_form_alter().
 *
 * @param $form
 *   Nested array of form elements that comprise the form.
 * @param $form_state
 *   A keyed array containing the current state of the form.
 */
function webform_confirm_email_form_webform_component_edit_form_alter(&$form, &$form_state) {
  if ('email' !== $form['type']['#value']) {
    return;
  }
  $form['advanced']['must_confirm'] = array(
    '#type' => 'checkbox',
    '#title' => t('Must confirm this email address'),
    '#default_value' => (int) $form['cid']['#value'] === _webform_confirm_email_required($form['nid']['#value']),
    '#description' => t('The user must confirm his email address before this form gets sent. For use in email campaigns. Checking this will replace the last selected email address requiring confirmation. In other words, you can only specify a single email address for confirmation per form.'),
    '#weight' => 3,
    '#access' => TRUE,
  );
  $form['#submit'][] = '_webform_confirm_email_component_submit';
}

/**
 * Does the webform require email address confirmation?
 * @param $nid
 *   Node ID of the webform.
 * @return
 *   component id of email address confirmation or false if there is none.
 */
function _webform_confirm_email_required($nid) {
  return _webform_confirm_email_required_set($nid, NULL);
}

/**
 * Make the webform require email address confirmation.
 * @param $nid
 *   Node ID of the webform.
 * @param $cid
 *   Component ID of the webform email component.
 */
function _webform_confirm_email_required_set($nid, $cid) {
  static $form_nids = FALSE;
  if (!$form_nids) {
    $form_nids = unserialize(variable_get('webform_confirm_email_required', 'a:0:{}'));
  }
  if (is_null($cid)) {
    return isset($form_nids[$nid]) ? $form_nids[$nid] : FALSE;
  }
  if ($cid) {
    $form_nids[$nid] = $cid;
  }
  else {
    unset($form_nids[$nid]);
  }
  variable_set('webform_confirm_email_required', serialize($form_nids));
}

/**
 * Checks if this webform necessitates email address confirmation.
 *
 * @param $form
 *   Nested array of form elements that comprise the form.
 * @param $form_state
 *   A keyed array containing the current state of the form.
 */
function _webform_confirm_email_submit($form, &$form_state) {
  drupal_set_message(t('You should receive an email with a link to confirm your email address. You must follow that link in order for your letter to be sent.'));
}

/**
 * Checks if this webform necessitates email address confirmation.
 *
 * @param $form
 *   Nested array of form elements that comprise the form.
 * @param $form_state
 *   A keyed array containing the current state of the form.
 */
function _webform_confirm_email_component_submit($form, &$form_state) {
  if ($form_state['values']['must_confirm']) {
    _webform_confirm_email_required_set((int) $form['nid']['#value'], (int) $form_state['values']['cid']);
  }
  else {
    _webform_confirm_email_required_set((int) $form['nid']['#value'], FALSE);
  }
}

/**
 * Takes email address of the form bob <bob@example.com> and returns bob@example.com
 * If only supplied bob@example.com it returns that
 *
 * @param $email
 *  Email address to be cleaned up.
 * @return
 *   Plain email address (removing name and <>).
 */
function _webform_confirm_email_strip_name($email) {
  if ('>' !== substr($email, -1)) {
    return $email;
  }
  list(, $email) = explode(' <', $email, 2);
  return substr($email, 0, -1);
}

/**
 * Get submission ID given
 *
 * @param $nid
 *   Node ID of the webform.
 * @param $from
 *   Email address of the webform submitter.
 * @param $cid
 *   Component ID of the webform email component.
 * @return
 *   Array containing submission ID and timestamp or false on error.
 */
function _webform_confirm_email_get_sid($nid, $from, $cid) {

  // get all submissions for this node
  $submissions = webform_get_submissions($nid, array(
    array(
      'field' => 'submitted',
      'sort' => 'desc',
    ),
  ));
  $from = _webform_confirm_email_strip_name($from);

  // adding sentinel (flag) to know if we reached the end without finding
  $submissions['flag']->data[$cid] = $from;
  foreach ($submissions as $sid => $submission) {
    if ($from === $submission->data[$cid]['value'][0]) {
      $timestamp = $submission->submitted;
      break;
    }
  }

  // not found, bailing out
  if ('flag' === $sid) {
    return FALSE;
  }
  return array(
    $sid,
    $timestamp,
  );
}

/**
 * Implementation of hook_menu().
 *
 * @return
 *   Menu structure.
 */
function webform_confirm_email_menu() {
  $items = array();
  $items['node/%/webform_confirm_email/%/%'] = array(
    'title' => 'Confirming email address to send letter',
    'page callback' => '_webform_confirm_email',
    'page arguments' => array(
      1,
      3,
      4,
    ),
    'access arguments' => array(
      'access content',
    ),
    'type' => MENU_CALLBACK,
  );
  return $items;
}

/**
 * Verify that the correct user is confirming his email address using cryptographic signature.
 *
 * @param $nid
 *   Node ID of the webform expecting an email address confirmation.
 * @param $sid
 *   Submission ID of the webform submission expecting an email address confirmation.
 * @param $hmac
 *   Cryptographic signature to make sure the correct user is confirming the given email address.
 * @return
 *   Text to say if confirmation was valid or not.
 */
function _webform_confirm_email($nid, $sid, $hmac) {
  if (hash_hmac('md5', $sid, drupal_get_private_key()) !== $hmac) {
    return t('Looks like you tried to confirm an email address to complete a form submission but you provided the wrong signature. Check the email you received again and make sure you copy the whole URL.');
  }

  // email address confirmed
  // time to send the actual letter
  module_load_include('inc', 'webform', 'webform_submissions');
  $submission = webform_get_submission($nid, $sid);

  // get the prepared mail we stored when webform_confirm_email_mail_alter()
  // was called and now send it.
  $result = db_query('SELECT message from {webform_confirm_email} WHERE sid = %d', $sid);
  $object = db_fetch_object($result);
  $message = unserialize($object->message);
  $message['body'] = is_array($message['body']) ? drupal_wrap_mail(implode("\n\n", $message['body'])) : drupal_wrap_mail($message['body']);
  drupal_mail_send($message);
  return t('Thank you. We sent the letter you completed now that we have confirmed your email address.');
}

/**
 * Store the message we hijacked when we sent confirmation email instead.
 *
 * @param $message
 *   The message originally sent from the webform that we're going to send after email address confirmation.
 * @return
 *   Valid URL containing node ID, submission ID and cryptographic signature
 *   or false if no confirmation is necessary.
 */
function _webform_confirm_email_store_message($message) {
  $nid = arg(1);
  if (!($cid = _webform_confirm_email_required($nid))) {
    return FALSE;
  }
  $object = new stdClass();
  $object->message = $message;
  list($object->sid, $timestamp) = _webform_confirm_email_get_sid($nid, $message['from'], $cid);
  $object->submitted = gmdate('Y-m-d H:i:s', $timestamp);
  $object->hmac = hash_hmac('md5', $object->sid, drupal_get_private_key());
  drupal_write_record('webform_confirm_email', $object);
  return url("node/{$nid}/webform_confirm_email/{$object->sid}/{$object->hmac}", array(
    'absolute' => TRUE,
  ));
}

/**
 * Implementation of hook_mail_alter().
 *
 * @param $message
 *   The message originally sent from the webform that we're going to send after email address confirmation.
 */
function webform_confirm_email_mail_alter(&$message) {
  $confirm_url = _webform_confirm_email_store_message($message);
  if (!$confirm_url) {
    return;
  }

  // rewrite message to get email address confirmation first
  $to = $message['to'];
  $message['to'] = $message['from'];
  $message['from'] = variable_get('site_mail', NULL);
  $message['subject'] = t('Confirmation email');
  $message['body'][0] = t("You must confirm this is really your email address to send a letter to !to.", array(
    '!to' => $to,
  ));
  $message['body'][1] = t("Visit !confirm_url to confirm it.", array(
    '!confirm_url' => $confirm_url,
  ));
}

/**
 * Provide online user help.
 *
 * @param $path
 *   The router menu path, as defined in hook_menu(), for the help that
 *   is being requested; e.g., 'admin/node' or 'user/edit'. If the router path
 *   includes a % wildcard, then this will appear in $path; for example,
 *   node pages would have $path equal to 'node/%' or 'node/%/view'. Your hook
 *   implementation may also be called with special descriptors after a
 *   "#" sign. Some examples:
 *   - admin/help#modulename
 *     The main module help text, displayed on the admin/help/modulename
 *     page and linked to from the admin/help page.
 *   - user/help#modulename
 *     The help for a distributed authorization module (if applicable).
 * @param $arg
 *   An array that corresponds to the return value of the arg() function, for
 *   modules that want to provide help that is specific to certain values
 *   of wildcards in $path. For example, you could provide help for the path
 *   'user/1' by looking for the path 'user/%' and $arg[1] == '1'. This
 *   array should always be used rather than directly invoking arg(), because
 *   your hook implementation may be called for other purposes besides building
 *   the current page's help. Note that depending on which module is invoking
 *   hook_help, $arg may contain only empty strings. Regardless, $arg[0] to
 *   $arg[11] will always be set.
 * @return
 *   A localized string containing the help text.
 */
function webform_confirm_email_help($path, $arg) {
  switch ($path) {
    case 'admin/help#webform_confirm_email':
      return '<p>' . t('This addon module for Webform let\'s you specify which, if any, email addresses should be confirmed by their owner before a webform is sent. Mostly used for email campaigns.') . '</p>';
    default:
      return '';
  }
}

Functions

Namesort descending Description
webform_confirm_email_form_alter Implementation of hook_form_alter().
webform_confirm_email_form_webform_component_edit_form_alter Implementation of hook_form_alter().
webform_confirm_email_help Provide online user help.
webform_confirm_email_mail_alter Implementation of hook_mail_alter().
webform_confirm_email_menu Implementation of hook_menu().
_webform_confirm_email Verify that the correct user is confirming his email address using cryptographic signature.
_webform_confirm_email_component_submit Checks if this webform necessitates email address confirmation.
_webform_confirm_email_get_sid Get submission ID given
_webform_confirm_email_required Does the webform require email address confirmation?
_webform_confirm_email_required_set Make the webform require email address confirmation.
_webform_confirm_email_store_message Store the message we hijacked when we sent confirmation email instead.
_webform_confirm_email_strip_name Takes email address of the form bob <bob@example.com> and returns bob@example.com If only supplied bob@example.com it returns that
_webform_confirm_email_submit Checks if this webform necessitates email address confirmation.