The blocks sub-system within Drupal 7 is great. It allows you to create a block, place that block wherever you want within your theme (limited to the defined regions within the theme of course) and finally specify which pages and roles your block is exposed to.
For the most part basic blocks provided by Drupal will be sufficient for the requirements you may have, but there will be instances where you may want to produce something custom, or perhaps isolate a users access to just one block. This is where the ability to create a custom block with editable pre-defined fields comes in handy.
In the following article we’re going to create a module that defines a custom block, which displays a series of contact details to the end-user. When editing the block, the user will be exposed to additional fields not there as standard on the configuration page (only one of which will be required). The fields that we will be adding are as follows:
1. Email address (textfield - will be validated as an email address)
2. Telephone number (textfield - no validation will be done on this field)
3. Address (required textarea - no validation will be done on this field)
Throughout the guide we’ll be using quite a few Drupal “hooks”. By typing “What is a Drupal hook” into Google, we are presented with this excellent definition:
“A hook is a PHP function that is named foo_bar(), where "foo" is the name of the module (whose filename is thus foo.module) and "bar" is the name of the hook. Each hook has a defined set of parameters and a specified result type. To extend Drupal, a module need simply implement a hook.”
The list of hooks we’ll be using in this tutorial are:
Note: I’ll be using a sandbox Drupal 7 site for this article using the most recent version of Drupal, which at the time of writing this is Drupal 7.36
Now, let’s get started!
Creating our module files and folder structure
I’m going to assume at this point you already have Drupal 7.36 installed and you’re ready to begin creating our custom module. First off, in your file browser of choice, navigate to “your-site-folder/sites/all/modules” (where “your-site-folder” is the location you installed Drupal ). If the “modules” directory is empty (which it most likely will be with a fresh site install), create a new folder within it called “custom” and finally navigate into that folder.
Within our newly created “custom” folder, create another folder called “custom_contact_info” (this will be the machine-name of our custom module). Once created, navigate into the newly created folder.
Note: Drupal 7 is intelligent enough to traverse down into sub-folders within the modules directories to find the modules that are contained within. As such, it’s a good idea to create sub-folders within the main modules directory (e.g. “custom”, “contrib”, “features”, etc). This allows you to group the various types of module together.
Next, we need to create several files within our module directory, which are:
4. custom-contact-info.tpl.php (place this file in a sub-folder within your module called “templates”)
The method used to create these files is entirely up to you, but I would recommend using a command-line tool such as Nano (http://www.nano-editor.org/) - an example usage of which follows:
1. Navigate to the module directory on your system (e.g. cd /var/www/site-folder/sites/all/modules/custom/custom_contact_info).
2. Create your first file by typing nano custom_contact_info.info
3. In the editor that opens press “Ctrl+O” to “Write out” your file (when prompted press “Y” for “Yes” and hit “Enter”)
4. Finally, press “Ctrl+X” to exit Nano
Repeat the steps described above to create the three additional files (to create the templates directory, use the command “mkdir templates” and navigate into the folder using the “cd” command illustrated in point one above).
Defining our *.info file
For our module to be visible to Drupal, we need to define a few attributes in our custom_contact_info.info file. Open the file in your favourite editor and add the following:
Let’s take a look at the attributes we’ve defined:
- Name: This is the human-readable name of our module. Wherever the module is listed (e.g. the module overview page), this will be used.
- Description: A short description of what functionality the module provides.
- Core: The version of Drupal your module is intended for (7.x includes all past and future releases of Drupal 7 - minor versions cannot be catered for in this manner).
- Package: If your module is part of a suite of other modules, you can define a package. It’s not required, but it can be handy. Here at CTI we often place custom modules in the package labelled “CTI” if we feel they’re modules that could be useful on future sites too. Otherwise when they’re developed to solve a very specific solution we put them in a package that is the name of the client.
- Dependencies: If your module has any dependencies (e.g. Views UI requires Views to work), you can list them here.
Defining our *.install file
Install files aren’t expressly required by Drupal modules and are really only needed when you want to perform a series of actions, or configure something prior to a module being run for the first time - for example, you may wish to create a database table that your module needs access to in order to run correctly.
For our module, we want to define a variable and add some placeholder data into it and to accomplish this we’ll use the hook “hook_install()” (https://api.drupal.org/api/drupal/modules%21system%21system.api.php/function/hook_install/7) along with setting a persistent variable with the use of “variable_set()” (https://api.drupal.org/api/drupal/includes%21bootstrap.inc/function/variable_set/7).
Add the following code into your *.install file:
As you can see, we’re defining an implementation of “hook_install” by creating a function called “custom_contact_info_install” and within that implementation we’re setting a variable called “custom_contact_info_data”, which contains an array of contact information.
If you create an install hook within Drupal, it’s good practice to also include an uninstall hook to delete, or remove any functionality your module adds when a user uninstalls the module.
Add the following to your *.install file:
Here we’re deleting the variable we defined in our install hook using “variable_del” (https://api.drupal.org/api/drupal/includes!bootstrap.inc/function/variable_del/7).
Defining our *.module file
At this point we could technically navigate to the module page, see our module listed along with all the others and enable it. Granted, it wouldn’t actually do anything at this stage because no functionality has been defined, so let’s remedy that by beginning to build up our module code.
To start, we’re going to create an implementation of “hook_permission()” (https://api.drupal.org/api/drupal/modules%21system%21system.api.php/function/hook_permission/7) in our module. Add the following code into your *.module file:
Any permissions defined here will be displayed on the permissions page of your Drupal site and this will allow you to check, or uncheck the user roles you wish to grant access to the defined permission.
Next up we’re going to define our custom block by creating an implementation of “hook_block_info()” (https://api.drupal.org/api/drupal/modules%21block%21block.api.php/function/hook_block_info/7). To do this, add the following code into your *.module file:
Here we’re defining a block called “custom_contact_info_block” and giving it an “info” value of “Block: Custom contact information” (this is the title of the block as displayed on the blocks overview page) and we’re also telling Drupal not to cache the block.
The next hook implementation we’re going to use in our module is an implementation of “hook_block_configure()” (https://api.drupal.org/api/drupal/modules!block!block.api.php/function/hook_block_configure/7), which is the hook required to add a configuration form for a block.
In this hook we’ll be defining form items using the Drupal form API (https://api.drupal.org/api/drupal/developer!topics!forms_api_reference.html/7) and we will also be leveraging the permissions we previously defined using the Drupal function “user_access()” (https://api.drupal.org/api/drupal/modules%21user%21user.module/function/user_access/7) ensuring that only users with the correct access privileges are able to edit the data contained in the block.
One final thing worth a mention is the fact that we’ll be using “#element_validate” to run some of our fields through a custom validation handler. Add the following code to your *.module file:
If you were to switch the module on at this point and navigate to the block settings page for our custom block you would be presented with the following output:
Great, so we have a configuration form, but if we were to change the values displayed and submit the form nothing would happen. Now we need to create a validation handler to validate our fields and we also need to create an implementation of “hook_block_save()” (https://api.drupal.org/api/drupal/modules%21block%21block.api.php/function/hook_block_save/7) to save any changes we make once validation has taken place.
Add the following code to your *.module file:
In the validation handler we’ve added a switch statement and we’re passing the name of the field through, which allows us to generalise the handler and use it for all fields in the configuration form if we so desire.
In the example code provided the email field has some simple validation, but due to the scope of this guide and the complexity of telephone validation this has simply been left with a placeholder.
Note: If validation of the email address fails, we’re using “form_set_error()” (https://api.drupal.org/api/drupal/includes%21form.inc/function/form_set_error/7) to return the user to the page whereby they will see a red error message along with a highlighted field making the error more apparent.
Now that we can validate the data in our configuration form we’re going to create an implementation of “hook_block_save()” to allow us to save any changes made.
Add the following code to your *.module file:
All we’re doing here is simply saving the data in the “custom_contact_info_data” variable effectively overwriting the data currently stored in it.
We want to be able to theme the output of our block and to allow us to do this we need to create an implementation of “hook_theme()” (https://api.drupal.org/api/drupal/modules%21system%21system.api.php/function/hook_theme/7). Add the following code to your *.module file:
In the code above we’re creating a new theme hook (called “custom_contact_info”), we’re pointing to a PHP template file for use in the hook, and we’re defining 3 variables (“email”, “telephone” and “address”) - all with a default value of NULL.
Finally, at least for the *.module file, we need to create an implementation of “hook_block_view()” (https://api.drupal.org/api/drupal/modules%21block%21block.api.php/function/hook_block_view/7) to allow us to view the block on the site. Add the following code to your *.module file:
Here if the $delta is determined to be “custom_contact_info_block” (the machine name of our custom block), we’re loading the data from the variable “custom_contact_info_data” into a PHP variable called “$contact_info” and we’re returning a themed output (using the previously defined theme hook), which contains data for the three variables previously defined.
Finally, open your template file “custom-contact-info.tpl.php” and add the following code:
Here’s all we are simply doing is ensuring that the variables passed to the template files aren’t equal to empty. If the condition passes each of them is added as a row within an unordered list.
Note: Notice that each of the variable names available here are identical to the names defined in the theme_hook().
If all went well, once you switch on your module and add the block to a region on your page, you should be presented with something similar to the following:
All code produced in this guide is available in it’s complete form from the following GIT repository: https://github.com/craigweb/custom-contact-info. As always, if you have any questions, please feel free to get in touch with me on Twitter at @craigperks