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
In this lesson, we'll get a grasp on the form settings of our webform node. We'll explore submission settings, form access by role, and advanced settings and we'll take a quick look at some of the contexts in which these advanced settings apply, for example multipage forms.
So far we've been looking at Webform from an administrator's point of view. Now it's time to take a step back and look at Webform from the perspective of anonymous users. We want to make sure that our users can do what we're asking and we're not creating security vulnerabilities. In this lesson, we'll configure permissions for our Webform case study.
In this lesson we'll add spam protection to our webforms using Honeypot module. We'll also take a second look at setting submission limits as a strategy for thwarting form submission abuse.
Additional resources
Introducing the Honeypot form spam protection module for Drupal
In this lesson, we'll explore the Results tab of our webform where we can view user submissions, get a basic analysis of our data, view the results as a table, download the results as a delimited text or Excel file, and clear all results.
Amber wraps up the series and takes a look back at what we covered in Webform Basics — from downloading and installing Webform module to building our form to analyzing the results.
In this lesson, we'll go over Webform's Global Settings. We'll learn how to attach forms and questionnaires to other content types besides the Webform content type. We'll discover how we can limit the list of available form components, set email header defaults, and take a look at the advanced options in Webform's global configuration.
Note: Webform settings are no longer set globally as described in this video. Instead you configure settings per content type. Go to Structure > Content Types and select the content type to edit. You can enable Webform for a content type in its edit form.
With the Gallery content type ready to go, we need work on a better display for the galleries so that they aren't just a big list of images. In this lesson, we're going to polish our galleries by adding several contributed modules that let us manipulate the display of our images in interesting ways. Specifically we will be adding Gallery Formatter and Colorbox modules to give us a nice interface for viewing the gallery images. Colorbox is dependent on the Libraries module, and we'll need to use an external javascript library, called Colorbox, to get the effect we want. We'll explain how to work with external libraries and make things are configured properly on our site.
Note: This series is covering Media Module version 1.x. To learn about Media Module 2.x, check out our Using Drupal Chapter 4: Media Management series.
Additional resources
Insert module provides a simple, clean way to get out images in-line, but we are not taking advantage of the fact that we have the Media browser for a unified experience. In order to get the Media browser in the picture for in-line images, and provide a really cohesive editing experience, we're going to give our users a WYSIWYG (what you see is what you get) toolbar, and add our browser to that toolbar. We'll be using the WYSIWYG module, along with the external CKeditor library.
Note: This series is covering Media Module version 1.x. To learn about Media Module 2.x, check out our Using Drupal Chapter 4: Media Management series.
Additional resources
We need to add our own audio player to make the audio experience nice and slick on the site so people don't have to download the files to listen to them. In this lesson we'll install the jPlayer module and the jPlayer library, then make sure our configuration is set the way we want.
Note: This series is covering Media Module version 1.x. To learn about Media Module 2.x, check out our Using Drupal Chapter 4: Media Management series.
Additional resources
Mapping with Leaflet
CourseBefore we can display a location on a map, we need to decide on a location storage method. This is a critical step because not all storage methods are compatible with every mapping module. Since we'll be using Leaflet, we'll need to use Geofield to store our geographic data. Geofield will provide us with a new type of field that we can add to any entity bundle or content type. Geofield provides many different field formatters that can display geographic data in a variety of formats, such as latitude and longitude or well known text.
By the end of this lesson, you should be able to:
- Download and install Geofield and its dependencies GeoPHP and CTools
- Create a field on a content type using Geofield
- Enter a latitude and longitude for a location node
Join Amber Matz as she walks you through how to prep your site to store location data using Geofield module.
Additional resources
Geographic information is used to find a location, get directions, or check on traffic – and so much more. In the Drupal mapping world, solutions range broadly.
Leaflet is one solution that works for many situations. In this series, you'll get up and running with mapping using Leaflet. We’ll add and configure field types, install Leaflet, and create and customize maps. When you complete this series, you'll be able to create a map of both single and multiple points, geocode address data, configure geofield to store geographic data, and customize the look and feel of your Leaflet maps interface.
Now that we've stored the latitude and longitude for a node, we can display it on a map. In this lesson, we'll take a look at the documentation for Leaflet and we'll download and install the Leaflet JavaScript library and the Leaflet Drupal module, which loads the Leaflet JavaScript library and provides configuration options that we'll use later on.
By the end of this lesson, you should be able to:
- Install Leaflet and its dependencies to your Drupal site
- Configure a Geofield field to display as a Leaflet map
Additional resources
Manually entering a latitude and longitude for each node is a tedious process. Just finding out what those numbers are for a given location less than ideal. One of the most common ways to communicate a location to another person is by using a postal address. How many times do you enter a latitude and longitude into your favorite mapping app to get directions? Hardly ever, right? Most of the time you would just enter the street address and the app "just knows" how to find it. In this lesson, we'll configure our Drupal site to Geocode a postal address into geographic data that our Leaflet map can display.
Additional resources
n the previous lesson, we configured the location content type to collect address data using Address Field module. Then we brought in Geocoder module and configured the Geofield on our location content type to use the data from the Address Field. With Geocoder all set to transform that address data, we are now ready to create some new location nodes. Since the process of geocoding addresses happens behind the scenes when a new node is created or when a node is edited and saved, it was important to do this configuration first, before creating any more location nodes. To expedite data-entry, in this lesson, I'll walk through the process of importing a comma separated value text file or CSV file into a Drupal site with Address Field using Feeds.
By the end of this lesson, you'll understand what data you need to be sure and include in your CSV to avoid trouble with Address Field, how to configure a feeds importer, and how to configure the field mapping in the importer with the somewhat esoteric field names that Address Field provides.
Additional resources
Before we can display a location on a map, we need to decide on a location storage method. This is a critical step because not all storage methods are compatible with every mapping module. Since we'll be using Leaflet, we'll need to use Geofield to store our geographic data. Geofield will provide us with a new type of field that we can add to any entity bundle or content type. Geofield provides many different field formatters that can display geographic data in a variety of formats, such as latitude and longitude or well known text. By the end of this lesson, you should be able to download and install Geofield and its dependencies, add a Geofield to a content type, and enter in the latitude and longitude for a location node.