How to add your own extra validate or submit function using Drupal's hook_form_alter

The Drupal Form API provides sophisticated form techniques and also allows for almost unlimited possibilities for custom theming, validation, and execution of forms. Even better, ANY form (even those in core) can be altered in almost any way imaginable - elements can be removed, added, and rearranged.

The Drupal Form API provides sophisticated form techniques and also allows for almost unlimited possibilities for custom theming, validation, and execution of forms. Even better, ANY form (even those in core) can be altered in almost any way imaginable - elements can be removed, added, and rearranged.

This feature comes very handy each time there is a need to change default form content or behavior - especially in case of all core forms or those provided by other contrib modules, where we cannot really change them directly in code/module source code.

Manipulating such forms sometimes is as easy as implementing hook_form_alter() in our module and adding/updating/removing form elements or parameters. There are scenarios though which could seems a little bit more tricky, at least at the first sight.

For example let's imagine a situation, in which on our site new user accounts could be created only by administrators, and right after user logging in for a very first time we want to show them Terms & Conditions page and require them to agree to those T&Cs before being able to use the site.

In normal circumstances users create their accounts themselves, and redirecting them to T&Cs page after creating an account is pretty easy (there are even modules doing exactly this).

In our case though, because user accounts are being created only by administrators, it gets a bit more tricky.

Extra submit function - redirecting users to Terms & Conditions page after first login

By default, right after logging in, users are redirected to their profile page. This is something we need to change first.

Unfortunately setting $form_state['redirect'] (or $form['#redirect'] in D6, which was removed in D7) will not work in this case, as user.module sets redirect in its own user_login_submit() function. Therefore to be able to change it we need to do it after user.module sets it, which can be done in additional submit function, called after user.module's submit.

The simplest implementation would look similar to this:

/**
 * Implements hook_form_alter().
 */
function MYMODULE_form_alter(&$form, &$form_state, $form_id) {
  if ($form_id == 'user_login') {
    $form['#submit'][] = 'MYMODULE_user_login_submit';
  }
}

/**
 * Additional handler for user_login form submit.
 */
function MYMODULE_user_login_submit($form, &$form_state) {
  $form_state['redirect'] = 'terms-and-conditions';
}

To know what is value of $form_id, we can just use drupal_set_message($form_id); in our hook_form_alter() (or anything similar actually, any print/var_dump/dpr/kpr will work as good).

Of course, to fully solve our hypothetical problem, after this we would need a way to check whether user logs in for a very first time (which could be done based on $user->access value, which equals 0 if user has not logged in before (actually, $user->login behaves in the same way, however it can't be used in this case, as it gets updated in user_login_submit() function, so before our new submit is called), and then create the Terms & Conditions page with a simple form for accepting them.

In short, our new submit function would finally look like this:

/**
 * Additional handler for user_login form submit.
 */
function MYMODULE_user_login_submit($form, &$form_state) {
  $user = user_load($form_state['uid']);
  if ($user->access == 0) {
    $form_state['redirect'] = 'terms-and-conditions';
  }
}

Extra validate function - requiring more complicated password during user registration

Exactly the same procedure applied to form validation functions.

In the following example we are going to modify user_register_form form to force users to use a bit more complicated passwords:

  • containing at least 10 characters
  • containing at least one lower case letter, one upper case letter, one digit and one special character (where valid special characters are @#$%^&+=

Our snippet now would look as follows:

/**
 * Implements hook_form_alter().
 */
function MYMODULE_form_alter(&$form, &$form_state, $form_id) {
  if ($form_id == 'user_register_form') {
    $form['#validate'][] = 'MYMODULE_user_register_form_validate';
  }
}

/**
 * Additional handler for user_register_form form validation.
 */
function MYMODULE_user_register_form_validate($form, &$form_state) {
  if (!preg_match('/^.*(?=.{10,})(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[@#$%^&+=]).*$/', $form_state['values']['pass'])) {
    form_set_error('pass', t('Password must be at least 10 characters and contain at least one lower case letter, one upper case letter, one digit and one special character.'));
  }
}