DTEK

Web Strategy for Progressive Causes and Big Ideas

Extending Drupal's Context Module: Custom Condition Based on Field Value

Andy's picture
Fri, 02/03/2012 - 11:45am -- Andy

We use the context module for Drupal in every site we build. It's a great way to do things like create separate "sections" of a site, with different behaviors and layouts based on an arbitrary set of "conditions" or triggers. For example, we may want Blog posts to have a different layout than other parts of a site.

In a current project, we needed a condition not supplied by context by default. Thankfully, it's pretty painless to extend context with custom conditions. An article from OpenSourcery pointed us in the right direction, and an article from Treehouse Agency gave us a Drupal 7 example, but otherwise we didn't find a ton of documentation out there, so we thought we'd share our technique.

To quote from the context API, adding a new condition (or reaction) is a 4-step process:

  1. Implement `hook_context_plugins()` to define your plugins, classes, and class hierarchy.
  2. Implement `hook_context_registry()` to define your conditions and/or reactions and map them to plugins.
  3. Write your condition or reaction plugin class.
  4. Add in a Drupal integration point for your plugin.

Our example: Add a context condition that checks against a specific field's value

Our site provides a field to content editors for many node types that lets them select a layout for the node. The field is of the "List (text)" type with only a few, known values. We want to add a context condition that checks against the value of the node field, and can then react in many ways.

Step 1: Implement hook_context_plugins

This code goes in your module.

 
/**
 * Implements hook_context_plugins().
 *
 */
function mymodule_context_plugins() {
  $plugins = array();
  $plugins['mymodule_context_condition_myfield'] = array(
    'handler' => array(
      'path' => drupal_get_path('module', 'mymodule'),
      'file' => 'mymodule_context_condition_myfield.inc',
      'class' => 'mymodule_context_condition_myfield',
      'parent' => 'context_condition',
    ),
  );
  return $plugins;
}

Step 2: Implement hook_context_registry

This code goes in your module.

/**
 * Implements hook_context_registry().
 *
 */
function mymodule_context_registry() {
  return array(
    'conditions' => array(
      'myfield' => array(
        'title' => t('myfield field'),
        'description' => t('Set this context based on the value of myfield field.'),
        'plugin' => 'mymodule_context_condition_myfield',
      ),
    ),
  );
}

Step 3: Write the condition plugin class

This code goes in your plugin file. As defined in mymodule_context_plugins above, this file should be in your module's directory, and be named "mymodule_context_condition_myfield.inc".

/**
 * Expose the myfield field value as a context condition.
 */
class mymodule_context_condition_myfield extends context_condition {
  function condition_values() {
    $values = array();
 
    // Get the allowed options from our field, and return these to context
    // as the values for our condition.
    $field = field_info_field('field_myfield');
    $field_values = list_allowed_values($field);
 
    foreach($field_values as $field_key => $field_value) {
      $values[$field_key] = check_plain($field_value);
    }
    return $values;
  }
 
  function execute($node) {
    // Grab the value this node has stored for our field.
    if ($items = field_get_items('node', $node, 'field_myfield', $node->language)) {
 
      // See if any of the field's values satisfy the condition.
      foreach ($items as $item) {
        foreach ($this->get_contexts($item['value']) as $context) {
          $this->condition_met($context, $item['value']);
        }
      }
    }
  }
}

Step 4: Add a Drupal integration point for the plugin

You might hook into Drupal at any number of different points. We want to check our condition when viewing nodes, so hook_node_view is a good choice.

This code goes in your module.

/**
 * Implements hook_node_view().
 */
function mymodule_node_view($node, $view_mode) {
  // Fire our context plugin when viewing nodes.
  if ($view_mode == 'full') {
    if ($plugin = context_get_plugin('condition', 'myfield')) {
      $plugin->execute($node);
    }
  }
}

That's it! Hopefully this example gets you thinking about many other ways to extend context. Thanks, context developers, for such a powerful and extensible module!

Comments

Submitted by Tim Cosgrove (not verified) on

Thanks for this post. It's well written and explains the use case and the code clearly.
-Tim Cosgrove, Treehouse Agency

Andy's picture
Submitted by Andy on

Thanks, Tim! Neil's article definitely got us started.

Cheers,
-- Andy

Submitted by pat (not verified) on

This is a cool example of how to create a custom condition, but instead of creating a custom field of predefined options and then creating a custom condition to display different contexts based on the selected option in that field, why wouldn't you just use Taxonomy? Using a taxonomy vocabulary to select the layout would be easier, since taxonomy terms are already a built-in condition.

Andy's picture
Submitted by Andy on

Yep, if taxonomy works for your use case, save yourself the trouble of writing any custom code! But there are any number of ways you could extend context -- hopefully this example gives you some ideas.

Submitted by Rick Pelletier (not verified) on

How would I make this work if the variable I wanted to trigger on wasn't a field in a node, but a custom session variable?

Andy's picture
Submitted by Andy on

Rick -- Well, you'd need to walk through the 4 steps above :) The interesting bits would be...

In the condition_values function in step 3, you'd want to get the condition values from the session variable. In the execute function in step 3, you'd want to iterate over your variable(s) rather than the field values from our example above.

And in step 4, you'd want to find the right hook to actually check the condition based on whatever you're doing with that information (i.e. when you want your context to fire its reactions based on your custom condition from the session variable).

I hope that helps. Post back here with your results if you get a chance!

Submitted by Jaxxed (not verified) on

As with all of the chaos based solutions, it seems that you must include a hook_ctools_plugin_api declaration. I am not entirely familiar with context, but I believe that it uses the ctools plugin system.

I wonder if it makes sense to start a chaos fork of Drupal so that these things can be integrated into the core.

RT @iamkevinholler: And now it’s time to leave Dropbox -http://t.co/fxIMqYFurm 3 months 2 weeks ago
RT @davidsirota: Trying to balance work/life with two kids is a reminder of how absurd the whole “having it all” notion is. You make choice… 4 months 1 week ago
Poor semantics and a maintenance headache, @technerdteitzel , but If it brings you joy, that's worth something :) 9 months 2 weeks ago
@rock_soup why else would we still have a php filter? 9 months 3 weeks ago
@eaton hmm, that's not my take. The freedom is important in principle, even if only a subset of people use it in practice 9 months 3 weeks ago

We couldn't have asked for a better website creation experience. We are thrilled with our branding and the functionality/look of the site. Early client response has been very positive!

- Felix L.