Beyond Spots & Dots | Digital

Creating an alternate user email-verification system with Drupal

If you are the administrator of a Drupal site that has members, you probably know that Drupal's stock user-registration system has problems, and in particular its email-verification system kinda... sucks. Here's a quick refresher on how it works:

  • If the administrator has turned "require email verification" off in the Drupal admin, anyone can sign up for the website.
  • If the administrator turns it on, it prevents new users from choosing a password. They can enter their email address, choose a username, and fill profile fields (if there are any), but the password fields are missing.
  • By design, Drupal then sends a "welcome to this site" email containing a link which takes the individual through the standard password-recovery process, at which point they are instructed to choose a password.
  • This system is clunky and requires extra steps, and the password-recovery process is convoluted, especially for new users. This raises the barrier to entry and confuses users about why they should not be able to set a password ahead of time.

To help remedy this, I wrote a module for a client site that significantly improves the experience of new users. Here's an overview of what it does:

  • First, I used the Rules module to redirect the user to a customized "welcome... please check your email" page immediately upon registration.
  • Some custom code behind this page automatically disables the newly-created account, so that in to Drupal administrators, the account appears blocked. This helps, later, when admins want to clear out all old accounts for users (or spammers) who never verified their email addresses.
  • A "welcome to this site" email is sent to the user, containing a special link which includes their User ID, in a format like this: example.com/verify/[user_id]/[user_creation_timestamp] or something along those lines. Upon landing on this page-- which the user could not reach except through clicking the custom link in the "welcome" email-- the account is automatically activated again. The user is then redirected to a "thank you" page notifying them of the process's success.

I logged the process in Drupal's database log, and I've noticed that the time between the account disabling and the re-enabling is usually several minutes. Members of this site also reported positively about the new experience.

Below is a code sample that disables and then re-enables the user account.


function modulename_menu() {
    
    $items['registration/nearly-finished'] = array(
        'title' => 'Check Your Email!',
        'description' => 'Registration: Nearly-Finished Page',
        'page callback' => 'modulename_registration_nearlyfinished',
        'access callback' => true,
        'type' => MENU_CALLBACK,
    );
    $items['verify'] = array(
        'title' => 'Verify your Email',
        'description' => 'Registration: Verify Page',
        'page callback' => 'modulename_registration_verify',
        'access callback' => true,
        'type' => MENU_CALLBACK,
    );
    
    return $items;
    
}



/*
 * The function handling the 'nearly finished' page. 
 * If we're on the 'nearly finished' page, the user was created in 
 * the last two minutes, and the 'u' parameter is filled, block the user.
 */

function modulename_registration_nearlyfinished() {
    
    // If 'created' and 'u' keys are in the query string:
    if (isset($_GET['created']) && isset($_GET['u'])):
        
        $time = time();
        $url_uid = $_GET['u'];
        
        // See when the user was created.
        $select_user = db_query('SELECT uid, created FROM {users} WHERE uid = :uid', array(':uid' => $url_uid));
        foreach ($select_user as $user_acct) {
            $db_created = $user_acct->created;
        } // end foreach
        
        // Now: if it's within 2 minutes of when the user was created (as a protective measure), and the items in the URL check out as true, let's deactivate the user.
        $time_since = $time - $db_created;
        if ($time_since < 120):
            $user_activated = db_update('users')
          ->fields(
              array(
                  'status' => 0,
              )
          )
          ->condition('uid', $url_uid, '=')
          ->execute();
          watchdog('modulename', 'Deactivated %uid', array('%uid' => $url_uid), WATCHDOG_NOTICE, $link = NULL);
        endif; // if ($url_email == $db_email)
        
    endif; // end if $_GET['created']
    
    // Now for the actual page content.
    
    $page_content = '<p>None of us like this, but spam registration and email spoofing are a fact of life.&nbsp;To complete your registration, we've just emailed you a confirmation link.&nbsp;<strong>Once you've verified your email address, we'll send you to a page where you can choose a password</strong>.</p>
    <p>If you don't see the email appear within several minutes, make sure to check your spam folder. <em>See you on the flip side!</em></p>';
    
    return $page_content;
    
}

/*
 * The function handling the 'nearly finished' page. 
 * If we're on the 'verify' page, activate the user and redirect to the 'thanks' page.
 */

function modulename_registration_verify() {
    
    if (isset($_GET['created']) && isset($_GET['u'])):
        
        // set the '$options' array for later use in drupal_goto()
        $options = array(
            "query" => '',
            "fragment" => '',
            "external" => FALSE
        );
        
        $url_created = $_GET['created'];
        $url_uid = $_GET['u'];
        $my_groups = '';
        $entity_type = '';
        $db_created = '';
        
        // See if the current user has a group membership.
        $select_user = db_query('
            SELECT u.uid, u.created AS user_created, m.entity_type AS entity_type, m.etid, m.gid AS group_id FROM {users} u
            LEFT JOIN {og_membership} m ON (u.uid = m.etid) 
            WHERE u.uid = :uid', 
            array(
                ':uid' => $url_uid
            )
        );
        foreach ($select_user as $user_acct) {
            $db_created = $user_acct->user_created;
            $my_groups = $user_acct->group_id;
            $entity_type = $user_acct->entity_type;
        } // end foreach
        
        // If the 'created' value lines up, we'll activate the user account.
        if ($url_created == $db_created):
            $user_activated = db_update('users')
          ->fields(
              array(
                  'status' => 1,
              )
          )
          ->condition('uid', $url_uid, '=')
          ->execute();
          watchdog('modulename', 'Activated %uid', array('%uid' => $url_uid), WATCHDOG_NOTICE, $link = NULL);
          
          // Find out if a user has already been assigned a role. If so, we'll skip the role-assignment step.
          $my_roles = '';
          $select_roles = db_query('SELECT uid, rid FROM {users_roles} WHERE uid = :uid', array(':uid' => $url_uid));
            foreach ($select_roles as $user_role) {
                $my_roles = $user_role->rid;
            } // end foreach
            
            // Check if the user does NOT have the role of 'School Member', and IS in a group.
          if ($my_roles != 6 && $my_groups != '' && $entity_type == 'user'):
              $user_promoted = db_insert('users_roles')
              ->fields(
                  array(
                      'rid' => 6,
                      'uid' => $url_uid,
                  )
              )
              ->execute();
            watchdog('modulename', 'Promoted %uid to School Member', array('%uid' => $url_uid), WATCHDOG_NOTICE, $link = NULL);
        endif; // end if $my_roles != 6
        
            drupal_goto('registration/thanks', $options, $http_response_headers = 302); 
        
        else:
            $page_content = 'Your User ID doesn't match. Sorry, but we're unable to activate your account.';
            return $page_content;
        endif; // end if ($url_created == $db_created)
    
    else:
        $page_content = 'We didn't find a User ID. Sorry, but we're unable to activate your account.';
        return $page_content;
    endif; // end if (isset($_GET['created']) && isset($_GET['u']))
    
}

 

Beyond Spots & Dots | Drupal