Need some help with your project? Contact me

Drupal 7 ctools content_type plugin with multiple subtypes

The content_type ctools plugin is the most used type of ctools plugin in Drupal 7. It allows us to quickly build complex (and configurable) components that can be used in the Panels interface. They are quick to set up, the easiest start being the definition of the $plugin array and the implementation of the plugin render function in a .inc file. Have you ever wondered though what the $subtype parameter of this render function is and what it serves?

Most of the time our content_type plugins only have one type so the $subtype argument is the same name as the plugin (and file name). However, it's possible to have multiple subtypes that have slight (but critical) differences while sharing common functionalities. Not many people are familiar with that. Intrigued? Let's see how they work.

When content_type plugins are being processed (loaded, prepared for use and cached), ctools asks them whether there are any subtypes it would like to define or they are single. By default the latter is true but in order to define variations we can either populate an array of subtype definitions in the main $plugin array or implement a function with a specific naming convention: module_name_plugin_name_content_type_content_types. This callback then needs to return the plugin information for all the subtypes of this plugin.

But since it's easier to show than explain, let's take a look at an example. Imagine you need a simple content_type plugin that outputs data which depends on a certain ctools context. You can define your plugin as such:

$plugin = array(
  'title' => t('My plugin'),
  'description' => t('My plugin description'),
  'category' => t('My category'),
  'required context' => new ctools_context_required(t('Node'), 'node'),
);

This is a simple example of a plugin that depends on the Node context. But what if you want it to depend on the Node context OR the current User context? In other words, it should work on the node_view page manager template or the user_view one. Or whatever page these contexts are on but nowhere else.

Instead of required context you could use 'all contexts' => true. But this would then pass in to your render function all the available contexts. And this is neither elegant nor a statement of dependency on one of those two contexts. In other words, it will be available on all page manager pages but maybe won't do anything on most and it's up to the render function to handle extra logic for checking the contexts.

This is where plugin subtypes come to help out. Since your render function does the exact same regardless of context (or very similar), you can have a subtype for each. So let's see how that's done.

First, we simplify the main plugin array:

$plugin = array(
  'title' => t('My plugin'),
  'description' => t('My plugin description'),
  'category' => t('My category'),
);

Then we implement the function that returns the subtypes (following this naming convention):

function my_module_my_plugin_content_type_content_types() {
  return array(
    'node' => array(
      'title' => 'My plugin for nodes',
      'required context' => new ctools_context_required(t('Node'), 'node'),
    ),
    'user' => array(
      'title' => 'My plugin for users',
      'required context' => new ctools_context_required(t('User'), 'user'),
    ),
  );
}

The subtype machine name is the key in the array and the rest is regular plugin definition as we are used to. In our case we define two, each for their respective dependencies. And with this in place we achieve a number of things.

First, we can add the My plugin for nodes content_type plugin whenever the Node context is available and the My plugin for users when the User context is present. They cannot be used in other cases. Second, we ensure that whatever context is passed to the render function is either a Node or a User (nothing else). This can come in really handy when your context is custom and wraps an object that implements a common interface. Third, the $subtype argument to the render function now will be either node or user which is helpful to maybe slightly fork the functionality depending on the subtype.

Clear the caches and give it a go. Let me know how it works out.

Comments

Hi Danny. Thanks for the write up. Having a written a ton of custom ctools content types before, I've never actually looked at the subtypes functionality. This could very well be useful for a project we have coming up which will have a dashboard with several interactive blocks on it. They'll need to be context aware so the subtypes could be a neat way of organising everything. Thanks for the seed of an idea on that one! :)

Hehe, cheers! Glad I could help.

Hey Danny,
Thanks for this great post. This is exactly i was looking for.

Add new comment

You can post comments in Markdown and basic HTML tags.
For code blocks, wrap your code within '~~~'. For example:
~~~
$var = 'my variable';
~~~