Upgrade Your Drupal Skills

We trained 1,000+ Drupal Developers over the last decade.

See Advanced Courses NAH, I know Enough

Creating a Context Plugin

On a recent project I was using the combination of Field Collection, Entity Reference, Taxonomy Terms, and Context to make a reusable set of references to terms on various content types. Then, based on the referenced term, I wanted to satisfy a context condition.

Due to the somewhat complex structure, the context was not aware of the term referenced through entity reference and the field collection.

In a case like this, creating a custom context plugin was a good solution.

I got started by reading a couple of helpful posts by others: Custom Context Conditions and Extending Drupal's Context Module: Custom Condition Based on Field Value.

The plugin must be included in a custom Drupal module. This involves setting up the .info and .module files, which is documented elsewhere. I will go through the needed functions in the necessary files.

There were four parts to the module I created:

  1. Letting Context know about the plugin.
  2. Describing the plugin for the Context UI.
  3. Giving Context the correct condition.
  4. Telling Context when to check for the condition.

Letting Context know about the plugin

The first function I implemented was hook_context_plugins(). It tells Context in which directory to find the plugin file, the name of the plugin file, the name of the class for the plugin, and the name of the parent class. I put this function in the .module file. (The string contextpluginexample is the name of the example module I am using in this post. You would replace it with your own module name when writing your own code.)

/**
 * Impelements hook_context_plugins().
 */
function contextpluginexample_context_plugins() {
  $plugins = array(
    'contextpluginexample_field_collection_entityreference_term' => array(
      'handler' => array(
        'path' => drupal_get_path('module', 'contextpluginexample') . '/plugins/context',
        'file' => 'contextpluginexample_field_collection_entityreference_term.inc',
        'class' => 'contextpluginexample_field_collection_entityreference_term',
        'parent' => 'context_condition_node_taxonomy',
      ),
    ),
  );
  return $plugins;
}

To make things easy, I set the filename for the plugin to be the same as the class name. I also made the name descriptive so I could remember what it does by looking at it. The parent class name is the class I wanted to extend for the plugin. The most generic class is context_condition, but I used context_condition_node_taxonomy because I needed most of the functionality of the included Taxonomy condition. (Look through the code in the plugins directory in the Context module for more classes that can be extended.)

Describing the plugin for the Context UI

The next function I implemented was hook_context_registry(). It tells Context what to display for the title and description of the plugin in the UI. I also put this function in the .module file.

/**
 * Implements hook_context_registry().
 */
function contextpluginexample_context_registry() {
  $registry = array(
    'conditions' => array(
      'contextpluginexample_field_collection_entityreference_term' => array(
        'title' => t('Field Collection Entity Reference Term'),
        'description' => t('Set this context based on whether or not the node has a Taxonomy Term referenced by Entity Reference in a Field Collection.'),
        'plugin' => 'contextpluginexample_field_collection_entityreference_term',
      ),
    ),
  );
  return $registry;
}

The title text displays in the select list or vertical tabs for Context conditions and the description text displays when the vertical tab for the selected context is active.

Giving Context the correct condition

This is where the action is for the plugin. Here is where I supply the values that Context uses to determine if the condition is met.

I created a new file named contextpluginexample_field_collection_entityreference_term.inc in the plugins/context directory of my module to hold the class I needed to extend.

Since my goal here was to mimic the way the Taxonomy condition worked by having a multi-select list with all the taxonomy terms in the site available for selection, I looked at the context_condition_node_taxonomy.inc file for examples.

The nice thing about extending the class of context_condition_node_taxonomy in a custom module is that I automatically got all its functions, which includes those that make the form, etc.

The execute() function was the only one I needed to override in this case. Looking at that function in the file from the Context module helped me understand what I needed to do.

function execute($node, $op) {
  // build a list of each taxonomy reference field belonging to the bundle for the current node
  $fields = field_info_fields();
  $instance_fields = field_info_instances('node', $node->type);
  $check_fields = array();
  foreach ($instance_fields as $key => $field_info) {
    if ($fields[$key]['type'] == 'taxonomy_term_reference') {
      $check_fields[] = $key;
    }
  }
 
  if ($this->condition_used() && !empty($check_fields)) {
    foreach ($check_fields as $field) {
      if ($terms = field_get_items('node', $node, $field)) {
        foreach ($terms as $term) {
          foreach ($this->get_contexts($term['tid']) as $context) {
            // Check the node form option.
            if ($op === 'form') {
              $options = $this->fetch_from_context($context, 'options');
              if (!empty($options['node_form'])) {
                $this->condition_met($context, $term['tid']);
              }
            }
            else {
              $this->condition_met($context, $term['tid']);
            }
          }
        }
      }
    }
  }
}

I could see what happens in the function: it checks for taxonomy_term_reference fields, and then gets the term IDs for the referenced terms.

In my execute() function I knew I would have to do a lot more traversing of fields because the term IDs I was after were a few hops away from the node itself because of the field collection and entity references.

Also, since I was using Entity Reference, the array key for the reference was target_id and not tid, so that part of the function had to be updated, too.

/**
 * Use Field Collections with Entity References to Taxonomy Terms as
 * Context conditions.
 */
class contextpluginexample_field_collection_entityreference_term extends context_condition_node_taxonomy {
  function execute($node, $op) {
    // build a list of each taxonomy reference field belonging to the bundle for the current node
    $fields = field_info_fields();
    $instance_fields = field_info_instances('node', $node->type);
    $check_fields = array();
    foreach ($instance_fields as $key => $field_info) {
      if ($fields[$key]['type'] == 'field_collection') {
        // get field collection field name
        $field_collection_name = $fields[$key]['field_name'];
        // Get field collection item IDs (allowing for multiple).
        $field_collection_values = field_get_items('node', $node, $field_collection_name);
        $field_collection_item_ids = array();
        if ($field_collection_values) {
          foreach ($field_collection_values as $field_collection_value) {
            $field_collection_item_ids[] = $field_collection_value['value'];
          }
        }
        // Load (multiple) field collection entities.
        $field_collection_items = field_collection_item_load_multiple($field_collection_item_ids);
        foreach ($field_collection_items as $field_collection_item) {
          // Get the list of fields in the field collection.
          $field_collection_instances = field_info_instances('field_collection_item');
          $field_collection_fields = $field_collection_instances["$field_collection_name"];
          // Get the field info for each field in the field collection.
          foreach ($field_collection_fields as $field_collection_field_key => $field_collection_field) {
            $field_collection_field_info = field_info_field($field_collection_field_key);
            // Check for entityreference fields referencing taxonomy terms.
            if ($field_collection_field_info['type'] == 'entityreference' && $field_collection_field_info['settings']['target_type'] == 'taxonomy_term') {
              // Get the term ID values.
              $check_fields[$field_collection_field_info['field_name']] = field_get_items('field_collection_item', $field_collection_item, $field_collection_field_info['field_name']);
            }
          }
        }
      }
    }
 
    if ($this->condition_used() && !empty($check_fields)) {
      foreach (array_filter($check_fields) as $terms) {
        foreach ($terms as $term) {
          foreach ($this->get_contexts($term['target_id']) as $context) {
            // Check the node form option.
            if ($op === 'form') {
              $options = $this->fetch_from_context($context, 'options');
              if (!empty($options['node_form'])) {
                $this->condition_met($context, $term['target_id']);
              }
            }
            else {
              $this->condition_met($context, $term['target_id']);
            }
          }
        }
      }
    }
  }
}

My function first changed when I checked for the field type. I needed to find fields that were of the type field_collection, not taxonomy_term_reference.

Once I found a field_collection field, I got its name and then had to get the field_collection_item IDs for the instances of that field collection on the node.

Then, once the instances were identified, I loaded the field collection entities so I could get the names of the fields in those entities and retrieve information about those fields.

At this point, I had to check whether each field in the field collection was an entityreference field that referenced a taxonomy term. If it was, then I stored the values of those references for later use.

Once I had all the values stored, then it came time to distill the target_id from those values to send to Context for it to compare against the term values set in the condition in the UI.

It took a lot of work navigating through the field collections, but thanks to the Devel module and the dpm() function I was able to figure out what I needed to know along the chain of references. (I was able to test a lot of this function by adding aspects of it it to page.tpl.php within

tags and using dpm() on the variables I needed information about.)

Telling Context when to check for the condition

The last function I needed to implement was harder for me to figure out. My first couple of tries were from examples I had seen in blog posts, but they didn’t quite work for my situation. I turned to the code itself.

The context.api.php file contains information about all the hooks that Context provides. By looking through that file, I was able to find the hook_context_node_condition_alter() function, which made sense to use for what I was doing. I knew it was the right one to implement when I saw that the parameters it passes to execute() were $node and $op, which were included in the execute() function I was implementing from the context_condition_node_taxonomy class.

/**
 * Implements hook_context_node_condition_alter().
 */
function contextpluginexample_context_node_condition_alter(&$node, $op) {
  if ($plugin = context_get_plugin('condition', 'contextpluginexample_field_collection_entityreference_term')) {
    $plugin->execute($node, $op);
  }
}

Once I had added this last function the .module file, everything came together and my context was active on nodes that had the terms referenced through an entity reference field in a field collection.

Author: 
Original Post: 

About Drupal Sun

Drupal Sun is an Evolving Web project. It allows you to:

  • Do full-text search on all the articles in Drupal Planet (thanks to Apache Solr)
  • Facet based on tags, author, or feed
  • Flip through articles quickly (with j/k or arrow keys) to find what you're interested in
  • View the entire article text inline, or in the context of the site where it was created

See the blog post at Evolving Web

Evolving Web