Theming the register form in Drupal 6

The following exercise consists in adding Terms of Use to the register form in Drupal 6. Provided that your website visitors aren't logged in, they can view the register form on the page myWebSite.com/user/register.

This exercise will show you how to:

Storing the Terms of Use in a node

Context: You're having a bad fuel day, and you're developing a Drupal site for Randy.com. You were lucky enough to find ready-made Terms of Use, while googling +"Terms of Use", Terms of Use you'll shamelessly 'use' on Randy.com — yes, you will use these copyrighted Terms of Use, to protect Randy's website content.

The Terms of Use are terse just as they ought to be:

Terms of Use

By visiting or using Randy.com (also referred to as "Site", "WebSite"), created and operated by Randy Solutions (also referred to as "we", "us", "Randy.com"), you (also referred to as the "user", "visitor") agree to be legally bound by the following terms and conditions. If you do not agree to these terms, you should not use this Site.

We reserve the right, to update or revise these Terms of Use at any time without notice. Please check the Terms of Use periodically for changes. The revised terms will be effective immediately as soon as they are posted on the WebSite and by continuing to use the Site you agree to be bound by the revised terms.

Disclaimer

All materials, information, software, products and services, included in or available through this Site (also referred to as "content") are provided on an "as is" basis. The content is provided without warranties of any kind.

We do not warrant that the content on this WebSite is accurate, reliable, correct, entertaining or fun; that this Site will be available at any particular time or location; that any defects or errors will be corrected; or that the content is free of viruses or other harmful components. Your use of this Site is solely at your risk. Any material viewed, downloaded or otherwise obtained through the use of this Site is done solely at your own discretion and risk, and you will be solely responsible for any damage, including without limitation personal injury or distress, damage to your computer system, or loss of data, that results from the viewing or download of any such material.

Copyright

Any use, including the reproduction, modification, distribution, transmission, republication, display or performance, whether or not for profit or commercial gain, of any part of the content on the WebSite or any part of the website design, without prior written permission or authorization is strictly prohibited.

You want to show these Terms of Use in the register form, and you want to add a required checkbox below these Terms with something like this written next to it: I agree with these Terms.

Randy agrees to use these Terms of Use, but not "as is". He would like to insert, here and there, words to elicit a WTF-reaction. But he's not a programmer. So you've created a node to store the Terms, and given the node alias to Randy (that is, terms-of-use), so that Randy may change the Terms whenever he wants to whatever he wants (click on the edit tab, Randy).

The node is not promoted to the first page.

You've taken mental note of the node hi-dee.

The node id was written down on a post-it.

You'll need that number later. Randy only needs to know the path alias. You remind Randy that he probably should not promote the 'page'. (Leave the publishing options alone, Randy.)

What theming function to override...?

So, now, you want to theme the register form. That's your goal. The first question you ask yourself is What theming function must I intercept and override?

To find answers to these types of questions, 11heavens.com recommends using the module Theme Developer.

Download the Devel set of modules from Drupal.org, extract it to sites/all/modules, and enable the Devel module on your admin/build/modules page.

The devel module must be enabled.

Make sure to move the Development block to some active region on your page.

The block must be placed in some active region on the page, such as the Right sidebar.

You would like to use the Themer info widget on the register page, but, alas, when you try and access the register page you get an Access denied message: You are not authorized to access this page.. You're the admin, what the hell is that? The explaination: you can view the register form — on the page myWebSite.com/user/register — only if you aren't logged in. However, when you log out, you can no longer use the Themer info widget. What to do?

If you're working locally, and not on some site that sits on a server somewhere, you can give access to the Themer info widget to anonymous users. Head over to the admin/user/permissions page and select the appropriate radio button. Then, log out.

The block will be seen even by anonymous users.

You might also want to change the Devel module 'permissions' to make the Switch user block available to all logged-in users, that is, 'authenticated users'. When you're working on your local machine (in your bunker), and you're tesing who can see and do what, it's efficient to be able to switch from being logged-in as yourself, to being logged-in as some Dude with role X, to being logged-in as Dudesse with role Y, to being logged-in as yourself again, etc. and in a snap. Having to log out and login is tedious.

The Switch User block.

The Switch User permission.

End of sidenote. Back to business.

Once you've finally landed on the register page as an anonymous user, click on the 'Enable Theme developer' link in your Development block.

The devel block should be visible to you now.

Now, enable the Themer info widget. The Themer info toggle checkbox sits on the bottom-left of your viewport. (Thought I should throw the fancy word "viewport" in there.)

The themer info toggle sits on the bottom-left of your viewport.

You want to click on the form, but you end up selecting one field or another within the form. Argh. Solution: click somewhere to the right of the form button(s). So, you click somewhere on the right of the Create a new account button.

Select the user register form by clicking to the left of its buttons.

There's no specific theming function that's been registered to theme the register form. How do you know that? Answer: the function theme_form() is used to theme the form. How do you know that theme_form() is used to theme the register form? The Themer info widget tells you that.

The all-purpose theme_form function is used to theme the register form.

You're not interested in overriding the theme_form() function. You want to theme the register form. You do not want to theme all forms. You want to theme only that form. What will you do ?

First, eat something.

You will register a theming function for the register form.

Modules and themes can both register theming functions. Modules and themes can both implement the function hook_theme.

Registering a theming function

To register a theming function for a form, you must know its Form ID.

There are at least two ways to find the Form ID.

You take mental note of the Form ID. (You and I both have amazing storage capacities in our brains.)

The Form ID of the register form.

Just for fun (and to learn a few things), you take a peek inside the user module to make sure that it provides no theming function for the register form. You're looking for the module's implementation of hook_theme(). In the modules/user folder, you search for the string function user_theme.

Search results guide you to the file user.module.

<?php
/**
* Implementation of hook_theme().
*/
function user_theme() {
  return array(
   
'user_picture' => array(
     
'arguments' => array('account' => NULL),
     
'template' => 'user-picture',
    ),
   
'user_profile' => array(
     
'arguments' => array('account' => NULL),
     
'template' => 'user-profile',
     
'file' => 'user.pages.inc',
    ),
   
'user_profile_category' => array(
     
'arguments' => array('element' => NULL),
     
'template' => 'user-profile-category',
     
'file' => 'user.pages.inc',
    ),
   
'user_profile_item' => array(
     
'arguments' => array('element' => NULL),
     
'template' => 'user-profile-item',
     
'file' => 'user.pages.inc',
    ),
   
'user_list' => array(
     
'arguments' => array('users' => NULL, 'title' => NULL),
    ),
   
'user_admin_perm' => array(
     
'arguments' => array('form' => NULL),
     
'file' => 'user.admin.inc',
    ),
   
'user_admin_new_role' => array(
     
'arguments' => array('form' => NULL),
     
'file' => 'user.admin.inc',
    ),
   
'user_admin_account' => array(
     
'arguments' => array('form' => NULL),
     
'file' => 'user.admin.inc',
    ),
   
'user_filter_form' => array(
     
'arguments' => array('form' => NULL),
     
'file' => 'user.admin.inc',
    ),
   
'user_filters' => array(
     
'arguments' => array('form' => NULL),
     
'file' => 'user.admin.inc',
    ),
   
'user_signature' => array(
     
'arguments' => array('signature' => NULL),
    ),
  );
}
?>

The module is registering quite a lot of theming functions, but nah, none for 'user_register'.

You make these fine observations:

So that you understand, in the hook_theme() function, the key to each sub-array is the internal name of the hook. And we mean hook in "themespeak", not "modulespeak". For a form, the name of the hook is the Form ID. Each sub-array contains info about said hook, and that info is an associative array. Among the info, there's only one required item, and that is the 'arguments'. Plural form.

So, now, you'll register a theming function for the user_register form. The user module hasn't done so. So, you will.

Registering a theming function for a form

You open your theme's template.php file. Randy.com uses the Garland theme. (For the money he's paying you, he better not fuss.) Here's the code to register a theming function for the 'user_register' form:

<?php
/* Register some theme functions for forms, theme functions
* that have not been registered by the module that created
* them...
*/
function garland_theme(){
  return array(
   
'user_register' => array(
     
'arguments' => array('form' => NULL),
     
// and if I use a template file, ie: user-register.tpl.php
     
'template' => 'user-register',
    ),
  );
}
?>

You take good note of the following:

Providing a template file and its preprocess function

When we want to override a template provided by a module, the process is the same. Always. We locate the template file and copy it to our theme folder. When we want to pass additional variables to the template, we define a preprocess function for the template in our theme. The second step is optional. It's usually not needed.

In our case, the module has provided no default template. Heck, it has not even provided a theming function! We had to register one! So we must go through these two steps:

By the way, are you still using global $user; in your template files? Just use $user, it's a default baseline variable available in all *.tpl.php files. It's part of your free meal.

For each template file used to theme content in Drupal 6, there must exist a function to pass to it whatever variables are needed to output content. For each template file used to theme content in Drupal 6, there exists somewhere a preprocess function.

Let's move on.

Create a file called user-register.tpl.php in your theme folder. In it, place this markup:

<h2>Hello world</h2>
<!-- Let's print the form's XHTML -->
<?php print $form_markup; ?>

Then, create the skeleton for the template preprocess function in your template.php file like so:

<?php
function garland_preprocess_user_register(&$vars) {
 
// That's where $form_markup is created
 
$vars['form_markup'] = drupal_render($vars['form']);
}
?>

The function garland_preprocess_user_register(), as defined above, creates a new variable and passes it to the template file. That new variable is $form_markup, and it contains the markup for the form 'user_register'.

You know this already about $vars:

You know this already about drupal_render():

Clear your Drupal cache. That will clear the Theme registry.

Using the devel block to clear the cache.

Does Drupal use my template?

After you've refreshed your page in your browser, you should see a 'Hello world' greeting between the title of the page and the register form.

A test to see if our new template file is used.

Satisfied and relieved, you remove the Hello World heading from your template file.

A little planning

You're thinking about how it is you want to include your Terms of Use. You want to create a new fieldset with title "Terms of Use". In that fieldset you want to print the text that's in node... what's the number? 402. Below the text you want to put some checkbox with text "I agree with these terms" next to it. It must be required for the "user" (also referred to as the "visitor") to check that box, otherwise the register form won't be submitted.

Checking out the form

To troubleshoot any problems we may encounter while editing the form, we need to inspect it. The devel module provides a kit of 5 functions to inspect the content of variables:

Function To inspect content of Content shown Using a recursive function
dsm() Any variable In a message Yes, perfect for all.
dvm() A complex variable, such as an object or array In a message No. Ugly print.
dpr() A complex variable, such as an object or array At the top of the page. Yes. Perfect for complex, ie: nested, objects and arrays.
dpm() A complex variable, such as an object or array In a message. Yes. Perfect for complex, ie: nested, objects and arrays.
dvr() Any variable At the top of a page. No. Ugly print.

To inspect complex nested arrays such as forms, you can use either dpr() or dpm()/dsm(). These functions pretty-print forms either at the top of the page, or in the 'message' area. dpm() and dsm() have the same behavior.

Creating a fieldset

You want to create a fieldset for the Terms of Use, titled 'Terms of Use'.

<?php
function garland_preprocess_user_register(&$vars) {
 
// Adding the fieldset
 
$vars['form']['terms_of_use'] = array(
   
'#type' => 'fieldset',
   
'#title' => t('Terms of Use'),
   
'#weight' => 10,
  );
 
// passing the form to the template
 
$vars['form_markup'] = drupal_render($vars['form']);
 
// dsm($vars['form']);
}
?>

Without the '#weight' attribute, the fieldset rises to the top. You want it to sink to the bottom of the form.

Adding the content of a node to a fieldset

To place text or HTML in a form is not exactly adding a field to it. But still, markup that just sits there is a field in its own right and it is the default type for a field in the Drupal form API. Hence, we need not specify a '#type' of 'markup' for it.

<?php
function garland_preprocess_user_register(&$vars) {
 
// Adding the fieldset
 
$vars['form']['terms_of_use'] = array(
   
'#type' => 'fieldset',
   
'#title' => t('Terms of Use'),
   
'#weight' => 10,
  );
 
// Getting the node that contains the Terms of Use
 
$node = node_prepare(node_load(402));
 
// Adding the Terms of Use to the fieldset
 
$vars['form']['terms_of_use']['terms_of_use_text'] = array(
   
'#prefix' => '<div class="content clear-block">',
   
'#value' => $node->body,
   
'#suffix' => '</div>',
  );
 
// passing the form to the template
 
$vars['form_markup'] = drupal_render($vars['form']);
 
// dsm($vars['form']);
}
?>

We use node_prepare() to apply the filters that would normally be applied to the node's content if it were shown at Randy.com/terms-of-use. Likewise, using '#prefix' and '#suffix', we wrap the Terms of Use in a <div> with the same class names used for any node when shown on any other page.

Adding a checkbox to a fieldset

<?php
function garland_preprocess_user_register(&$vars) {
 
// Adding the fieldset
 
$vars['form']['terms_of_use'] = array(
   
'#type' => 'fieldset',
   
'#title' => t('Terms of Use'),
   
'#weight' => 10,
  );
 
// Getting the node that contains the Terms of Use
 
$node = node_load(402);
 
$node = node_prepare($node);
 
// Adding the Terms of Use to the fieldset
 
$vars['form']['terms_of_use']['terms_of_use_text'] = array(
   
'#prefix' => '<div class="content clear-block">',
   
'#value' => $node->body,
   
'#suffix' => '</div>',
  );
 
// Adding the Terms of Use to the fieldset
 
$vars['form']['terms_of_use']['I_agree'] = array(
   
'#type' => 'checkbox',
   
'#title' => t('I agree with these terms.'),
   
'#required' => TRUE,
  );
 
// passing the form to the template
 
$vars['form_markup'] = drupal_render($vars['form']);
 
// dsm($vars['form']);
}
?>

The checkbox will appear below the Terms following the order in which the form fields have been added to the fieldset.

The final register form.

Two unresolved issues

You ask yourself: Why in the hell did I create a template file when all it does is print a variable ? It doesn't even contain HTML markup! All it contains is this:

<?php print $form_markup; ?>

So, you've changed your mind about this. You delete the template file user-register.tpl.php. (You heard that using a template is 5x slower than using a theming function.)

Then, you open your template.php file. You remove from the garland_theme() function any mention of the template file. The function becomes:

<?php
function garland_theme(){
  return array(
   
'user_register' => array(
     
'arguments' => array('form' => NULL),
     
// 'template' => 'user-register',
   
),
  );
}
?>

Then, you go ahead and comment out the whole preprocess function. Preprocess functions are for template files only.

Next, you write your theming function. This consists in moving the code from the commented-out preprocess function to a new function you call garland_user_register ($form). In that new function, you replace all mention of $vars['form'] with $form. What the theming function must return is themed content. Hence, you delete the line of code which consisted in adding a variable to $vars, and place the following 'return' instruction in its place:

<?php
// returning the themed form
return drupal_render($form);
?>

Here's the code for the entire function:

<?php
function garland_user_register($form) {
 
// Adding the fieldset
 
$form['terms_of_use'] = array(
   
'#type' => 'fieldset',
   
'#title' => t('Terms of Use'),
   
'#weight' => 10,
  );
 
// Getting the node that contains the Terms of Use
 
$node = node_load(402);
 
$node = node_prepare($node);
 
// Adding the Terms of Use to the fieldset
 
$form['terms_of_use']['terms_of_use_text'] = array(
   
'#prefix' => '<div class="content clear-block">',
   
'#value' => $node->body,
   
'#suffix' => '</div>',
  );
 
// Adding the Terms of Use to the fieldset
 
$form['terms_of_use']['I_agree'] = array(
   
'#type' => 'checkbox',
   
'#title' => t('I agree with these terms.'),
   
'#required' => TRUE,
  );
 
// dsm($form);   
  // returning the themed form
 
return drupal_render($form); 
}
?>

Don't forget to clear the cache.

Randy is happy. He sends you two-thirds of the money he promised you, along with this photo of him. He says he's on the left.

Picture of Randy

But I mentioned there were two problems. The second problem is that you cannot make that damn checkbox a requirement. It just doesn't work. I will leave this outstanding bug for you to fix. What's wrong here? Read the comments for the solution.

[]

This tutorial was helpful to you? Post a comment and/or donate. Thank you.