The Linux Page

Drupal & the No Error Reported

[tableofcontents title: Summary; minlevel:2; maxlevel:6]

Let's start with my rant!

I like Drupal, in general. But like most PHP code bases, if an "error" (mistake) occurs, nothing is reported.

I spent hours trying to make one of my menu work (Note that the "menu" module is also called the "links" module.) It just did not want to go. In order to have the nice breadcrumb and other such features, you need to have menus with sub-sub-menus...

The fact is, the number of menu "parts" (their naming convention) is limited to 7. I'm not even sure why they impose a limit, but they do and my module would not work because of that!

The interesting thing, that does work, is the fact that you can auto-load an "object" (a row in a database, it can also be dynamically generated data) using a name after the % in your path.

So say you have a module named padfile, and that a specific menu is used to edit the PAD File data. You create something like admin/content/padfile/edit/% and you get the PAD File identifier in the %. What you can do for enhanced menu usage, is put something after the % as in: admin/content/padfile/edit/%padfile_padfile and now the system will automatically call your function named padfile_padfile_load(). That is, if the function exists. Oh! Yeah! As I mentioned early: no error reported ever.

So if you write %padfile_padfiles by mistake (i.e. added an 's' at the end) not the % is ignored as such. It is viewed as the word "%padfile_padfiles" and not as a pattern matching mechanism. All of that because you put an 's'. And that is never reported anywhere. Maybe because it is assumed that some people will put % characters in their URL all over the place and you do not want to catch those, inadvertendly. Hmmm....

In any event, if you look at the padfile module, you will see a complete example on how to make full use of the menu and form system without having to type all the extra stuff that many module developers do (in part because Drupal 5 was not that slick.)

How to use the Menu system

The menu itself

Create a hook_menu() function. So if you module is called padfile, you create a function called padfile_menu().

function padfile_menu() {
  $items[] = array();
  ...
  return $items;
}

The initialization of the $items array is usually not necessary, but it is good practice.

The menu items are defined as an array of fields with parameters. A few fields are necessary, others will be assigned defaults. In general, you declare the following:

'title' -- the text appear in the menu (between the <a> and </a> tag delimiters.)
'description' -- the tooltip help
'page callback' -- a function to call whenever the menu is selected
'page arguments' -- the arguments used to call page callback
'access callback' -- in case you write your own access function
'access argument' -- a set of rights necessary to access this menu link
'type' -- the type of menu item (callback, local task, etc.)
'file' -- when the page callback is defined in another file, its name is here

The title and description are the two things shown to the end user (unless the type is set to CALLBACK in which case the menu may be accessible but it is not shown to the user.)

The title appears in the link (the blue, underlined text.)

The description appears when you hover with the mouse pointer over the link. That's also called the tooltip.

The page callback and arguments are used to call a function. It may be yours or a system function. It does not really matter, although there are powerful functions such as drupal_get_form() that are very useful.

The access and access argument are definitions used to know whether the current user has the right to access this menu. If not, the menu is automatically hidden (Drupal 6, in Drupal 5, see the Menu Per Role project).

By default, the access for the current user are checked.

The type of menu defines whether the menu is visible or not and whether it is presented in the menu itself or as a tab.

The file is an optimization feature which you may want to skip at first. It lets you create a seperate file that is loaded only if one of your menu is triggered. This means you do not need to load huge amount of unused code.

Access features

As we have seen, the menu items have a set of fields called access callback & access argument.

The access callback is set to access_user() by default. That function accepts one string argument which has to be the name of an existing permission. For instance, 'access content' is the default for nodes.

If you have specific permissions for your project, you must be declared them in the hook_perm() function as in:

function padfile_perm() {
  return array(
    'access padfile',
    'administer padfile',
  );
}

The list of permissions is presented in the User Permission screen where you can choose which role has which rights.

Now you can use the menu access argument to specify the name of the permission allowed:

  'access argument' => 'access padfile',

With that, a user who was given access permission will be able to see that menu entry and execute the function (obviously, the function is protected too.)

If you need more complex access checks (i.e. checking for multiple access rights,) then you will have to define the access callback (a function name) and define arguments as accordingly (i.e. it could be an array of rights.)

Page Callback

This is certainly the most important part. The page callback is the one function to execute whenever that menu entry is selected.

If you intend to create a form, I suggest you look into using the drupal_get_form() function. When using that function, the first argument is the name of one of your functions that will create the form and return it. No theming necessary. The arguments are either hard coded or the % dynamically replaced by the system.

Page Arguments

Yes. I have a specific chapter for the arguments because that's a complex one.

The % can be used by itself (i.e. <path>/%/<path>, or at the end: <path>/%) or followed by a name. When followed by a name, it is expected to name a load function corresponding to that parameter. Because each % is independent, you cannot make use of that feature if you need two parameters to load an object.

For example, for the padfile module, I can make use of it when editing the PAD Files, but not their descriptions. This is because the descriptions are defined as a PAD File identifier plus a language. Without both parameters, I cannot load anything.

In the padfile module, I used the following:

  $items['admin/content/padfile/edit/%padfile_padfile'] = ...

  function padfile_padfile_load($padid) {
    ... db_query(... WHERE padid = %d ..., $padid);
    ...
    return $row;
  }

As shown in the function, it receives a PAD identifier that we can use to load the PAD File.

Submit Function

When creating a form "by hand" (without using drupal_get_form()) you need to define the proper submit function. In our case, the submit function is automatically defined as the function defined by the first argument of the item 'page argument'. The system will add ..._submit() at the end of the name and call that function (if it exists.)

The submit function is otherwise the same as what you'd expect. It receives the form and form state.

   hook_submit($form, &$form_state)

The form parameter is usually ignored. The form state is used to save the new data. If you created a valid schema in your .install file, then you can use the drupal_write_record(). For that to work, you may need to tweak the data (i.e. checkboxes return an array, a date may need to be saved as an integer (Unix date), etc.)

This will work whenever you create forms that are used to edit one table (or part of a table.) If you edit multiple tables, it may be tricky. You will have to properly select the column names you are using.

Validation Function

Really very much optional, you can validate the data too. Obviously, this is a good idea if there are values that should not be used in your table.

For instance, you may have a price for goods and you want that price to always be positive. This can be validated.

The padfile module checks the Cost field that way. It also makes sure that the Cost is zero (0.00) if the software is marked as a Freeware.

Just like the submit function, the validation function is defined as the name of the form function plus the word ..._validate() at the end. It will automatically be called if you used drupal_get_form().

The function should not return anything. Instead, it will mark all the fields that are erroneous using the form_set_error() function. That has the effect of change the corresponding box to have red borders and red text. And it includes a message that will appear at the top of the screen.

You should check all the fields you can and generate all the possible errors to make sure the user knows what needs to be changed. You should not stop on the first error because otherwise the user will have to save once per error instead of saving once, fixing as many errors as possible and then save again.

The Delete feature

Another thing that is somehow fairly well managed internally is the Delete feature. You can look at the padfile edit function, it first checks to see what the operation is ($op). If that's Delete, then we know that the user clicked the Delete button. In that case, we show another form for the user to confirm his choice.

Again, that form is created using some internal function called confirm_form(). This enables the form to be created really quickly and without having to think much. The few data you add to the form will help you really delete the data as intended and return to a safe place (an existing page, a list of items, etc.)