Building Custom cTools Plugins in Drupal 7

Share this article

cTools is one of those critical Drupal 7 modules many others depend on. It provides a lot of APIs and functionality that makes life easier when developing modules. Views and Panels are just two examples of such powerhouses that depend on it.

cTools makes available different kinds of functionality. Object caching, configuration exportability, form wizards, dialogs and plugins are but a few. A lot of the credit you would normally attribute to Views or Panels is actually owed to cTools.

Drupal logo

In this article, we are going to take a look at cTools plugins, especially how we can create our very own. After a brief introduction, we will immediately go hands on with a custom module that will use the cTools plugins to make defining Drupal blocks nicer (more in tune to how we define them in Drupal 8).

Key Takeaways

  • cTools is a critical Drupal 7 module that many other modules depend upon, offering APIs and functionalities that simplify module development. Creating custom cTools plugins allows for easily defining reusable bits of functionality, or isolated business logic that is used in a specific context.
  • The process of defining a custom cTools plugin involves creating a custom module, defining the plugin type, and setting up the plugin directory. The plugin type is defined using the hook_ctools_plugin_type() function, while the hook_ctools_plugin_directory() function specifies where the plugin files can be found.
  • Custom cTools plugins can be used to transform the Drupal block system into a rudimentary plugin system. This involves creating a helper function to load all plugins, implementing the hook_block_info() and hook_block_view() functions, and defining block plugins.
  • Custom cTools plugins can be extended further to replace block-related configuration hooks with callbacks inside the plugin include file. Additionally, all data available in hook_block_info() can be defined inside the plugin, offering a more streamlined and efficient way of defining Drupal blocks.

Introduction

cTools plugins in Drupal 7 (conceptually not so dissimilar to the plugin system in Drupal 8) are meant for easily defining reusable bits of functionality. That is to say, for the ability to define isolated business logic that is used in some context. The goal is to set up that context and plugin type once, and allow other modules to then define plugins that can be used in that context automatically.

If you’ve been developing Drupal sites for more than a year you’ve probably encountered cTools plugins in one shape or form. I think the first plugin type we usually deal with is the content_type plugin which allows us to create our own custom panel panes that display dynamic content. And that is awesome. Some of the others you may have encountered in the same realm of Panels are probably context and access (visibility rules). Maybe even relationships and arguments. These are all provided by cTools. Panels adds to this list by introducing layouts and styles that we normally use for creating Panels layouts and individual pane styles. These are I think the more common ones.

However, all of the above are to a certain extent a black box to many. All we know is that we need to define a hook to specify a directory and then provide an include file with some definition and logic code and the rest happens by magic. Going forward, I would like us to look into how a plugin type is defined so that if the case arises, we can create our own plugins to represent some reusable bits of functionality. To demonstrate this, we will create a module that turns the pesky hook system of defining custom Drupal blocks into a plugin based approach similar to what Drupal 8 is using.

The final code (+ a bit more) can be found in this repository if you want to follow along. And I do expect you are familiar with the steps necessary for defining custom Drupal blocks.

The block_plugin module

As I mentioned, I would like to illustrate the power of cTools plugins with a custom plugin type that makes defining Drupal 7 blocks saner. Instead of implementing the 2 main hooks (hook_block_info() and hook_block_view()) necessary to define a block, we’ll be able to have separate plugin files each responsible for all the logic related to their own block. No more switch cases and changing the hook implementation every time we need a new block. So how do we do this?

First, let’s create our block_plugin.info file to get started with our module:

name = Block Plugin
description = Using cTools plugins to define Drupal core blocks
core = 7.x
dependencies[] = ctools

Simple enough.

The plugin type

In order to define our news plugin type, inside the block_plugin.module file we need to implement hook_ctools_plugin_type() which is responsible for defining new plugin types cTools will recognize:

function block_plugin_ctools_plugin_type() {
  return array(
    'block' => array(
      'label' => 'Block',
      'use hooks' => FALSE,
      'process' => 'block_plugin_process_plugin'
    )
  );
}

In this hook we need to return an associative array of all the plugin type definitions we need keyed by the machine name of the plugin type name. Today we are only creating one called block. For more information on all the options available here, feel free to consult the plugins-creating.html help file within the cTools module. No use repeating all that information here.

The process key defines a function name that gets triggered every time cTools loads for us a plugin and is responsible for shaping or massaging the plugin data before we use it. It’s sort of a helper function that prepares the plugin for us each time so we don’t have to bother. So let’s see what we can do inside that function:

function block_plugin_process_plugin(&$plugin, $info) {
  // Add a block admin title
  if (!isset($plugin['admin title'])) {
    $exploded = explode('_', $plugin['name']);
    $name = '';
    foreach ($exploded as $part) {
      $name .= ucfirst($part) . ' ';
    }
    $plugin['admin title'] = $name;
  }

  // By default we also show a block title but this can be overwritten
  if (!isset($plugin['show title'])) {
    $plugin['show title'] = TRUE;
  }

  // Add a block view function
  if (!isset($plugin['view'])) {
    $plugin['view'] = $plugin['module'] . '_' . $plugin['name'] . '_view';
  }

  // Add a block form function
  if (!isset($plugin['configure'])) {
    $plugin['configure'] = $plugin['module'] . '_' . $plugin['name'] . '_configure';
  }

  // Add a block save function
  if (!isset($plugin['save'])) {
    $plugin['save'] = $plugin['module'] . '_' . $plugin['name'] . '_save';
  }
}

This callback receives the plugin array as a reference and some information about the plugin type. The task at hand is to either change or add data to the plugin dynamically. So what do we achieve above?

First, if the developer hasn’t defined an admin title for the block plugin, we generate one automatically based on the machine name of the plugin. This is so that we always have an admin title in the Drupal block interface.

Second, we choose to always display the title of the block so we mark the show title key of the plugin array as TRUE. When defining the block plugin, the developer has the option of setting this to FALSE in which case we won’t show a block title (subject).

Third, fourth and fifth, we generate a callback function for the block view, save and configure actions (if they haven’t already been set by the developer for a given plugin). These callbacks will be used when implementing hook_block_view(), hook_block_configure() and hook_block_save(), respectively. We won’t be covering the latter two in this article but feel free to check out the repository to see what these can look like.

And that’s pretty much all we need for defining our custom plugin type. We should, however, also implement hook_ctools_plugin_directory() which, as you may know, is responsible for telling cTools where a plugin of a certain type can be found in the current module:

function block_plugin_ctools_plugin_directory($module, $plugin) {
  if ($module == 'block_plugin' && in_array($plugin, array_keys(block_plugin_ctools_plugin_type())) ) {
    return 'plugins/' . $plugin;
  }
}

This will need to be implemented also by any other module that wants to define block plugins.

Drupal blocks

Now that we have the plugin type, let’s write the code which turns any defined block plugin into a Drupal block. Will start with the hook_block_info() implementation:

function block_plugin_block_info() {
  $blocks = array();

  $plugins = block_plugin_get_all_plugins();
  foreach ($plugins as $plugin) {
    $blocks[DELTA_PREFIX . $plugin['name']] = array(
      'info' => $plugin['admin title'],
    );
  }

  return $blocks;
}

Here we load all of the plugins using a helper function and define the minimum required information for the block. Here you can add also more information but we are keeping it simple for brevity.

We know each plugin will have a machine name (the name of the include file basically) and an admin title because we generate one in the processing phase if one doesn’t exist. The DELTA_PREFIX is a simple constant in which we define the prefix we want for the block machine name because we need to reuse it and should be able to easily change it if we want to:

define('DELTA_PREFIX', 'block_plugin_');

Our helper function we saw earlier looks like this:

function block_plugin_get_all_plugins() {
  return ctools_get_plugins('block_plugin', 'block');
}

It’s a simple wrapper around the respective cTools function. And for that matter, we also have the following function responsible for loading a single plugin by its machine name:

function block_plugin_get_plugin($name) {
  return ctools_get_plugins('block_plugin', 'block', $name);
}

This is very similar to the one before.

In order to make our Drupal block definitions complete, we need to implement hook_block_view():

function block_plugin_block_view($delta = '') {
  $plugin = block_plugin_plugin_from_delta($delta);
  if (!$plugin) {
    return;
  }

  $block = array();

  // Optional title
  if (isset($plugin['title']) && $plugin['show title'] !== FALSE) {
    $block['subject'] = $plugin['title'];
  }

  // Block content
  $block['content'] = $plugin['view']($delta);

  return $block;
}

So what’s happening here?

First, we use another helper function to try to load a plugin based on the delta of the current block and do nothing if we are not dealing with a plugin block.

Second, we build the block. If the user specified a title key on the plugin and the show title key is not false, we set the subject of the block (its title basically) as the former’s value. As for the actual block content, we simply call the view callback defined in the plugin. And that’s it.

Let us quickly see also the helper function responsible for loading a plugin based on a block delta:

function block_plugin_plugin_from_delta($delta) {
  $prefix_length = strlen(DELTA_PREFIX);
  $name = substr($delta, $prefix_length);
  $plugin = block_plugin_get_plugin($name);
  return $plugin ? $plugin : FALSE;
}

Nothing complicated going on here.

Defining block plugins

Since we told cTools that it can find block plugins inside the plugins/block folder of our module, let’s go ahead and create that folder. In it, we can add our first block inside a file with the .inc extension, for example my_block.inc:

<?php

$plugin = array(
  'title' => t('This is my block'),
);

/**
 * Returns a renderable array that represents the block content
 */
function block_plugin_my_block_view($delta) {
  return array(
    '#type' => 'markup',
    '#markup' => 'Yo block!'
  );
}

Like we do with all other plugins (content_type, context, etc), the plugin definition is in the form of an array inside a variable called $plugin. And for our case all we need at this point is a title (and not even that since without it the block simply won’t show a title).

Below it, we defined our callback function to display the block. The naming of this function is important. It matches the pattern we used for it during the processing phase (module_name_plugin_name_view). If we want to name it differently, all we have to do is reference the function name in the view key of the $plugin and it will use that one instead.

And that is basically it. We can now clear our caches and go to the Block administration screen where we can find our block and add it to a region. Showing that block on the page should trigger the view callback for that block plugin and render the contents.

Conclusion

In this article we’ve talked a bit about cTools plugins and saw how we can define our own type of plugin. We used the latter for transforming the Drupal block system into a rudimentary plugin system. This can be extended further to allow also for the block related configuration hooks to be replaced by callbacks inside the plugin include file. Additionally, as mentioned earlier, you can also make sure all the data available in hook_block_info() can be defined inside the plugin. I leave these tasks up to you.

Frequently Asked Questions about Building Custom Ctools Plugins in Drupal 7

What are the prerequisites for building custom Ctools plugins in Drupal 7?

Before you start building custom Ctools plugins in Drupal 7, you need to have a basic understanding of Drupal’s module development and PHP. You should also be familiar with the Drupal API and the Ctools module. It’s also beneficial to have a working knowledge of Drupal’s hook system, as it’s extensively used in the creation of Ctools plugins.

How do I install the Ctools module in Drupal 7?

To install the Ctools module in Drupal 7, you first need to download it from the Drupal project page. Once downloaded, extract the files and place them in the ‘sites/all/modules’ directory of your Drupal installation. Then, navigate to the ‘Modules’ section in your Drupal admin panel, locate the ‘Chaos tool suite’ and enable it.

What is the purpose of the $plugin array in Ctools plugins?

The $plugin array is a crucial part of Ctools plugins. It defines the properties of the plugin, such as its title, description, and callback functions. The array is used by Ctools to identify and manage the plugin.

How can I create a custom content type plugin in Ctools?

To create a custom content type plugin in Ctools, you need to define a plugin file in your module’s directory. This file should include a $plugin array that defines the properties of your content type. You also need to define callback functions for rendering the content type.

What is the role of hook_ctools_plugin_directory() in Ctools?

The hook_ctools_plugin_directory() function is used to tell Ctools where to find your plugin files. By implementing this hook in your module, you can specify the directory path where your plugin files are located.

How can I use Ctools modal dialog in my plugin?

To use a Ctools modal dialog in your plugin, you need to include the Ctools ajax library in your module. You can then use the ctools_modal_command_display() function to display your content in a modal dialog.

How can I debug my Ctools plugin?

Debugging a Ctools plugin can be done using various methods. One common method is to use the dpm() function provided by the Devel module. This function prints a variable to the ‘message’ area of your site, which can be useful for debugging.

Can I use Ctools plugins in Drupal 8?

While Ctools is primarily used in Drupal 7, it is also compatible with Drupal 8. However, the process of creating plugins in Drupal 8 is different from Drupal 7 due to the introduction of the Plugin API in Drupal 8.

How can I create a multi-step form using Ctools?

Creating a multi-step form using Ctools involves creating a custom wizard plugin. This plugin should define a form for each step of the process and specify the order in which these forms are displayed.

What are some common issues when working with Ctools plugins and how can I solve them?

Some common issues when working with Ctools plugins include not implementing the hook_ctools_plugin_directory() function correctly, forgetting to clear the cache after making changes to your plugin, and not defining the $plugin array properly. These issues can be solved by checking your code for errors, clearing the cache regularly, and ensuring that your $plugin array is correctly defined.

Daniel SiposDaniel Sipos
View Author

Daniel Sipos is a Drupal developer who lives in Brussels, Belgium. He works professionally with Drupal but likes to use other PHP frameworks and technologies as well. He runs webomelette.com, a Drupal blog where he writes articles and tutorials about Drupal development, theming and site building.

ctoolsdrupaldrupal plugindrupal-7drupal-planetdrupal7
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week