In this article you learn how to create a very simple, custom module to provide announcements on a Web site. The information should not be interpreted as a rigid set of development guidelines, but rather, as a place to start when building your own custom modules. In many cases we provide a quick introduction to topics and refer to the Drupal documentation to provide in-depth details.
Our fictitious company, International Business Council (IBC), needs to display relevant announcements that are automatically published and then removed from the Web site after specified times. The announcements require a few new data fields:
- An abstract that is different from the default teaser and is controlled by the author of the announcement (and lets us illustrate several Drupal features)
- A publication date, when the announcement will first appear on the Web site
- An expiration date, when the announcement will be removed from the Web site
- The ability for the administration staff to see the announcements even if the announcement is not within the publication and expiration dates
The home page displays the current announcements in the main content area, as shown in Figure 1. A sidebar is used on every page to include the most recent announcements. Announcements that the current user created are highlighted with a colored background.
Figure 1. IBC home page

Before starting, we create the file that will contain all of our code that defines the new module. Then, we will modify the database to support the information relevant to our new module.
To get started, we create the modules/announcement directory under the ibc_site. We create an announcement.module file in that directory, as shown in our Eclipse environment in Figure 2. Typically, the name of a module is the same as the new node type it creates. This file will contain all of the specific announcement code we describe in this article.
Figure 2. Navigator view in Eclipse

All data represented by a node is stored in the node table and, in version 4.7, the node_revisions table. As a node is modified, revisions are stored in the node_revisions table, including the title, teaser, and body. Because the existing Drupal tables did not support the requirements of our new module, we created an announcement table to store the announcement-specific information. To have more control over the teaser, we included our own abstract in this new table. We first create the table and will later write the functions to access that table on specific events.
Listing 1 shows the command that creates an announcement table that is linked to the node table through the node ID, nid. The new columns in the table are the abstract, the publish_date, and the expiration_date. We create the new table in the database at the SQL command line or through the MySQL query browser. We saved this database command in the announcement.sql file in the announcement directory (Figure 2). This step documents the database tables for this module and also allows for easy updating of the database if needed.
Listing 1. Command to create new announcement table
CREATE TABLE announcement ( nid int(10) unsigned NOT NULL default '0', abstract varchar(255) default '', publish_date integer NOT NULL default '0', expiration_date integer NOT NULL default '0', PRIMARY KEY (nid) ); |
Drupal's interface for modules is through a set of functions called hooks. In this section we'll show the development of several hooks to support our custom module. They represent a starting set of functions to get our module up and running; we implemented only some of the Drupal hooks.
- hook_settings
- Creates attributes for this module that the administrator can modify.
- hook_help
- Provides documentation that appears in various places in the interface.
- hook_perm
- Defines the permission categories for information access.
- hook_access
- Defines the access permissions for different operations and users.
- hook_menu
- Sets up the URL paths and functions to call when handling access.
- hook_link
- Defines the links that can be added to presentations throughout the site.
- hook_block
- Defines a block of information from this module to be displayed.
- hook_form
- Defines the interface widgets to use when adding and editing this node.
- hook_validate
- Validates the input from a user before storing to the database.
- hook_submit
- Modifies the node, after validation, but before updating the database.
- hook_load
- Loads additional node information from the database.
- hook_insert
- Saves additional node information the first time.
- hook_update
- Saves additional node information if the node already exists.
- hook_delete
- Deletes additional node information when the node is deleted.
- hook_cron
- Performs scheduled actions as defined by the administrator.
- hook_search
- Defines custom search on the information of this node.
- hook_nodeapi
- Acts on nodes defined by other modules.
- hook_node_info
- Determines the name and attributes of the module's node types.
The settings hook provides a way to add attributes to the module that can be controlled by the administrator and can be used when displaying this module. For our announcement module we want to limit the number of announcements that are presented in the sidebar. The block hook will generate the presented announcement elements. However, we can give the administrator the ability to set the number of announcements that are shown in the sidebar through the administrator's user interface. These module-specific attributes are accessible in the interface by the following relative URL, as shown in below:
admin/settings/<module_name>
Listing 2 shows the announcement_settings hook implementation.
Listing 2. Announcement_settings hook implementation
function announcement_settings() {
$form = array();
$form['announcement_block_max_list_count'] = array(
'#type' => 'textfield',
'#title' => t('Maximum number of block announcements'),
'#default_value' => variable_get('announcement_block_max_list_count', 3),
'#description' => t('The maximum number of items listed in the announcement block'),
'#required' => FALSE,
'#weight' => 0
);
return $form;
}
|
The announcement_settings hook, as shown in Listing 2, defines an interface element that is used by the administrator when specifying the value for this attribute. The index to the form array is the variable name; for example announcement_block_max_list_count. The components of the array stored at this index define how the administrator's user interface will be constructed.
This value can then be accessed when building the sidebar of announcements to display. Using the code fragment in Listing 3, we retrieve the persistent variable defined in the settings hook when building the sidebar.
Listing 3. Retrieving the variable set in the settings hook
$items = variable_get('announcement_block_max_list_count', 3);
|
The help hook provides a place to put documentation that will be displayed when an administrator or user interacts with the system. Two places this occurs is when the administrator can enable or disable a module in the admin/modules page. Figure 3 shows the line in the administrators screen to enable this module, with the announcement module highlighted.
Figure 3. Help documentation on administrator's page

Similarly, when a user adds a new announcement using the create content link, they will see a description of the node they are adding, as shown in Figure 4.
Figure 4. Help description of the node type that can be added

Listing 4 shows the implementation of the help hook for the announcement module that generates these two descriptions. Other help descriptions can also be produced.
Listing 4. Announcement_help hook implementation
function announcement_help($section) {
switch ($section) {
case 'admin/modules#description':
return t('Enables the creation of announcement pages ' .
'that are presented on the home page.');
case 'node/add#announcement':
return t('An Announcement. Use this page to add an announcement page.');
}
}
|
The perm hook defines the access permissions that each role can be assigned. Arbitrary strings can be used to describe the actions relevant to your application. Here, we differentiate between creating an announcement and editing (or deleting) an announcement, as seen in Listing 5. The hook_access function can then use these permissions to control access to content.
Listing 5. Announcement_perm hook implementation
function announcement_perm() {
return array('create announcement', 'edit announcement');
}
|
The default Drupal roles are anonymous user and authenticated user. The administrator can create additional roles from within the interface from the path admin/access/control. These roles, combined with the permissions defined above, form the basis of the permissions console as shown in Figure 5. This matrix of check boxes allows the administrator to enable permissions to each of the distinct roles within the system. The announcement module has been highlighted here to show the two strings defined in the hook_perm in Listing 5. Only a user with the roles of administrator or operations can create and edit announcements.
Figure 5. Interface allowing administrator to assign permissions

Each module can restrict access to the data that it represents. The access hook allows the module author to control that access. In practice, call the user_access function to check if the current user has the given access permissions. In Listing 6, in the user_access function we refer to the permissions defined in the
announcement_perm function.
The access hook function is supplied the operation (for example, create, view, update or delete) and the node that is the focus of the operation. The function returns a Boolean value indicating whether or not the current user should have access to the operation on the specified node.
In this implementation, only users that have been assigned the "create announcement" permission can create an announcement. Users that have the "access content" permission (any authenticated user) can view an announcement. The update and delete operations can be executed if the user initiating the request is the owner of the content or if they have been explicitly granted permission to edit an announcement.
The first registered user created as part of the Drupal installation, the user with ID = 1, has root privileges and can edit and modify any piece of data in the system.
Listing 6. Announcement_access hook implementation
function announcement_access($op, $node) {
global $user;
if ($op == 'create') {
return user_access('create announcement');
}
else if ($op == 'view') {
return user_access('access content');
}
else if ($op == 'update' || $op == 'delete') {
if($user->uid == $node->uid || user_access('edit announcement')) {
return true;
}
else {
return false;
}
}
else {
return false;
}
} |
How Drupal responds to URLs is defined in the menu hook function. This function defines callbacks for specific URLs and menu items. The standard way Drupal constructs URLs when referring to a specific node is to put the node ID before the operation. Using this format, the URLs that need to be specified for our module include:
/announcements /announcements/add /announcements/<id>/view /announcements/<id>/edit /announcements/<id>/delete |
where <id> is the ID of the node being viewed, edited, or deleted. The definition of our announcement_menu function is shown in Listing 7.
Listing 7. Announcement_menu hook implementation
function announcement_menu($may_cache) {
$items = array();
if ($may_cache) {
$items[] = array('path' => 'announcements/add',
'title' => t('Add a new Announcement'),
'access' => node_access('create', 'announcement'),
'type' => MENU_CALLBACK,
'callback arguments' => array('announcement'),
'callback' => 'node_add');
$items[] = array('path' => 'announcements',
'title' => t('Announcements'),
'access' => user_access('access content'),
'type' => MENU_CALLBACK,
'callback' => 'announcement_all');
}
else {
if(is_numeric(arg(1))) {
$node = node_load(arg(1));
$items[] = array('path' => 'announcements/' . arg(1),
'title' => t('View an Announcement'),
'access' => node_access('view', $node),
'type' => MENU_CALLBACK,
'callback' => 'node_page');
$items[] = array('path' => 'announcements/' . arg(1) . '/view',
'title' => t('View an Announcement'),
'access' => node_access('view', $node),
'type' => MENU_CALLBACK,
'callback' => 'node_page');
$items[] = array('path' => 'announcements/' . arg(1) . '/edit',
'title' => t('Edit an Announcement'),
'access' => node_access('edit', $node),
'type' => MENU_CALLBACK,
'callback' => 'node_page');
$items[] = array('path' => 'announcements/' . arg(1) . '/delete',
'access' => node_access('delete', $node),
'type' => MENU_CALLBACK,
'callback' => 'node_delete_confirm');
}
}
return $items;
}
|
The function returns an array of arrays, where each array includes:
- path
- The URL to match
- title
- Title of this menu item which is surfaced in a mouse hover operation
- callback
- Function to invoke when this URL is accessed
- type
- Type of menu item.
- access
- Access permissions to this menu item
Caching is used to make the production Web site more efficient. Here we can control portions of the menu definition to be cached or not using the $may_cache conditional. However, when things are cached, they may not reflect changes in your code during development. Cacheable items should not include variables, such as arg(1), in their path specification.
One of the basic principles we tried to follow is to graphically place actions close to the item they act upon. In the case of announcements, we placed action links for adding, editing, deleting, and commenting next to the title of the announcement. Of course, these actions should only be presented if the current user has the appropriate access privileges. The actions are themed in a small, red font, as shown in Figure 6.
Figure 6. Action links next to title of an announcement

The link hook provides us with the mechanism to do this. Listing 8 shows our implementation of this hook. It lets us create the appropriate links based on access privileges, and then to theme these links later with the appropriate markup. This allows the CSS to style the text in a red, small font. The comment module also contributes to this set of links by providing the "Add Comment" action.
In the sequence l(t('Edit')...) in Listing 8, we use two utility functions to support link generation. The t function first translates the 'Edit' string to the current locale, then the l function formats that string to the appropriate internal Drupal link. Links are themed in the template engine with a call to theme('links', $links) and then mapped to the template variable $links.
Listing 8. Announcement_link hook implementation
function announcement_link($type, $node = NULL, $teaser = FALSE) {
global $user;
$links = array();
if($type == 'node' && $node->type == 'announcement') {
if (node_access('create', 'announcement')) {
$links[] = l(t('Add'), "node/add/announcement",
array('title' => t('Add a new announcement')));
}
if (node_access('update', $node)) {
$links[] = l(t('Edit'), "announcements/$node->nid/edit",
array('title' => t('Edit Announcement ') . $node->title));
$links[] = l(t('Delete'), "announcements/$node->nid/delete",
array('title' => t('Delete Announcement ') . $node->title));
}
}
return $links;
} |
Announcements appear in the center of the home page and in the navigation sidebar on every page, as shown in Figure 7. The sidebar content is enabled by the block hook, which allows a module to contribute content that can be displayed anywhere on the page. Usually the sidebar presentations are a synopsis of the full content. Notice that the highlighting of announcements for the current user is consistent in the sidebar and in the main content area. Figure 7 shows the sidebar for an administrator, so you see several Resources that can be used while logged in. These do not appear for a typical user.
Figure 7. Announcements in the sidebar using the block hook

Once this hook is defined, the administrator can specify the placement of the block information through the user interface, as shown in Figure 8. The announcements are enabled and given a weight of -10 in the right sidebar. The smaller the number, the higher in the sidebar the content is presented. This ensures that announcements are the first block of information presented in the right sidebar.
Figure 8. Administrator's interface for specifying block placement

Listing 9 shows the implementation of our block hook for the announcement module. This function is capable of constructing more than one block. Drupal uses the $delta argument to index the block that is to be displayed as defined in the $block array. The first condition the function tests for is the list operation. That information is used in the administrator's interface shown in Figure 8.
The second operation is view, which collects the appropriate announcements to show in the information block we use in the right sidebar. It uses the announcement setting announcement_block_max_list_count defined in the announcement_settings hook to determine how many announcements to display. The view operation uses the announcement_block_list template file (announcement_block_list.tpl.php) to theme the announcements. (The theming of the announcements will be described in a future article.)
Listing 9. Announcement_block hook implementation
function announcement_block($op = 'list', $delta = 0, $edit = array()) {
global $user;
if ($op == 'list') {
$blocks[0]['info'] = t('Recently updated announcements');
return $blocks;
}
else if ($op == 'view') {
$block = array();
$output = '';
switch ($delta) {
case 0:
$now = time();
if (user_access('access content')) {
$q = 'SELECT N.uid,N.nid,N.title,A.publish_date,N.status '.
'FROM {node} N JOIN {announcement} A USING(nid) '.
"WHERE N.type='announcement' ".
'AND N.status = 1 '.
'AND A.publish_date < ' . $now . ' '.
'AND A.expiration_date > ' . $now . ' '.
'ORDER BY A.publish_date DESC ';
$items = variable_get('announcement_block_max_list_count', 3);
if ($items) { $q .= "LIMIT 0,$items"; }
$announcements = db_query($q);
$announcement_items = array();
while (db_num_rows($announcements) > 0 and $announcement =
db_fetch_object($announcements)) {
$announcement_items[] = $announcement;
}
}
$block['subject'] = t('Announcements');
$block['content'] = theme('announcement_block_list',$announcement_items);
break;
}
return $block;
}
} |
The form hook is called to generate the user interface to add or edit the contents of a node. This hook returns an array of arrays for each piece of content that needs to be edited by the user. An announcement node is edited with the interface shown in Figure 9. We provide input forms for the title, the publish and expiration dates, the abstract, and the body. A short description is provided below each form element to indicate to the user what the information is and how it will be used.
Figure 9. Editing form for an announcement node

To generate the form element to edit the abstract of the announcement we create an array, as shown in Listing 10.
Listing 10. Produce a text area to edit the announcement abstract
$form['abstract'] = array(
'#type' => 'textarea',
'#title' => t('Abstract'),
'#default_value' => $node->abstract,
'#rows' => 3,
'#description' => t('Short summary of the full announcement'),
'#required' => TRUE,
'#weight' => 9
); |
The index of the array, abstract, is the name of the element. The value is accessed from the node data structure as $node->abstract. The details for this element include:
- type
- Type of widget is a textarea (which affects the other attributes available).
- title
- Title displayed transformed by the translation function "t."
- default_value
- Value used in the widget when initially displayed, for example current value.
- rows
- Rows to display for the textarea.
- description
- Text to display below the widget in the interface.
- required
- Whether this input field is required or not.
- weight
- Affects the ordering of the widgets: smaller numbers appear higher in the interface.
Publication is defined as a fieldset to group the publish and expiration dates. The full announcement_form function that produces the interface for editing an announcement node is shown in Listing 11. The first two if clauses set up reasonable defaults, if none are provided, for the publish and expiration dates. We use the #prefix and #suffix elements in the publication array to insert additional HTML tags to enable simple CSS theming of the date picker. The string index to the $form array is used to subsequently de-reference the specific value from the node.
Listing 11. Complete announcement_form
function announcement_form(&$node) {
if ($node->expiration_date == NULL) {
$node->expiration_date = time() + (365 * 86400);
}
if ($node->publish_date == NULL) {
$node->publish_date = time();
}
$form['title'] = array('#type' => 'textfield',
'#title' => t('Title'),
'#default_value' => $node->title,
'#description' => t('Title of the announcement'),
'#required' => TRUE,
'#weight' => 1
);
$form['publication'] = array('#type'=> 'fieldset',
'#collapsible' => FALSE,
'#title' => t('Publication dates'),
'#weight' => 5
);
$form['publication']['publish_date'] = array(
'#prefix' => '<div class="date_widget">',
'#suffix' => '</div>',
'#type' => 'date',
'#title' => t('Publication date'),
'#default_value' => _announcement_unixtime2drupaldate($node->publish_date)
);
$form['publication']['expiration_date'] = array(
'#prefix' => '<div class="date_widget">',
'#suffix' => '</div>',
'#type' => 'date',
'#title' => t('Expiration date'),
'#default_value' => _announcement_unixtime2drupaldate($node->expiration_date)
);
$form['abstract'] = array('#type' => 'textarea',
'#title' => t('Abstract'),
'#default_value' => $node->abstract,
'#rows' => 3,
'#description' => t('Short summary of the full announcement'),
'#required' => TRUE,
'#weight' => 9
);
$form['body'] = array('#type' => 'textarea',
'#title' => t('Body'),
'#default_value' => $node->body,
'#description' => t('Full content for the announcement which ' .
'is shown with the abstract on the details page'),
'#required' => TRUE,
'#weight' => 10
);
return $form;
} |
One of the big changes from Drupal 4.6 to 4.7 is the form hook implementation. The Drupal Web site has a lot of relevant documentation, including:
At the end of the editing process, the validate hook is triggered before storing the node. This can be used to validate the data before it is stored in the database. For announcements, we use this hook by ensuring that the start date comes before the end date. A more interactive implementation using client-side scripting would not allow this condition to occur by constraining the two fields to be consistent with each other. If you want to make additional changes to the node before storing it, use the submit hook.
Listing 12 shows the implementation of the validate hook. We first convert the dates to integers that we can compare. If there is a problem that the user needs to address, use the form_set_error function, which uses Drupal's error handling mechanism. The first argument to this function, publish_date in Listing 12, refers to the form array name used in the form hook (Listing 11) and will highlight that element in the interface.
Listing 12. Announcement_validate hook
function announcement_validate($node) {
if ($node) {
$publish_date =
_announcement_drupaldate2unixtime($node->publish_date);
$expiration_date =
_announcement_drupaldate2unixtime($node->expiration_date);
if ($publish_date >= $expiration_date) {
form_set_error('publish_date',
t('The publish date of an announcement must be before its expiration date.'));
}
}
} |
Once the node passes the validation phase, the submit hook is called where we can make additional changes to the node before the database is actually updated. In Listing 13, the submit hook updates the publish and expiration dates on the node and then modifies the node status based on the current date. If the current date is between the publish and expiration dates the status is set to 1 , else it is set to 0.
Listing 13. Announcement_submit hook
function announcement_submit(&$node) {
$node->publish_date =
_announcement_drupaldate2unixtime($node->publish_date);
$node->expiration_date =
_announcement_drupaldate2unixtime($node->expiration_date);
$now = time();
if ($now >= $node->publish_date &&
$now < $node->expiration_date) {
$node->status = 1;
}
else {
$node->status = 0;
}
}
|
Drupal triggers a hook function on various events that occur in the environment when interacting with the database. The important events include load, insert, update, and delete. See more information about these hook events.
Drupal has a database abstraction layer that is used throughout this code. These functions, which begin with db_, are used in the database hooks to access this abstraction layer.
The load hook is automatically called when a node of type announcement is loaded from the database. This function allows a custom module to load any additional content from the database to complete its definition. The return value from the function is an array of additional content to be merged into the node data structure. In our case, we need to load the three data items, the abstract, publish date, and expiration date from the new table. Listing 14 shows the code to load this additional information for our announcement node.
Listing 14. Announcement_load hook for loading a node of type announcement
function announcement_load(&$node) {
$additions = db_fetch_object(db_query('SELECT * FROM {announcement} ' .
'WHERE nid = %d', $node->nid));
return $additions;
} |
When an announcement node is created on the Web site, as shown in Listing 15, the insert hook is automatically invoked. This hook provides the opportunity for a new module to store any additional information into the database when a node is first created. For the announcement module, we create a new entry in the announcement table. The node object passed into the function contains all the data from the input forms. The publish and expiration dates are returned as an array with the month, day, and year. This is converted with the local function _announcement_drupaldate2unixtime. By convention, all local module functions are preceded by an underscore ("_") and the module name, such as _announcement. Then, we call the database abstract layer to insert the new row into the announcement table. The $node->nid is the primary key that links the announcement table to the node table.
Listing 15. Announcement_insert hook and support function for adding an announcement to the database
function _announcement_drupaldate2unixtime($drupal_date) {
$year = $drupal_date["year"];
$month = $drupal_date["month"];
$day = $drupal_date["day"];
return mktime(0,0,0, (int)$month, (int)$day, (int)$year);
}
function announcement_insert($node) {
$publish_date = _announcement_drupaldate2unixtime($node->publish_date);
$expiration_date = _announcement_drupaldate2unixtime($node->expiration_date);
db_query("INSERT INTO {announcement} (nid, abstract, publish_date, expiration_date) ".
"VALUES (%d, '%s', '%d', '%d')",
$node->nid, $node->abstract, $publish_date, $expiration_date);
} |
The update hook Listing 16 is called when the information already exists in the database and the user is editing it. This is similar to the insert hook, but the database command issued is UPDATE.
Listing 16. Announcement_update hook to modify an existing announcement node
function announcement_update($node) {
$publish_date = _announcement_drupaldate2unixtime($node->publish_date);
$expiration_date = _announcement_drupaldate2unixtime($node->expiration_date);
db_query("UPDATE {announcement} SET abstract='%s', publish_date = '%s', " .
"expiration_date = '%s' WHERE nid = %d",
$node->abstract, $publish_date, $expiration_date, $node->nid);
} |
Finally, the delete hook (shown in Listing 17) is called when the user deletes an announcement node. This gives the module an opportunity to delete any auxiliary content from other tables in the database. Here we delete the one row of the announcement table that is associated with the node based on the nid.
Listing 17. Announcement_delete hook to delete an announcement node
function announcement_delete($node) {
db_query('DELETE FROM {announcement} WHERE nid = %d', $node->nid);
} |
The cron hook allows modules to schedule tasks to be run at regular intervals. The intervals can be set by the site administrator through a cron job that does a HTTP GET on http://<sitename.com>/cron.php. This invokes the defined cron hook on all modules. The status of the cron job can be seen in the administrators interface under Administer > Settings (for example, /admin/settings) in the cron jobs section.
The announcement module relies on the publish and expiration dates to determine if an announcement should be visible. However, an announcement could still be displayed through the standard node mechanism (/node/id/view) if its node status is not set to 0. We use the cron hook to set the status flag on all announcement nodes that have expired. Listing 18 shows the announcement_cron function that first queries the database for those announcements past their expiration dates and then sets the node status to 0 for those nodes.
Listing 18. Announcement_cron hook implementation
function announcement_cron() {
$queryResult = db_query("UPDATE {node} AS n INNER JOIN {announcement} AS a " .
"ON n.nid = a.nid SET n.status = 0 WHERE n.type='announcement' " .
"AND n.status = 1 AND a.expiration_date < %d", time());
} |
The search hook allows a module to extend the search page with the ability to perform keyword searches on nodes the module creates. First the search module needs to be enabled through the administer > modules page. This lets us include the search block in the header using the administer > block page. By using the search hook, another tab is rendered in the search page when a simple search is presented. This search form is then used to look for keywords within nodes of the type created by your module.
The search module uses cron to build a table of indices of data found within the node, which allows Drupal to provide full-text searching against the node content.
For the announcements module, we want the search engine to pick up the new abstract field defined in the announcements table. We also want to change the action of the default search so that this abstract field is shown instead of the default content, and we don't want to use a separate tab for its presentation. Luckily, there is another alternative to the search hook for our needs.
To make sure the default search form can find keywords within the abstract field in the announcements table, we implemented the nodeapi hook function. This lets us include this field during the update of the indices. Listing 19 shows our implementation of the nodeapi hook.
Listing 19. Announcement_nodeapi hook implementation
function announcement_nodeapi(&$node, $op) {
switch ($op) {
case 'update index':
if ($node->type == 'announcement') {
$text = '';
$q = db_query('SELECT a.abstract FROM node n LEFT JOIN announcement a ' .
'ON n.nid = a.nid WHERE n.nid = %d', $node->nid);
if ($r = db_fetch_object($q)) {
$text = $r->abstract;
}
return $text;
}
}
} |
Within this function, we check for the update index operation indicating that Drupal is at the point of collecting extra data before indexing it in the database. If the node being indexed is of type announcement, the associated abstract field value is extracted from the announcement table and returned.
Now that Drupal can index this announcement abstract, we need to display this information, if it matches a keyword search, on the default search page results. We did this by overriding the theme_search_item function in the search module using the phptemplate_search_item function, as shown in Listing 20. Since this function can be considered a global theme change, we place it in the template.php file that sits in our theme directory.
Listing 20. phptemplate_search_item function
function phptemplate_search_item($item, $type) {
return _phptemplate_callback('search_item',
array('node' => $item), 'search_item-' . strtolower($item['type']));
} |
In this function we use the _phptemplate_callback function to associate the theming of the search item to a suggested template file. Rather like the way the phptemplate engine allows the use of node.tpl.php and node-<node-type>.tpl.php template files to customize the way a node is rendered, we are using this function to allow the use of search_item.tpl.php and search_item-<node-type>.tpl.php templates, as shown in Listing 21.
Now we can have a template for the default look of a search item, which is essentially a slightly modified version of the original themed search item found in the theme_search_item function in the search.module file. This useful technique could be applied to any themed entity.
Listing 21. search_item.tpl.php template
<dt class="title search_item">
<a href="<?php print check_url($node['link']); ?>"><?php print
check_plain($node['title']) ?></a>
</dt>
<?php
$info = array();
if ($node['type']) $info[] = strtolower($node['type']);
if ($node['user']) $info[] = $node['user'];
if ($node['date']) $info[] = format_date($node['date'],'small');
if (is_array($node['extra'])) $info = array_merge($info, $node['extra']);
?>
<dd class="search_item">
<p><?php print $node['snippet']; ?></p>
<p class="search-info"><?php print implode(' - ', $info); ?></p>
</dd> |
Using a search_item-announcement.tpl.php template we can theme the search item for an announcement type node and replace the default snippet with our own constructed abstract field. In Listing 22 notice we used the search_excerpt function to highlight the keywords in the abstract.
Listing 22. search_item-announcement.tpl.php template
<dt class="title search_item_announcement">
<a href="<?php print check_url($node['link']); ?>">
<?php print check_plain($node['title']) ?></a>
</dt>
<?php
$info = array();
if ($node['type']) $info[] = strtolower($node['type']);
if ($node['user']) $info[] = $node['user'];
if ($node['date']) $info[] = format_date($node['date'],'small');
if (is_array($node['extra'])) $info = array_merge($info, $node['extra']);
?>
<dd class="search_item_announcement">
<p><?php print ($node['node']->abstract ? '<p>'.
search_excerpt(search_get_keys(),$node['node']->abstract) .
'</p>' : $node['snippet']); ?></p>
<p class="search-info"><?php print implode(' - ', $info); ?></p>
</dd> |
Figure 10. Search results page with search terms highlighted in output

Listing 23 describes a function that allows node modules to define multiple custom node types, the node_info hook. We use this function to tell Drupal to define the announcement node type. There are two values Drupal needs to associate with the node type; the human readable name of the node type, used in the user interface; and the base, a prefix used for functions that are associated with this node type. If we wanted to add another node type, we could add another array item.
Listing 23. Announcement_node_info to determine name and attributes of module's node types
function announcement_node_info() {
return array('announcement' => array('name' => 'Announcement',
'base' => 'announcement'));
}
|
Module files provide several theme functions that are used to display the module's information in different contexts. For most purposes, we find it useful to think of these contexts in the following way:
- A detailed layout where all the information for a given node is presented
- A compact or summary representation where only the salient parts are presented to give an overview of the node
- A block that presents a smaller form of the node information that is typically embedded into the left or right sidebar
For the announcements module, we used a theme function for each of these contexts:
- An announcement page used the
theme_announcementfunction to create a detailed layout. - The front or home page of the site used the
theme_announcement_compactfunction to create a list of announcements. - The announcement block in the right sidebar on all pages used the
theme_announcement_block_listfunction.
As explained in Part 5: Getting started with Drupal, the theme functions can be used to create the default look of the announcement module output, regardless of the theme engine selected.
Since we use the phptemplate engine, we override these default theme functions, shown in Listing 24, using the phptemplate_announcement, phptemplate_announcement_compact, and phptemplate_announcenemt_block_list functions. We found it useful to separate the structure and style definitions from the logic of the module, and use the _phptemplate_callback function to associate a template file to each context.
Listing 24. Default theme functions and the PHP Template engine functions that override them
function theme_announcement($announcement) {
// Put your default theme for the announcement detail here
return '';
}
function theme_announcement_compact($announcement) {
// Put your default theme for the announcement summary here
return '';
}
function theme_announcement_block_list($announcement_list) {
// Put your default theme for the announcement block here
return '';
}
function phptemplate_announcement($announcement) {
return _theme_phptemplate_announcement($announcement, 'announcement');
}
function phptemplate_announcement_compact($announcement) {
return _theme_phptemplate_announcement($announcement, 'announcement_compact');
}
function phptemplate_announcement_block_list($announcement_list) {
global $user;
return _phptemplate_callback('announcement_block_list',
array('announcements' => $announcement_list,
'user' => $user));
}
function _theme_phptemplate_announcement($announcement, $announcement_template) {
$expired = FALSE;
if ($announcement->expiration_date < time()) {
$expired = TRUE;
}
$variables = array(
'title' => $announcement->title,
'body' => $announcement->body,
'links' => $announcement->links ?
theme('links', $announcement->links) : '',
'abstract' => $announcement->abstract,
'published' => format_date($announcement->publish_date,'custom','j M, Y'),
'expires' => format_date($announcement->expiration_date,'custom','j M, Y'),
'expired' => $expired,
'node' => $announcement
);
return _phptemplate_callback($announcement_template, $variables);
} |
Notice the use of a helper function, _theme_phptemplate_announcement, which provides a common way to prepare variables passed to the selected template file.
We've described a simple theming example for our custom announcement module. In a subsequent article, we will discuss the styling of nodes in more detail.
In this article, you learned about the implementation of a simple custom module, the announcement module, for a Web site. This module provides announcements that automatically appear and then disappear from the Web site based on publication and expiration dates. Announcements appear in the main area of the home page and in the sidebar on all other pages. Many of the core functions, or hooks, are used to provide a functioning module.
In upcoming articles in this series, you'll get more detail about the styling of our fictional Web site, as well as the interface to SQL and the database abstraction layer.
Learn
- Learn more about this series, where the Internet Technology Group team describes a fictitious organization that requires a customized Web site that includes, among other things, document storage, discussion groups, specialized workgroups, conference scheduling, and schedule session descriptions.
- Learn more about the hooks described in this article:
- hook_settings
- hook_help
- hook_perm
- hook_access
- hook_menu
- hook_link
- hook_block
- hook_form
- hook_validate
- hook_submit
- hook_load
- hook_insert
- hook_update
- hook_delete
- hook_cron
- hook_search
- hook_nodeapi
- hook_node_info
- _phptemplate_callback
- RSS feed for this series. (Find out more about RSS.)
- Stay current with developerWorks technical events and webcasts.
Get products and technologies
- Part 14 provides the complete announcement module download.
- Build your
next development project with IBM trial software, available for download directly from developerWorks.
Discuss
- Participate in the discussion forum.
- Participate in developerWorks
blogs and get involved in the developerWorks community.

Alister Lewis-Bowen is a senior software engineer in IBM's Internet Technology Group. He has worked on Internet and Web technologies as an IBM UK employee since 1993. Alister was brought to the U.S. to work on the Web sites for the IBM-sponsored sports events, then as senior Webmaster for ibm.com. He is currently helping create semantic Web prototypes.

Stephen Evanchik is a software engineer in IBM's Internet Technology Group. He has been a contributor to many open source software projects, the most notable being his IBM TrackPoint driver in the Linux kernel. Stephen is currently working with emerging semantic Web technologies.

Louis Weitzman is a senior software engineer in IBM's Internet Technology Group. For 30 years he has worked at the intersection of design and computation. He helped develop an XML, fragment-based content management system in use by ibm.com, and currently is involved with bringing the design process to emerging projects.

