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.