Many sites are built around their content, especially Drupal sites. To manage this content, a strict editorial workflow is often highly desirable to make sure the content is drafted, reviewed, published, updated, and archived when ready.
In this tutorial we'll:
- Discuss the various components you'll need to define before you can start building a custom editorial workflow
- Provide an example editorial workflow plan
By the end of this tutorial you will better understand the use case for content moderation, and be able to create an editorial workflow plan for your use-case.
In order to enforce that an editorial workflow is applied to a specific content type you need to update the workflow's configuration. Then, depending on your needs, you may also need to configure new user roles, giving them permission to transition a content item from one state to another.
This process works for any Content Moderation type workflow -- including the Editorial workflow that Drupal provides and any custom workflows you've created.
In this tutorial we'll:
- Update a workflow so that its rules are applied to a content type
- Review the list of permissions provided by a workflow and see how we can set things up to restrict certain users to only perform specific transitions
By the end of this tutorial you will be able to configure a workflow so that it applies to one or more content types, and configure permissions so only users in a specific role can transition content items from one state to another.
When building views of moderated content there are some important things to be aware of. One is the difference between choosing Content or Content revisions as the base for your view. You should also know about some fields and filters added by the Content Moderation module.
In this tutorial we'll:
- Understand when, and why, to choose Content revisions as the base for your view instead of Content
- Learn about the fields, and filters, added by the Content Moderation module
- Learn how to update the view at admin/content/moderate that comes with the Content Moderation module to make it work with any workflow.
By the end of this tutorial you should understand the important concepts necessary to create views of moderated content.
What Are Revisions?
FreeDrupal has had revisions for a long, long time. However, they have often been under-utilized. Understanding how revisions work and how the Content Moderation module works with them is important to for being able to take full advantage of the systems features.
In this tutorial we'll:
- Explain what the different types of revisions are
- Understand when, and how, revisions are created
By the end of this tutorial you should have an understanding of what each type of revision is, how they're created, and how to work with them.
The Workflows and Content Moderation modules allow an editorial team to put any type of content administered in Drupal through a customized editorial workflow and moderation process. Workflow states, such as draft, ready for review, or approved are defined using the Workflows module. The ability to attach moderation states to entity bundles -- a common example being content types -- is configurable by the Content Moderation module.
Both modules have stable releases and are perfectly safe to use in production.
In this tutorial we'll:
- Learn about the use case for Workflows and Content Moderation modules
- Define the role that each module performs
- Define some common terms you'll need to understand when working with these two modules
By the end of this tutorial you will have a good understanding of what the Workflows and Content Moderation modules are, what different functionality they provide, and the permissions made available by the modules.
There's quite a bit of documentation and other resources already available to help you better understand the Drupal 7 Field API. Lets take a look at what's already available on Drupal.org, in the Examples project, and in the Drupal 7 core code that will serve as good reference material. We'll be referring back to these resources in later lessons, and they'll serve as a great place to look up additional information or to continue your learning via other examples.
Resources covered in this video:
- The Drupal.org Handbook for Field API
- API documentation in field.api.php, which gives an overview of all the Field API hooks.
- Field API documentation available on api.drupal.org - https://api.drupal.org/api/drupal/modules%21field%21field.module/group/…
- Examples for Developers project
We've also got some additional resources here on Drupalize.Me that will serve as a good refresher for how/where fields are used in Drupal:
- For learning more about how to use fields in the UI and how the UI works - http://drupalize.me/series/intro-fields-site-builders-series
- Attaching fields to custom entities - http://drupalize.me/videos/make-your-entity-fieldable-bundles
Additional resources
Before we can actually get our field to store data for us we need to define what the data that we're going to store looks like. The Field API does this with hook_field_schema(), which uses a very similar syntax to what is used by the hook_schema() that modules can use to define database tables. In this particular case though we're only defining what the column, or columns, that store our specific data will be and allowing the Field Storage API to decide what the structure of the created table, or tables, should be. This allows our field to remain mostly storage system agnostic and frees us from having to worry about things like how the stored field data is connected back to the entity that it belongs to, or how to format our table for proper handling of revision data or translations.
- Documentation for hook_field_schema - https://api.drupal.org/api/drupal/modules%21field%21field.api.php/funct…
- Schema API docs - https://drupal.org/developing/api/schema
Example hook_field_schema()
implemenatation.
/**
* Implements hook_field_schema().
*/
function rgb_field_schema($field) {
$columns = array(
'rgb' => array(
'type' => 'varchar',
'length' => 6,
'not null' => FALSE,
),
'label' => array(
'type' => 'varchar',
'length' => 128,
'not null' => FALSE,
),
);
$indexes = array(
'rgb' => array('rgb'),
);
return array(
'columns' => $columns,
'indexes' => $indexes,
);
}
Additional resources
The term widget refers to the form element, or elements, that are presented to the user when they are entering data for a field. For example, the file upload field on the Article content type is the widget for the image field attached to that content type. When a field instance is attached to a bundle and an admin is creating or editing an entity of that bundle type the Field Attach API calls out to each individual field and asks it for the widget it would like to use to collect data. Adding a widget for a custom field type is a combination of implementing hook_field_widget_info() and hook_field_widget_form().
Examples:
/**
* Implements hook_field_widget_info().
*/
function rgb_field_widget_info() {
return array(
'rgb_textfield' => array(
'label' => t('RGB Textfields'),
'field types' => array('rgb_color'),
),
);
}
/**
* Implements hook_field_widget_form().
*/
function rgb_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
if ($field['cardinality'] == 1) {
$element['#type'] = 'fieldset';
}
$element['rgb'] = array(
'#type' => 'textfield',
'#field_prefix' => t('RGB: #'),
'#size' => 6,
'#default_value' => isset($items[$delta]['rgb']) ? $items[$delta]['rgb'] : '',
);
$element['label'] = array(
'#type' => 'textfield',
'#field_prefix' => t('Color name: '),
'#default_value' => isset($items[$delta]['label']) ? $items[$delta]['label'] : '',
);
return $element;
}
Additional resources
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
Stylizer enables site editors to change the styles of panel pane backgrounds, content, text styles, borders, and heading styles. It provides an extensive settings form, including a live preview and integration with the Color module, for point-and-click color picking.
In this lesson, we will:
- Identify style options provided by Panels
- Enable Stylizer module
- Change Styles of a Panel Pane and Heading using Stylizer
By the end of this lesson you should have a good idea of whether or not you want to enable Stylizer on your Panels-based site and if you do, how to access and use it.
Stylizer module comes packaged with CTools.
Demo site log in:
- Navigate to /user
- Login with admin/admin
Additional resources
Views Content Panes is a module that comes packaged with Panels. It provides a new type of Views display called a Content Pane that enables you to pass off Views configuration to the Panel Pane.
In this lesson, we will:
- Enable Views Content Panes module
- Build a View using Content Pane display
- Explore Pane Configuration in Views
By the end of this lesson, you will have a better idea of why you will want to use content panes in Views whenever you are placing Views in Panels.
Demo site log in:
- Navigate to /user
- Login with admin/admin
Additional resources
In a Views Content Pane display, it's possible to use exposed or contextual filters as panel pane configuration. We'll walk through this process and why you might want to utilize this feature of content panes.
In this lesson...
- Add an exposed filter to a view
- Use the exposed filter as panel pane configuration
- Place the same view twice with different configuration
Demo site log in:
- Navigate to /user
- Login with admin/admin
Additional resources
We'll use Page Manager, Panels, and Views to create a customized user account page that features articles authored by the user whose account is being viewed.
In this lesson...
- Build a view of articles with a contextual filter
- Create a customized user account page
By the end of this lesson, you'll walk away with ideas for how to create your own customized user account page.
Demo site log in:
- Navigate to /user
- Login with admin/admin
Additional resources
The default taxonomy term page provided by Drupal leaves much to be desired. If a taxonomy vocabulary has multiple levels, but content is only tagged with only the child term and not the parent, parent term pages are left with no content listed on them, despite the fact that there is content tagged with terms below it.
In this lesson...
- Create a taxonomy vocabulary with two levels of hierarchy
- Enable the Taxonomy Term Template
- Build a custom term page for each level of hierarchy
By the end of this lesson, you'll know how to create better taxonomy term pages using Views, contextual filters, and Panels.
Demo site log in:
- Navigate to /user
- Login with admin/admin
Additional resources
With mini-panels, you can build portable panels components and place them as blocks in regions of your theme.
In this lesson...
- Build a 3-column mini-panel
- Place a menu in each column
- Place the mini-panel in the footer region as a block
By the end of this lesson, you will be able to build a mini-panel and understand how to place it in a region using the block administration page.
Demo site log in:
- Navigate to /user
- Login with admin/admin
Additional resources
Panel Nodes module comes packaged with Panels and provides a new content type called Panel.
In this lesson...
- Enable Panel Nodes module
- Create a new node using Panel content type
- Build a simple multi-column page
By the end of this lesson, you will understand the basic functionality of Panel Nodes and why you may or may not want to use it to build one-off pages on your site.
Demo site log in:
- Navigate to /user
- Login with admin/admin