Advance Examples Which actually needed
This article is based on the O'Reilly article which is relevant to Drupal7 and some of it for Drupal8, but drupal did a lot of modifications since then, and I believe it's a good place to start with, but still should be modified to Drupal9 and on. from time to time I will fix this article and update it according to the last drupal version, you are all invited to send me remarks and suggestions.
These examples, I believe are most needed for the more advanced Drupal backend developer.
My development strategy recommendations are:
- Search for an existing module that best suited your needs.
- If no module is found, Search for a module that is most close to your needs, now you can decide between creating a module depending on it, a sub-module, or updating the found module (make sure you created <mymodule.info> file of your own)
- plan and implement your own module
The examples within this article are made according to Drupal 7.2, many things have changed since then, and I intend to fix the article with time and show the appropriate way to do the thing according to the relevant Drupal version. I will not keep the current examples after being updated, you can always return to the original article which references the link mentioned at the bottom.
Every section that will be updated according to current Drupal, will be mentioned neer its title.
Further programming examples:
- The Drupal core code itself, which includes extensive documentation and tests
- The Examples for Developers project, http://drupal.org/project/examples, which has comprehensive coverage of the Drupal core APIs and how to use them in your own modules
- Thousands of GPL-licensed contributed modules you can download from http://drupal.org/project/modules and then adapt for your own work
- The API reference site, http://api.drupal.org
This article includes the following subjects:
- Registering for URLs and Displaying Content
- Registering for URL
- Altering a URL Registration
- Registering a Block
- Providing Page and Block Output
- Generating Forms with the Form API
- Programming with Entities and Fields
- Defining an Entity Type
- Creating Rules Module Add-Ons
- Programming with Field Widgets
- Programming with Field Formatters
- Creating Views Module Add-Ons
- Views Programming Terminology and Output Construction
- Providing a New Views Data Source
- Adding Fields and Relationships to an Existing Views Data Source
- Providing a Display Plugin to Views
- Providing Default Views
- Creating Rules Module Add-Ons
- Providing Custom Actions to Rules
- Providing Default Rules
- Developer Tools
- Reference and additional resources
1. Registering for URLs and Displaying Content
How Drupal Handles URL Requests contains an overview of how Drupal 7 handles URL requests and returns the content to a web browser or other requester. This section of the book goes into more detail about how a module you write can be part of that process, by registering with Drupal to handle specific URLs, providing page and block content, and by generating and processing HTML forms.
WARNING
Given that Drupal has many hooks and that it is written in PHP, there are many ways that you could consider writing code for Drupal that would return output in response to a URL request, or that would place content in a region on a page or set of pages. Most of these ways would, however, amount to subverting Drupal’s standard processes, rather than working within the Drupal framework. Use the methods in this section of the book for the best results.
In Drupal 7 and earlier versions of Drupal, assuming that you have decided you need your module to output some content, the first choice you need to make is whether to provide a menu router entry or a block. A menu router entry allows you to respond to a URL request by providing the main HTML content for that page, or in some cases, the entire output for the URL (such as XML output that is used by a Flash script on your site, an RSS feed, or an AJAX response). A block allows you to provide a chunk of output that can be placed on one or more of a site’s HTML-based pages. In either case, you will need to register with Drupal for the block or URL, and then write a function to generate the output; in the case of a menu router entry, you will also need to define permissions for accessing the URL. All of these steps are described in the following sections.
Note that you should only write code to provide blocks and menu router entries if there is some logic or programming needed to generate the content of the block or page. If you are displaying static content, you can create a block or content item using Drupal’s user interface, and if you need to employ some logic to decide where or when to show the block, use the Context module or Panels module. Also, keep in mind that using the Views module is a better choice than making a custom module in many cases.
Further reading:
DRUPAL 8
The menu router system and block placement systems will be quite different in Drupal 8. As of this writing, the system that is envisioned is expected to have the following elements:
- When defining a block in Drupal 8, you will be able to define additional context information that is needed in order to decide what content to display.
- Drupal 7 uses the concept of a special "main page content" block (which modules register to provide at different URLs). In Drupal 8, all page elements will be blocks on equal footing, with no particular "main" block.
- In Drupal 8, URL registration will correspond to layouts, which determine which blocks are displayed under which context conditions.
2. Registering for a URL
To register to provide the main page content for a URL, define a menu router entry by implementing hook_menu()
it in your mymodule.module file First, you will need to choose a URL, with the following considerations:
- If you are providing an administrative page, the URL should be chosen to place the page in an appropriate, existing section of the Drupal core administration screens. For instance, if it’s "structural," it should start with
admin/structure/
, and if it’s for use by developers, it should start withadmin/config/development/
. You can see a complete list of the sections in functionsystem_menu()
in the modules/system/system.module file that comes with Drupal. - Make sure your URL does not conflict with a URL that another module might provide. Normally, prefixing with or including your module’s short name is a good idea (
mymodule
in this example). - Make your URL like others in Drupal. For instance, if you are defining an auto-complete responder for a form, make the URL
mymodule/autocomplete
, similar to the existinguser/autocomplete
URL defined by the core User module. - The URL can contain wildcards. For example, the core Node module defines a URL of
node/
followed by the node content item’s ID number.
After choosing your URL, implement hook_menu()
to tell Drupal about it:
function mymodule_menu() {
$items = array();
// Put the chosen URL here, minus the base site URL.
$items['mymodule/mypath'] = array(
'title' => 'My page title',
// Function that will generate the content.
'page callback' => 'mymodule_page_generate',
// Function used to check permissions. This defaults to user_access(),
// which is provided here as an illustration -- you can omit this line
// if you want to use the user_access() function. Put the name of your
// custom access check function here if you have one.
'access callback' => 'user_access',
// Arguments needed for your access callback function. If using the
// default user_access() function, the argument is the name of the
// permission a user must have to access the page.
'access arguments' => array('access content'),
);
return $items;
}
Notes:
- The
hook_menu()
implementation references a page-generating function (mymodule_page_generate()
in this example). Since block-generating functions are very similar to page-generating functions, the details of what this function should return are covered in a separate section below: Providing Page and Block Output. - There is no need to explicitly check for access permissions in your page-generating function or elsewhere, assuming that you set up an access callback in your
hook_menu()
implementation. Drupal will verify and run this access check for you automatically and return a 403 access denied response for unauthorized users.
AUTO-LOADING, ARGUMENTS, AND WILDCARDS IN HOOK_MENU()
The menu routing system in Drupal is fairly powerful, and one of the powerful features that new Drupal programmers often don’t know about is the ability to auto-load objects. Using this feature takes several steps:
- Use a wildcard in the URL you are registering for, and give it a name. Wildcards in
hook_menu()
URLs start with%
, so for instance you could register for a URL like'mymodule/%mymodule_object'
. - Define a function of the same name as your wildcard with a
load
suffix:mymodule_object_load()
in this example. It should load your object and return it. - When someone goes to a specific URL matching your pattern, such as example.com/mymodule/123, your load function will be called with the corresponding piece of the URL as its first argument (
'123'
in this example). You can tell Drupal to pass additional arguments to this function by adding a'load arguments'
element to yourhook_menu()
implementation. - In
'page arguments'
,'access arguments'
, and related elements of yourhook_menu()
implementation, you can pass the loaded object by using the numeric index of your wildcard in the URL. In URL'mymodule/%mymodule_object'
, for example, the 0 placeholder would have value'mymodule'
and the 1 placeholder would have your loaded object. So if your page-generating function has two arguments, the object and a view mode, you might put'page callback' => array(1, 'full'),
into yourhook_menu()
implementation to indicate this.
There is an example that uses an auto-loading wildcard in Step 4 of Defining an Entity Type.
Further reading and references:
- Programming with Hooks in Modules and Themes
- Drupal core’s main permission system
- The Page example in Examples for Developers: http://drupal.org/project/examples
- Look up
hook_menu()
on http://api.drupal.org for complete documentation of all its options.
3. Altering a URL Registration
A related task that you may need to do in a module is to alter how another module has registered for a URL. One common reason would be that you want to use a different access permission system for the URL. To do this, implement hook_menu_alter()
in your mymodule.module file. For example:
function mymodule_menu_alter(&$items) {
// $items contains all items from hook_menu() implementations.
$items['other/module/path']['access callback'] = 'mymodule_check_access';
}
function mymodule_check_access() {
// The user who is trying to access the page.
global $user;
// Calculate whether this user should get access or not,
// and return TRUE or FALSE.
}
Further reading and references:
- Lookup
hook_menu()
on http://api.drupal.org for complete documentation of all its options (which can be used inhook_menu_alter()
too). - Programming with Hooks in Modules and Themes
- Drupal core’s main permission system
4. Registering a Block
If you want to provide content that can be displayed on multiple pages, you should register for a block rather than for a URL in your module. To register for a block, you need to implement hook_block_info()
in your mymodule.module file to tell Drupal about the existence of your block, and then implement hook_block_view()
to generate the block content. For example:
// Tell Drupal about your block.
function mymodule_block_info() {
$blocks = array();
// The array key is known as the block "delta" (a unique identifier
// within your module), and is used in other block hooks. Choose
// something descriptive.
$blocks['first_block'] = array(
// The name shown on the Blocks administration page.
// Be descriptive and unique across all blocks.
'info' => t('First block from My Module'),
);
return $blocks;
}
// Generate the block content. Note that the $delta value passed in
// is the same as the array key you returned in your hook_block_info()
// implementation.
function mymodule_block_view($delta = '') {
if ($delta == 'first_block') {
return array(
// The block's title.
'subject' => t('First block'),
// The block's content.
'content' => mymodule_block_generate(),
);
}
}
Notes:
- Implementations of
hook_block_info()
can be more complex than this: they can specify cache parameters (block output is cached by default for efficiency) and default placement. - Blocks can also have configuration settings.
- The
hook_block_view()
implementation here calls a function (in this example,mymodule_block_generate()
) to provide the actual block content. Since block-generating functions are very similar to page-generating functions, the details of what this function should return are covered in a separate section below: Providing Page and Block Output. - After adding a new block to a
hook_block_info()
implementation, you will need to clear the Drupal cache to make it visible.
Further reading, examples, and references:
- Programming with Hooks in Modules and Themes
- The Drupal Cache
- The Block example in Examples for Developers and many Drupal core blocks include configuration options, cache settings, and other options.
- Look up
hook_block_info()
on http://api.drupal.org to find all the options and links to the Drupal core functions that implement it.
5. Providing Page and Block Output
Once your module has registered for a page or block (see previous sections), you need to write a function that returns the page or block output. In Drupal 6 and prior versions, this type of function would return a fully rendered text string containing both the data to display and the HTML markup. In Drupal 7, there has been a change in philosophy, however, and it is currently recommended that page and block functions return a render array, which contains the data to output along with formatting information.
Here is the general structure of a render array that you could return from a page- or block-generating function:
$output = array(
'sensible_identifier_1' => array(
'#type' => 'element_identifier',
// Other properties and data here.
),
'sensible_identifier_2' => array(
'#theme' => 'theme_hook',
// Other properties and data here.
),
// Other pieces of output here.
);
Notes:
- The outermost array keys are arbitrary: choose sensible identifiers that will remind you of what each piece of your block or page is.
- At the next level of arrays, keys starting with
'#'
are property keys that are recognized by the Render API. - Each sub-array needs to either have a
'#type'
property, whose value is the machine name of a render element, or a'#theme'
property, whose value is the name of a theme hook. - Render elements are basically sets of properties in an array that correspond to one or more HTML elements. They are defined in modules by implementing
hook_element_info()
; many of them are form elements. Each render element requires one or more other properties to be provided and may have optional properties that you can use to control the output. - Theme hooks are defined by modules by implementing
hook_theme()
, and each theme hook also requires one or more properties to be provided. - Be sure that all your text is internationalized.
Here’s an example of a render array that has an informational paragraph, followed by a list of items, followed by a table (the paragraph uses a 'markup'
render element; the list and table use the 'item_list'
and 'table'
theme hooks):
$output = array(
'introduction' => array(
'#type' => 'markup',
'#markup' => '<p>' . t('General information goes here.') . '</p>',
),
'colors' => array(
'#theme' => 'item_list',
'#items' => array(t('Red'), t('Blue'), t('Green')),
'#title' => t('Colors'),
),
'materials' => array(
'#theme' => 'table',
'#caption' => t('Materials'),
'#header' => array(t('Material'), t('Characteristic')),
'#rows' => array(
array(t('Steel'), t('Strong')),
array(t('Aluminum'), t('Light')),
),
),
);
Further reading and references:
- More about forms: Generating Forms with the Form API
- More about theme hooks: Making Your Output Themeable
- Internationalizing text: Principle: Drupal Is International
- Unfortunately, there is not currently a comprehensive reference for Drupal render elements (although one is in planning as of this writing). Modules register to provide render elements by implementing
hook_element_info()
. For example,system_element_info()
provides most of the Drupal core elements, such as'link'
and'markup'
, which are used in many render arrays. Look uphook_element_info()
on http://api.drupal.org to find Drupal core functions that provide render elements, and click through to find out what elements each module provides. - Find Drupal core theme hooks on the "Default theme implementations" topic page on http://api.drupal.org.
NOTE
It is still possible in Drupal 7 to return strings from your page and block content functions instead of render arrays. Using render arrays is preferred, however, because:
- They are self-documenting.
- They allow modules to use
hook_page_alter()
to alter the page before it is rendered. - They leave final rendering until late in the page generation process, so unnecessary rendering can be avoided if a particular section of the page is not actually displayed.
Generating paged output
If a page or block you are generating output for is listing data, you need to think about what should happen if the list of data gets long; usually you would want the output to be separated into pages. If you are using a database query to generate the list, Drupal’s Database API and theme system make separating the output into pages very easy. Here are the steps:
- Use a dynamic query with
db_select()
, rather than a static query withdb_query()
. - Add the
PagerDefault
extension to your database query. - Add
theme('pager')
to your output, either directly or as part of a render array. This will add links to the pages of output, which will make use of a URL query parameter called'page'
on the base URL of the page. ThePagerDefault
extension will read this URL query parameter to figure out what page the user is on, and return the appropriate rows in the database query automatically.
As an example, assume you want to show the titles of the most recently updated node content items, and you want to show 10 items per page. Here is the code you would need to put into your output-generating function for the block or page:
// Find the most recently updated nodes.
$query = db_select('node', 'n')
->fields('n', array('title'))
->orderBy('n.changed', 'DESC')
// Be sure to check permissions, and only show published items.
->addTag('node_access')
->condition('n.status', 1)
// Put this last, because the return value is a new object.
->extend('PagerDefault');
// This only applies with the PagerDefault extension.
$query->limit(10);
$result = $query->execute();
// Extract and sanitize the information from the query result.
$titles = array();
foreach ($result as $row) {
$titles[] = check_plain($row->title);
}
// Make the render array for a paged list of titles.
$build = array();
// The list of titles.
$build['items'] = array(
'#theme' => 'item_list',
'#items' => $titles,
);
// The pager.
$build['item_pager'] = array('#theme' => 'pager');
return $build;
Further reading and references:
- Dynamic queries
- Cleansing and Checking User-Provided Input
- It is usually better to use the Views module rather than doing your own page queries: Avoiding Custom Programming with Fielded Data
6. Generating Forms with the Form API
One of the real strengths of Drupal for programmers is the Form API, which has been in place with very little change through several versions of Drupal (it is not expected to change in Drupal 8 either). The basic idea of the Form API is that instead of writing the HTML for a form directly, you create a form-generating function that returns a structured form array. Form arrays have the same structure as the render arrays discussed in the previous section, and they contain information about the form elements along with their attributes (labels, sizes, etc.). Then you write separate functions that tell Drupal how to validate and process form submissions. The advantages of using the Form API over doing all of this in raw HTML and PHP are:
- You have to write a lot less code, since you’re letting Drupal handle all of the standard parts of form creation and submission.
- Your code will be easier to read and maintain.
- As with other parts of Drupal, your form will be alterable by other modules and the exact rendering is controlled by the theme system.
- When the form is rendered, Drupal adds a unique token to protect against cross-site scripting, and this is validated during form submission.
Here is a simple example of a form-generating function:
function mymodule_personal_data_form(&$form, &$form_state) {
$form = array();
// Plain text input element for first name.
$form['first_name'] = array(
'#type' => 'textfield',
'#title' => t('First name'),
);
// Plain text element for company name, only visible to some
// users.
$form['company'] = array(
'#type' => 'textfield',
'#title' => t('Company'),
// This assumes permission 'use company field' has been defined.
'#access' => user_access('use company field'),
);
// Some hidden information to be used later.
$form['information'] = array(
'#type' => 'value',
'#value' => $my_information,
);
// Submit button.
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Submit'),
);
return $form;
}
Notes:
- The notes about render arrays from Providing Page and Block Output also apply to form arrays.
- The
'value'
form element type can be used to pass information to the form validation and submission functions. This information is not rendered at all into the form’s HTML, in contrast to'hidden'
form elements (which render as HTML'input'
elements with type attribute'hidden'
), so they are more secure and can contain any PHP data structure. - Form elements have an
'#access'
property; if its value is FALSE, the form element is not presented to this user. If omitted, it defaults to TRUE. - The function arguments are
$form
(the form array) and$form_state
(an array of state information), followed by any additional input arguments that your form needs. The state information is carried through the form validation and submission process.
Creating a form array is just one step in the process of displaying and processing form input. To set up a form in your module, you will need to do the following:
- Choose an ID name for your form, which should typically start with your module name. For example, you might choose
mymodule_personal_data_form
. - Create a form generating function with the same name, which returns the form array (see previous example).
- If necessary, to validate form submissions, create a form validation function
mymodule_personal_data_form_validate()
. This function should callform_set_error()
if the submission is invalid, and it should do nothing if all is well. - Create a form submission function
mymodule_personal_data_form_submit()
to process the form submissions (save information to the database and so on). For example:
function mymodule_personal_data_form_submit(&$form, &$form_state) {
// The values submitted by the user are in $form_state['values'].
// They need to be sanitized.
$name = check_plain($form_state['values']['first_name']);
// Values you stored in the form array are also available.
$info = $form_state['values']['information'];
// Your processing code goes here, such as saving this to the database.
}
- Call
drupal_get_form('mymodule_personal_data_form')
to build the form—do not call your form-generating function directly. Your validation and submission functions will be called automatically when a user submits the form. If your form is the sole content of a page whose URL you are registering for in ahook_menu()
implementation, you can usedrupal_get_form()
as the page-generating function:
// Inside your hook_menu() implementation:
$item['mymodule/my_form_page'] = array(
'page callback' => 'drupal_get_form',
'page arguments' => array('mymodule_personal_data_form'),
// Don't forget the access information, title, etc.!
);
Further reading, examples, and resources:
- The Form example from the Examples for Developers project: http://drupal.org/project/examples
- Form-generating functions in Drupal core are listed in the "Form builder functions" topic on http://api.drupal.org
hook_menu()
: Registering for a URL- Drupal core’s main permission system
WARNING
Be careful about caching form output, because drupal_get_form()
adds verification information to the form output, and this information is invalid after some time has passed. If your form is displayed in a block, be sure that the block is not cached; this is not a problem if the form is part of the main page content.
Using confirmation forms
For security reasons, it is important to verify destructive actions connected with a URL. For instance, if your module has a URL that allows an administrative user to delete some data or a file, you should confirm this intention before deleting the data. The reason is that the user could have been tricked into visiting that URL by a hacking attack.
Drupal makes this type of confirmation easy. Here are the steps:
- Instead of registering your URL with a function that performs the deletion directly, use
drupal_get_form()
as the page callback, passing in the name of a form-generating function. - Have your form-generating function call
confirm_form()
to generate a confirmation form. - Perform the data deletion in the form submission function, which will only be called if the action is confirmed.
Here’s an example of the code:
// The menu router registration.
function mymodule_menu() {
// ...
// Assume there is a content ID number.
$items['admin/content/mycontent/delete/%'] = array(
'title' => 'Delete content item?',
'page callback' => 'drupal_get_form',
// Pass the content ID number to the form generating function.
'page arguments' => array('mymodule_confirm_delete', 4),
'access arguments' => array('delete mycontent items'),
);
// ...
}
// Form-generating function.
function mymodule_confirm_delete($form, $form_state, $id) {
// Save the ID for the submission function.
$form['mycontent_id'] = array(
'#type' => 'value',
'#value' => $id,
);
return confirm_form($form,
// You could load the item and display the title here.
t('Are you sure you want to delete content item %id?',
array('%id' => $id)),
// The URL path to return to if the user cancels.
'admin/content/mycontent');
}
// Form-submission function.
function mymodule_confirm_delete_submit($form, $form_state) {
// Read the ID saved in the form.
$id = $form_state['values']['mycontent_id'];
// Perform the data deletion.
// ...
// Redirect somewhere, for example the site home page.
drupal_goto('<front>');
}
Further reading and related topics:
- Principle: Drupal Is Secure; User Input Is Insecure
- Registering for a URL
- Generating Forms with the Form API
Altering forms
One of the more common reasons for someone building a Drupal site to create a custom module is to alter a form that is displayed by Drupal core or another module. Typically, the reason is that the site owner or site designer decides they find some of the text on the form confusing, they want some part of the form hidden, they want to change the order of fields on a form, or they want some additional validation to be done on form submissions. All of these alterations can be done easily by using hook_form_alter()
and related functions.
Before deciding you need a custom form-altering module, however, you should check to see if you can alter the form in a different way. Some core and contributed modules, for example, have configuration options that will let you alter labels on forms, and you can also use the String Overrides contributed module to make global text changes (such as changing all "Submit" buttons to say "Send"). If you want to add text at the top of a form, you might be able to use a block. Also, content editing forms are configurable in the administrative user interface: you can add help text to fields, change field labels, change the order of fields, add and remove fields from content types, and change the displayed name of the content type, among other settings. Each content type also has several settings for comments that affect the comment form, and there are many other examples of configuration options—so be sure to investigate before you start programming.
If you do need to alter a form via an alter hook in your custom module, here are the steps:
- Figure out the form ID of the form you are altering. The easiest way to do this is to look at the HTML source of the page with the form—the ID will be the "id" attribute of the HTML
form
tag. For this example, let’s assume the ID is'the_form_id'
. - Implement
hook_form_FORM_ID_alter()
by declaring a function calledmymodule_form_the_form_id_alter()
in your module.module file. Some forms, like field widget forms, use a different alter hook, such ashook_field_widget_form_alter()
; these hooks work the same way ashook_form_FORM_ID_alter()
. - Alter the form array in this function.
As an example, assume that you want to change the user registration form on a site so that it only allows people to register using email addresses within your company’s domain. The form ID in this case is 'user_register_form'
, and here is the alter function you would need to define:
// Form alteration.
function mymodule_form_user_register_form_alter(&$form, &$form_state, $form_id) {
// Change the label on the email address field.
$form['account']['mail']['#title'] = t('Company e-mail address');
// Add a validation function.
$form['#validate'][] = 'mymodule_validate_register_email';
}
// Validation function.
function mymodule_validate_register_email($form, $form_state) {
$email = $form_state['values']['mail'];
// Check that the email is within the company domain.
// If not, call form_set_error('mail', t('message goes here'));
}
Further reading and related topics:
Adding AJAX, JavaScript, and auto-complete to forms
A frequent need in web pages with forms is to have the form respond immediately to the user’s actions via JavaScript or AJAX; a common special case of this is an auto-complete text field (where suggestions pop up as the user types in a text field). Drupal has specific mechanisms in its Form API to handle auto-completes and other AJAX and JavaScript use cases.
To make a text input field have auto-complete behavior, here are the steps:
- Add an
'#autocomplete_path'
property to your'textfield'
form element array, with a URL path in it. This looks like:
// In a form-generating function:
$form['my_autocomplete_field'] = array(
'#type' => 'textfield',
'#autocomplete_path' => 'mymodule/autocomplete',
'#title' => t('My field label'),
);
Register for this URL path in your hook_menu() implementation, referencing a page callback function name. This looks like:
// In your hook_menu() implementation:
$items['mymodule/autocomplete'] = array(
'page callback' => 'mymodule_autocomplete',
// Use an appropriate permission here.
'access arguments' => array('access content'),
'type' => MENU_CALLBACK,
);
- Define the page callback function. It will take one argument (the string the user has typed), and should return an array of responses in JSON format, as in this example:
function mymodule_autocomplete($string = '') {
$matches = array();
if ($string) {
// Sanitize $string and find appropriate matches -- about 10 or fewer.
// Put them into $matches.
// ...
}
drupal_json_output($matches);
}
Generic JavaScript code and files can be added to a form by using the '#attached'
property. Drupal core includes the jQuery library, so you can make use of that when writing your JavaScript. Some examples:
// Attach a JavaScript file.
$form['#attached']['js'][] =
drupal_get_path('module', 'mymodule') . '/mymodule.js';
// Attach some in-line JavaScript code.
$form['#attached']['js'][] = array(
'type' => 'inline',
'data' => $my_code,
);
Generic AJAX responses to a form element require adding a '#ajax'
property to the form element, which defines a callback function to be called when the element changes, and the HTML ID of an area on the page to place the response. They are not covered in this book.
Further reading, examples, and resources:
- Registering for a URL
- There are several examples of auto-completes in Drupal core, such as the author name field in
node_form()
, which auto-completes on user names at path'user/autocomplete'
. This path is registered inuser_menu()
and its page callback function isuser_autocomplete()
. - There is also a complete standalone auto-complete example in the AJAX example in Examples for Developers (http://drupal.org/project/examples). File ajax_example_autocomplete.inc defines the forms and auto-complete callback functions, and function
ajax_example_menu()
in ajax_example.module registers the auto-complete paths. - The AJAX example in the Examples for Developers project also shows how to do more generic AJAX responses.
7. Programming with Entities and Fields
This part of the book covers programming with Drupal entities and fields. The sections on defining entity types, field types, widgets, and formatters are independent of one another, so skim the terminology section first, and then you can skip to the section you need. The code samples in this part of the book complement, but do not duplicate, the well-documented Entity and Field examples from the Examples for Developers project.
Further reading and examples:
- Avoiding Custom Programming with Fielded Data
- Entity example in Examples for Developers: http://drupal.org/project/examples
- Field example in Examples for Developers
WARNING
There is sometimes confusion between entity fields and database table fields. Within this section, the term "field" will always mean an entity field as defined in this section, and any references to database table fields will be clearly noted as such.
Terminology of Entities and Fields
As of Drupal version 7, Drupal core defines the concept of an entity, which stores data (such as content or settings) for a Drupal website. Drupal core version 7 defines four main user-visible entity types: node (for basic content), taxonomy (for classification of content), comment (for comments attached to content), and user (for user account information). Drupal 7 core also defines the file entity type, which is used internally to manage uploaded files. The Drupal API also allows modules to define additional entity types.
Each entity type can have one or more bundles, which are groupings of the items belonging to that entity type. For instance, the bundles of the node and comment entity types are content types, which an administrator can define within the Drupal user interface (modules can define them too); examples of content types are basic pages, news items, blog items, and forum posts. The bundles of the taxonomy entity type are vocabularies, and the items are the individual taxonomy terms; the user entity type has just one bundle, and its items are user accounts. Each entity item belongs to exactly one bundle.
Many entity types are fieldable, meaning that fields can be added to each bundle of the entity type (the fields can be different for each bundle within an entity type), and that each entity item will then have field values associated with it. Fields store additional information, which could be text, numbers, attached files, images, media URLs, or other data, and they can be single- or multiple-valued. Some entity types are not fieldable or do not allow administrators to change their fields; for example, an entity type used by a module for storing settings might define the fields and need to rely on those fields being present, so it would not want a user to be able to change them.
Each field has a field type, which defines what type of data the field stores; Drupal core defines several field types including one-line text, formatted long text, and images, and modules can define additional field types. When a field is attached to a bundle, it is known as a field instance, which encompasses the field type, an internal field identifier for programming use, a label, and other settings.
When a user is creating or editing an entity item, a field widget is used to receive the data on the entity editing form. For instance, a simple text field can use a normal HTML text input form field widget, or if its values are restricted to a small set, it could use an HTML select, radio buttons, or checkboxes. Drupal core defines the common widgets needed to edit its fields in standard ways, and modules can define their own widgets for their fields or other modules' fields. Widgets are assigned to each field instance when the field is attached to the bundle.
When an entity item is being displayed, a field formatter is used to display the data. For instance, a long text field could be formatted as plain text (with all HTML tags stripped out), passed through a text filter, or truncated to a particular length. Modules can define field formatters for their own or other modules' field types. Entity types can have view modes (such as full page and teaser for the node entity type), which allow entity items and their fields to be displayed differently under different circumstances. (Internal-use entity types do not need to have view modes, since these entity types' items are not directly displayed.) Formatters are assigned to each field instance for each view mode, or the field can be hidden in some or all view modes.
The data in entity items and their fields can be edited and translated, and many entity types keep track of revisions, making it possible to revert entity and field data to a prior version.
8. Defining an Entity Type
Before defining a new entity type, it is a good idea to think about whether you can instead use an existing entity type. For instance, if you need to store data that is basically site content, you should probably use the node entity type’s API to define a new content type instead of defining your own entity type. This will be a lot less work, because the core Node module includes administrative screens and other functionality, and it will also allow you to use the many add-on modules that work with nodes.
One good use case for defining a new entity type is to store groups of settings for a module, which would allow the settings to be internationalized. Another good use case is to define storage for a set of content for a site that needs a completely different permissions system and display mechanism from the Drupal core node entity type, where the additional programming that would be needed to coerce the node entity type into doing what you want would be greater than the programming needed to define a separate entity type.
The remainder of this section shows how to define a new entity type. You might want to download the Entity example from the Examples for Developers project (http://drupal.org/project/examples) and follow along there, or perhaps look at the code for one of the Drupal core entities.
DRUPAL 8
The code and process in this section is likely to be somewhat different in Drupal 8. In particular, it may not be necessary to use the contributed Entity API module, since some of its functionality may be included in Drupal core. Also, the page registration process will be different, so the code for that will need to change.
Step 1: Implement hook_entity_info()
The first step in defining a new entity type is to implement hook_entity_info()
in your module. In Drupal 7, it is advisable to make use of the contributed Entity API module, since it takes care of many standard operations for you; you may also want to make use of the Entity Construction Kit module. To use the Entity API module, you’ll need your module to have a dependency in its mymodule.info file:
dependencies[] = entity
With that taken care of, to define an entity type whose machine name is myentity, declare the following function in your mymodule.module file:
// Simple internal-use entity.
function mymodule_entity_info() {
$return = array();
$return['myentity'] = array(
// Define basic information.
'label' => t('Settings for My Module'),
'plural label' => t('Settings for My Module'),
'fieldable' => TRUE,
// Provide information about the database table.
'base table' => 'mymodule_myentity',
'entity keys' => array(
'id' => 'myentity_id',
'label' => 'title',
),
// Use classes from the Entity API module.
'entity class' => 'Entity',
'controller class' => 'EntityAPIController',
// Have Entity API set up an administrative UI.
'admin ui' => array(
'path' => 'admin/myentity',
),
'module' => 'mymodule',
'access callback' => 'mymodule_myentity_access',
// For content-type entities only.
'uri callback' => 'mymodule_myentity_uri',
);
return $return;
}
// For content-type entities, return the URI for an entity.
function mymodule_myentity_uri($entity) {
return array(
'path' => 'myentity/' . $entity->myentity_id,
);
}
Step 2: Implement hook_schema()
The next step, for both simple and more complex entity types, is to implement hook_schema()
in your mymodule.install file, to set up the database table for storing your entity information. The table name and some of the database field names need to match what you put into your hook_entity_info()
implementation, and you’ll also want a database field for language (assuming that you want your entity items to be translatable), and possibly additional database fields to keep track of when entity items are created and last updated. Here’s the schema for the internal-use entity type example:
function mymodule_schema() {
$schema = array();
$schema['mymodule_myentity'] = array(
'description' => 'Storage for myentity entity: settings for mymodule',
'fields' => array(
'myentity_id' => array(
'description' => 'Primary key: settings ID.',
'type' => 'serial',
'unsigned' => TRUE,
'not null' => TRUE,
),
'title' => array(
'description' => 'Label assigned to this set of settings',
'type' => 'varchar',
'length' => 200,
'default' => '',
),
'language' => array(
'description' => 'Language of this set of settings',
'type' => 'varchar',
'length' => 12,
'not null' => TRUE,
'default' => '',
),
// Consider adding additional fields for time created, time updated.
),
'primary key' => array('myentity_id'),
'indexes' => array(
'language' => array('language'),
// Add indexes for created/updated here too.
),
);
return $schema;
}
Step 3: Add pre-defined fields in hook_install()
If you are defining an entity type to use for settings, the next step is to attach fields to your entity bundle to store the settings you need. For a content-type entity, you may want to just let administrators add the fields in the administrative user interface (the Entity API module provides the URLs and screens), in which case you can skip this step. To add fields programmatically, implement hook_install()
in your mymodule.install file, using Drupal core Field API functions:
function mymodule_install() {
// Create a plain text field for a setting.
$field = field_create_field(array(
'field_name' => 'myentity_setting_1',
'type' => 'text',
'entity_types' => array('myentity'),
'locked' => TRUE,
'translatable' => TRUE,
));
// Attach the field to the entity bundle.
$instance = field_create_instance(array(
'field_name' => 'myentity_setting_1',
'entity_type' => 'myentity',
'bundle' => 'myentity',
'label' => t('Setting 1'),
'description' => t('Help for this setting'),
'required' => TRUE,
'widget' => array(
'type' => 'text_textfield',
),
'display' => array(
'default' => array(
'label' => 'above',
'type' => 'text_default',
),
),
));
// Repeat these two function calls for each additional field.
}
Step 4: Set up display
The next step is to set up your entity type so that its items can be displayed, which is only necessary for a content-type entity. Given the URL callback function mymodule_myentity_uri()
that was declared in Step 1, what we need to do is to register for the URL it returns, and tell Drupal to use the Entity API module’s entity_view()
function to display the entity:
function mymodule_menu() {
$items = array();
// Register for the URL that mymodule_myentity_uri() returns.
// The wildcard %entity_object in the URL is handled by the Entity
// API function entity_object_load().
$items['myentity/%entity_object'] = array(
// entity_object_load() needs to know what the entity type is.
'load arguments' => array('myentity'),
// This callback function, defined below, gives the page title.
'title callback' => 'mymodule_myentity_page_title',
// Use the Entity API function entity_view() to display the page.
'page callback' => 'entity_view',
// Pass in the loaded entity object from the URL.
'page arguments' => array(1),
// This access callback function is defined in Step 5.
// Its arguments are the operation being attempted and
// the loaded object.
'access callback' => 'mymodule_myentity_access',
'access arguments' => array('view', array(1)),
);
return $items;
}
// Title callback function registered above.
function mymodule_myentity_page_title($entity) {
return $entity->title;
}
Step 5: Set up editing and management
Both internal-use and content entity types need management pages and forms for creating and editing entity items. The Entity API module sets these up for you using the information that you provided in your hook_entity_info()
implementation (in step 1). There are several functions that you do need to define though:
- An access callback (which defines access permissions for your entity type). The function name is provided in your
hook_entity_info()
andhook_menu()
implementations. You’ll also need to implementhook_permission()
to define permissions. - A function to generate the entity item editing form, which must be called
myentity_form()
. A corresponding form submission handler is also needed. Your form needs to handle editing the title and the language, and then it needs to callfield_attach_form()
to let the Field module add the other fields to the form.
Here is the code for these functions:
// Define the permissions.
function mymodule_permission() {
return array(
'view myentity' => array(
'title' => t('View my entity content'),
),
'administer myentity' => array(
'title' => t('Administer my entities'),
),
);
}
// Access callback for Entity API.
function mymodule_myentity_access($op, $entity, $account = NULL) {
// $op is 'view', 'update', 'create', etc.
// $entity could be NULL (to check access for all entity items)
// or it could be a single entity item object.
// $account is either NULL or a user object.
// In this simple example, just check permissions for
// viewing or administering the entity type generically.
if ($op == 'view') {
return user_access('view myentity', $account);
}
return user_access('administer myentity', $account);
}
// Form-generating function for the editing form.
function myentity_form($form, $form_state, $entity) {
$form['title'] = array(
'#title' => t('Title'),
'#type' => 'textfield',
'#default_value' => isset($entity->title) ? $entity->title : '',
);
// Build language options list.
$default = language_default();
$options = array($default->language => $default->name);
if (module_exists('locale')) {
$options = array(LANGUAGE_NONE => t('All languages')) +
locale_language_list('name');
}
// Add language selector or value to the form.
$langcode = isset($entity->language) ? $entity->language : '';
if (count($options) > 1) {
$form['language'] = array(
'#type' => 'select',
'#title' => t('Language'),
'#options' => $options,
'#default_value' => $langcode,
);
}
else {
$form['language'] = array(
'#type' => 'value',
'#value' => $langcode,
);
}
$form['actions'] = array('#type' => 'actions');
$form['actions']['submit'] = array(
'#type' => 'submit',
'#value' => t('Save'),
'#weight' => 999,
);
field_attach_form('myentity', $entity, $form, $form_state, $langcode);
return $form;
}
// Form submission handler for editing form.
function myentity_form_submit($form, &$form_state) {
// Make use of Entity API class.
$entity = entity_ui_form_submit_build_entity($form, $form_state);
$entity->save();
// Redirect to the management page.
$form_state['redirect'] = 'admin/myentity';
}
Further reading, reference, related topics, and examples:
- Programming with Hooks in Modules and Themes
- Internationalizing User-Entered Text
- Setting Up Database Tables: Schema API and hook_update_N()
- Registering for a URL
- Auto-Loading, Arguments, and Wildcards in hook_menu()
- Checking Drupal Permissions
- Generating Forms with the Form API
- Entity example in Examples for Developers: http://drupal.org/project/examples. Note that this example is a bit different from what is illustrated here, because it does not make use of the contributed Entity API module.
- The Node example in Examples for Developers shows how to create a content type for the core Node entity in a module.
- Entity API module: http://drupal.org/project/entity
- Entity Construction Kit module: http://drupal.org/project/eck
9. Defining a Field Type
If you need to attach data to nodes or other entity types, you need to find a field type that stores this type of data. Between Drupal core and contributed modules, there are field types available for most of the common use cases for fielded content (plain text, numbers, formatted text, images, media attachments, etc.), so if you need to store a particular type of data, start by searching contributed modules for a field type that will suit your needs. Keep in mind that the field type only defines the stored data, while the formatter defines the display of the data and the widget defines the method for data input. So instead of defining a field, you may only need a custom widget or formatter for your use case. Here are several examples:
- You need to store plain text data, based on clicking in a region on an image or using a Flash-based custom input method. For this use case, use a core Text field for storage, and create a custom widget for data input.
- You need to select one of several predefined choices on input, and display a predefined icon or canned text on output based on that choice. For this use case, use a core Number field for storage, and a core Select widget for input (with text labels; you could also use a core Text field for storage). Create a custom formatter for display.
- You are creating a website that displays company profiles, using a "Company" node content type. For each company content item, you need to attach several office locations. For this use case, use the contributed Geofield, Location, Address Field, or another geographical information field module rather than defining your own custom field (search module category Location to find more).
- For this same Company content type, you need several related fields to be grouped together on input and display; for instance, you might want to group the company size, annual revenue, and other similar fields together under "Statistics." For this use case, use the Field Group contributed module to group the fields rather than creating a custom field type module.
- For this same Company content type, you need to keep track of staff people, where each staff person has a profile with several fields. For this use case, create a separate Staff node content type, and use the contributed Entity Reference or Relation module to relate staff people to companies or companies to staff people. Or, use the Field Collection contributed module to create a staff field collection that is attached to the Company content type.
- You have a field collection use case similar to the Staff of Company example, but you feel that it is general enough that many other websites would want to use this same field collection. In this case, it makes sense to create a custom field module and contribute it to drupal.org so that others can use it.
Assuming that you have decided you need a custom field module, here is an overview of how to define a field type:
- Implement
hook_field_info()
in your mymodule.module file to provide basic information about your field type (such as the label used to select it when attaching a field to an entity bundle in the administrative user interface). - Implement
hook_field_schema()
in your mymodule.install file to provide information about the data stored in your field. This defines database fields in a way similar tohook_schema()
, but it is not exactly the same. - Set up a widget for editing the field, and a formatter for displaying it (see following sections).
There are many field modules that are freely available for download from drupal.org, so rather than providing another programming example here, I’ll just suggest that you use one of the following as a starting point for finding examples of these two hooks in action:
- The Field example in the Examples for Developers project (http://drupal.org/project/examples), which has some extra documentation explaining what is going on.
- A Drupal core field module (Image, File, Text, List, Number, or Taxonomy, as of Drupal 7). The documentation for the two field hooks is also part of Drupal core, in the file modules/field/field.api.php (or look them up on http://api.drupal.org).
- Date, Link, or another contributed field module (search modules for category "Fields").
DRUPAL 8
The process of defining a field type is likely to change in Drupal 8, as the Field system is moving to the use of plugins.
Related topics in this book:
- Programming with Hooks in Modules and Themes
- Finding Drupal add-ons
- Defining an Entity Type
- Programming with Field Widgets
- Programming with Field Formatters
- Setting Up Database Tables: Schema API and hook_update_N()
10. Programming with Field Widgets
There are several reasons that you may need to do some programming with field widgets:
- If you have defined your own custom field type, you will need to define a widget for entering data for that field, or repurpose an existing widget for use on your field.
- You may need to define a custom input method for an existing field type.
- You may be want to repurpose an existing widget for use on a different field type.
DRUPAL 8
This is likely to change in Drupal 8, as the Field system is moving to the use of plugins.
Defining a field widget
To define a field widget, you need to implement two hooks in your mymodule.module file: hook_field_widget_info()
and hook_field_widget_form()
; the latter uses the Form API. If you’re defining a field widget for a custom field type that you’ve defined, I suggest going back to the field type module you used as a starting point, and using that module’s widget hook implementations as a starting point for your widget.
If you’re defining a new widget for an existing field, the following example may be helpful: assume that you want to define a widget for the core Text field that provides a custom method for input of plain text data, which could use Flash, JavaScript, or an image map to let the user click on a region on an image or map, and store their choice as a predefined text string in the field. As a proxy for the custom code, this example just uses an HTML select element (although Drupal core provides a select list widget for text fields, so if that is all you need, don’t define a custom widget). Here are the two hook implementations:
// Provide information about the widget.
function mymodule_field_widget_info() {
return array(
// Machine name of the widget.
'mymodule_mywidget' => array(
// Label for the administrative UI.
'label' => t('Custom text input'),
// Field types it supports.
'field types' => array('text'),
),
// Define additional widgets here, if desired.
);
}
// Set up an editing form.
// Return a Form API form array.
function mymodule_field_widget_form(&$form, &$form_state, $field,
$instance, $langcode, $items, $delta, $element) {
// Verify the widget type.
if ($instance['widget']['type'] == 'mymodule_mywidget') {
// Find the current text field value.
$value = isset($items[$delta]['value']) ? $items[$delta]['value'] : NULL;
// Set up the editing form element. Substitute your custom
// code here, instead of using an HTML select.
$element += array(
'#type' => 'select',
'#options' => array('x' => 'x value', 'y' => 'y value'),
'#default_value' => $value,
);
}
return $element;
}
Related topics in this book:
Repurposing an existing field widget
Since the module that defines the widget tells Drupal what field types it supports in its hook_field_widget_info()
implementation, if you want to repurpose an existing widget to apply to a different field type, in your mymodule.module file, you need to implement hook_field_widget_info_alter()
. This hook allows you to alter the information collected from all other modules' implementations of hook_widget_info_alter()
. For example:
function mymodule_field_widget_info_alter(&$info) {
// Add another field type to a widget.
$info['widget_machine_name']['field types'][] = 'another_field_type';
}
You may also need to alter the widget form so that the widget will work correctly with the new field type. There are two "form alter" hooks that you can use for this: hook_field_widget_form_alter()
, which gets called for all widget forms, and the more specific hook_field_widget_WIDGET_TYPE_form_alter()
, which gets called only for the widget you are interested in (and is therefore preferable).
Further reading and references:
- Programming with Hooks in Modules and Themes
- Generating Forms with the Form API
- Altering forms
- http://api.drupal.org is the best place to look up details of any of the hooks mentioned here.
11. Programming with Field Formatters
There are two reasons you might need to do some programming with field formatters:
- If you have defined your own custom field type, you will need to define a formatter that displays the data for that field, or re-purpose an existing field formatter.
- You may need to define a custom formatting method for an existing field type.
To define a field formatter, you need to implement two hooks in your mymodule.module file: hook_field_formatter_info()
and hook_field_formatter_view()
. If you’re defining a field formatter for a custom field type that you’ve defined, I suggest going back to the field type module you used as a starting point, and using that module’s formatter hook implementations as a starting point for your formatter. If you need to repurpose an existing field formatter for a different field type, use hook_field_formatter_info_alter()
, which works the same as hook_field_widget_info_alter()
described in the previous section.
If you’re defining a new formatter for an existing field, the following example may be helpful: assume that you have set up a Text field with several preselected values, and on output, you want to display an icon or some predefined text that corresponds to the preselected value.
Here are the hook implementations for this formatter example:
// Provide information about the formatter.
function mymodule_field_formatter_info() {
return array(
// Machine name of the formatter.
'mymodule_myformatter' => array(
// Label for the administrative UI.
'label' => t('Custom text output'),
// Field types it supports.
'field types' => array('text'),
),
// Define additional formatters here.
);
}
// Define how the field information is displayed.
// Return a render array.
function mymodule_field_formatter_view($entity_type, $entity,
$field, $instance, $langcode, $items, $display) {
$output = array();
// Verify the formatter type.
if ($display['type'] == 'mymodule_myformatter') {
// Handle multi-valued fields.
foreach ($items as $delta => $item) {
// See which option was selected.
switch ($item['value']) {
case 'predefined_value_1':
// Output the corresponding text or icon.
$output[$delta] = array('#markup' => '<p>' .
t('Predefined output text 1') . '</p>');
break;
// Handle other options here.
}
}
}
return $output;
}
DRUPAL 8
This is likely to change in Drupal 8, as the Field system is moving to the use of plugins.
Further reading, examples, and reference:
- Render arrays: Providing Page and Block Output
- There are many Drupal core examples of field formatters. You can find the core implementations of
hook_field_formatter_info()
by looking this up on http://api.drupal.org. - A contributed module that I wrote, Simple Google Maps (http://drupal.org/project/simple_gmap) is another good example to look at.
- The Field example from Examples for Developers (http://drupal.org/project/examples) is also good.
12. Creating Views Module Add-Ons
[Modified to Drupal 9]
The contributed Views module is, at its heart, a query engine for Drupal that can be used to make formatted lists of pretty much any data stored in the Drupal database.
The base Views module and other contributed Views add-on modules provide the ability to query Node module content items, comments, taxonomy terms, users, and other data; to filter and sort the data in various ways; to relate one type of data to another; and to display the data using a list, table, map, and other formats.
In addition, custom entities and fields that you have defined are well-supported by Views, and Views uses the Field system’s formatters to display field data.
This section of the book provides an overview of how to create your own Views abilities and plugins for the following purposes:
- Querying additional types of data
- Relating new data to existing data types
- Formatting the output in additional ways
- Providing default Views that site builders can use directly or adapt to their needs
The views module is now part of the Drupal core and is activated by default, so there is no need for separate installation and different view modules, you can integrate them into it almost everything in your system.
The Views module has been added to Drupal core in Drupal version 8, and in the process, it has adopted the Drupal 8 core plugin system.
Don't forget to flash the views cache after every change in code.
13. Views Programming Terminology and Output Construction
[Modified to Drupal 9]
You probably need to understand how Views uses handlers and plugins to construct its output. Here is a conceptual overview (the actual order of Views performing these steps may be a bit different):
- Views take all of the relationship, filter, and field definitions in the View and create and execute a database query.
- If the View uses fields, each field is run through its field display handler.
- Each row in the database query result is run through a row-style plugin if one is in use.
- Row style plugins format the rows.
- The formatted rows are handed off to the style plugin, which combines the rows into a larger output. The base Views module includes style plugins for HTML tables, HTML unordered lists, and so on, and each style plugin is compatible with a certain subset of row style plugins (for instance, an HTML list can use either a field row style or a row style that displays the entire entity, while an HTML table does not use a row style).
- The formatted output is handed off to the overall display plugin; examples of display plugins are the standard Views Page, Block, and Feed displays.
You can interact with Views in several ways:
- Provide plugins: Views plugins govern nearly every aspect of views, including querying (sorting, filtering, etc.) and display (at several levels of granularity, ranging from the entire view to the details of a field). See the Views plugins topic for more information.
- Provide data: Data types can be provided to Views by implementing hook_views_data(), and data types provided by other modules can be altered by implementing hook_views_data_alter(). To provide views data for an entity, create a class implementing \Drupal\views\EntityViewsDataInterface and reference this in the "views_data" annotation in the entity class. You can autogenerate big parts of the integration if you extend the \Drupal\views\EntityViewsData base class. See the Entity API topic for more information about entities.
- Implement hooks: A few operations in Views can be influenced by hooks. See the Views hooks topic for a list.
- Theming: See the Views templates topic for more information.
14. Providing a New Views Data Source
[Modified to Drupal 9]
A common need in a custom module is to integrate it with Views, which is to say, to make the data managed by the module available to Views.
If your data is stored in entities or fields, and you have used the Entity API module to define a custom entity or attached fields to an existing entity (whether they are Drupal core fields or fields that you have defined), then your data will be integrated with Views without any further work.
Alternatively, if your module stores its data in a custom database table, then you can integrate it with Views by defining a new Views data source (also known as a base table).
The data source can then be selected when setting up a new View: instead of selecting a Node-module Content view (the default), you can select your data source instead, or if appropriate, you can create a View using one data type, and use a relationship to join it with your data type.
Adding data sources is described in this section; the next section describes how to add fields and relationships to existing data sources.
To define a Views data source, start by creating a file called <mymodule.views.inc>, which should be located in the module root folder.
In this file, implement hook_views_data(),
The return value of this hook is an associative array of arrays, where the outermost array key is the database table name, and the array value gives information about that database table, the way it relates to other data tables known to Views, and the database table fields that can be used for filtering, sorting, and field display.
read more about this API at drupal.org:hook_views_data
Here is an example showing a working subset of the return value of this hook, which I wrote for the drupalvip_visits module:
ffunction drupalvip_visits_views_data() {
$data['drupalvip_visits_nid_summary']['table']['group'] = t('Visits Summary');
$data['drupalvip_visits_nid_summary']['table']['provider'] = 'drupalvip_visits';
$data['drupalvip_visits_nid_summary']['table']['base'] = [
// Identifier (primary) field in this table for Views.
'field' => 'nid',
// Label in the UI.
'title' => t('Visits summary Node ID'),
// Longer description in the UI. Required.
'help' => t('Visits summary table related to nodes.'),
'weight' => -10,
];
$data['drupalvip_visits_nid_summary']['table']['join'] = [
// Within the 'join' section, list one or more tables to automatically
// join to. In this example, every time 'node_field_data' is available in
// a view, 'example_table' will be too. The array keys here are the array
// keys for the other tables, given in their hook_views_data()
// implementations. If the table listed here is from another module's
// hook_views_data() implementation, make sure your module depends on that
// other module.
'node_field_data' => [
// Primary key field in node_field_data to use in the join.
'left_field' => 'nid',
// Foreign key field in example_table to use in the join.
'field' => 'nid',
],
];
$data['drupalvip_visits_nid_summary']['nid'] = [
'title' => t('Visits Node Id'),
'help' => t('Visits summary related to node '),
// Define a relationship to the node_field_data table, so views whose
// base table is example_table can add a relationship to nodes. To make a
// relationship in the other direction, you can:
// - Use hook_views_data_alter() -- see the function body example on that
// hook for details.
// - Use the implicit join method described above.
'relationship' => [
// Views name of the table to join to for the relationship.
'base' => 'node_field_data',
// Database field name in the other table to join on.
'base field' => 'nid',
// ID of relationship handler plugin to use.
'id' => 'standard',
// Default label for relationship in the UI.
'label' => t('Summary node'),
],
];
// Numeric field, exposed as a field, sort, filter, and argument.
$data['drupalvip_visits_nid_summary']['counter'] = [
'title' => t('Visits counter field'),
'help' => t('visits summary counter field.'),
'field' => [
// ID of field handler plugin to use.
'id' => 'numeric',
],
'sort' => [
// ID of sort handler plugin to use.
'id' => 'standard',
],
'filter' => [
// ID of filter handler plugin to use.
'id' => 'numeric',
],
'argument' => [
// ID of argument handler plugin to use.
'id' => 'numeric',
],
];
return $data;
}
Your hook_views_data()
implementation refers to the names of handler classes for field display, filtering, sorting, and contextual filtering.
This is it you are done, now you can select the table column from the view editor as a field.
few main details that you should be aware of:
- 'drupalvip_visits_nid_summary' is the name of the table
- group is the field filter during field selection.
- base gives you the option to select the field
- join gives you the way to link the field with the current results row
- the primary field for this view must be set with relationship
- there is standard handles for the field, filter, sort, argument and area,
like: numeric, standard, string, boolean, yes-no, date, text
15. Adding Fields and Relationships to an Existing Views Data Source
In addition to providing completely new Views data sources, as described in the previous section, some custom modules may need to provide additional fields or relationships to existing Views data sources.
A common use case would be that your module adds some data to Node module content items, and you would like this data to be available to Views defined on the Node table, either as a field or through a relationship.
This section tells you how to accomplish telling Views about your additional data; it assumes you have already followed the steps in Setting Up Your Module for Views.
To add a field or relationship to an existing Views data source, implement hook_views_data_alter()
it in your <mymodule.views.inc> file, which must be located in the root of your module.
The view fields are now implemented in hook_views_data_alter() This hook takes as its argument, by reference, the array of the information returned by all.
Editor note:
In order to simplify the way to integrate a module with views, hook_views_api() has been removed. This means that you can no longer place the .views.inc file in a subdirectory. It now must be in a .views.inc file at the root of your module. https://www.drupal.org/node/1875596
This example from the API module illustrates the two most common things you can do with this hook:
- Adding a relationship from an existing table to your table.
In this example, the reason is that the API module allows users to comment on API documentation pages, so if someone was creating a view whose base data source commented, they might want to add a relationship to the API documentation page that was being commented upon.
Relationships are defined on the base table side, so this relationship needs to be added to the comment data source. - Adding an existing automatic join to your table (automatic joins provide additional database fields to a data source without having to add a relationship to the View).
Again, this example is comment-related: thenode_comment_statistics
table is normally automatically joined to thenode
base table, so that the number-of-comments field is available on node content items.
This example adds the automatic join to theapi_documentation
base table as well.
Here is the code:
function api_views_data_alter(&$data) {
// Add a relationship to the Comment table.
$data['comment']['did'] = array(
'title' => t('Documentation ID'),
'help' => t('The ID of the documentation object the comment is a reply to.'),
'relationship' => array(
// Table to join to.
'base' => 'api_documentation',
// Field in that table to join with.
'base field' => 'did',
// Field in the comment table to join with.
'field' => 'nid',
'handler' => 'views_handler_relationship',
'label' => t('API documentation object'),
'title' => t('API documentation object'),
'help' => t('The ID of the documentation object the comment is a reply to.'),
),
);
// Add an automatic join between the comment statistics table and
// the API documentation table.
$data['node_comment_statistics']['table']['join']['api_documentation'] =
array(
// Use an inner join.
'type' => 'INNER',
// Field to join on in the API documentation table.
'left_field' => 'did',
// Field to join on in the comment statistics table.
'field' => 'nid',
);
}
.
Editor notes:
This section was written according to drupal 7.3, few things changed in Drupal 7, 8 and 9
16. Providing a Display Plugin to Views
Another common custom Views programming need is to create new style or row style plugins.
Here are the steps you’ll need to follow, assuming you have already followed the steps in Setting Up Your Module for Views:
- Implement
hook_views_plugins()
in your <mymodule.views.inc> file, which must be located in your module root.
The return value tells Views about your style and row style plugin classes.
Editor note:
In order to simplify the way to integrate a module with views, hook_views_api() has been removed. This means that you can no longer place the .views.inc file in a subdirectory. It now must be in a .views.inc file at the root of your module. https://www.drupal.org/node/1875596
For instance, you might have:
function mymodule_views_plugins() {
return array(
// Overall style plugins
'style' => array(
// First style plugin--machine name is the array key.
'mymodule_mystyle' => array(
// Information about this plugin.
'title' => t('My module my style'),
'help' => t('Longer description goes here'),
// The class for this plugin and where to find it.
'handler' => 'mymodule_views_plugin_style_mystyle',
'path' => drupal_get_path('module', 'mymodule') . '/views/plugins',
// Some settings.
'uses row plugin' => TRUE,
'uses fields' => TRUE,
),
// Additional style plugins go here.
),
// Row style plugins.
'row' => array(
// First row style plugin -- machine name is the array key.
'mymodule_myrowstyle' => array(
// Information about this plugin.
'title' => t('My module my row style'),
'help' => t('Longer description goes here'),
// The class for this plugin and where to find it.
'handler' => 'mymodule_views_plugin_row_myrowstyle',
'path' => drupal_get_path('module', 'mymodule') . '/views/plugins',
// Some settings.
'uses fields' => TRUE,
),
// Additional row style plugins go here.
),
);
}
- Create a file for each style or row style plugin class. For example, if you declared that your class is called
mymodule_views_plugin_style_mystyle
, create a file with the name mymodule_views_plugin_style_mystyle.inc. Put this file in the directory you specified in yourhook_views_plugins()
implementation (typically, plugins are either put into your Views directory or a subdirectory called plugins). - List each class-containing include file in your mymodule.info file, with a line like:
files[] = views/plugins/mymodule_views_plugin_style_mystyle.inc
- In each class-containing include file, declare your plugin class, which should extend either the
views_plugin_style
,views_plugin_row
, or another subclass of these classes. You will need to override theoption_definition()
andoptions_form()
methods, if your plugin has options, and (oddly enough), that is usually all you’ll need to override because the work of formatting the output is done in the theme layer. - Set up
hook_theme()
to define a theme template and preprocessing function for your plugin. The theme template goes into the template directory specified in yourhook_views_info()
implementation, and the name corresponds to the machine name you gave your plugin (in this example, mymodule-mystyle.tpl.php or mymodule-myrowstyle.tpl.php).
Further reading and examples of plugin classes:
- Making Your Output Themeable
- The Views module itself has several general-purpose plugin examples (http://drupal.org/project/views). The
hook_views_plugins()
implementation is in the file includes/plugins.inc. The plugin class files are in the directory plugins and are named views_plugin_style*.inc and views_plugin_row*.inc. The template files (named with the array keys from the hook implementation) are in the theme directory, and theme preprocessing functions are in the file theme.inc in the theme directory. - There are several contributed module projects that provide Views plugin add-ons. Commonly used examples are Views Data Export (http://drupal.org/project/views_data_export), Calendar (http://drupal.org/project/calendar), and Views Slideshow (http://drupal.org/project/views_slideshow). You can find others by browsing category "Views" at http://drupal.org/project/modules (but note that only some of the Views-related modules in that list provide style or row plugins).
Editor notes:
This section was written according to drupal 7.3, few things changed in Drupal 7, 8 and 9
17. Providing Default Views
Once you have your module’s data integrated with Views, either because it is stored in entities using the Entity API module, core entities, or fields or because you have provided a custom data source as described in the sections above, you may want to supply users of your module with one or more default Views. These Views can be used to provide administration pages for your module or sample output pages, and they can either be enabled by default or disabled by default (administrators can enable and modify them as needed).
Here are the steps to follow to provide one or more default Views in your module, assuming you have already followed the steps in Setting Up Your Module for Views:
- Create a View using the Views user interface.
- From the Views user interface, export the View. This will give you some PHP code starting with
$view = new view;
. - If you want to have the View disabled by default, find the line near the top that says
$view->disabled = FALSE;
and change it toTRUE
. - Put the exported Views' PHP code into this hook implementation:
function mymodule_views_default_views() {
// We'll return this array at the end.
$views = array();
// Exported view code starts here.
$view = new view;
// ... rest of exported code ...
// Exported code ends here.
// Add this view to the return array.
$views[$view->name] = $view;
// You can add additional exported views here.
return $views;
}
Related topics:
Editor notes:
This section was written according to drupal 7.3, few things changed in Drupal 7, 8 and 9
18. Creating Rules Module Add-Ons
The contributed Rules module lets you set up actions to respond to events under certain conditions on your website. For example, you could respond to a new comment submission event, under the condition that the submitter is an anonymous user, by sending the comment moderator an email message. The Rules module also gives you the ability to combine conditions via Boolean and/or logic, so that you can be quite specific about when to respond to a given event when configuring Rules (again, without any programming on your part). Furthermore, Rules actions can have parameter inputs and they can provide data outputs; this means that you can chain actions together, with the output data provided by one action feeding in as a parameter for the next action. It is also possible within Rules to loop actions, so if an action provides a list as output, you can execute a single-parameter action for each data item in the list.
The Rules module comes with a set of standard events, conditions, and actions, including (via integration with the Entity API module) many related to entities and fields. This means that if your module stores its custom data in an entity or fields, you will be able to use Rules with your module’s data without any further programming. But you may occasionally find that you need to do some programming to add additional functionality to the Rules module; in my experience, this has always been to add custom actions to Rules; this is described in Providing Custom Actions to Rules.
Rules that you compose using the Rules user interface can be exported into PHP code and shared with others. One way to do this is by using the Features contributed module. But sometimes Features is cumbersome, and there is a direct method for exporting and sharing Rules described in Providing Default Rules below.
Further reading and references:
- Rules module: http://drupal.org/project/rules
- Features module: http://drupal.org/project/features
- Programming with Entities and Fields
- For programming with Rules not covered in this book, see the rules.api.php file distributed with the Rules module for documentation. For instance, it is possible to set up custom conditions and events, although it is unlikely you will ever need to, given the flexibility of the base Rules module.
19. Providing Custom Actions to Rules
Rules actions are responses to events and conditions detected by the Rules module, and they can take many forms. Built-in actions that come with the Rules module include sending an email message, displaying a message or warning, and altering content (publishing, unpublishing, etc.). As mentioned in the introduction to this section, you can chain together the input and output of several actions and you can also use action output for looping, so some so-called "actions" in Rules are really more like processing steps that exist solely to provide input for other actions that are actually doing the work (modifying content, sending email, etc.).
Whether you are defining a processing step type of action or one that actually does work itself, here are the steps you will need to follow to provide a custom action to the Rules module:
- Create a file called mymodule.rules.inc in your main module directory, and implement
hook_rules_action_info()
in that file. The return value tells Rules about your custom action: its machine name, a human-readable label for the Rules user interface, the data that it requires as parameters (if any), and the data that it provides as output (if any). - Create a callback function that executes your action. You can either put this function in your mymodule.rules.inc file, or you can implement
hook_rules_file_info()
and specify a separate include file for callbacks. The name of the function is the same as the machine name you gave the action.
As an example, here is the code to provide a processing-step-type action that takes a content item as input, and outputs a list of users (you could then loop over the output list and send each user an email message, for instance):
// Optional hook_rules_file_info() implementation.
// This specifies a separate file for callback functions.
// It goes into mymodule.rules.inc.
function mymodule_rules_file_info() {
// Leave off the .inc file name suffix.
return array('mymodule.rules-callbacks');
}
// Required hook_rules_action_info() implementation.
// This gives information about your action.
// It goes into mymodule.rules.inc.
function mymodule_rules_action_info() {
$actions = array();
// Define one action.
// The array key is the machine name of the action.
$actions['mymodule_rules_action_user_list'] = array(
// Label and group in the user interface.
'label' => t('Load a list of users related to content'),
'group' => t('Mymodule custom'),
// Describe the parameter.
'parameter' => array(
'item' => array(
'label' => t('Content item to use'),
// Entity type (Node module).
'type' => 'node',
// Restrict to a particular content type.
// (optional)
'bundles' => array('my_content_type'),
),
// You can add additional parameters here.
),
// Describe the output.
'provides' => array(
'user_list' => array(
'type' => 'list<user>',
'label' => t('List of users related to content'),
),
// You could describe additional output here.
),
);
// Define other actions here.
return $actions;
}
// Required callback function that performs the action.
// This goes in mymodule.rules.inc, or the file defined in
// the optional hook_rules_file_info() implementation.
// The function name is the action's machine name.
function mymodule_rules_action_user_list($item) {
// Read some information from $item.
// ...
// Do some query to relate this to user IDs.
// ...
// As a proxy for your real code, return a list of one
// user -- the author of the content.
$ids = array($item->uid);
// Load the users and return them to Rules.
return array('user_list' => user_load_multiple($ids));
}
Further reading:
20. Providing Default Rules
In some cases, you may find that you want to put Rules you have created into PHP code so that you can use them on another site. You have three choices for how to do this:
- Define the Rule’s event, conditions, and reactions using pure PHP code. This is somewhat documented in the rules.api.php file distributed with the Rules module, but is not particularly recommended, since you’ll need to read a lot of Rules module code to figure out the machine names of all the components your rule needs to use, and there isn’t really any documentation on how to put it all together.
- Create the Rule using the Rules user interface, and use the contributed Features module to manage the export.
- Create the Rule using the Rules user interface, export the rule definition to a text file, and use the
rules_import()
function to read it into code. This process is recommended if you do not want to use the Features module; the process is described in the coming section.
Assuming you want to use the export-to-text option, here are the steps to follow:
- In the Rules user interface, create your Rule. If you do not want the rule to be active by default, be sure to deactivate it.
- From the main page of the Rules user interface, export your rule, and save the exported text in a file. Put this file in subdirectory rules of your main module directory, and name it sample_rule.txt (for example).
- Implement
hook_default_rules_configuration()
in a file named mymodule.rules_defaults.inc, with the following code:
function mymodule_default_rules_configuration() {
$configs = array();
// Read in one exported Rule.
$file = drupal_get_path('module', 'mymodule') . '/rules/sample_rule.txt';
$contents = file_get_contents($file);
$configs['mymodule_sample_rule'] = rules_import($contents);
// Add other Rules here if desired.
return $configs;
}
21. Drupal Development Tools
The Drupal community has developed several very useful development tools that can help you avoid making programming mistakes, adhere to the Drupal coding standards, and debug your Drupal sites and Drupal code. Here is a list of the most useful development tools:
-
Coder
-
A set of modules that points out coding errors and violations of the Drupal coding standards, and also helps you upgrade your code from one Drupal version to another. Some developers have, in the past, preferred to use the Drupal Code Sniffer project, which has now been merged into the Coder project. (http://drupal.org/project/coder)
-
Devel
-
A set of modules containing a number of helpful functions for debugging and developing modules and themes, as well as a fake lorem ipsum content generator for testing. ...