Getting Your Feet Wet: Building Your First Drupal Module
Objectives
It’s important to have a scope of your objectives before getting started. This will help you decide which hooks you will need to implement and gives you small tasks to accomplish, rather than taking on the entire module at once. In this case, our main objective is to remove the menu options from the node add/edit form for certain content types, but to be thorough, here’s the complete list.
- Only allow site admins to use this module
- Provide module settings to specify which node types to apply the form modification to
- Modify the node form to remove the menu options
Hooks
Hooks are how we use a module to modify something before it is delivered to the browser. It’s very tempting as a new module developer to hack into the core of Drupal to get things done. Hacking the core is bad, but hooking into the core of Drupal is good. It’s very rare that a hook or combination of hooks won’t allow you to achieve your objective. For more on hooks and what they do, check out the Drupal API for Hooks. Remember to always replace the word hook with the machine name of your module. We’re going to be using 3 very common, and very useful hooks to achieve our objectives:
- hook_perm
- hook_menu
- hook_form_alter
Let’s Get Started
This first thing to do when building a module is to come up with a name. In our case we’re excluding something from the menu, so I thought A La Carte was appropriate.
Now we’re ready to get serious. There are 2 files required to create a module: a .info file and a .module file. The .info file simply tells Drupal about the module, and the .module file contains the php code that makes the module work. Go ahead and create your module folder, called alacarte, and inside that folder create the alacarte.info and alacarte.module files.
Insert the following code into the alacarte.info file:
name = A la carte
description = Allows administrators to hide menu items like the menu and revisions, on node forms.
core = 6.x
version = 6.x-1.1
project = alacarte
project status url = http://library.hck2-interactive.net/release-history
Here’s a breakdown of the what’s going on:
- name
- This is the display name that appears on the Module page (admin/build/modules).
- description
- This description appears on the Module page (admin/build/modules).
- core
- The core version of Drupal that this module is built for.
- version
- This is the version of the actual module itself.
- project
- This is the machine name of the project. Make sure to use lowercase and no spaces.
- project status url
- This is optional. It is the URL of an XML feed about project versions. Drupal uses this feed to determine if updates are available for your module on the Available Updates page (admin/reports/updates). If you don’t include this, your module won’t be displayed on that page.
Module Permissions
Now that Drupal knows about our module, it’s time to get started on making it work. The first thing were going to do is use hook_perm as an access control method for our module. This hook simply returns an array of values, and those values are listed on the Permissions page (admin/user/permissions). This will allow you to grant (or not grant) permission for the settings form of your module. In our example, we are just returning a single value of ‘administer alacarte’. If we wanted to get more granular with the permissions or our module had more functionality, we could easily return multiple values. Add the following code to the alacarte.module file. Make sure to include an opening PHP tag (<?php).
/**
* Implementation of hook_perm().
*/
function alacarte_perm() {
return array('administer alacarte');
}
A Menu Item For Our Module Settings
The next thing we want to do is create the menu path for the settings form for our module. We will be using hook_menu to do so. The hook_menu hook is how we add things to the navigation structure in Drupal. Since we want to have administrative settings, we need to provide a way for a user with the proper permissions to get to those settings. This hook returns an array of menu items. Don’t forget to return the array, and make sure to clear the site cache (admin/settings/performance) when any modification to this hook occurs! You can see the index of our array item is ‘admin/settings/alacarte’. That is the path of this menu item. Since the path begins with ‘admin/settings/’ this menu item will appear in the Site Configuration section on the admin dashboard (/admin). Similarly, if the path began with ‘admin/build/’ this menu item would appear in the Site Building section. Add the following code to your alacarte.module file:
/**
* Implementation of hook_menu().
*/
function alacarte_menu() {
$items['admin/settings/alacarte'] = array(
'title' => 'A la carte Settings',
'page callback' => 'drupal_get_form',
'page arguments' => array('alacarte_settings'),
'access arguments' => array('administer alacarte'),
'file' => 'alacarte.admin.inc',
);
return $items;
}
Here’s a breakdown of the options we are using in the menu item array:
- title
- This option defines how the menu items will be titled.
- page callback
- The page callback is the function that we want to execute when this path is used. In this case we want to diplay our admin form, so we’ll use the core function drupal_get_form. You can also use a custom function from your module.
- page arguments
- The page arguments is an array of values to be used in the page callback function. Since drupal_get_form was used, we only need a single argument telling that function which form we want to use.
- access arguments
- This option defines who has permissions to use this menu item. Since we used hook_perm, we will return an array value that correlates with that hook.
- file
- Since our form definition function will be in a separate file, we must use this option to tell Drupal where to look. This is a relative path to the alacarte.module file.
Module Settings Form
It’s a good practice to keep your forms in individual and separate files. Let’s go ahead and create a file called alacarte.admin.inc.
Insert the following code into the alacarte.admin.inc , and don’t forget the opening PHP tag.
/**
* A la Carte admin menu settings.
*/
function alacarte_settings(){
$form['alacarte_content_types'] = array(
'#type' => 'checkboxes',
'#title' => t('Content types to exclude the menu settings from on the node form.'),
'#options' => node_get_types('names'),
'#default_value' => variable_get('alacarte_content_types',NULL),
);
return system_settings_form($form);
}
Notice that the function to define this form is called alacarte_settings. This correlates back to the page arguments in hook_menu. Here’s a breakdown of the options we’re using:
- type
- The type defines what kind of form element this will be. In our case we want to have a collection of checkboxes, but there are other types such as textfield, textarea, radios, etc… Check out the Forms API Reference Guide for more details.
- title
- The title is the name that will display for the form element.
- options
- Certain form elements require an array of options. Since we’re using the checkboxes type, we must provide options. In this case rather than hardcoding an array, we want to provide a dynamic array of content types so our module will include any future added content types. The node_get_types(‘names’) function does just that for us.
- default_value
- The default_values option populates the form element with the saved value(s). We are using the system_settings_form() function to return our form, so Drupal will automatically give us a submit button, and upon submitting will save the form element data to the variables database table. To retrieve these values, we use the variable_get(‘alacarte_content_types’,NULL) function. This simply says, “go to the variables table and get me the value for alacarte_content_types. If there is no value, use NULL”. Notice that the variable we’re looking for corresponds to the index of our form element.
Let’s try it out
Now we’re ready to see something happen. Go to the Modules page (admin/build/modules) and enable the module. For good practice, go ahead and clear the site cache out (admin/settings/performance). Now when you visit the admin dashboard (admin) you will see a link for “A la carte Settings” under the Site configuration section. Click the link and you’ll see our admin form. The form at this point is fully functional and will store your selections.
Make it Do Something
We’re in the home stretch. Now, we just need to use our settings to remove the menu options from those content types. The hook_form_alter hook is how we will do this. Add the following code to the alacarte.module file and we’ll go though line by line and dissect what’s happening.
/**
* Implementation of hook_form_alter().
*/
function alacarte_form_alter(&$form,$form_state,$form_id){
// if this form is a node edit form, we want to extract the content type name from the form id
$content_type = str_replace("_node_form",'',$form_id);
// create an array of excluded content types
$excluded = variable_get('alacarte_content_types',array());
// if the content type is excluded, unset the menu form elements
if($excluded[$content_type]){
unset($form['menu']);
}
}
- $content_type = str_replace(“_node_form”,”,$form_id);
- Here, we define a variable of $content_type so we can see if it is one of the types that gets the menu options excluded from the form. The $form_id variable that we are provided with this hook will always be nodetype_node_form for the node add/edit form. Based on that, we’re just going to do a quick str_replace() to remove the ‘_node_form’, leaving only the content type.
- $excluded = variable_get(‘alacarte_content_types’,array());
- Now we need to create an array of the excluded content types. To do so, we’ll use the variable_get() function. Remember that our module settings form stores the excluded content types in the ‘alacarte_content_types’ variable in the database.
- if($excluded[$content_type]){
- We’re now checking to see if our $content_type variable exists in the $excluded array, and if it does to execute the next line.
- unset($form[‘menu’]);
- If this content type is one of the chosen we’re going to unset the $form[‘menu’] array element. To get a clear understanding of how to do other modifications to a form, insert a print_r($form) and check out the results when you reload the page with the form on it. You’ll be able to see all of the form elements, and can make changes by redefining indices or dropping values.
Finishing Touches
At this point we have a fully functional module, but let’s take the extra time to make it really complete and add some functions for the installing and uninstalling the module. Create a file called alacarte.install.
Insert the following code into the alacarte.install file.
/**
* Implementation of hook_install().
*/
function alacarte_install() {
// set the variable that will hold our exclusion data
variable_set('alacarte_content_types', array());
}
/**
* Implementation of hook_uninstall().
*/
function alacarte_uninstall() {
// remove the variable(s) that we set up for the alacarte module
db_query("DELETE FROM {variable} WHERE name LIKE 'alacarte_%'");
// clear the cache
cache_clear_all('variables', 'cache');
}
These 2 hooks fire on install and uninstall respectively. On install, we’re setting up the ‘alacarte_content_types’ variable. The module settings form will do that for us on the first submit, but I think creating variables at installation a good practice to get into and will prepare you for building modules that will be distributed. This is also where you would create database tables if your module needs a custom table. On uninstall we’re simply cleaning up by removing the variable we created, and clearing the cache out. Now if a user uninstalls our module, we won’t leave any extra variables in the database.
Download the complete module: alacarte-6x.1.1.tar.gzip