Creating a Custom Plugin in Context

Custom Plugins

Context is one of the most useful architectural modules for Drupal. Context works by setting conditions such as a path or a node type and, if that condition is present, reactions such as adding blocks to regions or setting an active menu.

Context comes with a number of useful conditions and reactions. But what if you need something more specific? Context offers a well-built API that makes creating your own conditions and reactions a relatively painless procedure. The API is well documented but hopefully this example will be useful for some folks.

'No Comment' Context

For this example we will create a context that appears on a node when that node has no comments.

I also want to give admins the ability to determine if that context appears on the node edit form. This will give admins a little more flexibility and won't take too much more work since the 'Node type' Context already has this setup.

To create this context we will need to take the following steps:

1. Create a Custom Module

The first step is to create a module to house our code.
For the sake of this example I'm calling this 'mymodule'. To set this up:
  • create a file that lists node, context, and comment modules as dependencies
  • create a blank mymodule.module file
See the attached zip file for a full version of this exercise.

2. Create a Context Condition

Next we need to tell Context about our plugin.

Add our condition to the Context registry

Here we are creating the mymodule_no_comment condition and telling Context we will be using the mymodule_no_comment_context_condition plugin.

array( 'mymodule_no_comment' => array( 'title' => t('No Comments on a node'), 'description' => t('Set context on a node if there are NO comments on that node.'), 'plugin' => 'mymodule_no_comment_context_condition', ), ), ); } ?>

Add the definition for our Context plugin

Here we are telling Context where our plugin can be found and what class it uses.

array( 'path' => drupal_get_path('module', 'mymodule'), 'file' => '', 'class' => 'mymodule_no_comment_context_condition', 'parent' => 'context_condition_node', ), ); return $plugins; } ?>

3. Create a Context Class

Next we need to create the Context condition class.
As you can see above we are extending the context_condition_node. To do so we'll make a file and add the following: t('Enable this context.')); } function execute($op) { foreach ($this->get_contexts() as $context) { // Check the node form option. $options = $this->fetch_from_context($context, 'options'); if ($op === 'form') { if (!empty($options['node_form']) && in_array($options['node_form'], array(CONTEXT_NODE_COMMENTS_FORM, CONTEXT_NODE_COMMENTS_FORM_ONLY))) { $this->condition_met($context, 'no_comment'); } } elseif (empty($options['node_form']) || $options['node_form'] != CONTEXT_NODE_COMMENTS_FORM_ONLY) { $this->condition_met($context, 'no_comment'); } } } } ?>

As I mentioned before, we are going to be including the ability for admins to determine if the Context will be visible on the node edit form. The option in the UI will look like this:

context edit form

If we weren't going to need that we could just extend the context_condition and set a simple check in the execute method. Since we do want that option we have included some logic in the execute method that checks the admin's choice for whether the Context should be active on the node edit form. We are using constants defined by the context_condition_node class file.

It is also important to note that we are inheriting the options_form method from the context_condition_node class as well. This method creates the form shown in the image above.

4. Set the Context

Finally, we need to set the Context to execute when the correct conditions have occurred.
For this example our focus is on the node. We want to set the Context when we are
  1. Viewing a Node with no comments
  2. Editing a Node with no comments

Viewing a Node with no comments

To set the Context when we are viewing the node we will use hook_nodeapi(): comment_count > 0 && module_exists('context') && $page) { mymodule_no_comment_context_condition('view'); } break; } } /** * Function to execute 'no comments' context condition. */ function mymodule_no_comment_context_condition($op) if ($plugin = context_get_plugin('condition', 'mymodule_no_comment')) { $plugin->execute($op); } } ?>

Here I am checking to see that we are viewing an actual node page (instead of a teaser) and that there are more than 0 comments on the node.


Editing a Node with no comments

Unfortunately, because there are a number of ways to access a node form we are going to have to do more than set 'prepare' on our hook_nodeapi() call above.

First we are going to use a form_alter() to check that we aren't on an admin page and that we are on an node form:

comment_count > 0) { mymodule_no_comment_context_condition('form'); $form['#validate'][] = 'mymodule_form_alter_node_validate'; } } else if ($form_id == 'system_modules') { context_invalidate_cache(); } } ?>

Secondly we want to respect modules such as panels that use ctools page manager to decide if they are viewing or editing a node. This sets a 'view' op as well so even if we were doing a version of this module that didn't check for the node edit form it would have been best to set this anyway.

type === 'node' && !empty($ctools_context->data)) { $node = node_load(arg(1)); if (!$node->comment_count > 0) { mymodule_no_comment_context_condition('form', $task['name'] === 'node_view' ? 'view' : 'form'); break; } } } } } ?>

Note that an extra node_load() on every page is not great for performance. Use with discretion.

Finally, we are going to implement a fix that is still in the context issue queue that keeps the context active when a node is undergoing a validation error:

In our form_alter, you may have wondered why we added the line "$form['#validate'][] = 'mymodule_form_alter_node_validate';". We set that so we could grab the node validate and make sure the context is active there as well:


In a few steps we have setup a custom context for a specific use case.

Thanks to the folks who made this possible: !

Ready to get started?

Tell us about your project