Now that you have learned the basic principles of Drupal programming and how to avoid making common Drupal programming mistakes, it’s time to put your programming skills to work! This section of the book covers special topics in Drupal programming that you can use to enhance websites built with Drupal. My advice would be to skim the sections of this chapter now so you know what is there, and then read them in more detail when you need them.
I chose these particular examples because they are all things I’ve actually needed to do in my freelance work as a Drupal site builder and my volunteer work programming for the Drupal project. Actually, they cover nearly all of the programming I’ve needed to do as a freelance site builder, given that I tend to use existing contributed modules wherever I can, rather than jumping straight into programming at every opportunity. And in the realm of volunteer work that I’ve done for the Drupal project, which has included writing and maintaining contributed modules on drupal.org, custom programming for the drupal.org website, and providing patches for Drupal core and contributed modules, the Registering for URLs and Displaying Content section covers the common threads.
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, and this book’s guide on how to make the best use of it: Using api.drupal.org
How Drupal Handles URL Requests contains an overview of how Drupal 7 handles URL requests and returns 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, by 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 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 where under which context conditions.
To register to provide the main page content for a URL, define a menu router
entry by implementing hook_menu()
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.
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.
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:
-
Look up
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
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.
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.
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
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.
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:
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
)
{
=
$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:
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.
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.
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.
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.
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
,
);
}
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
;
}
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.
}
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
;
}
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
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:
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.
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:
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.
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.
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. But even with all of that ability, you may sometime have needs not covered by Views and existing add-on modules.
This section of the book provides an overview of how to create your own Views module add-ons 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
Each topic below is independent, except that they all depend on having your module set up so that Views recognizes it. So, start by reading Views Programming Terminology and Output Construction and Setting Up Your Module for Views, and then skip to the section that you need. Also, some of the topics in this section assume knowledge of advanced usage of the Views user interface, such as relationships and contextual filters.
Further reading and references:
- Views module: http://drupal.org/project/views
- Avoiding Custom Programming with Fielded Data
- Programming with Entities and Fields
- For Views programming not covered in this book, there is documentation in the views.api.php file distributed with the Views module.
Drupal 8
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. So, while the philosophy of the examples here will probably remain the same, the details will be somewhat different.
The Views module version 7.x-3.x uses the term handler to refer to a class that takes care of field display, sorting, filtering, contextual filtering, or relationships. In contrast, the term plugin in Views 7.x-3.x is used to denote a class related to the overall display of the View, and other classes that take care of the basic functions of Views. The distinction between handlers and plugins is somewhat arbitrary, but since they’re declared and defined differently, it’s important to know about.
Drupal 8
The terminology of plugins versus handlers described here will likely change in the Drupal 8 version of Views, which will be part of Drupal core and adopt the Drupal core plugin system.
Besides this terminology, in order to program effectively with Views, you also 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 takes all of the relationship, filter, and field definitions in the View and creates and executes 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.
The first step in any Views-related programming is to make sure Views recognizes
your module, by implementing the Views hook hook_views_api()
in your
mymodule.module file. To do that, you’ll need to choose
a location for some additional files; typically, you make a sub-directory called
views in your module directory to hold all of the Views files, and if you
are doing a lot of output formatting, optionally another sub-directory for the
theme template files. The hook_views_api()
implementation tells Views this
information. For example:
function
mymodule_views_api
()
{
return
array
(
// Which version of the Views API you are using. For Views 7.x-3.x, use 3.
'api'
=>
3
,
// Where your additional Views directory is.
'path'
=>
drupal_get_path
(
'module'
,
'mymodule'
)
.
'/views'
,
// Where Views-related theme templates are located.
'template path'
=>
drupal_get_path
(
'module'
,
'mymodule'
)
.
'/views/templates'
,
);
}
Any files that contain Views hooks (see sections below) will also need to be added to your mymodule.info file:
files[] = views/mymodule.views.inc
In addition, if Views integration is fundamental to the functioning of your module, you can make Views a module requirement by adding the following line to your mymodule.info file:
dependencies[] = views
Finally, Views caches information from its hooks, so whenever you implement a new Views hook, modify a Views hook implementation, or add new Views-related files to your module, you need to clear the Views cache. You can do this on the Views advanced settings page, where you can also disable Views caching while you’re developing; the Views cache is also cleared when you clear the main Drupal cache.
Further reading:
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, assuming you have
already followed the steps in Setting Up Your Module for Views,
start by creating a file called
mymodule.views.inc, which must be located in the Views directory
specified in your hook_views_api()
implementation. 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. Here is an example showing a working
subset of the return value of this hook, which I wrote for the contributed API
module:
// The table name from hook_schema() is 'api_documentation'.
$data
[
'api_documentation'
]
=
array
(
// Information about the table itself.
'table'
=>
array
(
// Group used for the fields in this table.
'group'
=>
t
(
'API documentation'
),
'base'
=>
array
(
// The primary key of this table.
'field'
=>
'did'
,
// The label shown when selecting this table in Views.
'title'
=>
t
(
'API documentation'
),
'help'
=>
t
(
'API documentation objects'
),
'weight'
=>
20
,
),
),
// Information about the primary key field. It cannot be used
// for display, filter, or sorting.
'did'
=>
array
(
'title'
=>
t
(
'Documentation ID'
),
// Relationship with the Comment table.
'relationship'
=>
array
(
'base'
=>
'comment'
,
// Field in the comment table that corresponds to this field.
'base field'
=>
'nid'
,
'handler'
=>
'views_handler_relationship'
,
'label'
=>
t
(
'Comments'
),
'title'
=>
t
(
'All comments'
),
'help'
=>
t
(
'All comments on the documentation object'
),
),
),
// Another field.
'object_name'
=>
array
(
'title'
=>
t
(
'Object name'
),
'help'
=>
t
(
'Name of this object'
),
// How this field can be used for display.
'field'
=>
array
(
// This uses a custom field display handler class.
'handler'
=>
'api_views_handler_field_api_linkable'
,
'click sortable'
=>
TRUE
,
),
// Sorting is handled by the generic Views sort handler.
'sort'
=>
array
(
'handler'
=>
'views_handler_sort'
,
'click sortable'
=>
TRUE
,
),
// Filtering is handled by the generic Views string filter handler.
'filter'
=>
array
(
'handler'
=>
'views_handler_filter_string'
,
),
// Contextual filtering (formerly known as using an "argument")
// is handled by the generic Views string argument handler.
'argument'
=>
array
(
'handler'
=>
'views_handler_argument_string'
,
),
),
);
Your hook_views_data()
implementation refers to the names of handler classes
for field display, filtering, sorting, and contextual filtering. You have the
choice to use the standard handler classes provided by the Views module (located
in the handlers subdirectory of the Views download) or a class you create. To
create your own handler class, here are the steps:
-
Create a handlers subdirectory inside the Views directory
specified in your
hook_views_api()
implementation. -
In that subdirectory, create an include file named for your handler class,
such as mymodule_views_handler_field_myfield.inc if your class is called
mymodule_views_handler_field_myfield
. Note that in contrast with the usual Drupal coding standards, for historical reasons Views-related classes are generally defined using all lowercase names with underscores, rather than CamelCase names. -
In that file, extend an existing Views handler class of the same type (field,
filter, and so on),
and override the appropriate methods to define the actions of your class. For
instance, if you are making a field handler, you’ll need to extend the
views_handler_field
class, and override therender()
method; if your field handler has display options, you’ll also need to override theoption_definition()
andoptions_form()
methods. - Add the handler file to your mymodule.info file:
files[] = views/handlers/mymodule_views_handler_field_myfield.inc
Further reading, examples, and references:
- Programming with Entities and Fields
- Setting Up Database Tables: Schema API and hook_update_N()
- The Views module’s handlers directory contains general purpose handlers. Use these as starting points when defining your own handlers.
- The API module also has some good examples of handler classes: http://drupal.org/project/api
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()
in your mymodule.views.inc file, which must be
located in the Views directory specified in your hook_views_api()
implementation. This hook takes as its argument, by
reference, the array of information returned by all modules' hook_views_data()
implementations, so that your module can alter or add to the information.
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 was comments, 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: the
node_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'
,
);
}
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 the Views directory specified in yourhook_views_api()
implementation. The return value tells Views about your style and row style plugin classes. 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).
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 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
. -
Implement
hook_views_default_views()
in a file called mymodule.views_default.inc, which must be located in the Views directory specified in yourhook_views_api()
implementation. - 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:
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.
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:
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 a 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
;
}
Further reading and references:
Get Programmer's Guide to Drupal now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.