Learning Drupal 6 Module Development
上QQ阅读APP看书,第一时间看更新

A Basic .module File

As mentioned in the first chapter, there are two files that every module must have (though many modules have more). The first, the .info file, we examined above. The second file is the .module (dot-module) file, which is a PHP script file. This file typically implements a handful of hook functions that Drupal will call at pre-determined times during a request.

Note

For an introduction to hooks and hook implementations, see the previous chapter.

Here, we will create a .module file that will display a small formatted section of information. Later in this chapter, we will configure Drupal to display this information to site visitors.

Our Goal: A Block Hook

For our very first module, we will implement the hook_block() function. In Drupal parlance, a block is a chunk of auxiliary information that is displayed on a page alongside the main page content. Sounds confusing? An example might help.

Think of your favorite news website. On a typical article page, the text of the article is displayed in the middle of the page. But on the left and right sides of the page and perhaps at the top and bottom as well, there are other bits of information: a site menu, a list of links to related articles, links to comments or forums about this article, etc. In Drupal, these extra pieces are treated as blocks.

The hook_block() function isn't just for displaying block contents, though. In fact, this function is responsible for displaying the block and providing all the administration and auxiliary functions related to this block. Don't worry... we'll start out simply and build up from there.

Starting the .module

As was mentioned in the last chapter, Drupal follows rigorous coding and documentation standards (http://drupal.org/coding-standards). In this book, we will do our best to follow these standards. So as we start out our module, the first thing we are going to do is provide some API documentation.

Just as with the .info file, the .module file should be named after the module. Following is the beginning of our goodreads.module file:

<?php
// $Id$
/**
* @file
* Module for fetching data from Goodreads.com.
* This module provides block content retrieved from a
* Goodreads.com bookshelf.
* @see http://www.goodreads.com
*/

The .module file is just a standard PHP file. So the first line is the opening of the PHP processing instruction:<?php. Throughout this book you may notice something. While all of our PHP libraries begin with the<?php opening, none of them end with the closing ?> characters.

This is intentional, in fact, it is not just intentional, but conventional for Drupal. As much as it might offend your well-formed markup language sensibilities, it is good coding practice to omit the closing characters for a library.

Why? Because it avoids printing whitespace characters in the script's output, and that can be very important in some cases. For example, if whitespace characters are output before HTTP headers are sent, the client will see ugly error messages at the top of the page.

After the PHP tag is the keyword for the version control system:

// $Id$

When the module is checked into the Drupal CVS, information about the current revision is placed here.

The third part of this example is the API documentation. API documentation is contained in a special comment block, which begins /** and ends with a */. Everything between these is treated as documentation. Special extraction programs like Doxygen can pull out this information and create user-friendly programming information.

Note

The Drupal API reference is generated from the API comments located in Drupal's source code. The program, Doxygen,(http://www.stack.nl/~dimitri/doxygen/) is used to generate the API documents from the comments in the code.

The majority of the content in these documentation blocks (docblocks, for short) is simply text. But there are a few additions to the text.

First, there are special identifiers that provide the documentation generating program with additional information. These are typically prefixed with an @ sign.

/**
* @file
* Module for fetching data from Goodreads.com.
* This module provides block content retrieved from a
* Goodreads.com bookshelf.
* @see http://www.goodreads.com 
*/

In the above example, there are two such identifiers. The @file identifier tells the documentation processor that this comment describes the entire file, not a particular function or variable inside the file. The first comment in every Drupal PHP file should, by convention, be a file-level comment.

The other identifier in the above example is the @see keyword. This instructs the documentation processor to attempt to link this file to some other piece of information. In this case, that piece of information is a URL. Functions, constants, and variables can also be referents of a @see identifier. In these cases, the documentation processor will link this docblock to the API information for that function, constant, or variable.

As we work through the modules in this book, we will add such documentation blocks to our code, and in the process we will encounter other features of docblocks.

With these formalities out of the way, we're ready to start coding our module.

The hook_block() Implementation

Our module will display information inside a Drupal block. To do this, we need to implement the hook_block() function.

Remember, what we are doing here is providing a function that Drupal will call. When Drupal calls a hook_block() function, Drupal passes it as many as three parameters:

  • $op
  • $delta
  • $edit

The $op parameter will contain information about the type of operation Drupal expects the module to perform. This single hook implementation is expected to be able to perform a variety of different operations. Is the module to output basic information about itself? Or display the block? Or provide some administration information? The value of $op will determine this.

$op can have the following four possible values:

  • list: This is passed when the module should provide information about itself. For example, when the list of modules is displayed in the module administration screen, the $op parameter is set to list.
  • view: This value is passed in $op when Drupal expects the block hook to provide content for displaying to the user.
  • configure: This value is passed when Drupal expects an administration form used to configure the block. We will look at this later.
  • save: This value is passed when configuration information from the form data generated by configure needs to be saved.

The $delta parameter is set during a particular operation. When $op is set to the string view, which is the operation for displaying the block, then the $delta will also be set. $delta contains extra information about what content should be displayed. We will not use it in our first example, but we will use it later in the book. Take a look at Chapter 4 for another example of a hook_block() implementation.

Note

Using deltas, you can define a single hook_block() function that can display several different blocks. For example, we might define two deltas—one that displays our Goodreads bookshelf, and the other that displays information about our Goodreads account. Which one is displayed will depend on which $delta value is passed into the goodreads_block() function. Other modules in this book will make use of deltas.

Finally, the $edit parameter is used during configuration (when the save operation is called). Since we are not implementing that operation in our first module, we will not use this parameter.

Note

Drupal is meticulously documented, and the API documents are available online at http://api.drupal.org. More information about hook_block() parameters is available at this URL: http://api.drupal.org/api/function/hook_block/6.

All hook methods should follow the module naming convention:<module name>_<hook name>. So our goodreads block hook will be named goodreads_block().

/**
* Implementation of hook_block()
*/
function goodreads_block($op='list', $delta=0, $edit=array()) {
switch ($op) {
case 'list':
$blocks[0]['info'] = t('Goodreads Bookshelf');
return $blocks;
case 'view':
$blocks['subject'] = t('On the Bookshelf');
$blocks['content'] = t('Temporary content');
return $blocks;
}
}

Following Drupal conventions, we precede the function with a documentation block. For hooks, it is customary for the documentation therein to indicate which hook it is implementing.

Next is our function signature: function goodreads_block($op='list', $delta=0, $edit=array()). The $op, $delta, and $edit parameters are all explained above. Each parameter is initialized to a default value. Here, we follow the customary defaults, but you can define them otherwise if you prefer.

As I mentioned earlier the $op parameter might be set to one of several different values.

What we do in this function is largely determined by which of those four values is set in the $op flag. For that reason, the first thing we do in this function is use a switch statement to find out which operation to execute.

Each case in the switch statement handles one of the different operations. For now, we don't have any administration configuration to perform, so there are no cases to handle either configure or save operations. We just need to handle the list and view operations. Let's look at each.

case 'list':
$blocks[0]['info'] = t('Goodreads Bookshelf');
return $blocks;

When Drupal calls this hook with $op set to'list', then this module will return a two‑dimensional array that looks as follows:

array(
[0]=> (
'info' => 'Goodreads Bookshelf'
))

Each element in this array is a block descriptor, which provides information about what this block implementation does. There should be one entry here for every $delta value that this function recognizes. Our block will only return one value (we don't make use of deltas), so there is only one entry in the block descriptor array.

A block descriptor can contain several different fields in the associative array. One is required: the'info' field that we have set above. But we could also provide information on caching, default weighting and placement, and so on.

Note

For detailed information on this and other aspects of the hook_block() hook, see the API documentation: http://api.drupal.org/api/function/hook_block/6

Drupal uses the'info' field to display an item in the module management list, which we will see in the Installing a Module section of this chapter.

The t() Function

In this example, there is one more thing worthy of mention. We use the function t(). This is the translation function. It is used to provide multi-language support and also provide a standard method of string substitution. When t() is called, Drupal will check to see if the user's preferred language is other than the default (US English). If the user prefers another language, and that language is supported, then Drupal will attempt to translate the string into the user's preferred language.

Note

For multi-language support, you will need to enable the Content translation module.

Whenever we present hard-coded text to a user, we will use the t() function to make sure that multi-language support is maintained.

In simple cases, the t() function takes just a string containing a message. In this case, the entire string will be translated. But sometimes, extra data needs to be passed into the string function. For example, we may want to add a URL into a string dynamically:

'Trying to access !url.'

In this case, we want t() to translate the string, but to substitute a URL in place of the !url placeholder. To do this, we would call t() with the following parameters:

t('Trying to access !url.', array('!url'=>'http://example.com'));

In this example, t() has two arguments: the string to translate, and an associative array where the key is the placeholder name and the value is the value to be substituted. Running the above when the locale is set to English will result in a string as follows:

Trying to access http://example.com.

There are three different kinds of placeholder. We have seen one above.

  • !: Placeholders that begin with the exclamation point (!) are substituted into the string exactly as is.

Sometimes it is desirable to do some escaping of the variables before substituting them into the string. The other two placeholder markers indicate that extra escaping is necessary.

  • @: Placeholders that begin with an @ sign will be escaped using the check_plain() function. This will, for example, convert HTML tags to escaped entities. t('Italics tag: @tag', array( '@tag', '<i>')) will produce the string'Italics tag: &lt;i&gt;'.
  • %: Placeholders that begin with the percent sign (%) are not only escaped, like those that start with the @, but are also themed. (We will look at theming in the next chapter.) Usually, the result of this theming is that the output value is placed in italics. So t('Replacing %value.', array('%value=>'test') will result in something like'Replacing <em>test</em>'. The<em></em> tags are added by the translation function.

    Note

    Don't trust user-entered data

    It is always better to err on the side of caution. Do not trust data from external sources (like users or remote sites). When it comes to the t() function, this means you should generally not use placeholders beginning with ! if the source of the string to be substituted is outside of your control. (For example, it is inadvisable to do this: t('Hello !user', array('!user' => $_GET['username']). Using @user or %user is safer.

We will use the t() function throughout this book. For now, though, let's continue looking at the hook_block() function we have created.

A view Operation

Now let's turn to the view case. This second case in our switch statement looks as follows:

case 'view':
$blocks['subject'] = t('On the Bookshelf');
$blocks['content'] = t('Temporary content');
return $blocks;

The view operation should return one block of content for displaying to the end user. This block of content must have two parts stored as name/value pairs in an associative array: a subject and a content item.

The subject is the title of the block, and the content is main content of the block. The value of the subject entry will be used as a title for the block, while the content will be placed into the block's content.

Again, we used the translation function, t(), to translate the title and content of this block.

While it is not terribly exciting yet, our module is ready to test. The next thing to do is install it.