Drupal 6: Creating an E-mail Subscription Block

The Drupal manual does a good job telling you how to create a plain old block module but other tutorials on module development are a hundredfold more complicated. The goal of this article is to follow up on the Drupal developer’s guide by creating another only slightly more advanced block.

Prerequisites

You should:

  1. have a Drupal installation to work with.
  2. know how to administer Drupal (e.g., install and activate modules, add blocks).
  3. know how to create a basic block.

Learning Objectives

You will learn:

  1. how to create a .install file to create a database table.
  2. how to create a block with a form.
  3. how to process a form in Drupal.
  4. how to perform database transactions in a Drupal module.
  5. how to theme a form.

The proposed block

The proposed block consists of a form with a fieldset, a text-input field, and a submit button. On submission, the text input field is validated as an e-mail address. If it does not validate, a Drupal error message is returned. Otherwise, the e-mail address will be saved to a database, an e-mail will be sent to the address, and a “thank you” message will be displayed. We will call this a “Persistent E-mail Capture Block” and use “pecapture” as its machine name.

This is what we’re aiming for:

The .info File

You know what it is and how it works from the Drupal developer’s guide. Our .info file will contain the following:

; $Id$
name = Persistent E-mail Capture
description = Provides a block with a form to enroll in subscriptions.
version = "6.x-0.0.1"
core = 6.x
php = 5.x
package = Other

If you need a refresher on what anything does, see the Drupal manual page on .info files.

The .install File

As described earlier, we are using a database table to store e-mail addresses. Here is our very simple database model:

We will now transform this model into an .install file schema:

<?php
// $Id$

/**
 * Implementation of hook_install().
 */
function pecapture_install() {
  drupal_install_schema('pecapture');
}

/**
 * Implementation of hook_uninstall().
 */
function pecapture_uninstall() {
  drupal_uninstall_schema('pecapture');
}

/**
 * Implementation of hook_schema().
 */
function pecapture_schema() {
  $schema = array();
  $schema['pecapture'] = array(
    'fields' => array(
      'capture_id' => array(
        'type' 		=> 'serial',
        'unsigned' 	=> TRUE,
        'not null'	=> TRUE,
      ),
      'email'=>array(
        'type'		=> 'varchar',
        'length'	=> '250',
        'not null'	=> TRUE,
        'default'	=> '',
      ),
    ),
    'indexes' => array('capture_id' => array('capture_id')),
    'primary_key' => array('capture_id'),
  );
  return $schema;
}

When you activate your module, Drupal executes the hook_install() function, pecapture_install() in this case. Likewise, in deactivation or removal, the hook_uninstall() function is executed— pecapture_uninstall() for us.
Our hooks are pretty simple. Their one task is to install or uninstall the pecapture schema. The function pecapture_schema() returns an array of arrays, which Drupal uses to build a table. The structure of this array of arrays is pretty straightforward and mirrors our database model exactly.

The .module File

The .module file should have an opening php tag and a CVS tag as its first two
lines:

<?php
// $Id$

After that, we will add our own code. I’ll go over each function based on its dependency on other functions. The hooks are not necessarily the order Drupal executes them, but that doesn’t matter.

General Functions

First up are the standard hooks. These are all described in the Drupal developer’s guide, so I won’t reinvent the wheel here.

/**
 * Display help and module information
 * @param path which path of the site we're displaying help
 * @param arg array that holds the current path as would be returned from arg() function
 * @return help text for the path
 */
function pecapture_help($path, $arg) {
  $output = '';  //declare your output variable
  switch ($path) {
    case "admin/help#pecapture":
      $output = '<p>'.  t("Provides a block for the persistent e-mail capture form.") .'</p>';
      break;
  }
  return $output;
} // function pecapture_help

/**
 * Implementation of hook_perm().
 */
function pecapture_perm() {
  return array('access pecapture content', 'administer pecapture');
}

Our Functions

Now for the good stuff: functions providing the unique functionality of our block. We will begin with the function that actually creates a block: hook_block(). In this case, our function is called pecapture_block().

/**
 * Implementation of hook_block
 * @param string $op one of "list", "view", "save" and "configure"
 * @param integer $delta code to identify the block
 * @param array $edit only for "save" operation
 */
function pecapture_block($op = 'list', $delta = 0, $edit = array()) {
  $block = array();
  if ($op == 'list') { // Generate listing of blocks from this module, for the admin/block page
    $block[0]['info'] = t('Persistent E-mail Capture Form Block');
  }
  else if ($op == 'view') { // Generate our block content
    $block['subject'] = ''; //'Persistent E-mail Capture Form';
    $block['content'] = pecapture_displayform();
  }
  return $block;
} // function pecapture_block

You’ll recognize the list part from the Drupal guide. Our ‘view’ operation is different, though. We aren’t giving it a subject, and we’re using a function to generate the contents.

Here is that function:

function pecapture_displayform() {
  return drupal_get_form('pecapture_blockform');
}

All this function does is return another function’s value! We use it to facilitate theming later on. The return value calls drupal_get_form(), which takes a function name as an argument.

The function name provided is pecapture_blockform():

function pecapture_blockform(&$form_state) {
  $form = array();
  $form['pecapture'] = array(
    '#type'			=> 'fieldset',
    '#title'		=> t('Don\'t Miss Out'),
    '#description'	=> t('Sign up for National Train Day updates.'),
    '#collapsible' 	=> FALSE,
    '#hidefieldsets' => TRUE,
  );
  $form['pecapture']['email'] = array(
    '#type' => 'textfield',
    '#title' => t('E-mail Addresss'),
    '#default_value' => 'email',
    '#description' => t("The e-mail address to which you will receive updates."),
    '#maxlength'=> 250,
    '#size'		=> 25,
    '#required' => TRUE,
  );
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Keep me updated!'),
  );
  $form['#theme']     = 'pecapture_displayform';
  $form['#validate']  = array('pecapture_blockform_validate');
  return $form;
}

This function holds a Drupal form definition. Drupal has a special system for creating forms using an array of arrays. It works the same as creating a database table in Drupal. First we build a fieldset with a title (which equates to the <legend> tag and a description, which is simply the first paragraph in the fieldset. Then we create the e-mail field and submit button.

The line $form['#theme'] = 'pecapture_displayform'; tells Drupal that when outputting this form, use ‘pecapture_displayform’ as the argument for the theme() function. The theme() function currently has no clue what to do with ‘pecapture_displayform’ so we have to give it a definition using hook_theme():

/**
 * Implementation of hook_theme()
 */
function pecapture_theme() {
  $path = drupal_get_path('module', 'pecapture') . '/theme';
  return array(
    'pecapture_displayform' => array(
      'arguments' => array('form' => NULL),
      'template' => 'pecapture-displayform',
      'path' => $path,
    ),
  );
}

This tells Drupal that when theme(‘pecapture_displayform’) is called, the file pecapture/theme/pecapture-displayform.tpl.php should be returned for output. We have to create that file:

<div class="sidebar-email">

<img src="<?php print base_path().path_to_theme(); ?>/images/headline_dontmissout.jpg" alt="Don't Miss Out" width="211" height="30" border="0" />
<p><?php print $form['pecapture']['#description']; ?></p>
<br />
	<?php print drupal_render($form['pecapture']['email']); ?>
	<?php print drupal_render($form['submit']); ?>
	<div style="display: none;">
	<?php
		unset($form['pecapture']);
		unset($form['submit']);
		print drupal_render($form);
	?>
	</div>
</div>

This creates a <div> for the block, displays a header image, prints the description, displays the e-mail field and submit button, and flushes the rest of the form that we don’t need. You can customize this to your liking. Save this in the theme subdirectory in your module’s directory.

Now that you know what happens with $form['#theme'] = 'pecapture_displayform'; we can go back to the function pecapture_blockform(), and explain the line: $form['#validate'] = array('pecapture_blockform_validate');

This line tells drupal to use the function pecapture_blockform_validate() to validate the form before it is submitted. Here is the code:

function pecapture_blockform_validate($form, &$form_state) {
  $email = $form_state['values']['email'];
  if (strlen($email) < 1) {
    form_set_error('email', t('Please provide your e-mail address.'));
  }
  else if (!eregi('^[a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+\.([a-zA-Z]{2,4})$', $email)) {
    form_set_error('email', t('That is not a valid e-mail address.'));
  }
  else if ($email_user = pecapture_getUserByField('email',$email)) {
    if (isset($email_user)) {
      form_set_error('answers', t('Your e-mail address has already been subscribed.'));
    }
  }
}

This checks to see if the e-mail address field was blank, if it matches an e-mail regular expression, and if the e-mail address already exists in the database. We are using the function pecapture_getUserByField(‘email’,$email) to find a user in the database, but we need to write that. Here it is:

function pecapture_getUserByField($field,$data){
  $q = 'SELECT capture_id FROM {pecapture} WHERE %s LIKE \'%s\' LIMIT 1';
  $q = db_fetch_array(db_query($q,array($field,$data)));
  if (isset($q['capture_id']))
    return true;
  else
    return false;
}

This very simply selects the e-mail address from the database and returns true or false. This concludes our validation function.

The final function in our module is the hook_submit() function for the form:

function pecapture_blockform_submit($form, &$form_state) {
  $values = $form_state['values'];
  $fields = array('email');
  $q = 'INSERT INTO {pecapture} ('.implode(',',$fields).') VALUES (\'%s\')';
  $insert = array();
  foreach ($fields as $f) {
	$insert[] = $values[$f];
  }
  db_query($q, $insert);
  $user = $insert;
  drupal_set_message('Thanks for subscribing!');
}

This just creates a query based on the form values and runs it, then queues a Drupal message.

Conclusion

This could have been accomplished with the modules Webform and Webform Block or Node As Block or PHPTemplate to insert a node into a region, but creating your own module in this case offers a level of theme output greater than what Webform allows without some intricate hooks (try and remove the form label!). You should now also have a better understanding of how to create a Drupal module, form, and hook into themes.

Further Reading

I recommend following these tutorials next, in order:

  1. Creating Our First Module using Drupal 6
  2. Creating “Simple” Drupal Modules, and Filling the Functionality Gap

Share this article:

  • del.icio.us
  • StumbleUpon
  • Digg
  • Reddit
  • DZone
  • Mixx
  • Twitter
  • email

Leave a Comment

*

*
<pre>, <code>, and <blockquote> allowed.