Before our field can save user provided data we need to use hook_field_validate() and hook_field_is_empty() to perform validation on field data. In certain context values like 0, FALSE, and NULL can all be a valid value. In fact, even a blank space could be valid input for a field. As such, it's not possible for Drupal to know what constitutes an empty state for a field without a little extra help. The same is true for checking if the value of a field is valid.
Examples:
/**
* Implementation of hook_field_is_empty().
*/
function rgb_field_is_empty($item, $field) {
if (empty($item['rgb']) || empty($item['label'])) {
return TRUE;
}
return FALSE;
}
/**
* Implements hook_field_validate().
*/
function rgb_field_validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors) {
foreach($items as $delta => $item) {
if (!empty($item['rgb'])) {
// Make sure it's 6 characters.
if (drupal_strlen($item['rgb']) !== 6) {
$errors[$field['field_name']][$langcode][$delta][] = array(
'error' => 'rgb_length',
'message' => t('%name: the hex color must be 6 characters.', array('%name' => $instance['label'])),
);
}
}
}
}
Additional resources
In order to allow for maximum flexibility in our widget we can add widget settings that apply to each individual instance of our field. By implementing hook_field_widget_settings_form() and then refactoring some of our existing code we can make it possible for a site administrator to set a custom prefix value for the label field, which the Field API will store for as part of the field instance's settings and we can use it when creating our widget.
Implementations of hook_field_widget_settings_form()
return a Form API array that represents the element or elements that you would like to add to the widget settings form. Values are automatically serialized and saved as part of the field's instance configuration and can be accessed by the passed in $instance
array's $instance['widget']['settings']
key.
Example:
/**
* Implements hook_field_widget_settings_form().
*/
function rgb_field_widget_settings_form($field, $instance) {
$element = array(
'rgb_label_text' => array(
'#type' => 'textfield',
'#title' => t('Alternate label text'),
'#description' => t('If an alternate label text is provided it will be used in place of the default "Color" title for the label field.'),
'#default_value' => isset($instance['widget']['settings']['rgb_label_text']) ? $instance['widget']['settings']['rgb_label_text'] : '',
),
);
return $element;
}
Additional resources
Displaying that data that was collected and saved for our a field requires creating a field formatter. Formatters consist of an implementation of hook_field_formatter_info() and hook_field_formatter_view(). The former provides meta-data about the formatter for the Field API and the latter does the heavy lifting of determining what the output is actually going to look like.
A module can define more than one field formatter.
Implementations of hook_field_formatter_view()
return a renderable array representing the content you would like to display to the end user. Generally this is an escaped version of content provided by a site administrator with some additional HTML formatting applied.
Depending on the values being output you would likely want to also use a theme function for your field formatter by implementing hook_theme() and providing either a theme() function that can be overriden or a template file. We're not going to cover that in this lesson since the focus here is on the technical requirements for impelementing a field formatter. Howerver, I would say that it's best practcie to always output any HTML with a theme function. You can find out more about creating themeable output by watching these videos from our library: http://drupalize.me/videos/integrating-theme-system and http://drupalize.me/videos/using-drupal-render-api
Example:
/**
* Implements hook_field_formatter_info().
*/
function rgb_field_formatter_info() {
return array(
'rgb_raw' => array(
'label' => t('Raw color value'),
'field types' => array('rgb_color'),
),
'rgb_box' => array(
'label' => t('Color block with label'),
'field types' => array('rgb_color'),
),
);
}
/**
* Implements hook_field_formatter_view().
*/
function rgb_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, &$items, $display) {
$element = array();
switch ($display['type']) {
case 'rgb_raw':
foreach ($items as $key => $value) {
$element[$key] = array(
'#type' => 'markup',
'#markup' => t('#@hex', array('@hex' => $value['rgb'])),
);
}
break;
case 'rgb_box':
foreach ($items as $key => $value) {
$element[$key] = array(
'#type' => 'markup',
'#markup' => '' . check_plain($value['label']) . '',
);
}
break;
}
return $element;
}
Additional resources
Sometimes display formatters need to allow for administrators to configure additional settings. For example choosing which image style to use when displaying an image field. The Field API allows for formatter settings and we can add them by implementing hook_field_formatter_settings_summary() and hook_field_formatter_settings_form(). This lesson shows how to add simple width and height settings for the rgb_box display formatter that will allow an admin to modify the dimensions of the block that is displayed. Then uses those entered values in the implementation of hook_field_formatter_view()
added in the previous lesson to set the CSS width and height of the HTML element being displayed. Allowing site administrators a greater amount of control over what the content looks like without having to write any code. Which, also makes are module more flexible, and more useful in a larger variety of scenarios.
Field formatter settings are access via the Manage Display tab for our Article content type. Any field which provides additional settings will display a gear icon along on the far right that once clicked will reveal the settings form. Field formatter settings are per instance settings.
In addition to providing a settings form we also need to provide a simple text sumary of the settings that can be displayed on the Manage Display tab. This summary is displayed next to our field prior to someone clicking the gear icon that reveals the settings form. This gives the administrator a quick overview of the current configuration for all fields formatters. This is done with hook_field_formatter_settings_summary()
, which despite not being documented as such is required in order to provide field display formatter settings in Drupal 7.
Example:
/**
* Implements hook_field_formatter_settings_summary().
*/
function rgb_field_formatter_settings_summary($field, $instance, $view_mode) {
$display = $instance['display'][$view_mode];
$settings = $instance['display'][$view_mode]['settings'];
if ($display['type'] == 'rgb_box') {
$output = t('Box size: @widthx@height', array('@width' => $settings['width'], '@height' => $settings['height']));
return $output;
}
}
/**
* Implements hook_field_formatter_settings_form().
*/
function rgb_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) {
$display = $instance['display'][$view_mode];
$settings = $display['settings'];
$element = array();
if ($display['type'] == 'rgb_box') {
$element['width'] = array(
'#type' => 'textfield',
'#title' => t('Box width'),
'#default_value' => $settings['width'],
);
$element['height'] = array(
'#type' => 'textfield',
'#title' => t('Box height'),
'#default_value' => $settings['height'],
);
}
return $element;
}
Additional resources
Sometimes we need to make use of the collected and stored field data in order to calculate additional values that can be used when displaying a field. The Amazon ASIN field for example can query the Amazon API and get additional information about a product like a thumbnail to display alongside the ASIN value. Using hook_field_load() we can perform additional operations on the stored field values at the time the Field API loads, or requests, the value of our field and present that calculated data long with the stored data.
Should the data be loaded during "view" or "load" operations? In my mind that depends on what it's needed for. A good question to ask might be, "if someone was accessing the content of this field as JSON would they want this data included?". If the answer is yes, you probably want to use hook_field_load()
to perform addition load operations.
When dealing with implementations of hook_field_load()
the most interesting paramater is probably the $items
array. An multi-dimentional array that contains a record for each value value for this particular field for the entity being viewed. Because $items can contain one or more values you'll need to loop over the values within $items
and update them accordingly.
Implementations of hook_field_load()
don't need to return any value. Instead they should update the $items
array which is passed in by reference.
In this example we'll be querying the Google Search API, which returns JSON data. The search API is accessible at URLs like the following: https://ajax.googleapis.com/ajax/services/search/web?v=1.0&q=663399. The most important part being the &q=663399
, which we'll replace with our specific search term.
Values that are added to the $items
array will be available to implementations of hook_field_formatter_view()
in the $items
parameter passed to those functions. From there, you can make use of any added data when displaying the field for end users. Because the data is added during the load operation for the entity that the field is attached to it's also available anytime you're making use of the $entity
object.
Example:
/**
* Implements hook_field_load().
*/
function rgb_field_load($entity_type, $entities, $field, $instances, $langcode, &$items, $age) {
dsm('rgb_field_load');
foreach ($items as $entity_id => $field_values) {
foreach ($field_values as $delta => $value) {
$url = 'https://ajax.googleapis.com/ajax/services/search/web?v=1.0&q=' . $value['rgb'];
$response = drupal_http_request($url);
if ($response->code == 200) {
$data = drupal_json_decode($response->data);
$links = array();
foreach ($data['responseData']['results'] as $result) {
$links[] = l($result['titleNoFormatting'], $result['url']);
}
$items[$entity_id][$delta]['google_links'] = $links;
}
}
}
}
Additional resources
A CTools Style Plugin allows a developer to provide a settings form and a template file that can be chosen and configured by a site administrator using the Panels "Style" interface.
In this lesson, we will:
- Explore Panels' Style Interface
- Identify Default Panels Styles
- Introduce Demo Style Plugin
By the end of this lesson, you will understand how to access Styles in Panels and why you might want to create your own custom Styles interface for your site's editors to use.
Additional resources
The code for this plugin and module is located in sites/all/modules/demo_panestyles. See Companion Files to download the Files export, which also contains a demo site for Lessons 8-19 of Building Websites in Drupal 7 with Panels. Log in at /user with username "admin" and password "admin."
Before we dive into the code of the module and plugins, let's set up the files and directories in a meaningful structure that's both scalable and one that will ensure that our plugin is disoverable by the CTools API.
In this lesson, we will:
- Create module files and directories
- Create plugin files and directories
By the end of this lesson, you will have all of the files created with a proper structure, ready for editing.
Additional resources
The code for this plugin and module is located in sites/all/modules/demo_panestyles. See Companion Files to download the Files export, which also contains a demo site for Lessons 8-19 of Building Websites in Drupal 7 with Panels. Log in at /user with username "admin" and password "admin."
The sole purpose of our custom module is to implement a hook that will tell the CTools API that we have a plugin. Next, in our plugin's ".inc" file, we'll walk through the extensive $plugin
array, understanding how the keys and values of this array correspond to functions and parameters inside the plugin.
In this lesson, we will:
- Hook into CTools inside custom module
- Explore
$plugin
array
By the end of this lesson, you should be able to implement the correct hook for CTools and understand how to customize your own $plugin
array.
Additional resources
The code for this plugin and module is located in sites/all/modules/demo_panestyles. See Companion Files to download the Files export, which also contains a demo site for Lessons 8-19 of Building Websites in Drupal 7 with Panels. Log in at /user with username "admin" and password "admin."
In order to print out the pane title and settings form values as class names in our pane template file, we need to thread the pane object and settings array through a theme function so that they will be available to print out in our pane's template file.
In this lesson, we will:
- Walk through the pane theme function
By the end of this lesson, you should be able to implement a theme function for a panel pane.
Additional resources
The code for this plugin and module is located in sites/all/modules/demo_panestyles. See Companion Files to download the Files export, which also contains a demo site for Lessons 8-19 of Building Websites in Drupal 7 with Panels. Log in at /user with username "admin" and password "admin."
The selling point of a CTools Style Plugin is the settings form. By providing a settings form to the site editor who can then change the style of the page using a pre-approved set of styles, you can both empower and provide appropriate constraints.
In this lesson, we will:
- Use the Form API
- Build a Styles Settings Form
By the end of this lesson, you should be able to build a settings form for your CTools Style Plugin.
Additional resources
The code for this plugin and module is located in sites/all/modules/demo_panestyles. See Companion Files to download the Files export, which also contains a demo site for Lessons 8-19 of Building Websites in Drupal 7 with Panels. Log in at /user with username "admin" and password "admin."
Now we have all the code in place for both our custom module and our style plugin. It's time to put it all together into our pane template file.
In this lesson...
- Connect values from settings form to template file
- Utilize values from pane object
- Utilize values from submitted settings form
All the code for the module and plugin is contained in the Resources section of this lesson. It's now your turn to create your own CTools Style Plugin!
Additional resources
The code for this plugin and module is located in sites/all/modules/demo_panestyles. See Companion Files to download the Files export, which also contains a demo site for Lessons 8-19 of Building Websites in Drupal 7 with Panels. Log in at /user with username "admin" and password "admin."
Tokens are simple strings of text that serve as placeholders for a dynamic value. This is such a common task for modules that, rather than have every module developer reinvent the wheel, Drupal's token API allows for this kind of placeholder replacement using a unified syntax and a set of hooks and functions, which we'll cover throughout the series.
This video gives an overview of the six lessons in this series, from background to a sample installation of modules we’ll need, implementing tokens using hooks, and looking at the relationships between core and token modules.
After watching this series, you should have a firm grasp on the Drupal token API and be able to use it in your own custom modules in order to provide your users with static placeholder tokens that can be replaced with a dynamically calculated value.
Additional resources
There just a couple of components that make up the token system in Drupal 7, which is nice because it means fewer new things we have to learn. But, it's important to understand what those components are and some of the fundamentals of how tokens work in order to make the most of Drupal's Token API. In this lesson we'll take a look the use case for tokens, talk about token types, and the concept of global tokens vs. those that require some additional context in order to have their dynamic values calculated.
What are tokens?
Tokens are specially formatted chunks of text that serve as placeholders for a dynamically generated value. Here's a really simple example: You want to display a welcome message to user's of your site, and you want it to be personalized so how about you add their name and instead of just saying "Welcome", you can say "Welcome, Joe". In order to avoid having to hard-code a welcome string for every single user of the site it would be nice to dynamically generate the string. So you use a one like the following, "Welcome, [current-user:name]
".
The [current-user:name]
here is what Drupal refers to as a token. A string of static text that will be located and replaced with a dynamic value. This token is made up of a few parts, inside of the mandatory square brackets that signify that this is a token. The first part current-user:
in this case is the token type.
Token types are used to group like things together into a namespace. User's for example have name, mail, and last-login properties. Nodes have title, nid, and author properties. Token type also plays an important role in determining what tokens are available in what context. User tokens for example might be available when sending an email to a specific user but node type tokens might be irrelevant in this use case. We need to be able to tell end user's what types of tokens can be used in what context so that they don't use a node token (which has no value) in a user context.
The second part, after the colon (:
) is the token itself. This signifies what value will be substituted into the string containing the token. :name
in this case indicates we want the current user's username. This also brings up another important point. Global vs. contextual tokens. Some tokens like this one [system:date]
can be calculated without any additional information. Simply call the PHP date function and you've got a value. This token [current-user:name]
, on the other hand, requires knowledge about the currently logged-in user in order to be able to determine that user's name. This token requires additional context in order to be useful.
Additional resources
Before we can get started creating our own tokens we'll need a basic Drupal site setup and module to start from. This lesson will walk through the prerequisites for the rest of the series including having Drupal installed, downloading and installing the databasics module, creation of some dummy content, and a quick tour of what the module does.
In order to follow along in this series you'll want to have a copy of Drupal 7 installed. If you need a refresher on installing Drupal check out this series on getting up and running with Drupal 7.
Once you've got Drupal 7 installed you'll want to download and install a copy of the databasics module that was created as part of the Drupal 7 module development series. We'll use this as a starting point for adding custom tokens so that we have some new data to play with. You can grab a copy of the files attached to this page.
Finally, we'll create some dummy content on our site and walk through what the databasics module does and talk about the problem we're trying to solve.
The databasics module has a string of text that's displayed at the bottom of each node that displays some dynamic information. We would like this text to be configurable by an administrator, so we'll add some new tokens and then update both the form where text is entered and the code that displays the text to allow for use of the newly created tokens.
Additional resources
The first of two steps required to add new tokens to Drupal 7 is implementing hook_token_info() in order to give Drupal a list of the placeholders your module provides. Placeholders can be grouped together by creating a new token type and placing new tokens under that grouping or by adding new tokens to an existing token type provided by another module like the node module. Token types can be defined as global meaning their value can be calculated without any additional context, or as 'needs-data' tokens that require additional information about the context in which they are being used in order to calculate their value. For example, a [node:title]
token needs to know which node is being referenced, whereas a [system:date]
token doesn't need any additional reference to calculate the current date. In this lesson we will be adding the following token types:
[databasics-totals]
- For tokens that can be calculated without any additional context.[databasics-page]
- For tokens that require additional context in order to be rendered. In this case, they need to know which page is currently being viewed and are thus only useful in the context of viewing a page.
And these tokens:
[databasics-totals:count]
- Total page views for the entire site.[databasics-page:view-count]
- Number of times the current page has been viewed by the current user..[databasics-page:last-viewed]
- Date the current page was last viewed..
We use hook_token_info()
to provide Drupal with information about available placeholder tokens and their types. Here's an example hook_token_info()
implementation:
/**
* Implements hook_token_info().
*/
function databasics_token_info() {
$info = array();
$info['types'] = array(
'databasics-totals' => array(
'name' => t('Databasics totals'),
'description' => t('Global databasics tokens.'),
),
// [databasics-page:]
'databasics-page' => array(
'name' => t('Databasics'),
'description' => t('Tokens for databasics page counts.'),
'needs-data' => array('databasics_record'),
),
);
$info['tokens'] = array(
'databasics-totals' => array(
// [databasics-totals:count]
'count' => array(
'name' => t('Total page views'),
'description' => t('Total page views for entire site.'),
),
),
// Page specific tokens.
'databasics-page' => array(
// Add a token for the view count.
// [databasics-page:view-count]
'view-count' => array(
'name' => t('View count'),
'description' => t('Number of times the page has been viewed by the current user.'),
),
// [databasics-page:last-viewed]
'last-viewed' => array(
'name' => t('Last viewed'),
'description' => t('Date the page was last viewed.'),
),
),
);
// [node:view-count], [node:view-count:last-viewed]
$info['tokens']['node'] = array(
'view-count' => array(
'name' => t('View count'),
'description' => t('Number of times the current node has been viewed by the current user.'),
'type' => 'databasics-page',
),
);
return $info;
}
Additional resources
Note: In the video, around 9:30, we added at the :uid
argument to the query, but did not add the corresponding portion to the WHERE clause of the query. The final query should look like the one in the code sample below.
Now that we've got placeholder tokens that we can enter into a string of text we need to provide the actual values that should be used to replace those placeholders. In this lesson we'll implement hook_tokens() in order to provide the Drupal Token API with the values that correspond to the placeholders our module provides.
Implementations of hook_tokens()
are called once for every token type that's in scope for the current string that's being processed. So, one for all user tokens, once for all node tokens, and once for all global tokens. Each time the hook is passed an array which contains all the tokens of that specific type that where found in the string being processed as well as any additional contextual information such as the current $user
or $node
object.
hook_tokens()
is expected to return an array of values for each of the tokens in question that your module is responsible for. In our case, since we added the [databasics-page:*]
and [databasics-totals:*]
tokens we're responsible for calculating and returning their value whenever they are requested.
We'll also look at using the token_find_with_prefix() function which will allow us to detect and provide values for any chained tokens. Like for example [node:view-count:last-viewed]
.
Example:
/**
* Implements hook_tokens().
*/
function databasics_tokens($type, $tokens, array $data = array(), array $options = array()) {
$replacements = array();
if ($type == 'databasics-totals') {
foreach($tokens as $name => $original) {
switch($name) {
case 'count':
$count = db_query('SELECT SUM(view_count) FROM {databasics}')->fetchField();
$replacements[$original] = $count;
break;
}
}
}
if ($type == 'databasics-page' && !empty($data['databasics_record'])) {
$record = $data['databasics_record'];
foreach ($tokens as $name => $original) {
switch ($name) {
case 'view-count':
$replacements[$original] = $record->view_count;
break;
case 'last-viewed':
$replacements[$original] = $record->last_viewed;
break;
}
}
}
if ($type == 'node' && isset($tokens['view-count']) && !empty($data['node'])) {
$node = $data['node'];
global $user;
$count = db_query('SELECT SUM(view_count) FROM {databasics} WHERE nid = :nid AND uid = :uid', array(':nid' => $node->nid, ':uid' => $user->uid))->fetchField();
$replacements[$tokens['view-count']] = $count;
// [node:view-count:last-viewed]
if ($count_tokens = token_find_with_prefix($tokens, 'view-count')) {
$record = databasics_get_record($node->nid, $user->uid);
$replacements += token_generate('databasics-page', $count_tokens, array('databasics_record' => $record), $options);
}
}
return $replacements;
}
Additional resources
With a list of placeholders and a way to retrieve the values for those placeholders we can now bring it all together and use the token_replace() function to locate placeholders in a string of text and replace them with their dynamically generated counterparts. We'll use token_replace()
to process the $message
variable displayed on nodes by the databasics module so that we can use our new tokens. Then we'll look at passing contextual data like the current $node
to the token_replace()
function so the code that does the actual value calculation can have all the information it needs to do so.
The token_replace()
function will take a string of text like the following: "Welcome, [current-user:name]", and perform the replacement of the token with it's value resulting in something like "Welcome, Joe".
Additional resources
The Token API built into Drupal 7 core provides the ability to add placeholder tokens and replace them with dynamic values. But it's missing some critical features. A UI for allowing end users to browse the list of available tokens for example. And core only provides some very basic tokens, missing support for things like field values. In this lesson we'll look at additional functionality provided by the token module from contrib and how we can make use if it to provide both a UI for browsing available tokens as well as better validation of form elements with tokens in them.
First we'll download and install the token module from contrib and look at some of the features it provides us with without having to write any code.
Then, we'll look at using theme_token_tree_link()
to provide our end users with a user interface for browsing the tokens available in a given context. theme_token_tree_link()
also allows us to specify the token types that should be displayed so we can limit the list to just those that are relevant.
Finally, we'll look at using the token_element_validate()
function as an extra element validator on our Form API element where we allow users to enter strings of text that contain tokens. This validator will provide valuation of the tokens entered into the form element, for example: if the valid token types are 'node' which means they can only use 'node' tokens in that field. This makes sure that the tokens you enter into a field will work in that context.
Additional resources
Web services, APIs, and structured data are all the rage right now, and with good reason. As more and more internet enabled devices start wanting to make use of the data in our websites, we need to give them some way to interact with that data that isn't point and click in a browser. Enter the Drupal 7 services module.
In this series Joe Shindelar will walk through the services module and teach you how to create a RESTful web services API that exposes Drupal's internal data like nodes and users as JSON or XML. This paves the way for a huge variety of clients using a number of programming languages to access the data contained within your Drupal site, from native mobile applications and partner websites to internet enabled refrigerators.
The first few lessons in the series are focused more on theory. They explain the various terms that are used both in Drupal and the wider web development sphere to describe web services and all their components. Joe describes REST itself and explains why it's a good fit, and discusses Drupal 7's content model and the ways that it lends itself nicely to serializing data into various formats via the services module in order to be consumed in structured and meaningful ways.
Throughout the series we'll see a couple of examples of how to first enable Drupal to return requested data elements as JSON and then subsequently how to properly format an HTTP request to retrieve those elements. Joe will demonstrate making requests to the API via both cURL and the Chrome REST Console. This demonstration will help students to learn about both the Drupal configuration that is required and the way in which any third party applications can request data from Drupal via concrete examples.
With the basics of creating an endpoint and making simple requests out of the way, Joe will demonstrate how to enable authentication via the services module and then use both cURL and the REST Console to explain the authentication handshake: a somewhat complex exchange of a username and password for a session authentication token that takes place in HTTP requests and headers whenever you want to perform an action via the API that requires an authenticated user. This will enable to you create third party applications that can access Drupal as a specific user, allowing for greater personalization.
After that, Joe looks at ways to integrate the lists that site administrators create using the views module with services in order to output their contents as structured data, as well as ways to use views to create entirely new custom services resources, followed by an example of leveraging the power of views exposed filters via services.
Finally, for those scenarios where you simply can't point and click your way to an answer, Joe will teach you how to implement your own custom services resources for both saving and retrieving data from within Drupal, demonstrating the basic knowledge required to allow you to use services to solve all of your own custom API needs.
Things you'll be able to do after completion of this series:
- Understand the basic principles of both web services and RESTful APIs.
- Explain why Drupal is a good fit for web applications that want to allow third party clients to access their data.
- Create a RESTful web services API using the services module for Drupal 7.
- Retrieve data from your Drupal site in both JSON and XML format.
- Create new nodes in Drupal via web services.
- Make authenticated requests to Drupal via web serivces.
- Integrate the views module with services to retrieve views data in various formats.
- Write your own module providing custom services data and actions.
- Create meaningful documentation that will enable others to take advantage of your newly created service.
This series provides information about the services module for both intermediate and advanced Drupal users. Since this module makes use of basic Drupal site building skills and custom views, users should be familiar with those topics. Also, while not required, knowledge of running commands via the command line will be helpful. Finally, if you want to write your own custom services resources you'll need to understand basic module development.