10 min read

The User Warn module

In this article we will be creating the User Warn module. This module allows administrators to send users a warning via e-mail when that user violates a site’s terms of service or otherwise behaves in a way that is inappropriate. The User Warn module will implement the following features:

  • The module will expose configuration settings to site administrators, including default mail text
  • This e-mail will include Drupal tokens, which allow the admin to replace and/or add site-specific variables to the e-mail
  • Site administrators will be able to send a user mail via a new tab on their user profile page
  • Warning e-mails will be sent using Drupal’s default mail implementation

Starting our module

We will begin by creating a new folder for our module called user_warn in the sites/default/modules directory in our Drupal installation. We can then create a user_warn.info file as shown in the following:

;$Id$ name = User Warn description = Exposes an admin interface to send behavior warning e-mails to users. core = 7.x package = Drupal 7 Development files[] = user_warn.module

You should be pretty familiar with this now. We will also create our user_warn.module file and add an implementation of hook_help() to let site administrators know what our module does.


The example is as follows:

/** * Implement hook_menu(). */ function user_warn_menu() { $items = array(); $items[‘admin/config/people/user_warn’] = array( ‘title’ => ‘User Warn’, ‘description’ => ‘Configuration for the User Warn module.’, ‘page callback’ => ‘drupal_get_form’, ‘page arguments’ => array(‘user_warn_form’), ‘access arguments’ => array(‘administer users’), ‘type’ => MENU_NORMAL_ITEM, ); $items[‘user/%/warn’] = array( ‘title’ => ‘Warn’, ‘description’ => ‘Send e-mail to a user about improper site behavior.’, ‘page callback’ => ‘drupal_get_form’, ‘page arguments’ => array(‘user_warn_confirm_form’, 1), ‘access arguments’ => array(‘administer users’), ‘type’ => MENU_LOCAL_TASK, ); return $items; }

Like many Drupal hook implementations, hook_menu() returns a structured associative array with information about the menu items being defined. The first item in our example defines the module configuration page, and the second one defines the user tab where administrators can go to send the actual e-mail. Let’s look at the first item in more detail.

Menu items are keyed off their path. This is an internal Drupal path with no leading or trailing slashes. This path not only defines the location of a page, but also its place in the menu hierarchy, with each part of the URL being a child of the last. In this example, people is a child of config which is itself a child of admin.

If a requested path does not exist, Drupal will work its way up the hierarchy until it encounters a page that does exist. You can see this in action by requesting admin/config/people/xyzzy which displays the page at admin/config/people.

If you are creating a menu item for site administration it must begin with admin. This places it into Drupal’s administrative interface and applies the admin theme defined by the site settings.

Module-specific settings should always be present under admin/config. Drupal 7 offers several categories which module developers should use to better organize their settings according to Drupal functional groups like People and Permissions or Content Authoring.

The value associated with this key is itself an associative array with several keys that define what action should be taken when this URL is requested. We can now look at those in detail. The first item defines your page title:

‘title’ => ‘User Warn’,

This is used in a variety of display contexts—as your page’s heading, in the HTML <title> tag and as a subheading in the administration interface in combination with the description (if you are defining an administrative menu item).

‘description’ => ‘Configuration for the User Warn module.’,

The description is just that—a longer text description of the page that this menu item defines. This should provide the user with more detailed information about the actions they can take on this page. This description is also used as the title tag when you hover over a link.

Menu item titles and descriptions are passed through t() internally by Drupal, so this is one case where we don’t need to worry about doing that ourselves.

For an administration page, these two items define how your page is listed in Drupal’s admin area as shown in the following:

The next two items define what will happen when your page is requested:

‘page callback’ => ‘drupal_get_form’, ‘page arguments’ => array(‘user_warn_form’),

‘page callback’ defines the function that will get called (without the parentheses) and ‘page arguments’ contains an array of arguments that get passed to this function.

Often you will create a custom function that processes, formats, and returns specific data. However, in our case we are calling the internal Drupal function drupal_get_form() that returns an array as defined by Drupal’s Form API. As an argument we are passing the form ID of the form we want to display.

The fifth item controls who can access your page.

‘access arguments’ => array(‘administer users’),

‘access arguments’ takes an array containing a permissions strings. Any user who has been assigned one of these permissions will be able to access this page. Anyone else will be given an Access denied page. Permissions are defined by modules using hook_permission(). You can see a full list of the currently defined permissions at admin/people/permissions as shown:

You can see the ‘administer users’ permission at the bottom of this list. In the preceding example, only the Administrator role has this permission, and as a result only those users assigned this role will be able to access our page.

Note that the titles of the permissions here do not necessarily match what you will need to enter in the access arguments array. Unfortunately, the only good way to find this information is by checking the hook_perm() implementation of the module in question.

The final item defines what type of menu item we are creating:


The ‘type’ is a bitmask of flags that describe what features we want our menu item to have (for instance, whether it is visible in the breadcrumb trail). Drupal defines over 20 constants for menu items that should cover any situation developers will find themselves in. The default type is MENU_NORMAL_ITEM, which indicates that this item will be visible in the menu tree as well as the breadcrumb trail.

This is all the information that is needed to register our path. Now when Drupal receives a request for this URL, it will return the results of drupal_get_form(user_warn_form).

Drupal caches the entire menu, so new/updated menu items will not be reflected immediately. To manually clear the cache, visit Admin | Configuration | Development | Performance and click on Clear all caches.

Using wildcards in menu paths

We have created a simple menu item, but sometimes simple won’t do the job. In the User Warn module we want to have a menu item that is tied to each individual user’s profile page. Profile pages in Drupal live at the path user/<user_id>, so how do we create a distinct menu item for each user? Fortunately the menu system allows us to use wildcards when we define our menu paths.

If you look at the second menu item defined in the preceding example, you will see that its definition differs a bit from our first example.

$items[‘user/%/warn’] = array( ‘title’ => ‘Warn’, ‘description’ => ‘Send e-mail to a user about improper site behavior.’, ‘page callback’ => ‘drupal_get_form’, ‘page arguments’ => array(‘user_warn_confirm_form’, 1), ‘access arguments’ => array(‘administer users’), ‘type’ => MENU_LOCAL_TASK, );

The first difference is that the path is defined with % as one of the path entries. This indicates a wildcard; anything can be entered here and the menu item’s hierarchy will be maintained. In Drupal, that will always be a user’s ID. However, there is nothing stopping any user from entering a URL like user/xyzzy/warn or something else potentially more malicious. Your code should always be written in such a way as to handle these eventualities, for instance by verifying that the argument actually maps to a Drupal user. This would be a good improvement.

The other difference in this example is that we have added 1 as an additional argument to be passed to our page callback.

Each argument in a menu item’s path can be accessed as an argument that is available to be passed to our page callback, starting with 0 for the root argument. So here the string user is item 0, and the user’s ID is item 1. To use the user’s ID as a page callback argument, we reference it by its number. The result in this case is that the user’s ID will be passed as an additional argument to drupal_get_form().

We have one other difference in this second menu item:

‘type’ => MENU_LOCAL_TASK,

We have defined our type as MENU_LOCAL_TASK. This tells Drupal that our menu item describes actions that can be performed on the parent item. In this example, Warn is an action that can be performed on a user. These are usually rendered as an additional tab on the page in question, as you can see in the following example user profile screen:

Having defined the paths for our pages through hook_menu(), we now need to build our forms.

Form API

In standard web development, one of the most tedious and unrewarding tasks is defining HTML forms and handling their submissions. Lay out the form, create labels, write the submission function, figure out error handling, and the worst part is that from site to site much of this code is boilerplate—it’s fundamentally the same, differing only in presentation. Drupal’s Form API is a powerful tool allowing developers to create forms and handle form submissions quickly and easily. This is done by defining arrays of form elements and creating validation and submit callbacks for the form.

In past versions of Drupal, Form API was commonly referred to as FAPI. However, Drupal 7 now has three APIs which could fit this acronym—Form API, Field API and File API. We will avoid using the acronym FAPI completely, to prevent confusion, but you will still encounter it widely in online references.

Form API is also a crucial element in Drupal’s security. It provides unique form tokens that are verified on form submission, preventing Cross-site Request Forgery attacks, and automatically validating required fields, field lengths, and a variety of other form element properties.

Using drupal_get_form()

In our first menu implementation seen earlier, we defined the page callback as drupal_get_form(). This Form API function returns a structured array that represents an HTML form. This gets rendered by Drupal and presented as an HTML form for the user. drupal_get_form() takes a form ID as a parameter. This form ID can be whatever you want, but it must be unique within Drupal. Typically it will be <module_name>_<description>_form.

The form ID is also the name of the callback function drupal_get_form() will call to build your form. The specified function should return a properly formatted array containing all the elements your form needs.

Since the form ID also serves as the form’s callback function, it must be a valid PHP variable name. Spaces and hyphens are not allowed. All form IDs should be prefaced by the name of your module followed by an underscore, in order to prevent name collision.

Other parameters can be passed into drupal_get_form() in addition to the form ID. These extra parameters simply get passed through to the callback function for its own use.

In Drupal 6, drupal_get_form() returned a fully rendered HTML form. This has been changed in Drupal 7 in order to allow more flexibility in theming and easier form manipulation. drupal_get_form() now returns an unrendered form array which must be passed to drupal_render() for final output. In the preceding example the menu system handles the change transparently, but other code converted from Drupal 6 may need to be changed.

Subscribe to the weekly Packt Hub newsletter. We'll send you this year's Skill Up Developer Skills Report.

* indicates required


Please enter your comment!
Please enter your name here